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,281 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as timeTools from "../tools/time.js";
|
|
3
|
+
import * as timerTools from "../tools/timers.js";
|
|
4
|
+
import { LIMITS } from "../utils/validation.js";
|
|
5
|
+
import { ANNOTATIONS } from "../utils/tool-annotations.js";
|
|
6
|
+
const gameDateTimeSchema = z.object({
|
|
7
|
+
year: z.number(),
|
|
8
|
+
month: z.number(),
|
|
9
|
+
day: z.number(),
|
|
10
|
+
hour: z.number(),
|
|
11
|
+
minute: z.number(),
|
|
12
|
+
});
|
|
13
|
+
// A declared, structured on-expiry consequence: when the event/timer
|
|
14
|
+
// expires, the server applies this delta to the named resource itself, in
|
|
15
|
+
// the same call, with no LLM action required. Opt-in -- omit entirely for
|
|
16
|
+
// the old passive-only behavior.
|
|
17
|
+
const expiryConsequenceSchema = z.object({
|
|
18
|
+
resourceId: z.string().max(100).describe("The resource to modify when this expires"),
|
|
19
|
+
delta: z.number().describe("Amount to add to the resource's value (negative to subtract)"),
|
|
20
|
+
});
|
|
21
|
+
export function registerTimeTools(server) {
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// TIME/CALENDAR TOOLS
|
|
24
|
+
// ============================================================================
|
|
25
|
+
server.registerTool("set_calendar", {
|
|
26
|
+
description: "Configure the calendar system for a game (months, days per month, hours per day, etc.)",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
29
|
+
config: z.object({
|
|
30
|
+
monthNames: z.array(z.string().max(100)).max(24).optional().describe("Names of months"),
|
|
31
|
+
daysPerMonth: z.array(z.number()).max(24).optional().describe("Days in each month"),
|
|
32
|
+
hoursPerDay: z.number().optional().describe("Hours per day (default: 24)"),
|
|
33
|
+
minutesPerHour: z.number().optional().describe("Minutes per hour (default: 60)"),
|
|
34
|
+
startYear: z.number().optional().describe("Starting year number"),
|
|
35
|
+
eraName: z.string().max(LIMITS.NAME_MAX).optional().describe("Name of the era/age"),
|
|
36
|
+
}).optional().describe("Calendar configuration (defaults to fantasy calendar)"),
|
|
37
|
+
currentTime: gameDateTimeSchema.optional().describe("Starting time"),
|
|
38
|
+
},
|
|
39
|
+
annotations: ANNOTATIONS.SET,
|
|
40
|
+
}, async ({ gameId, config, currentTime }) => {
|
|
41
|
+
const gameTime = timeTools.setCalendar(gameId, config || {}, currentTime);
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: JSON.stringify(gameTime, null, 2) }],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
server.registerTool("get_time", {
|
|
47
|
+
description: "Get the current in-game time",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
50
|
+
},
|
|
51
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
52
|
+
}, async ({ gameId }) => {
|
|
53
|
+
const gameTime = timeTools.getTime(gameId);
|
|
54
|
+
if (!gameTime) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: "No calendar set for this game. Use set_calendar first." }],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const formatted = timeTools.formatDateTime(gameTime.currentTime, gameTime.calendarConfig);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: JSON.stringify({ ...gameTime, formatted }, null, 2) }],
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
server.registerTool("set_time", {
|
|
66
|
+
description: "Set the current in-game time to a specific value",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
69
|
+
time: gameDateTimeSchema.describe("The time to set"),
|
|
70
|
+
},
|
|
71
|
+
annotations: ANNOTATIONS.SET,
|
|
72
|
+
}, async ({ gameId, time }) => {
|
|
73
|
+
const gameTime = timeTools.setTime(gameId, time);
|
|
74
|
+
if (!gameTime) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: "No calendar set for this game" }],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const formatted = timeTools.formatDateTime(gameTime.currentTime, gameTime.calendarConfig);
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: JSON.stringify({ ...gameTime, formatted }, null, 2) }],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
server.registerTool("advance_time", {
|
|
86
|
+
description: "Advance time by a duration. Any scheduled events whose trigger time is crossed fire, and their declared consequences (if any) are applied automatically -- no follow-up call needed. Check consequenceFailures for any that couldn't be applied (e.g. target resource missing); those events remain pending and are not silently marked handled.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
89
|
+
days: z.number().optional().describe("Days to advance"),
|
|
90
|
+
hours: z.number().optional().describe("Hours to advance"),
|
|
91
|
+
minutes: z.number().optional().describe("Minutes to advance"),
|
|
92
|
+
},
|
|
93
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
94
|
+
}, async ({ gameId, days, hours, minutes }) => {
|
|
95
|
+
const result = timeTools.advanceTime(gameId, { days, hours, minutes });
|
|
96
|
+
if (!result) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: "No calendar set for this game" }],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const gameTime = timeTools.getTime(gameId);
|
|
103
|
+
if (!gameTime) {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: "Failed to get time after advancing" }],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const formatted = timeTools.formatDateTime(result.newTime, gameTime.calendarConfig);
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: JSON.stringify({
|
|
114
|
+
...result,
|
|
115
|
+
formatted,
|
|
116
|
+
triggeredCount: result.triggeredEvents.length,
|
|
117
|
+
}, null, 2),
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
server.registerTool("schedule_event", {
|
|
122
|
+
description: "Schedule an event to trigger at a specific in-game time. Optionally attach a consequence -- a resource delta the server applies itself the moment the event fires, atomically, with no further action required.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
125
|
+
name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Event name"),
|
|
126
|
+
description: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("Event description"),
|
|
127
|
+
triggerTime: gameDateTimeSchema.describe("When the event should trigger"),
|
|
128
|
+
recurring: z.enum(["daily", "weekly", "monthly", "yearly"]).optional().describe("Recurrence pattern"),
|
|
129
|
+
metadata: z.record(z.string(), z.unknown()).optional().describe("Additional event data"),
|
|
130
|
+
consequence: expiryConsequenceSchema.optional().describe("Optional: a resource delta applied automatically when this event triggers"),
|
|
131
|
+
},
|
|
132
|
+
annotations: ANNOTATIONS.CREATE,
|
|
133
|
+
}, async (params) => {
|
|
134
|
+
const event = timeTools.scheduleEvent(params);
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: "text", text: JSON.stringify(event, null, 2) }],
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
server.registerTool("list_scheduled_events", {
|
|
140
|
+
description: "List upcoming scheduled events",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
143
|
+
includeTriggered: z.boolean().optional().describe("Include already-triggered events"),
|
|
144
|
+
},
|
|
145
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
146
|
+
}, async ({ gameId, includeTriggered }) => {
|
|
147
|
+
const events = timeTools.listScheduledEvents(gameId, includeTriggered);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: JSON.stringify(events, null, 2) }],
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
server.registerTool("cancel_event", {
|
|
153
|
+
description: "Cancel a scheduled event",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
eventId: z.string().max(100).describe("The event ID to cancel"),
|
|
156
|
+
},
|
|
157
|
+
annotations: ANNOTATIONS.DESTRUCTIVE,
|
|
158
|
+
}, async ({ eventId }) => {
|
|
159
|
+
const success = timeTools.cancelEvent(eventId);
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: success ? "Event cancelled" : "Event not found" }],
|
|
162
|
+
isError: !success,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// TIMER TOOLS
|
|
167
|
+
// ============================================================================
|
|
168
|
+
server.registerTool("create_timer", {
|
|
169
|
+
description: "Create a countdown, stopwatch, or segmented clock. Optionally attach a consequence -- a resource delta the server applies itself the moment the timer crosses its trigger value, atomically, with no further action required.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
172
|
+
name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Timer name (e.g., 'Doom Clock', 'Ritual Progress')"),
|
|
173
|
+
description: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("Timer description"),
|
|
174
|
+
timerType: z.enum(["countdown", "stopwatch", "clock"]).describe("Type: countdown (decreases), stopwatch (increases), clock (segmented like Blades in the Dark)"),
|
|
175
|
+
currentValue: z.number().optional().describe("Starting value"),
|
|
176
|
+
maxValue: z.number().optional().describe("Maximum value (for clocks: number of segments, default 6)"),
|
|
177
|
+
direction: z.enum(["up", "down"]).optional().describe("Direction of change"),
|
|
178
|
+
triggerAt: z.number().optional().describe("Value that triggers an event. If not provided, defaults to 0 for countdowns (direction 'down') or maxValue for stopwatches (direction 'up')"),
|
|
179
|
+
unit: z.string().max(100).optional().describe("Unit label (e.g., 'rounds', 'hours', 'segments')"),
|
|
180
|
+
visibleToPlayers: z.boolean().optional().describe("Whether players can see this timer"),
|
|
181
|
+
consequence: expiryConsequenceSchema.optional().describe("Optional: a resource delta applied automatically the moment this timer triggers"),
|
|
182
|
+
},
|
|
183
|
+
annotations: ANNOTATIONS.CREATE,
|
|
184
|
+
}, async (params) => {
|
|
185
|
+
const timer = timerTools.createTimer(params);
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: JSON.stringify(timer, null, 2) }],
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
server.registerTool("get_timer", {
|
|
191
|
+
description: "Get timer details",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
timerId: z.string().max(100).describe("The timer ID"),
|
|
194
|
+
},
|
|
195
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
196
|
+
}, async ({ timerId }) => {
|
|
197
|
+
const timer = timerTools.getTimer(timerId);
|
|
198
|
+
if (!timer) {
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: "Timer not found" }],
|
|
201
|
+
isError: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
content: [{ type: "text", text: JSON.stringify(timer, null, 2) }],
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
server.registerTool("update_timer", {
|
|
209
|
+
description: "Update timer settings",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
timerId: z.string().max(100).describe("The timer ID"),
|
|
212
|
+
name: z.string().max(LIMITS.NAME_MAX).optional().describe("New name"),
|
|
213
|
+
description: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("New description"),
|
|
214
|
+
maxValue: z.number().nullable().optional().describe("New max value"),
|
|
215
|
+
triggerAt: z.number().nullable().optional().describe("New trigger value"),
|
|
216
|
+
unit: z.string().max(100).optional().describe("New unit label"),
|
|
217
|
+
visibleToPlayers: z.boolean().optional().describe("Visibility to players"),
|
|
218
|
+
},
|
|
219
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
220
|
+
}, async ({ timerId, ...updates }) => {
|
|
221
|
+
const timer = timerTools.updateTimer(timerId, updates);
|
|
222
|
+
if (!timer) {
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: "text", text: "Timer not found" }],
|
|
225
|
+
isError: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: JSON.stringify(timer, null, 2) }],
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
server.registerTool("delete_timer", {
|
|
233
|
+
description: "Delete a timer",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
timerId: z.string().max(100).describe("The timer ID"),
|
|
236
|
+
},
|
|
237
|
+
annotations: ANNOTATIONS.DESTRUCTIVE,
|
|
238
|
+
}, async ({ timerId }) => {
|
|
239
|
+
const success = timerTools.deleteTimer(timerId);
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: "text", text: success ? "Timer deleted" : "Timer not found" }],
|
|
242
|
+
isError: !success,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
server.registerTool("list_timers", {
|
|
246
|
+
description: "List active timers in a game",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
249
|
+
includeTriggered: z.boolean().optional().describe("Include triggered timers"),
|
|
250
|
+
},
|
|
251
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
252
|
+
}, async ({ gameId, includeTriggered }) => {
|
|
253
|
+
const timers = timerTools.listTimers(gameId, includeTriggered);
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text: JSON.stringify(timers, null, 2) }],
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// MODIFY TIMER - CONSOLIDATED (replaces tick_timer + reset_timer)
|
|
260
|
+
// ============================================================================
|
|
261
|
+
server.registerTool("modify_timer", {
|
|
262
|
+
description: "Modify a timer's state. Use mode 'tick' to advance by amount, or 'reset' to reset to initial state. If ticking crosses the timer's trigger value and it has a declared consequence, that consequence is applied automatically in this same call (see consequenceApplied on the result).",
|
|
263
|
+
inputSchema: {
|
|
264
|
+
timerId: z.string().max(100).describe("The timer ID"),
|
|
265
|
+
mode: z.enum(["tick", "reset"]).describe("'tick' to advance/reduce, 'reset' to reset to initial state"),
|
|
266
|
+
amount: z.number().optional().describe("Amount to tick (only for mode 'tick', default: 1)"),
|
|
267
|
+
},
|
|
268
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
269
|
+
}, async ({ timerId, mode, amount }) => {
|
|
270
|
+
const result = timerTools.modifyTimerState(timerId, { mode, amount });
|
|
271
|
+
if (!result) {
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: "Timer not found" }],
|
|
274
|
+
isError: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as worldTools from "../tools/world.js";
|
|
3
|
+
import { imageGenSchema } from "../schemas/index.js";
|
|
4
|
+
import { LIMITS } from "../utils/validation.js";
|
|
5
|
+
import { ANNOTATIONS } from "../utils/tool-annotations.js";
|
|
6
|
+
export function registerWorldTools(server) {
|
|
7
|
+
server.registerTool("create_location", {
|
|
8
|
+
description: "Create a new location in the game world. IMPORTANT: Call this IMMEDIATELY when describing any new place, BEFORE narrating what happens there. Every location the player visits or hears about should exist in the database.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
gameId: z.string().max(100).describe("The game ID"),
|
|
11
|
+
name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Location name"),
|
|
12
|
+
description: z.string().max(LIMITS.DESCRIPTION_MAX).describe("Location description"),
|
|
13
|
+
properties: z.object({
|
|
14
|
+
features: z.array(z.string().max(LIMITS.NAME_MAX)).max(LIMITS.ARRAY_MAX).optional(),
|
|
15
|
+
atmosphere: z.string().max(LIMITS.DESCRIPTION_MAX).optional(),
|
|
16
|
+
}).optional().describe("Additional location properties"),
|
|
17
|
+
imageGen: imageGenSchema.optional().describe("Image generation metadata for location art"),
|
|
18
|
+
},
|
|
19
|
+
annotations: ANNOTATIONS.CREATE,
|
|
20
|
+
}, async ({ gameId, name, description, properties, imageGen }) => {
|
|
21
|
+
const location = worldTools.createLocation({ gameId, name, description, properties, imageGen });
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: JSON.stringify(location, null, 2) }],
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
server.registerTool("get_location", {
|
|
27
|
+
description: "Get location details",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
locationId: z.string().describe("The location ID"),
|
|
30
|
+
},
|
|
31
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
32
|
+
}, async ({ locationId }) => {
|
|
33
|
+
const location = worldTools.getLocation(locationId);
|
|
34
|
+
if (!location) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: "Location not found" }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: JSON.stringify(location, null, 2) }],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
server.registerTool("update_location", {
|
|
45
|
+
description: "Update a location",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
locationId: z.string().describe("The location ID"),
|
|
48
|
+
name: z.string().optional().describe("New name"),
|
|
49
|
+
description: z.string().optional().describe("New description"),
|
|
50
|
+
properties: z.record(z.string(), z.unknown()).optional().describe("Property updates"),
|
|
51
|
+
imageGen: imageGenSchema.nullable().optional().describe("Image generation metadata (null to remove)"),
|
|
52
|
+
},
|
|
53
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
54
|
+
}, async ({ locationId, name, description, properties, imageGen }) => {
|
|
55
|
+
const location = worldTools.updateLocation(locationId, { name, description, properties, imageGen });
|
|
56
|
+
if (!location) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: "Location not found" }],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: JSON.stringify(location, null, 2) }],
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
server.registerTool("list_locations", {
|
|
67
|
+
description: "List all locations in a game",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
gameId: z.string().describe("The game ID"),
|
|
70
|
+
},
|
|
71
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
72
|
+
}, async ({ gameId }) => {
|
|
73
|
+
const locations = worldTools.listLocations(gameId);
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: JSON.stringify(locations, null, 2) }],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
server.registerTool("connect_locations", {
|
|
79
|
+
description: "Create exits/paths between two locations. Call this whenever you describe how locations connect to each other - the player should be able to navigate based on database connections.",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
fromLocationId: z.string().describe("First location ID"),
|
|
82
|
+
toLocationId: z.string().describe("Second location ID"),
|
|
83
|
+
fromDirection: z.string().describe("Direction from first location (e.g., 'north', 'up', 'through the door')"),
|
|
84
|
+
toDirection: z.string().describe("Direction from second location back (e.g., 'south', 'down')"),
|
|
85
|
+
description: z.string().optional().describe("Description of the path"),
|
|
86
|
+
bidirectional: z.boolean().optional().describe("Create exit in both directions (default: true)"),
|
|
87
|
+
},
|
|
88
|
+
annotations: ANNOTATIONS.CREATE,
|
|
89
|
+
}, async ({ fromLocationId, toLocationId, fromDirection, toDirection, description, bidirectional }) => {
|
|
90
|
+
const result = worldTools.connectLocations({
|
|
91
|
+
fromLocationId,
|
|
92
|
+
toLocationId,
|
|
93
|
+
fromDirection,
|
|
94
|
+
toDirection,
|
|
95
|
+
description,
|
|
96
|
+
bidirectional,
|
|
97
|
+
});
|
|
98
|
+
if (!result) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: "Failed to connect locations - one or both locations not found" }],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
server.registerTool("get_location_by_name", {
|
|
109
|
+
description: "Look up a location by name within a game. Supports exact, partial, and fuzzy matching. Returns the best match or an error if no reasonable match found.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
gameId: z.string().describe("The game ID to search within"),
|
|
112
|
+
name: z.string().describe("Location name to search for (case-insensitive)"),
|
|
113
|
+
},
|
|
114
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
115
|
+
}, async ({ gameId, name }) => {
|
|
116
|
+
const location = worldTools.getLocationByName(gameId, name);
|
|
117
|
+
if (!location) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: `No location found matching "${name}"` }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: "text", text: JSON.stringify(location, null, 2) }],
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|