run-dmcp 0.4.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.
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import * as worldTools from "../tools/world.js";
3
3
  import { imageGenSchema } from "../schemas/index.js";
4
- import { LIMITS } from "../utils/validation.js";
4
+ import { LIMITS, validatedSchemas } from "../utils/validation.js";
5
5
  import { ANNOTATIONS } from "../utils/tool-annotations.js";
6
6
  export function registerWorldTools(server) {
7
7
  server.registerTool("create_location", {
@@ -26,7 +26,7 @@ export function registerWorldTools(server) {
26
26
  server.registerTool("get_location", {
27
27
  description: "Get location details",
28
28
  inputSchema: {
29
- locationId: z.string().describe("The location ID"),
29
+ locationId: validatedSchemas.id.describe("The location ID"),
30
30
  },
31
31
  annotations: ANNOTATIONS.READ_ONLY,
32
32
  }, async ({ locationId }) => {
@@ -44,9 +44,9 @@ export function registerWorldTools(server) {
44
44
  server.registerTool("update_location", {
45
45
  description: "Update a location",
46
46
  inputSchema: {
47
- locationId: z.string().describe("The location ID"),
48
- name: z.string().optional().describe("New name"),
49
- description: z.string().optional().describe("New description"),
47
+ locationId: validatedSchemas.id.describe("The location ID"),
48
+ name: validatedSchemas.token.optional().describe("New name"),
49
+ description: validatedSchemas.description.optional().describe("New description"),
50
50
  properties: z.record(z.string(), z.unknown()).optional().describe("Property updates"),
51
51
  imageGen: imageGenSchema.nullable().optional().describe("Image generation metadata (null to remove)"),
52
52
  },
@@ -66,7 +66,7 @@ export function registerWorldTools(server) {
66
66
  server.registerTool("list_locations", {
67
67
  description: "List all locations in a game",
68
68
  inputSchema: {
69
- gameId: z.string().describe("The game ID"),
69
+ gameId: validatedSchemas.id.describe("The game ID"),
70
70
  },
71
71
  annotations: ANNOTATIONS.READ_ONLY,
72
72
  }, async ({ gameId }) => {
@@ -78,11 +78,11 @@ export function registerWorldTools(server) {
78
78
  server.registerTool("connect_locations", {
79
79
  description: "Create exits/paths between two locations. Call this whenever you describe how locations connect to each other - the player should be able to navigate based on database connections.",
80
80
  inputSchema: {
81
- fromLocationId: z.string().describe("First location ID"),
82
- toLocationId: z.string().describe("Second location ID"),
83
- fromDirection: z.string().describe("Direction from first location (e.g., 'north', 'up', 'through the door')"),
84
- toDirection: z.string().describe("Direction from second location back (e.g., 'south', 'down')"),
85
- description: z.string().optional().describe("Description of the path"),
81
+ fromLocationId: validatedSchemas.id.describe("First location ID"),
82
+ toLocationId: validatedSchemas.id.describe("Second location ID"),
83
+ fromDirection: validatedSchemas.token.describe("Direction from first location (e.g., 'north', 'up', 'through the door')"),
84
+ toDirection: validatedSchemas.token.describe("Direction from second location back (e.g., 'south', 'down')"),
85
+ description: validatedSchemas.description.optional().describe("Description of the path"),
86
86
  bidirectional: z.boolean().optional().describe("Create exit in both directions (default: true)"),
87
87
  },
88
88
  annotations: ANNOTATIONS.CREATE,
@@ -108,8 +108,8 @@ export function registerWorldTools(server) {
108
108
  server.registerTool("get_location_by_name", {
109
109
  description: "Look up a location by name within a game. Supports exact, partial, and fuzzy matching. Returns the best match or an error if no reasonable match found.",
110
110
  inputSchema: {
111
- gameId: z.string().describe("The game ID to search within"),
112
- name: z.string().describe("Location name to search for (case-insensitive)"),
111
+ gameId: validatedSchemas.id.describe("The game ID to search within"),
112
+ name: validatedSchemas.token.describe("Location name to search for (case-insensitive)"),
113
113
  },
114
114
  annotations: ANNOTATIONS.READ_ONLY,
115
115
  }, async ({ gameId, name }) => {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import * as characterTools from "../../tools/character.js";
3
3
  import * as narrativeTools from "../../tools/narrative.js";
4
4
  import * as combatTools from "../tools/combat.js";
5
- import { LIMITS } from "../../utils/validation.js";
5
+ import { LIMITS, validatedSchemas } from "../../utils/validation.js";
6
6
  import { ANNOTATIONS } from "../../utils/tool-annotations.js";
7
7
  // The one multi-entity workflow tool that reaches into combat, split out of
8
8
  // src/register/batch.ts (design §8, issue #17): everything else there --
@@ -17,8 +17,8 @@ export function registerRpgBatchTools(server) {
17
17
  server.registerTool("setup_combat_encounter", {
18
18
  description: "Complete combat setup in one call: creates enemy NPCs and starts combat with all participants (enemies + players at location). Returns the ready-to-play combat state.",
19
19
  inputSchema: {
20
- gameId: z.string().describe("The game ID"),
21
- locationId: z.string().describe("Location where combat takes place"),
20
+ gameId: validatedSchemas.id.describe("The game ID"),
21
+ locationId: validatedSchemas.id.describe("Location where combat takes place"),
22
22
  enemies: z
23
23
  .array(z.object({
24
24
  name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Enemy name"),
@@ -1,121 +1,140 @@
1
+ // Reusable Zod schemas, shared by the register modules.
2
+ //
3
+ // EVERY string here is bounded, and this file is where most of the engine's
4
+ // bounds actually live: `imageGenSchema` alone is accepted by seven tools, so
5
+ // one unbounded leaf in it was seven unbounded declarations in the published
6
+ // contract (issue #29 -- 698 of them, against 348 hand-written `.max()` calls
7
+ // spread across the register modules). Bounds belong in the shared schema, not
8
+ // at each call site, for the same reason the schema itself is shared.
9
+ //
10
+ // The tiers come from `validatedSchemas` (../utils/validation.ts) and nowhere
11
+ // else: `.token` for a short descriptor, `.description` for free text a model
12
+ // writes prose into, `.embeddedData` for inline base64. Reach for one of those
13
+ // rather than hand-rolling `z.string().max(...)` -- inlining the bound at each
14
+ // site is exactly how 698 of them came to be forgotten. Each access hands back
15
+ // a FRESH schema instance, deliberately; that file's comment says why, and it
16
+ // is the difference between publishing a declaration and publishing a `$ref`.
1
17
  import { z } from "zod";
18
+ import { LIMITS, validatedSchemas } from "../utils/validation.js";
2
19
  // Reusable Zod schemas for image generation
3
20
  export const subjectDescriptionSchema = z.object({
4
21
  type: z.enum(["character", "location", "item", "scene"]),
5
- primaryDescription: z.string(),
22
+ primaryDescription: validatedSchemas.description,
6
23
  physicalTraits: z.object({
7
- age: z.string().optional(),
8
- gender: z.string().optional(),
9
- bodyType: z.string().optional(),
10
- height: z.string().optional(),
11
- skinTone: z.string().optional(),
12
- hairColor: z.string().optional(),
13
- hairStyle: z.string().optional(),
14
- eyeColor: z.string().optional(),
15
- facialFeatures: z.string().optional(),
16
- distinguishingMarks: z.array(z.string()).optional(),
24
+ age: validatedSchemas.token.optional(),
25
+ gender: validatedSchemas.token.optional(),
26
+ bodyType: validatedSchemas.token.optional(),
27
+ height: validatedSchemas.token.optional(),
28
+ skinTone: validatedSchemas.token.optional(),
29
+ hairColor: validatedSchemas.token.optional(),
30
+ hairStyle: validatedSchemas.token.optional(),
31
+ eyeColor: validatedSchemas.token.optional(),
32
+ facialFeatures: validatedSchemas.description.optional(),
33
+ distinguishingMarks: validatedSchemas.stringArray.optional(),
17
34
  }).optional(),
18
35
  attire: z.object({
19
- description: z.string(),
20
- colors: z.array(z.string()).optional(),
21
- materials: z.array(z.string()).optional(),
22
- accessories: z.array(z.string()).optional(),
36
+ description: validatedSchemas.description,
37
+ colors: validatedSchemas.stringArray.optional(),
38
+ materials: validatedSchemas.stringArray.optional(),
39
+ accessories: validatedSchemas.stringArray.optional(),
23
40
  }).optional(),
24
41
  environment: z.object({
25
- setting: z.string(),
26
- timeOfDay: z.string().optional(),
27
- weather: z.string().optional(),
28
- lighting: z.string().optional(),
29
- architecture: z.string().optional(),
30
- vegetation: z.string().optional(),
31
- notableFeatures: z.array(z.string()).optional(),
42
+ setting: validatedSchemas.description,
43
+ timeOfDay: validatedSchemas.token.optional(),
44
+ weather: validatedSchemas.token.optional(),
45
+ lighting: validatedSchemas.token.optional(),
46
+ architecture: validatedSchemas.token.optional(),
47
+ vegetation: validatedSchemas.token.optional(),
48
+ notableFeatures: validatedSchemas.stringArray.optional(),
32
49
  }).optional(),
33
50
  objectDetails: z.object({
34
- material: z.string().optional(),
35
- size: z.string().optional(),
36
- condition: z.string().optional(),
37
- glowOrEffects: z.string().optional(),
51
+ material: validatedSchemas.token.optional(),
52
+ size: validatedSchemas.token.optional(),
53
+ condition: validatedSchemas.token.optional(),
54
+ glowOrEffects: validatedSchemas.description.optional(),
38
55
  }).optional(),
39
- pose: z.string().optional(),
40
- expression: z.string().optional(),
41
- action: z.string().optional(),
56
+ pose: validatedSchemas.description.optional(),
57
+ expression: validatedSchemas.description.optional(),
58
+ action: validatedSchemas.description.optional(),
42
59
  });
43
60
  export const styleDescriptionSchema = z.object({
44
- artisticStyle: z.string(),
45
- genre: z.string(),
46
- mood: z.string(),
47
- colorScheme: z.string().optional(),
48
- influences: z.array(z.string()).optional(),
49
- qualityTags: z.array(z.string()).optional(),
50
- negativeElements: z.array(z.string()).optional(),
61
+ artisticStyle: validatedSchemas.description,
62
+ genre: validatedSchemas.token,
63
+ mood: validatedSchemas.token,
64
+ colorScheme: validatedSchemas.token.optional(),
65
+ influences: validatedSchemas.stringArray.optional(),
66
+ qualityTags: validatedSchemas.stringArray.optional(),
67
+ negativeElements: validatedSchemas.stringArray.optional(),
51
68
  });
52
69
  export const compositionDescriptionSchema = z.object({
53
- framing: z.string(),
54
- cameraAngle: z.string().optional(),
55
- aspectRatio: z.string().optional(),
56
- focusPoint: z.string().optional(),
57
- background: z.string().optional(),
58
- depth: z.string().optional(),
70
+ framing: validatedSchemas.description,
71
+ cameraAngle: validatedSchemas.token.optional(),
72
+ aspectRatio: validatedSchemas.token.optional(),
73
+ focusPoint: validatedSchemas.description.optional(),
74
+ background: validatedSchemas.description.optional(),
75
+ depth: validatedSchemas.token.optional(),
59
76
  });
60
77
  export const comfyUIPromptSchema = z.object({
61
- positive: z.string(),
62
- negative: z.string(),
63
- checkpoint: z.string().optional(),
78
+ positive: validatedSchemas.description,
79
+ negative: validatedSchemas.description,
80
+ checkpoint: validatedSchemas.token.optional(),
64
81
  loras: z.array(z.object({
65
- name: z.string(),
82
+ name: validatedSchemas.name,
66
83
  weight: z.number(),
67
- })).optional(),
84
+ })).max(LIMITS.ARRAY_MAX).optional(),
68
85
  samplerSettings: z.object({
69
- sampler: z.string().optional(),
70
- scheduler: z.string().optional(),
86
+ sampler: validatedSchemas.token.optional(),
87
+ scheduler: validatedSchemas.token.optional(),
71
88
  steps: z.number().optional(),
72
89
  cfg: z.number().optional(),
73
90
  }).optional(),
74
91
  });
75
92
  export const generatedImageSchema = z.object({
76
- id: z.string(),
77
- tool: z.string(),
78
- prompt: z.string(),
79
- url: z.string().optional(),
80
- base64: z.string().optional(),
93
+ id: validatedSchemas.id,
94
+ tool: validatedSchemas.token,
95
+ prompt: validatedSchemas.description,
96
+ // A URL, including a `data:` one. Over DESCRIPTION_MAX it is not an address,
97
+ // it is a payload, and `base64` below is the field for those.
98
+ url: validatedSchemas.description.optional(),
99
+ base64: validatedSchemas.embeddedData.optional(),
81
100
  seed: z.number().optional(),
82
- timestamp: z.string(),
83
- metadata: z.record(z.string(), z.unknown()).optional(),
101
+ timestamp: validatedSchemas.token,
102
+ metadata: z.record(validatedSchemas.token, z.unknown()).optional(),
84
103
  });
85
104
  export const imageGenSchema = z.object({
86
105
  subject: subjectDescriptionSchema,
87
106
  style: styleDescriptionSchema,
88
107
  composition: compositionDescriptionSchema,
89
108
  prompts: z.object({
90
- generic: z.string().optional(),
91
- sdxl: z.string().optional(),
92
- dalle: z.string().optional(),
93
- midjourney: z.string().optional(),
94
- flux: z.string().optional(),
109
+ generic: validatedSchemas.description.optional(),
110
+ sdxl: validatedSchemas.description.optional(),
111
+ dalle: validatedSchemas.description.optional(),
112
+ midjourney: validatedSchemas.description.optional(),
113
+ flux: validatedSchemas.description.optional(),
95
114
  comfyui: comfyUIPromptSchema.optional(),
96
115
  }).optional(),
97
- generations: z.array(generatedImageSchema).optional(),
116
+ generations: z.array(generatedImageSchema).max(LIMITS.ARRAY_MAX).optional(),
98
117
  consistency: z.object({
99
- characterRef: z.string().optional(),
100
- seedImage: z.string().optional(),
101
- colorPalette: z.array(z.string()).optional(),
102
- styleRef: z.string().optional(),
118
+ characterRef: validatedSchemas.description.optional(),
119
+ seedImage: validatedSchemas.description.optional(),
120
+ colorPalette: validatedSchemas.stringArray.optional(),
121
+ styleRef: validatedSchemas.description.optional(),
103
122
  }).optional(),
104
123
  }).describe("Image generation metadata for visual representation");
105
124
  // Voice schema for characters
106
125
  export const voiceSchema = z.object({
107
126
  pitch: z.enum(["very_low", "low", "medium", "high", "very_high"]).describe("Voice pitch"),
108
127
  speed: z.enum(["very_slow", "slow", "medium", "fast", "very_fast"]).describe("Speaking speed"),
109
- tone: z.string().describe("Voice tone (e.g., 'gravelly', 'melodic', 'nasal', 'breathy')"),
110
- accent: z.string().optional().describe("Accent (e.g., 'Scottish', 'French', 'Brooklyn')"),
111
- quirks: z.array(z.string()).optional().describe("Speech quirks (e.g., 'stutters when nervous')"),
112
- description: z.string().optional().describe("Free-form voice description for more nuance"),
128
+ tone: validatedSchemas.token.describe("Voice tone (e.g., 'gravelly', 'melodic', 'nasal', 'breathy')"),
129
+ accent: validatedSchemas.token.optional().describe("Accent (e.g., 'Scottish', 'French', 'Brooklyn')"),
130
+ quirks: validatedSchemas.stringArray.optional().describe("Speech quirks (e.g., 'stutters when nervous')"),
131
+ description: validatedSchemas.description.optional().describe("Free-form voice description for more nuance"),
113
132
  });
114
133
  // Random table entry schema
115
134
  export const tableEntrySchema = z.object({
116
135
  minRoll: z.number().optional().describe("Minimum roll to get this result (for ranged tables)"),
117
136
  maxRoll: z.number().optional().describe("Maximum roll for this result"),
118
137
  weight: z.number().optional().describe("Weight for weighted random selection"),
119
- result: z.string().describe("The result text"),
120
- effects: z.record(z.unknown()).optional().describe("Optional structured effects"),
138
+ result: validatedSchemas.description.describe("The result text"),
139
+ effects: z.record(validatedSchemas.token, z.unknown()).optional().describe("Optional structured effects"),
121
140
  });
@@ -1,24 +1,53 @@
1
1
  import { z } from "zod";
2
2
  /**
3
3
  * Common input length limits for security and performance.
4
- * These prevent DoS attacks via extremely large inputs.
4
+ *
5
+ * These prevent DoS attacks via extremely large inputs -- and they are also
6
+ * the engine's PUBLISHED CONTRACT. A `.max()` here lands in the tool's
7
+ * `input_schema` and `tools/list` hands it to every client before the first
8
+ * call, so a consumer can read what a field takes instead of hard-coding a
9
+ * copy of these numbers. One does. Leaving a string parameter unbounded is
10
+ * therefore not a missing safety net but an active statement that the field
11
+ * takes anything; `src/__tests__/schemaBounds.test.ts` fails on any that is
12
+ * (issue #29).
5
13
  */
6
14
  export declare const LIMITS: {
7
15
  readonly NAME_MAX: 200;
8
16
  readonly DESCRIPTION_MAX: 5000;
9
17
  readonly CONTENT_MAX: 50000;
10
18
  readonly NARRATIVE_MAX: 200000;
19
+ readonly EMBEDDED_DATA_MAX: 2000000;
11
20
  readonly ARRAY_MAX: 100;
12
21
  readonly MAX_DEPTH: 10;
13
22
  };
14
23
  /**
15
- * Pre-built Zod schemas with length limits
24
+ * Pre-built Zod schemas with length limits.
25
+ *
26
+ * EVERY PROPERTY HERE IS A GETTER, AND HANDS BACK A FRESH INSTANCE.
27
+ *
28
+ * That is not a style: `zod-to-json-schema` (which the MCP SDK runs over
29
+ * every `inputSchema` on its way to `tools/list`) de-duplicates by object
30
+ * IDENTITY. Reuse one instance twice inside a single tool and the second
31
+ * occurrence is published as
32
+ *
33
+ * { "$ref": "#/properties/imageGen/properties/subject/properties/..." }
34
+ *
35
+ * -- a pointer into a sibling property, in place of the declaration a
36
+ * consumer came to read. Sharing these as plain constants put 648 of those
37
+ * into the contract, where there had been none; a fresh instance per access
38
+ * puts the declaration back at every site. `src/__tests__/schemaBounds.test.ts`
39
+ * fails on any `$ref` in a published schema, which is what caught it.
40
+ *
41
+ * The cost is a schema object built per access. They are read at server
42
+ * construction, not per call.
16
43
  */
17
44
  export declare const validatedSchemas: {
18
45
  readonly name: z.ZodString;
46
+ readonly token: z.ZodString;
19
47
  readonly description: z.ZodString;
20
48
  readonly content: z.ZodString;
21
49
  readonly narrative: z.ZodString;
50
+ readonly embeddedData: z.ZodString;
22
51
  readonly id: z.ZodString;
23
52
  readonly tag: z.ZodString;
24
53
  readonly stringArray: z.ZodArray<z.ZodString, "many">;
@@ -1,7 +1,15 @@
1
1
  import { z } from "zod";
2
2
  /**
3
3
  * Common input length limits for security and performance.
4
- * These prevent DoS attacks via extremely large inputs.
4
+ *
5
+ * These prevent DoS attacks via extremely large inputs -- and they are also
6
+ * the engine's PUBLISHED CONTRACT. A `.max()` here lands in the tool's
7
+ * `input_schema` and `tools/list` hands it to every client before the first
8
+ * call, so a consumer can read what a field takes instead of hard-coding a
9
+ * copy of these numbers. One does. Leaving a string parameter unbounded is
10
+ * therefore not a missing safety net but an active statement that the field
11
+ * takes anything; `src/__tests__/schemaBounds.test.ts` fails on any that is
12
+ * (issue #29).
5
13
  */
6
14
  export const LIMITS = {
7
15
  // Short text fields (names, titles)
@@ -12,31 +20,80 @@ export const LIMITS = {
12
20
  CONTENT_MAX: 50000,
13
21
  // Very long text fields (story exports, narrative)
14
22
  NARRATIVE_MAX: 200000,
23
+ // Inline binary carried as a data URI or bare base64 -- an image, not prose.
24
+ // Deliberately generous rather than a prose tier: the fields that take one
25
+ // (`imageGen.generations[].base64`) accepted anything before they were
26
+ // bounded, and a real inline PNG is hundreds of kilobytes.
27
+ EMBEDDED_DATA_MAX: 2000000,
15
28
  // Array limits
16
29
  ARRAY_MAX: 100,
17
30
  // JSON object depth limit (for nested structures)
18
31
  MAX_DEPTH: 10,
19
32
  };
20
33
  /**
21
- * Pre-built Zod schemas with length limits
34
+ * Pre-built Zod schemas with length limits.
35
+ *
36
+ * EVERY PROPERTY HERE IS A GETTER, AND HANDS BACK A FRESH INSTANCE.
37
+ *
38
+ * That is not a style: `zod-to-json-schema` (which the MCP SDK runs over
39
+ * every `inputSchema` on its way to `tools/list`) de-duplicates by object
40
+ * IDENTITY. Reuse one instance twice inside a single tool and the second
41
+ * occurrence is published as
42
+ *
43
+ * { "$ref": "#/properties/imageGen/properties/subject/properties/..." }
44
+ *
45
+ * -- a pointer into a sibling property, in place of the declaration a
46
+ * consumer came to read. Sharing these as plain constants put 648 of those
47
+ * into the contract, where there had been none; a fresh instance per access
48
+ * puts the declaration back at every site. `src/__tests__/schemaBounds.test.ts`
49
+ * fails on any `$ref` in a published schema, which is what caught it.
50
+ *
51
+ * The cost is a schema object built per access. They are read at server
52
+ * construction, not per call.
22
53
  */
23
54
  export const validatedSchemas = {
24
55
  // Name fields (character names, location names, etc.)
25
- name: z.string().min(1).max(LIMITS.NAME_MAX),
56
+ get name() {
57
+ return z.string().min(1).max(LIMITS.NAME_MAX);
58
+ },
59
+ // Short free text with a ceiling and NO floor -- a colour, a direction, an
60
+ // aspect ratio, a sampler. Same length as `name`, without `.min(1)`: bounding
61
+ // a field that accepts "" today must not also start rejecting it.
62
+ get token() {
63
+ return z.string().max(LIMITS.NAME_MAX);
64
+ },
26
65
  // Description fields
27
- description: z.string().max(LIMITS.DESCRIPTION_MAX),
66
+ get description() {
67
+ return z.string().max(LIMITS.DESCRIPTION_MAX);
68
+ },
28
69
  // Content fields (notes, large text)
29
- content: z.string().max(LIMITS.CONTENT_MAX),
70
+ get content() {
71
+ return z.string().max(LIMITS.CONTENT_MAX);
72
+ },
30
73
  // Narrative content (very large text allowed)
31
- narrative: z.string().max(LIMITS.NARRATIVE_MAX),
74
+ get narrative() {
75
+ return z.string().max(LIMITS.NARRATIVE_MAX);
76
+ },
77
+ // Inline binary carried as base64 or a data URI -- an image, not prose.
78
+ get embeddedData() {
79
+ return z.string().max(LIMITS.EMBEDDED_DATA_MAX);
80
+ },
32
81
  // ID fields (UUIDs are 36 characters)
33
- id: z.string().max(100),
82
+ get id() {
83
+ return z.string().max(100);
84
+ },
34
85
  // Tag/category strings
35
- tag: z.string().min(1).max(100),
86
+ get tag() {
87
+ return z.string().min(1).max(100);
88
+ },
36
89
  // Array of strings with limits
37
- stringArray: z.array(z.string().max(LIMITS.NAME_MAX)).max(LIMITS.ARRAY_MAX),
90
+ get stringArray() {
91
+ return z.array(z.string().max(LIMITS.NAME_MAX)).max(LIMITS.ARRAY_MAX);
92
+ },
38
93
  // Array of tags
39
- tagArray: z.array(z.string().min(1).max(100)).max(LIMITS.ARRAY_MAX),
94
+ get tagArray() {
95
+ return z.array(z.string().min(1).max(100)).max(LIMITS.ARRAY_MAX);
96
+ },
40
97
  };
41
98
  /**
42
99
  * Helper to create a bounded string schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run-dmcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "An MCP server for LLM-run interactive fiction where the server owns what is true - including when it was true. A continuation of DMCP.",
5
5
  "license": "MIT",
6
6
  "author": "Derek Ferguson",