@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,367 @@
|
|
|
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 { extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
|
|
9
|
+
import { jsonSchemaToZod } from "./schema-utils.js";
|
|
10
|
+
import { findApiKeyBySessionToken } from "@open-mercato/core/modules/api_keys/services/apiKeyService";
|
|
11
|
+
async function resolveSessionContext(sessionToken, baseContext, debug) {
|
|
12
|
+
try {
|
|
13
|
+
const em = baseContext.container.resolve("em");
|
|
14
|
+
const rbacService = baseContext.container.resolve("rbacService");
|
|
15
|
+
const sessionKey = await findApiKeyBySessionToken(em, sessionToken);
|
|
16
|
+
if (!sessionKey) {
|
|
17
|
+
if (debug) {
|
|
18
|
+
console.error(`[MCP HTTP] Session token not found or expired: ${sessionToken}`);
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const userId = sessionKey.sessionUserId || sessionKey.createdBy;
|
|
23
|
+
if (!userId) {
|
|
24
|
+
if (debug) {
|
|
25
|
+
console.error(`[MCP HTTP] Session key has no associated user`);
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {
|
|
30
|
+
tenantId: sessionKey.tenantId ?? null,
|
|
31
|
+
organizationId: sessionKey.organizationId ?? null
|
|
32
|
+
});
|
|
33
|
+
if (debug) {
|
|
34
|
+
console.error(`[MCP HTTP] Session context resolved for user ${userId}:`, {
|
|
35
|
+
tenantId: sessionKey.tenantId,
|
|
36
|
+
organizationId: sessionKey.organizationId,
|
|
37
|
+
features: acl.features.length,
|
|
38
|
+
isSuperAdmin: acl.isSuperAdmin
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
tenantId: sessionKey.tenantId ?? null,
|
|
43
|
+
organizationId: sessionKey.organizationId ?? null,
|
|
44
|
+
userId,
|
|
45
|
+
container: baseContext.container,
|
|
46
|
+
userFeatures: acl.features,
|
|
47
|
+
isSuperAdmin: acl.isSuperAdmin,
|
|
48
|
+
apiKeySecret: baseContext.apiKeySecret
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (debug) {
|
|
52
|
+
console.error(`[MCP HTTP] Error resolving session context:`, error);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function createMcpServerForRequest(config, toolContext) {
|
|
58
|
+
const server = new McpServer(
|
|
59
|
+
{ name: config.name, version: config.version },
|
|
60
|
+
{ capabilities: { tools: {} } }
|
|
61
|
+
);
|
|
62
|
+
const registry = getToolRegistry();
|
|
63
|
+
const tools = Array.from(registry.getTools().values());
|
|
64
|
+
if (config.debug) {
|
|
65
|
+
console.error(`[MCP HTTP] Registering ${tools.length} tools (ACL checked per-call via session token)`);
|
|
66
|
+
}
|
|
67
|
+
for (const tool of tools) {
|
|
68
|
+
if (config.debug) {
|
|
69
|
+
console.error(`[MCP HTTP] Registering tool: ${tool.name}`);
|
|
70
|
+
}
|
|
71
|
+
let safeSchema;
|
|
72
|
+
if (tool.inputSchema) {
|
|
73
|
+
try {
|
|
74
|
+
const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: "any" });
|
|
75
|
+
const properties = jsonSchema.properties ?? {};
|
|
76
|
+
properties._sessionToken = {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Session authorization token (REQUIRED for all tool calls)"
|
|
79
|
+
};
|
|
80
|
+
jsonSchema.properties = properties;
|
|
81
|
+
const converted = jsonSchemaToZod(jsonSchema);
|
|
82
|
+
safeSchema = converted.passthrough();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (config.debug) {
|
|
85
|
+
console.error(
|
|
86
|
+
`[MCP HTTP] Skipping tool ${tool.name} - schema conversion failed:`,
|
|
87
|
+
error instanceof Error ? error.message : error
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
safeSchema = z.object({
|
|
94
|
+
_sessionToken: z.string().optional().describe("Session authorization token (REQUIRED for all tool calls)")
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
server.registerTool(
|
|
99
|
+
tool.name,
|
|
100
|
+
{
|
|
101
|
+
description: tool.description,
|
|
102
|
+
inputSchema: safeSchema
|
|
103
|
+
},
|
|
104
|
+
async (args) => {
|
|
105
|
+
const toolArgs = args ?? {};
|
|
106
|
+
const sessionToken = toolArgs._sessionToken;
|
|
107
|
+
delete toolArgs._sessionToken;
|
|
108
|
+
if (config.debug) {
|
|
109
|
+
console.error(`[MCP HTTP] Calling tool: ${tool.name}`, {
|
|
110
|
+
hasSessionToken: !!sessionToken,
|
|
111
|
+
args: JSON.stringify(toolArgs)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
let effectiveContext = toolContext;
|
|
115
|
+
if (sessionToken) {
|
|
116
|
+
const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug);
|
|
117
|
+
if (sessionContext) {
|
|
118
|
+
effectiveContext = sessionContext;
|
|
119
|
+
} else {
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify({
|
|
125
|
+
error: "Your chat session has expired. Please close and reopen the chat window to continue.",
|
|
126
|
+
code: "SESSION_EXPIRED"
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
isError: true
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: JSON.stringify({
|
|
140
|
+
error: "Session token required (_sessionToken parameter)",
|
|
141
|
+
code: "UNAUTHORIZED"
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
isError: true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (tool.requiredFeatures?.length) {
|
|
150
|
+
const hasAccess = hasRequiredFeatures(
|
|
151
|
+
tool.requiredFeatures,
|
|
152
|
+
effectiveContext.userFeatures,
|
|
153
|
+
effectiveContext.isSuperAdmin
|
|
154
|
+
);
|
|
155
|
+
if (!hasAccess) {
|
|
156
|
+
return {
|
|
157
|
+
content: [
|
|
158
|
+
{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: JSON.stringify({
|
|
161
|
+
error: `Insufficient permissions for tool "${tool.name}". Required: ${tool.requiredFeatures.join(", ")}`,
|
|
162
|
+
code: "UNAUTHORIZED"
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
isError: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const result = await executeTool(tool.name, toolArgs, effectiveContext);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: JSON.stringify({ error: result.error, code: result.errorCode })
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
isError: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify(result.result, null, 2)
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (config.debug) {
|
|
194
|
+
console.error(
|
|
195
|
+
`[MCP HTTP] Skipping tool ${tool.name} - registration failed:`,
|
|
196
|
+
error instanceof Error ? error.message : error
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return server;
|
|
203
|
+
}
|
|
204
|
+
const MAX_BODY_SIZE = 1 * 1024 * 1024;
|
|
205
|
+
async function parseJsonBody(req) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const chunks = [];
|
|
208
|
+
let totalSize = 0;
|
|
209
|
+
req.on("data", (chunk) => {
|
|
210
|
+
totalSize += chunk.length;
|
|
211
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
212
|
+
req.destroy();
|
|
213
|
+
reject(new Error("Request payload too large"));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
chunks.push(chunk);
|
|
217
|
+
});
|
|
218
|
+
req.on("end", () => {
|
|
219
|
+
try {
|
|
220
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
221
|
+
resolve(body ? JSON.parse(body) : void 0);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
reject(error);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
req.on("error", reject);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function runMcpHttpServer(options) {
|
|
230
|
+
const { config, container, port } = options;
|
|
231
|
+
await loadAllModuleTools();
|
|
232
|
+
try {
|
|
233
|
+
const searchService = container.resolve("searchService");
|
|
234
|
+
await indexToolsForSearch(searchService);
|
|
235
|
+
const { indexApiEndpoints } = await import("./api-endpoint-index.js");
|
|
236
|
+
const endpointCount = await indexApiEndpoints(searchService);
|
|
237
|
+
if (endpointCount > 0) {
|
|
238
|
+
console.error(`[MCP HTTP] Indexed ${endpointCount} API endpoints for hybrid search`);
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error("[MCP HTTP] Search indexing skipped (search service not available):", error);
|
|
242
|
+
}
|
|
243
|
+
const httpServer = createServer(async (req, res) => {
|
|
244
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
245
|
+
if (url.pathname === "/health") {
|
|
246
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
247
|
+
res.end(JSON.stringify({
|
|
248
|
+
status: "ok",
|
|
249
|
+
tools: getToolRegistry().listToolNames().length,
|
|
250
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
251
|
+
}));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (url.pathname !== "/mcp") {
|
|
255
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
256
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const headers = {};
|
|
260
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
261
|
+
headers[key] = Array.isArray(value) ? value[0] : value;
|
|
262
|
+
}
|
|
263
|
+
const providedApiKey = extractApiKeyFromHeaders(headers);
|
|
264
|
+
if (!providedApiKey) {
|
|
265
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
266
|
+
res.end(JSON.stringify({ error: "API key required (x-api-key header)" }));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const serverApiKey = options.serverApiKey || process.env.MCP_SERVER_API_KEY;
|
|
270
|
+
if (!serverApiKey) {
|
|
271
|
+
console.error("[MCP HTTP] Warning: MCP_SERVER_API_KEY not configured, rejecting all requests");
|
|
272
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
273
|
+
res.end(JSON.stringify({ error: "MCP server not properly configured" }));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (providedApiKey !== serverApiKey) {
|
|
277
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
278
|
+
res.end(JSON.stringify({ error: "Invalid API key" }));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (config.debug) {
|
|
282
|
+
console.error(`[MCP HTTP] Server-level auth passed (${req.method})`);
|
|
283
|
+
}
|
|
284
|
+
const toolContext = {
|
|
285
|
+
tenantId: null,
|
|
286
|
+
organizationId: null,
|
|
287
|
+
userId: null,
|
|
288
|
+
container,
|
|
289
|
+
userFeatures: [],
|
|
290
|
+
isSuperAdmin: false,
|
|
291
|
+
apiKeySecret: providedApiKey
|
|
292
|
+
};
|
|
293
|
+
try {
|
|
294
|
+
const transport = new StreamableHTTPServerTransport({
|
|
295
|
+
sessionIdGenerator: void 0,
|
|
296
|
+
enableJsonResponse: req.method === "POST"
|
|
297
|
+
});
|
|
298
|
+
const mcpServer = createMcpServerForRequest(config, toolContext);
|
|
299
|
+
if (config.debug) {
|
|
300
|
+
const registeredTools = mcpServer._registeredTools || {};
|
|
301
|
+
console.error(`[MCP HTTP] Registered tools in McpServer:`, Object.keys(registeredTools));
|
|
302
|
+
console.error(`[MCP HTTP] Tool handlers initialized:`, mcpServer._toolHandlersInitialized);
|
|
303
|
+
}
|
|
304
|
+
await mcpServer.connect(transport);
|
|
305
|
+
if (req.method === "POST") {
|
|
306
|
+
const body = await parseJsonBody(req);
|
|
307
|
+
await transport.handleRequest(req, res, body);
|
|
308
|
+
} else {
|
|
309
|
+
await transport.handleRequest(req, res);
|
|
310
|
+
}
|
|
311
|
+
res.on("finish", () => {
|
|
312
|
+
transport.close();
|
|
313
|
+
mcpServer.close();
|
|
314
|
+
if (config.debug) {
|
|
315
|
+
console.error(`[MCP HTTP] Request completed, cleaned up`);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error("[MCP HTTP] Error handling request:", error);
|
|
320
|
+
if (!res.headersSent) {
|
|
321
|
+
if (error instanceof Error && error.message === "Request payload too large") {
|
|
322
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
323
|
+
res.end(JSON.stringify({ error: "Request payload too large (max 1MB)" }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
327
|
+
res.end(
|
|
328
|
+
JSON.stringify({
|
|
329
|
+
jsonrpc: "2.0",
|
|
330
|
+
error: {
|
|
331
|
+
code: -32603,
|
|
332
|
+
message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`
|
|
333
|
+
},
|
|
334
|
+
id: null
|
|
335
|
+
})
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
const toolCount = getToolRegistry().listToolNames().length;
|
|
341
|
+
const serverKeyConfigured = !!(options.serverApiKey || process.env.MCP_SERVER_API_KEY);
|
|
342
|
+
console.error(`[MCP HTTP] Starting ${config.name} v${config.version}`);
|
|
343
|
+
console.error(`[MCP HTTP] Endpoint: http://localhost:${port}/mcp`);
|
|
344
|
+
console.error(`[MCP HTTP] Health: http://localhost:${port}/health`);
|
|
345
|
+
console.error(`[MCP HTTP] Tools registered: ${toolCount}`);
|
|
346
|
+
console.error(`[MCP HTTP] Mode: Stateless (new server per request)`);
|
|
347
|
+
console.error(`[MCP HTTP] Server Auth: ${serverKeyConfigured ? "MCP_SERVER_API_KEY configured" : "WARNING: MCP_SERVER_API_KEY not set!"}`);
|
|
348
|
+
console.error(`[MCP HTTP] User Auth: Session token in _sessionToken parameter`);
|
|
349
|
+
return new Promise((resolve) => {
|
|
350
|
+
httpServer.listen(port, () => {
|
|
351
|
+
console.error(`[MCP HTTP] Server listening on port ${port}`);
|
|
352
|
+
});
|
|
353
|
+
const shutdown = async () => {
|
|
354
|
+
console.error("[MCP HTTP] Shutting down...");
|
|
355
|
+
httpServer.close(() => {
|
|
356
|
+
console.error("[MCP HTTP] Server closed");
|
|
357
|
+
resolve();
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
process.on("SIGINT", shutdown);
|
|
361
|
+
process.on("SIGTERM", shutdown);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
export {
|
|
365
|
+
runMcpHttpServer
|
|
366
|
+
};
|
|
367
|
+
//# sourceMappingURL=http-server.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/http-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 type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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, toSafeZodSchema } from './schema-utils'\nimport type { McpServerConfig, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { findApiKeyBySessionToken } from '@open-mercato/core/modules/api_keys/services/apiKeyService'\n\n/**\n * Options for the HTTP MCP server.\n */\nexport type McpHttpServerOptions = {\n config: McpServerConfig\n container: AwilixContainer\n port: number\n /** Static API key for server-level authentication (from env MCP_SERVER_API_KEY) */\n serverApiKey?: string\n}\n\n/**\n * Resolve user context from session token.\n * Returns null if session token is invalid or expired.\n */\nasync function resolveSessionContext(\n sessionToken: string,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const em = baseContext.container.resolve<EntityManager>('em')\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n\n // Look up ephemeral key by session token\n const sessionKey = await findApiKeyBySessionToken(em, sessionToken)\n if (!sessionKey) {\n if (debug) {\n console.error(`[MCP HTTP] Session token not found or expired: ${sessionToken}`)\n }\n return null\n }\n\n // Load ACL for the session user\n const userId = sessionKey.sessionUserId || sessionKey.createdBy\n if (!userId) {\n if (debug) {\n console.error(`[MCP HTTP] Session key has no associated user`)\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n })\n\n if (debug) {\n console.error(`[MCP HTTP] Session context resolved for user ${userId}:`, {\n tenantId: sessionKey.tenantId,\n organizationId: sessionKey.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n })\n }\n\n return {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n apiKeySecret: baseContext.apiKeySecret,\n }\n } catch (error) {\n if (debug) {\n console.error(`[MCP HTTP] Error resolving session context:`, error)\n }\n return null\n }\n}\n\n/**\n * Create a stateless MCP server instance for a single request.\n * Tools are registered without pre-filtering - permission checks happen at execution time\n * based on the session token provided in each tool call.\n */\nfunction createMcpServerForRequest(\n config: McpServerConfig,\n toolContext: McpToolContext\n): McpServer {\n const server = new McpServer(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n if (config.debug) {\n console.error(`[MCP HTTP] Registering ${tools.length} tools (ACL checked per-call via session token)`)\n }\n\n // Register ALL tools - permission checks happen at execution time via session token\n for (const tool of tools) {\n if (config.debug) {\n console.error(`[MCP HTTP] Registering tool: ${tool.name}`)\n }\n\n // Convert Zod schema to a \"safe\" schema without Date types\n // This uses JSON Schema round-trip to avoid issues with MCP SDK's internal conversion\n // Also inject _sessionToken as an optional parameter so the AI knows to pass it\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n // Convert to JSON Schema first\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Inject _sessionToken into the JSON schema properties\n const properties = (jsonSchema.properties ?? {}) as Record<string, unknown>\n properties._sessionToken = {\n type: 'string',\n description: 'Session authorization token (REQUIRED for all tool calls)',\n }\n jsonSchema.properties = properties\n\n // Convert back to Zod with passthrough to allow extra properties\n const converted = jsonSchemaToZod(jsonSchema)\n // Use type assertion since we know it's an object schema (we added properties above)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (config.debug) {\n console.error(\n `[MCP HTTP] Skipping tool ${tool.name} - schema conversion failed:`,\n error instanceof Error ? error.message : error\n )\n }\n continue\n }\n } else {\n // If no schema, create one with just _sessionToken\n safeSchema = z.object({\n _sessionToken: z\n .string()\n .optional()\n .describe('Session authorization token (REQUIRED for all tool calls)'),\n })\n }\n\n // Wrap in try/catch to handle any remaining edge cases\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 // Extract session token from args\n const sessionToken = toolArgs._sessionToken as string | undefined\n delete toolArgs._sessionToken // Remove before passing to tool handler\n\n if (config.debug) {\n console.error(`[MCP HTTP] Calling tool: ${tool.name}`, {\n hasSessionToken: !!sessionToken,\n args: JSON.stringify(toolArgs),\n })\n }\n\n // Resolve user context from session token\n let effectiveContext = toolContext\n if (sessionToken) {\n const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug)\n if (sessionContext) {\n effectiveContext = sessionContext\n } else {\n // Session token expired - return user-friendly error for AI to relay\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Your chat session has expired. Please close and reopen the chat window to continue.',\n code: 'SESSION_EXPIRED',\n }),\n },\n ],\n isError: true,\n }\n }\n } else {\n // No session token provided - reject if base context has no permissions\n if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Session token required (_sessionToken parameter)',\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n // Check if user has required permissions for this tool\n if (tool.requiredFeatures?.length) {\n const hasAccess = hasRequiredFeatures(\n tool.requiredFeatures,\n effectiveContext.userFeatures,\n effectiveContext.isSuperAdmin\n )\n if (!hasAccess) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: `Insufficient permissions for tool \"${tool.name}\". Required: ${tool.requiredFeatures.join(', ')}`,\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n const result = await executeTool(tool.name, toolArgs, effectiveContext)\n\n if (!result.success) {\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 // Skip tools with schemas that can't be registered\n if (config.debug) {\n console.error(\n `[MCP HTTP] Skipping tool ${tool.name} - registration failed:`,\n error instanceof Error ? error.message : error\n )\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Maximum request body size (1MB).\n * Prevents memory exhaustion from oversized payloads.\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 * Run MCP server with HTTP transport (stateless mode).\n *\n * Each request creates a new MCP server instance and transport.\n * The server authenticates requests using API keys from the x-api-key header.\n */\nexport async function runMcpHttpServer(options: McpHttpServerOptions): Promise<void> {\n const { config, container, port } = options\n\n await loadAllModuleTools()\n\n // Index tools and API endpoints for hybrid search discovery (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index API endpoints for api_discover\n const { indexApiEndpoints } = await import('./api-endpoint-index')\n const endpointCount = await indexApiEndpoints(searchService)\n if (endpointCount > 0) {\n console.error(`[MCP HTTP] Indexed ${endpointCount} API endpoints for hybrid search`)\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n console.error('[MCP HTTP] Search indexing skipped (search service not available):', error)\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 tools: getToolRegistry().listToolNames().length,\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 headers\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 // Server-level authentication with static API key\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 // Check against static server API key (from env MCP_SERVER_API_KEY)\n const serverApiKey = options.serverApiKey || process.env.MCP_SERVER_API_KEY\n if (!serverApiKey) {\n console.error('[MCP HTTP] Warning: MCP_SERVER_API_KEY not configured, rejecting all requests')\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'MCP server not properly configured' }))\n return\n }\n\n if (providedApiKey !== serverApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid API key' }))\n return\n }\n\n if (config.debug) {\n console.error(`[MCP HTTP] Server-level auth passed (${req.method})`)\n }\n\n // Create base tool context (will be overridden by session token per-tool)\n // Start with minimal permissions - session tokens provide user-level auth\n const toolContext: McpToolContext = {\n tenantId: null,\n organizationId: null,\n userId: null,\n container,\n userFeatures: [],\n isSuperAdmin: false,\n apiKeySecret: providedApiKey,\n }\n\n try {\n // Create stateless transport (no session ID generator = stateless)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create new server for this request\n const mcpServer = createMcpServerForRequest(config, toolContext)\n\n if (config.debug) {\n // Check registered tools on the server\n const registeredTools = (mcpServer as any)._registeredTools || {}\n console.error(`[MCP HTTP] Registered tools in McpServer:`, Object.keys(registeredTools))\n console.error(`[MCP HTTP] Tool handlers initialized:`, (mcpServer as any)._toolHandlersInitialized)\n }\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 (config.debug) {\n console.error(`[MCP HTTP] Request completed, cleaned up`)\n }\n })\n } catch (error) {\n console.error('[MCP HTTP] Error handling request:', error)\n if (!res.headersSent) {\n // Handle payload too large error\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 const serverKeyConfigured = !!(options.serverApiKey || process.env.MCP_SERVER_API_KEY)\n\n console.error(`[MCP HTTP] Starting ${config.name} v${config.version}`)\n console.error(`[MCP HTTP] Endpoint: http://localhost:${port}/mcp`)\n console.error(`[MCP HTTP] Health: http://localhost:${port}/health`)\n console.error(`[MCP HTTP] Tools registered: ${toolCount}`)\n console.error(`[MCP HTTP] Mode: Stateless (new server per request)`)\n console.error(`[MCP HTTP] Server Auth: ${serverKeyConfigured ? 'MCP_SERVER_API_KEY configured' : 'WARNING: MCP_SERVER_API_KEY not set!'}`)\n console.error(`[MCP HTTP] User Auth: Session token in _sessionToken parameter`)\n\n // Return a Promise that keeps the process alive until shutdown\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n console.error(`[MCP HTTP] Server listening on port ${port}`)\n })\n\n const shutdown = async () => {\n console.error('[MCP HTTP] Shutting down...')\n httpServer.close(() => {\n console.error('[MCP HTTP] 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,126 @@
|
|
|
1
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
+
import { getToolRegistry } from "./tool-registry.js";
|
|
3
|
+
import { executeTool } from "./tool-executor.js";
|
|
4
|
+
import { loadAllModuleTools } from "./tool-loader.js";
|
|
5
|
+
import { authenticateMcpRequest, hasRequiredFeatures } from "./auth.js";
|
|
6
|
+
class InProcessMcpClient {
|
|
7
|
+
constructor(auth, container) {
|
|
8
|
+
this.toolsLoaded = false;
|
|
9
|
+
this.auth = auth;
|
|
10
|
+
this.container = container;
|
|
11
|
+
this.toolContext = {
|
|
12
|
+
tenantId: auth.tenantId,
|
|
13
|
+
organizationId: auth.organizationId,
|
|
14
|
+
userId: auth.userId,
|
|
15
|
+
container,
|
|
16
|
+
userFeatures: auth.features,
|
|
17
|
+
isSuperAdmin: auth.isSuperAdmin
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create and authenticate an in-process client using API key.
|
|
22
|
+
*/
|
|
23
|
+
static async create(options) {
|
|
24
|
+
const { apiKeySecret, container } = options;
|
|
25
|
+
const authResult = await authenticateMcpRequest(apiKeySecret, container);
|
|
26
|
+
if (!authResult.success) {
|
|
27
|
+
throw new Error(`Authentication failed: ${authResult.error}`);
|
|
28
|
+
}
|
|
29
|
+
return new InProcessMcpClient(authResult, container);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create an in-process client with pre-authenticated context.
|
|
33
|
+
* Use this when you already have user auth context (e.g., from session auth).
|
|
34
|
+
*/
|
|
35
|
+
static async createWithAuthContext(options) {
|
|
36
|
+
const { container, authContext } = options;
|
|
37
|
+
const syntheticAuth = {
|
|
38
|
+
success: true,
|
|
39
|
+
keyId: "session-auth",
|
|
40
|
+
keyName: "Session Authentication",
|
|
41
|
+
tenantId: authContext.tenantId,
|
|
42
|
+
organizationId: authContext.organizationId,
|
|
43
|
+
userId: authContext.userId,
|
|
44
|
+
features: authContext.userFeatures,
|
|
45
|
+
isSuperAdmin: authContext.isSuperAdmin
|
|
46
|
+
};
|
|
47
|
+
return new InProcessMcpClient(syntheticAuth, container);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Ensure tools are loaded from all modules.
|
|
51
|
+
*/
|
|
52
|
+
async ensureToolsLoaded() {
|
|
53
|
+
if (!this.toolsLoaded) {
|
|
54
|
+
await loadAllModuleTools();
|
|
55
|
+
this.toolsLoaded = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* List available tools filtered by API key's permissions.
|
|
60
|
+
* Returns JSON Schema format (for MCP protocol compatibility).
|
|
61
|
+
*/
|
|
62
|
+
async listTools() {
|
|
63
|
+
await this.ensureToolsLoaded();
|
|
64
|
+
const registry = getToolRegistry();
|
|
65
|
+
const tools = Array.from(registry.getTools().values());
|
|
66
|
+
const accessibleTools = tools.filter(
|
|
67
|
+
(tool) => hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)
|
|
68
|
+
);
|
|
69
|
+
return accessibleTools.map((tool) => ({
|
|
70
|
+
name: tool.name,
|
|
71
|
+
description: tool.description,
|
|
72
|
+
inputSchema: zodToJsonSchema(tool.inputSchema)
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* List available tools with raw Zod schemas.
|
|
77
|
+
* Use this for AI SDK integration which requires Zod schemas.
|
|
78
|
+
*/
|
|
79
|
+
async listToolsWithSchemas() {
|
|
80
|
+
await this.ensureToolsLoaded();
|
|
81
|
+
const registry = getToolRegistry();
|
|
82
|
+
const tools = Array.from(registry.getTools().values());
|
|
83
|
+
const accessibleTools = tools.filter(
|
|
84
|
+
(tool) => hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)
|
|
85
|
+
);
|
|
86
|
+
return accessibleTools.map((tool) => ({
|
|
87
|
+
name: tool.name,
|
|
88
|
+
description: tool.description,
|
|
89
|
+
inputSchema: tool.inputSchema
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Execute a tool directly.
|
|
94
|
+
*/
|
|
95
|
+
async callTool(name, args) {
|
|
96
|
+
await this.ensureToolsLoaded();
|
|
97
|
+
const result = await executeTool(name, args ?? {}, this.toolContext);
|
|
98
|
+
return {
|
|
99
|
+
success: result.success,
|
|
100
|
+
result: result.result,
|
|
101
|
+
error: result.error
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Close the client (no-op for in-process).
|
|
106
|
+
*/
|
|
107
|
+
async close() {
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the authenticated context info.
|
|
111
|
+
*/
|
|
112
|
+
getAuthInfo() {
|
|
113
|
+
return {
|
|
114
|
+
keyId: this.auth.keyId,
|
|
115
|
+
keyName: this.auth.keyName,
|
|
116
|
+
tenantId: this.auth.tenantId,
|
|
117
|
+
organizationId: this.auth.organizationId,
|
|
118
|
+
userId: this.auth.userId,
|
|
119
|
+
isSuperAdmin: this.auth.isSuperAdmin
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
InProcessMcpClient
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=in-process-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/in-process-client.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools } from './tool-loader'\nimport { authenticateMcpRequest, hasRequiredFeatures, type McpAuthSuccess } from './auth'\nimport type { McpToolContext, McpClientInterface, ToolInfo, ToolResult, McpToolDefinition } from './types'\n\n/**\n * Options for creating an in-process MCP client.\n */\nexport type InProcessClientOptions = {\n /** API key secret for authentication */\n apiKeySecret: string\n /** DI container */\n container: AwilixContainer\n}\n\n/**\n * Options for creating an in-process MCP client with direct auth context.\n * Used when the caller already has authenticated user context (e.g., from session).\n */\nexport type AuthContextOptions = {\n /** DI container */\n container: AwilixContainer\n /** Pre-authenticated user context */\n authContext: {\n tenantId: string | null\n organizationId: string | null\n userId: string\n userFeatures: string[]\n isSuperAdmin: boolean\n }\n}\n\n/**\n * Tool info with raw Zod schema for AI SDK integration.\n */\nexport type ToolInfoWithSchema = {\n name: string\n description: string\n inputSchema: z.ZodType<unknown>\n}\n\n/**\n * In-process MCP client for direct tool execution.\n *\n * This client executes tools directly without MCP protocol overhead,\n * making it the fastest option when running in the same process as\n * the LLM service.\n *\n * Authentication is still performed via API key to ensure proper\n * ACL filtering of available tools.\n */\nexport class InProcessMcpClient implements McpClientInterface {\n private auth: McpAuthSuccess\n private container: AwilixContainer\n private toolContext: McpToolContext\n private toolsLoaded = false\n\n private constructor(auth: McpAuthSuccess, container: AwilixContainer) {\n this.auth = auth\n this.container = container\n this.toolContext = {\n tenantId: auth.tenantId,\n organizationId: auth.organizationId,\n userId: auth.userId,\n container,\n userFeatures: auth.features,\n isSuperAdmin: auth.isSuperAdmin,\n }\n }\n\n /**\n * Create and authenticate an in-process client using API key.\n */\n static async create(options: InProcessClientOptions): Promise<InProcessMcpClient> {\n const { apiKeySecret, container } = options\n\n const authResult = await authenticateMcpRequest(apiKeySecret, container)\n if (!authResult.success) {\n throw new Error(`Authentication failed: ${authResult.error}`)\n }\n\n return new InProcessMcpClient(authResult, container)\n }\n\n /**\n * Create an in-process client with pre-authenticated context.\n * Use this when you already have user auth context (e.g., from session auth).\n */\n static async createWithAuthContext(options: AuthContextOptions): Promise<InProcessMcpClient> {\n const { container, authContext } = options\n\n // Create a synthetic auth result (no API key lookup needed)\n const syntheticAuth: McpAuthSuccess = {\n success: true,\n keyId: 'session-auth',\n keyName: 'Session Authentication',\n tenantId: authContext.tenantId,\n organizationId: authContext.organizationId,\n userId: authContext.userId,\n features: authContext.userFeatures,\n isSuperAdmin: authContext.isSuperAdmin,\n }\n\n return new InProcessMcpClient(syntheticAuth, container)\n }\n\n /**\n * Ensure tools are loaded from all modules.\n */\n private async ensureToolsLoaded(): Promise<void> {\n if (!this.toolsLoaded) {\n await loadAllModuleTools()\n this.toolsLoaded = true\n }\n }\n\n /**\n * List available tools filtered by API key's permissions.\n * Returns JSON Schema format (for MCP protocol compatibility).\n */\n async listTools(): Promise<ToolInfo[]> {\n await this.ensureToolsLoaded()\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)\n )\n\n return accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\n }))\n }\n\n /**\n * List available tools with raw Zod schemas.\n * Use this for AI SDK integration which requires Zod schemas.\n */\n async listToolsWithSchemas(): Promise<ToolInfoWithSchema[]> {\n await this.ensureToolsLoaded()\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin)\n )\n\n return accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n }))\n }\n\n /**\n * Execute a tool directly.\n */\n async callTool(name: string, args: unknown): Promise<ToolResult> {\n await this.ensureToolsLoaded()\n\n const result = await executeTool(name, args ?? {}, this.toolContext)\n\n return {\n success: result.success,\n result: result.result,\n error: result.error,\n }\n }\n\n /**\n * Close the client (no-op for in-process).\n */\n async close(): Promise<void> {\n // No resources to clean up for in-process client\n }\n\n /**\n * Get the authenticated context info.\n */\n getAuthInfo(): {\n keyId: string\n keyName: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n isSuperAdmin: boolean\n } {\n return {\n keyId: this.auth.keyId,\n keyName: this.auth.keyName,\n tenantId: this.auth.tenantId,\n organizationId: this.auth.organizationId,\n userId: this.auth.userId,\n isSuperAdmin: this.auth.isSuperAdmin,\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|