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,1040 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as gameTools from "../tools/game.js";
|
|
3
|
+
import * as rulesTools from "../tools/rules.js";
|
|
4
|
+
import { LIMITS } from "../utils/validation.js";
|
|
5
|
+
import { getGameUrl, getWebUiBaseUrl } from "../utils/webui.js";
|
|
6
|
+
import { ANNOTATIONS } from "../utils/tool-annotations.js";
|
|
7
|
+
export function registerCoreTools(server) {
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// GAME TOOLS
|
|
10
|
+
// ============================================================================
|
|
11
|
+
server.registerTool("get_game_menu", {
|
|
12
|
+
description: "CALL THIS FIRST when player wants to play. Returns existing games (most recent first) or instructs to start new game if none exist.",
|
|
13
|
+
inputSchema: {},
|
|
14
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
15
|
+
}, async () => {
|
|
16
|
+
const menu = gameTools.getGameMenu();
|
|
17
|
+
const menuWithUrls = {
|
|
18
|
+
...menu,
|
|
19
|
+
games: menu.games.map((g) => ({
|
|
20
|
+
...g,
|
|
21
|
+
webUiUrl: getGameUrl(g.id),
|
|
22
|
+
})),
|
|
23
|
+
webUi: {
|
|
24
|
+
baseUrl: getWebUiBaseUrl(),
|
|
25
|
+
message: menu.hasExistingGames
|
|
26
|
+
? `Web UI available at: ${getWebUiBaseUrl()}`
|
|
27
|
+
: `Web UI will be available at: ${getWebUiBaseUrl()}`,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: JSON.stringify(menuWithUrls, null, 2) }],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
server.registerTool("create_game", {
|
|
35
|
+
description: "Create a new game with a setting and style",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Name for this game"),
|
|
38
|
+
setting: z.string().min(1).max(LIMITS.DESCRIPTION_MAX).describe("The game setting (e.g., 'dark fantasy', 'cyberpunk', 'cosmic horror')"),
|
|
39
|
+
style: z.string().min(1).max(LIMITS.NAME_MAX).describe("The narrative style (e.g., 'gritty', 'heroic', 'survival')"),
|
|
40
|
+
},
|
|
41
|
+
annotations: ANNOTATIONS.CREATE,
|
|
42
|
+
}, async ({ name, setting, style }) => {
|
|
43
|
+
const game = gameTools.createGame({ name, setting, style });
|
|
44
|
+
const webUiUrl = getGameUrl(game.id);
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
...game,
|
|
50
|
+
webUi: {
|
|
51
|
+
url: webUiUrl,
|
|
52
|
+
message: `View game at: ${webUiUrl}`,
|
|
53
|
+
},
|
|
54
|
+
}, null, 2),
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
server.registerTool("load_game", {
|
|
59
|
+
description: "Load an existing game by ID",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
gameId: z.string().max(100).describe("The game ID to load"),
|
|
62
|
+
},
|
|
63
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
64
|
+
}, async ({ gameId }) => {
|
|
65
|
+
const game = gameTools.loadGame(gameId);
|
|
66
|
+
if (!game) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: `Game ${gameId} not found` }],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const webUiUrl = getGameUrl(game.id);
|
|
73
|
+
return {
|
|
74
|
+
content: [{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: JSON.stringify({
|
|
77
|
+
...game,
|
|
78
|
+
webUi: {
|
|
79
|
+
url: webUiUrl,
|
|
80
|
+
message: `View game at: ${webUiUrl}`,
|
|
81
|
+
},
|
|
82
|
+
}, null, 2),
|
|
83
|
+
}],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
server.registerTool("list_games", {
|
|
87
|
+
description: "List all saved games",
|
|
88
|
+
inputSchema: {},
|
|
89
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
90
|
+
}, async () => {
|
|
91
|
+
// Use lightweight summaries (no rules/preferences) for listing
|
|
92
|
+
const games = gameTools.listGameSummaries();
|
|
93
|
+
const gamesWithUrls = games.map((g) => ({
|
|
94
|
+
...g,
|
|
95
|
+
webUiUrl: getGameUrl(g.id),
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify({
|
|
101
|
+
games: gamesWithUrls,
|
|
102
|
+
webUi: {
|
|
103
|
+
baseUrl: getWebUiBaseUrl(),
|
|
104
|
+
},
|
|
105
|
+
}, null, 2),
|
|
106
|
+
}],
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
server.registerTool("get_game_state", {
|
|
110
|
+
description: "Get full current state overview for a game",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
gameId: z.string().describe("The game ID"),
|
|
113
|
+
},
|
|
114
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
115
|
+
}, async ({ gameId }) => {
|
|
116
|
+
const state = gameTools.getGameState(gameId);
|
|
117
|
+
if (!state) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: `Game ${gameId} not found` }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const webUiUrl = getGameUrl(gameId);
|
|
124
|
+
return {
|
|
125
|
+
content: [{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: JSON.stringify({
|
|
128
|
+
...state,
|
|
129
|
+
webUi: {
|
|
130
|
+
url: webUiUrl,
|
|
131
|
+
message: `View game at: ${webUiUrl}`,
|
|
132
|
+
},
|
|
133
|
+
tips: {
|
|
134
|
+
visualization: "Use the location and character data to describe the world. Generate maps or other visualizations as appropriate to enhance immersion.",
|
|
135
|
+
},
|
|
136
|
+
}, null, 2),
|
|
137
|
+
}],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
server.registerTool("delete_game", {
|
|
141
|
+
description: "Delete a game and all its data. This is IRREVERSIBLE and removes all characters, locations, quests, and history.",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
gameId: z.string().describe("The game ID to delete"),
|
|
144
|
+
},
|
|
145
|
+
annotations: ANNOTATIONS.DESTRUCTIVE,
|
|
146
|
+
}, async ({ gameId }) => {
|
|
147
|
+
const success = gameTools.deleteGame(gameId);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: success ? "Game deleted" : "Game not found" }],
|
|
150
|
+
isError: !success,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
server.registerTool("update_game", {
|
|
154
|
+
description: "Update a game's name, setting, or style",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
gameId: z.string().describe("The game ID to update"),
|
|
157
|
+
name: z.string().min(1).max(LIMITS.NAME_MAX).optional().describe("New name for the game"),
|
|
158
|
+
setting: z.string().min(1).max(LIMITS.DESCRIPTION_MAX).optional().describe("New setting description"),
|
|
159
|
+
style: z.string().min(1).max(LIMITS.NAME_MAX).optional().describe("New narrative style"),
|
|
160
|
+
},
|
|
161
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
162
|
+
}, async ({ gameId, name, setting, style }) => {
|
|
163
|
+
if (!name && !setting && !style) {
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: "No updates provided" }],
|
|
166
|
+
isError: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const game = gameTools.updateGame(gameId, { name, setting, style });
|
|
170
|
+
if (!game) {
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: "text", text: "Game not found" }],
|
|
173
|
+
isError: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const webUiUrl = getGameUrl(game.id);
|
|
177
|
+
return {
|
|
178
|
+
content: [{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: JSON.stringify({
|
|
181
|
+
...game,
|
|
182
|
+
webUi: {
|
|
183
|
+
url: webUiUrl,
|
|
184
|
+
message: `View game at: ${webUiUrl}`,
|
|
185
|
+
},
|
|
186
|
+
}, null, 2),
|
|
187
|
+
}],
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
server.registerTool("set_game_title_image", {
|
|
191
|
+
description: "Set or remove the title image for a game",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
gameId: z.string().describe("The game ID"),
|
|
194
|
+
imageId: z.string().nullable().describe("The image ID to set as title image, or null to remove"),
|
|
195
|
+
},
|
|
196
|
+
annotations: ANNOTATIONS.SET,
|
|
197
|
+
}, async ({ gameId, imageId }) => {
|
|
198
|
+
const game = gameTools.setGameTitleImage(gameId, imageId);
|
|
199
|
+
if (!game) {
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: "text", text: "Game not found" }],
|
|
202
|
+
isError: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const webUiUrl = getGameUrl(game.id);
|
|
206
|
+
return {
|
|
207
|
+
content: [{
|
|
208
|
+
type: "text",
|
|
209
|
+
text: JSON.stringify({
|
|
210
|
+
...game,
|
|
211
|
+
webUi: {
|
|
212
|
+
url: webUiUrl,
|
|
213
|
+
message: `View game at: ${webUiUrl}`,
|
|
214
|
+
},
|
|
215
|
+
}, null, 2),
|
|
216
|
+
}],
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
server.registerTool("set_game_favicon", {
|
|
220
|
+
description: "Set or remove the favicon image for a game. This image will be displayed as the browser favicon when viewing this game in the web UI. Should be a square image (ideally 32x32 or larger).",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
gameId: z.string().describe("The game ID"),
|
|
223
|
+
imageId: z.string().nullable().describe("The image ID to set as favicon, or null to remove"),
|
|
224
|
+
},
|
|
225
|
+
annotations: ANNOTATIONS.SET,
|
|
226
|
+
}, async ({ gameId, imageId }) => {
|
|
227
|
+
const game = gameTools.setGameFavicon(gameId, imageId);
|
|
228
|
+
if (!game) {
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: "Game not found" }],
|
|
231
|
+
isError: true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const webUiUrl = getGameUrl(game.id);
|
|
235
|
+
return {
|
|
236
|
+
content: [{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: JSON.stringify({
|
|
239
|
+
...game,
|
|
240
|
+
webUi: {
|
|
241
|
+
url: webUiUrl,
|
|
242
|
+
faviconUrl: `${webUiUrl.replace(/\/games\/.*/, '')}/api/games/${game.id}/favicon`,
|
|
243
|
+
message: `Favicon set. View game at: ${webUiUrl}`,
|
|
244
|
+
},
|
|
245
|
+
}, null, 2),
|
|
246
|
+
}],
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// GAME SETUP INTERVIEW TOOLS
|
|
251
|
+
// ============================================================================
|
|
252
|
+
server.registerTool("get_interview_template", {
|
|
253
|
+
description: "Get the full game setup interview template with all questions. Use this to guide players through game creation.",
|
|
254
|
+
inputSchema: {},
|
|
255
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
256
|
+
}, async () => {
|
|
257
|
+
const template = {
|
|
258
|
+
introduction: "Let's create your perfect game experience! For each question, you can choose an option, give a custom answer, or say 'You decide' to let me handle it.",
|
|
259
|
+
categories: [
|
|
260
|
+
{
|
|
261
|
+
name: "Core Identity",
|
|
262
|
+
description: "The fundamental nature of your game",
|
|
263
|
+
questions: [
|
|
264
|
+
{
|
|
265
|
+
id: "genre",
|
|
266
|
+
question: "What genre or type of game appeals to you?",
|
|
267
|
+
description: "This shapes the entire world and story",
|
|
268
|
+
options: [
|
|
269
|
+
{ value: "high-fantasy", label: "High Fantasy", description: "Magic, heroes, epic quests (Lord of the Rings)" },
|
|
270
|
+
{ value: "dark-fantasy", label: "Dark Fantasy", description: "Grim, dangerous, morally grey (Dark Souls, Witcher)" },
|
|
271
|
+
{ value: "sci-fi-space", label: "Space Opera", description: "Galaxy-spanning adventure (Star Wars, Mass Effect)" },
|
|
272
|
+
{ value: "sci-fi-cyber", label: "Cyberpunk", description: "High tech, low life, corporate dystopia" },
|
|
273
|
+
{ value: "horror-cosmic", label: "Cosmic Horror", description: "Unknowable terrors, sanity at stake (Lovecraft)" },
|
|
274
|
+
{ value: "horror-gothic", label: "Gothic Horror", description: "Vampires, curses, dark romance" },
|
|
275
|
+
{ value: "post-apocalyptic", label: "Post-Apocalyptic", description: "Survival after the fall" },
|
|
276
|
+
{ value: "historical", label: "Historical/Alt-History", description: "Real world periods with twists" },
|
|
277
|
+
{ value: "modern-supernatural", label: "Urban Fantasy", description: "Magic hidden in modern world" },
|
|
278
|
+
{ value: "steampunk", label: "Steampunk", description: "Victorian era with fantastical technology" },
|
|
279
|
+
],
|
|
280
|
+
allowFreeform: true,
|
|
281
|
+
allowDelegate: true,
|
|
282
|
+
delegateLabel: "Surprise me with something interesting",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "tone",
|
|
286
|
+
question: "What tone should the game have?",
|
|
287
|
+
description: "How serious or lighthearted the experience feels",
|
|
288
|
+
options: [
|
|
289
|
+
{ value: "grimdark", label: "Grimdark", description: "Bleak, brutal, hope is scarce" },
|
|
290
|
+
{ value: "gritty", label: "Gritty", description: "Realistic consequences, tough choices" },
|
|
291
|
+
{ value: "balanced", label: "Balanced", description: "Mix of light and dark moments" },
|
|
292
|
+
{ value: "heroic", label: "Heroic", description: "Triumph over evil, inspiring" },
|
|
293
|
+
{ value: "comedic", label: "Comedic", description: "Humor and absurdity welcome" },
|
|
294
|
+
{ value: "whimsical", label: "Whimsical", description: "Lighthearted, magical, wonder-filled" },
|
|
295
|
+
],
|
|
296
|
+
allowFreeform: true,
|
|
297
|
+
allowDelegate: true,
|
|
298
|
+
delegateLabel: "Match it to the genre",
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "setting",
|
|
302
|
+
question: "Any specific setting details you want?",
|
|
303
|
+
description: "Particular world elements, time period, or unique features",
|
|
304
|
+
options: [],
|
|
305
|
+
allowFreeform: true,
|
|
306
|
+
allowDelegate: true,
|
|
307
|
+
delegateLabel: "Build a world that fits the genre and tone",
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "Mechanics & Challenge",
|
|
313
|
+
description: "How the game plays",
|
|
314
|
+
questions: [
|
|
315
|
+
{
|
|
316
|
+
id: "complexity",
|
|
317
|
+
question: "How complex should the rules be?",
|
|
318
|
+
description: "Affects character sheets, combat, and decision-making",
|
|
319
|
+
options: [
|
|
320
|
+
{ value: "rules-light", label: "Rules-Light", description: "Simple, narrative-focused, quick resolution" },
|
|
321
|
+
{ value: "medium", label: "Medium", description: "Some crunch but not overwhelming" },
|
|
322
|
+
{ value: "crunchy", label: "Crunchy", description: "Detailed mechanics, tactical depth" },
|
|
323
|
+
],
|
|
324
|
+
allowFreeform: false,
|
|
325
|
+
allowDelegate: true,
|
|
326
|
+
delegateLabel: "Whatever fits the genre best",
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "combatFrequency",
|
|
330
|
+
question: "How often should combat occur?",
|
|
331
|
+
description: "Balance between fighting and other activities",
|
|
332
|
+
options: [
|
|
333
|
+
{ value: "rare", label: "Rare", description: "Combat is a last resort, mostly avoided" },
|
|
334
|
+
{ value: "occasional", label: "Occasional", description: "Some fights, but not the focus" },
|
|
335
|
+
{ value: "frequent", label: "Frequent", description: "Regular combat encounters" },
|
|
336
|
+
{ value: "constant", label: "Constant", description: "Action-heavy, always in danger" },
|
|
337
|
+
],
|
|
338
|
+
allowFreeform: false,
|
|
339
|
+
allowDelegate: true,
|
|
340
|
+
delegateLabel: "Balance it naturally",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "combatStyle",
|
|
344
|
+
question: "What combat style do you prefer?",
|
|
345
|
+
description: "How fights play out",
|
|
346
|
+
options: [
|
|
347
|
+
{ value: "tactical", label: "Tactical", description: "Positioning, strategy, detailed actions" },
|
|
348
|
+
{ value: "narrative", label: "Narrative", description: "Descriptive, cinematic, story-driven" },
|
|
349
|
+
{ value: "quick-resolution", label: "Quick Resolution", description: "Fast, decisive, minimal bookkeeping" },
|
|
350
|
+
],
|
|
351
|
+
allowFreeform: false,
|
|
352
|
+
allowDelegate: true,
|
|
353
|
+
delegateLabel: "Match the complexity level",
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "lethality",
|
|
357
|
+
question: "How dangerous should the game be?",
|
|
358
|
+
description: "Risk of character death",
|
|
359
|
+
options: [
|
|
360
|
+
{ value: "safe", label: "Safe", description: "Death only if narratively appropriate" },
|
|
361
|
+
{ value: "dangerous", label: "Dangerous", description: "Bad decisions have consequences" },
|
|
362
|
+
{ value: "brutal", label: "Brutal", description: "Death is always a possibility" },
|
|
363
|
+
{ value: "permadeath", label: "Permadeath", description: "Dead is dead, high stakes" },
|
|
364
|
+
],
|
|
365
|
+
allowFreeform: false,
|
|
366
|
+
allowDelegate: true,
|
|
367
|
+
delegateLabel: "Match the tone",
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "Narrative & Story",
|
|
373
|
+
description: "How the story unfolds",
|
|
374
|
+
questions: [
|
|
375
|
+
{
|
|
376
|
+
id: "narrativeStyle",
|
|
377
|
+
question: "What narrative structure do you prefer?",
|
|
378
|
+
description: "How the story is organized",
|
|
379
|
+
options: [
|
|
380
|
+
{ value: "linear", label: "Linear", description: "Clear main plot to follow" },
|
|
381
|
+
{ value: "branching", label: "Branching", description: "Major choices create different paths" },
|
|
382
|
+
{ value: "sandbox", label: "Sandbox", description: "Open world, player-driven goals" },
|
|
383
|
+
{ value: "emergent", label: "Emergent", description: "Story arises from character actions" },
|
|
384
|
+
],
|
|
385
|
+
allowFreeform: false,
|
|
386
|
+
allowDelegate: true,
|
|
387
|
+
delegateLabel: "Whatever works for the story",
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: "playerAgency",
|
|
391
|
+
question: "How much control do you want over the story?",
|
|
392
|
+
description: "Your influence on events",
|
|
393
|
+
options: [
|
|
394
|
+
{ value: "guided", label: "Guided", description: "Clear objectives, directed experience" },
|
|
395
|
+
{ value: "balanced", label: "Balanced", description: "Mix of direction and freedom" },
|
|
396
|
+
{ value: "full-sandbox", label: "Full Sandbox", description: "Complete freedom, I make my own path" },
|
|
397
|
+
],
|
|
398
|
+
allowFreeform: false,
|
|
399
|
+
allowDelegate: true,
|
|
400
|
+
delegateLabel: "Guide me when needed",
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: "npcDepth",
|
|
404
|
+
question: "How detailed should NPCs be?",
|
|
405
|
+
description: "Depth of non-player characters",
|
|
406
|
+
options: [
|
|
407
|
+
{ value: "functional", label: "Functional", description: "Serve their purpose, move on" },
|
|
408
|
+
{ value: "developed", label: "Developed", description: "Personalities, some backstory" },
|
|
409
|
+
{ value: "deep-characterization", label: "Deep", description: "Rich histories, complex motivations" },
|
|
410
|
+
],
|
|
411
|
+
allowFreeform: false,
|
|
412
|
+
allowDelegate: true,
|
|
413
|
+
delegateLabel: "Important NPCs should be deep",
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "romanceContent",
|
|
417
|
+
question: "How should romantic content be handled?",
|
|
418
|
+
description: "If romance occurs in the story",
|
|
419
|
+
options: [
|
|
420
|
+
{ value: "none", label: "None", description: "No romantic subplots" },
|
|
421
|
+
{ value: "fade-to-black", label: "Fade to Black", description: "Romance exists but not detailed" },
|
|
422
|
+
{ value: "tasteful", label: "Tasteful", description: "Romantic moments, nothing explicit" },
|
|
423
|
+
{ value: "detailed", label: "Detailed", description: "Romantic storylines are important" },
|
|
424
|
+
],
|
|
425
|
+
allowFreeform: false,
|
|
426
|
+
allowDelegate: true,
|
|
427
|
+
delegateLabel: "Keep it tasteful if it comes up",
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "World Building",
|
|
433
|
+
description: "The nature of the game world",
|
|
434
|
+
questions: [
|
|
435
|
+
{
|
|
436
|
+
id: "worldFamiliarity",
|
|
437
|
+
question: "How familiar should the world feel?",
|
|
438
|
+
description: "Recognizable vs. alien",
|
|
439
|
+
options: [
|
|
440
|
+
{ value: "established-ip", label: "Established IP", description: "Based on existing fiction I know" },
|
|
441
|
+
{ value: "familiar-tropes", label: "Familiar Tropes", description: "Classic genre conventions" },
|
|
442
|
+
{ value: "unique-twists", label: "Unique Twists", description: "Familiar base with surprises" },
|
|
443
|
+
{ value: "alien-unfamiliar", label: "Alien/Unfamiliar", description: "Strange and new to discover" },
|
|
444
|
+
],
|
|
445
|
+
allowFreeform: true,
|
|
446
|
+
allowDelegate: true,
|
|
447
|
+
delegateLabel: "Surprise me but keep it coherent",
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "magicOrTechLevel",
|
|
451
|
+
question: "How prevalent is magic/advanced technology?",
|
|
452
|
+
description: "Supernatural or technological elements",
|
|
453
|
+
options: [
|
|
454
|
+
{ value: "none", label: "None", description: "Realistic, no supernatural elements" },
|
|
455
|
+
{ value: "rare", label: "Rare", description: "Exists but uncommon, special" },
|
|
456
|
+
{ value: "common", label: "Common", description: "Part of everyday life" },
|
|
457
|
+
{ value: "ubiquitous", label: "Ubiquitous", description: "Everywhere, defines the world" },
|
|
458
|
+
],
|
|
459
|
+
allowFreeform: false,
|
|
460
|
+
allowDelegate: true,
|
|
461
|
+
delegateLabel: "Match the genre",
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: "politicalComplexity",
|
|
465
|
+
question: "How complex should politics/factions be?",
|
|
466
|
+
description: "Intrigue and power dynamics",
|
|
467
|
+
options: [
|
|
468
|
+
{ value: "simple", label: "Simple", description: "Clear good/evil, straightforward" },
|
|
469
|
+
{ value: "moderate", label: "Moderate", description: "Some factions, reasonable complexity" },
|
|
470
|
+
{ value: "intricate", label: "Intricate", description: "Web of alliances, betrayals, schemes" },
|
|
471
|
+
],
|
|
472
|
+
allowFreeform: false,
|
|
473
|
+
allowDelegate: true,
|
|
474
|
+
delegateLabel: "Match the narrative style",
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "Session & Pacing",
|
|
480
|
+
description: "How we play together",
|
|
481
|
+
questions: [
|
|
482
|
+
{
|
|
483
|
+
id: "sessionLength",
|
|
484
|
+
question: "How long are your typical play sessions?",
|
|
485
|
+
description: "Affects story pacing",
|
|
486
|
+
options: [
|
|
487
|
+
{ value: "quick-30min", label: "Quick (30 min)", description: "Short bursts of play" },
|
|
488
|
+
{ value: "standard-1hr", label: "Standard (1 hour)", description: "Typical session length" },
|
|
489
|
+
{ value: "extended-2hr", label: "Extended (2+ hours)", description: "Long immersive sessions" },
|
|
490
|
+
{ value: "marathon", label: "Marathon", description: "Epic multi-hour adventures" },
|
|
491
|
+
],
|
|
492
|
+
allowFreeform: true,
|
|
493
|
+
allowDelegate: false,
|
|
494
|
+
delegateLabel: null,
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "pacingPreference",
|
|
498
|
+
question: "What pacing do you enjoy?",
|
|
499
|
+
description: "Story rhythm",
|
|
500
|
+
options: [
|
|
501
|
+
{ value: "action-packed", label: "Action-Packed", description: "Constant momentum, little downtime" },
|
|
502
|
+
{ value: "balanced", label: "Balanced", description: "Mix of action and quiet moments" },
|
|
503
|
+
{ value: "slow-burn", label: "Slow Burn", description: "Build-up, atmosphere, payoff" },
|
|
504
|
+
],
|
|
505
|
+
allowFreeform: false,
|
|
506
|
+
allowDelegate: true,
|
|
507
|
+
delegateLabel: "Vary it naturally",
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: "Character",
|
|
513
|
+
description: "Your character in the story",
|
|
514
|
+
questions: [
|
|
515
|
+
{
|
|
516
|
+
id: "characterCreation",
|
|
517
|
+
question: "How do you want to create your character?",
|
|
518
|
+
description: "Level of involvement in character creation",
|
|
519
|
+
options: [
|
|
520
|
+
{ value: "pre-made", label: "Pre-made", description: "Give me a character to play" },
|
|
521
|
+
{ value: "guided", label: "Guided", description: "Help me build one step by step" },
|
|
522
|
+
{ value: "full-custom", label: "Full Custom", description: "I'll define everything myself" },
|
|
523
|
+
],
|
|
524
|
+
allowFreeform: false,
|
|
525
|
+
allowDelegate: true,
|
|
526
|
+
delegateLabel: "Suggest a character that fits",
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: "startingPowerLevel",
|
|
530
|
+
question: "Where should your character start?",
|
|
531
|
+
description: "Initial capability level",
|
|
532
|
+
options: [
|
|
533
|
+
{ value: "zero-to-hero", label: "Zero to Hero", description: "Start weak, grow powerful" },
|
|
534
|
+
{ value: "competent", label: "Competent", description: "Capable but room to grow" },
|
|
535
|
+
{ value: "experienced", label: "Experienced", description: "Already skilled and capable" },
|
|
536
|
+
{ value: "legendary", label: "Legendary", description: "Start as a powerhouse" },
|
|
537
|
+
],
|
|
538
|
+
allowFreeform: false,
|
|
539
|
+
allowDelegate: true,
|
|
540
|
+
delegateLabel: "Match the story needs",
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "Content & Safety",
|
|
546
|
+
description: "Boundaries and preferences",
|
|
547
|
+
questions: [
|
|
548
|
+
{
|
|
549
|
+
id: "contentToAvoid",
|
|
550
|
+
question: "Any topics or themes you want to AVOID?",
|
|
551
|
+
description: "Things that should not appear in the game",
|
|
552
|
+
options: [],
|
|
553
|
+
allowFreeform: true,
|
|
554
|
+
allowDelegate: false,
|
|
555
|
+
delegateLabel: null,
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: "contentToInclude",
|
|
559
|
+
question: "Any topics or themes you definitely WANT included?",
|
|
560
|
+
description: "Things you'd enjoy seeing",
|
|
561
|
+
options: [],
|
|
562
|
+
allowFreeform: true,
|
|
563
|
+
allowDelegate: false,
|
|
564
|
+
delegateLabel: null,
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "inspirations",
|
|
568
|
+
question: "Any movies, books, games, or shows you want this to feel like?",
|
|
569
|
+
description: "Reference points for tone and style",
|
|
570
|
+
options: [],
|
|
571
|
+
allowFreeform: true,
|
|
572
|
+
allowDelegate: true,
|
|
573
|
+
delegateLabel: "Pick inspirations that fit my choices",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: "additionalNotes",
|
|
577
|
+
question: "Anything else you want me to know?",
|
|
578
|
+
description: "Other preferences, ideas, or requests",
|
|
579
|
+
options: [],
|
|
580
|
+
allowFreeform: true,
|
|
581
|
+
allowDelegate: false,
|
|
582
|
+
delegateLabel: null,
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
instructions: {
|
|
588
|
+
forDM: "Present questions conversationally. Group related questions. Remember: players can always say 'you decide' or 'surprise me' to delegate any choice.",
|
|
589
|
+
delegatePhrase: "When a player delegates, note it with delegatedToDM: true and make an appropriate choice that fits their other preferences.",
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: JSON.stringify(template, null, 2) }],
|
|
594
|
+
};
|
|
595
|
+
});
|
|
596
|
+
server.registerTool("save_game_preferences", {
|
|
597
|
+
description: "Save the player's game preferences after the interview",
|
|
598
|
+
inputSchema: {
|
|
599
|
+
gameId: z.string().describe("The game ID"),
|
|
600
|
+
preferences: z.object({
|
|
601
|
+
genre: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
602
|
+
tone: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
603
|
+
setting: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
604
|
+
complexity: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
605
|
+
combatFrequency: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
606
|
+
combatStyle: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
607
|
+
lethality: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
608
|
+
narrativeStyle: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
609
|
+
playerAgency: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
610
|
+
npcDepth: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
611
|
+
romanceContent: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
612
|
+
worldFamiliarity: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
613
|
+
magicOrTechLevel: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
614
|
+
politicalComplexity: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
615
|
+
sessionLength: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
616
|
+
pacingPreference: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
617
|
+
characterCreation: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
618
|
+
startingPowerLevel: z.object({ value: z.string().nullable(), delegatedToDM: z.boolean(), notes: z.string().optional() }),
|
|
619
|
+
contentToAvoid: z.array(z.string()),
|
|
620
|
+
contentToInclude: z.array(z.string()),
|
|
621
|
+
inspirations: z.array(z.string()),
|
|
622
|
+
additionalNotes: z.string(),
|
|
623
|
+
}).describe("The collected preferences"),
|
|
624
|
+
},
|
|
625
|
+
annotations: ANNOTATIONS.CREATE,
|
|
626
|
+
}, async ({ gameId, preferences }) => {
|
|
627
|
+
const success = gameTools.updateGamePreferences(gameId, preferences);
|
|
628
|
+
if (!success) {
|
|
629
|
+
return {
|
|
630
|
+
content: [{ type: "text", text: "Game not found" }],
|
|
631
|
+
isError: true,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
// Count delegated vs specified
|
|
635
|
+
const prefEntries = Object.entries(preferences).filter(([key]) => !["contentToAvoid", "contentToInclude", "inspirations", "additionalNotes"].includes(key));
|
|
636
|
+
const delegated = prefEntries.filter(([, v]) => v.delegatedToDM).length;
|
|
637
|
+
const specified = prefEntries.length - delegated;
|
|
638
|
+
return {
|
|
639
|
+
content: [{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: JSON.stringify({
|
|
642
|
+
saved: true,
|
|
643
|
+
summary: {
|
|
644
|
+
specifiedByPlayer: specified,
|
|
645
|
+
delegatedToDM: delegated,
|
|
646
|
+
contentBoundaries: preferences.contentToAvoid.length + preferences.contentToInclude.length,
|
|
647
|
+
inspirations: preferences.inspirations.length,
|
|
648
|
+
},
|
|
649
|
+
nextStep: "Use get_rules to check if rules exist, or generate rules based on these preferences.",
|
|
650
|
+
}, null, 2),
|
|
651
|
+
}],
|
|
652
|
+
};
|
|
653
|
+
});
|
|
654
|
+
server.registerTool("get_game_preferences", {
|
|
655
|
+
description: "Get the saved game preferences for a game",
|
|
656
|
+
inputSchema: {
|
|
657
|
+
gameId: z.string().describe("The game ID"),
|
|
658
|
+
},
|
|
659
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
660
|
+
}, async ({ gameId }) => {
|
|
661
|
+
const preferences = gameTools.getGamePreferences(gameId);
|
|
662
|
+
if (!preferences) {
|
|
663
|
+
return {
|
|
664
|
+
content: [{ type: "text", text: "No preferences found for this game" }],
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: "text", text: JSON.stringify(preferences, null, 2) }],
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
// ============================================================================
|
|
672
|
+
// IMAGE GENERATION PRESET TOOLS
|
|
673
|
+
// ============================================================================
|
|
674
|
+
// Reusable schema for image generation config (used in create and update)
|
|
675
|
+
const imageGenerationConfigSchema = z.object({
|
|
676
|
+
defaultTool: z.enum(["dalle", "sdxl", "midjourney", "comfyui", "flux", "other"]).optional()
|
|
677
|
+
.describe("Default image generation tool/service"),
|
|
678
|
+
defaultStyle: z.object({
|
|
679
|
+
artisticStyle: z.string().optional().describe("E.g., 'digital painting', 'oil painting', 'anime'"),
|
|
680
|
+
mood: z.string().optional().describe("E.g., 'dark', 'epic', 'whimsical'"),
|
|
681
|
+
colorScheme: z.string().optional().describe("E.g., 'warm', 'cold', 'muted', 'vibrant'"),
|
|
682
|
+
qualityTags: z.array(z.string()).optional().describe("E.g., ['highly detailed', '8k', 'masterpiece']"),
|
|
683
|
+
negativePrompts: z.array(z.string()).optional().describe("Things to avoid globally"),
|
|
684
|
+
influences: z.array(z.string()).optional().describe("Artist/game/movie style references"),
|
|
685
|
+
}).optional().describe("Default style settings applied to all images"),
|
|
686
|
+
comfyui: z.object({
|
|
687
|
+
endpoint: z.string().optional().describe("ComfyUI server URL"),
|
|
688
|
+
checkpoint: z.string().optional().describe("Default checkpoint model"),
|
|
689
|
+
loras: z.array(z.object({
|
|
690
|
+
name: z.string(),
|
|
691
|
+
weight: z.number(),
|
|
692
|
+
})).optional().describe("LoRA models to apply"),
|
|
693
|
+
samplerSettings: z.object({
|
|
694
|
+
sampler: z.string().optional(),
|
|
695
|
+
scheduler: z.string().optional(),
|
|
696
|
+
steps: z.number().optional(),
|
|
697
|
+
cfg: z.number().optional(),
|
|
698
|
+
}).optional(),
|
|
699
|
+
workflows: z.record(z.string(), z.object({
|
|
700
|
+
name: z.string().describe("Human-readable workflow name"),
|
|
701
|
+
description: z.string().optional().describe("What this workflow does"),
|
|
702
|
+
workflow: z.record(z.string(), z.unknown()).describe("Full ComfyUI workflow JSON (API format)"),
|
|
703
|
+
inputNodes: z.object({
|
|
704
|
+
positivePrompt: z.string().optional().describe("Node ID for positive prompt"),
|
|
705
|
+
negativePrompt: z.string().optional().describe("Node ID for negative prompt"),
|
|
706
|
+
checkpoint: z.string().optional().describe("Node ID for checkpoint loader"),
|
|
707
|
+
seed: z.string().optional().describe("Node ID for seed/noise"),
|
|
708
|
+
width: z.string().optional().describe("Node ID for width"),
|
|
709
|
+
height: z.string().optional().describe("Node ID for height"),
|
|
710
|
+
steps: z.string().optional().describe("Node ID for steps"),
|
|
711
|
+
cfg: z.string().optional().describe("Node ID for CFG scale"),
|
|
712
|
+
sampler: z.string().optional().describe("Node ID for sampler"),
|
|
713
|
+
scheduler: z.string().optional().describe("Node ID for scheduler"),
|
|
714
|
+
}).optional().describe("Node IDs for dynamic value injection"),
|
|
715
|
+
})).optional().describe("Named workflow templates - full ComfyUI workflows"),
|
|
716
|
+
defaultWorkflowId: z.string().optional().describe("ID of the default workflow to use"),
|
|
717
|
+
defaultWorkflow: z.string().optional().describe("Legacy: Workflow name or ID"),
|
|
718
|
+
workflowOverrides: z.record(z.string(), z.unknown()).optional(),
|
|
719
|
+
}).optional().describe("ComfyUI-specific settings"),
|
|
720
|
+
dalle: z.object({
|
|
721
|
+
model: z.string().optional().describe("'dall-e-3' or 'dall-e-2'"),
|
|
722
|
+
quality: z.enum(["standard", "hd"]).optional(),
|
|
723
|
+
style: z.enum(["vivid", "natural"]).optional(),
|
|
724
|
+
size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional(),
|
|
725
|
+
}).optional().describe("DALL-E specific settings"),
|
|
726
|
+
midjourney: z.object({
|
|
727
|
+
version: z.string().optional().describe("'v5', 'v6', 'niji'"),
|
|
728
|
+
stylize: z.number().optional().describe("0-1000"),
|
|
729
|
+
chaos: z.number().optional().describe("0-100"),
|
|
730
|
+
quality: z.number().optional().describe("0.25, 0.5, 1, 2"),
|
|
731
|
+
aspectRatio: z.string().optional().describe("E.g., '1:1', '16:9', '2:3'"),
|
|
732
|
+
}).optional().describe("Midjourney specific settings"),
|
|
733
|
+
sdxl: z.object({
|
|
734
|
+
model: z.string().optional().describe("Model ID or path"),
|
|
735
|
+
samplerName: z.string().optional(),
|
|
736
|
+
steps: z.number().optional(),
|
|
737
|
+
cfg: z.number().optional(),
|
|
738
|
+
width: z.number().optional(),
|
|
739
|
+
height: z.number().optional(),
|
|
740
|
+
negativePrompt: z.string().optional(),
|
|
741
|
+
}).optional().describe("Stable Diffusion / SDXL settings"),
|
|
742
|
+
flux: z.object({
|
|
743
|
+
model: z.string().optional().describe("'schnell', 'dev', 'pro'"),
|
|
744
|
+
steps: z.number().optional(),
|
|
745
|
+
guidance: z.number().optional(),
|
|
746
|
+
}).optional().describe("Flux settings"),
|
|
747
|
+
defaults: z.object({
|
|
748
|
+
aspectRatio: z.string().optional().describe("Default aspect ratio for images"),
|
|
749
|
+
generateOnCreate: z.boolean().optional().describe("Auto-generate images when entities are created"),
|
|
750
|
+
savePrompts: z.boolean().optional().describe("Store prompts with entities"),
|
|
751
|
+
framing: z.object({
|
|
752
|
+
character: z.string().optional().describe("Default framing for character portraits"),
|
|
753
|
+
location: z.string().optional().describe("Default framing for location scenes"),
|
|
754
|
+
item: z.string().optional().describe("Default framing for item images"),
|
|
755
|
+
}).optional(),
|
|
756
|
+
}).optional().describe("Generation defaults"),
|
|
757
|
+
consistency: z.object({
|
|
758
|
+
maintainColorPalette: z.boolean().optional(),
|
|
759
|
+
characterSeedImages: z.record(z.string(), z.string()).optional().describe("characterId -> seed image"),
|
|
760
|
+
styleReferenceImage: z.string().optional().describe("Game-wide style reference"),
|
|
761
|
+
useCharacterRefs: z.boolean().optional().describe("Use existing character images as reference"),
|
|
762
|
+
}).optional().describe("Consistency settings"),
|
|
763
|
+
notes: z.string().optional().describe("Custom notes for the DM about image generation"),
|
|
764
|
+
});
|
|
765
|
+
server.registerTool("list_image_generation_presets", {
|
|
766
|
+
description: "List all image generation presets for a game. Presets allow different configurations for different use cases (character portraits, location art, items with text, etc.)",
|
|
767
|
+
inputSchema: {
|
|
768
|
+
gameId: z.string().describe("The game ID"),
|
|
769
|
+
},
|
|
770
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
771
|
+
}, async ({ gameId }) => {
|
|
772
|
+
const presets = gameTools.listImageGenerationPresets(gameId);
|
|
773
|
+
const defaultPreset = gameTools.getDefaultImagePreset(gameId);
|
|
774
|
+
return {
|
|
775
|
+
content: [{
|
|
776
|
+
type: "text",
|
|
777
|
+
text: JSON.stringify({
|
|
778
|
+
count: presets.length,
|
|
779
|
+
defaultPresetId: defaultPreset?.id || null,
|
|
780
|
+
presets: presets.map(p => ({
|
|
781
|
+
id: p.id,
|
|
782
|
+
name: p.name,
|
|
783
|
+
description: p.description,
|
|
784
|
+
entityTypes: p.entityTypes,
|
|
785
|
+
isDefault: p.isDefault,
|
|
786
|
+
tool: p.config.defaultTool,
|
|
787
|
+
})),
|
|
788
|
+
}, null, 2),
|
|
789
|
+
}],
|
|
790
|
+
};
|
|
791
|
+
});
|
|
792
|
+
server.registerTool("get_image_generation_preset", {
|
|
793
|
+
description: "Get a specific image generation preset by ID, including full configuration details",
|
|
794
|
+
inputSchema: {
|
|
795
|
+
gameId: z.string().describe("The game ID"),
|
|
796
|
+
presetId: z.string().describe("The preset ID"),
|
|
797
|
+
},
|
|
798
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
799
|
+
}, async ({ gameId, presetId }) => {
|
|
800
|
+
const preset = gameTools.getImageGenerationPreset(gameId, presetId);
|
|
801
|
+
if (!preset) {
|
|
802
|
+
return {
|
|
803
|
+
content: [{ type: "text", text: "Preset not found" }],
|
|
804
|
+
isError: true,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
content: [{ type: "text", text: JSON.stringify(preset, null, 2) }],
|
|
809
|
+
};
|
|
810
|
+
});
|
|
811
|
+
server.registerTool("get_default_image_preset", {
|
|
812
|
+
description: "Get the default image generation preset for a game",
|
|
813
|
+
inputSchema: {
|
|
814
|
+
gameId: z.string().describe("The game ID"),
|
|
815
|
+
},
|
|
816
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
817
|
+
}, async ({ gameId }) => {
|
|
818
|
+
const preset = gameTools.getDefaultImagePreset(gameId);
|
|
819
|
+
if (!preset) {
|
|
820
|
+
return {
|
|
821
|
+
content: [{
|
|
822
|
+
type: "text",
|
|
823
|
+
text: JSON.stringify({
|
|
824
|
+
message: "No image generation presets configured for this game",
|
|
825
|
+
hint: "Use create_image_generation_preset to add presets for different use cases",
|
|
826
|
+
}, null, 2),
|
|
827
|
+
}],
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
content: [{ type: "text", text: JSON.stringify(preset, null, 2) }],
|
|
832
|
+
};
|
|
833
|
+
});
|
|
834
|
+
server.registerTool("create_image_generation_preset", {
|
|
835
|
+
description: "Create a new image generation preset. Use different presets for different purposes: character portraits, location art, items with text, etc. Each preset can have its own tool, model, style, and workflow configuration.",
|
|
836
|
+
inputSchema: {
|
|
837
|
+
gameId: z.string().describe("The game ID"),
|
|
838
|
+
name: z.string().describe("Human-readable preset name (e.g., 'Character Portraits', 'Location Art', 'Items with Text')"),
|
|
839
|
+
description: z.string().optional().describe("What this preset is for"),
|
|
840
|
+
entityTypes: z.array(z.string().max(50)).optional()
|
|
841
|
+
.describe("Which entity types this preset is best suited for (e.g., character, location, item, scene, faction)"),
|
|
842
|
+
isDefault: z.boolean().optional().describe("Make this the default preset"),
|
|
843
|
+
config: imageGenerationConfigSchema.describe("The image generation configuration"),
|
|
844
|
+
},
|
|
845
|
+
annotations: ANNOTATIONS.CREATE,
|
|
846
|
+
}, async ({ gameId, name, description, entityTypes, isDefault, config }) => {
|
|
847
|
+
const preset = gameTools.createImageGenerationPreset(gameId, {
|
|
848
|
+
name,
|
|
849
|
+
description,
|
|
850
|
+
entityTypes,
|
|
851
|
+
isDefault,
|
|
852
|
+
config,
|
|
853
|
+
});
|
|
854
|
+
return {
|
|
855
|
+
content: [{
|
|
856
|
+
type: "text",
|
|
857
|
+
text: JSON.stringify({
|
|
858
|
+
created: true,
|
|
859
|
+
preset: {
|
|
860
|
+
id: preset.id,
|
|
861
|
+
name: preset.name,
|
|
862
|
+
description: preset.description,
|
|
863
|
+
entityTypes: preset.entityTypes,
|
|
864
|
+
isDefault: preset.isDefault,
|
|
865
|
+
tool: preset.config.defaultTool,
|
|
866
|
+
},
|
|
867
|
+
}, null, 2),
|
|
868
|
+
}],
|
|
869
|
+
};
|
|
870
|
+
});
|
|
871
|
+
server.registerTool("update_image_generation_preset", {
|
|
872
|
+
description: "Update an existing image generation preset. Only specified fields are updated; others are preserved.",
|
|
873
|
+
inputSchema: {
|
|
874
|
+
gameId: z.string().describe("The game ID"),
|
|
875
|
+
presetId: z.string().describe("The preset ID to update"),
|
|
876
|
+
name: z.string().optional().describe("New preset name"),
|
|
877
|
+
description: z.string().optional().describe("New description"),
|
|
878
|
+
entityTypes: z.array(z.string().max(50)).optional()
|
|
879
|
+
.describe("Update which entity types this preset is for (e.g., character, location, item, scene, faction)"),
|
|
880
|
+
isDefault: z.boolean().optional().describe("Make this the default preset"),
|
|
881
|
+
config: imageGenerationConfigSchema.partial().optional().describe("Partial config updates (deep merged)"),
|
|
882
|
+
},
|
|
883
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
884
|
+
}, async ({ gameId, presetId, name, description, entityTypes, isDefault, config }) => {
|
|
885
|
+
const preset = gameTools.updateImageGenerationPreset(gameId, presetId, {
|
|
886
|
+
name,
|
|
887
|
+
description,
|
|
888
|
+
entityTypes,
|
|
889
|
+
isDefault,
|
|
890
|
+
config,
|
|
891
|
+
});
|
|
892
|
+
if (!preset) {
|
|
893
|
+
return {
|
|
894
|
+
content: [{ type: "text", text: "Preset not found" }],
|
|
895
|
+
isError: true,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
content: [{ type: "text", text: JSON.stringify(preset, null, 2) }],
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
server.registerTool("delete_image_generation_preset", {
|
|
903
|
+
description: "Delete an image generation preset. This is IRREVERSIBLE.",
|
|
904
|
+
inputSchema: {
|
|
905
|
+
gameId: z.string().describe("The game ID"),
|
|
906
|
+
presetId: z.string().describe("The preset ID to delete"),
|
|
907
|
+
},
|
|
908
|
+
annotations: ANNOTATIONS.DELETE,
|
|
909
|
+
}, async ({ gameId, presetId }) => {
|
|
910
|
+
const success = gameTools.deleteImageGenerationPreset(gameId, presetId);
|
|
911
|
+
if (!success) {
|
|
912
|
+
return {
|
|
913
|
+
content: [{ type: "text", text: "Preset not found" }],
|
|
914
|
+
isError: true,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
content: [{ type: "text", text: JSON.stringify({ deleted: true, presetId }, null, 2) }],
|
|
919
|
+
};
|
|
920
|
+
});
|
|
921
|
+
server.registerTool("set_default_image_preset", {
|
|
922
|
+
description: "Set which image generation preset should be used by default",
|
|
923
|
+
inputSchema: {
|
|
924
|
+
gameId: z.string().describe("The game ID"),
|
|
925
|
+
presetId: z.string().describe("The preset ID to make default"),
|
|
926
|
+
},
|
|
927
|
+
annotations: ANNOTATIONS.SET,
|
|
928
|
+
}, async ({ gameId, presetId }) => {
|
|
929
|
+
const success = gameTools.setDefaultImagePreset(gameId, presetId);
|
|
930
|
+
if (!success) {
|
|
931
|
+
return {
|
|
932
|
+
content: [{ type: "text", text: "Preset not found" }],
|
|
933
|
+
isError: true,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, defaultPresetId: presetId }, null, 2) }],
|
|
938
|
+
};
|
|
939
|
+
});
|
|
940
|
+
// ============================================================================
|
|
941
|
+
// RULES TOOLS
|
|
942
|
+
// ============================================================================
|
|
943
|
+
server.registerTool("set_rules", {
|
|
944
|
+
description: "Store a rule system for the game",
|
|
945
|
+
inputSchema: {
|
|
946
|
+
gameId: z.string().describe("The game ID"),
|
|
947
|
+
rules: z.object({
|
|
948
|
+
name: z.string(),
|
|
949
|
+
description: z.string(),
|
|
950
|
+
attributes: z.array(z.object({
|
|
951
|
+
name: z.string(),
|
|
952
|
+
abbreviation: z.string(),
|
|
953
|
+
description: z.string(),
|
|
954
|
+
defaultValue: z.number(),
|
|
955
|
+
minValue: z.number(),
|
|
956
|
+
maxValue: z.number(),
|
|
957
|
+
})),
|
|
958
|
+
skills: z.array(z.object({
|
|
959
|
+
name: z.string(),
|
|
960
|
+
governingAttribute: z.string(),
|
|
961
|
+
description: z.string(),
|
|
962
|
+
})),
|
|
963
|
+
derivedStats: z.array(z.object({
|
|
964
|
+
name: z.string(),
|
|
965
|
+
abbreviation: z.string(),
|
|
966
|
+
description: z.string(),
|
|
967
|
+
formula: z.string(),
|
|
968
|
+
})),
|
|
969
|
+
combatRules: z.object({
|
|
970
|
+
initiativeFormula: z.string(),
|
|
971
|
+
actionsPerTurn: z.number(),
|
|
972
|
+
attackFormula: z.string(),
|
|
973
|
+
defenseFormula: z.string(),
|
|
974
|
+
damageFormula: z.string(),
|
|
975
|
+
conditions: z.array(z.object({
|
|
976
|
+
name: z.string(),
|
|
977
|
+
description: z.string(),
|
|
978
|
+
effects: z.string(),
|
|
979
|
+
})),
|
|
980
|
+
}),
|
|
981
|
+
checkMechanics: z.object({
|
|
982
|
+
baseDice: z.string(),
|
|
983
|
+
modifierCalculation: z.string(),
|
|
984
|
+
difficultyScale: z.record(z.string(), z.number()),
|
|
985
|
+
criticalSuccess: z.number().optional(),
|
|
986
|
+
criticalFailure: z.number().optional(),
|
|
987
|
+
}),
|
|
988
|
+
progression: z.object({
|
|
989
|
+
experienceFormula: z.string(),
|
|
990
|
+
levelUpThresholds: z.array(z.number()),
|
|
991
|
+
attributePointsPerLevel: z.number(),
|
|
992
|
+
skillPointsPerLevel: z.number(),
|
|
993
|
+
}),
|
|
994
|
+
}).describe("The complete rule system"),
|
|
995
|
+
},
|
|
996
|
+
annotations: ANNOTATIONS.SET,
|
|
997
|
+
}, async ({ gameId, rules }) => {
|
|
998
|
+
const success = rulesTools.setRules(gameId, rules);
|
|
999
|
+
return {
|
|
1000
|
+
content: [{ type: "text", text: success ? "Rules set successfully" : "Failed to set rules" }],
|
|
1001
|
+
isError: !success,
|
|
1002
|
+
};
|
|
1003
|
+
});
|
|
1004
|
+
server.registerTool("get_rules", {
|
|
1005
|
+
description: "Get the current rule system for a game",
|
|
1006
|
+
inputSchema: {
|
|
1007
|
+
gameId: z.string().describe("The game ID"),
|
|
1008
|
+
},
|
|
1009
|
+
annotations: ANNOTATIONS.READ_ONLY,
|
|
1010
|
+
}, async ({ gameId }) => {
|
|
1011
|
+
const rules = rulesTools.getRules(gameId);
|
|
1012
|
+
if (!rules) {
|
|
1013
|
+
return {
|
|
1014
|
+
content: [{ type: "text", text: "No rules set for this game" }],
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
content: [{ type: "text", text: JSON.stringify(rules, null, 2) }],
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
server.registerTool("update_rules", {
|
|
1022
|
+
description: "Partially update the rule system",
|
|
1023
|
+
inputSchema: {
|
|
1024
|
+
gameId: z.string().describe("The game ID"),
|
|
1025
|
+
updates: z.record(z.string(), z.unknown()).describe("Partial rule updates"),
|
|
1026
|
+
},
|
|
1027
|
+
annotations: ANNOTATIONS.UPDATE,
|
|
1028
|
+
}, async ({ gameId, updates }) => {
|
|
1029
|
+
const rules = rulesTools.updateRules(gameId, updates);
|
|
1030
|
+
if (!rules) {
|
|
1031
|
+
return {
|
|
1032
|
+
content: [{ type: "text", text: "No rules to update" }],
|
|
1033
|
+
isError: true,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
content: [{ type: "text", text: JSON.stringify(rules, null, 2) }],
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
}
|