Skip to main content
MCP Security Hardening for LLM DeploymentsResearch
4 min readFor Security Engineers

MCP Security Hardening for LLM Deployments

Research presented at RSAC 2026 highlights a significant issue: Model Context Protocol (MCP) introduces architectural vulnerabilities in LLM environments that can't be resolved with patches. Unlike a library with a CVE, these risks arise from how MCP connects your LLM to external data sources and tools.

This isn't about waiting for a vendor fix. You need a security architecture that accounts for MCP's design from the start.

The Problem: Architecture vs. Implementation

Traditional security models assume you can patch vulnerabilities. Find the bug, apply the fix, move on. MCP breaks that model because the risk is embedded in the architecture itself.

MCP acts as a bridge between your LLM and external resources, handling context injection, tool invocation, and data retrieval. The protocol's design requires your LLM to trust whatever MCP surfaces, creating an inherent trust boundary issue. An attacker who compromises an MCP server can feed malicious context directly into your model's decision-making process.

You're not dealing with a buffer overflow or an injection flaw. You're dealing with a protocol that, by design, lets external systems influence your LLM's behavior. That's an architectural choice, not a bug.

What You Need Before Starting

Before implementing MCP hardening, ensure you have:

  • Inventory of MCP servers: List every MCP server your LLM environment connects to, including third-party integrations.
  • Network segmentation capability: Isolate MCP traffic from production networks.
  • Logging infrastructure: Centralized logging that can handle high-volume LLM query logs (plan for 10-50GB daily per production LLM).
  • Authentication system: OAuth 2.0 or similar that supports scoped tokens.
  • Container runtime: If you're running MCP servers, you need container isolation (Docker, containerd, or similar).

Step-by-Step Implementation

1. Implement MCP Server Isolation

Don't run MCP servers in your production LLM namespace. Create a dedicated network segment:

# Create isolated network for MCP servers
kubectl create namespace mcp-isolation
kubectl label namespace mcp-isolation security-zone=mcp-bridge

# Apply network policy to restrict egress
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-server-isolation
  namespace: mcp-isolation
spec:
  podSelector:
    matchLabels:
      role: mcp-server
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          security-zone: llm-runtime
    ports:
    - protocol: TCP
      port: 443
EOF

This limits MCP servers to only communicate with your LLM runtime, not arbitrary external services.

2. Enforce Context Validation

MCP servers return context that your LLM consumes. You need a validation layer between MCP responses and your model:

# Example validation middleware
class MCPContextValidator:
    def __init__(self, max_context_size=8000, allowed_domains=None):
        self.max_context_size = max_context_size
        self.allowed_domains = allowed_domains or []
    
    def validate_context(self, mcp_response):
        # Size check
        if len(mcp_response.get('content', '')) > self.max_context_size:
            raise ValidationError("Context exceeds size limit")
        
        # Domain validation for URLs
        urls = self.extract_urls(mcp_response)
        for url in urls:
            if not any(domain in url for domain in self.allowed_domains):
                raise ValidationError(f"Unauthorized domain: {url}")
        
        # Schema validation
        if not self.validate_schema(mcp_response):
            raise ValidationError("Invalid MCP response schema")
        
        return True

Deploy this validator as a sidecar container alongside your LLM runtime.

3. Implement Scoped Authentication

Each MCP server should authenticate with minimal permissions:

# MCP server service account with limited scope
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mcp-server-limited
  namespace: mcp-isolation
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: mcp-server-role
  namespace: mcp-isolation
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]
  resourceNames: ["mcp-config"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: mcp-server-binding
  namespace: mcp-isolation
subjects:
- kind: ServiceAccount
  name: mcp-server-limited
  namespace: mcp-isolation
roleRef:
  kind: Role
  name: mcp-server-role
  apiGroup: rbac.authorization.k8s.io

Don't grant MCP servers cluster-admin or broad read permissions. They should only access resources needed for their specific function.

4. Log All MCP Interactions

Enable comprehensive logging at the MCP boundary:

# Logging wrapper for MCP calls
import logging
import hashlib

logger = logging.getLogger('mcp_security')

def log_mcp_interaction(mcp_server, request, response, user_id):
    request_hash = hashlib.sha256(str(request).encode()).hexdigest()[:16]
    
    logger.info({
        'event': 'mcp_query',
        'server': mcp_server,
        'request_hash': request_hash,
        'response_size': len(str(response)),
        'user_id': user_id,
        'timestamp': datetime.utcnow().isoformat()
    })

Ship these logs to your SIEM. Look for patterns like unusual response sizes, repeated failures, or access from unexpected user contexts.

Validation: How to Verify It Works

Run these checks weekly:

Test isolation: Attempt to access production databases from your MCP namespace. This should fail:

kubectl run -it --rm debug --image=postgres:latest \
  --namespace=mcp-isolation \
  --command -- psql -h production-db.default.svc.cluster.local
# Should timeout or be denied

Verify logging coverage: Check that every MCP interaction generates a log entry:

# Query your log aggregator
kubectl logs -n mcp-isolation -l role=mcp-server | grep "mcp_query" | wc -l
# Compare to application-level MCP call count

Test context injection: Send a test payload with oversized context through your validator. It should reject:

curl -X POST https://your-llm-api/query \
  -H "Content-Type: application/json" \
  -d '{"context": "'$(python -c 'print("A"*10000)')'"}'
# Should return 400 with validation error

Maintenance and Ongoing Tasks

Monthly: Review MCP server access logs for anomalies. Look for new domains, unusual query patterns, or authentication failures.

Quarterly: Update your allowed domain list. Remove MCP servers you're no longer using. Audit service account permissions.

After any LLM deployment change: Re-run isolation tests. Changes to your LLM runtime can inadvertently expand MCP server permissions.

When onboarding new MCP servers: Don't grant production access immediately. Run the server in a staging environment for two weeks, review its behavior, then promote with minimal permissions.

The architectural nature of MCP security means you can't set this up once and forget it. Your security posture depends on continuous validation that the boundaries you've established remain intact.

Topics:Research

You Might Also Like