# Write safety: expected_tenant and target_tenant_id

> A tenant with `mcp.writes_require_expected_tenant` enabled refuses every write that omits `expected_tenant`, raising `expected_tenant_required`. A platform PAT calling a tenant-scoped tool must pass `target_tenant_id`. Both exist so a connector pointed at the wrong tenant errors rather than silently acting.

URL: https://biloh.com.au/docs/reference/write-safety-expected-tenant
Category: Reference | Audience: builder | Updated: 2026-08-28

## Why these exist

Several connectors can appear in one client at once — a live business tenant, a test tenant, the platform connector — and they are **indistinguishable by tool name**. `create_invoice` looks identical on all of them. A write lands in whichever tenant the connection is bound to.

These two parameters turn "I assumed the right connector" into an assertion the server checks.

## `expected_tenant` — assert the tenant you meant

Available on every tool. Pass the tenant's name exactly as it appears in `meta.tenant.name`, or its UUID.

```
propose_send_invoice({
  invoice_id: "…",
  expected_tenant: "Example Facilities Pty Ltd"
})
```

If the connector resolves to a different tenant, the call **fails and does nothing**:

```
code: expected_tenant_mismatch
```

### When it is mandatory

A tenant can require it on every write by enabling `mcp.writes_require_expected_tenant`. On such a tenant, omitting it fails before anything happens:

```
code: expected_tenant_required
detail: This tenant requires expected_tenant on all write calls
        (mcp.writes_require_expected_tenant is enabled). Pass
        expected_tenant with the tenant name or UUID to confirm
        intent, or disable the setting.
```

This is a **first-call surprise** for any integration. If you are writing a client or an agent loop, pass `expected_tenant` on every write from the start — it costs nothing on tenants that do not require it, and it is the difference between a caught mistake and a silent one.

Reads are unaffected.

## `target_tenant_id` — platform PATs calling tenant tools

The platform connector can act across tenants. When it calls a **tenant-scoped** tool, it must name the tenant explicitly:

```
error: Platform PAT must include target_tenant_id when calling a
       tenant tool. Use list_all_tenants to discover tenant ids.
```

Discover the ids with `list_all_tenants`. Tenant-PAT callers omit the parameter entirely — their tenant is inferred from the session, and passing it is neither needed nor accepted.

The route validates and strips `target_tenant_id` before the handler runs, so handler behaviour is identical either way.

## The orientation habit that avoids both

Call `get_session_context` when you first connect. It returns the active tenant's name, id and mode (`LIVE`, `TEST`, or `PLATFORM`). Every tool response also carries `meta.tenant` with the same three fields.

Check `meta.tenant.mode` before any write. `LIVE` means real client data.

## Error reference

| Code | Cause | Fix |
|---|---|---|
| `expected_tenant_required` | Tenant requires the assertion; call omitted it | Add `expected_tenant` |
| `expected_tenant_mismatch` | The assertion did not match the resolved tenant | You are on the wrong connector |
| *(message, no code)* Platform PAT must include `target_tenant_id` | Platform connector calling a tenant tool | Add `target_tenant_id`, or use the tenant connector |

## Related

- [Verify an email actually reached your client](/docs/how-to/verify-an-email-reached-your-client)
