For power users & automators
Bring your own AI to your campaign.
Want to ask questions of your campaign from ChatGPT, Claude, Gemini or your own agent? Recap Raven’s MCP server exposes your campaign memory and AI answers as tools any MCP client can call — so the recap engine is reachable from wherever your agent lives.
Get started
Connect your MCP client to mcp.recapraven.com/mcp using a personal API key from your Recap Raven account area. Choose your client to see the setup steps:
ChatGPT Desktop setup
- In your Recap Raven account area, open API Keys → Create key. You need a paid plan, and the key is shown once.
- Open ChatGPT Desktop → Settings → Connectors → Add a new connector.
- Set the URL to https://mcp.recapraven.com/mcp
- Set the Authorization header to: Bearer raven_sk_… (the key from step 1).
- Save, then start a chat — Recap Raven’s tools become available to ChatGPT.
Available on all paid plans
MCP is unavailable on the Free tier. The included tool calls below run on every paid plan (Premium, Studio, Pro) at no extra cost; credit-charged tools spend your existing Ask credits.
| Free tier | Unavailable |
|---|---|
| Paid plans | All included tools at no extra cost; credit-charged tools spend your existing Ask credits. |
| API key | Create one personal API key in your account area. Revoke any time; keys are disabled if the account returns to the Free tier. |
Fair use & rate limits
The MCP server is a shared, metered surface. To keep it sustainable (and stop a runaway agent loop from burning a whole licence in minutes), every API key is rate-limited in addition to your credit balance.
| Server fair-use | Per-API-key request ceiling on the MCP server itself (all tool calls combined) — prevents a single key from flooding the endpoint. Responses over the limit return 429 with Retry-After. |
|---|---|
| Premium | 30 requests/minute and 300 requests/hour. |
| Studio | 60 requests/minute and 900 requests/hour. |
| Pro | 120 requests/minute and 2400 requests/hour. |
| Included tools | Rate-limited per API key. Keyword search (search_transcript) and read endpoints cost you no credits. |
| Credit-charged tools | Bounded by both your Ask credit balance and a per-key rate limit. The credit gate stops spend when the balance is empty; the rate limit stops a burst from draining it instantly. No separate MCP quota — your agent spends the same per-licence Ask credits it uses in the app. |
Included tool calls
Read-only data tools, included on every paid plan at no extra cost. They return stored campaign data and keyword-search transcript matches — no AI answer, no credit charged. Expand any tool for example input, example output and field reference.
list_campaignsIncludedDiscovery tool: list the campaigns available to your MCP key so other tools can use the right campaign_id.
list_campaignsIncludedDiscovery tool: list the campaigns available to your MCP key so other tools can use the right campaign_id.
Example input
{}Example output
{
"campaigns": [
{
"campaign_id": "a1b2c3d4-…",
"name": "The White Moose",
"module": "Dolmenwood"
}
]
}| Field | Type | Description |
|---|---|---|
| campaigns | array | Campaigns owned by your account. |
| campaigns[].campaign_id | string (uuid) | Use this id with campaign-scoped MCP tools. |
| campaigns[].name | string | Campaign name. |
| campaigns[].module | string | null | Optional module/system label. |
list_recapsIncludedDiscovery tool: list ready recaps/sessions so get_recap and transcript windows can use the right recap_id.
list_recapsIncludedDiscovery tool: list ready recaps/sessions so get_recap and transcript windows can use the right recap_id.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | omitted | Optional campaign filter. |
Example output
{
"recaps": [
{
"recap_id": "r1e2c3a4-…",
"campaign_id": "a1b2c3d4-…",
"title": "Session 12: The Choosing",
"status": "ready"
}
]
}| Field | Type | Description |
|---|---|---|
| recaps | array | Ready recaps visible to your account. |
| recaps[].recap_id | string (uuid) | Use this id with get_recap and get_transcript_window. |
| recaps[].campaign_id | string (uuid) | null | Campaign that owns the recap. |
| recaps[].title | string | null | Session/recap title. |
| recaps[].status | string | Only ready recaps are listed. |
get_engagement_trendIncludedPer-speaker participation %, turn count, talk time and word count across every ready session.
get_engagement_trendIncludedPer-speaker participation %, turn count, talk time and word count across every ready session.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. Must be owned by your account. |
Example output
{
"sessions": [
{
"recap_id": "…",
"session_ref": "S01AB",
"session_at": "2026-06-20T19:00:00Z"
}
],
"players": [
{
"speaker": "Mira",
"points": [
{
"session_ref": "S01AB",
"participation_pct": 28.4,
"turn_count": 42,
"talk_time_seconds": 612,
"word_count": 1840
}
]
}
]
}| Field | Type | Description |
|---|---|---|
| sessions | array | One entry per ready session in the campaign. |
| sessions[].recap_id | string (uuid) | The recap/session id. |
| sessions[].session_ref | string | Short human reference, e.g. S01AB. |
| sessions[].session_at | string (ISO 8601) | When the session started (UTC). |
| players | array | One entry per recognised speaker. |
| players[].speaker | string | Speaker display name. |
| players[].points | array | Per-session metrics for this speaker. |
| players[].points[].participation_pct | number | Share of conversation, 0–100. |
| players[].points[].turn_count | integer | Number of speaking turns taken. |
| players[].points[].talk_time_seconds | integer | Total seconds speaking. |
| players[].points[].word_count | integer | Total words spoken. |
get_campaign_synthesisIncludedThe cached cross-session AI narrative — campaign summary and key-events timeline. The synthesis already cost credits at refresh time.
get_campaign_synthesisIncludedThe cached cross-session AI narrative — campaign summary and key-events timeline. The synthesis already cost credits at refresh time.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"synthesis": {
"campaign_summary": "…",
"key_events": [
{
"session_ref": "S01AB",
"title": "The white moose hunt",
"detail": "…"
}
],
"synthesised_at": "2026-06-21T08:00:00Z",
"source_session_count": 4
}
}| Field | Type | Description |
|---|---|---|
| synthesis | object | null | null until at least two sessions are synthesised. |
| synthesis.campaign_summary | string | AI-generated cross-session narrative. |
| synthesis.key_events | array | Timeline of notable events. |
| synthesis.key_events[].title | string | Short event label. |
| synthesis.key_events[].detail | string | Event description. |
| synthesis.synthesised_at | string (ISO 8601) | When the synthesis was last built. |
| synthesis.source_session_count | integer | Sessions included in the synthesis. |
get_credit_balanceIncludedYour available credits, total granted, and per-lane (GM / player) usage %. Lets an agent self-throttle before calling a paid tool.
get_credit_balanceIncludedYour available credits, total granted, and per-lane (GM / player) usage %. Lets an agent self-throttle before calling a paid tool.
Example input
{}Example output
{
"available_credits": 142,
"total_credits": 200,
"gm_chat": {
"used_pct": 29
},
"player_chat": {
"used_pct": 6
}
}| Field | Type | Description |
|---|---|---|
| available_credits | number | Credits remaining across both lanes. |
| total_credits | number | Credits granted this billing period. |
| gm_chat.used_pct | number | GM lane usage, 0–100. |
| player_chat.used_pct | number | Player lane usage, 0–100. |
get_recent_activityIncludedA capped feed (≤300 rows) of session-recorded, recap-ready and credit-ledger events.
get_recent_activityIncludedA capped feed (≤300 rows) of session-recorded, recap-ready and credit-ledger events.
Example input
{}Example output
{
"activity": [
{
"type": "recap_ready",
"label": "Session S01AB recap is ready",
"date": "2026-06-21T08:02:00Z",
"recap_id": "…"
},
{
"type": "credit",
"label": "Asked your campaign",
"date": "2026-06-21T08:05:00Z"
}
]
}| Field | Type | Description |
|---|---|---|
| activity | array | Newest-first feed, capped at 300 rows. |
| activity[].type | string | Event kind.values: recap_ready | session_recorded | credit |
| activity[].label | string | Human-readable summary. |
| activity[].date | string (ISO 8601) | When the event occurred. |
| activity[].recap_id | string (uuid) | omitted | Present for recap/session-related events. |
get_memory_overviewIncludedRichest GM situational-awareness blob: distribution, engagement trend + overall score, per-session participation, pillars of play.
get_memory_overviewIncludedRichest GM situational-awareness blob: distribution, engagement trend + overall score, per-session participation, pillars of play.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"distribution": [
{
"label": "Roleplay",
"pct": 58
}
],
"engagement": {
"overall": {
"score": 74,
"label": "Playful / tense"
}
},
"pillars": [
"The green-eyed stranger",
"The old inn",
"The white moose hunt"
]
}| Field | Type | Description |
|---|---|---|
| distribution | array | Share of play by category. |
| distribution[].label | string | Category name. |
| distribution[].pct | number | Share, 0–100. |
| engagement.overall.score | number | null | Composite engagement score, 0–100. |
| engagement.overall.label | string | null | Qualitative label, e.g. "Playful / tense". |
| pillars | string[] | null | Recurring themes / pillars of play. |
get_open_hooksIncludedUnresolved narrative hooks — what to prep next session.
get_open_hooksIncludedUnresolved narrative hooks — what to prep next session.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"hooks": [
{
"text": "Nobody bypasses the Choosing.",
"session_ref": "S01AB",
"session_date": "2026-06-20"
}
]
}| Field | Type | Description |
|---|---|---|
| hooks | array | Currently open narrative hooks. |
| hooks[].text | string | The hook description. |
| hooks[].session_ref | string | Session where the hook was introduced. |
| hooks[].session_date | string (YYYY-MM-DD) | Date of that session. |
get_previously_onIncludedPreviously-on context for the campaign, built from owner-scoped memory.
get_previously_onIncludedPreviously-on context for the campaign, built from owner-scoped memory.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"sections": [
{
"title": "Last time",
"bullets": [
"The party followed the white moose into the mist."
]
}
]
}| Field | Type | Description |
|---|---|---|
| sections | array | Campaign memory sections for recap/context. |
| sections[].title | string | Section heading. |
| sections[].bullets | string[] | Concise memory bullets. |
get_chronicleIncludedScenes-per-session map — the structured campaign chronicle.
get_chronicleIncludedScenes-per-session map — the structured campaign chronicle.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"sessions": [
{
"recap_id": "…",
"session_ref": "S01AB",
"scenes": [
"The market",
"The hunt",
"The banshee fight"
]
}
]
}| Field | Type | Description |
|---|---|---|
| sessions | array | One entry per session. |
| sessions[].recap_id | string (uuid) | The recap/session id. |
| sessions[].scenes | string[] | Ordered scene labels for that session. |
get_entitiesIncludedNamed entities (NPCs, places, items) extracted across the campaign.
get_entitiesIncludedNamed entities (NPCs, places, items) extracted across the campaign.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"entities": [
{
"name": "The green-eyed stranger",
"description": "…",
"session_ref": "S01AB",
"session_date": "2026-06-20"
}
]
}| Field | Type | Description |
|---|---|---|
| entities | array | Named entities across the campaign. |
| entities[].name | string | Entity display name. |
| entities[].description | string | Short description. |
get_lore_documentsIncludedThe list of lore documents for the campaign.
get_lore_documentsIncludedThe list of lore documents for the campaign.
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"documents": [
{
"id": "…",
"title": "The Choosing",
"updated_at": "2026-06-20"
}
]
}| Field | Type | Description |
|---|---|---|
| documents | array | Lore documents. |
| documents[].id | string (uuid) | Document identifier. |
| documents[].title | string | Document title. |
get_lore_documentIncludedA single lore document with its full body.
get_lore_documentIncludedA single lore document with its full body.
Example input
{
"campaign_id": "a1b2c3d4-…",
"doc_id": "…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
| doc_id | string (uuid) | The lore document to fetch. |
Example output
{
"document": {
"id": "…",
"title": "The Choosing",
"body": "…"
}
}| Field | Type | Description |
|---|---|---|
| document.id | string (uuid) | Document identifier. |
| document.title | string | Document title. |
| document.body | string | Full document body (markdown). |
get_lexiconIncludedLexicon entries (canonical names, aliases, type, confidence).
get_lexiconIncludedLexicon entries (canonical names, aliases, type, confidence).
Example input
{
"campaign_id": "a1b2c3d4-…"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to read. |
Example output
{
"entries": [
{
"id": "…",
"canonical": "Sol’Thun",
"type": "npc",
"aliases": [
"the archmage"
],
"confidence": 0.92,
"enabled": true
}
]
}| Field | Type | Description |
|---|---|---|
| entries | array | Lexicon entries. |
| entries[].canonical | string | Canonical name. |
| entries[].type | string | Entity kind.values: npc | place | item | creature | other |
| entries[].aliases | string[] | Alternate names / aliases. |
| entries[].confidence | number | Extraction confidence, 0–1. |
| entries[].enabled | boolean | Whether this entry is active. |
get_transcript_windowIncludedA positional slice of a session’s stored turns (no content search — just from/to turn indices).
get_transcript_windowIncludedA positional slice of a session’s stored turns (no content search — just from/to turn indices).
Example input
{
"recap_id": "…",
"from": 0,
"to": 20
}| Field | Type | Description |
|---|---|---|
| recap_id | string (uuid) | The recap/session to read. Use list_recaps to discover ids. |
| from | integer | Start turn index (inclusive, default 0). |
| to | integer | End turn index. Omit for a 50-turn window from the start index. |
Example output
{
"recap_id": "…",
"total_turns": 184,
"from": 0,
"to": 20,
"turns": [
{
"index": 0,
"t": "~00:01",
"speaker": "GM",
"text": "…"
}
]
}| Field | Type | Description |
|---|---|---|
| total_turns | integer | Total turns in the session. |
| from | integer | Start index returned. |
| to | integer | End index returned. |
| turns | array | Turns in the requested window. |
| turns[].index | integer | Positional index in the session. |
| turns[].t | string | Relative timestamp, e.g. ~00:01. |
| turns[].speaker | string | Speaker display name. |
| turns[].text | string | The spoken text. |
get_recapIncludedThe player-safe recap HTML for a ready recap. The artifact type is server-owned; there is no type input.
get_recapIncludedThe player-safe recap HTML for a ready recap. The artifact type is server-owned; there is no type input.
Example input
{
"recap_id": "…"
}| Field | Type | Description |
|---|---|---|
| recap_id | string (uuid) | The recap/session whose player-safe recap should be fetched. |
Example output
{
"recap": {
"recap_id": "…",
"content_type": "player_recap_html",
"html": "<!doctype html>…",
"truncated": false
}
}| Field | Type | Description |
|---|---|---|
| recap.recap_id | string (uuid) | The recap/session id. |
| recap.content_type | string | Always player_recap_html.values: player_recap_html |
| recap.html | string | The player-safe recap HTML. |
| recap.truncated | boolean | True when the MCP response cap trimmed the HTML. |
search_transcriptIncludedExact keyword (full-text) search over your campaign transcript turns. Returns turns containing the exact phrase, with their session, speaker and timestamp — no LLM, no embedding, no credits.
search_transcriptIncludedExact keyword (full-text) search over your campaign transcript turns. Returns turns containing the exact phrase, with their session, speaker and timestamp — no LLM, no embedding, no credits.
Example input
{
"campaign_id": "a1b2c3d4-…",
"query": "white moose"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to search. |
| query | string | Exact phrase to match (full-text search against transcript text). |
Example output
{
"query": "white moose",
"matches": [
{
"recap_id": "…",
"speaker": "GM",
"t": "~01:04",
"text": "All you know is that it’s a white moose."
}
]
}| Field | Type | Description |
|---|---|---|
| query | string | Echo of the submitted query. |
| matches | array | Transcript turns containing the exact phrase, best match first. |
| matches[].recap_id | string (uuid) | Recap/session the match came from. |
| matches[].speaker | string | Speaker of the matched turn. |
| matches[].t | string | Relative timestamp, e.g. ~01:04. |
| matches[].text | string | The matched transcript text. |
Ask-credit tool calls
These tools spend your existing Ask credits — the same ledger as in-app chat. Semantic RAG search (search_corpus) uses an embedding call and costs 0.25 credits; the agent answer (ask_gm) costs 1 credit for a simple answer, 2 credits for the deep / planning lanes. Rate-limited per API key in addition to your balance.
search_corpus0.25 creditsHybrid RAG search over your campaign transcripts + memory using semantic embeddings. Returns retrieved chunks (not an LLM answer) — bring your own model to reason over them. Uses an embedding call (LLM-adjacent), so costs 0.25 credits. Rate-limited per API key in addition to your credit balance.
search_corpus0.25 creditsHybrid RAG search over your campaign transcripts + memory using semantic embeddings. Returns retrieved chunks (not an LLM answer) — bring your own model to reason over them. Uses an embedding call (LLM-adjacent), so costs 0.25 credits. Rate-limited per API key in addition to your credit balance.
Example input
{
"campaign_id": "a1b2c3d4-…",
"query": "where did the hunter see the moose?"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to search. |
| query | string | Natural-language search query (semantic matching). |
Example output
{
"query": "where did the hunter see the moose?",
"chunks": [
{
"kind": "transcript",
"recap_id": "…",
"speaker": "Skoll",
"text": "…",
"score": 0.81
},
{
"kind": "memory",
"text": "The hunter saw the white moose near the old inn.",
"score": 0.76
}
],
"credits_spent": 0.25
}| Field | Type | Description |
|---|---|---|
| query | string | Echo of the submitted query. |
| chunks | array | Retrieved chunks, best match first. |
| chunks[].kind | string | Source of the chunk.values: transcript | memory |
| chunks[].text | string | The matched text. |
| chunks[].score | number | Relevance score, 0–1. |
| chunks[].recap_id | string (uuid) | null | Recap/session id when the chunk is anchored to a session. |
| chunks[].speaker | string (transcript only) | Speaker of a transcript chunk. |
| credits_spent | number | Credits debited (0.25). |
ask_gm1–2 creditsAsk your campaign a question and get the AI answer — the same agent/planning engine as in-app chat, with transcript receipts. Cost mirrors the in-app chat agent (deep/planning lanes = 2 credits). Rate-limited per API key in addition to your credit balance.
ask_gm1–2 creditsAsk your campaign a question and get the AI answer — the same agent/planning engine as in-app chat, with transcript receipts. Cost mirrors the in-app chat agent (deep/planning lanes = 2 credits). Rate-limited per API key in addition to your credit balance.
Example input
{
"campaign_id": "a1b2c3d4-…",
"question": "Where did the hunter see the moose?"
}| Field | Type | Description |
|---|---|---|
| campaign_id | string (uuid) | The campaign to ask. |
| question | string | Your question. |
| clarification | object | omitted | Optional: answer a clarifying question the agent asked. |
| clarification.question | string | The clarifying question from the agent. |
| clarification.answer | string | Your answer to that question. |
| supersedes | string | omitted | Message id of a prior answer to replace. |
Example output
{
"answer": "The hunter reported seeing the white moose near the old inn to the north of town.",
"receipts": [
{
"session_ref": "S01AB",
"speaker": "Skoll",
"text": "…"
}
],
"credits_spent": 2
}| Field | Type | Description |
|---|---|---|
| answer | string | The AI answer. |
| receipts | array | Transcript evidence backing the answer. |
| receipts[].session_ref | string | Session the receipt came from. |
| receipts[].speaker | string | Speaker of the quoted turn. |
| receipts[].text | string | The quoted transcript text. |
| credits_spent | integer | Credits debited (1 or 2 depending on lane). |
Questions or feedback? See how Recap Raven works or invite the bot below.