---
summary: "End-to-end recipe \u2014 register a hosted MCP server, set credentials,\
  \ manage tenant-shared scope, rotate, and confirm tools surface in /mcp."
title: Register a cloud MCP server
path: tutorials/register-a-cloud-mcp-server
status: published
---

You have a hosted MCP server — your own deployment or a third party's — and you want its tools to appear platform-wide in your tenant. This tutorial covers the registration, the scope decision, the credential model, the discovery cycle, and the rotation flow.

Roughly 15 minutes if you have the server URL and credential ready.

## 1. Decide the shape

Settle these before any API calls:

- **Scope.** Personal (only you use it) or tenant-shared (everyone in your tenant uses it under one credential)?
- **Auth.** Single bearer token (`bearer`), multiple HTTP headers (`api_key_header`), or open (`none`)?
- **Transport.** `streamable_http` unless you know the server is SSE-only.
- **User-id forwarding.** Should the third party see internal user IDs (per-user rate limits, their own audit log)? Default off.
- **Consent tier.** `auto` runs without confirmation, `confirm` prompts the agent's chat caller on each invocation, `locked` forbids invocation outside a manual UI flow.

## 2. Register a personal server with bearer auth

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scailink/remote-servers" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Notion MCP",
    "endpoint_url": "https://mcp.notion.example.com/mcp",
    "auth_type": "bearer",
    "credentials": {"authorization": "ntn_..."},
    "is_tenant_shared": false,
    "transport": "streamable_http",
    "consent_tier": "auto"
  }'
```

```python
import httpx, os

server = httpx.post(
    f"{os.environ['SCAIGRID_HOST']}/v1/modules/scailink/remote-servers",
    headers={"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"},
    json={
        "name": "Acme Notion MCP",
        "endpoint_url": "https://mcp.notion.example.com/mcp",
        "auth_type": "bearer",
        "credentials": {"authorization": "ntn_..."},
        "is_tenant_shared": False,
        "transport": "streamable_http",
        "consent_tier": "auto",
    },
).json()["data"]
```

```javascript
const res = await fetch(`${process.env.SCAIGRID_HOST}/v1/modules/scailink/remote-servers`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIGRID_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Acme Notion MCP",
    endpoint_url: "https://mcp.notion.example.com/mcp",
    auth_type: "bearer",
    credentials: { authorization: "ntn_..." },
    is_tenant_shared: false,
    transport: "streamable_http",
    consent_tier: "auto",
  }),
});
const { data: server } = await res.json();
```

Save `server.id` — every subsequent call targets it.

## 3. Multi-header auth (api_key_header)

When a server needs both `X-API-Key` and `X-Org-Id`, register with `auth_type: "api_key_header"` and one credential field per outbound header:

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scailink/remote-servers" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Internal CRM MCP",
    "endpoint_url": "https://crm.internal.example.com/mcp",
    "auth_type": "api_key_header",
    "credentials": {
      "x-api-key": "ak_...",
      "x-org-id": "org_..."
    },
    "is_tenant_shared": true,
    "transport": "streamable_http"
  }'
```

Each field name maps to an outbound header verbatim (lowercased on send).

## 4. Tenant-shared registrations

For a server that everyone in your tenant should use under one service account, set `is_tenant_shared=true` and supply credentials owned by your tenant. The caller needs `scailink:remote.manage_tenant` — tenant admins have it; regular users normally don't.

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scailink/remote-servers" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Shared GitHub MCP",
    "endpoint_url": "https://gh-mcp.example.com/mcp",
    "auth_type": "bearer",
    "credentials": {"authorization": "ghp_service_..."},
    "is_tenant_shared": true,
    "forward_user_id": true
  }'
```

`forward_user_id: true` adds `X-ScaiGrid-User: <user_id>` to outbound calls so the third party can attribute usage to your users — useful when you want their per-user rate-limiting to apply.

## 5. Confirm discovery succeeded

```bash
curl "$SCAIGRID_HOST/v1/modules/scailink/remote-servers/$SERVER_ID" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

Look for:

- `status: "active"` (not `error`).
- `last_health_status: "ok"`.
- `consecutive_failures: 0`.
- `capabilities` populated with the tools you expected.

If `status` is `error`, fix the upstream issue, then call refresh — you don't need to re-register.

## 6. On-demand refresh

After a change on the server side (new tool added, schema updated), force a re-discovery instead of waiting for the 15-minute cron:

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scailink/remote-servers/$SERVER_ID/refresh" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

The response is the same shape as `GET /remote-servers/{id}` — same capability rows, freshly upserted, with a recomputed health status.

## 7. Rotate a credential

When upstream rotates the token, mirror it without re-registering:

```bash
curl -X PUT "$SCAIGRID_HOST/v1/modules/scailink/remote-servers/$SERVER_ID/credentials/authorization" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "ntn_new..."}'
```

The PUT is idempotent on `(server, field)` — it overwrites both ciphertext and DEK. The server's id and slug do not change; downstream namespaced tool names stay stable.

## 8. Watch for rotation reminders

The detail response includes `credential_oldest_days`. The admin UI shows a warning at 90 days. Rotate proactively rather than reactively — upstream tokens that expire mid-cron cause `consecutive_failures` to climb and eventually trip the `error` status.

## Done

The server is registered, discovered, and visible in `/mcp` to everyone the scope and permissions allow. Iterate from here — add servers, scope them by group via custom roles, set `consent_tier` per-server, and let the cron do the rest.
