DocsDevelopers

Connect an AI assistant MCP

Crumb runs a Model Context Protocol server, so an AI assistant (Claude Code, Claude Desktop, Cursor, or any MCP host) can connect straight to your workspace and read and triage feedback in plain language. Ask it "what are our highest-ARR accounts asking for?", or tell it to "reply to FB-42 and mark it shipped." It runs through the same code paths as the dashboard, so replies and notifications fire exactly as if you'd done it by hand.

1 · Create an API key

In Settings, API keys, an admin creates a key; a workspace can hold 20 active ones. The full key (crumb_sk_…) is shown once with a Copy button, and Crumb stores only a SHA-256 hash of it, so copy it then and treat it like a password. The same page shows your exact server URL and config snippets.

A key acts as the teammate who created it, with that person's current role: admins and PMs can read and write, and viewers can read and post internal notes. Status changes and replies made with it are attributed to that teammate, and demoting them narrows the key at once. Revoking a key takes effect on the next request, and removing its creator from the team deletes their keys. Keys only work on /api/mcp; the customer API doesn't accept them.

2 · Add it to Claude Code

One command wires it up. Hit the button to copy it, paste it in your terminal, and swap in the key you just created:

claude mcp add --transport http crumb \
  https://crumb-app.localhostlabs.net/api/mcp \
  --header "Authorization: Bearer crumb_sk_…"
Self-hosting? Swap the host for your own; the path is always /api/mcp.

Any other MCP client

HTTP-capable hosts (Claude Desktop, Cursor, and most others) take a config block:

{
  "mcpServers": {
    "crumb": {
      "type": "http",
      "url": "https://crumb-app.localhostlabs.net/api/mcp",
      "headers": { "Authorization": "Bearer crumb_sk_…" }
    }
  }
}

For a client that only speaks stdio, bridge with mcp-remote:

npx mcp-remote https://crumb-app.localhostlabs.net/api/mcp \
  --header "Authorization: Bearer crumb_sk_…"

What the assistant can do

Every tool reads and writes only the key's workspace. Reading works with any key.

Read tools

ToolTakes and returns
list_itemsTakes status, type, account, limit (default 50), and cursor, all optional. Returns items newest first, filtered by status, type (bug, idea, question), or exact account name, each with short_id, title, type, status, account, created_at, and updated_at.
search_itemsTakes query (required), limit (default 25), and cursor. Returns items whose title or body contains the text, case-insensitive, newest first. % and _ match literally.
get_itemTakes short_id (required), like FB-42. Returns the item with its body, account, submitter, and assignee, every reply (internal notes included, marked internal), and its status timeline.
list_roadmapTakes nothing. Returns every initiative, grouped into now, next, later, and unscheduled, with its status and whether it's public.
list_accountsTakes limit (default 100) and cursor. Returns customer accounts, highest ARR first: name, arr_usd, and since (when they became a customer, or null).

Write tools

These need a key whose creator is an admin or PM, except internal notes, which any key can post. They run the same code as the dashboard, so webhooks, Slack and Teams notifications, and customer emails fire exactly as they would there.

ToolTakes and does
update_item_statusTakes short_id and status (both required) and reason. Moves an item to open, review, planned, progress, shipped, declined, deferred, or duplicate. The last three need a reason, which the customer sees. Returns emailed.
reply_to_itemTakes short_id and body (both required) and internal (default false). Replies to the customer, or with internal: true adds a private team note, which never emails the customer or reaches webhooks. Returns reply_id and emailed.
create_itemTakes account, submitter_email, type, and title (all required), plus body and submitter_name. Logs feedback for a customer, creating the account and the person if they're new. type is bug, idea, or question. Returns the new short_id.
assign_itemTakes short_id (required) and assignee_email. Assigns the item to a teammate by email; leave out assignee_email, or send it empty, to unassign.

Only planned, progress, shipped, declined, and deferred email the customer, and only when they can be emailed: they wrote in through the widget, have an address, haven't opted out, and your workspace can send email. A customer-facing reply emails under the same conditions, and emailed in the result says whether the email went out. Items made with create_item never email the customer, since they didn't write in through the widget.

Paging through lists

list_items, search_items, and list_accounts return at most limit rows (200 at most) and a next_cursor. When it isn't null, call the tool again with the same arguments plus "cursor": "<next_cursor>" for the next page. Cursors are opaque and continue strictly after the last row you got, so items created between calls never shift or repeat a page.

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_items","arguments":{"status":"open","limit":20}}}

Every tool answers with one text block holding JSON:

{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\n  \"count\": 20,\n  \"items\": [ … ],\n  \"next_cursor\": \"MTc2…\"\n}"}]}}

Protocol

  • Streamable HTTP, stateless. Each POST carries one JSON-RPC 2.0 message and gets one JSON response. There's no SSE stream and no session id, and GET and DELETE answer 405.
  • Versions: 2025-06-18 (latest), 2025-03-26, and 2024-11-05. initialize answers with the version the client asked for when it's one of these, and with 2025-06-18 otherwise.
  • Methods: initialize, ping, tools/list, and tools/call. Notifications, like notifications/initialized, get 202 Accepted with no body. JSON-RPC batches are refused.
  • Rate limit: a burst of 120 requests per key, refilling 2 per second.

To check a key by hand, run the handshake:

curl -s https://crumb-app.localhostlabs.net/api/mcp \
  -H "Authorization: Bearer $CRUMB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"crumb","version":"1.0.0"}}}

Errors

SituationHTTPResponse
Missing, unknown, or revoked key401JSON-RPC error -32600, unauthorized
Over the rate limit429-32603, rate_limited
The body isn't JSON400-32700, parse error
A batch400-32600, batch not supported
Unknown method200-32601, method not found: …
Unknown tool200-32602, unknown tool: …
Unexpected server failure200-32603, internal error
The tool ran and failed200A normal result with "isError": true and the reason as its text

The 401 carries WWW-Authenticate: Bearer, and the 429 a Retry-After header. A tool that fails gives one of these reasons:

ReasonMeans
forbiddenThe key's creator is a viewer. Writes need an admin or PM; viewers can still post internal notes.
not_foundNo item with that short_id in this workspace.
bad_statusNot a status your team can set. Only customers set resolved.
reason_requireddeclined, deferred, and duplicate need a reason.
invalid_cursorcursor isn't a next_cursor this server issued.
assignee_not_foundNo teammate in this workspace has that email.
bad_typetype isn't bug, idea, or question.
<argument> is requiredA required argument is missing or empty.
A sentenceA create_item check failed, like "Enter a valid email for the submitter."
Both the MCP server and API keys ship in core, so they work the same on Crumb Cloud or self-hosted.