Skip to content

Detect concealed characters in server metadata - #1122

Closed
Adeniyikayodee wants to merge 1 commit into
modelcontextprotocol:mainfrom
Adeniyikayodee:feature/metadata-policy
Closed

Detect concealed characters in server metadata#1122
Adeniyikayodee wants to merge 1 commit into
modelcontextprotocol:mainfrom
Adeniyikayodee:feature/metadata-policy

Conversation

@Adeniyikayodee

Copy link
Copy Markdown

Closes #1121

Summary

Adds a check on the client listing paths for characters in server metadata
that stay invisible to a reviewer while still reaching the model.

Motivation

A server advertises a tool through tools/list with a name, a description
and a schema. A host typically renders this once for approval, then supplies
the same bytes to the model on every later turn. MCP allows the rendered view
and the delivered bytes to differ, so a code point that lacks a glyph is
absent from what a person approves and arrives at the model intact.

Approach

Two classes of character are reported:

  • The Unicode TAG block, U+E0000 to U+E007F. Mainstream terminals, chat
    clients and IDEs leave it unrendered, and it lacks a legitimate use in tool
    metadata.
  • Bidirectional overrides and isolates, U+202A to U+202E and U+2066 to
    U+2069, which reorder how the surrounding text is displayed.

Zero width joiners and the remaining format characters are left alone, since
they carry meaning in Indic, Arabic and Persian text and in emoji sequences.
A test covers שלום, مرحبا, नमस्ते and a joined family emoji.

Metadata is reported and left as it arrived. A stripped description is still
text the server chose, and rewriting a name would break the calls that use
it.

The shape follows ToolNameValidator, which already runs on the same listing
path: a public final class in util, static methods, one system property.

Configuration

Warn by default, so current behaviour is preserved. Set
io.modelcontextprotocol.strictMetadataValidation=true to fail the listing
instead.

Tests

11 unit tests and 3 client level tests. A payload is encoded into the TAG
block and confirmed in a description, inside a nested JSON schema, and in map
keys. The client tests cover the default path and the strict path.

Verification

./mvnw clean test passes across all 11 modules, 1496 tests, conformance
included.

Open question

Whether the character set stays fixed as written or becomes configurable.
Fixed seems safer to me... a configurable blocklist invites someone to add
the whole Cf category, which would fire on Persian and Indic descriptions.

A server advertises tools through tools/list with a name, a description
and a schema. A host renders that once for approval and then feeds the
same bytes to the model on every later turn. Nothing in MCP requires the
rendered view and the delivered bytes to agree, so a code point with no
glyph is absent from what a person approves while reaching the model
intact.

Report two classes of character on the client listing paths: the Unicode
TAG block, which no mainstream renderer draws, and bidirectional
overrides and isolates, which reorder displayed text. Zero-width joiners
and other format characters are left alone, since they carry meaning in
Indic, Arabic and Persian text and in emoji sequences.

Nothing is rewritten. A stripped description is still text the server
chose, and editing a name would break calls that use it. Warn only by
default; set io.modelcontextprotocol.strictMetadataValidation to fail the
listing instead.

Signed-off-by: mkbadeniyi <adeniyikayode22@gmail.com>
@Kehrlann Kehrlann self-assigned this Sep 3, 2026
@Kehrlann Kehrlann added the wontfix This will not be worked on label Sep 3, 2026
@Kehrlann Kehrlann closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect concealed characters in server metadata

2 participants