Wymcp.Plugs.Dispatch (Wymcp v0.6.2)

View Source

The method surface of both eras: routes a validated message to the module that answers it. The last plug in Wymcp.Plugs.Pipeline.

Three branches, in clause order. A JSON-RPC response — a client's answer to a server-initiated request — is delivered to the session that minted its id. A modern-classified message takes the modern lane. Everything else takes the legacy lane. The response branch is legacy-only: a client's answer to a server-initiated request reaches wymcp only on the legacy lane. The contract survives via MRTR on the modern lane, and the branch goes at the legacy decommission.

The modern lane

server/discover, tools/list and tools/call — the whole modern method surface. An unknown modern method answers HTTP 404 plus -32601: the spec's MUST, and deliberately distinguishable from a legacy HTTP+SSE server's 404. Wymcp.Response.send_json/2 preserves a previously-set status, which is why one method module serves both eras' unknown-method bodies while only this lane sets 404. That 404 is a rejection, so this plug records it through Wymcp.Response.record_rejection/5 before the body is built; the legacy lane's 200 is not one.

A modern notification never reaches a method: the modern core defines no client→server notification over HTTP, and JSON-RPC forbids an error response to a notification — so it is answered 202 and dropped, whatever its body. That is also why Wymcp.Plugs.Validate lets it through unvalidated.

The legacy lane

initialize, notifications/initialized, ping, tools/list, tools/call, logging/setLevel and notifications/cancelled. Anything else falls through to the same unknown-method module with no status override, so it answers -32601 in a 200 envelope. The whole lane is legacy-only: these methods exist only because the legacy era does, and the section goes with them at the legacy decommission.

The tool list this plug passes on is the mount module's mount tools — its :tools option — together with the definitions Wymcp.Router.init/1 built for them, which are tools/list's alone: tools/call is handed the tool list and nothing else. Wymcp.Router copies its init options into conn.assigns[:wymcp], and this plug reads both from there. That list is a starting point, not the effective one — each method module resolves the effective list itself. On the legacy lane the tools/list and tools/call modules re-read the session's registered tools, so a runtime Wymcp.Session.register_tool/2 wins there; the modern lane is stateless, has no session to consult, and serves the mount list as passed. A tool registered on a live session has no stored definition — no registration moment saw it — so tools/list builds that one itself: a legacy-only path, since only the legacy lane resolves its tools through a session, and it goes with the era branch at the decommission.