skip to content

Why are MCP's clientInfo and serverInfo fields explicitly untrusted?

level: middleimportance: should knowfreq 40%

answer

  1. Self-declared, nothing verifies it
  2. Diagnostics, not credentials
  3. SHOULD-level, unlike version and capabilities
  4. Real identity comes from the token
  5. Untrusted input: bound it and escape it

basics

~20 s

Both are self-reported strings with no verification behind them: any peer can claim any name and version. MCP marks them untrusted so implementations use them for logging and display only, never for authorization or trust decisions.

solid answer

~40 s

In revision 2026-07-28 a client SHOULD send `io.modelcontextprotocol/clientInfo` in each request's `_meta`, and a server SHOULD put `io.modelcontextprotocol/serverInfo` in its result `_meta`. Both are plain declarations — a name and version the peer types about itself. The protocol performs no attestation, signing or registration check on them, so anything can present any identity. The specification therefore calls them explicitly untrusted, and the practical rule follows: they are diagnostics. Log them, show them in operator UI, use them to correlate bug reports and to decide which compatibility workaround to reach for. Do not gate features, skip a consent prompt, grant elevated tool access, or make any authorization decision on them. Real identity comes from the authorization layer — for a remote server, the audience-bound OAuth 2.1 access token the request presents.

go deeper

for a junior

Know that both fields are what the peer says about itself, that nothing checks them, and that they belong in logs and displays rather than in any security decision.

for a middle

Explain the contrast with clientCapabilities: capabilities are a behavioural commitment the server acts on, identity is an unverifiable claim it merely records. Note that both are SHOULD-level, not required.

for a senior

Show the handling you would ship: bounded, escaped storage of an untrusted string; authorization taken from the validated access token; version-based workarounds allowed only where they change behaviour, never privilege.

for a principal

Own the consent-surface question — how much self-reported identity your host shows a user before a tool call, and how you label it so it informs the decision without being mistaken for verified provenance.

## What the fields are MCP revision 2026-07-28 carries peer identity in metadata rather than in a handshake. A client SHOULD include `io.modelcontextprotocol/clientInfo` inside `params._meta` on its requests; a server SHOULD include `io.modelcontextprotocol/serverInfo` inside the `_meta` of its results. Each is a small object naming the implementation and its version. They are SHOULD-level, not required — a request missing `clientInfo` is still well-formed, unlike one missing the protocol version or the capability object. Before 2026-07-28 these values travelled once, in the handshake. Removing the handshake had to put them somewhere, and per-request metadata is where they landed. ## Why the specification flags them as untrusted There is no mechanism behind them. Nothing signs them, nothing registers the names, nothing cross-checks a claimed version against a build. They are strings a peer writes about itself, and any process that can speak the protocol can write any string. Calling them untrusted is not a warning about malice specifically — it is a statement about what the field can possibly mean. An unverifiable claim cannot carry a security decision no matter how honest most senders are. This matters more in MCP than in many protocols because of who is on the other end. Servers are often small programs installed by an end user from a registry or a repository; clients are host applications the server did not choose. Neither side has a prior relationship with the other, and the protocol deliberately does not invent one. ## What they are good for Plenty, as long as nothing security-relevant hangs off them: - **Logs and traces.** Knowing which client build produced a malformed request is the fastest route to a fix. - **Operator-facing UI.** Showing the user which server they are about to grant a tool call to is meaningful, provided the display makes clear it is self-reported. - **Support and telemetry.** Version distributions tell you when it is safe to drop a workaround. - **Bug workarounds.** Steering around a known defect in a specific peer version is a legitimate, low-stakes use — you are choosing a code path, not granting a privilege. ## What they must never be used for - **Authorization.** Never map a claimed client name onto a permission set. On a remote server the identity that counts is the one in the presented OAuth 2.1 access token, which the server validates and which must be audience-bound to it. - **Skipping consent.** MCP's consent model puts the host at the enforcement boundary; a self-reported name is not grounds to bypass a prompt the user would otherwise see. - **Feature gating with security consequences.** Exposing a privileged tool only to "a known client" is not a control, because the check is a string comparison against attacker-controlled input. - **Rate-limit or quota identity.** Anything an unauthenticated peer can change at will is a poor key for enforcement. ## A useful contrast: capabilities versus identity Both arrive in `_meta`, and both describe the peer, but they play different roles. `clientCapabilities` is a *commitment about behaviour* — if a client declares it can be asked a question, the server may act on that and, when the declaration falls short, respond with `-32021` `MissingRequiredClientCapabilityError`. `clientInfo` is a *claim about identity*, and the protocol offers no equivalent enforcement because none is possible. A server relies on the first and merely records the second. ## How to implement it On the server: parse `clientInfo` defensively — it may be absent, and its contents are arbitrary text of arbitrary length. Treat every field as untrusted input: bound the length you store, and escape it before it reaches a log viewer, an HTML surface or a terminal, exactly as you would any other user-supplied string. Never key a permissions table on it. On the client: send it, keep it accurate, and never assume the server's `serverInfo` proves what a server is. If you display it to a user alongside a consent prompt, present it as a self-declared name rather than as verified provenance — the honest label is what keeps the field useful without letting it become a trust signal.

  • If clientInfo cannot be trusted, where does a remote MCP server get real caller identity?
    From the authorization layer. A remote server acts as an OAuth 2.1 resource server and validates the presented access token, which must be audience-bound to that server. The validated token — not a self-declared name in `_meta` — is what any authorization decision is made from.
  • Is it acceptable to branch on a peer's reported version to work around a known bug?
    Yes, with care. Choosing a compatibility code path is a functional decision, not a privilege grant, so a forged value costs the forger correctness rather than buying them access. The line to hold is that no branch keyed on self-reported identity may widen what the peer is allowed to do.
  • What handling does clientInfo need before it reaches a log or an admin screen?
    The same treatment as any untrusted input: bound the stored length, and escape it for whatever surface renders it — log viewer, HTML page or terminal. It is arbitrary attacker-controllable text arriving on every request, so unescaped interpolation into a display is a straightforward injection route.

saying these in an interview costs you the question

  • Grants elevated tool access to recognised client names
  • Treats serverInfo as proof of a server's provenance
  • Skips a consent prompt for a trusted-looking client
  • Uses clientInfo as the identity for authorization
  • Logs or renders the reported name without escaping it

context