Bare `dict` / `list` / `tuple` return annotations silently produce no structured output
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- backend-api-design
Research direction
Start by reading _create_output_model in src/mcp/server/mcpserver/utilities/func_metadata.py and the structured output docs at docs/servers/structured-output.md. Reproduce the difference between bare and typed container annotations, then check the existing tests for output schemas and structured content. Done means bare dict, list, and tuple annotations have the expected structured output behavior and the docs explain it.
Written by the indexing model from the issue text.
Description
Initial Checks
- I confirm that I'm using the newest release of my line (the latest 2.x, or the latest 1.x if I'm still on v1)
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Release line
2.x (current stable)
Description
A tool annotated with a bare dict (or list / tuple) gets no outputSchema and no structuredContent. The result only arrives as JSON text. -> dict[str, Any] works, and -> str gets {"result": ...}. With structured_output=True the bare annotation raises InvalidSignature: Function f: return type <class 'dict'> is not serializable for structured output, which reads as if a dict weren't serializable.
Expected: -> dict behaves like dict[str, Any], and -> list / -> tuple like list[Any] (wrapped in {"result": ...}). Failing that, the structured-output docs could say that bare containers opt out.
Cause: _create_output_model sends bare dict / list / tuple to the "other class types" case. get_type_hints(dict) is empty, so the model stays None: the silent opt-out documented for classes without type hints, which isn't obvious for builtins. We hit it while building an MCP server: our tools were -> dict, and our client code expected structured_content that was always None.
Proposed fix: a case before the class fallback that sends bare dict down the dict[str, ...] path and wraps bare list / tuple like their generic forms, plus a test and one sentence in docs/servers/structured-output.md. It's about 10 lines of code, and the full suite passes (6206 passed). One behaviour change to weigh: existing bare -> dict / -> list tools would start publishing an outputSchema and validating their results.
I'd like to send this as a PR if you're open to it. It's on a branch: https://github.com/g-s-jithesh/python-sdk/commit/6ecfa04ece6032816c7d3a087fd8c841cb0c43e1
I used an AI coding assistant (Claude Code) to help investigate this and draft the fix. I reproduced the bug myself and reviewed the change.
Example Code
import asyncio
from typing import Any
from mcp import Client
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("repro")
@mcp.tool()
def bare() -> dict:
return {"id": 1}
@mcp.tool()
def typed() -> dict[str, Any]:
return {"id": 1}
async def main() -> None:
async with Client(mcp) as client:
for name in ("bare", "typed"):
print(name, (await client.call_tool(name, {})).structured_content)
asyncio.run(main())
# bare None
# typed {'id': 1}
Python & MCP Python SDK
Python 3.14, Windows 11; mcp 2.3.0, and main at 9f66f37 (src/mcp/server/mcpserver/utilities/func_metadata.py, _create_output_model)
- Dominant language
- Python
- Stars
- 24.6k
- Forks
- 4.2k
- Avg merge
- 18h
- Merged PRs (30d)
- 39
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing 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.
More from modelcontextprotocol/python-sdk
-
bug v1 v2
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
modelcontextprotocol/python-sdk#3729 ·
Maintainers usually reply within 1 day
-
bug v1 v2
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
modelcontextprotocol/python-sdk#3698 · 2 comments ·
Maintainers usually reply within 1 day
-
Streamable HTTP: Content-Type handling is order dependent and accepts invalid valuesMay be free again @jayzuccarelli claimed this 7 days ago, and no pull request is open. Openbug v1 v2
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
modelcontextprotocol/python-sdk#3691 · 1 comment ·
Maintainers usually reply within 1 day
-
Skills: URI traversal validation skips percent-encoded dot-segmentsPossibly taken @Varshith-Kali claimed this 6 days ago. Openbug v2
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
modelcontextprotocol/python-sdk#3685 · 2 comments ·
Maintainers usually reply within 1 day
-
Type annotation gaps and unhandled FileNotFoundError in src/mcp/cli/cli.pyMay be free again A pull request for this issue was closed without being merged. Openenhancement
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
modelcontextprotocol/python-sdk#3673 · 4 comments ·
Maintainers usually reply within 1 day
All issues in modelcontextprotocol/python-sdk
Similar issues
-
Issue Triage-Needed
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
pulp/pulp_container#2581 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
521xueweihan/HelloGitHub#4040 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
ClanGenOfficial/clangen#6313 ·
Maintainers usually reply within 2 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
bug(trainer): CustomTrainer func_args with non literal values fail inside the TrainJobPossibly taken @avanishkasar claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
kubeflow/sdk#879 · 1 comment ·
Maintainers usually reply within 1 day