Back to Blog
Rows of server hardware representing remote MCP servers connecting AI clients to internal systems

MCP Server Security for Internal AI Workflows

11 min read

MCP server security fails when a team treats OAuth as the whole boundary. A remote Model Context Protocol server can have a valid login flow 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 mistakes can turn an AI client into a bridge to internal systems. The fix is a layered deployment plan that binds identity, resource, tool, request, and approval at every call. This guide gives platform and security engineers that plan, plus negative tests that prove the controls work before production.

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

Adding these controls after tool development leaves their ownership unclear. Build them in the same order that a request crosses the system.

Pin server trust and discovery

Maintain an allowlist of approved remote MCP server origins. Store the expected issuer, protected resource, transport, and owner beside each entry. Do not let a prompt, tool result, or untrusted configuration 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.

Keep the server catalog under change control. An owner should review origin changes, authorization metadata, and tool-catalog changes. A plausible description does not make a newly discovered 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.

Do not accept a token merely because a known authorization server signed it. A token issued for a calendar API is not automatically valid at an MCP server that can update customer records. Audience validation prevents that substitution. Scope validation limits the class of operation, while business authorization answers whether this caller may act on this specific 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 influence 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.

Pin a reviewed tool catalog for sensitive integrations. Compare newly advertised names, descriptions, input schemas, and annotations with the approved version. Disable the server or quarantine changed tools until review. Tool annotations can help a user interface explain behavior, but 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 returned text as data, not instructions. Remove secrets and internal error details. Limit output size so a tool cannot flood the context window or hide a malicious instruction inside excessive data.

Bind sessions to identity, not possession

A session identifier is routing state, not an authentication credential. Generate unpredictable identifiers, expire them, and bind each session to the authenticated user and MCP client. Recheck that binding on every request. Do not restore authorization from a session after its token has expired or been revoked.

Keep tenant and user state separate. A reconnect must not inherit another employee's tool results, approvals, or pending tasks. If the workflow supports background jobs, store the initiating subject, acting workload, approved action, and expiry with the job. Reauthorize at execution time when the target or permissions may have changed.

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 should show confirmation prompts for operations. Approval is most useful after the server has resolved the real target and normalized the arguments.

A prompt such as "allow CRM update" is too broad. Show the user the customer record, fields, old and new values, and external effects. Bind the approval to a hash of the normalized operation, the user, and a short expiry. If any argument changes, request approval 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.

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 active MCP local-server security pull request was opened 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.

  1. Present a valid token issued for another resource. Expect a token-audience denial before tool dispatch.
  2. Remove the required scope from a valid token. Expect a scope denial even when the user can log in.
  3. Request an object in another tenant. Expect a business-authorization denial and no downstream call.
  4. Change a reviewed tool description or input schema. Expect catalog quarantine or an administrator review requirement.
  5. Put an internal or link-local address in discovery metadata and behind a redirect. Expect the outbound request guard to block both.
  6. Reuse a session identifier with a different user. Expect termination of the request and a security event.
  7. Change an argument after approval. Expect the approval binding to fail.
  8. Attempt to pass the incoming token to an upstream API. Expect the server architecture test or outbound mock to reject it.
  9. Return instruction-like text and a secret-shaped value from a tool. Expect output labeling, redaction, and size limits to apply.
  10. 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.

Review the operating controls before release

Code checks will not catch an unowned integration or a stale grant. 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

Pick one remote MCP integration and draw its trust-boundary map. Then implement the ten negative tests above in a non-production environment. Do not add another write-capable tool until token audience, tenant isolation, catalog change, session mismatch, SSRF, and approval-tampering tests all fail closed. That exercise turns MCP server security from a login feature into a verified control system.

References


About Fire In Belly: Independent senior engineering from Tallinn, Estonia. We design and build AI workflow automation with the layered trust boundaries, token validation, and negative-test discipline described above, at published fixed prices. Schedule a call to discuss your next project.