Multi-tenant MCP memory isolation
A practical guide to keeping one tenant’s data, memory and model context out of another tenant’s answers in a hosted MCP server.
Why MCP needs its own isolation design
A Model Context Protocol server sits between a model and your data. In a hosted, multi-tenant setup one process answers many organisations, and the caller is a language model that writes its own tool arguments. That changes the threat model: any value the model can type, the model can also get wrong, or be talked into changing by text inside a document it just read.
So the rule for a multi-tenant MCP server is simple to state: the tenant comes from authentication, and every read is checked against it. The sections below turn that into concrete steps.
1. Bind the tenant at the session, not in tool arguments
Resolve the tenant once, when the connection is authenticated: from the OAuth access token subject and its granted scope, or from a per-user connector URL whose secret maps to exactly one identity. Store it on the transport session (for Streamable HTTP, alongside the Mcp-Session-Id) and pass it to tool handlers from there.
Tool input schemas should carry no tenant_id, business_id or workspace field. If a tool needs to address one of several businesses a user may see, accept a reference and resolve it against the list that session is allowed to see. An unknown or foreign reference returns the same error as a missing one, so the response leaks nothing.
2. Assert tenant ownership on every retrieve
Filtering queries by tenant is necessary, and a second check after the read catches the bugs a filter misses: a forgotten WHERE clause, a shared cache key, a vector index queried without its namespace. After loading any row, chunk or embedding hit, assert row.tenant_id == session.tenant_id before it reaches the model. A failed assertion drops the row and logs an alert.
Apply the same check to search results, file reads, resource reads and anything a tool returns by ID. Retrieval-augmented answers are where cross-tenant leaks usually surface, because a ranked list mixes sources by similarity, not by owner.
3. Keep memory and workspaces per tenant
Conversation memory, learned answers and per-customer notes are tenant data too. Key them by tenant and by end user, and load only the memory of the user in the current session. Memory written in one customer’s conversation is read only in that customer’s conversations.
Where the server calls a model itself, give each tenant its own model workspace or API project and short-lived credentials. Usage, rate limits and data retention then follow tenant lines, and one tenant’s traffic stays out of another’s logs.
4. Make separation physical where you can
Row-level filters in one shared database work, and a separate database file per tenant removes a whole class of mistakes: a query opened on one tenant’s file has no other tenant’s rows to return. Encrypting each tenant’s files and backups with its own key keeps the same line on disk.
How Avenbridge does it
Avenbridge is a hosted knowledge MCP: a business forwards its emails and documents, confirms its answers, and buyers ask them from their own AI through MCP at https://mcp.avenbridgetech.com/mcp. Each business has one space: its own database, encryption key and model workspace, and each response is checked against its business.
Business data lives in a separate database file per business. Files and backups are encrypted with each business’s own key. Model access uses an own Claude workspace per business with short-lived tokens. Customer memory is used only in that customer’s conversations. Each answer sits in an access tier you choose: public, verified customers, or customer groups. Usage records hold no question text and no email addresses.
The quickest way to see the session-bound model is to add Avenbridge to Claude: the try link gives you a personal connector address, and every tool call on it runs as that visitor only.
Checklist
Tenant resolved from OAuth subject or connector secret, stored on the session. No tenant field in any tool schema. Ownership asserted after every read. Memory keyed by tenant and user. One model workspace and key per tenant. Separate storage per tenant where practical. Cross-tenant tests that try to read another tenant by ID, by search and by prompt injection.
Last updated: 31 October 2026