OpenBrief
Log in Sign up
Developers · MCP server

MCP server

Drive your AI agent (Claude Desktop, Cursor, custom client) directly against alitheia's data + analytical surfaces. Same Team-tier API keys as the REST API; same scopes; same provenance.

Overview

The Model Context Protocol (MCP) server exposes alitheia as a tool surface for AI agents. It speaks JSON-RPC over HTTP using the streamable transport defined by the MCP 2025-03-26 spec, and ships:

  • 33 atomic tools — resolvers, list/detail readers, lenses, Pro signals, watchlist mutations. Direct map to the REST surface.
  • 9 composed tools — intent-driven workflows (build_actor_dossier, compare_actors, find_angle_gap, surface_underreported_spikes, quote_finder, trace_money_and_influence, frame_narrative, expand_section, refine) that fan out across multiple atomic queries and return summary + query_id for follow-ups.
  • 5 persona prompts — named slash commands the agent client renders for the user.
  • 5 resources — subscribable JSON feeds plus a static methodology document.

Every successful response carries a citation envelope. Provenance is the safety story; alitheia returns evidence, the agent decides framing.

Endpoint

This deployment advertises the MCP endpoint at:

https://openbrief.co.nz/mcp

Operator notes — binary configuration:

Env varDefaultPurpose
MCP_LISTEN_ADDR :8090 Bind address for the streamable HTTP transport. Required (binary refuses to boot without it).
MCP_ADVERTISED_URL derived from PUBLIC_BASE_URL Public URL surfaced in setup snippets on this page and on /account/api-keys. Override when the MCP listener sits behind a different host (e.g. mcp.example.com) or when the public base URL has no canonical /mcp path.
API_TOKEN_FORMAT opaque Shared with the REST API. opaque = DB-backed sha256-hashed strings; jwt = RS256-signed JWTs with JWKS at /.well-known/jwks.json.
DATABASE_URL Same Postgres as cmd/api. The MCP binary doesn't issue keys (issuance lives on the website); it validates tokens minted there.

Auth

Bearer tokens. Tier team required — non-Team tokens get a 401 with a tier-mismatch hint in the body.

Mint a key from /account/api-keys; it works for both the REST API and the MCP endpoint. Per-tool scopes apply:

  • read:core — people, organisations, donations, speeches, topics, elections, audio appearances. The atomic data surface.
  • read:discourse — commentary corpus signals (Discourse Lens).
  • read:press — news corpus signals (Press Lens).
  • read:pro — daily brief, narratives, actor snapshots, alerts, watchlist.
  • write:watchlistadd_watchlist / remove_watchlist.

Tools that span corpora (e.g. find_angle_gap, frame_narrative) require both read:discourse and read:press. Scope mismatches return a tool-level scope_required error rather than a transport 403, so the agent gets a structured signal instead of a connection failure.

Setup

Claude Desktop — paste into claude_desktop_config.json:

{
  "mcpServers": {
    "alitheia": {
      "url": "https://openbrief.co.nz/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY_HERE" }
    }
  }
}

Generic MCP client — configure the streamable HTTP transport with:

  • Endpoint: https://openbrief.co.nz/mcp
  • Header: Authorization: Bearer YOUR_KEY_HERE

Replace YOUR_KEY_HERE with the raw token shown once after key creation. The key is opaque (ali_…) by default; JWT mode is also supported.

Atomic tools (33)

Direct map to the REST API. Each tool returns the citation envelope with one or more citations[] entries.

Resolvers
  • resolve_person(query) — free-text → person_id candidates.
  • resolve_topic(query) — phrase → topic_slug candidates.
  • resolve_organisation(query, kind?) — partial name → org candidates with NZBN.
Core data read:core
  • list_mps / get_mp — sitting MPs + full briefing.
  • list_people / get_person — broader anchored people set.
  • list_organisations / get_organisation — parties, companies, charities, lobby principals.
  • search_donations — the >$20k disclosure register.
  • search_speeches / get_speech — Hansard.
  • list_topics / get_topic — canonical topic taxonomy.
  • list_elections / get_election_year — per-cycle stats.
  • list_candidates / get_candidate — per-cycle candidate roster + full candidacy briefing.
  • get_electorate — seat profile: sitting MP + contested history.
  • person_appearances / get_episode_appearance — audio episodes + diarised turns.
Discourse Lens read:discourse
  • discourse_digest — top topics this week with framing samples.
  • discourse_heatmap — topic-by-week intensity grid.
Press Lens read:press
  • press_digest — top news topics this week.
  • press_heatmap — topic-by-week intensity for the news corpus.
  • press_topic_detail(slug) — per-topic news detail.
Pro signals read:pro
  • pro_brief_today — daily alert cards.
  • pro_narratives_active — top 7-day narratives.
  • pro_actor_snapshots — daily per-actor snapshots.
  • pro_alerts — watchlist-scoped anomaly feed.
  • list_watchlist — caller's watchlist items.
Watchlist mutations write:watchlist
  • add_watchlist(kind, ref, label, sublabel?) — watch an MP / topic / source.
  • remove_watchlist(id) — drop a watchlist row.

Composed tools (9)

Fan out across multiple atomic queries, return summary + per-section top-N + a query_id. Drill into a section without re-paying retrieval cost via expand_section; layer extra filters via refine.

  • build_actor_dossier(person_id|name, lookback_days?, include?, angle?, top_n?) — identity, interests, directorships, meetings, recent speeches, press/commentary mentions, audio appearances, focus topics. Keystone tool.
  • compare_actors(person_ids[], topic_slug|topic_query, window_days?) — 2..5 person side-by-side on a topic axis with directorship + interest overlap sets.
  • find_angle_gap(topic_slug|topic_query, window_days?, direction?) — cross-corpus discourse-vs-press counts, modal framings per side, candidate angles. Requires both read:discourse and read:press.
  • surface_underreported_spikes(within_hours?, min_zscore?, corpus?, kinds?, limit?) — today's anomaly feed re-ranked by spike-vs-press-coverage asymmetry.
  • quote_finder(person_id, topic_slug|topic_query, since?, until?, corpus[]?, limit?) — verbatim Hansard / audio / commentary / news quotes by person.
  • trace_money_and_influence(person_id|org_id, depth?, min_amount_cents?, year?) — donation + directorship + interest + ministerial-meeting graph. Always carries donations_election_year_only.
  • frame_narrative(topic_slug|topic_query, agenda?, lookback_days?) — modal framings per corpus, framing drift (week-by-week alias chain), adjacent topics, stance breakdown, recent press headlines. No content generation — alitheia returns evidence, the agent composes the framing. Requires both lens scopes.
  • expand_section(query_id, section, offset?, limit?) — paginate one section of a prior composed-tool result.
  • refine(query_id, patch) — layer extra filters on top of the original inputs and re-issue.

Prompts (5)

Persona-anchored slash commands. The agent client renders these to the user; selecting one pushes a primed instruction into the conversation that names the composed tools to chain.

  • /dossier (strategist) — opposition dossier on {person} for {electorate?}, weighted toward {topic_focus}. Chains build_actor_dossiercompare_actorsframe_narrativefind_angle_gap.
  • /morning_brief (newsroom)surface_underreported_spikes → drill top 3 → output 3 pitchable angles each with ≥2 source URLs.
  • /topic_deep_dive (researcher) — quote_finder per relevant MP + trace_money on top declared interests, ends with stance-by-party table.
  • /counter_narrative (PR) — frame_narrative → find_angle_gap → quote_finder per opposing voice; output 3 reframes each grounded in ≥3 verbatim quotes.
  • /follow_money (cross-cutting) — trace_money_and_influence depth=2 → quote_finder per notable edge endpoint, with explicit cycle-gap callout.

Resources (5)

Read-only URIs. The agent client subscribes / pulls; alitheia returns JSON (or markdown for the methodology doc). Pull-only today; SSE / push is a future add.

  • alitheia://brief/today — today's Pro brief cards. Requires read:pro.
  • alitheia://narratives/active — top 7-day cross-corpus narratives. Requires read:pro.
  • alitheia://watchlist/alerts — caller's watchlist alert feed. Requires read:pro.
  • alitheia://taxonomy/topics — canonical topic catalogue (slug → label + aliases). Use to resolve free-text phrases without a tool call. Requires read:core.
  • alitheia://methodology — static markdown reference: stance scale, anomaly z-score thresholds, donations cycle caveat, elections.nz block caveat, the >$20k threshold. Public.

Citation envelope

Every successful tool response returns a structured envelope:

{
  "data": <tool-specific>,
  "citations": [
    {
      "source": "elections.nz",
      "source_label": "Electoral Commission — Donations exceeding $20,000",
      "url": "https://elections.nz/...",
      "fetched_at": "2026-05-08T12:34:56Z",
      "extraction_method": "scraper",
      "caveats": ["donations_election_year_only"]
    }
  ],
  "retrieved_at": "2026-05-08T12:34:56Z",
  "query_id": "qry_AbC123_xY",
  "next_cursor": null
}
  • citations[] is non-optional on success. If a tool returns a synthesised summary without per-claim citations, that's a bug.
  • caveats[] on a citation is a list of stable slugs. See Caveats below.
  • query_id is the handle for expand_section and refine. Continuations are scoped to the api key; a peer's query_id is invisible.
  • next_cursor is opaque. Pass back to expand_section verbatim.

Caveats

Caveats are stable slugs surfaced as citation.caveats[]. Propagate them when summarising or quoting; they encode known data-quality bounds the agent must not gloss over.

SlugMeaning
donations_election_year_only The Electoral Commission's >$20,000 disclosure register publishes per electoral cycle, not annually. Non-election years (e.g. 2024, 2025) have no rows by design — absence means "not yet disclosed for this cycle", not "no donations". Annual returns PDFs would fill the gap; alitheia doesn't ingest them yet.
elections_nz_blocked elections.nz is intermittently hard-blocked by Imperva at the network edge. Candidate rosters for blocked cycles surface this caveat instead of silently returning partial data.

Read alitheia://methodology from your agent for the full methodology + corpus-bound reference.

Open the REST API docs for the equivalent JSON / OpenAPI surface, or jump to /account/api-keys to mint a key.