examples/stories/schema_validators/README.md
Four ways to type a tool parameter so MCPServer derives the JSON-Schema
inputSchema and validates arguments before your handler runs: a pydantic
BaseModel, a TypedDict, a @dataclass, and a bare dict[str, Any]. The
client lists the tools, resolves each who schema, and round-trips a call.
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.schema_validators.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.schema_validators.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.schema_validators.client --http --server server_lowlevel
client.py main — the body opens with async with Client(target, mode=mode) as client:. target is anything Client accepts (an in-process server, a
transport, or an HTTP URL); the entry point picks it, the story constructs it.server.py — who.name vs who["name"]: pydantic and dataclass parameters
arrive as instances (attribute access); TypedDict and dict[str, Any]
arrive as plain dicts.client.py — the listed inputSchema for the three typed variants nests a
$defs/$ref object with a name property; greet_dict publishes only
{"type": "object", "additionalProperties": true} — no field validation.server_lowlevel.py — the same schemas written by hand. There is no
reflection layer at this tier; you author JSON Schema and unpack
params.arguments yourself.#/$defs/ references for nested models. The SDK does
not dereference network $refs (SEP-2106 MUST NOT); only same-document refs
are resolved during validation.PersonTD is total=True, so its nested schema requires both name and
title; the BaseModel and @dataclass variants default title="friend",
so only name is required there. Use typing.NotRequired[...] to mark
optional TypedDict fields.tools/ (output schema → structuredContent), error_handling/ (what
happens when validation fails).