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 intostdout. (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 dataThis is a proposed design, not a mandatory MCP topology. Assign responsibilities explicitly:
| Boundary | Proposed control | Required evidence |
|---|---|---|
| Client → gateway | TLS, authentication, and input limits | Requests without credentials are denied |
| Gateway → server | Verifiable identity and no bypass | Direct access cannot bypass enforcement |
| Server → tool | Action- and object-level authorization | Tenant A cannot access tenant B's objects |
| Server → dependency | Dedicated credentials and restricted egress | Unapproved destinations are blocked |
| Result → host | Untrusted-data handling | Returned 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
stdiowhen 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
S256when 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 executionDo 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
mcpnamespace; - server Pods labeled
app: mcp-server; - a gateway in namespace
mcp-gateway, labeledapp: mcp-gateway; - an egress proxy in namespace
egress-system, labeledapp: 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: 3128This 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:
| Control | Illustrative initial value |
|---|---|
| Request body | 256 KiB |
| Tool result | 1 MiB, with pagination |
| Per-identity concurrency | 4 operations |
| Query deadline | 15 seconds |
| Queue | Bounded, 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:
| Test | Expected result |
|---|---|
| Missing or expired token | Protected operation is not executed |
| Token for another audience | Rejection |
| Read-only scope used for a mutation | Rejection |
| Object owned by another tenant | No data returned |
| Invalid origin with valid authentication | HTTP 403 |
| URL redirects to a prohibited destination | Outbound access blocked |
| Malicious text inside a runbook | No permission expansion |
| Concurrency saturation | Controlled rejection, no unbounded queue |
| Restart during a write operation | Reconciliation, 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
| Symptom | Possible cause | Investigation | Correction |
|---|---|---|---|
| 401 after login | Expired token or wrong audience | Inspect validator decisions without logging tokens | Correct issuance, resource selection, and refresh |
| 403 only in the browser | Rejected origin or insufficient scope | Correlate gateway and application decisions | Correct the exact origin or required permission |
| Timeout after NetworkPolicy rollout | Blocked DNS, proxy, or dependency | Inspect labels, peers, and denied flows | Add the smallest necessary exception |
| Failures after upgrade | Client/server revision mismatch | Inspect versions and protocol errors | Fix compatibility without disabling authentication |
| Repeated Pod restarts | Insufficient memory or an unsuitable probe | Inspect previous state and events | Correct 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.creationTimestampFor a specific restart, replace POD_NAME with the observed Pod name:
kubectl describe pod POD_NAME -n mcp
kubectl logs POD_NAME -n mcp --previous11. 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
| Concern | Development | Proposed production baseline |
|---|---|---|
| Deployment | Local or a single replica | Multiple replicas and zones |
| Data | Synthetic | Classification and object-level authorization |
| Identity | Restricted test credentials | Individual identity and rotation |
| Observability | Simplified diagnostics | Audit, SLOs, and alerts |
| Changes | Isolated experimentation | Pipeline, 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:
- Only intended consumers can reach it.
- Credentials are validated for the correct resource.
- Every operation respects identity, tenant, and object boundaries.
- External content cannot change permissions or approved destinations.
- 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.
Related articles
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 callGet new articles by email
One email when a new article on infrastructure, backend and AI is published. No spam, unsubscribe anytime.