holonovel 2026.9.1 → 2026.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +268 -385
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -53,7 +53,7 @@ state.buildFingerprint.lastSpecReview = new Date().toISOString();
|
|
|
53
53
|
// ── Server ─────────────────────────────────────────────────────────
|
|
54
54
|
const server = new McpServer({
|
|
55
55
|
name: "inform-holonovel",
|
|
56
|
-
version: "2026.09.
|
|
56
|
+
version: "2026.09.02",
|
|
57
57
|
});
|
|
58
58
|
// REQ-426c — MCP Apps capability negotiation: the server declares the
|
|
59
59
|
// `io.modelcontextprotocol/ui` extension in its capabilities; a client that
|
|
@@ -452,7 +452,10 @@ function terseOutput(tool, args, normal, terse) {
|
|
|
452
452
|
}
|
|
453
453
|
// REQ-409 — normalize the per-call detail request: absent → summary (lean
|
|
454
454
|
// default); explicit `true` → full entries; explicit `false` → summary.
|
|
455
|
-
|
|
455
|
+
// REQ-427 — every advertised input parameter carries a JSON Schema description
|
|
456
|
+
// (verified by T509); REQ-024 — three-clause tool descriptions ("Use when" /
|
|
457
|
+
// "Do NOT use when").
|
|
458
|
+
const detailZod = { detail: z.boolean().optional().describe("When true, return full schema and description instead of a summary.") };
|
|
456
459
|
function wantsDetail(detail) {
|
|
457
460
|
return detail === true;
|
|
458
461
|
}
|
|
@@ -1233,8 +1236,8 @@ function badgeLabel(badge) {
|
|
|
1233
1236
|
// context; REQ-305 — observer mode: read-only spectator, AI plays both roles.
|
|
1234
1237
|
server.registerTool("set_badge", {
|
|
1235
1238
|
title: "Set Active Badge",
|
|
1236
|
-
description: "Switch active badge
|
|
1237
|
-
inputSchema: { badge: z.enum(["player", "game_master", "observer", "none"]) },
|
|
1239
|
+
description: "Switch the active badge to player, game_master, observer, or none (Editor), gating tool access server-side for the session; always callable. Use when: entering the story, spectating, or stepping away to edit. Do NOT use when: answering a pending workflow decision — use respond.",
|
|
1240
|
+
inputSchema: { badge: z.enum(["player", "game_master", "observer", "none"]).describe("The badge to activate: player, game_master, observer, or none (Editor).") },
|
|
1238
1241
|
}, async ({ badge }) => {
|
|
1239
1242
|
const novel = state.activeNovel;
|
|
1240
1243
|
if (novel) {
|
|
@@ -1258,8 +1261,8 @@ function canon(text) {
|
|
|
1258
1261
|
}
|
|
1259
1262
|
server.registerTool("respond", {
|
|
1260
1263
|
title: "Respond to Workflow Decision",
|
|
1261
|
-
description: "
|
|
1262
|
-
inputSchema: { decision: z.string(), option: z.string() },
|
|
1264
|
+
description: "Answer a pending workflow decision, atomically draining it and persisting the outcome to the Novel. Use when: the server emitted a [NEED_INPUT] prompt and the caller must choose. Do NOT use when: no decision is pending — use set_badge or a state tool instead.",
|
|
1265
|
+
inputSchema: { decision: z.string().describe("The canonical decision text the workflow is waiting on."), option: z.string().describe("The chosen option, or 'cancel' to abort the workflow and restore its snapshot.") },
|
|
1263
1266
|
}, async ({ decision, option }) => {
|
|
1264
1267
|
requireNotObserver();
|
|
1265
1268
|
const novel = requireNovel();
|
|
@@ -1493,7 +1496,7 @@ function kwMatch(canonicalDecision, keywords) {
|
|
|
1493
1496
|
}
|
|
1494
1497
|
server.registerTool("undo", {
|
|
1495
1498
|
title: "Undo",
|
|
1496
|
-
description: "Undo the most recent mutation
|
|
1499
|
+
description: "Undo the most recent state mutation, restoring the prior per-badge snapshot. Use when: reverting a mistaken or unwanted change. Do NOT use when: re-applying an undone change — use redo.",
|
|
1497
1500
|
inputSchema: {},
|
|
1498
1501
|
}, async () => {
|
|
1499
1502
|
requireNotObserver();
|
|
@@ -1506,7 +1509,7 @@ server.registerTool("undo", {
|
|
|
1506
1509
|
});
|
|
1507
1510
|
server.registerTool("redo", {
|
|
1508
1511
|
title: "Redo",
|
|
1509
|
-
description: "
|
|
1512
|
+
description: "Re-apply the most recently undone mutation, restoring the per-badge snapshot that undo removed. Use when: an undo was issued by mistake and the change should be restored. Do NOT use when: reverting a new change — use undo.",
|
|
1510
1513
|
inputSchema: {},
|
|
1511
1514
|
}, async () => {
|
|
1512
1515
|
requireNotObserver();
|
|
@@ -1519,8 +1522,8 @@ server.registerTool("redo", {
|
|
|
1519
1522
|
});
|
|
1520
1523
|
server.registerTool("help", {
|
|
1521
1524
|
title: "Help and Tool Discovery",
|
|
1522
|
-
description: "Show available
|
|
1523
|
-
inputSchema: { query: z.string().optional() },
|
|
1525
|
+
description: "Show the available tools grouped by category, badge-filtered for the active badge. Use when: the caller needs to discover what tools exist or find one by keyword. Do NOT use when: reading the current badge's guidance — use the badge_briefing prompt.",
|
|
1526
|
+
inputSchema: { query: z.string().optional().describe("Optional search term matched against tool name, description, and title.") },
|
|
1524
1527
|
}, async ({ query }) => {
|
|
1525
1528
|
// REQ-024 — tool documentation: tools carry a human title and descriptions
|
|
1526
1529
|
// using the ruleset's own terms; full descriptions remain at resources/read.
|
|
@@ -1597,8 +1600,8 @@ server.registerTool("help", {
|
|
|
1597
1600
|
});
|
|
1598
1601
|
server.registerTool("set_help_category", {
|
|
1599
1602
|
title: "Set Help Category Override",
|
|
1600
|
-
description: "Override the builder-assigned category
|
|
1601
|
-
inputSchema: { tool_name: z.string(), category: z.string().nullable() },
|
|
1603
|
+
description: "Override the builder-assigned category a tool appears under in help output, persisted to the Novel (Game Master only). Use when: reorganizing the tool catalog for a session. Do NOT use when: setting briefing section order — use set_briefing_order.",
|
|
1604
|
+
inputSchema: { tool_name: z.string().describe("The registered tool name to reassign."), category: z.string().nullable().describe("The new category label, or null/empty to restore the builder default.") },
|
|
1602
1605
|
}, async ({ tool_name, category }) => {
|
|
1603
1606
|
requireGM();
|
|
1604
1607
|
const novel = requireNovel();
|
|
@@ -1688,30 +1691,10 @@ function buildCharacterStats(build, rules) {
|
|
|
1688
1691
|
}
|
|
1689
1692
|
server.registerTool("create_character", {
|
|
1690
1693
|
title: "Create Character",
|
|
1691
|
-
description: "Create a character
|
|
1694
|
+
description: "Create a character via quick-create (name, species, classes) or a guided [NEED_INPUT] step-by-step workflow when called without a name, persisting the entity to the Novel. Use when: introducing a new player character or NPC. Do NOT use when: modifying an existing entity — use set_personality, set_voice_examples, or set_active_entity.",
|
|
1692
1695
|
// REQ-408 — parameter ceiling (8): compact entry (name + identity + source)
|
|
1693
1696
|
// with mechanical and personality detail grouped into refinement objects.
|
|
1694
|
-
inputSchema: {
|
|
1695
|
-
name: z.string().optional(),
|
|
1696
|
-
species: z.string().optional(),
|
|
1697
|
-
classes: z.union([z.string(), z.array(z.object({ className: z.string(), levels: z.number().optional() }))]).optional(),
|
|
1698
|
-
stat_method: z.string().optional(),
|
|
1699
|
-
seed: z.string().optional(),
|
|
1700
|
-
stage_to_roster: z.boolean().optional(),
|
|
1701
|
-
personality: z.object({
|
|
1702
|
-
description: z.string().optional(),
|
|
1703
|
-
voice: z.string().optional(),
|
|
1704
|
-
background: z.string().optional(),
|
|
1705
|
-
goals: z.string().optional(),
|
|
1706
|
-
}).optional(),
|
|
1707
|
-
details: z.object({
|
|
1708
|
-
ability_scores: z.union([z.string(), z.array(z.number())]).optional(),
|
|
1709
|
-
skills: z.union([z.string(), z.array(z.string())]).optional(),
|
|
1710
|
-
feats: z.union([z.string(), z.array(z.string())]).optional(),
|
|
1711
|
-
talents: z.union([z.string(), z.array(z.string())]).optional(),
|
|
1712
|
-
equipment: z.union([z.string(), z.array(z.string())]).optional(),
|
|
1713
|
-
}).optional(),
|
|
1714
|
-
},
|
|
1697
|
+
inputSchema: { name: z.string().optional().describe("The character's name; omit to begin the step-by-step creation workflow."), species: z.string().optional().describe("The species name; required for quick-create."), classes: z.union([z.string(), z.array(z.object({ className: z.string(), levels: z.number().optional() }))]).optional().describe("Class levels as 'Class 5 / Other 2' or an array of {className, levels}."), stat_method: z.string().optional().describe("The stat-generation method; defaults to the ruleset's first method."), seed: z.string().optional().describe("Deterministic seed for ability-score generation."), stage_to_roster: z.boolean().optional().describe("When true, also stage the entity into the persistent roster."), personality: z.object({ description: z.string().optional().describe("Narrative description."), voice: z.string().optional().describe("Voice and speech pattern."), background: z.string().optional().describe("Backstory."), goals: z.string().optional().describe("Character goals.") }).optional().describe("Grouped personality fields."), details: z.object({ ability_scores: z.union([z.string(), z.array(z.number())]).optional().describe("Ability scores as '15 14 13 12 10 8' or an array."), skills: z.union([z.string(), z.array(z.string())]).optional().describe("Trained skills."), feats: z.union([z.string(), z.array(z.string())]).optional().describe("Feats."), talents: z.union([z.string(), z.array(z.string())]).optional().describe("Talents."), equipment: z.union([z.string(), z.array(z.string())]).optional().describe("Starting equipment.") }).optional().describe("Grouped mechanical details.") },
|
|
1715
1698
|
}, async ({ name, species, classes, stat_method, seed, stage_to_roster, personality: personalityObj, details, description, voice, background, goals, ability_scores, skills, feats, talents, equipment }) => {
|
|
1716
1699
|
// Legacy-tolerant normalization: accept the grouped objects or their
|
|
1717
1700
|
// top-level spellings interchangeably.
|
|
@@ -1819,8 +1802,8 @@ ${stage_to_roster ? `Staged to roster as ${entity.id}.` : `Character '${name}' c
|
|
|
1819
1802
|
});
|
|
1820
1803
|
server.registerTool("stage_character", {
|
|
1821
1804
|
title: "Stage Character to Roster",
|
|
1822
|
-
description: "Stage an existing
|
|
1823
|
-
inputSchema: { entity_id: z.string().optional() },
|
|
1805
|
+
description: "Stage an existing Novel entity into the persistent roster, so it survives the Novel and can be imported elsewhere. Use when: persisting a character for reuse in another Novel. Do NOT use when: importing a roster character into the Novel — use import_character.",
|
|
1806
|
+
inputSchema: { entity_id: z.string().optional().describe("The entity to stage; defaults to the active entity.") },
|
|
1824
1807
|
}, async ({ entity_id }) => {
|
|
1825
1808
|
requireNotObserver();
|
|
1826
1809
|
const novel = requireNovel();
|
|
@@ -1833,8 +1816,8 @@ server.registerTool("stage_character", {
|
|
|
1833
1816
|
});
|
|
1834
1817
|
server.registerTool("import_character", {
|
|
1835
1818
|
title: "Import Character",
|
|
1836
|
-
description: "Import a roster character into the active
|
|
1837
|
-
inputSchema: { roster_id: z.string() },
|
|
1819
|
+
description: "Import a roster character into the active Novel, copying name, personality, voice examples, and inventory. Use when: bringing a staged character into play. Do NOT use when: persisting a Novel entity to the roster — use stage_character.",
|
|
1820
|
+
inputSchema: { roster_id: z.string().describe("The roster identifier to import.") },
|
|
1838
1821
|
}, async ({ roster_id }) => {
|
|
1839
1822
|
requireNotObserver();
|
|
1840
1823
|
const novel = requireNovel();
|
|
@@ -1847,16 +1830,13 @@ server.registerTool("import_character", {
|
|
|
1847
1830
|
});
|
|
1848
1831
|
server.registerTool("character_sheet", {
|
|
1849
1832
|
title: "Character Sheet",
|
|
1850
|
-
description: "Render a character sheet for an entity
|
|
1833
|
+
description: "Render a character sheet for an entity or NPC in markdown (default), json, html, or ascii. Use when: the caller needs the full mechanical or profile view of a character. Do NOT use when: creating or editing a character — use create_character or set_personality.",
|
|
1851
1834
|
// REQ-120 — NPC rendering via the same sheet mechanism; REQ-124 — NPC damage
|
|
1852
1835
|
// resolution targets NPCs by identifier; REQ-129 — property group cardinality.
|
|
1853
1836
|
// REQ-425a/b — the `format` selector is drawn from the output format catalog
|
|
1854
1837
|
// (STATBLOCK_FORMATS) and validated at call time; REQ-426b — the result
|
|
1855
1838
|
// carries `ui://` linkage metadata when the Apps extension is negotiated.
|
|
1856
|
-
inputSchema: {
|
|
1857
|
-
entity_id: z.string().optional(),
|
|
1858
|
-
format: z.string().optional(),
|
|
1859
|
-
},
|
|
1839
|
+
inputSchema: { entity_id: z.string().optional().describe("The entity or NPC to render; defaults to the active entity."), format: z.string().optional().describe("Output format: markdown (default), json, html, or ascii.") },
|
|
1860
1840
|
}, async ({ entity_id, format }) => {
|
|
1861
1841
|
const entity = resolveEntityOrNpc(entity_id);
|
|
1862
1842
|
if (!entity)
|
|
@@ -1881,8 +1861,8 @@ server.registerTool("character_sheet", {
|
|
|
1881
1861
|
});
|
|
1882
1862
|
server.registerTool("set_active_entity", {
|
|
1883
1863
|
title: "Set Active Entity",
|
|
1884
|
-
description: "Set the currently active entity.",
|
|
1885
|
-
inputSchema: { entity_id: z.string(), pov: z.enum(["character", "omniscient"]).optional() },
|
|
1864
|
+
description: "Set the currently active entity and, optionally, the point-of-view mode (character or omniscient), persisting the choice to the Novel. Use when: switching which character the Player inhabits. Do NOT use when: changing the acting badge — use set_badge.",
|
|
1865
|
+
inputSchema: { entity_id: z.string().describe("The entity to make active."), pov: z.enum(["character", "omniscient"]).optional().describe("Point-of-view mode for the active entity.") },
|
|
1886
1866
|
}, async ({ entity_id, pov }) => {
|
|
1887
1867
|
requireNotObserver();
|
|
1888
1868
|
const novel = requireNovel();
|
|
@@ -1896,19 +1876,13 @@ server.registerTool("set_active_entity", {
|
|
|
1896
1876
|
});
|
|
1897
1877
|
server.registerTool("set_personality", {
|
|
1898
1878
|
title: "Set Entity or NPC Personality",
|
|
1899
|
-
description: "Set narrative personality fields for an entity or NPC.",
|
|
1879
|
+
description: "Set narrative personality fields for an entity or NPC, persisting the most recent write across all read surfaces. Use when: defining how a character speaks, thinks, or what they want. Do NOT use when: setting dialogue examples — use set_voice_examples.",
|
|
1900
1880
|
// REQ-127 — ruleset-native personality mapping (set_personality description
|
|
1901
1881
|
// references ruleset-native construct names when a ruleset defines them);
|
|
1902
1882
|
// REQ-165 — entity ownership gating (Player for own entities, GM for all);
|
|
1903
1883
|
// REQ-166 — personality briefing rendering (fields surfaced in badge_briefing
|
|
1904
1884
|
// alongside stats); REQ-122 — NPC narrative fields (NPC identifiers accepted).
|
|
1905
|
-
inputSchema: {
|
|
1906
|
-
entity_id: z.string(),
|
|
1907
|
-
description: z.string().optional(),
|
|
1908
|
-
voice: z.string().optional(),
|
|
1909
|
-
background: z.string().optional(),
|
|
1910
|
-
goals: z.string().optional(),
|
|
1911
|
-
},
|
|
1885
|
+
inputSchema: { entity_id: z.string().describe("The entity or NPC to update."), description: z.string().optional().describe("Narrative description."), voice: z.string().optional().describe("Voice and speech pattern."), background: z.string().optional().describe("Backstory."), goals: z.string().optional().describe("Character goals.") },
|
|
1912
1886
|
}, async ({ entity_id, description, voice, background, goals }) => {
|
|
1913
1887
|
requireNotObserver();
|
|
1914
1888
|
const novel = requireNovel();
|
|
@@ -1938,13 +1912,10 @@ server.registerTool("set_personality", {
|
|
|
1938
1912
|
});
|
|
1939
1913
|
server.registerTool("set_voice_examples", {
|
|
1940
1914
|
title: "Set Voice Examples",
|
|
1941
|
-
description: "Set voice and dialogue examples for an entity or NPC.",
|
|
1915
|
+
description: "Set voice and dialogue examples for an entity or NPC, rendered ahead of trait descriptions to show rather than tell. Use when: giving the narrator concrete lines to imitate. Do NOT use when: setting abstract personality traits — use set_personality.",
|
|
1942
1916
|
// REQ-126 — voice examples render ahead of trait descriptions in prompts
|
|
1943
1917
|
// (show-don't-tell); REQ-077f — the primary dialogue-consistency mechanism.
|
|
1944
|
-
inputSchema: {
|
|
1945
|
-
entity_id: z.string(),
|
|
1946
|
-
examples: z.array(z.object({ context: z.string(), dialogue: z.string(), tag: z.string().optional() })),
|
|
1947
|
-
},
|
|
1918
|
+
inputSchema: { entity_id: z.string().describe("The entity or NPC to update."), examples: z.array(z.object({ context: z.string().describe("When the line was spoken."), dialogue: z.string().describe("The example line."), tag: z.string().optional().describe("Optional tag.") })).describe("Voice and dialogue examples.") },
|
|
1948
1919
|
}, async ({ entity_id, examples }) => {
|
|
1949
1920
|
requireNotObserver();
|
|
1950
1921
|
const novel = requireNovel();
|
|
@@ -1959,11 +1930,8 @@ server.registerTool("set_voice_examples", {
|
|
|
1959
1930
|
// REQ-069 — player feedback signal to the GM.
|
|
1960
1931
|
server.registerTool("player_signal", {
|
|
1961
1932
|
title: "Player Signal",
|
|
1962
|
-
description: "Send a narrative signal from the
|
|
1963
|
-
inputSchema: {
|
|
1964
|
-
signal: z.enum(["pace", "difficulty", "tone", "focus", "boundary", "voice_feedback"]),
|
|
1965
|
-
value: z.string(),
|
|
1966
|
-
},
|
|
1933
|
+
description: "Send a narrative feedback signal from the Player to the GM (Player badge only), recording it to the Novel and correcting the entity's voice examples when the signal is voice_feedback. Use when: the player wants to steer pacing, difficulty, tone, focus, or set a boundary. Do NOT use when: making a game action — use command or a mechanical tool.",
|
|
1934
|
+
inputSchema: { signal: z.enum(["pace", "difficulty", "tone", "focus", "boundary", "voice_feedback"]).describe("The feedback category."), value: z.string().describe("The feedback text.") },
|
|
1967
1935
|
}, async ({ signal, value }) => {
|
|
1968
1936
|
requirePlayer();
|
|
1969
1937
|
const novel = requireNovel();
|
|
@@ -1992,13 +1960,8 @@ server.registerTool("player_signal", {
|
|
|
1992
1960
|
// ── Autonomy (REQ-306) ────────────────────────────────────────────
|
|
1993
1961
|
server.registerTool("set_autonomy", {
|
|
1994
1962
|
title: "Adjustable Autonomy",
|
|
1995
|
-
description: "Set the AI autonomy sliders for the active Novel
|
|
1996
|
-
inputSchema: {
|
|
1997
|
-
level: z.enum(["full", "mechanical_prompt", "manual"]).optional(),
|
|
1998
|
-
confirmation: z.enum(["auto", "confirm", "prompt"]).optional(),
|
|
1999
|
-
safety: z.enum(["safe", "moderate", "hardcore"]).optional(),
|
|
2000
|
-
creativity: z.enum(["predictable", "standard", "chaotic"]).optional(),
|
|
2001
|
-
},
|
|
1963
|
+
description: "Set the AI autonomy sliders for the active Novel (level, confirmation, safety, creativity), persisting them and escalating safety raises through a confirmation workflow (Game Master only). Use when: tuning how much the AI auto-plays. Do NOT use when: switching the acting badge — use set_badge.",
|
|
1964
|
+
inputSchema: { level: z.enum(["full", "mechanical_prompt", "manual"]).optional().describe("How much the AI auto-plays: full, mechanical_prompt, or manual."), confirmation: z.enum(["auto", "confirm", "prompt"]).optional().describe("When the AI asks before acting: auto, confirm, or prompt."), safety: z.enum(["safe", "moderate", "hardcore"]).optional().describe("Safety tier: safe, moderate, or hardcore."), creativity: z.enum(["predictable", "standard", "chaotic"]).optional().describe("Creativity: predictable, standard, or chaotic.") },
|
|
2002
1965
|
}, async ({ level, confirmation, safety, creativity }) => {
|
|
2003
1966
|
requireGM();
|
|
2004
1967
|
const novel = requireNovel();
|
|
@@ -2127,8 +2090,8 @@ function recordExplorationKnowledge(novel, entity, type, name) {
|
|
|
2127
2090
|
}
|
|
2128
2091
|
server.registerTool("command", {
|
|
2129
2092
|
title: "Parser Command",
|
|
2130
|
-
description: "Execute a natural-language parser command against the world model
|
|
2131
|
-
inputSchema: { command: z.string() },
|
|
2093
|
+
description: "Execute a natural-language parser command against the world model, mutating it for navigation, inspection, object interaction, inventory, and wait. Use when: a player or narrator takes a physical action in the world. Do NOT use when: checking the outcome of an action without mutating state — use resolve_intent.",
|
|
2094
|
+
inputSchema: { command: z.string().describe("The natural-language command, e.g. 'go north', 'look', 'take torch', 'open door'.") },
|
|
2132
2095
|
}, async ({ command }) => {
|
|
2133
2096
|
const novel = requireNovel();
|
|
2134
2097
|
// REQ-197 — description mode commands are always recognized verbs.
|
|
@@ -2293,11 +2256,8 @@ function findMatchingThing(name, world, roomName) {
|
|
|
2293
2256
|
// --- World-Model CRUD (GM-only) ---
|
|
2294
2257
|
server.registerTool("create_room", {
|
|
2295
2258
|
title: "Create Room",
|
|
2296
|
-
description: "Create a new room in the world model
|
|
2297
|
-
inputSchema: {
|
|
2298
|
-
name: z.string(),
|
|
2299
|
-
description: z.string().optional(),
|
|
2300
|
-
},
|
|
2259
|
+
description: "Create a new room in the world model, persisting it to the Novel save file (Game Master only). Use when: the GM needs to add a location to the world model. Do NOT use when: adding a thing or an exit — use create_thing or create_exit.",
|
|
2260
|
+
inputSchema: { name: z.string().describe("The room name."), description: z.string().optional().describe("Optional room description.") },
|
|
2301
2261
|
}, async ({ name, description }) => {
|
|
2302
2262
|
requireGM();
|
|
2303
2263
|
const novel = requireNovel();
|
|
@@ -2319,8 +2279,8 @@ server.registerTool("create_room", {
|
|
|
2319
2279
|
});
|
|
2320
2280
|
server.registerTool("remove_room", {
|
|
2321
2281
|
title: "Remove Room",
|
|
2322
|
-
description: "Remove a room
|
|
2323
|
-
inputSchema: { name: z.string() },
|
|
2282
|
+
description: "Remove a room along with its contained things and exits, persisting the removal (Game Master only). Use when: deleting a location that is no longer needed. Do NOT use when: removing a single thing or exit — use remove_thing or remove_exit.",
|
|
2283
|
+
inputSchema: { name: z.string().describe("The room to remove.") },
|
|
2324
2284
|
}, async ({ name }) => {
|
|
2325
2285
|
requireGM();
|
|
2326
2286
|
const novel = requireNovel();
|
|
@@ -2348,29 +2308,8 @@ server.registerTool("remove_room", {
|
|
|
2348
2308
|
});
|
|
2349
2309
|
server.registerTool("create_thing", {
|
|
2350
2310
|
title: "Create Thing",
|
|
2351
|
-
description: "Create a new thing in the world model
|
|
2352
|
-
inputSchema: {
|
|
2353
|
-
name: z.string(),
|
|
2354
|
-
kind: z.string().optional(),
|
|
2355
|
-
description: z.string().optional(),
|
|
2356
|
-
location: z.string().optional(),
|
|
2357
|
-
location_type: z.enum(["room", "container", "supporter"]).optional(),
|
|
2358
|
-
fixed: z.boolean().optional(),
|
|
2359
|
-
openable: z.boolean().optional(),
|
|
2360
|
-
lockable: z.boolean().optional(),
|
|
2361
|
-
locked: z.boolean().optional(),
|
|
2362
|
-
lit: z.boolean().optional(),
|
|
2363
|
-
switched_on: z.boolean().optional(),
|
|
2364
|
-
switchable: z.boolean().optional(),
|
|
2365
|
-
transparent: z.boolean().optional(),
|
|
2366
|
-
readable: z.boolean().optional(),
|
|
2367
|
-
read_text: z.string().optional(),
|
|
2368
|
-
wearable: z.boolean().optional(),
|
|
2369
|
-
edible: z.boolean().optional(),
|
|
2370
|
-
drinkable: z.boolean().optional(),
|
|
2371
|
-
enterable: z.boolean().optional(),
|
|
2372
|
-
climbable: z.boolean().optional(),
|
|
2373
|
-
},
|
|
2311
|
+
description: "Create a new thing in the world model with an optional kind, container, and properties, persisting it (Game Master only). Use when: the GM needs to place an object, door, or device. Do NOT use when: adding a room — use create_room.",
|
|
2312
|
+
inputSchema: { name: z.string().describe("The thing name."), kind: z.string().optional().describe("Optional kind from the hierarchy (thing, container, supporter, door, device, vehicle, person, backdrop, region)."), description: z.string().optional().describe("Optional description."), location: z.string().optional().describe("Optional containing room or thing name."), location_type: z.enum(["room", "container", "supporter"]).optional().describe("Where the thing is placed: room, container, or supporter."), fixed: z.boolean().optional().describe("When true the thing cannot be taken."), openable: z.boolean().optional().describe("When true the thing can be opened."), lockable: z.boolean().optional().describe("When true the thing can be locked."), locked: z.boolean().optional().describe("When true the thing starts locked."), lit: z.boolean().optional().describe("When true the thing is lit."), switched_on: z.boolean().optional().describe("When true the thing is switched on."), switchable: z.boolean().optional().describe("When true the thing can be switched."), transparent: z.boolean().optional().describe("When true the thing is transparent."), readable: z.boolean().optional().describe("When true the thing can be read."), read_text: z.string().optional().describe("Text revealed when the thing is read."), wearable: z.boolean().optional().describe("When true the thing can be worn."), edible: z.boolean().optional().describe("When true the thing can be eaten."), drinkable: z.boolean().optional().describe("When true the thing can be drunk."), enterable: z.boolean().optional().describe("When true the thing can be entered."), climbable: z.boolean().optional().describe("When true the thing can be climbed.") },
|
|
2374
2313
|
}, async ({ name, kind, description, location, location_type, fixed, openable, lockable, locked, lit, switched_on, switchable, transparent, readable, read_text, wearable, edible, drinkable, enterable, climbable }) => {
|
|
2375
2314
|
requireGM();
|
|
2376
2315
|
const novel = requireNovel();
|
|
@@ -2424,8 +2363,8 @@ server.registerTool("create_thing", {
|
|
|
2424
2363
|
});
|
|
2425
2364
|
server.registerTool("remove_thing", {
|
|
2426
2365
|
title: "Remove Thing",
|
|
2427
|
-
description: "Remove a thing from the world model
|
|
2428
|
-
inputSchema: { name: z.string() },
|
|
2366
|
+
description: "Remove a thing from the world model, persisting the removal (Game Master only). Use when: deleting an object that no longer exists in the fiction. Do NOT use when: removing a room — use remove_room.",
|
|
2367
|
+
inputSchema: { name: z.string().describe("The thing to remove.") },
|
|
2429
2368
|
}, async ({ name }) => {
|
|
2430
2369
|
requireGM();
|
|
2431
2370
|
const novel = requireNovel();
|
|
@@ -2440,12 +2379,8 @@ server.registerTool("remove_thing", {
|
|
|
2440
2379
|
});
|
|
2441
2380
|
server.registerTool("create_exit", {
|
|
2442
2381
|
title: "Create Exit",
|
|
2443
|
-
description: "Create a directional exit between two rooms
|
|
2444
|
-
inputSchema: {
|
|
2445
|
-
direction: z.string(),
|
|
2446
|
-
room_a: z.string(),
|
|
2447
|
-
room_b: z.string(),
|
|
2448
|
-
},
|
|
2382
|
+
description: "Create a directional exit between two rooms, creating the reverse exit implicitly and persisting both (Game Master only). Use when: connecting two locations. Do NOT use when: removing a connection — use remove_exit.",
|
|
2383
|
+
inputSchema: { direction: z.string().describe("The direction from room_a (n/s/e/w/up/down/out or a named direction)."), room_a: z.string().describe("The source room."), room_b: z.string().describe("The destination room.") },
|
|
2449
2384
|
}, async ({ direction, room_a, room_b }) => {
|
|
2450
2385
|
requireGM();
|
|
2451
2386
|
const novel = requireNovel();
|
|
@@ -2467,11 +2402,8 @@ server.registerTool("create_exit", {
|
|
|
2467
2402
|
});
|
|
2468
2403
|
server.registerTool("remove_exit", {
|
|
2469
2404
|
title: "Remove Exit",
|
|
2470
|
-
description: "Remove a directional exit from a room
|
|
2471
|
-
inputSchema: {
|
|
2472
|
-
direction: z.string(),
|
|
2473
|
-
room: z.string(),
|
|
2474
|
-
},
|
|
2405
|
+
description: "Remove a directional exit from a room, persisting the removal (Game Master only). Use when: severing a connection between locations. Do NOT use when: creating a connection — use create_exit.",
|
|
2406
|
+
inputSchema: { direction: z.string().describe("The direction of the exit to remove."), room: z.string().describe("The room the exit belongs to.") },
|
|
2475
2407
|
}, async ({ direction, room: roomName }) => {
|
|
2476
2408
|
requireGM();
|
|
2477
2409
|
const novel = requireNovel();
|
|
@@ -2491,8 +2423,8 @@ server.registerTool("remove_exit", {
|
|
|
2491
2423
|
});
|
|
2492
2424
|
server.registerTool("convert_source", {
|
|
2493
2425
|
title: "Convert Source",
|
|
2494
|
-
description: "Parse hybrid world-model assertions and populate the Novel's world model
|
|
2495
|
-
inputSchema: { source: z.string() },
|
|
2426
|
+
description: "Parse hybrid world-model assertions and populate the Novel's world model (Game Master only, empty world model only). Use when: importing a large room/thing map from prose or structured text. Do NOT use when: adding a single room or thing — use create_room or create_thing.",
|
|
2427
|
+
inputSchema: { source: z.string().describe("Hybrid world-model assertions in prose or structured text.") },
|
|
2496
2428
|
}, async ({ source }) => {
|
|
2497
2429
|
requireGM();
|
|
2498
2430
|
const novel = requireNovel();
|
|
@@ -2608,7 +2540,7 @@ function composeRoomContext(room, novel, world) {
|
|
|
2608
2540
|
server.registerTool("resolve_intent", {
|
|
2609
2541
|
title: "Resolve Intent",
|
|
2610
2542
|
description: "Resolve a natural-language spatial intent against the world model without mutating state. Use when: a player or the AI narrator needs to determine the outcome of a movement or inspection against the world model. Do NOT use when: you are the Game Master inspecting the model directly — use the parser command tool for that.",
|
|
2611
|
-
inputSchema: { intent: z.string() },
|
|
2543
|
+
inputSchema: { intent: z.string().describe("The natural-language spatial intent to resolve (e.g. 'go north', 'open the door').") },
|
|
2612
2544
|
}, async ({ intent }) => {
|
|
2613
2545
|
const badge = getBadge();
|
|
2614
2546
|
if (badge === "player") {
|
|
@@ -2622,12 +2554,8 @@ server.registerTool("resolve_intent", {
|
|
|
2622
2554
|
// --- Combat (GM, auto-advance in ruleset-free mode) ---
|
|
2623
2555
|
server.registerTool("init_combat", {
|
|
2624
2556
|
title: "Initiate Combat",
|
|
2625
|
-
description: "Start a combat encounter
|
|
2626
|
-
inputSchema: {
|
|
2627
|
-
participants: z.array(z.string()),
|
|
2628
|
-
dangers: z.array(z.object({ name: z.string(), ac: z.number().optional(), hp: z.number().optional(), initiative_bonus: z.number().optional() })).optional(),
|
|
2629
|
-
seed: z.string().optional(),
|
|
2630
|
-
},
|
|
2557
|
+
description: "Start a combat encounter with participants and optional dangers, persisting the combat state (Game Master only). Use when: initiating a fight. Do NOT use when: progressing an active fight — use advance_combat.",
|
|
2558
|
+
inputSchema: { participants: z.array(z.string()).describe("Entity identifiers participating in combat."), dangers: z.array(z.object({ name: z.string(), ac: z.number().optional(), hp: z.number().optional(), initiative_bonus: z.number().optional() })).optional().describe("Optional non-entity combatants with armor class, hit points, and initiative bonus."), seed: z.string().optional().describe("Optional deterministic seed for initiative and rolls.") },
|
|
2631
2559
|
}, async ({ participants, dangers, seed }) => {
|
|
2632
2560
|
requireGM();
|
|
2633
2561
|
const novel = requireNovel();
|
|
@@ -2650,7 +2578,7 @@ server.registerTool("init_combat", {
|
|
|
2650
2578
|
});
|
|
2651
2579
|
server.registerTool("advance_combat", {
|
|
2652
2580
|
title: "Advance Combat",
|
|
2653
|
-
description: "Advance to the next turn
|
|
2581
|
+
description: "Advance combat to the next turn, applying round effects and persisting the change (Game Master only). Use when: moving the active fight forward one turn. Do NOT use when: starting or ending combat — use init_combat or end_combat.",
|
|
2654
2582
|
inputSchema: {},
|
|
2655
2583
|
}, async () => {
|
|
2656
2584
|
requireGM();
|
|
@@ -2670,8 +2598,8 @@ server.registerTool("advance_combat", {
|
|
|
2670
2598
|
});
|
|
2671
2599
|
server.registerTool("end_combat", {
|
|
2672
2600
|
title: "End Combat",
|
|
2673
|
-
description: "End the active combat encounter
|
|
2674
|
-
inputSchema: { outcome: z.string().optional() },
|
|
2601
|
+
description: "End the active combat encounter, clearing combat state and persisting it (Game Master only). Use when: the fight is resolved. Do NOT use when: advancing the fight — use advance_combat.",
|
|
2602
|
+
inputSchema: { outcome: z.string().optional().describe("Optional text describing how the combat ended.") },
|
|
2675
2603
|
}, async ({ outcome }) => {
|
|
2676
2604
|
requireGM();
|
|
2677
2605
|
const novel = requireNovel();
|
|
@@ -2683,8 +2611,8 @@ server.registerTool("end_combat", {
|
|
|
2683
2611
|
});
|
|
2684
2612
|
server.registerTool("add_combat_participant", {
|
|
2685
2613
|
title: "Add Combat Participant",
|
|
2686
|
-
description: "Add a participant to active combat
|
|
2687
|
-
inputSchema: { participant_id: z.string() },
|
|
2614
|
+
description: "Add a participant to the active combat, persisting the roster change (Game Master only). Use when: a new combatant joins mid-fight. Do NOT use when: removing a combatant — use remove_combat_participant.",
|
|
2615
|
+
inputSchema: { participant_id: z.string().describe("The entity identifier to add to combat.") },
|
|
2688
2616
|
}, async ({ participant_id }) => {
|
|
2689
2617
|
requireGM();
|
|
2690
2618
|
const novel = requireNovel();
|
|
@@ -2695,8 +2623,8 @@ server.registerTool("add_combat_participant", {
|
|
|
2695
2623
|
});
|
|
2696
2624
|
server.registerTool("remove_combat_participant", {
|
|
2697
2625
|
title: "Remove Combat Participant",
|
|
2698
|
-
description: "Remove a participant from active combat
|
|
2699
|
-
inputSchema: { participant_id: z.string() },
|
|
2626
|
+
description: "Remove a participant from the active combat, persisting the roster change (Game Master only). Use when: a combatant flees or is removed. Do NOT use when: adding a combatant — use add_combat_participant.",
|
|
2627
|
+
inputSchema: { participant_id: z.string().describe("The participant to remove from combat.") },
|
|
2700
2628
|
}, async ({ participant_id }) => {
|
|
2701
2629
|
requireGM();
|
|
2702
2630
|
const novel = requireNovel();
|
|
@@ -2846,28 +2774,28 @@ function advanceSceneTransitionCountdowns(novel) {
|
|
|
2846
2774
|
}
|
|
2847
2775
|
server.registerTool("set_scene_state", {
|
|
2848
2776
|
title: "Set Scene State",
|
|
2849
|
-
description: "Set the scene description and
|
|
2777
|
+
description: "Set the scene description, location, time of day, atmosphere, and scene type, persisting them (Game Master only). Use when: the fiction moves to a new scene. Do NOT use when: setting the narrative directive — use set_narrative_directive.",
|
|
2850
2778
|
inputSchema: {
|
|
2851
|
-
description: z.string(),
|
|
2852
|
-
location: z.string().optional(),
|
|
2853
|
-
time_of_day: z.string().optional(),
|
|
2854
|
-
atmosphere: z.string().optional(),
|
|
2779
|
+
description: z.string().describe("The scene description."),
|
|
2780
|
+
location: z.string().optional().describe("Optional scene location."),
|
|
2781
|
+
time_of_day: z.string().optional().describe("Optional time of day."),
|
|
2782
|
+
atmosphere: z.string().optional().describe("Optional atmosphere."),
|
|
2855
2783
|
// REQ-087 — scene type tagging: scene_type accepts a single tag or an array
|
|
2856
2784
|
// from the canonical catalog (social/exploration/neutral; combat is a
|
|
2857
2785
|
// resolution mode, added automatically on init_combat).
|
|
2858
|
-
scene_type: z.union([z.enum(["combat", "social", "exploration", "neutral"]), z.array(z.enum(["combat", "social", "exploration", "neutral"]))]).optional(),
|
|
2859
|
-
beat: z.enum(BEAT_VALUES).optional(),
|
|
2860
|
-
skip_transition_hook: z.boolean().optional(),
|
|
2786
|
+
scene_type: z.union([z.enum(["combat", "social", "exploration", "neutral"]), z.array(z.enum(["combat", "social", "exploration", "neutral"]))]).optional().describe("A scene-type tag or array of tags from combat/social/exploration/neutral."),
|
|
2787
|
+
beat: z.enum(BEAT_VALUES).optional().describe("Optional story-beat tag."),
|
|
2788
|
+
skip_transition_hook: z.boolean().optional().describe("When true, skip the scene-transition hook."),
|
|
2861
2789
|
// REQ-250 — adventure scene waypoint: heading anchor from the adventure
|
|
2862
2790
|
// structural index; empty/null clears; unknown anchors → [NOT_FOUND].
|
|
2863
|
-
adventure_scene: z.string().nullable().optional(),
|
|
2791
|
+
adventure_scene: z.string().nullable().optional().describe("Optional adventure-scene waypoint anchor; empty or null clears it."),
|
|
2864
2792
|
// REQ-252 — narrative fast-forward: skip intervening time with a bridging
|
|
2865
2793
|
// summary, countdown adjustments, and NPC changes.
|
|
2866
2794
|
fast_forward: z.object({
|
|
2867
|
-
interval: z.string(),
|
|
2868
|
-
changes: z.array(z.object({ npc_id: z.string(), location: z.string().optional(), disposition: z.string().optional(), condition: z.string().optional() })).optional(),
|
|
2869
|
-
skip_countdowns: z.boolean().optional(),
|
|
2870
|
-
}).optional(),
|
|
2795
|
+
interval: z.string().describe("The time interval to skip."),
|
|
2796
|
+
changes: z.array(z.object({ npc_id: z.string().describe("The NPC to change."), location: z.string().optional().describe("New location."), disposition: z.string().optional().describe("New disposition."), condition: z.string().optional().describe("New condition.") })).optional().describe("Optional NPC changes during the skip."),
|
|
2797
|
+
skip_countdowns: z.boolean().optional().describe("When true, do not advance countdowns during the skip."),
|
|
2798
|
+
}).optional().describe("Optional fast-forward: skip intervening time with a bridging summary, countdown adjustments, and NPC changes."),
|
|
2871
2799
|
},
|
|
2872
2800
|
}, async ({ description, location, time_of_day, atmosphere, scene_type, beat, skip_transition_hook, adventure_scene, fast_forward }) => {
|
|
2873
2801
|
requireGM();
|
|
@@ -2998,8 +2926,8 @@ server.registerTool("set_scene_state", {
|
|
|
2998
2926
|
});
|
|
2999
2927
|
server.registerTool("set_narrative_directive", {
|
|
3000
2928
|
title: "Set Narrative Directive",
|
|
3001
|
-
description: "Set overarching narrative directive for the current scene
|
|
3002
|
-
inputSchema: { directive: z.string() },
|
|
2929
|
+
description: "Set the overarching narrative directive for the current scene, persisting it (Game Master only). Use when: steering tone or scene focus. Do NOT use when: setting scene location or description — use set_scene_state.",
|
|
2930
|
+
inputSchema: { directive: z.string().describe("The overarching narrative directive for the current scene.") },
|
|
3003
2931
|
}, async ({ directive }) => {
|
|
3004
2932
|
requireGM();
|
|
3005
2933
|
const novel = requireNovel();
|
|
@@ -3011,15 +2939,15 @@ server.registerTool("set_narrative_directive", {
|
|
|
3011
2939
|
// --- NPCs (GM) ---
|
|
3012
2940
|
server.registerTool("create_npc", {
|
|
3013
2941
|
title: "Create NPC",
|
|
3014
|
-
description: "Create a named NPC with optional description and
|
|
2942
|
+
description: "Create a named NPC with optional description, disposition, location, and goals, persisting it to the Novel (Game Master only). Use when: introducing a non-player character. Do NOT use when: editing an NPC — use update_npc.",
|
|
3015
2943
|
inputSchema: {
|
|
3016
|
-
name: z.string(),
|
|
3017
|
-
description: z.string().optional(),
|
|
3018
|
-
disposition: z.string().optional(),
|
|
3019
|
-
location: z.string().optional(),
|
|
3020
|
-
goals: z.string().optional(),
|
|
2944
|
+
name: z.string().describe("The NPC name."),
|
|
2945
|
+
description: z.string().optional().describe("Optional description."),
|
|
2946
|
+
disposition: z.string().optional().describe("Optional disposition."),
|
|
2947
|
+
location: z.string().optional().describe("Optional location."),
|
|
2948
|
+
goals: z.string().optional().describe("Optional goals."),
|
|
3021
2949
|
// REQ-119 — optional ruleset stat-block reference.
|
|
3022
|
-
ruleset_reference: z.string().optional(),
|
|
2950
|
+
ruleset_reference: z.string().optional().describe("Optional ruleset stat-block reference."),
|
|
3023
2951
|
},
|
|
3024
2952
|
}, async ({ name, description, disposition, location, goals, ruleset_reference }) => {
|
|
3025
2953
|
requireGM();
|
|
@@ -3069,17 +2997,10 @@ server.registerTool("create_npc", {
|
|
|
3069
2997
|
});
|
|
3070
2998
|
server.registerTool("update_npc", {
|
|
3071
2999
|
title: "Update NPC",
|
|
3072
|
-
description: "Update an existing NPC's
|
|
3000
|
+
description: "Update an existing NPC's name, description, disposition, location, or goals, persisting the change (Game Master only). Use when: revising a non-player character. Do NOT use when: creating an NPC — use create_npc.",
|
|
3073
3001
|
// REQ-123 — builder-defined NPC stat fields: fields are builder-determined
|
|
3074
3002
|
// from the ruleset's stat conventions; every field optional except name.
|
|
3075
|
-
inputSchema: {
|
|
3076
|
-
npc_id: z.string(),
|
|
3077
|
-
name: z.string().optional(),
|
|
3078
|
-
description: z.string().optional(),
|
|
3079
|
-
disposition: z.string().optional(),
|
|
3080
|
-
location: z.string().optional(),
|
|
3081
|
-
goals: z.string().optional(),
|
|
3082
|
-
},
|
|
3003
|
+
inputSchema: { npc_id: z.string().describe("The NPC identifier."), name: z.string().optional().describe("Optional new name."), description: z.string().optional().describe("Optional description."), disposition: z.string().optional().describe("Optional disposition."), location: z.string().optional().describe("Optional location."), goals: z.string().optional().describe("Optional goals.") },
|
|
3083
3004
|
}, async ({ npc_id, name, description, disposition, location, goals }) => {
|
|
3084
3005
|
requireGM();
|
|
3085
3006
|
const novel = requireNovel();
|
|
@@ -3103,8 +3024,8 @@ server.registerTool("update_npc", {
|
|
|
3103
3024
|
});
|
|
3104
3025
|
server.registerTool("remove_npc", {
|
|
3105
3026
|
title: "Remove NPC",
|
|
3106
|
-
description: "Remove an NPC from the
|
|
3107
|
-
inputSchema: { npc_id: z.string() },
|
|
3027
|
+
description: "Remove an NPC from the Novel, persisting the removal (Game Master only). Use when: a non-player character leaves the story. Do NOT use when: removing a player entity — use remove_entity.",
|
|
3028
|
+
inputSchema: { npc_id: z.string().describe("The NPC to remove.") },
|
|
3108
3029
|
}, async ({ npc_id }) => {
|
|
3109
3030
|
requireGM();
|
|
3110
3031
|
const novel = requireNovel();
|
|
@@ -3119,26 +3040,26 @@ server.registerTool("remove_npc", {
|
|
|
3119
3040
|
// --- Countdowns (GM) ---
|
|
3120
3041
|
server.registerTool("set_countdown", {
|
|
3121
3042
|
title: "Set Countdown",
|
|
3122
|
-
description: "Set a countdown timer
|
|
3043
|
+
description: "Set a countdown timer with ticks, type, scope, direction, and optional world-model triggers, persisting it (Game Master only). Use when: starting a clock or timer. Do NOT use when: advancing a clock — use advance_countdown.",
|
|
3123
3044
|
inputSchema: {
|
|
3124
|
-
name: z.string(),
|
|
3125
|
-
ticks: z.number().min(1),
|
|
3126
|
-
type: z.enum(["round", "narrative"]).optional(),
|
|
3127
|
-
scope: z.string().optional(),
|
|
3128
|
-
direction: z.string().optional(),
|
|
3129
|
-
on_scene_transition: z.boolean().optional(),
|
|
3045
|
+
name: z.string().describe("The countdown name."),
|
|
3046
|
+
ticks: z.number().min(1).describe("Starting number of ticks (minimum 1)."),
|
|
3047
|
+
type: z.enum(["round", "narrative"]).optional().describe("round or narrative."),
|
|
3048
|
+
scope: z.string().optional().describe("Optional scope name."),
|
|
3049
|
+
direction: z.string().optional().describe("Optional direction: increment or decrement."),
|
|
3050
|
+
on_scene_transition: z.boolean().optional().describe("When true, advance on each scene transition."),
|
|
3130
3051
|
// REQ-329 — world-model coupling triggers (on_room_enter/on_thing_take/
|
|
3131
3052
|
// on_door_open). Any match advances one tick; supplements normal advancement.
|
|
3132
|
-
triggers: z.array(z.string()).optional(),
|
|
3053
|
+
triggers: z.array(z.string()).optional().describe("Optional world-model triggers (on_room_enter/on_thing_take/on_door_open)."),
|
|
3133
3054
|
// REQ-368 — world-model effect coupling applied when the countdown fires.
|
|
3134
3055
|
world_effect: z.object({
|
|
3135
|
-
type: z.enum(["describe", "property", "exit"]),
|
|
3136
|
-
target: z.string(),
|
|
3137
|
-
direction: z.string().optional(),
|
|
3138
|
-
destination: z.string().optional(),
|
|
3139
|
-
property: z.string().optional(),
|
|
3140
|
-
value: z.string().optional(),
|
|
3141
|
-
}).optional(),
|
|
3056
|
+
type: z.enum(["describe", "property", "exit"]).describe("The effect kind."),
|
|
3057
|
+
target: z.string().describe("The effect target."),
|
|
3058
|
+
direction: z.string().optional().describe("Optional direction (for exits)."),
|
|
3059
|
+
destination: z.string().optional().describe("Optional destination (for exits)."),
|
|
3060
|
+
property: z.string().optional().describe("Optional property (for property effects)."),
|
|
3061
|
+
value: z.string().optional().describe("Optional value (for property effects)."),
|
|
3062
|
+
}).optional().describe("Optional world-model effect applied when the countdown fires."),
|
|
3142
3063
|
},
|
|
3143
3064
|
}, async ({ name, ticks, type, scope, direction, on_scene_transition, triggers, world_effect }) => {
|
|
3144
3065
|
requireGM();
|
|
@@ -3152,8 +3073,8 @@ server.registerTool("set_countdown", {
|
|
|
3152
3073
|
});
|
|
3153
3074
|
server.registerTool("advance_countdown", {
|
|
3154
3075
|
title: "Advance Countdown",
|
|
3155
|
-
description: "Advance a countdown timer by one tick
|
|
3156
|
-
inputSchema: { name: z.string() },
|
|
3076
|
+
description: "Advance a countdown timer by one tick, firing it when it reaches its end (Game Master only). Use when: the fiction moves a clock forward. Do NOT use when: creating or removing a clock — use set_countdown or remove_countdown.",
|
|
3077
|
+
inputSchema: { name: z.string().describe("The countdown to advance.") },
|
|
3157
3078
|
}, async ({ name }) => {
|
|
3158
3079
|
requireGM();
|
|
3159
3080
|
const novel = requireNovel();
|
|
@@ -3193,8 +3114,8 @@ server.registerTool("advance_countdown", {
|
|
|
3193
3114
|
});
|
|
3194
3115
|
server.registerTool("remove_countdown", {
|
|
3195
3116
|
title: "Remove Countdown",
|
|
3196
|
-
description: "Remove a countdown timer
|
|
3197
|
-
inputSchema: { name: z.string() },
|
|
3117
|
+
description: "Remove a countdown timer, persisting the removal (Game Master only). Use when: a clock is no longer relevant. Do NOT use when: advancing a clock — use advance_countdown.",
|
|
3118
|
+
inputSchema: { name: z.string().describe("The countdown to remove.") },
|
|
3198
3119
|
}, async ({ name }) => {
|
|
3199
3120
|
requireGM();
|
|
3200
3121
|
const novel = requireNovel();
|
|
@@ -3209,17 +3130,17 @@ server.registerTool("remove_countdown", {
|
|
|
3209
3130
|
// --- Lore (GM) ---
|
|
3210
3131
|
server.registerTool("set_lore_entry", {
|
|
3211
3132
|
title: "Set Lore Entry",
|
|
3212
|
-
description: "
|
|
3133
|
+
description: "Create a lore entry with content, triggers, badge scope, priority, and group for the active Novel (Game Master only). Use when: recording world facts the narrator should recall. Do NOT use when: recording a story beat — use record_story.",
|
|
3213
3134
|
inputSchema: {
|
|
3214
|
-
key: z.string(),
|
|
3215
|
-
content: z.string(),
|
|
3216
|
-
triggers: z.array(z.string()).optional(),
|
|
3217
|
-
badge_scope: z.enum(["game_master", "shared"]).optional(),
|
|
3218
|
-
priority: z.number().optional(),
|
|
3219
|
-
sticky: z.number().optional(),
|
|
3220
|
-
group: z.string().optional(),
|
|
3135
|
+
key: z.string().describe("The lore key."),
|
|
3136
|
+
content: z.string().describe("The lore content."),
|
|
3137
|
+
triggers: z.array(z.string()).optional().describe("Optional recall triggers."),
|
|
3138
|
+
badge_scope: z.enum(["game_master", "shared"]).optional().describe("game_master or shared."),
|
|
3139
|
+
priority: z.number().optional().describe("Optional priority."),
|
|
3140
|
+
sticky: z.number().optional().describe("Optional sticky weight."),
|
|
3141
|
+
group: z.string().optional().describe("Optional group name."),
|
|
3221
3142
|
// REQ-328 — lore-world coupling: world-model target (room/thing/exit ref).
|
|
3222
|
-
world_target: z.string().optional(),
|
|
3143
|
+
world_target: z.string().optional().describe("Optional world-model target reference."),
|
|
3223
3144
|
},
|
|
3224
3145
|
}, async ({ key, content, triggers, badge_scope, priority, sticky, group, world_target }) => {
|
|
3225
3146
|
requireGM();
|
|
@@ -3244,16 +3165,8 @@ server.registerTool("set_lore_entry", {
|
|
|
3244
3165
|
});
|
|
3245
3166
|
server.registerTool("update_lore_entry", {
|
|
3246
3167
|
title: "Update Lore Entry",
|
|
3247
|
-
description: "Update fields of an existing lore entry
|
|
3248
|
-
inputSchema: {
|
|
3249
|
-
key: z.string(),
|
|
3250
|
-
content: z.string().optional(),
|
|
3251
|
-
triggers: z.array(z.string()).optional(),
|
|
3252
|
-
badge_scope: z.enum(["game_master", "shared"]).optional(),
|
|
3253
|
-
priority: z.number().optional(),
|
|
3254
|
-
sticky: z.number().optional(),
|
|
3255
|
-
group: z.string().nullable().optional(),
|
|
3256
|
-
},
|
|
3168
|
+
description: "Update fields of an existing lore entry, persisting the change (Game Master only). Use when: revising a world fact. Do NOT use when: creating a lore entry — use set_lore_entry.",
|
|
3169
|
+
inputSchema: { key: z.string().describe("The lore key to update."), content: z.string().optional().describe("Optional new content."), triggers: z.array(z.string()).optional().describe("Optional recall triggers."), badge_scope: z.enum(["game_master", "shared"]).optional().describe("game_master or shared."), priority: z.number().optional().describe("Optional priority."), sticky: z.number().optional().describe("Optional sticky weight."), group: z.string().nullable().optional().describe("Optional group name, or null to clear.") },
|
|
3257
3170
|
}, async ({ key, content, triggers, badge_scope, priority, sticky, group }) => {
|
|
3258
3171
|
requireGM();
|
|
3259
3172
|
const novel = requireNovel();
|
|
@@ -3284,8 +3197,8 @@ server.registerTool("update_lore_entry", {
|
|
|
3284
3197
|
});
|
|
3285
3198
|
server.registerTool("remove_lore_entry", {
|
|
3286
3199
|
title: "Remove Lore Entry",
|
|
3287
|
-
description: "Remove a lore entry
|
|
3288
|
-
inputSchema: { key: z.string() },
|
|
3200
|
+
description: "Remove a lore entry from the Novel, persisting the removal (Game Master only). Use when: a world fact is obsolete. Do NOT use when: hiding a lore entry without deleting it — use toggle_lore_entry.",
|
|
3201
|
+
inputSchema: { key: z.string().describe("The lore key to remove.") },
|
|
3289
3202
|
}, async ({ key }) => {
|
|
3290
3203
|
requireGM();
|
|
3291
3204
|
const novel = requireNovel();
|
|
@@ -3298,8 +3211,8 @@ server.registerTool("remove_lore_entry", {
|
|
|
3298
3211
|
});
|
|
3299
3212
|
server.registerTool("toggle_lore_entry", {
|
|
3300
3213
|
title: "Toggle Lore Entry",
|
|
3301
|
-
description: "Enable or disable a lore entry
|
|
3302
|
-
inputSchema: { key: z.string() },
|
|
3214
|
+
description: "Enable or disable a lore entry without deleting it, persisting the toggle (Game Master only). Use when: temporarily hiding a world fact. Do NOT use when: deleting a lore entry — use remove_lore_entry.",
|
|
3215
|
+
inputSchema: { key: z.string().describe("The lore key to toggle.") },
|
|
3303
3216
|
}, async ({ key }) => {
|
|
3304
3217
|
requireGM();
|
|
3305
3218
|
const novel = requireNovel();
|
|
@@ -3313,8 +3226,8 @@ server.registerTool("toggle_lore_entry", {
|
|
|
3313
3226
|
});
|
|
3314
3227
|
server.registerTool("set_lore_group", {
|
|
3315
3228
|
title: "Set Lore Group",
|
|
3316
|
-
description: "Assign or remove a lore entry from a named group
|
|
3317
|
-
inputSchema: { key: z.string(), group: z.string().nullable() },
|
|
3229
|
+
description: "Assign or remove a lore entry from a named group, persisting the grouping (Game Master only). Use when: organizing lore by theme or faction. Do NOT use when: creating a lore entry — use set_lore_entry.",
|
|
3230
|
+
inputSchema: { key: z.string().describe("The lore key."), group: z.string().nullable().describe("The group name, or null to remove from its group.") },
|
|
3318
3231
|
}, async ({ key, group }) => {
|
|
3319
3232
|
requireGM();
|
|
3320
3233
|
const novel = requireNovel();
|
|
@@ -3331,7 +3244,7 @@ server.registerTool("set_lore_group", {
|
|
|
3331
3244
|
});
|
|
3332
3245
|
server.registerTool("suggest_lore", {
|
|
3333
3246
|
title: "Suggest Lore",
|
|
3334
|
-
description: "Suggest lore entries from enrichment templates based on current scene
|
|
3247
|
+
description: "Suggest lore entries from enrichment templates based on the current scene, without persisting them (Game Master only). Use when: the GM wants candidate world facts to adopt. Do NOT use when: creating a lore entry directly — use set_lore_entry.",
|
|
3335
3248
|
inputSchema: {},
|
|
3336
3249
|
}, async () => {
|
|
3337
3250
|
requireGM();
|
|
@@ -3344,12 +3257,12 @@ server.registerTool("suggest_lore", {
|
|
|
3344
3257
|
});
|
|
3345
3258
|
server.registerTool("export_lorebook", {
|
|
3346
3259
|
title: "Export Lorebook",
|
|
3347
|
-
description: "Export
|
|
3260
|
+
description: "Export the Novel's lore entries in interchange format for backup or transfer (Game Master only). Use when: moving lore between Novels. Do NOT use when: importing lore — use import_lorebook.",
|
|
3348
3261
|
// REQ-094 — lorebook interchange: lore-only export/import with merge, replace,
|
|
3349
3262
|
// and dry-run modes; round-trip preserves lore metadata (Appendix L).
|
|
3350
3263
|
// REQ-425b — interchange surfaces accept only json/markdown; html is a
|
|
3351
3264
|
// presentation-only format and returns [INVALID_INPUT] enumerating the set.
|
|
3352
|
-
inputSchema: { format: z.string().optional() },
|
|
3265
|
+
inputSchema: { format: z.string().optional().describe("Optional output format.") },
|
|
3353
3266
|
}, async ({ format: fmt }) => {
|
|
3354
3267
|
requireGM();
|
|
3355
3268
|
if (fmt && !INTERCHANGE_FORMATS.includes(fmt)) {
|
|
@@ -3372,11 +3285,8 @@ server.registerTool("export_lorebook", {
|
|
|
3372
3285
|
});
|
|
3373
3286
|
server.registerTool("import_lorebook", {
|
|
3374
3287
|
title: "Import Lorebook",
|
|
3375
|
-
description: "Import lore entries from JSON or Markdown
|
|
3376
|
-
inputSchema: {
|
|
3377
|
-
data: z.string(),
|
|
3378
|
-
mode: z.enum(["dry-run", "merge", "replace"]).optional(),
|
|
3379
|
-
},
|
|
3288
|
+
description: "Import lore entries from JSON or Markdown in dry-run, merge, or replace mode (Game Master only). Use when: loading a lorebook. Do NOT use when: exporting lore — use export_lorebook.",
|
|
3289
|
+
inputSchema: { data: z.string().describe("JSON or Markdown lorebook data."), mode: z.enum(["dry-run", "merge", "replace"]).optional().describe("dry-run, merge, or replace.") },
|
|
3380
3290
|
}, async ({ data, mode }) => {
|
|
3381
3291
|
requireGM();
|
|
3382
3292
|
const novel = requireNovel();
|
|
@@ -3422,8 +3332,8 @@ function conditionCatalogue(novel) {
|
|
|
3422
3332
|
}
|
|
3423
3333
|
server.registerTool("apply_condition", {
|
|
3424
3334
|
title: "Apply Condition",
|
|
3425
|
-
description: "Apply a condition to an entity.",
|
|
3426
|
-
inputSchema: { entity_id: z.string(), condition: z.string(), rounds: z.number().optional() },
|
|
3335
|
+
description: "Apply a condition to an entity, persisting it to the Novel. Use when: an entity gains a mechanical or narrative status. Do NOT use when: clearing a condition — use remove_condition.",
|
|
3336
|
+
inputSchema: { entity_id: z.string().describe("The entity to affect."), condition: z.string().describe("The condition name."), rounds: z.number().optional().describe("Optional duration in rounds.") },
|
|
3427
3337
|
}, async ({ entity_id, condition, rounds }) => {
|
|
3428
3338
|
requireGM();
|
|
3429
3339
|
const novel = requireNovel();
|
|
@@ -3452,8 +3362,8 @@ server.registerTool("apply_condition", {
|
|
|
3452
3362
|
});
|
|
3453
3363
|
server.registerTool("remove_condition", {
|
|
3454
3364
|
title: "Remove Condition",
|
|
3455
|
-
description: "Remove a condition from an entity.",
|
|
3456
|
-
inputSchema: { entity_id: z.string(), condition: z.string() },
|
|
3365
|
+
description: "Remove a condition from an entity, persisting the change. Use when: a status ends or is cured. Do NOT use when: applying a condition — use apply_condition.",
|
|
3366
|
+
inputSchema: { entity_id: z.string().describe("The entity to affect."), condition: z.string().describe("The condition to remove.") },
|
|
3457
3367
|
}, async ({ entity_id, condition }) => {
|
|
3458
3368
|
requireGM();
|
|
3459
3369
|
const novel = requireNovel();
|
|
@@ -3473,8 +3383,8 @@ server.registerTool("remove_condition", {
|
|
|
3473
3383
|
// --- Factions (GM) ---
|
|
3474
3384
|
server.registerTool("create_faction", {
|
|
3475
3385
|
title: "Create Faction",
|
|
3476
|
-
description: "Create a named faction with goals, resources, and a progress clock
|
|
3477
|
-
inputSchema: { name: z.string(), description: z.string().optional(), goals: z.array(z.string()).optional(), resources: z.string().optional(), territory: z.array(z.string()).optional() },
|
|
3386
|
+
description: "Create a named faction with goals, resources, and a progress clock, persisting it (Game Master only). Use when: introducing an organization. Do NOT use when: editing a faction — use update_faction.",
|
|
3387
|
+
inputSchema: { name: z.string().describe("The faction name."), description: z.string().optional().describe("Optional description."), goals: z.array(z.string()).optional().describe("Optional goals."), resources: z.string().optional().describe("Optional resources."), territory: z.array(z.string()).optional().describe("Optional territory names.") },
|
|
3478
3388
|
}, async ({ name, description, goals, resources, territory }) => {
|
|
3479
3389
|
requireGM();
|
|
3480
3390
|
const novel = requireNovel();
|
|
@@ -3492,8 +3402,8 @@ server.registerTool("create_faction", {
|
|
|
3492
3402
|
});
|
|
3493
3403
|
server.registerTool("update_faction", {
|
|
3494
3404
|
title: "Update Faction",
|
|
3495
|
-
description: "Update a faction's fields
|
|
3496
|
-
inputSchema: { faction_id: z.string(), description: z.string().optional(), goals: z.array(z.string()).optional(), resources: z.string().optional(), territory: z.array(z.string()).optional() },
|
|
3405
|
+
description: "Update a faction's fields, persisting the change (Game Master only). Use when: revising an organization. Do NOT use when: creating a faction — use create_faction.",
|
|
3406
|
+
inputSchema: { faction_id: z.string().describe("The faction identifier."), description: z.string().optional().describe("Optional description."), goals: z.array(z.string()).optional().describe("Optional goals."), resources: z.string().optional().describe("Optional resources."), territory: z.array(z.string()).optional().describe("Optional territory names.") },
|
|
3497
3407
|
}, async ({ faction_id, ...fields }) => {
|
|
3498
3408
|
requireGM();
|
|
3499
3409
|
const novel = requireNovel();
|
|
@@ -3506,8 +3416,8 @@ server.registerTool("update_faction", {
|
|
|
3506
3416
|
});
|
|
3507
3417
|
server.registerTool("remove_faction", {
|
|
3508
3418
|
title: "Remove Faction",
|
|
3509
|
-
description: "Remove a faction and its clock
|
|
3510
|
-
inputSchema: { faction_id: z.string() },
|
|
3419
|
+
description: "Remove a faction and its progress clock, persisting the removal (Game Master only). Use when: an organization leaves the story. Do NOT use when: editing a faction — use update_faction.",
|
|
3420
|
+
inputSchema: { faction_id: z.string().describe("The faction to remove.") },
|
|
3511
3421
|
}, async ({ faction_id }) => {
|
|
3512
3422
|
requireGM();
|
|
3513
3423
|
const novel = requireNovel();
|
|
@@ -3523,8 +3433,8 @@ server.registerTool("remove_faction", {
|
|
|
3523
3433
|
// --- Secrets (GM) ---
|
|
3524
3434
|
server.registerTool("set_secret", {
|
|
3525
3435
|
title: "Set Secret",
|
|
3526
|
-
description: "Create a secret lore entry
|
|
3527
|
-
inputSchema: { key: z.string(), content: z.string(), triggers: z.array(z.string()).optional(), badge_scope: z.enum(["game_master", "shared"]).optional(), world_target: z.string().optional() },
|
|
3436
|
+
description: "Create a GM-only secret lore entry that becomes visible to an entity only after reveal_secret (Game Master only). Use when: recording hidden information. Do NOT use when: creating public lore — use set_lore_entry.",
|
|
3437
|
+
inputSchema: { key: z.string().describe("The secret key."), content: z.string().describe("The secret content."), triggers: z.array(z.string()).optional().describe("Optional recall triggers."), badge_scope: z.enum(["game_master", "shared"]).optional().describe("game_master or shared."), world_target: z.string().optional().describe("Optional world-model target.") },
|
|
3528
3438
|
}, async ({ key, content, triggers, badge_scope, world_target }) => {
|
|
3529
3439
|
requireGM();
|
|
3530
3440
|
const novel = requireNovel();
|
|
@@ -3536,8 +3446,8 @@ server.registerTool("set_secret", {
|
|
|
3536
3446
|
});
|
|
3537
3447
|
server.registerTool("reveal_secret", {
|
|
3538
3448
|
title: "Reveal Secret",
|
|
3539
|
-
description: "
|
|
3540
|
-
inputSchema: { key: z.string(), entity_id: z.string() },
|
|
3449
|
+
description: "Reveal a secret to a specific entity, persisting that entity's knowledge (Game Master only). Use when: a character learns hidden information. Do NOT use when: creating a secret — use set_secret.",
|
|
3450
|
+
inputSchema: { key: z.string().describe("The secret to reveal."), entity_id: z.string().describe("The entity to reveal it to.") },
|
|
3541
3451
|
}, async ({ key, entity_id }) => {
|
|
3542
3452
|
requireGM();
|
|
3543
3453
|
const novel = requireNovel();
|
|
@@ -3553,8 +3463,8 @@ server.registerTool("reveal_secret", {
|
|
|
3553
3463
|
});
|
|
3554
3464
|
server.registerTool("get_knowledge", {
|
|
3555
3465
|
title: "Get Knowledge",
|
|
3556
|
-
description: "Return
|
|
3557
|
-
inputSchema: { entity_id: z.string(), key: z.string().optional() },
|
|
3466
|
+
description: "Return which secrets an entity currently knows (Game Master only). Use when: checking what a character has learned. Do NOT use when: revealing a secret — use reveal_secret.",
|
|
3467
|
+
inputSchema: { entity_id: z.string().describe("The entity whose knowledge to read."), key: z.string().optional().describe("Optional secret key filter.") },
|
|
3558
3468
|
}, async ({ entity_id, key }) => {
|
|
3559
3469
|
requireGM();
|
|
3560
3470
|
const novel = requireNovel();
|
|
@@ -3570,8 +3480,8 @@ server.registerTool("get_knowledge", {
|
|
|
3570
3480
|
// --- Relationships (GM) ---
|
|
3571
3481
|
server.registerTool("set_relationship", {
|
|
3572
3482
|
title: "Set Relationship",
|
|
3573
|
-
description: "Set a directed relationship between entities, NPCs, or factions.
|
|
3574
|
-
inputSchema: { entity_a: z.string(), entity_b: z.string(), type: z.enum(["ally", "rival", "neutral", "mentor", "dependent", "suspicious"]), value: z.number().optional(), description: z.string().optional() },
|
|
3483
|
+
description: "Set a directed relationship (ally, rival, neutral, mentor, dependent, suspicious) between entities, NPCs, or factions, persisting it (Game Master only). Use when: recording how two parties relate. Do NOT use when: listing relationships — use get_relationships.",
|
|
3484
|
+
inputSchema: { entity_a: z.string().describe("The source entity."), entity_b: z.string().describe("The target entity."), type: z.enum(["ally", "rival", "neutral", "mentor", "dependent", "suspicious"]).describe("Relationship type."), value: z.number().optional().describe("Optional relationship strength."), description: z.string().optional().describe("Optional description.") },
|
|
3575
3485
|
}, async ({ entity_a, entity_b, type, value, description }) => {
|
|
3576
3486
|
requireGM();
|
|
3577
3487
|
const novel = requireNovel();
|
|
@@ -3581,8 +3491,8 @@ server.registerTool("set_relationship", {
|
|
|
3581
3491
|
});
|
|
3582
3492
|
server.registerTool("get_relationships", {
|
|
3583
3493
|
title: "Get Relationships",
|
|
3584
|
-
description: "Return all
|
|
3585
|
-
inputSchema: { entity_id: z.string() },
|
|
3494
|
+
description: "Return all incoming and outgoing relationships for an entity (Game Master only). Use when: reviewing how a character is connected. Do NOT use when: setting a relationship — use set_relationship.",
|
|
3495
|
+
inputSchema: { entity_id: z.string().describe("The entity whose relationships to list.") },
|
|
3586
3496
|
}, async ({ entity_id }) => {
|
|
3587
3497
|
requireGM();
|
|
3588
3498
|
const novel = requireNovel();
|
|
@@ -3593,8 +3503,8 @@ server.registerTool("get_relationships", {
|
|
|
3593
3503
|
// --- Vows (GM) ---
|
|
3594
3504
|
server.registerTool("set_vow", {
|
|
3595
3505
|
title: "Set Vow",
|
|
3596
|
-
description: "Track a narrative vow, quest, or obligation
|
|
3597
|
-
inputSchema: { name: z.string(), description: z.string(), parties: z.array(z.string()), difficulty: z.enum(["troublesome", "dangerous", "formidable", "extreme", "epic"]), scope: z.enum(["gm", "shared", "faction", "party"]).optional() },
|
|
3506
|
+
description: "Track a narrative vow, quest, or obligation with milestones, persisting it (Game Master only). Use when: the story commits a character to a goal. Do NOT use when: advancing a vow — use mark_milestone.",
|
|
3507
|
+
inputSchema: { name: z.string().describe("The vow name."), description: z.string().describe("The vow description."), parties: z.array(z.string()).describe("Parties bound by the vow."), difficulty: z.enum(["troublesome", "dangerous", "formidable", "extreme", "epic"]).describe("troublesome, dangerous, formidable, extreme, or epic."), scope: z.enum(["gm", "shared", "faction", "party"]).optional().describe("gm, shared, faction, or party.") },
|
|
3598
3508
|
}, async ({ name, description, parties, difficulty, scope }) => {
|
|
3599
3509
|
requireGM();
|
|
3600
3510
|
const novel = requireNovel();
|
|
@@ -3621,8 +3531,8 @@ server.registerTool("set_vow", {
|
|
|
3621
3531
|
});
|
|
3622
3532
|
server.registerTool("mark_milestone", {
|
|
3623
3533
|
title: "Mark Milestone",
|
|
3624
|
-
description: "Advance a vow's progress by one milestone
|
|
3625
|
-
inputSchema: { vow_name: z.string() },
|
|
3534
|
+
description: "Advance a vow's progress by one milestone, persisting it (Game Master only). Use when: the character makes progress. Do NOT use when: closing a vow — use resolve_vow.",
|
|
3535
|
+
inputSchema: { vow_name: z.string().describe("The vow to advance.") },
|
|
3626
3536
|
}, async ({ vow_name }) => {
|
|
3627
3537
|
requireGM();
|
|
3628
3538
|
const novel = requireNovel();
|
|
@@ -3646,8 +3556,8 @@ server.registerTool("mark_milestone", {
|
|
|
3646
3556
|
});
|
|
3647
3557
|
server.registerTool("resolve_vow", {
|
|
3648
3558
|
title: "Resolve Vow",
|
|
3649
|
-
description: "Close a completed vow with outcome and consequences
|
|
3650
|
-
inputSchema: { vow_name: z.string(), outcome: z.string(), consequences: z.string().optional() },
|
|
3559
|
+
description: "Close a completed vow with an outcome and consequences, persisting it (Game Master only). Use when: the goal is achieved. Do NOT use when: abandoning a vow — use forsake_vow.",
|
|
3560
|
+
inputSchema: { vow_name: z.string().describe("The vow to close."), outcome: z.string().describe("The resolution outcome."), consequences: z.string().optional().describe("Optional consequences.") },
|
|
3651
3561
|
}, async ({ vow_name, outcome, consequences }) => {
|
|
3652
3562
|
requireGM();
|
|
3653
3563
|
const novel = requireNovel();
|
|
@@ -3662,8 +3572,8 @@ server.registerTool("resolve_vow", {
|
|
|
3662
3572
|
});
|
|
3663
3573
|
server.registerTool("forsake_vow", {
|
|
3664
3574
|
title: "Forsake Vow",
|
|
3665
|
-
description: "Abandon a vow with a reason
|
|
3666
|
-
inputSchema: { vow_name: z.string(), reason: z.string() },
|
|
3575
|
+
description: "Abandon a vow with a reason, persisting the abandonment (Game Master only). Use when: the character gives up. Do NOT use when: completing a vow — use resolve_vow.",
|
|
3576
|
+
inputSchema: { vow_name: z.string().describe("The vow to abandon."), reason: z.string().describe("The reason for abandoning.") },
|
|
3667
3577
|
}, async ({ vow_name, reason }) => {
|
|
3668
3578
|
requireGM();
|
|
3669
3579
|
const novel = requireNovel();
|
|
@@ -3680,8 +3590,8 @@ server.registerTool("forsake_vow", {
|
|
|
3680
3590
|
// immutable → [RULE_VIOLATION]; existing key → [STATE_CONFLICT]).
|
|
3681
3591
|
server.registerTool("promote_story_to_lore", {
|
|
3682
3592
|
title: "Promote Story to Lore",
|
|
3683
|
-
description: "Promote a story journal entry into a lore entry
|
|
3684
|
-
inputSchema: { index: z.number(), key: z.string().optional() },
|
|
3593
|
+
description: "Promote a story journal entry into a lore entry, persisting it (Game Master only). Use when: a story moment becomes a durable world fact. Do NOT use when: recording a new story beat — use record_story.",
|
|
3594
|
+
inputSchema: { index: z.number().describe("The story journal index to promote."), key: z.string().optional().describe("Optional lore key.") },
|
|
3685
3595
|
}, async ({ index, key }) => {
|
|
3686
3596
|
requireGM();
|
|
3687
3597
|
const novel = requireNovel();
|
|
@@ -3710,8 +3620,8 @@ server.registerTool("promote_story_to_lore", {
|
|
|
3710
3620
|
// --- Story Journal (GM) ---
|
|
3711
3621
|
server.registerTool("record_story", {
|
|
3712
3622
|
title: "Record Story",
|
|
3713
|
-
description: "Record a narrative memory
|
|
3714
|
-
inputSchema: { type: z.enum(["decision", "moment", "revelation", "bond", "consequence"]), entry: z.string() },
|
|
3623
|
+
description: "Record a narrative memory (decision, moment, revelation, bond, or consequence) in the story journal (Game Master only). Use when: capturing a story beat. Do NOT use when: recording a durable world fact — use set_lore_entry.",
|
|
3624
|
+
inputSchema: { type: z.enum(["decision", "moment", "revelation", "bond", "consequence"]).describe("The story entry type."), entry: z.string().describe("The story entry text.") },
|
|
3715
3625
|
}, async ({ type, entry }) => {
|
|
3716
3626
|
requireGM();
|
|
3717
3627
|
const novel = requireNovel();
|
|
@@ -3735,8 +3645,8 @@ server.registerTool("record_story", {
|
|
|
3735
3645
|
});
|
|
3736
3646
|
server.registerTool("update_story", {
|
|
3737
3647
|
title: "Update Story",
|
|
3738
|
-
description: "Edit a story journal entry by index
|
|
3739
|
-
inputSchema: { index: z.number().min(0), type: z.enum(["decision", "moment", "revelation", "bond", "consequence"]).optional(), entry: z.string().optional() },
|
|
3648
|
+
description: "Edit a story journal entry by index; decision and consequence entries are immutable (Game Master only). Use when: correcting a recorded memory. Do NOT use when: deleting an entry — use remove_story.",
|
|
3649
|
+
inputSchema: { index: z.number().min(0).describe("The story entry index."), type: z.enum(["decision", "moment", "revelation", "bond", "consequence"]).optional().describe("Optional new type."), entry: z.string().optional().describe("Optional new entry text.") },
|
|
3740
3650
|
}, async ({ index, type, entry }) => {
|
|
3741
3651
|
requireGM();
|
|
3742
3652
|
const novel = requireNovel();
|
|
@@ -3754,8 +3664,8 @@ server.registerTool("update_story", {
|
|
|
3754
3664
|
});
|
|
3755
3665
|
server.registerTool("remove_story", {
|
|
3756
3666
|
title: "Remove Story",
|
|
3757
|
-
description: "Delete a story journal entry by index
|
|
3758
|
-
inputSchema: { index: z.number().min(0) },
|
|
3667
|
+
description: "Delete a story journal entry by index, persisting the removal (Game Master only). Use when: removing a mistaken memory. Do NOT use when: editing an entry — use update_story.",
|
|
3668
|
+
inputSchema: { index: z.number().min(0).describe("The story entry index to remove.") },
|
|
3759
3669
|
}, async ({ index }) => {
|
|
3760
3670
|
requireGM();
|
|
3761
3671
|
const novel = requireNovel();
|
|
@@ -3767,8 +3677,8 @@ server.registerTool("remove_story", {
|
|
|
3767
3677
|
});
|
|
3768
3678
|
server.registerTool("list_stories", {
|
|
3769
3679
|
title: "List Stories",
|
|
3770
|
-
description: "List story journal entries with optional type filter and pagination
|
|
3771
|
-
inputSchema: { filter: z.enum(["decision", "moment", "revelation", "bond", "consequence"]).optional(), offset: z.number().min(0).optional(), limit: z.number().min(0).optional(), ...detailZod },
|
|
3680
|
+
description: "List story journal entries with an optional type filter and pagination (Game Master only). Use when: reviewing the story so far. Do NOT use when: recording a new entry — use record_story.",
|
|
3681
|
+
inputSchema: { filter: z.enum(["decision", "moment", "revelation", "bond", "consequence"]).optional().describe("Optional type filter."), offset: z.number().min(0).optional().describe("Optional pagination offset."), limit: z.number().min(0).optional().describe("Optional page size."), ...detailZod },
|
|
3772
3682
|
}, async ({ filter, offset, limit, detail }) => {
|
|
3773
3683
|
requireGM();
|
|
3774
3684
|
const novel = requireNovel();
|
|
@@ -3786,8 +3696,8 @@ server.registerTool("list_stories", {
|
|
|
3786
3696
|
// --- Notes ---
|
|
3787
3697
|
server.registerTool("set_note", {
|
|
3788
3698
|
title: "Set Note",
|
|
3789
|
-
description: "Create or update a key-value note
|
|
3790
|
-
inputSchema: { key: z.string(), content: z.string(), badge_scope: z.enum(["game_master", "player", "shared"]).optional() },
|
|
3699
|
+
description: "Create or update a key-value note, badge-scoped to game_master (default), player, or shared. Use when: storing scratch state the caller will reuse. Do NOT use when: recording durable world facts — use set_lore_entry.",
|
|
3700
|
+
inputSchema: { key: z.string().describe("The note key."), content: z.string().describe("The note content."), badge_scope: z.enum(["game_master", "player", "shared"]).optional().describe("game_master, player, or shared.") },
|
|
3791
3701
|
}, async ({ key, content, badge_scope }) => {
|
|
3792
3702
|
requireNotObserver();
|
|
3793
3703
|
const novel = requireNovel();
|
|
@@ -3813,8 +3723,8 @@ server.registerTool("set_note", {
|
|
|
3813
3723
|
});
|
|
3814
3724
|
server.registerTool("remove_note", {
|
|
3815
3725
|
title: "Remove Note",
|
|
3816
|
-
description: "Remove a note by key
|
|
3817
|
-
inputSchema: { key: z.string() },
|
|
3726
|
+
description: "Remove a note by key; the caller's badge must own the note's scope. Use when: discarding scratch state. Do NOT use when: creating a note — use set_note.",
|
|
3727
|
+
inputSchema: { key: z.string().describe("The note key to remove.") },
|
|
3818
3728
|
}, async ({ key }) => {
|
|
3819
3729
|
requireNotObserver();
|
|
3820
3730
|
const novel = requireNovel();
|
|
@@ -3831,7 +3741,7 @@ server.registerTool("remove_note", {
|
|
|
3831
3741
|
});
|
|
3832
3742
|
server.registerTool("list_notes", {
|
|
3833
3743
|
title: "List Notes",
|
|
3834
|
-
description: "List all notes
|
|
3744
|
+
description: "List all notes with a 100-character preview, badge-filtered to the caller's scope. Use when: reviewing stored scratch state. Do NOT use when: setting a note — use set_note.",
|
|
3835
3745
|
inputSchema: {},
|
|
3836
3746
|
}, async () => {
|
|
3837
3747
|
const novel = requireNovel();
|
|
@@ -3843,8 +3753,8 @@ server.registerTool("list_notes", {
|
|
|
3843
3753
|
// --- Server Notes (GM) ---
|
|
3844
3754
|
server.registerTool("set_server_note", {
|
|
3845
3755
|
title: "Set Server Note",
|
|
3846
|
-
description: "Create or update a server-level note
|
|
3847
|
-
inputSchema: { key: z.string(), content: z.string(), narrative_tag: z.enum(["campaign_bible", "house_rules", "lore_seed", "session_reminder"]).optional() },
|
|
3756
|
+
description: "Create or update a server-level note that survives Novels and rebuilds (Game Master only). Use when: storing cross-Novel scratch state. Do NOT use when: storing Novel-scoped notes — use set_note.",
|
|
3757
|
+
inputSchema: { key: z.string().describe("The note key."), content: z.string().describe("The note content."), narrative_tag: z.enum(["campaign_bible", "house_rules", "lore_seed", "session_reminder"]).optional().describe("Optional narrative tag: campaign_bible, house_rules, lore_seed, or session_reminder.") },
|
|
3848
3758
|
}, async ({ key, content, narrative_tag }) => {
|
|
3849
3759
|
requireGM();
|
|
3850
3760
|
state.serverNotes.set(key, { content, narrative_tag });
|
|
@@ -3853,8 +3763,8 @@ server.registerTool("set_server_note", {
|
|
|
3853
3763
|
});
|
|
3854
3764
|
server.registerTool("remove_server_note", {
|
|
3855
3765
|
title: "Remove Server Note",
|
|
3856
|
-
description: "Remove a server-level note
|
|
3857
|
-
inputSchema: { key: z.string() },
|
|
3766
|
+
description: "Remove a server-level note, persisting the removal (Game Master only). Use when: discarding cross-Novel scratch state. Do NOT use when: creating a server note — use set_server_note.",
|
|
3767
|
+
inputSchema: { key: z.string().describe("The server note key to remove.") },
|
|
3858
3768
|
}, async ({ key }) => {
|
|
3859
3769
|
requireGM();
|
|
3860
3770
|
if (!state.serverNotes.has(key))
|
|
@@ -3865,7 +3775,7 @@ server.registerTool("remove_server_note", {
|
|
|
3865
3775
|
});
|
|
3866
3776
|
server.registerTool("list_server_notes", {
|
|
3867
3777
|
title: "List Server Notes",
|
|
3868
|
-
description: "List all server-level notes
|
|
3778
|
+
description: "List all server-level notes (Game Master only). Use when: reviewing cross-Novel scratch state. Do NOT use when: listing Novel-scoped notes — use list_notes.",
|
|
3869
3779
|
inputSchema: {},
|
|
3870
3780
|
}, async () => {
|
|
3871
3781
|
requireGM();
|
|
@@ -3875,8 +3785,8 @@ server.registerTool("list_server_notes", {
|
|
|
3875
3785
|
// --- Pause/Resume (GM) ---
|
|
3876
3786
|
server.registerTool("set_pause_context", {
|
|
3877
3787
|
title: "Set Pause Context",
|
|
3878
|
-
description: "Save GM context for session resumption
|
|
3879
|
-
inputSchema: { current_scene: z.string().optional(), immediate_situation: z.string().optional(), pending_player_action: z.string().optional(), short_term_plans: z.string().optional(), long_term_plans: z.string().optional(), player_goals: z.string().optional() },
|
|
3788
|
+
description: "Save GM context for session resumption, persisting it (Game Master only). Use when: ending a session with notes for the next. Do NOT use when: reading saved context — use get_pause_context.",
|
|
3789
|
+
inputSchema: { current_scene: z.string().optional().describe("Optional current-scene summary."), immediate_situation: z.string().optional().describe("Optional immediate situation."), pending_player_action: z.string().optional().describe("Optional pending player action."), short_term_plans: z.string().optional().describe("Optional short-term plans."), long_term_plans: z.string().optional().describe("Optional long-term plans."), player_goals: z.string().optional().describe("Optional player goals.") },
|
|
3880
3790
|
}, async (fields) => {
|
|
3881
3791
|
requireGM();
|
|
3882
3792
|
const novel = requireNovel();
|
|
@@ -3899,7 +3809,7 @@ server.registerTool("set_pause_context", {
|
|
|
3899
3809
|
});
|
|
3900
3810
|
server.registerTool("get_pause_context", {
|
|
3901
3811
|
title: "Get Pause Context",
|
|
3902
|
-
description: "Return the saved GM context plus Novel state summary for session resumption.",
|
|
3812
|
+
description: "Return the saved GM context plus a Novel state summary for session resumption. Use when: resuming after a break. Do NOT use when: saving context — use set_pause_context.",
|
|
3903
3813
|
inputSchema: {},
|
|
3904
3814
|
}, async () => {
|
|
3905
3815
|
const novel = requireNovel();
|
|
@@ -3915,8 +3825,8 @@ server.registerTool("get_pause_context", {
|
|
|
3915
3825
|
// --- Checkpoints (GM) ---
|
|
3916
3826
|
server.registerTool("set_checkpoint", {
|
|
3917
3827
|
title: "Set Checkpoint",
|
|
3918
|
-
description: "Save a named checkpoint of the full Novel state
|
|
3919
|
-
inputSchema: { label: z.string() },
|
|
3828
|
+
description: "Save a named checkpoint of the full Novel state, persisting it (Game Master only). Use when: marking a returnable point in the story. Do NOT use when: restoring a checkpoint — use restore_checkpoint.",
|
|
3829
|
+
inputSchema: { label: z.string().describe("The checkpoint label.") },
|
|
3920
3830
|
}, async ({ label }) => {
|
|
3921
3831
|
requireGM();
|
|
3922
3832
|
const novel = requireNovel();
|
|
@@ -3926,7 +3836,7 @@ server.registerTool("set_checkpoint", {
|
|
|
3926
3836
|
});
|
|
3927
3837
|
server.registerTool("list_checkpoints", {
|
|
3928
3838
|
title: "List Checkpoints",
|
|
3929
|
-
description: "List all checkpoints for the active Novel
|
|
3839
|
+
description: "List all checkpoints for the active Novel (Game Master only). Use when: reviewing available return points. Do NOT use when: creating a checkpoint — use set_checkpoint.",
|
|
3930
3840
|
inputSchema: {},
|
|
3931
3841
|
}, async () => {
|
|
3932
3842
|
requireGM();
|
|
@@ -3935,8 +3845,8 @@ server.registerTool("list_checkpoints", {
|
|
|
3935
3845
|
});
|
|
3936
3846
|
server.registerTool("restore_checkpoint", {
|
|
3937
3847
|
title: "Restore Checkpoint",
|
|
3938
|
-
description: "Restore a checkpoint
|
|
3939
|
-
inputSchema: { label: z.string() },
|
|
3848
|
+
description: "Restore a checkpoint after a confirmation workflow, replacing the Novel state (Game Master only). Use when: returning to a prior point. Do NOT use when: saving a checkpoint — use set_checkpoint.",
|
|
3849
|
+
inputSchema: { label: z.string().describe("The checkpoint to restore.") },
|
|
3940
3850
|
}, async ({ label }) => {
|
|
3941
3851
|
requireGM();
|
|
3942
3852
|
const novel = requireNovel();
|
|
@@ -3957,8 +3867,8 @@ Options: yes, cancel`);
|
|
|
3957
3867
|
});
|
|
3958
3868
|
server.registerTool("remove_checkpoint", {
|
|
3959
3869
|
title: "Remove Checkpoint",
|
|
3960
|
-
description: "Remove a named checkpoint
|
|
3961
|
-
inputSchema: { label: z.string() },
|
|
3870
|
+
description: "Remove a named checkpoint, persisting the removal (Game Master only). Use when: discarding a return point. Do NOT use when: restoring a checkpoint — use restore_checkpoint.",
|
|
3871
|
+
inputSchema: { label: z.string().describe("The checkpoint to remove.") },
|
|
3962
3872
|
}, async ({ label }) => {
|
|
3963
3873
|
requireGM();
|
|
3964
3874
|
const novel = requireNovel();
|
|
@@ -3972,8 +3882,8 @@ server.registerTool("remove_checkpoint", {
|
|
|
3972
3882
|
// --- Novel Lifecycle additions (GM) ---
|
|
3973
3883
|
server.registerTool("rename_novel", {
|
|
3974
3884
|
title: "Rename Novel",
|
|
3975
|
-
description: "Rename the active Novel on disk
|
|
3976
|
-
inputSchema: { new_slug: z.string() },
|
|
3885
|
+
description: "Rename the active Novel on disk, persisting the change (Game Master only). Use when: correcting the Novel's name. Do NOT use when: switching to another Novel — use switch_novel.",
|
|
3886
|
+
inputSchema: { new_slug: z.string().describe("The new Novel slug.") },
|
|
3977
3887
|
}, async ({ new_slug }) => {
|
|
3978
3888
|
requireGM();
|
|
3979
3889
|
const novel = requireNovel();
|
|
@@ -3993,8 +3903,8 @@ server.registerTool("rename_novel", {
|
|
|
3993
3903
|
// description, surfaced in novel_info and badge_briefing.
|
|
3994
3904
|
server.registerTool("update_novel_description", {
|
|
3995
3905
|
title: "Update Novel Description",
|
|
3996
|
-
description: "Set or replace the active Novel's description
|
|
3997
|
-
inputSchema: { description: z.string() },
|
|
3906
|
+
description: "Set or replace the active Novel's description; an empty string clears it (Game Master only). Use when: summarizing the campaign premise. Do NOT use when: setting the genre — use set_genre.",
|
|
3907
|
+
inputSchema: { description: z.string().describe("The new Novel description; empty clears it.") },
|
|
3998
3908
|
}, async ({ description }) => {
|
|
3999
3909
|
requireGM();
|
|
4000
3910
|
const novel = requireNovel();
|
|
@@ -4005,8 +3915,8 @@ server.registerTool("update_novel_description", {
|
|
|
4005
3915
|
});
|
|
4006
3916
|
server.registerTool("list_novels", {
|
|
4007
3917
|
title: "List Novels",
|
|
4008
|
-
description: "List all Novels on disk with metadata
|
|
4009
|
-
inputSchema: { ...detailZod, filter: z.enum(["active", "archived", "all"]).optional() },
|
|
3918
|
+
description: "List all Novels on disk with metadata, always callable. Use when: discovering available save files. Do NOT use when: inspecting one Novel — use novel_info.",
|
|
3919
|
+
inputSchema: { ...detailZod, filter: z.enum(["active", "archived", "all"]).optional().describe("Optional filter: active, archived, or all.") },
|
|
4010
3920
|
}, async ({ detail, filter }) => {
|
|
4011
3921
|
const arch = state.archivedNovels();
|
|
4012
3922
|
const archivedSlugs = new Set(arch.map((a) => a.slug));
|
|
@@ -4025,8 +3935,8 @@ server.registerTool("list_novels", {
|
|
|
4025
3935
|
// REQ-334 — archive/unarchive Novel (Game Master only).
|
|
4026
3936
|
server.registerTool("archive_novel", {
|
|
4027
3937
|
title: "Archive Novel",
|
|
4028
|
-
description: "Move a Novel to the long-term
|
|
4029
|
-
inputSchema: { slug: z.string() },
|
|
3938
|
+
description: "Move a Novel to the long-term read-only archive (Game Master only). Use when: retiring a finished campaign. Do NOT use when: restoring an archived Novel — use unarchive_novel.",
|
|
3939
|
+
inputSchema: { slug: z.string().describe("The Novel slug to archive.") },
|
|
4030
3940
|
}, async ({ slug }) => {
|
|
4031
3941
|
requireGM();
|
|
4032
3942
|
if (state.activeNovelId === slug)
|
|
@@ -4037,8 +3947,8 @@ server.registerTool("archive_novel", {
|
|
|
4037
3947
|
});
|
|
4038
3948
|
server.registerTool("unarchive_novel", {
|
|
4039
3949
|
title: "Unarchive Novel",
|
|
4040
|
-
description: "Restore an archived Novel to active status
|
|
4041
|
-
inputSchema: { slug: z.string() },
|
|
3950
|
+
description: "Restore an archived Novel to active status (Game Master only). Use when: reviving a retired campaign. Do NOT use when: archiving a Novel — use archive_novel.",
|
|
3951
|
+
inputSchema: { slug: z.string().describe("The Novel slug to restore.") },
|
|
4042
3952
|
}, async ({ slug }) => {
|
|
4043
3953
|
requireGM();
|
|
4044
3954
|
const novel = state.unarchiveNovel(slug);
|
|
@@ -4047,8 +3957,8 @@ server.registerTool("unarchive_novel", {
|
|
|
4047
3957
|
});
|
|
4048
3958
|
server.registerTool("novel_info", {
|
|
4049
3959
|
title: "Novel Info",
|
|
4050
|
-
description: "Return extended metadata for a Novel
|
|
4051
|
-
inputSchema: { slug: z.string().optional() },
|
|
3960
|
+
description: "Return extended metadata for a Novel, always callable. Use when: inspecting a Novel's settings and stats. Do NOT use when: listing all Novels — use list_novels.",
|
|
3961
|
+
inputSchema: { slug: z.string().optional().describe("Optional Novel slug; defaults to the active Novel.") },
|
|
4052
3962
|
}, async ({ slug }) => {
|
|
4053
3963
|
const novel = slug ? state.novels.get(slug) : state.activeNovel;
|
|
4054
3964
|
if (!novel)
|
|
@@ -4068,8 +3978,8 @@ server.registerTool("novel_info", {
|
|
|
4068
3978
|
const GENRE_CATALOG = ["noir", "high_fantasy", "sword_and_sorcery", "sci_fi_horror", "cosmic_horror", "historical", "western", "modern", "cyberpunk"];
|
|
4069
3979
|
server.registerTool("set_genre", {
|
|
4070
3980
|
title: "Set Genre",
|
|
4071
|
-
description: "Set the active Novel's genre tag
|
|
4072
|
-
inputSchema: { genre: z.string() },
|
|
3981
|
+
description: "Set the active Novel's genre tag from the fixed catalog (noir, high_fantasy, sword_and_sorcery, sci_fi_horror, cosmic_horror, historical, western, modern, cyberpunk) (Game Master only). Use when: declaring the campaign's genre. Do NOT use when: setting the Novel description — use update_novel_description.",
|
|
3982
|
+
inputSchema: { genre: z.string().describe("The genre tag from the fixed catalog.") },
|
|
4073
3983
|
}, async ({ genre }) => {
|
|
4074
3984
|
requireGM();
|
|
4075
3985
|
const novel = requireNovel();
|
|
@@ -4083,8 +3993,8 @@ server.registerTool("set_genre", {
|
|
|
4083
3993
|
});
|
|
4084
3994
|
server.registerTool("clone_novel", {
|
|
4085
3995
|
title: "Clone Novel",
|
|
4086
|
-
description: "Create an independent copy of a Novel
|
|
4087
|
-
inputSchema: { source_slug: z.string(), new_name: z.string(), trim_audit_sessions: z.number().min(0).optional() },
|
|
3996
|
+
description: "Create an independent copy of a Novel, persisting the copy (Game Master only). Use when: branching the story or testing an alternative. Do NOT use when: renaming a Novel — use rename_novel.",
|
|
3997
|
+
inputSchema: { source_slug: z.string().describe("The Novel to copy."), new_name: z.string().describe("The name for the copy."), trim_audit_sessions: z.number().min(0).optional().describe("Optional number of recent audit sessions to keep.") },
|
|
4088
3998
|
}, async ({ source_slug, new_name }) => {
|
|
4089
3999
|
requireGM();
|
|
4090
4000
|
const source = state.novels.get(source_slug);
|
|
@@ -4104,11 +4014,11 @@ server.registerTool("clone_novel", {
|
|
|
4104
4014
|
// --- Entity Management ---
|
|
4105
4015
|
server.registerTool("remove_entity", {
|
|
4106
4016
|
title: "Remove Entity",
|
|
4107
|
-
description: "Remove an entity from the active Novel
|
|
4017
|
+
description: "Remove an entity from the active Novel, persisting the removal (Game Master only). Use when: a character permanently leaves. Do NOT use when: removing an NPC — use remove_npc.",
|
|
4108
4018
|
// REQ-176 — entity removal: clears the active-entity field when the removed
|
|
4109
4019
|
// entity was active; party://current excludes removed entities; roster
|
|
4110
4020
|
// baseline unaffected (re-import creates a fresh copy). Player → [FORBIDDEN].
|
|
4111
|
-
inputSchema: { entity_id: z.string() },
|
|
4021
|
+
inputSchema: { entity_id: z.string().describe("The entity to remove.") },
|
|
4112
4022
|
}, async ({ entity_id }) => {
|
|
4113
4023
|
requireGM();
|
|
4114
4024
|
const novel = requireNovel();
|
|
@@ -4122,11 +4032,11 @@ server.registerTool("remove_entity", {
|
|
|
4122
4032
|
});
|
|
4123
4033
|
server.registerTool("remove_roster_character", {
|
|
4124
4034
|
title: "Remove Roster Character",
|
|
4125
|
-
description: "Remove a character from the roster
|
|
4035
|
+
description: "Remove a character from the roster, persisting the removal (Game Master only). Use when: discarding a staged character. Do NOT use when: listing roster characters — use list_roster_characters.",
|
|
4126
4036
|
// REQ-177 — roster entity removal: removes from roster:// only; Novel copies
|
|
4127
4037
|
// survive independently; Player → [FORBIDDEN]; absent → [NOT_FOUND] with
|
|
4128
4038
|
// valid roster IDs enumerated.
|
|
4129
|
-
inputSchema: { roster_id: z.string() },
|
|
4039
|
+
inputSchema: { roster_id: z.string().describe("The roster character to remove.") },
|
|
4130
4040
|
}, async ({ roster_id }) => {
|
|
4131
4041
|
requireGM();
|
|
4132
4042
|
if (!state.roster.has(roster_id))
|
|
@@ -4137,7 +4047,7 @@ server.registerTool("remove_roster_character", {
|
|
|
4137
4047
|
});
|
|
4138
4048
|
server.registerTool("list_roster_characters", {
|
|
4139
4049
|
title: "List Roster Characters",
|
|
4140
|
-
description: "List all characters in the roster.",
|
|
4050
|
+
description: "List all characters in the persistent roster. Use when: discovering staged characters to import. Do NOT use when: importing a roster character — use import_character.",
|
|
4141
4051
|
// REQ-178 — roster listing: any-badge, structured (id/name), empty-state
|
|
4142
4052
|
// marker when the roster is empty; novel_setup sources its list from here.
|
|
4143
4053
|
inputSchema: {},
|
|
@@ -4150,7 +4060,7 @@ server.registerTool("list_roster_characters", {
|
|
|
4150
4060
|
// --- Special tools ---
|
|
4151
4061
|
server.registerTool("toggle_action_patterns", {
|
|
4152
4062
|
title: "Toggle Action Patterns",
|
|
4153
|
-
description: "Toggle enrich-derived action patterns on or off for the active Novel
|
|
4063
|
+
description: "Toggle enrich-derived action patterns on or off for the active Novel (Game Master only). Use when: enabling or disabling suggested actions. Do NOT use when: setting autonomy — use set_autonomy.",
|
|
4154
4064
|
// REQ-115 — action pattern activation: flips the Novel-scoped boolean; when
|
|
4155
4065
|
// enabled, synthesis patterns supplement suggest_actions; pure-resolution.
|
|
4156
4066
|
inputSchema: {},
|
|
@@ -4163,13 +4073,8 @@ server.registerTool("toggle_action_patterns", {
|
|
|
4163
4073
|
});
|
|
4164
4074
|
server.registerTool("present_choices", {
|
|
4165
4075
|
title: "Present Choices",
|
|
4166
|
-
description: "Present structured choice prompts to the player
|
|
4167
|
-
inputSchema: {
|
|
4168
|
-
prompt: z.string(),
|
|
4169
|
-
choices: z.array(z.object({ id: z.string(), label: z.string(), description: z.string().optional() })),
|
|
4170
|
-
allow_freeform: z.boolean().optional(),
|
|
4171
|
-
context: z.record(z.string(), z.any()).optional(),
|
|
4172
|
-
},
|
|
4076
|
+
description: "Present structured choice prompts to the player, resolved later via respond (Game Master only). Use when: offering the player a decision. Do NOT use when: resolving an open decision — use respond.",
|
|
4077
|
+
inputSchema: { prompt: z.string().describe("The choice prompt."), choices: z.array(z.object({ id: z.string(), label: z.string(), description: z.string().optional() })).describe("The list of choices."), allow_freeform: z.boolean().optional().describe("When true, allow a free-form response."), context: z.record(z.string(), z.any()).optional().describe("Optional context for the choice.") },
|
|
4173
4078
|
}, async ({ prompt, choices, allow_freeform, context }) => {
|
|
4174
4079
|
requireGM();
|
|
4175
4080
|
const novel = requireNovel();
|
|
@@ -4188,8 +4093,8 @@ server.registerTool("present_choices", {
|
|
|
4188
4093
|
});
|
|
4189
4094
|
server.registerTool("ask_oracle", {
|
|
4190
4095
|
title: "Ask Oracle",
|
|
4191
|
-
description: "Resolve uncertainty with a d100 roll against the Ask-the-Oracle ladder
|
|
4192
|
-
inputSchema: { question: z.string(), likelihood: z.enum(["almost_certain", "likely", "50_50", "unlikely", "small_chance"]).optional(), seed: z.string().optional() },
|
|
4096
|
+
description: "Resolve uncertainty with a deterministic d100 roll against the Ask-the-Oracle ladder (almost_certain, likely, 50_50, unlikely, small_chance), callable by Player and Game Master. Use when: the rules or fiction leave an outcome open. Do NOT use when: rolling on a fixed table — use roll_on_table.",
|
|
4097
|
+
inputSchema: { question: z.string().describe("The question to resolve."), likelihood: z.enum(["almost_certain", "likely", "50_50", "unlikely", "small_chance"]).optional().describe("almost_certain, likely, 50_50, unlikely, or small_chance."), seed: z.string().optional().describe("Optional deterministic seed.") },
|
|
4193
4098
|
}, async ({ question, likelihood, seed }) => {
|
|
4194
4099
|
requireNotObserver();
|
|
4195
4100
|
const novel = requireNovel();
|
|
@@ -4329,18 +4234,18 @@ function normalizeSceneTypeState(raw) {
|
|
|
4329
4234
|
// --- Guidance (GM) ---
|
|
4330
4235
|
server.registerTool("set_verbosity", {
|
|
4331
4236
|
title: "Set Output Verbosity",
|
|
4332
|
-
description: "Set the session output verbosity
|
|
4237
|
+
description: "Set the session output verbosity to normal (full entries) or terse (minimum mechanical content), callable by both badges. Use when: the caller wants shorter or fuller replies. Do NOT use when: setting briefing section order — use set_briefing_order.",
|
|
4333
4238
|
// REQ-253 — tool-output verbosity control: session-scoped mode, discarded on
|
|
4334
4239
|
// connection close; reported in spec_health.
|
|
4335
|
-
inputSchema: { mode: z.enum(["normal", "terse"]) },
|
|
4240
|
+
inputSchema: { mode: z.enum(["normal", "terse"]).describe("normal or terse.") },
|
|
4336
4241
|
}, async ({ mode }) => {
|
|
4337
4242
|
outputVerbosity = mode;
|
|
4338
4243
|
return ok(`Output verbosity set to '${mode}'.`);
|
|
4339
4244
|
});
|
|
4340
4245
|
server.registerTool("set_briefing_order", {
|
|
4341
4246
|
title: "Set Briefing Order",
|
|
4342
|
-
description: "Reorder sections of badge_briefing
|
|
4343
|
-
inputSchema: { sections: z.array(z.string()) },
|
|
4247
|
+
description: "Reorder sections of badge_briefing, persisting the order (Game Master only). Use when: customizing what the briefing emphasizes. Do NOT use when: setting verbosity — use set_verbosity.",
|
|
4248
|
+
inputSchema: { sections: z.array(z.string()).describe("The ordered list of briefing sections.") },
|
|
4344
4249
|
}, async ({ sections }) => {
|
|
4345
4250
|
requireGM();
|
|
4346
4251
|
const novel = requireNovel();
|
|
@@ -4359,11 +4264,11 @@ server.registerTool("set_briefing_order", {
|
|
|
4359
4264
|
});
|
|
4360
4265
|
server.registerTool("compress_audit", {
|
|
4361
4266
|
title: "Compress Audit Log",
|
|
4362
|
-
description: "Return a Markdown prompt compressing recent audit entries
|
|
4267
|
+
description: "Return a Markdown prompt compressing recent audit entries, callable by both badges. Use when: the caller needs a compact history. Do NOT use when: permanently compacting the audit log — use compact_audit_log.",
|
|
4363
4268
|
// REQ-086 — audit compression: header line + one line per entry in
|
|
4364
4269
|
// `[timestamp] [badge] tool_name — output_prefix` (or [BOUNDARY_VIOLATION]);
|
|
4365
4270
|
// pure-generation, badge-filtered; max_entries ≤ 0 → [INVALID_INPUT].
|
|
4366
|
-
inputSchema: { max_entries: z.number().optional() },
|
|
4271
|
+
inputSchema: { max_entries: z.number().optional().describe("Optional maximum number of entries.") },
|
|
4367
4272
|
}, async ({ max_entries }) => {
|
|
4368
4273
|
const novel = requireNovel();
|
|
4369
4274
|
const max = max_entries ?? 20;
|
|
@@ -4384,8 +4289,8 @@ server.registerTool("compress_audit", {
|
|
|
4384
4289
|
// compact_audit_log: legacy alias for compress_audit (REQ-086).
|
|
4385
4290
|
server.registerTool("compact_audit_log", {
|
|
4386
4291
|
title: "Compact Audit Log",
|
|
4387
|
-
description: "Alias for compress_audit.",
|
|
4388
|
-
inputSchema: { max_entries: z.number().optional() },
|
|
4292
|
+
description: "Alias for compress_audit: permanently compacts the audit log. Use when: reducing stored audit size. Do NOT use when: generating a compression prompt — use compress_audit.",
|
|
4293
|
+
inputSchema: { max_entries: z.number().optional().describe("Optional maximum number of entries.") },
|
|
4389
4294
|
}, async ({ max_entries }) => {
|
|
4390
4295
|
const novel = requireNovel();
|
|
4391
4296
|
const max = max_entries ?? 20;
|
|
@@ -4422,14 +4327,11 @@ function assessGenerationGuard(input) {
|
|
|
4422
4327
|
// (default otherwise), or both. No Novel is required for the codex target.
|
|
4423
4328
|
server.registerTool("generate_adventure", {
|
|
4424
4329
|
title: "Generate Adventure",
|
|
4425
|
-
description: "Generate an adventure scaffold from a premise
|
|
4330
|
+
description: "Generate an adventure scaffold from a premise, targeting the Novel, the codex, or both (Game Master only). Use when: the GM wants a new adventure outline. Do NOT use when: generating a single scene — use generate_encounter.",
|
|
4426
4331
|
// REQ-132 — adventure generation lifecycle: transient Novel-scoped artifact
|
|
4427
4332
|
// surfaced at adventure://generated/<anchor>, replaced on regeneration,
|
|
4428
4333
|
// discarded by end_novel, never persisted to TTRPG_ADVENTURE.
|
|
4429
|
-
inputSchema: {
|
|
4430
|
-
premise: z.string(),
|
|
4431
|
-
target: z.enum(["novel", "codex", "both"]).optional(),
|
|
4432
|
-
},
|
|
4334
|
+
inputSchema: { premise: z.string().describe("The adventure premise."), target: z.enum(["novel", "codex", "both"]).optional().describe("novel, codex, or both.") },
|
|
4433
4335
|
}, async ({ premise, target }) => {
|
|
4434
4336
|
requireGM();
|
|
4435
4337
|
const novel = state.activeNovel;
|
|
@@ -4525,8 +4427,8 @@ server.registerTool("generate_adventure", {
|
|
|
4525
4427
|
// undo target. Player badge → [FORBIDDEN].
|
|
4526
4428
|
server.registerTool("generate_encounter", {
|
|
4527
4429
|
title: "Generate Encounter",
|
|
4528
|
-
description: "Generate a scene
|
|
4529
|
-
inputSchema: { context: z.string() },
|
|
4430
|
+
description: "Generate a scene, NPC, and lore entry from context (Game Master only). Use when: the GM wants an encounter on demand. Do NOT use when: generating a full adventure — use generate_adventure.",
|
|
4431
|
+
inputSchema: { context: z.string().describe("The scene context to generate from.") },
|
|
4530
4432
|
}, async ({ context }) => {
|
|
4531
4433
|
requireGM();
|
|
4532
4434
|
const novel = requireNovel();
|
|
@@ -4590,8 +4492,8 @@ server.registerTool("generate_encounter", {
|
|
|
4590
4492
|
});
|
|
4591
4493
|
server.registerTool("load_adventure", {
|
|
4592
4494
|
title: "Load Adventure",
|
|
4593
|
-
description: "Load an adventure module
|
|
4594
|
-
inputSchema: { slug: z.string() },
|
|
4495
|
+
description: "Load an adventure module, persisting it as the active adventure (Game Master only). Use when: starting a prepared adventure. Do NOT use when: listing adventures — use list_adventures.",
|
|
4496
|
+
inputSchema: { slug: z.string().describe("The adventure module slug to load.") },
|
|
4595
4497
|
}, async ({ slug }) => {
|
|
4596
4498
|
requireGM();
|
|
4597
4499
|
const novel = requireNovel();
|
|
@@ -4650,8 +4552,8 @@ server.registerTool("load_adventure", {
|
|
|
4650
4552
|
// empty-state when no modules; badge-filtered.
|
|
4651
4553
|
server.registerTool("list_adventures", {
|
|
4652
4554
|
title: "List Adventures",
|
|
4653
|
-
description: "List adventure modules with metadata
|
|
4654
|
-
inputSchema: { filter: z.string().optional() },
|
|
4555
|
+
description: "List adventure modules with metadata, always callable. Use when: discovering available adventures. Do NOT use when: loading an adventure — use load_adventure.",
|
|
4556
|
+
inputSchema: { filter: z.string().optional().describe("Optional filter.") },
|
|
4655
4557
|
}, async ({ filter }) => {
|
|
4656
4558
|
const adventureDir = process.env.TTRPG_ADVENTURE_DIR ?? path.join(__dirname, "..", "adventures");
|
|
4657
4559
|
let files = [];
|
|
@@ -4741,7 +4643,7 @@ server.registerResource("adventure-navigation", new ResourceTemplate("adventure:
|
|
|
4741
4643
|
// REQ-072 — session_recap summarizes recent session activity.
|
|
4742
4644
|
server.registerTool("session_recap", {
|
|
4743
4645
|
title: "Session Recap",
|
|
4744
|
-
description: "Summarize recent session activity.",
|
|
4646
|
+
description: "Summarize recent session activity into a recap. Use when: reviewing what happened this session. Do NOT use when: reading the story journal in detail — use list_stories.",
|
|
4745
4647
|
inputSchema: {},
|
|
4746
4648
|
}, async () => {
|
|
4747
4649
|
const novel = requireNovel();
|
|
@@ -4827,8 +4729,8 @@ server.registerTool("session_recap", {
|
|
|
4827
4729
|
// --- Novel Lifecycle ---
|
|
4828
4730
|
server.registerTool("create_novel", {
|
|
4829
4731
|
title: "Create Novel",
|
|
4830
|
-
description: "Create a named
|
|
4831
|
-
inputSchema: { name: z.string(), ruleset: z.string().optional(), genre: z.string().optional(), description: z.string().optional(), codex_adventure: z.string().optional() },
|
|
4732
|
+
description: "Create a named Novel that persists to disk. Use when: starting a new campaign. Do NOT use when: resuming an existing Novel — use resume_novel.",
|
|
4733
|
+
inputSchema: { name: z.string().describe("The Novel name."), ruleset: z.string().optional().describe("Optional ruleset slug."), genre: z.string().optional().describe("Optional genre."), description: z.string().optional().describe("Optional description."), codex_adventure: z.string().optional().describe("Optional codex adventure to seed from.") },
|
|
4832
4734
|
}, async ({ name, ruleset, genre, description, codex_adventure }) => {
|
|
4833
4735
|
requireNotObserver();
|
|
4834
4736
|
if (ruleset && !rulesets.isInstalled(ruleset)) {
|
|
@@ -4873,8 +4775,8 @@ Next step: run the novel_setup guide to add characters, choose a story source, a
|
|
|
4873
4775
|
});
|
|
4874
4776
|
server.registerTool("resume_novel", {
|
|
4875
4777
|
title: "Resume Novel",
|
|
4876
|
-
description: "Resume a previously created
|
|
4877
|
-
inputSchema: { slug: z.string() },
|
|
4778
|
+
description: "Resume a previously created Novel from disk, making it the active Novel. Use when: continuing an existing campaign. Do NOT use when: creating a new Novel — use create_novel.",
|
|
4779
|
+
inputSchema: { slug: z.string().describe("The Novel slug to resume.") },
|
|
4878
4780
|
}, async ({ slug }) => {
|
|
4879
4781
|
// REQ-402 — resuming closes the prior session window; a window with zero
|
|
4880
4782
|
// state writes is surfaced as [session-no-mutations].
|
|
@@ -4900,8 +4802,8 @@ server.registerTool("resume_novel", {
|
|
|
4900
4802
|
});
|
|
4901
4803
|
server.registerTool("switch_novel", {
|
|
4902
4804
|
title: "Switch Novel",
|
|
4903
|
-
description: "Switch the active
|
|
4904
|
-
inputSchema: { slug: z.string() },
|
|
4805
|
+
description: "Switch the active Novel for this connection, always callable. Use when: changing which save file the session works on. Do NOT use when: ending a Novel — use end_novel.",
|
|
4806
|
+
inputSchema: { slug: z.string().describe("The Novel slug to switch to.") },
|
|
4905
4807
|
}, async ({ slug }) => {
|
|
4906
4808
|
// REQ-403b — TTRPG_STATE_GATE=block refuses to leave a drifting Novel.
|
|
4907
4809
|
const active = state.activeNovel;
|
|
@@ -4929,7 +4831,7 @@ server.registerTool("switch_novel", {
|
|
|
4929
4831
|
});
|
|
4930
4832
|
server.registerTool("end_novel", {
|
|
4931
4833
|
title: "End Novel",
|
|
4932
|
-
description: "End the current
|
|
4834
|
+
description: "End the current Novel, deactivating the badge and removing the save file after a confirmation workflow. Use when: concluding a campaign. Do NOT use when: merely switching Novels — use switch_novel.",
|
|
4933
4835
|
inputSchema: {},
|
|
4934
4836
|
}, async () => {
|
|
4935
4837
|
requireNotObserver();
|
|
@@ -4949,8 +4851,8 @@ Options: yes, cancel`);
|
|
|
4949
4851
|
});
|
|
4950
4852
|
server.registerTool("export_novel", {
|
|
4951
4853
|
title: "Export Novel",
|
|
4952
|
-
description: "Export the active
|
|
4953
|
-
inputSchema: { format: z.string().optional(), scope: z.string().optional() },
|
|
4854
|
+
description: "Export the active Novel in interchange format for backup or transfer (Game Master only). Use when: moving a Novel between servers. Do NOT use when: importing a Novel — use import_novel.",
|
|
4855
|
+
inputSchema: { format: z.string().optional().describe("Optional output format."), scope: z.string().optional().describe("Optional export scope.") },
|
|
4954
4856
|
}, async ({ format: fmt, scope }) => {
|
|
4955
4857
|
requireGM();
|
|
4956
4858
|
// REQ-425b — interchange surfaces accept only json/markdown; html returns
|
|
@@ -5043,12 +4945,8 @@ server.registerTool("export_novel", {
|
|
|
5043
4945
|
});
|
|
5044
4946
|
server.registerTool("import_novel", {
|
|
5045
4947
|
title: "Import Novel",
|
|
5046
|
-
description: "Import a previously exported
|
|
5047
|
-
inputSchema: {
|
|
5048
|
-
data: z.string(),
|
|
5049
|
-
mode: z.enum(["dry-run", "merge", "replace"]).optional(),
|
|
5050
|
-
strict: z.boolean().optional(),
|
|
5051
|
-
},
|
|
4948
|
+
description: "Import a previously exported Novel in dry-run, merge, or replace mode (Game Master only). Use when: loading a Novel from interchange format. Do NOT use when: exporting a Novel — use export_novel.",
|
|
4949
|
+
inputSchema: { data: z.string().describe("The exported Novel JSON."), mode: z.enum(["dry-run", "merge", "replace"]).optional().describe("dry-run, merge, or replace."), strict: z.boolean().optional().describe("When true, fail on any cross-reference mismatch.") },
|
|
5052
4950
|
}, async ({ data, mode, strict }) => {
|
|
5053
4951
|
requireGM();
|
|
5054
4952
|
const m = mode ?? "dry-run";
|
|
@@ -5159,7 +5057,7 @@ server.registerTool("import_novel", {
|
|
|
5159
5057
|
// --- Enrichment ---
|
|
5160
5058
|
server.registerTool("revert_synthesis", {
|
|
5161
5059
|
title: "Revert Synthesis",
|
|
5162
|
-
description: "Remove all synthesis state, restoring pre-synthesis server state
|
|
5060
|
+
description: "Remove all synthesis state, restoring pre-synthesis server state (Game Master only). Use when: discarding generated synthesis content wholesale. Do NOT use when: deactivating a single item — use deactivate_synthesis_item.",
|
|
5163
5061
|
inputSchema: {},
|
|
5164
5062
|
}, async () => {
|
|
5165
5063
|
requireGM();
|
|
@@ -5172,8 +5070,8 @@ server.registerTool("revert_synthesis", {
|
|
|
5172
5070
|
// --- Anchor-only tools (ruleset-free, REQ-218) ---
|
|
5173
5071
|
server.registerTool("search_rules", {
|
|
5174
5072
|
title: "Search Rules",
|
|
5175
|
-
description: "Search the active ruleset's index for matching terms
|
|
5176
|
-
inputSchema: { query: z.string(), max_results: z.number().optional() },
|
|
5073
|
+
description: "Search the active ruleset's index for matching terms, returning empty when no ruleset is bound. Use when: looking up a rule, item, or concept in the ruleset. Do NOT use when: the Novel is ruleset-free — use suggest_actions or spec_health.",
|
|
5074
|
+
inputSchema: { query: z.string().describe("The search query."), max_results: z.number().optional().describe("Optional maximum number of results.") },
|
|
5177
5075
|
}, async ({ query, max_results }) => {
|
|
5178
5076
|
const novel = state.activeNovel;
|
|
5179
5077
|
const slug = novel?.ruleset ?? null;
|
|
@@ -5194,16 +5092,8 @@ server.registerTool("search_rules", {
|
|
|
5194
5092
|
});
|
|
5195
5093
|
server.registerTool("install_ruleset", {
|
|
5196
5094
|
title: "Install Ruleset",
|
|
5197
|
-
description: "Install a ruleset package from a files bundle
|
|
5198
|
-
inputSchema: {
|
|
5199
|
-
slug: z.string(),
|
|
5200
|
-
manifest: z.any(),
|
|
5201
|
-
index: z.any().optional(),
|
|
5202
|
-
model: z.any().optional(),
|
|
5203
|
-
tools: z.any().optional(),
|
|
5204
|
-
resources: z.any().optional(),
|
|
5205
|
-
prompts: z.any().optional(),
|
|
5206
|
-
},
|
|
5095
|
+
description: "Install a ruleset package from a files bundle, persisting it to the install directory (Game Master or Editor only). Use when: adding a ruleset to the host. Do NOT use when: removing a ruleset — use remove_ruleset.",
|
|
5096
|
+
inputSchema: { slug: z.string().describe("The ruleset slug."), manifest: z.any().describe("The package manifest."), index: z.any().optional().describe("Optional search index."), model: z.any().optional().describe("Optional extraction model."), tools: z.any().optional().describe("Optional tool schemas."), resources: z.any().optional().describe("Optional resources."), prompts: z.any().optional().describe("Optional prompts.") },
|
|
5207
5097
|
}, async (args) => {
|
|
5208
5098
|
requireGM();
|
|
5209
5099
|
try {
|
|
@@ -5223,8 +5113,8 @@ server.registerTool("install_ruleset", {
|
|
|
5223
5113
|
});
|
|
5224
5114
|
server.registerTool("remove_ruleset", {
|
|
5225
5115
|
title: "Remove Ruleset",
|
|
5226
|
-
description: "Remove an installed ruleset package
|
|
5227
|
-
inputSchema: { slug: z.string() },
|
|
5116
|
+
description: "Remove an installed ruleset package, deregistering its tools, resources, and prompts (Game Master or Editor only). Use when: uninstalling a ruleset. Do NOT use when: listing rulesets — use list_rulesets.",
|
|
5117
|
+
inputSchema: { slug: z.string().describe("The ruleset slug to remove.") },
|
|
5228
5118
|
}, async ({ slug }) => {
|
|
5229
5119
|
requireGM();
|
|
5230
5120
|
const novel = state.activeNovel;
|
|
@@ -5241,7 +5131,7 @@ server.registerTool("remove_ruleset", {
|
|
|
5241
5131
|
});
|
|
5242
5132
|
server.registerTool("list_rulesets", {
|
|
5243
5133
|
title: "List Rulesets",
|
|
5244
|
-
description: "List installed ruleset packages with loaded-versus-installed state.",
|
|
5134
|
+
description: "List installed ruleset packages with loaded-versus-installed state. Use when: reviewing which rulesets the host knows. Do NOT use when: installing a ruleset — use install_ruleset.",
|
|
5245
5135
|
// REQ-391 — scoped tool listing: default surface is active Novel's ruleset
|
|
5246
5136
|
// tools plus infrastructure; list_rulesets exposes per-package state without
|
|
5247
5137
|
// forcing hydration of inactive packages (REQ-390 lazy hydration).
|
|
@@ -5258,8 +5148,8 @@ server.registerTool("list_rulesets", {
|
|
|
5258
5148
|
});
|
|
5259
5149
|
server.registerTool("bind_novel_ruleset", {
|
|
5260
5150
|
title: "Bind Novel Ruleset",
|
|
5261
|
-
description: "Bind the active ruleset-free Novel to an installed ruleset
|
|
5262
|
-
inputSchema: { slug: z.string() },
|
|
5151
|
+
description: "Bind the active ruleset-free Novel to an installed ruleset, a one-way, audited migration (Game Master or Editor only). Use when: adding mechanical rules to a freeform Novel. Do NOT use when: removing a ruleset — use remove_ruleset.",
|
|
5152
|
+
inputSchema: { slug: z.string().describe("The installed ruleset slug to bind.") },
|
|
5263
5153
|
}, async ({ slug }) => {
|
|
5264
5154
|
requireGM();
|
|
5265
5155
|
if (!rulesets.isInstalled(slug)) {
|
|
@@ -5276,8 +5166,8 @@ server.registerTool("bind_novel_ruleset", {
|
|
|
5276
5166
|
});
|
|
5277
5167
|
server.registerTool("suggest_actions", {
|
|
5278
5168
|
title: "Suggest Actions",
|
|
5279
|
-
description: "Map player intent to world-model tool invocations
|
|
5280
|
-
inputSchema: { intent: z.string(), entity_id: z.string().optional() },
|
|
5169
|
+
description: "Map player intent to world-model tool invocations, omitting mechanical suggestions in ruleset-free mode. Use when: translating natural-language intent into concrete tool calls. Do NOT use when: resolving a spatial action directly — use command or resolve_intent.",
|
|
5170
|
+
inputSchema: { intent: z.string().describe("The player intent to map to tool calls."), entity_id: z.string().optional().describe("Optional entity context.") },
|
|
5281
5171
|
}, async ({ intent, entity_id }) => {
|
|
5282
5172
|
const novel = requireNovel();
|
|
5283
5173
|
const entity = entity_id ? novel.entities.get(entity_id) : state.getActiveEntity();
|
|
@@ -5374,7 +5264,7 @@ const REQ022_URI_CATALOG = [
|
|
|
5374
5264
|
// REQ-025 — spec_health reports build health, indexed counts, and URI completeness.
|
|
5375
5265
|
server.registerTool("spec_health", {
|
|
5376
5266
|
title: "Spec Health",
|
|
5377
|
-
description: "Report build health, indexed counts, and resource URI completeness.",
|
|
5267
|
+
description: "Report build health, indexed counts, and resource URI completeness derived from live registrations. Use when: diagnosing server or ruleset state. Do NOT use when: searching ruleset content — use search_rules.",
|
|
5378
5268
|
inputSchema: {},
|
|
5379
5269
|
}, async () => {
|
|
5380
5270
|
const novel = state.activeNovel;
|
|
@@ -6092,7 +5982,7 @@ server.registerResource("ui-novel", "ui://novel/current", { title: "Active Novel
|
|
|
6092
5982
|
server.registerTool("set_party_presence", {
|
|
6093
5983
|
title: "Set Party Presence",
|
|
6094
5984
|
description: "Declare which entities are present in the current scene. Use when: the GM needs to override party presence without altering other scene fields. Do NOT use when: setting scene description — use set_scene_state.",
|
|
6095
|
-
inputSchema: { entity_ids: z.array(z.string()), location: z.string().optional() },
|
|
5985
|
+
inputSchema: { entity_ids: z.array(z.string()).describe("The entities present in the current scene."), location: z.string().optional().describe("Optional location override.") },
|
|
6096
5986
|
}, async ({ entity_ids, location }) => {
|
|
6097
5987
|
requireGM();
|
|
6098
5988
|
const novel = requireNovel();
|
|
@@ -6104,7 +5994,7 @@ server.registerTool("set_party_presence", {
|
|
|
6104
5994
|
server.registerTool("roll_on_table", {
|
|
6105
5995
|
title: "Roll On Table",
|
|
6106
5996
|
description: "Roll on a generation table from the bound ruleset. Use when: resolving a random-generation table (names, treasure, events). Do NOT use when: resolving a fixed lookup — use a lookup tool.",
|
|
6107
|
-
inputSchema: { table: z.string(), seed: z.string().optional() },
|
|
5997
|
+
inputSchema: { table: z.string().describe("The generation table to roll on."), seed: z.string().optional().describe("Optional deterministic seed.") },
|
|
6108
5998
|
}, async ({ table, seed }) => {
|
|
6109
5999
|
const novel = state.activeNovel;
|
|
6110
6000
|
const slug = novel?.ruleset ?? null;
|
|
@@ -6138,14 +6028,7 @@ server.registerTool("roll_on_table", {
|
|
|
6138
6028
|
server.registerTool("codex_set", {
|
|
6139
6029
|
title: "Set Codex Entry",
|
|
6140
6030
|
description: "Create or update a typed codex entry that persists across Novels. Use when: storing reusable content (NPCs, factions, rooms, spells, etc.) for later import. Do NOT use when: storing Novel-scoped content — use set_lore_entry or set_note.",
|
|
6141
|
-
inputSchema: {
|
|
6142
|
-
kind: z.string(),
|
|
6143
|
-
name: z.string(),
|
|
6144
|
-
content: z.any(),
|
|
6145
|
-
description: z.string().optional(),
|
|
6146
|
-
tags: z.array(z.string()).optional(),
|
|
6147
|
-
visibility: z.enum(["library", "shared", "private"]).optional(),
|
|
6148
|
-
},
|
|
6031
|
+
inputSchema: { kind: z.string().describe("The codex entry kind."), name: z.string().describe("The entry name."), content: z.any().describe("The entry content."), description: z.string().optional().describe("Optional description."), tags: z.array(z.string()).optional().describe("Optional tags."), visibility: z.enum(["library", "shared", "private"]).optional().describe("library, shared, or private.") },
|
|
6149
6032
|
}, async ({ kind, name, content, description, tags, visibility }) => {
|
|
6150
6033
|
requireGM();
|
|
6151
6034
|
const id = `${kind.toLowerCase()}_${name.toLowerCase().replace(/[^a-z0-9]+/g, "_")}`;
|
|
@@ -6166,7 +6049,7 @@ server.registerTool("codex_set", {
|
|
|
6166
6049
|
server.registerTool("codex_list", {
|
|
6167
6050
|
title: "List Codex Entries",
|
|
6168
6051
|
description: "List codex entries by kind, badge-filtered by visibility. Use when: discovering reusable content to import. Do NOT use when: listing Novel entities — use list_notes or list_stories.",
|
|
6169
|
-
inputSchema: { kind: z.string().optional() },
|
|
6052
|
+
inputSchema: { kind: z.string().optional().describe("Optional kind filter.") },
|
|
6170
6053
|
}, async ({ kind }) => {
|
|
6171
6054
|
const badge = getBadge();
|
|
6172
6055
|
let entries = [...state.codex.values()];
|
|
@@ -6180,7 +6063,7 @@ server.registerTool("codex_list", {
|
|
|
6180
6063
|
server.registerTool("codex_capture", {
|
|
6181
6064
|
title: "Capture to Codex",
|
|
6182
6065
|
description: "Capture an entity's voice profile to the Codex. Use when: persisting a character's corrected voice across Novels. Do NOT use when: storing Novel-scoped lore — use set_lore_entry.",
|
|
6183
|
-
inputSchema: { kind: z.enum(["voice_profile"]), entity_id: z.string(), update_source: z.boolean().optional() },
|
|
6066
|
+
inputSchema: { kind: z.enum(["voice_profile"]).describe("The codex kind (voice_profile)."), entity_id: z.string().describe("The entity whose voice to capture."), update_source: z.boolean().optional().describe("When true, update the source entity too.") },
|
|
6184
6067
|
}, async ({ kind, entity_id, update_source }) => {
|
|
6185
6068
|
requireGM();
|
|
6186
6069
|
const novel = requireNovel();
|
|
@@ -6204,7 +6087,7 @@ server.registerTool("codex_capture", {
|
|
|
6204
6087
|
server.registerTool("codex_import", {
|
|
6205
6088
|
title: "Import from Codex",
|
|
6206
6089
|
description: "Import a Codex entry into the active Novel. Use when: pulling reusable content (voice profiles, adventures) in. Do NOT use when: reading the Codex — use codex_list.",
|
|
6207
|
-
inputSchema: { entry_id: z.string() },
|
|
6090
|
+
inputSchema: { entry_id: z.string().describe("The codex entry identifier to import.") },
|
|
6208
6091
|
}, async ({ entry_id }) => {
|
|
6209
6092
|
requireGM();
|
|
6210
6093
|
const novel = requireNovel();
|
|
@@ -6252,8 +6135,8 @@ server.registerTool("codex_import", {
|
|
|
6252
6135
|
const PLAYER_SYNTH_MODULES = ["voice_examples", "action_patterns", "supplementary_guidance", "narrative_voices", "lore_templates"];
|
|
6253
6136
|
server.registerTool("player_synthesize", {
|
|
6254
6137
|
title: "Player Synthesize",
|
|
6255
|
-
description: "Create a player-authored synthesis item
|
|
6256
|
-
inputSchema: { module: z.string(), key: z.string(), content: z.string(), triggers: z.array(z.string()).optional(), badge_scope: z.enum(["shared", "player"]).optional() },
|
|
6138
|
+
description: "Create a player-authored synthesis item (Player badge only), persisting it to the Novel. Use when: the player wants to add their own lore or voice content. Do NOT use when: the GM is synthesizing — use synthesize.",
|
|
6139
|
+
inputSchema: { module: z.string().describe("The synthesis module."), key: z.string().describe("The item key."), content: z.string().describe("The item content."), triggers: z.array(z.string()).optional().describe("Optional recall triggers."), badge_scope: z.enum(["shared", "player"]).optional().describe("shared or player.") },
|
|
6257
6140
|
}, async ({ module, key, content, triggers, badge_scope }) => {
|
|
6258
6141
|
requirePlayer();
|
|
6259
6142
|
const novel = requireNovel();
|
|
@@ -6273,8 +6156,8 @@ server.registerTool("player_synthesize", {
|
|
|
6273
6156
|
});
|
|
6274
6157
|
server.registerTool("player_remove_synthesis", {
|
|
6275
6158
|
title: "Player Remove Synthesis",
|
|
6276
|
-
description: "Remove a player-authored synthesis item
|
|
6277
|
-
inputSchema: { module: z.string(), key: z.string() },
|
|
6159
|
+
description: "Remove a player-authored synthesis item (Player badge only), persisting the removal. Use when: the player discards their own synthesis content. Do NOT use when: deactivating an item without deleting it — use deactivate_synthesis_item.",
|
|
6160
|
+
inputSchema: { module: z.string().describe("The synthesis module."), key: z.string().describe("The item key to remove.") },
|
|
6278
6161
|
}, async ({ module, key }) => {
|
|
6279
6162
|
requirePlayer();
|
|
6280
6163
|
const novel = requireNovel();
|
|
@@ -6289,8 +6172,8 @@ server.registerTool("player_remove_synthesis", {
|
|
|
6289
6172
|
});
|
|
6290
6173
|
server.registerTool("player_list_synthesis", {
|
|
6291
6174
|
title: "Player List Synthesis",
|
|
6292
|
-
description: "List player-authored synthesis items
|
|
6293
|
-
inputSchema: { module: z.string().optional() },
|
|
6175
|
+
description: "List player-authored synthesis items (Player badge only). Use when: reviewing the player's own synthesis content. Do NOT use when: listing all synthesis items — use list_synthesis_items.",
|
|
6176
|
+
inputSchema: { module: z.string().optional().describe("Optional module filter.") },
|
|
6294
6177
|
}, async ({ module }) => {
|
|
6295
6178
|
requirePlayer();
|
|
6296
6179
|
const novel = requireNovel();
|
|
@@ -6307,7 +6190,7 @@ server.registerTool("synthesize", {
|
|
|
6307
6190
|
// items with novel:// source URIs; fingerprint staleness + force bypass.
|
|
6308
6191
|
// REQ-264 — confidence model: explicit-field items carry MEDIUM, inferred
|
|
6309
6192
|
// items LOW, tagged [supplementary] [MEDIUM|LOW].
|
|
6310
|
-
inputSchema: { force: z.boolean().optional() },
|
|
6193
|
+
inputSchema: { force: z.boolean().optional().describe("When true, re-run synthesis even if unchanged.") },
|
|
6311
6194
|
}, async ({ force }) => {
|
|
6312
6195
|
requireGM();
|
|
6313
6196
|
const novel = requireNovel();
|
|
@@ -6323,7 +6206,7 @@ server.registerTool("synthesize", {
|
|
|
6323
6206
|
server.registerTool("list_synthesis_items", {
|
|
6324
6207
|
title: "List Synthesis Items",
|
|
6325
6208
|
description: "List synthesis items by module and tier. Use when: reviewing available synthesis content. Do NOT use when: browsing the codex — use codex_list.",
|
|
6326
|
-
inputSchema: { module: z.string().optional(), ...detailZod },
|
|
6209
|
+
inputSchema: { module: z.string().optional().describe("Optional module filter."), ...detailZod },
|
|
6327
6210
|
}, async ({ module, detail }) => {
|
|
6328
6211
|
const manifest = state.enrichmentManifest;
|
|
6329
6212
|
if (!manifest)
|
|
@@ -6344,7 +6227,7 @@ server.registerTool("list_synthesis_items", {
|
|
|
6344
6227
|
server.registerTool("activate_synthesis_item", {
|
|
6345
6228
|
title: "Activate Synthesis Item",
|
|
6346
6229
|
description: "Activate a synthesis item for the active Novel. Use when: incorporating synthesis content into play. Do NOT use when: deactivating — use deactivate_synthesis_item.",
|
|
6347
|
-
inputSchema: { module: z.string(), key: z.number() },
|
|
6230
|
+
inputSchema: { module: z.string().describe("The synthesis module."), key: z.number().describe("The item key to activate.") },
|
|
6348
6231
|
}, async ({ module, key }) => {
|
|
6349
6232
|
requireGM();
|
|
6350
6233
|
const novel = requireNovel();
|
|
@@ -6359,7 +6242,7 @@ server.registerTool("activate_synthesis_item", {
|
|
|
6359
6242
|
server.registerTool("deactivate_synthesis_item", {
|
|
6360
6243
|
title: "Deactivate Synthesis Item",
|
|
6361
6244
|
description: "Deactivate a synthesis item for the active Novel. Use when: removing a synthesis item from play without deleting it. Do NOT use when: removing Ruleset Wisdom — use revert_synthesis.",
|
|
6362
|
-
inputSchema: { module: z.string() },
|
|
6245
|
+
inputSchema: { module: z.string().describe("The synthesis module to deactivate.") },
|
|
6363
6246
|
}, async ({ module }) => {
|
|
6364
6247
|
requireGM();
|
|
6365
6248
|
const novel = requireNovel();
|
|
@@ -6372,7 +6255,7 @@ server.registerTool("deactivate_synthesis_item", {
|
|
|
6372
6255
|
server.registerTool("toggle_synthesis_module", {
|
|
6373
6256
|
title: "Toggle Synthesis Module",
|
|
6374
6257
|
description: "Enable or disable a synthesis module for the active Novel. Use when: controlling whether a module's content appears in surfaces. Do NOT use when: activating a single item — use activate_synthesis_item.",
|
|
6375
|
-
inputSchema: { module: z.string(), enabled: z.boolean() },
|
|
6258
|
+
inputSchema: { module: z.string().describe("The synthesis module."), enabled: z.boolean().describe("Whether to enable or disable it.") },
|
|
6376
6259
|
}, async ({ module, enabled }) => {
|
|
6377
6260
|
requireGM();
|
|
6378
6261
|
const novel = requireNovel();
|