run-dmcp 0.1.0

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.
Files changed (167) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +79 -0
  3. package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
  4. package/dist/__tests__/engineVocabulary.test.js +147 -0
  5. package/dist/db/__tests__/connection.test.d.ts +1 -0
  6. package/dist/db/__tests__/connection.test.js +72 -0
  7. package/dist/db/__tests__/testDb.d.ts +33 -0
  8. package/dist/db/__tests__/testDb.js +41 -0
  9. package/dist/db/connection.d.ts +22 -0
  10. package/dist/db/connection.js +107 -0
  11. package/dist/db/schema.d.ts +1 -0
  12. package/dist/db/schema.js +725 -0
  13. package/dist/events/emitter.d.ts +22 -0
  14. package/dist/events/emitter.js +71 -0
  15. package/dist/http/server.d.ts +3 -0
  16. package/dist/http/server.js +649 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +92 -0
  19. package/dist/register/abilities.d.ts +2 -0
  20. package/dist/register/abilities.js +165 -0
  21. package/dist/register/audio.d.ts +2 -0
  22. package/dist/register/audio.js +326 -0
  23. package/dist/register/batch.d.ts +2 -0
  24. package/dist/register/batch.js +343 -0
  25. package/dist/register/character.d.ts +2 -0
  26. package/dist/register/character.js +324 -0
  27. package/dist/register/combat.d.ts +2 -0
  28. package/dist/register/combat.js +207 -0
  29. package/dist/register/core.d.ts +2 -0
  30. package/dist/register/core.js +1040 -0
  31. package/dist/register/display.d.ts +2 -0
  32. package/dist/register/display.js +263 -0
  33. package/dist/register/factions.d.ts +2 -0
  34. package/dist/register/factions.js +186 -0
  35. package/dist/register/images.d.ts +2 -0
  36. package/dist/register/images.js +400 -0
  37. package/dist/register/inventory.d.ts +2 -0
  38. package/dist/register/inventory.js +115 -0
  39. package/dist/register/mcp-prompts.d.ts +2 -0
  40. package/dist/register/mcp-prompts.js +684 -0
  41. package/dist/register/mcp-resources.d.ts +2 -0
  42. package/dist/register/mcp-resources.js +335 -0
  43. package/dist/register/narrative.d.ts +2 -0
  44. package/dist/register/narrative.js +242 -0
  45. package/dist/register/notes.d.ts +2 -0
  46. package/dist/register/notes.js +170 -0
  47. package/dist/register/pause.d.ts +2 -0
  48. package/dist/register/pause.js +580 -0
  49. package/dist/register/quests.d.ts +2 -0
  50. package/dist/register/quests.js +118 -0
  51. package/dist/register/relationships.d.ts +2 -0
  52. package/dist/register/relationships.js +147 -0
  53. package/dist/register/resources.d.ts +2 -0
  54. package/dist/register/resources.js +277 -0
  55. package/dist/register/secrets.d.ts +2 -0
  56. package/dist/register/secrets.js +192 -0
  57. package/dist/register/status.d.ts +2 -0
  58. package/dist/register/status.js +130 -0
  59. package/dist/register/tables.d.ts +2 -0
  60. package/dist/register/tables.js +146 -0
  61. package/dist/register/tags.d.ts +2 -0
  62. package/dist/register/tags.js +114 -0
  63. package/dist/register/time.d.ts +2 -0
  64. package/dist/register/time.js +281 -0
  65. package/dist/register/world.d.ts +2 -0
  66. package/dist/register/world.js +127 -0
  67. package/dist/schemas/index.d.ts +921 -0
  68. package/dist/schemas/index.js +121 -0
  69. package/dist/test-setup.d.ts +1 -0
  70. package/dist/test-setup.js +13 -0
  71. package/dist/tools/__tests__/audio.test.d.ts +1 -0
  72. package/dist/tools/__tests__/audio.test.js +59 -0
  73. package/dist/tools/__tests__/conserved.test.d.ts +1 -0
  74. package/dist/tools/__tests__/conserved.test.js +488 -0
  75. package/dist/tools/__tests__/constraint.test.d.ts +1 -0
  76. package/dist/tools/__tests__/constraint.test.js +212 -0
  77. package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
  78. package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
  79. package/dist/tools/__tests__/images.test.d.ts +1 -0
  80. package/dist/tools/__tests__/images.test.js +59 -0
  81. package/dist/tools/__tests__/relationship.test.d.ts +1 -0
  82. package/dist/tools/__tests__/relationship.test.js +132 -0
  83. package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
  84. package/dist/tools/__tests__/resource-constraints.test.js +131 -0
  85. package/dist/tools/__tests__/resource.test.d.ts +1 -0
  86. package/dist/tools/__tests__/resource.test.js +190 -0
  87. package/dist/tools/__tests__/time.test.d.ts +1 -0
  88. package/dist/tools/__tests__/time.test.js +404 -0
  89. package/dist/tools/__tests__/timers.test.d.ts +1 -0
  90. package/dist/tools/__tests__/timers.test.js +426 -0
  91. package/dist/tools/__tests__/world.test.d.ts +1 -0
  92. package/dist/tools/__tests__/world.test.js +70 -0
  93. package/dist/tools/ability.d.ts +48 -0
  94. package/dist/tools/ability.js +238 -0
  95. package/dist/tools/audio.d.ts +24 -0
  96. package/dist/tools/audio.js +365 -0
  97. package/dist/tools/character.d.ts +70 -0
  98. package/dist/tools/character.js +309 -0
  99. package/dist/tools/combat.d.ts +13 -0
  100. package/dist/tools/combat.js +195 -0
  101. package/dist/tools/constraint.d.ts +132 -0
  102. package/dist/tools/constraint.js +269 -0
  103. package/dist/tools/dice.d.ts +23 -0
  104. package/dist/tools/dice.js +111 -0
  105. package/dist/tools/display.d.ts +120 -0
  106. package/dist/tools/display.js +528 -0
  107. package/dist/tools/faction.d.ts +61 -0
  108. package/dist/tools/faction.js +269 -0
  109. package/dist/tools/game.d.ts +96 -0
  110. package/dist/tools/game.js +526 -0
  111. package/dist/tools/image-prompt.d.ts +49 -0
  112. package/dist/tools/image-prompt.js +479 -0
  113. package/dist/tools/images.d.ts +47 -0
  114. package/dist/tools/images.js +449 -0
  115. package/dist/tools/inventory.d.ts +20 -0
  116. package/dist/tools/inventory.js +145 -0
  117. package/dist/tools/narrative.d.ts +58 -0
  118. package/dist/tools/narrative.js +237 -0
  119. package/dist/tools/notes.d.ts +41 -0
  120. package/dist/tools/notes.js +220 -0
  121. package/dist/tools/pause.d.ts +110 -0
  122. package/dist/tools/pause.js +1254 -0
  123. package/dist/tools/quest.d.ts +34 -0
  124. package/dist/tools/quest.js +164 -0
  125. package/dist/tools/relationship.d.ts +74 -0
  126. package/dist/tools/relationship.js +324 -0
  127. package/dist/tools/resource.d.ts +93 -0
  128. package/dist/tools/resource.js +374 -0
  129. package/dist/tools/rules.d.ts +4 -0
  130. package/dist/tools/rules.js +30 -0
  131. package/dist/tools/secrets.d.ts +49 -0
  132. package/dist/tools/secrets.js +195 -0
  133. package/dist/tools/status.d.ts +36 -0
  134. package/dist/tools/status.js +218 -0
  135. package/dist/tools/tables.d.ts +33 -0
  136. package/dist/tools/tables.js +209 -0
  137. package/dist/tools/tags.d.ts +52 -0
  138. package/dist/tools/tags.js +176 -0
  139. package/dist/tools/time.d.ts +33 -0
  140. package/dist/tools/time.js +276 -0
  141. package/dist/tools/timers.d.ts +41 -0
  142. package/dist/tools/timers.js +215 -0
  143. package/dist/tools/world.d.ts +78 -0
  144. package/dist/tools/world.js +331 -0
  145. package/dist/types/index.d.ts +969 -0
  146. package/dist/types/index.js +1 -0
  147. package/dist/utils/__tests__/json.test.d.ts +1 -0
  148. package/dist/utils/__tests__/json.test.js +55 -0
  149. package/dist/utils/__tests__/validation.test.d.ts +1 -0
  150. package/dist/utils/__tests__/validation.test.js +90 -0
  151. package/dist/utils/errors.d.ts +44 -0
  152. package/dist/utils/errors.js +121 -0
  153. package/dist/utils/json.d.ts +9 -0
  154. package/dist/utils/json.js +23 -0
  155. package/dist/utils/logger.d.ts +7 -0
  156. package/dist/utils/logger.js +50 -0
  157. package/dist/utils/output-schemas.d.ts +594 -0
  158. package/dist/utils/output-schemas.js +331 -0
  159. package/dist/utils/tool-annotations.d.ts +147 -0
  160. package/dist/utils/tool-annotations.js +98 -0
  161. package/dist/utils/validation.d.ts +34 -0
  162. package/dist/utils/validation.js +52 -0
  163. package/dist/utils/verbosity.d.ts +57 -0
  164. package/dist/utils/verbosity.js +67 -0
  165. package/dist/utils/webui.d.ts +20 -0
  166. package/dist/utils/webui.js +35 -0
  167. package/package.json +75 -0
@@ -0,0 +1,684 @@
1
+ import { z } from "zod";
2
+ import * as gameTools from "../tools/game.js";
3
+ import * as characterTools from "../tools/character.js";
4
+ import * as narrativeTools from "../tools/narrative.js";
5
+ import * as pauseTools from "../tools/pause.js";
6
+ import * as worldTools from "../tools/world.js";
7
+ import * as questTools from "../tools/quest.js";
8
+ import * as relationshipTools from "../tools/relationship.js";
9
+ export function registerMcpPrompts(server) {
10
+ // ============================================================================
11
+ // DM PERSONA - Initialize the DM with full game context
12
+ // ============================================================================
13
+ server.registerPrompt("dm-persona", {
14
+ description: "Initialize DM persona with full game context and preferences",
15
+ argsSchema: {
16
+ gameId: z.string().describe("The game ID to load context from"),
17
+ },
18
+ }, async ({ gameId }) => {
19
+ const game = gameTools.loadGame(gameId);
20
+ if (!game) {
21
+ return {
22
+ messages: [
23
+ {
24
+ role: "user",
25
+ content: {
26
+ type: "text",
27
+ text: `Error: Game ${gameId} not found.`,
28
+ },
29
+ },
30
+ ],
31
+ };
32
+ }
33
+ const state = gameTools.getGameState(gameId);
34
+ const preferences = game.preferences;
35
+ let promptText = `You are the Dungeon Master for "${game.name}", a ${game.style} ${game.setting} game.\n\n`;
36
+ if (preferences) {
37
+ promptText += `## Player Preferences\n`;
38
+ promptText += `- **Tone**: ${preferences.tone?.value || "Not specified"}\n`;
39
+ promptText += `- **Complexity**: ${preferences.complexity?.value || "Not specified"}\n`;
40
+ promptText += `- **Combat Frequency**: ${preferences.combatFrequency?.value || "Not specified"}\n`;
41
+ promptText += `- **Lethality**: ${preferences.lethality?.value || "Not specified"}\n`;
42
+ promptText += `- **Narrative Style**: ${preferences.narrativeStyle?.value || "Not specified"}\n`;
43
+ promptText += `- **Player Agency**: ${preferences.playerAgency?.value || "Not specified"}\n`;
44
+ promptText += `- **NPC Depth**: ${preferences.npcDepth?.value || "Not specified"}\n`;
45
+ promptText += `- **Romance Content**: ${preferences.romanceContent?.value || "Not specified"}\n`;
46
+ if (preferences.contentToAvoid?.length) {
47
+ promptText += `\n### Content to AVOID\n`;
48
+ preferences.contentToAvoid.forEach((item) => {
49
+ promptText += `- ${item}\n`;
50
+ });
51
+ }
52
+ if (preferences.contentToInclude?.length) {
53
+ promptText += `\n### Content to INCLUDE\n`;
54
+ preferences.contentToInclude.forEach((item) => {
55
+ promptText += `- ${item}\n`;
56
+ });
57
+ }
58
+ if (preferences.inspirations?.length) {
59
+ promptText += `\n### Inspirations\n`;
60
+ promptText += preferences.inspirations.join(", ") + "\n";
61
+ }
62
+ }
63
+ if (state) {
64
+ promptText += `\n## Current State\n`;
65
+ promptText += `- Characters: ${state.characterCount}\n`;
66
+ promptText += `- Locations: ${state.locationCount}\n`;
67
+ promptText += `- Active Quests: ${state.activeQuests}\n`;
68
+ promptText += `- Combat Active: ${state.activeCombat ? "Yes" : "No"}\n`;
69
+ }
70
+ promptText += `\n## DM Guidelines\n`;
71
+ promptText += `1. Stay consistent with the established tone and setting\n`;
72
+ promptText += `2. Honor player preferences and avoid restricted content\n`;
73
+ promptText += `3. Create immersive descriptions that match the genre\n`;
74
+ promptText += `\n## CRITICAL: State Persistence Rules\n`;
75
+ promptText += `The database IS the game state. Persist entities IMMEDIATELY as you introduce them in narrative:\n\n`;
76
+ promptText += `### Before Narrating, Persist:\n`;
77
+ promptText += `| Narrative Element | Tool to Call | When |\n`;
78
+ promptText += `|-------------------|--------------|------|\n`;
79
+ promptText += `| Named NPC | \`create_character\` | BEFORE their first dialogue/action |\n`;
80
+ promptText += `| New location | \`create_location\` + \`connect_locations\` | BEFORE describing what's there |\n`;
81
+ promptText += `| Item introduced | \`create_item\` | BEFORE player can interact with it |\n`;
82
+ promptText += `| Quest/mission given | \`create_quest\` | When objectives become clear |\n`;
83
+ promptText += `| NPC attitude established | \`create_relationship\` | When feelings toward player are shown |\n`;
84
+ promptText += `| Hidden information | \`create_secret\` | Even if revealed immediately |\n\n`;
85
+ promptText += `### After Every Narrative Beat:\n`;
86
+ promptText += `- Call \`log_event\` to record what happened (dialogue, action, discovery, combat, travel, decision)\n`;
87
+ promptText += `- Update relationships if interactions changed how NPCs feel\n`;
88
+ promptText += `- Transfer items if ownership changed\n`;
89
+ promptText += `- Mark quest objectives complete when achieved\n`;
90
+ promptText += `- Reveal secrets when characters learn information\n\n`;
91
+ promptText += `### Periodic Context Preservation:\n`;
92
+ promptText += `- Call \`check_context_freshness\` every 5-10 turns\n`;
93
+ promptText += `- Call \`save_context_snapshot\` after significant story beats\n`;
94
+ promptText += `- Never let more than 10-15 turns pass without a snapshot\n\n`;
95
+ promptText += `### Persistence Checklist (Mental Check After Each Response):\n`;
96
+ promptText += `- [ ] Did I mention anyone new? → create_character\n`;
97
+ promptText += `- [ ] Did I describe a new place? → create_location\n`;
98
+ promptText += `- [ ] Did items change hands? → create_item/transfer_item\n`;
99
+ promptText += `- [ ] Did relationships change? → create/update_relationship\n`;
100
+ promptText += `- [ ] Did anyone learn something? → create_secret/modify_secret_visibility\n`;
101
+ promptText += `- [ ] What happened? → log_event\n`;
102
+ return {
103
+ messages: [
104
+ {
105
+ role: "user",
106
+ content: { type: "text", text: promptText },
107
+ },
108
+ ],
109
+ };
110
+ });
111
+ // ============================================================================
112
+ // SESSION RECAP - Generate a narrative summary
113
+ // ============================================================================
114
+ server.registerPrompt("session-recap", {
115
+ description: "Generate a narrative recap of the game so far",
116
+ argsSchema: {
117
+ gameId: z.string().describe("The game ID"),
118
+ },
119
+ }, async ({ gameId }) => {
120
+ const game = gameTools.loadGame(gameId);
121
+ if (!game) {
122
+ return {
123
+ messages: [
124
+ {
125
+ role: "user",
126
+ content: {
127
+ type: "text",
128
+ text: `Error: Game ${gameId} not found.`,
129
+ },
130
+ },
131
+ ],
132
+ };
133
+ }
134
+ const summary = narrativeTools.getSummary(gameId);
135
+ let promptText = `# Session Recap: ${game.name}\n\n`;
136
+ promptText += `**Setting**: ${game.setting}\n`;
137
+ promptText += `**Style**: ${game.style}\n\n`;
138
+ promptText += `## Statistics\n`;
139
+ promptText += `- Total Events: ${summary.totalEvents}\n`;
140
+ promptText += `- First Event: ${summary.firstEvent || "N/A"}\n`;
141
+ promptText += `- Last Event: ${summary.lastEvent || "N/A"}\n\n`;
142
+ if (Object.keys(summary.eventTypes).length > 0) {
143
+ promptText += `## Event Breakdown\n`;
144
+ for (const [type, count] of Object.entries(summary.eventTypes)) {
145
+ promptText += `- ${type}: ${count}\n`;
146
+ }
147
+ promptText += "\n";
148
+ }
149
+ if (summary.recentEvents.length > 0) {
150
+ promptText += `## Recent Events\n`;
151
+ for (const event of summary.recentEvents) {
152
+ promptText += `**[${event.eventType}]** ${event.content}\n\n`;
153
+ }
154
+ }
155
+ promptText += `---\n\nPlease provide a narrative summary of this session suitable for reminding the player where we left off.`;
156
+ return {
157
+ messages: [
158
+ {
159
+ role: "user",
160
+ content: { type: "text", text: promptText },
161
+ },
162
+ ],
163
+ };
164
+ });
165
+ // ============================================================================
166
+ // NEW GAME SETUP - Interview template for game creation
167
+ // ============================================================================
168
+ server.registerPrompt("new-game-setup", {
169
+ description: "Interview template for creating a new game",
170
+ argsSchema: {
171
+ genre: z.string().optional().describe("Optional: Pre-select a genre"),
172
+ },
173
+ }, async ({ genre }) => {
174
+ let promptText = `# New Game Setup Interview\n\n`;
175
+ promptText += `You are helping a player create their perfect tabletop RPG experience. Guide them through the following questions conversationally.\n\n`;
176
+ promptText += `## Instructions\n`;
177
+ promptText += `- Present questions in a friendly, conversational manner\n`;
178
+ promptText += `- Group related questions together\n`;
179
+ promptText += `- For each question, remind them they can:\n`;
180
+ promptText += ` - Choose from the options provided\n`;
181
+ promptText += ` - Give a custom answer\n`;
182
+ promptText += ` - Say "you decide" to delegate the choice to you\n\n`;
183
+ if (genre) {
184
+ promptText += `The player has indicated interest in: **${genre}**\n\n`;
185
+ }
186
+ promptText += `## Question Categories\n\n`;
187
+ promptText += `### 1. Core Identity\n`;
188
+ promptText += `- Genre (fantasy, sci-fi, horror, etc.)\n`;
189
+ promptText += `- Tone (grimdark, heroic, comedic, etc.)\n`;
190
+ promptText += `- Specific setting details\n\n`;
191
+ promptText += `### 2. Mechanics & Challenge\n`;
192
+ promptText += `- Rules complexity (light, medium, crunchy)\n`;
193
+ promptText += `- Combat frequency and style\n`;
194
+ promptText += `- Lethality level\n\n`;
195
+ promptText += `### 3. Narrative & Story\n`;
196
+ promptText += `- Narrative structure (linear, branching, sandbox)\n`;
197
+ promptText += `- Player agency level\n`;
198
+ promptText += `- NPC depth\n`;
199
+ promptText += `- Romance content handling\n\n`;
200
+ promptText += `### 4. World Building\n`;
201
+ promptText += `- World familiarity\n`;
202
+ promptText += `- Magic/tech prevalence\n`;
203
+ promptText += `- Political complexity\n\n`;
204
+ promptText += `### 5. Session & Pacing\n`;
205
+ promptText += `- Typical session length\n`;
206
+ promptText += `- Pacing preference\n\n`;
207
+ promptText += `### 6. Character\n`;
208
+ promptText += `- Character creation style\n`;
209
+ promptText += `- Starting power level\n\n`;
210
+ promptText += `### 7. Content & Safety\n`;
211
+ promptText += `- Topics to avoid\n`;
212
+ promptText += `- Topics to include\n`;
213
+ promptText += `- Media inspirations\n\n`;
214
+ promptText += `---\n\n`;
215
+ promptText += `After gathering preferences, use:\n`;
216
+ promptText += `1. \`create_game\` to create the game\n`;
217
+ promptText += `2. \`save_game_preferences\` to store their choices\n`;
218
+ promptText += `3. \`set_rules\` to establish the rule system\n`;
219
+ return {
220
+ messages: [
221
+ {
222
+ role: "user",
223
+ content: { type: "text", text: promptText },
224
+ },
225
+ ],
226
+ };
227
+ });
228
+ // ============================================================================
229
+ // CONTINUE GAME - Resume a paused session with full context
230
+ // ============================================================================
231
+ server.registerPrompt("continue-game", {
232
+ description: "Resume a paused game with full context restoration",
233
+ argsSchema: {
234
+ gameId: z.string().describe("The game ID to resume"),
235
+ },
236
+ }, async ({ gameId }) => {
237
+ const resumeContext = pauseTools.getResumeContext(gameId);
238
+ if (!resumeContext) {
239
+ // No pause state - try basic session load
240
+ const game = gameTools.loadGame(gameId);
241
+ if (!game) {
242
+ return {
243
+ messages: [
244
+ {
245
+ role: "user",
246
+ content: {
247
+ type: "text",
248
+ text: `Error: Game ${gameId} not found.`,
249
+ },
250
+ },
251
+ ],
252
+ };
253
+ }
254
+ return {
255
+ messages: [
256
+ {
257
+ role: "user",
258
+ content: {
259
+ type: "text",
260
+ text: `# Resuming: ${game.name}\n\nNo pause state was saved for this game. Use \`get_game_state\` and \`get_history\` to understand the current game state before continuing.`,
261
+ },
262
+ },
263
+ ],
264
+ };
265
+ }
266
+ let promptText = `# Resuming Game: ${resumeContext.gameState.game.name}\n\n`;
267
+ // Pause state context
268
+ const ps = resumeContext.pauseState;
269
+ promptText += `## Where We Left Off\n`;
270
+ promptText += `**Scene**: ${ps.currentScene}\n\n`;
271
+ promptText += `**Immediate Situation**: ${ps.immediateSituation}\n\n`;
272
+ if (ps.awaitingResponseTo) {
273
+ promptText += `**Awaiting Player Response To**: ${ps.awaitingResponseTo}\n\n`;
274
+ }
275
+ if (ps.pendingPlayerAction) {
276
+ promptText += `**Pending Player Action**: ${ps.pendingPlayerAction}\n\n`;
277
+ }
278
+ // DM Plans
279
+ if (ps.dmShortTermPlans || ps.dmLongTermPlans) {
280
+ promptText += `## DM Notes\n`;
281
+ if (ps.dmShortTermPlans) {
282
+ promptText += `**Short-term Plans**: ${ps.dmShortTermPlans}\n`;
283
+ }
284
+ if (ps.dmLongTermPlans) {
285
+ promptText += `**Long-term Plans**: ${ps.dmLongTermPlans}\n`;
286
+ }
287
+ promptText += "\n";
288
+ }
289
+ // Active threads
290
+ if (ps.activeThreads?.length) {
291
+ promptText += `## Active Story Threads\n`;
292
+ for (const thread of ps.activeThreads) {
293
+ promptText += `- **${thread.name}** (${thread.status}, ${thread.urgency} urgency): ${thread.description}\n`;
294
+ }
295
+ promptText += "\n";
296
+ }
297
+ // Player character
298
+ if (resumeContext.gameState.playerCharacter) {
299
+ const pc = resumeContext.gameState.playerCharacter;
300
+ promptText += `## Player Character\n`;
301
+ promptText += `**${pc.name}** - HP: ${pc.status?.health || "?"}/${pc.status?.maxHealth || "?"}\n`;
302
+ if (resumeContext.gameState.currentLocation) {
303
+ promptText += `**Location**: ${resumeContext.gameState.currentLocation.name}\n`;
304
+ }
305
+ promptText += "\n";
306
+ }
307
+ // Warnings
308
+ if (resumeContext.warnings?.length) {
309
+ promptText += `## Warnings\n`;
310
+ for (const warning of resumeContext.warnings) {
311
+ promptText += `- ${warning}\n`;
312
+ }
313
+ promptText += "\n";
314
+ }
315
+ // Recent events
316
+ if (resumeContext.gameState.recentEvents?.length) {
317
+ promptText += `## Recent Events\n`;
318
+ for (const event of resumeContext.gameState.recentEvents.slice(0, 5)) {
319
+ promptText += `- [${event.eventType}] ${event.content}\n`;
320
+ }
321
+ promptText += "\n";
322
+ }
323
+ promptText += `---\n\nResume the game from this point. The player is ready to continue.`;
324
+ return {
325
+ messages: [
326
+ {
327
+ role: "user",
328
+ content: { type: "text", text: promptText },
329
+ },
330
+ ],
331
+ };
332
+ });
333
+ // ============================================================================
334
+ // CHARACTER VOICE - NPC voice prompt for roleplay
335
+ // ============================================================================
336
+ server.registerPrompt("character-voice", {
337
+ description: "Get NPC voice characteristics for roleplay",
338
+ argsSchema: {
339
+ characterId: z.string().describe("The character ID"),
340
+ },
341
+ }, async ({ characterId }) => {
342
+ const character = characterTools.getCharacter(characterId);
343
+ if (!character) {
344
+ return {
345
+ messages: [
346
+ {
347
+ role: "user",
348
+ content: {
349
+ type: "text",
350
+ text: `Error: Character ${characterId} not found.`,
351
+ },
352
+ },
353
+ ],
354
+ };
355
+ }
356
+ let promptText = `# Voice Profile: ${character.name}\n\n`;
357
+ if (character.voice) {
358
+ const v = character.voice;
359
+ promptText += `## Voice Characteristics\n`;
360
+ promptText += `- **Pitch**: ${v.pitch}\n`;
361
+ promptText += `- **Speed**: ${v.speed}\n`;
362
+ promptText += `- **Tone**: ${v.tone}\n`;
363
+ if (v.accent) {
364
+ promptText += `- **Accent**: ${v.accent}\n`;
365
+ }
366
+ if (v.quirks?.length) {
367
+ promptText += `\n## Speech Quirks\n`;
368
+ for (const quirk of v.quirks) {
369
+ promptText += `- ${quirk}\n`;
370
+ }
371
+ }
372
+ if (v.description) {
373
+ promptText += `\n## Additional Notes\n${v.description}\n`;
374
+ }
375
+ }
376
+ else {
377
+ promptText += `No voice profile defined for this character.\n\n`;
378
+ promptText += `Consider these defaults based on the character:\n`;
379
+ promptText += `- Name: ${character.name}\n`;
380
+ promptText += `- Player Character: ${character.isPlayer ? "Yes" : "No"}\n`;
381
+ if (character.notes) {
382
+ promptText += `- Notes: ${character.notes}\n`;
383
+ }
384
+ }
385
+ promptText += `\n---\n\nWhen voicing ${character.name}, embody these characteristics in dialogue and narration.`;
386
+ return {
387
+ messages: [
388
+ {
389
+ role: "user",
390
+ content: { type: "text", text: promptText },
391
+ },
392
+ ],
393
+ };
394
+ });
395
+ // ============================================================================
396
+ // PERSISTENCE RULES - Standalone reminder of state persistence best practices
397
+ // ============================================================================
398
+ server.registerPrompt("persistence-rules", {
399
+ description: "Get a reminder of state persistence best practices - call this when you need guidance on what to persist and when",
400
+ argsSchema: {},
401
+ }, async () => {
402
+ let promptText = `# DMCP State Persistence Rules\n\n`;
403
+ promptText += `The database IS the game state. Everything mentioned in narrative should exist in the database.\n\n`;
404
+ promptText += `## The Golden Rule\n`;
405
+ promptText += `**Persist BEFORE you narrate.** Create entities in the database before describing them to the player.\n\n`;
406
+ promptText += `## Entity Persistence Guide\n\n`;
407
+ promptText += `### Characters (NPCs)\n`;
408
+ promptText += `**Trigger**: Any named character mentioned in dialogue or description\n`;
409
+ promptText += `**Tool**: \`create_character\` with \`isPlayer: false\`\n`;
410
+ promptText += `**When**: IMMEDIATELY when you decide to introduce them, BEFORE their first line of dialogue\n`;
411
+ promptText += `**Include**: name, initial location, basic attributes, notes about their role\n`;
412
+ promptText += `**Example flow**:\n`;
413
+ promptText += `1. Decide to introduce a blacksmith named Greta\n`;
414
+ promptText += `2. Call \`create_character\` for Greta\n`;
415
+ promptText += `3. THEN narrate: "A burly woman looks up from her anvil..."\n\n`;
416
+ promptText += `### Locations\n`;
417
+ promptText += `**Trigger**: Any place the player visits, hears about, or could travel to\n`;
418
+ promptText += `**Tools**: \`create_location\` then \`connect_locations\`\n`;
419
+ promptText += `**When**: BEFORE describing what the player sees there\n`;
420
+ promptText += `**Include**: name, atmospheric description, notable features\n`;
421
+ promptText += `**Example flow**:\n`;
422
+ promptText += `1. Player says "I go to the tavern"\n`;
423
+ promptText += `2. Call \`create_location\` for "The Rusty Nail Tavern"\n`;
424
+ promptText += `3. Call \`connect_locations\` to link it to current location\n`;
425
+ promptText += `4. THEN narrate the tavern scene\n\n`;
426
+ promptText += `### Items\n`;
427
+ promptText += `**Trigger**: Any item mentioned that could be picked up, used, or is plot-relevant\n`;
428
+ promptText += `**Tools**: \`create_item\`, \`transfer_item\`\n`;
429
+ promptText += `**When**: When the item first appears in narrative\n`;
430
+ promptText += `**Include**: name, description, owner (character or location), properties\n`;
431
+ promptText += `**Example flow**:\n`;
432
+ promptText += `1. Shopkeeper offers a magic sword for sale\n`;
433
+ promptText += `2. Call \`create_item\` with owner = shopkeeper's character ID\n`;
434
+ promptText += `3. If player buys it, call \`transfer_item\` to player\n\n`;
435
+ promptText += `### Relationships\n`;
436
+ promptText += `**Trigger**: NPC shows attitude toward player, alliances form, enmities established\n`;
437
+ promptText += `**Tools**: \`create_relationship\`, \`update_relationship_value\`\n`;
438
+ promptText += `**When**: When feelings/attitudes are expressed or implied\n`;
439
+ promptText += `**Include**: source, target, type (attitude, bond, rivalry), value (-100 to 100), label\n`;
440
+ promptText += `**Example flow**:\n`;
441
+ promptText += `1. NPC thanks player warmly for help\n`;
442
+ promptText += `2. Call \`create_relationship\` or \`update_relationship_value\` with positive delta\n`;
443
+ promptText += `3. Consider value ranges: -100 (hatred) to 0 (neutral) to +100 (devotion)\n\n`;
444
+ promptText += `### Quests\n`;
445
+ promptText += `**Trigger**: Player receives mission, discovers objective, or sets personal goal\n`;
446
+ promptText += `**Tools**: \`create_quest\`, \`modify_objectives\`\n`;
447
+ promptText += `**When**: When quest objectives become clear\n`;
448
+ promptText += `**Include**: name, description, clear trackable objectives\n`;
449
+ promptText += `**Example flow**:\n`;
450
+ promptText += `1. Village elder asks player to clear the mine\n`;
451
+ promptText += `2. Call \`create_quest\` with objectives like "Defeat the goblin chief", "Report back"\n`;
452
+ promptText += `3. When player defeats chief, call \`modify_objectives\` to mark complete\n\n`;
453
+ promptText += `### Secrets\n`;
454
+ promptText += `**Trigger**: Hidden information exists, even if revealed immediately\n`;
455
+ promptText += `**Tools**: \`create_secret\`, \`modify_secret_visibility\`, \`add_clue\`\n`;
456
+ promptText += `**When**: When you introduce information that could be discovered\n`;
457
+ promptText += `**Include**: name (DM reference), description (the actual secret), category, clues\n`;
458
+ promptText += `**Example flow**:\n`;
459
+ promptText += `1. You decide the innkeeper is secretly a spy\n`;
460
+ promptText += `2. Call \`create_secret\` with this information\n`;
461
+ promptText += `3. Add clues as you drop hints\n`;
462
+ promptText += `4. Call \`modify_secret_visibility\` when player discovers it\n\n`;
463
+ promptText += `### Narrative Events\n`;
464
+ promptText += `**Trigger**: EVERY significant narrative beat\n`;
465
+ promptText += `**Tool**: \`log_event\`\n`;
466
+ promptText += `**When**: After each narrative response, or during for long sequences\n`;
467
+ promptText += `**Event types**: dialogue, action, discovery, combat, travel, decision, scene_transition\n`;
468
+ promptText += `**Include**: eventType, content (what happened), optional metadata\n`;
469
+ promptText += `**Example**: After player negotiates with a merchant, log a "dialogue" event summarizing the exchange\n\n`;
470
+ promptText += `## Context Preservation\n\n`;
471
+ promptText += `### Regular Snapshots\n`;
472
+ promptText += `- Call \`check_context_freshness\` every 5-10 narrative turns\n`;
473
+ promptText += `- Call \`save_context_snapshot\` after significant story beats\n`;
474
+ promptText += `- Never let more than 10-15 turns pass without a snapshot\n\n`;
475
+ promptText += `### End of Session\n`;
476
+ promptText += `- Use the \`save-game-checklist\` prompt to verify all state is persisted\n`;
477
+ promptText += `- Call \`prepare_pause\` then \`save_pause_state\` with detailed context\n\n`;
478
+ promptText += `## Post-Response Checklist\n`;
479
+ promptText += `After EVERY narrative response, mentally verify:\n`;
480
+ promptText += `- [ ] Did I introduce anyone new? → \`create_character\`\n`;
481
+ promptText += `- [ ] Did I describe a new place? → \`create_location\` + \`connect_locations\`\n`;
482
+ promptText += `- [ ] Did items appear or change hands? → \`create_item\` / \`transfer_item\`\n`;
483
+ promptText += `- [ ] Did relationships change? → \`create_relationship\` / \`update_relationship_value\`\n`;
484
+ promptText += `- [ ] Did anyone learn something new? → \`create_secret\` / \`modify_secret_visibility\`\n`;
485
+ promptText += `- [ ] Did quest progress happen? → \`modify_objectives\`\n`;
486
+ promptText += `- [ ] What happened narratively? → \`log_event\`\n`;
487
+ return {
488
+ messages: [
489
+ {
490
+ role: "user",
491
+ content: { type: "text", text: promptText },
492
+ },
493
+ ],
494
+ };
495
+ });
496
+ // ============================================================================
497
+ // SAVE GAME CHECKLIST - Thorough pre-save verification
498
+ // ============================================================================
499
+ server.registerPrompt("save-game-checklist", {
500
+ description: "Comprehensive checklist for properly saving/pausing a game - ensures all entities are persisted",
501
+ argsSchema: {
502
+ gameId: z.string().describe("The game ID to save"),
503
+ },
504
+ }, async ({ gameId }) => {
505
+ const game = gameTools.loadGame(gameId);
506
+ if (!game) {
507
+ return {
508
+ messages: [
509
+ {
510
+ role: "user",
511
+ content: {
512
+ type: "text",
513
+ text: `Error: Game ${gameId} not found.`,
514
+ },
515
+ },
516
+ ],
517
+ };
518
+ }
519
+ // Gather current state
520
+ const characters = characterTools.listCharacters(gameId);
521
+ const locations = worldTools.listLocations(gameId);
522
+ const quests = questTools.listQuests(gameId);
523
+ const relationships = relationshipTools.listRelationships(gameId);
524
+ const recentEvents = narrativeTools.getHistory(gameId, { limit: 20 });
525
+ // Build character and location name lists for reference
526
+ const _characterNames = characters.map(c => c.name.toLowerCase());
527
+ const _locationNames = locations.map(l => l.name.toLowerCase());
528
+ let promptText = `# Save Game Checklist: ${game.name}\n\n`;
529
+ promptText += `Before pausing the game, work through this checklist to ensure all game state is properly persisted.\n\n`;
530
+ // ========================================
531
+ // SECTION 1: Review Recent Narrative
532
+ // ========================================
533
+ promptText += `## 1. Review Recent Narrative\n\n`;
534
+ promptText += `Examine the recent narrative events below. For each event, verify that all mentioned entities exist in the database.\n\n`;
535
+ if (recentEvents.length > 0) {
536
+ promptText += `### Recent Events (${recentEvents.length})\n`;
537
+ promptText += `\`\`\`\n`;
538
+ for (const event of recentEvents.slice(0, 10)) {
539
+ promptText += `[${event.eventType}] ${event.content.slice(0, 200)}${event.content.length > 200 ? "..." : ""}\n\n`;
540
+ }
541
+ promptText += `\`\`\`\n\n`;
542
+ }
543
+ else {
544
+ promptText += `*No recent narrative events found.*\n\n`;
545
+ }
546
+ // ========================================
547
+ // SECTION 2: Character Verification
548
+ // ========================================
549
+ promptText += `## 2. Character Verification\n\n`;
550
+ promptText += `**Current Characters (${characters.length}):**\n`;
551
+ for (const char of characters) {
552
+ const status = char.isPlayer ? "PC" : "NPC";
553
+ promptText += `- ${char.name} (${status}) - HP: ${char.status.health}/${char.status.maxHealth}`;
554
+ if (char.status.conditions.length > 0) {
555
+ promptText += ` - Conditions: ${char.status.conditions.join(", ")}`;
556
+ }
557
+ promptText += `\n`;
558
+ }
559
+ promptText += `\n`;
560
+ promptText += `### Verify:\n`;
561
+ promptText += `- [ ] All NPCs mentioned in recent dialogue/narrative are created\n`;
562
+ promptText += `- [ ] Character health reflects any damage/healing from recent events\n`;
563
+ promptText += `- [ ] Status conditions (poisoned, stunned, etc.) are applied/removed\n`;
564
+ promptText += `- [ ] Character locations are up to date\n`;
565
+ promptText += `- [ ] XP/level changes are recorded\n`;
566
+ promptText += `- [ ] Any new skills or abilities are added\n\n`;
567
+ // ========================================
568
+ // SECTION 3: Location Verification
569
+ // ========================================
570
+ promptText += `## 3. Location Verification\n\n`;
571
+ promptText += `**Current Locations (${locations.length}):**\n`;
572
+ for (const loc of locations) {
573
+ const exits = loc.properties.exits?.length || 0;
574
+ promptText += `- ${loc.name} (${exits} exits)\n`;
575
+ }
576
+ promptText += `\n`;
577
+ promptText += `### Verify:\n`;
578
+ promptText += `- [ ] All visited locations are created with descriptions\n`;
579
+ promptText += `- [ ] Location connections (exits) are properly set up\n`;
580
+ promptText += `- [ ] Items left at locations are recorded\n`;
581
+ promptText += `- [ ] Location atmosphere/state changes are updated\n\n`;
582
+ // ========================================
583
+ // SECTION 4: Quest & Objective Tracking
584
+ // ========================================
585
+ promptText += `## 4. Quest & Objective Tracking\n\n`;
586
+ const activeQuests = quests.filter(q => q.status === "active");
587
+ const _completedQuests = quests.filter(q => q.status === "completed");
588
+ promptText += `**Active Quests (${activeQuests.length}):**\n`;
589
+ for (const quest of activeQuests) {
590
+ const completed = quest.objectives.filter(o => o.completed).length;
591
+ const total = quest.objectives.length;
592
+ promptText += `- ${quest.name} (${completed}/${total} objectives)\n`;
593
+ for (const obj of quest.objectives) {
594
+ promptText += ` ${obj.completed ? "✅" : "⬜"} ${obj.description}\n`;
595
+ }
596
+ }
597
+ promptText += `\n`;
598
+ promptText += `### Verify:\n`;
599
+ promptText += `- [ ] Any new quests/missions mentioned are created\n`;
600
+ promptText += `- [ ] Completed objectives are marked as complete\n`;
601
+ promptText += `- [ ] Failed quests are marked as failed\n`;
602
+ promptText += `- [ ] New objectives discovered are added\n`;
603
+ promptText += `- [ ] Quest rewards given are recorded\n\n`;
604
+ // ========================================
605
+ // SECTION 5: Relationships
606
+ // ========================================
607
+ promptText += `## 5. Relationship Updates\n\n`;
608
+ promptText += `**Current Relationships (${relationships.length}):**\n`;
609
+ for (const rel of relationships.slice(0, 10)) {
610
+ promptText += `- ${rel.sourceId} → ${rel.targetId}: ${rel.relationshipType} (${rel.value}, ${rel.label || "no label"})\n`;
611
+ }
612
+ if (relationships.length > 10) {
613
+ promptText += ` ... and ${relationships.length - 10} more\n`;
614
+ }
615
+ promptText += `\n`;
616
+ promptText += `### Verify:\n`;
617
+ promptText += `- [ ] NPC attitudes changed by recent interactions are updated\n`;
618
+ promptText += `- [ ] New alliances or enmities are recorded\n`;
619
+ promptText += `- [ ] Faction relationships affected by player actions are updated\n`;
620
+ promptText += `- [ ] Romantic/friendship developments are tracked\n\n`;
621
+ // ========================================
622
+ // SECTION 6: Items & Inventory
623
+ // ========================================
624
+ promptText += `## 6. Items & Inventory\n\n`;
625
+ promptText += `### Verify:\n`;
626
+ promptText += `- [ ] Items given to players are added to their inventory\n`;
627
+ promptText += `- [ ] Items used/consumed are removed or updated\n`;
628
+ promptText += `- [ ] Loot from combat is distributed\n`;
629
+ promptText += `- [ ] Gold/currency changes are recorded\n`;
630
+ promptText += `- [ ] Items dropped at locations are transferred\n\n`;
631
+ // ========================================
632
+ // SECTION 7: World State & Time
633
+ // ========================================
634
+ promptText += `## 7. World State & Time\n\n`;
635
+ promptText += `### Verify:\n`;
636
+ promptText += `- [ ] In-game time has been advanced appropriately\n`;
637
+ promptText += `- [ ] Scheduled events are set for future triggers\n`;
638
+ promptText += `- [ ] Timers/countdowns are updated\n`;
639
+ promptText += `- [ ] World changes from player actions are reflected\n\n`;
640
+ // ========================================
641
+ // SECTION 8: Secrets & Knowledge
642
+ // ========================================
643
+ promptText += `## 8. Secrets & Knowledge\n\n`;
644
+ promptText += `### Verify:\n`;
645
+ promptText += `- [ ] Secrets discovered by players are revealed to them\n`;
646
+ promptText += `- [ ] Clues found are added to relevant secrets\n`;
647
+ promptText += `- [ ] New secrets introduced are created\n`;
648
+ promptText += `- [ ] Character knowledge is updated\n\n`;
649
+ // ========================================
650
+ // SECTION 9: Save Pause State
651
+ // ========================================
652
+ promptText += `## 9. Save DM Context\n\n`;
653
+ promptText += `After verifying the above, use \`save_pause_state\` to capture:\n\n`;
654
+ promptText += `### Required:\n`;
655
+ promptText += `- **currentScene**: Where are we in the story?\n`;
656
+ promptText += `- **immediateSituation**: What is happening RIGHT NOW?\n\n`;
657
+ promptText += `### Recommended:\n`;
658
+ promptText += `- **dmShortTermPlans**: What was about to happen next?\n`;
659
+ promptText += `- **dmLongTermPlans**: Major arcs being developed\n`;
660
+ promptText += `- **activeThreads**: Ongoing storylines and their status\n`;
661
+ promptText += `- **npcAttitudes**: Current NPC emotional states\n`;
662
+ promptText += `- **pendingPlayerAction**: What was the player considering?\n`;
663
+ promptText += `- **upcomingReveals**: Secrets close to being discovered\n`;
664
+ promptText += `- **sceneAtmosphere**: Mood, lighting, tension\n\n`;
665
+ // ========================================
666
+ // SECTION 10: Final Actions
667
+ // ========================================
668
+ promptText += `## 10. Final Actions\n\n`;
669
+ promptText += `1. **Log a summary event**: Use \`log_event\` with type "session_end" to record a brief summary\n`;
670
+ promptText += `2. **Generate recap note**: Use \`generate_recap\` to create a recap note\n`;
671
+ promptText += `3. **Save pause state**: Use \`save_pause_state\` with all relevant context\n`;
672
+ promptText += `4. **Confirm to player**: Let them know the game is saved and summarize where they left off\n\n`;
673
+ promptText += `---\n\n`;
674
+ promptText += `Work through each section, making tool calls to create/update any missing entities, then save the pause state.`;
675
+ return {
676
+ messages: [
677
+ {
678
+ role: "user",
679
+ content: { type: "text", text: promptText },
680
+ },
681
+ ],
682
+ };
683
+ });
684
+ }