Secure · Advanced

How to secure MCP servers

  • Time: About half a day to inventory and apply client-side controls; one to three days to harden a server you build
  • Cost: No licence cost for the controls described. You pay for your own identity provider, logging and engineering time.
  • Level: Advanced
On this page
  1. Short answer
  2. Before you start
  3. Which transport, and which controls apply
  4. 1. Inventory every server and read what it can do
  5. 2. Require OAuth with audience-checked tokens on remote servers
  6. 3. Refuse token passthrough and add per-client consent to proxies
  7. 4. Grant the smallest scopes and raise them on demand
  8. 5. Put local servers behind consent and a sandbox
  9. 6. Keep an allow-list of approved servers
  10. 7. Gate sensitive tools with a person and validate inputs
  11. 8. Restrict outbound requests from clients and servers
  12. 9. Log every call and review on a schedule
  13. About tool description poisoning
  14. Troubleshooting
  15. Verify it worked
  16. Next steps
  17. FAQ
  18. How Swfte can help
  19. Sources and last verified

Short answer

Treat every MCP server as code you are choosing to run and every tool result as untrusted input. Require OAuth with audience-checked tokens on remote servers, refuse token passthrough, grant the smallest scopes and raise them only on demand, sandbox local servers behind explicit consent, keep an allow-list of approved servers, put a person in front of sensitive tools, and log each call.

The steps at a glance

  1. Inventory every server and read what it can do
  2. Require OAuth with audience-checked tokens on remote servers
  3. Refuse token passthrough and add per-client consent to proxies
  4. Grant the smallest scopes and raise them on demand
  5. Put local servers behind consent and a sandbox
  6. Keep an allow-list of approved servers
  7. Gate sensitive tools with a person and validate inputs
  8. Restrict outbound requests from clients and servers
  9. Log every call and review on a schedule

Before you start

Who this is for

  • Platform and security engineers rolling out MCP servers to developers or to production agents.
  • Teams that build their own MCP server and need to know what the specification requires of it.
  • Anyone approving which MCP servers a company may connect to.

Probably not for you if

Prerequisites

  • A list of the MCP servers in use today (local and remote), and who owns each.
  • For servers you build: an OAuth 2.1 capable authorisation server, or a plan to use your existing identity provider.
  • Access to your MCP client configuration. The examples use Claude Code because its documentation was readable in full; the controls apply to any client.
  • Agreement on which tools are read-only and which change something outside the model (send, write, delete, pay, deploy).
Time
About half a day to inventory and apply client-side controls; one to three days to harden a server you build
Cost
No licence cost for the controls described. You pay for your own identity provider, logging and engineering time.
Skill
Comfortable with OAuth concepts, HTTP headers and JSON configuration

Estimates are ours, not measurements, and move with your hardware, data and network.

Which transport, and which controls apply

The controls differ by transport. Pick the row that matches each server before you start, so you do not apply OAuth thinking to a stdio server or sandbox thinking to a remote one.

Local (stdio)Remote (HTTP)
Who can reach itOnly the client process, if you use stdioAnything that can reach the URL and present a token
Main riskCode runs with your privilegesStolen or misdirected tokens, SSRF, confused deputy
CredentialsTaken from the environment (the specification says not to run the OAuth flow)OAuth 2.1 with audience-checked tokens
Key controlsConsent for the command, sandbox, pinned packageAudience validation, minimal scopes, no passthrough, egress limits
  1. Step 1Inventory every server and read what it can do

    You end up with: A table of servers with owner, transport, data reached, and read versus write tools.

    List every MCP server your people and agents connect to. Include servers that arrive through a project file checked into a repository, because anyone who clones that repository inherits the entry. For each one record the owner, whether it runs locally or remotely, the systems it can reach and the tools it exposes.

    Split the tools into two groups: ones that only read, and ones that change something outside the model. The second group is where a mistake costs money or data, so it gets the strictest controls in the later steps. The specification itself says clients must treat tool annotations as untrusted unless they come from trusted servers, so do not accept a server's own claim that a tool is read-only. Check by reading the tool code or by testing against a copy.

    Add a column for "can fetch external content". A server that reads web pages, tickets or email brings text you do not control into the model, which is the route for indirect prompt injection. Claude Code's documentation says the same thing in its warning: servers that fetch external content can expose you to prompt injection risk.

    Inventory columns worth recording
    ColumnWhy it matters
    Owner and contactSomeone must answer when a server misbehaves or its maintainer disappears.
    Transport (stdio or HTTP)Decides which controls apply: local sandboxing versus OAuth and network policy.
    Systems and data reachedSets the blast radius if a token or the server is compromised.
    Write-capable toolsThese get approval gates and narrower scopes.
    Fetches external contentMarks the servers that can carry injected instructions into the model.

    Checked against: MCP specification: Tools (latest), Claude Code documentation: Connect to tools via MCP

  2. Step 2Require OAuth with audience-checked tokens on remote servers

    You end up with: Remote servers reject any token that was not issued for them.

    For HTTP transports the specification (revision 2026-07-28, the latest when we read it) builds on OAuth 2.1. The MCP server acts as an OAuth resource server and must implement OAuth 2.0 Protected Resource Metadata (RFC 9728). Clients use that metadata to find the authorisation server. Stdio servers are the exception: the specification says they should not follow this flow and should take credentials from the environment instead.

    Two rules do most of the work. First, the client must send a resource parameter (RFC 8707) naming the MCP server in both the authorisation request and the token request. Second, the server must check that the token was issued specifically for it, and the specification states that servers "MUST NOT accept or transit any other tokens". Tokens go in the Authorization header on every request and never in the query string. Expired or invalid tokens get an HTTP 401.

    If you build the server, validate the audience claim on every request before any tool code runs. If you only consume servers, ask the vendor how they validate audience, and prefer servers that publish the metadata document. Whichever side you are on, test the failure cases in the next step: the controls only count if a wrong token is actually refused.

    What a correct 401 challenge looks like (from the specification) · text
    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                             scope="files:read"
    What the client appends to authorisation and token requests · text
    &resource=https%3A%2F%2Fmcp.example.com

    Checked against: MCP specification: Authorization (latest)

  3. Step 3Refuse token passthrough and add per-client consent to proxies

    You end up with: No token crosses from one service to another unchecked, and proxies ask the user before forwarding.

    Token passthrough is the shortcut where an MCP server accepts a token from the client and forwards it to a downstream API unchanged. The security best-practices page forbids it. It breaks audit trails (the downstream API cannot tell which client acted), skips controls that rely on the token audience, and lets a stolen token use your server as a proxy. The fix is plain: the server accepts only tokens issued for itself and, if it needs downstream access, obtains its own token for that API.

    If you run an MCP proxy server that sits in front of a third-party API using one static OAuth client ID, you also face the confused deputy problem. The page describes the conditions: a static client ID, dynamic client registration by MCP clients, a consent cookie on the third-party authorisation server, and no per-client consent in the proxy. The attacker registers a client with a malicious redirect URI, sends the victim a crafted link, and the existing consent cookie lets the code flow to the attacker without a consent screen.

    The required protections are specific. Keep a registry of approved client IDs per user and check it before forwarding to the third party. Show a consent page that names the client, lists the scopes and shows the registered redirect URI, with CSRF protection and anti-framing headers. Match redirect URIs by exact string, not by pattern. Generate a random single-use state value, and set the state cookie only after the user approves. Use the __Host- prefix and the Secure, HttpOnly and SameSite=Lax attributes on any consent cookie.

    Negative tests to run against a server you build (each should fail)
    TestExpected result
    Call a tool with a valid token issued for a different resource401, tool code never runs
    Call with an expired token401
    Put the token in the query stringRejected: tokens must not be in the URI
    Register a client whose redirect URI differs by one character from the approved oneRequest rejected (exact match)
    Replay an authorisation callback with an old or missing stateCallback rejected

    Checked against: MCP specification: Security Best Practices (latest), MCP specification: Authorization (latest)

  4. Step 4Grant the smallest scopes and raise them on demand

    You end up with: Tokens carry only what the current task needs, and every elevation is logged.

    Broad scopes turn a leaked token into a skeleton key. The specification's guidance is progressive least privilege: start with a minimal set (it gives mcp:tools-basic as an example) covering low-risk discovery and read operations, and ask for more only when a privileged operation is first attempted.

    On the server, use the WWW-Authenticate header to say what is needed for the operation in hand. A 403 with error="insufficient_scope" and the missing scope tells a compliant client to run a step-up authorisation. Include everything the operation needs in one challenge, so the user is not sent round the loop several times. Do not publish every scope you have in scopes_supported, do not use wildcard scopes such as * or all, and do not trust the scopes claimed in a token as a substitute for your own authorisation check on the resource.

    Log each elevation with the scope requested, the subset granted and a correlation ID. Over a month those logs show which clients ask for more than they use, and which scopes users decline.

    Insufficient scope response (from the specification) · text
    HTTP/1.1 403 Forbidden
    WWW-Authenticate: Bearer error="insufficient_scope",
                             scope="files:write",
                             resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                             error_description="File write permission required for this operation"

    Checked against: MCP specification: Authorization (latest), MCP specification: Security Best Practices (latest)

  5. Step 5Put local servers behind consent and a sandbox

    You end up with: A local server cannot start without a person seeing the exact command, and it runs with least privilege.

    A local MCP server is a program that runs with your privileges. The specification lists how it goes wrong: a malicious startup command in a shared configuration, a malicious payload inside the server, or an insecure server left listening on localhost and reached through DNS rebinding. Its examples include a startup command that posts a private SSH key to a remote host.

    The client-side rules are clear. If a client offers one-click setup it must show the exact command, untruncated, mark it as code execution, and require approval. Clients should flag patterns such as sudo, rm -rf, network calls and access outside expected directories, and run servers in a sandbox with minimal default privileges. As the person configuring clients, choose clients that do this, and keep project-level server files under code review like any other file that executes code.

    If you write a local server, use the stdio transport so only the client can reach it. If you must use HTTP locally, require an authorisation token or use a Unix domain socket with restricted access. Do not leave an unauthenticated server on a localhost port.

    Claude Code adds one control worth turning on knowingly: it asks for approval in interactive sessions before using project-scoped servers from a .mcp.json file. You can reset those approval choices with a command, which is useful after a repository changes hands.

    Reset project-scoped server approvals in Claude Code · bash
    claude mcp reset-project-choices

    Checked against: MCP specification: Security Best Practices (latest), Claude Code documentation: Connect to tools via MCP

  6. Step 6Keep an allow-list of approved servers

    You end up with: Only servers you have reviewed can be connected, and the rule is enforced by configuration, not by memory.

    Review each server once against the inventory from step 1, then enforce the result. In Claude Code, organisations can set allowedMcpServers and deniedMcpServers, by server name or by URL pattern, in managed configuration. Other clients have equivalents or can be fronted by a gateway that only forwards to approved servers.

    Pin what you can. Record the exact package or image you approved, and re-review when it changes, because a server that changes its tool descriptions after approval changes what the model reads. The protocol lets servers announce changes to their tool list, so clients and gateways should surface a changed list for review instead of silently accepting it.

    Prefix tool names with a server identifier where several servers are aggregated. The specification notes that two servers may both expose a tool called search, and recommends a disambiguation strategy such as prefixing. Without it, a hostile server can shadow a trusted tool.

    Allow and deny lists by name (Claude Code managed configuration) · json
    {
      "allowedMcpServers": ["github", "slack", "notion"],
      "deniedMcpServers": ["internal-api"]
    }
    Or by URL pattern · json
    {
      "allowedMcpServers": [
        {"serverUrl": "https://mcp.*.example.com/.*"}
      ],
      "deniedMcpServers": [
        {"serverUrl": "https://untrusted.com/.*"}
      ]
    }

    Checked against: Claude Code documentation: Connect to tools via MCP, MCP specification: Tools (latest)

  7. Step 7Gate sensitive tools with a person and validate inputs

    You end up with: Write-capable tools need explicit approval, and the server rejects malformed or hostile arguments.

    The tools page says there should always be a human in the loop with the ability to deny tool invocations, and that clients should show tool inputs to the user before calling the server to avoid accidental or malicious data exfiltration. Apply that to the write-capable list from step 1: send, delete, pay, deploy, change permissions. Read-only tools on trusted servers can run without a prompt, but keep the log.

    On the server, the specification says you must validate all tool inputs, implement access controls, rate-limit tool invocations and sanitise tool outputs. Check against the schema, not just the type: lengths, allowed values, path prefixes. A tool that takes a file path should resolve it and refuse anything outside its allowed directory. A tool that runs a query should use the caller's permissions, not the server's.

    Clients should also validate results before passing them to the model and set timeouts on tool calls. A tool result is data from outside your trust boundary. Treat any instruction inside it as text to report, not an order to follow. The same applies to resource and prompt content that servers provide.

    Checked against: MCP specification: Tools (latest)

  8. Step 8Restrict outbound requests from clients and servers

    You end up with: A hostile server or metadata URL cannot make your client call internal addresses.

    During OAuth discovery a client fetches several URLs that a malicious MCP server controls. The security page describes the result: a server can point those fetches at a cloud metadata address, an internal admin service or a local database, and the client acts as a proxy past your firewall.

    Mitigations from the page: require HTTPS for OAuth URLs outside local development; block private, loopback and link-local ranges (it lists 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16 and the IPv6 equivalents); validate redirect targets and consider disabling automatic redirects; and for server-side clients, route requests through an egress proxy. It names Smokescreen as an example. It also warns against writing your own IP validation, because encoding tricks defeat custom parsers, and notes DNS rebinding can change an address between check and use.

    Apply the same egress thinking to the MCP server process. A server that only needs to reach one API should be allowed to reach one API, whether you enforce that with network policy, a proxy or a container profile.

    Checked against: MCP specification: Security Best Practices (latest)

  9. Step 9Log every call and review on a schedule

    You end up with: You can answer who called which tool, with what arguments, under which token, and what came back.

    The tools page asks clients to log tool usage for audit purposes, and the best-practices page asks servers to log scope elevations. Put both in one place. For each call record the time, the user or agent identity, the server, the tool, a hash or redacted copy of the arguments, the scopes on the token, the outcome and whether a person approved it.

    Keep arguments out of logs where they may hold personal data or secrets, or store them with the same controls as the source system. Decide retention before you need it. A log nobody reads protects nobody, so set a review: weekly for new servers and write-capable tools, monthly for the rest. Look for tools called at odd hours, scopes requested but never used, and servers whose tool lists changed.

    When you find a compromise, you need two things fast: a way to revoke the server's tokens and a way to disable it in the allow-list. Practise both once on a test server, so the first time is not during an incident.

    Checked against: MCP specification: Tools (latest), MCP specification: Security Best Practices (latest)

About tool description poisoning

A tool's name and description are text the model reads, so a malicious or compromised server can write instructions into them. The specification does not give this attack its own section. What it does say bears directly on it: tool annotations are untrusted unless they come from trusted servers, tool lists can change over time, and clients should validate results and keep a human in the loop. The practical answer is the combination in steps 6 and 7: an allow-list so only reviewed servers load, review when a tool list changes, and approval gates on tools that can act. We have not found an official control that removes the risk, and the OWASP guidance on prompt injection says plainly that it is unclear whether fool-proof prevention exists.

Troubleshooting

What you seeLikely causeFix
A client gets 401 from your MCP server after a successful loginThe token audience does not match the server, usually because the client omitted the resource parameter or sent a different canonical URI.Send resource with the canonical server URI in both the authorisation and token requests, and check the token's audience claim against the same string.
Repeated authorisation prompts for the same operationThe server returns one missing scope per challenge, or the client replaces its scope set instead of taking the union.Return all scopes the operation needs in a single insufficient_scope challenge. In the client, combine previously requested scopes with the new ones, and cap retries.
A proxy lets a registered client skip the consent screenA consent cookie on the third-party authorisation server is trusted for a static client ID, with no per-client consent in the proxy.Add per-client consent in the proxy before forwarding, store approvals by client ID, and set the state cookie only after approval.
A local server starts without any promptThe client config was shared through a repository and approvals are disabled, or the client does not show the startup command.Reset approvals, review the committed config as code, and choose a client that shows the full command before it runs.
Two servers expose a tool with the same name and the wrong one runsTool names are only unique within one server; an aggregator did not prefix them.Prefix tool names with a server identifier at the gateway or client, and deny servers that shadow names of approved tools.
The audit log shows calls but no identityCalls go through a shared service credential, or tokens were passed through to downstream APIs.Issue per-user or per-agent tokens and log the identity from the verified token, not from a request field.

Verify it worked

Next steps

Related guides

  • How to Stop Prompt Injection: Layered Defences That Work: A layered defence for prompt injection: assume it will happen, keep untrusted content apart from instructions, give agents the fewest tools and shortest-lived privileges, require human approval for risky actions, block data leaving, and test with canary documents.
  • How to Set Up Human Approval for AI Agents (With Code): Decide which agent actions need a person, set thresholds, pause the agent with LangGraph interrupts, route requests to a queue with a timeout that denies by default, show reviewers the evidence, and record every decision.
  • How to Govern AI Agents: Identity, Policy, Approvals: Govern agents at runtime: list every agent, give each an identity and an owner, write down what it may and may not do in a Trust Profile, enforce allow, deny and approve rules, choose an autonomy level, record every action and review on a schedule.
  • How to Red Team an LLM App: Tools, Scoring, Retest: A step-by-step LLM red-team exercise: authorisation and scope, a threat model mapped to the OWASP LLM Top 10, automated testing with promptfoo, garak and PyRIT, manual attack sessions, scoring, fixes and retest.
  • How to Monitor AI Agents in Production (2026 Guide): Give every agent run an id, record each model and tool step as a span, redact before you store, alert on loops, tool failures and cost per run, and read a weekly sample by hand.

Frequently asked questions

Is MCP secure by default?

No. The protocol defines authorisation for HTTP transports and publishes security guidance, but it is optional and most protections depend on how servers and clients are built and configured. Local servers run with your privileges, and tool results are untrusted input to the model.

What is token passthrough in MCP and why is it forbidden?

It is when an MCP server accepts a client token and forwards it unchanged to a downstream API. The specification forbids it because it bypasses audience checks and controls, weakens audit trails and lets a stolen token use the server as a proxy. Servers must accept only tokens issued for them.

Do stdio MCP servers need OAuth?

No. The authorisation specification says stdio implementations should not follow the OAuth flow and should take credentials from the environment. Their risk is different: they execute code locally, so use consent for the startup command, a sandbox and pinned, reviewed packages.

What is the confused deputy problem in MCP?

It affects MCP proxy servers that use one static client ID with a third-party authorisation server while letting clients register dynamically. A consent cookie from an earlier approval can let an attacker's client obtain a code without a consent screen. Per-client consent in the proxy prevents it.

How do I restrict which MCP servers my team can use?

Review servers against an inventory, then enforce an allow-list in the client or a gateway. In Claude Code, managed configuration accepts allowedMcpServers and deniedMcpServers by name or URL pattern. Re-review a server when its package or tool list changes.

Can I stop tool poisoning completely?

We found no official control that removes it. Reduce it by loading only reviewed servers, reviewing changed tool lists, treating annotations as untrusted, validating results and requiring human approval for tools that act. OWASP says it is unclear whether fool-proof prevention of prompt injection exists.

How Swfte can help

Everything above works with open standards and your own identity provider. If you want one place to apply the allow-list, audit and policy for agent tool calls, Swfte describes an MCP gateway designed for that.

Whether a given control is available in your Swfte deployment depends on your configuration: <MCP gateway availability and deployment options - founder to fill>. You can complete every step in this guide without Swfte.

Missing a step or found a command that no longer works? Tell us, or request a how-to.

Sources and last verified

Commands, versions and facts in this guide were checked against the sources below on . Tools change quickly: if something differs from what you see, trust the official documentation and let us know.

  1. MCP specification: Security Best Practices (latest): Confused deputy, token passthrough, SSRF, state handles, local server compromise, authorisation URL validation, scope minimisation, as read on 2026-10-06 (links resolve to revision 2026-07-28).
  2. MCP specification: Authorization (latest): OAuth 2.1 basis, Protected Resource Metadata, resource parameter, audience validation, scope challenges, 401 and 403 handling, stdio exception.
  3. MCP specification: Tools (latest): Human in the loop wording, untrusted annotations, tool name collisions, server and client security considerations, audit logging.
  4. Claude Code documentation: Connect to tools via MCP: Third-party server warning, .mcp.json approval prompt, reset-project-choices command, allowedMcpServers and deniedMcpServers examples.
  5. OWASP GenAI: LLM01 Prompt Injection: Statement that fool-proof prevention is unclear, and the list of mitigations including least privilege and human approval.

Topics

  • MCP
  • OAuth
  • tool security
  • agents
  • allow-list

Machine-readable copies: this guide as markdown, index of all guides (JSON). Canonical address: https://www.swfte.com/how-to-secure-mcp-servers.

Build this in Studio

Describe what you need in plain language. Studio builds the agents and workflows, and you keep every version.