Skip to content

Harden deserializeParams against nested stringified params#325

Merged
ksinder merged 3 commits into
mainfrom
ksinder-mcp-deserialize-params-hardening
Jun 22, 2026
Merged

Harden deserializeParams against nested stringified params#325
ksinder merged 3 commits into
mainfrom
ksinder-mcp-deserialize-params-hardening

Conversation

@ksinder

@ksinder ksinder commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

MCP clients sometimes double-serialize nested object/array parameters, sending them as JSON strings. deserializeParams already 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 bounded unwrapJsonString helper 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 parent on create-a-comment makes 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?

  • Automated test (unit, integration, etc.)
  • Manual test (provide reproducible testing steps below)

Added tests for create-a-comment parent as a JSON string, a stringified object nested inside an array element object, a double-stringified parent, and scalar/quoted-scalar params being preserved. Full suite (109 tests) and tsc build pass.

Screenshots

N/A

ksinder and others added 3 commits June 22, 2026 10:24
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>
@ksinder ksinder marked this pull request as ready for review June 22, 2026 17:59
@ksinder ksinder requested review from hallie and vshen-notion June 22, 2026 18:00
@ksinder ksinder merged commit d7e3bbd into main Jun 22, 2026
9 checks passed
@ksinder ksinder deleted the ksinder-mcp-deserialize-params-hardening branch June 22, 2026 18:15
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.

2 participants