Harden deserializeParams against nested stringified params#325
Merged
Conversation
deserializeParams handled top-level stringified params and shallowly
parsed array items, but used asymmetric recursion: it descended into
object properties yet only string-parsed array items one level deep. A
stringified object that was a property of an array element (e.g.
`{ children: [{ paragraph: '{"rich_text":[...]}' }] }`) was forwarded to
the Notion API as a raw string.
Replace it with a single uniform recursive walk (deserializeValue) that
visits every object property and array element, parses JSON-looking
strings, and recurses into the parsed result. Non-JSON strings and scalar
values (including numbers/booleans encoded as strings) are left intact.
Add tests for create-a-comment `parent` sent as a JSON string and for a
stringified object nested inside an array element object.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend deserializeValue to resolve values that were JSON-encoded more than once (e.g. JSON.stringify(JSON.stringify(parent))) via a bounded unwrapJsonString helper. A string is only transformed when it ultimately decodes to an object or array; strings that decode to scalars (numbers/booleans/null) or to other plain strings are returned unchanged, so genuine string values are never coerced or unwrapped. Add tests for a double-stringified parent and for scalar/quoted-scalar string params being preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Publishes the deserializeParams hardening on merge via the Publish workflow (OIDC trusted publishing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hallie
approved these changes
Jun 22, 2026
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.
Description
MCP clients sometimes double-serialize nested object/array parameters, sending them as JSON strings.
deserializeParamsalready handled top-level stringified params and shallowly parsed array items, but it used asymmetric recursion: it descended into object properties yet only string-parsed array items one level deep. A stringified object that was a property of an array element (e.g.{ children: [{ paragraph: '{"rich_text":[...]}' }] }) was forwarded to the Notion API as a raw string.This replaces it with a single uniform recursive walk (
deserializeValue) that visits every object property and array element, decodes JSON-looking strings, and recurses into the result. A boundedunwrapJsonStringhelper also resolves values that were JSON-encoded more than once (e.g.JSON.stringify(JSON.stringify(parent))). A string is only transformed when it ultimately decodes to an object or array; strings that decode to scalars (numbers/booleans/null) or to other plain strings are returned unchanged, so genuine string values are never coerced or unwrapped. This is behavior-compatible for everything the previous code handled.Motivating case: a stringified
parentoncreate-a-commentmakes the Notion API throw on"block_id" in <string>and return a 500; normalizing it client-side avoids that. (A complementary server-side guard is being added in notion-next.)Also bumps the package version to v2.4.1 so the change publishes on merge via the Publish workflow (OIDC trusted publishing).
How was this change tested?
Added tests for
create-a-commentparentas a JSON string, a stringified object nested inside an array element object, a double-stringifiedparent, and scalar/quoted-scalar params being preserved. Full suite (109 tests) andtscbuild pass.Screenshots
N/A