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.
- package/dist/mcp-server.d.ts +5 -1
- package/dist/mcp-server.js +5 -1
- package/dist/register/batch.js +11 -10
- package/dist/register/character.js +16 -16
- package/dist/register/core.js +128 -111
- package/dist/register/display.js +42 -41
- package/dist/register/resolve.js +4 -1
- package/dist/register/world.js +13 -13
- package/dist/rpg/register/batch.js +3 -3
- package/dist/schemas/index.js +91 -72
- package/dist/utils/validation.d.ts +31 -2
- package/dist/utils/validation.js +67 -10
- package/package.json +1 -1
package/dist/register/world.js
CHANGED
|
@@ -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:
|
|
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:
|
|
48
|
-
name:
|
|
49
|
-
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:
|
|
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:
|
|
82
|
-
toLocationId:
|
|
83
|
-
fromDirection:
|
|
84
|
-
toDirection:
|
|
85
|
-
description:
|
|
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:
|
|
112
|
-
name:
|
|
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:
|
|
21
|
-
locationId:
|
|
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"),
|
package/dist/schemas/index.js
CHANGED
|
@@ -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:
|
|
22
|
+
primaryDescription: validatedSchemas.description,
|
|
6
23
|
physicalTraits: z.object({
|
|
7
|
-
age:
|
|
8
|
-
gender:
|
|
9
|
-
bodyType:
|
|
10
|
-
height:
|
|
11
|
-
skinTone:
|
|
12
|
-
hairColor:
|
|
13
|
-
hairStyle:
|
|
14
|
-
eyeColor:
|
|
15
|
-
facialFeatures:
|
|
16
|
-
distinguishingMarks:
|
|
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:
|
|
20
|
-
colors:
|
|
21
|
-
materials:
|
|
22
|
-
accessories:
|
|
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:
|
|
26
|
-
timeOfDay:
|
|
27
|
-
weather:
|
|
28
|
-
lighting:
|
|
29
|
-
architecture:
|
|
30
|
-
vegetation:
|
|
31
|
-
notableFeatures:
|
|
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:
|
|
35
|
-
size:
|
|
36
|
-
condition:
|
|
37
|
-
glowOrEffects:
|
|
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:
|
|
40
|
-
expression:
|
|
41
|
-
action:
|
|
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:
|
|
45
|
-
genre:
|
|
46
|
-
mood:
|
|
47
|
-
colorScheme:
|
|
48
|
-
influences:
|
|
49
|
-
qualityTags:
|
|
50
|
-
negativeElements:
|
|
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:
|
|
54
|
-
cameraAngle:
|
|
55
|
-
aspectRatio:
|
|
56
|
-
focusPoint:
|
|
57
|
-
background:
|
|
58
|
-
depth:
|
|
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:
|
|
62
|
-
negative:
|
|
63
|
-
checkpoint:
|
|
78
|
+
positive: validatedSchemas.description,
|
|
79
|
+
negative: validatedSchemas.description,
|
|
80
|
+
checkpoint: validatedSchemas.token.optional(),
|
|
64
81
|
loras: z.array(z.object({
|
|
65
|
-
name:
|
|
82
|
+
name: validatedSchemas.name,
|
|
66
83
|
weight: z.number(),
|
|
67
|
-
})).optional(),
|
|
84
|
+
})).max(LIMITS.ARRAY_MAX).optional(),
|
|
68
85
|
samplerSettings: z.object({
|
|
69
|
-
sampler:
|
|
70
|
-
scheduler:
|
|
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:
|
|
77
|
-
tool:
|
|
78
|
-
prompt:
|
|
79
|
-
|
|
80
|
-
base64
|
|
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:
|
|
83
|
-
metadata: z.record(
|
|
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:
|
|
91
|
-
sdxl:
|
|
92
|
-
dalle:
|
|
93
|
-
midjourney:
|
|
94
|
-
flux:
|
|
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:
|
|
100
|
-
seedImage:
|
|
101
|
-
colorPalette:
|
|
102
|
-
styleRef:
|
|
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:
|
|
110
|
-
accent:
|
|
111
|
-
quirks:
|
|
112
|
-
description:
|
|
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:
|
|
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
|
-
*
|
|
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">;
|
package/dist/utils/validation.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/**
|
|
3
3
|
* Common input length limits for security and performance.
|
|
4
|
-
*
|
|
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
|
|
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
|
|
66
|
+
get description() {
|
|
67
|
+
return z.string().max(LIMITS.DESCRIPTION_MAX);
|
|
68
|
+
},
|
|
28
69
|
// Content fields (notes, large text)
|
|
29
|
-
content
|
|
70
|
+
get content() {
|
|
71
|
+
return z.string().max(LIMITS.CONTENT_MAX);
|
|
72
|
+
},
|
|
30
73
|
// Narrative content (very large text allowed)
|
|
31
|
-
narrative
|
|
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
|
|
82
|
+
get id() {
|
|
83
|
+
return z.string().max(100);
|
|
84
|
+
},
|
|
34
85
|
// Tag/category strings
|
|
35
|
-
tag
|
|
86
|
+
get tag() {
|
|
87
|
+
return z.string().min(1).max(100);
|
|
88
|
+
},
|
|
36
89
|
// Array of strings with limits
|
|
37
|
-
stringArray
|
|
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
|
|
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.
|
|
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",
|