Quick Answer
Explore a productionâready multi-tenant MCP server design that balances isolation, security, and Azureâscale for modern SaaS platforms.
OneâTenantâPerâProcess Myth Crumbles
In a production SaaS that started with a handful of customers, the temptation is to spin a single LLM worker per tenant. That model looks clean, but it hides a cascade of hidden costs and failure surfaces. When a 10,000âtenant platform goes live, the naive isolation strategy turns into a maintenance nightmare:
- Each tenantâs process consumes a fixed 2 GiB of RAM, so the cluster swells linearly with users.
- Process creation is expensive (hundreds of milliseconds), inflating coldâstart latency for every new tenant.
- Thereâs no shared cache, so prompt embeddings and token counters are duplicated per tenant, blowing up storage and increasing read latency.
- GPU sharing becomes a bottleneck; a single tenant that spikes traffic can hog the GPU, starving others.
These symptoms surface as crossâtenant latency spikes, unpredictable GPU utilization, and cost overruns that are hard to trace back to a single tenant.
RealâWorld Example: A 12âMonth RollâOut of a 10KâTenant MCP Platform on Azure
Company X built an AIâfirst SaaS that needed a multiâtenant MCP server to serve custom chatbot workflows. The stack was:
-
Frontâend: Azure Front Door + WAF, JWT issued by Azure AD B2C (claim
tid). - Orchestration: Azure Container Apps (ACA) with a shared stateless worker pool.
- Worker: .NET 8 microservice, each container limited to 2 GiB memory and 1 vCPU.
- Perâtenant Redis Enterprise for quota, prompt cache, and API key rotation.
- Perâtenant Azure Cognitive Search index for vector similarity.
- LLM: Azure OpenAI gptâ4âturbo, API key stored in Key Vault and cached per container.
Key metrics after 12 months:
| Metric | Value |
|---|---|
| p95 request latency | 420 ms |
| Token cost reduction via caching | 32 % |
| Peak concurrent tenants | 9,800 |
| GPU memory per tenant | †12 % of total |
| Onâcall incidents due to quota bleed | 0.2 % |
These numbers are attainable only when isolation is baked into the MCP envelope, the runtime, and the Observability stack from day one.
TradeâOffs: Process vs Thread vs Container Isolation
Choosing the right isolation level is a classic design decision that trades security, cost, and performance. Below is a pragmatic comparison that reflects what we saw in production:
| Isolation | Memory Footprint | ColdâStart | Security Boundary | Cost / Scale |
|---|---|---|---|---|
| Process per tenant | 2 GiB + overhead | â 200 ms | Strong, but no shared cache | Linear scaling; high ACI cost |
| Thread pool per tenant | Shared 2 GiB + perâthread stack (256 kB) | â 50 ms | Weaker â static state can leak | Better than process, but still high perâtenant memory |
| Container per tenant (ACA) | 2 GiB + container overhead (~10 %) | â 30 ms | Strong â cgroup limits + network isolation | Excellent costâtoâperformance; autoâscale per region |
In practice, container isolation with a shared stateless worker pool hits the sweet spot: it gives you a hard memory boundary, lets you reuse prompt caches, and keeps coldâstart latency low enough for interactive workloads.
Tenant Size, Isolation, and Throttling
- Define tenant size & burst profile. If a tenant can generate > 10 k requests per minute, you need hard cgroup limits.
- Choose container isolation if: you need perâtenant GPU quotas, want to use Azure Managed Identities per tenant, and want to keep cost linear.
- Use shared stateless workers only if: you have a very high tenant density (â„ 50 k) and can tolerate shared cache pollution.
- Implement a twoâtier throttling model: soft tier in Redis (leaky bucket) + hard tier via cgroup limits.
- Automate scaling: use ACAâs eventâdriven scaling on Azure Metrics (CPU, request count) and set min/max per region.
- Observability: instrument perâtenant latency, token usage, cache hit ratio, and throttle events.
-
Security: always validate
tenantIdin the MCP envelope against the JWTtidclaim before any downstream call.
When This Fails in Production
- Stale quota cache: A 5âminute TTL on the Redis key that tracks perâtenant GPU quota caused a burst of traffic from a new tenant to exceed the GPU budget. The OOM killer killed a container belonging to a different tenant, leading to a cascade of 429s.
-
Prompt injection via function names: A malicious tenant sent a function called
DeleteAllKeys. The worker matched it against a shared helper and executed it, wiping the tenantâs own Redis namespace. - Container memory fragmentation: Over time, the cgroup memory limit was hit, but the OOM killer chose a container with a longârunning background task instead of the one that was actively using GPU memory, causing unrelated tenant requests to fail.
- Telemetry overload: Perârequest logs were sent to Azure Log Analytics without sampling, pushing the cost 3Ă and saturating ingestion pipelines.
- Key Vault throttling: Fetching perâtenant API keys on each request hit the 5 k req/s limit, causing 503s from Key Vault.
Common Mistakes Engineers Make
- Embedding
tenantIdonly in HTTP headers, not in the MCP envelope; downstream services that rely on the MCP can bypass the header check. - Using a single Redis database for all tenants without key prefixes; this leads to accidental data leakage and cache stampedes.
- Not configuring
memory.highandmemory.lowcgroup thresholds; the OOM killer ends up killing the wrong container. - Ignoring the cost of perâtenant Azure Cognitive Search indexes; a naive oneâindexâperâtenant strategy can explode the index count and storage cost.
- Failing to sample telemetry; sending every request payload to Log Analytics inflates costs and hampers realâtime alerting.
Better Approach Based on Experience
-
Enforce tenant identity in the MCP contract. The
tenantIdfield must be signed by the same JWT that authorizes the request. Reject any request where the two do not match. -
Use perâtenant Redis databases. Allocate a dedicated database per region and use
DB 0âNfor each tenant; this isolates cache traffic and simplifies key rotation. - Cache API keys in Redis with a 30âsecond TTL. This eliminates Key Vault throttling while still allowing key rotation.
- Leverage Azure Managed Identities per tenant. Grant each container only the secrets it needs; this reduces blast radius if a container is compromised.
-
Apply a hybrid cache strategy. Store common prompts in a shared Redis cache keyed by a hash of
tenantId+promptHash, but keep tenantâspecific embeddings in a perâtenant vector store. - Implement dynamic scaling rules that trigger on perâtenant request rate spikes; use Azure Monitor alerts to spin up additional containers before the GPU becomes saturated.
- Enable structured logging with sampling. Use a 5 % sample for nonâerror paths and ship only the essential fields (tenantId, latency, token count).
Performance Considerations & Scaling Notes
- Tokenization overhead: Offload tokenization to a preâwarmed .NET worker; avoid perârequest Python calls to the OpenAI tokenizer.
-
GPU scheduling: Use Azure Batch or Kubernetes GPU nodes with fairâshare scheduling; set
GPU_MEMORY_LIMITper container to enforce hard quotas. - Cold start mitigation: Preâwarm 5 % of containers per region and keep a pool of âwarmâ containers that can be promoted to handle a burst.
-
Autoscaling thresholds: Set CPU
>80âŻ%and request rate>200âŻrpsper container as the trigger; keep a buffer of spare containers to absorb sudden spikes. -
Latency budgets: Target
p95âŻ<âŻ500âŻmsfor userâfacing requests; if the GPU latency exceeds 300 ms, fall back to a lowerâcost model (e.g., gptâ3.5). - Cost per token: Monitor token cost per tenant and autoâscale the GPU quota when a tenant exceeds 80 % of its allocated budget.
Checklist for Shipping a MultiâTenant MCP Service
- Embed
tenantIdandpolicyVersionin the MCP envelope. - Validate JWT
tidagainst the MCPtenantId. - Configure cgroup limits:
memory.limit_in_bytes,memory.high,memory.lowper container. - Deploy Redis Enterprise with dedicated databases per region; store quota, prompt cache, and API keys.
- Set up Azure Front Door with WAF rules that block oversized payloads (> 8 KB).
- Instrument OpenTelemetry: perâtenant latency, token usage, cache hit/miss, throttle events.
- Automate integration tests that simulate 10 k concurrent tenants with mixed workloads.
- Enable rolling deployments in ACA; healthâcheck must verify tenantâspecific key access.
- Establish a costâmonitoring dashboard that alerts when a tenant exceeds 80 % of its token budget.
- Document the failureâmode checklist (stale cache, OOM kills, telemetry overload, etc.) for onâcall engineers.
Conclusion: Isolation as a FirstâClass Design Principle
In a multiâtenant MCP server, isolation is not an afterâthought; it must be encoded in the protocol, enforced in the runtime, and surfaced in observability from day one. The tradeâoffs between process, thread, and container isolation are clear when you look at memory, cost, and security. By following the decision guide above, you can build a platform that scales to tens of thousands of tenants, keeps GPU usage predictable, and stays within a tight cost envelopeâall while remaining maintainable and auditable.
Related Articles
- AI Orchestration for Enterprise .NET Applications: Scaling Intelligent Agents with Azure
- Designing a Multi-Tenant KV-Cache Layer in ASP.NET Core for Scalable Inference Serving
- Observability for LLM Apps in ASP.NET Core: Trace First, Metrics
- Designing a Distributed Task Queue Architecture for Code Execution at Scale
- FineâTune vs Prompt vs RAG Decision Framework for .NET Teams â Choose the Right LLM Strategy

