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
package/README.md CHANGED
@@ -59,14 +59,28 @@ schema comes up, where the database lives, and whether anything listens.
59
59
 
60
60
  **The package root is the core, and the tabletop surface is a layer above it.** Dice, combat,
61
61
  abilities, status effects, random tables and quests are genuinely game-shaped — an optional
62
- dependency, not part of the engine (see [docs/DESIGN.md](docs/DESIGN.md) §8). A consumer that only
63
- needs entities, facts, events and the timeline imports `run-dmcp` and calls `createCoreMcpServer`. A
64
- consumer that wants the full tabletop surface imports `run-dmcp/rpg` and calls `createMcpServer`
65
- same name, same options, the whole assembly this package has always served:
62
+ dependency, not part of the engine (see [docs/DESIGN.md](docs/DESIGN.md) §8).
63
+
64
+ **Mechanism and assembly are separate entries, and importing one never loads the other** (since
65
+ 0.4.0). Four specifiers, two layers by two kinds:
66
+
67
+ | | mechanism — functions, constants, types | assembly — builds a server |
68
+ |---|---|---|
69
+ | **core** | `run-dmcp` | `run-dmcp/server` → `createCoreMcpServer` |
70
+ | **+ tabletop** | `run-dmcp/rpg` | `run-dmcp/rpg/server` → `createMcpServer` |
71
+
72
+ Assembling a server means loading the MCP SDK, twenty-one register modules and (for the full
73
+ assembly) the web UI. Wanting `createGame` or `LIMITS` does not, and until 0.4.0 both entries
74
+ charged for it anyway: the core entry cost 97.4ms per process cold and costs 47.0ms now, which is
75
+ below the MCP SDK's own 55.0ms, because without the assembly it no longer loads the SDK at all.
76
+
77
+ A consumer that *does* build a server loads the SDK regardless, so it saves less than that
78
+ difference suggests — measured with the SDK warm, importing both entries, 67.4ms → 38.0ms, about
79
+ 29ms per process. The functions did not change; their specifiers did.
66
80
 
67
81
  ```ts
68
82
  import { initializeSchema, type SchemaMigration } from "run-dmcp";
69
- import { createMcpServer } from "run-dmcp/rpg";
83
+ import { createMcpServer } from "run-dmcp/rpg/server";
70
84
 
71
85
  const migrations: SchemaMigration[] = [
72
86
  {
@@ -18,7 +18,7 @@ import { startHttpServer } from "../http/server.js";
18
18
  // (design §8, issue #17). The application always served the full surface,
19
19
  // so it reaches for it here rather than the core-only `createCoreMcpServer`
20
20
  // in ../mcp-server.js.
21
- import { createMcpServer } from "../rpg/index.js";
21
+ import { createMcpServer } from "../rpg/server.js";
22
22
  import { httpPortFromEnv, setHttpPort, webUiEnabled } from "../utils/webui.js";
23
23
  import { createLogger } from "../utils/logger.js";
24
24
  const log = createLogger("bin");
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { createCoreMcpServer, SERVER_NAME, SERVER_VERSION } from "./mcp-server.js";
2
1
  export { getDatabase, closeDatabase, withTransaction, getDatabasePath, getDataDir, resolveDataPathFrom, } from "./db/connection.js";
3
2
  export type { DataPathInputs } from "./db/connection.js";
4
3
  export { initializeSchema } from "./db/schema.js";
package/dist/index.js CHANGED
@@ -26,7 +26,13 @@
26
26
  // depending up into it. `src/__tests__/layerBoundary.test.ts` walks the
27
27
  // static import graph from this file and fails if anything under `src/rpg/`
28
28
  // is reachable from it.
29
- export { createCoreMcpServer, SERVER_NAME, SERVER_VERSION } from "./mcp-server.js";
29
+ // The assembled core server is NOT here. It moved to "run-dmcp/server"
30
+ // (src/server.ts) so that importing mechanism stops loading it: building a
31
+ // server costs the MCP SDK and twenty-one register modules, and a consumer
32
+ // reaching for `LIMITS` or `createGame` was paying for both -- 97.4ms per
33
+ // process against 46.8ms without it, cold, at 0.3.0. Enforced by
34
+ // src/__tests__/assemblyBoundary.test.ts, which walks this file's runtime
35
+ // import graph and fails if the assembly is reachable from it again.
30
36
  // The database, and where it lives. The path resolves against the consuming
31
37
  // application (DMCP_DB_PATH, else an existing XDG data directory, else the
32
38
  // working directory) and never against this package's install location.
@@ -2,7 +2,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { type Mechanic } from "./timeline/resolve.js";
3
3
  import { type RenderVocabulary } from "./timeline/render.js";
4
4
  export declare const SERVER_NAME = "dmcp";
5
- export declare const SERVER_VERSION = "0.3.0";
5
+ /** What the server tells a client it is, in the MCP handshake. Kept equal to
6
+ * the published package version by src/__tests__/serverVersion.test.ts --
7
+ * this said "0.3.0" for the whole of 0.4.0, because a release bumps
8
+ * package.json and nothing was watching this. */
9
+ export declare const SERVER_VERSION = "0.5.0";
6
10
  /**
7
11
  * Build an MCP server with every CORE tool, resource and prompt this engine
8
12
  * serves -- entities, facts, events, the timeline, and the entity/property
@@ -41,7 +41,11 @@ import { registerRenderTools } from "./register/render.js";
41
41
  import { createResolver } from "./timeline/resolve.js";
42
42
  import { createStateRenderer } from "./timeline/render.js";
43
43
  export const SERVER_NAME = "dmcp";
44
- export const SERVER_VERSION = "0.3.0";
44
+ /** What the server tells a client it is, in the MCP handshake. Kept equal to
45
+ * the published package version by src/__tests__/serverVersion.test.ts --
46
+ * this said "0.3.0" for the whole of 0.4.0, because a release bumps
47
+ * package.json and nothing was watching this. */
48
+ export const SERVER_VERSION = "0.5.0";
45
49
  /**
46
50
  * Build an MCP server with every CORE tool, resource and prompt this engine
47
51
  * serves -- entities, facts, events, the timeline, and the entity/property
@@ -3,7 +3,7 @@ import * as characterTools from "../tools/character.js";
3
3
  import * as worldTools from "../tools/world.js";
4
4
  import * as inventoryTools from "../tools/inventory.js";
5
5
  import * as narrativeTools from "../tools/narrative.js";
6
- import { LIMITS } from "../utils/validation.js";
6
+ import { LIMITS, validatedSchemas } from "../utils/validation.js";
7
7
  import { ANNOTATIONS } from "../utils/tool-annotations.js";
8
8
  import { imageGenSchema, voiceSchema } from "../schemas/index.js";
9
9
  // Multi-entity workflow tools over entity/property concepts -- characters,
@@ -18,7 +18,7 @@ export function registerBatchTools(server) {
18
18
  server.registerTool("batch_create_npcs", {
19
19
  description: "Create multiple NPCs at once. Use this when populating a location with several characters. Returns all created characters.",
20
20
  inputSchema: {
21
- gameId: z.string().describe("The game ID"),
21
+ gameId: validatedSchemas.id.describe("The game ID"),
22
22
  npcs: z
23
23
  .array(z.object({
24
24
  name: z.string().min(1).max(LIMITS.NAME_MAX).describe("NPC name"),
@@ -28,10 +28,10 @@ export function registerBatchTools(server) {
28
28
  .object({
29
29
  health: z.number().optional(),
30
30
  maxHealth: z.number().optional(),
31
- conditions: z.array(z.string()).optional(),
31
+ conditions: validatedSchemas.stringArray.optional(),
32
32
  })
33
33
  .optional(),
34
- locationId: z.string().optional().describe("Starting location"),
34
+ locationId: validatedSchemas.id.optional().describe("Starting location"),
35
35
  notes: z.string().max(LIMITS.CONTENT_MAX).optional(),
36
36
  voice: voiceSchema.optional(),
37
37
  imageGen: imageGenSchema.optional(),
@@ -105,12 +105,13 @@ export function registerBatchTools(server) {
105
105
  server.registerTool("scene_transition", {
106
106
  description: "Complete scene transition in one call: moves specified characters to a new location and logs a narrative event. Perfect for moving between scenes.",
107
107
  inputSchema: {
108
- gameId: z.string().describe("The game ID"),
108
+ gameId: validatedSchemas.id.describe("The game ID"),
109
109
  characterIds: z
110
- .array(z.string())
110
+ .array(validatedSchemas.id)
111
111
  .min(1)
112
+ .max(LIMITS.ARRAY_MAX)
112
113
  .describe("Characters to move to the new scene"),
113
- destinationId: z.string().describe("Destination location ID"),
114
+ destinationId: validatedSchemas.id.describe("Destination location ID"),
114
115
  narrativeDescription: z
115
116
  .string()
116
117
  .max(LIMITS.DESCRIPTION_MAX)
@@ -165,7 +166,7 @@ export function registerBatchTools(server) {
165
166
  server.registerTool("get_character_context", {
166
167
  description: "Get comprehensive character context in one call: character details, inventory, and current location info. Reduces multiple tool calls to one.",
167
168
  inputSchema: {
168
- characterId: z.string().describe("The character ID"),
169
+ characterId: validatedSchemas.id.describe("The character ID"),
169
170
  },
170
171
  annotations: ANNOTATIONS.READ_ONLY,
171
172
  }, async ({ characterId }) => {
@@ -219,8 +220,8 @@ export function registerBatchTools(server) {
219
220
  server.registerTool("get_location_context", {
220
221
  description: "Get comprehensive location context in one call: location details, present characters, and items here. Reduces multiple tool calls to one.",
221
222
  inputSchema: {
222
- locationId: z.string().describe("The location ID"),
223
- gameId: z.string().describe("The game ID (needed for character lookup)"),
223
+ locationId: validatedSchemas.id.describe("The location ID"),
224
+ gameId: validatedSchemas.id.describe("The game ID (needed for character lookup)"),
224
225
  },
225
226
  annotations: ANNOTATIONS.READ_ONLY,
226
227
  }, async ({ locationId, gameId }) => {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import * as characterTools from "../tools/character.js";
3
3
  import { imageGenSchema, voiceSchema } 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
  import { characterOutputSchema, characterStatusSchema, conditionModifyOutputSchema, } from "../utils/output-schemas.js";
7
7
  import { verbositySchema, applyVerbosity } from "../utils/verbosity.js";
@@ -44,7 +44,7 @@ export function registerCharacterTools(server) {
44
44
  server.registerTool("get_character", {
45
45
  description: "Get character details",
46
46
  inputSchema: {
47
- characterId: z.string().describe("The character ID"),
47
+ characterId: validatedSchemas.id.describe("The character ID"),
48
48
  },
49
49
  outputSchema: characterOutputSchema,
50
50
  annotations: ANNOTATIONS.READ_ONLY,
@@ -67,13 +67,13 @@ export function registerCharacterTools(server) {
67
67
  server.registerTool("update_character", {
68
68
  description: "Update character attributes, skills, status, or voice",
69
69
  inputSchema: {
70
- characterId: z.string().describe("The character ID"),
71
- name: z.string().optional().describe("New name"),
70
+ characterId: validatedSchemas.id.describe("The character ID"),
71
+ name: validatedSchemas.token.optional().describe("New name"),
72
72
  attributes: z.record(z.string(), z.number()).optional().describe("Attribute updates"),
73
73
  skills: z.record(z.string(), z.number()).optional().describe("Skill updates"),
74
74
  status: z.record(z.string(), z.unknown()).optional().describe("Status updates"),
75
- locationId: z.string().optional().describe("New location"),
76
- notes: z.string().optional().describe("Notes update"),
75
+ locationId: validatedSchemas.id.optional().describe("New location"),
76
+ notes: validatedSchemas.content.optional().describe("Notes update"),
77
77
  voice: voiceSchema.nullable().optional().describe("Voice characteristics (null to remove)"),
78
78
  imageGen: imageGenSchema.nullable().optional().describe("Image generation metadata (null to remove)"),
79
79
  },
@@ -98,9 +98,9 @@ export function registerCharacterTools(server) {
98
98
  server.registerTool("list_characters", {
99
99
  description: "List characters in a game. Use verbosity to control response size: 'minimal' for quick lookups (id/name only), 'standard' for common fields, 'full' for all details.",
100
100
  inputSchema: {
101
- gameId: z.string().describe("The game ID"),
101
+ gameId: validatedSchemas.id.describe("The game ID"),
102
102
  isPlayer: z.boolean().optional().describe("Filter by player/NPC"),
103
- locationId: z.string().optional().describe("Filter by location"),
103
+ locationId: validatedSchemas.id.optional().describe("Filter by location"),
104
104
  verbosity: verbositySchema,
105
105
  },
106
106
  outputSchema: {
@@ -128,8 +128,8 @@ export function registerCharacterTools(server) {
128
128
  server.registerTool("move_character", {
129
129
  description: "Move a character to a different location",
130
130
  inputSchema: {
131
- characterId: z.string().describe("The character ID"),
132
- locationId: z.string().describe("The destination location ID"),
131
+ characterId: validatedSchemas.id.describe("The character ID"),
132
+ locationId: validatedSchemas.id.describe("The destination location ID"),
133
133
  },
134
134
  outputSchema: {
135
135
  success: z.boolean(),
@@ -163,7 +163,7 @@ export function registerCharacterTools(server) {
163
163
  server.registerTool("modify_health", {
164
164
  description: "Modify a character's health. Use mode 'damage' to reduce health, or 'heal' to restore health.",
165
165
  inputSchema: {
166
- characterId: z.string().describe("The character ID"),
166
+ characterId: validatedSchemas.id.describe("The character ID"),
167
167
  mode: z.enum(["damage", "heal"]).describe("'damage' to reduce health, 'heal' to restore health"),
168
168
  amount: z.number().describe("Amount of damage or healing"),
169
169
  },
@@ -201,7 +201,7 @@ export function registerCharacterTools(server) {
201
201
  server.registerTool("modify_conditions", {
202
202
  description: "Add and/or remove conditions from a character in a single call. More efficient than separate add/remove calls.",
203
203
  inputSchema: {
204
- characterId: z.string().describe("The character ID"),
204
+ characterId: validatedSchemas.id.describe("The character ID"),
205
205
  add: z.array(z.string().max(100)).max(LIMITS.ARRAY_MAX).optional().describe("Conditions to add"),
206
206
  remove: z.array(z.string().max(100)).max(LIMITS.ARRAY_MAX).optional().describe("Conditions to remove"),
207
207
  },
@@ -244,8 +244,8 @@ export function registerCharacterTools(server) {
244
244
  server.registerTool("get_character_by_name", {
245
245
  description: "Look up a character by name within a game. Supports exact, partial, and fuzzy matching. Returns the best match or an error if no reasonable match found.",
246
246
  inputSchema: {
247
- gameId: z.string().describe("The game ID to search within"),
248
- name: z.string().describe("Character name to search for (case-insensitive)"),
247
+ gameId: validatedSchemas.id.describe("The game ID to search within"),
248
+ name: validatedSchemas.token.describe("Character name to search for (case-insensitive)"),
249
249
  },
250
250
  outputSchema: characterOutputSchema,
251
251
  annotations: ANNOTATIONS.READ_ONLY,
@@ -268,7 +268,7 @@ export function registerCharacterTools(server) {
268
268
  server.registerTool("list_character_summaries", {
269
269
  description: "List characters with quick-identification summaries. Use this to help identify characters by appearance before looking up their full details. More efficient than loading full character data when you just need to identify who's who.",
270
270
  inputSchema: {
271
- gameId: z.string().describe("The game ID"),
271
+ gameId: validatedSchemas.id.describe("The game ID"),
272
272
  },
273
273
  outputSchema: {
274
274
  summaries: z.array(z.object({
@@ -297,7 +297,7 @@ export function registerCharacterTools(server) {
297
297
  server.registerTool("delete_character", {
298
298
  description: "Delete a character permanently. This is IRREVERSIBLE and will remove the character and all associated data.",
299
299
  inputSchema: {
300
- characterId: z.string().describe("The character ID to delete"),
300
+ characterId: validatedSchemas.id.describe("The character ID to delete"),
301
301
  },
302
302
  outputSchema: {
303
303
  success: z.boolean(),