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_lowlevelclient.pymain— the body opens withasync with Client(target, mode=mode) as client:.targetis anythingClientaccepts (an in-process server, a transport, or an HTTP URL); the entry point picks it, the story constructs it.server.py—who.namevswho["name"]: pydantic and dataclass parameters arrive as instances (attribute access); TypedDict anddict[str, Any]arrive as plain dicts.client.py— the listedinputSchemafor the three typed variants nests a$defs/$refobject with anameproperty;greet_dictpublishes 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 unpackparams.argumentsyourself.
- Pydantic emits local
#/$defs/references for nested models. The SDK does not dereference network$refs (SEP-2106 MUST NOT); only same-document refs are resolved during validation. PersonTDistotal=True, so its nested schema requires bothnameandtitle; theBaseModeland@dataclassvariants defaulttitle="friend", so onlynameis required there. Usetyping.NotRequired[...]to mark optional TypedDict fields.
tools/ (output schema → structuredContent), error_handling/ (what
happens when validation fails).