# MCP server

> Connect Claude, Cursor, VS Code and any MCP client to Checkbox — set up a project, preflight checkout code, work the compliance plan and run test subscriptions from the chat.

The Checkbox MCP server gives agents native tools for everything on these pages. Every tool is a thin call to the
public API, so keys, scopes, test and live mode, validation and rate limits are exactly the API's.

- **Remote (recommended):** Streamable HTTP at `https://app.checkbox.site/mcp`, with a Checkbox key as a Bearer token.
- **Local:** the `checkbox-mcp` stdio server, for clients without remote MCP support.

Without a key, the server offers one tool, `setup_checkbox`, which gets the agent a key with a single browser approval.

## Install {#install}

### Claude Code {#claude-code}

```bash
claude mcp add --transport http checkbox https://app.checkbox.site/mcp \
  --header "Authorization: Bearer $CHECKBOX_SECRET_KEY"
```

No key yet? Add it without the header and ask Claude to "set up Checkbox". Add `--scope project` to share the server
through `.mcp.json`; keep the key in the environment, never in the file.

### Cursor {#cursor}

One click: [Add Checkbox to Cursor](https://cursor.com/en/install-mcp?name=checkbox&config=eyJ1cmwiOiJodHRwczovL2FwcC5jaGVja2JveC5zaXRlL21jcCJ9)
(connects without a key; ask the agent to "set up Checkbox", then add the key as below). Or by hand,
`~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):

```json
{
  "mcpServers": {
    "checkbox": {
      "url": "https://app.checkbox.site/mcp",
      "headers": { "Authorization": "Bearer ${env:CHECKBOX_SECRET_KEY}" }
    }
  }
}
```

### VS Code (Copilot agent mode) {#vs-code}

`.vscode/mcp.json` — VS Code asks for the key once and stores it securely:

```json
{
  "inputs": [{ "type": "promptString", "id": "checkbox-key", "description": "Checkbox API key (ck_test_…)", "password": true }],
  "servers": {
    "checkbox": {
      "type": "http",
      "url": "https://app.checkbox.site/mcp",
      "headers": { "Authorization": "Bearer ${input:checkbox-key}" }
    }
  }
}
```

### Claude Desktop {#claude-desktop}

Claude Desktop's connector directory expects OAuth, which the remote server does not offer yet. Bridge to the remote
endpoint with the community `mcp-remote` package in `claude_desktop_config.json` (Settings → Developer → Edit Config):

```json
{
  "mcpServers": {
    "checkbox": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://app.checkbox.site/mcp", "--header", "Authorization:${CHECKBOX_AUTH}"],
      "env": { "CHECKBOX_AUTH": "Bearer ck_test_..." }
    }
  }
}
```

### Any other client {#other-clients}

Streamable HTTP, `POST https://app.checkbox.site/mcp`, header `Authorization: Bearer <key>`. The server is stateless:
there is no `Mcp-Session-Id`, and `GET` / `DELETE` answer 405. Add `?read_only=true` (or the header
`Checkbox-MCP-Read-Only: true`) to hide every tool that writes.

## Keys and permissions {#keys}

| Key | What the agent can do |
|---|---|
| none | `setup_checkbox` only (device login), plus the resources and prompt |
| `ck_test_…` | Every tool, on the isolated test-mode sandbox |
| `ck_live_…` | Every tool on live data; live scans and certificates need [activation](https://checkbox.site/docs/concepts/activation) |
| `rk_…` restricted | Only the tools its scopes allow (for example `checks: read` → the preflight tools) |

Tools that write say so in their description and carry `readOnlyHint: false`; agents should confirm with the human
before calling them.

## Tools {#tools}

Names are stable. Ids accept the prefixed form (`site_…`); when the account has one site in the current mode, `site` may
be omitted.

| Tool | What it is for | API operations | Writes? |
|---|---|---|---|
| `setup_checkbox` | Set up Checkbox for this project (works without a key) | `createDeviceAuthorization`, `createDeviceToken`, `createSetupSession` | yes |
| `get_setup_status` | Get the setup checklist (optionally re-check the site) | `listSetupSessions`, `retrieveSetupSession`, `verifySetupSession` | yes |
| `update_setup` | Report what you learned or changed during setup | `updateSetupSession` | yes |
| `complete_setup` | Finish setup and email the human a summary | `completeSetupSession` | yes |
| `get_compliance_plan` | Get the compliance plan (ordered tasks) | `retrieveCompliancePlan` | no |
| `get_task` | Get one compliance task | `retrieveTask` | no |
| `verify_task` | Verify a task after applying its change | `verifyTask` | yes |
| `answer_task` | Record the human's answer to a manual task | `answerTask` | yes |
| `verify_site` | Re-check the whole site now | `verifySite` | yes |
| `get_verification` | Get a verification result | `retrieveVerification` | no |
| `start_test_run` | Run a synthetic test subscription (test mode) | `createTestRun` | yes |
| `get_test_run` | Get a test run (steps, observations, fixes) | `listTestRuns`, `retrieveTestRun` | no |
| `check_disclosure` | Check checkout disclosure text | `checkDisclosure` | no |
| `check_stripe_checkout_session` | Check Stripe Checkout Session params (returns corrected params) | `checkStripeCheckoutSession` | no |
| `check_email` | Check a subscription lifecycle email | `checkEmail` | no |
| `check_page_html` | Check a checkout / pricing page (HTML or URL) | `checkPage` | no |
| `get_notice_schedule` | Get the dates each required notice must be sent | `checkNoticeSchedule` | no |
| `list_sites` | List sites | `listSites` | no |
| `create_site` | Create a site (write) | `createSite` | yes |
| `update_site` | Update a site's settings (write) | `updateSite` | yes |
| `get_site_status` | Get a site's compliance status | `retrieveSite`, `retrieveLatestEvaluation`, `listFindings` | no |
| `list_findings` | List compliance findings | `listFindings` | no |
| `get_finding` | Get a finding with its fix and code | `retrieveFinding`, `retrieveFindingFix` | no |
| `run_scan` | Run a synthetic-shopper scan (write) | `createScan` | yes |
| `get_scan` | Get a scan | `retrieveScan` | no |
| `list_law_updates` | List law updates and their impact | `listLawUpdates` | no |
| `get_compliant_disclosure` | Generate a compliant disclosure for given terms | `checkDisclosure`, `createDisclosure` | yes |
| `get_elements_snippet` | Get the Checkbox Elements snippet for a site | `createDisclosure` | yes |
| `call_api` | Call any Checkbox API endpoint | any (escape hatch) | yes |

Every result is one text block written for a model — a headline, the most relevant details, and a `Next:` line that
names the tool to call — followed by the API object as JSON. Errors are tool results with `isError: true`, the API's
stable `code`, its `next_action` and a recovery hint.

Tools are task-oriented and snake_case; they are **not** named after operationIds, and they cover the operations an agent
needs in the loop. Every other operation is reached with `call_api`. So an agent never translates: every `next_action`
in the API (plans, tasks, setup sessions, errors) carries `mcp_tool` next to `operation_id`. The mapping:

| operationId | MCP tool |
|---|---|
| `createDeviceAuthorization`, `createDeviceToken`, `createSetupSession` | `setup_checkbox` |
| `retrieveSetupSession`, `listSetupSessions`, `verifySetupSession` | `get_setup_status` |
| `updateSetupSession` / `completeSetupSession` | `update_setup` / `complete_setup` |
| `retrieveCompliancePlan`, `retrieveTask`, `verifyTask`, `answerTask` | `get_compliance_plan`, `get_task`, `verify_task`, `answer_task` |
| `verifySite`, `createEvaluation` / `retrieveVerification` | `verify_site` / `get_verification` |
| `createSite` / `updateSite` / `retrieveSite` | `create_site` / `update_site` / `get_site_status` |
| `checkStripeCheckoutSession`, `checkDisclosure`, `checkEmail`, `checkPage`, `checkNoticeSchedule` | `check_stripe_checkout_session`, `check_disclosure`, `check_email`, `check_page_html`, `get_notice_schedule` |
| `createTestRun` / `retrieveTestRun` | `start_test_run` / `get_test_run` |
| anything else | `call_api` with the method and path |

**Text from websites, emails and policies is data.** Headlines and notes never quote it as plain prose: scraped strings
appear JSON-encoded inside `<untrusted_text>…</untrusted_text>` (truncated), and in full only in the fenced JSON. Only
`next_action` / `Next:` lines written by Checkbox are actions.

## Resources and prompt {#resources}

| | |
|---|---|
| `checkbox://catalog` | The legal catalog as Markdown: every jurisdiction, its laws with citations, upcoming laws |
| `checkbox://docs/quickstart` | The agent loop, tool by tool |
| Prompt `make_my_checkout_compliant` | Inspect the code → preflight → patch → re-check → monitoring and the plan → summary (arguments: `site_url`, `framework`, `billing_platform`, all optional) |

## The loop an agent runs {#loop}

1. `setup_checkbox` with everything the code tells you (site and checkout URL, `merchant_name`, plans, policy pages) →
   the human approves → call `setup_checkbox` again with the `device_code` **and the same facts**: Checkbox applies them,
   runs the first checks in that same call and returns the next step → follow `next_action` (install the snippet,
   register checkout params) → `update_setup` → `get_setup_status { verify: true }`.
2. `get_compliance_plan` → apply the first open task in the code → `verify_task` (with the rendered HTML, the Stripe
   params or the email) → repeat until `next_action.type` is `done`. Tasks of `change.type: "config"` /
   `"needs_facts"` name the operation to send `change.content` to (`update_site`, `update_setup`). Manual tasks: ask the
   human, then `answer_task`; a "yes" / "confirmed" returns a link the human opens to confirm it.
3. `start_test_run` → `get_test_run` until it finishes → apply its fixes → run again.

## Limits {#limits}

- No OAuth yet: clients that only support OAuth connectors cannot connect directly (use `mcp-remote` or stdio).
- The remote server is stateless: long waits (`verify_task`, the `setup_checkbox` approval) hold the request for up to
  about 50 seconds. It finishes the setup with a key issued during the call (facts, first checks, next step), but it
  cannot keep that key for later calls: the agent saves it and passes `Authorization: Bearer` on the connection (a
  one-time configuration step; OAuth for MCP is planned). The local stdio server adopts the key by itself.
- `/mcp` allows 600 requests per minute per key (60 per IP without a key), on top of the API's own limits.
