@open-mercato/ai-assistant 0.4.2-canary-c02407ff85

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 (193) hide show
  1. package/AGENTS.md +1090 -0
  2. package/README.md +607 -0
  3. package/build.mjs +92 -0
  4. package/dist/di.js +8 -0
  5. package/dist/di.js.map +7 -0
  6. package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
  7. package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
  8. package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
  9. package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
  10. package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
  11. package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
  12. package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
  13. package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
  14. package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
  15. package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
  16. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
  17. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
  18. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
  19. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
  20. package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
  21. package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
  22. package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
  23. package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
  24. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
  25. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
  26. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
  27. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
  28. package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
  29. package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
  30. package/dist/frontend/components/CommandPalette/index.js +28 -0
  31. package/dist/frontend/components/CommandPalette/index.js.map +7 -0
  32. package/dist/frontend/constants.js +41 -0
  33. package/dist/frontend/constants.js.map +7 -0
  34. package/dist/frontend/hooks/index.js +13 -0
  35. package/dist/frontend/hooks/index.js.map +7 -0
  36. package/dist/frontend/hooks/useCommandPalette.js +1094 -0
  37. package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
  38. package/dist/frontend/hooks/useMcpTools.js +66 -0
  39. package/dist/frontend/hooks/useMcpTools.js.map +7 -0
  40. package/dist/frontend/hooks/usePageContext.js +48 -0
  41. package/dist/frontend/hooks/usePageContext.js.map +7 -0
  42. package/dist/frontend/hooks/useRecentActions.js +56 -0
  43. package/dist/frontend/hooks/useRecentActions.js.map +7 -0
  44. package/dist/frontend/hooks/useRecentTools.js +55 -0
  45. package/dist/frontend/hooks/useRecentTools.js.map +7 -0
  46. package/dist/frontend/index.js +35 -0
  47. package/dist/frontend/index.js.map +7 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.js.map +7 -0
  50. package/dist/frontend/utils/index.js +7 -0
  51. package/dist/frontend/utils/index.js.map +7 -0
  52. package/dist/frontend/utils/toolMatcher.js +95 -0
  53. package/dist/frontend/utils/toolMatcher.js.map +7 -0
  54. package/dist/index.js +57 -0
  55. package/dist/index.js.map +7 -0
  56. package/dist/modules/ai_assistant/acl.js +14 -0
  57. package/dist/modules/ai_assistant/acl.js.map +7 -0
  58. package/dist/modules/ai_assistant/api/chat/route.js +152 -0
  59. package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
  60. package/dist/modules/ai_assistant/api/health/route.js +27 -0
  61. package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
  62. package/dist/modules/ai_assistant/api/route/route.js +123 -0
  63. package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
  64. package/dist/modules/ai_assistant/api/settings/route.js +60 -0
  65. package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
  66. package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
  67. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
  68. package/dist/modules/ai_assistant/api/tools/route.js +48 -0
  69. package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
  70. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
  71. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
  72. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
  73. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
  74. package/dist/modules/ai_assistant/cli.js +192 -0
  75. package/dist/modules/ai_assistant/cli.js.map +7 -0
  76. package/dist/modules/ai_assistant/di.js +11 -0
  77. package/dist/modules/ai_assistant/di.js.map +7 -0
  78. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
  79. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
  80. package/dist/modules/ai_assistant/index.js +13 -0
  81. package/dist/modules/ai_assistant/index.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
  83. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
  85. package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
  87. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
  89. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/auth.js +87 -0
  91. package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
  93. package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
  95. package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/http-server.js +367 -0
  97. package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
  99. package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
  101. package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
  103. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
  105. package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
  107. package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
  109. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
  111. package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
  113. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
  115. package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
  117. package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
  119. package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
  121. package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
  123. package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
  125. package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/types.js +1 -0
  127. package/dist/modules/ai_assistant/lib/types.js.map +7 -0
  128. package/package.json +108 -0
  129. package/src/di.ts +11 -0
  130. package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
  131. package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
  132. package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
  133. package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
  134. package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
  135. package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
  136. package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
  137. package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
  138. package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
  139. package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
  140. package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
  141. package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
  142. package/src/frontend/components/CommandPalette/index.ts +14 -0
  143. package/src/frontend/constants.ts +35 -0
  144. package/src/frontend/hooks/index.ts +5 -0
  145. package/src/frontend/hooks/useCommandPalette.ts +1389 -0
  146. package/src/frontend/hooks/useMcpTools.ts +73 -0
  147. package/src/frontend/hooks/usePageContext.ts +61 -0
  148. package/src/frontend/hooks/useRecentActions.ts +64 -0
  149. package/src/frontend/hooks/useRecentTools.ts +69 -0
  150. package/src/frontend/index.ts +39 -0
  151. package/src/frontend/types.ts +260 -0
  152. package/src/frontend/utils/index.ts +1 -0
  153. package/src/frontend/utils/toolMatcher.ts +127 -0
  154. package/src/index.ts +92 -0
  155. package/src/modules/ai_assistant/acl.ts +10 -0
  156. package/src/modules/ai_assistant/api/chat/route.ts +213 -0
  157. package/src/modules/ai_assistant/api/health/route.ts +30 -0
  158. package/src/modules/ai_assistant/api/route/route.ts +149 -0
  159. package/src/modules/ai_assistant/api/settings/route.ts +73 -0
  160. package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
  161. package/src/modules/ai_assistant/api/tools/route.ts +57 -0
  162. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
  163. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
  164. package/src/modules/ai_assistant/cli.ts +233 -0
  165. package/src/modules/ai_assistant/di.ts +9 -0
  166. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
  167. package/src/modules/ai_assistant/index.ts +11 -0
  168. package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
  169. package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
  170. package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
  171. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
  172. package/src/modules/ai_assistant/lib/auth.ts +185 -0
  173. package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
  174. package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
  175. package/src/modules/ai_assistant/lib/http-server.ts +498 -0
  176. package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
  177. package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
  178. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
  179. package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
  180. package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
  181. package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
  182. package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
  183. package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
  184. package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
  185. package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
  186. package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
  187. package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
  188. package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
  189. package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
  190. package/src/modules/ai_assistant/lib/types.ts +147 -0
  191. package/test-schema.ts +37 -0
  192. package/tsconfig.json +10 -0
  193. package/watch.mjs +6 -0
@@ -0,0 +1,146 @@
1
+ import { spawn } from "node:child_process";
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ class McpClient {
6
+ constructor(client, transport, apiKeySecret, childProcess) {
7
+ this.client = client;
8
+ this.transport = transport;
9
+ this.apiKeySecret = apiKeySecret;
10
+ this.childProcess = childProcess;
11
+ }
12
+ /**
13
+ * Connect to an MCP server via the specified transport.
14
+ */
15
+ static async connect(options) {
16
+ if (options.transport === "stdio") {
17
+ return McpClient.connectStdio(options);
18
+ } else {
19
+ return McpClient.connectHttp(options);
20
+ }
21
+ }
22
+ /**
23
+ * Connect via stdio transport (spawn subprocess).
24
+ */
25
+ static async connectStdio(options) {
26
+ const command = options.command ?? "yarn";
27
+ const args = options.args ?? [
28
+ "mercato",
29
+ "ai_assistant",
30
+ "mcp:serve",
31
+ "--api-key",
32
+ options.apiKeySecret
33
+ ];
34
+ const cwd = options.cwd ?? process.cwd();
35
+ const childProcess = spawn(command, args, {
36
+ cwd,
37
+ stdio: ["pipe", "pipe", "pipe"],
38
+ env: { ...process.env }
39
+ });
40
+ childProcess.stderr?.on("data", (data) => {
41
+ const message = data.toString().trim();
42
+ if (message) {
43
+ console.error(`[MCP Client] ${message}`);
44
+ }
45
+ });
46
+ const transport = new StdioClientTransport({
47
+ command,
48
+ args,
49
+ cwd,
50
+ env: process.env
51
+ });
52
+ const client = new Client(
53
+ { name: "open-mercato-client", version: "0.1.0" },
54
+ { capabilities: {} }
55
+ );
56
+ await client.connect(transport);
57
+ return new McpClient(client, transport, options.apiKeySecret, childProcess);
58
+ }
59
+ /**
60
+ * Connect via HTTP transport.
61
+ */
62
+ static async connectHttp(options) {
63
+ const transport = new StreamableHTTPClientTransport(
64
+ new URL(options.url),
65
+ {
66
+ requestInit: {
67
+ headers: {
68
+ "x-api-key": options.apiKeySecret
69
+ }
70
+ }
71
+ }
72
+ );
73
+ const client = new Client(
74
+ { name: "open-mercato-client", version: "0.1.0" },
75
+ { capabilities: {} }
76
+ );
77
+ await client.connect(transport);
78
+ return new McpClient(client, transport, options.apiKeySecret);
79
+ }
80
+ /**
81
+ * List available tools from the server.
82
+ */
83
+ async listTools() {
84
+ const response = await this.client.listTools();
85
+ return response.tools.map((tool) => ({
86
+ name: tool.name,
87
+ description: tool.description ?? "",
88
+ inputSchema: tool.inputSchema ?? {}
89
+ }));
90
+ }
91
+ /**
92
+ * Call a tool on the server.
93
+ */
94
+ async callTool(name, args) {
95
+ try {
96
+ const response = await this.client.callTool({
97
+ name,
98
+ arguments: args
99
+ });
100
+ const content = response.content;
101
+ if (!Array.isArray(content) || content.length === 0) {
102
+ return { success: true, result: null };
103
+ }
104
+ const firstContent = content[0];
105
+ if (firstContent.type === "text") {
106
+ try {
107
+ const parsed = JSON.parse(firstContent.text);
108
+ if (response.isError || parsed.error) {
109
+ return {
110
+ success: false,
111
+ error: parsed.error ?? "Unknown error"
112
+ };
113
+ }
114
+ return { success: true, result: parsed };
115
+ } catch {
116
+ return { success: true, result: firstContent.text };
117
+ }
118
+ }
119
+ return { success: true, result: content };
120
+ } catch (error) {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ return { success: false, error: message };
123
+ }
124
+ }
125
+ /**
126
+ * Close the client and release resources.
127
+ */
128
+ async close() {
129
+ try {
130
+ await this.client.close();
131
+ } catch {
132
+ }
133
+ try {
134
+ await this.transport.close();
135
+ } catch {
136
+ }
137
+ if (this.childProcess) {
138
+ this.childProcess.kill();
139
+ this.childProcess = void 0;
140
+ }
141
+ }
142
+ }
143
+ export {
144
+ McpClient
145
+ };
146
+ //# sourceMappingURL=mcp-client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/mcp-client.ts"],
4
+ "sourcesContent": ["import { spawn, type ChildProcess } from 'node:child_process'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { McpClientInterface, ToolInfo, ToolResult } from './types'\n\n/**\n * Options for stdio transport.\n */\nexport type StdioClientOptions = {\n transport: 'stdio'\n /** API key secret (passed to server via --api-key) */\n apiKeySecret: string\n /** Command to run (default: 'yarn') */\n command?: string\n /** Arguments for the command (default: mercato mcp:serve with api-key) */\n args?: string[]\n /** Working directory (default: process.cwd()) */\n cwd?: string\n}\n\n/**\n * Options for HTTP transport.\n */\nexport type HttpClientOptions = {\n transport: 'http'\n /** API key secret (sent via x-api-key header) */\n apiKeySecret: string\n /** MCP server URL (e.g., 'http://localhost:3001/mcp') */\n url: string\n}\n\n/**\n * Combined options for McpClient.\n */\nexport type McpClientOptions = StdioClientOptions | HttpClientOptions\n\n/**\n * MCP protocol client for connecting to MCP servers.\n *\n * Supports two transport modes:\n * - stdio: Spawns server as subprocess\n * - http: Connects to HTTP server\n */\nexport class McpClient implements McpClientInterface {\n private client: Client\n private transport: StdioClientTransport | StreamableHTTPClientTransport\n private childProcess?: ChildProcess\n private apiKeySecret: string\n\n private constructor(\n client: Client,\n transport: StdioClientTransport | StreamableHTTPClientTransport,\n apiKeySecret: string,\n childProcess?: ChildProcess\n ) {\n this.client = client\n this.transport = transport\n this.apiKeySecret = apiKeySecret\n this.childProcess = childProcess\n }\n\n /**\n * Connect to an MCP server via the specified transport.\n */\n static async connect(options: McpClientOptions): Promise<McpClient> {\n if (options.transport === 'stdio') {\n return McpClient.connectStdio(options)\n } else {\n return McpClient.connectHttp(options)\n }\n }\n\n /**\n * Connect via stdio transport (spawn subprocess).\n */\n private static async connectStdio(options: StdioClientOptions): Promise<McpClient> {\n const command = options.command ?? 'yarn'\n const args = options.args ?? [\n 'mercato',\n 'ai_assistant',\n 'mcp:serve',\n '--api-key',\n options.apiKeySecret,\n ]\n const cwd = options.cwd ?? process.cwd()\n\n const childProcess = spawn(command, args, {\n cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: { ...process.env },\n })\n\n // Forward stderr for debugging\n childProcess.stderr?.on('data', (data) => {\n const message = data.toString().trim()\n if (message) {\n console.error(`[MCP Client] ${message}`)\n }\n })\n\n const transport = new StdioClientTransport({\n command,\n args,\n cwd,\n env: process.env as Record<string, string>,\n })\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret, childProcess)\n }\n\n /**\n * Connect via HTTP transport.\n */\n private static async connectHttp(options: HttpClientOptions): Promise<McpClient> {\n const transport = new StreamableHTTPClientTransport(\n new URL(options.url),\n {\n requestInit: {\n headers: {\n 'x-api-key': options.apiKeySecret,\n },\n },\n }\n )\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret)\n }\n\n /**\n * List available tools from the server.\n */\n async listTools(): Promise<ToolInfo[]> {\n const response = await this.client.listTools()\n\n return response.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? '',\n inputSchema: (tool.inputSchema ?? {}) as Record<string, unknown>,\n }))\n }\n\n /**\n * Call a tool on the server.\n */\n async callTool(name: string, args: unknown): Promise<ToolResult> {\n try {\n const response = await this.client.callTool({\n name,\n arguments: args as Record<string, unknown>,\n })\n\n // Parse content from response\n const content = response.content\n if (!Array.isArray(content) || content.length === 0) {\n return { success: true, result: null }\n }\n\n const firstContent = content[0]\n if (firstContent.type === 'text') {\n try {\n const parsed = JSON.parse(firstContent.text)\n\n // Check if it's an error response\n if (response.isError || parsed.error) {\n return {\n success: false,\n error: parsed.error ?? 'Unknown error',\n }\n }\n\n return { success: true, result: parsed }\n } catch {\n // Not JSON, return as-is\n return { success: true, result: firstContent.text }\n }\n }\n\n return { success: true, result: content }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return { success: false, error: message }\n }\n }\n\n /**\n * Close the client and release resources.\n */\n async close(): Promise<void> {\n try {\n await this.client.close()\n } catch {\n // Ignore close errors\n }\n\n try {\n await this.transport.close()\n } catch {\n // Ignore close errors\n }\n\n if (this.childProcess) {\n this.childProcess.kill()\n this.childProcess = undefined\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,283 @@
1
+ import { createServer } from "node:http";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { z } from "zod";
5
+ import { getToolRegistry } from "./tool-registry.js";
6
+ import { executeTool } from "./tool-executor.js";
7
+ import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
8
+ import { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
9
+ import { jsonSchemaToZod } from "./schema-utils.js";
10
+ const DEFAULT_PORT = 3001;
11
+ const log = (message, ...args) => {
12
+ console.error(`[MCP Dev] ${message}`, ...args);
13
+ };
14
+ async function getApiKeyFromMcpJson() {
15
+ const { readFile } = await import("node:fs/promises");
16
+ const { resolve } = await import("node:path");
17
+ try {
18
+ const mcpJsonPath = resolve(process.cwd(), ".mcp.json");
19
+ const content = await readFile(mcpJsonPath, "utf-8");
20
+ const config = JSON.parse(content);
21
+ const serverConfig = config?.mcpServers?.["open-mercato"];
22
+ return serverConfig?.env?.OPEN_MERCATO_API_KEY ?? serverConfig?.headers?.["x-api-key"];
23
+ } catch {
24
+ return void 0;
25
+ }
26
+ }
27
+ const MAX_BODY_SIZE = 1 * 1024 * 1024;
28
+ async function parseJsonBody(req) {
29
+ return new Promise((resolve, reject) => {
30
+ const chunks = [];
31
+ let totalSize = 0;
32
+ req.on("data", (chunk) => {
33
+ totalSize += chunk.length;
34
+ if (totalSize > MAX_BODY_SIZE) {
35
+ req.destroy();
36
+ reject(new Error("Request payload too large"));
37
+ return;
38
+ }
39
+ chunks.push(chunk);
40
+ });
41
+ req.on("end", () => {
42
+ try {
43
+ const body = Buffer.concat(chunks).toString("utf-8");
44
+ resolve(body ? JSON.parse(body) : void 0);
45
+ } catch (error) {
46
+ reject(error);
47
+ }
48
+ });
49
+ req.on("error", reject);
50
+ });
51
+ }
52
+ function createDevMcpServer(toolContext, authFeatures, isSuperAdmin, debug) {
53
+ const server = new McpServer(
54
+ { name: "open-mercato-mcp-dev", version: "0.1.0" },
55
+ { capabilities: { tools: {} } }
56
+ );
57
+ const registry = getToolRegistry();
58
+ const tools = Array.from(registry.getTools().values());
59
+ const accessibleTools = tools.filter(
60
+ (tool) => hasRequiredFeatures(tool.requiredFeatures, authFeatures, isSuperAdmin)
61
+ );
62
+ if (debug) {
63
+ log(`Registering ${accessibleTools.length}/${tools.length} tools (filtered by API key permissions)`);
64
+ }
65
+ for (const tool of accessibleTools) {
66
+ if (debug) {
67
+ log(`Registering tool: ${tool.name}`);
68
+ }
69
+ let safeSchema;
70
+ if (tool.inputSchema) {
71
+ try {
72
+ const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: "any" });
73
+ const converted = jsonSchemaToZod(jsonSchema);
74
+ safeSchema = converted.passthrough();
75
+ } catch (error) {
76
+ if (debug) {
77
+ log(`Skipping tool ${tool.name} - schema conversion failed:`, error instanceof Error ? error.message : error);
78
+ }
79
+ continue;
80
+ }
81
+ } else {
82
+ safeSchema = z.object({}).passthrough();
83
+ }
84
+ try {
85
+ server.registerTool(
86
+ tool.name,
87
+ {
88
+ description: tool.description,
89
+ inputSchema: safeSchema
90
+ },
91
+ async (args) => {
92
+ const toolArgs = args ?? {};
93
+ if (debug) {
94
+ log(`Calling tool: ${tool.name}`, JSON.stringify(toolArgs));
95
+ }
96
+ const result = await executeTool(tool.name, toolArgs, toolContext);
97
+ if (!result.success) {
98
+ log(`Tool error: ${result.error}`);
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: JSON.stringify({ error: result.error, code: result.errorCode })
104
+ }
105
+ ],
106
+ isError: true
107
+ };
108
+ }
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify(result.result, null, 2)
114
+ }
115
+ ]
116
+ };
117
+ }
118
+ );
119
+ } catch (error) {
120
+ if (debug) {
121
+ log(`Skipping tool ${tool.name} - registration failed:`, error instanceof Error ? error.message : error);
122
+ }
123
+ continue;
124
+ }
125
+ }
126
+ return server;
127
+ }
128
+ async function runMcpDevServer() {
129
+ const envApiKey = process.env.OPEN_MERCATO_API_KEY || await getApiKeyFromMcpJson();
130
+ const port = parseInt(process.env.MCP_DEV_PORT ?? "", 10) || DEFAULT_PORT;
131
+ const debug = process.env.MCP_DEBUG === "true";
132
+ if (!envApiKey) {
133
+ log("Error: OPEN_MERCATO_API_KEY environment variable is required");
134
+ log("");
135
+ log("To get an API key:");
136
+ log(" 1. Log into Open Mercato as an admin");
137
+ log(" 2. Go to Settings > API Keys");
138
+ log(" 3. Create a new key with the required permissions");
139
+ log("");
140
+ log("Then either:");
141
+ log(" - Set environment variable: export OPEN_MERCATO_API_KEY=omk_xxx...");
142
+ log(" - Or configure in .mcp.json with headers.x-api-key");
143
+ process.exit(1);
144
+ }
145
+ log("Starting development MCP HTTP server...");
146
+ const { createRequestContainer } = await import("@open-mercato/shared/lib/di/container");
147
+ const container = await createRequestContainer();
148
+ log("Authenticating API key...");
149
+ const authResult = await authenticateMcpRequest(envApiKey, container);
150
+ if (!authResult.success) {
151
+ log(`Authentication failed: ${authResult.error}`);
152
+ process.exit(1);
153
+ }
154
+ log(`Authenticated as: ${authResult.keyName}`);
155
+ log(`Tenant: ${authResult.tenantId ?? "(global)"}`);
156
+ log(`Organization: ${authResult.organizationId ?? "(none)"}`);
157
+ log(`Super admin: ${authResult.isSuperAdmin}`);
158
+ log(`Features: ${authResult.features.length > 0 ? authResult.features.join(", ") : "(none)"}`);
159
+ log("Loading tools...");
160
+ await loadAllModuleTools();
161
+ try {
162
+ const searchService = container.resolve("searchService");
163
+ await indexToolsForSearch(searchService);
164
+ const { indexApiEndpoints } = await import("./api-endpoint-index.js");
165
+ const endpointCount = await indexApiEndpoints(searchService);
166
+ if (endpointCount > 0) {
167
+ log(`Indexed ${endpointCount} API endpoints for discovery`);
168
+ }
169
+ } catch {
170
+ log("Search indexing skipped (search service not available)");
171
+ }
172
+ const toolContext = {
173
+ tenantId: authResult.tenantId,
174
+ organizationId: authResult.organizationId,
175
+ userId: authResult.userId,
176
+ container,
177
+ userFeatures: authResult.features,
178
+ isSuperAdmin: authResult.isSuperAdmin,
179
+ apiKeySecret: envApiKey
180
+ };
181
+ const httpServer = createServer(async (req, res) => {
182
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
183
+ if (url.pathname === "/health") {
184
+ res.writeHead(200, { "Content-Type": "application/json" });
185
+ res.end(JSON.stringify({
186
+ status: "ok",
187
+ mode: "development",
188
+ tools: getToolRegistry().listToolNames().length,
189
+ tenant: authResult.tenantId,
190
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
191
+ }));
192
+ return;
193
+ }
194
+ if (url.pathname !== "/mcp") {
195
+ res.writeHead(404, { "Content-Type": "application/json" });
196
+ res.end(JSON.stringify({ error: "Not found" }));
197
+ return;
198
+ }
199
+ const headers = {};
200
+ for (const [key, value] of Object.entries(req.headers)) {
201
+ headers[key] = Array.isArray(value) ? value[0] : value;
202
+ }
203
+ const providedApiKey = extractApiKeyFromHeaders(headers);
204
+ if (!providedApiKey) {
205
+ res.writeHead(401, { "Content-Type": "application/json" });
206
+ res.end(JSON.stringify({ error: "API key required (x-api-key header)" }));
207
+ return;
208
+ }
209
+ if (providedApiKey !== envApiKey) {
210
+ res.writeHead(401, { "Content-Type": "application/json" });
211
+ res.end(JSON.stringify({ error: "Invalid API key" }));
212
+ return;
213
+ }
214
+ if (debug) {
215
+ log(`Authenticated request (${req.method})`);
216
+ }
217
+ try {
218
+ const transport = new StreamableHTTPServerTransport({
219
+ sessionIdGenerator: void 0,
220
+ enableJsonResponse: req.method === "POST"
221
+ });
222
+ const mcpServer = createDevMcpServer(toolContext, authResult.features, authResult.isSuperAdmin, debug);
223
+ await mcpServer.connect(transport);
224
+ if (req.method === "POST") {
225
+ const body = await parseJsonBody(req);
226
+ await transport.handleRequest(req, res, body);
227
+ } else {
228
+ await transport.handleRequest(req, res);
229
+ }
230
+ res.on("finish", () => {
231
+ transport.close();
232
+ mcpServer.close();
233
+ if (debug) {
234
+ log(`Request completed, cleaned up`);
235
+ }
236
+ });
237
+ } catch (error) {
238
+ log("Error handling request:", error);
239
+ if (!res.headersSent) {
240
+ if (error instanceof Error && error.message === "Request payload too large") {
241
+ res.writeHead(413, { "Content-Type": "application/json" });
242
+ res.end(JSON.stringify({ error: "Request payload too large (max 1MB)" }));
243
+ return;
244
+ }
245
+ res.writeHead(500, { "Content-Type": "application/json" });
246
+ res.end(
247
+ JSON.stringify({
248
+ jsonrpc: "2.0",
249
+ error: {
250
+ code: -32603,
251
+ message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`
252
+ },
253
+ id: null
254
+ })
255
+ );
256
+ }
257
+ }
258
+ });
259
+ const toolCount = getToolRegistry().listToolNames().length;
260
+ log(`Tools registered: ${toolCount}`);
261
+ log(`Endpoint: http://localhost:${port}/mcp`);
262
+ log(`Health: http://localhost:${port}/health`);
263
+ log(`Mode: Development (API key auth, no session tokens)`);
264
+ return new Promise((resolve) => {
265
+ httpServer.listen(port, () => {
266
+ log(`Server listening on port ${port}`);
267
+ log("Ready for Claude Code connections");
268
+ });
269
+ const shutdown = async () => {
270
+ log("Shutting down...");
271
+ httpServer.close(() => {
272
+ log("Server closed");
273
+ resolve();
274
+ });
275
+ };
276
+ process.on("SIGINT", shutdown);
277
+ process.on("SIGTERM", shutdown);
278
+ });
279
+ }
280
+ export {
281
+ runMcpDevServer
282
+ };
283
+ //# sourceMappingURL=mcp-dev-server.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/mcp-dev-server.ts"],
4
+ "sourcesContent": ["import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod } from './schema-utils'\nimport type { McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\n\nconst DEFAULT_PORT = 3001\n\nconst log = (message: string, ...args: unknown[]) => {\n console.error(`[MCP Dev] ${message}`, ...args)\n}\n\nasync function getApiKeyFromMcpJson(): Promise<string | undefined> {\n const { readFile } = await import('node:fs/promises')\n const { resolve } = await import('node:path')\n\n try {\n const mcpJsonPath = resolve(process.cwd(), '.mcp.json')\n const content = await readFile(mcpJsonPath, 'utf-8')\n const config = JSON.parse(content)\n const serverConfig = config?.mcpServers?.['open-mercato']\n\n // Check env.OPEN_MERCATO_API_KEY first, then headers.x-api-key (HTTP style)\n return serverConfig?.env?.OPEN_MERCATO_API_KEY ?? serverConfig?.headers?.['x-api-key']\n } catch {\n return undefined\n }\n}\n\n/**\n * Maximum request body size (1MB).\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Create MCP server with tools pre-authenticated for dev use.\n * No session tokens required - uses API key authentication directly.\n */\nfunction createDevMcpServer(\n toolContext: McpToolContext,\n authFeatures: string[],\n isSuperAdmin: boolean,\n debug: boolean\n): McpServer {\n const server = new McpServer(\n { name: 'open-mercato-mcp-dev', version: '0.1.0' },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n // Filter tools based on API key permissions\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, authFeatures, isSuperAdmin)\n )\n\n if (debug) {\n log(`Registering ${accessibleTools.length}/${tools.length} tools (filtered by API key permissions)`)\n }\n\n for (const tool of accessibleTools) {\n if (debug) {\n log(`Registering tool: ${tool.name}`)\n }\n\n // Convert Zod schema to safe schema without Date types\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n const converted = jsonSchemaToZod(jsonSchema)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - schema conversion failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n } else {\n safeSchema = z.object({}).passthrough()\n }\n\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n if (debug) {\n log(`Calling tool: ${tool.name}`, JSON.stringify(toolArgs))\n }\n\n const result = await executeTool(tool.name, toolArgs, toolContext)\n\n if (!result.success) {\n log(`Tool error: ${result.error}`)\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n }\n )\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - registration failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Development MCP server for Claude Code integration.\n *\n * This server uses HTTP transport and authenticates via the\n * OPEN_MERCATO_API_KEY environment variable, .mcp.json file,\n * or x-api-key header.\n *\n * Usage:\n * OPEN_MERCATO_API_KEY=omk_xxx yarn mcp:dev\n *\n * Or configure in .mcp.json for Claude Code with HTTP transport.\n */\nexport async function runMcpDevServer(): Promise<void> {\n const envApiKey = process.env.OPEN_MERCATO_API_KEY || (await getApiKeyFromMcpJson())\n const port = parseInt(process.env.MCP_DEV_PORT ?? '', 10) || DEFAULT_PORT\n const debug = process.env.MCP_DEBUG === 'true'\n\n if (!envApiKey) {\n log('Error: OPEN_MERCATO_API_KEY environment variable is required')\n log('')\n log('To get an API key:')\n log(' 1. Log into Open Mercato as an admin')\n log(' 2. Go to Settings > API Keys')\n log(' 3. Create a new key with the required permissions')\n log('')\n log('Then either:')\n log(' - Set environment variable: export OPEN_MERCATO_API_KEY=omk_xxx...')\n log(' - Or configure in .mcp.json with headers.x-api-key')\n process.exit(1)\n }\n\n log('Starting development MCP HTTP server...')\n\n // Create DI container\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n\n // Authenticate the API key upfront\n log('Authenticating API key...')\n const authResult = await authenticateMcpRequest(envApiKey, container)\n\n if (!authResult.success) {\n log(`Authentication failed: ${authResult.error}`)\n process.exit(1)\n }\n\n log(`Authenticated as: ${authResult.keyName}`)\n log(`Tenant: ${authResult.tenantId ?? '(global)'}`)\n log(`Organization: ${authResult.organizationId ?? '(none)'}`)\n log(`Super admin: ${authResult.isSuperAdmin}`)\n log(`Features: ${authResult.features.length > 0 ? authResult.features.join(', ') : '(none)'}`)\n\n // Load tools\n log('Loading tools...')\n await loadAllModuleTools()\n\n // Index tools for search (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n await indexToolsForSearch(searchService)\n\n const { indexApiEndpoints } = await import('./api-endpoint-index')\n const endpointCount = await indexApiEndpoints(searchService)\n if (endpointCount > 0) {\n log(`Indexed ${endpointCount} API endpoints for discovery`)\n }\n } catch {\n log('Search indexing skipped (search service not available)')\n }\n\n // Create tool context from auth result\n const toolContext: McpToolContext = {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n userId: authResult.userId,\n container,\n userFeatures: authResult.features,\n isSuperAdmin: authResult.isSuperAdmin,\n apiKeySecret: envApiKey,\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n mode: 'development',\n tools: getToolRegistry().listToolNames().length,\n tenant: authResult.tenantId,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n // Extract and validate API key from header\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate against the configured API key\n if (providedApiKey !== envApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid API key' }))\n return\n }\n\n if (debug) {\n log(`Authenticated request (${req.method})`)\n }\n\n try {\n // Create stateless transport\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create server with pre-authenticated context (no session tokens needed)\n const mcpServer = createDevMcpServer(toolContext, authResult.features, authResult.isSuperAdmin, debug)\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (debug) {\n log(`Request completed, cleaned up`)\n }\n })\n } catch (error) {\n log('Error handling request:', error)\n if (!res.headersSent) {\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n log(`Tools registered: ${toolCount}`)\n log(`Endpoint: http://localhost:${port}/mcp`)\n log(`Health: http://localhost:${port}/health`)\n log(`Mode: Development (API key auth, no session tokens)`)\n\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n log(`Server listening on port ${port}`)\n log('Ready for Claude Code connections')\n })\n\n const shutdown = async () => {\n log('Shutting down...')\n httpServer.close(() => {\n log('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,160 @@
1
+ const MCP_SERVERS_CONFIG_KEY = "mcp_servers";
2
+ async function getMcpServerConfigs(resolver) {
3
+ let service;
4
+ try {
5
+ service = resolver.resolve("moduleConfigService");
6
+ } catch {
7
+ return [];
8
+ }
9
+ try {
10
+ const value = await service.getValue(
11
+ "ai_assistant",
12
+ MCP_SERVERS_CONFIG_KEY,
13
+ { defaultValue: [] }
14
+ );
15
+ return value ?? [];
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+ async function getMcpServerConfig(resolver, serverId) {
21
+ const configs = await getMcpServerConfigs(resolver);
22
+ return configs.find((c) => c.id === serverId) ?? null;
23
+ }
24
+ async function getEnabledMcpServerConfigs(resolver) {
25
+ const configs = await getMcpServerConfigs(resolver);
26
+ return configs.filter((c) => c.enabled);
27
+ }
28
+ async function saveMcpServerConfig(resolver, config) {
29
+ let service;
30
+ try {
31
+ service = resolver.resolve("moduleConfigService");
32
+ } catch {
33
+ throw new Error("Configuration service unavailable");
34
+ }
35
+ const configs = await getMcpServerConfigs(resolver);
36
+ const now = (/* @__PURE__ */ new Date()).toISOString();
37
+ let updatedConfigs;
38
+ let savedConfig;
39
+ if (config.id) {
40
+ const existingIndex = configs.findIndex((c) => c.id === config.id);
41
+ if (existingIndex === -1) {
42
+ throw new Error(`MCP server config not found: ${config.id}`);
43
+ }
44
+ savedConfig = {
45
+ ...configs[existingIndex],
46
+ ...config,
47
+ id: config.id,
48
+ updatedAt: now
49
+ };
50
+ updatedConfigs = [
51
+ ...configs.slice(0, existingIndex),
52
+ savedConfig,
53
+ ...configs.slice(existingIndex + 1)
54
+ ];
55
+ } else {
56
+ savedConfig = {
57
+ ...config,
58
+ id: generateId(),
59
+ createdAt: now,
60
+ updatedAt: now
61
+ };
62
+ updatedConfigs = [...configs, savedConfig];
63
+ }
64
+ await service.setValue("ai_assistant", MCP_SERVERS_CONFIG_KEY, updatedConfigs);
65
+ return savedConfig;
66
+ }
67
+ async function updateMcpServerConfig(resolver, serverId, updates) {
68
+ let service;
69
+ try {
70
+ service = resolver.resolve("moduleConfigService");
71
+ } catch {
72
+ throw new Error("Configuration service unavailable");
73
+ }
74
+ const configs = await getMcpServerConfigs(resolver);
75
+ const existingIndex = configs.findIndex((c) => c.id === serverId);
76
+ if (existingIndex === -1) {
77
+ throw new Error(`MCP server config not found: ${serverId}`);
78
+ }
79
+ const updatedConfig = {
80
+ ...configs[existingIndex],
81
+ ...updates,
82
+ id: serverId,
83
+ // Ensure ID is preserved
84
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
85
+ };
86
+ const updatedConfigs = [
87
+ ...configs.slice(0, existingIndex),
88
+ updatedConfig,
89
+ ...configs.slice(existingIndex + 1)
90
+ ];
91
+ await service.setValue("ai_assistant", MCP_SERVERS_CONFIG_KEY, updatedConfigs);
92
+ return updatedConfig;
93
+ }
94
+ async function deleteMcpServerConfig(resolver, serverId) {
95
+ let service;
96
+ try {
97
+ service = resolver.resolve("moduleConfigService");
98
+ } catch {
99
+ throw new Error("Configuration service unavailable");
100
+ }
101
+ const configs = await getMcpServerConfigs(resolver);
102
+ const existingIndex = configs.findIndex((c) => c.id === serverId);
103
+ if (existingIndex === -1) {
104
+ return false;
105
+ }
106
+ const updatedConfigs = [
107
+ ...configs.slice(0, existingIndex),
108
+ ...configs.slice(existingIndex + 1)
109
+ ];
110
+ await service.setValue("ai_assistant", MCP_SERVERS_CONFIG_KEY, updatedConfigs);
111
+ return true;
112
+ }
113
+ async function toggleMcpServerEnabled(resolver, serverId) {
114
+ const config = await getMcpServerConfig(resolver, serverId);
115
+ if (!config) {
116
+ throw new Error(`MCP server config not found: ${serverId}`);
117
+ }
118
+ return updateMcpServerConfig(resolver, serverId, {
119
+ enabled: !config.enabled
120
+ });
121
+ }
122
+ function generateId() {
123
+ return `mcp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
124
+ }
125
+ function validateMcpServerConfig(config) {
126
+ if (!config.name?.trim()) {
127
+ return { valid: false, error: "Name is required" };
128
+ }
129
+ if (!["http", "stdio"].includes(config.type)) {
130
+ return { valid: false, error: 'Type must be "http" or "stdio"' };
131
+ }
132
+ if (config.type === "http") {
133
+ if (!config.url?.trim()) {
134
+ return { valid: false, error: "URL is required for HTTP servers" };
135
+ }
136
+ try {
137
+ new URL(config.url);
138
+ } catch {
139
+ return { valid: false, error: "Invalid URL format" };
140
+ }
141
+ }
142
+ if (config.type === "stdio") {
143
+ if (!config.command?.trim()) {
144
+ return { valid: false, error: "Command is required for stdio servers" };
145
+ }
146
+ }
147
+ return { valid: true };
148
+ }
149
+ export {
150
+ MCP_SERVERS_CONFIG_KEY,
151
+ deleteMcpServerConfig,
152
+ getEnabledMcpServerConfigs,
153
+ getMcpServerConfig,
154
+ getMcpServerConfigs,
155
+ saveMcpServerConfig,
156
+ toggleMcpServerEnabled,
157
+ updateMcpServerConfig,
158
+ validateMcpServerConfig
159
+ };
160
+ //# sourceMappingURL=mcp-server-config.js.map