
Zero-Trust Authorization and Discovery for Enterprise MCP
This arXiv paper analyzes six official MCP SDKs (Python, TypeScript, Go, Rust, C#, Swift) and finds structural gaps for enterprise zero-trust use: Authorization headers can't cleanly support both human SSO and service-account callers, there's no pre-authentication tool discovery, and base SDKs lack fine-grained per-tool authorization. The authors build composable FastMCP extensions — cross-header credential normalization, cached token verification across IdPs, an unauthenticated metadata endpoint for registry discovery, and a declarative per-tool permission annotation — and show that visibility-only filtering alone is insufficient (models reference hidden tools by name up to 94% of the time) while combining visibility filtering with invocation-time enforcement drives forbidden tool exposure from 21.1% to 0% across 2160 attempts on four frontier LLMs.
What it claims
The useful contribution is consistency across three surfaces, not a new authorization mechanism. The authors say so directly: FastMCP 3.0+ already offers component-level auth= that governs both listing and direct invocation, and they "do not claim novelty for those two surfaces." What they add is deriving all three of these from one server-local, per-tool DNF permission declaration:
- sanitized pre-auth metadata
- authenticated
list_toolsresults - invocation checks
They also add cross-header credential handling for servers that take both human SSO and service-account callers. They state a formal invariant: a tool appears in a caller's listing and is invocable iff the caller satisfies its declared permission, and the agent's natural-language input never enters that decision.
The headline exposure numbers carry more detail than the summary gives. Across 2160 attempts on four frontier LLMs, a server that checks only inside the tool body exposed forbidden tools in 152/720 attempts (21.1%). Per-model rates ranged from 0% to 50% on identical prompts. The authors read that spread as evidence that the safe boundary has to be architectural rather than dependent on any one model's alignment.
How it works
flowchart LR H[Human: SSO Bearer in Authorization] --> N[Normalize to one principal] S[Service account: other header] --> N N --> C[Cached verify, bounded TTL, SHA-256 keys] C --> L[list_tools filtered by DNF policy] C --> I[Invocation check, same policy] R[Registry, no credentials] --> M[Pre-auth metadata endpoint] P[Per-tool decorator] --> L P --> I P --> M
- Native extension points only. The extensions use Starlette HTTP middleware, FastMCP application middleware, FastMCP
custom_routefor the metadata route, and Python decorators. There is no monkey-patching, no private APIs, and no protocol changes. - Auth composition. Backends combine with OR, so any one accepted credential type is enough. The same mechanism also supports AND, requiring multiple credentials for high-assurance deployments.
- Opaque-token IdPs. Tokens that need an API round-trip to validate are verified once and cached within a TTL.
- Signature preservation. The permission decorator rebuilds the wrapped function's signature through dynamic code generation, so FastMCP's JSON-schema introspection keeps working.
Evidence
The cross-SDK matrix (Table 1) lists what each SDK supports:
| Capability | Python SDK + FastMCP | TypeScript, Go, Rust, C#, Swift |
|---|---|---|
| Pre-auth schema+auth discovery | No | No |
| Vendor-API token verification beyond RFC 7662 | No | No |
| Permission-based filtering | Yes | No |
| Declarative per-tool permissions | Yes | No |
| Pluggable auth backend | Partial (extraction tied to Authorization) | Partial for TS and Go; Yes for Rust, C# and Swift |
On the adversarial evaluation:
- Permission-aware visibility brought exposure to 0/720.
- Visibility-only filtering was still bypassed by scripted clients.
- Models named the hidden tool in up to 94% of settings where the injected prompt let them infer it.
Operationally, the extensions run in production on an enterprise agent platform used by "dozens" of authenticated MCP servers. No latency or throughput figures appear in the fetched text.
Limits
- Threat model assumptions. The IdP, OS, TLS chain and host are trusted. The agent can be prompt-injected but is assumed unable to forge HTTP headers. Supply-chain compromise of FastMCP is out of scope.
- Out of scope. Denial of service is left to the upstream gateway, and preventing prompt injection itself is not addressed.
- Delegation not handled. Forwarding a caller's authorization downstream (RFC 8693) is listed as future work.
- Revocation window. A token revoked at the IdP stays usable until its cache entry expires. The authors analyze this as goal G4 but cannot eliminate it.
- Timing side channel. Hashed cache keys stop token recovery, but timing can still reveal whether a given token is cached.
- Public metadata endpoint. It is deliberately unauthenticated, so tool schemas are readable by anyone who can reach it. This is an explicit tradeoff.
- Version-pinned survey. The gap analysis reflects SDK versions as of the paper's snapshot. FastMCP has already closed part of the gap, so re-check current SDKs before treating the matrix as current.
- Python only. The implementation is FastMCP-based and does not carry over directly to the other SDKs.
- Partial source. The fetched text cuts off in §3.2, so the detailed evaluation sections were not available. No repository, install command or licence appears in the text.
Organisations named here
- Model Context Protocol
Technologies named here
- Model Context Protocol
- FastMCP
- Python MCP SDK
- TypeScript MCP SDK
- Go MCP SDK
- Rust MCP SDK
- C# MCP SDK
- Swift MCP SDK
- TypeScript
- Rust
- C#
- Go
- Python
- Swift
Techniques named here
- Dual-persona authorization model
- Cross-header credential normalization
- Cached token verification across heterogeneous IdPs
- Unauthenticated metadata endpoint
- Permission-filtered tool visibility
Related in mcp

OKX Agent Trade Kit (okx-trade-mcp / okx-trade-cli)
