# Working with more than one organization

For a support engineer who manages several customer organizations plus internal production, the
dangerous mistake is not a wrong command. It is a correct command aimed at the wrong customer. This
page describes how the server makes that mistake visible, and the recipe for setting up one
connection per organization.

## The rule that shapes everything

**A TiDB Cloud API key belongs to exactly one organization, permanently.** There is no key that spans
organizations. So a connection to this server is bound to one organization for its whole life, and
"which organization am I about to change?" always has a single, knowable answer.

Everything below follows from that.

## Org visibility

The active organization is stated wherever a decision gets made:

- **`tidb_capabilities`** reports the organization id and label, in every mode. Call it when you are
  not certain which connection you are on.
- **Every preflight** opens with the organization, before the action:

  ```
  Org: Acme Corp (1372813089187041280)

  PREFLIGHT: no action taken yet.

  Action:  Delete cluster
  Target:  prod-db
  ```

  The order is deliberate. You read who before you read what.

- **Not-found errors name the organization the lookup ran in**, because "cluster prod-db not found"
  most often means the connection points at a different customer than you assumed:

  ```
  Cluster prod-db was not found ... The lookup ran in Acme Corp (1372813089187041280).
  If you expected a different organization, note that this connection is bound to one
  organization's API key permanently, so a different org needs a different connection.
  ```

  This covers cluster and branch lookups both, since a branch call takes a cluster id and another
  organization's cluster id is indistinguishable from a nonexistent one.

### When the organization cannot be verified

Resolution can fail: an API blip, a revoked key, a network hiccup. The tool still works, because
bricking recovery tooling during an incident would be worse than proceeding carefully. Instead the
preflight opens with a warning, and the same warning is echoed on the response after you confirm:

```
WARNING: the target organization could not be verified. This connection is bound to one
organization's API key, but the server could not confirm which. If you are managing more than one
organization, stop and confirm you are on the intended connection before approving.

Org: UNVERIFIED
```

`confirmTarget` matching is completely unchanged by any of this. Org display is additive: it never
weakens, replaces, or substitutes for the exact target-name match on a destructive action.

### About the organization label

The management API exposes no organization display name (verified 2026-07-30: every `orgs` and
`organizations` endpoint across the four API hosts returns 404, and the projects payload carries a
project name only). So the label is **operator-supplied**, via `--org-label "Acme Corp"` or
`TIDB_MCP_ORG_LABEL`.

That is the safer arrangement regardless. A fetched organization name would be attacker-influenced:
an organization admin can rename their org to `PRODUCTION - DO NOT TOUCH` or to a competitor's
company name, and a careless approver could be steered by it. The immutable organization id is
always printed beside the label, so a misleading label can never impersonate a different
organization.

If you set no label, the id is shown alone. That is still unambiguous, just less readable.

**The label applies to single-tenant deployments only.** On the multi-tenant remote (`api-key` auth
mode) every request carries its own key and therefore its own organization, so one process-wide
label cannot describe them all. It is dropped there, and approvals show the organization id alone.
The server prints a startup notice if you set a label in that mode, so the setting never fails
silently. Local `stdio` and single-tenant or OAuth remote deployments keep the label, because there
the process genuinely does own the one organization.

**The label is sanitized, because operator-supplied is not the same as trusted.** A label can arrive
from a deployment manifest, a gateway config, or a paste. Rendered raw it could forge the very
structure an approver relies on: a newline yields a second `Org:` line, and a longer injection
fabricates a whole PREFLIGHT block above the real one.

So the label is filtered against an allowlist: letters, combining marks, digits, space, and
`- _ . , & / '`. Anything else is dropped, and the result is capped at 64 characters (counted by
code point) with a visible `…`. Whitespace runs collapse to a single space.

Letters are matched by Unicode category, not by an ASCII range, so `北京平凯星辰科技`,
`ООО Ромашка`, and `Müller & Söhne` all survive intact. An ASCII-only rule would erase a
non-Latin customer name back to a bare id.

An allowlist rather than a list of banned characters, because the banned list was tried first and
lost three rounds running: each version blocked the exact characters in the previous report and left
their neighbours working (ASCII controls, then U+2028 and the bidi and zero-width sets, then U+061C,
U+2060, and five more bracket lookalikes). Unicode has hundreds of format characters and dozens of
bracket lookalikes, so a banned list always trails the next probe by one round. A company name is a
bounded thing, so saying what a label may contain ends the argument.

What survives is always a single line ending in the real organization id. If nothing survives, the
id is shown alone.

## Recipe: one entry per organization

Configure one MCP server entry per organization, named after the organization, each with that
organization's key.

```json
{
  "mcpServers": {
    "tidb-acme": {
      "type": "http",
      "url": "https://tidbcloud-mcp.fly.dev/mcp",
      "headers": { "Authorization": "Basic <base64 of acme-public:acme-private>" }
    },
    "tidb-globex-readonly": {
      "type": "http",
      "url": "https://tidbcloud-mcp.fly.dev/mcp?readonly=true",
      "headers": { "Authorization": "Basic <base64 of globex-public:globex-private>" }
    }
  }
}
```

Three habits that make this safe in practice:

1. **Name the entry after the organization**, not after the product. `tidb-acme` tells you what
   `tidb` does not.
2. **Give each entry the narrowest scope it needs.** A customer org you only investigate should be
   read-only (`?readonly=true` on the remote, or no `--allow-write` locally). Reserve write and admin
   for the connections that genuinely need them. An entry that cannot mutate cannot be the one that
   mutates the wrong customer.
3. **Set `--org-label` on single-tenant entries** so preflights read `Org: Acme Corp (...)` rather
   than an id you have to recognize by sight. The remote entries above are multi-tenant, so they
   show the id alone; that is why the JSON carries no label.

For local `stdio`, the same idea with one server entry per organization, each with its own key pair
in `env` and its own `--org-label`.

### Behind a gateway

If you register this server behind a gateway that injects credentials per user (Workato,
TrueFoundry, or similar), the gateway's auth override supplies the `Authorization` header. The
one-key-one-org rule still holds: each override maps to exactly one organization, so a gateway user
sees exactly the organization their credential belongs to. Register one gateway connection per
organization for the same reason you would locally.

## Deliberately not built: a multi-key keychain

An obvious-looking feature is to let one connection carry several keys and pick an organization per
call. That is rejected, and it should stay rejected. Three reasons:

1. **It multiplies credential blast radius per request.** Today a compromised or misused request can
   reach one organization, because it carries one key. A key bundle turns every request into a
   potential incident across every organization at once.
2. **It breaks the one-request-one-tenant assumption the server is built on.** Fingerprinting,
   telemetry tenant identity, org resolution caching, and rate limiting all key off "this request
   has one credential and therefore one tenant". A bundle makes each of those ambiguous, and the
   failure would be silent misattribution rather than an error.
3. **Behind a gateway it hands every gateway user all organizations at once.** When the server is
   registered as a shared connector, a bundled credential is not scoped per user. The blast radius
   stops being "one engineer's laptop" and becomes "everyone with access to the connector".

The one-entry-per-organization recipe achieves the same practical goal, with the isolation enforced
by the transport rather than by our own bookkeeping.

## Deferred: local profiles and org switching

A local profile mechanism (named profiles, a switch tool) would improve ergonomics for a single
engineer on one machine. It is deliberately not built yet, because it overlaps heavily with the
OAuth/IAM work: if per-user delegated auth lands, profiles built now would be thrown away. Build this
only if OAuth is more than a quarter out.
