v1.2.0 — say what you return
MCP tools now publish the names of the keys they return (#47)
fastmcp derives outputSchema from a tool's return annotation, so dict[str, Any] published {"type": "object", "additionalProperties": true} — an object with no key names. A programmatic caller had to guess, and a wrong guess produces a program that validates, runs, and answers incorrectly.
Measured across four local models driven through lackpy: 0/24 correct while 17/24 called the right tool. len() on events' returned dict gives its key count, not its event count.
14 tools now return TypedDicts whose keys come from the implementations' own return statements.
Declaring the shapes immediately caught three places where the code did not match a reasonable reading of it:
output.streamsis a list of stream names, not a mappingquery.rowsis positional value lists paired withcolumns, not per-row objectsci_generate.availableis the registered command names, not a boolean
No data is dropped — keys a TypedDict does not declare still reach the client in structuredContent, verified directly.
One note for Python callers
With an outputSchema present, fastmcp's own client deserializes .data into a generated model rather than a dict, so result.data["key"] no longer works. Read structured_content instead — that is the JSON a real MCP client receives over the wire. Anything speaking JSON-RPC (Claude Code, lackpy, any stdio client) is unaffected and strictly better off.
Suite: 1382 passing.