# MCP server — Sumloop

Sumloop speaks the Model Context Protocol, so an assistant can answer
questions about a company's spend out of its own ledger — over an API key
that is workspace-bound, scoped, and revocable on its own. Included in every
band; nothing about it is metered.

Endpoint: `POST https://sumloop.app/mcp` — JSON-RPC 2.0 over HTTP, one request and
one JSON response, no streaming. Authenticate with
`Authorization: Bearer sumloop_sk_…`.

## Tools

Fifteen, and deliberately not a mirror of the REST API. Storing provider
secrets, deleting entries, managing keys and single sign-on are absent rather
than gated, so the worst a leaked key can do is read a ledger and write one
cost row. A tool the key lacks the scope for is never advertised. Every tool
that reports money takes `basis` — `amortized` (default) or `cash` — and names
the one it used. Nothing uploads: an assistant can find the invoice behind a
charge and link to it, but putting a file into Sumloop stays an act in the app
or over the API.

| Tool | Scope | What it answers |
| --- | --- | --- |
| `list_organizations` | — | The workspace this key can reach |
| `get_cost_overview` | `dashboard:read` | Monthly totals, this month against last, top services |
| `spend_by_service` | `dashboard:read` | Spend grouped by service over a window |
| `compare_periods` | `dashboard:read` | Two windows compared vendor by vendor: what moved, started, stopped |
| `service_trend` | `dashboard:read` | One vendor month by month, with its average and range |
| `spending_profile` | `dashboard:read` | Cadence, concentration, direction and source mix per vendor |
| `list_transactions` | `transactions:read` | Cost entries filtered by service, amount or month |
| `forecast_spend` | `forecast:read` | Projected total with prediction intervals |
| `forecast_by_service` | `forecast:read` | The same projection per vendor, with the model chosen |
| `list_integrations` | `integrations:read` | Every supported provider, and which are connected |
| `connection_health` | `integrations:read` | Whether each connection still syncs, and why it stopped |
| `list_documents` | `documents:read` | The stored invoices, what each backs, and a download link |
| `list_datasets` | any dataset scope | The exportable datasets, their columns and filters |
| `export_dataset` | the dataset's own | Rows from one dataset, filtered server-side, as JSON or CSV |
| `record_manual_cost` | `manual_entries:write` | Record one validated cost. The only write. |

## Connecting

Create the key first, in Settings → API keys (owner or admin). The full key is
shown once, on the screen that also prints the connect command with the key
already in it.

Claude Code:

```
claude mcp add --transport http sumloop https://sumloop.app/mcp \
  --header "Authorization: Bearer sumloop_sk_…"
```

Cursor, VS Code, Zed — any client with a JSON server config:

```json
{
  "mcpServers": {
    "sumloop": {
      "url": "https://sumloop.app/mcp",
      "headers": { "Authorization": "Bearer sumloop_sk_…" }
    }
  }
}
```

Claude Desktop and claude.ai add remote servers as custom connectors, which
carry OAuth or nothing and have nowhere to put a static header. Until Sumloop
speaks OAuth, bridge it locally with `mcp-remote`, passing the header through
an environment variable — inline, the space in `Bearer sk…` splits into two
arguments and the server answers 401.

```json
{
  "mcpServers": {
    "sumloop": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://sumloop.app/mcp", "--header", "Authorization:${AUTH}"],
      "env": { "AUTH": "Bearer sumloop_sk_…" }
    }
  }
}
```

## Responses worth recognising

- `401` — the key is wrong, revoked, or expired
- `402` — the workspace has no live subscription. The same lock the browser
  sees, so a workspace is never locked in one place and open in another.
- A tool refusal naming a scope — the key was issued without it

Full page: https://sumloop.app/mcp-server
