@mcpjam/inspector 0.9.4 → 0.9.6
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/dist/client/assets/index-CxSz4W4P.css +1 -0
- package/dist/client/assets/index-Uo2CGMHd.js +1738 -0
- package/dist/client/assets/index-Uo2CGMHd.js.map +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +1272 -544
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/client/assets/index-C-vxFpw4.css +0 -1
- package/dist/client/assets/index-CTiiyjex.js +0 -1723
- package/dist/client/assets/index-CTiiyjex.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
|
-
);
|
|
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;
|
|
323
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,14 +536,16 @@ 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";
|
|
646
540
|
import { Agent } from "@mastra/core/agent";
|
|
647
541
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
648
542
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
649
543
|
import { createOllama } from "ollama-ai-provider";
|
|
650
|
-
import { TextEncoder as
|
|
544
|
+
import { TextEncoder as TextEncoder3 } from "util";
|
|
651
545
|
|
|
652
546
|
// ../client/src/lib/chat-utils.ts
|
|
547
|
+
import { clsx } from "clsx";
|
|
548
|
+
import { twMerge } from "tailwind-merge";
|
|
653
549
|
function getDefaultTemperatureByProvider(provider) {
|
|
654
550
|
switch (provider) {
|
|
655
551
|
case "openai":
|
|
@@ -673,7 +569,7 @@ try {
|
|
|
673
569
|
} catch {
|
|
674
570
|
}
|
|
675
571
|
var pendingElicitations2 = /* @__PURE__ */ new Map();
|
|
676
|
-
var chat = new
|
|
572
|
+
var chat = new Hono6();
|
|
677
573
|
var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
678
574
|
if (!modelDefinition?.id || !modelDefinition?.provider) {
|
|
679
575
|
throw new Error(
|
|
@@ -696,40 +592,11 @@ var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
|
696
592
|
})(modelDefinition.id, {
|
|
697
593
|
simulateStreaming: true
|
|
698
594
|
});
|
|
699
|
-
default:
|
|
700
|
-
throw new Error(
|
|
701
|
-
`Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
var createElicitationHandler = (streamingContext) => {
|
|
706
|
-
return async (elicitationRequest) => {
|
|
707
|
-
const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
708
|
-
if (streamingContext.controller && streamingContext.encoder) {
|
|
709
|
-
streamingContext.controller.enqueue(
|
|
710
|
-
streamingContext.encoder.encode(
|
|
711
|
-
`data: ${JSON.stringify({
|
|
712
|
-
type: "elicitation_request",
|
|
713
|
-
requestId,
|
|
714
|
-
message: elicitationRequest.message,
|
|
715
|
-
schema: elicitationRequest.requestedSchema,
|
|
716
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
717
|
-
})}
|
|
718
|
-
|
|
719
|
-
`
|
|
720
|
-
)
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
return new Promise((resolve, reject) => {
|
|
724
|
-
pendingElicitations2.set(requestId, { resolve, reject });
|
|
725
|
-
setTimeout(() => {
|
|
726
|
-
if (pendingElicitations2.has(requestId)) {
|
|
727
|
-
pendingElicitations2.delete(requestId);
|
|
728
|
-
reject(new Error("Elicitation timeout"));
|
|
729
|
-
}
|
|
730
|
-
}, ELICITATION_TIMEOUT);
|
|
731
|
-
});
|
|
732
|
-
};
|
|
595
|
+
default:
|
|
596
|
+
throw new Error(
|
|
597
|
+
`Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
733
600
|
};
|
|
734
601
|
var wrapToolsWithStreaming = (tools2, streamingContext) => {
|
|
735
602
|
const wrappedTools = {};
|
|
@@ -862,6 +729,29 @@ var handleAgentStepFinish = (streamingContext, text, toolCalls, toolResults) =>
|
|
|
862
729
|
}
|
|
863
730
|
}
|
|
864
731
|
}
|
|
732
|
+
streamingContext.stepIndex = (streamingContext.stepIndex || 0) + 1;
|
|
733
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
734
|
+
streamingContext.controller.enqueue(
|
|
735
|
+
streamingContext.encoder.encode(
|
|
736
|
+
`data: ${JSON.stringify({
|
|
737
|
+
type: "trace_step",
|
|
738
|
+
step: streamingContext.stepIndex,
|
|
739
|
+
text,
|
|
740
|
+
toolCalls: (toolCalls || []).map((c) => ({
|
|
741
|
+
name: c.name || c.toolName,
|
|
742
|
+
params: c.params || c.args || {}
|
|
743
|
+
})),
|
|
744
|
+
toolResults: (toolResults || []).map((r) => ({
|
|
745
|
+
result: r.result,
|
|
746
|
+
error: r.error
|
|
747
|
+
})),
|
|
748
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
749
|
+
})}
|
|
750
|
+
|
|
751
|
+
`
|
|
752
|
+
)
|
|
753
|
+
);
|
|
754
|
+
}
|
|
865
755
|
} catch (err) {
|
|
866
756
|
dbg("onStepFinish error", err);
|
|
867
757
|
}
|
|
@@ -916,15 +806,6 @@ var fallbackToCompletion = async (agent, messages, streamingContext, provider) =
|
|
|
916
806
|
);
|
|
917
807
|
}
|
|
918
808
|
};
|
|
919
|
-
var safeDisconnect = async (client) => {
|
|
920
|
-
if (client) {
|
|
921
|
-
try {
|
|
922
|
-
await client.disconnect();
|
|
923
|
-
} catch (cleanupError) {
|
|
924
|
-
console.warn("[mcp/chat] Error cleaning up MCP client:", cleanupError);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
809
|
var createStreamingResponse = async (agent, messages, toolsets, streamingContext, provider) => {
|
|
929
810
|
const stream = await agent.stream(messages, {
|
|
930
811
|
maxSteps: MAX_AGENT_STEPS,
|
|
@@ -955,7 +836,7 @@ var createStreamingResponse = async (agent, messages, toolsets, streamingContext
|
|
|
955
836
|
);
|
|
956
837
|
};
|
|
957
838
|
chat.post("/", async (c) => {
|
|
958
|
-
|
|
839
|
+
const mcpClientManager = c.mcpJamClientManager;
|
|
959
840
|
try {
|
|
960
841
|
const requestData = await c.req.json();
|
|
961
842
|
const {
|
|
@@ -994,110 +875,592 @@ chat.post("/", async (c) => {
|
|
|
994
875
|
pendingElicitations2.delete(requestId);
|
|
995
876
|
return c.json({ success: true });
|
|
996
877
|
}
|
|
997
|
-
if (!model?.id || !apiKey || !messages) {
|
|
998
|
-
return c.json(
|
|
999
|
-
{
|
|
1000
|
-
success: false,
|
|
1001
|
-
error: "model (with id), apiKey, and messages are required"
|
|
1002
|
-
},
|
|
1003
|
-
400
|
|
1004
|
-
);
|
|
878
|
+
if (!model?.id || !apiKey || !messages) {
|
|
879
|
+
return c.json(
|
|
880
|
+
{
|
|
881
|
+
success: false,
|
|
882
|
+
error: "model (with id), apiKey, and messages are required"
|
|
883
|
+
},
|
|
884
|
+
400
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
888
|
+
return c.json(
|
|
889
|
+
{
|
|
890
|
+
success: false,
|
|
891
|
+
error: "No server configs provided"
|
|
892
|
+
},
|
|
893
|
+
400
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const serverErrors = {};
|
|
897
|
+
const connectedServers = [];
|
|
898
|
+
for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
|
|
899
|
+
try {
|
|
900
|
+
await mcpClientManager.connectToServer(serverName, serverConfig);
|
|
901
|
+
connectedServers.push(serverName);
|
|
902
|
+
dbg("Connected to server", { serverName });
|
|
903
|
+
} catch (error) {
|
|
904
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
905
|
+
serverErrors[serverName] = errorMessage;
|
|
906
|
+
dbg("Failed to connect to server", { serverName, error: errorMessage });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (connectedServers.length === 0) {
|
|
910
|
+
return c.json(
|
|
911
|
+
{
|
|
912
|
+
success: false,
|
|
913
|
+
error: "Failed to connect to any servers",
|
|
914
|
+
details: serverErrors
|
|
915
|
+
},
|
|
916
|
+
400
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (Object.keys(serverErrors).length > 0) {
|
|
920
|
+
dbg("Some servers failed to connect", {
|
|
921
|
+
connectedServers,
|
|
922
|
+
failedServers: Object.keys(serverErrors),
|
|
923
|
+
errors: serverErrors
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
|
|
927
|
+
const agent = new Agent({
|
|
928
|
+
name: "MCP Chat Agent",
|
|
929
|
+
instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
|
|
930
|
+
model: llmModel,
|
|
931
|
+
tools: void 0
|
|
932
|
+
// Start without tools, add them in streaming context
|
|
933
|
+
});
|
|
934
|
+
const formattedMessages = messages.map((msg) => ({
|
|
935
|
+
role: msg.role,
|
|
936
|
+
content: msg.content
|
|
937
|
+
}));
|
|
938
|
+
const allTools = mcpClientManager.getAvailableTools();
|
|
939
|
+
const toolsByServer = {};
|
|
940
|
+
for (const tool of allTools) {
|
|
941
|
+
if (!toolsByServer[tool.serverId]) {
|
|
942
|
+
toolsByServer[tool.serverId] = {};
|
|
943
|
+
}
|
|
944
|
+
toolsByServer[tool.serverId][tool.name] = {
|
|
945
|
+
description: tool.description,
|
|
946
|
+
inputSchema: tool.inputSchema,
|
|
947
|
+
execute: async (params) => {
|
|
948
|
+
return await mcpClientManager.executeToolDirect(
|
|
949
|
+
`${tool.serverId}:${tool.name}`,
|
|
950
|
+
params
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
dbg("Streaming start", {
|
|
956
|
+
connectedServers,
|
|
957
|
+
toolCount: allTools.length,
|
|
958
|
+
messageCount: formattedMessages.length
|
|
959
|
+
});
|
|
960
|
+
const encoder = new TextEncoder3();
|
|
961
|
+
const readableStream = new ReadableStream({
|
|
962
|
+
async start(controller) {
|
|
963
|
+
const streamingContext = {
|
|
964
|
+
controller,
|
|
965
|
+
encoder,
|
|
966
|
+
toolCallId: 0,
|
|
967
|
+
lastEmittedToolCallId: null,
|
|
968
|
+
stepIndex: 0
|
|
969
|
+
};
|
|
970
|
+
const flattenedTools = {};
|
|
971
|
+
Object.values(toolsByServer).forEach((serverTools) => {
|
|
972
|
+
Object.assign(flattenedTools, serverTools);
|
|
973
|
+
});
|
|
974
|
+
const streamingWrappedTools = wrapToolsWithStreaming(
|
|
975
|
+
flattenedTools,
|
|
976
|
+
streamingContext
|
|
977
|
+
);
|
|
978
|
+
const streamingAgent = new Agent({
|
|
979
|
+
name: agent.name,
|
|
980
|
+
instructions: agent.instructions,
|
|
981
|
+
model: agent.model,
|
|
982
|
+
tools: Object.keys(streamingWrappedTools).length > 0 ? streamingWrappedTools : void 0
|
|
983
|
+
});
|
|
984
|
+
mcpClientManager.setElicitationCallback(async (request) => {
|
|
985
|
+
const elicitationRequest = {
|
|
986
|
+
message: request.message,
|
|
987
|
+
requestedSchema: request.schema
|
|
988
|
+
};
|
|
989
|
+
if (streamingContext.controller && streamingContext.encoder) {
|
|
990
|
+
streamingContext.controller.enqueue(
|
|
991
|
+
streamingContext.encoder.encode(
|
|
992
|
+
`data: ${JSON.stringify({
|
|
993
|
+
type: "elicitation_request",
|
|
994
|
+
requestId: request.requestId,
|
|
995
|
+
message: elicitationRequest.message,
|
|
996
|
+
schema: elicitationRequest.requestedSchema,
|
|
997
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
998
|
+
})}
|
|
999
|
+
|
|
1000
|
+
`
|
|
1001
|
+
)
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
return new Promise((resolve, reject) => {
|
|
1005
|
+
pendingElicitations2.set(request.requestId, { resolve, reject });
|
|
1006
|
+
setTimeout(() => {
|
|
1007
|
+
if (pendingElicitations2.has(request.requestId)) {
|
|
1008
|
+
pendingElicitations2.delete(request.requestId);
|
|
1009
|
+
reject(new Error("Elicitation timeout"));
|
|
1010
|
+
}
|
|
1011
|
+
}, ELICITATION_TIMEOUT);
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
try {
|
|
1015
|
+
await createStreamingResponse(
|
|
1016
|
+
streamingAgent,
|
|
1017
|
+
formattedMessages,
|
|
1018
|
+
toolsByServer,
|
|
1019
|
+
streamingContext,
|
|
1020
|
+
provider
|
|
1021
|
+
);
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
controller.enqueue(
|
|
1024
|
+
encoder.encode(
|
|
1025
|
+
`data: ${JSON.stringify({
|
|
1026
|
+
type: "error",
|
|
1027
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1028
|
+
})}
|
|
1029
|
+
|
|
1030
|
+
`
|
|
1031
|
+
)
|
|
1032
|
+
);
|
|
1033
|
+
} finally {
|
|
1034
|
+
mcpClientManager.clearElicitationCallback();
|
|
1035
|
+
controller.close();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
return new Response(readableStream, {
|
|
1040
|
+
headers: {
|
|
1041
|
+
"Content-Type": "text/event-stream",
|
|
1042
|
+
"Cache-Control": "no-cache",
|
|
1043
|
+
Connection: "keep-alive"
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.error("[mcp/chat] Error in chat API:", error);
|
|
1048
|
+
mcpClientManager.clearElicitationCallback();
|
|
1049
|
+
return c.json(
|
|
1050
|
+
{
|
|
1051
|
+
success: false,
|
|
1052
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1053
|
+
},
|
|
1054
|
+
500
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
var chat_default = chat;
|
|
1059
|
+
|
|
1060
|
+
// routes/mcp/tests.ts
|
|
1061
|
+
import { Hono as Hono7 } from "hono";
|
|
1062
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
1063
|
+
import { join, dirname } from "path";
|
|
1064
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
1065
|
+
import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
1066
|
+
import { createOllama as createOllama2 } from "ollama-ai-provider";
|
|
1067
|
+
import { Agent as Agent2 } from "@mastra/core/agent";
|
|
1068
|
+
|
|
1069
|
+
// utils/mcp-utils.ts
|
|
1070
|
+
import { MCPClient } from "@mastra/mcp";
|
|
1071
|
+
function validateServerConfig(serverConfig) {
|
|
1072
|
+
if (!serverConfig) {
|
|
1073
|
+
return {
|
|
1074
|
+
success: false,
|
|
1075
|
+
error: {
|
|
1076
|
+
message: "Server configuration is required",
|
|
1077
|
+
status: 400
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
const config = { ...serverConfig };
|
|
1082
|
+
if (config.url) {
|
|
1083
|
+
try {
|
|
1084
|
+
if (typeof config.url === "string") {
|
|
1085
|
+
const parsed = new URL(config.url);
|
|
1086
|
+
parsed.search = "";
|
|
1087
|
+
parsed.hash = "";
|
|
1088
|
+
config.url = parsed;
|
|
1089
|
+
} else if (typeof config.url === "object" && !config.url.href) {
|
|
1090
|
+
return {
|
|
1091
|
+
success: false,
|
|
1092
|
+
error: {
|
|
1093
|
+
message: "Invalid URL configuration",
|
|
1094
|
+
status: 400
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
if (config.oauth?.access_token) {
|
|
1099
|
+
const authHeaders = {
|
|
1100
|
+
Authorization: `Bearer ${config.oauth.access_token}`,
|
|
1101
|
+
...config.requestInit?.headers || {}
|
|
1102
|
+
};
|
|
1103
|
+
config.requestInit = {
|
|
1104
|
+
...config.requestInit,
|
|
1105
|
+
headers: authHeaders
|
|
1106
|
+
};
|
|
1107
|
+
config.eventSourceInit = {
|
|
1108
|
+
fetch(input, init) {
|
|
1109
|
+
const headers = new Headers(init?.headers || {});
|
|
1110
|
+
headers.set(
|
|
1111
|
+
"Authorization",
|
|
1112
|
+
`Bearer ${config.oauth.access_token}`
|
|
1113
|
+
);
|
|
1114
|
+
if (config.requestInit?.headers) {
|
|
1115
|
+
const requestHeaders = new Headers(config.requestInit.headers);
|
|
1116
|
+
requestHeaders.forEach((value, key) => {
|
|
1117
|
+
if (key.toLowerCase() !== "authorization") {
|
|
1118
|
+
headers.set(key, value);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
return fetch(input, {
|
|
1123
|
+
...init,
|
|
1124
|
+
headers
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
} else if (config.requestInit?.headers) {
|
|
1129
|
+
config.eventSourceInit = {
|
|
1130
|
+
fetch(input, init) {
|
|
1131
|
+
const headers = new Headers(init?.headers || {});
|
|
1132
|
+
const requestHeaders = new Headers(config.requestInit.headers);
|
|
1133
|
+
requestHeaders.forEach((value, key) => {
|
|
1134
|
+
headers.set(key, value);
|
|
1135
|
+
});
|
|
1136
|
+
return fetch(input, {
|
|
1137
|
+
...init,
|
|
1138
|
+
headers
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
return {
|
|
1145
|
+
success: false,
|
|
1146
|
+
error: {
|
|
1147
|
+
message: `Invalid URL format: ${error}`,
|
|
1148
|
+
status: 400
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
success: true,
|
|
1155
|
+
config
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function generateUniqueServerID(serverName) {
|
|
1159
|
+
const normalizedBase = normalizeServerConfigName(serverName);
|
|
1160
|
+
const timestamp = Date.now().toString(36);
|
|
1161
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1162
|
+
return `${normalizedBase}_${timestamp}_${random}`;
|
|
1163
|
+
}
|
|
1164
|
+
var validateMultipleServerConfigs = (serverConfigs) => {
|
|
1165
|
+
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
1166
|
+
return {
|
|
1167
|
+
success: false,
|
|
1168
|
+
error: {
|
|
1169
|
+
message: "At least one server configuration is required",
|
|
1170
|
+
status: 400
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const validConfigs = {};
|
|
1175
|
+
const serverNameMapping = {};
|
|
1176
|
+
const errors = {};
|
|
1177
|
+
let hasErrors = false;
|
|
1178
|
+
for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
|
|
1179
|
+
const validationResult = validateServerConfig(serverConfig);
|
|
1180
|
+
if (validationResult.success && validationResult.config) {
|
|
1181
|
+
const serverID = generateUniqueServerID(serverName);
|
|
1182
|
+
validConfigs[serverID] = validationResult.config;
|
|
1183
|
+
serverNameMapping[serverID] = serverName;
|
|
1184
|
+
} else {
|
|
1185
|
+
hasErrors = true;
|
|
1186
|
+
let errorMessage = "Configuration validation failed";
|
|
1187
|
+
if (validationResult.error) {
|
|
1188
|
+
errorMessage = validationResult.error.message;
|
|
1189
|
+
}
|
|
1190
|
+
errors[serverName] = errorMessage;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (!hasErrors) {
|
|
1194
|
+
return {
|
|
1195
|
+
success: true,
|
|
1196
|
+
validConfigs,
|
|
1197
|
+
serverNameMapping
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
if (Object.keys(validConfigs).length > 0) {
|
|
1201
|
+
return {
|
|
1202
|
+
success: false,
|
|
1203
|
+
validConfigs,
|
|
1204
|
+
serverNameMapping,
|
|
1205
|
+
errors
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
success: false,
|
|
1210
|
+
errors,
|
|
1211
|
+
error: {
|
|
1212
|
+
message: "All server configurations failed validation",
|
|
1213
|
+
status: 400
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
};
|
|
1217
|
+
function createMCPClientWithMultipleConnections(serverConfigs) {
|
|
1218
|
+
const originalMCPClient = new MCPClient({
|
|
1219
|
+
id: `chat-${Date.now()}`,
|
|
1220
|
+
servers: serverConfigs
|
|
1221
|
+
});
|
|
1222
|
+
const originalGetTools = originalMCPClient.getTools.bind(originalMCPClient);
|
|
1223
|
+
originalMCPClient.getTools = async () => {
|
|
1224
|
+
const tools2 = await originalGetTools();
|
|
1225
|
+
const fixedTools = {};
|
|
1226
|
+
for (const [toolName, toolConfig] of Object.entries(tools2)) {
|
|
1227
|
+
const parts = toolName.split("_");
|
|
1228
|
+
if (parts.length >= 3 && parts[0] === parts[1]) {
|
|
1229
|
+
const fixedName = parts.slice(1).join("_");
|
|
1230
|
+
fixedTools[fixedName] = toolConfig;
|
|
1231
|
+
} else {
|
|
1232
|
+
fixedTools[toolName] = toolConfig;
|
|
1233
|
+
}
|
|
1005
1234
|
}
|
|
1006
|
-
|
|
1235
|
+
return fixedTools;
|
|
1236
|
+
};
|
|
1237
|
+
return originalMCPClient;
|
|
1238
|
+
}
|
|
1239
|
+
function normalizeServerConfigName(serverName) {
|
|
1240
|
+
return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// routes/mcp/tests.ts
|
|
1244
|
+
var tests = new Hono7();
|
|
1245
|
+
tests.post("/generate", async (c) => {
|
|
1246
|
+
try {
|
|
1247
|
+
const body = await c.req.json();
|
|
1248
|
+
const test = body?.test;
|
|
1249
|
+
const servers2 = body?.servers;
|
|
1250
|
+
const model = body?.model;
|
|
1251
|
+
if (!test?.id || !test?.prompt || !servers2 || Object.keys(servers2).length === 0) {
|
|
1007
1252
|
return c.json(
|
|
1008
|
-
{
|
|
1009
|
-
success: false,
|
|
1010
|
-
error: "No server configs provided"
|
|
1011
|
-
},
|
|
1253
|
+
{ success: false, error: "Missing test, servers, or prompt" },
|
|
1012
1254
|
400
|
|
1013
1255
|
);
|
|
1014
1256
|
}
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1257
|
+
const safeName = String(test.title || test.id).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1258
|
+
const filename = `@TestAgent_${safeName || test.id}.ts`;
|
|
1259
|
+
const fileContents = `import { Agent } from "@mastra/core/agent";
|
|
1260
|
+
import { MCPClient } from "@mastra/mcp";
|
|
1261
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1262
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
1263
|
+
import { createOllama } from "ollama-ai-provider";
|
|
1264
|
+
|
|
1265
|
+
const servers = ${JSON.stringify(servers2, null, 2)} as const;
|
|
1266
|
+
|
|
1267
|
+
function createModel() {
|
|
1268
|
+
const def = ${JSON.stringify(model || null)} as any;
|
|
1269
|
+
if (!def) throw new Error("Model not provided by UI when generating test agent");
|
|
1270
|
+
switch (def.provider) {
|
|
1271
|
+
case "anthropic": return createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })(def.id);
|
|
1272
|
+
case "openai": return createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })(def.id);
|
|
1273
|
+
case "deepseek": return createOpenAI({ apiKey: process.env.DEEPSEEK_API_KEY!, baseURL: "https://api.deepseek.com/v1" })(def.id);
|
|
1274
|
+
case "ollama": return createOllama({ baseURL: process.env.OLLAMA_BASE_URL || "http://localhost:11434" })(def.id, { simulateStreaming: true });
|
|
1275
|
+
default: throw new Error("Unsupported provider: " + def.provider);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export const createTestAgent = async () => {
|
|
1280
|
+
const mcp = new MCPClient({ servers });
|
|
1281
|
+
const toolsets = await mcp.getToolsets();
|
|
1282
|
+
return new Agent({
|
|
1283
|
+
name: ${JSON.stringify(test.title || "Test Agent")},
|
|
1284
|
+
instructions: ${JSON.stringify(test.prompt)},
|
|
1285
|
+
model: createModel(),
|
|
1286
|
+
tools: undefined,
|
|
1287
|
+
defaultGenerateOptions: { toolChoice: "auto" }
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
`;
|
|
1291
|
+
const targetPath = join(process.cwd(), "server", "agents", filename);
|
|
1292
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
1293
|
+
await writeFile(targetPath, fileContents, "utf8");
|
|
1294
|
+
return c.json({ success: true, file: `server/agents/${filename}` });
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1297
|
+
return c.json({ success: false, error: msg }, 500);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
var tests_default = tests;
|
|
1301
|
+
tests.post("/run-all", async (c) => {
|
|
1302
|
+
const encoder = new TextEncoder();
|
|
1303
|
+
try {
|
|
1304
|
+
let createModel2 = function(model) {
|
|
1305
|
+
switch (model.provider) {
|
|
1306
|
+
case "anthropic":
|
|
1307
|
+
return createAnthropic2({
|
|
1308
|
+
apiKey: providerApiKeys?.anthropic || process.env.ANTHROPIC_API_KEY || ""
|
|
1309
|
+
})(model.id);
|
|
1310
|
+
case "openai":
|
|
1311
|
+
return createOpenAI2({
|
|
1312
|
+
apiKey: providerApiKeys?.openai || process.env.OPENAI_API_KEY || ""
|
|
1313
|
+
})(model.id);
|
|
1314
|
+
case "deepseek":
|
|
1315
|
+
return createOpenAI2({
|
|
1316
|
+
apiKey: providerApiKeys?.deepseek || process.env.DEEPSEEK_API_KEY || "",
|
|
1317
|
+
baseURL: "https://api.deepseek.com/v1"
|
|
1318
|
+
})(model.id);
|
|
1319
|
+
case "ollama":
|
|
1320
|
+
return createOllama2({
|
|
1321
|
+
baseURL: ollamaBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434"
|
|
1322
|
+
})(model.id, { simulateStreaming: true });
|
|
1323
|
+
default:
|
|
1324
|
+
throw new Error(`Unsupported provider: ${model.provider}`);
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
var createModel = createModel2;
|
|
1328
|
+
const body = await c.req.json();
|
|
1329
|
+
const testsInput = body?.tests || [];
|
|
1330
|
+
const allServers = body?.allServers || {};
|
|
1331
|
+
const providerApiKeys = body?.providerApiKeys || {};
|
|
1332
|
+
const ollamaBaseUrl = body?.ollamaBaseUrl;
|
|
1333
|
+
const maxConcurrency = Math.max(
|
|
1334
|
+
1,
|
|
1335
|
+
Math.min(8, body?.concurrency ?? 5)
|
|
1336
|
+
);
|
|
1337
|
+
if (!Array.isArray(testsInput) || testsInput.length === 0) {
|
|
1338
|
+
return c.json({ success: false, error: "No tests provided" }, 400);
|
|
1029
1339
|
}
|
|
1030
|
-
client = createMCPClientWithMultipleConnections(validation.validConfigs);
|
|
1031
|
-
const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
|
|
1032
|
-
const tools2 = await client.getTools();
|
|
1033
|
-
const agent = new Agent({
|
|
1034
|
-
name: "MCP Chat Agent",
|
|
1035
|
-
instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
|
|
1036
|
-
model: llmModel,
|
|
1037
|
-
tools: void 0
|
|
1038
|
-
// Start without tools, add them in streaming context
|
|
1039
|
-
});
|
|
1040
|
-
const formattedMessages = messages.map((msg) => ({
|
|
1041
|
-
role: msg.role,
|
|
1042
|
-
content: msg.content
|
|
1043
|
-
}));
|
|
1044
|
-
const toolsets = await client.getToolsets();
|
|
1045
|
-
dbg("Streaming start", {
|
|
1046
|
-
toolsetServers: Object.keys(toolsets),
|
|
1047
|
-
messageCount: formattedMessages.length
|
|
1048
|
-
});
|
|
1049
|
-
const encoder = new TextEncoder2();
|
|
1050
1340
|
const readableStream = new ReadableStream({
|
|
1051
1341
|
async start(controller) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
const elicitationHandler = createElicitationHandler(streamingContext);
|
|
1072
|
-
client.elicitation.onRequest(normalizedName, elicitationHandler);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
try {
|
|
1076
|
-
if (client) {
|
|
1077
|
-
await createStreamingResponse(
|
|
1078
|
-
streamingAgent,
|
|
1079
|
-
formattedMessages,
|
|
1080
|
-
toolsets,
|
|
1081
|
-
streamingContext,
|
|
1082
|
-
provider
|
|
1083
|
-
);
|
|
1084
|
-
} else {
|
|
1085
|
-
throw new Error("MCP client is null");
|
|
1342
|
+
let active = 0;
|
|
1343
|
+
let index = 0;
|
|
1344
|
+
let failed = false;
|
|
1345
|
+
const runNext = async () => {
|
|
1346
|
+
if (index >= testsInput.length) {
|
|
1347
|
+
if (active === 0) {
|
|
1348
|
+
controller.enqueue(
|
|
1349
|
+
encoder.encode(
|
|
1350
|
+
`data: ${JSON.stringify({ type: "run_complete", passed: !failed })}
|
|
1351
|
+
|
|
1352
|
+
`
|
|
1353
|
+
)
|
|
1354
|
+
);
|
|
1355
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
1356
|
+
|
|
1357
|
+
`));
|
|
1358
|
+
controller.close();
|
|
1359
|
+
}
|
|
1360
|
+
return;
|
|
1086
1361
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1362
|
+
const test = testsInput[index++];
|
|
1363
|
+
active++;
|
|
1364
|
+
(async () => {
|
|
1365
|
+
const calledTools = /* @__PURE__ */ new Set();
|
|
1366
|
+
const expectedSet = new Set(test.expectedTools || []);
|
|
1367
|
+
let step = 0;
|
|
1368
|
+
let client = null;
|
|
1369
|
+
try {
|
|
1370
|
+
let serverConfigs = {};
|
|
1371
|
+
if (test.selectedServers && test.selectedServers.length > 0) {
|
|
1372
|
+
for (const name of test.selectedServers) {
|
|
1373
|
+
if (allServers[name]) serverConfigs[name] = allServers[name];
|
|
1374
|
+
}
|
|
1375
|
+
} else {
|
|
1376
|
+
for (const [name, cfg] of Object.entries(allServers)) {
|
|
1377
|
+
serverConfigs[name] = cfg;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
const validation = validateMultipleServerConfigs(serverConfigs);
|
|
1381
|
+
let finalServers = {};
|
|
1382
|
+
if (validation.success && validation.validConfigs) {
|
|
1383
|
+
finalServers = validation.validConfigs;
|
|
1384
|
+
} else if (validation.validConfigs && Object.keys(validation.validConfigs).length > 0) {
|
|
1385
|
+
finalServers = validation.validConfigs;
|
|
1386
|
+
} else {
|
|
1387
|
+
throw new Error("No valid MCP server configs for test");
|
|
1388
|
+
}
|
|
1389
|
+
client = createMCPClientWithMultipleConnections(finalServers);
|
|
1390
|
+
const model = createModel2(test.model);
|
|
1391
|
+
const agent = new Agent2({
|
|
1392
|
+
name: `TestAgent-${test.id}`,
|
|
1393
|
+
instructions: "You are a helpful assistant with access to MCP tools",
|
|
1394
|
+
model
|
|
1395
|
+
});
|
|
1396
|
+
const toolsets = await client.getToolsets();
|
|
1397
|
+
const stream = await agent.stream(
|
|
1398
|
+
[{ role: "user", content: test.prompt || "" }],
|
|
1399
|
+
{
|
|
1400
|
+
maxSteps: 10,
|
|
1401
|
+
toolsets,
|
|
1402
|
+
onStepFinish: ({ text, toolCalls, toolResults }) => {
|
|
1403
|
+
step += 1;
|
|
1404
|
+
(toolCalls || []).forEach((c2) => {
|
|
1405
|
+
const toolName = c2?.name || c2?.toolName;
|
|
1406
|
+
if (toolName) {
|
|
1407
|
+
calledTools.add(toolName);
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
controller.enqueue(
|
|
1411
|
+
encoder.encode(
|
|
1412
|
+
`data: ${JSON.stringify({
|
|
1413
|
+
type: "trace_step",
|
|
1414
|
+
testId: test.id,
|
|
1415
|
+
step,
|
|
1416
|
+
text,
|
|
1417
|
+
toolCalls,
|
|
1418
|
+
toolResults
|
|
1419
|
+
})}
|
|
1094
1420
|
|
|
1095
1421
|
`
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1422
|
+
)
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
);
|
|
1427
|
+
for await (const _ of stream.textStream) {
|
|
1428
|
+
}
|
|
1429
|
+
const called = Array.from(calledTools);
|
|
1430
|
+
const missing = Array.from(expectedSet).filter(
|
|
1431
|
+
(t) => !calledTools.has(t)
|
|
1432
|
+
);
|
|
1433
|
+
const unexpected = called.filter((t) => !expectedSet.has(t));
|
|
1434
|
+
const passed = missing.length === 0 && unexpected.length === 0;
|
|
1435
|
+
if (!passed) failed = true;
|
|
1436
|
+
controller.enqueue(
|
|
1437
|
+
encoder.encode(
|
|
1438
|
+
`data: ${JSON.stringify({ type: "result", testId: test.id, passed, calledTools: called, missingTools: missing, unexpectedTools: unexpected })}
|
|
1439
|
+
|
|
1440
|
+
`
|
|
1441
|
+
)
|
|
1442
|
+
);
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
failed = true;
|
|
1445
|
+
controller.enqueue(
|
|
1446
|
+
encoder.encode(
|
|
1447
|
+
`data: ${JSON.stringify({ type: "result", testId: test.id, passed: false, error: err?.message })}
|
|
1448
|
+
|
|
1449
|
+
`
|
|
1450
|
+
)
|
|
1451
|
+
);
|
|
1452
|
+
} finally {
|
|
1453
|
+
try {
|
|
1454
|
+
await client?.disconnect();
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
active--;
|
|
1458
|
+
runNext();
|
|
1459
|
+
}
|
|
1460
|
+
})();
|
|
1461
|
+
};
|
|
1462
|
+
for (let i = 0; i < Math.min(maxConcurrency, testsInput.length); i++) {
|
|
1463
|
+
runNext();
|
|
1101
1464
|
}
|
|
1102
1465
|
}
|
|
1103
1466
|
});
|
|
@@ -1108,23 +1471,17 @@ chat.post("/", async (c) => {
|
|
|
1108
1471
|
Connection: "keep-alive"
|
|
1109
1472
|
}
|
|
1110
1473
|
});
|
|
1111
|
-
} catch (
|
|
1112
|
-
console.error("[mcp/chat] Error in chat API:", error);
|
|
1113
|
-
await safeDisconnect(client);
|
|
1474
|
+
} catch (err) {
|
|
1114
1475
|
return c.json(
|
|
1115
|
-
{
|
|
1116
|
-
success: false,
|
|
1117
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1118
|
-
},
|
|
1476
|
+
{ success: false, error: err?.message || "Unknown error" },
|
|
1119
1477
|
500
|
|
1120
1478
|
);
|
|
1121
1479
|
}
|
|
1122
1480
|
});
|
|
1123
|
-
var chat_default = chat;
|
|
1124
1481
|
|
|
1125
1482
|
// routes/mcp/oauth.ts
|
|
1126
|
-
import { Hono as
|
|
1127
|
-
var oauth = new
|
|
1483
|
+
import { Hono as Hono8 } from "hono";
|
|
1484
|
+
var oauth = new Hono8();
|
|
1128
1485
|
oauth.get("/metadata", async (c) => {
|
|
1129
1486
|
try {
|
|
1130
1487
|
const url = c.req.query("url");
|
|
@@ -1170,7 +1527,7 @@ oauth.get("/metadata", async (c) => {
|
|
|
1170
1527
|
var oauth_default = oauth;
|
|
1171
1528
|
|
|
1172
1529
|
// routes/mcp/index.ts
|
|
1173
|
-
var mcp = new
|
|
1530
|
+
var mcp = new Hono9();
|
|
1174
1531
|
mcp.get("/health", (c) => {
|
|
1175
1532
|
return c.json({
|
|
1176
1533
|
service: "MCP API",
|
|
@@ -1180,12 +1537,378 @@ mcp.get("/health", (c) => {
|
|
|
1180
1537
|
});
|
|
1181
1538
|
mcp.route("/chat", chat_default);
|
|
1182
1539
|
mcp.route("/connect", connect_default);
|
|
1540
|
+
mcp.route("/servers", servers_default);
|
|
1183
1541
|
mcp.route("/tools", tools_default);
|
|
1542
|
+
mcp.route("/tests", tests_default);
|
|
1184
1543
|
mcp.route("/resources", resources_default);
|
|
1185
1544
|
mcp.route("/prompts", prompts_default);
|
|
1186
1545
|
mcp.route("/oauth", oauth_default);
|
|
1187
1546
|
var mcp_default = mcp;
|
|
1188
1547
|
|
|
1548
|
+
// services/mcpjam-client-manager.ts
|
|
1549
|
+
import { MCPClient as MCPClient2 } from "@mastra/mcp";
|
|
1550
|
+
function generateUniqueServerId(serverId) {
|
|
1551
|
+
const normalizedBase = serverId.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
1552
|
+
const timestamp = Date.now().toString(36);
|
|
1553
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1554
|
+
return `${normalizedBase}_${timestamp}_${random}`;
|
|
1555
|
+
}
|
|
1556
|
+
var MCPJamClientManager = class {
|
|
1557
|
+
mcpClients = /* @__PURE__ */ new Map();
|
|
1558
|
+
statuses = /* @__PURE__ */ new Map();
|
|
1559
|
+
configs = /* @__PURE__ */ new Map();
|
|
1560
|
+
// Map original server names to unique IDs
|
|
1561
|
+
serverIdMapping = /* @__PURE__ */ new Map();
|
|
1562
|
+
// Track in-flight connections to avoid duplicate concurrent connects
|
|
1563
|
+
pendingConnections = /* @__PURE__ */ new Map();
|
|
1564
|
+
toolRegistry = /* @__PURE__ */ new Map();
|
|
1565
|
+
resourceRegistry = /* @__PURE__ */ new Map();
|
|
1566
|
+
promptRegistry = /* @__PURE__ */ new Map();
|
|
1567
|
+
// Store for pending elicitation requests with Promise resolvers
|
|
1568
|
+
pendingElicitations = /* @__PURE__ */ new Map();
|
|
1569
|
+
// Optional callback for handling elicitation requests
|
|
1570
|
+
elicitationCallback;
|
|
1571
|
+
// Helper method to get unique ID for a server name
|
|
1572
|
+
getServerUniqueId(serverName) {
|
|
1573
|
+
return this.serverIdMapping.get(serverName);
|
|
1574
|
+
}
|
|
1575
|
+
// Public method to get server ID for external use (like frontend)
|
|
1576
|
+
getServerIdForName(serverName) {
|
|
1577
|
+
return this.serverIdMapping.get(serverName);
|
|
1578
|
+
}
|
|
1579
|
+
async connectToServer(serverId, serverConfig) {
|
|
1580
|
+
const pending = this.pendingConnections.get(serverId);
|
|
1581
|
+
if (pending) {
|
|
1582
|
+
await pending;
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const connectPromise = (async () => {
|
|
1586
|
+
let id = this.serverIdMapping.get(serverId);
|
|
1587
|
+
if (!id) {
|
|
1588
|
+
id = generateUniqueServerId(serverId);
|
|
1589
|
+
this.serverIdMapping.set(serverId, id);
|
|
1590
|
+
}
|
|
1591
|
+
if (this.mcpClients.has(id)) return;
|
|
1592
|
+
const validation = validateServerConfig(serverConfig);
|
|
1593
|
+
if (!validation.success) {
|
|
1594
|
+
this.statuses.set(id, "error");
|
|
1595
|
+
throw new Error(validation.error.message);
|
|
1596
|
+
}
|
|
1597
|
+
this.configs.set(id, validation.config);
|
|
1598
|
+
this.statuses.set(id, "connecting");
|
|
1599
|
+
const client = new MCPClient2({
|
|
1600
|
+
id: `mcpjam-${id}`,
|
|
1601
|
+
servers: { [id]: validation.config }
|
|
1602
|
+
});
|
|
1603
|
+
try {
|
|
1604
|
+
await client.getTools();
|
|
1605
|
+
this.mcpClients.set(id, client);
|
|
1606
|
+
this.statuses.set(id, "connected");
|
|
1607
|
+
if (client.elicitation?.onRequest) {
|
|
1608
|
+
client.elicitation.onRequest(
|
|
1609
|
+
id,
|
|
1610
|
+
async (elicitationRequest) => {
|
|
1611
|
+
return await this.handleElicitationRequest(elicitationRequest);
|
|
1612
|
+
}
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
await this.discoverServerResources(id);
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
this.statuses.set(id, "error");
|
|
1618
|
+
try {
|
|
1619
|
+
await client.disconnect();
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
this.mcpClients.delete(id);
|
|
1623
|
+
throw err;
|
|
1624
|
+
}
|
|
1625
|
+
})().finally(() => {
|
|
1626
|
+
this.pendingConnections.delete(serverId);
|
|
1627
|
+
});
|
|
1628
|
+
this.pendingConnections.set(serverId, connectPromise);
|
|
1629
|
+
await connectPromise;
|
|
1630
|
+
}
|
|
1631
|
+
async disconnectFromServer(serverId) {
|
|
1632
|
+
const id = this.getServerUniqueId(serverId);
|
|
1633
|
+
if (!id) return;
|
|
1634
|
+
const client = this.mcpClients.get(id);
|
|
1635
|
+
if (client) {
|
|
1636
|
+
try {
|
|
1637
|
+
await client.disconnect();
|
|
1638
|
+
} catch {
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
this.mcpClients.delete(id);
|
|
1642
|
+
this.statuses.set(id, "disconnected");
|
|
1643
|
+
this.serverIdMapping.delete(serverId);
|
|
1644
|
+
for (const key of Array.from(this.toolRegistry.keys())) {
|
|
1645
|
+
const item = this.toolRegistry.get(key);
|
|
1646
|
+
if (item.serverId === id) this.toolRegistry.delete(key);
|
|
1647
|
+
}
|
|
1648
|
+
for (const key of Array.from(this.resourceRegistry.keys())) {
|
|
1649
|
+
const item = this.resourceRegistry.get(key);
|
|
1650
|
+
if (item.serverId === id) this.resourceRegistry.delete(key);
|
|
1651
|
+
}
|
|
1652
|
+
for (const key of Array.from(this.promptRegistry.keys())) {
|
|
1653
|
+
const item = this.promptRegistry.get(key);
|
|
1654
|
+
if (item.serverId === id) this.promptRegistry.delete(key);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
getConnectionStatus(serverId) {
|
|
1658
|
+
const id = this.getServerUniqueId(serverId);
|
|
1659
|
+
return id ? this.statuses.get(id) || "disconnected" : "disconnected";
|
|
1660
|
+
}
|
|
1661
|
+
getConnectedServers() {
|
|
1662
|
+
const servers2 = {};
|
|
1663
|
+
for (const [originalName, uniqueId] of this.serverIdMapping.entries()) {
|
|
1664
|
+
servers2[originalName] = {
|
|
1665
|
+
status: this.statuses.get(uniqueId) || "disconnected",
|
|
1666
|
+
config: this.configs.get(uniqueId)
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
return servers2;
|
|
1670
|
+
}
|
|
1671
|
+
async discoverAllResources() {
|
|
1672
|
+
const serverIds = Array.from(this.mcpClients.keys());
|
|
1673
|
+
await Promise.all(serverIds.map((id) => this.discoverServerResources(id)));
|
|
1674
|
+
}
|
|
1675
|
+
async discoverServerResources(serverId) {
|
|
1676
|
+
const client = this.mcpClients.get(serverId);
|
|
1677
|
+
if (!client) return;
|
|
1678
|
+
const toolsets = await client.getToolsets();
|
|
1679
|
+
const flattenedTools = {};
|
|
1680
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1681
|
+
Object.assign(flattenedTools, serverTools);
|
|
1682
|
+
});
|
|
1683
|
+
for (const [name, tool] of Object.entries(flattenedTools)) {
|
|
1684
|
+
this.toolRegistry.set(`${serverId}:${name}`, {
|
|
1685
|
+
name,
|
|
1686
|
+
description: tool.description,
|
|
1687
|
+
inputSchema: tool.inputSchema,
|
|
1688
|
+
outputSchema: tool.outputSchema,
|
|
1689
|
+
serverId
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
const res = await client.resources.list();
|
|
1694
|
+
for (const [, list] of Object.entries(res)) {
|
|
1695
|
+
for (const r of list) {
|
|
1696
|
+
this.resourceRegistry.set(`${serverId}:${r.uri}`, {
|
|
1697
|
+
uri: r.uri,
|
|
1698
|
+
name: r.name,
|
|
1699
|
+
description: r.description,
|
|
1700
|
+
mimeType: r.mimeType,
|
|
1701
|
+
serverId
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
} catch {
|
|
1706
|
+
}
|
|
1707
|
+
try {
|
|
1708
|
+
const prompts2 = await client.prompts.list();
|
|
1709
|
+
for (const [, list] of Object.entries(prompts2)) {
|
|
1710
|
+
for (const p of list) {
|
|
1711
|
+
this.promptRegistry.set(`${serverId}:${p.name}`, {
|
|
1712
|
+
name: p.name,
|
|
1713
|
+
description: p.description,
|
|
1714
|
+
arguments: p.arguments,
|
|
1715
|
+
serverId
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
} catch {
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
getAvailableTools() {
|
|
1723
|
+
return Array.from(this.toolRegistry.values());
|
|
1724
|
+
}
|
|
1725
|
+
async getToolsetsForServer(serverId) {
|
|
1726
|
+
const id = this.getServerUniqueId(serverId);
|
|
1727
|
+
if (!id) {
|
|
1728
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1729
|
+
}
|
|
1730
|
+
const client = this.mcpClients.get(id);
|
|
1731
|
+
if (!client) {
|
|
1732
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1733
|
+
}
|
|
1734
|
+
const toolsets = await client.getToolsets();
|
|
1735
|
+
const flattenedTools = {};
|
|
1736
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1737
|
+
Object.assign(flattenedTools, serverTools);
|
|
1738
|
+
});
|
|
1739
|
+
return flattenedTools;
|
|
1740
|
+
}
|
|
1741
|
+
getAvailableResources() {
|
|
1742
|
+
return Array.from(this.resourceRegistry.values());
|
|
1743
|
+
}
|
|
1744
|
+
getResourcesForServer(serverId) {
|
|
1745
|
+
const id = this.getServerUniqueId(serverId);
|
|
1746
|
+
if (!id) return [];
|
|
1747
|
+
return Array.from(this.resourceRegistry.values()).filter(
|
|
1748
|
+
(r) => r.serverId === id
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
getAvailablePrompts() {
|
|
1752
|
+
return Array.from(this.promptRegistry.values());
|
|
1753
|
+
}
|
|
1754
|
+
getPromptsForServer(serverId) {
|
|
1755
|
+
const id = this.getServerUniqueId(serverId);
|
|
1756
|
+
if (!id) return [];
|
|
1757
|
+
return Array.from(this.promptRegistry.values()).filter(
|
|
1758
|
+
(p) => p.serverId === id
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
async executeToolDirect(toolName, parameters = {}) {
|
|
1762
|
+
let serverId = "";
|
|
1763
|
+
let name = toolName;
|
|
1764
|
+
if (toolName.includes(":")) {
|
|
1765
|
+
const [sid, n] = toolName.split(":", 2);
|
|
1766
|
+
const mappedId = this.getServerUniqueId(sid);
|
|
1767
|
+
serverId = mappedId || (this.mcpClients.has(sid) ? sid : "");
|
|
1768
|
+
name = n;
|
|
1769
|
+
} else {
|
|
1770
|
+
for (const tool2 of this.toolRegistry.values()) {
|
|
1771
|
+
if (tool2.name === toolName) {
|
|
1772
|
+
serverId = tool2.serverId;
|
|
1773
|
+
name = toolName;
|
|
1774
|
+
break;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (!serverId) {
|
|
1779
|
+
for (const [clientServerId, client2] of this.mcpClients.entries()) {
|
|
1780
|
+
try {
|
|
1781
|
+
const toolsets2 = await client2.getToolsets();
|
|
1782
|
+
const flattenedTools2 = {};
|
|
1783
|
+
Object.values(toolsets2).forEach((serverTools) => {
|
|
1784
|
+
Object.assign(flattenedTools2, serverTools);
|
|
1785
|
+
});
|
|
1786
|
+
if (flattenedTools2[toolName]) {
|
|
1787
|
+
serverId = clientServerId;
|
|
1788
|
+
name = toolName;
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (!serverId) {
|
|
1796
|
+
throw new Error(`Tool not found in any connected server: ${toolName}`);
|
|
1797
|
+
}
|
|
1798
|
+
const client = this.mcpClients.get(serverId);
|
|
1799
|
+
if (!client)
|
|
1800
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1801
|
+
const toolsets = await client.getToolsets();
|
|
1802
|
+
const flattenedTools = {};
|
|
1803
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1804
|
+
Object.assign(flattenedTools, serverTools);
|
|
1805
|
+
});
|
|
1806
|
+
const tool = flattenedTools[name];
|
|
1807
|
+
if (!tool)
|
|
1808
|
+
throw new Error(`Tool '${name}' not found in server '${serverId}'`);
|
|
1809
|
+
const schema = tool.inputSchema;
|
|
1810
|
+
const hasContextProperty = schema && typeof schema === "object" && schema.properties && Object.prototype.hasOwnProperty.call(
|
|
1811
|
+
schema.properties,
|
|
1812
|
+
"context"
|
|
1813
|
+
);
|
|
1814
|
+
const requiresContext = hasContextProperty || schema && Array.isArray(schema.required) && schema.required.includes("context");
|
|
1815
|
+
const contextWrapped = { context: parameters || {} };
|
|
1816
|
+
const direct = parameters || {};
|
|
1817
|
+
const attempts = requiresContext ? [contextWrapped, direct] : [direct, contextWrapped];
|
|
1818
|
+
let lastError = void 0;
|
|
1819
|
+
for (const args of attempts) {
|
|
1820
|
+
try {
|
|
1821
|
+
const result = await tool.execute(args);
|
|
1822
|
+
return { result };
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
lastError = err;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
throw lastError;
|
|
1828
|
+
}
|
|
1829
|
+
async getResource(resourceUri, serverId) {
|
|
1830
|
+
let uri = resourceUri;
|
|
1831
|
+
const mappedId = this.getServerUniqueId(serverId);
|
|
1832
|
+
const resolvedServerId = mappedId || (this.mcpClients.has(serverId) ? serverId : void 0);
|
|
1833
|
+
if (!resolvedServerId) {
|
|
1834
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1835
|
+
}
|
|
1836
|
+
const client = this.mcpClients.get(resolvedServerId);
|
|
1837
|
+
if (!client) throw new Error("No MCP client available");
|
|
1838
|
+
const content = await client.resources.read(resolvedServerId, uri);
|
|
1839
|
+
return { contents: content?.contents || [] };
|
|
1840
|
+
}
|
|
1841
|
+
async getPrompt(promptName, serverId, args) {
|
|
1842
|
+
const mappedId = this.getServerUniqueId(serverId);
|
|
1843
|
+
const resolvedServerId = mappedId || (this.mcpClients.has(serverId) ? serverId : void 0);
|
|
1844
|
+
if (!resolvedServerId) {
|
|
1845
|
+
throw new Error(`No MCP client available for server: ${serverId}`);
|
|
1846
|
+
}
|
|
1847
|
+
const client = this.mcpClients.get(resolvedServerId);
|
|
1848
|
+
if (!client) throw new Error("No MCP client available");
|
|
1849
|
+
const content = await client.prompts.get({
|
|
1850
|
+
serverName: resolvedServerId,
|
|
1851
|
+
name: promptName,
|
|
1852
|
+
args: args || {}
|
|
1853
|
+
});
|
|
1854
|
+
return { content };
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Handles elicitation requests from MCP servers during direct tool execution
|
|
1858
|
+
*/
|
|
1859
|
+
async handleElicitationRequest(elicitationRequest) {
|
|
1860
|
+
const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1861
|
+
return new Promise((resolve, reject) => {
|
|
1862
|
+
this.pendingElicitations.set(requestId, { resolve, reject });
|
|
1863
|
+
if (this.elicitationCallback) {
|
|
1864
|
+
this.elicitationCallback({
|
|
1865
|
+
requestId,
|
|
1866
|
+
message: elicitationRequest.message,
|
|
1867
|
+
schema: elicitationRequest.requestedSchema
|
|
1868
|
+
}).then(resolve).catch(reject);
|
|
1869
|
+
} else {
|
|
1870
|
+
const error = new Error("ELICITATION_REQUIRED");
|
|
1871
|
+
error.elicitationRequest = {
|
|
1872
|
+
requestId,
|
|
1873
|
+
message: elicitationRequest.message,
|
|
1874
|
+
schema: elicitationRequest.requestedSchema
|
|
1875
|
+
};
|
|
1876
|
+
reject(error);
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Responds to a pending elicitation request
|
|
1882
|
+
*/
|
|
1883
|
+
respondToElicitation(requestId, response) {
|
|
1884
|
+
const pending = this.pendingElicitations.get(requestId);
|
|
1885
|
+
if (!pending) {
|
|
1886
|
+
return false;
|
|
1887
|
+
}
|
|
1888
|
+
pending.resolve(response);
|
|
1889
|
+
this.pendingElicitations.delete(requestId);
|
|
1890
|
+
return true;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Gets the pending elicitations map for external access
|
|
1894
|
+
*/
|
|
1895
|
+
getPendingElicitations() {
|
|
1896
|
+
return this.pendingElicitations;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Sets a callback to handle elicitation requests
|
|
1900
|
+
*/
|
|
1901
|
+
setElicitationCallback(callback) {
|
|
1902
|
+
this.elicitationCallback = callback;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Clears the elicitation callback
|
|
1906
|
+
*/
|
|
1907
|
+
clearElicitationCallback() {
|
|
1908
|
+
this.elicitationCallback = void 0;
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1189
1912
|
// index.ts
|
|
1190
1913
|
function logBox(content, title) {
|
|
1191
1914
|
const lines = content.split("\n");
|
|
@@ -1219,7 +1942,12 @@ function getMCPConfigFromEnv() {
|
|
|
1219
1942
|
// Default name for CLI-provided servers
|
|
1220
1943
|
};
|
|
1221
1944
|
}
|
|
1222
|
-
var app = new
|
|
1945
|
+
var app = new Hono10();
|
|
1946
|
+
var mcpJamClientManager = new MCPJamClientManager();
|
|
1947
|
+
app.use("*", async (c, next) => {
|
|
1948
|
+
c.mcpJamClientManager = mcpJamClientManager;
|
|
1949
|
+
await next();
|
|
1950
|
+
});
|
|
1223
1951
|
app.use("*", logger());
|
|
1224
1952
|
var serverPort = process.env.PORT || "3001";
|
|
1225
1953
|
var corsOrigins = [
|
|
@@ -1251,7 +1979,7 @@ if (process.env.NODE_ENV === "production") {
|
|
|
1251
1979
|
if (path.startsWith("/api/")) {
|
|
1252
1980
|
return c.notFound();
|
|
1253
1981
|
}
|
|
1254
|
-
const indexPath =
|
|
1982
|
+
const indexPath = join2(process.cwd(), "dist", "client", "index.html");
|
|
1255
1983
|
let htmlContent = readFileSync(indexPath, "utf-8");
|
|
1256
1984
|
const mcpConfig = getMCPConfigFromEnv();
|
|
1257
1985
|
if (mcpConfig) {
|