Wymcp.Tool.Schema (Wymcp v0.9.1)

View Source

Builds the JSON Schema inputSchema for Wymcp tools.

One shape: the action field lists the declared action names as an enum whose description carries the action summaries, one per line, and data is a bare object. build/1 and action_summaries/1 render those summaries from one private function, so the enum description and the help index cannot drift. The newline separator stays unambiguous because Wymcp.Tool.Actions.fetch_validated!/1 — run at every moment a tool's schemas are validated — rejects a newline in an action's name or its description, the two halves a summary joins. Wymcp.Tool's generated input_schema/0 is the only caller of build/1.

Both documented functions take the tool module, not an actions map, and obtain the schemas themselves through Wymcp.Tool.Actions.fetch_schemas!/1. Each obtains once and hands the map on: obtaining twice on one path could publish an action enum that disagrees with its own summaries, for a tool whose Wymcp.Tool.actions/0 varies between calls.

One function takes a map instead, and is kept out of these docs because only the registration moment calls it: build_from_actions builds the schema from the actions map Wymcp.Tool.Actions.fetch_validated!/1 has just validated, so a macro tool's definition is built without obtaining its actions a second time. build/1, which the generated input_schema/0 calls, is the same build over a fresh obtain. So every map this module reads has been through a validator — a documented function's through the obtaining accessor, build_from_actions' through its one caller, Wymcp.Tool — and the direct schema.description read below needs no check of its own.

The key set is closed and said so out loud: the root carries additionalProperties: false, which is the server stating its contract in tools/list so a schema-aware client can catch a misspelled key before it sends. Enforcement is not this schema's job — a dispatch gate answers a stray key or a wrong-typed argument in the tool dialect, naming it and pointing at help (see "Dispatch errors and self-correction" in Wymcp.Tool). The declaration is the root's alone: data stays a bare object in tools/list, which keeps the payload compact; the key sets inside it are closed per action, published by Wymcp.Help and enforced by that same gate.

The generated schema carries no header annotation, so a modern tools/call to a use Wymcp.Tool tool mirrors no argument into an Mcp-Param-* header. The spec leaves annotating to the server and has it refuse a call whose client left an annotated argument unmirrored, so a header annotation the framework wrote on every tool's behalf would put each tool's whole surface behind a client feature its author never asked for — one some clients still lack. A tool that wants an argument routable by header implements Wymcp.Tool without the macro and writes its own input schema. header_annotations/1 reads the header annotations out of such a schema, and validate_header_annotations!/2 is what it is held to.

Per-action constraints are deliberately not encoded here: :required, :required_one_of and every property's own schema are enforced at dispatch by Wymcp.Tool — values by the value gate (Wymcp.Tool.ValueGate) — and the full per-action schemas are surfaced on demand by Wymcp.Help. This keeps the tools/list payload compact; agents act from the action summaries and pay for a tool's full schemas only when they ask.

Summary

Functions

An action summary is one action's name joined to its description as "<action>: <description>". Returns one summary per action of module, sorted by action name.

The header annotations schema declares, sorted by property path.

Validates every header annotation in schema, raising Wymcp.ConfigurationError naming module and the rule it breaks — and the property, wherever there is one to name. The configuration refusal carries module and input_schema/0 as the callback at fault.

Types

json_schema()

@type json_schema() :: %{required(String.t()) => term()}

Functions

action_summaries(module)

An action summary is one action's name joined to its description as "<action>: <description>". Returns one summary per action of module, sorted by action name.

What the help tool's server index renders from. build/1 renders the tools/list action description from the same private function rather than from this one, so a change made here alone reaches the index only.

Example

Given a tool whose Wymcp.Tool.actions/0 returns

%{
  get: %{description: "Get a widget", properties: %{}},
  create: %{description: "Create a widget", properties: %{}}
}

the summaries are

["create: Create a widget", "get: Get a widget"]

build(module)

header_annotations(schema)

The header annotations schema declares, sorted by property path.

Each entry is a map of :header — the annotation's value, the display half of the Mcp-Param-* name a conforming client sends — :path, the property's keys from the schema root, which is also the path into a call's arguments, and :type, the property's declared JSON Schema type.

Only properties reachable from the root through "properties" keys are returned, because those are the only ones the spec lets a client honour. An annotation anywhere else is not silently ignored: it is what validate_header_annotations!/2 refuses, by counting both sets and comparing them.

validate_header_annotations!(module, schema)

Validates every header annotation in schema, raising Wymcp.ConfigurationError naming module and the rule it breaks — and the property, wherever there is one to name. The configuration refusal carries module and input_schema/0 as the callback at fault.

The rules are the spec's, and a conforming client enforces the same set by excluding a tool whose schema breaks any of them from its own tool list — silently, as far as the server is concerned. Refusing the schema at the registration moment is what turns that disappearance into a message naming the tool.

Only a hand-written Wymcp.Tool.input_schema/0 can break a rule: the generated schema carries no header annotation. What reaches this function is a map that is not a struct, because the callback-shape check (Wymcp.Tool.validate_callback_shape!/1) refuses any other return from that callback before a definition is built.