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.
- package/LICENSE +22 -0
- package/README.md +79 -0
- package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
- package/dist/__tests__/engineVocabulary.test.js +147 -0
- package/dist/db/__tests__/connection.test.d.ts +1 -0
- package/dist/db/__tests__/connection.test.js +72 -0
- package/dist/db/__tests__/testDb.d.ts +33 -0
- package/dist/db/__tests__/testDb.js +41 -0
- package/dist/db/connection.d.ts +22 -0
- package/dist/db/connection.js +107 -0
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.js +725 -0
- package/dist/events/emitter.d.ts +22 -0
- package/dist/events/emitter.js +71 -0
- package/dist/http/server.d.ts +3 -0
- package/dist/http/server.js +649 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +92 -0
- package/dist/register/abilities.d.ts +2 -0
- package/dist/register/abilities.js +165 -0
- package/dist/register/audio.d.ts +2 -0
- package/dist/register/audio.js +326 -0
- package/dist/register/batch.d.ts +2 -0
- package/dist/register/batch.js +343 -0
- package/dist/register/character.d.ts +2 -0
- package/dist/register/character.js +324 -0
- package/dist/register/combat.d.ts +2 -0
- package/dist/register/combat.js +207 -0
- package/dist/register/core.d.ts +2 -0
- package/dist/register/core.js +1040 -0
- package/dist/register/display.d.ts +2 -0
- package/dist/register/display.js +263 -0
- package/dist/register/factions.d.ts +2 -0
- package/dist/register/factions.js +186 -0
- package/dist/register/images.d.ts +2 -0
- package/dist/register/images.js +400 -0
- package/dist/register/inventory.d.ts +2 -0
- package/dist/register/inventory.js +115 -0
- package/dist/register/mcp-prompts.d.ts +2 -0
- package/dist/register/mcp-prompts.js +684 -0
- package/dist/register/mcp-resources.d.ts +2 -0
- package/dist/register/mcp-resources.js +335 -0
- package/dist/register/narrative.d.ts +2 -0
- package/dist/register/narrative.js +242 -0
- package/dist/register/notes.d.ts +2 -0
- package/dist/register/notes.js +170 -0
- package/dist/register/pause.d.ts +2 -0
- package/dist/register/pause.js +580 -0
- package/dist/register/quests.d.ts +2 -0
- package/dist/register/quests.js +118 -0
- package/dist/register/relationships.d.ts +2 -0
- package/dist/register/relationships.js +147 -0
- package/dist/register/resources.d.ts +2 -0
- package/dist/register/resources.js +277 -0
- package/dist/register/secrets.d.ts +2 -0
- package/dist/register/secrets.js +192 -0
- package/dist/register/status.d.ts +2 -0
- package/dist/register/status.js +130 -0
- package/dist/register/tables.d.ts +2 -0
- package/dist/register/tables.js +146 -0
- package/dist/register/tags.d.ts +2 -0
- package/dist/register/tags.js +114 -0
- package/dist/register/time.d.ts +2 -0
- package/dist/register/time.js +281 -0
- package/dist/register/world.d.ts +2 -0
- package/dist/register/world.js +127 -0
- package/dist/schemas/index.d.ts +921 -0
- package/dist/schemas/index.js +121 -0
- package/dist/test-setup.d.ts +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/tools/__tests__/audio.test.d.ts +1 -0
- package/dist/tools/__tests__/audio.test.js +59 -0
- package/dist/tools/__tests__/conserved.test.d.ts +1 -0
- package/dist/tools/__tests__/conserved.test.js +488 -0
- package/dist/tools/__tests__/constraint.test.d.ts +1 -0
- package/dist/tools/__tests__/constraint.test.js +212 -0
- package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
- package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
- package/dist/tools/__tests__/images.test.d.ts +1 -0
- package/dist/tools/__tests__/images.test.js +59 -0
- package/dist/tools/__tests__/relationship.test.d.ts +1 -0
- package/dist/tools/__tests__/relationship.test.js +132 -0
- package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
- package/dist/tools/__tests__/resource-constraints.test.js +131 -0
- package/dist/tools/__tests__/resource.test.d.ts +1 -0
- package/dist/tools/__tests__/resource.test.js +190 -0
- package/dist/tools/__tests__/time.test.d.ts +1 -0
- package/dist/tools/__tests__/time.test.js +404 -0
- package/dist/tools/__tests__/timers.test.d.ts +1 -0
- package/dist/tools/__tests__/timers.test.js +426 -0
- package/dist/tools/__tests__/world.test.d.ts +1 -0
- package/dist/tools/__tests__/world.test.js +70 -0
- package/dist/tools/ability.d.ts +48 -0
- package/dist/tools/ability.js +238 -0
- package/dist/tools/audio.d.ts +24 -0
- package/dist/tools/audio.js +365 -0
- package/dist/tools/character.d.ts +70 -0
- package/dist/tools/character.js +309 -0
- package/dist/tools/combat.d.ts +13 -0
- package/dist/tools/combat.js +195 -0
- package/dist/tools/constraint.d.ts +132 -0
- package/dist/tools/constraint.js +269 -0
- package/dist/tools/dice.d.ts +23 -0
- package/dist/tools/dice.js +111 -0
- package/dist/tools/display.d.ts +120 -0
- package/dist/tools/display.js +528 -0
- package/dist/tools/faction.d.ts +61 -0
- package/dist/tools/faction.js +269 -0
- package/dist/tools/game.d.ts +96 -0
- package/dist/tools/game.js +526 -0
- package/dist/tools/image-prompt.d.ts +49 -0
- package/dist/tools/image-prompt.js +479 -0
- package/dist/tools/images.d.ts +47 -0
- package/dist/tools/images.js +449 -0
- package/dist/tools/inventory.d.ts +20 -0
- package/dist/tools/inventory.js +145 -0
- package/dist/tools/narrative.d.ts +58 -0
- package/dist/tools/narrative.js +237 -0
- package/dist/tools/notes.d.ts +41 -0
- package/dist/tools/notes.js +220 -0
- package/dist/tools/pause.d.ts +110 -0
- package/dist/tools/pause.js +1254 -0
- package/dist/tools/quest.d.ts +34 -0
- package/dist/tools/quest.js +164 -0
- package/dist/tools/relationship.d.ts +74 -0
- package/dist/tools/relationship.js +324 -0
- package/dist/tools/resource.d.ts +93 -0
- package/dist/tools/resource.js +374 -0
- package/dist/tools/rules.d.ts +4 -0
- package/dist/tools/rules.js +30 -0
- package/dist/tools/secrets.d.ts +49 -0
- package/dist/tools/secrets.js +195 -0
- package/dist/tools/status.d.ts +36 -0
- package/dist/tools/status.js +218 -0
- package/dist/tools/tables.d.ts +33 -0
- package/dist/tools/tables.js +209 -0
- package/dist/tools/tags.d.ts +52 -0
- package/dist/tools/tags.js +176 -0
- package/dist/tools/time.d.ts +33 -0
- package/dist/tools/time.js +276 -0
- package/dist/tools/timers.d.ts +41 -0
- package/dist/tools/timers.js +215 -0
- package/dist/tools/world.d.ts +78 -0
- package/dist/tools/world.js +331 -0
- package/dist/types/index.d.ts +969 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/__tests__/json.test.d.ts +1 -0
- package/dist/utils/__tests__/json.test.js +55 -0
- package/dist/utils/__tests__/validation.test.d.ts +1 -0
- package/dist/utils/__tests__/validation.test.js +90 -0
- package/dist/utils/errors.d.ts +44 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/json.d.ts +9 -0
- package/dist/utils/json.js +23 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/output-schemas.d.ts +594 -0
- package/dist/utils/output-schemas.js +331 -0
- package/dist/utils/tool-annotations.d.ts +147 -0
- package/dist/utils/tool-annotations.js +98 -0
- package/dist/utils/validation.d.ts +34 -0
- package/dist/utils/validation.js +52 -0
- package/dist/utils/verbosity.d.ts +57 -0
- package/dist/utils/verbosity.js +67 -0
- package/dist/utils/webui.d.ts +20 -0
- package/dist/utils/webui.js +35 -0
- package/package.json +75 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as pauseTools from "../tools/pause.js";
|
|
3
|
+
import { LIMITS } from "../utils/validation.js";
|
|
4
|
+
import { ANNOTATIONS } from "../utils/tool-annotations.js";
|
|
5
|
+
// Zod schemas for nested types
|
|
6
|
+
const NarrativeThreadSchema = z.object({
|
|
7
|
+
name: z.string().max(LIMITS.NAME_MAX).describe("Thread name/label"),
|
|
8
|
+
description: z.string().max(LIMITS.DESCRIPTION_MAX).describe("What this thread is about"),
|
|
9
|
+
status: z
|
|
10
|
+
.enum(["active", "background", "climax", "resolving"])
|
|
11
|
+
.describe("Thread status"),
|
|
12
|
+
urgency: z.enum(["low", "medium", "high", "critical"]).describe("Urgency level"),
|
|
13
|
+
involvedCharacterIds: z.array(z.string().max(100)).max(LIMITS.ARRAY_MAX).optional(),
|
|
14
|
+
involvedLocationIds: z.array(z.string().max(100)).max(LIMITS.ARRAY_MAX).optional(),
|
|
15
|
+
relatedQuestId: z.string().max(100).optional(),
|
|
16
|
+
notes: z.string().max(LIMITS.DESCRIPTION_MAX).optional(),
|
|
17
|
+
});
|
|
18
|
+
const ActiveConversationSchema = z.object({
|
|
19
|
+
npcId: z.string().max(100).describe("Character ID of NPC"),
|
|
20
|
+
topic: z.string().max(LIMITS.NAME_MAX).describe("What's being discussed"),
|
|
21
|
+
npcEmotionalState: z.string().max(LIMITS.NAME_MAX).describe("How they're feeling"),
|
|
22
|
+
lastNpcStatement: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("What they just said"),
|
|
23
|
+
playerIntent: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("What player seems to want"),
|
|
24
|
+
});
|
|
25
|
+
export function registerPauseTools(server) {
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// PAUSE PREPARATION
|
|
28
|
+
// ============================================================================
|
|
29
|
+
server.registerTool("prepare_pause", {
|
|
30
|
+
description: `Prepare to pause the game. Returns a checklist of context to save and current game state.
|
|
31
|
+
|
|
32
|
+
WHEN TO CALL: Before ending a game or when the player says they need to stop.
|
|
33
|
+
|
|
34
|
+
This tool returns:
|
|
35
|
+
- Current game state summary
|
|
36
|
+
- Checklist of ephemeral context that should be saved
|
|
37
|
+
- Any existing pause state
|
|
38
|
+
- Instructions for what to save
|
|
39
|
+
|
|
40
|
+
After calling this, use save_pause_state to persist your context.`,
|
|
41
|
+
inputSchema: {
|
|
42
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
43
|
+
},
|
|
44
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
45
|
+
}, async ({ gameId }) => {
|
|
46
|
+
const checklist = pauseTools.preparePause(gameId);
|
|
47
|
+
if (!checklist) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: `Game ${gameId} not found` }],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(checklist, null, 2) }],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// SAVE PAUSE STATE
|
|
59
|
+
// ============================================================================
|
|
60
|
+
server.registerTool("save_pause_state", {
|
|
61
|
+
description: `Save your context for seamless game resumption. Call this before ending a game.
|
|
62
|
+
|
|
63
|
+
REQUIRED FIELDS:
|
|
64
|
+
- currentScene: Where we are in the story
|
|
65
|
+
- immediateSituation: What's happening RIGHT NOW (be specific!)
|
|
66
|
+
|
|
67
|
+
The more detail you provide, the smoother the resume will be for the next DM
|
|
68
|
+
(which might be you in a new context window, or a different model entirely).
|
|
69
|
+
|
|
70
|
+
Write as if briefing a replacement DM who's taking over mid-game.`,
|
|
71
|
+
inputSchema: {
|
|
72
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
73
|
+
// Required
|
|
74
|
+
currentScene: z
|
|
75
|
+
.string()
|
|
76
|
+
.max(LIMITS.CONTENT_MAX)
|
|
77
|
+
.describe("Description of where we are in the story - the scene, location, narrative moment"),
|
|
78
|
+
immediateSituation: z
|
|
79
|
+
.string()
|
|
80
|
+
.max(LIMITS.CONTENT_MAX)
|
|
81
|
+
.describe("What is happening RIGHT NOW - the exact moment we're pausing at. Be specific!"),
|
|
82
|
+
// Scene context
|
|
83
|
+
sceneAtmosphere: z
|
|
84
|
+
.string()
|
|
85
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Mood, lighting, sounds, emotional tension"),
|
|
88
|
+
recentTone: z
|
|
89
|
+
.string()
|
|
90
|
+
.max(LIMITS.NAME_MAX)
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Recent narrative tone (tense, comedic, romantic, etc.)"),
|
|
93
|
+
// Player interaction
|
|
94
|
+
pendingPlayerAction: z
|
|
95
|
+
.string()
|
|
96
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("What action was the player about to take?"),
|
|
99
|
+
awaitingResponseTo: z
|
|
100
|
+
.string()
|
|
101
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("What question/prompt awaits player response?"),
|
|
104
|
+
presentedChoices: z
|
|
105
|
+
.array(z.string().max(LIMITS.DESCRIPTION_MAX))
|
|
106
|
+
.max(LIMITS.ARRAY_MAX)
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Formal choices presented to player"),
|
|
109
|
+
// Player context
|
|
110
|
+
playerApparentGoals: z
|
|
111
|
+
.string()
|
|
112
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("What the player seems to be trying to accomplish"),
|
|
115
|
+
unresolvedHooks: z
|
|
116
|
+
.array(z.string().max(LIMITS.DESCRIPTION_MAX))
|
|
117
|
+
.max(LIMITS.ARRAY_MAX)
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Plot hooks player noticed but hasn't pursued"),
|
|
120
|
+
// Narrative threads
|
|
121
|
+
activeThreads: z
|
|
122
|
+
.array(NarrativeThreadSchema)
|
|
123
|
+
.max(LIMITS.ARRAY_MAX)
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Ongoing storylines, investigations, subplots"),
|
|
126
|
+
// DM notes
|
|
127
|
+
dmShortTermPlans: z
|
|
128
|
+
.string()
|
|
129
|
+
.max(LIMITS.CONTENT_MAX)
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("What was about to happen next? Planned encounters/reveals?"),
|
|
132
|
+
dmLongTermPlans: z
|
|
133
|
+
.string()
|
|
134
|
+
.max(LIMITS.CONTENT_MAX)
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("Major plot arcs being developed"),
|
|
137
|
+
upcomingReveals: z
|
|
138
|
+
.array(z.string().max(LIMITS.DESCRIPTION_MAX))
|
|
139
|
+
.max(LIMITS.ARRAY_MAX)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Secrets close to being discovered"),
|
|
142
|
+
// NPC state
|
|
143
|
+
npcAttitudes: z
|
|
144
|
+
.record(z.string(), z.string().max(LIMITS.NAME_MAX))
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("NPC emotional states/attitudes (characterId -> disposition)"),
|
|
147
|
+
activeConversations: z
|
|
148
|
+
.array(ActiveConversationSchema)
|
|
149
|
+
.max(LIMITS.ARRAY_MAX)
|
|
150
|
+
.optional()
|
|
151
|
+
.describe("Ongoing conversations and where they left off"),
|
|
152
|
+
// Metadata
|
|
153
|
+
pauseReason: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("Why is the game pausing?"),
|
|
154
|
+
modelUsed: z
|
|
155
|
+
.string()
|
|
156
|
+
.max(LIMITS.NAME_MAX)
|
|
157
|
+
.optional()
|
|
158
|
+
.describe("Which model/agent is saving this state"),
|
|
159
|
+
},
|
|
160
|
+
annotations: ANNOTATIONS.CREATE,
|
|
161
|
+
}, async (params) => {
|
|
162
|
+
const pauseState = pauseTools.savePauseState(params);
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify({
|
|
168
|
+
saved: true,
|
|
169
|
+
pauseStateId: pauseState.id,
|
|
170
|
+
message: "Context saved successfully. Game can be resumed seamlessly using get_resume_context.",
|
|
171
|
+
savedFields: {
|
|
172
|
+
required: {
|
|
173
|
+
currentScene: true,
|
|
174
|
+
immediateSituation: true,
|
|
175
|
+
},
|
|
176
|
+
optional: {
|
|
177
|
+
sceneAtmosphere: !!pauseState.sceneAtmosphere,
|
|
178
|
+
pendingPlayerAction: !!pauseState.pendingPlayerAction,
|
|
179
|
+
awaitingResponseTo: !!pauseState.awaitingResponseTo,
|
|
180
|
+
presentedChoices: (pauseState.presentedChoices?.length || 0) > 0,
|
|
181
|
+
activeThreads: pauseState.activeThreads.length > 0,
|
|
182
|
+
dmShortTermPlans: !!pauseState.dmShortTermPlans,
|
|
183
|
+
dmLongTermPlans: !!pauseState.dmLongTermPlans,
|
|
184
|
+
upcomingReveals: pauseState.upcomingReveals.length > 0,
|
|
185
|
+
npcAttitudes: Object.keys(pauseState.npcAttitudes).length > 0,
|
|
186
|
+
activeConversations: pauseState.activeConversations.length > 0,
|
|
187
|
+
recentTone: !!pauseState.recentTone,
|
|
188
|
+
playerApparentGoals: !!pauseState.playerApparentGoals,
|
|
189
|
+
unresolvedHooks: pauseState.unresolvedHooks.length > 0,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
}, null, 2),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// GET PAUSE STATE
|
|
199
|
+
// ============================================================================
|
|
200
|
+
server.registerTool("get_pause_state", {
|
|
201
|
+
description: "Get the saved pause state for a game (if any). Use this to check what context was preserved.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
204
|
+
},
|
|
205
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
206
|
+
}, async ({ gameId }) => {
|
|
207
|
+
const pauseState = pauseTools.getPauseState(gameId);
|
|
208
|
+
if (!pauseState) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify({
|
|
214
|
+
hasPauseState: false,
|
|
215
|
+
message: "No pause state saved for this game. If resuming, rely on narrative_events and game state.",
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: "text", text: JSON.stringify(pauseState, null, 2) }],
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// GET RESUME CONTEXT
|
|
227
|
+
// ============================================================================
|
|
228
|
+
server.registerTool("get_resume_context", {
|
|
229
|
+
description: `Get everything needed to resume a paused game seamlessly.
|
|
230
|
+
|
|
231
|
+
WHEN TO CALL: At the start of a game when continuing a paused game.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
- Full pause state (DM context, scene, plans)
|
|
235
|
+
- Current game state (characters, quests, location)
|
|
236
|
+
- Recent narrative events
|
|
237
|
+
- A ready-to-use resume briefing/prompt
|
|
238
|
+
- Warnings about things that need attention (active combat, etc.)
|
|
239
|
+
|
|
240
|
+
Use this to get up to speed quickly and resume exactly where the game left off.`,
|
|
241
|
+
inputSchema: {
|
|
242
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
243
|
+
},
|
|
244
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
245
|
+
}, async ({ gameId }) => {
|
|
246
|
+
const context = pauseTools.getResumeContext(gameId);
|
|
247
|
+
if (!context) {
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: `No pause state found for game ${gameId}. Use load_game and get_history instead.`,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
isError: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Return the formatted resume prompt prominently
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: "text",
|
|
263
|
+
text: context.resumePrompt +
|
|
264
|
+
"\n\n" +
|
|
265
|
+
"═══════════════════════════════════════════════════════════════════════════════\n" +
|
|
266
|
+
" FULL DATA (JSON)\n" +
|
|
267
|
+
"═══════════════════════════════════════════════════════════════════════════════\n\n" +
|
|
268
|
+
JSON.stringify({
|
|
269
|
+
pauseState: context.pauseState,
|
|
270
|
+
gameState: context.gameState,
|
|
271
|
+
warnings: context.warnings,
|
|
272
|
+
}, null, 2),
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// CONTEXT SNAPSHOT (Lightweight incremental save)
|
|
279
|
+
// ============================================================================
|
|
280
|
+
server.registerTool("save_context_snapshot", {
|
|
281
|
+
description: `Quick lightweight context save - use this DURING play to preserve context incrementally.
|
|
282
|
+
|
|
283
|
+
CRITICAL: Call this regularly to prevent context loss:
|
|
284
|
+
- After EVERY significant story beat
|
|
285
|
+
- After important NPC interactions
|
|
286
|
+
- When player makes major decisions
|
|
287
|
+
- Every 10-15 turns as a safety net
|
|
288
|
+
- Before any potentially long narrative sequence
|
|
289
|
+
|
|
290
|
+
This is faster than full save_pause_state but captures essential state.
|
|
291
|
+
For end-of-session, use prepare_pause + save_pause_state instead.`,
|
|
292
|
+
inputSchema: {
|
|
293
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
294
|
+
situation: z
|
|
295
|
+
.string()
|
|
296
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
297
|
+
.describe("Brief description of current situation (what's happening right now)"),
|
|
298
|
+
notes: z.string().max(LIMITS.CONTENT_MAX).optional().describe("Any additional context to preserve"),
|
|
299
|
+
npcMood: z
|
|
300
|
+
.record(z.string(), z.string().max(LIMITS.NAME_MAX))
|
|
301
|
+
.optional()
|
|
302
|
+
.describe("Current NPC attitudes (characterId -> mood)"),
|
|
303
|
+
playerIntent: z
|
|
304
|
+
.string()
|
|
305
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("What the player seems to be trying to do"),
|
|
308
|
+
},
|
|
309
|
+
annotations: ANNOTATIONS.CREATE,
|
|
310
|
+
}, async (params) => {
|
|
311
|
+
const result = pauseTools.saveContextSnapshot(params);
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// CHECK CONTEXT FRESHNESS
|
|
318
|
+
// ============================================================================
|
|
319
|
+
server.registerTool("check_context_freshness", {
|
|
320
|
+
description: `Check if context needs to be saved. Returns a reminder if it's been too long since last save.
|
|
321
|
+
|
|
322
|
+
IMPORTANT: Call this every 5-10 narrative turns to ensure context is being preserved. If stale, immediately call save_context_snapshot.`,
|
|
323
|
+
inputSchema: {
|
|
324
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
325
|
+
},
|
|
326
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
327
|
+
}, async ({ gameId }) => {
|
|
328
|
+
const result = pauseTools.checkContextFreshness(gameId);
|
|
329
|
+
return {
|
|
330
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// CLEAR PAUSE STATE
|
|
335
|
+
// ============================================================================
|
|
336
|
+
server.registerTool("clear_pause_state", {
|
|
337
|
+
description: "Clear the saved pause state for a game. Use after successfully resuming to start fresh.",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
340
|
+
},
|
|
341
|
+
annotations: ANNOTATIONS.DESTRUCTIVE,
|
|
342
|
+
}, async ({ gameId }) => {
|
|
343
|
+
const deleted = pauseTools.deletePauseState(gameId);
|
|
344
|
+
return {
|
|
345
|
+
content: [
|
|
346
|
+
{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: deleted
|
|
349
|
+
? "Pause state cleared. The game can now accumulate fresh context."
|
|
350
|
+
: "No pause state to clear.",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// EXTERNAL UPDATES - Multi-agent collaboration
|
|
357
|
+
// ============================================================================
|
|
358
|
+
server.registerTool("push_external_update", {
|
|
359
|
+
description: `Push an update from an external agent into the game game.
|
|
360
|
+
|
|
361
|
+
USE CASE: External agents (research agents, worldbuilders, lore generators, etc.)
|
|
362
|
+
can push updates that the primary DM agent will receive and incorporate.
|
|
363
|
+
|
|
364
|
+
EXAMPLES:
|
|
365
|
+
- A research agent discovers detailed lore about a faction
|
|
366
|
+
- A worldbuilder generates NPC backstory
|
|
367
|
+
- A consistency checker flags a plot hole
|
|
368
|
+
- An atmosphere generator provides environmental details
|
|
369
|
+
|
|
370
|
+
The DM agent should periodically check for pending updates via get_pending_updates.`,
|
|
371
|
+
inputSchema: {
|
|
372
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
373
|
+
sourceAgent: z.string().max(LIMITS.NAME_MAX).describe("ID/name of the agent pushing this update"),
|
|
374
|
+
sourceDescription: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("Description of what this agent does"),
|
|
375
|
+
updateType: z
|
|
376
|
+
.string()
|
|
377
|
+
.max(100)
|
|
378
|
+
.describe("Type of update (e.g., 'lore', 'npc_backstory', 'world_event', 'item_details', 'plot_suggestion')"),
|
|
379
|
+
category: z.string().max(100).optional().describe("Optional category for organization"),
|
|
380
|
+
title: z.string().max(LIMITS.NAME_MAX).describe("Brief title/summary of the update"),
|
|
381
|
+
content: z.string().max(LIMITS.CONTENT_MAX).describe("The full content/information being pushed"),
|
|
382
|
+
structuredData: z
|
|
383
|
+
.record(z.string(), z.unknown())
|
|
384
|
+
.optional()
|
|
385
|
+
.describe("Optional structured data (JSON) for programmatic use"),
|
|
386
|
+
targetEntityId: z.string().max(100).optional().describe("If this relates to a specific entity, its ID"),
|
|
387
|
+
targetEntityType: z
|
|
388
|
+
.string()
|
|
389
|
+
.max(100)
|
|
390
|
+
.optional()
|
|
391
|
+
.describe("Type of target entity (character, location, item, quest, etc.)"),
|
|
392
|
+
priority: z
|
|
393
|
+
.enum(["low", "normal", "high", "urgent"])
|
|
394
|
+
.optional()
|
|
395
|
+
.describe("Priority level (default: normal). Urgent updates demand immediate DM attention."),
|
|
396
|
+
},
|
|
397
|
+
annotations: ANNOTATIONS.CREATE,
|
|
398
|
+
}, async (params) => {
|
|
399
|
+
const update = pauseTools.pushExternalUpdate(params);
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: JSON.stringify({
|
|
405
|
+
pushed: true,
|
|
406
|
+
updateId: update.id,
|
|
407
|
+
priority: update.priority,
|
|
408
|
+
message: `Update pushed successfully. The DM agent will see this when checking pending updates.`,
|
|
409
|
+
tip: update.priority === "urgent"
|
|
410
|
+
? "This urgent update will be highlighted for immediate DM attention."
|
|
411
|
+
: "The DM will see this in their pending updates queue.",
|
|
412
|
+
}, null, 2),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
server.registerTool("get_pending_updates", {
|
|
418
|
+
description: `Check for pending updates from external agents.
|
|
419
|
+
|
|
420
|
+
WHEN TO CALL:
|
|
421
|
+
- At the start of each scene/turn
|
|
422
|
+
- Before making major narrative decisions
|
|
423
|
+
- Periodically during long sessions
|
|
424
|
+
|
|
425
|
+
Returns all pending updates, prioritized by urgency.
|
|
426
|
+
Use acknowledge_update, apply_update, or reject_update to process them.`,
|
|
427
|
+
inputSchema: {
|
|
428
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
429
|
+
},
|
|
430
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
431
|
+
}, async ({ gameId }) => {
|
|
432
|
+
const result = pauseTools.getPendingUpdates(gameId);
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
435
|
+
};
|
|
436
|
+
});
|
|
437
|
+
server.registerTool("acknowledge_update", {
|
|
438
|
+
description: "Mark an external update as acknowledged (seen by DM). Use when you've read but haven't yet incorporated an update.",
|
|
439
|
+
inputSchema: {
|
|
440
|
+
updateId: z.string().max(100).describe("The update ID"),
|
|
441
|
+
},
|
|
442
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
443
|
+
}, async ({ updateId }) => {
|
|
444
|
+
const update = pauseTools.acknowledgeUpdate(updateId);
|
|
445
|
+
if (!update) {
|
|
446
|
+
return {
|
|
447
|
+
content: [{ type: "text", text: "Update not found" }],
|
|
448
|
+
isError: true,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
content: [{ type: "text", text: JSON.stringify(update, null, 2) }],
|
|
453
|
+
};
|
|
454
|
+
});
|
|
455
|
+
server.registerTool("apply_update", {
|
|
456
|
+
description: "Mark an external update as applied (incorporated into the narrative). Use when you've woven the update into the story.",
|
|
457
|
+
inputSchema: {
|
|
458
|
+
updateId: z.string().max(100).describe("The update ID"),
|
|
459
|
+
dmNotes: z
|
|
460
|
+
.string()
|
|
461
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
462
|
+
.optional()
|
|
463
|
+
.describe("Notes on how the update was incorporated"),
|
|
464
|
+
},
|
|
465
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
466
|
+
}, async ({ updateId, dmNotes }) => {
|
|
467
|
+
const update = pauseTools.applyUpdate(updateId, dmNotes);
|
|
468
|
+
if (!update) {
|
|
469
|
+
return {
|
|
470
|
+
content: [{ type: "text", text: "Update not found" }],
|
|
471
|
+
isError: true,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: JSON.stringify({
|
|
479
|
+
applied: true,
|
|
480
|
+
update,
|
|
481
|
+
message: "Update marked as applied. It will no longer appear in pending updates.",
|
|
482
|
+
}, null, 2),
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
server.registerTool("reject_update", {
|
|
488
|
+
description: "Reject an external update (not appropriate for the narrative). Use when an update doesn't fit the current story direction.",
|
|
489
|
+
inputSchema: {
|
|
490
|
+
updateId: z.string().max(100).describe("The update ID"),
|
|
491
|
+
dmNotes: z
|
|
492
|
+
.string()
|
|
493
|
+
.max(LIMITS.DESCRIPTION_MAX)
|
|
494
|
+
.optional()
|
|
495
|
+
.describe("Reason for rejection (helps external agents improve)"),
|
|
496
|
+
},
|
|
497
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
498
|
+
}, async ({ updateId, dmNotes }) => {
|
|
499
|
+
const update = pauseTools.rejectUpdate(updateId, dmNotes);
|
|
500
|
+
if (!update) {
|
|
501
|
+
return {
|
|
502
|
+
content: [{ type: "text", text: "Update not found" }],
|
|
503
|
+
isError: true,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
content: [
|
|
508
|
+
{
|
|
509
|
+
type: "text",
|
|
510
|
+
text: JSON.stringify({
|
|
511
|
+
rejected: true,
|
|
512
|
+
update,
|
|
513
|
+
message: "Update rejected. It will no longer appear in pending updates.",
|
|
514
|
+
}, null, 2),
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
};
|
|
518
|
+
});
|
|
519
|
+
server.registerTool("list_external_updates", {
|
|
520
|
+
description: "List all external updates for a game with optional status filter.",
|
|
521
|
+
inputSchema: {
|
|
522
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
523
|
+
status: z
|
|
524
|
+
.enum(["pending", "acknowledged", "applied", "rejected"])
|
|
525
|
+
.optional()
|
|
526
|
+
.describe("Filter by status"),
|
|
527
|
+
},
|
|
528
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
529
|
+
}, async ({ gameId, status }) => {
|
|
530
|
+
const updates = pauseTools.listExternalUpdates(gameId, status);
|
|
531
|
+
return {
|
|
532
|
+
content: [
|
|
533
|
+
{
|
|
534
|
+
type: "text",
|
|
535
|
+
text: JSON.stringify({
|
|
536
|
+
count: updates.length,
|
|
537
|
+
status: status || "all",
|
|
538
|
+
updates,
|
|
539
|
+
}, null, 2),
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
server.registerTool("get_external_update", {
|
|
545
|
+
description: "Get details of a specific external update by ID.",
|
|
546
|
+
inputSchema: {
|
|
547
|
+
updateId: z.string().max(100).describe("The update ID"),
|
|
548
|
+
},
|
|
549
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
550
|
+
}, async ({ updateId }) => {
|
|
551
|
+
const update = pauseTools.getExternalUpdate(updateId);
|
|
552
|
+
if (!update) {
|
|
553
|
+
return {
|
|
554
|
+
content: [{ type: "text", text: "Update not found" }],
|
|
555
|
+
isError: true,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
content: [{ type: "text", text: JSON.stringify(update, null, 2) }],
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
server.registerTool("delete_external_update", {
|
|
563
|
+
description: "Delete an external update entirely.",
|
|
564
|
+
inputSchema: {
|
|
565
|
+
updateId: z.string().max(100).describe("The update ID"),
|
|
566
|
+
},
|
|
567
|
+
annotations: ANNOTATIONS.DESTRUCTIVE,
|
|
568
|
+
}, async ({ updateId }) => {
|
|
569
|
+
const deleted = pauseTools.deleteExternalUpdate(updateId);
|
|
570
|
+
return {
|
|
571
|
+
content: [
|
|
572
|
+
{
|
|
573
|
+
type: "text",
|
|
574
|
+
text: deleted ? "Update deleted" : "Update not found",
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
isError: !deleted,
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
}
|