Securing a remote MCP server: OAuth 2.1, scopes, and approval gates
An MCP server is an API driven by a probabilistic caller on behalf of a real user. Secure it like one. A remote MCP server sits between an AI agent and your real systems. This guide covers the threat model, then the controls we put in every build.
The threat model is different
A traditional API has a deterministic caller: code you or your customer wrote. An MCP server’s caller is a language model acting for a user, and it can be steered by any content it reads along the way. The threats that matter most:
- Prompt injection through tool results. A ticket, email, or document contains instructions, and the agent follows them.
- Confused deputy. The server acts with more authority than the user it’s acting for actually has.
- Over-broad tokens. One credential that can read and write everything, for everyone.
- Exfiltration by chaining. Read something sensitive with one tool, send it somewhere with another.
- Poisoned tool descriptions. Third-party servers in the same session whose descriptions try to influence how your tools are used.
Authorization with OAuth 2.1
In MCP’s authorization model, your server is an OAuth 2.1 resource server. Tokens come from an authorization server, ideally the identity provider you already run, so SSO, MFA, and offboarding keep working as they do today.
- Publish OAuth protected resource metadata so clients can discover which authorization server to use.
- Use the authorization code flow with PKCE, and follow the current specification’s guidance on client registration.
- Validate the token’s audience. A token minted for some other service must be rejected, even if it’s otherwise valid.
- Never pass the client’s token through to upstream APIs. Call them with the server’s own credentials, scoped to what this user may do.
Least-privilege scopes, mapped to tools
Every tool should declare the scope it needs, and reads and writes should
use different scopes. A user who can only read shouldn’t even see write
tools: filter tools/list per user so the model
isn’t tempted by tools it can’t use. Enforce tenant isolation in server
code. A prompt that says “only show this customer’s data” is not a control.
Approval gates for consequential actions
Anything irreversible or outward-facing (sending a message, moving money, deleting a record, publishing content) should require explicit human confirmation. Use elicitation or a confirm step that the user sees and approves, rather than trusting the model’s judgment that the user “clearly meant it”.
Tool annotations like readOnlyHint and destructiveHint help clients present tools sensibly. They’re hints, not security controls;
the server must enforce the rule either way.
Treat tool output as untrusted input
Whatever your server returns becomes part of the model’s context, and anything in that context can steer it. Return the minimum data the task needs. Strip secrets and internal identifiers the user shouldn’t see. Fence user-generated content clearly so it reads as data, not instructions. Cap response sizes and rate-limit per user, so a runaway agent can’t page through your entire database.
Audit and observe
Log every tool call with the user, tenant, tool, redacted arguments, result status, and a correlation ID that ties it to the session. Alert on unusual patterns: a sudden burst of reads, writes outside business hours, repeated authorization failures. Put the logs somewhere your security team already looks, in a format they can query.
A pre-launch checklist
- Server validates token audience and rejects tokens issued for other resources
- Protected resource metadata is published and points at your authorization server
- No client tokens are passed through to upstream APIs
- Every tool declares a scope; reads and writes use separate scopes
- tools/list is filtered to what the current user can call
- Tenant isolation is enforced in server code, not in prompts
- Consequential actions require explicit human confirmation
- Tool results return minimum data, with secrets stripped and user content fenced
- Every call is logged with user, tenant, tool, redacted arguments, and outcome
- Dependencies are pinned, secrets live in a manager, and evals run against staging
Security and tool design go together. Smaller, task-shaped tools are easier to scope and audit; see MCP tool design. To have an existing server reviewed against this list, see our MCP Audit & Eval.
Want this done for your systems?
We build MCP servers with OAuth 2.1, scoped permissions, approval gates, and audit logs, deployed in your cloud.
Start an MCP project