Repository navigation
PydanticSchemaGenerationError: Unable to generate pydantic-core schema for Image type #1060
Copy link
Copy link
Open
Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working on
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
The Image type in question is imported like this: from mcp.server.fastmcp.utilities.types import Image
We are upgrading our dependency on mcp from 1.9.4 to 1.10.1.
Once on 1.10.1 our application and tests began to fail with error like this:
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema
for <class 'mcp.server.fastmcp.utilities.types.Image'>. Set `arbitrary_types_allowed=True` in
the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your
type to fully support it.
I found a test tool function among this repo's unit tests, that is pretty close to the mixed content
use case in our server. So I tweaked it to create a repro case for you:
diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py
index c30930f..20c25af 100644
--- a/tests/server/fastmcp/test_server.py
+++ b/tests/server/fastmcp/test_server.py
@@ -194,12 +194,12 @@ def image_tool_fn(path: str) -> Image:
return Image(path)
-def mixed_content_tool_fn() -> list[ContentBlock]:
- return [
- TextContent(type="text", text="Hello"),
- ImageContent(type="image", data="abc", mimeType="image/png"),
+def mixed_content_tool_fn() -> tuple[str, Image, AudioContent]:
+ return (
+ "Hello",
+ Image(data=b"abc", format="png"),
AudioContent(type="audio", data="def", mimeType="audio/wav"),
- ]
+ )
class TestServerTools:
@@ -311,7 +311,7 @@ class TestServerTools:
assert content1.text == "Hello"
assert isinstance(content2, ImageContent)
assert content2.mimeType == "image/png"
- assert content2.data == "abc"
+ assert content2.data == "YWJj"
assert isinstance(content3, AudioContent)
assert content3.mimeType == "audio/wav"
assert content3.data == "def"test_tool_mixed_content fails with PydanticSchemaGenerationError on 1.10.1 but succeeds on 1.9.4.
Python & MCP Python SDK
Python 3.12, mcp 1.10.1
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working on
Activity
paxan commentedon Jun 30, 2025
FYI, this is also reproducible on today's main (6f43d1f)
Kludex commentedon Aug 9, 2025
Whats your pydantic version?
paxan commentedon Aug 10, 2025
When running the above modified unit test using mcp 1.9.4:
When running the above modified unit test using mcp 1.12.4:
As originally described, in mcp 1.9.4 this style of mixed return from a tool was working.
But in current versions it still fails with:
We've managed to work around this by using
ImageContenttype instead ofImage, as it would seemImageis not compatible with Pydantic-based schema generation, and older versions of MCP SDK had, perhaps, some special case forImagetype.Perhaps
Imagetype should be avoided? And docs/readme should steer people to useImageContent?opensource-joe commentedon Aug 15, 2026
Still reproduces on
main(52ad0a88, pydantic 2.12.5, Python 3.12), and it turns out to be a bit wider than the report.The class moved to
mcp.server.mcpserver.utilities.types.Imagesince this was filed, but the behaviour is the same. What matters is that a bareImagereturn is fine, and every container of one fails:mainImageoutput_schema=NoneAudiooutput_schema=Nonelist[Image]PydanticSchemaGenerationErrordict[str, Image]PydanticSchemaGenerationErrortuple[str, Image, AudioContent]PydanticSchemaGenerationError(the reported case)Image | NonePydanticSchemaGenerationErrorlist[Audio]PydanticSchemaGenerationErrorThe error escapes
func_metadata(), so it takes out tool registration rather than just the output schema.Cause
In
src/mcp/server/mcpserver/utilities/func_metadata.py,_try_create_model_and_schema()wraps only the schema call:but
PydanticSchemaGenerationErroris raised earlier than that, duringcreate_model()inside_create_wrapped_model(), which sits above thetry. It is a subclass ofPydanticUserError(and ofTypeError), so that existing tuple would already catch it if it were in scope. The guard is in the right shape and just does not cover model construction.That also explains the bare/container split: a bare
Imagenever reachescreate_model(), because it falls to the "other class types" branch, has no type hints, and leavesmodelasNone.Possible fix
Split model building into its own helper and guard both halves with the same exception tuple. Containers of
Imagethen degrade tooutput_schema=None, which is exactly what a bareImagealready does, andstructured_output=Trueraises the intendedInvalidSignature("is not serializable for structured output") instead of leaking a raw pydantic error.I have this written and verified locally:
Image.list[Image]tool registers, lists withoutputSchema: null, and returns realImageContentblocks with the right mime types and decodable data. The runtime path inconvert_resultalready handledImagecorrectly, so only schema derivation was breaking.ruff check,ruff format --checkandpyrightare clean.The alternative would be giving
ImageandAudioa__get_pydantic_core_schema__so they describe themselves properly, but that is a larger call about whether these helpers should be part of the structured output surface at all, and it would change the schema of tools that currently return them. I went with the narrow fix, though I am happy to go the other way if you would rather.Before I open anything
This is labelled
ready for work, which per CONTRIBUTING is the maintainer queue rather than an invitation, so I would rather ask than assume. If you would like this from an outside contributor, could you assign it to me and I will open the PR. If you would rather keep it in house, no problem, and hopefully the diagnosis above still saves someone the reproduction work.Disclosure: I used AI assistance while investigating and writing this. I have reviewed and verified all of it myself and can speak to any part of it.