Zenaique

Design a multi-tenant remote MCP server that exposes each customer's Postgres database. Walk through transport, auth, isolation, and observability.

Short answer·Hard·4.0 · 0·~3 min·Asked atCloudflareEyTruera·Relevant atAnthropicMicrosoftOpenAI
Attempt it

Design a multi-tenant remote MCP server that exposes each customer's Postgres database. Walk through transport, auth, isolation, and observability.

Free · 2 AI evals / day
TL;DR

Streamable HTTP, OAuth 2.1 with tenant_id claim, per-tenant DB credentials and async contexts, token-bucket rate limits per (tenant, tool), OTEL spans tagged per tenant. MCP provides none of this.

Memory aid
Sign in to see the mnemonic that makes this stick.
Easy to grasp

Imagine a building where many companies share one management office. Each company has its own locked room with its own key, its own water meter, and its own usage cap. The front desk checks every visitor's badge before letting them in, writes their name on a log with the company they belong to, and only lets them visit their own room. If one company starts running loud equipment that slows the elevator for everyone, the building manager notices because each floor has its own speed meter. Some situations force a remodel: one company wants a custom lobby sign that confuses the directory, another company's government says their mail must stay in a different building, and sometimes a visitor's task takes so long the front desk forgets they came in. A multi-tenant MCP server is the same building with code in place of doors and meters.

Concept explanation~2 min read

Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.

A multi-tenant remote MCP server that exposes each customer's Postgres database sits at the intersection of two high-risk properties: untrusted LLM tool calls touching a sensitive data store, and multiple tenants sharing infrastructure on a protocol that deliberately provides no isolation, sandboxing, or rate limiting.

The design requires six load-bearing layers. Transport is streamable HTTP because stdio cannot serve remote clients. Auth is OAuth 2.1 with PKCE plus a tenant_id claim on the access token, validated on every request. Isolation is per-tenant Postgres credentials, per-tenant connection pools, and per-tenant async execution contexts. Rate limiting is a token bucket keyed on (tenant_id, tool_name) with JSON-RPC error -32000 plus retry_after on rejection. Observability is two separate OpenTelemetry spans (JSON-RPC handler and Postgres call), both tagged with tenant_id and request_id. And a set of failure modes that will force redesign: dynamic per-tenant tool descriptions defeating client caching, data residency requiring regional splits, and long-running tools exceeding host timeouts.

This walkthrough covers each layer, names the anti-patterns each one prevents, and addresses two protocol-level gaps (rug-pull on tool definitions, prompt injection from query results) that the spec leaves entirely to the server implementer.

Transport and auth: streamable HTTP plus OAuth 2.1 with tenant binding

MCP supports two transport families: stdio and HTTP. Stdio is process-local, meaning the host spawns the server as a subprocess on the same machine. It cannot serve remote clients over a network. For a multi-tenant remote server, streamable HTTP is the only option. The 2025 spec's streamable HTTP profile is production-relevant in 2026 because it allows long tool calls to push incremental chunks back to the host instead of holding the connection silent until completion. This matters operationally: hosts have silent timeouts you cannot control, and a 90 second Postgres query without progress output can be killed mid-execution by the host.

Auth is where most multi-tenant MCP designs fail early. The correct baseline is OAuth 2.1 with PKCE for human users and bearer tokens for service to service. The access token must carry a tenant_id claim. Every JSON-RPC request validates four things: token signature, token expiry, scope match for the requested tool, and tenant binding between the token's claim and the request's tenant identifier (URL path or header).

Three anti-patterns produce real incidents. Bearer-only auth without a tenant claim means a leaked token authorizes access to whichever tenant the attacker can guess. Long-lived tokens without refresh stay valid for months after compromise. Tenant inferred from the URL path without token validation means any caller can claim any tenant by changing the path. The OAuth 2.1 plus PKCE plus tenant-claim baseline eliminates all three.

Per-tool OAuth scopes are the second essential discipline. Each tool should declare the scope it requires (tables:read, tables:write, schema:read), and the server should reject calls where the token lacks the needed scope. Without this, every token becomes a universal credential for everything the tenant can do.

Isolation: per-tenant credentials, connection pools, and async contexts
Rate limiting: per-(tenant, tool) buckets and the error contract
Observability: two separate spans, both tagged per tenant
Failure modes and protocol-level pitfalls
Sign in to unlock the full deep dive.

Situations where this technique stops working.

Sign in to see when this approach fails.

2–4 min · Everything important, quickly.

Sign in to see the quick scan of the deep dive.

Real products, models, and research that use this idea.

  • Cloudflare Workers MCP runs remote servers at the edge with OAuth 2.1 auth and per-tenant isolation enforced by the Workers runtime, serving as the reference architecture for streamable HTTP deployment in 2026.
  • Vercel's MCP integration ships OAuth 2.1 with PKCE as the default for human users and bearer tokens for server to server, matching the spec's resource-server design.
Sign in to see more production examples.

What an interviewer would ask next. Try answering before peeking at the approach.

QHow would you handle a tenant that needs custom tool descriptions when most MCP clients aggressively cache tools/list?
A

Three options with tradeoffs. (a) Static tool superset for every tenant, with per-tenant gating that returns a permission error at call time. Works with every host cache but loses per-tenant naming. (b) Per-tenant URL endpoint so each tenant's cache is correctly partitioned, at the cost of operational fan-out. (c) Push a tools/list_changed notification on the connection, effective only for subscribing clients. Most production deployments start with (a) and add (b) for high-value enterprise tenants.

3 more follow-ups an interviewer would ask next. Sign in to reveal them.

Red flags & common mistakes

The phrases that signal junior thinking. Click to expand.

Most common mistake

Sharing a single Postgres superuser across tenants and scoping with SET ROLE at runtime. One SQL injection in any tool reads every tenant's data, collapsing the isolation model entirely.

Sign in to see all red flags and common mistakes.

60 second bullets to scan on the way to the call.

  • Name streamable HTTP as the transport and explain why stdio cannot serve remote multi-tenant traffic.

  • Specify OAuth 2.1 with PKCE for human users, bearer for services, with a tenant_id claim validated on every request.

Sign in to unlock the revision sheet.

Primary sources. Browse if you want the original framing.

Similar questions

Same topic, related formats. Practice these next.

4 curated
Next question
What is the Model Context Protocol (MCP) and what problem does it solve?
MCQ·Easy