@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,205 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { z } from 'zod'
3
+ import { zodToJsonSchema } from 'zod-to-json-schema'
4
+ import { getToolRegistry } from './tool-registry'
5
+ import { executeTool } from './tool-executor'
6
+ import { loadAllModuleTools } from './tool-loader'
7
+ import { authenticateMcpRequest, hasRequiredFeatures, type McpAuthSuccess } from './auth'
8
+ import type { McpToolContext, McpClientInterface, ToolInfo, ToolResult, McpToolDefinition } from './types'
9
+
10
+ /**
11
+ * Options for creating an in-process MCP client.
12
+ */
13
+ export type InProcessClientOptions = {
14
+ /** API key secret for authentication */
15
+ apiKeySecret: string
16
+ /** DI container */
17
+ container: AwilixContainer
18
+ }
19
+
20
+ /**
21
+ * Options for creating an in-process MCP client with direct auth context.
22
+ * Used when the caller already has authenticated user context (e.g., from session).
23
+ */
24
+ export type AuthContextOptions = {
25
+ /** DI container */
26
+ container: AwilixContainer
27
+ /** Pre-authenticated user context */
28
+ authContext: {
29
+ tenantId: string | null
30
+ organizationId: string | null
31
+ userId: string
32
+ userFeatures: string[]
33
+ isSuperAdmin: boolean
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Tool info with raw Zod schema for AI SDK integration.
39
+ */
40
+ export type ToolInfoWithSchema = {
41
+ name: string
42
+ description: string
43
+ inputSchema: z.ZodType<unknown>
44
+ }
45
+
46
+ /**
47
+ * In-process MCP client for direct tool execution.
48
+ *
49
+ * This client executes tools directly without MCP protocol overhead,
50
+ * making it the fastest option when running in the same process as
51
+ * the LLM service.
52
+ *
53
+ * Authentication is still performed via API key to ensure proper
54
+ * ACL filtering of available tools.
55
+ */
56
+ export class InProcessMcpClient implements McpClientInterface {
57
+ private auth: McpAuthSuccess
58
+ private container: AwilixContainer
59
+ private toolContext: McpToolContext
60
+ private toolsLoaded = false
61
+
62
+ private constructor(auth: McpAuthSuccess, container: AwilixContainer) {
63
+ this.auth = auth
64
+ this.container = container
65
+ this.toolContext = {
66
+ tenantId: auth.tenantId,
67
+ organizationId: auth.organizationId,
68
+ userId: auth.userId,
69
+ container,
70
+ userFeatures: auth.features,
71
+ isSuperAdmin: auth.isSuperAdmin,
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Create and authenticate an in-process client using API key.
77
+ */
78
+ static async create(options: InProcessClientOptions): Promise<InProcessMcpClient> {
79
+ const { apiKeySecret, container } = options
80
+
81
+ const authResult = await authenticateMcpRequest(apiKeySecret, container)
82
+ if (!authResult.success) {
83
+ throw new Error(`Authentication failed: ${authResult.error}`)
84
+ }
85
+
86
+ return new InProcessMcpClient(authResult, container)
87
+ }
88
+
89
+ /**
90
+ * Create an in-process client with pre-authenticated context.
91
+ * Use this when you already have user auth context (e.g., from session auth).
92
+ */
93
+ static async createWithAuthContext(options: AuthContextOptions): Promise<InProcessMcpClient> {
94
+ const { container, authContext } = options
95
+
96
+ // Create a synthetic auth result (no API key lookup needed)
97
+ const syntheticAuth: McpAuthSuccess = {
98
+ success: true,
99
+ keyId: 'session-auth',
100
+ keyName: 'Session Authentication',
101
+ tenantId: authContext.tenantId,
102
+ organizationId: authContext.organizationId,
103
+ userId: authContext.userId,
104
+ features: authContext.userFeatures,
105
+ isSuperAdmin: authContext.isSuperAdmin,
106
+ }
107
+
108
+ return new InProcessMcpClient(syntheticAuth, container)
109
+ }
110
+
111
+ /**
112
+ * Ensure tools are loaded from all modules.
113
+ */
114
+ private async ensureToolsLoaded(): Promise<void> {
115
+ if (!this.toolsLoaded) {
116
+ await loadAllModuleTools()
117
+ this.toolsLoaded = true
118
+ }
119
+ }
120
+
121
+ /**
122
+ * List available tools filtered by API key's permissions.
123
+ * Returns JSON Schema format (for MCP protocol compatibility).
124
+ */
125
+ async listTools(): Promise<ToolInfo[]> {
126
+ await this.ensureToolsLoaded()
127
+
128
+ const registry = getToolRegistry()
129
+ const tools = Array.from(registry.getTools().values())
130
+
131
+ const accessibleTools = tools.filter((tool) =>
132
+ hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)
133
+ )
134
+
135
+ return accessibleTools.map((tool) => ({
136
+ name: tool.name,
137
+ description: tool.description,
138
+ inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,
139
+ }))
140
+ }
141
+
142
+ /**
143
+ * List available tools with raw Zod schemas.
144
+ * Use this for AI SDK integration which requires Zod schemas.
145
+ */
146
+ async listToolsWithSchemas(): Promise<ToolInfoWithSchema[]> {
147
+ await this.ensureToolsLoaded()
148
+
149
+ const registry = getToolRegistry()
150
+ const tools = Array.from(registry.getTools().values())
151
+
152
+ const accessibleTools = tools.filter((tool) =>
153
+ hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)
154
+ )
155
+
156
+ return accessibleTools.map((tool) => ({
157
+ name: tool.name,
158
+ description: tool.description,
159
+ inputSchema: tool.inputSchema,
160
+ }))
161
+ }
162
+
163
+ /**
164
+ * Execute a tool directly.
165
+ */
166
+ async callTool(name: string, args: unknown): Promise<ToolResult> {
167
+ await this.ensureToolsLoaded()
168
+
169
+ const result = await executeTool(name, args ?? {}, this.toolContext)
170
+
171
+ return {
172
+ success: result.success,
173
+ result: result.result,
174
+ error: result.error,
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Close the client (no-op for in-process).
180
+ */
181
+ async close(): Promise<void> {
182
+ // No resources to clean up for in-process client
183
+ }
184
+
185
+ /**
186
+ * Get the authenticated context info.
187
+ */
188
+ getAuthInfo(): {
189
+ keyId: string
190
+ keyName: string
191
+ tenantId: string | null
192
+ organizationId: string | null
193
+ userId: string
194
+ isSuperAdmin: boolean
195
+ } {
196
+ return {
197
+ keyId: this.auth.keyId,
198
+ keyName: this.auth.keyName,
199
+ tenantId: this.auth.tenantId,
200
+ organizationId: this.auth.organizationId,
201
+ userId: this.auth.userId,
202
+ isSuperAdmin: this.auth.isSuperAdmin,
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,221 @@
1
+ import { spawn, type ChildProcess } 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
+ import type { McpClientInterface, ToolInfo, ToolResult } from './types'
6
+
7
+ /**
8
+ * Options for stdio transport.
9
+ */
10
+ export type StdioClientOptions = {
11
+ transport: 'stdio'
12
+ /** API key secret (passed to server via --api-key) */
13
+ apiKeySecret: string
14
+ /** Command to run (default: 'yarn') */
15
+ command?: string
16
+ /** Arguments for the command (default: mercato mcp:serve with api-key) */
17
+ args?: string[]
18
+ /** Working directory (default: process.cwd()) */
19
+ cwd?: string
20
+ }
21
+
22
+ /**
23
+ * Options for HTTP transport.
24
+ */
25
+ export type HttpClientOptions = {
26
+ transport: 'http'
27
+ /** API key secret (sent via x-api-key header) */
28
+ apiKeySecret: string
29
+ /** MCP server URL (e.g., 'http://localhost:3001/mcp') */
30
+ url: string
31
+ }
32
+
33
+ /**
34
+ * Combined options for McpClient.
35
+ */
36
+ export type McpClientOptions = StdioClientOptions | HttpClientOptions
37
+
38
+ /**
39
+ * MCP protocol client for connecting to MCP servers.
40
+ *
41
+ * Supports two transport modes:
42
+ * - stdio: Spawns server as subprocess
43
+ * - http: Connects to HTTP server
44
+ */
45
+ export class McpClient implements McpClientInterface {
46
+ private client: Client
47
+ private transport: StdioClientTransport | StreamableHTTPClientTransport
48
+ private childProcess?: ChildProcess
49
+ private apiKeySecret: string
50
+
51
+ private constructor(
52
+ client: Client,
53
+ transport: StdioClientTransport | StreamableHTTPClientTransport,
54
+ apiKeySecret: string,
55
+ childProcess?: ChildProcess
56
+ ) {
57
+ this.client = client
58
+ this.transport = transport
59
+ this.apiKeySecret = apiKeySecret
60
+ this.childProcess = childProcess
61
+ }
62
+
63
+ /**
64
+ * Connect to an MCP server via the specified transport.
65
+ */
66
+ static async connect(options: McpClientOptions): Promise<McpClient> {
67
+ if (options.transport === 'stdio') {
68
+ return McpClient.connectStdio(options)
69
+ } else {
70
+ return McpClient.connectHttp(options)
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Connect via stdio transport (spawn subprocess).
76
+ */
77
+ private static async connectStdio(options: StdioClientOptions): Promise<McpClient> {
78
+ const command = options.command ?? 'yarn'
79
+ const args = options.args ?? [
80
+ 'mercato',
81
+ 'ai_assistant',
82
+ 'mcp:serve',
83
+ '--api-key',
84
+ options.apiKeySecret,
85
+ ]
86
+ const cwd = options.cwd ?? process.cwd()
87
+
88
+ const childProcess = spawn(command, args, {
89
+ cwd,
90
+ stdio: ['pipe', 'pipe', 'pipe'],
91
+ env: { ...process.env },
92
+ })
93
+
94
+ // Forward stderr for debugging
95
+ childProcess.stderr?.on('data', (data) => {
96
+ const message = data.toString().trim()
97
+ if (message) {
98
+ console.error(`[MCP Client] ${message}`)
99
+ }
100
+ })
101
+
102
+ const transport = new StdioClientTransport({
103
+ command,
104
+ args,
105
+ cwd,
106
+ env: process.env as Record<string, string>,
107
+ })
108
+
109
+ const client = new Client(
110
+ { name: 'open-mercato-client', version: '0.1.0' },
111
+ { capabilities: {} }
112
+ )
113
+
114
+ await client.connect(transport)
115
+
116
+ return new McpClient(client, transport, options.apiKeySecret, childProcess)
117
+ }
118
+
119
+ /**
120
+ * Connect via HTTP transport.
121
+ */
122
+ private static async connectHttp(options: HttpClientOptions): Promise<McpClient> {
123
+ const transport = new StreamableHTTPClientTransport(
124
+ new URL(options.url),
125
+ {
126
+ requestInit: {
127
+ headers: {
128
+ 'x-api-key': options.apiKeySecret,
129
+ },
130
+ },
131
+ }
132
+ )
133
+
134
+ const client = new Client(
135
+ { name: 'open-mercato-client', version: '0.1.0' },
136
+ { capabilities: {} }
137
+ )
138
+
139
+ await client.connect(transport)
140
+
141
+ return new McpClient(client, transport, options.apiKeySecret)
142
+ }
143
+
144
+ /**
145
+ * List available tools from the server.
146
+ */
147
+ async listTools(): Promise<ToolInfo[]> {
148
+ const response = await this.client.listTools()
149
+
150
+ return response.tools.map((tool) => ({
151
+ name: tool.name,
152
+ description: tool.description ?? '',
153
+ inputSchema: (tool.inputSchema ?? {}) as Record<string, unknown>,
154
+ }))
155
+ }
156
+
157
+ /**
158
+ * Call a tool on the server.
159
+ */
160
+ async callTool(name: string, args: unknown): Promise<ToolResult> {
161
+ try {
162
+ const response = await this.client.callTool({
163
+ name,
164
+ arguments: args as Record<string, unknown>,
165
+ })
166
+
167
+ // Parse content from response
168
+ const content = response.content
169
+ if (!Array.isArray(content) || content.length === 0) {
170
+ return { success: true, result: null }
171
+ }
172
+
173
+ const firstContent = content[0]
174
+ if (firstContent.type === 'text') {
175
+ try {
176
+ const parsed = JSON.parse(firstContent.text)
177
+
178
+ // Check if it's an error response
179
+ if (response.isError || parsed.error) {
180
+ return {
181
+ success: false,
182
+ error: parsed.error ?? 'Unknown error',
183
+ }
184
+ }
185
+
186
+ return { success: true, result: parsed }
187
+ } catch {
188
+ // Not JSON, return as-is
189
+ return { success: true, result: firstContent.text }
190
+ }
191
+ }
192
+
193
+ return { success: true, result: content }
194
+ } catch (error) {
195
+ const message = error instanceof Error ? error.message : String(error)
196
+ return { success: false, error: message }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Close the client and release resources.
202
+ */
203
+ async close(): Promise<void> {
204
+ try {
205
+ await this.client.close()
206
+ } catch {
207
+ // Ignore close errors
208
+ }
209
+
210
+ try {
211
+ await this.transport.close()
212
+ } catch {
213
+ // Ignore close errors
214
+ }
215
+
216
+ if (this.childProcess) {
217
+ this.childProcess.kill()
218
+ this.childProcess = undefined
219
+ }
220
+ }
221
+ }