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.
Summary
When a
tools/call's arguments fail the tool'sinputSchema,McpServerreturns 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/server2.0.0 and 2.1.0,McpServer.validateToolInputisprivate. On failure it throws:The
tools/callhandler's catch turns this intocreateToolError(message), which is alsoprivate:{ isError: true, content: [{ type: 'text', text }] }. There is nostructuredContent, 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 anoutputSchemacovering both success and failure. That way an agent can branch on an errortypeinstead of parsing prose.A schema failure is the one failure that can't follow the format:
Workaround
Build each tool's
inputSchemawithfromJsonSchema(jsonSchema, validator), where thevalidatoris ajsonSchemaValidatorthat accepts every input. Then validate inside the callback and return the structured error there.This keeps
tools/listcorrect, 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 inregisterTool) that receives the failure and returns theCallToolResult:issueswould be the Standard Schema issues, each with apathand amessage.structuredContent, or in_meta. That would leave the text unchanged but make the path readable without parsing.Related
.refineemptyinginputSchemaon 1.x, is another reason servers move to v2, where this hook would live.Versions checked:
@modelcontextprotocol/server2.0.0 and 2.1.0 (packages/server/src/server/mcp.ts), and@modelcontextprotocol/sdk1.30.1 for comparison.