orbit-mcp
by Devotel
README.md
# Orbit CPaaS — MCP Server
Stdio-based [Model Context Protocol](https://modelcontextprotocol.io) server
that exposes the Orbit CPaaS API as first-class tools for any MCP client
(Claude Desktop, Claude Code, Cursor, VS Code extensions, custom agent
harnesses, …).
Docs: <https://docs.orbit.devotel.io/guides/mcp-claude-cursor>
## Install
Most MCP clients launch the server on demand with `npx`, so no separate
install step is needed — the per-client config blocks below do it for you.
To install it explicitly:
```bash
npm install -g @devotel-orbit/mcp-server
```
The package installs a single `orbit-mcp-server` binary.
## Quick start
```bash
ORBIT_API_KEY=dv_live_sk_xxxxxxxxxxxxxxxx npx @devotel-orbit/mcp-server
```
The process speaks JSON-RPC framing on stdio per the MCP spec. All logs go
to stderr — never write to stdout from this server (it would corrupt MCP
framing).
---
## Claude Desktop setup
Add an entry to `~/Library/Application Support/Claude/claude_desktop_config.json`
(macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Restart Claude Desktop. The Orbit tools appear in the toolbox on the next
conversation. Verify with: "List my Orbit campaigns."
### Sandbox mode (no charges)
Use a test key (`dv_test_sk_*`) to exercise every tool against the sandbox
without incurring real charges. Sandbox calls are logged in the Orbit
dashboard under **Settings → API Logs → sandbox**.
---
## Cursor setup
[](cursor://anysphere.cursor-deeplink/mcp/install?name=orbit&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBkZXZvdGVsLW9yYml0L21jcC1zZXJ2ZXIiXSwiZW52Ijp7Ik9SQklUX0FQSV9LRVkiOiJkdl90ZXN0X3NrX3h4eHh4eHh4eHh4eHh4eHgifX0=)
Click the badge above to install with one click (Cursor prompts you to
confirm, then fill in your real `ORBIT_API_KEY`), or set it up manually.
Open **Cursor Settings → Features → MCP → Add new MCP server** and set:
| Field | Value |
| ------- | --------------------------------------------- |
| Name | `orbit` |
| Type | `command` |
| Command | `npx -y @devotel-orbit/mcp-server` |
| Env | `ORBIT_API_KEY=dv_live_sk_xxxxxxxxxxxxxxxx` |
Alternatively, add to `.cursor/mcp.json` in your project root:
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Cursor automatically reloads MCP config on file save. Test with `@orbit` in
any Cursor chat: "Using orbit, send an SMS to +14155551234 saying hello."
---
## Claude Code (CLI)
```bash
export ORBIT_API_KEY=dv_live_sk_xxxxxxxxxxxxxxxx
claude mcp add orbit -- npx -y @devotel-orbit/mcp-server
```
Or add the block below to your project's `.claude/settings.json` /
`.mcp.json`:
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
---
## VS Code (GitHub Copilot Chat / MCP extension)
Add to `.vscode/mcp.json` in your workspace (or User Settings → `mcp.servers`
for a global install):
```json
{
"servers": {
"orbit": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Run **MCP: List Servers → orbit → Start Server** from the command palette,
then invoke Orbit tools from Copilot Chat's agent mode.
---
## Windsurf
Open **Windsurf Settings → Cascade → MCP Servers → View raw config** (or edit
`~/.codeium/windsurf/mcp_config.json` directly) and add:
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Click **Refresh** in the MCP panel, then call Orbit tools from Cascade.
---
## Cline (VS Code extension)
Open **Cline → MCP Servers → Configure MCP Servers**, which opens
`cline_mcp_settings.json`, and add:
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Cline reloads MCP servers automatically after the file is saved.
---
## Zed
Add to your Zed `settings.json` (**Zed → Settings → Open Settings**):
```json
{
"context_servers": {
"orbit": {
"source": "custom",
"command": {
"path": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
}
```
Orbit tools then appear in the Zed Assistant panel's context-server picker.
---
## Continue
Add a block to `~/.continue/config.json` (or the workspace `.continue/config.json`):
```json
{
"mcpServers": {
"orbit": {
"command": "npx",
"args": ["-y", "@devotel-orbit/mcp-server"],
"env": {
"ORBIT_API_KEY": "dv_live_sk_xxxxxxxxxxxxxxxx"
}
}
}
}
```
Reload the Continue window (Cmd/Ctrl+Shift+P → **Continue: Reload**) to pick
up the new server.
---
## Configuration
| Env var | Default | Notes |
| ---------------------------- | ------------------------------------ | --------------------------------------------------- |
| `ORBIT_API_KEY` | _(required)_ | `dv_live_sk_*` (live) or `dv_test_sk_*` (sandbox). |
| `ORBIT_API_URL` | `https://api.orbit.devotel.io` | Override for self-hosted / staging clusters. |
| `MCP_TOOLS_ALLOW_LIST` | _(empty — all enabled)_ | Comma-separated list of tools to expose. Case-insensitive. |
| `MCP_TOOLS_DENY_LIST` | _(empty)_ | Comma-separated list of tools to hide. Wins over allow list. |
| `MCP_TOOLS_BUDGET_LIMIT_USD` | `0` (disabled) | Per-process advisory cap on chargeable tool spend. |
| `MCP_SESSION_ID` | _(auto-generated)_ | Override the per-process session id for idempotency-key salt. |
### Auth header
The Orbit REST API authenticates MCP traffic via the `X-API-Key` header.
You can also pass a Devotel API key as `Authorization: Bearer …`: since
DC-UNCOVERED_SURFACES-01 (2026-06-01) Bearer tokens that carry a Devotel key
prefix (`dv_live_sk_`, `dv_test_sk_`, `dv_live_pk_`, `dv_test_pk_`) are accepted
as API keys. Only a non-Devotel Bearer token falls through to Clerk session-JWT
verification and is rejected with 401 when no Clerk cookie is present (verified
at `packages/auth/src/middleware.ts` ~880-994).
The MCP server attaches `X-API-Key` automatically. A boot-time ping validates
the key and surfaces a clear error to stderr:
```
[MCP] ORBIT_API_KEY rejected by API; check key format
(should start with dv_live_sk_ or dv_test_sk_).
```
Generate a key at **Orbit Dashboard → Settings → API Keys**.
---
## Idempotency (LLM retry semantics)
Every chargeable POST tool attaches a deterministic `Idempotency-Key` header
derived from `(toolName, args, sessionId)`. `callId` is intentionally
**excluded** from the key: it advances on every tools/call request (including
network retries), so folding it in would produce a fresh key on each retry,
defeat the 24h idempotency cache, and double-charge. `callId` is retained only
as an audit-trace breadcrumb. When an LLM retries a tool call after a network
blip with the same arguments the Orbit API replays its 24h Redis-cached
response — operators see one message / purchase / campaign-start, never a
duplicate.
Key shape: `mcp_<toolName>_<32 hex chars>` (well under the 255-char API cap).
---
## Per-tool budget guard
Set `MCP_TOOLS_BUDGET_LIMIT_USD=10` to refuse chargeable calls whose
pessimistic estimated cost would exceed $10 in a single MCP session. Advisory
bonus on top of the API's own per-tenant spend caps.
---
## Per-tool allow / deny lists
```bash
# Read-only mode — no chargeable tools.
MCP_TOOLS_ALLOW_LIST=lookup_contact,list_campaigns,kb_query,list_segments,list_templates,get_message_status,search_numbers,check_balance,list_agents,analytics_summary
# Block high-impact tools.
MCP_TOOLS_DENY_LIST=purchase_number,start_campaign,voice_call
```
Disabled tools are NOT advertised in the `tools/list` response — the LLM
never sees them as available primitives.
---
## Docs-MCP tools (no Orbit account required)
Three tools are pure documentation lookups (plus one live, unauthenticated
fetch) — they work even with no `ORBIT_API_KEY` set, so an agent can look up
the real endpoint for a channel, get a copy-pasteable quickstart, or check
current pricing before a developer has signed up for Orbit at all:
| Tool | What it does |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_api_reference` | Real endpoint/method/body shape for a resource (sms, whatsapp, email, voice, templates, numbers, verify, contacts, pricing) — grounded in the same endpoints the execute tools below actually call. |
| `get_quickstart` | Copy-pasteable "add `<channel>` to my app" curl example + the 5-step sandbox-to-live path. |
| `get_pricing` | Live fetch of `GET /api/v1/public/pricing` (no auth) — never a stale hard-coded rate. |
See `src/docs-tools.ts` for the full design note (grounding + SSRF-safety
rationale for `get_pricing`'s fetch).
---
## Tools (47 total)
44 API / documentation tools are listed below; the remaining 3 are the
host-model reasoning tools in the next section. Listed in registration order
(see `src/index.ts`). The `Chargeable` column
reflects membership of the `CHARGEABLE_TOOLS` set — those calls flow through the
Idempotency-Key + audit + budget-guard path.
| Tool | Method | Endpoint | Chargeable |
| ---------------------------- | ------ | ---------------------------------------------- | :--------: |
| `get_api_reference` | — | static reference (no API call) | no |
| `get_quickstart` | — | static reference (no API call) | no |
| `get_pricing` | GET | `/api/v1/public/pricing` (unauthenticated) | no |
| `send_sms` | POST | `/api/v1/messages` | yes |
| `send_whatsapp` | POST | `/api/v1/messages` | yes |
| `send_email` | POST | `/api/v1/messages` | yes |
| `lookup_contact` | GET | `/api/v1/contacts` | no |
| `create_contact` | POST | `/api/v1/contacts` | no |
| `list_campaigns` | GET | `/api/v1/campaigns` | no |
| `start_campaign` | POST | `/api/v1/campaigns/{id}/send` | yes\* |
| `search_numbers` | GET | `/api/v1/numbers/available` | no |
| `purchase_number` | POST | `/api/v1/numbers/purchase` | yes |
| `number_lookup` | POST | `/api/v1/numbers/lookup` | no |
| `list_segments` | GET | `/api/v1/contacts/segments` | no |
| `create_segment_from_prompt` | POST | `/api/v1/contacts/segments/from-prompt` | no |
| `list_journeys` | GET | `/api/v1/flows` | no |
| `start_journey` | POST | `/api/v1/flows/{id}/start` | yes\* |
| `agent_handoff_to_human` | POST | `/api/v1/conversations/{id}/handoff` | yes\* |
| `kb_query` | POST | `/api/v1/agents/kb/query` | no |
| `list_templates` | GET | `/api/v1/messages/templates` | no |
| `send_template` | POST | `/api/v1/messages` | yes |
| `get_message_status` | GET | `/api/v1/messages/{id}` | no |
| `verify_start` | POST | `/api/v1/verify/send` | yes |
| `verify_check` | POST | `/api/v1/verify/check` | no |
| `voice_call` | POST | `/api/v1/voice/calls` | yes |
| `list_agents` | GET | `/api/v1/agents` | no |
| `analytics_summary` | GET | `/api/v1/analytics/messages` | no |
| `check_balance` | GET | `/api/v1/billing/balance` | no |
| `analyze_text` | POST | `/api/v1/ai/analyze` | no |
| `create_support_ticket` | POST | `/api/v1/inbox/tickets/internal` | no |
| `track_event` | POST | `/api/v1/events/track` | no |
| `identify_contact` | POST | `/api/v1/contacts` | no |
| `create_calendar_event` | POST | `/api/v1/agents/calendar-events` | no |
| `add_memory` | POST | `/api/v1/agents/memory` | no |
| `search_memory` | GET | `/api/v1/agents/memory/search` | no |
| `list_memories` | GET | `/api/v1/agents/memory` | no |
| `delete_memory` | DELETE | `/api/v1/agents/memory/{id}` | no |
| `delete_contact_memories` | DELETE | `/api/v1/agents/memory` | no |
| `video_create_room` | POST | `/api/v1/video/rooms-scheduled` | no |
| `video_send_invite` | POST | `/api/v1/video/rooms-scheduled/{id}/invites` | no |
| `video_end_session` | DELETE | `/api/v1/video/rooms/{name}` | no |
| `cdp_track` | POST | `/api/v1/events/track` | no |
| `cdp_identify` | POST | `/api/v1/events/track` | no |
| `update_contact` | PUT | `/api/v1/contacts/{id}` | no |
\* Charges accrue per-message at fan-out time, not at invocation. The budget
guard treats them as zero-cost; the audit log still records every invocation.
### Host-model reasoning (MCP sampling)
Three tools don't call the Orbit API at all — they reason over free text by
asking the connected host to run an LLM completion on the server's behalf
(`sampling/createMessage`). The completion runs on the **host's** model, so no
separate Orbit inference key or spend is involved, and the host stays in
control of model choice and human approval. They return generated text only —
never send or spend — so they are neither chargeable nor high-risk.
| Tool | Uses | What it returns |
| ----------------------- | --------------- | ------------------------------------------------- |
| `summarize_transcript` | host completion | A concise summary of a call / chat transcript |
| `draft_message` | host completion | Draft SMS/WhatsApp/email/campaign copy (not sent) |
| `classify_conversation` | host completion | The best-matching label + a short rationale |
Requires a host that advertises the `sampling` capability (Claude Desktop,
Claude Code, Cursor, or your own agent). When it's absent, each tool returns a
clear message saying so rather than failing silently.
---
## Interactive app cards (Apps SDK)
In an Apps-SDK host (ChatGPT, Claude), three tools render as interactive
cards instead of plain text. The server advertises the `resources`
capability and serves each card as a `ui://widget/*.html` template that
the host mounts and hydrates with the tool's `structuredContent`:
| Tool | Card | Action button → tool |
| -------------------- | ------------------- | ---------------------------- |
| `search_numbers` | Buy a number | Buy → `purchase_number` |
| `list_campaigns` | Launch a campaign | Launch → `start_campaign` |
| `get_message_status` | Delivery status | Refresh → `get_message_status` |
Each button is confirm-to-act and routes back through the existing tool,
so the idempotency, audit, and budget guarantees are unchanged. The card
templates are self-contained (no outbound network) and hidden when the
producing tool is denied via the allow/deny lists. Clients that don't
support generative UI receive the same JSON text content and ignore the
extra metadata.
## Audit log
Every chargeable tool call emits a JSON-Lines audit entry to stderr:
```json
{
"ts": "2026-05-20T14:33:21.402Z",
"level": "audit",
"component": "orbit-mcp",
"event": "tool.chargeable.invoked",
"tool": "voice_call",
"session_id": "mcp_session_94821_1715353101204",
"call_id": "call_3",
"args": { "to": "+14155551234", "record": true }
}
```
Pipe stderr to your structured-log sink to retain a full trace. The API key
is never logged.
---
## Local development
```bash
pnpm --filter @devotel-orbit/mcp-server dev # tsx watch src/index.ts
pnpm --filter @devotel-orbit/mcp-server test # vitest run
pnpm --filter @devotel-orbit/mcp-server type-check # tsc --noEmit
```
---
## License
MIT. See `LICENSE`.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues