Seven tools, one endpoint. The website and the MCP call the same deterministic services against the same read-only market database — an agent and a person asking the same question get the same answer. Reading needs no authentication; watches use an owner token. No tool can purchase, cancel or switch a service, and none requests credentials, personal data or private customer content.
claude mcp add --transport http agentcosts https://agentcosts.com/api/mcp
https://agentcosts.com/api/mcp
The endpoint speaks JSON-RPC 2.0 (initialize, tools/list, tools/call). Verified from a live client against the HTTPS endpoint on 6 Sep 2026.
Compose a call and run it against the live endpoint. Write tools ask for explicit acknowledgement; nothing runs on page load.
// response appears here — nothing runs until you click
Assess up to 10 services you currently use: current plans per tier and charging basis, direct accepted alternatives, and an honest gaps list.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
items | required | array of { listing (handle, required), series_id (optional exact plan) } — 1 to 10 items |
explain | optional, default false | adds a plain-language explanation generated only from the deterministic result |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "review_stack",
"arguments": {
"items": [
{
"listing": "openstatus",
"series_id": "openstatus|Pro|pro|flat"
}
]
}
}
}{ "tool": "review_stack", "items": [ { "listing": {...}, "your_plan": {...}, "all_current_plans": [...], "direct_alternatives": [...], "exclusions": [...], "gaps": [...] } ], "note": "assessment only…" }Unknown handles land in items[].error ("not found or not live") and gaps — never guessed.
Direct accepted substitutes for one service — one hop in the graph, never transitive. Filters are explained, never silent.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
listing | required | provider handle |
buyer_tier | optional | individual | pro | team_sme | enterprise — providers with no plan in this tier in the current observed data are excluded WITH that reason (absence of evidence, not confirmed nonexistence) |
charging_basis | optional | flat_subscription | usage | one_time | contact_sales |
max_monthly_usd | optional | applies ONLY to monthly-comparable USD plans; it cannot filter per-call, one-time or quote plans |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "find_alternatives",
"arguments": {
"listing": "ais_gateway",
"buyer_tier": "pro",
"charging_basis": "flat_subscription"
}
}
}{ "tool": "find_alternatives", "reference": "ais_gateway", "candidates": [ { "listing": {...}, "relationship": { "job_similarity": 0.78, ... }, "plans": [...] } ], "exclusions": [ { "listing": "...", "reason": "..." } ], "graph_note": "..." }A provider with no accepted edges returns candidates: [] — it currently stands alone at our similarity bar.
The evidence bundle: one reference series plus up to 8 alternative series, each with its own daily observations, qualified consecutive-day pairs, gaps and provenance flags.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
reference_series | required | exact series id: handle|plan_name|tier|unit — copy from review_stack / find_alternatives output, never construct by hand |
alternative_series | optional | array of series ids (max 8) |
days | optional, default 60, max 90 | history window |
explain | optional, default false | plain-language explanation of the deterministic result |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_evidence",
"arguments": {
"reference_series": "openstatus|Pro|pro|flat",
"alternative_series": [
"ais_gateway|Campus Pass|pro|flat"
],
"days": 30
}
}
}{ "tool": "get_evidence", "reference": { "series_id": "...", "current": {...}, "history": { "observations": [{date, rate, currency, mode}], "consecutive_pairs": [{from, to, ratio, qualifies, exclusion_reason}], "gaps_note": "...", "provenance": {...} } }, "alternatives": [...], "honesty": "..." }A series with no observations in the window returns observations: [] — one observation day is presented as exactly that, never a 60-day trend.
Create a durable watch on an exact series, a provider, or a job description. Evaluated once per day after the pricing refresh — not real time.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
kind | required | series | listing | job |
series_id / handle / job_query | one required by kind | what to watch |
triggers | optional, default all | price_change | became_unobserved | new_alternative |
owner_token | optional | omit on first use → a new token is issued IN THE RESPONSE, shown once, stored only as a hash |
label | optional | your display label |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_watch",
"arguments": {
"kind": "series",
"series_id": "openstatus|Pro|pro|flat",
"triggers": [
"price_change",
"became_unobserved"
]
}
}
}{ "tool": "create_watch", "owner_token": "ac_… (only when newly issued)", "token_note": "…never shown again", "watch_id": "w_…", "kind": "series", "triggers": [...] }Retry behaviour: creating the same watch twice makes two watches (creation is not idempotent); events ARE deduplicated per watch/type/series/day, so duplicate watches produce duplicate event streams, not corrupted ones. Delete extras with manage_watch.
Cursor-paginated events for all watches owned by a token, plus the last successful scan day.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
owner_token | required | your ac_ token |
cursor | optional, default 0 | return events with id greater than this; use next_cursor from the previous call |
limit | optional, default 50, max 200 | page size |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_updates",
"arguments": {
"owner_token": "ac_YOUR_TOKEN",
"cursor": 0,
"limit": 20
}
}
}{ "events": [ { "id": 14, "watch_id": "w_…", "event_type": "price_change", "event_date": "2026-09-05", "series_id": "...", "detail": {...} } ], "next_cursor": 14, "last_scan_day": "2026-09-06", "freshness": "present only when empty" }An empty page carries freshness + last_scan_day: "no qualifying events after a successful scan" is different from "the scan did not run", and the response says which.
List, pause, resume or delete your watches. Strictly owner-scoped.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
owner_token | required | your ac_ token |
action | required | list | pause | resume | delete |
watch_id | required except for list | the w_ id |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "manage_watch",
"arguments": {
"owner_token": "ac_YOUR_TOKEN",
"action": "list"
}
}
}{ "watches": [...] } or { "tool": "manage_watch", "watch_id": "w_…", "status": "paused" }A wrong token gets "watch not found for this owner" — never another owner's watch. pause/resume/delete are idempotent: repeating the call re-asserts the same state.
Optional, permissioned feedback after trying (or deciding against) a replacement. Failures are as useful as successes. Never a condition of access.
| FIELD | REQUIRED | MEANING & CONSTRAINTS |
|---|---|---|
candidate | required | handle of the listing you tried |
outcome | required | worked | failed | partial |
candidate_series / reference_series | optional | exact plans involved |
reported_cost_note | optional | what YOU say it cost — recorded as reported, never presented as verified savings |
share_permitted | optional, default false | explicit permission for your outcome to inform aggregate signals |
owner_token | optional | links the report to your watches |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "report_outcome",
"arguments": {
"candidate": "openstatus",
"outcome": "worked",
"share_permitted": false
}
}
}{ "ok": true, "recorded": { "candidate": "...", "outcome": "worked", "correlated": true|false }, "cost_note_policy": "..." }correlated is true only when THIS endpoint served you evidence about that provider in the previous 30 days — the link is proven, never fabricated.
handle|plan_name|tier|unit. A renamed plan is a new series; its disappearance prompts review — histories are never silently rejoined.openstatus|Pro|pro|flat.fx_normalised flag so wiggles are not read as repricings.There are no accounts. Your first create_watch without a token issues one ac_… token in the response — shown once, stored server-side only as a SHA-256 hash, unrecoverable if lost. The website’s My watches page keeps the token in your browser’s local storage only (never in URLs, analytics or logs) and offers a restore-by-token flow. We do not promise recovery we cannot provide: a lost token means creating new watches.
report_outcome is reachable after an evidence review or decision, is dismissible, and is never a condition of free access. Three records are kept distinct: a user report (what you said), a correlated report (we can prove this endpoint served you the evidence within 30 days), and verified results (which we do not claim — reported costs are not verified savings).
watch not found for this owner.handle|plan_name|tier|unit shape.manage_watch list after a network timeout instead of blindly retrying.short_history flag; one observation day is presented as exactly that.