Skip to content

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/json

One 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; the Retry-After header 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 carries resultType: "complete" plus a server-info _meta block. It offers server/discover, tools/list and tools/call; initialize and ping do not exist in it and answer -32601 Method not found at HTTP 404.
  • 2025-06-18 — the handshake revision, for older clients: initialize, notifications/initialized, ping, tools/list and tools/call. A request that declares no version — neither in _meta nor in the MCP-Protocol-Version header — is served in this dialect, so an existing client keeps working unchanged, and a client that explicitly asks for 2025-06-18 is 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:

ToolRequired scopeReturns
get_entitlementsread:entitlementsOne entry per subscription a customer has, each with an isEntitled boolean.
list_subscriptionsread:subscriptionsThe shop's subscriptions, cursor-paginated, with an optional status filter.
get_subscriptionread:subscriptionsOne subscription's full detail including line items, by numeric id.
get_metrics_summaryread:metricsThe retention KPI summary — the same figures as the admin dashboard, aggregate only.
list_plansread:plansThe shop's selling plans with a pricing summary, active flag, and product count.
list_cancellationsread:metricsCancel-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/list

Mcp-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 / 429 with the REST envelope (see Authentication above).
  • A malformed JSON body returns -32700 Parse error inside a normal JSON-RPC envelope at HTTP 200, because MCP clients expect the envelope.
  • A notification (a message with no id) is accepted and ignored: HTTP 202 with an empty body.
  • Bad tool arguments — an out-of-range limit, an unknown status, an id that isn't yours — come back as a normal tool result with isError: true and a plain-text message, not as a JSON-RPC error.
  • 2025-06-18 errors are always HTTP 200 with a JSON-RPC error body. 2026-07-28 additionally uses the statuses its transport mandates: 400 for header, _meta, or version failures, and 404 for a method that revision removed.