@pilot-status/mcp-server
v0.1.2
Published
Model Context Protocol (MCP) server exposing the Pilot Status public /v1 API as tools over streamable HTTP (with OAuth 2.1 for the claude.ai custom connector).
Maintainers
Readme
@pilot-status/mcp-server
A Model Context Protocol (MCP) server that
exposes the Pilot Status public /v1 API as MCP tools, so any MCP-capable
client (Claude Desktop, Cursor, the claude.ai connector, custom agents, …) can
send WhatsApp messages, manage numbers/templates, read conversations, and more —
using a Pilot Status API key.
It speaks the streamable HTTP transport (POST /mcp). Clients connect over
HTTP and authenticate either with an x-api-key header or with an OAuth 2.1
bearer token (for the claude.ai custom connector).
There are two ways to use it:
- Self-host via npm — run the server and point a client at your own HTTP
endpoint (default
http://localhost:8787/mcp), sending anx-api-keyheader. - Hosted (available) — the managed instance at
https://mcp.pilotstatuss.com, used as a claude.ai custom connector over OAuth.
Install
# Run straight from npm (no install needed) — starts the HTTP server:
npx @pilot-status/mcp-server # listens on http://0.0.0.0:8787/mcp
# Or install the binary globally:
npm install -g @pilot-status/mcp-server
pilot-status-mcpThe published package exposes the pilot-status-mcp bin, which starts the
streamable HTTP server. Configuration is via environment variables (see
.env.example / Configuration). The API key
is sent per-request via the x-api-key header; you can optionally set a
process-wide fallback:
PORT=8787 npx @pilot-status/mcp-serverAuthentication
There are two ways to authenticate against the public /v1 API:
- API key (
x-api-key) — aps_*key, sent per-request by HTTP clients (optionally falling back toPILOT_STATUS_API_KEYset on the server). - OAuth 2.1 bearer token — used by the claude.ai custom connector. claude.ai handles the login, so no API key is shared with the client. See Remote (OAuth).
Scope matters (same rules as the REST API):
- Number-scoped key → per-number action/data tools:
messages_*,conversations_list,groups_list,newsletters_list,templates_*,analytics_dashboard,api_keys_*,numbers_*. - Tenant-scoped key → tenant-wide tools:
branding_*,subscription_extra_numbers_*. (Number-scoped keys get HTTP 403 on these, surfaced as a tool error.)
Under an OAuth/tenant token there is no number scope, so number-scoped tools
accept an optional whatsappNumberId argument so the backend can resolve the
target number. It is ignored when the API key is already scoped to a single
number.
Token-based pairing/connect routes (interactive browser flows) are intentionally not exposed as tools.
Tools
| Tool | Endpoint |
| --- | --- |
| messages_send | POST /v1/messages/send |
| message_get | GET /v1/messages/{id} |
| messages_cancel | DELETE /v1/messages/cancel |
| messages_group | GET /v1/messages/group |
| messages_unread | GET /v1/messages/unread |
| conversations_list | GET /v1/conversations |
| groups_list | GET /v1/groups |
| newsletters_list | GET /v1/newsletters |
| numbers_list | GET /v1/numbers |
| numbers_create | POST /v1/numbers |
| numbers_delete | DELETE /v1/numbers/{id} |
| numbers_status | GET /v1/numbers/{id}/status |
| templates_list | GET /v1/templates |
| templates_create | POST /v1/templates |
| templates_get | GET /v1/templates/{id} |
| templates_update | PUT /v1/templates/{id} |
| branding_get | GET /v1/branding |
| branding_upsert | PUT /v1/branding |
| analytics_dashboard | GET /v1/analytics/dashboard |
| api_keys_list | GET /v1/api-keys |
| api_keys_create | POST /v1/api-keys |
| subscription_extra_numbers_get | GET /v1/subscription/extra-numbers |
| subscription_extra_numbers_add | POST /v1/subscription/extra-numbers |
Privacy: when a number's privacy mode is
RELAY_ONLY, the read tools (messages_group,messages_unread,conversations_list) return empty results;STORE_X_DAYSlimits results to the retention window.
Configuration
See .env.example. Key variables:
| Variable | Default | Notes |
| --- | --- | --- |
| PILOT_STATUS_API_URL | https://pilotstatuss.com | Public API base URL (no trailing slash). |
| PILOT_STATUS_API_KEY | — | ps_* key. Optional per-request fallback (clients normally send x-api-key). |
| PILOT_STATUS_API_KEY_ID | — | Optional x-api-key-id companion. |
| MCP_RESOURCE_URL | https://mcp.pilotstatuss.com | Public URL of this server (OAuth resource). |
| PILOT_STATUS_AUTH_SERVER_URL | https://pilotstatuss.com | OAuth authorization server (the backend). |
| PORT | 8787 | HTTP listen port. |
| HOST | 0.0.0.0 | HTTP bind host. |
HTTP endpoint
When running the HTTP transport, the server exposes:
POST /mcp— JSON-RPC over Streamable HTTP. Authenticate withx-api-key: ps_xxxorAuthorization: Bearer <oauth-token>. With no credentials it replies401+WWW-Authenticate, which starts the OAuth flow.GET /.well-known/oauth-protected-resource— OAuth 2.1 protected-resource metadata (RFC 9728); points discovery at the authorization server.
curl -s https://mcp.pilotstatuss.com/mcp \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-H "x-api-key: ps_xxx" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Configuring MCP clients
Self-hosted (npm, HTTP)
Run the server with npx @pilot-status/mcp-server and point the client at your
own HTTP endpoint, supplying the API key as a header:
{
"mcpServers": {
"pilot-status": {
"type": "http",
"url": "http://localhost:8787/mcp",
"headers": { "x-api-key": "ps_your_key_here" }
}
}
}Hosted / remote (API key header)
Point the client at the hosted URL and supply the API key as a header:
{
"mcpServers": {
"pilot-status": {
"type": "http",
"url": "https://mcp.pilotstatuss.com/mcp",
"headers": { "x-api-key": "ps_your_key_here" }
}
}
}Remote (OAuth) — claude.ai connector
For the claude.ai Custom Connector, point claude.ai at the hosted HTTPS URL and let it run the OAuth login — no API key is shared with the client.
- In claude.ai → Settings → Connectors → Add custom connector, set the URL
to
https://mcp.pilotstatuss.com/mcp. - claude.ai fetches
GET /.well-known/oauth-protected-resource, discovers the authorization server, and runs the OAuth 2.1 login (Pilot Status SSO). The resulting access token is sent on everyPOST /mcpasAuthorization: Bearer <token>.
If you run your own instance, serve the HTTP transport at a public HTTPS URL and
set MCP_RESOURCE_URL to that URL and PILOT_STATUS_AUTH_SERVER_URL to the
Pilot Status backend (https://pilotstatuss.com). Leave PILOT_STATUS_API_KEY
unset so unauthenticated requests return the 401 challenge that starts the
OAuth flow.
