run-dmcp 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +19 -5
  2. package/dist/bin/run-dmcp.js +1 -1
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.js +7 -1
  5. package/dist/mcp-server.d.ts +5 -1
  6. package/dist/mcp-server.js +5 -1
  7. package/dist/register/batch.js +11 -10
  8. package/dist/register/character.js +16 -16
  9. package/dist/register/core.js +128 -111
  10. package/dist/register/display.js +42 -41
  11. package/dist/register/resolve.js +4 -1
  12. package/dist/register/world.js +13 -13
  13. package/dist/rpg/index.d.ts +0 -16
  14. package/dist/rpg/index.js +4 -22
  15. package/dist/rpg/register/batch.js +3 -3
  16. package/dist/rpg/server.d.ts +16 -0
  17. package/dist/rpg/server.js +22 -0
  18. package/dist/schemas/index.d.ts +62 -62
  19. package/dist/schemas/index.js +91 -72
  20. package/dist/server.d.ts +1 -0
  21. package/dist/server.js +20 -0
  22. package/dist/utils/output-schemas.d.ts +58 -58
  23. package/dist/utils/validation.d.ts +31 -2
  24. package/dist/utils/validation.js +67 -10
  25. package/package.json +9 -1
  26. package/dist/register/abilities.d.ts +0 -2
  27. package/dist/register/abilities.js +0 -165
  28. package/dist/register/combat.d.ts +0 -2
  29. package/dist/register/combat.js +0 -207
  30. package/dist/register/mcp-prompts.d.ts +0 -2
  31. package/dist/register/mcp-prompts.js +0 -684
  32. package/dist/register/quests.d.ts +0 -2
  33. package/dist/register/quests.js +0 -118
  34. package/dist/register/status.d.ts +0 -2
  35. package/dist/register/status.js +0 -130
  36. package/dist/register/tables.d.ts +0 -2
  37. package/dist/register/tables.js +0 -146
  38. package/dist/tools/ability.d.ts +0 -48
  39. package/dist/tools/ability.js +0 -238
  40. package/dist/tools/combat.d.ts +0 -13
  41. package/dist/tools/combat.js +0 -195
  42. package/dist/tools/dice.d.ts +0 -23
  43. package/dist/tools/dice.js +0 -111
  44. package/dist/tools/quest.d.ts +0 -34
  45. package/dist/tools/quest.js +0 -164
  46. package/dist/tools/status.d.ts +0 -36
  47. package/dist/tools/status.js +0 -218
  48. package/dist/tools/tables.d.ts +0 -33
  49. package/dist/tools/tables.js +0 -209
@@ -1,9 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import * as gameTools from "../tools/game.js";
3
3
  import * as rulesTools from "../tools/rules.js";
4
- import { LIMITS } from "../utils/validation.js";
4
+ import { LIMITS, validatedSchemas } from "../utils/validation.js";
5
5
  import { getGameUrl, getWebUiBaseUrl } from "../utils/webui.js";
6
6
  import { ANNOTATIONS } from "../utils/tool-annotations.js";
7
+ /**
8
+ * One answer from the preferences interview: what the player said, or a
9
+ * delegation to the DM. `save_game_preferences` takes eighteen of these and
10
+ * they were eighteen copies of the same three fields, which is how half of
11
+ * them ended up unbounded.
12
+ *
13
+ * A FUNCTION, not a shared constant. Reusing one instance eighteen times
14
+ * inside a single tool publishes seventeen `$ref` pointers where a consumer
15
+ * expects a declaration -- see the comment on `validatedSchemas`.
16
+ */
17
+ const preferenceAnswer = () => z.object({
18
+ value: validatedSchemas.token.nullable(),
19
+ delegatedToDM: z.boolean(),
20
+ notes: validatedSchemas.description.optional(),
21
+ });
7
22
  export function registerCoreTools(server) {
8
23
  // ============================================================================
9
24
  // GAME TOOLS
@@ -109,7 +124,7 @@ export function registerCoreTools(server) {
109
124
  server.registerTool("get_game_state", {
110
125
  description: "Get full current state overview for a game",
111
126
  inputSchema: {
112
- gameId: z.string().describe("The game ID"),
127
+ gameId: validatedSchemas.id.describe("The game ID"),
113
128
  },
114
129
  annotations: ANNOTATIONS.READ_ONLY,
115
130
  }, async ({ gameId }) => {
@@ -140,7 +155,7 @@ export function registerCoreTools(server) {
140
155
  server.registerTool("delete_game", {
141
156
  description: "Delete a game and all its data. This is IRREVERSIBLE and removes all characters, locations, quests, and history.",
142
157
  inputSchema: {
143
- gameId: z.string().describe("The game ID to delete"),
158
+ gameId: validatedSchemas.id.describe("The game ID to delete"),
144
159
  },
145
160
  annotations: ANNOTATIONS.DESTRUCTIVE,
146
161
  }, async ({ gameId }) => {
@@ -153,7 +168,7 @@ export function registerCoreTools(server) {
153
168
  server.registerTool("update_game", {
154
169
  description: "Update a game's name, setting, or style",
155
170
  inputSchema: {
156
- gameId: z.string().describe("The game ID to update"),
171
+ gameId: validatedSchemas.id.describe("The game ID to update"),
157
172
  name: z.string().min(1).max(LIMITS.NAME_MAX).optional().describe("New name for the game"),
158
173
  setting: z.string().min(1).max(LIMITS.DESCRIPTION_MAX).optional().describe("New setting description"),
159
174
  style: z.string().min(1).max(LIMITS.NAME_MAX).optional().describe("New narrative style"),
@@ -190,8 +205,8 @@ export function registerCoreTools(server) {
190
205
  server.registerTool("set_game_title_image", {
191
206
  description: "Set or remove the title image for a game",
192
207
  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"),
208
+ gameId: validatedSchemas.id.describe("The game ID"),
209
+ imageId: validatedSchemas.id.nullable().describe("The image ID to set as title image, or null to remove"),
195
210
  },
196
211
  annotations: ANNOTATIONS.SET,
197
212
  }, async ({ gameId, imageId }) => {
@@ -219,8 +234,8 @@ export function registerCoreTools(server) {
219
234
  server.registerTool("set_game_favicon", {
220
235
  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
236
  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"),
237
+ gameId: validatedSchemas.id.describe("The game ID"),
238
+ imageId: validatedSchemas.id.nullable().describe("The image ID to set as favicon, or null to remove"),
224
239
  },
225
240
  annotations: ANNOTATIONS.SET,
226
241
  }, async ({ gameId, imageId }) => {
@@ -596,30 +611,30 @@ export function registerCoreTools(server) {
596
611
  server.registerTool("save_game_preferences", {
597
612
  description: "Save the player's game preferences after the interview",
598
613
  inputSchema: {
599
- gameId: z.string().describe("The game ID"),
614
+ gameId: validatedSchemas.id.describe("The game ID"),
600
615
  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(),
616
+ genre: preferenceAnswer(),
617
+ tone: preferenceAnswer(),
618
+ setting: preferenceAnswer(),
619
+ complexity: preferenceAnswer(),
620
+ combatFrequency: preferenceAnswer(),
621
+ combatStyle: preferenceAnswer(),
622
+ lethality: preferenceAnswer(),
623
+ narrativeStyle: preferenceAnswer(),
624
+ playerAgency: preferenceAnswer(),
625
+ npcDepth: preferenceAnswer(),
626
+ romanceContent: preferenceAnswer(),
627
+ worldFamiliarity: preferenceAnswer(),
628
+ magicOrTechLevel: preferenceAnswer(),
629
+ politicalComplexity: preferenceAnswer(),
630
+ sessionLength: preferenceAnswer(),
631
+ pacingPreference: preferenceAnswer(),
632
+ characterCreation: preferenceAnswer(),
633
+ startingPowerLevel: preferenceAnswer(),
634
+ contentToAvoid: validatedSchemas.stringArray,
635
+ contentToInclude: validatedSchemas.stringArray,
636
+ inspirations: validatedSchemas.stringArray,
637
+ additionalNotes: validatedSchemas.description,
623
638
  }).describe("The collected preferences"),
624
639
  },
625
640
  annotations: ANNOTATIONS.CREATE,
@@ -654,7 +669,7 @@ export function registerCoreTools(server) {
654
669
  server.registerTool("get_game_preferences", {
655
670
  description: "Get the saved game preferences for a game",
656
671
  inputSchema: {
657
- gameId: z.string().describe("The game ID"),
672
+ gameId: validatedSchemas.id.describe("The game ID"),
658
673
  },
659
674
  annotations: ANNOTATIONS.READ_ONLY,
660
675
  }, async ({ gameId }) => {
@@ -676,96 +691,98 @@ export function registerCoreTools(server) {
676
691
  defaultTool: z.enum(["dalle", "sdxl", "midjourney", "comfyui", "flux", "other"]).optional()
677
692
  .describe("Default image generation tool/service"),
678
693
  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"),
694
+ artisticStyle: validatedSchemas.token.optional().describe("E.g., 'digital painting', 'oil painting', 'anime'"),
695
+ mood: validatedSchemas.token.optional().describe("E.g., 'dark', 'epic', 'whimsical'"),
696
+ colorScheme: validatedSchemas.token.optional().describe("E.g., 'warm', 'cold', 'muted', 'vibrant'"),
697
+ qualityTags: validatedSchemas.stringArray.optional().describe("E.g., ['highly detailed', '8k', 'masterpiece']"),
698
+ negativePrompts: validatedSchemas.stringArray.optional().describe("Things to avoid globally"),
699
+ influences: validatedSchemas.stringArray.optional().describe("Artist/game/movie style references"),
685
700
  }).optional().describe("Default style settings applied to all images"),
686
701
  comfyui: z.object({
687
- endpoint: z.string().optional().describe("ComfyUI server URL"),
688
- checkpoint: z.string().optional().describe("Default checkpoint model"),
702
+ endpoint: validatedSchemas.token.optional().describe("ComfyUI server URL"),
703
+ checkpoint: validatedSchemas.token.optional().describe("Default checkpoint model"),
689
704
  loras: z.array(z.object({
690
- name: z.string(),
705
+ name: validatedSchemas.token,
691
706
  weight: z.number(),
692
707
  })).optional().describe("LoRA models to apply"),
693
708
  samplerSettings: z.object({
694
- sampler: z.string().optional(),
695
- scheduler: z.string().optional(),
709
+ sampler: validatedSchemas.token.optional(),
710
+ scheduler: validatedSchemas.token.optional(),
696
711
  steps: z.number().optional(),
697
712
  cfg: z.number().optional(),
698
713
  }).optional(),
699
714
  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"),
715
+ name: validatedSchemas.token.describe("Human-readable workflow name"),
716
+ description: validatedSchemas.description.optional().describe("What this workflow does"),
702
717
  workflow: z.record(z.string(), z.unknown()).describe("Full ComfyUI workflow JSON (API format)"),
703
718
  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"),
719
+ positivePrompt: validatedSchemas.token.optional().describe("Node ID for positive prompt"),
720
+ negativePrompt: validatedSchemas.token.optional().describe("Node ID for negative prompt"),
721
+ checkpoint: validatedSchemas.token.optional().describe("Node ID for checkpoint loader"),
722
+ seed: validatedSchemas.token.optional().describe("Node ID for seed/noise"),
723
+ width: validatedSchemas.token.optional().describe("Node ID for width"),
724
+ height: validatedSchemas.token.optional().describe("Node ID for height"),
725
+ steps: validatedSchemas.token.optional().describe("Node ID for steps"),
726
+ cfg: validatedSchemas.token.optional().describe("Node ID for CFG scale"),
727
+ sampler: validatedSchemas.token.optional().describe("Node ID for sampler"),
728
+ scheduler: validatedSchemas.token.optional().describe("Node ID for scheduler"),
714
729
  }).optional().describe("Node IDs for dynamic value injection"),
715
730
  })).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"),
731
+ defaultWorkflowId: validatedSchemas.id.optional().describe("ID of the default workflow to use"),
732
+ defaultWorkflow: validatedSchemas.token.optional().describe("Legacy: Workflow name or ID"),
718
733
  workflowOverrides: z.record(z.string(), z.unknown()).optional(),
719
734
  }).optional().describe("ComfyUI-specific settings"),
720
735
  dalle: z.object({
721
- model: z.string().optional().describe("'dall-e-3' or 'dall-e-2'"),
736
+ model: validatedSchemas.token.optional().describe("'dall-e-3' or 'dall-e-2'"),
722
737
  quality: z.enum(["standard", "hd"]).optional(),
723
738
  style: z.enum(["vivid", "natural"]).optional(),
724
739
  size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional(),
725
740
  }).optional().describe("DALL-E specific settings"),
726
741
  midjourney: z.object({
727
- version: z.string().optional().describe("'v5', 'v6', 'niji'"),
742
+ version: validatedSchemas.token.optional().describe("'v5', 'v6', 'niji'"),
728
743
  stylize: z.number().optional().describe("0-1000"),
729
744
  chaos: z.number().optional().describe("0-100"),
730
745
  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'"),
746
+ aspectRatio: validatedSchemas.token.optional().describe("E.g., '1:1', '16:9', '2:3'"),
732
747
  }).optional().describe("Midjourney specific settings"),
733
748
  sdxl: z.object({
734
- model: z.string().optional().describe("Model ID or path"),
735
- samplerName: z.string().optional(),
749
+ model: validatedSchemas.token.optional().describe("Model ID or path"),
750
+ samplerName: validatedSchemas.token.optional(),
736
751
  steps: z.number().optional(),
737
752
  cfg: z.number().optional(),
738
753
  width: z.number().optional(),
739
754
  height: z.number().optional(),
740
- negativePrompt: z.string().optional(),
755
+ // Free text, not a node ID -- a negative prompt is a comma-separated
756
+ // list that routinely runs past a token's 200.
757
+ negativePrompt: validatedSchemas.description.optional(),
741
758
  }).optional().describe("Stable Diffusion / SDXL settings"),
742
759
  flux: z.object({
743
- model: z.string().optional().describe("'schnell', 'dev', 'pro'"),
760
+ model: validatedSchemas.token.optional().describe("'schnell', 'dev', 'pro'"),
744
761
  steps: z.number().optional(),
745
762
  guidance: z.number().optional(),
746
763
  }).optional().describe("Flux settings"),
747
764
  defaults: z.object({
748
- aspectRatio: z.string().optional().describe("Default aspect ratio for images"),
765
+ aspectRatio: validatedSchemas.token.optional().describe("Default aspect ratio for images"),
749
766
  generateOnCreate: z.boolean().optional().describe("Auto-generate images when entities are created"),
750
767
  savePrompts: z.boolean().optional().describe("Store prompts with entities"),
751
768
  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"),
769
+ character: validatedSchemas.token.optional().describe("Default framing for character portraits"),
770
+ location: validatedSchemas.token.optional().describe("Default framing for location scenes"),
771
+ item: validatedSchemas.token.optional().describe("Default framing for item images"),
755
772
  }).optional(),
756
773
  }).optional().describe("Generation defaults"),
757
774
  consistency: z.object({
758
775
  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"),
776
+ characterSeedImages: z.record(z.string(), validatedSchemas.description).optional().describe("characterId -> seed image"),
777
+ styleReferenceImage: validatedSchemas.description.optional().describe("Game-wide style reference"),
761
778
  useCharacterRefs: z.boolean().optional().describe("Use existing character images as reference"),
762
779
  }).optional().describe("Consistency settings"),
763
- notes: z.string().optional().describe("Custom notes for the DM about image generation"),
780
+ notes: validatedSchemas.description.optional().describe("Custom notes for the DM about image generation"),
764
781
  });
765
782
  server.registerTool("list_image_generation_presets", {
766
783
  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
784
  inputSchema: {
768
- gameId: z.string().describe("The game ID"),
785
+ gameId: validatedSchemas.id.describe("The game ID"),
769
786
  },
770
787
  annotations: ANNOTATIONS.READ_ONLY,
771
788
  }, async ({ gameId }) => {
@@ -792,8 +809,8 @@ export function registerCoreTools(server) {
792
809
  server.registerTool("get_image_generation_preset", {
793
810
  description: "Get a specific image generation preset by ID, including full configuration details",
794
811
  inputSchema: {
795
- gameId: z.string().describe("The game ID"),
796
- presetId: z.string().describe("The preset ID"),
812
+ gameId: validatedSchemas.id.describe("The game ID"),
813
+ presetId: validatedSchemas.id.describe("The preset ID"),
797
814
  },
798
815
  annotations: ANNOTATIONS.READ_ONLY,
799
816
  }, async ({ gameId, presetId }) => {
@@ -811,7 +828,7 @@ export function registerCoreTools(server) {
811
828
  server.registerTool("get_default_image_preset", {
812
829
  description: "Get the default image generation preset for a game",
813
830
  inputSchema: {
814
- gameId: z.string().describe("The game ID"),
831
+ gameId: validatedSchemas.id.describe("The game ID"),
815
832
  },
816
833
  annotations: ANNOTATIONS.READ_ONLY,
817
834
  }, async ({ gameId }) => {
@@ -834,9 +851,9 @@ export function registerCoreTools(server) {
834
851
  server.registerTool("create_image_generation_preset", {
835
852
  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
853
  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"),
854
+ gameId: validatedSchemas.id.describe("The game ID"),
855
+ name: validatedSchemas.token.describe("Human-readable preset name (e.g., 'Character Portraits', 'Location Art', 'Items with Text')"),
856
+ description: validatedSchemas.description.optional().describe("What this preset is for"),
840
857
  entityTypes: z.array(z.string().max(50)).optional()
841
858
  .describe("Which entity types this preset is best suited for (e.g., character, location, item, scene, faction)"),
842
859
  isDefault: z.boolean().optional().describe("Make this the default preset"),
@@ -871,10 +888,10 @@ export function registerCoreTools(server) {
871
888
  server.registerTool("update_image_generation_preset", {
872
889
  description: "Update an existing image generation preset. Only specified fields are updated; others are preserved.",
873
890
  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"),
891
+ gameId: validatedSchemas.id.describe("The game ID"),
892
+ presetId: validatedSchemas.id.describe("The preset ID to update"),
893
+ name: validatedSchemas.token.optional().describe("New preset name"),
894
+ description: validatedSchemas.description.optional().describe("New description"),
878
895
  entityTypes: z.array(z.string().max(50)).optional()
879
896
  .describe("Update which entity types this preset is for (e.g., character, location, item, scene, faction)"),
880
897
  isDefault: z.boolean().optional().describe("Make this the default preset"),
@@ -902,8 +919,8 @@ export function registerCoreTools(server) {
902
919
  server.registerTool("delete_image_generation_preset", {
903
920
  description: "Delete an image generation preset. This is IRREVERSIBLE.",
904
921
  inputSchema: {
905
- gameId: z.string().describe("The game ID"),
906
- presetId: z.string().describe("The preset ID to delete"),
922
+ gameId: validatedSchemas.id.describe("The game ID"),
923
+ presetId: validatedSchemas.id.describe("The preset ID to delete"),
907
924
  },
908
925
  annotations: ANNOTATIONS.DELETE,
909
926
  }, async ({ gameId, presetId }) => {
@@ -921,8 +938,8 @@ export function registerCoreTools(server) {
921
938
  server.registerTool("set_default_image_preset", {
922
939
  description: "Set which image generation preset should be used by default",
923
940
  inputSchema: {
924
- gameId: z.string().describe("The game ID"),
925
- presetId: z.string().describe("The preset ID to make default"),
941
+ gameId: validatedSchemas.id.describe("The game ID"),
942
+ presetId: validatedSchemas.id.describe("The preset ID to make default"),
926
943
  },
927
944
  annotations: ANNOTATIONS.SET,
928
945
  }, async ({ gameId, presetId }) => {
@@ -943,50 +960,50 @@ export function registerCoreTools(server) {
943
960
  server.registerTool("set_rules", {
944
961
  description: "Store a rule system for the game",
945
962
  inputSchema: {
946
- gameId: z.string().describe("The game ID"),
963
+ gameId: validatedSchemas.id.describe("The game ID"),
947
964
  rules: z.object({
948
- name: z.string(),
949
- description: z.string(),
965
+ name: validatedSchemas.token,
966
+ description: validatedSchemas.description,
950
967
  attributes: z.array(z.object({
951
- name: z.string(),
952
- abbreviation: z.string(),
953
- description: z.string(),
968
+ name: validatedSchemas.token,
969
+ abbreviation: validatedSchemas.token,
970
+ description: validatedSchemas.description,
954
971
  defaultValue: z.number(),
955
972
  minValue: z.number(),
956
973
  maxValue: z.number(),
957
974
  })),
958
975
  skills: z.array(z.object({
959
- name: z.string(),
960
- governingAttribute: z.string(),
961
- description: z.string(),
976
+ name: validatedSchemas.token,
977
+ governingAttribute: validatedSchemas.token,
978
+ description: validatedSchemas.description,
962
979
  })),
963
980
  derivedStats: z.array(z.object({
964
- name: z.string(),
965
- abbreviation: z.string(),
966
- description: z.string(),
967
- formula: z.string(),
981
+ name: validatedSchemas.token,
982
+ abbreviation: validatedSchemas.token,
983
+ description: validatedSchemas.description,
984
+ formula: validatedSchemas.token,
968
985
  })),
969
986
  combatRules: z.object({
970
- initiativeFormula: z.string(),
987
+ initiativeFormula: validatedSchemas.token,
971
988
  actionsPerTurn: z.number(),
972
- attackFormula: z.string(),
973
- defenseFormula: z.string(),
974
- damageFormula: z.string(),
989
+ attackFormula: validatedSchemas.token,
990
+ defenseFormula: validatedSchemas.token,
991
+ damageFormula: validatedSchemas.token,
975
992
  conditions: z.array(z.object({
976
- name: z.string(),
977
- description: z.string(),
978
- effects: z.string(),
993
+ name: validatedSchemas.token,
994
+ description: validatedSchemas.description,
995
+ effects: validatedSchemas.description,
979
996
  })),
980
997
  }),
981
998
  checkMechanics: z.object({
982
- baseDice: z.string(),
983
- modifierCalculation: z.string(),
999
+ baseDice: validatedSchemas.token,
1000
+ modifierCalculation: validatedSchemas.token,
984
1001
  difficultyScale: z.record(z.string(), z.number()),
985
1002
  criticalSuccess: z.number().optional(),
986
1003
  criticalFailure: z.number().optional(),
987
1004
  }),
988
1005
  progression: z.object({
989
- experienceFormula: z.string(),
1006
+ experienceFormula: validatedSchemas.token,
990
1007
  levelUpThresholds: z.array(z.number()),
991
1008
  attributePointsPerLevel: z.number(),
992
1009
  skillPointsPerLevel: z.number(),
@@ -1004,7 +1021,7 @@ export function registerCoreTools(server) {
1004
1021
  server.registerTool("get_rules", {
1005
1022
  description: "Get the current rule system for a game",
1006
1023
  inputSchema: {
1007
- gameId: z.string().describe("The game ID"),
1024
+ gameId: validatedSchemas.id.describe("The game ID"),
1008
1025
  },
1009
1026
  annotations: ANNOTATIONS.READ_ONLY,
1010
1027
  }, async ({ gameId }) => {
@@ -1021,7 +1038,7 @@ export function registerCoreTools(server) {
1021
1038
  server.registerTool("update_rules", {
1022
1039
  description: "Partially update the rule system",
1023
1040
  inputSchema: {
1024
- gameId: z.string().describe("The game ID"),
1041
+ gameId: validatedSchemas.id.describe("The game ID"),
1025
1042
  updates: z.record(z.string(), z.unknown()).describe("Partial rule updates"),
1026
1043
  },
1027
1044
  annotations: ANNOTATIONS.UPDATE,
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { getDisplayConfig, setDisplayConfig, resetDisplayConfig, applyThemePreset, listThemePresets, getGameDisplayConfig, setGameDisplayConfig, applyGameThemePreset, resetGameTheme, inferAndApplyTheme, } from "../tools/display.js";
3
3
  import { ANNOTATIONS } from "../utils/tool-annotations.js";
4
+ import { validatedSchemas } from "../utils/validation.js";
4
5
  // All available theme presets
5
6
  const themePresetNames = [
6
7
  "high-fantasy",
@@ -37,19 +38,19 @@ export function registerDisplayTools(server) {
37
38
  server.registerTool("set_display_config", {
38
39
  description: "Set display configuration for the web viewer. Can set any subset of options.",
39
40
  inputSchema: {
40
- bgColor: z.string().optional().describe("Background color (e.g., '#1a1a2e')"),
41
- bgSecondary: z.string().optional().describe("Secondary background color"),
42
- bgElevated: z.string().optional().describe("Elevated surface background"),
43
- textColor: z.string().optional().describe("Main text color"),
44
- textMuted: z.string().optional().describe("Muted text color"),
45
- accentColor: z.string().optional().describe("Accent color for links and highlights"),
46
- accentHover: z.string().optional().describe("Accent color on hover"),
47
- borderColor: z.string().optional().describe("Border color"),
48
- successColor: z.string().optional().describe("Success/positive color"),
49
- warningColor: z.string().optional().describe("Warning/caution color"),
50
- dangerColor: z.string().optional().describe("Danger/error color"),
51
- codeBackground: z.string().optional().describe("Code/monospace block background"),
52
- codeText: z.string().optional().describe("Code/monospace text color"),
41
+ bgColor: validatedSchemas.token.optional().describe("Background color (e.g., '#1a1a2e')"),
42
+ bgSecondary: validatedSchemas.token.optional().describe("Secondary background color"),
43
+ bgElevated: validatedSchemas.token.optional().describe("Elevated surface background"),
44
+ textColor: validatedSchemas.token.optional().describe("Main text color"),
45
+ textMuted: validatedSchemas.token.optional().describe("Muted text color"),
46
+ accentColor: validatedSchemas.token.optional().describe("Accent color for links and highlights"),
47
+ accentHover: validatedSchemas.token.optional().describe("Accent color on hover"),
48
+ borderColor: validatedSchemas.token.optional().describe("Border color"),
49
+ successColor: validatedSchemas.token.optional().describe("Success/positive color"),
50
+ warningColor: validatedSchemas.token.optional().describe("Warning/caution color"),
51
+ dangerColor: validatedSchemas.token.optional().describe("Danger/error color"),
52
+ codeBackground: validatedSchemas.token.optional().describe("Code/monospace block background"),
53
+ codeText: validatedSchemas.token.optional().describe("Code/monospace text color"),
53
54
  borderRadius: z
54
55
  .enum(["sharp", "rounded", "soft"])
55
56
  .optional()
@@ -58,13 +59,13 @@ export function registerDisplayTools(server) {
58
59
  .enum(["clean", "grungy", "tech", "parchment", "metallic", "wooden"])
59
60
  .optional()
60
61
  .describe("Card visual style"),
61
- fontDisplay: z.string().optional().describe("Display/heading font (Google Font name)"),
62
- fontBody: z.string().optional().describe("Body text font (Google Font name)"),
63
- fontMono: z.string().optional().describe("Monospace font (Google Font name)"),
62
+ fontDisplay: validatedSchemas.token.optional().describe("Display/heading font (Google Font name)"),
63
+ fontBody: validatedSchemas.token.optional().describe("Body text font (Google Font name)"),
64
+ fontMono: validatedSchemas.token.optional().describe("Monospace font (Google Font name)"),
64
65
  showHealthBars: z.boolean().optional().describe("Show health bars on character cards"),
65
66
  showConditionTags: z.boolean().optional().describe("Show condition tags"),
66
67
  showImages: z.boolean().optional().describe("Show images in the viewer"),
67
- appTitle: z.string().optional().describe("Custom title for the web viewer"),
68
+ appTitle: validatedSchemas.token.optional().describe("Custom title for the web viewer"),
68
69
  },
69
70
  annotations: ANNOTATIONS.SET,
70
71
  }, async (args) => {
@@ -143,7 +144,7 @@ export function registerDisplayTools(server) {
143
144
  server.registerTool("get_game_theme", {
144
145
  description: "Get the display configuration for a specific game",
145
146
  inputSchema: {
146
- gameId: z.string().describe("The game ID"),
147
+ gameId: validatedSchemas.id.describe("The game ID"),
147
148
  },
148
149
  annotations: ANNOTATIONS.READ_ONLY,
149
150
  }, async ({ gameId }) => {
@@ -161,29 +162,29 @@ export function registerDisplayTools(server) {
161
162
  server.registerTool("set_game_theme", {
162
163
  description: "Set display configuration for a specific game. Each game can have its own visual theme.",
163
164
  inputSchema: {
164
- gameId: z.string().describe("The game ID"),
165
- bgColor: z.string().optional().describe("Background color"),
166
- bgSecondary: z.string().optional().describe("Secondary background"),
167
- bgElevated: z.string().optional().describe("Elevated surface background"),
168
- textColor: z.string().optional().describe("Text color"),
169
- textMuted: z.string().optional().describe("Muted text"),
170
- accentColor: z.string().optional().describe("Accent color"),
171
- accentHover: z.string().optional().describe("Accent hover color"),
172
- borderColor: z.string().optional().describe("Border color"),
173
- successColor: z.string().optional().describe("Success color"),
174
- warningColor: z.string().optional().describe("Warning color"),
175
- dangerColor: z.string().optional().describe("Danger color"),
176
- codeBackground: z.string().optional().describe("Code block background"),
177
- codeText: z.string().optional().describe("Code text color"),
165
+ gameId: validatedSchemas.id.describe("The game ID"),
166
+ bgColor: validatedSchemas.token.optional().describe("Background color"),
167
+ bgSecondary: validatedSchemas.token.optional().describe("Secondary background"),
168
+ bgElevated: validatedSchemas.token.optional().describe("Elevated surface background"),
169
+ textColor: validatedSchemas.token.optional().describe("Text color"),
170
+ textMuted: validatedSchemas.token.optional().describe("Muted text"),
171
+ accentColor: validatedSchemas.token.optional().describe("Accent color"),
172
+ accentHover: validatedSchemas.token.optional().describe("Accent hover color"),
173
+ borderColor: validatedSchemas.token.optional().describe("Border color"),
174
+ successColor: validatedSchemas.token.optional().describe("Success color"),
175
+ warningColor: validatedSchemas.token.optional().describe("Warning color"),
176
+ dangerColor: validatedSchemas.token.optional().describe("Danger color"),
177
+ codeBackground: validatedSchemas.token.optional().describe("Code block background"),
178
+ codeText: validatedSchemas.token.optional().describe("Code text color"),
178
179
  borderRadius: z.enum(["sharp", "rounded", "soft"]).optional().describe("Border radius style"),
179
180
  cardStyle: z.enum(["clean", "grungy", "tech", "parchment", "metallic", "wooden"]).optional().describe("Card style"),
180
- fontDisplay: z.string().optional().describe("Display font"),
181
- fontBody: z.string().optional().describe("Body font"),
182
- fontMono: z.string().optional().describe("Mono font"),
181
+ fontDisplay: validatedSchemas.token.optional().describe("Display font"),
182
+ fontBody: validatedSchemas.token.optional().describe("Body font"),
183
+ fontMono: validatedSchemas.token.optional().describe("Mono font"),
183
184
  showHealthBars: z.boolean().optional().describe("Show health bars"),
184
185
  showConditionTags: z.boolean().optional().describe("Show condition tags"),
185
186
  showImages: z.boolean().optional().describe("Show images"),
186
- appTitle: z.string().optional().describe("App title"),
187
+ appTitle: validatedSchemas.token.optional().describe("App title"),
187
188
  },
188
189
  annotations: ANNOTATIONS.SET,
189
190
  }, async ({ gameId, ...config }) => {
@@ -201,7 +202,7 @@ export function registerDisplayTools(server) {
201
202
  server.registerTool("apply_game_theme_preset", {
202
203
  description: "Apply a predefined theme preset to a specific game. This allows different games to have completely different visual themes.",
203
204
  inputSchema: {
204
- gameId: z.string().describe("The game ID"),
205
+ gameId: validatedSchemas.id.describe("The game ID"),
205
206
  preset: z.enum(themePresetNames).describe("Theme preset name"),
206
207
  },
207
208
  annotations: ANNOTATIONS.SET,
@@ -226,7 +227,7 @@ export function registerDisplayTools(server) {
226
227
  server.registerTool("reset_game_theme", {
227
228
  description: "Remove a game's custom theme, reverting to the global theme",
228
229
  inputSchema: {
229
- gameId: z.string().describe("The game ID"),
230
+ gameId: validatedSchemas.id.describe("The game ID"),
230
231
  },
231
232
  annotations: ANNOTATIONS.SET,
232
233
  }, async ({ gameId }) => {
@@ -244,9 +245,9 @@ export function registerDisplayTools(server) {
244
245
  server.registerTool("auto_theme_game", {
245
246
  description: "Automatically apply an appropriate theme to a game based on its genre and setting. Call this when creating a new game to set up the visual style.",
246
247
  inputSchema: {
247
- gameId: z.string().describe("The game ID"),
248
- genre: z.string().describe("Game genre (e.g., 'fantasy', 'sci-fi', 'western', 'noir')"),
249
- setting: z.string().optional().describe("Optional setting description for more accurate theme matching"),
248
+ gameId: validatedSchemas.id.describe("The game ID"),
249
+ genre: validatedSchemas.token.describe("Game genre (e.g., 'fantasy', 'sci-fi', 'western', 'noir')"),
250
+ setting: validatedSchemas.description.optional().describe("Optional setting description for more accurate theme matching"),
250
251
  },
251
252
  annotations: ANNOTATIONS.SET,
252
253
  }, async ({ gameId, genre, setting }) => {
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { ANNOTATIONS } from "../utils/tool-annotations.js";
3
+ import { validatedSchemas } from "../utils/validation.js";
3
4
  import { createLogger } from "../utils/logger.js";
4
5
  import { errors, formatErrorResponse } from "../utils/errors.js";
5
6
  import { ConstraintViolationError } from "../timeline/registry.js";
@@ -37,7 +38,9 @@ export function registerResolveTools(server, resolver) {
37
38
  .array(z.object({
38
39
  entityId: z.string().max(100).describe("The entity this expectation is about"),
39
40
  key: z.string().max(200).describe("The fact key this expectation is about"),
40
- value: z.union([z.string(), z.number()]).describe("The value this proposal declares it depends on"),
41
+ value: z
42
+ .union([validatedSchemas.token, z.number()])
43
+ .describe("The value this proposal declares it depends on"),
41
44
  }))
42
45
  .optional()
43
46
  .describe("Facts this proposal declares it depends on, verified BEFORE the mechanic is dispatched. A caller's " +