@mcpjam/inspector 0.9.4 → 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/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/index.html +2 -2
- package/dist/server/index.js +1059 -421
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -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,61 +502,215 @@ prompts.post("/list", async (c) => {
|
|
|
595
502
|
});
|
|
596
503
|
prompts.post("/get", async (c) => {
|
|
597
504
|
try {
|
|
598
|
-
const {
|
|
599
|
-
|
|
600
|
-
|
|
505
|
+
const { serverId, name, args } = await c.req.json();
|
|
506
|
+
if (!serverId) {
|
|
507
|
+
return c.json({ success: false, error: "serverId is required" }, 400);
|
|
508
|
+
}
|
|
509
|
+
if (!name) {
|
|
601
510
|
return c.json(
|
|
602
|
-
{
|
|
603
|
-
|
|
511
|
+
{
|
|
512
|
+
success: false,
|
|
513
|
+
error: "Prompt name is required"
|
|
514
|
+
},
|
|
515
|
+
400
|
|
604
516
|
);
|
|
605
517
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
518
|
+
const mcpJamClientManager2 = c.mcpJamClientManager;
|
|
519
|
+
const content = await mcpJamClientManager2.getPrompt(
|
|
520
|
+
name,
|
|
521
|
+
serverId,
|
|
522
|
+
args || {}
|
|
523
|
+
);
|
|
524
|
+
return c.json({ content });
|
|
525
|
+
} catch (error) {
|
|
526
|
+
console.error("Error getting prompt:", error);
|
|
527
|
+
return c.json(
|
|
528
|
+
{
|
|
529
|
+
success: false,
|
|
530
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
531
|
+
},
|
|
532
|
+
500
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
var prompts_default = prompts;
|
|
537
|
+
|
|
538
|
+
// routes/mcp/chat.ts
|
|
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
|
|
614
675
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
}
|
|
630
695
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
500
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
var prompts_default = prompts;
|
|
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
|
+
}
|
|
643
703
|
|
|
644
704
|
// routes/mcp/chat.ts
|
|
645
|
-
import { Hono as Hono5 } from "hono";
|
|
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 { TextEncoder as
|
|
709
|
+
import { TextEncoder as TextEncoder3 } from "util";
|
|
651
710
|
|
|
652
711
|
// ../client/src/lib/chat-utils.ts
|
|
712
|
+
import { clsx } from "clsx";
|
|
713
|
+
import { twMerge } from "tailwind-merge";
|
|
653
714
|
function getDefaultTemperatureByProvider(provider) {
|
|
654
715
|
switch (provider) {
|
|
655
716
|
case "openai":
|
|
@@ -673,7 +734,7 @@ try {
|
|
|
673
734
|
} catch {
|
|
674
735
|
}
|
|
675
736
|
var pendingElicitations2 = /* @__PURE__ */ new Map();
|
|
676
|
-
var chat = new
|
|
737
|
+
var chat = new Hono6();
|
|
677
738
|
var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
678
739
|
if (!modelDefinition?.id || !modelDefinition?.provider) {
|
|
679
740
|
throw new Error(
|
|
@@ -862,6 +923,29 @@ var handleAgentStepFinish = (streamingContext, text, toolCalls, toolResults) =>
|
|
|
862
923
|
}
|
|
863
924
|
}
|
|
864
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
|
+
}
|
|
865
949
|
} catch (err) {
|
|
866
950
|
dbg("onStepFinish error", err);
|
|
867
951
|
}
|
|
@@ -1029,7 +1113,6 @@ chat.post("/", async (c) => {
|
|
|
1029
1113
|
}
|
|
1030
1114
|
client = createMCPClientWithMultipleConnections(validation.validConfigs);
|
|
1031
1115
|
const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
|
|
1032
|
-
const tools2 = await client.getTools();
|
|
1033
1116
|
const agent = new Agent({
|
|
1034
1117
|
name: "MCP Chat Agent",
|
|
1035
1118
|
instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
|
|
@@ -1046,17 +1129,22 @@ chat.post("/", async (c) => {
|
|
|
1046
1129
|
toolsetServers: Object.keys(toolsets),
|
|
1047
1130
|
messageCount: formattedMessages.length
|
|
1048
1131
|
});
|
|
1049
|
-
const encoder = new
|
|
1132
|
+
const encoder = new TextEncoder3();
|
|
1050
1133
|
const readableStream = new ReadableStream({
|
|
1051
1134
|
async start(controller) {
|
|
1052
1135
|
const streamingContext = {
|
|
1053
1136
|
controller,
|
|
1054
1137
|
encoder,
|
|
1055
1138
|
toolCallId: 0,
|
|
1056
|
-
lastEmittedToolCallId: null
|
|
1139
|
+
lastEmittedToolCallId: null,
|
|
1140
|
+
stepIndex: 0
|
|
1057
1141
|
};
|
|
1142
|
+
const flattenedTools = {};
|
|
1143
|
+
Object.values(toolsets).forEach((serverTools) => {
|
|
1144
|
+
Object.assign(flattenedTools, serverTools);
|
|
1145
|
+
});
|
|
1058
1146
|
const streamingWrappedTools = wrapToolsWithStreaming(
|
|
1059
|
-
|
|
1147
|
+
flattenedTools,
|
|
1060
1148
|
streamingContext
|
|
1061
1149
|
);
|
|
1062
1150
|
const streamingAgent = new Agent({
|
|
@@ -1122,9 +1210,255 @@ chat.post("/", async (c) => {
|
|
|
1122
1210
|
});
|
|
1123
1211
|
var chat_default = chat;
|
|
1124
1212
|
|
|
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}`);
|
|
1302
|
+
}
|
|
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
|
+
}
|
|
1317
|
+
const readableStream = new ReadableStream({
|
|
1318
|
+
async start(controller) {
|
|
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) {
|
|
1325
|
+
controller.enqueue(
|
|
1326
|
+
encoder.encode(
|
|
1327
|
+
`data: ${JSON.stringify({ type: "run_complete", passed: !failed })}
|
|
1328
|
+
|
|
1329
|
+
`
|
|
1330
|
+
)
|
|
1331
|
+
);
|
|
1332
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
1333
|
+
|
|
1334
|
+
`));
|
|
1335
|
+
controller.close();
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
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;
|
|
1346
|
+
try {
|
|
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;
|
|
1363
|
+
} else {
|
|
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
|
+
})}
|
|
1397
|
+
|
|
1398
|
+
`
|
|
1399
|
+
)
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
);
|
|
1404
|
+
for await (const _ of stream.textStream) {
|
|
1405
|
+
}
|
|
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;
|
|
1413
|
+
controller.enqueue(
|
|
1414
|
+
encoder.encode(
|
|
1415
|
+
`data: ${JSON.stringify({ type: "result", testId: test.id, passed, calledTools: called, missingTools: missing, unexpectedTools: unexpected })}
|
|
1416
|
+
|
|
1417
|
+
`
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
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 })}
|
|
1425
|
+
|
|
1426
|
+
`
|
|
1427
|
+
)
|
|
1428
|
+
);
|
|
1429
|
+
} finally {
|
|
1430
|
+
try {
|
|
1431
|
+
await client?.disconnect();
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
active--;
|
|
1435
|
+
runNext();
|
|
1436
|
+
}
|
|
1437
|
+
})();
|
|
1438
|
+
};
|
|
1439
|
+
for (let i = 0; i < Math.min(maxConcurrency, testsInput.length); i++) {
|
|
1440
|
+
runNext();
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
return new Response(readableStream, {
|
|
1445
|
+
headers: {
|
|
1446
|
+
"Content-Type": "text/event-stream",
|
|
1447
|
+
"Cache-Control": "no-cache",
|
|
1448
|
+
Connection: "keep-alive"
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
return c.json(
|
|
1453
|
+
{ success: false, error: err?.message || "Unknown error" },
|
|
1454
|
+
500
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1125
1459
|
// routes/mcp/oauth.ts
|
|
1126
|
-
import { Hono as
|
|
1127
|
-
var oauth = new
|
|
1460
|
+
import { Hono as Hono8 } from "hono";
|
|
1461
|
+
var oauth = new Hono8();
|
|
1128
1462
|
oauth.get("/metadata", async (c) => {
|
|
1129
1463
|
try {
|
|
1130
1464
|
const url = c.req.query("url");
|
|
@@ -1170,7 +1504,7 @@ oauth.get("/metadata", async (c) => {
|
|
|
1170
1504
|
var oauth_default = oauth;
|
|
1171
1505
|
|
|
1172
1506
|
// routes/mcp/index.ts
|
|
1173
|
-
var mcp = new
|
|
1507
|
+
var mcp = new Hono9();
|
|
1174
1508
|
mcp.get("/health", (c) => {
|
|
1175
1509
|
return c.json({
|
|
1176
1510
|
service: "MCP API",
|
|
@@ -1180,12 +1514,311 @@ mcp.get("/health", (c) => {
|
|
|
1180
1514
|
});
|
|
1181
1515
|
mcp.route("/chat", chat_default);
|
|
1182
1516
|
mcp.route("/connect", connect_default);
|
|
1517
|
+
mcp.route("/servers", servers_default);
|
|
1183
1518
|
mcp.route("/tools", tools_default);
|
|
1519
|
+
mcp.route("/tests", tests_default);
|
|
1184
1520
|
mcp.route("/resources", resources_default);
|
|
1185
1521
|
mcp.route("/prompts", prompts_default);
|
|
1186
1522
|
mcp.route("/oauth", oauth_default);
|
|
1187
1523
|
var mcp_default = mcp;
|
|
1188
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
|
+
|
|
1189
1822
|
// index.ts
|
|
1190
1823
|
function logBox(content, title) {
|
|
1191
1824
|
const lines = content.split("\n");
|
|
@@ -1219,7 +1852,12 @@ function getMCPConfigFromEnv() {
|
|
|
1219
1852
|
// Default name for CLI-provided servers
|
|
1220
1853
|
};
|
|
1221
1854
|
}
|
|
1222
|
-
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
|
+
});
|
|
1223
1861
|
app.use("*", logger());
|
|
1224
1862
|
var serverPort = process.env.PORT || "3001";
|
|
1225
1863
|
var corsOrigins = [
|
|
@@ -1251,7 +1889,7 @@ if (process.env.NODE_ENV === "production") {
|
|
|
1251
1889
|
if (path.startsWith("/api/")) {
|
|
1252
1890
|
return c.notFound();
|
|
1253
1891
|
}
|
|
1254
|
-
const indexPath =
|
|
1892
|
+
const indexPath = join2(process.cwd(), "dist", "client", "index.html");
|
|
1255
1893
|
let htmlContent = readFileSync(indexPath, "utf-8");
|
|
1256
1894
|
const mcpConfig = getMCPConfigFromEnv();
|
|
1257
1895
|
if (mcpConfig) {
|