Skip to main content
NEW · Blast Radius: a field playbook for leaked API keys on AWS, GCP and Azure
Back to Blog

MCP Servers Are Taking API Keys Through the Chat Window

We sent one initialize request to every remote MCP server in the official registry. 71.3% of 22,027 responding servers opened a session with no header authentication, and 3,705 credential fields sit in tool arguments. Three of them are marked secret.

Ben Kim
Written by
Ben Kim
Published
10 min read1,659 words
Share:
MCP Servers Are Taking API Keys Through the Chat Window

The official MCP registry lists a remote server at shpbl.com. Its write_to_repo tool opens a branch and a pull request in your repository. The input schema has a string field called github_token, described as a "One-off GitHub token with Contents and Pull requests write. Used for this call only and never stored." The user must pass a write-scoped token to the tool as an argument, directly from the chat window.

This does not mean the server is malicious. It also offers GitHub App installation as a safer path. Cremit set out to measure how common this design is across the registry. On October 6 and 7, 2026, we surveyed every remote MCP server published there.

In September, our study of 33 public surfaces counted keys inside agent config files such as .mcp.json on GitHub. This post examines the other side of that connection: how the remote servers those configs point to receive credentials.

What we measured

We collected metadata for all 40,252 servers in the official registry API (registry.modelcontextprotocol.io). Of those, 25,270 published a remote endpoint URL, for 25,857 unique URLs.

We sent each endpoint one MCP initialize request, the protocol's standard handshake, and classified authentication from the status code and the WWW-Authenticate header. For servers that opened a session without header authentication, we sent tools/list, resources/list and prompts/list once each. All three are read-only requests that describe what a server can do.

We called no tools. We read no resource contents and made no attempt to bypass authentication. Everything below is static analysis of the tool names, descriptions and input schemas the servers published themselves.

Seven in ten remote servers asked for no header authentication

22,027 servers returned a valid initialize response. Unreachable endpoints and those returning 404 or 5xx are excluded from the denominator.

Authentication posture of 22,027 remote MCP servers that answered initialize. 15,696 (71.3%) opened a session with no header auth, 5,561 (25.2%) required OAuth, 770 (3.5%) required other auth

15,696 (71.3%) opened a session with no credential at all. 5,561 (25.2%) returned an OAuth 401 challenge, and 770 (3.5%) required some other form of authentication.

That 71.3% should not be read as a count of insecure servers. Many services, such as weather or public datasets, require no authentication by design. Many of these servers do authenticate, through a channel other than the HTTP header. That channel is the subject of this post.

Authentication has moved into tool arguments

13,610 of the servers that opened without header authentication returned a tool list, containing 200,286 tools in total.

We looked for input fields that take a credential by parameter name: api_key, access_token, password, client_secret and similar. We removed 106 fields that only shared the name, such as crypto token addresses and CSRF tokens. That left 3,705 credential fields across 3,582 tools on 529 servers.

Parameter nameCount
api_key1,709
apikey656
password173
session_token172
access_token165
auth_token138
credential52

Of those 3,705 fields, three were marked as secret in the schema. All three were password fields on a single server, ship.page, carrying writeOnly: true. JSON Schema has standard keywords for this, format: password and writeOnly: true, which give a client a reason to mask the input or keep it out of logs. Without them, a client has no way to tell the field apart from a search box.

1,043 of the fields were required. The tool cannot be used without the key.

Exposure paths for credentials passed as tool arguments

An API key sent as an HTTP header is handled only by the client configuration and the transport layer. A key sent as a tool argument behaves differently, because tool arguments are produced by the LLM. For the model to build the call, the key has to be in the conversation context first.

One field description on mcp.ledgerfc.com states the distinction directly: "Preferred instead: send it as an Authorization: Bearer header, so it never enters the conversation."

Three places a key can persist when it is a tool argument: the LLM conversation record, the MCP client's request and debug logs, and the request received by the remote server

This adds three points where the key can be recorded. The first is the conversation record, which can be retained by both the client and the model provider. The second is the request and debug logging of the MCP client. The third is the request the remote server received, which the server operator holds.

A "never stored" note in a tool description is a promise about the third place only. The operator has no control over the first two. If a conversation is shared as a link, the key travels with it, which is why our September study treated 30,990 shared links from ChatGPT, Claude, Gemini and Grok as a surface of their own.

This part is an inference from the design, not a measurement. Where and for how long a key is retained depends on each client and server, and since we called no tools we did not verify it.

Fifteen servers that request third-party credentials

Most of the 3,705 fields take a key the server itself issues. api.datronis.com, for example, asks for its own dtk_ token and recommends sending it as a header instead. bounty.brelsfordsoftware.com tells users to "treat it like a password." When the operator is also the issuer, a leaked key stays inside that one service.

A more serious case is a server that requests a key issued by a third party. We read all 51 fields whose descriptions name another service. After setting aside webhook signing secrets, public verification keys, the server's own tokens and IDs of saved credentials, fifteen servers remained.

Key it takesServers
GitHub tokenshpbl.com, nittim.com, neblla.com, api.fadehost.com, handoff.lol
LLM provider key (OpenAI, Anthropic, Gemini)www.ia-qa.com, api.chieflab.io, pseo.quantumcx.net
Atlassian API tokenwww.ia-qa.com
Cloudflare API token or Turnstile secretapi.proof.holdings, api.furrowforms.com
Telegram or Discord bot tokenapi.vendo-ai.com, api.proof.holdings, getsocialclaw.com, sendit.infiniteappsai.com
AWS S3 access key and secret keymcp.pandavideo.com
Etherscan API keyapi.timzinin.com
Verbatim excerpt of the write_to_repo schema from shpbl.com. The github_token field takes a token with Contents and Pull requests write and carries no format or writeOnly marker

Two cases stand out. www.ia-qa.com publishes 152 tools, and its post_jira_comment requires a Jira URL, an email address and an Atlassian API token. An Atlassian API token created without scopes inherits every permission of the user who made it. The field description asks for no particular scope, so such a token can be transmitted through the chat window to a third-party server. api.proof.holdings requires a Cloudflare API token with DNS edit permission.

Four of the five servers that take GitHub tokens also point to a better route, either a GitHub App or a fine-grained token. The operators are aware of the risk. The token field remains because pasting a token is far more convenient for users than installing an app.

Users cannot distinguish a malicious lookalike

We found nothing suggesting any of these fifteen servers is malicious. The problem is that users have no means of verifying it.

The official registry verifies publisher namespaces. io.github.<username> is verified through GitHub sign-in, and a custom-domain namespace through DNS or HTTP. That check answers whether a publisher owns a name. It says nothing about what a server does with the keys it receives.

The barrier to publishing is low. 64.6% of registry servers (25,989) sit under an io.github.* namespace, which needs nothing more than a GitHub account. 12,603 accounts have published that way, and one account alone published 2,365 servers.

Server names are not a reliable signal either. 314 servers carry the name of one of 19 well-known services such as GitHub, Slack, Notion, Stripe or Jira. Four of them come from that company's own namespace. All 18 servers with "slack" in the name were published by someone other than Slack. Community-built integrations are a normal part of the ecosystem, but a user cannot pick the official one by name.

Building a server that looks identical to a legitimate one is not difficult. Copy the tool names and descriptions, and add "used for this call only and never stored" to the token field. From the schema alone, neither the user nor the model can tell them apart. A comparable incident has already occurred. In September 2025, postmark-mcp appeared on npm with the same name and code as Postmark's official library. Version 1.0.16 added one line that BCC'd every email sent through the server to an outside address, and the package had 1,643 downloads before it was removed (The Hacker News).

Recommendations

If you run an MCP server, take credentials at the transport layer rather than in tool arguments. With the OAuth flow defined in the MCP specification, or plain header authentication, the key never enters the conversation. If an argument is unavoidable, mark it writeOnly: true or format: password as ship.page does, so clients can mask it. For third-party integrations, default to narrowly scoped, easily revocable mechanisms such as a GitHub App.

If you use MCP servers, assume a key pasted into a chat has already been copied to several places. Use fine-grained tokens with short expiry, and revoke or rotate them as soon as the work is done.

If you run a security team, treat agent conversations, and everywhere they get copied, as a credential exposure surface. Chat content lands in Slack threads, Jira tickets, Notion pages and repository issues. Cremit Platform detects more than 1,000 secret types across GitHub, GitLab, Bitbucket, Slack, Jira, Confluence, Notion, Google Drive and AWS S3, and checks with the issuing service whether each key is still live. Identifying where leaked credentials persist outside the chat window is a practical starting point.

Limitations

The population is remote servers in the official registry. Servers that run outside it were not counted, so actual exposure may be larger.

Classification is static analysis of tool names, descriptions and input schemas. How a tool actually handles a key can only be confirmed by calling it, and this study called no tools.

Most of the 3,705 credential fields take the server's own key. We confirmed third-party keys only for the fifteen servers whose descriptions name the issuing service. Fields labeled only api_key may also contain third-party keys. The count of servers named after well-known services is a string match, so it may miss some official publishers.

This study is not intended to single out individual servers. Every server named here published itself to a public registry, and we used only the standard handshake and read-only listing requests.

Share it with your networkLinkedInX

Enjoyed this post?

Share it with your network

Share:

Read next

Newsletter

Get the next one in your inbox

Monthly NHI security brief from Cremit. One email, high signal.

We never sell your email. Unsubscribe anytime.