Skip to content

The validation of ElicitResultSchema is excessively strict and violates the MCP Specifications. #966

@amannirala13

Description

@amannirala13

ElicitResultSchema violates MCP Specs

ElicitResultSchema validation fails when content: null is used for cancel/decline responses, even though this is a reasonable developer pattern and the MCP spec allows flexibility. The MCP spec says content is "typically omitted" (not forbidden) for cancel/decline actions, but the SDK rejects content: null.

To Reproduce
Steps to reproduce the behavior:

  1. Install the SDK: npm install @modelcontextprotocol/sdk@1.17.5
  2. Run this code:
import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";

ElicitResultSchema.parse({ action: "cancel", content: null });
// Error: Expected object, received null

Expected behavior
The schema should accept content: null for cancel/decline responses. According to the MCP Elicitation Specification:

Decline (action: "decline"): User explicitly declined the request

  • The content field is typically omitted

Cancel (action: "cancel"): User dismissed without making an explicit choice

  • The content field is typically omitted

The spec uses "typically omitted" (not "must be omitted"), allowing flexibility for different developer patterns like content: null.

Evidence that the current schema is inconsistent:

  • This works: ElicitResultSchema.parse({ action: "cancel" }) (omitting content)
  • This works: ElicitResultSchema.parse({ action: "cancel", content: {} }) (empty object)
  • This fails: ElicitResultSchema.parse({ action: "cancel", content: null }) (explicit null)

Logs

Error: [
  {
    "code": "invalid_type",
    "expected": "object",
    "received": "null",
    "path": ["content"],
    "message": "Expected object, received null"
  }
]

Additional context
This breaks real elicitation workflows where clients use content: null as a sensible default:

client.setRequestHandler(ElicitRequestSchema, async (request, extra) => {
  return { action: "cancel", content: null };  // Fails validation
});

Full reproduction with server/client setup:

Server code:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "test-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Tool that uses elicitInput - this is where the validation failure occurs
server.setRequestHandler({ method: "tools/call" }, async (request) => {
  if (request.params.name === "test-tool") {
    const result = await server.elicitInput({
      message: "Choose an option",
      requestedSchema: { type: "object", properties: { choice: { type: "string" } } }
    });
    
    // The validation error happens when processing result from client
    return { content: [{ type: "text", text: `Action: ${result.action}` }] };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

Client code:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";

const client = new Client(
  { name: "test-client", version: "1.0.0" },
  { capabilities: { elicitation: {} } }
);

// This handler causes the validation error
client.setRequestHandler(ElicitRequestSchema, async (request, extra) => {
  return {
    action: "cancel",
    content: null  // This breaks SDK validation
  };
});

const transport = new StdioClientTransport({
  command: "node", args: ["server.js"]
});
await client.connect(transport);

Proposed fix:

// only validate content for accept actions
z.union([
  // Accept: content required
  ResultSchema.extend({
    action: z.literal("accept"),
    content: z.record(z.string(), z.unknown())
  }),
  // Cancel/decline: content optional and flexible
  ResultSchema.extend({
    action: z.enum(["decline", "cancel"]),
    content: z.optional(z.any())
  })
])

Environment: @modelcontextprotocol/sdk@1.17.5, Node v22.17.0, macOS Darwin 24.6.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions