@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.
- package/AGENTS.md +1090 -0
- package/README.md +607 -0
- package/build.mjs +92 -0
- package/dist/di.js +8 -0
- package/dist/di.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
- package/dist/frontend/components/CommandPalette/index.js +28 -0
- package/dist/frontend/components/CommandPalette/index.js.map +7 -0
- package/dist/frontend/constants.js +41 -0
- package/dist/frontend/constants.js.map +7 -0
- package/dist/frontend/hooks/index.js +13 -0
- package/dist/frontend/hooks/index.js.map +7 -0
- package/dist/frontend/hooks/useCommandPalette.js +1094 -0
- package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
- package/dist/frontend/hooks/useMcpTools.js +66 -0
- package/dist/frontend/hooks/useMcpTools.js.map +7 -0
- package/dist/frontend/hooks/usePageContext.js +48 -0
- package/dist/frontend/hooks/usePageContext.js.map +7 -0
- package/dist/frontend/hooks/useRecentActions.js +56 -0
- package/dist/frontend/hooks/useRecentActions.js.map +7 -0
- package/dist/frontend/hooks/useRecentTools.js +55 -0
- package/dist/frontend/hooks/useRecentTools.js.map +7 -0
- package/dist/frontend/index.js +35 -0
- package/dist/frontend/index.js.map +7 -0
- package/dist/frontend/types.js +1 -0
- package/dist/frontend/types.js.map +7 -0
- package/dist/frontend/utils/index.js +7 -0
- package/dist/frontend/utils/index.js.map +7 -0
- package/dist/frontend/utils/toolMatcher.js +95 -0
- package/dist/frontend/utils/toolMatcher.js.map +7 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/ai_assistant/acl.js +14 -0
- package/dist/modules/ai_assistant/acl.js.map +7 -0
- package/dist/modules/ai_assistant/api/chat/route.js +152 -0
- package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/health/route.js +27 -0
- package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/route/route.js +123 -0
- package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +60 -0
- package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/route.js +48 -0
- package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +192 -0
- package/dist/modules/ai_assistant/cli.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +11 -0
- package/dist/modules/ai_assistant/di.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/index.js +13 -0
- package/dist/modules/ai_assistant/index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/auth.js +87 -0
- package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
- package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
- package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
- package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
- package/dist/modules/ai_assistant/lib/http-server.js +367 -0
- package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
- package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
- package/dist/modules/ai_assistant/lib/types.js +1 -0
- package/dist/modules/ai_assistant/lib/types.js.map +7 -0
- package/package.json +108 -0
- package/src/di.ts +11 -0
- package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
- package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
- package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
- package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
- package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
- package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
- package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
- package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
- package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
- package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
- package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
- package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
- package/src/frontend/components/CommandPalette/index.ts +14 -0
- package/src/frontend/constants.ts +35 -0
- package/src/frontend/hooks/index.ts +5 -0
- package/src/frontend/hooks/useCommandPalette.ts +1389 -0
- package/src/frontend/hooks/useMcpTools.ts +73 -0
- package/src/frontend/hooks/usePageContext.ts +61 -0
- package/src/frontend/hooks/useRecentActions.ts +64 -0
- package/src/frontend/hooks/useRecentTools.ts +69 -0
- package/src/frontend/index.ts +39 -0
- package/src/frontend/types.ts +260 -0
- package/src/frontend/utils/index.ts +1 -0
- package/src/frontend/utils/toolMatcher.ts +127 -0
- package/src/index.ts +92 -0
- package/src/modules/ai_assistant/acl.ts +10 -0
- package/src/modules/ai_assistant/api/chat/route.ts +213 -0
- package/src/modules/ai_assistant/api/health/route.ts +30 -0
- package/src/modules/ai_assistant/api/route/route.ts +149 -0
- package/src/modules/ai_assistant/api/settings/route.ts +73 -0
- package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
- package/src/modules/ai_assistant/api/tools/route.ts +57 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +233 -0
- package/src/modules/ai_assistant/di.ts +9 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
- package/src/modules/ai_assistant/index.ts +11 -0
- package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
- package/src/modules/ai_assistant/lib/auth.ts +185 -0
- package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
- package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
- package/src/modules/ai_assistant/lib/http-server.ts +498 -0
- package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
- package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
- package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
- package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
- package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
- package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
- package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
- package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
- package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
- package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
- package/src/modules/ai_assistant/lib/types.ts +147 -0
- package/test-schema.ts +37 -0
- package/tsconfig.json +10 -0
- 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
|
+
}
|