|
| 1 | +--- |
| 2 | +title: "Multi-tenant MCP" |
| 3 | +description: "Install one governed MCP server per tenant, so each organization, workspace, or region keeps its own credentials, slug, and audit trail." |
| 4 | +guideCategory: "Build and host tools" |
| 5 | +--- |
| 6 | + |
| 7 | +import { Callout } from "@/mdx/components"; |
| 8 | + |
| 9 | +Vendor MCP servers often serve many customer organizations from one URL. A tenant might be an organization, a workspace, a region, or a customer account, and each one has its own credentials and its own data. Connecting every tenant through a single Control Plane server mixes credentials and audit trails. |
| 10 | + |
| 11 | +The pattern is one MCP server per tenant. Installing the same catalog entry again creates a second independent server. Each server stores its own upstream headers, has its own slug and access rules, and logs its own traffic. |
| 12 | + |
| 13 | +This guide covers repeating a catalog install, naming servers before the slug is locked, and two registry behaviors that look like extra tenants and are not. |
| 14 | + |
| 15 | +## One catalog entry, several servers |
| 16 | + |
| 17 | +Installing an entry that is already in the project is allowed. It creates another server rather than editing the existing one. The "already in this project" marker is informational. |
| 18 | + |
| 19 | +Each install is a full [remote MCP server](/docs/ai-control-plane/distribute/mcp-servers/remote-servers): |
| 20 | + |
| 21 | +- Its own upstream URL |
| 22 | +- Its own encrypted upstream headers |
| 23 | +- Its own visibility, team access, and plugin membership |
| 24 | +- Its own tool logs |
| 25 | + |
| 26 | +Two tenants on the same vendor deployment share one MCP URL. The second install is what separates them, not a second remote on the same catalog card. |
| 27 | + |
| 28 | +A catalog entry can also publish several remote endpoints. That is a different case: one install creates one server per selected endpoint, after discarding non-streamable-HTTP remotes and deduplicating by URL. Re-installing the entry is the path for two tenants that share an endpoint URL. |
| 29 | + |
| 30 | +The diagram below shows two tenants of one vendor, each with its own Control Plane server. Agents connect to the Control Plane URLs, not to the vendor directly. |
| 31 | + |
| 32 | +```mermaid |
| 33 | +flowchart LR |
| 34 | + subgraph vendor [Vendor] |
| 35 | + T1[Production tenant] |
| 36 | + T2[Staging tenant] |
| 37 | + end |
| 38 | + subgraph cp [Control Plane] |
| 39 | + S1[vendor-prod] |
| 40 | + S2[vendor-staging] |
| 41 | + end |
| 42 | + T1 --> S1 |
| 43 | + T2 --> S2 |
| 44 | +``` |
| 45 | + |
| 46 | +## Name the server at install |
| 47 | + |
| 48 | +The install dialog asks for a **Server name**. That name becomes the display name and seeds the URL slug. |
| 49 | + |
| 50 | +The display name can be renamed later from **Settings**. The URL slug is fixed at creation. Client configs, plugin installs, and custom-domain routes keep pointing at the original slug, so renaming the display name does not fix a poorly chosen slug. |
| 51 | + |
| 52 | +Pick a name that identifies the tenant before confirming the install. A name that combines the vendor with the tenant dimension that varies works well: |
| 53 | + |
| 54 | +- `datadog-us1-prod` |
| 55 | +- `github-acme-corp` |
| 56 | +- `linear-eu-staging` |
| 57 | + |
| 58 | +<Callout type="warning"> |
| 59 | + Changing an MCP server slug after creation is not supported. Recreating the |
| 60 | + server is the only way to get a different slug, and that issues a new |
| 61 | + endpoint URL. |
| 62 | +</Callout> |
| 63 | + |
| 64 | +## Credentials stay on the server |
| 65 | + |
| 66 | +The **configure** step of the install dialog collects upstream headers. Values are encrypted at rest and sent with every upstream request. Skip the step when the keys are not ready and fill them in later from the server's **Settings** tab. |
| 67 | + |
| 68 | +Each server holds its own header values. An API key stored on one tenant's server is never sent on calls through another tenant's server. |
| 69 | + |
| 70 | +Two authentication models fit a tenant-scoped server: |
| 71 | + |
| 72 | +| Tenant model | Use | Shared across callers? | |
| 73 | +| --- | --- | --- | |
| 74 | +| One tenant, people acting as themselves | OAuth, set up automatically when the upstream supports dynamic client registration | No, per end user | |
| 75 | +| One tenant, one service account | Static headers holding that tenant's API key | Yes, for that server only | |
| 76 | + |
| 77 | +Prefer OAuth where the upstream supports it, because the audit trail then answers who made a call. Static keys are the fallback for environments that cannot complete an OAuth flow. [Upstream credentials](/docs/ai-control-plane/distribute/mcp-servers/authentication/upstream-credentials) covers both. |
| 78 | + |
| 79 | +<Callout title="Header rows from the registry are definitions, not values" type="info"> |
| 80 | + Catalog installs copy the header names the registry publishes onto the |
| 81 | + server. Skipping values at install does not remove those rows. Empty rows can |
| 82 | + be filled later. A row deleted in **Settings** can reappear on the next |
| 83 | + source deploy, because the registry definition is written again. That is |
| 84 | + expected. It is not a saved secret coming back. |
| 85 | +</Callout> |
| 86 | + |
| 87 | +Authorization is never collected during catalog install. On servers that support dynamic client registration, automatic auth setup owns it. Everywhere else it belongs in the server's **Authentication** section. |
| 88 | + |
| 89 | +## Install once per tenant |
| 90 | + |
| 91 | +Adding the entry from the [catalog](/docs/ai-control-plane/connect/catalog) is the shortest path when the published URL already matches the deployment for that tenant. |
| 92 | + |
| 93 | +- Open **Catalog**, search for the vendor, and click **Add**. |
| 94 | +- Set **Server name** to a tenant-specific value. |
| 95 | +- Enter that tenant's credentials if the server authenticates with keys, or skip headers and complete OAuth afterward. |
| 96 | +- Confirm. Repeat the install for the next tenant. |
| 97 | + |
| 98 | +The by-URL path is the alternative when the catalog URL does not match the deployment for that tenant. Open **Connect > Sources**, choose **Add a custom remote MCP server**, and enter the resolved MCP URL. [Govern a third-party MCP server](/docs/ai-control-plane/guides/govern-a-third-party-mcp-server) walks that form. |
| 99 | + |
| 100 | +After each server exists, add it to a plugin or share its install page the same way as any other remote server. |
| 101 | + |
| 102 | +## Registry caveats |
| 103 | + |
| 104 | +Two properties of a catalog entry look like extra tenants and are not. |
| 105 | + |
| 106 | +### Duplicate remotes share one URL |
| 107 | + |
| 108 | +An entry can publish two remotes at the same streamable-HTTP URL, one for OAuth and another for API-key headers. The install dialog collapses those to a single checkbox, matching deploy-time behavior. The first matching URL wins. The second variant is not a second server. |
| 109 | + |
| 110 | +OAuth versus API keys is configured on the created server, not by selecting both remotes. One install still produces one server. |
| 111 | + |
| 112 | +### Templated URLs are not filled in at install |
| 113 | + |
| 114 | +Registry remotes can include URL template variables, such as a region or site. The install dialog collects headers, not those variables. The URL stored on the server is the URL the registry published. |
| 115 | + |
| 116 | +If that URL still contains a placeholder, or points at the wrong region for the tenant, the proxy cannot reach the right tenant. Register the server by URL with the fully resolved endpoint, or edit **Upstream URL** on the server's **Settings** tab after install. |
| 117 | + |
| 118 | +Entries that publish distinct paths per region are different. Those URLs are already concrete, so selecting several endpoints in one install is correct. |
| 119 | + |
| 120 | +## Example: one server per Datadog organization |
| 121 | + |
| 122 | +Datadog is a typical multi-tenant upstream. Each organization has its own site, API keys, and data, and the entry hits both caveats above. |
| 123 | + |
| 124 | +Datadog hosts a different MCP endpoint per site. The host is `mcp.` plus the site domain, and the path is `/api/unstable/mcp-server/mcp`. |
| 125 | + |
| 126 | +| Datadog site | MCP URL | |
| 127 | +| --- | --- | |
| 128 | +| US1 (`datadoghq.com`) | `https://mcp.datadoghq.com/api/unstable/mcp-server/mcp` | |
| 129 | +| US3 (`us3.datadoghq.com`) | `https://mcp.us3.datadoghq.com/api/unstable/mcp-server/mcp` | |
| 130 | +| US5 (`us5.datadoghq.com`) | `https://mcp.us5.datadoghq.com/api/unstable/mcp-server/mcp` | |
| 131 | +| EU1 (`datadoghq.eu`) | `https://mcp.datadoghq.eu/api/unstable/mcp-server/mcp` | |
| 132 | +| AP1 (`ap1.datadoghq.com`) | `https://mcp.ap1.datadoghq.com/api/unstable/mcp-server/mcp` | |
| 133 | +| AP2 (`ap2.datadoghq.com`) | `https://mcp.ap2.datadoghq.com/api/unstable/mcp-server/mcp` | |
| 134 | + |
| 135 | +Confirm the site from the Datadog app URL before registering. A valid key against the wrong site fails to connect. Datadog government sites (`ddog-gov.com`) do not host the MCP server. |
| 136 | + |
| 137 | +Two organizations on the same site share one URL, so the second catalog install is what separates them. Name each server for its organization, such as `datadog-us1-prod` and `datadog-eu-staging`. Datadog recommends OAuth; `DD_API_KEY` and `DD_APPLICATION_KEY` are the fallback, and each pair stays on its own server. |
| 138 | + |
| 139 | +## Further reading |
| 140 | + |
| 141 | +- [Remote MCP servers](/docs/ai-control-plane/distribute/mcp-servers/remote-servers) is the reference for catalog installs, upstream headers, and editing a server after creation. |
| 142 | +- [Govern a third-party MCP server](/docs/ai-control-plane/guides/govern-a-third-party-mcp-server) covers registering an upstream by URL when the catalog path does not apply. |
| 143 | +- [Catalog overview](/docs/ai-control-plane/connect/sources/catalog/overview) documents **Add another** for a second copy of the same entry. |
0 commit comments