Skip to content

output schema has unnecessary restriction type=object #1149

Closed

Description

@spacewander

Describe the bug
A clear and concise description of what the bug is.

The typescript SDK requires that the outputSchema be in type: "object".

typescript-sdk/src/types.ts

Lines 1013 to 1018 in 0c1cbf0

outputSchema: z
.object({
type: z.literal('object'),
properties: z.record(z.string(), AssertObjectSchema).optional(),
required: z.optional(z.array(z.string())),
additionalProperties: z.optional(z.boolean())

The current constraint prevents describing the response for list and batch requests.
This is noteworthy as the specification does not strictly mandate that the outputSchema must be an object (see: https://modelcontextprotocol.io/specification/draft/server/tools#output-schema).
Consequently, the Python SDK does not enforce this schema constraint on the output.

To Reproduce
Steps to reproduce the behavior:

Let the MCP server return an outputSchema in this format:

         {
            type: "array",
            properties: {
              foo: { type: "string" }
            }
          }

Then an error is thrown:

ZodError: [
  {
    "received": "array",
    "code": "invalid_literal",
    "expected": "object",
    "path": [
      "tools",
      0,
      "outputSchema",
      "type"
    ],
    "message": "Invalid literal value, expected \"object\""
  }
]

Expected behavior
A clear and concise description of what you expected to happen.

Should accept any valid JSONSchema, at least the array one.

Activity

  1. added 2 commits that reference this issue on Nov 25, 2025
    3e41a67
    b567184
  2. RobJacobson commented on Dec 12, 2025

    @RobJacobson

    Additional Details: Runtime Crash with Non-Object Output Schemas

    I encountered this issue in a real-world scenario and traced it to a runtime crash. Here are the details:

    Environment

    • SDK Version: 1.24.3
    • Zod Version: 4.x
    • Use case: MCP server wrapping APIs that return optional dates (z.date().optional()) and arrays

    Stack Trace

    TypeError: Cannot read properties of undefined (reading '_zod')
        at isZ4Schema (zod-compat.ts:61:21)
        at safeParseAsync (zod-compat.ts:97:9)
        at McpServer.validateToolOutput (mcp.ts:201)
    

    Root Cause Analysis

    In validateToolOutput() (mcp.ts lines 200-201):

    const outputObj = normalizeObjectSchema(tool.outputSchema);
    const parseResult = await safeParseAsync(outputObj, result.structuredContent);

    normalizeObjectSchema() returns undefined for non-object schemas (arrays, optionals, etc.). This undefined is then passed to safeParseAsync(), which calls isZ4Schema(undefined), causing the crash.

    Proposed Fix (4 lines)

    const outputObj = normalizeObjectSchema(tool.outputSchema);
    
    // Cannot validate non-object schemas (arrays, optionals at root level, etc.)
    if (!outputObj) {
        return;
    }
    
    const parseResult = await safeParseAsync(outputObj, result.structuredContent);

    Why This Fix

    1. Graceful degradation: Tools with non-object output schemas work; validation is skipped
    2. No breaking changes: Existing tools with object schemas continue to be validated
    3. Minimal change: 4 lines, no architectural changes
    4. Aligns with spec: As you noted, the MCP specification doesn't mandate object-only outputSchema

    Workaround (for others hitting this)

    Until this is fixed, you can work around it by ensuring your outputSchema is always an object:

    // Instead of:
    outputSchema: z.date().optional()
    
    // Use:
    outputSchema: z.object({ value: z.string().datetime().optional() })

    Happy to submit a PR for this fix if it would be helpful!

  3. added
    ready for workEnough information for someone to start working on
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Dec 16, 2025
  4. felixweinberger commented on Dec 16, 2025

    @felixweinberger
    Contributor

    Confirmed still an issue on v1.25.1. The spec doesn't require outputSchema to be type: object.

    The quick fix (skip validation for non-object schemas) would work but leaves those schemas unvalidated. The proper fix is to use the existing jsonSchemaValidator (AJV) infrastructure for output validation instead of Zod, which would support all JSON Schema types.

    Adding to v2 backlog.

  5. domdomegg commented on Dec 16, 2025

    @domdomegg
    Member
  6. dastrobu commented on Jan 8, 2026

    @dastrobu
  7. ghost added
    fix proposedBot has a verified fix diff in the comment
    on Apr 16, 2026
  8. tobiasBora commented on Jul 21, 2026

    @tobiasBora

    Any news? This bug is also present in v4: when the structuredContent is a string is seems to wrap it inside a { result: str } object, but it's NOT what the spec mentions.

  9. olaservo commented on Aug 2, 2026

    @olaservo
    Member

    I think this can now be closed since v2.0 allows any 2020-12 JSON schema type for outputSchema, as of 2026-07-28.

    Edit: expanding on this comment for more detail:

    The original object restriction was lifted by SEP-2106, which landed in #2337 and shipped in @modelcontextprotocol/core@2.0.0. Both enforcement points named in the triage comment above are gone: ToolSchema no longer pins type: z.literal('object'), and standardSchemaToJsonSchema no longer throws for a non-object root.

    Re-running the repro from this thread against the published 2.0.0, array, string, number, boolean, anyOf, oneOf and $ref roots are all accepted, and registerTool takes a bare z.array(...) as an outputSchema.

    A couple other things to note:

    This was only fixed in v2, and @modelcontextprotocol/sdk@1.30.0 still rejects the same non-object schemas.

    @tobiasBora re: the { result: … } wrapping you raised just above: that is a different mechanism and it is working as intended. On protocol revision 2026-07-28 a tool with a non-object outputSchema returns the bare value. On the older revisions the wire shape requires structuredContent to be an object, so the SDK projects both halves together and the advertised schema becomes {"type":"object","properties":{"result":…},"required":["result"]} and the payload becomes {"result": …}.

    This is meant to keep the schema and the value consistent for a legacy-era client. The client's versionNegotiation defaults to legacy, so an unconfigured client negotiates the older revision and sees the wrapped form. If it connects with a 2026-07-28 negotiation and the same tool returns your string unwrapped.

    #1169 was the fix proposed for this and is superseded by #2337, so it should be closed alongside.

  10. mattzcarey commented on Sep 10, 2026

    @mattzcarey
    Member

    Closed as resolved by the spec: SEP-2106 lifted the type: object restriction in the 2026-07-28 revision (changelog, minor change 10). The SDK's 2026-07-28 wire schema accepts any JSON Schema 2020-12 for outputSchema in buildSchemas.ts#L728-L732. The 2025-11-25 restriction remains correct for that revision.

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

Metadata

Metadata

Assignees

Labels

P2Moderate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingfix proposedBot has a verified fix diff in the commentready for workEnough information for someone to start working onv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions