MCP Server Security for Internal AI Workflows
A remote Model Context Protocol server can have a working OAuth login and still accept a token meant for another service, forward an upstream token, trust hostile tool metadata, mix user sessions, or fetch attacker-selected discovery URLs. Any one of those turns an AI client into a bridge to internal systems. OAuth is one boundary among several. This guide gives platform and security engineers a layered plan that binds identity, resource, tool, request, and approval at every call, plus negative tests that prove the controls work. It also covers two points most MCP security writing skips: models rarely refuse a poisoned tool, and an internal workflow may not need an MCP server at all.
Start with the actual trust boundaries
An MCP connection has more actors than a normal API integration. The user interacts with an AI host. The host runs an MCP client. The client connects to an MCP server. That server may call an authorization server, internal APIs, databases, or third-party services. A model can influence which tool is selected and what arguments reach it, but it should not decide what the caller may do.
Draw these actors before choosing controls. For each connection, record:
- who authenticates the human or workload;
- which component receives an access token;
- the exact resource and audience for that token;
- which component checks scopes and business permissions;
- where tool names, descriptions, and schemas originate;
- what state is stored in a session and how it maps to a user;
- which outbound destinations the server can reach;
- which actions require a person to confirm the final target and effect.
The MCP authorization specification defines a remote MCP server as an OAuth resource server and the MCP client as an OAuth client. That distinction matters. The MCP server is not a transparent token relay. It owns the decision to accept or reject the credential presented for its resource.
Each boundary must make its own authorization decision from validated context. The AI host decides which servers may be installed. The MCP client decides which server and tool metadata it will expose to the model. The MCP server validates the token and request. The downstream business system enforces its own permissions. Approval logic checks the proposed effect before a consequential operation runs.
Build the MCP server security controls in sequence
Build the controls in the order a request crosses the system. Added after the tools are written, they have no clear owner.
Pin server trust and discovery
Keep an allowlist of approved remote MCP server origins, with the expected issuer, protected resource, transport, and owner beside each entry. A prompt, tool result, or untrusted configuration must never choose a new server URL at runtime.
Discovery endpoints create a server-side request forgery path if the implementation follows arbitrary URLs or redirects. The MCP security best-practices guide treats SSRF as a protocol implementation risk. Restrict discovery to HTTPS origins that match approved server configuration. Resolve hostnames before connection, block loopback, link-local, private, and cloud-metadata address ranges unless a specific internal deployment requires them, and recheck the destination after redirects and DNS resolution.
Put the server catalog under change control. An owner reviews origin changes, authorization metadata, and tool-catalog changes, because a plausible description does not make a new server trustworthy.
Validate tokens for this resource
Publish OAuth protected-resource metadata for the MCP server and use the authorization-server discovery flow defined by the specification. On every request, validate the token signature, issuer, expiration, resource or audience, and required scopes. Then apply business authorization to the user and target object.
A signature from a known authorization server is not enough. A token issued for a calendar API must not be valid at an MCP server that can update customer records, and audience validation prevents that substitution. Scope validation limits the class of operation. Business authorization answers whether this caller may act on this account, document, or tenant.
Never pass the token received from the MCP client directly to an upstream API. The official security guide calls this token passthrough and rejects it because the MCP server would be accepting a token not issued for itself. If the server must call another resource, use a documented delegation or token-exchange flow that produces a token for that downstream audience. Keep that exchange separate from the incoming validation step.
A request guard can follow this shape:
function authorizeMcpCall(request, tool):
claims = verifySignatureAndIssuer(request.accessToken)
require claims.expiresAt > now
require claims.audience contains MCP_SERVER_RESOURCE
require claims.scopes contains tool.requiredScope
principal = bindSubjectAndActor(claims)
require policy.allows(principal, tool.name, request.target)
require request.session.userId == principal.subjectId
if tool.hasConsequentialEffect:
require validApproval(principal, tool, request.arguments)
return principal
The policy decision should use server-side tool definitions. Do not let client-supplied annotations lower the required scope or disable approval.
Separate proxy consent from upstream consent
An MCP server that proxies a third-party API can become a confused deputy. The upstream provider may remember that the user consented to one static client. An attacker can then try to route a different MCP client through that remembered consent and obtain an authorization result the user did not intend.
The MCP security guide describes this failure and recommends per-client consent at the proxy layer. Show the user which MCP client is requesting access and which upstream permissions will be used. Record consent against that client identity. Do not treat an upstream consent cookie as proof that every downstream MCP client has approval.
For internal deployments, make the consent record inspectable. Security staff should be able to answer which host connected, which user approved it, which scopes were granted, and when that grant expires. Revocation should stop future calls even if an upstream session remains active.
Treat tool metadata as untrusted input
Tool descriptions steer model selection, so a malicious or compromised server can place instructions in names, descriptions, annotations, or returned content. The MCP tools specification tells clients to treat tool annotations as untrusted unless they come from a trusted server. It also requires servers to validate tool inputs and implement access controls, with rate limiting and output sanitization among the advised controls.
Do not count on the model to refuse a poisoned tool. The MCPTox benchmark built 1,348 malicious test cases on 45 live MCP servers and 353 real tools and ran them against 20 LLM agents. The best attack reached a 72.8 percent success rate, refusals were rare (the highest refusal rate was below 3 percent), and the authors report that more capable models are often more susceptible because the attack exploits their stronger instruction-following (MCPTox, arXiv 2508.14925). A model upgrade can therefore make tool poisoning worse. The control that works is the catalog review: if a changed description never reaches the model unreviewed, the model's judgment is not needed.
Pin a reviewed tool catalog for sensitive integrations. Compare newly advertised names, descriptions, input schemas, and annotations with the approved version, and quarantine changed tools until review. Annotations help a user interface explain behavior. They cannot grant trust.
Validate arguments against a strict schema and then apply semantic checks. A schema can prove that account_id is a string, but it cannot prove that the caller owns the account. Resolve identifiers within the authenticated tenant, reject unknown fields, cap input sizes, and normalize paths or URLs before policy evaluation.
Sanitize tool output before it reenters model context: mark it as data, remove secrets and internal error details, and cap its size so a tool cannot flood the context or bury an instruction in bulk.
Bind sessions to identity, not possession
A session identifier is routing state and not a credential. Generate unpredictable identifiers, expire them, bind each to the authenticated user and MCP client, and recheck the binding on every request. Never restore authorization from a session after its token has expired or been revoked.
Keep tenant and user state apart. A reconnect must not inherit another employee's tool results, approvals, or pending tasks. For background jobs, store the initiating subject, acting workload, approved action, and expiry with the job, and reauthorize at execution time.
Log session creation, reconnection, user mismatch, and termination without recording raw tokens. A cross-user session attempt should produce a denial and a security event, not a new implicit binding.
Put approval after argument resolution
The MCP tools specification says applications should keep a human able to deny tool invocations and show confirmation prompts. Approval is most useful after the server has resolved the real target and normalized the arguments.
"Allow CRM update" is too broad. Show the customer record, fields, old and new values, and external effects, and bind the approval to a hash of the normalized operation, the user, and a short expiry. If any argument changes, ask again.
Read-only tools may use policy-based auto-approval when their outputs and access are bounded. Writes, external messages, money movement, permission changes, and destructive operations should default to explicit confirmation. The model can propose an action, but it cannot approve its own proposal.
A public case: the server's OAuth metadata is untrusted input
In 2025 JFrog disclosed CVE-2025-6514 (CVSS 9.6) in mcp-remote, a widely used proxy that lets desktop AI hosts connect to remote MCP servers. During the OAuth handshake a malicious server could return a crafted authorization_endpoint in its metadata. The proxy passed that value to a URL constructor and then to the open package, which launches the user's browser. On Windows the value could carry PowerShell subexpression syntax, so connecting to a hostile server ran attacker-chosen commands on the user's machine. Versions 0.0.5 through 0.1.15 were affected, and 0.1.16 fixed it.
The client did not trust the server's tools, but it did trust the server's discovery document. Treat every field a remote server returns as untrusted input, including OAuth metadata, redirect and authorization URLs, and tool descriptions. Restrict connections to servers on an approved list, so a user cannot paste in a hostile URL. Allow only https URLs from the same host family as the server, and pin and update proxy and client packages like any dependency that handles untrusted input.
Handle local and remote MCP servers differently
Remote authorization controls do not sandbox a local MCP server. A local server is an ordinary process and may inherit the user's environment variables, filesystem permissions, and network access. The MCP project has since merged a local-server security guide to document this machine-level gap separately from protocol authorization.
For local servers, verify package provenance and pin versions. Run the process with a minimal environment, task-specific filesystem access, and restricted network egress. Do not put broad cloud credentials in the parent process and assume MCP scopes will contain them. If a local server only needs one directory and one internal API, its operating-system boundary should expose only those resources.
For remote servers, keep the process boundary on the server side and focus the client on origin trust, OAuth resource binding, tool review, approvals, and response handling. The same tool name can require different controls depending on where its code executes.
Test the failures, not just the happy path
A successful login and tool call do not prove that the boundaries fail closed. Build a negative suite with a known denial result for each boundary.
- Present a valid token issued for another resource. Expect a token-audience denial before tool dispatch.
- Remove the required scope from a valid token. Expect a scope denial even when the user can log in.
- Request an object in another tenant. Expect a business-authorization denial and no downstream call.
- Change a reviewed tool description or input schema. Expect catalog quarantine or an administrator review requirement.
- Put an internal or link-local address in discovery metadata and behind a redirect. Expect the outbound request guard to block both.
- Reuse a session identifier with a different user. Expect termination of the request and a security event.
- Change an argument after approval. Expect the approval binding to fail.
- Attempt to pass the incoming token to an upstream API. Expect the server architecture test or outbound mock to reject it.
- Return instruction-like text and a secret-shaped value from a tool. Expect output labeling, redaction, and size limits to apply.
- Revoke the grant while a background operation is queued. Expect reauthorization to stop execution.
Record the expected HTTP or protocol error, audit event, and absence of side effects for each test. Verification is incomplete if a test only checks the error message while the downstream write still occurs.
The OpenAI MCP server guide is a useful implementation reference for defining tools and authenticating users, but the deployment gate should remain product-independent. Run the same boundary tests against any host or SDK that connects to the server.
Ask whether you need an MCP server
MCP earns its cost when many hosts and tools must interoperate. If you own both the agent and the tool, and nobody else will connect a client to it, an in-process function with typed arguments and your own authorization check has no network boundary, no token to confuse, no session to hijack, and no tool catalog an outside party can change. A server is another service to authenticate, patch, and monitor. Choose MCP for internal workflows when a second host will consume the same tools or when a vendor already ships the server you need, and prefer a plain function when neither is true.
Review the operating controls before release
Code checks will not catch an unowned integration or a stale grant, so assign an owner to every approved MCP server and tool catalog. Set review dates for server origin, package version, scopes, downstream resources, and approval policy. Provide one place to revoke a user, client, server, or tool without waiting for a deployment.
Monitor denials as well as successful calls. Repeated audience mismatches, blocked discovery addresses, cross-user session attempts, tool-catalog changes, and altered approvals are useful security signals. Keep audit records separate from model prompts and diagnostic payloads so incident review does not depend on storing sensitive conversation text.
Use a staged release. Start with test identities and read-only tools, then a small internal group. Add write tools only after the denial suite and audit reconstruction pass. If the team cannot identify the user, client, token audience, tool version, normalized target, approval, and result for a call, the integration is not ready for broader access.
Next action
Draw the trust-boundary map for one remote MCP integration, then implement the ten negative tests above in a non-production environment. Add no further write-capable tool until the token-audience, tenant-isolation, catalog-change, session-mismatch, SSRF, and approval-tampering tests all fail closed.
References
- MCP authorization specification: OAuth roles, protected-resource metadata, and authorization discovery for remote MCP servers.
- MCP security best practices: protocol attacks and mitigations for confused deputy behavior, token passthrough, SSRF, and session handling.
- MCP tools specification: tool trust, input validation, access controls, output handling, and human denial guidance.
- MCPTox: a benchmark of tool-poisoning attacks on real MCP servers, with attack success and refusal rates for 20 LLM agents.
- MCP pull request 3072: the merged local-server security guide covering process, credential, filesystem, and network risks outside protocol authorization.
- OpenAI guide to building MCP servers: implementation reference for server tools and user authentication.
- JFrog, CVE-2025-6514 critical mcp-remote RCE vulnerability: a malicious server's OAuth metadata leading to command execution on the client.