Short answer
Secure an MCP server the way you would secure any API that acts on a user’s behalf, then add two assumptions: the caller is a model that may have been manipulated, and the content flowing through your tools is untrusted. In practice that means authenticating every request, accepting only tokens issued for your server, giving each tool the narrowest scope it needs, checking that the caller owns whatever a tool touches, and requiring a person to approve anything destructive. The MCP specification’s own security best practices list the attacks to design against.
What is an MCP server, and why does it need its own thinking?
The Model Context Protocol lets an AI client, such as the coding tool in your editor, call tools and read resources that a server exposes. If you ship an MCP server with your app, you are giving AI agents a way to act inside your product for your users. The glossary has a one paragraph definition of MCP.
Two things make that different from an ordinary API. The caller is a model, and models can be steered by text they read, so a request can be well formed and still not be what the user wanted. And MCP servers often sit in the middle of other systems, holding tokens for third party APIs, which makes them a tempting place to borrow someone else’s authority.
What are the main risks?
Two public sources are worth reading in full. The protocol’s Security Best Practices page describes concrete attacks and the required mitigations. The OWASP MCP Top 10, a project in beta, names the risk areas: token mismanagement and secret exposure, privilege escalation through scope creep, tool poisoning, supply chain attacks, command injection, prompt injection through contextual payloads, insufficient authentication and authorization, lack of audit and telemetry, shadow MCP servers, and context over-sharing.
The sections below take the attacks the specification spells out, in the order a small team is likely to meet them.
Which tokens should your server accept?
Only tokens issued for it. The specification is direct: MCP servers “MUST NOT accept any tokens that were not explicitly issued for the MCP server.” That means checking the token’s audience, not just its signature.
The anti-pattern it warns against is token passthrough: accepting a token from the client and forwarding it unchanged to a downstream API. It looks convenient, and it breaks several things at once. Your rate limits and checks can be bypassed, your logs cannot tell clients apart, and a stolen token can use your server as a proxy for data exfiltration. If your server calls another API, it should hold its own credentials for that API and decide for itself what the user may do.
What is the confused deputy problem?
It applies when your MCP server acts as a proxy to a third party API using one static client id for everyone. The specification describes how an attacker can register their own client, send a user a crafted link, and ride on a consent cookie the third party set earlier, so the user never sees a consent screen and the authorization code lands on the attacker’s server.
The required fix is per client consent owned by your server, shown before the third party flow. The consent page must name the requesting client, show the scopes and the redirect address, and be protected against cross site request forgery and framing. Redirect addresses must match the registered value exactly, with no wildcards, and the OAuth state value must be random, single use, short lived and set only after the user approves.
How narrow should each tool’s access be?
As narrow as the task. The specification’s guidance on scope minimisation describes the failure: a token granted every scope up front is stolen from a log, and the attacker can immediately use every tool. Its recommendation is a progressive model: a minimal starting scope for low risk reads, and a targeted request for more when a privileged tool is first used.
The common mistakes it lists are a useful review list for your own server: publishing every scope as supported, wildcard or “full access” scopes, bundling unrelated privileges to avoid future prompts, and treating the scopes in a token as enough without checking on the server whether this user may do this thing to this record.
How do you stop one user reaching another’s data?
The same way as in any API, and the specification has its own name for the MCP version: state handle hijacking. MCP is stateless, so a server that needs state across calls mints a handle, such as a cart or workflow id, and the client passes it back as an argument. If the server does not check that the handle belongs to the caller, anyone who obtains or guesses it can act on another user’s state.
The required rule is that servers “MUST NOT treat possession of a state handle as authentication.” Bind each handle to the user id taken from the verified token, reject it from anyone else, and use random, expiring handles. It is the MCP form of IDOR, and the fix is the same: check ownership on every call.
What about prompt injection through tools?
Every tool result your server returns is text a model will read, and every tool description you publish is text a model will trust. Both are routes for prompt injection. OWASP lists tool poisoning, malicious instructions hidden in tool metadata, as its own risk.
- Return data, not instructions. Structured fields are safer than prose that a model might follow.
- Treat content you fetch on a user’s behalf as untrusted, and say so in the result.
- Keep destructive tools separate from read tools, and require confirmation from a person for anything that sends, pays, deletes or changes permissions.
- Version your tool descriptions and review changes the way you review code.
Is a local MCP server safer?
Not automatically. A local server runs with the user’s own privileges on their machine. The specification asks clients to show the exact command before running a new local server and to sandbox it, and asks servers meant for local use to prefer the stdio transport, or to require an authorization token if they listen over HTTP, so that other processes and malicious web pages cannot reach them. If you distribute a local server, publish exactly what it runs and what it can touch.
What should be on your review list?
- Every request authenticated; tokens checked for audience; no passthrough.
- Per client consent if you proxy a third party API; exact redirect matching; proper state handling.
- Least privilege scopes, raised step by step, checked on the server for each call.
- Ownership checked for every record and handle a tool touches.
- Tool output treated as untrusted by the client side, and destructive tools behind human approval.
- Logs that record which client called which tool for which user, without storing tokens or secrets.
The rest of your app still matters: an MCP server is one more way in, not a separate product. The free Vibe Coding Security Checklist covers the app around it, including access control, secrets and AI features, ranked by severity with a fix deadline. If you want to see how VibeLock approaches its own hosted MCP server, the MCP documentation describes the design.
Sources
- Security Best Practices, Model Context Protocol specification
- OWASP MCP Top 10, OWASP Foundation
- LLM01:2025 Prompt Injection, OWASP Gen AI Security Project