fix(server): surface structured validation errors in CallToolResult._meta (#3351)

Closed
LI
2mo ago
+142-4

Summary

Fixes #3351.

When Server.call_tool() (low-level) fails schema validation, it currently returns a CallToolResult(isError=True) whose content is only the interpolated jsonschema.ValidationError.message, e.g. "Input validation error: 'b' is a required property". As #3351 points out, jsonschema.ValidationError already carries stable structured fields (validator, validator_value, schema_path, json_path) — but they are all discarded before crossing the transport, forcing clients to regex-match brittle wording (whose exact form is not guaranteed by upstream jsonschema) just to distinguish required vs type vs enum failures.

Change

Forward the structured jsonschema fields into CallToolResult._meta under the MCP-namespaced key io.modelcontextprotocol/schema-validation-error, for both input- and output-schema failures. isError=True and the existing free-text message stay unchanged, so no existing client breaks.

Example payload on _meta after a missing-required failure:

{
  "io.modelcontextprotocol/schema-validation-error": {
    "kind": "input",
    "validator": "required",
    "validator_value": ["a", "b"],
    "schema_path": ["required"],
    "json_path": "$",
    "message": "'b' is a required property"
  }
}

Implementation notes

  • _make_error_result now takes an optional structured_data keyword and attaches it under the namespaced _meta key.
  • New _validation_error_data(...) staticmethod extracts the stable fields from jsonschema.ValidationError.
  • New module-level _jsonable(...) helper coerces non-JSON schema fragments (deques, sets, callables that live in validator_value for custom formats/keywords) into JSON-safe values before they cross the JSON-RPC transport, so the addition can't crash the response.
  • Both call sites (inputSchema and outputSchema validation) forward kind="input" / kind="output" accordingly.

Tests

  • test_input_validation_error_carries_structured_meta — asserts validator, validator_value, json_path for both missing-required and wrong-type failures.
  • test_enum_validation_error_carries_structured_meta — asserts enum classification exposes the allowed values in validator_value.
  • All existing test_lowlevel_input_validation.py and test_lowlevel_output_validation.py cases keep passing.

Backport target

Opened against v1.x since the issue is labeled v1 + v2; happy to also cherry-pick to main (v2) if maintainers prefer.