run-dmcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +79 -0
  3. package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
  4. package/dist/__tests__/engineVocabulary.test.js +147 -0
  5. package/dist/db/__tests__/connection.test.d.ts +1 -0
  6. package/dist/db/__tests__/connection.test.js +72 -0
  7. package/dist/db/__tests__/testDb.d.ts +33 -0
  8. package/dist/db/__tests__/testDb.js +41 -0
  9. package/dist/db/connection.d.ts +22 -0
  10. package/dist/db/connection.js +107 -0
  11. package/dist/db/schema.d.ts +1 -0
  12. package/dist/db/schema.js +725 -0
  13. package/dist/events/emitter.d.ts +22 -0
  14. package/dist/events/emitter.js +71 -0
  15. package/dist/http/server.d.ts +3 -0
  16. package/dist/http/server.js +649 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +92 -0
  19. package/dist/register/abilities.d.ts +2 -0
  20. package/dist/register/abilities.js +165 -0
  21. package/dist/register/audio.d.ts +2 -0
  22. package/dist/register/audio.js +326 -0
  23. package/dist/register/batch.d.ts +2 -0
  24. package/dist/register/batch.js +343 -0
  25. package/dist/register/character.d.ts +2 -0
  26. package/dist/register/character.js +324 -0
  27. package/dist/register/combat.d.ts +2 -0
  28. package/dist/register/combat.js +207 -0
  29. package/dist/register/core.d.ts +2 -0
  30. package/dist/register/core.js +1040 -0
  31. package/dist/register/display.d.ts +2 -0
  32. package/dist/register/display.js +263 -0
  33. package/dist/register/factions.d.ts +2 -0
  34. package/dist/register/factions.js +186 -0
  35. package/dist/register/images.d.ts +2 -0
  36. package/dist/register/images.js +400 -0
  37. package/dist/register/inventory.d.ts +2 -0
  38. package/dist/register/inventory.js +115 -0
  39. package/dist/register/mcp-prompts.d.ts +2 -0
  40. package/dist/register/mcp-prompts.js +684 -0
  41. package/dist/register/mcp-resources.d.ts +2 -0
  42. package/dist/register/mcp-resources.js +335 -0
  43. package/dist/register/narrative.d.ts +2 -0
  44. package/dist/register/narrative.js +242 -0
  45. package/dist/register/notes.d.ts +2 -0
  46. package/dist/register/notes.js +170 -0
  47. package/dist/register/pause.d.ts +2 -0
  48. package/dist/register/pause.js +580 -0
  49. package/dist/register/quests.d.ts +2 -0
  50. package/dist/register/quests.js +118 -0
  51. package/dist/register/relationships.d.ts +2 -0
  52. package/dist/register/relationships.js +147 -0
  53. package/dist/register/resources.d.ts +2 -0
  54. package/dist/register/resources.js +277 -0
  55. package/dist/register/secrets.d.ts +2 -0
  56. package/dist/register/secrets.js +192 -0
  57. package/dist/register/status.d.ts +2 -0
  58. package/dist/register/status.js +130 -0
  59. package/dist/register/tables.d.ts +2 -0
  60. package/dist/register/tables.js +146 -0
  61. package/dist/register/tags.d.ts +2 -0
  62. package/dist/register/tags.js +114 -0
  63. package/dist/register/time.d.ts +2 -0
  64. package/dist/register/time.js +281 -0
  65. package/dist/register/world.d.ts +2 -0
  66. package/dist/register/world.js +127 -0
  67. package/dist/schemas/index.d.ts +921 -0
  68. package/dist/schemas/index.js +121 -0
  69. package/dist/test-setup.d.ts +1 -0
  70. package/dist/test-setup.js +13 -0
  71. package/dist/tools/__tests__/audio.test.d.ts +1 -0
  72. package/dist/tools/__tests__/audio.test.js +59 -0
  73. package/dist/tools/__tests__/conserved.test.d.ts +1 -0
  74. package/dist/tools/__tests__/conserved.test.js +488 -0
  75. package/dist/tools/__tests__/constraint.test.d.ts +1 -0
  76. package/dist/tools/__tests__/constraint.test.js +212 -0
  77. package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
  78. package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
  79. package/dist/tools/__tests__/images.test.d.ts +1 -0
  80. package/dist/tools/__tests__/images.test.js +59 -0
  81. package/dist/tools/__tests__/relationship.test.d.ts +1 -0
  82. package/dist/tools/__tests__/relationship.test.js +132 -0
  83. package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
  84. package/dist/tools/__tests__/resource-constraints.test.js +131 -0
  85. package/dist/tools/__tests__/resource.test.d.ts +1 -0
  86. package/dist/tools/__tests__/resource.test.js +190 -0
  87. package/dist/tools/__tests__/time.test.d.ts +1 -0
  88. package/dist/tools/__tests__/time.test.js +404 -0
  89. package/dist/tools/__tests__/timers.test.d.ts +1 -0
  90. package/dist/tools/__tests__/timers.test.js +426 -0
  91. package/dist/tools/__tests__/world.test.d.ts +1 -0
  92. package/dist/tools/__tests__/world.test.js +70 -0
  93. package/dist/tools/ability.d.ts +48 -0
  94. package/dist/tools/ability.js +238 -0
  95. package/dist/tools/audio.d.ts +24 -0
  96. package/dist/tools/audio.js +365 -0
  97. package/dist/tools/character.d.ts +70 -0
  98. package/dist/tools/character.js +309 -0
  99. package/dist/tools/combat.d.ts +13 -0
  100. package/dist/tools/combat.js +195 -0
  101. package/dist/tools/constraint.d.ts +132 -0
  102. package/dist/tools/constraint.js +269 -0
  103. package/dist/tools/dice.d.ts +23 -0
  104. package/dist/tools/dice.js +111 -0
  105. package/dist/tools/display.d.ts +120 -0
  106. package/dist/tools/display.js +528 -0
  107. package/dist/tools/faction.d.ts +61 -0
  108. package/dist/tools/faction.js +269 -0
  109. package/dist/tools/game.d.ts +96 -0
  110. package/dist/tools/game.js +526 -0
  111. package/dist/tools/image-prompt.d.ts +49 -0
  112. package/dist/tools/image-prompt.js +479 -0
  113. package/dist/tools/images.d.ts +47 -0
  114. package/dist/tools/images.js +449 -0
  115. package/dist/tools/inventory.d.ts +20 -0
  116. package/dist/tools/inventory.js +145 -0
  117. package/dist/tools/narrative.d.ts +58 -0
  118. package/dist/tools/narrative.js +237 -0
  119. package/dist/tools/notes.d.ts +41 -0
  120. package/dist/tools/notes.js +220 -0
  121. package/dist/tools/pause.d.ts +110 -0
  122. package/dist/tools/pause.js +1254 -0
  123. package/dist/tools/quest.d.ts +34 -0
  124. package/dist/tools/quest.js +164 -0
  125. package/dist/tools/relationship.d.ts +74 -0
  126. package/dist/tools/relationship.js +324 -0
  127. package/dist/tools/resource.d.ts +93 -0
  128. package/dist/tools/resource.js +374 -0
  129. package/dist/tools/rules.d.ts +4 -0
  130. package/dist/tools/rules.js +30 -0
  131. package/dist/tools/secrets.d.ts +49 -0
  132. package/dist/tools/secrets.js +195 -0
  133. package/dist/tools/status.d.ts +36 -0
  134. package/dist/tools/status.js +218 -0
  135. package/dist/tools/tables.d.ts +33 -0
  136. package/dist/tools/tables.js +209 -0
  137. package/dist/tools/tags.d.ts +52 -0
  138. package/dist/tools/tags.js +176 -0
  139. package/dist/tools/time.d.ts +33 -0
  140. package/dist/tools/time.js +276 -0
  141. package/dist/tools/timers.d.ts +41 -0
  142. package/dist/tools/timers.js +215 -0
  143. package/dist/tools/world.d.ts +78 -0
  144. package/dist/tools/world.js +331 -0
  145. package/dist/types/index.d.ts +969 -0
  146. package/dist/types/index.js +1 -0
  147. package/dist/utils/__tests__/json.test.d.ts +1 -0
  148. package/dist/utils/__tests__/json.test.js +55 -0
  149. package/dist/utils/__tests__/validation.test.d.ts +1 -0
  150. package/dist/utils/__tests__/validation.test.js +90 -0
  151. package/dist/utils/errors.d.ts +44 -0
  152. package/dist/utils/errors.js +121 -0
  153. package/dist/utils/json.d.ts +9 -0
  154. package/dist/utils/json.js +23 -0
  155. package/dist/utils/logger.d.ts +7 -0
  156. package/dist/utils/logger.js +50 -0
  157. package/dist/utils/output-schemas.d.ts +594 -0
  158. package/dist/utils/output-schemas.js +331 -0
  159. package/dist/utils/tool-annotations.d.ts +147 -0
  160. package/dist/utils/tool-annotations.js +98 -0
  161. package/dist/utils/validation.d.ts +34 -0
  162. package/dist/utils/validation.js +52 -0
  163. package/dist/utils/verbosity.d.ts +57 -0
  164. package/dist/utils/verbosity.js +67 -0
  165. package/dist/utils/webui.d.ts +20 -0
  166. package/dist/utils/webui.js +35 -0
  167. package/package.json +75 -0
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { initializeSchema } from "./db/schema.js";
5
+ import { closeDatabase } from "./db/connection.js";
6
+ import { startHttpServer } from "./http/server.js";
7
+ import { setHttpPort } from "./utils/webui.js";
8
+ // Import registration functions
9
+ import { registerCoreTools } from "./register/core.js";
10
+ import { registerWorldTools } from "./register/world.js";
11
+ import { registerCharacterTools } from "./register/character.js";
12
+ import { registerCombatTools } from "./register/combat.js";
13
+ import { registerInventoryTools } from "./register/inventory.js";
14
+ import { registerQuestTools } from "./register/quests.js";
15
+ import { registerNarrativeTools } from "./register/narrative.js";
16
+ import { registerResourceTools } from "./register/resources.js";
17
+ import { registerTimeTools } from "./register/time.js";
18
+ import { registerTableTools } from "./register/tables.js";
19
+ import { registerSecretTools } from "./register/secrets.js";
20
+ import { registerRelationshipTools } from "./register/relationships.js";
21
+ import { registerTagTools } from "./register/tags.js";
22
+ import { registerStatusTools } from "./register/status.js";
23
+ import { registerFactionTools } from "./register/factions.js";
24
+ import { registerAbilityTools } from "./register/abilities.js";
25
+ import { registerNoteTools } from "./register/notes.js";
26
+ import { registerPauseTools } from "./register/pause.js";
27
+ import { registerImageTools } from "./register/images.js";
28
+ import { registerAudioTools } from "./register/audio.js";
29
+ import { registerDisplayTools } from "./register/display.js";
30
+ import { registerBatchTools } from "./register/batch.js";
31
+ import { registerMcpResources } from "./register/mcp-resources.js";
32
+ import { registerMcpPrompts } from "./register/mcp-prompts.js";
33
+ // Initialize database
34
+ initializeSchema();
35
+ // Create MCP server
36
+ const server = new McpServer({
37
+ name: "dmcp",
38
+ version: "0.1.0",
39
+ });
40
+ // Register all tools by domain
41
+ registerCoreTools(server); // Game, Interview, Rules
42
+ registerWorldTools(server); // Locations, Connections, Map
43
+ registerCharacterTools(server); // Characters (PC/NPC)
44
+ registerCombatTools(server); // Combat, Dice, Checks
45
+ registerInventoryTools(server); // Items
46
+ registerQuestTools(server); // Quests, Objectives
47
+ registerNarrativeTools(server); // Events, History, Export, Player Choices
48
+ registerResourceTools(server); // Custom Resources
49
+ registerTimeTools(server); // Calendar, Time, Timers
50
+ registerTableTools(server); // Random Tables
51
+ registerSecretTools(server); // Secrets, Knowledge
52
+ registerRelationshipTools(server); // Relationships
53
+ registerTagTools(server); // Tags
54
+ registerStatusTools(server); // Status Effects
55
+ registerFactionTools(server); // Factions
56
+ registerAbilityTools(server); // Abilities/Powers
57
+ registerNoteTools(server); // Game Notes
58
+ registerPauseTools(server); // Pause/Resume, Context Snapshots, External Updates
59
+ registerImageTools(server); // Stored Images
60
+ registerAudioTools(server); // Stored Audio (TTS, Voice References)
61
+ registerDisplayTools(server); // Display/Theme Configuration
62
+ registerBatchTools(server); // Batch Operations (multi-entity, workflows)
63
+ // Register MCP Resources and Prompts
64
+ registerMcpResources(server); // Read-only data access via URI
65
+ registerMcpPrompts(server); // Reusable prompt templates
66
+ // ============================================================================
67
+ // START SERVER
68
+ // ============================================================================
69
+ // HTTP server port (configurable via environment variable)
70
+ const HTTP_PORT = parseInt(process.env.DMCP_HTTP_PORT || "3456", 10);
71
+ async function main() {
72
+ // Start HTTP server for web UI (runs alongside MCP)
73
+ const actualPort = await startHttpServer(HTTP_PORT);
74
+ setHttpPort(actualPort);
75
+ // Start MCP server with stdio transport
76
+ const transport = new StdioServerTransport();
77
+ await server.connect(transport);
78
+ }
79
+ // Handle cleanup
80
+ process.on("SIGINT", () => {
81
+ closeDatabase();
82
+ process.exit(0);
83
+ });
84
+ process.on("SIGTERM", () => {
85
+ closeDatabase();
86
+ process.exit(0);
87
+ });
88
+ main().catch((error) => {
89
+ console.error("Server error:", error);
90
+ closeDatabase();
91
+ process.exit(1);
92
+ });
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAbilityTools(server: McpServer): void;
@@ -0,0 +1,165 @@
1
+ import { z } from "zod";
2
+ import * as abilityTools from "../tools/ability.js";
3
+ import { LIMITS } from "../utils/validation.js";
4
+ import { ANNOTATIONS } from "../utils/tool-annotations.js";
5
+ export function registerAbilityTools(server) {
6
+ server.registerTool("create_ability", {
7
+ description: "Create an ability template or character-owned ability",
8
+ inputSchema: {
9
+ gameId: z.string().max(100).describe("The game ID"),
10
+ ownerType: z.enum(["template", "character"]).describe("'template' for reusable, 'character' for owned"),
11
+ ownerId: z.string().max(100).optional().describe("Character ID if ownerType is 'character'"),
12
+ name: z.string().min(1).max(LIMITS.NAME_MAX).describe("Ability name"),
13
+ description: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("Ability description"),
14
+ category: z.string().max(100).optional().describe("Category (e.g., 'spell', 'skill', 'power')"),
15
+ cost: z.record(z.number()).optional().describe("Resource costs (e.g., {mana: 10, stamina: 5})"),
16
+ cooldown: z.number().optional().describe("Cooldown in rounds"),
17
+ effects: z.array(z.string().max(LIMITS.DESCRIPTION_MAX)).max(LIMITS.ARRAY_MAX).optional().describe("Effect descriptions"),
18
+ requirements: z.record(z.number()).optional().describe("Requirements (e.g., {level: 5, strength: 10})"),
19
+ tags: z.array(z.string().max(LIMITS.NAME_MAX)).max(LIMITS.ARRAY_MAX).optional().describe("Tags for categorization"),
20
+ },
21
+ annotations: ANNOTATIONS.CREATE,
22
+ }, async (params) => {
23
+ const ability = abilityTools.createAbility(params);
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(ability, null, 2) }],
26
+ };
27
+ });
28
+ server.registerTool("get_ability", {
29
+ description: "Get an ability by ID",
30
+ inputSchema: {
31
+ abilityId: z.string().max(100).describe("The ability ID"),
32
+ },
33
+ annotations: ANNOTATIONS.READ_ONLY,
34
+ }, async ({ abilityId }) => {
35
+ const ability = abilityTools.getAbility(abilityId);
36
+ if (!ability) {
37
+ return {
38
+ content: [{ type: "text", text: "Ability not found" }],
39
+ isError: true,
40
+ };
41
+ }
42
+ return {
43
+ content: [{ type: "text", text: JSON.stringify(ability, null, 2) }],
44
+ };
45
+ });
46
+ server.registerTool("update_ability", {
47
+ description: "Update an ability's details",
48
+ inputSchema: {
49
+ abilityId: z.string().max(100).describe("The ability ID"),
50
+ name: z.string().max(LIMITS.NAME_MAX).optional().describe("New name"),
51
+ description: z.string().max(LIMITS.DESCRIPTION_MAX).optional().describe("New description"),
52
+ category: z.string().max(100).nullable().optional().describe("New category"),
53
+ cost: z.record(z.number()).optional().describe("New cost"),
54
+ cooldown: z.number().nullable().optional().describe("New cooldown"),
55
+ effects: z.array(z.string().max(LIMITS.DESCRIPTION_MAX)).max(LIMITS.ARRAY_MAX).optional().describe("New effects"),
56
+ requirements: z.record(z.number()).optional().describe("New requirements"),
57
+ tags: z.array(z.string().max(LIMITS.NAME_MAX)).max(LIMITS.ARRAY_MAX).optional().describe("New tags"),
58
+ },
59
+ annotations: ANNOTATIONS.UPDATE,
60
+ }, async ({ abilityId, ...updates }) => {
61
+ const ability = abilityTools.updateAbility(abilityId, updates);
62
+ if (!ability) {
63
+ return {
64
+ content: [{ type: "text", text: "Ability not found" }],
65
+ isError: true,
66
+ };
67
+ }
68
+ return {
69
+ content: [{ type: "text", text: JSON.stringify(ability, null, 2) }],
70
+ };
71
+ });
72
+ server.registerTool("delete_ability", {
73
+ description: "Delete an ability",
74
+ inputSchema: {
75
+ abilityId: z.string().max(100).describe("The ability ID"),
76
+ },
77
+ annotations: ANNOTATIONS.DESTRUCTIVE,
78
+ }, async ({ abilityId }) => {
79
+ const success = abilityTools.deleteAbility(abilityId);
80
+ return {
81
+ content: [{ type: "text", text: success ? "Ability deleted" : "Ability not found" }],
82
+ isError: !success,
83
+ };
84
+ });
85
+ server.registerTool("list_abilities", {
86
+ description: "List abilities with optional filters",
87
+ inputSchema: {
88
+ gameId: z.string().max(100).describe("The game ID"),
89
+ ownerType: z.enum(["template", "character"]).optional().describe("Filter by owner type"),
90
+ ownerId: z.string().max(100).optional().describe("Filter by owner ID"),
91
+ category: z.string().max(100).optional().describe("Filter by category"),
92
+ },
93
+ annotations: ANNOTATIONS.READ_ONLY,
94
+ }, async ({ gameId, ...filter }) => {
95
+ const abilities = abilityTools.listAbilities(gameId, filter);
96
+ return {
97
+ content: [{ type: "text", text: JSON.stringify(abilities, null, 2) }],
98
+ };
99
+ });
100
+ server.registerTool("learn_ability", {
101
+ description: "Copy a template ability to a character",
102
+ inputSchema: {
103
+ templateId: z.string().max(100).describe("The template ability ID"),
104
+ characterId: z.string().max(100).describe("The character ID to learn the ability"),
105
+ },
106
+ annotations: ANNOTATIONS.CREATE,
107
+ }, async ({ templateId, characterId }) => {
108
+ const ability = abilityTools.learnAbility(templateId, characterId);
109
+ if (!ability) {
110
+ return {
111
+ content: [{ type: "text", text: "Template not found or is not a template" }],
112
+ isError: true,
113
+ };
114
+ }
115
+ return {
116
+ content: [{ type: "text", text: JSON.stringify(ability, null, 2) }],
117
+ };
118
+ });
119
+ server.registerTool("use_ability", {
120
+ description: "Use an ability (checks cooldown, sets new cooldown)",
121
+ inputSchema: {
122
+ abilityId: z.string().max(100).describe("The ability ID"),
123
+ characterId: z.string().max(100).describe("The character using the ability"),
124
+ },
125
+ annotations: ANNOTATIONS.UPDATE,
126
+ }, async ({ abilityId, characterId }) => {
127
+ const result = abilityTools.useAbility(abilityId, characterId);
128
+ return {
129
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
130
+ isError: !result.success,
131
+ };
132
+ });
133
+ server.registerTool("tick_ability_cooldowns", {
134
+ description: "Reduce all ability cooldowns (call at end of round)",
135
+ inputSchema: {
136
+ gameId: z.string().max(100).describe("The game ID"),
137
+ amount: z.number().optional().describe("Rounds to tick (default: 1)"),
138
+ },
139
+ annotations: ANNOTATIONS.UPDATE,
140
+ }, async ({ gameId, amount }) => {
141
+ const updated = abilityTools.tickCooldowns(gameId, amount);
142
+ return {
143
+ content: [{ type: "text", text: JSON.stringify({ updatedCount: updated.length, abilities: updated }, null, 2) }],
144
+ };
145
+ });
146
+ server.registerTool("check_ability_requirements", {
147
+ description: "Check if a character meets an ability's requirements",
148
+ inputSchema: {
149
+ abilityId: z.string().max(100).describe("The ability ID"),
150
+ characterId: z.string().max(100).describe("The character ID"),
151
+ },
152
+ annotations: ANNOTATIONS.READ_ONLY,
153
+ }, async ({ abilityId, characterId }) => {
154
+ const result = abilityTools.checkRequirements(abilityId, characterId);
155
+ if (!result) {
156
+ return {
157
+ content: [{ type: "text", text: "Ability or character not found" }],
158
+ isError: true,
159
+ };
160
+ }
161
+ return {
162
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
163
+ };
164
+ });
165
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAudioTools(server: McpServer): void;
@@ -0,0 +1,326 @@
1
+ import { z } from "zod";
2
+ import * as audioTools from "../tools/audio.js";
3
+ import { LIMITS } from "../utils/validation.js";
4
+ import { ANNOTATIONS } from "../utils/tool-annotations.js";
5
+ export function registerAudioTools(server) {
6
+ server.registerTool("store_audio", {
7
+ description: "Store an audio file for an entity. Provide either a URL to fetch from or a local file path. Useful for TTS-generated narration, character dialogue, or voice reference clips for voice cloning.",
8
+ inputSchema: {
9
+ gameId: z.string().max(100).describe("The game ID"),
10
+ entityId: z
11
+ .string()
12
+ .max(100)
13
+ .describe("ID of the entity (character, location, game, scene, etc.)"),
14
+ entityType: z
15
+ .string()
16
+ .max(50)
17
+ .describe("Type of entity (e.g., character, location, game, scene, narration, dialogue)"),
18
+ url: z
19
+ .string()
20
+ .max(2000)
21
+ .optional()
22
+ .describe("URL to fetch the audio from (provide url OR filePath)"),
23
+ filePath: z
24
+ .string()
25
+ .max(1000)
26
+ .optional()
27
+ .describe("Local file path to copy from (provide url OR filePath)"),
28
+ label: z
29
+ .string()
30
+ .max(LIMITS.NAME_MAX)
31
+ .optional()
32
+ .describe("Label for the audio (e.g., 'Greeting', 'Battle Cry', 'Voice Sample')"),
33
+ description: z
34
+ .string()
35
+ .max(LIMITS.DESCRIPTION_MAX)
36
+ .optional()
37
+ .describe("Description of the audio content"),
38
+ mimeType: z
39
+ .string()
40
+ .max(100)
41
+ .optional()
42
+ .describe("MIME type (e.g., 'audio/mp3'). Inferred if not provided."),
43
+ ttsEngine: z
44
+ .string()
45
+ .max(100)
46
+ .optional()
47
+ .describe("TTS engine used to generate (e.g., 'maya1', 'chatterbox', 'kokoro', 'xtts', 'mira', 'cosyvoice')"),
48
+ ttsVoice: z
49
+ .string()
50
+ .max(200)
51
+ .optional()
52
+ .describe("Voice name/ID used for TTS generation"),
53
+ ttsText: z
54
+ .string()
55
+ .max(LIMITS.CONTENT_MAX)
56
+ .optional()
57
+ .describe("The text that was synthesized (for TTS audio)"),
58
+ ttsSettings: z
59
+ .record(z.unknown())
60
+ .optional()
61
+ .describe("Engine-specific TTS settings (temperature, exaggeration, etc.)"),
62
+ isVoiceReference: z
63
+ .boolean()
64
+ .optional()
65
+ .describe("Mark this as a voice reference clip for voice cloning. These clips can be used as reference audio for TTS engines that support voice cloning."),
66
+ voiceName: z
67
+ .string()
68
+ .max(LIMITS.NAME_MAX)
69
+ .optional()
70
+ .describe("Name for this voice (for voice cloning reference clips)"),
71
+ voiceDescription: z
72
+ .string()
73
+ .max(LIMITS.DESCRIPTION_MAX)
74
+ .optional()
75
+ .describe("Description of voice characteristics (accent, tone, pitch, etc.) for voice cloning"),
76
+ setAsPrimary: z
77
+ .boolean()
78
+ .optional()
79
+ .describe("Set this as the primary audio for the entity"),
80
+ },
81
+ annotations: ANNOTATIONS.EXTERNAL,
82
+ }, async (params) => {
83
+ try {
84
+ if (!params.url && !params.filePath) {
85
+ return {
86
+ content: [
87
+ { type: "text", text: "Error: Either url or filePath must be provided" },
88
+ ],
89
+ isError: true,
90
+ };
91
+ }
92
+ const audio = await audioTools.storeAudio(params);
93
+ return {
94
+ content: [{ type: "text", text: JSON.stringify(audio, null, 2) }],
95
+ };
96
+ }
97
+ catch (error) {
98
+ return {
99
+ content: [{ type: "text", text: `Error storing audio: ${error}` }],
100
+ isError: true,
101
+ };
102
+ }
103
+ });
104
+ server.registerTool("get_audio", {
105
+ description: "Get audio file metadata by ID",
106
+ inputSchema: {
107
+ audioId: z.string().max(100).describe("The audio ID"),
108
+ },
109
+ annotations: ANNOTATIONS.READ_ONLY,
110
+ }, async ({ audioId }) => {
111
+ const audio = audioTools.getAudio(audioId);
112
+ if (!audio) {
113
+ return {
114
+ content: [{ type: "text", text: "Audio not found" }],
115
+ isError: true,
116
+ };
117
+ }
118
+ return {
119
+ content: [{ type: "text", text: JSON.stringify(audio, null, 2) }],
120
+ };
121
+ });
122
+ server.registerTool("get_audio_file_path", {
123
+ description: "Get the full local file path for an audio file. Useful when you need to pass the audio file to another tool (like TTS voice cloning).",
124
+ inputSchema: {
125
+ audioId: z.string().max(100).describe("The audio ID"),
126
+ },
127
+ annotations: ANNOTATIONS.READ_ONLY,
128
+ }, async ({ audioId }) => {
129
+ const filePath = audioTools.getAudioFilePath(audioId);
130
+ if (!filePath) {
131
+ return {
132
+ content: [{ type: "text", text: "Audio file not found" }],
133
+ isError: true,
134
+ };
135
+ }
136
+ return {
137
+ content: [
138
+ {
139
+ type: "text",
140
+ text: JSON.stringify({ audioId, filePath }, null, 2),
141
+ },
142
+ ],
143
+ };
144
+ });
145
+ server.registerTool("get_audio_data", {
146
+ description: "Get audio with base64 data included (for playback or processing)",
147
+ inputSchema: {
148
+ audioId: z.string().max(100).describe("The audio ID"),
149
+ },
150
+ annotations: ANNOTATIONS.READ_ONLY,
151
+ }, async ({ audioId }) => {
152
+ const result = audioTools.getAudioData(audioId);
153
+ if (!result) {
154
+ return {
155
+ content: [{ type: "text", text: "Audio not found" }],
156
+ isError: true,
157
+ };
158
+ }
159
+ return {
160
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
161
+ };
162
+ });
163
+ server.registerTool("list_entity_audio", {
164
+ description: "List all audio files for an entity",
165
+ inputSchema: {
166
+ entityId: z.string().max(100).describe("ID of the entity"),
167
+ entityType: z
168
+ .string()
169
+ .max(50)
170
+ .describe("Type of entity (e.g., character, location, game, scene)"),
171
+ },
172
+ annotations: ANNOTATIONS.READ_ONLY,
173
+ }, async ({ entityId, entityType }) => {
174
+ const result = audioTools.listEntityAudio(entityId, entityType);
175
+ return {
176
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
177
+ };
178
+ });
179
+ server.registerTool("list_game_audio", {
180
+ description: "List all audio files in a game",
181
+ inputSchema: {
182
+ gameId: z.string().max(100).describe("The game ID"),
183
+ },
184
+ annotations: ANNOTATIONS.READ_ONLY,
185
+ }, async ({ gameId }) => {
186
+ const audioFiles = audioTools.listGameAudio(gameId);
187
+ return {
188
+ content: [
189
+ {
190
+ type: "text",
191
+ text: JSON.stringify({ gameId, count: audioFiles.length, audioFiles }, null, 2),
192
+ },
193
+ ],
194
+ };
195
+ });
196
+ server.registerTool("list_voice_references", {
197
+ description: "List voice reference clips for voice cloning. These are audio samples that can be used as reference audio for TTS engines that support voice cloning (like Chatterbox, XTTS, CosyVoice).",
198
+ inputSchema: {
199
+ gameId: z.string().max(100).describe("The game ID"),
200
+ characterId: z
201
+ .string()
202
+ .max(100)
203
+ .optional()
204
+ .describe("Filter by character ID (optional, returns all if not specified)"),
205
+ },
206
+ annotations: ANNOTATIONS.READ_ONLY,
207
+ }, async ({ gameId, characterId }) => {
208
+ const voiceRefs = audioTools.listVoiceReferences(gameId, characterId);
209
+ return {
210
+ content: [
211
+ {
212
+ type: "text",
213
+ text: JSON.stringify({
214
+ gameId,
215
+ characterId: characterId || "all",
216
+ count: voiceRefs.length,
217
+ voiceReferences: voiceRefs,
218
+ }, null, 2),
219
+ },
220
+ ],
221
+ };
222
+ });
223
+ server.registerTool("get_character_voice_references", {
224
+ description: "Get all voice reference clips for a specific character, along with character info. Returns file paths ready to use with voice cloning TTS engines.",
225
+ inputSchema: {
226
+ gameId: z.string().max(100).describe("The game ID"),
227
+ characterId: z.string().max(100).describe("The character ID"),
228
+ },
229
+ annotations: ANNOTATIONS.READ_ONLY,
230
+ }, async ({ gameId, characterId }) => {
231
+ const result = audioTools.getCharacterVoiceReferences(gameId, characterId);
232
+ if (!result) {
233
+ return {
234
+ content: [{ type: "text", text: "Character not found" }],
235
+ isError: true,
236
+ };
237
+ }
238
+ return {
239
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
240
+ };
241
+ });
242
+ server.registerTool("delete_audio", {
243
+ description: "Delete a stored audio file (removes file and database record)",
244
+ inputSchema: {
245
+ audioId: z.string().max(100).describe("The audio ID"),
246
+ },
247
+ annotations: ANNOTATIONS.DESTRUCTIVE,
248
+ }, async ({ audioId }) => {
249
+ const success = audioTools.deleteAudio(audioId);
250
+ return {
251
+ content: [
252
+ { type: "text", text: success ? "Audio deleted" : "Audio not found" },
253
+ ],
254
+ isError: !success,
255
+ };
256
+ });
257
+ server.registerTool("set_primary_audio", {
258
+ description: "Set an audio file as the primary audio for its entity",
259
+ inputSchema: {
260
+ audioId: z.string().max(100).describe("The audio ID to set as primary"),
261
+ },
262
+ annotations: ANNOTATIONS.UPDATE,
263
+ }, async ({ audioId }) => {
264
+ const audio = audioTools.setPrimaryAudio(audioId);
265
+ if (!audio) {
266
+ return {
267
+ content: [{ type: "text", text: "Audio not found" }],
268
+ isError: true,
269
+ };
270
+ }
271
+ return {
272
+ content: [{ type: "text", text: JSON.stringify(audio, null, 2) }],
273
+ };
274
+ });
275
+ server.registerTool("update_audio_metadata", {
276
+ description: "Update metadata for an audio file (label, description, voice reference info)",
277
+ inputSchema: {
278
+ audioId: z.string().max(100).describe("The audio ID"),
279
+ label: z.string().max(LIMITS.NAME_MAX).optional().describe("New label"),
280
+ description: z
281
+ .string()
282
+ .max(LIMITS.DESCRIPTION_MAX)
283
+ .optional()
284
+ .describe("New description"),
285
+ ttsText: z
286
+ .string()
287
+ .max(LIMITS.CONTENT_MAX)
288
+ .optional()
289
+ .describe("Update the TTS text transcript"),
290
+ voiceName: z
291
+ .string()
292
+ .max(LIMITS.NAME_MAX)
293
+ .optional()
294
+ .describe("New voice name (for voice references)"),
295
+ voiceDescription: z
296
+ .string()
297
+ .max(LIMITS.DESCRIPTION_MAX)
298
+ .optional()
299
+ .describe("New voice description (for voice references)"),
300
+ isVoiceReference: z
301
+ .boolean()
302
+ .optional()
303
+ .describe("Mark/unmark as voice reference clip"),
304
+ },
305
+ annotations: ANNOTATIONS.UPDATE,
306
+ }, async ({ audioId, ...updates }) => {
307
+ try {
308
+ const audio = audioTools.updateAudioMetadata(audioId, updates);
309
+ if (!audio) {
310
+ return {
311
+ content: [{ type: "text", text: "Audio not found" }],
312
+ isError: true,
313
+ };
314
+ }
315
+ return {
316
+ content: [{ type: "text", text: JSON.stringify(audio, null, 2) }],
317
+ };
318
+ }
319
+ catch (error) {
320
+ return {
321
+ content: [{ type: "text", text: `Error updating audio: ${error}` }],
322
+ isError: true,
323
+ };
324
+ }
325
+ });
326
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerBatchTools(server: McpServer): void;