A Keyless MCP Server Is Its Own Trust Tier

A keyless MCP server has no grant to revoke, no published rate limit and feeds open-world text into context. The allowlist tier, five controls, a probe script.

Keyless MCP server config entry beside OAuth and IAM entries, with the keyless line tagged as its own trust tier
Three config lines, three tiers. The one with no credential is the one nothing upstream is watching.

On Sep 19 we sent a bare initialize to explorer.solana.com/mcp with no Authorization header and got a server back: explorer-mcp version 0.1.0, two tools, no session id, no rate-limit header, no terms page we could read. A keyless MCP server like that takes one line in a harness config, and the line looks identical to the entry for a GitLab or AWS server that took a group Owner, an OAuth grant or an IAM role to wire up. The config file cannot tell the difference. Your allowlist has to.

The move this piece teaches: give keyless public servers their own tier in the allowlist, separate from OAuth-brokered servers and from local stdio processes, with five controls you supply yourself because nobody upstream is positioned to. No key means nothing to revoke, nothing to meter and nothing that ties a call to a person. It also means the data coming back is public-internet text, written by whoever felt like publishing it, dropped straight into an agent’s context.

The keyless tier is not the most dangerous tier. A local stdio server can run a shell; an OAuth grant can open a merge request as you. What makes keyless distinct is that every control you were quietly leaning on lived inside the credential, and there is no credential.

One endpoint, two tools, zero credentials: the Solana Explorer MCP server

The news once, then the runbook. Solana Compass reported on Sep 17, 2026 that the Solana block explorer had shipped an MCP server, live at explorer.solana.com/mcp, which “requires no API key” and covers mainnet-beta, devnet and testnet (Solana Compass). No first-party announcement surfaced in our search, and the explorer’s own setup page sits behind a bot challenge, so the launch date rests on that secondary report.

What we can vouch for is the endpoint, because we probed it on Sep 19 without credentials. A plain POST without an SSE Accept header returns the Streamable HTTP error Not Acceptable: Client must accept text/event-stream with HTTP 406, which is what a spec-conforming endpoint does. initialize returned serverInfo name explorer-mcp, version 0.1.0, protocol 2025-06-18 and tools.listChanged: true, and issued no Mcp-Session-Id.

tools/list returned exactly two tools: inspect_entity, titled Inspect Solana Entity, and ping, a scaffold health check. A keyless tools/call of inspect_entity on the SPL Token Program address returned decoded data, including a verification status of verified with a signer label of Foundation. There was no rate-limit header of any kind, and we found no published rate limit. Record that as no published rate limit as of Sep 19, never as unlimited.

inspect_entity declares four annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true. Its input is an identifier string of 1 to 128 characters plus a cluster enum defaulting to mainnet-beta. The description the server injects into every connected agent’s context tells the model when to reach for it: “Use this tool when a user asks about a Solana address, program, token, NFT, wallet, or other blockchain entity.”

That is the whole surface: one read tool anyone on the internet can call, returning data anyone on the internet can influence. Expect more servers shaped like it; a read-only public data server with no signup is the cheapest MCP server a vendor can ship.

Why a missing key removes three controls you were leaning on

Anthropic’s September 2026 misuse report puts the credential frame in one sentence: “Organizations should treat AI keys and agent integrations with the same level of seriousness as they do production credentials—because attackers treat them with the same level of seriousness, too.” (Anthropic). A keyless integration has no key to treat that way, which sounds like relief and is the opposite. Three controls that come free with a credential are simply absent.

Revocation. An OAuth-brokered server such as GitLab’s registers the client, issues a token and acts under that person’s identity (GitLab docs); pull the grant and the server goes dark for that harness. A keyless endpoint has no grant to pull. The only revoke is deleting the config line on every host that carries it, which makes it an inventory problem before it is a security one.

Metering and attribution. With a token, the vendor meters calls and you can ask who spent the budget. Without one, the vendor sees an IP and you see nothing unless your own harness or egress proxy counts.

Identity of the far end. Air Security’s MCPJacking research, published Aug 27, 2026, found 155 registry entries whose backing domain had expired; whoever registered the domain became the far end for every agent that connected through the entry (Air Security). The registry side of that story is marketplace hygiene; for a keyless server it is a warning about identity. Air’s rule applies with no discount: “MCPs require continuous vetting. Service availability or ownership can change without notice. An MCP authorized previously might now be under unauthorized control.”

A brokered server tends to fail loudly when the far end changes hands, because the token stops validating. A keyless server changes hands silently, and your agent keeps calling.

Then the fact that is not a control: what comes back is untrusted text. GitLab says this about its own authenticated server, on objects inside your own tenant: “You’re responsible for guarding against prompt injection when you use these tools. Exercise extreme caution or use MCP tools only on GitLab objects you trust.” (GitLab docs).

A public chain explorer returns objects nobody on your team has ever trusted, by design. The Solana tool’s own description concedes it: “Anyone may publish an entry for any program under their own authority, so only the canonical entry and those under an authority the resolver consults are ever resolved.” Labels are open-publish, the resolver filters some, and the text still lands in context.

The three-tier allowlist: keyless public, OAuth-brokered, local stdio

Shadow MCP covered finding servers nobody registered; the weekly inventory covers scoring them. Neither gives the allowlist a column for how the server authenticates, and that column decides which controls you have to build. Three tiers cover every entry I have seen in a harness config.

Three concentric rings around an agent: local stdio touches the host, OAuth or IAM brokered servers touch your tenant under a named grant, keyless public servers touch open-world data with no identity either way What each tier may touch. The outer ring is the only one where neither side knows who the other is.

Keyless public OAuth- or IAM-brokered Local stdio
Example explorer.solana.com/mcp GitLab MCP server at /api/v4/mcp; AWS MCP Server under IAM any npx-spawned server in .mcp.json
Identity of the caller none; a source IP a named person or role the OS user running the harness
Identity of the far end DNS and a TLS certificate the broker’s client registration, plus DNS a package version, if pinned
Revoke delete the line on every host revoke the grant or the role delete the line, kill the process
Rate cap yours, or none the vendor’s, plus yours host CPU
Data trust open world your tenant, still untrusted your own files
Who may add it platform team only the person whose grant it is, under review repo owners, pinned
Evidence your transcript only vendor audit log plus transcript transcript only

Tier is per tool set, not per vendor. AWS’s serverless capability for Lambda is “scoped to the caller’s own account and is read-only” (AWS docs), squarely the brokered tier, and the same AWS MCP Server also answers without a credential: “Agents can search AWS documentation and retrieve service information without authentication.” (AWS docs). One server, two tiers. The read/write split on that server is the companion piece; the point here is that the allowlist row is the tool set, and a keyless tool set gets the keyless row even when it lives next to an IAM-gated one.

AWS Agent Toolkit capabilities documentation showing the serverless capability description with the line that it is scoped to the caller’s own account and is read-only Screenshot: AWS Documentation, “Capabilities - Agent Toolkit for AWS” (undated), captured Sep 19, 2026.

What a governance layer can trust, and what it is only told

GitLab 19.4, released Sep 17, 2026, classifies MCP tools by their annotations: readOnlyHint: true maps to Read, readOnlyHint: false to Write, destructiveHint: true to Delete, and a tool with neither annotation is treated as Delete (GitLab docs). Reads of GitLab resources default to Always Allow, where “The tool executes silently without prompting the user.” The blog states the intent: “Read-only tools default to Always allow, so routine lookups run without interrupting the team.” (GitLab blog). That policy governs GitLab’s own tool surface, it fails closed on missing annotations, and the Always-ask dialect is worth copying everywhere.

Now put the two facts side by side, and read what follows as our inference rather than GitLab’s or Solana’s claim. A governance layer that sorts tools by annotation is trusting whatever the server declared about itself. inspect_entity says readOnlyHint: true. Nothing verified that.

The field is named a hint, and the protocol’s own tools page says clients “MUST consider tool annotations to be untrusted unless they come from trusted servers” (MCP specification, Tools). The server is version 0.1.0 with listChanged: true, and the operator has no contract with whoever runs it. For a brokered server, a false annotation is a vendor you can phone. For a keyless one, it is a claim from a stranger, and a keyless tool’s self-declared annotations are exactly what an annotation-driven governance layer would wave through as a silent read.

GitLab Docs agent tool governance page listing the Read, Write and Delete tool categories and the Always Allow, Always Ask and Always Deny modes Screenshot: GitLab Docs, “Agent tool governance” (version history through GitLab 19.4), captured Sep 19, 2026.

The runbook: admit a keyless MCP server in five moves

Each move ends in something written down. Budget an hour for a new server and ten minutes for the weekly re-check.

Move 1: probe it yourself and freeze the snapshot

Never admit a server from its README or a news post. Send initialize and tools/list from a host that holds nothing else, save the raw responses and headers, and hash the tool list.

# probe-keyless.sh -- illustrative: freeze what a keyless MCP endpoint says about itself
URL="https://explorer.solana.com/mcp"
post() { curl -sS -D "$2.headers" -o "$2.body" -X POST "$URL" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' --data "$1"; }
post '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' init
post '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' tools
grep -i -E 'mcp-session-id|ratelimit|retry-after' init.headers tools.headers || echo "no session or rate headers"
sed -n 's/^data: //p; /^{/p' tools.body | jq -c '.result.tools | map({name, annotations}) | sort_by(.name)' | tee tools.json | sha256sum

Record eight things: server name and version, protocol version, tool names, annotations, whether a session id was issued, any rate headers, the TLS issuer and the resolved IP at probe time. The last two are your only identity of the far end, and MCPJacking is the reason you keep them.

Move 2: write the row, with a tier and a gatekeeper

Extend the inventory ledger with a tier field and the probe record. The rule that gives the row teeth: a keyless entry is added by the platform team only, never through a project-scope config in a pull request, because project files travel with the repo into every headless run, and a keyless server in one becomes a server every CI runner calls (headless trust tier).

# mcp-ledger.yaml -- illustrative row for a keyless public server
- server: explorer-mcp
  url: https://explorer.solana.com/mcp
  tier: keyless-public
  tools_hash: sha256-<from tools.json>
  far_end: {resolved_ip: <at probe>, tls_issuer: <at probe>}
  approved_by: platform-team
  hosts: [research-box-01]
  rate_cap: {per_session: 30, per_host_per_day: 300}
  data_trust: open-world
  reviewed_on: 2026-09-19

Move 3: cap the rate yourself, because nobody published one

The numbers are illustrative; the shape is not. Thirty calls per session and 300 per host per day, enforced as a hard stop rather than a warning, in the harness and again at the egress proxy. For a coordinator that fans out, the cap is per coordinator: eight subagents each under the cap is eight times the cap.

The failure you are preventing is not the server’s bill, since there is none. It is the retry loop. The explorer’s setup page already answered our fetch with HTTP 429 and a challenge page; an agent that gets a challenge page instead of JSON will try again, and the retry is the abuse. A keyless server is a shared public good, and the noisy tenant gets the office IP blocked for everyone.

# keyless-cap.sh -- illustrative pre-tool-use hook: refuse the 31st keyless call in a session
in=$(cat); tool=$(jq -r .tool_name <<<"$in"); sid=$(jq -r .session_id <<<"$in")
case "$tool" in mcp__explorer__*) ;; *) exit 0 ;; esac
f="${TMPDIR:-/tmp}/keyless-$sid"; n=$(( $(cat "$f" 2>/dev/null || echo 0) + 1 )); echo "$n" > "$f"
[ "$n" -le 30 ] && exit 0
echo "keyless cap reached ($n) for $tool; a human decides whether to continue" >&2; exit 2

The shape follows Claude Code’s PreToolUse hook, which receives tool_name and session_id and blocks the call on exit code 2 (Claude Code hooks); other harnesses name the fields differently.

Move 4: screen what comes back as open-internet text

Three rules, in order of how often they will save you.

  1. Output of a keyless tool never drives a write tool in the same turn. Few harnesses can express that rule directly, so the practical version is structural: a session that holds a keyless server holds no write-capable brokered server. Separate sessions, separate config files, and the findings cross over as a document a human reads.
  2. Fence the payload as data. Whatever sits between the server and the model, a gateway or a thin wrapper, labels the response as third-party text and strips lines that read as instructions. Keep the decoded fields; mark the labels as published by strangers, which the tool description itself admits.
  3. Watch the input parameter in reverse. identifier accepts up to 128 characters per call to a third-party server. That is an exfiltration channel, our inference and nobody’s documented feature. Signal: identifiers that are not valid base58 or do not match the 32-byte and 64-byte length classes the tool documents.

Move 5: keep evidence, since the server keeps none you can read

The server issued no session id, and nothing we could read says how it logs or retains queries. So the transcript is the only record. Log per call: host, session, tool, identifier, cluster, a hash of the response and the tools hash in force at that moment. The last field is what tells you, three weeks later, which tool description the model was reading when it made the call.

Illustrative heat table of who supplies each control by MCP trust tier: allowlist, rate cap, injection screening, egress and evidence across keyless public, brokered and local stdio servers Illustrative: who supplies each control. The keyless row is dark end to end because nothing upstream supplies any of it.

Six keyless MCP server failures and the signal for each

The tool list changes under a stable URL. The server advertises listChanged: true and is at version 0.1.0. Signal: this week’s tools hash differs from last week’s, a tool appears, or an annotation flips. Action: hold the row and re-probe; the approve-once problem is worse here because the approval was of a stranger’s claim.

The far end changes hands. Signal: the TLS issuer or the resolved IP differs from the probe record, or serverInfo comes back with a different name. Action: pull the row from every host the same day.

The challenge page. Signal: HTTP 429 or HTML where JSON should be. Action: the cap holds, and no automatic retry.

A keyless read feeds a write. Signal: a transcript shows inspect_entity and a write tool in the same turn. Action: split the sessions, then find out how the two servers ended up in one config.

The annotation lie. Signal: none, which is the point. Action: the session split and the identifier check stay in force regardless of what readOnlyHint says, so a lie costs you nothing.

The entry spreads. Signal: the inventory sweep finds the URL in a project .mcp.json, or the host count climbs without a ledger change. Action: the platform-team-only rule, applied by pruning.

The tier column belongs in fleet policy

A tier only works if the same word means the same controls on every host and in every harness. That makes it fleet policy: a restricted mode for the whole fleet is where a keyless-public row gets its cap, its session isolation and its evidence fields, and a command center is where the row’s call count sits next to the sessions that produced it. The allowlist is the list; the tier is the column that says which controls are yours to build.

The vendor did nothing wrong by shipping a keyless server, and I would rather have a read-only public endpoint than another key to rotate. A missing credential just moves five jobs onto your desk, and the desk should know it took them.

FAQ: keyless MCP servers

Is a keyless MCP server safe to use with a coding agent?

Safe enough for a read-only research session with no write tools in reach, and unsafe as a peer of your GitLab or AWS entries. Probe it yourself, record the tool list and annotations, cap the calls in the harness, treat every response as open-internet text, and keep the transcript, since the server keeps nothing.

How do I rate limit an MCP server that has no API key?

The vendor cannot do it for you, so count it yourself. A pre-tool-use hook that tallies calls per session and blocks past a cap, plus a per-host limit at the egress proxy, covers the single session and the fan-out case. Make it a hard stop; a warning is a retry loop with a log line.

Should read-only MCP tools be auto-approved?

For brokered servers with a vendor you can hold to the annotation, yes, and GitLab 19.4 defaults reads to Always Allow for that reason. For a keyless public server, readOnlyHint is a self-declaration from a party you have no contract with, so auto-approve only inside a session that has no write tools to reach.

Sources