Contents
<!-- MAINTENANCE: the MCP tool surface is also listed inline in /llms.txt (src/app/llms.txt/route.ts). If you add, remove, or rename an MCP tool, update that file too so the agent-facing entry point stays current. -->

mumo MCP Server

Run multi-model deliberations directly from MCP-compatible clients such as Claude Code, Cursor, Codex, VS Code, Hermes, OpenClaw, Grok Bot, Claude Desktop, and others. Your agent sends a question to models from different labs — Claude, GPT, Gemini, Grok, DeepSeek, Kimi, and more — and gets back their full responses plus typed cross-model reactions: what each model keeps, challenges, or wants explored in the others' claims.

The MCP server uses the same auth and session artifacts as the REST API, but it is an operator-controlled surface: your agent starts a panel round, waits for the result, reads the responses and claim map, then decides whether to append another round. Use MCP if you want deliberation as tool calls; use the REST API if you're integrating from your own backend.

New to mumo MCP? Start with the generic install guide. It walks you through adding the server config, generating your API key, and verifying the tools. This page goes deeper on client-specific examples, tool behavior, and response fields.

Setup#

Claude Code#

The mumo plugin installs straight from its repo (/plugin marketplace add mumo-chat/mumo-claude, then /plugin install mumo@mumo); it bundles the MCP server config plus an auto-triggering skill. Your API key is stored in your system keychain (no env-var export required). Full walkthrough at mumo.chat/install/claude-code.

Codex#

The mumo Codex plugin bundles the MCP server config and an auto-triggering skill. Register the marketplace, then install from the Codex desktop app (Plugins → publisher dropdown → mumo → + → Install mumo). Auth via the MUMO_API_KEY env var; the install page covers exposing it to GUI-launched Codex on macOS and the plaintext-file tradeoff every persistent option carries. Full walkthrough at mumo.chat/install/codex.

Cursor#

The mumo Cursor plugin ships the behavior layer — an auto-triggering skill and a Cursor rule. The MCP server is added separately with a one-click Add to Cursor install link (paste your mmo_live_* key at mumo.chat/install/cursor); it writes the server into your user-level ~/.cursor/mcp.json — no environment variables. Full walkthrough at mumo.chat/install/cursor.

Invocation. Cursor's rule system treats plugin rules and skills as soft priors — auto-trigger on contested decisions is best-effort. For reliable routing, name mumo explicitly in your prompt: "ask mumo about…", "run this by a mumo panel", "get me a second opinion from mumo."

Grok Bot#

The only client with no package to install — no repo, no plugin, no skill. You add the remote MCP server from inside the chat, and the guidance arrives over HTTP instead of on disk. Tell your Bot to visit mumo.chat/install/grok-bot.md and follow it: it mints the key itself (you sign in with Google in its browser), wires https://mumo.chat/api/mcp with a Bearer header, and smoke-tests with list_models. Full walkthrough at mumo.chat/install/grok-bot.

Three agent-facing URLs, each with one job:

URLJob
/install/grok-bot.mdSetup runbook. Read once, at install.
/install/grok-bot.instructions.mdA ≤500-char stub the Bot persists via SetMcpInstructions. Trigger plus a pointer.
/install/grok-bot.skill.mdOperating guide. What the stub points at when the Bot needs depth.

Two host quirks worth knowing if you're integrating. Grok Bot does not surface the MCP instructions field from initialize — the server sends it, but it never reaches the model, which is why the persisted stub exists. And SetMcpInstructions returns success while silently truncating at 500 characters, so the stub is sized to fit and the runbook tells the Bot to read the stored value back and compare rather than trusting the success return.

Hermes Agent#

The mumo Hermes skill clones into ~/.hermes/skills/<category>/mumo/ and ships with the canonical SKILL.md, four cognitive-shape playbooks, and reference docs. Merge the bundled config/mumo.yaml into ~/.hermes/config.yaml under mcp_servers — its header references ${MUMO_API_KEY}, which Hermes resolves from ~/.hermes/.env (chmod 600), so the config block never holds the key — restart Hermes (the /reload-mcp slash command is unreliable across versions), verify with hermes mcp test mumo (a real connection; hermes mcp list only echoes saved config), and the mumo tools surface as mcp_mumo_*. Full walkthrough at mumo.chat/install/hermes.

OpenClaw#

The mumo OpenClaw skill installs via ClawHub (openclaw skills install mumo — published listing at clawhub.ai/ericatmumo/mumo — lands in the active agent workspace) or by cloning the source repo into ~/.openclaw/skills/mumo/. Either path lands the canonical SKILL.md, four cognitive-shape playbooks, and reference docs. Register the MCP server with openclaw mcp set mumo '<json>' (Streamable HTTP; the Authorization header references ${MUMO_API_KEY}, which OpenClaw resolves from ~/.openclaw/.env and warns about at startup when unset), restart OpenClaw, verify with a free list_models call through the agent (openclaw mcp show reads config only), and the mumo tools surface as mumo__create_deliberation, mumo__wait_for_round, etc. (double-underscore prefix). Full walkthrough at mumo.chat/install/openclaw.

VS Code (GitHub Copilot)#

The mumo extension installs from the Visual Studio Marketplace and stores your key in SecretStorage (macOS Keychain / Windows Credential Manager / Linux libsecret). A Get Started walkthrough opens on first install. Full walkthrough at mumo.chat/install/vs-code.

Invocation. VS Code's Copilot doesn't have a SKILL.md discovery mechanism the way Claude Code and Cursor do, so the auto-triggering skill from those plugins doesn't apply. Invoke mumo explicitly in Agent chat: "ask mumo about…", "run this by a mumo panel", "get me a second opinion from mumo."

Claude Desktop#

Two first-party routes are documented here, and the generic mcp.json block in the next section is not one of them — Anthropic documents claude_desktop_config.json for local (stdio) servers.

  1. Custom connector — the native Chat-tab route. Add a custom connector (Customize → Connectors → Add custom connector; Team/Enterprise owners: Organization settings → Connectors), enter https://mumo.chat/api/mcp as the remote MCP server URL, set Authentication to None, and under Request headers add Authorization with the full value Bearer mmo_live_… — include Bearer and the space, because Claude sends the value exactly as entered. Request-header authentication is in beta and available to a limited set of organizations: if the dialog has no Request headers section, your organization doesn't have it yet, and mumo can't currently be added as a custom connector. Authentication settings can't be changed after the connector is added (remove and re-add it to rotate a key), and on Team/Enterprise the header credential is shared by the organization rather than entered per user.
  2. The Code tab runs Claude Code, so install the Claude Code plugin. The Desktop app also loads servers defined in claude_desktop_config.json into local Code tab sessions, alongside Claude Code's own config files.

Other clients (Windsurf, Cline, Zed, …)#

Any MCP-compatible client that supports HTTP transport with a custom Authorization header will work.

  1. Add a remote MCP server named mumo in your client.
  2. Point it at https://mumo.chat/api/mcp with an Authorization header placeholder.
  3. Generate a platform API key at mumo.chat/settings/api-keys. Keys begin with mmo_live_.
  4. Replace the placeholder with your real key, reload your client, and verify the mumo tools appear.
  5. Ask for your first deliberation.

Most clients accept a generic mcp.json shape:

{
  "mcpServers": {
    "mumo": {
      "url": "https://mumo.chat/api/mcp",
      "headers": {
        "Authorization": "Bearer mmo_live_YOUR_KEY_HERE"
      }
    }
  }
}

Some clients use different keys (servers instead of mcpServers), TOML instead of JSON, or nested header settings — check your client's docs.

Gateways that reserve Authorization (Smithery, some enterprise proxies): send the same key bare in X-Mumo-Api-Key: mmo_live_… instead. Same key, same lookup, same errors; if the gateway also injects its own Authorization upstream, your key in X-Mumo-Api-Key still wins.

Discovery without a key#

The read-only handshake works unauthenticated, so a client, registry, or directory scanner can see what mumo offers before anyone creates a key: initialize, ping, and tools/list return the server identity, the instructions text, and every tool's description and schema (the other standard listings, resources/list, resources/templates/list and prompts/list, answer too — mumo registers none, so they return method-not-found rather than a 401). Everything that reaches a tool — tools/call, including the read-only tools like list_models — still requires Authorization: Bearer mmo_live_…; a keyless call gets a 401 whose body carries keys_url and setup_url, and whose WWW-Authenticate header names the Bearer scheme.

The same facts are published as static documents, so nothing needs a live connection to index mumo:

URLWhat it is
https://mumo.chat/.well-known/mcp.jsonSelf-describing manifest — endpoint, transport, bearer contract, tool list.
https://mumo.chat/api/mcp/server-cardThe MCP Server Card (SEP-2127) at the spec's recommended location, <endpoint>/server-card; registry server.json shape. Also at /.well-known/mcp-server-card and /.well-known/mcp/server-card.json.
https://mumo.chat/mcpContent-negotiating discovery address: JSON for agents, a redirect to the install guide for browsers.

Your first call#

The simplest way to verify everything is wired up is to fire off a round. From your agent:

Create a deliberation: "Should we use Postgres or MongoDB for an event store?"

The agent invokes create_deliberation. The call returns in under a second with a session id, round id, display-friendly round number, and progress URL — but the models are still running. Ask the agent to call wait_for_round with the returned session_id — that alone resolves to the round in flight; round_id is optional (typically 15–120s depending on model choice). Then:

Session id abc-123, status ready.
3 models responded. claim_map.claims[] has 4 entries with cross-model positions.

The loop: tool call (fast ack) → wait_for_round → responses + claim map → agent reads → append or stop.


Tools#

MCP keeps canonical API field names where the same concept crosses the boundary, but omits REST-only controls that do not belong in agent-moderated sessions.

ToolPurpose
create_deliberationStart a new deliberation.
append_roundAdd a follow-up round with steering snippets.
wait_for_roundWait for a round to finish, then return the full session.
get_sessionFetch full session state.
share_sessionShare a session at a public URL.
list_sessionsList your sessions, optionally filtered.
list_modelsSee models available to your tier.
get_creditCheck current credit wallet balance.

create_deliberation#

Start a new agent-moderated session. We'll return an ack immediately and run the models in the background. Call wait_for_round to read the round's artifacts once it reports complete. You'll receive raw responses + a cross-model claim map. If one round gives you everything you need, you're done. If you want more, call append_round with the existing session_id. Participating models will automatically get full context and you'll get the benefit of input caching when available on subsequent rounds.

Inputs:

  • prompt (required) — the question or topic.
  • reference — optional doc, spec, or design. Injected as shared context for all models.
  • models — array of 2–3 model IDs. Omit it and the platform curates a panel of up to your plan's seat cap from the models your plan can run. Call list_models to enumerate.
  • moderator_name — display name for the steering identity (e.g., your agent's name). Surfaces in the published transcript. Also important for optimizing participating model selection when you don't specify models.
  • application — display name of the client (e.g., "Claude Code"). Surfaces in the session info panel.
  • improvement_consent — optional per-session Platform Improvement request. Any account may pass true or false; omitted uses the account default. This is narrowing-only: false excludes the session even if the account default is on; true never widens beyond the account's own choice.
  • dataset_consent — optional per-session Dataset Contribution request, the second election. Same rules as improvement_consent: narrowing-only, create-only; an account that has not yet made its Dataset Contribution choice stays excluded whatever the session requests. See Consent elections.
  • takeaway — optional boolean (default false). When true, generates a structured per-round Takeaway (round_takeaway) when round 0 completes. See Round Takeaway artifacts for the shape and pricing.
  • register — deprecated, ignored. The platform runs a single prompt environment for all sessions. Still accepted (and still part of idempotency-key derivation) so existing callers' retries stay stable; omit it in new integrations.
  • web_search — optional boolean (default true). Set false to skip web search entirely (no search gate, no source pack on round 0). Disable for prompts grounded purely in supplied reference material.

Returns: an ack — session_id, round_id, round_index (0-based API value; display as round round_index + 1), progress_url, and polling metadata:

{
  "session_id": "abc-123",
  "round_id": "round-456",
  "round_index": 0,
  "status": "processing",
  "progress_url": "/api/sessions/abc-123/progress",
  "poll_after_ms": 5000,
  "progress_version": 0
}

Actual debits settle as models complete. Use get_credit when you need a fresh wallet read — it is the only surface that reports what was charged. See Credit wallet.

Models run in the background; read the session (rounds, claim maps) via wait_for_round once the round is complete.

append_round#

Add a follow-up round. Use after reading wait_for_round or get_session to steer the next round.

Inputs:

  • session_id (required) — session ID from a prior create_deliberation.
  • prompt (required) — the steering prompt for this round.
  • snippets — array of typed cross-model forwards. Each:
    • type — KEEP | EXPLORE | CHALLENGE | CORE | SHIFT
    • quote — verbatim quote from a prior round's response
    • quoted_model — model ID that said it
    • comment — optional commentary explaining why you're forwarding it
  • moderator_name — supply only when the steering identity changes mid-session.
  • takeaway — optional boolean (default false). When true, generates a round_takeaway for this round.

Snippets are the highest-signal way to direct attention. Models see them as curated forwards from the moderator, with the snippet type shaping how they respond.

Returns: same ack shape as create_deliberation. Call wait_for_round with the session_id for the new round's artifacts and its cost_usd (it resolves to the round in flight; round_id is optional).

Errors:

  • The session must be in status: "ready". If it's streaming or processing, wait. If failed, you can't continue.
  • Round-append duplication corrupts deliberation history — the MCP server passes a derived idempotency key automatically based on the call args.

wait_for_round#

Wait for a round to finish, then return the full session. This is the intended follow-up to create_deliberation and append_round.

Under the hood, the tool polls the lightweight progress endpoint and fetches the full session exactly once after the target round reaches terminal state. Use this instead of repeatedly calling get_session while models are still running.

Inputs:

  • session_id (required) — session ID from create_deliberation or append_round.
  • round_id — optional. Omit it and the call resolves to the latest round — the one in flight, since a session has at most one — which is the common case right after create_deliberation or append_round. The waiter pins to that round on its first poll, so a round appended mid-wait by another moderator (the web UI, a second agent) never switches the target. Pass round_id to wait on or read an earlier round.
  • round_index — optional 0-based alternative to round_id.
  • timeout_seconds — max wait before returning a timeout response (default 40; max 120). On timeout, the response carries recommended_client_action: "poll_again" and the round_id it was waiting on — re-call with that round_id to continue waiting on the same round.
  • poll_after_ms — initial polling interval (default 1000; backs off up to 5000).

On timeout, the round keeps running. Call wait_for_round again with the round_id from the timeout response (the pin is per call, so a session-only retry could land on a round another moderator appended in between), or call get_session if you specifically need a partial read.

Web-created rounds. wait_for_round also works on sessions started from the mumo web UI (an agent taking over moderation of a web session). Those rounds resolve when all model responses have settled; round artifacts (claim map, takeaway) may still be generating at that moment. claim_map_url is always present on terminal responses and does not imply the map is ready — if you need the map itself, re-read via get_session shortly after.

Structured output. wait_for_round returns both human-readable text (content[0].text — full session markdown on terminal, short prose summary on timeout) and protocol-native structured data (structuredContent) that lets agents branch on round state without parsing prose. Clients that don't support structuredContent still see the existing text channel unchanged.

{
  "session_id": "ses_...",
  "round_id": "rnd_...",
  "round_index": 1,
  "round_status": "partial_failure",
  "is_terminal": true,
  "is_usable": true,
  "completed_models": [
    { "model": "claude-opus-4-7", "tokens_in": 4120, "tokens_out": 891, "finish_reason": "end_turn", "is_partial": false, "output_cap": null }
  ],
  "in_progress_models": [],
  "failed_models": [
    { "model": "qwen-3.6-plus", "error": "stream ended without done event", "message": "...", "error_code": "stream_ended_without_final_marker", "output_cap": null, "partial_text": "...", "partial_text_length": 1804 }
  ],
  "recommended_client_action": "proceed_with_partial_result",
  "claim_map_url": "https://mumo.chat/cm/rnd_...?src=mcp"
}

claim_map_url (terminal responses only) opens this round's claim map — verbatim model quotes with typed cross-model reactions — inside the full session view, alongside the raw responses with click-through to the inline text. It's auth-gated and owner-only (requires signing in with the mumo account that owns the API key), so it's safe to print into terminals. Always surface it to the user at the end of your summary so they can review the deliberation directly. The same URL appears per round in get_session output. MCP-emitted URLs carry a ?src=mcp query param (analytics attribution only — it resolves identically without it; the REST API returns the bare URL).

Handling partial rounds

wait_for_round always returns HTTP 200 with explicit semantic state. Branch on round_status and recommended_client_action rather than parsing prose or pattern-matching on error strings.

round_status values:

  • complete — every target model produced a final response. Call get_session for full content.
  • partial_failure — at least one model produced a final response, at least one failed. is_usable: true. Read successful answers from completed_models[]; read attribution from failed_models[].
  • in_progress — round is still running. Call wait_for_round again, passing the response's round_id; recommended_client_action: "poll_again".
  • failed — every target model failed and the round produced no usable output. Inspect recommended_client_action: "retry" when at least one failure's error_code is in the transient set (rate_limit, provider_error, internal_deadline_reached, deadline_expired, stream_interrupted, stream_ended_without_final_marker, empty_completion), "abandon" otherwise. Codes that look like failures but aren't safely retryable — pre_stream_provider_error (covers auth/malformed 4xx as well as 5xx), provider_auth_failure, pre_stream_failure, max_retries_exceeded — map to abandon. provider_refusal means the model provider's safety system declined the request: do not retry or rephrase automatically — surface it to the user. A refusal dominates: a failed round with any provider_refusal entry is "abandon", even when another model failed with a transient code (a partial_failure round is unaffected). credit_exhausted means your remaining credit could not cover that model when it was about to run, so it was skipped (no provider call, no charge). It dominates the same way ("abandon"): re-running before the balance changes is refused again, so check get_credit first. When every model is skipped this way, the round's failure_code is all_providers_failed. Each entry in failed_models[] carries a canonical error_code (or null on legacy rows) alongside the free-text error / message; switch on error_code rather than parsing prose.

recommended_client_action contract:

ActionMeaning
proceed_with_complete_resultRound is complete; read responses from the full session.
proceed_with_partial_resultAt least one model succeeded; partial result is usable.
poll_againRound still in flight; call wait_for_round again with the response's round_id.
retryRound failed but the failure looks transient; safe to call append_round (or a fresh create_deliberation) again.
abandonRound failed non-transiently (auth, malformed input, a provider refusal, etc.); retrying is unlikely to help.

The action is server-derived guidance, not authority. Sophisticated agents can apply their own logic over the raw fields (round_status, is_terminal, is_usable, the per-model arrays).

Polling guidance. wait_for_round blocks server-side with internal polling. Default timeout is 40s, max 120s — the upper bound matches the empirical MCP transport ceiling so the schema enforces the safe range; the 40s default leaves headroom against shorter host-side request ceilings observed in some clients. For rounds longer than the timeout, the response carries recommended_client_action: "poll_again" and the agent re-calls. The /api/sessions/{id}/progress endpoint supports If-None-Match/ETag for cheap no-change polls if you'd rather poll directly.

Partial-text handling. Partial text appears in two distinct places depending on outcome:

  • Successful-but-truncated responses stay in completed_models[] (and rounds[].responses[] on get_session) with is_partial: true and a populated finish_reason. The model produced output and the call reached done, but the provider signaled truncation. Common cause: finish_reason: "max_tokens" (or "length", "MAX_TOKENS" per provider vocabulary) — the output cap was hit. Treat as a partial answer; consider asking the user whether to extend.
  • Platform output caps. Every completed_models[] and failed_models[] entry (and rounds[].responses[] / failed_models[] on get_session) carries output_cap: null unless a platform cap bound the call's output length, else { tokens, reason } — tokens is the ceiling the cap imposed, reason is guest (the guest output limit) or balance (your remaining credit bounded the output). A truncated response with a non-null output_cap was cut by the platform, not the model's own limit; a call the cap left with no usable text is a failed_models[] entry with it set. On reason: "balance", top up or wait for the credit reset (get_credit) rather than retrying.
  • Refusal stops also stay in completed_models[], with is_partial: false and a refusal finish_reason: refusal, content_filter, SAFETY, PROHIBITED_CONTENT, BLOCKLIST, SPII or IMAGE_SAFETY. The provider's safety system stopped the output partway; the text is what the model produced before it stopped. Do not retry or rephrase automatically — surface it to the user. A refusal before any output is a failed_models[] entry with error_code: "provider_refusal".
  • Error rows that managed to produce text appear in failed_models[] with partial_text and partial_text_length populated, NOT in completed_models[]/responses[]. Those collections are reserved for successful answers. Common causes: provider_error (the provider failed after it started responding — on a round you run through MCP too), stream_ended_without_final_marker (SDK closed cleanly without a done event despite emitting bytes), and internal_deadline_reached (a round you run through MCP has a 330s per-model deadline; a round streamed in the web app is cut after 90s with nothing from the provider, or at 280s in total). In each case the text produced before the cut or failure is kept.

In both surfaces, the text is "what the model managed to produce before it stopped" — useful for diagnostic recovery, sometimes usable as a partial answer.

get_session#

Fetch the full state of a session — all rounds, responses, snippets, claim maps, and metadata. Use this to read or re-read completed state; while a round is running, prefer wait_for_round.

The most useful field for downstream decisions: rounds[].claim_map.claims[]. Each claim has:

  • quote — the verbatim claim
  • originator — model that said it
  • positions[] — the cross-model reactions, each with model, type (KEEP/CHALLENGE/etc), and comment. model may be moderator for a moderator snippet that reacted to this round's content from a later round (it backfills onto the prior round's claim); a moderator reaction can also introduce a claim no two models cross-reacted on.
  • reaction_count — how many positions reacted (counts moderator reactions too, not just models)

This is the highest-signal view of where the panel agrees and where they're stuck.

When claim_map is null, rounds[].post_round_status says why: pending (still being generated — re-read shortly), no_claims (reactions finished with nothing to map, typical for a one-model round, or the round has no reactions step), failed (the claim map failed permanently), or skipped_balance: The reactions and the takeaway (if requested) were skipped because the account's balance could not cover them; the account owner can generate them from the web app after adding credit. ready means claim_map has claims. The get_session text output prints a Post-round status: line for every value except ready.

After a round completes, the session response carries settled cost rollups so agents can self-evaluate "was this worth it?":

  • total_cost_usd — top-level ground-truth cost for the active session, sourced from the v_llm_spend ledger. Covers every billable bucket (deliberation + moderator + round summary (Takeaway) + snippet extraction + editorial + search) — the same totals the admin dashboard reports. Markup-exclusive (distinct from wallet debits, which are markup-included).
  • rounds[].cost_usd — same source, attributed per completed round. Useful for distinguishing rounds that delivered value from rounds that were convergence noise. During an in-flight round this may be 0 or incomplete; wait_for_round returns it after the target round is terminal.

There is no per-transaction debit array on the round. cost_usd is ledger cost and markup-exclusive, so it is smaller than what the wallet was charged — to see the actual charge, read get_credit before and after.

share_session#

Share a session at a public URL — anyone with the link can view it in the read-only reader deck. No web UI required at any point.

Inputs:

  • session_id — the session to share (must be owned by the caller's API key)

Returns:

{
  "session_id": "…",
  "status": "shared",
  "slug": "your-session-title-a1b2c3",
  "share_url": "https://mumo.chat/p/your-session-title-a1b2c3",
  "markdown_url": "https://mumo.chat/p/your-session-title-a1b2c3.md",
  "brief_url": "https://mumo.chat/p/your-session-title-a1b2c3.brief.md"
}

The two markdown twins are the agent-native surfaces: markdown_url is the full transcript with per-round claim maps (the review/audit tier), brief_url is a synthesis-only triage document (~1–2k tokens). Hand the markdown twin to a reviewer model, or share_url to a human.

Semantics:

  • Idempotent — re-calling returns the same URL.
  • Point-in-time snapshot, kept current — if you've appended rounds since the last share, re-calling refreshes the public page at the same URL.
  • Link-access only — a shared page is not indexed by search engines and not listed anywhere on the platform. Elevation to the public library is a separate platform-curated step, not available through the API.
  • Registered accounts only — anonymous callers get registration_required.
  • The first share of a long session generates any missing round takeaways, so it can take ~15–30s. A share_failed error whose message says summaries are still generating is transient — retry after a moment.

get_session carries read parity: share_url and share_status on the session response show an existing share without re-sharing.

list_sessions#

List the caller's sessions, optionally filtered.

Inputs:

  • status — ready | streaming
  • limit — 1–200 (default 7)
  • offset — pagination

Returns a lightweight list (no response bodies). Useful for agents managing concurrent sessions — status: "ready" finds sessions awaiting your next round.

list_models#

Returns model id, provider, display name, context window, max output tokens, and pricing. Per-model available + unavailable_reason reflects both plan entitlement and credit-gate state — unavailable_reason: "membership_required" (rendered "not included in your plan") means your plan does not include that model, and a call naming it is refused with forbidden_model whatever your balance; a model with available: false and unavailable_reason: "credit_exhausted" means your wallet is exhausted (balance at or below zero), not that this model in particular is out of reach. The round minimum is checked per call, so available: true does not promise that a given panel fits a low balance. Call before create_deliberation if the user wants specific models.

get_credit#

Fetch the caller's full wallet resource. Returns:

{
  "effective_balance_usd": 1.42,
  "buckets": {
    "free": {
      "balance_usd": 1.42,
      "monthly_grant_usd": 1.50,
      "resets_at": "2026-05-01T00:00:00Z"
    },
    "subscription": {
      "balance_usd": 0,
      "rollover_cap_usd": 30.00,
      "subscription_status": null
    },
    "refill": {
      "balance_usd": 0,
      "auto_refill_enabled": false
    }
  },
  "per_model_minimum_usd_default": 0.05,
  "debit_order": ["free", "subscription", "refill"]
}

Field semantics:

  • effective_balance_usd — sum across buckets; markup-included.
  • buckets.free — monthly grant + next reset boundary.
  • buckets.subscription — paid-tier balance + rollover cap + status ("active" | "past_due" | "cancelled" | "expired" | null).
  • buckets.refill — auto-refill top-up balance + enabled flag. When auto_refill_enabled: true, two additional fields appear: auto_refill_threshold_usd (trigger level) and auto_refill_amount_usd (top-up size).
  • debit_order — FIFO bucket drain sequence.

Use get_credit for wallet state (dashboards, post-refund checks, pre-session reads outside the write-op flow). Write operations still perform their own balance preflight and return credit_exhausted if the wallet is exhausted or cannot cover the round's minimum.


Credit wallet#

Every billable LLM call debits a dollar-denominated wallet. A create_deliberation or append_round call is admitted when the caller's effective balance is above zero and covers the round's minimum: each model's reserved output room plus the input, plus the post-round reactions and takeaway. Otherwise it is rejected pre-flight with credit_exhausted — see Errors. At zero or below every call is refused; at a low positive balance a larger panel can be refused while a smaller one is admitted. A round whose true cost overshoots the remaining balance still completes; the balance stops at $0.00 and the excess is never carried as debt. If the balance can't cover a panel round's reactions and takeaway, they are skipped (post_round_status: skipped_balance).

Wallet amounts are markup-included; ledger-cost amounts are not. Every figure get_credit returns, and every figure in a credit_exhausted body, is markup-included — what the user actually paid. The two cost_usd fields on get_session (total_cost_usd and rounds[].cost_usd) are the exception: they report ledger cost, which is markup-exclusive and therefore lower than the corresponding wallet charge. Do not treat the two as interchangeable, and do not reconcile a balance delta against a cost_usd.

Three places wallet state surfaces:

  • get_credit — standalone wallet read with effective balance, bucket breakdown, and the reported (non-gating) per-model minimum default.
  • get_session round cost_usd — ledger cost attributed to that round. Markup-exclusive, so it does NOT equal the wallet debit and balance deltas will not reconcile against it. Call get_credit before and after to see the actual charge.

Balance is not embedded on get_session top-level — that call is high-volume during polling, and wallet state on a read-heavy path creates cache-coherency and semantic-coupling problems. Use get_credit when you need a fresh balance outside the write-op flow.


Snippet types#

The five buckets — KEEP / EXPLORE / CHALLENGE / CORE / SHIFT — are the steering primitive. Each carries a different framing into the next round's prompt:

BucketFraming
KEEP"This resonates with me."
EXPLORE"Let's go deeper on this."
CHALLENGE"I'm not sold on this."
CORE"This is what it comes down to."
SHIFT"This shifted my perspective."

The model receiving a snippet sees the framing — it's not a neutral forward. Use them deliberately. Concrete example:

Round 1: GPT proposes per-seat pricing for the enterprise tier. Claude proposes usage-based. The agent reads round 1, decides per-seat is the weaker option for early pilots. Round 2 append: prompt "Resolve the pricing model. Pick one." plus two snippets: • CHALLENGE on GPT's "per-seat assumes teams of >10," with comment "most pilots start at 3–5" • KEEP on Claude's "usage-based aligns incentives" Models read those framings and converge on usage-based with a per-seat fallback for >25.


Round Takeaway artifacts#

One optional boolean opts rounds in to per-round Takeaway generation:

  • takeaway (default false) — accepted on create_deliberation AND append_round. Generates a per-round summary (round_takeaway) when the round completes — bottom_line + items[] of { question, answer, consensus, claim_ids }. Surfaces on get_session once written.

Pricing. The Takeaway bills via the standard credit wallet but with 0 bps markup — at-cost passthrough.

Reading the artifacts. On get_session / wait_for_round:

  • rounds[].round_takeaway — the sole per-round summary artifact, for every source. Populated for any round whose generation has completed; null otherwise.

Legacy distill removed. Rounds no longer carry the former distill field. Use takeaway for MCP-owned per-round summaries.


Confidence scores#

When models emit self-reported confidence tags in their prose, those scores surface on responses:

  • rounds[].responses[].claim_confidence — per-claim scores
  • rounds[].responses[].snippets[].comment_confidence — per-snippet-comment scores
  • confidence_disclaimer — short advisory string

These are self-reported and not calibrated across models. Surface the disclaimer if you display them to users.


Identity metadata#

Both create_deliberation and append_round accept two optional identity fields:

  • moderator_name — display name of who/what is steering the deliberation. Shown in the session info panel and replaces "You" attribution in the transcript. Also important for optimizing participating model selection when you don't specify models. On append_round, supply only when the steering entity changes (e.g., a human takes over from an agent) — otherwise the existing value is preserved.
  • application — display name of the client driving the session (e.g., "Claude Code", "Cursor"). Shown in the session info panel only. Only meaningful on create_deliberation.

Sessions opened through MCP are tagged source: "mcp" server-side. Neither field is auto-populated — pass whatever your client wants to display.

MCP follows the same session-level consent model as the REST API, for both elections. Any account can use improvement_consent: false or dataset_consent: false on create_deliberation to exclude a new session. Sending true never widens beyond the account's own choice — if the account's Platform Improvement default is off, or its Dataset Contribution choice is off or not yet made, the session is still excluded. Both are create-only; append_round does not accept them. Consent is resolved at session creation and returned on get_session as:

"improvement_consent": {
  "enabled": true,
  "reason": "account_default",
  "requested": null,
  "disclosure": null
},
"dataset_consent": {
  "enabled": false,
  "reason": "not_yet_chosen",
  "requested": null
}

That is the REST session shape; get_session's markdown carries the same two decisions on one Consent: line. Both enabled values are the stamps recorded at creation, not a live eligibility check — for Dataset Contribution in particular, the account's current switch must also be on for the session to be used.


Errors#

The MCP server returns errors as text content with a structured prefix. Common cases:

  • credit_exhausted — from create_deliberation or append_round: the wallet is exhausted (zero or negative), or is positive but can't cover the round's minimum. Returned as isError: true with readable text and structuredContent carrying the REST 403 body: error, message, retryable, effective_balance_usd, free_usd, subscription_usd, refill_usd, per_model_minimum_usd (reported for reference, not the rejection threshold), next_reset_at (plus models_requested from create_deliberation). A positive-balance refusal adds required_usd — the minimum the round needs to start, including any credit your in-flight rounds already hold; a minimum, not the expected cost; web search is not part of it, so a round your balance covers without search is admitted and runs without web search — and affordable_alternatives, the rosters from your request that fit the balance now (possibly empty; populated only from create_deliberation, always empty from append_round): { kind: "drop_most_expensive", models, dropped } (the panel minus its most expensive model, offered for 3-model panels) or { kind: "cheapest_solo", models } (one model — create_deliberation needs 2–3, so this only tells you one model still fits). The text names the minimum and lists the alternatives. Repeating the same call won't help. Call create_deliberation again with a drop_most_expensive roster as models — append_round can't change a session's roster, so a cheaper roster means a new session — or wait for the free-tier reset at next_reset_at. On a zero-or-negative refusal, wait for the reset (top-ups arrive with the paid tier).
  • forbidden_model — from create_deliberation or append_round: a requested model (on append_round, a model in the session's panel) is not included in the caller's plan. Returned as isError: true with text that names the action — a plan change by the account's human owner, with the upgrade link — and structuredContent carrying the REST 403 body: error, message, retryable: false, reason ("membership_required"; treat any value as "not on this plan"), denied_models, upgrade_url, human_action_required: true. Retrying the same call is refused again; surface it to the user, or drop the model (list_models marks it membership_required). Checked before the wallet; no credit debited.
  • panel_seat_limit — from create_deliberation or append_round: the round seats more models than the caller's plan allows. isError: true, text telling you how to fix it (on create_deliberation, pass at most seat_limit models; on append_round, start a new deliberation with at most seat_limit models), and structuredContent with error, message, retryable: false, seat_limit, requested_seats, upgrade_url, human_action_required: false — you can fix it yourself: call create_deliberation with at most seat_limit models (append_round can't change a session's roster, so that means a new session). No credit debited.
  • input_too_long — from create_deliberation or append_round: the call's new text is longer than the caller's plan allows — on create_deliberation the prompt plus reference, on append_round the prompt plus your snippets (quotes, comments and quoted_models), counted in tokens as characters ÷ 4; session history and search results never count. isError: true, text telling you to shorten it, and structuredContent carrying the REST 413 body: error, message, retryable: false, typed_tokens, cap, upgrade_url, human_action_required: false — shorten the text to at most cap and call again. No credit debited.
  • unknown_models — one or more requested model IDs aren't in the registry. Body carries unknown_models: string[] (plus models_requested: string[] from create_deliberation). Call list_models to enumerate valid IDs. Preflight check; no credit debited.
  • insufficient_active_models — caller omitted models and the curated default panel couldn't produce ≥2 picks from the active registry. Body carries collapsed_buckets: number[]. Pass an explicit models array.
  • attachments_web_only — from append_round: the session has documents attached in the mumo web app, so it can only be continued there. Returned as an error with a readable message. Not retryable — start a new deliberation with create_deliberation instead. No credit debited.
  • session_busy — a round is in flight. Wait, then retry.
  • daily_limit_reached — round budget exhausted. Returns resets_at.
  • region_unavailable — the MCP client's network location is outside the US and Canada (or in Quebec). initialize and tools/list still work; every tool call returns isError: true with the readable message and structuredContent: { error: "region_unavailable", scope }. Location is the calling machine's, not the account owner's.
  • account_suspended — the key owner's account is suspended. initialize and tools/list still work; every tool call returns isError: true with the readable message (it carries the support contact) and structuredContent: { error: "account_suspended" }. Not retryable — surface the message to the user.
  • not_found — session ID doesn't exist or isn't yours.
  • unauthorized — HTTP 401 at the transport boundary on any request that reaches a tool. The keyless read-only handshake (initialize, tools/list) succeeds without credentials, so on a keyless setup this first appears on the first tools/call, not at connect; clients that surface transport errors as tool errors show it there. Two distinct cases, both self-describing: no credential header at all — neither Authorization nor X-Mumo-Api-Key (body carries keys_url and setup_url) — or a key that was rejected as revoked, expired, or malformed (body carries keys_url only — you already have a key, so the first-time setup guide is the wrong destination). An agent that hits this can read the body and recover without being told where to look.

The full REST error reference is in the API docs.


Naming philosophy#

MCP uses canonical API field names where the same concept crosses the boundary (session_id, round_id, claim_map, snippet type, etc.), but it is not a field-for-field mirror of REST. REST-only controls like autonomous moderation are intentionally omitted from MCP so agents follow one clear loop: create, wait, read, append or stop.

One convention to know: snippet types (type field on append_round snippets and claim_map.claims[].positions[]) are always UPPERCASE — KEEP, EXPLORE, CHALLENGE, CORE, SHIFT.

Full internal mapping is in docs/CONVENTIONS.md — useful if you're contributing or auditing.


See also#