{"server":{"name":"storywright","version":"0.2.0","endpoint":"https://mcp.storywright.io/mcp","transport":"streamable-http","instructions":"Storywright lets a tabletop RPG Game Master or player work with their campaign.\nUse these tools to read worlds, sessions, player journals, Story Arcs, lore\n(\"World Builder\" entities), characters, and the Weave knowledge graph — and to\nwrite journal entries, arcs and their beats, lore, Threads, annotations,\nsession notes, and recaps.\n\nStart with `list_worlds` to find the world id the user means. If it returns\nno worlds, the account is brand new — that is not an error to work around,\nit is the signal to offer `list_world_templates` and `create_world` next\n(the `start-new-campaign` prompt walks this choreography end to end).\n\nPrefer a glance over reassembling one: `get_campaign_overview` answers \"where\nis this campaign?\" in a single call, `check_world_health` answers \"what needs\nmy attention before next session?\", and `get_arc_nudges` answers \"what is\ngating each live arc?\" — all three are computed server-side and none of them\nspend AI budget. Reach for the list tools when you need specific rows.\n\nPeople vs. places: an NPC is a Character, created with `create_character`. The\nWorld Builder tools cover Locations, Inventory, History, and Organizations only\n— they reject people. A Story Arc is a \"quest\" here; its beats are objectives.\nJoining two things on the Weave is a Thread (`create_thread`); nesting one\nLocation inside another is not a Thread but a parent\n(`update_world_builder_item`).\n\nLists are paged — you get at most `limit` rows (default 50) plus `total` and\n`has_more`, so a short page always tells you it is short. Page with `offset`\nrather than assuming you have everything.\n\nPermissions are enforced server-side: a player only sees public + their own\ncontent; GM-only actions return a permission error for players. GM-only fields\nare absent rather than empty when the reader is a player.\n\nDeleting is rarely the right move: mark a beat complete or dismissed, an arc\ncomplete, a character deceased or missing. Those keep the campaign's record,\nwhich is the thing this product exists to hold.\n"},"authentication":{"mode":"oauth2.1","grantTypes":["authorization_code","refresh_token"],"pkce":"S256 (required)","requiredScopes":[],"requiredScopesNote":"Empty means this build requires no scope at the MCP boundary; authorization is still enforced by the Storywright API. See scopes_supported in the authorization server metadata for what a client should request.","authorizationServerMetadata":"https://storywright.io/.well-known/oauth-authorization-server","protectedResourceMetadata":"https://mcp.storywright.io/.well-known/oauth-protected-resource/mcp","dynamicClientRegistration":"https://storywright.io/api/oauth/register/"},"rateLimit":{"maxRequests":120,"windowSeconds":60.0,"scope":"per authenticated token, else per client IP","coordination":"in_process","appliesTo":"/mcp","onExceeded":{"status":429,"retryAfterHeader":"Retry-After","body":{"error":"rate_limited"}},"onSharedStoreUnavailable":null},"stability":{"versioned":false,"note":"Tool names, arguments, and behaviour can change; this manifest is generated from the running server, so it is always current. Breaking changes are announced on /developers and in mcp/README.md, never silently."},"counts":{"tools":59,"read":27,"write":26,"delete":6,"prompts":6,"resources":5},"tools":[{"name":"list_worlds","title":"List worlds","description":"List the campaigns/worlds the current user belongs to, with their role (gm/player). Call this first to find a world id.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"include_archived":{"default":false,"title":"Include Archived","type":"boolean"}},"title":"list_worldsArguments","type":"object"}},{"name":"get_world","title":"Get world","description":"Get full detail for one world by id (settings, arc phase, member count, game system).","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"get_worldArguments","type":"object"}},{"name":"list_world_members","title":"List world members","description":"List the party: members of a world with their roles and characters.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"list_world_membersArguments","type":"object"}},{"name":"get_weave","title":"Get the Weave","description":"Get the campaign knowledge graph (entities and their links), scoped to what the current user can see. This returns the WHOLE graph in one response and cannot be paged, so on a large campaign it may be refused as too large. When you want one entity's connections rather than the whole map, prefer `get_entity_references` or `search_world_builder`; for \"where is this campaign?\", prefer `get_campaign_overview`.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"get_weaveArguments","type":"object"}},{"name":"get_campaign_overview","title":"Get campaign overview","description":"The state-of-the-campaign digest in ONE call: member count, where the party left off, and bounded slices of the sessions, arcs, and lore. This is the right first read for \"catch me up on this campaign\" — it is the aggregation the app's own Overview page uses, and it replaces walking list_sessions → get_session → list_quests → search_world_builder by hand. Permission-aware and bounded, so it is safe to call on a large world.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"get_campaign_overviewArguments","type":"object"}},{"name":"get_world_pulse","title":"Get world pulse","description":"The glance: the next scheduled session, the most recently published recap, and the party with their characters. Cheaper and narrower than `get_campaign_overview` — use it for \"what's next?\", not \"where are we?\".","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"get_world_pulseArguments","type":"object"}},{"name":"check_world_health","title":"Check world health","description":"GM only. Run the four continuity checks over a campaign and return what is thin or dangling — arcs with no movement, beats left unresolved, sessions without notes, that class of finding. Fully deterministic: no model call, no AI budget, no writes, and there is deliberately NO overall score. This is the answer to \"what needs my attention before next session?\" and it is computed server-side rather than inferred from a pile of list calls.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"check_world_healthArguments","type":"object"}},{"name":"list_world_templates","title":"List world templates","description":"The starter templates `create_world` can seed from, with what each one contains. Call this before offering a GM a choice; the set is defined server-side and changes without this server changing.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{},"title":"list_world_templatesArguments","type":"object"}},{"name":"create_world","title":"Create world","description":"Create a new campaign/world owned by the current user, who becomes its GM. Pass `template` (a key from `list_world_templates`) to seed starter entities, a session, and an arc; omit it for an empty world. Returns the world including its `invite_code` — the code players join with. NOTE: the free tier allows one world per GM, so this can return 402 with an upgrade message rather than a validation error; that is a billing limit, not a malformed request.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"name":{"title":"Name","type":"string"},"description":{"default":"","title":"Description","type":"string"},"template":{"default":"","title":"Template","type":"string"}},"required":["name"],"title":"create_worldArguments","type":"object"}},{"name":"list_sessions","title":"List sessions","description":"List the sessions for a world, in play order. Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_sessionsArguments","type":"object"}},{"name":"get_session","title":"Get session","description":"Get one session's detail. Notes are returned as readable text. GM-only notes are omitted for players by the server.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"}},"required":["world_id","session_id"],"title":"get_sessionArguments","type":"object"}},{"name":"get_session_briefing","title":"Get session briefing","description":"Get the published player-facing 'Previously on…' recap for a session.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"}},"required":["world_id","session_id"],"title":"get_session_briefingArguments","type":"object"}},{"name":"create_session","title":"Create session","description":"GM only. Start a new session in a world. `notes` seeds the GM's private prep notes — the natural place to carry last session's open threads forward. Notes are plain text: blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted (it would be stored literally). `session_date` is YYYY-MM-DD. The session's order in the campaign is assigned by the server from the date — do not try to set it.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"title":{"title":"Title","type":"string"},"session_date":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Session Date"},"notes":{"default":"","title":"Notes","type":"string"}},"required":["world_id","title"],"title":"create_sessionArguments","type":"object"}},{"name":"append_session_notes","title":"Add to session notes","description":"GM only. Add text to the END of a session's private GM notes, keeping everything already there. This is the tool to use for 'note that…', 'add to my notes…', or recording something mid-session. Safe to use while the GM is typing in their browser: the server appends under a lock, so their edit and yours both survive. Plain text: blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted. Notes are never shown to players.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"},"notes":{"title":"Notes","type":"string"}},"required":["world_id","session_id","notes"],"title":"append_session_notesArguments","type":"object"}},{"name":"write_session_notes","title":"Replace session notes","description":"GM only. REPLACE a session's private GM notes wholesale, discarding what is there. To add to existing notes use `append_session_notes` instead — it is safe against the GM editing at the same time, and this tool is not. Pass `expected_notes_version` (from `get_session`) to be told with a conflict error if the notes changed since you read them, rather than silently overwriting. Plain text: blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted. Notes are never shown to players.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"},"notes":{"title":"Notes","type":"string"},"expected_notes_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Expected Notes Version"}},"required":["world_id","session_id","notes"],"title":"write_session_notesArguments","type":"object"}},{"name":"update_session","title":"Update session","description":"GM only. Rename or re-date a session. Only the fields you pass are changed. Re-dating may change the session's position in the campaign — the server renumbers the world's sessions to keep them in date order. To change notes use `write_session_notes`.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"session_date":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Session Date"}},"required":["world_id","session_id"],"title":"update_sessionArguments","type":"object"}},{"name":"draft_session_recap","title":"Draft session recap (AI)","description":"GM only. Ask Storywright's AI to draft a recap for a session from its notes. Rate-limited and subject to the world's AI budget; returns 503 if AI is disabled. Does not publish — review then publish.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"},"source_text":{"title":"Source Text","type":"string"}},"required":["world_id","session_id","source_text"],"title":"draft_session_recapArguments","type":"object"}},{"name":"publish_session_briefing","title":"Publish session briefing","description":"GM only. Publish a session's recap to the party as the 'Previously on…' briefing.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"}},"required":["world_id","session_id"],"title":"publish_session_briefingArguments","type":"object"}},{"name":"end_session","title":"End session","description":"GM only. Mark a session ended (the after-play close-out). This is a MARKER, not a lock: notes, journals, and beats all still change afterwards. Idempotent — ending an already-ended session keeps the original end time rather than moving it. Publishing a briefing and the 'previously on' surfaces key off the most recent ENDED session, so a session left open never becomes 'last session'.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"}},"required":["world_id","session_id"],"title":"end_sessionArguments","type":"object"}},{"name":"reopen_session","title":"Reopen session","description":"GM only. Reopen an ended session — for a session closed by mistake, or one that ran on. Clears the end time. Idempotent on an already-active session.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"title":"Session Id","type":"integer"}},"required":["world_id","session_id"],"title":"reopen_sessionArguments","type":"object"}},{"name":"list_journals","title":"List journal entries","description":"List journal entries in a world that the current user may see (their own, plus party-visible entries). Optionally filter to one session or one character (the in-fiction speaker). Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"character_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Character Id"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_journalsArguments","type":"object"}},{"name":"get_journal","title":"Get journal entry","description":"Get one journal entry by id; content is returned as readable text.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"journal_id":{"title":"Journal Id","type":"integer"}},"required":["journal_id"],"title":"get_journalArguments","type":"object"}},{"name":"create_journal_entry","title":"Create journal entry","description":"Add a journal entry to a world. `content` is plain text: blank lines start new paragraphs and single newlines become line breaks. Markdown syntax is NOT interpreted (it would be stored as literal characters). `visibility` is private (author only), gm_only (author + GM), or party (all members). Optionally attach to a session and a character.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"content":{"title":"Content","type":"string"},"visibility":{"default":"private","enum":["private","gm_only","party"],"title":"Visibility","type":"string"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"character_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Character Id"}},"required":["world_id","content"],"title":"create_journal_entryArguments","type":"object"}},{"name":"update_journal_entry","title":"Update journal entry","description":"Update your own journal entry. Provide new `content` (plain text; blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted) and/or a new `visibility`. If you previously read the entry, pass its `content_version` as `expected_content_version`; the server returns a conflict instead of overwriting a newer browser/device edit.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"journal_id":{"title":"Journal Id","type":"integer"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Content"},"visibility":{"anyOf":[{"enum":["private","gm_only","party"],"type":"string"},{"type":"null"}],"default":null,"title":"Visibility"},"expected_content_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Expected Content Version"}},"required":["journal_id"],"title":"update_journal_entryArguments","type":"object"}},{"name":"delete_journal_entry","title":"Delete journal entry","description":"Delete one of YOUR OWN journal entries. The server allows this for the author only — not the GM, who can read a party-visible entry but never remove someone else's writing. Consider `update_journal_entry(visibility='private')` first if the goal is just to take it back out of the party's view.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"journal_id":{"title":"Journal Id","type":"integer"}},"required":["journal_id"],"title":"delete_journal_entryArguments","type":"object"}},{"name":"list_quests","title":"List quests","description":"List Story Arcs (quests) in a world. Optionally filter by status (not_started, in_progress, complete). Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"status":{"anyOf":[{"enum":["not_started","in_progress","complete"],"type":"string"},{"type":"null"}],"default":null,"title":"Status"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_questsArguments","type":"object"}},{"name":"get_quest","title":"Get quest","description":"Get one quest by id. Rich-text fields are also returned as readable text. `gm_notes` is omitted for players by the server.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"quest_id":{"title":"Quest Id","type":"integer"}},"required":["quest_id"],"title":"get_questArguments","type":"object"}},{"name":"create_quest","title":"Create quest","description":"GM only. Create a quest in a world. `description` and `gm_notes` are plain text: blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted (it would be stored literally). `horizon` is the time scale (long/medium/short). Set `is_public` to reveal it to players; `gm_notes` stays GM-only regardless. `entity_refs` links the arc to the locations, organizations, and NPCs it involves — each is {id, kind} where kind is 'item' for a World Builder entity or 'npc' for a Character. Those links are what make the arc show up in the Weave, so pass them whenever you know the cast.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"$defs":{"EntityRef":{"description":"A typed reference to a linkable entity.\n\n``kind='item'`` → a World Builder entity id; ``kind='npc'`` → a Character\nid. The discriminator is required because the two tables have independent\nid spaces, so a bare id is ambiguous and could link the wrong row.","properties":{"id":{"title":"Id","type":"integer"},"kind":{"enum":["item","npc"],"title":"Kind","type":"string"}},"required":["id","kind"],"title":"EntityRef","type":"object"}},"properties":{"world_id":{"title":"World Id","type":"integer"},"title":{"title":"Title","type":"string"},"description":{"default":"","title":"Description","type":"string"},"gm_notes":{"default":"","title":"Gm Notes","type":"string"},"horizon":{"anyOf":[{"enum":["long","medium","short"],"type":"string"},{"type":"null"}],"default":null,"title":"Horizon"},"is_public":{"default":false,"title":"Is Public","type":"boolean"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"entity_refs":{"anyOf":[{"items":{"$ref":"#/$defs/EntityRef"},"type":"array"},{"type":"null"}],"default":null,"title":"Entity Refs"}},"required":["world_id","title"],"title":"create_questArguments","type":"object"}},{"name":"update_quest","title":"Update quest","description":"GM only. Update a Story Arc's title, description, gm_notes, horizon, visibility, or status. Only the fields you pass are changed. Prefer this over `toggle_quest_status` when you know the status you want — toggling cycles one step at a time. `entity_refs` REPLACES the arc's entity links; omit it to leave them untouched.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"$defs":{"EntityRef":{"description":"A typed reference to a linkable entity.\n\n``kind='item'`` → a World Builder entity id; ``kind='npc'`` → a Character\nid. The discriminator is required because the two tables have independent\nid spaces, so a bare id is ambiguous and could link the wrong row.","properties":{"id":{"title":"Id","type":"integer"},"kind":{"enum":["item","npc"],"title":"Kind","type":"string"}},"required":["id","kind"],"title":"EntityRef","type":"object"}},"properties":{"quest_id":{"title":"Quest Id","type":"integer"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"},"gm_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Gm Notes"},"horizon":{"anyOf":[{"enum":["long","medium","short"],"type":"string"},{"type":"null"}],"default":null,"title":"Horizon"},"is_public":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Is Public"},"status":{"anyOf":[{"enum":["not_started","in_progress","complete"],"type":"string"},{"type":"null"}],"default":null,"title":"Status"},"entity_refs":{"anyOf":[{"items":{"$ref":"#/$defs/EntityRef"},"type":"array"},{"type":"null"}],"default":null,"title":"Entity Refs"}},"required":["quest_id"],"title":"update_questArguments","type":"object"}},{"name":"toggle_quest_status","title":"Toggle quest status","description":"GM only. Advance a quest's status by one step: not_started → in_progress → complete → not_started.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"inputSchema":{"properties":{"quest_id":{"title":"Quest Id","type":"integer"}},"required":["quest_id"],"title":"toggle_quest_statusArguments","type":"object"}},{"name":"create_objective","title":"Create objective","description":"GM only. Create an objective — one line of text, the beat a GM drip-feeds to the party. It must hang off a Story Arc (`quest_id`) or a session (`session_id`); pass at least one. Objectives are HIDDEN from players by default: set `is_revealed` only for beats the party already knows. Use `order` to sequence the beats of an arc.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"text":{"title":"Text","type":"string"},"quest_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Quest Id"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"status":{"default":"not_started","enum":["not_started","in_progress","complete","dismissed"],"title":"Status","type":"string"},"is_revealed":{"default":false,"title":"Is Revealed","type":"boolean"},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Order"}},"required":["world_id","text"],"title":"create_objectiveArguments","type":"object"}},{"name":"list_objectives","title":"List objectives","description":"List the beats (objectives) of a world. Narrow with `quest_id` (the beats of one Story Arc, in order) or `session_id`. `unresolved=true` is the LOOSE-THREADS view: every beat across the whole campaign that is neither complete nor dismissed, each keeping its originating session — this is the answer to \"what did we leave hanging?\", and it is one call rather than walking every arc. Visibility-scoped: a player sees party-revealed beats plus their own proposals, never the GM's unrevealed prep. Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"quest_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Quest Id"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Session Id"},"unresolved":{"default":false,"title":"Unresolved","type":"boolean"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_objectivesArguments","type":"object"}},{"name":"update_objective","title":"Update objective","description":"Update a beat: its text, status, reveal state, or order. Only the fields you pass change. Setting `is_revealed=true` is the one-tap reveal that shows the beat to the party — GM only; a player may edit only their own unrevealed proposals. Mark a beat `complete` when the party resolved it and `dismissed` when it stopped mattering; both drop it out of the `unresolved` loose-threads view.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"objective_id":{"title":"Objective Id","type":"integer"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Text"},"status":{"anyOf":[{"enum":["not_started","in_progress","complete","dismissed"],"type":"string"},{"type":"null"}],"default":null,"title":"Status"},"is_revealed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Is Revealed"},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Order"}},"required":["objective_id"],"title":"update_objectiveArguments","type":"object"}},{"name":"delete_objective","title":"Delete objective","description":"Delete a beat outright. Prefer `update_objective` with status='dismissed' for a beat the story moved past — that keeps the record of having planned it. Delete is for a beat created in error. GM only, except your own unrevealed proposals.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"objective_id":{"title":"Objective Id","type":"integer"}},"required":["objective_id"],"title":"delete_objectiveArguments","type":"object"}},{"name":"delete_quest","title":"Delete quest","description":"GM only. Delete a Story Arc and its beats. Prefer `update_quest` with status='complete' for an arc the party finished — a completed arc is the campaign's history and worth keeping. This is for an arc created in error.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"quest_id":{"title":"Quest Id","type":"integer"}},"required":["quest_id"],"title":"delete_questArguments","type":"object"}},{"name":"get_arc_nudges","title":"Get arc nudges (prep)","description":"GM only. For every IN-PROGRESS Story Arc, the unresolved beats that gate it — \"the party cannot open the rest of this story until these land.\" Grouped by arc, which is what makes it prep material rather than a flat list; `list_objectives(unresolved=true)` gives you the same beats without the arc they belong to. Fully deterministic: no model call, no AI budget, and it never writes.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"}},"required":["world_id"],"title":"get_arc_nudgesArguments","type":"object"}},{"name":"list_player_goals","title":"List player goals","description":"What the players want out of this campaign, in their own words. Visibility-scoped: the GM sees every goal, a player sees their own plus other members' shared ones. Narrow with `character_id`. This is the read that makes prep about the table rather than the plot. Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"character_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Character Id"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_player_goalsArguments","type":"object"}},{"name":"list_character_fates","title":"List character fates","description":"The GM-authored fates hanging over the characters — what the story has in store for them. GM-authored and top-down, unlike player goals. A player sees only REVEALED fates on their own characters, so never quote one to a player unless it came back from their own connection. Narrow with `character_id`. Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"character_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Character Id"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_character_fatesArguments","type":"object"}},{"name":"list_arc_goals","title":"List arc goals","description":"GM only. What one Story Arc asks of each character — the arc ↔ character edge, which is prep and never player-facing. Use it to answer \"what does this arc mean for Ismark?\" rather than inferring it from the arc description.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"quest_id":{"title":"Quest Id","type":"integer"}},"required":["quest_id"],"title":"list_arc_goalsArguments","type":"object"}},{"name":"search_world_builder","title":"Search World Builder","description":"Search a world's lore entities (locations, items, NPCs, history, organizations) by name substring. Omit `query` to list all visible entities. Optionally restrict to one `item_type`. Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Query"},"item_type":{"anyOf":[{"enum":["Location","Inventory","NPC","History","Organization"],"type":"string"},{"type":"null"}],"default":null,"title":"Item Type"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"search_world_builderArguments","type":"object"}},{"name":"get_world_builder_item","title":"Get World Builder item","description":"Get one lore entity by id. Rich-text fields are also returned as readable text. GM-only notes are omitted for players by the server.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_id":{"title":"Item Id","type":"integer"}},"required":["world_id","item_id"],"title":"get_world_builder_itemArguments","type":"object"}},{"name":"create_world_builder_item","title":"Create World Builder item","description":"GM only. Create a lore entity: Location, Inventory, History, or Organization. To create an NPC or any other person, use `create_character` — people are not world-builder entities. `description` (public 'About') and `gm_notes` (GM-only secrets) are plain text: blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted (it would be stored literally). Set `is_public` to reveal the entity to players; `gm_notes` stays GM-only regardless. `parent_id` nests one Location inside another (Region > Town > Tavern) and is only valid for Locations.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_type":{"enum":["Location","Inventory","History","Organization"],"title":"Item Type","type":"string"},"name":{"title":"Name","type":"string"},"description":{"default":"","title":"Description","type":"string"},"gm_notes":{"default":"","title":"Gm Notes","type":"string"},"is_public":{"default":false,"title":"Is Public","type":"boolean"},"parent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Parent Id"}},"required":["world_id","item_type","name"],"title":"create_world_builder_itemArguments","type":"object"}},{"name":"update_world_builder_item","title":"Update World Builder item","description":"GM only. Update a lore entity's name, description (public 'About'), gm_notes (GM-only secrets), visibility, or parent location. `description` and `gm_notes` are plain text (blank lines start new paragraphs, single newlines become line breaks; Markdown is NOT interpreted). Only the fields you pass are changed. `parent_id` nests this Location inside another (pass 0 to un-nest it) — this is how a bulk-imported location gets its place in the hierarchy, since `import_world_entities` creates everything flat.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_id":{"title":"Item Id","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"},"gm_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Gm Notes"},"is_public":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Is Public"},"parent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Parent Id"}},"required":["world_id","item_id"],"title":"update_world_builder_itemArguments","type":"object"}},{"name":"add_annotation","title":"Add annotation","description":"Add or replace your personal note (annotation) on a lore entity. Each user has one annotation per entity, so this overwrites your previous note for the same item. Annotations are private to the author — useful for a player's own observations.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_id":{"title":"Item Id","type":"integer"},"content":{"title":"Content","type":"string"}},"required":["world_id","item_id","content"],"title":"add_annotationArguments","type":"object"}},{"name":"get_entity_references","title":"Get entity references","description":"Where this lore entity has come up: the journal entries and sessions that mention it, ordered by session date. `get_world_builder_item` gives the dossier; this gives the history. Visibility-scoped to the caller.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_id":{"title":"Item Id","type":"integer"}},"required":["world_id","item_id"],"title":"get_entity_referencesArguments","type":"object"}},{"name":"create_thread","title":"Draw a Thread (link two entities)","description":"GM only. Draw a Thread — a labelled relationship — between two things in a world, which is what makes them joined on the Weave. Each side is an entity (`kind='entity'`, a World Builder id) or a person (`kind='character'`, a Character id); the kind is REQUIRED because the two tables have overlapping id spaces, so a bare id could join the wrong row. Give it a `label` — \"sworn enemy of\", \"smuggles for\" — because an unlabelled edge tells a reader only that something connects. Threads default to GM-only; set `is_public` to show the party. To nest a Location inside another Location use `update_world_builder_item(parent_id=…)` instead — containment is not a Thread. Returns 409 if this exact Thread already exists.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"source_id":{"title":"Source Id","type":"integer"},"target_id":{"title":"Target Id","type":"integer"},"source_kind":{"default":"entity","enum":["entity","character"],"title":"Source Kind","type":"string"},"target_kind":{"default":"entity","enum":["entity","character"],"title":"Target Kind","type":"string"},"label":{"default":"","title":"Label","type":"string"},"description":{"default":"","title":"Description","type":"string"},"is_public":{"default":false,"title":"Is Public","type":"boolean"}},"required":["world_id","source_id","target_id"],"title":"create_threadArguments","type":"object"}},{"name":"update_thread","title":"Update a Thread","description":"GM only. Re-label or re-describe an existing Thread. Only the free text is mutable — to re-point a Thread at different entities, delete it and draw a new one.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"link_id":{"title":"Link Id","type":"integer"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"}},"required":["world_id","link_id"],"title":"update_threadArguments","type":"object"}},{"name":"delete_thread","title":"Delete a Thread","description":"GM only. Remove a Thread from the Weave. The two entities it joined are untouched — only the relationship goes.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"link_id":{"title":"Link Id","type":"integer"}},"required":["world_id","link_id"],"title":"delete_threadArguments","type":"object"}},{"name":"delete_world_builder_item","title":"Delete World Builder item","description":"GM only. Delete a lore entity. This is the correction path for a bad `import_world_entities` batch — 200 entities can be created in one call, and until now nothing here could remove one of them. Contained children are orphaned to top level, not deleted with it.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"item_id":{"title":"Item Id","type":"integer"}},"required":["world_id","item_id"],"title":"delete_world_builder_itemArguments","type":"object"}},{"name":"check_duplicate_name","title":"Check for a duplicate name","description":"GM only. Before creating something, ask whether this world already has it. Case-insensitive exact match on `name` across BOTH lore entities and characters, so it catches an NPC that already exists as a person. Advisory only — a duplicate name is never blocked, because two \"Guard\" NPCs are legitimate. This matters most before `import_world_entities` when a changed plan or a different source may overlap earlier/manual prep. Exact tool retries are protected by a durable idempotency receipt. Pass `item_type` to have matches flagged `same_type`, and `exclude_item_id` when checking a RENAME so the row being renamed does not match itself.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"name":{"title":"Name","type":"string"},"item_type":{"anyOf":[{"enum":["Location","Inventory","NPC","History","Organization"],"type":"string"},{"type":"null"}],"default":null,"title":"Item Type"},"exclude_item_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Exclude Item Id"}},"required":["world_id","name"],"title":"check_duplicate_nameArguments","type":"object"}},{"name":"set_visibility_bulk","title":"Set visibility in bulk","description":"GM only. Reveal or hide many things at once — the \"share my prep with the party\" lever. `is_public` is an explicit TARGET, not a toggle, so the same call twice lands the same state. Select either explicitly, with `item_ids` (lore entities) and/or `character_ids` (unclaimed characters only), or sweepingly with `select_all=true`, optionally narrowed by `item_type`. `select_all` is resolved server-side, so you never have to page the world to collect ids. Imported entities all land PRIVATE, which is what makes this the companion to `import_world_entities` — otherwise revealing a module means one call per entity. Claimed player characters are never touched, and `gm_notes` stay GM-only regardless of what this sets.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"is_public":{"title":"Is Public","type":"boolean"},"item_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"default":null,"title":"Item Ids"},"character_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"default":null,"title":"Character Ids"},"select_all":{"default":false,"title":"Select All","type":"boolean"},"item_type":{"anyOf":[{"enum":["Location","Inventory","NPC","History","Organization"],"type":"string"},{"type":"null"}],"default":null,"title":"Item Type"}},"required":["world_id","is_public"],"title":"set_visibility_bulkArguments","type":"object"}},{"name":"list_characters","title":"List / search characters","description":"List the people of a world — player characters and NPCs, visibility-scoped to the caller (a player sees claimed characters plus public NPCs; GM-only NPCs stay hidden). Pass `query` to match on name or bio, so \"what do we know about the harbormaster?\" resolves without pulling the whole roster. `unclaimed=true` narrows to NPCs (nobody plays them). Results are paged: you get at most `limit` rows (default 50, max 500) plus `total` and `has_more`, so a short page always says so. Pass `offset` to walk further in.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Query"},"unclaimed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Unclaimed"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Limit"},"offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Offset"}},"required":["world_id"],"title":"list_charactersArguments","type":"object"}},{"name":"get_character","title":"Get character","description":"Get one character by id — bio, life status, who plays them, and (for a GM) the GM-only notes. Ids come from `list_characters`.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"character_id":{"title":"Character Id","type":"integer"}},"required":["character_id"],"title":"get_characterArguments","type":"object"}},{"name":"get_character_references","title":"Get character references","description":"Where this character has come up: session notes, published recaps, and journals that mention them, newest first. This is the \"what do we know about them?\" answer — `get_character` gives the dossier, this gives the history. Visibility-scoped, so a player never sees GM-only mentions.","class":"read","annotations":{"readOnlyHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"character_id":{"title":"Character Id","type":"integer"}},"required":["world_id","character_id"],"title":"get_character_referencesArguments","type":"object"}},{"name":"add_character_annotation","title":"Annotate character","description":"Add or replace your personal note on a character. Each user has one annotation per character, so this overwrites your previous note. Annotations are private to their author — the character-side mirror of `add_annotation` on lore entities.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"character_id":{"title":"Character Id","type":"integer"},"content":{"title":"Content","type":"string"}},"required":["world_id","character_id","content"],"title":"add_character_annotationArguments","type":"object"}},{"name":"create_character","title":"Create character (NPC)","description":"GM only. Create a person in the world — an NPC, or a pregen player character waiting to be claimed. This is the ONLY way to create an NPC; the World Builder tools reject item_type='NPC' because people live in the characters table. `bio` is the public-facing description and `gm_notes` the GM-only secrets (both plain text). Set `is_public` to reveal the NPC to players, and `is_claimable` only for characters a player is meant to take over. Characters are always created UNCLAIMED — a player claims their own PC in the app; this never assigns one to anybody.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"properties":{"world_id":{"title":"World Id","type":"integer"},"name":{"title":"Name","type":"string"},"bio":{"default":"","title":"Bio","type":"string"},"gm_notes":{"default":"","title":"Gm Notes","type":"string"},"life_status":{"default":"alive","enum":["alive","deceased","missing","unknown"],"title":"Life Status","type":"string"},"is_public":{"default":false,"title":"Is Public","type":"boolean"},"is_claimable":{"default":false,"title":"Is Claimable","type":"boolean"}},"required":["world_id","name"],"title":"create_characterArguments","type":"object"}},{"name":"update_character","title":"Update character","description":"GM only. Update a character's name, bio, gm_notes, life status, or visibility. Only the fields you pass are changed — useful for revealing an NPC to the party (`is_public=true`) or marking one dead after a session.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"character_id":{"title":"Character Id","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Bio"},"gm_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Gm Notes"},"life_status":{"anyOf":[{"enum":["alive","deceased","missing","unknown"],"type":"string"},{"type":"null"}],"default":null,"title":"Life Status"},"is_public":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Is Public"},"is_claimable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Is Claimable"}},"required":["character_id"],"title":"update_characterArguments","type":"object"}},{"name":"delete_character","title":"Delete character","description":"GM only (or the player who owns the character). Delete a person from the world. Almost never the right call for an NPC the party has met: use `update_character(life_status='deceased')` or 'missing' instead, which keeps them in the record and on the Weave. The server REFUSES this with a 409 if any journal entry is attached to them — the history outranks a clean roster — and the error says so. This is for a person created in error.","class":"delete","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"inputSchema":{"properties":{"character_id":{"title":"Character Id","type":"integer"}},"required":["character_id"],"title":"delete_characterArguments","type":"object"}},{"name":"import_world_entities","title":"Import world entities (bulk)","description":"GM only. Create many world entities, their Weave links, and optionally one session — in a SINGLE transaction. Use this to slot a published campaign module (or any prepared notes) into a world instead of calling create_world_builder_item hundreds of times.\n\nEach item is {name, type, description, action?, existingId?} where type is Location, Inventory, History, or Organization. `action` defaults to `create`; use `merge` to append the description to an existing entity's private GM notes, or `skip` to leave that row unchanged. For `merge`, pass the matched entity's `existingId` whenever possible. A merge appends, so do not replay it after it has landed; use `skip` for unchanged rows on a rerun. NPCs are NOT importable here — create people with `create_character`.\n\nLinks are {source, target} BY NAME and may reference entities being created in this same call.\n\nThe optional session accepts `action: create|skip` and defaults to `create`.\n\nLimits: at most 200 item rows per call — split larger modules into several calls, importing containers (regions, factions) first so later batches can link and nest against them. Imported entities are PRIVATE and carry only a description; use `update_world_builder_item` afterwards for gm_notes or to reveal one to players.\n\nFree-tier worlds have a total entity cap. If this batch would push the world over it, the commit still succeeds and creates as many as fit — it never rejects the whole batch. The response's `cap` field says exactly what happened: {key, tier, limit, have, requested, landed, not_landed, remaining, not_landed_names}. When `not_landed` > 0, tell the user how many landed, how many didn't, name the entries in `not_landed_names` (never invent names if that list is empty), and that upgrading to Pro removes the cap — don't just report success.\n\nReturns {created_count, ids, entity_ids, character_ids, merged_count, links_created, placeholder_converted, session_id?, cap, detail?}. A link-only success is real work: report links_created rather than calling it an empty import.","class":"write","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"inputSchema":{"$defs":{"ImportItem":{"description":"One reviewed entity row. ``name`` and ``type`` are required.\n\n``description`` is plain text or Markdown — the backend converts it to a\nTiptap doc, so Markdown headings/lists DO survive here (unlike the\nsingle-entity ``create_world_builder_item`` tool, which stores text\nliterally). ``action`` defaults to ``create``. ``merge`` appends the\ndescription to an existing entity's private GM notes; pass its\n``existingId`` whenever possible so same-name entities cannot redirect the\nmerge. ``skip`` makes the reviewed row a no-op.","properties":{"name":{"title":"Name","type":"string"},"type":{"enum":["Location","Inventory","History","Organization"],"title":"Type","type":"string"},"description":{"title":"Description","type":"string"},"action":{"enum":["create","merge","skip"],"title":"Action","type":"string"},"existingId":{"title":"Existingid","type":"integer"}},"required":["name","type"],"title":"ImportItem","type":"object"},"ImportLink":{"description":"A RELATED Weave edge between two entities, referenced BY NAME.\n\nNames resolve against the entities created in this same call plus every\nentity already in the world. Unresolvable names are skipped silently by the\nbackend rather than failing the import.","properties":{"source":{"title":"Source","type":"string"},"target":{"title":"Target","type":"string"}},"required":["source","target"],"title":"ImportLink","type":"object"},"ImportSession":{"description":"An optional session created alongside the entities.\n\n``notes_markdown`` is real Markdown and may contain @mentions of the\nimported entities, which is what lights up the Weave on first open.\n``session_date`` is ``YYYY-MM-DD``.","properties":{"title":{"title":"Title","type":"string"},"order":{"title":"Order","type":"integer"},"session_date":{"title":"Session Date","type":"string"},"notes_markdown":{"title":"Notes Markdown","type":"string"},"action":{"enum":["create","skip"],"title":"Action","type":"string"}},"title":"ImportSession","type":"object"}},"properties":{"world_id":{"title":"World Id","type":"integer"},"items":{"items":{"$ref":"#/$defs/ImportItem"},"title":"Items","type":"array"},"links":{"anyOf":[{"items":{"$ref":"#/$defs/ImportLink"},"type":"array"},{"type":"null"}],"default":null,"title":"Links"},"session":{"anyOf":[{"$ref":"#/$defs/ImportSession"},{"type":"null"}],"default":null}},"required":["world_id","items"],"title":"import_world_entitiesArguments","type":"object"}}],"prompts":[{"name":"prep-my-next-session","title":"Prep my next session","description":"Build a GM prep packet: where the story left off, open quests and objectives, and the entities likely to matter next.","arguments":[{"name":"world","description":null,"required":false}]},{"name":"what-happened-last-session","title":"What happened last session?","description":"Summarize the most recent session from its recap, notes, and any player journals written about it.","arguments":[{"name":"world","description":null,"required":false}]},{"name":"find-loose-threads","title":"Find loose threads","description":"Hunt for unresolved quests, dangling objectives, and entities the campaign has not touched in a while.","arguments":[{"name":"world","description":null,"required":false}]},{"name":"catch-me-up","title":"Catch me up (player)","description":"Player-scoped recap: published briefings and recaps since you last played, plus what your character has on their plate.","arguments":[{"name":"world","description":null,"required":false}]},{"name":"start-new-campaign","title":"Start a new campaign","description":"For a GM starting a brand-new campaign from nothing — no world yet, no notes, nothing prepared. Offer a genre template, create the world from their pick, and show them what got seeded.","arguments":[]},{"name":"import-campaign-module","title":"Import a campaign module","description":"Turn a prepared campaign document — a published module, Notion export, Markdown notes, a spreadsheet — into locations, NPCs, story arcs, and beats in a world, after showing the GM a plan.","arguments":[{"name":"world","description":null,"required":false}]}],"resources":[{"uri":"storywright://worlds","name":"Your campaigns","title":"Your campaigns","description":"Every world you belong to, with your role in each and the resource URIs for reading further into them.","mimeType":"text/markdown"},{"uriTemplate":"storywright://worlds/{world_id}","name":"Campaign brief","title":"Campaign brief","description":"Where a campaign stands: the party, recent sessions, the live Story Arcs, and where they left off.","mimeType":"text/markdown"},{"uriTemplate":"storywright://worlds/{world_id}/entities/{item_id}","name":"Lore entity","title":"Lore entity","description":"One place, item, organization, or piece of history, as readable text. GM-only notes appear only for a GM.","mimeType":"text/markdown"},{"uriTemplate":"storywright://worlds/{world_id}/characters/{character_id}","name":"Character dossier","title":"Character dossier","description":"One person — player character or NPC: bio, life status, who plays them, and a GM's private notes when the reader is the GM.","mimeType":"text/markdown"},{"uriTemplate":"storywright://worlds/{world_id}/sessions/{session_id}","name":"Session record","title":"Session record","description":"One session: its recap and, for a GM, the private notes.","mimeType":"text/markdown"}]}