---
summary: "How registered hosted MCP servers are scoped, named, credentialed, health-checked,\
  \ and pooled \u2014 the user-facing model for the v1.1+ registry."
title: Cloud MCP registry
path: concepts/cloud-mcp-registry
status: published
---

The cloud MCP registry lets your tenant register hosted MCP servers once and have their tools, resources, and prompts appear automatically in every ScaiGrid surface that consumes MCP. Each registration carries the URL, the credentials, a scope, and a discovery cycle; everything else is handled by ScaiLink.

## Scope

Two scopes today, both keyed to the registrar's tenant:

- **Personal** (`is_tenant_shared=false`, default) — only the registrar can see and invoke the server. The credentials are the registrar's own. Requires `scailink:remote.manage_own` to create, rotate, or delete.
- **Tenant-shared** (`is_tenant_shared=true`) — every user in the tenant with `scailink:remote.use` can invoke. Credentials are typically a service account the tenant admin manages. Requires `scailink:remote.manage_tenant`.

A platform-global scope visible across tenants is not in v1.x. It would need a SuperAdmin licensing posture like ScaiSpeak's global voices and is parked for a later release.

## Tool naming

Registered tools appear in the ScaiMCP catalog with a namespaced name so two tenants registering the same server can never collide:

| Scope | Namespace |
|---|---|
| Personal | `remote.{user_id}.{slug}.{tool_name}` |
| Tenant-shared | `remote.tenant.{slug}.{tool_name}` |

The `slug` is the display name kebab-cased plus a stable 6-character hash over the same name, so the slug is deterministic across re-registrations of the same display name.

## Auth shapes (v1.1)

The registration specifies how outbound calls to the upstream server are authenticated.

| `auth_type` | Behaviour |
|---|---|
| `none` | Public server. No auth headers added. |
| `bearer` | Adds `Authorization: Bearer <value>`. The credential field is named `authorization` or `token`. |
| `api_key_header` | Each credential field maps to an outbound HTTP header verbatim. Use for multi-header auth like `X-API-Key` plus `X-Org-Id`. |

OAuth2 (refresh-token flow) is on the v1.2 roadmap. The current ScaiKey-issued JWT is not used as outbound auth, because most third-party MCP servers don't trust the ScaiKey issuer.

## Transports

| `transport` | When to use |
|---|---|
| `streamable_http` (default) | Modern MCP transport. Pick this unless you know the server doesn't support it. |
| `sse` | Legacy SSE-only servers. Same MCP semantics, slightly slower handshake. |

## Credentials at rest

Every credential field is encrypted with AES-256-GCM. Each field gets its own data-encryption key, wrapped by the platform KEK. The write path is one-way — values go in via `POST /remote-servers` or `PUT /remote-servers/{id}/credentials/{field}` and are never returned by any read endpoint. Only the outbound runtime can decrypt to make a call.

The detail endpoint returns `credential_fields` (a list of field names) and `credential_oldest_days` (the age of the oldest credential), but never the values.

## Health and discovery

Discovery runs on three triggers:

- **On registration.** The first handshake happens synchronously inside `POST /remote-servers`. The row is committed even if discovery fails — `status='error'` lets you fix the network issue and call refresh rather than re-entering credentials.
- **Every 15 minutes.** The refresh cron picks `status='active'` servers in stale-first order with a per-tenant budget of 10 servers per tick. Three consecutive failures flip `status` to `error` and the server's tools disappear from the catalog. A successful refresh resets the streak and restores the tools.
- **On demand.** `POST /remote-servers/{id}/refresh` re-runs discovery immediately. Useful after a server-side change.

Stale capability rows (last seen more than 24 hours ago) are evicted during refresh so a server that drops a tool stops advertising it.

## Session pool

Opening a fresh MCP session for every tool call pays a 100-to-300ms handshake on every request. The session pool keeps one outbound session warm per `(user, server)` for 5 minutes after the last call. Subsequent calls within that window skip the handshake.

Bounds in v1.1:

- **Per worker, not cross-worker.** Each Python worker holds its own pool; correctness is fine across workers, hit rate is just lower.
- **LRU cap at 50 sessions** per worker. Protects memory from a runaway tenant.
- **Sweeper every 30 seconds** closes idle sessions past the 5-minute TTL.
- **RPC error closes the cached session** so the next call gets a fresh handshake.

## User-id forwarding (opt-in)

By default, ScaiGrid does not send your internal user IDs to the registered server. Flip `forward_user_id=true` on the registration to add `X-ScaiGrid-User: <user_id>` to every outbound call. Useful when the third party needs per-user attribution (per-user rate limits, multi-tenancy on their side). Off by default; opt-in only.

## Rotation reminders

Detail responses surface `credential_oldest_days` — the age of the oldest credential on the server. The admin UI's detail pane shows a warning at 90 days, encouraging rotation. Rotate in place with `PUT /remote-servers/{id}/credentials/{field}`; the server keeps its id, capability rows don't churn, and the namespaced tool names downstream stay stable.

## Three-tier permissions

The cloud registry uses three module permissions rather than one so tenant admins can scope the feature to specific groups:

- `scailink:remote.use` — see registered servers, call their tools through `/mcp`.
- `scailink:remote.manage_own` — register, rotate, and delete personal servers.
- `scailink:remote.manage_tenant` — register, edit, and delete tenant-shared servers.

Default for non-admin roles is deny. Tenant admins enable the feature by creating a custom role that includes the relevant keys and assigning it via the standard group-roles UI. Tenant, partner, and super admins satisfy all three automatically via the module-permission catch-all.

## Operational chips

The detail response surfaces a few fields the admin UI uses to flag attention:

- `consecutive_failures` — N misses in a row, drives the warning/error tier on the dashboard chip.
- `last_health_check_at` / `last_health_status` — when the last refresh ran and what it found.
- `credential_oldest_days` — drives the rotation warning at 90 days.
- `status` — `active` (in the catalog), `paused` (admin disabled), or `error` (three consecutive misses, hidden from the catalog).

## Limits

A few hard caps to keep one tenant from swamping the platform:

- **100 servers per tenant.** `MAX_SERVERS_PER_TENANT`. Hit it and `POST /remote-servers` returns 429 with `SCAILINK_REMOTE_LIMIT_EXCEEDED`.
- **10 refreshes per tenant per cron tick.** Stale-first ordering means every server gets touched within a few ticks even when a tenant is at the cap.
- **50 warm outbound sessions per worker.** LRU-evicted. With many workers, the effective platform capacity is higher.
- **24-hour staleness window.** A capability row not seen in the last 24 hours of refreshes gets evicted, so a server that drops a tool stops advertising it.

## What is not in v1.x

A few features deliberately parked for later:

- **Platform-global scope** — visible to every tenant. Needs a SuperAdmin licensing posture similar to ScaiSpeak's global voices. v1.2+.
- **OAuth2 refresh-token auth** — the most common third-party MCP server auth shape after bearer tokens. v1.2.
- **Cross-worker session-pool coordination** — today each worker keeps its own pool. Correctness is fine; hit rate scales 1/N. v1.2.
- **Per-tool-scoping** — today the consent tier applies to the whole server, not per tool. Per-tool tiers are a tractable v1.3 follow-up.

The cron, the audit, the credential rotation, the three-tier permissions, and the namespacing are all in place — those were the operational essentials.
