modelcontextprotocol/typescript-sdk

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

Opened this issue · 0 comments

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