Guide · Infrastructure
MCP server service mesh
A service mesh is a dedicated infrastructure layer for service-to-service communication. It moves cross-cutting concerns — mutual TLS, retries, timeouts, circuit breaking, traffic shaping, distributed tracing — out of application code and into sidecar proxies that run alongside each service. For MCP servers deployed on Kubernetes alongside other microservices, a service mesh lets you apply and audit these policies consistently without modifying the MCP server code at all.
TL;DR
Deploy Linkerd (simpler) or Istio (more features) as your sidecar mesh. Mutual TLS between pods is automatic after mesh injection — no certificate management in application code. Define VirtualService (Istio) or ServiceProfile (Linkerd) resources to set retry budgets, timeout policies, and circuit-breaker thresholds at the mesh layer without changing Node.js code. Propagate trace context (traceparent header) through MCP tool calls to get end-to-end distributed traces. Use AliveMCP to probe from outside the mesh — mesh health does not imply MCP application health.
Why a service mesh for MCP servers
MCP servers often live in a broader microservices environment: they call upstream APIs, write to databases, enqueue background jobs. When those dependencies are also services in the same cluster, service mesh provides:
- Zero-trust networking — mTLS between every pod pair without managing certificates in each service. An MCP server that calls an internal search API cannot be impersonated by a compromised pod.
- Consistent retry and timeout policies — define once in a Kubernetes CRD, enforced for all traffic between specific services without touching code.
- Traffic observability — golden signals (request rate, error rate, latency percentiles) for every service-to-service call from the sidecar, not from application instrumentation.
- Canary and blue-green deployments — weight traffic between versions at the mesh layer, enabling gradual MCP server rollouts.
The tradeoff: mesh sidecars add per-pod CPU and memory overhead, and the configuration surface is large. For a single-server deployment, the complexity cost exceeds the benefit. Mesh makes sense when you have three or more services that call each other and you want uniform policy.
Mutual TLS with Linkerd
Linkerd injects the linkerd-proxy sidecar automatically when namespaces or pods are annotated. mTLS between pods is on by default — no configuration required beyond enabling the annotation.
# namespace annotation — all pods in this namespace get Linkerd sidecars
apiVersion: v1
kind: Namespace
metadata:
name: mcp-services
annotations:
linkerd.io/inject: enabled
---
# MCP server deployment — sidecar injected automatically
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
namespace: mcp-services
spec:
replicas: 3
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
containers:
- name: mcp-server
image: your-registry/mcp-server:latest
ports:
- containerPort: 3000
readinessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
After injection, traffic between mcp-server and internal dependencies is automatically mTLS-encrypted. Use linkerd viz stat deploy to see request rate, success rate, and latency for every deployment without any application-layer instrumentation.
Traffic policies with Istio VirtualService
Istio's VirtualService resource defines routing rules, retries, and timeouts for traffic to a specific service. You can set retry policies for calls from the MCP server to upstream dependencies at the mesh layer — the application code does not need its own retry logic if the mesh handles it (though application-level retry logic remains valuable for request idempotency semantics the mesh cannot know about).
# VirtualService — retry + timeout policy for the search-api dependency
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: search-api
namespace: mcp-services
spec:
hosts:
- search-api.mcp-services.svc.cluster.local
http:
- retries:
attempts: 3
perTryTimeout: 5s
retryOn: gateway-error,connect-failure,retriable-4xx,503
timeout: 20s
route:
- destination:
host: search-api.mcp-services.svc.cluster.local
port:
number: 8080
The retryOn: retriable-4xx flag retries HTTP 429 (Too Many Requests) — Istio understands this as a transient rate-limit response. Set a total timeout (here 20s) to cap the worst case across all retry attempts.
Circuit breaking at the mesh layer
Istio's DestinationRule defines connection pool and outlier detection settings that implement circuit-breaker behaviour at the mesh layer:
# DestinationRule — outlier detection for the search-api dependency
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: search-api
namespace: mcp-services
spec:
host: search-api.mcp-services.svc.cluster.local
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
http2MaxRequests: 100
outlierDetection:
consecutive5xxErrors: 5 # eject endpoint after 5 consecutive 5xx
interval: 10s # evaluation window
baseEjectionTime: 30s # initial ejection duration
maxEjectionPercent: 100 # eject all endpoints if all are failing
Mesh-layer circuit breaking is coarser than application-layer circuit breakers implemented with Opossum. Istio tracks failures per endpoint (pod) in the connection pool; Opossum tracks failures per logical dependency function. Use both: Istio ejection protects against a specific bad pod; Opossum detects that the entire search API cluster is degraded regardless of pod.
SSE and long-lived connections in a mesh
MCP SSE transport uses long-lived HTTP connections. Some mesh sidecar defaults will terminate or interfere with connections that exceed a configurable idle timeout. Ensure your mesh is configured to allow long-lived connections on the MCP port.
For Istio, set a longer idle timeout on the VirtualService for the MCP server's own ingress:
# VirtualService — allow long-lived MCP SSE connections
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: mcp-server-ingress
namespace: mcp-services
spec:
hosts:
- mcp-server.mcp-services.svc.cluster.local
http:
- match:
- uri:
prefix: /sse
- uri:
prefix: /mcp/stream
timeout: 0s # 0 = no timeout — allow SSE connections to persist indefinitely
route:
- destination:
host: mcp-server.mcp-services.svc.cluster.local
port:
number: 3000
- timeout: 30s # other routes: 30-second timeout
route:
- destination:
host: mcp-server.mcp-services.svc.cluster.local
port:
number: 3000
The timeout: 0s on SSE routes prevents the sidecar from terminating sessions mid-stream. Match the same exemption pattern in your load balancer and compression middleware for consistency.
Distributed tracing with OpenTelemetry
Service mesh sidecars emit trace spans for each network hop automatically. To stitch those spans into a complete trace that includes tool execution time, the MCP server must propagate the incoming trace context and create child spans for its own work.
// tracing.ts — propagate W3C traceparent through MCP tool calls
import { trace, context, propagation } from '@opentelemetry/api';
const tracer = trace.getTracer('mcp-server');
server.tool('search', searchSchema, async (params, extra) => {
// Extract trace context from the HTTP request headers
const carrier = extra.headers ?? {};
const ctx = propagation.extract(context.active(), carrier);
return context.with(ctx, () =>
tracer.startActiveSpan('search_tool', async span => {
try {
span.setAttributes({ 'tool.name': 'search', 'query.length': params.query.length });
const result = await deps.searchBreaker.fire(params.query);
span.setStatus({ code: 1 }); // OK
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: 2, message: String(err) }); // ERROR
throw err;
} finally {
span.end();
}
})
);
});
With propagation in place, a single user request that triggers an MCP tool call produces a trace: ingress gateway → mcp-server → search-api, with each hop timed and correlated. See MCP server tracing for the full OpenTelemetry setup.
Canary deployments for MCP server updates
Updating an MCP server that has live sessions in progress is risky: an in-flight tool call interrupted by a pod restart fails. Canary deployments let you route a small percentage of new sessions to the new version while existing sessions drain on the old version.
# VirtualService — 10% canary traffic to new MCP server version
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: mcp-server
namespace: mcp-services
spec:
hosts:
- mcp-server.mcp-services.svc.cluster.local
http:
- route:
- destination:
host: mcp-server.mcp-services.svc.cluster.local
subset: stable
weight: 90
- destination:
host: mcp-server.mcp-services.svc.cluster.local
subset: canary
weight: 10
---
# DestinationRule — define stable and canary subsets by label
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: mcp-server
namespace: mcp-services
spec:
host: mcp-server.mcp-services.svc.cluster.local
subsets:
- name: stable
labels:
version: stable
- name: canary
labels:
version: canary
Watch AliveMCP's probe success rate on both the stable and canary deployments simultaneously during the rollout. A drop in the canary success rate while the stable rate holds is a clear signal to roll back before increasing canary traffic. See MCP Server Resilience Guide for how to coordinate canary traffic with feature flags to limit the blast radius of changes.
AliveMCP probes and mesh observability
Service mesh metrics (Kiali, Grafana, Linkerd Viz) show internal service-to-service traffic. They do not show what an external client experiences — latency from the public internet to the MCP server, TLS handshake time at the ingress, or whether the MCP initialize handshake succeeds end-to-end.
AliveMCP probes from external infrastructure, traversing the full path: DNS → ingress → gateway → mesh → application. A mesh metric that shows 0% error rate internally can coexist with 100% client-facing failure if the ingress is misconfigured. Use both: mesh metrics for internal policy enforcement and performance, AliveMCP for end-to-end availability from the client's perspective.