MCP tool handlers must defensively parse stringified args
Building MCP tool handlers that accept array or object arguments, and the subtle silent corruption that follows when one client passes those args as stringified JSON.
Different MCP clients serialize tool-call arguments differently. The MCP spec passes args through JSON-RPC so in theory you receive native types, but at least one harness passes array and object args as already-stringified JSON, and the SDK low-level path does not validate or coerce against the declared JSON Schema. Naive handler bodies corrupt data invisibly: for-of over a string iterates character-by-character so each char gets pushed as a separate tag; an object assignment of a JSON string ends up with character-indexed numeric keys 0, 1, 2 in the stored frontmatter. Unit tests pass because you supply native arrays. The bug only surfaces against the specific client that stringifies. Concrete production damage: a CRM person record had 27 single-character tags inserted by one add_tag call before manual cleanup. The fix is small. At every handler entry-point that takes an array or object, run a defensive asArray asObject helper that JSON-parses strings and passes native values through. Ship tests for BOTH native and stringified inputs so any future client that serializes either way is covered.
Whenever you ship an MCP tool that accepts array or object arguments, write paired tests that exercise both native and stringified input shapes, and add defensive JSON.parse coercion in the handler. The spec does not force the transport to deliver native types, and at least one popular client does not.