MCP Unicode Concealment Attacks
Abstract
The Model Context Protocol (MCP) has become the dominant way coding agents discover and invoke external tools. A server advertises each tool through a tools/list handshake that returns a name, a natural-language description, and a JSON input schema; the client renders this metadata once, in a one-time approval dialog, and then injects it verbatim into the models context on every subsequent turn. Nothing in the protocol requires the rendered approval view and the bytes delivered to the model to match. We isolate that gap as a single structural mechanism, concealment encoding, and show with a model-free, protocol-free analysis that Unicodes TAG block (U+E0000U+E007F) has no assigned glyph in any mainstream terminal, chat, or IDE renderer, so a payload written in it is absent from what a human reviewer sees while surviving byte-for-byte into the models tokenizer. We then measure whether this mechanism, and the surface it generalizes across, actually defeats todays client-side defenses, building a proof-of-concept that speaks the real MCP JSONRPC/stdio protocol against a genuine client and server. Across 5 distinct MCP metadata surfaces we implement 8 concrete techniques with a deterministic, protocol-level harness. All 8/8 techniques deliver an attacker-controlled payload into the models context; 4/8 evade a representative string-matching sanitizer; and, exactly as the mechanism analysis predicts, only the TAG-block encoding (1/8) is invisible in the human approval view while still reaching the model verbatim, making it the only technique in our set that defeats both defense layers at once. We further show that MCP forces re-approval for 0/8 techniques even under a time-of-check to time-of-use rugpull, and we distill the structural fix the protocol currently lacks: approval views must be byte-faithful, not merely visually plausible. To test whether these outcomes are a property of the protocol or an artifact of one server codebase, we re-implement the full technique catalogue against 3 independently developed Python MCP server libraries and find total agreement across all 32 cross-library outcome cells, and we confirm the baseline sanitizer does not simply reject everything by checking it against 25 representative benign tool descriptions, 0 of which are flagged.