Wymcp.Modern (Wymcp v0.6.1)

View Source

Modern-lane result shapes — the two result types the lane answers and the framework defaults every one of them carries, with zero consumer surface.

complete_result/2 stamps resultType: "complete" on a finished result. input_required_result/3 builds the input-required one a tools/call answers when the run stops at an unanswered elicit; the tasks extension's "task" arrives with a planned change. Both stamp the io.modelcontextprotocol/serverInfo result _meta (built by Wymcp.ServerInfo from the :server_info router option and app config). with_cache_hints/3 merges the cache-hint defaults into the two cacheable modern results:

ResultttlMscacheScope
server/discover (:discover)3_600_000"private"
tools/list (:tools_list)300_000"private"

"private" is the never-wrong scope: the spec allows tool sets to vary by authorization context, so "public" could silently become wrong for a future consumer. Both results change only at deploy/upgrade — on the modern lane the tool definitions tools/list serves are built once, at the registration moment — so minutes of staleness is harmless; the values are revisable without consumer churn. An input-required result takes no hints at all: the spec forbids caching one, and no tools/call result carries them in any case.

The requestState codec

requestState is the opaque string a client echoes back byte-exact on every retry of a call it is answering. input_required_result/3 encodes the answers accumulated so far as JSON and then as unpadded base64url; decode_request_state/1 reads them back on the next round, and answers :error for every way the string can fail to be one — not base64url, not JSON, or JSON that is not an object — with two absent states read as the empty map: a missing field, and the empty string a client sends when it marshals a field it never populated.

The value carries no integrity envelope — no signature, no expiry, no binding to a principal or to the arguments it was minted for. It holds only answers the client itself supplied, so altering one yields nothing that client could not have sent as a fresh answer, and a tool validates elicited content as the user input it is. That reasoning ends the day the state carries anything a principal could abuse; until then the envelope would buy nothing.

complete_result/2 and with_cache_hints/3 are era-guarded no-ops on a legacy conn, so the shared serving methods (Wymcp.Methods.ToolsList, Wymcp.Methods.ToolsCall) pipe through them unconditionally. input_required_result/3 needs no guard: only the modern lane can stop a run mid-way, because only there does an elicit have nowhere to block. The guard is legacy-only: it exists only because a second era does, and both no-ops go at the legacy decommission.

Summary

Functions

The interim tools/call result a run answers when it stops at an unanswered elicit: resultType: "input_required", the run's input requests, and the prior answers as requestState.

Functions

complete_result(result, conn)

input_required_result(input_requests, answers, conn)

The interim tools/call result a run answers when it stops at an unanswered elicit: resultType: "input_required", the run's input requests, and the prior answers as requestState.

input_requests is keyed by the ids this server assigned, and each entry is a bare {method, params} object carrying no JSON-RPC id: the request travels inside a result, not as a message of its own. A client answers the latest keys and echoes requestState back untouched. The result carries no content and no isError — neither field is defined for this result type — and no cache hints.

with_cache_hints(result, key, conn)