Part 5 of 7 · 4 min read
Securing an MCP server that exposes internal data: a complete answer
Covers: Securing an MCP server that exposes internal data: a complete answer; A checklist.
Securing an MCP server that exposes internal data: a complete answer
This is the interview question, and the whole module is the answer. Put it in this order.
- Decide whether it needs network exposure at all. A local stdio server for one user needs no OAuth; treat the launch command as code you run.
- Authenticate every request (HTTPS only;
Authorization: Bearer; never a token in the URL). Verify signature, issuer, expiry and audience; the audience is this server. Return401with a challenge,403for scope. - Publish Protected Resource Metadata and use an authorization server you did not write. Require PKCE (S256) and exact redirect matching; use Client ID Metadata Documents or pre-registration, not open registration with a shared consent cookie.
- Scope by capability, minimal first. Challenge for what the call needs. Check scope before the tool runs, then check the record: the caller comes from the token, never from an argument.
- Never pass a client's token to an upstream API. Use a separate token issued to the server.
- Make handles random and bound to the user; log who did what, with no tokens in logs.
- Guard what you fetch: discovery URLs, client metadata, anything a stranger names. Resolve once, check, pin; block internal ranges and non-HTTPS.
- Treat what you return as untrusted by the model. Keep descriptions short and static, version them so a host can pin, return data not instructions, and mark untrusted content in results.
- Least privilege inside the server: a read-only database user, no shell, parameterised queries, rate limits.
- Test the failures, not the happy path: each row of the tables in this module is a test.
Warning: check the current specification
The pages I read describe an optional but detailed authorization specification and a security best-practices page. The protocol changes often, so check the current text before relying on a detail here.
A checklist
- Say which transport and whether authorization applies. HTTP: follow it. stdio: environment credentials, and review the launch command.
- Challenge correctly:
401withresource_metadataand the minimumscope. - Validate every token five ways (signature, issuer, audience, expiry, scope). Reject tokens with no audience.
- Refuse passthrough. One token per audience.
- Use PKCE with S256, the
resourceparameter,state, and theisscheck. Match redirect addresses exactly. - Take the user from the token. Bind handles to the user. Do not log tokens.
- Guard every fetched URL and every URL you open. No shell.
- Require consent per client if you proxy a third party.
- Show tools in full, pin them, and re-check on change.
- Gate the dangerous combination: after private or untrusted content, ask before anything that can send data out.
- Test each attack in this module.
Interview angle
"How would you secure an MCP server that exposes internal data?" Give the ten-point answer in section 11, in this shape: who is calling (OAuth, audience-bound, short-lived tokens, PKCE), what they may do (minimal scopes, step-up, per-record checks with the user from the token), what the server fetches (SSRF guard), what the model reads (poisoning, rug pull, shadowing, injection, and why approvals tied to data flow beat approvals tied to attention), and how you would know (audit logs without tokens, tests for each attack). Then name a trade-off: approval prompts cost attention, and a person who clicks yes stops nothing.
Likely follow-ups: "What is token passthrough and why is it banned?" (audience; accountability; trust boundaries). "What does PKCE protect against?" (a stolen code; not mix-up, which needs the iss check). "Why is OAuth not enough for an agent?" (it authenticates the caller; it does not make the text the model reads trustworthy). A weak answer says "add an API key" and stops.
Check yourself
Your MCP server accepts any token signed by your identity provider. A token issued for a different service arrives. Should it be accepted? (No. The server must check the audience: the token has to be issued for this server. Accepting it is the confused deputy problem.)
For the curious Status codes and who checks what
A missing or invalid token gets 401; a valid token with too few scopes gets 403. Scopes start minimal and step up when needed, because a broad list in the first challenge trains people to click through. Discovery fetches URLs the other side supplies, so it needs the same server-side request protections as any fetcher.
What you will use daily at work
| Practice | What it looks like |
|---|---|
| Validate every token | Signature, issuer, audience, expiry, scope. |
| Never pass a token through | Get a separate token for any downstream service. |
| Treat tool text as untrusted | A tool description that changes after approval is a rug pull; pin and review it. |
Draft: confirm which identity provider and OAuth features your MCP servers will use.