Skip to content

[v2][Enhancement][Fix Proposed] McpServer input validation error hook #2879

Description

@CyberStrike

Summary

When a tools/call's arguments fail the tool's inputSchema, McpServer returns a fixed plain-text tool error. A server can't change what that result contains, so it can't give a schema failure the same structured error its tool callbacks return for every other failure. I'd like a supported way to shape that result.

Current behaviour

In @modelcontextprotocol/server 2.0.0 and 2.1.0, McpServer.validateToolInput is private. On failure it throws:

throw new ProtocolError(
  ProtocolErrorCode.InvalidParams,
  `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`
);

The tools/call handler's catch turns this into createToolError(message), which is also private: { isError: true, content: [{ type: 'text', text }] }. There is no structuredContent, and the issues aren't structured (the path and message of each failing argument).

Returning a tool execution error rather than a protocol error matches SEP-1303. The problem is only that the result's shape is fixed. On 1.x a server could override validateToolInput; in v2 it can't.

Why it matters

Many servers wrap an API that already has a structured error format (RFC 9457 problem details, for example). Their tool callbacks return that format as structuredContent, with an outputSchema covering both success and failure. That way an agent can branch on an error type instead of parsing prose.

A schema failure is the one failure that can't follow the format:

  • An agent sees two error shapes from one tool, depending on whether the SDK or the callback refused the call.
  • The failing argument's path is only available by parsing the message string.

Workaround

Build each tool's inputSchema with fromJsonSchema(jsonSchema, validator), where the validator is a jsonSchemaValidator that accepts every input. Then validate inside the callback and return the structured error there.

This keeps tools/list correct, and it uses the documented validator extension point. But it defers validation in a way that surprises readers. It also depends on the SDK continuing to validate through the schema's ~standard.validate, rather than against the JSON Schema directly.

Proposal

An option on McpServer (or per tool in registerTool) that receives the failure and returns the CallToolResult:

new McpServer(info, {
  onToolInputValidationError: ({ toolName, issues, arguments: args }) => ({
    isError: true,
    content: [{ type: 'text', text: summarize(issues) }],
    structuredContent: { problem: toProblem(toolName, issues) },
  }),
});
  • issues would be the Standard Schema issues, each with a path and a message.
  • The default stays today's text result, so existing servers see no change.
  • A smaller alternative: include the structured issues in the default result's structuredContent, or in _meta. That would leave the text unchanged but make the path readable without parsing.

Related

Versions checked: @modelcontextprotocol/server 2.0.0 and 2.1.0 (packages/server/src/server/mcp.ts), and @modelcontextprotocol/sdk 1.30.1 for comparison.

Activity

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

    v1Issues / PRs related to v1.xv2Ideas, 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