> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted MCP server

> Devotel Orbit exposed as an MCP server — one JSON-RPC endpoint, a curated tool catalog, and a default-deny scope model where an unscoped API key sees zero tools.

# Hosted MCP server

Devotel Orbit can be exposed **as an MCP (Model Context Protocol) server**,
so an external MCP client — Claude, ChatGPT, or an IDE agent — operates
your tenant through a curated catalog of platform operations. This page is
the conceptual anchor for that surface: why it exists, what the catalog
contains, how scope resolution decides which tools a given API key can see,
and how the authorize layer composes with the platform's normal role and
scope gates. Task-level walkthroughs (client setup, protocol handshake)
link out at the end; this page defines the model they all reference.

## Section 1 — Why Orbit hosts an MCP server

Most integrations talking to Orbit are outbound MCP consumers: your Orbit
agents register third-party MCP servers (for example, a CRM or a calendar
provider) and call their tools. This surface is the reverse direction. One
curated JSON-RPC endpoint — `POST /api/v1/mcp` (with a `GET` probe that
returns the capability descriptor) — lets an external client drive *your*
tenant through a fixed catalog of Orbit's own platform operations.

The design choice is deliberate: an MCP client gets a **catalog, not
arbitrary tool injection**. Every tool in the catalog dispatches to an
already-shipped, already-gated REST route — sender resolution, quota
checks, TCPA/quiet-hours gates, provider routing, and audit writes all run
through the same code path a normal API call would. The MCP layer is a
protocol adapter over the platform, not a second implementation of it.

Nothing here depends on a local `npx` process or a client-side broker —
the server is hosted behind the API gateway, and authentication is the
platform's normal API-key bearer (`X-API-Key` or
`Authorization: Bearer <key>`).

## Section 2 — The curated tool catalog

The catalog is intentionally **small and read-heavy**: one write tool per
chargeable surface (send an SMS, place a call) plus read tools for
campaigns, numbers, analytics, and CDP profiles. Each tool declares the
same OAuth-style scope string the underlying REST route enforces — there
is no MCP-specific scope namespace to keep in sync.

| Tool                             | What it does                             | Scope it requires |
| -------------------------------- | ---------------------------------------- | ----------------- |
| `list_campaigns`                 | List campaigns, most recent first        | `campaigns:read`  |
| `get_campaign`                   | Get one campaign's detail by id          | `campaigns:read`  |
| `list_numbers`                   | List phone numbers the account owns      | `numbers:read`    |
| `get_message_analytics`          | Messaging volume/delivery/error overview | `analytics:read`  |
| `get_contact_profile`            | A contact's CDP trait profile by user id | `contacts:read`   |
| `send_sms`                       | Send an SMS from a registered number     | `messages:write`  |
| `place_call` / `send_voice_call` | Place an outbound voice call             | `voice:write`     |

Two names cover the same voice-verb dispatch (`place_call`, and the
Twilio-parity alias `send_voice_call` with optional `answer_url`
call-control). Outbound voice still exits only via the Devotel softswitch —
these tools call back into the already-gated REST route; they wire nothing
new to a carrier.

Each tool's `inputSchema` is a JSON-Schema subset (type, properties,
required) so an MCP client can render argument forms or validate input
before it calls. A required-argument check runs before dispatch, so a
missing `campaign_id` on `get_campaign` errors cleanly instead of
silently collapsing onto the list route.

## Section 3 — Default-deny scope resolution

Scope resolution answers "which of the catalog's tools does this key's
`scopes` array grant access to" at `tools/list` time, and again at
`tools/call` time:

* **A key with no scopes sees zero tools.** An empty or absent scopes
  array unlocks nothing — `tools/list` returns an empty `tools[]`, and
  every `tools/call` is rejected.
* **The `*` wildcard scope opens the whole catalog.** Full-access keys
  enumerate every catalogued tool, matching what `*` means everywhere
  else on the platform.
* **A scope the catalog doesn't carry unlocks nothing.** Granting
  `voice:read` to a key whose tools only accept `voice:write` doesn't
  leak voice tools into the list.

There is no enumeration side-channel: a client only ever sees tools it is
entitled to call. An unknown tool name and a known-but-unscoped tool name
are indistinguishable to the caller — both surface as `tool not found` —
so a disallowed tool's mere existence is never leaked.

<Info>
  The legacy "scopeless key = full access" grandfather window that the
  platform's general-purpose API routes honour for long-lived keys is
  deliberately **not** honoured here. The MCP surface is new, so every
  caller must opt in with explicit scopes — a brand-new attack surface for
  external LLM agents gets tightened defaults, not the laxest historical
  semantics.
</Info>

Grant or revoke scopes under **Dashboard → Settings → API Keys**, then
re-run `tools/list` to refresh the catalog for that key.

## Section 4 — Transport and handshake

The endpoint is a single JSON-RPC 2.0 service that always replies with a
plain JSON body — it never upgrades to an SSE stream. The session flows:

1. **initialize** — capability negotiation; returns the protocol version
   and the server descriptor (`name: orbit`, plus its version).
2. **tools/list** — enumerate the scoped catalog (Section 3).
3. **tools/call** — dispatch one tool by name with arguments.
4. **notifications/initialized** — acknowledged per JSON-RPC notification
   semantics (no response body required).

The dashboard probe page (**Dashboard → Developer → MCP Server**)
exercises exactly this lifecycle without any client setup: a "Test
connection" button fires `initialize`, and only on success does the page
issue `tools/list` to render the catalog rows. An empty result renders
an explicit "no tools in scope" message rather than hanging on an empty
list.

## Section 5 — Authorization vs role gates

Two distinct gates stack on this surface:

* **Scope gates (`requireScope`)** decide *which catalog entries* the key
  may see and call. The scope claim lives on the API key and is resolved
  per tool, as in Section 3.
* **Role gates (`requireRole`)** decide whether the caller's derived role
  is allowed onto a given class of route at all — for example an
  `analytics:read` tool still routes through a handler that may require a
  minimum dashboard role in addition to the scope.

The dispatch itself re-injects the caller's own auth header into the
underlying REST route, so the inner route's normal middleware chain
re-resolves tenant and re-runs its own scope/role checks. Even if this
MCP layer's pre-check were ever bypassed, the inner route's gate still
applies — defence in depth, not a single point of failure. Tenant
isolation is identical to a direct REST call: the injected request
re-resolves `req.ctx.tenant` from the same credential, so an MCP caller
can never reach another tenant's data.

## Section 6 — Relationship to the guides

This page is the conceptual anchor for the surface. The task-level
walkthroughs build on it:

* [Hosted server handshake →](/guides/mcp-server-handshake) — protocol
  payload shapes and the dashboard probe flow in practice.
* [Claude / Cursor client setup →](/guides/mcp-claude-cursor) — per-client
  configuration (Claude Desktop, Cursor, Claude Code, VS Code, Windsurf,
  Cline, Zed) against this endpoint.
* [Connected apps →](/guides/connected-apps) — managing third-party OAuth
  app scopes in the same scope taxonomy the catalog uses.

## See also

* [Tenant isolation →](/concepts/tenant-isolation) — why an injected
  request can't reach another tenant.
* [Developer portal →](/guides/developer-portal) — the dashboard pillar
  hosting **Developer → MCP Server**.
* [Connect Orbit to your AI coding assistant →](/guides/mcp-claude-cursor)
  — per-client walkthroughs for the surface this page defines.
