AI Security
MCP Server Security Best Practices: A Builder's Checklist
Codesprint Consulting
By,Codesprint Consulting
  • 13 October 2026

If you are building or running a Model Context Protocol server, the best place to start with MCP server security best practices is the protocol's own security page. It lists specific attacks, says which behaviors are required, and says which are only recommended. Most blog posts on the subject paraphrase it. This one walks through it directly and turns it into a checklist you can hand to whoever owns your MCP server.

We read the Security Best Practices page from the MCP specification site. That page is the draft version, so details may change, and the page itself points to the 2025-11-25 version for some older material such as server-assigned session IDs. We have not tested any of these attacks ourselves, and where a point is our own advice rather than the spec's, we say so. Source

Who the page is for

The spec says the document is meant for developers implementing MCP authorization flows, MCP server operators and security professionals evaluating MCP-based systems. It is meant to be read alongside the MCP Authorization specification and OAuth 2.0 security best practices. Source

That scope matters. Much of the page is about the authorization layer, meaning how a client gets permission to talk to a server and, through it, to a third-party API. If your MCP server only runs locally and touches nothing else, some sections will not apply to you. If it sits in front of Slack, GitHub, a CRM or a database, most of them will.

The first attack in the spec targets MCP proxy servers, which connect MCP clients to third-party APIs and act as a single OAuth client to that API. The spec calls the risk a confused deputy problem. Source

It becomes possible when four things are all true: the proxy uses a static client ID with the third-party authorization server, it lets MCP clients register dynamically, the third-party server sets a consent cookie after the first approval, and the proxy does not ask for consent per client before forwarding the request. An attacker then registers a malicious client, sends the user a link, and the browser's old consent cookie lets the third-party server skip the consent screen. The authorization code goes to the attacker's redirect address. Source

The required fixes are concrete. The proxy must keep a registry of approved client IDs per user and check it before starting the third-party flow. Its own consent page must name the requesting client, show the scopes requested and the registered redirect address, use CSRF protection, and block framing. If it uses cookies for consent, they must use the __Host- prefix, be Secure, HttpOnly and SameSite=Lax, be signed or backed by a server session, and be tied to the specific client ID. Source

Two more rules close the gap. The redirect address must match the registered one by exact string, with no patterns or wildcards. And the OAuth state value must be random, single-use, short-lived, checked at the callback, and only set after the user approves the consent screen. Setting it earlier makes the consent screen useless. Source

2. Token passthrough: only accept tokens issued for your server

The spec calls token passthrough an anti-pattern. It means an MCP server accepts a token from a client without checking that the token was issued to that server, then passes it on to a downstream API. Source

The listed risks are easy to picture. Rate limits and request checks that depend on the token's audience get bypassed. Logs on the downstream API show a different identity than the server that actually made the call, which makes incident investigation harder. A stolen token can use your server as a proxy to pull data out. And trust boundaries between services blur, so a compromise of one service reaches the others. Source

The rule is one sentence: MCP servers must not accept any tokens that were not explicitly issued for the MCP server. In practice, that means checking the audience claim on every token. This is also the cheapest item on the list to audit. Source

3. SSRF: treat every URL from a server as hostile

Server-side request forgery is the next attack. During OAuth discovery, an MCP client fetches URLs that a malicious server can control, including the resource metadata URL, the authorization server URLs and the token and authorization endpoints. A bad server can point them at internal addresses, cloud metadata endpoints such as 169.254.169.254, localhost services, or domains whose DNS changes between the check and the request. Source

The risks include leaked cloud credentials, a map of the internal network, and mutations on internal services through POST requests. The client ends up acting as a proxy that bypasses the firewall. Source

Clients deployed on a server are told to consider these risks. The spec recommends requiring HTTPS for OAuth URLs in production, blocking private and reserved IP ranges, validating redirect targets or turning off automatic redirects, and using an egress proxy that enforces network policy. It also warns against writing IP validation by hand, because encoding tricks such as octal, hex and IPv4-mapped IPv6 defeat custom parsers. For DNS rebinding, it suggests pinning the resolved address between check and use. Source

The spec adds that the risk is not limited to clients. An authorization server that fetches client metadata documents from a URL supplied by an unknown client has the same exposure. MCP Specification For a deeper look at one real class of these flaws, see our earlier post on MCP SSRF flaws and protocol pivoting.

4. State handles: a handle is not a login

The draft says MCP is stateless and has no protocol-level sessions. A server that needs to carry state across requests hands out an explicit handle, such as a cart ID or workflow ID, and gets it back as a normal tool argument. State handle hijacking is when someone obtains or guesses that handle and uses it to read or change another user's state. Source

The mitigation has three parts. Servers must verify every inbound request and must not treat possession of a handle as authentication. They should use secure random handles, not sequential or guessable IDs, and can expire them. And they should bind each handle to the authenticated user on the server, for example by keying stored state on the user ID taken from the verified token, then reject the handle if anyone else presents it. Source

If you have read our post on vibe-coded app audits, this is the same record-ownership check applied to an agent tool. That link is our observation, not the spec's.

5. Local servers: the startup command is the attack

Local MCP servers run on the user's machine and may be reachable by other processes. The spec describes three routes: a malicious startup command planted in a client configuration, a malicious payload inside the server itself, and an insecure local server reached through DNS rebinding. Its example commands show data theft and file deletion hidden behind a harmless-looking install. Source

For clients, the spec requires a consent dialog before a new local server is connected through one-click setup. The dialog must show the exact command without truncation, label it as a dangerous operation that runs code, require explicit approval and allow cancelling. Clients should also flag risky patterns such as sudo, rm -rf and access to the home directory or SSH keys, and run servers in a sandbox with minimal privileges. Source

For server authors, the advice is to use the stdio transport so only the client can reach the server, or, if you use HTTP, to require an authorization token or use unix domain sockets with restricted access. Source

6. Authorization URLs and scopes

Two smaller sections are worth a line each. First, a malicious server can supply an authorization URL such as a javascript: link, which a client may pass to a browser API, or a string that a shell interprets as a command. The spec says clients must allow only http and https, reject schemes such as javascript:, data: and file:, and must not open URLs through a shell. It prefers allowlists to blocklists. Source

Second, scope design. If a server lists every possible scope and a client requests them all, a stolen token carries a very large blast radius and revocation disrupts everything. The spec recommends a minimal initial scope, step-up through targeted WWW-Authenticate challenges when a privileged action is first tried, and acceptance of reduced-scope tokens. Its list of common mistakes includes wildcard or omnibus scopes such as * and full-access, and trusting the scopes claimed in a token without server-side authorization logic. Source

A checklist you can use this week

This list is our summary of the spec's requirements, written for a team that owns an MCP server. The wording is ours.

  1. Do you accept only tokens whose audience is your server?

  2. If you proxy a third-party API, do you store consent per client ID, and does your own consent page show the client, scopes and redirect address?

  3. Is redirect matching exact, and is the state value random, single-use and set only after consent?

  4. Does any server-side client of yours fetch URLs from remote servers without blocking private ranges, metadata addresses and unsafe redirects?

  5. Are your state handles random, expiring and bound to the verified user?

  6. If your server runs locally, does it use stdio or an authenticated transport, and does your client show the full startup command before running it?

  7. Do you publish only the scopes you need, and can you elevate step by step?

  8. Do you log elevation events and failed authorization attempts so someone can see them?

None of this replaces a review of what each tool on your server can do. The protocol page covers the connection. It does not decide whether your delete_record tool should exist or who may call it. For that side, our AgentOps services page covers tool access, permissions and logging for agents in production.

FAQ

From the official spec: prevent confused deputy attacks with per-client consent, never accept tokens not issued for your server, defend against SSRF, bind state handles to the authenticated user, sandbox and gate local servers, validate authorization URLs, and keep scopes minimal. MCP Specification

It is when an MCP server accepts a client's token without checking it was issued for that server, then forwards it to a downstream API. The spec forbids it and says servers must not accept tokens not explicitly issued for them. MCP Specification

The spec says stdio is not inherently vulnerable. It recommends it for local servers because it limits access to the client. It also describes a proxy setup where stdio can become a path from a web-based attack to full system compromise. MCP Specification

Some of them do whatever the size. Audience checks, user-bound handles and minimal scopes cost little. The proxy consent rules matter only if you act as an OAuth proxy to a third-party API. Which items apply to you is our judgment, so check the spec's conditions against your design.

The page we read is the draft version, and the spec's own text refers to the 2025-11-25 version for some older guidance. Check the current version before you rely on a specific requirement. MCP Specification

Tag:
  • MCP
  • MCP security
  • OAuth
  • SSRF
  • agent tools

Have a project in mind? Let's talk.

Drop Us a Line

Connect with Codesprint Consulting

Ready to take the first step towards unlocking opportunities, realizing goals, and embracing innovation? We're here and eager to connect.

Your Success Starts Here!