Docs / WallaB Developer Platform
MCP server
Connect an AI assistant over the Model Context Protocol: the read-only MCP mirror of the v1 API, with the same keys, scopes, and rate limits.
The WallaB Developer Platform also speaks the Model Context Protocol (MCP), so an AI assistant or agent can read your retention data directly instead of you writing a REST client for it. The MCP server is a mirror of the v1 REST API: one read-only tool per endpoint, the same API keys, the same scopes, the same plan gate, and the same rate limits. No tool can change anything in your shop.
The endpoint
POST https://wallab.ai/api/mcp
Authorization: Bearer wlb_your_key_here
Content-Type: application/jsonOne URL handles everything: JSON-RPC 2.0 messages over HTTP POST (MCP's Streamable HTTP transport). The server is stateless — there is no session to open or close and no server-to-client event stream, so GET and DELETE return 405 with an Allow: POST header. Every response that carries a body is application/json.
Heads up
This is a server-to-server endpoint. It sends no CORS headers, and an API key must never ship inside a browser page or a mobile app bundle. Configure the connector in a server-side or desktop MCP client, and keep the key in a secret manager or environment variable.
Authentication
Use the same key as the REST API: create it in Settings → WallaB Developer Platform (owner-only), then send it as a bearer token on every POST. The platform is available on the Growth plan or higher. Keys, scopes, and rotation are covered on the Authentication page.
Authentication problems come back as HTTP status codes, not JSON-RPC errors, so an MCP client handles them like any other HTTP auth failure:
401— any missing, malformed, unknown, or revoked key. The body is the identical{ "success": false, "message": "Unauthorized" }every time; which check failed is never distinguishable.403— the shop's plan lacks developer API access:{ "success": false, "message": "Developer API access requires the Growth plan or higher." }429— rate limited; theRetry-Afterheader says how many seconds to wait.
Rate limits
The MCP endpoint shares the REST API's limiter — requests are counted per API key, on a rolling one-minute window, by plan: Growth 60/min, Pro 300/min, Enterprise Plus uncapped. One JSON-RPC message is one request.
Protocol revisions
Two MCP revisions are served side by side, and the choice is made per request (never per connection — a stateless server infers nothing from earlier traffic):
- 2026-07-28 — the stateless revision. There is no handshake: every request carries its protocol version and client capabilities in
params._meta, and every result carriesresultType: "complete"plus a server-info_metablock. It offersserver/discover,tools/listandtools/call;initializeandpingdo not exist in it and answer-32601 Method not foundat HTTP404. - 2025-06-18 — the handshake revision, for older clients:
initialize,notifications/initialized,ping,tools/listandtools/call. A request that declares no version — neither in_metanor in theMCP-Protocol-Versionheader — is served in this dialect, so an existing client keeps working unchanged, and a client that explicitly asks for2025-06-18is never silently upgraded.
server/discover is answered in either dialect — a client may call it before it knows which revision the server speaks — and reports the supported versions newest-first: 2026-07-28, then 2025-06-18.
Two things the server deliberately does not do. JSON-RPC batching was removed in 2025-06-18, so an array body is -32600 Invalid Request. And there are no sessions: Mcp-Session-Id and Last-Event-ID are never read.
Tools
tools/list returns only the tools the calling key's scopes permit. A tool whose scope you did not grant is indistinguishable from one that does not exist ("Unknown tool: …"), so the list can't be used to probe your scopes. Each tool mirrors one v1 endpoint and requires that endpoint's scope:
| Tool | Required scope | Returns |
|---|---|---|
get_entitlements | read:entitlements | One entry per subscription a customer has, each with an isEntitled boolean. |
list_subscriptions | read:subscriptions | The shop's subscriptions, cursor-paginated, with an optional status filter. |
get_subscription | read:subscriptions | One subscription's full detail including line items, by numeric id. |
get_metrics_summary | read:metrics | The retention KPI summary — the same figures as the admin dashboard, aggregate only. |
list_plans | read:plans | The shop's selling plans with a pricing summary, active flag, and product count. |
list_cancellations | read:metrics | Cancel-save concierge outcomes (reason, outcome, offer type), cursor-paginated. |
Arguments mirror the REST query parameters: limit and cursor on the paginated tools, plus the same status / outcome filters. Field-by-field response shapes live in the API reference.
Example: list the tools
curl -sS https://wallab.ai/api/mcp \
-H "Authorization: Bearer wlb_your_key_here" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "list_plans",
"title": "List selling plans",
"description": "List this shop selling plans (subscribe-and-save cadences) with a pricing summary, active flag, and product count.",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"properties": {},
"required": []
}
}
]
}
}Trimmed to one tool here; a key with every scope lists all six, always in the same order.
Example: call a tool
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "list_plans", "arguments": {} }
}{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"plans\":[{\"id\":7,\"name\":\"Monthly\",\"active\":true}]}"
}
],
"structuredContent": {
"plans": [{ "id": 7, "name": "Monthly", "active": true }]
}
}
}Every result carries the endpoint's payload twice — once as a serialized-JSON text block (so a client with no structured-output support still gets everything) and once as structuredContent. The plan objects are shown trimmed above; the full shape is the REST endpoint's.
The 2026-07-28 request framing
A client on the stateless revision declares itself on every message. Two _meta fields are required — a missing one is -32602 Invalid params at HTTP 400:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}The same request must mirror those fields into headers, so an intermediary routing on the header and this server executing the body can never disagree:
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/listMcp-Name is additionally required on tools/call, carrying the same value as params.name. A header that is missing or disagrees with the body is -32020 at HTTP 400; a declared protocol version we do not implement is -32022 at HTTP 400, with the versions we do support in error.data.
Results on this revision carry resultType: "complete" and an io.modelcontextprotocol/serverInfo _meta block, and tools/list and server/discover add cache hints (ttlMs, cacheScope). The hints are advisory only — permission is re-checked on the server for every tools/call, so a cached tool list can never grant access a key no longer has.
Error handling
- Auth, plan, and rate-limit failures are HTTP
401/403/429with the REST envelope (see Authentication above). - A malformed JSON body returns
-32700 Parse errorinside a normal JSON-RPC envelope at HTTP200, because MCP clients expect the envelope. - A notification (a message with no
id) is accepted and ignored: HTTP202with an empty body. - Bad tool arguments — an out-of-range
limit, an unknownstatus, an id that isn't yours — come back as a normal tool result withisError: trueand a plain-text message, not as a JSON-RPC error. - 2025-06-18 errors are always HTTP
200with a JSON-RPC error body. 2026-07-28 additionally uses the statuses its transport mandates:400for header,_meta, or version failures, and404for a method that revision removed.