Summary
Server.call_tool()'s low-level dispatch validates tool arguments against tool.inputSchema and, on failure, builds the error result like this:
try:
jsonschema.validate(instance=arguments, schema=tool.inputSchema)
except jsonschema.ValidationError as e:
return self._make_error_result(f"Input validation error: {e.message}")
_make_error_result produces CallToolResult(content=[TextContent(text=error_message)], isError=True) — no code, no structured data, nothing beyond the interpolated message string. jsonschema.ValidationError carries several structured attributes that would make a good stable identifier — e.validator (e.g. "type", "required", "enum", "additionalProperties"), e.schema_path, e.json_path — but all of them are discarded before the text crosses the transport (stdio, in our case).
Why this matters
A client that wants to programmatically distinguish why a tool call was rejected (missing required field vs. wrong type vs. enum mismatch, etc.) currently has no option but to regex e.message. That's brittle by construction: jsonschema's message wording already varies per validator keyword ("'X' is not of type 'Y'", "'X' is a required property", "'X' is not one of [...]", ...) with no shared machine-readable code across them, and nothing upstream commits to keeping that wording stable across jsonschema versions.
We hit this trying to classify tool-call failures from an MCP server (google-analytics-mcp, built on this SDK's low-level Server class) into "agent-fixable bad input" vs. "genuine server fault" for log-severity purposes, and had to give up — there's no code or structured field anywhere in the CallToolResult to key on, only the interpolated message. Confirmed this isn't something a well-behaved MCP client (or our own client's JSON-RPC handling) is dropping — the schema-validation error result is a normal JSON-RPC success envelope wrapping isError: true, and _make_error_result simply never puts anything but a message string into it.
Suggested fix
Forward e.validator (and ideally e.schema_path/e.json_path) into a structured data field on the error result, alongside the existing human-readable message — e.g. _make_error_result(message, data={"validator": e.validator, "schema_path": list(e.schema_path)}) — so any server built on the low-level Server class gets locale-stable, machine-readable input-validation errors "for free," without every server author having to reimplement schema validation themselves to get one.
Happy to provide a full repro (raw JSON-RPC request/response) if useful.
Summary
Server.call_tool()'s low-level dispatch validates tool arguments against tool.inputSchema and, on failure, builds the error result like this:
_make_error_result produces CallToolResult(content=[TextContent(text=error_message)], isError=True) — no code, no structured data, nothing beyond the interpolated message string. jsonschema.ValidationError carries several structured attributes that would make a good stable identifier — e.validator (e.g. "type", "required", "enum", "additionalProperties"), e.schema_path, e.json_path — but all of them are discarded before the text crosses the transport (stdio, in our case).
Why this matters
A client that wants to programmatically distinguish why a tool call was rejected (missing required field vs. wrong type vs. enum mismatch, etc.) currently has no option but to regex e.message. That's brittle by construction: jsonschema's message wording already varies per validator keyword ("'X' is not of type 'Y'", "'X' is a required property", "'X' is not one of [...]", ...) with no shared machine-readable code across them, and nothing upstream commits to keeping that wording stable across jsonschema versions.
We hit this trying to classify tool-call failures from an MCP server (google-analytics-mcp, built on this SDK's low-level Server class) into "agent-fixable bad input" vs. "genuine server fault" for log-severity purposes, and had to give up — there's no code or structured field anywhere in the CallToolResult to key on, only the interpolated message. Confirmed this isn't something a well-behaved MCP client (or our own client's JSON-RPC handling) is dropping — the schema-validation error result is a normal JSON-RPC success envelope wrapping isError: true, and _make_error_result simply never puts anything but a message string into it.
Suggested fix
Forward e.validator (and ideally e.schema_path/e.json_path) into a structured data field on the error result, alongside the existing human-readable message — e.g. _make_error_result(message, data={"validator": e.validator, "schema_path": list(e.schema_path)}) — so any server built on the low-level Server class gets locale-stable, machine-readable input-validation errors "for free," without every server author having to reimplement schema validation themselves to get one.
Happy to provide a full repro (raw JSON-RPC request/response) if useful.