AgentCosts
Free · feedback optionalConnect your agent

Plan-aware MCP documentation

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.

Connect

CLAUDE CODE
claude mcp add --transport http agentcosts https://agentcosts.com/api/mcp
ANY MCP CLIENT · STREAMABLE HTTP, NO AUTH
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.

Playground

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

review_stack

READ-ONLY

Assess up to 10 services you currently use: current plans per tier and charging basis, direct accepted alternatives, and an honest gaps list.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
itemsrequiredarray of { listing (handle, required), series_id (optional exact plan) } — 1 to 10 items
explainoptional, default falseadds a plain-language explanation generated only from the deterministic result

Copyable example (valid JSON, real identities from today’s data)

tools/call · review_stack
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "review_stack",
    "arguments": {
      "items": [
        {
          "listing": "openstatus",
          "series_id": "openstatus|Pro|pro|flat"
        }
      ]
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "tool": "review_stack", "items": [ { "listing": {...}, "your_plan": {...}, "all_current_plans": [...], "direct_alternatives": [...], "exclusions": [...], "gaps": [...] } ], "note": "assessment only…" }

Empty / partial results & write semantics

Unknown handles land in items[].error ("not found or not live") and gaps — never guessed.

Errors

  • items[] missing or empty → structured error with an example shape

find_alternatives

READ-ONLY

Direct accepted substitutes for one service — one hop in the graph, never transitive. Filters are explained, never silent.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
listingrequiredprovider handle
buyer_tieroptionalindividual | 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_basisoptionalflat_subscription | usage | one_time | contact_sales
max_monthly_usdoptionalapplies ONLY to monthly-comparable USD plans; it cannot filter per-call, one-time or quote plans

Copyable example (valid JSON, real identities from today’s data)

tools/call · find_alternatives
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "find_alternatives",
    "arguments": {
      "listing": "ais_gateway",
      "buyer_tier": "pro",
      "charging_basis": "flat_subscription"
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "tool": "find_alternatives", "reference": "ais_gateway", "candidates": [ { "listing": {...}, "relationship": { "job_similarity": 0.78, ... }, "plans": [...] } ], "exclusions": [ { "listing": "...", "reason": "..." } ], "graph_note": "..." }

Empty / partial results & write semantics

A provider with no accepted edges returns candidates: [] — it currently stands alone at our similarity bar.

Errors

  • listing missing → 'listing (handle) required'
  • exclusion reasons distinguish provider-level absence (no plan in the tier observed) from filter effects — a provider is only excluded after ALL its plans fail the filter, one wrong-tier plan alone never rejects a provider

get_evidence

READ-ONLY

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.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
reference_seriesrequiredexact series id: handle|plan_name|tier|unit — copy from review_stack / find_alternatives output, never construct by hand
alternative_seriesoptionalarray of series ids (max 8)
daysoptional, default 60, max 90history window
explainoptional, default falseplain-language explanation of the deterministic result

Copyable example (valid JSON, real identities from today’s data)

tools/call · get_evidence
{
  "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
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "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": "..." }

Empty / partial results & write semantics

A series with no observations in the window returns observations: [] — one observation day is presented as exactly that, never a 60-day trend.

Errors

  • malformed reference_series → error naming the handle|plan_name|tier|unit shape
  • renamed plans start a NEW series; histories are never auto-joined

create_watch

WRITES A DURABLE RECORD

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.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
kindrequiredseries | listing | job
series_id / handle / job_queryone required by kindwhat to watch
triggersoptional, default allprice_change | became_unobserved | new_alternative
owner_tokenoptionalomit on first use → a new token is issued IN THE RESPONSE, shown once, stored only as a hash
labeloptionalyour display label

Copyable example (valid JSON, real identities from today’s data)

tools/call · create_watch
{
  "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"
      ]
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "tool": "create_watch", "owner_token": "ac_… (only when newly issued)", "token_note": "…never shown again", "watch_id": "w_…", "kind": "series", "triggers": [...] }

Empty / partial results & write semantics

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.

Errors

  • invalid series_id → structured error
  • watch limit per token enforced server-side

get_updates

READ-ONLY

Cursor-paginated events for all watches owned by a token, plus the last successful scan day.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
owner_tokenrequiredyour ac_ token
cursoroptional, default 0return events with id greater than this; use next_cursor from the previous call
limitoptional, default 50, max 200page size

Copyable example (valid JSON, real identities from today’s data)

tools/call · get_updates
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_updates",
    "arguments": {
      "owner_token": "ac_YOUR_TOKEN",
      "cursor": 0,
      "limit": 20
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "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" }

Empty / partial results & write semantics

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.

Errors

  • missing/unknown token → owner-scoped empty result, never someone else's events

manage_watch

WRITES A DURABLE RECORD

List, pause, resume or delete your watches. Strictly owner-scoped.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
owner_tokenrequiredyour ac_ token
actionrequiredlist | pause | resume | delete
watch_idrequired except for listthe w_ id

Copyable example (valid JSON, real identities from today’s data)

tools/call · manage_watch
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "manage_watch",
    "arguments": {
      "owner_token": "ac_YOUR_TOKEN",
      "action": "list"
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "watches": [...] } or { "tool": "manage_watch", "watch_id": "w_…", "status": "paused" }

Empty / partial results & write semantics

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.

Errors

  • deleting an already-deleted watch → 'not found for this owner (or already deleted)'

report_outcome

WRITES A DURABLE RECORD

Optional, permissioned feedback after trying (or deciding against) a replacement. Failures are as useful as successes. Never a condition of access.

Fields

FIELDREQUIREDMEANING & CONSTRAINTS
candidaterequiredhandle of the listing you tried
outcomerequiredworked | failed | partial
candidate_series / reference_seriesoptionalexact plans involved
reported_cost_noteoptionalwhat YOU say it cost — recorded as reported, never presented as verified savings
share_permittedoptional, default falseexplicit permission for your outcome to inform aggregate signals
owner_tokenoptionallinks the report to your watches

Copyable example (valid JSON, real identities from today’s data)

tools/call · report_outcome
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "report_outcome",
    "arguments": {
      "candidate": "openstatus",
      "outcome": "worked",
      "share_permitted": false
    }
  }
}

Successful response shape

RESPONSE (SHAPE)
{ "ok": true, "recorded": { "candidate": "...", "outcome": "worked", "correlated": true|false }, "cost_note_policy": "..." }

Empty / partial results & write semantics

correlated is true only when THIS endpoint served you evidence about that provider in the previous 30 days — the link is proven, never fabricated.

Errors

  • unknown candidate handle → structured error
  • invalid outcome value → the allowed enum is returned

Plan identity & evidence

  • Plan identity is handle|plan_name|tier|unit. A renamed plan is a new series; its disappearance prompts review — histories are never silently rejoined.
  • Tier labels are re-derived deterministically and can change over time (OpenStatus Starter moved from pro to individual in the July reclassification). That is why examples here are generated from the running service: today’s valid reference is openstatus|Pro|pro|flat.
  • Trend claims use only qualified consecutive-day pairs (same currency, same charging mode, no anomaly flags). Gaps are never interpolated.
  • Contact-sales is reported as contact-sales, never a number. Unknown stays unknown; missing or negotiated prices are never rendered as zero.
  • FX-converted rates (non-USD sources) can wiggle a few cents day to day; such plans carry an fx_normalised flag so wiggles are not read as repricings.

Watch ownership

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.

Optional feedback

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).

Errors

  • Unknown handle / series → structured error with a reason, not an empty guess.
  • Wrong or missing owner token → watch not found for this owner.
  • Malformed series id → error naming the expected handle|plan_name|tier|unit shape.
  • Invalid JSON-RPC params → code −32602 with a message; unknown tool → −32601.
  • Write retries: watch state changes are idempotent; watch creation is not (see create_watch) — check manage_watch list after a network timeout instead of blindly retrying.

Data limitations (read this)

  • We observe public pricing pages daily. Usage-based bills, negotiated discounts and private quotes are invisible to us.
  • Alternatives come from a curated accepted-substitute graph, one hop only. Graph similarity is relationship metadata — not a probability of suitability, not an assurance of equivalent capacity.
  • Young listings carry a short_history flag; one observation day is presented as exactly that.
  • “No plan in this tier” means none found in the current observed data — absence of evidence, not confirmed nonexistence.
  • Freshness: every count and scan date shown on this site comes from a live query with its scope stated; nothing is copied from samples.