modelcontextprotocol / modelcontextprotocol/python-sdk
Type the MCPServer handler pipeline: tool/resource/prompt return types
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.3k
- Forks
- 4k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 31
Description
The chain from user @tool()-decorated functions through to CallToolResult is typed as Any end-to-end, hiding a wrong public return type and dead code.
Tool call chain
FuncMetadata.convert_result() returns exactly three shapes:
CallToolResult(when the user returned one directly)Sequence[ContentBlock](unstructured)tuple[Sequence[ContentBlock], dict[str, Any]](unstructured + structured)
But every step of the chain is -> Any:
FuncMetadata.convert_result()—-> AnyTool.run()—-> AnyToolManager.call_tool()—-> AnyMCPServer.call_tool()— declared-> Sequence[ContentBlock] | dict[str, Any], wrong — never returnsdict
Root cause: the convert_result: bool flag on Tool.run() / ToolManager.call_tool() toggles the return type, which is unrepresentable. The False path isn't used in production (only server.py:405, always True).
Symptoms:
- Dead code + TODO acknowledging the wrong type at server.py:317-324
# type: ignore[arg-type]shoving tuple elements intoCallToolResultat server.py:314-315
Prompt / Resource
PromptResultalready includesAwaitable, butPrompt.fnis typedCallable[..., PromptResult | Awaitable[PromptResult]]— doubly wrappedResourceTemplate.matches()returnsdict[str, Any] | Nonebut values are alwaysstr(regex groupdict + unquote)
User-facing return types
What you can return from @tool() is undocumented. A strict bound is impossible (_convert_to_content() ends with pydantic_core.to_json(fallback=str) — everything works), but the docstrings should enumerate what gets first-class handling (str/primitives/BaseModel/ContentBlock/Image/Audio/CallToolResult/list/tuple) vs what gets JSON-dumped.
Related
- #1207 — excessive generics (reason to not introduce
Tool[ReturnT]; aTypeAliasfor the converted result is sufficient) - #2153 — handler error semantics
- #2201 — Tasks support (adds
CreateTaskResultto the converted-result union)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with FuncMetadata.convert_result(), Tool.run(), ToolManager.call_tool(), and MCPServer.call_tool() to trace the tool return chain and the convert_result flag. Then inspect Prompt.fn, PromptResult, and ResourceTemplate.matches() for the stated annotation mismatches. Done means the handler pipeline has accurate public types, dead or incorrect typing paths are addressed, and user-facing return handling is documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, documentation
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100