Connect an AI assistant (MCP)
Connect Claude, ChatGPT, Cursor, and other MCP clients to Spendrein — issue a credential, the tool reference, worked examples, security, rate limits, and errors.
Spendrein speaks Model Context Protocol (MCP) over HTTP, so any MCP-compatible assistant — Claude, ChatGPT, Cursor, Claude Code, VS Code, Windsurf — can read your audits, look up vendor benchmarks, and (on a paid plan) trigger cancellations and generate negotiation scripts directly from chat. There's no SDK, no background process, and nothing to install on your machine: Spendrein runs as a remote MCP server at a single endpoint.
The assistant is the JSON-RPC client; https://spendrein.com/api/mcp is the
JSON-RPC server. Every request is authenticated, rate-limited, dispatched to a
tool, and logged — in that order.
One credential, two minutes
Setup is a one-time job: issue a credential in Settings, paste it into your assistant's connector config alongside the endpoint, and ask a question. The rest of this page is reference you'll rarely need to re-read.
What an assistant can do
| Tool | What it does | Plan |
|---|---|---|
audit.query | Returns your most recent completed audit, or a specific audit by ID — with the parsed subscription list, monthly cost, AI confidence, and recommendation. | Free + paid |
benchmark.lookup | Returns Spendrein's typical-spend range for a named vendor, so the assistant can tell you whether you're above or below market. | Free + paid |
subscription.cancel | Queues Spendrein's managed cancellation flow for a specific subscription. | Paid only |
subscription.negotiate | Generates a negotiation script (three talking points plus a full script) and saves it to your negotiation history. | Paid only |
The two write tools require an active paid plan. If a free-tier account's assistant calls them, the tool returns a clear upgrade message rather than executing anything.
Issue a credential
- Open Settings → AI assistant integration.
- Click Issue credential.
- The credential appears in a one-time dialog. Copy it immediately — Spendrein only stores a one-way SHA-256 hash, so the cleartext can't be recovered or re-displayed if you lose it.
Each account has one active credential at a time. Issuing a new one
automatically revokes the previous one. The string starts with spr_mcp_
followed by 32 random bytes (base64url). Treat it like a password — anyone
holding it can read your audits and, on a paid plan, trigger cancellations.
Header-only authentication
The endpoint accepts the credential only in the Authorization header.
URL-parameter tokens (?token=…) are rejected with a 401, to keep
credentials out of server logs and browser history.
Connect your assistant
The two values are universal everywhere: the endpoint is
https://spendrein.com/api/mcp and the auth header is
Authorization: Bearer spr_mcp_…. Everything else is client-specific.
Claude / Claude Desktop. Add Spendrein as a remote connector under
Settings → Connectors → Add custom remote MCP. Use the URL above and the
Bearer credential. (The claude_desktop_config.json file is for local
servers — Spendrein is remote, so use the connector flow.)
ChatGPT. Settings → Connectors → New connector → Custom MCP server. Paste the URL, choose Bearer token, and enter your credential.
Cursor — add an HTTP MCP server in ~/.cursor/mcp.json:
{
"mcpServers": {
"spendrein": {
"type": "http",
"url": "https://spendrein.com/api/mcp",
"headers": {
"Authorization": "Bearer spr_mcp_REPLACE_WITH_YOUR_CREDENTIAL"
}
}
}
}Claude Code — one command:
claude mcp add spendrein \
--transport http \
--header "Authorization: Bearer spr_mcp_REPLACE_WITH_YOUR_CREDENTIAL" \
https://spendrein.com/api/mcpVS Code — add an HTTP server in .vscode/mcp.json and set the
Authorization header (a promptString input keeps the credential out of the
committed file). Windsurf — add a remote HTTP MCP in Cascade's settings or
~/.codeium/windsurf/mcp_config.json with the same URL and header.
Any other client — point any client that supports remote HTTP MCP with
custom headers at the endpoint above. The wire protocol is JSON-RPC 2.0;
implement initialize, tools/list, and tools/call.
To verify the connection, a tools/list request should return all four tools:
curl -s https://spendrein.com/api/mcp \
-H "Authorization: Bearer $SPR_MCP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' \
| jq '.result.tools[].name'
# expected:
# "audit.query"
# "benchmark.lookup"
# "subscription.cancel"
# "subscription.negotiate"Tool reference
All inputs and outputs are validated against strict schemas — extra properties are rejected.
audit.query · free + paid
Returns the most recent completed audit for your workspace, or a specific one
by audit_id. Pass no arguments for the latest. The response carries an
audit summary (id, created_at, totals, currency, subscription_count) and a
subscriptions[] list (vendor, category, monthly cost, billing cycle, status,
AI confidence, and recommendation reasoning).
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "audit.query", "arguments": {} }
}benchmark.lookup · free + paid
Returns Spendrein's typical-spend range for a vendor — price_low,
price_typical, price_high. The input is free-form; Notion, Notion Labs Inc., and NOTION all normalize to the same record. When Spendrein has no
data for the vendor, benchmark is null and the response is still HTTP 200,
not an error.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "benchmark.lookup",
"arguments": { "vendor_name": "Notion" }
}
}subscription.cancel · paid only
Queues Spendrein's managed cancellation flow for a specific subscription. Pass
subscription_id (preferred — guarantees an unambiguous match; always
available from a prior audit.query). vendor_name is a fallback: if more
than one subscription matches, the tool returns a candidates[] list and you
must re-call with the chosen subscription_id. Spendrein never auto-substitutes
a similar-looking vendor. Re-calls are idempotent — they return the existing
queued cancellation.
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "subscription.cancel",
"arguments": { "subscription_id": "8d2e..." }
}
}subscription.negotiate · paid only
Generates a negotiation script for a subscription — three talking points plus a
complete script — and persists it to your negotiation history. The script is
never auto-sent. Requires subscription_id; pass an optional target_price
(the monthly price you want). With no target, the tool returns no_target_price
— call benchmark.lookup first to get a sensible figure.
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "subscription.negotiate",
"arguments": {
"subscription_id": "8d2e...",
"target_price": 12,
"context_hint": "Team shrinking from 12 to 6 seats."
}
}
}Worked examples
The wording isn't load-bearing, but using the tool verbs ("query", "benchmark", "cancel", "negotiate") helps the assistant pick the right tool on the first try.
Find waste in the latest audit.
"Pull my latest Spendrein audit. Group the subscriptions by AI recommendation (cancel, downgrade, keep) and show the monthly total per group."
The assistant calls audit.query with no arguments, buckets the
subscriptions by recommendation, sums the monthly cost per bucket, and renders
a table — no follow-up tool call needed.
Decide whether you're overpaying.
"Look up market benchmarks for my three most expensive subscriptions. Tell me which ones are above the typical-spend range."
The assistant calls audit.query, sorts by monthly cost, takes the top three,
calls benchmark.lookup for each vendor, and flags anything above
price_high as overpaying.
Security
- Credential storage. Only a SHA-256 hash is stored — never the cleartext. A database compromise yields no usable credentials.
- Audit log of every call. Each
tools/callis logged totool_call_events(tool name, outcome, rejection reason, duration, remaining rate-limit budget) — but never the input or output payloads. The log is visible in Settings; the CSV export carries a# checksum sha256=…line and always covers at least 90 days, so an empty-but-checksummed file proves "no events in this window" for compliance. - Payload privacy. Tool responses never include raw bank statements, transaction descriptions, payment instruments, customer email addresses, Stripe IDs, or workspace IDs.
- Instant revocation. Revoke from Settings; the next request — even one already in flight — gets a 401. Re-issuing produces a fresh credential with no shared state.
Your AI provider's data policy
When your assistant calls a Spendrein tool, the request and response transit through your AI provider (Anthropic, OpenAI, etc.). Each provider sets its own policy on whether assistant-tool conversations train future models — Spendrein can't control that. Review your provider's settings before connecting.
Rate limits
Limits are enforced per credential, per tool class, on a rolling one-minute window. Rejected calls still consume budget, which closes the "trigger errors to starve the limiter" loophole.
| Class | Limit | Tools |
|---|---|---|
| Read tools | 60 / minute | audit.query, benchmark.lookup |
| Write tools | 10 / minute | subscription.cancel, subscription.negotiate |
| Credential lifecycle | 5 / hour / account | Issuance + revocation |
Rate-limited calls return HTTP 429 with a Retry-After header in seconds.
Honor it — most clients (Claude, Cursor, Claude Code) already do.
Error reference
Errors return a JSON-RPC envelope with a stable error discriminator and a
human-readable message.
| Error | HTTP | What happened |
|---|---|---|
invalid_credential | 401 | Bearer header missing, malformed, or didn't match an active credential. Re-issue and update your client config. |
invalid_input | 400 | Input failed schema validation (missing field, extra property, wrong type). The message names the offending field. |
tier_gate | 403 | A free-tier account hit a paid-only tool (subscription.cancel / subscription.negotiate). Upgrade to unblock. |
rate_limit | 429 | Per-credential, per-tool-class budget exhausted. The Retry-After header carries the wait in seconds. |
audit_not_found | 404 | audit.query got an audit_id that doesn't exist or isn't in your workspace. |
subscription_not_found | 404 | A cancel/negotiate call got a subscription_id outside your workspace. |
subscription_not_found_by_name | 404 | subscription.cancel got a vendor_name with multiple matches. The response includes a candidates[] array — re-call with the chosen subscription_id. |
no_target_price | 422 | subscription.negotiate needs a target_price. Call benchmark.lookup first. |
tool_error | 500 | Unexpected internal error. Surface a friendly message and offer to retry. |
Fail-closed under outage
If the rate-limit ledger is unreachable, the MCP server fails closed (a 503) rather than waving requests through — attackers can't bypass limits during an outage. Retry shortly; contact support if it persists.