@mcpjam/inspector 0.9.3 → 0.9.5
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/README.md +2 -6
- package/dist/client/assets/index-GNDQHxBB.css +1 -0
- package/dist/client/assets/index-bdptyNo6.js +1735 -0
- package/dist/client/assets/index-bdptyNo6.js.map +1 -0
- package/dist/client/deepseek_logo.svg +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +1391 -733
- package/dist/server/index.js.map +1 -1
- package/package.json +8 -6
- package/dist/client/assets/index-Bv0p7Vhi.css +0 -1
- package/dist/client/assets/index-DzF9hvwr.js +0 -1713
- package/dist/client/assets/index-DzF9hvwr.js.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,213 +1,58 @@
|
|
|
1
1
|
// index.ts
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
|
-
import { Hono as
|
|
3
|
+
import { Hono as Hono10 } from "hono";
|
|
4
4
|
import { cors } from "hono/cors";
|
|
5
5
|
import { logger } from "hono/logger";
|
|
6
6
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
7
7
|
import { readFileSync } from "fs";
|
|
8
|
-
import { join } from "path";
|
|
8
|
+
import { join as join2 } from "path";
|
|
9
9
|
|
|
10
10
|
// routes/mcp/index.ts
|
|
11
|
-
import { Hono as
|
|
11
|
+
import { Hono as Hono9 } from "hono";
|
|
12
12
|
|
|
13
13
|
// routes/mcp/connect.ts
|
|
14
14
|
import { Hono } from "hono";
|
|
15
|
-
|
|
16
|
-
// utils/mcp-utils.ts
|
|
17
|
-
import { MCPClient } from "@mastra/mcp";
|
|
18
|
-
function validateServerConfig(serverConfig) {
|
|
19
|
-
if (!serverConfig) {
|
|
20
|
-
return {
|
|
21
|
-
success: false,
|
|
22
|
-
error: {
|
|
23
|
-
message: "Server configuration is required",
|
|
24
|
-
status: 400
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
const config = { ...serverConfig };
|
|
29
|
-
if (config.url) {
|
|
30
|
-
try {
|
|
31
|
-
if (typeof config.url === "string") {
|
|
32
|
-
const parsed = new URL(config.url);
|
|
33
|
-
parsed.search = "";
|
|
34
|
-
parsed.hash = "";
|
|
35
|
-
config.url = parsed;
|
|
36
|
-
} else if (typeof config.url === "object" && !config.url.href) {
|
|
37
|
-
return {
|
|
38
|
-
success: false,
|
|
39
|
-
error: {
|
|
40
|
-
message: "Invalid URL configuration",
|
|
41
|
-
status: 400
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
if (config.oauth?.access_token) {
|
|
46
|
-
const authHeaders = {
|
|
47
|
-
Authorization: `Bearer ${config.oauth.access_token}`,
|
|
48
|
-
...config.requestInit?.headers || {}
|
|
49
|
-
};
|
|
50
|
-
config.requestInit = {
|
|
51
|
-
...config.requestInit,
|
|
52
|
-
headers: authHeaders
|
|
53
|
-
};
|
|
54
|
-
config.eventSourceInit = {
|
|
55
|
-
fetch(input, init) {
|
|
56
|
-
const headers = new Headers(init?.headers || {});
|
|
57
|
-
headers.set(
|
|
58
|
-
"Authorization",
|
|
59
|
-
`Bearer ${config.oauth.access_token}`
|
|
60
|
-
);
|
|
61
|
-
if (config.requestInit?.headers) {
|
|
62
|
-
const requestHeaders = new Headers(config.requestInit.headers);
|
|
63
|
-
requestHeaders.forEach((value, key) => {
|
|
64
|
-
if (key.toLowerCase() !== "authorization") {
|
|
65
|
-
headers.set(key, value);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
return fetch(input, {
|
|
70
|
-
...init,
|
|
71
|
-
headers
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
} else if (config.requestInit?.headers) {
|
|
76
|
-
config.eventSourceInit = {
|
|
77
|
-
fetch(input, init) {
|
|
78
|
-
const headers = new Headers(init?.headers || {});
|
|
79
|
-
const requestHeaders = new Headers(config.requestInit.headers);
|
|
80
|
-
requestHeaders.forEach((value, key) => {
|
|
81
|
-
headers.set(key, value);
|
|
82
|
-
});
|
|
83
|
-
return fetch(input, {
|
|
84
|
-
...init,
|
|
85
|
-
headers
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
return {
|
|
92
|
-
success: false,
|
|
93
|
-
error: {
|
|
94
|
-
message: `Invalid URL format: ${error}`,
|
|
95
|
-
status: 400
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
success: true,
|
|
102
|
-
config
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
function createMCPClient(config, id) {
|
|
106
|
-
return new MCPClient({
|
|
107
|
-
id,
|
|
108
|
-
servers: {
|
|
109
|
-
server: config
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
var validateMultipleServerConfigs = (serverConfigs) => {
|
|
114
|
-
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
115
|
-
return {
|
|
116
|
-
success: false,
|
|
117
|
-
error: {
|
|
118
|
-
message: "At least one server configuration is required",
|
|
119
|
-
status: 400
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const validConfigs = {};
|
|
124
|
-
const errors = {};
|
|
125
|
-
let hasErrors = false;
|
|
126
|
-
for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
|
|
127
|
-
const validationResult = validateServerConfig(serverConfig);
|
|
128
|
-
if (validationResult.success && validationResult.config) {
|
|
129
|
-
validConfigs[serverName] = validationResult.config;
|
|
130
|
-
} else {
|
|
131
|
-
hasErrors = true;
|
|
132
|
-
let errorMessage = "Configuration validation failed";
|
|
133
|
-
if (validationResult.error) {
|
|
134
|
-
errorMessage = validationResult.error.message;
|
|
135
|
-
}
|
|
136
|
-
errors[serverName] = errorMessage;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (!hasErrors) {
|
|
140
|
-
return {
|
|
141
|
-
success: true,
|
|
142
|
-
validConfigs
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
if (Object.keys(validConfigs).length > 0) {
|
|
146
|
-
return {
|
|
147
|
-
success: false,
|
|
148
|
-
validConfigs,
|
|
149
|
-
errors
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
success: false,
|
|
154
|
-
errors,
|
|
155
|
-
error: {
|
|
156
|
-
message: "All server configurations failed validation",
|
|
157
|
-
status: 400
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
|
-
function createMCPClientWithMultipleConnections(serverConfigs) {
|
|
162
|
-
const normalizedConfigs = {};
|
|
163
|
-
for (const [serverName, config] of Object.entries(serverConfigs)) {
|
|
164
|
-
const normalizedName = normalizeServerConfigName(serverName);
|
|
165
|
-
normalizedConfigs[normalizedName] = config;
|
|
166
|
-
}
|
|
167
|
-
return new MCPClient({
|
|
168
|
-
id: `chat-${Date.now()}`,
|
|
169
|
-
servers: normalizedConfigs
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
function normalizeServerConfigName(serverName) {
|
|
173
|
-
return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// routes/mcp/connect.ts
|
|
177
15
|
var connect = new Hono();
|
|
178
16
|
connect.post("/", async (c) => {
|
|
179
17
|
try {
|
|
180
|
-
const { serverConfig } = await c.req.json();
|
|
181
|
-
|
|
182
|
-
if (!validation.success) {
|
|
183
|
-
const error = validation.error;
|
|
18
|
+
const { serverConfig, serverId } = await c.req.json();
|
|
19
|
+
if (!serverConfig) {
|
|
184
20
|
return c.json(
|
|
185
21
|
{
|
|
186
22
|
success: false,
|
|
187
|
-
error:
|
|
23
|
+
error: "serverConfig is required"
|
|
188
24
|
},
|
|
189
|
-
|
|
25
|
+
400
|
|
190
26
|
);
|
|
191
27
|
}
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
client = createMCPClient(validation.config, `test-${Date.now()}`);
|
|
195
|
-
} catch (error) {
|
|
28
|
+
if (!serverId) {
|
|
196
29
|
return c.json(
|
|
197
30
|
{
|
|
198
31
|
success: false,
|
|
199
|
-
error:
|
|
200
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
32
|
+
error: "serverId is required"
|
|
201
33
|
},
|
|
202
|
-
|
|
34
|
+
400
|
|
203
35
|
);
|
|
204
36
|
}
|
|
37
|
+
const mcpClientManager = c.mcpJamClientManager;
|
|
205
38
|
try {
|
|
206
|
-
await
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
39
|
+
await mcpClientManager.connectToServer(serverId, serverConfig);
|
|
40
|
+
const status = mcpClientManager.getConnectionStatus(serverId);
|
|
41
|
+
if (status === "connected") {
|
|
42
|
+
return c.json({
|
|
43
|
+
success: true,
|
|
44
|
+
status: "connected"
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
return c.json(
|
|
48
|
+
{
|
|
49
|
+
success: false,
|
|
50
|
+
error: "Connection failed",
|
|
51
|
+
status
|
|
52
|
+
},
|
|
53
|
+
500
|
|
54
|
+
);
|
|
55
|
+
}
|
|
211
56
|
} catch (error) {
|
|
212
57
|
return c.json(
|
|
213
58
|
{
|
|
@@ -231,16 +76,119 @@ connect.post("/", async (c) => {
|
|
|
231
76
|
});
|
|
232
77
|
var connect_default = connect;
|
|
233
78
|
|
|
234
|
-
// routes/mcp/
|
|
79
|
+
// routes/mcp/servers.ts
|
|
235
80
|
import { Hono as Hono2 } from "hono";
|
|
81
|
+
var servers = new Hono2();
|
|
82
|
+
servers.get("/", async (c) => {
|
|
83
|
+
try {
|
|
84
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
85
|
+
const connectedServers = mcpJamClientManager2.getConnectedServers();
|
|
86
|
+
const serverList = Object.entries(connectedServers).map(
|
|
87
|
+
([serverId, serverInfo]) => ({
|
|
88
|
+
id: serverId,
|
|
89
|
+
name: serverId,
|
|
90
|
+
status: serverInfo.status,
|
|
91
|
+
config: serverInfo.config
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
return c.json({
|
|
95
|
+
success: true,
|
|
96
|
+
servers: serverList
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("Error listing servers:", error);
|
|
100
|
+
return c.json(
|
|
101
|
+
{
|
|
102
|
+
success: false,
|
|
103
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
104
|
+
},
|
|
105
|
+
500
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
servers.get("/status/:serverId", async (c) => {
|
|
110
|
+
try {
|
|
111
|
+
const serverId = c.req.param("serverId");
|
|
112
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
113
|
+
const status = mcpJamClientManager2.getConnectionStatus(serverId);
|
|
114
|
+
return c.json({
|
|
115
|
+
success: true,
|
|
116
|
+
serverId,
|
|
117
|
+
status
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error("Error getting server status:", error);
|
|
121
|
+
return c.json(
|
|
122
|
+
{
|
|
123
|
+
success: false,
|
|
124
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
125
|
+
},
|
|
126
|
+
500
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
servers.delete("/:serverId", async (c) => {
|
|
131
|
+
try {
|
|
132
|
+
const serverId = c.req.param("serverId");
|
|
133
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
134
|
+
await mcpJamClientManager2.disconnectFromServer(serverId);
|
|
135
|
+
return c.json({
|
|
136
|
+
success: true,
|
|
137
|
+
message: `Disconnected from server: ${serverId}`
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("Error disconnecting server:", error);
|
|
141
|
+
return c.json(
|
|
142
|
+
{
|
|
143
|
+
success: false,
|
|
144
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
145
|
+
},
|
|
146
|
+
500
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
servers.post("/reconnect", async (c) => {
|
|
151
|
+
try {
|
|
152
|
+
const { serverId, serverConfig } = await c.req.json();
|
|
153
|
+
if (!serverId || !serverConfig) {
|
|
154
|
+
return c.json(
|
|
155
|
+
{
|
|
156
|
+
success: false,
|
|
157
|
+
error: "serverId and serverConfig are required"
|
|
158
|
+
},
|
|
159
|
+
400
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
163
|
+
await mcpJamClientManager2.disconnectFromServer(serverId);
|
|
164
|
+
await mcpJamClientManager2.connectToServer(serverId, serverConfig);
|
|
165
|
+
const status = mcpJamClientManager2.getConnectionStatus(serverId);
|
|
166
|
+
return c.json({
|
|
167
|
+
success: true,
|
|
168
|
+
serverId,
|
|
169
|
+
status,
|
|
170
|
+
message: `Reconnected to server: ${serverId}`
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("Error reconnecting server:", error);
|
|
174
|
+
return c.json(
|
|
175
|
+
{
|
|
176
|
+
success: false,
|
|
177
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
178
|
+
},
|
|
179
|
+
500
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
var servers_default = servers;
|
|
184
|
+
|
|
185
|
+
// routes/mcp/tools.ts
|
|
186
|
+
import { Hono as Hono3 } from "hono";
|
|
236
187
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
237
|
-
import { TextEncoder } from "util";
|
|
238
|
-
var tools = new
|
|
188
|
+
import { TextEncoder as TextEncoder2 } from "util";
|
|
189
|
+
var tools = new Hono3();
|
|
239
190
|
var pendingElicitations = /* @__PURE__ */ new Map();
|
|
240
191
|
tools.post("/", async (c) => {
|
|
241
|
-
let client = null;
|
|
242
|
-
let encoder = null;
|
|
243
|
-
let streamController = null;
|
|
244
192
|
let action;
|
|
245
193
|
let toolName;
|
|
246
194
|
try {
|
|
@@ -267,8 +215,18 @@ tools.post("/", async (c) => {
|
|
|
267
215
|
400
|
|
268
216
|
);
|
|
269
217
|
}
|
|
270
|
-
const
|
|
271
|
-
|
|
218
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
219
|
+
const success = mcpJamClientManager2.respondToElicitation(
|
|
220
|
+
requestId,
|
|
221
|
+
response
|
|
222
|
+
);
|
|
223
|
+
if (!success) {
|
|
224
|
+
const pending = pendingElicitations.get(requestId);
|
|
225
|
+
if (pending) {
|
|
226
|
+
pending.resolve(response);
|
|
227
|
+
pendingElicitations.delete(requestId);
|
|
228
|
+
return c.json({ success: true });
|
|
229
|
+
}
|
|
272
230
|
return c.json(
|
|
273
231
|
{
|
|
274
232
|
success: false,
|
|
@@ -277,177 +235,171 @@ tools.post("/", async (c) => {
|
|
|
277
235
|
404
|
|
278
236
|
);
|
|
279
237
|
}
|
|
280
|
-
pending.resolve(response);
|
|
281
|
-
pendingElicitations.delete(requestId);
|
|
282
238
|
return c.json({ success: true });
|
|
283
239
|
}
|
|
284
|
-
const
|
|
285
|
-
if (!validation.success) {
|
|
286
|
-
return c.json(
|
|
287
|
-
{ success: false, error: validation.error.message },
|
|
288
|
-
validation.error.status
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
encoder = new TextEncoder();
|
|
240
|
+
const encoder = new TextEncoder2();
|
|
292
241
|
const readableStream = new ReadableStream({
|
|
293
242
|
async start(controller) {
|
|
294
|
-
streamController = controller;
|
|
295
243
|
try {
|
|
296
|
-
|
|
297
|
-
client = createMCPClient(validation.config, clientId);
|
|
298
|
-
if (action === "list") {
|
|
244
|
+
if (!serverConfig) {
|
|
299
245
|
controller.enqueue(
|
|
300
246
|
encoder.encode(
|
|
301
|
-
`data: ${JSON.stringify({
|
|
302
|
-
type: "tools_loading",
|
|
303
|
-
message: "Fetching tools from server..."
|
|
304
|
-
})}
|
|
247
|
+
`data: ${JSON.stringify({ type: "tool_error", error: "serverConfig is required" })}
|
|
305
248
|
|
|
306
249
|
`
|
|
307
250
|
)
|
|
308
251
|
);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
})
|
|
322
|
-
);
|
|
323
|
-
controller.enqueue(
|
|
252
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
253
|
+
|
|
254
|
+
`));
|
|
255
|
+
controller.close();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
259
|
+
const serverId = serverConfig.name || serverConfig.id || "server";
|
|
260
|
+
await mcpJamClientManager2.connectToServer(serverId, serverConfig);
|
|
261
|
+
mcpJamClientManager2.setElicitationCallback(async (request) => {
|
|
262
|
+
const { requestId: requestId2, message, schema } = request;
|
|
263
|
+
controller.enqueue(
|
|
324
264
|
encoder.encode(
|
|
325
265
|
`data: ${JSON.stringify({
|
|
326
|
-
type: "
|
|
327
|
-
|
|
266
|
+
type: "elicitation_request",
|
|
267
|
+
requestId: requestId2,
|
|
268
|
+
message,
|
|
269
|
+
schema,
|
|
270
|
+
toolName: toolName || "unknown",
|
|
271
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
328
272
|
})}
|
|
329
273
|
|
|
330
274
|
`
|
|
331
275
|
)
|
|
332
276
|
);
|
|
333
|
-
|
|
334
|
-
|
|
277
|
+
return new Promise((resolve, reject) => {
|
|
278
|
+
pendingElicitations.set(requestId2, {
|
|
279
|
+
resolve: (response2) => {
|
|
280
|
+
resolve(response2);
|
|
281
|
+
pendingElicitations.delete(requestId2);
|
|
282
|
+
},
|
|
283
|
+
reject: (error) => {
|
|
284
|
+
reject(error);
|
|
285
|
+
pendingElicitations.delete(requestId2);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
if (pendingElicitations.has(requestId2)) {
|
|
290
|
+
pendingElicitations.delete(requestId2);
|
|
291
|
+
reject(new Error("Elicitation timeout"));
|
|
292
|
+
}
|
|
293
|
+
}, 3e5);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
if (action === "list") {
|
|
297
|
+
try {
|
|
298
|
+
const flattenedTools = await mcpJamClientManager2.getToolsetsForServer(serverId);
|
|
299
|
+
const toolsWithJsonSchema = {};
|
|
300
|
+
for (const [name, tool] of Object.entries(flattenedTools)) {
|
|
301
|
+
let inputSchema = tool.inputSchema;
|
|
302
|
+
try {
|
|
303
|
+
inputSchema = zodToJsonSchema(inputSchema);
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
toolsWithJsonSchema[name] = {
|
|
307
|
+
name,
|
|
308
|
+
description: tool.description,
|
|
309
|
+
inputSchema,
|
|
310
|
+
outputSchema: tool.outputSchema
|
|
311
|
+
};
|
|
312
|
+
}
|
|
335
313
|
controller.enqueue(
|
|
336
314
|
encoder.encode(
|
|
337
|
-
`data: ${JSON.stringify({
|
|
338
|
-
type: "tool_error",
|
|
339
|
-
error: "Tool name is required for execution"
|
|
340
|
-
})}
|
|
315
|
+
`data: ${JSON.stringify({ type: "tools_list", tools: toolsWithJsonSchema })}
|
|
341
316
|
|
|
342
317
|
`
|
|
343
318
|
)
|
|
344
319
|
);
|
|
320
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
321
|
+
|
|
322
|
+
`));
|
|
323
|
+
controller.close();
|
|
345
324
|
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
type: "tool_executing",
|
|
351
|
-
toolName,
|
|
352
|
-
parameters: parameters || {},
|
|
353
|
-
message: "Executing tool..."
|
|
354
|
-
})}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
controller.enqueue(
|
|
327
|
+
encoder.encode(
|
|
328
|
+
`data: ${JSON.stringify({ type: "tool_error", error: err instanceof Error ? err.message : String(err) })}
|
|
355
329
|
|
|
356
330
|
`
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
334
|
+
|
|
335
|
+
`));
|
|
336
|
+
controller.close();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (action === "execute") {
|
|
341
|
+
if (!toolName) {
|
|
362
342
|
controller.enqueue(
|
|
363
343
|
encoder.encode(
|
|
364
|
-
`data: ${JSON.stringify({
|
|
365
|
-
type: "tool_error",
|
|
366
|
-
error: `Tool '${toolName}' not found`
|
|
367
|
-
})}
|
|
344
|
+
`data: ${JSON.stringify({ type: "tool_error", error: "Tool name is required for execution" })}
|
|
368
345
|
|
|
369
346
|
`
|
|
370
347
|
)
|
|
371
348
|
);
|
|
349
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
350
|
+
|
|
351
|
+
`));
|
|
352
|
+
controller.close();
|
|
372
353
|
return;
|
|
373
354
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (streamController && encoder) {
|
|
378
|
-
streamController.enqueue(
|
|
379
|
-
encoder.encode(
|
|
380
|
-
`data: ${JSON.stringify({
|
|
381
|
-
type: "elicitation_request",
|
|
382
|
-
requestId: requestId2,
|
|
383
|
-
message: elicitationRequest.message,
|
|
384
|
-
schema: elicitationRequest.requestedSchema,
|
|
385
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
386
|
-
})}
|
|
355
|
+
controller.enqueue(
|
|
356
|
+
encoder.encode(
|
|
357
|
+
`data: ${JSON.stringify({ type: "tool_executing", toolName, parameters: parameters || {}, message: "Executing tool..." })}
|
|
387
358
|
|
|
388
359
|
`
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (pendingElicitations.has(requestId2)) {
|
|
396
|
-
pendingElicitations.delete(requestId2);
|
|
397
|
-
reject(new Error("Elicitation timeout"));
|
|
398
|
-
}
|
|
399
|
-
}, 3e5);
|
|
400
|
-
});
|
|
401
|
-
};
|
|
402
|
-
if (client.elicitation && client.elicitation.onRequest) {
|
|
403
|
-
const serverName = "server";
|
|
404
|
-
client.elicitation.onRequest(serverName, elicitationHandler);
|
|
405
|
-
}
|
|
406
|
-
const result = await tool.execute({
|
|
407
|
-
context: toolArgs
|
|
408
|
-
});
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
const exec = await mcpJamClientManager2.executeToolDirect(
|
|
363
|
+
toolName,
|
|
364
|
+
parameters || {}
|
|
365
|
+
);
|
|
409
366
|
controller.enqueue(
|
|
410
367
|
encoder.encode(
|
|
411
|
-
`data: ${JSON.stringify({
|
|
412
|
-
type: "tool_result",
|
|
413
|
-
toolName,
|
|
414
|
-
result
|
|
415
|
-
})}
|
|
368
|
+
`data: ${JSON.stringify({ type: "tool_result", toolName, result: exec.result })}
|
|
416
369
|
|
|
417
370
|
`
|
|
418
371
|
)
|
|
419
372
|
);
|
|
420
373
|
controller.enqueue(
|
|
421
374
|
encoder.encode(
|
|
422
|
-
`data: ${JSON.stringify({
|
|
423
|
-
type: "elicitation_complete",
|
|
424
|
-
toolName
|
|
425
|
-
})}
|
|
375
|
+
`data: ${JSON.stringify({ type: "elicitation_complete", toolName })}
|
|
426
376
|
|
|
427
377
|
`
|
|
428
378
|
)
|
|
429
379
|
);
|
|
430
|
-
|
|
431
|
-
controller.enqueue(encoder.encode(`data: [DONE]
|
|
380
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
432
381
|
|
|
433
382
|
`));
|
|
434
|
-
|
|
435
|
-
|
|
383
|
+
controller.close();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
436
387
|
controller.enqueue(
|
|
437
388
|
encoder.encode(
|
|
438
|
-
`data: ${JSON.stringify({
|
|
439
|
-
type: "tool_error",
|
|
440
|
-
error: errorMsg
|
|
441
|
-
})}
|
|
389
|
+
`data: ${JSON.stringify({ type: "tool_error", error: err instanceof Error ? err.message : String(err) })}
|
|
442
390
|
|
|
443
391
|
`
|
|
444
392
|
)
|
|
445
393
|
);
|
|
394
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
395
|
+
|
|
396
|
+
`));
|
|
397
|
+
controller.close();
|
|
446
398
|
} finally {
|
|
447
|
-
|
|
448
|
-
|
|
399
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
400
|
+
if (mcpJamClientManager2) {
|
|
401
|
+
mcpJamClientManager2.clearElicitationCallback();
|
|
449
402
|
}
|
|
450
|
-
controller.close();
|
|
451
403
|
}
|
|
452
404
|
}
|
|
453
405
|
});
|
|
@@ -460,12 +412,6 @@ tools.post("/", async (c) => {
|
|
|
460
412
|
});
|
|
461
413
|
} catch (error) {
|
|
462
414
|
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
463
|
-
if (client) {
|
|
464
|
-
try {
|
|
465
|
-
await client.disconnect();
|
|
466
|
-
} catch (cleanupError) {
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
415
|
return c.json(
|
|
470
416
|
{
|
|
471
417
|
success: false,
|
|
@@ -478,30 +424,17 @@ tools.post("/", async (c) => {
|
|
|
478
424
|
var tools_default = tools;
|
|
479
425
|
|
|
480
426
|
// routes/mcp/resources.ts
|
|
481
|
-
import { Hono as
|
|
482
|
-
var resources = new
|
|
427
|
+
import { Hono as Hono4 } from "hono";
|
|
428
|
+
var resources = new Hono4();
|
|
483
429
|
resources.post("/list", async (c) => {
|
|
484
430
|
try {
|
|
485
|
-
const {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
return c.json(
|
|
489
|
-
{ success: false, error: validation.error.message },
|
|
490
|
-
validation.error.status
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
const client = createMCPClient(
|
|
494
|
-
validation.config,
|
|
495
|
-
`resources-list-${Date.now()}`
|
|
496
|
-
);
|
|
497
|
-
try {
|
|
498
|
-
const resources2 = await client.resources.list();
|
|
499
|
-
await client.disconnect();
|
|
500
|
-
return c.json({ resources: resources2 });
|
|
501
|
-
} catch (error) {
|
|
502
|
-
await client.disconnect();
|
|
503
|
-
throw error;
|
|
431
|
+
const { serverId } = await c.req.json();
|
|
432
|
+
if (!serverId) {
|
|
433
|
+
return c.json({ success: false, error: "serverId is required" }, 400);
|
|
504
434
|
}
|
|
435
|
+
const mcpClientManager = c.mcpJamClientManager;
|
|
436
|
+
const serverResources = mcpClientManager.getResourcesForServer(serverId);
|
|
437
|
+
return c.json({ resources: { [serverId]: serverResources } });
|
|
505
438
|
} catch (error) {
|
|
506
439
|
console.error("Error fetching resources:", error);
|
|
507
440
|
return c.json(
|
|
@@ -515,13 +448,9 @@ resources.post("/list", async (c) => {
|
|
|
515
448
|
});
|
|
516
449
|
resources.post("/read", async (c) => {
|
|
517
450
|
try {
|
|
518
|
-
const {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
return c.json(
|
|
522
|
-
{ success: false, error: validation.error.message },
|
|
523
|
-
validation.error.status
|
|
524
|
-
);
|
|
451
|
+
const { serverId, uri } = await c.req.json();
|
|
452
|
+
if (!serverId) {
|
|
453
|
+
return c.json({ success: false, error: "serverId is required" }, 400);
|
|
525
454
|
}
|
|
526
455
|
if (!uri) {
|
|
527
456
|
return c.json(
|
|
@@ -532,18 +461,9 @@ resources.post("/read", async (c) => {
|
|
|
532
461
|
400
|
|
533
462
|
);
|
|
534
463
|
}
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
);
|
|
539
|
-
try {
|
|
540
|
-
const content = await client.resources.read("server", uri);
|
|
541
|
-
await client.disconnect();
|
|
542
|
-
return c.json({ content });
|
|
543
|
-
} catch (error) {
|
|
544
|
-
await client.disconnect();
|
|
545
|
-
throw error;
|
|
546
|
-
}
|
|
464
|
+
const mcpClientManager = c.mcpJamClientManager;
|
|
465
|
+
const content = await mcpClientManager.getResource(uri, serverId);
|
|
466
|
+
return c.json({ content });
|
|
547
467
|
} catch (error) {
|
|
548
468
|
console.error("Error reading resource:", error);
|
|
549
469
|
return c.json(
|
|
@@ -558,30 +478,17 @@ resources.post("/read", async (c) => {
|
|
|
558
478
|
var resources_default = resources;
|
|
559
479
|
|
|
560
480
|
// routes/mcp/prompts.ts
|
|
561
|
-
import { Hono as
|
|
562
|
-
var prompts = new
|
|
481
|
+
import { Hono as Hono5 } from "hono";
|
|
482
|
+
var prompts = new Hono5();
|
|
563
483
|
prompts.post("/list", async (c) => {
|
|
564
484
|
try {
|
|
565
|
-
const {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return c.json(
|
|
569
|
-
{ success: false, error: validation.error.message },
|
|
570
|
-
validation.error.status
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
const client = createMCPClient(
|
|
574
|
-
validation.config,
|
|
575
|
-
`prompts-list-${Date.now()}`
|
|
576
|
-
);
|
|
577
|
-
try {
|
|
578
|
-
const prompts2 = await client.prompts.list();
|
|
579
|
-
await client.disconnect();
|
|
580
|
-
return c.json({ prompts: prompts2 });
|
|
581
|
-
} catch (error) {
|
|
582
|
-
await client.disconnect();
|
|
583
|
-
throw error;
|
|
485
|
+
const { serverId } = await c.req.json();
|
|
486
|
+
if (!serverId) {
|
|
487
|
+
return c.json({ success: false, error: "serverId is required" }, 400);
|
|
584
488
|
}
|
|
489
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
490
|
+
const serverPrompts = mcpJamClientManager2.getPromptsForServer(serverId);
|
|
491
|
+
return c.json({ prompts: { [serverId]: serverPrompts } });
|
|
585
492
|
} catch (error) {
|
|
586
493
|
console.error("Error fetching prompts:", error);
|
|
587
494
|
return c.json(
|
|
@@ -595,13 +502,9 @@ prompts.post("/list", async (c) => {
|
|
|
595
502
|
});
|
|
596
503
|
prompts.post("/get", async (c) => {
|
|
597
504
|
try {
|
|
598
|
-
const {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
return c.json(
|
|
602
|
-
{ success: false, error: validation.error.message },
|
|
603
|
-
validation.error.status
|
|
604
|
-
);
|
|
505
|
+
const { serverId, name, args } = await c.req.json();
|
|
506
|
+
if (!serverId) {
|
|
507
|
+
return c.json({ success: false, error: "serverId is required" }, 400);
|
|
605
508
|
}
|
|
606
509
|
if (!name) {
|
|
607
510
|
return c.json(
|
|
@@ -612,22 +515,13 @@ prompts.post("/get", async (c) => {
|
|
|
612
515
|
400
|
|
613
516
|
);
|
|
614
517
|
}
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
518
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
519
|
+
const content = await mcpJamClientManager2.getPrompt(
|
|
520
|
+
name,
|
|
521
|
+
serverId,
|
|
522
|
+
args || {}
|
|
618
523
|
);
|
|
619
|
-
|
|
620
|
-
const content = await client.prompts.get({
|
|
621
|
-
serverName: "server",
|
|
622
|
-
name,
|
|
623
|
-
args: args || {}
|
|
624
|
-
});
|
|
625
|
-
await client.disconnect();
|
|
626
|
-
return c.json({ content });
|
|
627
|
-
} catch (error) {
|
|
628
|
-
await client.disconnect();
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
524
|
+
return c.json({ content });
|
|
631
525
|
} catch (error) {
|
|
632
526
|
console.error("Error getting prompt:", error);
|
|
633
527
|
return c.json(
|
|
@@ -642,15 +536,196 @@ prompts.post("/get", async (c) => {
|
|
|
642
536
|
var prompts_default = prompts;
|
|
643
537
|
|
|
644
538
|
// routes/mcp/chat.ts
|
|
645
|
-
import { Hono as
|
|
539
|
+
import { Hono as Hono6 } from "hono";
|
|
540
|
+
|
|
541
|
+
// utils/mcp-utils.ts
|
|
542
|
+
import { MCPClient } from "@mastra/mcp";
|
|
543
|
+
function validateServerConfig(serverConfig) {
|
|
544
|
+
if (!serverConfig) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
error: {
|
|
548
|
+
message: "Server configuration is required",
|
|
549
|
+
status: 400
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const config = { ...serverConfig };
|
|
554
|
+
if (config.url) {
|
|
555
|
+
try {
|
|
556
|
+
if (typeof config.url === "string") {
|
|
557
|
+
const parsed = new URL(config.url);
|
|
558
|
+
parsed.search = "";
|
|
559
|
+
parsed.hash = "";
|
|
560
|
+
config.url = parsed;
|
|
561
|
+
} else if (typeof config.url === "object" && !config.url.href) {
|
|
562
|
+
return {
|
|
563
|
+
success: false,
|
|
564
|
+
error: {
|
|
565
|
+
message: "Invalid URL configuration",
|
|
566
|
+
status: 400
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (config.oauth?.access_token) {
|
|
571
|
+
const authHeaders = {
|
|
572
|
+
Authorization: `Bearer ${config.oauth.access_token}`,
|
|
573
|
+
...config.requestInit?.headers || {}
|
|
574
|
+
};
|
|
575
|
+
config.requestInit = {
|
|
576
|
+
...config.requestInit,
|
|
577
|
+
headers: authHeaders
|
|
578
|
+
};
|
|
579
|
+
config.eventSourceInit = {
|
|
580
|
+
fetch(input, init) {
|
|
581
|
+
const headers = new Headers(init?.headers || {});
|
|
582
|
+
headers.set(
|
|
583
|
+
"Authorization",
|
|
584
|
+
`Bearer ${config.oauth.access_token}`
|
|
585
|
+
);
|
|
586
|
+
if (config.requestInit?.headers) {
|
|
587
|
+
const requestHeaders = new Headers(config.requestInit.headers);
|
|
588
|
+
requestHeaders.forEach((value, key) => {
|
|
589
|
+
if (key.toLowerCase() !== "authorization") {
|
|
590
|
+
headers.set(key, value);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return fetch(input, {
|
|
595
|
+
...init,
|
|
596
|
+
headers
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
} else if (config.requestInit?.headers) {
|
|
601
|
+
config.eventSourceInit = {
|
|
602
|
+
fetch(input, init) {
|
|
603
|
+
const headers = new Headers(init?.headers || {});
|
|
604
|
+
const requestHeaders = new Headers(config.requestInit.headers);
|
|
605
|
+
requestHeaders.forEach((value, key) => {
|
|
606
|
+
headers.set(key, value);
|
|
607
|
+
});
|
|
608
|
+
return fetch(input, {
|
|
609
|
+
...init,
|
|
610
|
+
headers
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
} catch (error) {
|
|
616
|
+
return {
|
|
617
|
+
success: false,
|
|
618
|
+
error: {
|
|
619
|
+
message: `Invalid URL format: ${error}`,
|
|
620
|
+
status: 400
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
success: true,
|
|
627
|
+
config
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
var validateMultipleServerConfigs = (serverConfigs) => {
|
|
631
|
+
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
632
|
+
return {
|
|
633
|
+
success: false,
|
|
634
|
+
error: {
|
|
635
|
+
message: "At least one server configuration is required",
|
|
636
|
+
status: 400
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const validConfigs = {};
|
|
641
|
+
const errors = {};
|
|
642
|
+
let hasErrors = false;
|
|
643
|
+
for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
|
|
644
|
+
const validationResult = validateServerConfig(serverConfig);
|
|
645
|
+
if (validationResult.success && validationResult.config) {
|
|
646
|
+
validConfigs[serverName] = validationResult.config;
|
|
647
|
+
} else {
|
|
648
|
+
hasErrors = true;
|
|
649
|
+
let errorMessage = "Configuration validation failed";
|
|
650
|
+
if (validationResult.error) {
|
|
651
|
+
errorMessage = validationResult.error.message;
|
|
652
|
+
}
|
|
653
|
+
errors[serverName] = errorMessage;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!hasErrors) {
|
|
657
|
+
return {
|
|
658
|
+
success: true,
|
|
659
|
+
validConfigs
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (Object.keys(validConfigs).length > 0) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
validConfigs,
|
|
666
|
+
errors
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
success: false,
|
|
671
|
+
errors,
|
|
672
|
+
error: {
|
|
673
|
+
message: "All server configurations failed validation",
|
|
674
|
+
status: 400
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
function createMCPClientWithMultipleConnections(serverConfigs) {
|
|
679
|
+
const originalMCPClient = new MCPClient({
|
|
680
|
+
id: `chat-${Date.now()}`,
|
|
681
|
+
servers: serverConfigs
|
|
682
|
+
});
|
|
683
|
+
const originalGetTools = originalMCPClient.getTools.bind(originalMCPClient);
|
|
684
|
+
originalMCPClient.getTools = async () => {
|
|
685
|
+
const tools2 = await originalGetTools();
|
|
686
|
+
const fixedTools = {};
|
|
687
|
+
for (const [toolName, toolConfig] of Object.entries(tools2)) {
|
|
688
|
+
const parts = toolName.split("_");
|
|
689
|
+
if (parts.length >= 3 && parts[0] === parts[1]) {
|
|
690
|
+
const fixedName = parts.slice(1).join("_");
|
|
691
|
+
fixedTools[fixedName] = toolConfig;
|
|
692
|
+
} else {
|
|
693
|
+
fixedTools[toolName] = toolConfig;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return fixedTools;
|
|
697
|
+
};
|
|
698
|
+
return originalMCPClient;
|
|
699
|
+
}
|
|
700
|
+
function normalizeServerConfigName(serverName) {
|
|
701
|
+
return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// routes/mcp/chat.ts
|
|
646
705
|
import { Agent } from "@mastra/core/agent";
|
|
647
706
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
648
707
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
649
708
|
import { createOllama } from "ollama-ai-provider";
|
|
650
|
-
import {
|
|
651
|
-
|
|
652
|
-
|
|
709
|
+
import { TextEncoder as TextEncoder3 } from "util";
|
|
710
|
+
|
|
711
|
+
// ../client/src/lib/chat-utils.ts
|
|
712
|
+
import { clsx } from "clsx";
|
|
713
|
+
import { twMerge } from "tailwind-merge";
|
|
714
|
+
function getDefaultTemperatureByProvider(provider) {
|
|
715
|
+
switch (provider) {
|
|
716
|
+
case "openai":
|
|
717
|
+
return 1;
|
|
718
|
+
case "anthropic":
|
|
719
|
+
return 0;
|
|
720
|
+
default:
|
|
721
|
+
return 0;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// routes/mcp/chat.ts
|
|
653
726
|
var DEBUG_ENABLED = process.env.MCP_DEBUG !== "false";
|
|
727
|
+
var ELICITATION_TIMEOUT = 3e5;
|
|
728
|
+
var MAX_AGENT_STEPS = 10;
|
|
654
729
|
var dbg = (...args) => {
|
|
655
730
|
if (DEBUG_ENABLED) console.log("[mcp/chat]", ...args);
|
|
656
731
|
};
|
|
@@ -659,6 +734,310 @@ try {
|
|
|
659
734
|
} catch {
|
|
660
735
|
}
|
|
661
736
|
var pendingElicitations2 = /* @__PURE__ */ new Map();
|
|
737
|
+
var chat = new Hono6();
|
|
738
|
+
var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
739
|
+
if (!modelDefinition?.id || !modelDefinition?.provider) {
|
|
740
|
+
throw new Error(
|
|
741
|
+
`Invalid model definition: ${JSON.stringify(modelDefinition)}`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
switch (modelDefinition.provider) {
|
|
745
|
+
case "anthropic":
|
|
746
|
+
return createAnthropic({ apiKey })(modelDefinition.id);
|
|
747
|
+
case "openai":
|
|
748
|
+
return createOpenAI({ apiKey })(modelDefinition.id);
|
|
749
|
+
case "deepseek":
|
|
750
|
+
return createOpenAI({ apiKey, baseURL: "https://api.deepseek.com/v1" })(
|
|
751
|
+
modelDefinition.id
|
|
752
|
+
);
|
|
753
|
+
case "ollama":
|
|
754
|
+
const baseUrl = ollamaBaseUrl || "http://localhost:11434";
|
|
755
|
+
return createOllama({
|
|
756
|
+
baseURL: `${baseUrl}`
|
|
757
|
+
})(modelDefinition.id, {
|
|
758
|
+
simulateStreaming: true
|
|
759
|
+
});
|
|
760
|
+
default:
|
|
761
|
+
throw new Error(
|
|
762
|
+
`Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
var createElicitationHandler = (streamingContext) => {
|
|
767
|
+
return async (elicitationRequest) => {
|
|
768
|
+
const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
769
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
770
|
+
streamingContext.controller.enqueue(
|
|
771
|
+
streamingContext.encoder.encode(
|
|
772
|
+
`data: ${JSON.stringify({
|
|
773
|
+
type: "elicitation_request",
|
|
774
|
+
requestId,
|
|
775
|
+
message: elicitationRequest.message,
|
|
776
|
+
schema: elicitationRequest.requestedSchema,
|
|
777
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
778
|
+
})}
|
|
779
|
+
|
|
780
|
+
`
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return new Promise((resolve, reject) => {
|
|
785
|
+
pendingElicitations2.set(requestId, { resolve, reject });
|
|
786
|
+
setTimeout(() => {
|
|
787
|
+
if (pendingElicitations2.has(requestId)) {
|
|
788
|
+
pendingElicitations2.delete(requestId);
|
|
789
|
+
reject(new Error("Elicitation timeout"));
|
|
790
|
+
}
|
|
791
|
+
}, ELICITATION_TIMEOUT);
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
};
|
|
795
|
+
var wrapToolsWithStreaming = (tools2, streamingContext) => {
|
|
796
|
+
const wrappedTools = {};
|
|
797
|
+
for (const [name, tool] of Object.entries(tools2)) {
|
|
798
|
+
wrappedTools[name] = {
|
|
799
|
+
...tool,
|
|
800
|
+
execute: async (params) => {
|
|
801
|
+
const currentToolCallId = ++streamingContext.toolCallId;
|
|
802
|
+
const startedAt = Date.now();
|
|
803
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
804
|
+
streamingContext.controller.enqueue(
|
|
805
|
+
streamingContext.encoder.encode(
|
|
806
|
+
`data: ${JSON.stringify({
|
|
807
|
+
type: "tool_call",
|
|
808
|
+
toolCall: {
|
|
809
|
+
id: currentToolCallId,
|
|
810
|
+
name,
|
|
811
|
+
parameters: params,
|
|
812
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
813
|
+
status: "executing"
|
|
814
|
+
}
|
|
815
|
+
})}
|
|
816
|
+
|
|
817
|
+
`
|
|
818
|
+
)
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
dbg("Tool executing", { name, currentToolCallId, params });
|
|
822
|
+
try {
|
|
823
|
+
const result = await tool.execute(params);
|
|
824
|
+
dbg("Tool result", {
|
|
825
|
+
name,
|
|
826
|
+
currentToolCallId,
|
|
827
|
+
ms: Date.now() - startedAt
|
|
828
|
+
});
|
|
829
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
830
|
+
streamingContext.controller.enqueue(
|
|
831
|
+
streamingContext.encoder.encode(
|
|
832
|
+
`data: ${JSON.stringify({
|
|
833
|
+
type: "tool_result",
|
|
834
|
+
toolResult: {
|
|
835
|
+
id: currentToolCallId,
|
|
836
|
+
toolCallId: currentToolCallId,
|
|
837
|
+
result,
|
|
838
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
839
|
+
}
|
|
840
|
+
})}
|
|
841
|
+
|
|
842
|
+
`
|
|
843
|
+
)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return result;
|
|
847
|
+
} catch (error) {
|
|
848
|
+
dbg("Tool error", {
|
|
849
|
+
name,
|
|
850
|
+
currentToolCallId,
|
|
851
|
+
error: error instanceof Error ? error.message : String(error)
|
|
852
|
+
});
|
|
853
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
854
|
+
streamingContext.controller.enqueue(
|
|
855
|
+
streamingContext.encoder.encode(
|
|
856
|
+
`data: ${JSON.stringify({
|
|
857
|
+
type: "tool_result",
|
|
858
|
+
toolResult: {
|
|
859
|
+
id: currentToolCallId,
|
|
860
|
+
toolCallId: currentToolCallId,
|
|
861
|
+
error: error instanceof Error ? error.message : String(error),
|
|
862
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
863
|
+
}
|
|
864
|
+
})}
|
|
865
|
+
|
|
866
|
+
`
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
return wrappedTools;
|
|
876
|
+
};
|
|
877
|
+
var handleAgentStepFinish = (streamingContext, text, toolCalls, toolResults) => {
|
|
878
|
+
try {
|
|
879
|
+
if (toolCalls && Array.isArray(toolCalls)) {
|
|
880
|
+
for (const call of toolCalls) {
|
|
881
|
+
const currentToolCallId = ++streamingContext.toolCallId;
|
|
882
|
+
streamingContext.lastEmittedToolCallId = currentToolCallId;
|
|
883
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
884
|
+
streamingContext.controller.enqueue(
|
|
885
|
+
streamingContext.encoder.encode(
|
|
886
|
+
`data: ${JSON.stringify({
|
|
887
|
+
type: "tool_call",
|
|
888
|
+
toolCall: {
|
|
889
|
+
id: currentToolCallId,
|
|
890
|
+
name: call.name || call.toolName,
|
|
891
|
+
parameters: call.params || call.args || {},
|
|
892
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
893
|
+
status: "executing"
|
|
894
|
+
}
|
|
895
|
+
})}
|
|
896
|
+
|
|
897
|
+
`
|
|
898
|
+
)
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (toolResults && Array.isArray(toolResults)) {
|
|
904
|
+
for (const result of toolResults) {
|
|
905
|
+
const currentToolCallId = streamingContext.lastEmittedToolCallId != null ? streamingContext.lastEmittedToolCallId : ++streamingContext.toolCallId;
|
|
906
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
907
|
+
streamingContext.controller.enqueue(
|
|
908
|
+
streamingContext.encoder.encode(
|
|
909
|
+
`data: ${JSON.stringify({
|
|
910
|
+
type: "tool_result",
|
|
911
|
+
toolResult: {
|
|
912
|
+
id: currentToolCallId,
|
|
913
|
+
toolCallId: currentToolCallId,
|
|
914
|
+
result: result.result,
|
|
915
|
+
error: result.error,
|
|
916
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
917
|
+
}
|
|
918
|
+
})}
|
|
919
|
+
|
|
920
|
+
`
|
|
921
|
+
)
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
streamingContext.stepIndex = (streamingContext.stepIndex || 0) + 1;
|
|
927
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
928
|
+
streamingContext.controller.enqueue(
|
|
929
|
+
streamingContext.encoder.encode(
|
|
930
|
+
`data: ${JSON.stringify({
|
|
931
|
+
type: "trace_step",
|
|
932
|
+
step: streamingContext.stepIndex,
|
|
933
|
+
text,
|
|
934
|
+
toolCalls: (toolCalls || []).map((c) => ({
|
|
935
|
+
name: c.name || c.toolName,
|
|
936
|
+
params: c.params || c.args || {}
|
|
937
|
+
})),
|
|
938
|
+
toolResults: (toolResults || []).map((r) => ({
|
|
939
|
+
result: r.result,
|
|
940
|
+
error: r.error
|
|
941
|
+
})),
|
|
942
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
943
|
+
})}
|
|
944
|
+
|
|
945
|
+
`
|
|
946
|
+
)
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
} catch (err) {
|
|
950
|
+
dbg("onStepFinish error", err);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var streamAgentResponse = async (streamingContext, stream) => {
|
|
954
|
+
let hasContent = false;
|
|
955
|
+
let chunkCount = 0;
|
|
956
|
+
for await (const chunk of stream.textStream) {
|
|
957
|
+
if (chunk && chunk.trim()) {
|
|
958
|
+
hasContent = true;
|
|
959
|
+
chunkCount++;
|
|
960
|
+
streamingContext.controller.enqueue(
|
|
961
|
+
streamingContext.encoder.encode(
|
|
962
|
+
`data: ${JSON.stringify({ type: "text", content: chunk })}
|
|
963
|
+
|
|
964
|
+
`
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
dbg("Streaming finished", { hasContent, chunkCount });
|
|
970
|
+
return { hasContent, chunkCount };
|
|
971
|
+
};
|
|
972
|
+
var fallbackToCompletion = async (agent, messages, streamingContext, provider) => {
|
|
973
|
+
try {
|
|
974
|
+
const result = await agent.generate(messages, {
|
|
975
|
+
temperature: getDefaultTemperatureByProvider(provider)
|
|
976
|
+
});
|
|
977
|
+
if (result.text && result.text.trim()) {
|
|
978
|
+
streamingContext.controller.enqueue(
|
|
979
|
+
streamingContext.encoder.encode(
|
|
980
|
+
`data: ${JSON.stringify({
|
|
981
|
+
type: "text",
|
|
982
|
+
content: result.text
|
|
983
|
+
})}
|
|
984
|
+
|
|
985
|
+
`
|
|
986
|
+
)
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
} catch (fallbackErr) {
|
|
990
|
+
streamingContext.controller.enqueue(
|
|
991
|
+
streamingContext.encoder.encode(
|
|
992
|
+
`data: ${JSON.stringify({
|
|
993
|
+
type: "text",
|
|
994
|
+
content: "Failed to generate response. Please try again. ",
|
|
995
|
+
error: fallbackErr instanceof Error ? fallbackErr.message : "Unknown error"
|
|
996
|
+
})}
|
|
997
|
+
|
|
998
|
+
`
|
|
999
|
+
)
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
var safeDisconnect = async (client) => {
|
|
1004
|
+
if (client) {
|
|
1005
|
+
try {
|
|
1006
|
+
await client.disconnect();
|
|
1007
|
+
} catch (cleanupError) {
|
|
1008
|
+
console.warn("[mcp/chat] Error cleaning up MCP client:", cleanupError);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var createStreamingResponse = async (agent, messages, toolsets, streamingContext, provider) => {
|
|
1013
|
+
const stream = await agent.stream(messages, {
|
|
1014
|
+
maxSteps: MAX_AGENT_STEPS,
|
|
1015
|
+
temperature: getDefaultTemperatureByProvider(provider),
|
|
1016
|
+
toolsets,
|
|
1017
|
+
onStepFinish: ({ text, toolCalls, toolResults }) => {
|
|
1018
|
+
handleAgentStepFinish(streamingContext, text, toolCalls, toolResults);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
const { hasContent } = await streamAgentResponse(streamingContext, stream);
|
|
1022
|
+
if (!hasContent) {
|
|
1023
|
+
dbg("No content from textStream; falling back to completion");
|
|
1024
|
+
await fallbackToCompletion(agent, messages, streamingContext, provider);
|
|
1025
|
+
}
|
|
1026
|
+
streamingContext.controller.enqueue(
|
|
1027
|
+
streamingContext.encoder.encode(
|
|
1028
|
+
`data: ${JSON.stringify({
|
|
1029
|
+
type: "elicitation_complete"
|
|
1030
|
+
})}
|
|
1031
|
+
|
|
1032
|
+
`
|
|
1033
|
+
)
|
|
1034
|
+
);
|
|
1035
|
+
streamingContext.controller.enqueue(
|
|
1036
|
+
streamingContext.encoder.encode(`data: [DONE]
|
|
1037
|
+
|
|
1038
|
+
`)
|
|
1039
|
+
);
|
|
1040
|
+
};
|
|
662
1041
|
chat.post("/", async (c) => {
|
|
663
1042
|
let client = null;
|
|
664
1043
|
try {
|
|
@@ -666,6 +1045,7 @@ chat.post("/", async (c) => {
|
|
|
666
1045
|
const {
|
|
667
1046
|
serverConfigs,
|
|
668
1047
|
model,
|
|
1048
|
+
provider,
|
|
669
1049
|
apiKey,
|
|
670
1050
|
systemPrompt,
|
|
671
1051
|
messages,
|
|
@@ -698,7 +1078,7 @@ chat.post("/", async (c) => {
|
|
|
698
1078
|
pendingElicitations2.delete(requestId);
|
|
699
1079
|
return c.json({ success: true });
|
|
700
1080
|
}
|
|
701
|
-
if (!model
|
|
1081
|
+
if (!model?.id || !apiKey || !messages) {
|
|
702
1082
|
return c.json(
|
|
703
1083
|
{
|
|
704
1084
|
success: false,
|
|
@@ -707,345 +1087,357 @@ chat.post("/", async (c) => {
|
|
|
707
1087
|
400
|
|
708
1088
|
);
|
|
709
1089
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (!validation.success) {
|
|
719
|
-
dbg("Server config validation failed", validation.errors || validation.error);
|
|
720
|
-
return c.json(
|
|
721
|
-
{
|
|
722
|
-
success: false,
|
|
723
|
-
error: validation.error.message,
|
|
724
|
-
details: validation.errors
|
|
725
|
-
},
|
|
726
|
-
validation.error.status
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
client = createMCPClientWithMultipleConnections(validation.validConfigs);
|
|
730
|
-
dbg("Created MCP client with servers", Object.keys(validation.validConfigs));
|
|
731
|
-
} else {
|
|
732
|
-
client = new MCPClient2({
|
|
733
|
-
id: `chat-${Date.now()}`,
|
|
734
|
-
servers: {}
|
|
735
|
-
});
|
|
736
|
-
dbg("Created MCP client without servers");
|
|
737
|
-
}
|
|
738
|
-
const llmModel = getLlmModel(model, apiKey, ollamaBaseUrl);
|
|
739
|
-
dbg("LLM model initialized", { provider: model.provider, id: model.id });
|
|
740
|
-
let toolCallId = 0;
|
|
741
|
-
let streamController = null;
|
|
742
|
-
let encoder = null;
|
|
743
|
-
let lastEmittedToolCallId = null;
|
|
744
|
-
const elicitationHandler = async (elicitationRequest) => {
|
|
745
|
-
const requestId2 = `elicit_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
746
|
-
if (streamController && encoder) {
|
|
747
|
-
streamController.enqueue(
|
|
748
|
-
encoder.encode(
|
|
749
|
-
`data: ${JSON.stringify({
|
|
750
|
-
type: "elicitation_request",
|
|
751
|
-
requestId: requestId2,
|
|
752
|
-
message: elicitationRequest.message,
|
|
753
|
-
schema: elicitationRequest.requestedSchema,
|
|
754
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
755
|
-
})}
|
|
756
|
-
|
|
757
|
-
`
|
|
758
|
-
)
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
dbg("Elicitation requested", { requestId: requestId2 });
|
|
762
|
-
return new Promise((resolve, reject) => {
|
|
763
|
-
pendingElicitations2.set(requestId2, { resolve, reject });
|
|
764
|
-
setTimeout(() => {
|
|
765
|
-
if (pendingElicitations2.has(requestId2)) {
|
|
766
|
-
pendingElicitations2.delete(requestId2);
|
|
767
|
-
reject(new Error("Elicitation timeout"));
|
|
768
|
-
}
|
|
769
|
-
}, 3e5);
|
|
770
|
-
});
|
|
771
|
-
};
|
|
772
|
-
if (client.elicitation && client.elicitation.onRequest && serverConfigs) {
|
|
773
|
-
for (const serverName of Object.keys(serverConfigs)) {
|
|
774
|
-
const normalizedName = serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
775
|
-
client.elicitation.onRequest(normalizedName, elicitationHandler);
|
|
776
|
-
dbg("Registered elicitation handler", { serverName, normalizedName });
|
|
777
|
-
}
|
|
1090
|
+
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
1091
|
+
return c.json(
|
|
1092
|
+
{
|
|
1093
|
+
success: false,
|
|
1094
|
+
error: "No server configs provided"
|
|
1095
|
+
},
|
|
1096
|
+
400
|
|
1097
|
+
);
|
|
778
1098
|
}
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
toolCall: {
|
|
794
|
-
id: currentToolCallId,
|
|
795
|
-
name,
|
|
796
|
-
parameters: params,
|
|
797
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
798
|
-
status: "executing"
|
|
799
|
-
}
|
|
800
|
-
})}
|
|
801
|
-
|
|
802
|
-
`
|
|
803
|
-
)
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
dbg("Tool executing", { name, currentToolCallId, params });
|
|
807
|
-
try {
|
|
808
|
-
const result = await tool.execute(params);
|
|
809
|
-
dbg("Tool result", { name, currentToolCallId, ms: Date.now() - startedAt });
|
|
810
|
-
if (streamController && encoder) {
|
|
811
|
-
streamController.enqueue(
|
|
812
|
-
encoder.encode(
|
|
813
|
-
`data: ${JSON.stringify({
|
|
814
|
-
type: "tool_result",
|
|
815
|
-
toolResult: {
|
|
816
|
-
id: currentToolCallId,
|
|
817
|
-
toolCallId: currentToolCallId,
|
|
818
|
-
result,
|
|
819
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
820
|
-
}
|
|
821
|
-
})}
|
|
822
|
-
|
|
823
|
-
`
|
|
824
|
-
)
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
return result;
|
|
828
|
-
} catch (error) {
|
|
829
|
-
dbg("Tool error", { name, currentToolCallId, error: error instanceof Error ? error.message : String(error) });
|
|
830
|
-
if (streamController && encoder) {
|
|
831
|
-
streamController.enqueue(
|
|
832
|
-
encoder.encode(
|
|
833
|
-
`data: ${JSON.stringify({
|
|
834
|
-
type: "tool_result",
|
|
835
|
-
toolResult: {
|
|
836
|
-
id: currentToolCallId,
|
|
837
|
-
toolCallId: currentToolCallId,
|
|
838
|
-
error: error instanceof Error ? error.message : String(error),
|
|
839
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
840
|
-
}
|
|
841
|
-
})}
|
|
842
|
-
|
|
843
|
-
`
|
|
844
|
-
)
|
|
845
|
-
);
|
|
846
|
-
}
|
|
847
|
-
throw error;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
};
|
|
1099
|
+
const validation = validateMultipleServerConfigs(serverConfigs);
|
|
1100
|
+
if (!validation.success) {
|
|
1101
|
+
dbg(
|
|
1102
|
+
"Server config validation failed",
|
|
1103
|
+
validation.errors || validation.error
|
|
1104
|
+
);
|
|
1105
|
+
return c.json(
|
|
1106
|
+
{
|
|
1107
|
+
success: false,
|
|
1108
|
+
error: validation.error.message,
|
|
1109
|
+
details: validation.errors
|
|
1110
|
+
},
|
|
1111
|
+
validation.error.status
|
|
1112
|
+
);
|
|
851
1113
|
}
|
|
1114
|
+
client = createMCPClientWithMultipleConnections(validation.validConfigs);
|
|
1115
|
+
const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
|
|
852
1116
|
const agent = new Agent({
|
|
853
1117
|
name: "MCP Chat Agent",
|
|
854
1118
|
instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
|
|
855
1119
|
model: llmModel,
|
|
856
|
-
tools:
|
|
1120
|
+
tools: void 0
|
|
1121
|
+
// Start without tools, add them in streaming context
|
|
857
1122
|
});
|
|
858
1123
|
const formattedMessages = messages.map((msg) => ({
|
|
859
1124
|
role: msg.role,
|
|
860
1125
|
content: msg.content
|
|
861
1126
|
}));
|
|
862
|
-
const toolsets =
|
|
1127
|
+
const toolsets = await client.getToolsets();
|
|
863
1128
|
dbg("Streaming start", {
|
|
864
|
-
toolsetServers:
|
|
1129
|
+
toolsetServers: Object.keys(toolsets),
|
|
865
1130
|
messageCount: formattedMessages.length
|
|
866
1131
|
});
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1132
|
+
const encoder = new TextEncoder3();
|
|
1133
|
+
const readableStream = new ReadableStream({
|
|
1134
|
+
async start(controller) {
|
|
1135
|
+
const streamingContext = {
|
|
1136
|
+
controller,
|
|
1137
|
+
encoder,
|
|
1138
|
+
toolCallId: 0,
|
|
1139
|
+
lastEmittedToolCallId: null,
|
|
1140
|
+
stepIndex: 0
|
|
1141
|
+
};
|
|
1142
|
+
const flattenedTools = {};
|
|
1143
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1144
|
+
Object.assign(flattenedTools, serverTools);
|
|
1145
|
+
});
|
|
1146
|
+
const streamingWrappedTools = wrapToolsWithStreaming(
|
|
1147
|
+
flattenedTools,
|
|
1148
|
+
streamingContext
|
|
1149
|
+
);
|
|
1150
|
+
const streamingAgent = new Agent({
|
|
1151
|
+
name: agent.name,
|
|
1152
|
+
instructions: agent.instructions,
|
|
1153
|
+
model: agent.model,
|
|
1154
|
+
tools: Object.keys(streamingWrappedTools).length > 0 ? streamingWrappedTools : void 0
|
|
1155
|
+
});
|
|
1156
|
+
if (client?.elicitation?.onRequest) {
|
|
1157
|
+
for (const serverName of Object.keys(serverConfigs)) {
|
|
1158
|
+
const normalizedName = normalizeServerConfigName(serverName);
|
|
1159
|
+
const elicitationHandler = createElicitationHandler(streamingContext);
|
|
1160
|
+
client.elicitation.onRequest(normalizedName, elicitationHandler);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
873
1163
|
try {
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
)
|
|
1164
|
+
if (client) {
|
|
1165
|
+
await createStreamingResponse(
|
|
1166
|
+
streamingAgent,
|
|
1167
|
+
formattedMessages,
|
|
1168
|
+
toolsets,
|
|
1169
|
+
streamingContext,
|
|
1170
|
+
provider
|
|
882
1171
|
);
|
|
1172
|
+
} else {
|
|
1173
|
+
throw new Error("MCP client is null");
|
|
883
1174
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
encoder.encode(
|
|
892
|
-
`data: ${JSON.stringify({
|
|
893
|
-
type: "tool_call",
|
|
894
|
-
toolCall: {
|
|
895
|
-
id: currentToolCallId,
|
|
896
|
-
name: call.name || call.toolName,
|
|
897
|
-
parameters: call.params || call.args || {},
|
|
898
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
899
|
-
status: "executing"
|
|
900
|
-
}
|
|
901
|
-
})}
|
|
902
|
-
|
|
903
|
-
`
|
|
904
|
-
)
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const trList = toolResults;
|
|
910
|
-
if (trList && Array.isArray(trList)) {
|
|
911
|
-
for (const result of trList) {
|
|
912
|
-
const currentToolCallId = lastEmittedToolCallId != null ? lastEmittedToolCallId : ++toolCallId;
|
|
913
|
-
if (streamController && encoder) {
|
|
914
|
-
streamController.enqueue(
|
|
915
|
-
encoder.encode(
|
|
916
|
-
`data: ${JSON.stringify({
|
|
917
|
-
type: "tool_result",
|
|
918
|
-
toolResult: {
|
|
919
|
-
id: currentToolCallId,
|
|
920
|
-
toolCallId: currentToolCallId,
|
|
921
|
-
result: result.result,
|
|
922
|
-
error: result.error,
|
|
923
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
924
|
-
}
|
|
925
|
-
})}
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
controller.enqueue(
|
|
1177
|
+
encoder.encode(
|
|
1178
|
+
`data: ${JSON.stringify({
|
|
1179
|
+
type: "error",
|
|
1180
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1181
|
+
})}
|
|
926
1182
|
|
|
927
1183
|
`
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
} catch (err) {
|
|
934
|
-
dbg("onStepFinish error", err);
|
|
1184
|
+
)
|
|
1185
|
+
);
|
|
1186
|
+
} finally {
|
|
1187
|
+
await safeDisconnect(client);
|
|
1188
|
+
controller.close();
|
|
935
1189
|
}
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
return new Response(readableStream, {
|
|
1193
|
+
headers: {
|
|
1194
|
+
"Content-Type": "text/event-stream",
|
|
1195
|
+
"Cache-Control": "no-cache",
|
|
1196
|
+
Connection: "keep-alive"
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
console.error("[mcp/chat] Error in chat API:", error);
|
|
1201
|
+
await safeDisconnect(client);
|
|
1202
|
+
return c.json(
|
|
1203
|
+
{
|
|
1204
|
+
success: false,
|
|
1205
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
936
1206
|
},
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
streamController.enqueue(
|
|
943
|
-
encoder.encode(
|
|
944
|
-
`data: ${JSON.stringify({ type: "text", content: text })}
|
|
1207
|
+
500
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
var chat_default = chat;
|
|
945
1212
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1213
|
+
// routes/mcp/tests.ts
|
|
1214
|
+
import { Hono as Hono7 } from "hono";
|
|
1215
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
1216
|
+
import { join, dirname } from "path";
|
|
1217
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
1218
|
+
import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
1219
|
+
import { createOllama as createOllama2 } from "ollama-ai-provider";
|
|
1220
|
+
import { Agent as Agent2 } from "@mastra/core/agent";
|
|
1221
|
+
var tests = new Hono7();
|
|
1222
|
+
tests.post("/generate", async (c) => {
|
|
1223
|
+
try {
|
|
1224
|
+
const body = await c.req.json();
|
|
1225
|
+
const test = body?.test;
|
|
1226
|
+
const servers2 = body?.servers;
|
|
1227
|
+
const model = body?.model;
|
|
1228
|
+
if (!test?.id || !test?.prompt || !servers2 || Object.keys(servers2).length === 0) {
|
|
1229
|
+
return c.json(
|
|
1230
|
+
{ success: false, error: "Missing test, servers, or prompt" },
|
|
1231
|
+
400
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
const safeName = String(test.title || test.id).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1235
|
+
const filename = `@TestAgent_${safeName || test.id}.ts`;
|
|
1236
|
+
const fileContents = `import { Agent } from "@mastra/core/agent";
|
|
1237
|
+
import { MCPClient } from "@mastra/mcp";
|
|
1238
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1239
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
1240
|
+
import { createOllama } from "ollama-ai-provider";
|
|
1241
|
+
|
|
1242
|
+
const servers = ${JSON.stringify(servers2, null, 2)} as const;
|
|
1243
|
+
|
|
1244
|
+
function createModel() {
|
|
1245
|
+
const def = ${JSON.stringify(model || null)} as any;
|
|
1246
|
+
if (!def) throw new Error("Model not provided by UI when generating test agent");
|
|
1247
|
+
switch (def.provider) {
|
|
1248
|
+
case "anthropic": return createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })(def.id);
|
|
1249
|
+
case "openai": return createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })(def.id);
|
|
1250
|
+
case "deepseek": return createOpenAI({ apiKey: process.env.DEEPSEEK_API_KEY!, baseURL: "https://api.deepseek.com/v1" })(def.id);
|
|
1251
|
+
case "ollama": return createOllama({ baseURL: process.env.OLLAMA_BASE_URL || "http://localhost:11434" })(def.id, { simulateStreaming: true });
|
|
1252
|
+
default: throw new Error("Unsupported provider: " + def.provider);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export const createTestAgent = async () => {
|
|
1257
|
+
const mcp = new MCPClient({ servers });
|
|
1258
|
+
const toolsets = await mcp.getToolsets();
|
|
1259
|
+
return new Agent({
|
|
1260
|
+
name: ${JSON.stringify(test.title || "Test Agent")},
|
|
1261
|
+
instructions: ${JSON.stringify(test.prompt)},
|
|
1262
|
+
model: createModel(),
|
|
1263
|
+
tools: undefined,
|
|
1264
|
+
defaultGenerateOptions: { toolChoice: "auto" }
|
|
1265
|
+
});
|
|
1266
|
+
};
|
|
1267
|
+
`;
|
|
1268
|
+
const targetPath = join(process.cwd(), "server", "agents", filename);
|
|
1269
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
1270
|
+
await writeFile(targetPath, fileContents, "utf8");
|
|
1271
|
+
return c.json({ success: true, file: `server/agents/${filename}` });
|
|
1272
|
+
} catch (err) {
|
|
1273
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1274
|
+
return c.json({ success: false, error: msg }, 500);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
var tests_default = tests;
|
|
1278
|
+
tests.post("/run-all", async (c) => {
|
|
1279
|
+
const encoder = new TextEncoder();
|
|
1280
|
+
try {
|
|
1281
|
+
let createModel2 = function(model) {
|
|
1282
|
+
switch (model.provider) {
|
|
1283
|
+
case "anthropic":
|
|
1284
|
+
return createAnthropic2({
|
|
1285
|
+
apiKey: providerApiKeys?.anthropic || process.env.ANTHROPIC_API_KEY || ""
|
|
1286
|
+
})(model.id);
|
|
1287
|
+
case "openai":
|
|
1288
|
+
return createOpenAI2({
|
|
1289
|
+
apiKey: providerApiKeys?.openai || process.env.OPENAI_API_KEY || ""
|
|
1290
|
+
})(model.id);
|
|
1291
|
+
case "deepseek":
|
|
1292
|
+
return createOpenAI2({
|
|
1293
|
+
apiKey: providerApiKeys?.deepseek || process.env.DEEPSEEK_API_KEY || "",
|
|
1294
|
+
baseURL: "https://api.deepseek.com/v1"
|
|
1295
|
+
})(model.id);
|
|
1296
|
+
case "ollama":
|
|
1297
|
+
return createOllama2({
|
|
1298
|
+
baseURL: ollamaBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434"
|
|
1299
|
+
})(model.id, { simulateStreaming: true });
|
|
1300
|
+
default:
|
|
1301
|
+
throw new Error(`Unsupported provider: ${model.provider}`);
|
|
953
1302
|
}
|
|
954
|
-
}
|
|
955
|
-
|
|
1303
|
+
};
|
|
1304
|
+
var createModel = createModel2;
|
|
1305
|
+
const body = await c.req.json();
|
|
1306
|
+
const testsInput = body?.tests || [];
|
|
1307
|
+
const allServers = body?.allServers || {};
|
|
1308
|
+
const providerApiKeys = body?.providerApiKeys || {};
|
|
1309
|
+
const ollamaBaseUrl = body?.ollamaBaseUrl;
|
|
1310
|
+
const maxConcurrency = Math.max(
|
|
1311
|
+
1,
|
|
1312
|
+
Math.min(8, body?.concurrency ?? 5)
|
|
1313
|
+
);
|
|
1314
|
+
if (!Array.isArray(testsInput) || testsInput.length === 0) {
|
|
1315
|
+
return c.json({ success: false, error: "No tests provided" }, 400);
|
|
1316
|
+
}
|
|
956
1317
|
const readableStream = new ReadableStream({
|
|
957
1318
|
async start(controller) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if (
|
|
964
|
-
hasContent = true;
|
|
965
|
-
chunkCount++;
|
|
1319
|
+
let active = 0;
|
|
1320
|
+
let index = 0;
|
|
1321
|
+
let failed = false;
|
|
1322
|
+
const runNext = async () => {
|
|
1323
|
+
if (index >= testsInput.length) {
|
|
1324
|
+
if (active === 0) {
|
|
966
1325
|
controller.enqueue(
|
|
967
1326
|
encoder.encode(
|
|
968
|
-
`data: ${JSON.stringify({ type: "
|
|
1327
|
+
`data: ${JSON.stringify({ type: "run_complete", passed: !failed })}
|
|
969
1328
|
|
|
970
1329
|
`
|
|
971
1330
|
)
|
|
972
1331
|
);
|
|
1332
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
1333
|
+
|
|
1334
|
+
`));
|
|
1335
|
+
controller.close();
|
|
973
1336
|
}
|
|
1337
|
+
return;
|
|
974
1338
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1339
|
+
const test = testsInput[index++];
|
|
1340
|
+
active++;
|
|
1341
|
+
(async () => {
|
|
1342
|
+
const calledTools = /* @__PURE__ */ new Set();
|
|
1343
|
+
const expectedSet = new Set(test.expectedTools || []);
|
|
1344
|
+
let step = 0;
|
|
1345
|
+
let client = null;
|
|
978
1346
|
try {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1347
|
+
let serverConfigs = {};
|
|
1348
|
+
if (test.selectedServers && test.selectedServers.length > 0) {
|
|
1349
|
+
for (const name of test.selectedServers) {
|
|
1350
|
+
if (allServers[name]) serverConfigs[name] = allServers[name];
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
for (const [name, cfg] of Object.entries(allServers)) {
|
|
1354
|
+
serverConfigs[name] = cfg;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const validation = validateMultipleServerConfigs(serverConfigs);
|
|
1358
|
+
let finalServers = {};
|
|
1359
|
+
if (validation.success && validation.validConfigs) {
|
|
1360
|
+
finalServers = validation.validConfigs;
|
|
1361
|
+
} else if (validation.validConfigs && Object.keys(validation.validConfigs).length > 0) {
|
|
1362
|
+
finalServers = validation.validConfigs;
|
|
992
1363
|
} else {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1364
|
+
throw new Error("No valid MCP server configs for test");
|
|
1365
|
+
}
|
|
1366
|
+
client = createMCPClientWithMultipleConnections(finalServers);
|
|
1367
|
+
const model = createModel2(test.model);
|
|
1368
|
+
const agent = new Agent2({
|
|
1369
|
+
name: `TestAgent-${test.id}`,
|
|
1370
|
+
instructions: "You are a helpful assistant with access to MCP tools",
|
|
1371
|
+
model
|
|
1372
|
+
});
|
|
1373
|
+
const toolsets = await client.getToolsets();
|
|
1374
|
+
const stream = await agent.stream(
|
|
1375
|
+
[{ role: "user", content: test.prompt || "" }],
|
|
1376
|
+
{
|
|
1377
|
+
maxSteps: 10,
|
|
1378
|
+
toolsets,
|
|
1379
|
+
onStepFinish: ({ text, toolCalls, toolResults }) => {
|
|
1380
|
+
step += 1;
|
|
1381
|
+
(toolCalls || []).forEach((c2) => {
|
|
1382
|
+
const toolName = c2?.name || c2?.toolName;
|
|
1383
|
+
if (toolName) {
|
|
1384
|
+
calledTools.add(toolName);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
controller.enqueue(
|
|
1388
|
+
encoder.encode(
|
|
1389
|
+
`data: ${JSON.stringify({
|
|
1390
|
+
type: "trace_step",
|
|
1391
|
+
testId: test.id,
|
|
1392
|
+
step,
|
|
1393
|
+
text,
|
|
1394
|
+
toolCalls,
|
|
1395
|
+
toolResults
|
|
1396
|
+
})}
|
|
997
1397
|
|
|
998
1398
|
`
|
|
999
|
-
|
|
1000
|
-
|
|
1399
|
+
)
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
);
|
|
1404
|
+
for await (const _ of stream.textStream) {
|
|
1001
1405
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1406
|
+
const called = Array.from(calledTools);
|
|
1407
|
+
const missing = Array.from(expectedSet).filter(
|
|
1408
|
+
(t) => !calledTools.has(t)
|
|
1409
|
+
);
|
|
1410
|
+
const unexpected = called.filter((t) => !expectedSet.has(t));
|
|
1411
|
+
const passed = missing.length === 0 && unexpected.length === 0;
|
|
1412
|
+
if (!passed) failed = true;
|
|
1004
1413
|
controller.enqueue(
|
|
1005
1414
|
encoder.encode(
|
|
1006
|
-
`data: ${JSON.stringify({ type: "
|
|
1415
|
+
`data: ${JSON.stringify({ type: "result", testId: test.id, passed, calledTools: called, missingTools: missing, unexpectedTools: unexpected })}
|
|
1007
1416
|
|
|
1008
1417
|
`
|
|
1009
1418
|
)
|
|
1010
1419
|
);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
type: "elicitation_complete"
|
|
1017
|
-
})}
|
|
1018
|
-
|
|
1019
|
-
`
|
|
1020
|
-
)
|
|
1021
|
-
);
|
|
1022
|
-
controller.enqueue(encoder.encode(`data: [DONE]
|
|
1023
|
-
|
|
1024
|
-
`));
|
|
1025
|
-
} catch (error) {
|
|
1026
|
-
console.error("[mcp/chat] Streaming error:", error);
|
|
1027
|
-
controller.enqueue(
|
|
1028
|
-
encoder.encode(
|
|
1029
|
-
`data: ${JSON.stringify({
|
|
1030
|
-
type: "error",
|
|
1031
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1032
|
-
})}
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
failed = true;
|
|
1422
|
+
controller.enqueue(
|
|
1423
|
+
encoder.encode(
|
|
1424
|
+
`data: ${JSON.stringify({ type: "result", testId: test.id, passed: false, error: err?.message })}
|
|
1033
1425
|
|
|
1034
1426
|
`
|
|
1035
|
-
|
|
1036
|
-
);
|
|
1037
|
-
} finally {
|
|
1038
|
-
if (client) {
|
|
1039
|
-
try {
|
|
1040
|
-
await client.disconnect();
|
|
1041
|
-
} catch (cleanupError) {
|
|
1042
|
-
console.warn(
|
|
1043
|
-
"[mcp/chat] Error cleaning up MCP client after streaming:",
|
|
1044
|
-
cleanupError
|
|
1427
|
+
)
|
|
1045
1428
|
);
|
|
1429
|
+
} finally {
|
|
1430
|
+
try {
|
|
1431
|
+
await client?.disconnect();
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
active--;
|
|
1435
|
+
runNext();
|
|
1046
1436
|
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1437
|
+
})();
|
|
1438
|
+
};
|
|
1439
|
+
for (let i = 0; i < Math.min(maxConcurrency, testsInput.length); i++) {
|
|
1440
|
+
runNext();
|
|
1049
1441
|
}
|
|
1050
1442
|
}
|
|
1051
1443
|
});
|
|
@@ -1056,55 +1448,17 @@ chat.post("/", async (c) => {
|
|
|
1056
1448
|
Connection: "keep-alive"
|
|
1057
1449
|
}
|
|
1058
1450
|
});
|
|
1059
|
-
} catch (
|
|
1060
|
-
console.error("[mcp/chat] Error in chat API:", error);
|
|
1061
|
-
if (client) {
|
|
1062
|
-
try {
|
|
1063
|
-
await client.disconnect();
|
|
1064
|
-
} catch (cleanupError) {
|
|
1065
|
-
console.warn("Error cleaning up MCP client after error:", cleanupError);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1451
|
+
} catch (err) {
|
|
1068
1452
|
return c.json(
|
|
1069
|
-
{
|
|
1070
|
-
success: false,
|
|
1071
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1072
|
-
},
|
|
1453
|
+
{ success: false, error: err?.message || "Unknown error" },
|
|
1073
1454
|
500
|
|
1074
1455
|
);
|
|
1075
1456
|
}
|
|
1076
1457
|
});
|
|
1077
|
-
var getLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
1078
|
-
if (!modelDefinition || !modelDefinition.id || !modelDefinition.provider) {
|
|
1079
|
-
throw new Error(
|
|
1080
|
-
`Invalid model definition: ${JSON.stringify(modelDefinition)}`
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
switch (modelDefinition.provider) {
|
|
1084
|
-
case "anthropic":
|
|
1085
|
-
return createAnthropic({ apiKey })(modelDefinition.id);
|
|
1086
|
-
case "openai":
|
|
1087
|
-
return createOpenAI({ apiKey })(modelDefinition.id);
|
|
1088
|
-
case "ollama":
|
|
1089
|
-
const baseUrl = ollamaBaseUrl || "http://localhost:11434";
|
|
1090
|
-
return createOllama({
|
|
1091
|
-
// The provider expects the root Ollama URL; it internally targets the /api endpoints
|
|
1092
|
-
baseURL: `${baseUrl}`
|
|
1093
|
-
})(modelDefinition.id, {
|
|
1094
|
-
simulateStreaming: true
|
|
1095
|
-
// Enable streaming for Ollama models
|
|
1096
|
-
});
|
|
1097
|
-
default:
|
|
1098
|
-
throw new Error(
|
|
1099
|
-
`Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
};
|
|
1103
|
-
var chat_default = chat;
|
|
1104
1458
|
|
|
1105
1459
|
// routes/mcp/oauth.ts
|
|
1106
|
-
import { Hono as
|
|
1107
|
-
var oauth = new
|
|
1460
|
+
import { Hono as Hono8 } from "hono";
|
|
1461
|
+
var oauth = new Hono8();
|
|
1108
1462
|
oauth.get("/metadata", async (c) => {
|
|
1109
1463
|
try {
|
|
1110
1464
|
const url = c.req.query("url");
|
|
@@ -1150,7 +1504,7 @@ oauth.get("/metadata", async (c) => {
|
|
|
1150
1504
|
var oauth_default = oauth;
|
|
1151
1505
|
|
|
1152
1506
|
// routes/mcp/index.ts
|
|
1153
|
-
var mcp = new
|
|
1507
|
+
var mcp = new Hono9();
|
|
1154
1508
|
mcp.get("/health", (c) => {
|
|
1155
1509
|
return c.json({
|
|
1156
1510
|
service: "MCP API",
|
|
@@ -1160,12 +1514,311 @@ mcp.get("/health", (c) => {
|
|
|
1160
1514
|
});
|
|
1161
1515
|
mcp.route("/chat", chat_default);
|
|
1162
1516
|
mcp.route("/connect", connect_default);
|
|
1517
|
+
mcp.route("/servers", servers_default);
|
|
1163
1518
|
mcp.route("/tools", tools_default);
|
|
1519
|
+
mcp.route("/tests", tests_default);
|
|
1164
1520
|
mcp.route("/resources", resources_default);
|
|
1165
1521
|
mcp.route("/prompts", prompts_default);
|
|
1166
1522
|
mcp.route("/oauth", oauth_default);
|
|
1167
1523
|
var mcp_default = mcp;
|
|
1168
1524
|
|
|
1525
|
+
// services/mcpjam-client-manager.ts
|
|
1526
|
+
import { MCPClient as MCPClient2 } from "@mastra/mcp";
|
|
1527
|
+
function normalizeServerId(serverId) {
|
|
1528
|
+
return serverId.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
1529
|
+
}
|
|
1530
|
+
var MCPJamClientManager = class {
|
|
1531
|
+
mcpClients = /* @__PURE__ */ new Map();
|
|
1532
|
+
statuses = /* @__PURE__ */ new Map();
|
|
1533
|
+
configs = /* @__PURE__ */ new Map();
|
|
1534
|
+
toolRegistry = /* @__PURE__ */ new Map();
|
|
1535
|
+
resourceRegistry = /* @__PURE__ */ new Map();
|
|
1536
|
+
promptRegistry = /* @__PURE__ */ new Map();
|
|
1537
|
+
// Store for pending elicitation requests with Promise resolvers
|
|
1538
|
+
pendingElicitations = /* @__PURE__ */ new Map();
|
|
1539
|
+
// Optional callback for handling elicitation requests
|
|
1540
|
+
elicitationCallback;
|
|
1541
|
+
async connectToServer(serverId, serverConfig) {
|
|
1542
|
+
const id = normalizeServerId(serverId);
|
|
1543
|
+
if (this.mcpClients.has(id)) return;
|
|
1544
|
+
const validation = validateServerConfig(serverConfig);
|
|
1545
|
+
if (!validation.success) {
|
|
1546
|
+
this.statuses.set(id, "error");
|
|
1547
|
+
throw new Error(validation.error.message);
|
|
1548
|
+
}
|
|
1549
|
+
this.configs.set(id, validation.config);
|
|
1550
|
+
this.statuses.set(id, "connecting");
|
|
1551
|
+
const client = new MCPClient2({
|
|
1552
|
+
id: `mcpjam-${id}`,
|
|
1553
|
+
servers: { [id]: validation.config }
|
|
1554
|
+
});
|
|
1555
|
+
try {
|
|
1556
|
+
await client.getTools();
|
|
1557
|
+
this.mcpClients.set(id, client);
|
|
1558
|
+
this.statuses.set(id, "connected");
|
|
1559
|
+
if (client.elicitation?.onRequest) {
|
|
1560
|
+
const normalizedName = normalizeServerConfigName(serverId);
|
|
1561
|
+
client.elicitation.onRequest(
|
|
1562
|
+
normalizedName,
|
|
1563
|
+
async (elicitationRequest) => {
|
|
1564
|
+
return await this.handleElicitationRequest(elicitationRequest);
|
|
1565
|
+
}
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
await this.discoverServerResources(id);
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
this.statuses.set(id, "error");
|
|
1571
|
+
try {
|
|
1572
|
+
await client.disconnect();
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
this.mcpClients.delete(id);
|
|
1576
|
+
throw err;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
async disconnectFromServer(serverId) {
|
|
1580
|
+
const id = normalizeServerId(serverId);
|
|
1581
|
+
const client = this.mcpClients.get(id);
|
|
1582
|
+
if (client) {
|
|
1583
|
+
try {
|
|
1584
|
+
await client.disconnect();
|
|
1585
|
+
} catch {
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
this.mcpClients.delete(id);
|
|
1589
|
+
this.statuses.set(id, "disconnected");
|
|
1590
|
+
for (const key of Array.from(this.toolRegistry.keys())) {
|
|
1591
|
+
const item = this.toolRegistry.get(key);
|
|
1592
|
+
if (item.serverId === id) this.toolRegistry.delete(key);
|
|
1593
|
+
}
|
|
1594
|
+
for (const key of Array.from(this.resourceRegistry.keys())) {
|
|
1595
|
+
const item = this.resourceRegistry.get(key);
|
|
1596
|
+
if (item.serverId === id) this.resourceRegistry.delete(key);
|
|
1597
|
+
}
|
|
1598
|
+
for (const key of Array.from(this.promptRegistry.keys())) {
|
|
1599
|
+
const item = this.promptRegistry.get(key);
|
|
1600
|
+
if (item.serverId === id) this.promptRegistry.delete(key);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
getConnectionStatus(serverId) {
|
|
1604
|
+
const id = normalizeServerId(serverId);
|
|
1605
|
+
return this.statuses.get(id) || "disconnected";
|
|
1606
|
+
}
|
|
1607
|
+
getConnectedServers() {
|
|
1608
|
+
const servers2 = {};
|
|
1609
|
+
for (const [serverId, status] of this.statuses.entries()) {
|
|
1610
|
+
servers2[serverId] = {
|
|
1611
|
+
status,
|
|
1612
|
+
config: this.configs.get(serverId)
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
return servers2;
|
|
1616
|
+
}
|
|
1617
|
+
async discoverAllResources() {
|
|
1618
|
+
const serverIds = Array.from(this.mcpClients.keys());
|
|
1619
|
+
await Promise.all(serverIds.map((id) => this.discoverServerResources(id)));
|
|
1620
|
+
}
|
|
1621
|
+
async discoverServerResources(serverId) {
|
|
1622
|
+
const id = normalizeServerId(serverId);
|
|
1623
|
+
const client = this.mcpClients.get(id);
|
|
1624
|
+
if (!client) return;
|
|
1625
|
+
const tools2 = await client.getTools();
|
|
1626
|
+
for (const [name, tool] of Object.entries(tools2)) {
|
|
1627
|
+
this.toolRegistry.set(`${id}:${name}`, {
|
|
1628
|
+
name,
|
|
1629
|
+
description: tool.description,
|
|
1630
|
+
inputSchema: tool.inputSchema,
|
|
1631
|
+
outputSchema: tool.outputSchema,
|
|
1632
|
+
serverId: id
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
const res = await client.resources.list();
|
|
1637
|
+
for (const [group, list] of Object.entries(res)) {
|
|
1638
|
+
for (const r of list) {
|
|
1639
|
+
this.resourceRegistry.set(`${id}:${r.uri}`, {
|
|
1640
|
+
uri: r.uri,
|
|
1641
|
+
name: r.name,
|
|
1642
|
+
description: r.description,
|
|
1643
|
+
mimeType: r.mimeType,
|
|
1644
|
+
serverId: id
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
const prompts2 = await client.prompts.list();
|
|
1652
|
+
for (const [group, list] of Object.entries(prompts2)) {
|
|
1653
|
+
for (const p of list) {
|
|
1654
|
+
this.promptRegistry.set(`${id}:${p.name}`, {
|
|
1655
|
+
name: p.name,
|
|
1656
|
+
description: p.description,
|
|
1657
|
+
arguments: p.arguments,
|
|
1658
|
+
serverId: id
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
} catch {
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
getAvailableTools() {
|
|
1666
|
+
return Array.from(this.toolRegistry.values());
|
|
1667
|
+
}
|
|
1668
|
+
async getToolsetsForServer(serverId) {
|
|
1669
|
+
const id = normalizeServerId(serverId);
|
|
1670
|
+
const client = this.mcpClients.get(id);
|
|
1671
|
+
if (!client) {
|
|
1672
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1673
|
+
}
|
|
1674
|
+
const toolsets = await client.getToolsets();
|
|
1675
|
+
const flattenedTools = {};
|
|
1676
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1677
|
+
Object.assign(flattenedTools, serverTools);
|
|
1678
|
+
});
|
|
1679
|
+
return flattenedTools;
|
|
1680
|
+
}
|
|
1681
|
+
getAvailableResources() {
|
|
1682
|
+
return Array.from(this.resourceRegistry.values());
|
|
1683
|
+
}
|
|
1684
|
+
getResourcesForServer(serverId) {
|
|
1685
|
+
const id = normalizeServerId(serverId);
|
|
1686
|
+
return Array.from(this.resourceRegistry.values()).filter(
|
|
1687
|
+
(r) => r.serverId === id
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
getAvailablePrompts() {
|
|
1691
|
+
return Array.from(this.promptRegistry.values());
|
|
1692
|
+
}
|
|
1693
|
+
getPromptsForServer(serverId) {
|
|
1694
|
+
const id = normalizeServerId(serverId);
|
|
1695
|
+
return Array.from(this.promptRegistry.values()).filter(
|
|
1696
|
+
(p) => p.serverId === id
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
async executeToolDirect(toolName, parameters) {
|
|
1700
|
+
let serverId = "";
|
|
1701
|
+
let name = toolName;
|
|
1702
|
+
if (toolName.includes(":")) {
|
|
1703
|
+
const [sid, n] = toolName.split(":", 2);
|
|
1704
|
+
serverId = normalizeServerId(sid);
|
|
1705
|
+
name = n;
|
|
1706
|
+
} else {
|
|
1707
|
+
for (const [key, tool2] of this.toolRegistry.entries()) {
|
|
1708
|
+
if (tool2.name === toolName) {
|
|
1709
|
+
serverId = tool2.serverId;
|
|
1710
|
+
name = toolName;
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
if (!serverId) {
|
|
1716
|
+
for (const [clientServerId, client2] of this.mcpClients.entries()) {
|
|
1717
|
+
try {
|
|
1718
|
+
const toolsets2 = await client2.getToolsets();
|
|
1719
|
+
const flattenedTools2 = {};
|
|
1720
|
+
Object.values(toolsets2).forEach((serverTools) => {
|
|
1721
|
+
Object.assign(flattenedTools2, serverTools);
|
|
1722
|
+
});
|
|
1723
|
+
if (flattenedTools2[toolName]) {
|
|
1724
|
+
serverId = clientServerId;
|
|
1725
|
+
name = toolName;
|
|
1726
|
+
break;
|
|
1727
|
+
}
|
|
1728
|
+
} catch {
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (!serverId) {
|
|
1733
|
+
throw new Error(`Tool not found in any connected server: ${toolName}`);
|
|
1734
|
+
}
|
|
1735
|
+
const client = this.mcpClients.get(serverId);
|
|
1736
|
+
if (!client)
|
|
1737
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1738
|
+
const toolsets = await client.getToolsets();
|
|
1739
|
+
const flattenedTools = {};
|
|
1740
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1741
|
+
Object.assign(flattenedTools, serverTools);
|
|
1742
|
+
});
|
|
1743
|
+
const tool = flattenedTools[name];
|
|
1744
|
+
if (!tool)
|
|
1745
|
+
throw new Error(`Tool '${name}' not found in server '${serverId}'`);
|
|
1746
|
+
const result = await tool.execute({ context: parameters || {} });
|
|
1747
|
+
return { result };
|
|
1748
|
+
}
|
|
1749
|
+
async getResource(resourceUri, serverId) {
|
|
1750
|
+
let uri = resourceUri;
|
|
1751
|
+
const client = this.mcpClients.get(serverId);
|
|
1752
|
+
if (!client) throw new Error("No MCP client available");
|
|
1753
|
+
const content = await client.resources.read(serverId, uri);
|
|
1754
|
+
return { contents: content?.contents || [] };
|
|
1755
|
+
}
|
|
1756
|
+
async getPrompt(promptName, serverId, args) {
|
|
1757
|
+
const client = this.mcpClients.get(serverId);
|
|
1758
|
+
if (!client) throw new Error("No MCP client available");
|
|
1759
|
+
const content = await client.prompts.get({
|
|
1760
|
+
serverName: serverId,
|
|
1761
|
+
name: promptName,
|
|
1762
|
+
args: args || {}
|
|
1763
|
+
});
|
|
1764
|
+
return { content };
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Handles elicitation requests from MCP servers during direct tool execution
|
|
1768
|
+
*/
|
|
1769
|
+
async handleElicitationRequest(elicitationRequest) {
|
|
1770
|
+
const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1771
|
+
return new Promise((resolve, reject) => {
|
|
1772
|
+
this.pendingElicitations.set(requestId, { resolve, reject });
|
|
1773
|
+
if (this.elicitationCallback) {
|
|
1774
|
+
this.elicitationCallback({
|
|
1775
|
+
requestId,
|
|
1776
|
+
message: elicitationRequest.message,
|
|
1777
|
+
schema: elicitationRequest.requestedSchema
|
|
1778
|
+
}).then(resolve).catch(reject);
|
|
1779
|
+
} else {
|
|
1780
|
+
const error = new Error("ELICITATION_REQUIRED");
|
|
1781
|
+
error.elicitationRequest = {
|
|
1782
|
+
requestId,
|
|
1783
|
+
message: elicitationRequest.message,
|
|
1784
|
+
schema: elicitationRequest.requestedSchema
|
|
1785
|
+
};
|
|
1786
|
+
reject(error);
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Responds to a pending elicitation request
|
|
1792
|
+
*/
|
|
1793
|
+
respondToElicitation(requestId, response) {
|
|
1794
|
+
const pending = this.pendingElicitations.get(requestId);
|
|
1795
|
+
if (!pending) {
|
|
1796
|
+
return false;
|
|
1797
|
+
}
|
|
1798
|
+
pending.resolve(response);
|
|
1799
|
+
this.pendingElicitations.delete(requestId);
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Gets the pending elicitations map for external access
|
|
1804
|
+
*/
|
|
1805
|
+
getPendingElicitations() {
|
|
1806
|
+
return this.pendingElicitations;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Sets a callback to handle elicitation requests
|
|
1810
|
+
*/
|
|
1811
|
+
setElicitationCallback(callback) {
|
|
1812
|
+
this.elicitationCallback = callback;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Clears the elicitation callback
|
|
1816
|
+
*/
|
|
1817
|
+
clearElicitationCallback() {
|
|
1818
|
+
this.elicitationCallback = void 0;
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1169
1822
|
// index.ts
|
|
1170
1823
|
function logBox(content, title) {
|
|
1171
1824
|
const lines = content.split("\n");
|
|
@@ -1199,7 +1852,12 @@ function getMCPConfigFromEnv() {
|
|
|
1199
1852
|
// Default name for CLI-provided servers
|
|
1200
1853
|
};
|
|
1201
1854
|
}
|
|
1202
|
-
var app = new
|
|
1855
|
+
var app = new Hono10();
|
|
1856
|
+
var mcpJamClientManager = new MCPJamClientManager();
|
|
1857
|
+
app.use("*", async (c, next) => {
|
|
1858
|
+
c.mcpJamClientManager = mcpJamClientManager;
|
|
1859
|
+
await next();
|
|
1860
|
+
});
|
|
1203
1861
|
app.use("*", logger());
|
|
1204
1862
|
var serverPort = process.env.PORT || "3001";
|
|
1205
1863
|
var corsOrigins = [
|
|
@@ -1231,7 +1889,7 @@ if (process.env.NODE_ENV === "production") {
|
|
|
1231
1889
|
if (path.startsWith("/api/")) {
|
|
1232
1890
|
return c.notFound();
|
|
1233
1891
|
}
|
|
1234
|
-
const indexPath =
|
|
1892
|
+
const indexPath = join2(process.cwd(), "dist", "client", "index.html");
|
|
1235
1893
|
let htmlContent = readFileSync(indexPath, "utf-8");
|
|
1236
1894
|
const mcpConfig = getMCPConfigFromEnv();
|
|
1237
1895
|
if (mcpConfig) {
|