Skip to content

fix: don't JSON pre-parse string args whose annotation only accepts str or None#3062

Closed
akminx wants to merge 1 commit into
modelcontextprotocol:mainfrom
akminx:fix-optional-str-pre-parse
Closed

fix: don't JSON pre-parse string args whose annotation only accepts str or None#3062
akminx wants to merge 1 commit into
modelcontextprotocol:mainfrom
akminx:fix-optional-str-pre-parse

Conversation

@akminx

@akminx akminx commented Jul 4, 2026

Copy link
Copy Markdown

Motivation and Context

Fixes #3055.

FuncMetadata.pre_parse_json decides whether to json.loads a string argument with field_info.annotation is not str. That treats str | None like a non-string annotation, so a valid string value that happens to parse as a JSON object/array (e.g. a JSON-serialized template body) is silently replaced with a dict/list and then fails validation of the argument model with Input should be a valid string.

How Has This Been Tested?

Added test_optional_str_not_parsed_as_json, which fails on main and passes with this change. The full test_func_metadata.py suite passes (43 tests), and ruff check / ruff format are clean.

Implementation notes

The issue suggested skipping pre-parsing for any union that includes str, but that would change the documented behavior covered by test_str_vs_list_str: for str | list[str], a stringified JSON array is still expected to parse into a list (the Claude Desktop workaround this hook exists for). So the new check is narrower: pre-parsing is skipped only when every union member is str or None — in that case a plain string is already fully valid and parsing could only corrupt it. Unions with non-str members keep the existing behavior, and Annotated members are unwrapped before the check.

Breaking Changes

None. Behavior changes only for parameters whose annotation is str/None-only unions, where the previous behavior corrupted valid input.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

…tr or None

pre_parse_json decided whether to json.loads a string argument with
'field_info.annotation is not str', which treats 'str | None' like a
non-string annotation. A valid string value that happens to parse as a
JSON object or array (e.g. a JSON-serialized template body) was silently
replaced with a dict/list and then failed model validation.

Only skip pre-parsing when every union member is str or None, so a plain
string is already fully valid. Unions with non-str members such as
'str | list[str]' keep the existing stringified-JSON handling.

Fixes modelcontextprotocol#3055
@akminx

akminx commented Jul 4, 2026

Copy link
Copy Markdown
Author

Closing in favor of #3056, which predates this PR and I hadn't spotted before opening. Left a note there about the Annotated[str, ...] | None spelling this PR also covered.

@akminx akminx closed this Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FuncMetadata.pre_parse_json mis-detects str | None as non-string and corrupts JSON-looking string arguments

1 participant