How to Harden MCP: A Practical Engineering Playbook (Part-2)
Concrete blueprints for secure MCP servers: authenticate, scope, sanitise and audit every model I/O.
Part-1 framed the risk: MCP turns models into networked agents that can be poisoned, tricked or used to exfiltrate data. Now we build defences: a hardened MCP architecture that enforces deterministic policy and auditable behaviour.
High-Level Architecture:
The hardened stack has five components:
(1) MCP API Gateway (auth, rate limits, allowlists),
(2) Context Ingestor (sanitiser & provenance checks),
(3) Vector/Index Store (tagged, risk-scored),
(4) MCP Tool Runner (least-privilege runtime for side-effects),
(5) Output Post-Processor (deterministic filters & scrubbers).
Every request carries a correlation ID and is logged to an immutable (WORM: Write Once Read Many) audit trail. This architecture maps directly to MCP implementations and to MITRE/NIST recommendations for layered defences.
MCP API Gateway - Example YAML/Configs:
Purpose: Authenticate clients (models), enforce per-tenant policy and apply pre-flight checks.
Example Gateway YAML:
mcp_gateway:
host: 0.0.0.0
port: 8443
auth:
mode: mTLS
trusted_certs_path: /etc/mcp/ca.pem
jwt:
issuer: https://auth.example.com
jwks_uri: https://auth.example.com/.well-known/jwks.json
rate_limits:
tenant_default_rpm: 600
allowlists:
allowed_mcp_servers:
- mcp.internal-payroll.svc.cluster.local
- mcp.knowledgebase.svc.cluster.local
headers:
required: [”X-Trace-ID”, “X-MCP-Client”]
max_payload_bytes: 5242880 # 5 MB
env:
- name: MCP_ALLOWED_DOMAINS
value: “internal-payroll.svc.cluster.local,knowledgebase.svc.cluster.local”Key Configs & Headers to enforce at runtime:
MCP_ALLOWED_DOMAINS - comma separated list of allowed MCP endpoints (deny by default).
MAX_MEDIA_SIZE - max bytes for attachments.
TOKEN_BUDGET_PER_REQUEST - token cap heuristic for downstream model calls.
HTTP Headers required from the model/runtime:
X-Trace-ID: <uuid>
X-MCP-Client: <model-name>/<version>
Authorisation: Bearer <jwt> or TLS client cert
Context Ingestor - Sanitisation & Provenance:
Goals: Reject or down-score suspicious inputs before they enter the index. Use deterministic checks (no ML-based “maybe” blocks only).
Sanitisation Pipeline Steps:
Strip/normalise rich text (remove <script>, onerror, CSS).
Remove or canonicalise HTML link query parameters.
Remove EXIF metadata for images.
Disallow formats with embedded execution (e.g. macros).
Check source provenance: known internal repo vs external upload. Tag risk score.
Example Python-Style Sanitiser Skeleton:
from html_sanitizer import sanitize
def ingest_document(doc, source):
clean_text = sanitize(doc.html, allowed_tags=[’p’,’b’,’a’])
# strip query params from links
clean_text = rewrite_links(clean_text, drop_query=True)
# image EXIF strip
if doc.is_image:
doc = strip_exif(doc)
risk = compute_provenance_score(source)
if risk > 8: # 0-10 scale
reject_document()
index_document(clean_text, metadata={’risk’:risk})Vector Store & Retrieval Policies:
Index with provenance metadata: every embedding stores source_id, risk_score, ingest_user and ingest_time.
Retrieve with allowlist/denylist: retrieval API must accept corpus_ids and a disallow_sources param.
Down-ranking of high-risk docs: when risk_score > threshold, exclude from top-k unless human override.
Example Query:
POST /retrieve
{
“query”: “...”,
“corpus_ids”:[”kb-finance”],
“disallow_sources”:[”mailbox”,”public_web”],
“max_k”:5
}Tool Runner - Least Privilege Execution:
Execute side-effecting tools in sandbox accounts e.g. read-only service account, separate audit logs.
Require an explicit manifest for each tool: allowed arguments, rate limits and resource scope.
Dry-run default for destructive verbs, require reviewer approval for irreversible operations.
Example Tool Manifest YAML Snippet:
tool: send_email
allowed_scopes:
- domain: example.com
arg_schema:
to: string
subject: string
body: string
mode: dry-run
reviewer_group: security_opsOutput Post-Processor - Deterministic Filters & Logging:
Rewrite or drop external links unless allow-listed.
Strip image URLs / prevent client prefetch for sensitive workflows.
Deny answers that contain high-risk patterns e.g. “print all secrets”, private hostnames.
Log every output with X-Trace-ID and make logs immutable (WORM storage for audits).
Test & CI - Red-Teaming MCP Interactions:
Integrate automated red-team checks into CI: inject crafted docs into the ingestion pipeline and assert “retrieval + post-processing” blocks or neutralises them.
Example checks: attempt to surface a document with “ignore previous instructions” patterns and assert that retrieval returns zero results or that post-processor strips the offending content.
Next in the Series:
In Part-3, we zoom out: governance, risk metrics, KPIs, procurement controls and how executives should embed MCP risk into quarterly reviews.
We’re FortifyRoot - the LLM Cost, Safety & Audit Control Layer for Production GenAI.
If you’re facing unpredictable LLM spend, safety risks or need auditability across GenAI workloads - we’d be glad to help.


