Back to blog

Practical MCP server security checklist: exposure, authentication, and trust boundaries

A server that searches incidents may also be able to reach internal documents, credentials, and administrative APIs. When that integration uses the Model Context Protocol (MCP), securing the connection is only the beginning. You also need to control which operations an identity may perform and which data may cross each boundary. MCP connects applications to tools and context; it does not replace the security policies of the systems behind those tools. (modelcontextprotocol.io)

This guide proposes a deployment and operations checklist for platform teams. Its scenario is illustrative, not a production case study. The protocol baseline is revision 2026-07-28, available in the official documentation reviewed for this publication. Older clients require an explicit compatibility matrix: do not apply assumptions about 2025 protocol sessions indiscriminately to 2026 implementations. (modelcontextprotocol.io)

1. Understand the components before assigning permissions

The host coordinates the interaction and consent decisions. An MCP client communicates with a server. The MCP server exposes tools, resources, and prompts. A tool performs an operation, a resource supplies context, and a prompt provides a reusable interaction template. Each component should receive only the context it needs. (modelcontextprotocol.io)

Two deployment modes matter here:

  • stdio: the client starts a subprocess and exchanges messages through standard input and output. The server needs no TCP listener, but the deployment executes local code. Send logs to stderr; do not mix non-protocol output into stdout. (modelcontextprotocol.io)
  • Streamable HTTP: supports remote deployment. In revision 2026-07-28, each message uses POST, with JSON or Server-Sent Events (SSE) responses. This revision removed protocol-level sessions and the independent GET stream. (modelcontextprotocol.io)

Authentication establishes identity; authorization determines whether an operation is allowed. A valid token does not grant unrestricted access. Decisions must account for the tool, target resource, tenant—an organizational isolation boundary—and request context. (cheatsheetseries.owasp.org)

2. Map the trust boundaries

For this article's scenario, use the following architecture:

User → MCP host/client → Private gateway → MCP server
                                ↓              ↓
                       Identity provider   Per-operation policy
                                               ↓
                                      Controlled egress proxy
                                               ↓
                                      Authorized APIs and data

This is a proposed design, not a mandatory MCP topology. Assign responsibilities explicitly:

BoundaryProposed controlRequired evidence
Client → gatewayTLS, authentication, and input limitsRequests without credentials are denied
Gateway → serverVerifiable identity and no bypassDirect access cannot bypass enforcement
Server → toolAction- and object-level authorizationTenant A cannot access tenant B's objects
Server → dependencyDedicated credentials and restricted egressUnapproved destinations are blocked
Result → hostUntrusted-data handlingReturned text cannot expand permissions

Keep business authorization in the server even if the gateway validates tokens. If identity is propagated through internal headers, remove client-supplied values and authenticate the internal sender. A bare X-User header must not become proof of identity. Enforcement placement and consistency are essential parts of access control. (cheatsheetseries.owasp.org)

3. Exposure checklist

Local servers

  • [ ] Prefer stdio when remote access is unnecessary.
  • [ ] Review the package, version, complete startup command, and arguments before installation.
  • [ ] Sandbox the process and restrict its filesystem, network, and credential access.
  • [ ] Bind local HTTP servers to loopback, including a review of IPv6 exposure.
  • [ ] Validate Origin; a present but invalid origin must receive HTTP 403.

These controls address local execution risks and DNS rebinding, where name resolution is manipulated to reach internal services. Loopback does not make a process inherently trustworthy. (modelcontextprotocol.io)

Remote servers

For the corporate deployment in this guide, I recommend this policy:

  • [ ] Private endpoints by default; internet exposure requires an owner and a documented reason.
  • [ ] Transport Layer Security (TLS) at the edge and an explicit protection model for the internal hop.
  • [ ] No administrative or diagnostic endpoints published alongside MCP.
  • [ ] Explicit lists of accepted hosts and origins.
  • [ ] No alternate path through a NodePort, another LoadBalancer, or direct backend access.
  • [ ] An inventory of ports, domains, certificates, owners, and consumers.

Apply the same policy to temporary environments. Kubernetes security guidance includes exposure restrictions and segmentation; an internal network does not remove the need for application controls. (kubernetes.io)

Do not use Cross-Origin Resource Sharing (CORS) as consumer identity. Non-browser clients do not depend on that policy. Handle requests without Origin according to the supported transport and consumers while enforcing authentication separately. (modelcontextprotocol.io)

4. Authentication and authorization checklist

For protected HTTP deployments, MCP defines an OAuth integration: the MCP server is a resource server, and the authorization server issues access tokens. Authorization is optional at the general protocol level, but the policy proposed here requires it for corporate data and privileged operations. Include credentials on every request, never in the query string. (modelcontextprotocol.io)

Discovery and token issuance

  • [ ] Publish Protected Resource Metadata and test client discovery.
  • [ ] Use an approved authorization server rather than automatically trusting received URLs.
  • [ ] For authorization code flows, require Proof Key for Code Exchange (PKCE), verify support, and use S256 when technically possible.
  • [ ] Validate redirects and correlate responses with the initiating flow.
  • [ ] Request tokens for the correct MCP resource.

Discovery and flow protection must work together. Adding a login screen alone does not establish secure interoperability. (modelcontextprotocol.io)

Server-side validation

  • [ ] Reject expired, invalid, or wrong-audience tokens.
  • [ ] Distinguish authentication failures, normally 401, from insufficient permissions, normally 403.
  • [ ] Do not automatically substitute an ID token for an access token.
  • [ ] Do not blindly forward the incoming token to downstream APIs.

The token used by the MCP server to call another API must belong to that integration. Token passthrough breaks audience separation and is prohibited by the specification. (modelcontextprotocol.io)

If the access token is a JSON Web Token (JWT), use a validated library to check its signature, issuer, audience, and validity, with explicitly permitted algorithms. Decoding the payload is not validation. Handle opaque tokens according to the provider's contract instead of assuming every access token is a JWT. (rfc-editor.org)

Business permissions

For this project, I propose scopes such as incidents:read and incidents:write. These are application-defined names, not standard MCP scopes. Each invocation should pass through this decision sequence:

Verified identity
  → authorized tenant
  → permitted tool
  → permitted object
  → valid arguments
  → bounded execution

Do not treat a model-supplied tenant_id as authoritative. Derive identity context from verified credentials and validate object membership. Hiding a tool from tools/list is not a substitute for enforcing tools/call. Tool annotations, including read-only hints, are not authorization controls. (cheatsheetseries.owasp.org)

5. Trust checklist: content is not authority

Prompt injection places malicious instructions inside content processed by an application, including documents and tool results. A result that asks the host to disclose credentials or disable controls must not change host policy. Separate trusted instructions from external data and keep critical decisions outside model-generated text. (cheatsheetseries.owasp.org)

For this scenario, establish these acceptance criteria:

  • [ ] An approved server and tool catalog, with change review.
  • [ ] Human approval for destructive operations, showing the actual target and arguments.
  • [ ] Approval bound to a specific operation rather than blanket permission for future actions.
  • [ ] Purpose-built tools instead of unrestricted shells or arbitrary SQL.
  • [ ] Input and output validation, including type and size limits.
  • [ ] No secrets returned to the model when they can remain in the backend.

These measures strengthen prompt-injection defenses without making them infallible. The tool specification also recommends human oversight and validation. (cheatsheetseries.owasp.org)

SSRF and outbound access

Server-Side Request Forgery (SSRF) can induce a service to contact unintended destinations. Protect both URL-taking tools and components that retrieve OAuth metadata. Validate schemes, destinations, DNS resolution, and redirects. Block cloud metadata, loopback, and unauthorized networks. Required private services should be narrow exceptions, not permission to access the entire internal network. (cheatsheetseries.owasp.org)

State and isolation

Business state can exist even without protocol-level sessions in this revision. A workflow identifier does not authenticate whoever presents it: bind it to the identity and tenant. Apply revision-specific session controls when supporting legacy clients. (modelcontextprotocol.io)

6. Everyday example: incident search without an administrative console

Problem: a team wants to search incidents and read runbooks. Its prototype uses a shared token with write privileges and a generic tool that makes arbitrary HTTP requests.

Proposed solution: separate queries from mutations. Expose purpose-built tools such as search_incidents and get_runbook, with project-level authorization. These names are examples of application tools.

Planned implementation: a private endpoint, per-user OAuth, dedicated credentials for the incident API, a restrictive egress proxy, and a versioned catalog. A future incident-closing tool will require an additional scope and approval tied to the specific incident.

Expected outcome: compromising a search client should not grant permission to modify incidents or access other projects. This is a testable security hypothesis, not an observed risk-reduction metric.

7. Kubernetes: a complete network-isolation example

The following manifest is an illustrative policy for existing workloads, not a complete MCP server deployment. It assumes:

  • an existing mcp namespace;
  • server Pods labeled app: mcp-server;
  • a gateway in namespace mcp-gateway, labeled app: mcp-gateway;
  • an egress proxy in namespace egress-system, labeled app: egress-proxy;
  • cluster DNS identified by k8s-app: kube-dns;
  • a Container Network Interface (CNI) plugin that actually enforces NetworkPolicy.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-restricted-network
  namespace: mcp
spec:
  podSelector:
    matchLabels:
      app: mcp-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: mcp-gateway
          podSelector:
            matchLabels:
              app: mcp-gateway
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: egress-system
          podSelector:
            matchLabels:
              app: egress-proxy
      ports:
        - protocol: TCP
          port: 3128

This policy isolates selected Pods in both directions. Within one peer entry, namespaceSelector and podSelector apply together. Only the selected gateway can enter on port 8080; outbound traffic is limited to DNS and the proxy. Other permissive policies can expand access because NetworkPolicies are additive. They neither configure TLS nor filter URLs. (kubernetes.io)

Adapt ports, labels, and DNS handling, particularly with NodeLocal DNSCache. Configure the application to use the proxy explicitly: this policy does not redirect traffic. The proxy must enforce approved destinations and validate resolution and redirects. Without those rules, proxy access merely moves the problem elsewhere. (kubernetes.io)

Before production, test telemetry, OAuth discovery, signing-key updates, and credential retrieval. In this design, external HTTP dependencies must use the proxy. Non-HTTP connections require specific rules. Do not open 0.0.0.0/0 simply to resolve a timeout.

Pod identity and hardening

Use a dedicated ServiceAccount and disable automatic token mounting when the server does not need the Kubernetes API. Avoid administrator permissions. Role-Based Access Control (RBAC), non-root execution, a read-only filesystem, seccompProfile: RuntimeDefault, dropped capabilities, and disabled privilege escalation should form part of the workload policy. (kubernetes.io)

Restrict Secret access and enable encryption at rest. Keep credentials out of images, arguments, and Git. Define rotation and verify credential reload behavior: creating a replacement Secret does not guarantee that an already-running process will use it. (kubernetes.io)

8. Execution limits, capacity, and availability

IP-based rate limiting alone does not meet the needs of this multiuser design. I propose quotas per identity, tenant, and tool, plus a global limit. The following are hypothetical load-test starting points, not benchmarks:

ControlIllustrative initial value
Request body256 KiB
Tool result1 MiB, with pagination
Per-identity concurrency4 operations
Query deadline15 seconds
QueueBounded, with explicit rejection at saturation

Tune these values against load tests and business requirements. Bound downstream queries, connections, and work after cancellation as well; do not rely only on a gateway timeout. Layered limits reduce opportunities for resource exhaustion. (cheatsheetseries.owasp.org)

For production, I propose three replicas spread across available zones, with sufficient capacity to lose one replica without saturating the others. Set requests from observed demand and choose limits that protect the cluster without unnecessary throttling or memory-related termination. (kubernetes.io)

Use startupProbe for initialization, readinessProbe for traffic eligibility, and livenessProbe for local failures that a restart can resolve. Do not make liveness depend on every external service: a remote outage should not trigger cascading restarts. (kubernetes.io)

A PodDisruptionBudget helps with voluntary disruptions but does not guarantee survival of a zone failure. Horizontal Pod Autoscaler (HPA) supports resource and custom metrics. For this service, evaluate active operations and queue saturation rather than relying exclusively on CPU. (kubernetes.io)

9. Acceptance tests before release

Turn the checklist into automated tests. For the proposed architecture, require:

TestExpected result
Missing or expired tokenProtected operation is not executed
Token for another audienceRejection
Read-only scope used for a mutationRejection
Object owned by another tenantNo data returned
Invalid origin with valid authenticationHTTP 403
URL redirects to a prohibited destinationOutbound access blocked
Malicious text inside a runbookNo permission expansion
Concurrency saturationControlled rejection, no unbounded queue
Restart during a write operationReconciliation, not blind replay

These are proposed acceptance criteria for this service. Test origin validation with valid credentials so authentication rejection does not mask a missing origin check. Verify the absence of side effects, not just the HTTP status.

For revision 2026-07-28 clients, also test consistency between metadata and the request body. A gateway must not authorize the tool named in a header while the backend executes a different tool named in the JSON. (modelcontextprotocol.io)

10. Evidence-driven troubleshooting

SymptomPossible causeInvestigationCorrection
401 after loginExpired token or wrong audienceInspect validator decisions without logging tokensCorrect issuance, resource selection, and refresh
403 only in the browserRejected origin or insufficient scopeCorrelate gateway and application decisionsCorrect the exact origin or required permission
Timeout after NetworkPolicy rolloutBlocked DNS, proxy, or dependencyInspect labels, peers, and denied flowsAdd the smallest necessary exception
Failures after upgradeClient/server revision mismatchInspect versions and protocol errorsFix compatibility without disabling authentication
Repeated Pod restartsInsufficient memory or an unsuitable probeInspect previous state and eventsCorrect consumption, limits, or probe behavior

Confirm these hypotheses against logs and configuration. Authentication, network-policy, compatibility, and probe behavior have separate documented contracts. (modelcontextprotocol.io)

Investigation commands for the workload assumed in this example:

kubectl get pods -n mcp -o wide
kubectl get deployments -n mcp
kubectl get networkpolicy -n mcp -o yaml
kubectl describe deployment mcp-server -n mcp
kubectl logs -n mcp deployment/mcp-server --all-containers=true --tail=200
kubectl get events -n mcp --sort-by=.metadata.creationTimestamp

For a specific restart, replace POD_NAME with the observed Pod name:

kubectl describe pod POD_NAME -n mcp
kubectl logs POD_NAME -n mcp --previous

11. Observability without building a secret repository

I propose an audit event containing a pseudonymized identity, tenant, tool, decision, duration, outcome, policy version, and correlation identifier. Avoid recording full arguments by default. Remove tokens, cookies, and sensitive content before telemetry reaches storage. (opentelemetry.io)

For this service, track:

  • latency, throughput, and failures by tool;
  • authorization denials by reason, without automatically classifying them as downtime;
  • queues, concurrency, cancellations, and saturated dependencies;
  • CPU, memory, restarts, and available replicas;
  • signing-key update and OAuth discovery failures.

Define a Service Level Objective (SLO) for authorized queries completed within their deadline. HTTP success alone is insufficient because tool execution can fail semantically. To control observability costs, use separate retention and sampling policies for audit and diagnostic data, with restricted access. These are proposed operational choices to validate against team requirements.

12. Development, production, and FinOps

ConcernDevelopmentProposed production baseline
DeploymentLocal or a single replicaMultiple replicas and zones
DataSyntheticClassification and object-level authorization
IdentityRestricted test credentialsIndividual identity and rotation
ObservabilitySimplified diagnosticsAudit, SLOs, and alerts
ChangesIsolated experimentationPipeline, review, and rollback

Even in development, do not run unreviewed packages with production credentials.

For FinOps, budget for idle replicas, the gateway, egress proxy, cross-zone traffic, audit storage, and downstream API calls. I recommend starting with predictable minimum capacity, measuring utilization, and adjusting requests and autoscaling. Consider Spot for interruption-tolerant extra capacity, not as the only foundation of a critical service. This example assumes no prices or guaranteed savings.

13. Upgrades, rollback, and incident response

Version the image, dependencies, tool catalog, and policies. Run canary releases against the supported-client matrix. A Deployment rollback restores the Pod template; it does not automatically restore Secrets, external policies, or database changes. Version those changes separately. (kubernetes.io)

The proposed incident runbook should allow responders to disable a tool, interrupt its outbound access, revoke credentials, and preserve audit evidence. Back up business data and persistent consent records, and test restoration. Do not reintroduce revoked credentials during recovery.

14. When to use this design—and when to simplify

Choose a governed remote architecture when multiple consumers, sensitive data, tenant isolation, and audit requirements justify it. For a narrowly scoped local tool, evaluate sandboxed stdio before building a dedicated gateway, cluster, and proxy. A conventional API may be enough for deterministic automation between two services.

These are decision criteria, not a universal recommendation. A central gateway supports standardization but concentrates operational dependencies. Service-level policies preserve business context but require implementation discipline. Kubernetes is appropriate when the team can already operate isolation, observability, and upgrades safely.

Practical summary

Before approving the service, request evidence for five properties:

  1. Only intended consumers can reach it.
  2. Credentials are validated for the correct resource.
  3. Every operation respects identity, tenant, and object boundaries.
  4. External content cannot change permissions or approved destinations.
  5. Failures can be detected, contained, and recovered from without duplicate execution or secret exposure.

Conclusion

The purpose of this checklist is to make MCP security an operational contract: named owners, explicit boundaries, negative tests, and reproducible evidence. DevOps and Platform Engineering teams can turn that contract into a standard deployment path. SRE teams should exercise the same controls during failures and changes, not just during the initial review.

Approval should mean more than successfully connecting a client. It should mean demonstrating what the server cannot do—even with inappropriate credentials, malicious content, or unavailable dependencies.

Want to apply this to your environment?

Book a free 30-minute call. I will look at your setup and tell you where to start.

Book a call

Get new articles by email

One email when a new article on infrastructure, backend and AI is published. No spam, unsubscribe anytime.