Skip to content

fix: skip JSON pre-parsing for str union annotations in pre_parse_json#2462

Open
Christian-Sidak wants to merge 3 commits intomodelcontextprotocol:mainfrom
Christian-Sidak:fix/str-union-pre-parse-json-1873
Open

fix: skip JSON pre-parsing for str union annotations in pre_parse_json#2462
Christian-Sidak wants to merge 3 commits intomodelcontextprotocol:mainfrom
Christian-Sidak:fix/str-union-pre-parse-json-1873

Conversation

@Christian-Sidak
Copy link
Copy Markdown

Summary

  • pre_parse_json() uses field_info.annotation is not str to decide whether to attempt json.loads() on incoming string values. For union types like str | None, this identity check passes because str | None is not str, so string values get JSON-parsed when they should not be.
  • This can corrupt string identifiers -- for example, UUIDs beginning with digits may be partially parsed as numbers or scientific notation, causing silent data loss before the tool function ever runs.
  • Replaces the identity check with a _should_pre_parse_json() helper that inspects union annotations: unions of only simple scalar types (str, int, float, bool, NoneType) skip pre-parsing, while complex unions like list[str] | None still get pre-parsed.

Closes #1873

Test plan

  • test_str_union_pre_parse_preserves_strings -- verifies JSON object strings, JSON array strings, plain strings, and UUID strings are all preserved as-is when annotation is str | None
  • test_complex_union_still_pre_parses -- verifies list[str] | None still deserializes a JSON-encoded list from a string
  • test_str_union_uuid_end_to_end -- end-to-end validation that a UUID passed to a str | None parameter arrives intact through call_fn_with_arg_validation
  • All existing tests continue to pass

The `pre_parse_json` method used an identity check (`field_info.annotation
is not str`) to decide whether to attempt `json.loads` on string values.
For union types like `str | None`, this check passed because `str | None`
is not identical to `str`, causing string values to be unnecessarily
JSON-parsed. This could corrupt string identifiers (UUIDs, etc.) when
the parsed result was a non-scalar type like a dict or list.

Replace the identity check with a `_should_pre_parse_json` helper that
inspects union annotations. Unions containing only simple scalar types
(str, int, float, bool, NoneType) skip pre-parsing entirely, while
complex unions like `list[str] | None` still benefit from it.

Closes modelcontextprotocol#1873
Remove extra blank line before _SIMPLE_TYPES and between test functions
to satisfy pre-commit linting. Remove # pragma: no cover from test
inner functions that are exercised by the tests.
The inner functions func_optional_str and func_optional_list are only
used for metadata extraction and pre_parse_json testing -- their bodies
are never actually called, so coverage cannot track them.
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.

Bug: String parameters starting with digits coerced to numbers, causing UUID data loss

1 participant