feat: add MCP_HIDE_INPUT_IN_ERRORS env var to redact payloads from validation errors#2346
Draft
feat: add MCP_HIDE_INPUT_IN_ERRORS env var to redact payloads from validation errors#2346
Conversation
…lidation errors Pydantic's ValidationError repr includes the raw input_value by default. When the SDK calls model_validate_json() on untrusted data (SSE messages, OAuth responses) and validation fails, logger.exception() dumps the entire payload into logs. This can leak sensitive tool output or OAuth tokens. Setting MCP_HIDE_INPUT_IN_ERRORS=1 before importing the SDK applies hide_input_in_errors=True to the jsonrpc_message_adapter TypeAdapter and the OAuth models (OAuthToken, OAuthClientMetadata, OAuthMetadata, ProtectedResourceMetadata). The error type and location remain in the message; only the raw input is omitted. Opt-in via env var to preserve the current debugging-friendly default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in
MCP_HIDE_INPUT_IN_ERRORSenvironment variable that suppresses theinput_valuefield in pydanticValidationErrorreprs for models that parse untrusted external data.Motivation and Context
Pydantic's
ValidationError.__repr__includes the rawinput_valueby default. When the SDK callsmodel_validate_json()on untrusted input and validation fails, anylogger.exception(...)at the call site dumps the entire payload into logs.Example from a production deployment — a truncated SSE message caused the client to log 55KB of tool result content:
This pattern exists at several call sites:
client/streamable_http.py— SSE and JSON response parsing →logger.exception("Error parsing SSE message")client/sse.py,client/stdio.py,client/websocket.py— same pattern forjsonrpc_message_adapter.validate_json()client/auth/oauth2.py—OAuthToken.model_validate_json()→logger.exception("Invalid refresh response")(can leak token payloads)client/auth/utils.py,server/auth/handlers/register.py— OAuth metadata and client registration parsingSetting
MCP_HIDE_INPUT_IN_ERRORS=1before importing the SDK applies pydantic'shide_input_in_errors=Trueconfig so errors still show the type and location (e.g.EOF while parsing a string at line 1 column 55456) but omit the raw payload.How Has This Been Tested?
test_validation_error_shows_input_by_default— verifies current behavior unchanged (input shown by default)test_hide_input_in_errors_env_var— subprocess test verifyinginput_valueis absent from errors forjsonrpc_message_adapter,OAuthToken,OAuthMetadata,ProtectedResourceMetadata, andOAuthClientMetadatawhen the env var is set (parametrized over1/true/True/TRUE)Subprocess is used because the env var is read at import time —
monkeypatch.setenvwould have no effect on the already-constructedTypeAdapter.Breaking Changes
None. The default behavior is unchanged; the env var is opt-in.
Types of changes
Checklist
Additional context
Why env var instead of a
configure()function? Thejsonrpc_message_adapteris a module-levelTypeAdapterthat's directly imported (from mcp.types import jsonrpc_message_adapter) in several transports. A runtimeconfigure()that rebuilds the adapter would leave those direct imports holding stale references. Checking the env var at import time avoids this entirely with zero runtime cost.Why config on the
TypeAdapterand not the union's member models? Verified that settinghide_input_in_errors=TrueonJSONRPCRequest/JSONRPCResponse/etc. does not propagate to top-level JSON parse errors raised by theTypeAdapter— the config must be on the adapter itself. The OAuth models are validated directly viaModel.model_validate_json(), so they getmodel_configindividually.Why not
SecretStr?SecretStronly masks field values after successful parsing. The motivating error is a JSON parse failure (EOF while parsing) — validation fails before any field types run, soSecretStrnever executes. The leaked data is alsoJSONRPCResponse.result: dict[str, Any](arbitrary tool output), not a nameable secret field.AI Disclaimer