Skip to content

PydanticSchemaGenerationError: Unable to generate pydantic-core schema for Image type #1060

Description

@paxan

Initial Checks

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

Activity

  1. paxan commented on Jun 30, 2025

    @paxan
    Author

    FYI, this is also reproducible on today's main (6f43d1f)

  2. Kludex commented on Aug 9, 2025

    @Kludex
    Member

    Whats your pydantic version?

  3. self-assigned this
    on Aug 9, 2025
  4. added
    needs confirmationNeeds confirmation that the PR is actually required or needed.
    on Aug 9, 2025
  5. paxan commented on Aug 10, 2025

    @paxan
    Author

    When running the above modified unit test using mcp 1.9.4:

    $ uv tree --frozen | grep pydantic
    │   ├── pydantic v2.10.1
    │   │   ├── pydantic-core v2.27.1
    │   ├── pydantic-settings v2.6.1
    │   │   ├── pydantic v2.10.1 (*)
    ├── pydantic v2.10.1 (*)
    ├── pydantic-settings v2.6.1 (*)
    

    When running the above modified unit test using mcp 1.12.4:

    $ uv tree --frozen | grep pydantic
        ├── pydantic v2.11.7
        │   ├── pydantic-core v2.33.2
        ├── pydantic-settings v2.10.1
        │   ├── pydantic v2.11.7 (*)
    ├── pydantic v2.11.7 (*)
    ├── pydantic-settings v2.10.1 (*)
    

    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:

    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.
    

    We've managed to work around this by using ImageContent type instead of Image, as it would seem Image is not compatible with Pydantic-based schema generation, and older versions of MCP SDK had, perhaps, some special case for Image type.

    Perhaps Image type should be avoided? And docs/readme should steer people to use ImageContent?

  6. added
    bugSomething isn't working
    ready for workEnough information for someone to start working on
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    and removed
    needs confirmationNeeds confirmation that the PR is actually required or needed.
    on Oct 6, 2025
  7. opensource-joe commented on Aug 15, 2026

    @opensource-joe

    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.Image since this was filed, but the behaviour is the same. What matters is that a bare Image return is fine, and every container of one fails:

    return annotation on main
    Image ok, output_schema=None
    Audio ok, output_schema=None
    list[Image] PydanticSchemaGenerationError
    dict[str, Image] PydanticSchemaGenerationError
    tuple[str, Image, AudioContent] PydanticSchemaGenerationError (the reported case)
    Image | None PydanticSchemaGenerationError
    list[Audio] PydanticSchemaGenerationError

    The 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:

    try:
        schema = model.model_json_schema(schema_generator=StrictJsonSchema)
    except (PydanticUserError, TypeError, ValueError, pydantic_core.SchemaError, pydantic_core.ValidationError):

    but PydanticSchemaGenerationError is raised earlier than that, during create_model() inside _create_wrapped_model(), which sits above the try. It is a subclass of PydanticUserError (and of TypeError), 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 Image never reaches create_model(), because it falls to the "other class types" branch, has no type hints, and leaves model as None.

    Possible fix

    Split model building into its own helper and guard both halves with the same exception tuple. Containers of Image then degrade to output_schema=None, which is exactly what a bare Image already does, and structured_output=True raises the intended InvalidSignature ("is not serializable for structured output") instead of leaking a raw pydantic error.

    I have this written and verified locally:

    • All five failing shapes above degrade to no structured output, matching bare Image.
    • End to end, a list[Image] tool registers, lists with outputSchema: null, and returns real ImageContent blocks with the right mime types and decodable data. The runtime path in convert_result already handled Image correctly, so only schema derivation was breaking.
    • Full suite passes, 5633 passed / 8 skipped / 1 xfailed. ruff check, ruff format --check and pyright are clean.
    • The regression test is parametrized over the five shapes and is not vacuous: reverting only the source change turns 5 of its 6 cases red.

    The alternative would be giving Image and Audio a __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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions