# Platform MCP Server

> Connect an AI agent to the FirstByte control plane over MCP. Public read-only tools and privileged tools are separate, and each privileged call needs one narrow scope.

The FirstByte platform has an MCP (Model Context Protocol) server on the
control plane. An agent that connects to it can operate your workspace. The
agent can list calls, start calls, manage agents, and read analytics. Your
role and your grants set the limit of what the agent can do.

The endpoint is:

```
https://portal.firstbyte.fast/mcp
```

The transport is Streamable HTTP. The agent sends JSON-RPC 2.0 messages with
`POST`. The server is different from the docs MCP server. The docs server
gives read-only reference pages. This server operates the platform. See
[Coding agents](/concepts/coding-agents) for the docs server.

## Authentication

The server accepts two credentials:

1. A console session JWT. A session holds the full scope vocabulary,
   because it is the console credential. The role checks and grant checks of
   the platform still apply. A session can reach all organizations that the
   user belongs to.
2. An MCP access key. A key is a token with the prefix `mpk_`. Send it as a
   Bearer token in the `Authorization` header. A key holds only the narrow
   scopes that you select when you make it.

A key acts as the user who made it. On each call, the server loads that
user's live memberships again. The server confines the key to one
organization. If the user leaves the organization, the key stops working.
After you revoke a key, the server rejects it in 30 seconds or less.

## Scopes

A scope has the form `mcp:<domain>:<action>`. One scope permits one action
class in one operation group. There is no scope that permits everything.

The domains are:

| Domain      | What it covers                                                        |
|-------------|-----------------------------------------------------------------------|
| `catalog`   | The operation catalog only. No tenant data.                           |
| `telephony` | Live call control, SIP trunks, and routing (VDNs, vectors, ACLs).     |
| `cdr`       | Call detail records and transcripts.                                  |
| `reports`   | Reports, analytics, monitoring, and diagnostics.                      |
| `agents`    | Contact-center agents, teams, skills, and wrap-up codes.              |
| `ai`        | AI voice agents, agent specs, inference, assist, and knowledge bases. |
| `billing`   | Billing, invoices, plans, and entitlements.                           |
| `media`     | Streams, media apps, stored objects, and branding.                    |
| `robotics`  | Fleet robotics, teleoperation, and game sessions.                     |
| `infra`     | Modules, hub, tunnels, VPN, QuickDesk, realtime, webhooks, and alerts. |
| `security`  | Organization administration, member sessions, and the audit log.      |
| `keys`      | MCP access keys and registered external MCP servers.                  |

The actions are:

| Action   | What it permits                                                         |
|----------|-------------------------------------------------------------------------|
| `read`   | Queries. A `read` scope can never change data.                          |
| `write`  | Mutations that create or update.                                        |
| `delete` | Mutations that destroy or revoke. A `write` scope does not permit these. |
| `admin`  | Every mutation in the `security` and `keys` domains.                    |

The `security` and `keys` domains have no `write` scope and no `delete`
scope. Every mutation in these two domains needs `admin`.

For example, `mcp:cdr:read` permits `cdr.recent`. The same scope does not
permit `billing.summary`, and it does not permit any mutation.

To see the exact scope of one operation, call `describe_operation`. The
result contains a `requiredScope` field.

The server publishes the full list at
`https://portal.firstbyte.fast/.well-known/oauth-protected-resource`, in the
`scopes_supported` field. This is OAuth 2.0 Protected Resource Metadata
(RFC 9728).

## Make an MCP access key

Only owners and admins can make keys.

1. Open the portal at `https://portal.firstbyte.fast`.
2. Go to **Settings → API Keys → MCP access keys**.
3. Make a new key. Give the key a name that identifies the agent that will
   hold it.
4. Select the scopes. The picker is a grid: one row for each domain, one
   column for each action. Select only the cells that the agent needs. For
   an agent that reads call records and nothing else, select one cell:
   `mcp:cdr:read`.
5. Set an expiry date.
6. Copy the `mpk_` token. The console shows the token one time.

> **WARNING:**
> An MCP access key is a durable credential. Select the smallest set of
>   scopes that does the job. Do not select a `write`, `delete`, or `admin`
>   scope for an agent that only reads. Set an expiry. Name the key after the
>   agent that holds it. A clear name makes the correct key easy to revoke
>   later.

> **NOTE:**
> A key can never make another key. The `keys` domain refuses an `mpk_`
>   credential, even one that holds `mcp:keys:admin`. Use the console for key
>   administration.

## Connect an agent

Add the server to your MCP client configuration:

**Claude Code (.mcp.json):**
```json
{
  "mcpServers": {
    "firstbyte-platform": {
      "url": "https://portal.firstbyte.fast/mcp",
      "headers": { "Authorization": "Bearer mpk_…" }
    }
  }
}
```

**Cursor (.cursor/mcp.json):**
```json
{
  "mcpServers": {
    "firstbyte-platform": {
      "url": "https://portal.firstbyte.fast/mcp",
      "headers": { "Authorization": "Bearer mpk_…" }
    }
  }
}
```

## The meta-tools

The platform API has more than 540 operations. The server does not expose
one MCP tool for each operation. That count is too large for MCP clients.
The server exposes five meta-tools. The meta-tools reach the full catalog.

The tools are in two sets. Each tool declares its set in the tool listing,
in the `annotations` field and in the `_meta` field. A client can therefore
separate the two sets before it calls anything.

**Public tools.** These are read-only. They return metadata about the server.
They return no tenant data, and they need no scope.

| Tool                 | Arguments                       | Returns                                                  |
|----------------------|---------------------------------|----------------------------------------------------------|
| `whoami`             | none                            | The acting user, the organizations, the roles, the scopes, and the domains that each scope covers. |
| `list_operations`    | `group?`, `filter?`, `granted?` | The operation catalog. Each line names the scope that the operation needs, and marks the scopes that you hold. |
| `describe_operation` | `path`                          | The operation type, the required scope, the org scoping, and the JSON schema of the input. |

**Privileged tools.** These reach tenant data. Each call needs the one narrow
scope of the operation.

| Tool              | Arguments        | Needs                                    | Returns                          |
|-------------------|------------------|------------------------------------------|----------------------------------|
| `read_operation`  | `path`, `input?` | `mcp:<domain>:read`                      | The JSON result of a query.      |
| `call_operation`  | `path`, `input?` | `mcp:<domain>:write`, `:delete`, or `:admin` | The JSON result of a mutation. |

`read_operation` refuses a mutation. `call_operation` refuses a query. The
two tools are separate for this reason: a client can permit the read tool
without permitting the write tool, and a key that holds only `read` scopes
can never enter the write tool.

An agent follows this sequence:

1. Call `whoami`. The result shows the identity and the scopes of the
   credential.
2. Call `list_operations`. With no arguments, the result is one summary line
   per group. Pass `group` to list the operations of one group. Pass
   `filter` to search all operation paths for a substring. Pass
   `granted: true` to list only the operations that your scopes permit.
3. Call `describe_operation` with the operation path. The result names the
   required scope. Do this before the first call of an operation that the
   agent does not know.
4. Call `read_operation` for a query, or `call_operation` for a mutation.

## What the privileged tools enforce

- Each call needs the one scope of the operation. The server makes this
  check before it dispatches anything.
- An operation in a router group that has no scope domain cannot be called
  at all. A new product area is unreachable over MCP until an operator
  classifies it.
- All role checks and grant checks of the platform apply. The server does
  not add a second permission system. Each call runs as the acting user.
- For an org-scoped operation, the server pins the organization id. For a
  key, the server overwrites `input.orgId` with the key's organization. An
  agent cannot reach a different tenant. For a session, pass `orgId` in the
  input. If the user belongs to exactly one organization, the server fills
  `orgId` in.
- The server truncates a result at 200,000 characters. A truncated result
  contains an explicit truncation marker.

## Rate limits

The server accepts a maximum of 240 requests per minute from one client IP
address. This limit applies before authentication. Each platform operation
also has its own rate limit. A `call_operation` call inherits that limit.
When you exceed a limit, the server returns HTTP status 429.

## Error codes

| Status | Meaning                                                                    |
|--------|----------------------------------------------------------------------------|
| 401    | No credential, or the credential is not valid, is expired, or is revoked.  |
| 403    | The credential is valid, but it does not hold the scope of the operation.  |
| 429    | A rate limit. See above.                                                    |

A 403 response carries a `WWW-Authenticate` header with
`error="insufficient_scope"` and a `scope` parameter. The `scope` parameter
names the exact scope that the operation needs. The response body also
carries the JSON-RPC result, so an agent can read the reason.

## Audit

The server writes each mutation to the organization audit log as an
`mcp.call` entry. The entry names the key that made the call and the scope that permitted it.
The server
redacts secret values (tokens, passwords, headers) from the entry before it
writes the entry.

## Smoke test

Send one `whoami` call with `curl`:

```bash
curl -X POST https://portal.firstbyte.fast/mcp \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer mpk_…' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'
```

The result shows the acting user, the organization, the roles, and the
scopes. If the server returns HTTP status 401, the key is not valid, is
expired, or is revoked. If the server returns HTTP status 403, the key is
valid but does not hold the scope.

To read the scope vocabulary without a credential:

```bash
curl -s https://portal.firstbyte.fast/.well-known/oauth-protected-resource \
  | python3 -m json.tool
```
