mcp-use 1.2.4 → 1.2.5-canary.1
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/.tsbuildinfo +1 -1
- package/dist/index.cjs +0 -38
- package/dist/index.js +15 -15
- package/dist/src/agents/index.cjs +0 -6
- package/dist/src/browser.cjs +0 -20
- package/dist/src/browser.js +7 -7
- package/dist/src/react/index.cjs +0 -9
- package/dist/src/server/connect-adapter.d.ts +31 -0
- package/dist/src/server/connect-adapter.d.ts.map +1 -0
- package/dist/src/server/index.cjs +766 -165
- package/dist/src/server/index.d.ts +2 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +764 -154
- package/dist/src/server/logging.d.ts +4 -5
- package/dist/src/server/logging.d.ts.map +1 -1
- package/dist/src/server/mcp-server.d.ts +49 -9
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/tsup.config.d.ts.map +1 -1
- package/package.json +53 -52
|
@@ -31,53 +31,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/server/index.ts
|
|
32
32
|
var server_exports = {};
|
|
33
33
|
__export(server_exports, {
|
|
34
|
+
adaptConnectMiddleware: () => adaptConnectMiddleware,
|
|
35
|
+
adaptMiddleware: () => adaptMiddleware,
|
|
34
36
|
buildWidgetUrl: () => buildWidgetUrl,
|
|
35
37
|
createExternalUrlResource: () => createExternalUrlResource,
|
|
36
38
|
createMCPServer: () => createMCPServer,
|
|
37
39
|
createRawHtmlResource: () => createRawHtmlResource,
|
|
38
40
|
createRemoteDomResource: () => createRemoteDomResource,
|
|
39
|
-
createUIResourceFromDefinition: () => createUIResourceFromDefinition
|
|
41
|
+
createUIResourceFromDefinition: () => createUIResourceFromDefinition,
|
|
42
|
+
isExpressMiddleware: () => isExpressMiddleware
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(server_exports);
|
|
42
45
|
|
|
43
46
|
// src/server/mcp-server.ts
|
|
44
47
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
48
|
+
var import_hono = require("hono");
|
|
49
|
+
var import_cors = require("hono/cors");
|
|
45
50
|
var import_zod = require("zod");
|
|
46
|
-
var import_express = __toESM(require("express"), 1);
|
|
47
|
-
var import_cors = __toESM(require("cors"), 1);
|
|
48
|
-
var import_node_fs = require("fs");
|
|
49
|
-
var import_node_path = require("path");
|
|
50
|
-
var import_node_fs2 = require("fs");
|
|
51
|
-
|
|
52
|
-
// src/server/logging.ts
|
|
53
|
-
function requestLogger(req, res, next) {
|
|
54
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
55
|
-
const method = req.method;
|
|
56
|
-
const url = req.url;
|
|
57
|
-
const originalEnd = res.end.bind(res);
|
|
58
|
-
res.end = function(chunk, encoding, cb) {
|
|
59
|
-
const statusCode = res.statusCode;
|
|
60
|
-
let statusColor = "";
|
|
61
|
-
if (statusCode >= 200 && statusCode < 300) {
|
|
62
|
-
statusColor = "\x1B[32m";
|
|
63
|
-
} else if (statusCode >= 300 && statusCode < 400) {
|
|
64
|
-
statusColor = "\x1B[33m";
|
|
65
|
-
} else if (statusCode >= 400 && statusCode < 500) {
|
|
66
|
-
statusColor = "\x1B[31m";
|
|
67
|
-
} else if (statusCode >= 500) {
|
|
68
|
-
statusColor = "\x1B[35m";
|
|
69
|
-
}
|
|
70
|
-
let logMessage = `[${timestamp}] ${method} \x1B[1m${url}\x1B[0m`;
|
|
71
|
-
if (method === "POST" && url === "/mcp" && req.body?.method) {
|
|
72
|
-
logMessage += ` \x1B[1m[${req.body.method}]\x1B[0m`;
|
|
73
|
-
}
|
|
74
|
-
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
75
|
-
console.log(logMessage);
|
|
76
|
-
return originalEnd(chunk, encoding, cb);
|
|
77
|
-
};
|
|
78
|
-
next();
|
|
79
|
-
}
|
|
80
|
-
__name(requestLogger, "requestLogger");
|
|
81
51
|
|
|
82
52
|
// src/server/adapters/mcp-ui-adapter.ts
|
|
83
53
|
var import_server = require("@mcp-ui/server");
|
|
@@ -186,9 +156,264 @@ function createUIResourceFromDefinition(definition, params, config) {
|
|
|
186
156
|
}
|
|
187
157
|
__name(createUIResourceFromDefinition, "createUIResourceFromDefinition");
|
|
188
158
|
|
|
159
|
+
// src/server/connect-adapter.ts
|
|
160
|
+
function isExpressMiddleware(middleware) {
|
|
161
|
+
if (!middleware || typeof middleware !== "function") {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const paramCount = middleware.length;
|
|
165
|
+
if (paramCount === 3 || paramCount === 4) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (paramCount === 2) {
|
|
169
|
+
const fnString = middleware.toString();
|
|
170
|
+
const expressPatterns = [
|
|
171
|
+
/\bres\.(send|json|status|end|redirect|render|sendFile|download)\b/,
|
|
172
|
+
/\breq\.(body|params|query|cookies|session)\b/,
|
|
173
|
+
/\breq\.get\s*\(/,
|
|
174
|
+
/\bres\.set\s*\(/
|
|
175
|
+
];
|
|
176
|
+
const hasExpressPattern = expressPatterns.some(
|
|
177
|
+
(pattern) => pattern.test(fnString)
|
|
178
|
+
);
|
|
179
|
+
if (hasExpressPattern) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
__name(isExpressMiddleware, "isExpressMiddleware");
|
|
187
|
+
async function adaptMiddleware(middleware, middlewarePath = "*") {
|
|
188
|
+
if (isExpressMiddleware(middleware)) {
|
|
189
|
+
return adaptConnectMiddleware(middleware, middlewarePath);
|
|
190
|
+
}
|
|
191
|
+
return middleware;
|
|
192
|
+
}
|
|
193
|
+
__name(adaptMiddleware, "adaptMiddleware");
|
|
194
|
+
async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
|
|
195
|
+
let createRequest;
|
|
196
|
+
let createResponse;
|
|
197
|
+
try {
|
|
198
|
+
const httpMocks = await import("node-mocks-http");
|
|
199
|
+
createRequest = httpMocks.createRequest;
|
|
200
|
+
createResponse = httpMocks.createResponse;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(
|
|
203
|
+
"[WIDGETS] node-mocks-http not available. Install connect and node-mocks-http for Vite middleware support."
|
|
204
|
+
);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
let normalizedPath = middlewarePath;
|
|
208
|
+
if (normalizedPath.endsWith("*")) {
|
|
209
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
210
|
+
}
|
|
211
|
+
if (normalizedPath.endsWith("/")) {
|
|
212
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
213
|
+
}
|
|
214
|
+
const honoMiddleware = /* @__PURE__ */ __name(async (c, next) => {
|
|
215
|
+
const request = c.req.raw;
|
|
216
|
+
const parsedURL = new URL(request.url, "http://localhost");
|
|
217
|
+
const query = {};
|
|
218
|
+
for (const [key, value] of parsedURL.searchParams.entries()) {
|
|
219
|
+
query[key] = value;
|
|
220
|
+
}
|
|
221
|
+
let middlewarePathname = parsedURL.pathname;
|
|
222
|
+
if (normalizedPath && middlewarePathname.startsWith(normalizedPath)) {
|
|
223
|
+
middlewarePathname = middlewarePathname.substring(normalizedPath.length);
|
|
224
|
+
if (middlewarePathname === "") {
|
|
225
|
+
middlewarePathname = "/";
|
|
226
|
+
} else if (!middlewarePathname.startsWith("/")) {
|
|
227
|
+
middlewarePathname = "/" + middlewarePathname;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const mockRequest = createRequest({
|
|
231
|
+
method: request.method.toUpperCase(),
|
|
232
|
+
url: middlewarePathname + parsedURL.search,
|
|
233
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
234
|
+
query,
|
|
235
|
+
...request.body && { body: request.body }
|
|
236
|
+
});
|
|
237
|
+
const mockResponse = createResponse();
|
|
238
|
+
let responseResolved = false;
|
|
239
|
+
const res = await new Promise((resolve) => {
|
|
240
|
+
const originalEnd = mockResponse.end.bind(mockResponse);
|
|
241
|
+
mockResponse.end = (...args) => {
|
|
242
|
+
const result = originalEnd(...args);
|
|
243
|
+
if (!responseResolved && mockResponse.writableEnded) {
|
|
244
|
+
responseResolved = true;
|
|
245
|
+
const statusCode = mockResponse.statusCode;
|
|
246
|
+
const noBodyStatuses = [204, 304];
|
|
247
|
+
const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
|
|
248
|
+
const connectResponse = new Response(responseBody, {
|
|
249
|
+
status: statusCode,
|
|
250
|
+
statusText: mockResponse.statusMessage,
|
|
251
|
+
headers: mockResponse.getHeaders()
|
|
252
|
+
});
|
|
253
|
+
resolve(connectResponse);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
};
|
|
257
|
+
connectMiddleware(mockRequest, mockResponse, () => {
|
|
258
|
+
if (!responseResolved && !mockResponse.writableEnded) {
|
|
259
|
+
responseResolved = true;
|
|
260
|
+
const statusCode = mockResponse.statusCode;
|
|
261
|
+
const noBodyStatuses = [204, 304];
|
|
262
|
+
const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
|
|
263
|
+
const preparedHeaders = c.newResponse(null, 204, {}).headers;
|
|
264
|
+
for (const key of [...preparedHeaders.keys()]) {
|
|
265
|
+
if (preparedHeaders.has(key)) {
|
|
266
|
+
c.header(key, void 0);
|
|
267
|
+
}
|
|
268
|
+
if (c.res && c.res.headers.has(key)) {
|
|
269
|
+
c.res.headers.delete(key);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const connectHeaders = mockResponse.getHeaders();
|
|
273
|
+
for (const [key, value] of Object.entries(connectHeaders)) {
|
|
274
|
+
if (value !== void 0) {
|
|
275
|
+
c.header(
|
|
276
|
+
key,
|
|
277
|
+
Array.isArray(value) ? value.join(", ") : String(value)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
c.status(statusCode);
|
|
282
|
+
if (noBodyStatuses.includes(statusCode)) {
|
|
283
|
+
resolve(c.newResponse(null, statusCode));
|
|
284
|
+
} else if (responseBody) {
|
|
285
|
+
resolve(c.body(responseBody));
|
|
286
|
+
} else {
|
|
287
|
+
resolve(void 0);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
if (res) {
|
|
293
|
+
c.res = res;
|
|
294
|
+
return res;
|
|
295
|
+
}
|
|
296
|
+
await next();
|
|
297
|
+
}, "honoMiddleware");
|
|
298
|
+
return honoMiddleware;
|
|
299
|
+
}
|
|
300
|
+
__name(adaptConnectMiddleware, "adaptConnectMiddleware");
|
|
301
|
+
|
|
302
|
+
// src/server/logging.ts
|
|
303
|
+
async function requestLogger(c, next) {
|
|
304
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
305
|
+
const method = c.req.method;
|
|
306
|
+
const url = c.req.url;
|
|
307
|
+
let body = null;
|
|
308
|
+
if (method === "POST" && url.includes("/mcp")) {
|
|
309
|
+
try {
|
|
310
|
+
const clonedRequest = c.req.raw.clone();
|
|
311
|
+
body = await clonedRequest.json().catch(() => null);
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
await next();
|
|
316
|
+
const statusCode = c.res.status;
|
|
317
|
+
let statusColor = "";
|
|
318
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
319
|
+
statusColor = "\x1B[32m";
|
|
320
|
+
} else if (statusCode >= 300 && statusCode < 400) {
|
|
321
|
+
statusColor = "\x1B[33m";
|
|
322
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
323
|
+
statusColor = "\x1B[31m";
|
|
324
|
+
} else if (statusCode >= 500) {
|
|
325
|
+
statusColor = "\x1B[35m";
|
|
326
|
+
}
|
|
327
|
+
let logMessage = `[${timestamp}] ${method} \x1B[1m${new URL(url).pathname}\x1B[0m`;
|
|
328
|
+
if (method === "POST" && url.includes("/mcp") && body?.method) {
|
|
329
|
+
logMessage += ` \x1B[1m[${body.method}]\x1B[0m`;
|
|
330
|
+
}
|
|
331
|
+
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
332
|
+
console.log(logMessage);
|
|
333
|
+
}
|
|
334
|
+
__name(requestLogger, "requestLogger");
|
|
335
|
+
|
|
189
336
|
// src/server/mcp-server.ts
|
|
190
|
-
var import_vite = require("vite");
|
|
191
337
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
338
|
+
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
339
|
+
function getEnv(key) {
|
|
340
|
+
if (isDeno) {
|
|
341
|
+
return globalThis.Deno.env.get(key);
|
|
342
|
+
}
|
|
343
|
+
return process.env[key];
|
|
344
|
+
}
|
|
345
|
+
__name(getEnv, "getEnv");
|
|
346
|
+
function getCwd() {
|
|
347
|
+
if (isDeno) {
|
|
348
|
+
return globalThis.Deno.cwd();
|
|
349
|
+
}
|
|
350
|
+
return process.cwd();
|
|
351
|
+
}
|
|
352
|
+
__name(getCwd, "getCwd");
|
|
353
|
+
var fsHelpers = {
|
|
354
|
+
async readFileSync(path, encoding = "utf8") {
|
|
355
|
+
if (isDeno) {
|
|
356
|
+
return await globalThis.Deno.readTextFile(path);
|
|
357
|
+
}
|
|
358
|
+
const { readFileSync } = await import("fs");
|
|
359
|
+
const result = readFileSync(path, encoding);
|
|
360
|
+
return typeof result === "string" ? result : result.toString(encoding);
|
|
361
|
+
},
|
|
362
|
+
async readFile(path) {
|
|
363
|
+
if (isDeno) {
|
|
364
|
+
const data = await globalThis.Deno.readFile(path);
|
|
365
|
+
return data.buffer;
|
|
366
|
+
}
|
|
367
|
+
const { readFileSync } = await import("fs");
|
|
368
|
+
const buffer = readFileSync(path);
|
|
369
|
+
return buffer.buffer.slice(
|
|
370
|
+
buffer.byteOffset,
|
|
371
|
+
buffer.byteOffset + buffer.byteLength
|
|
372
|
+
);
|
|
373
|
+
},
|
|
374
|
+
async existsSync(path) {
|
|
375
|
+
if (isDeno) {
|
|
376
|
+
try {
|
|
377
|
+
await globalThis.Deno.stat(path);
|
|
378
|
+
return true;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const { existsSync } = await import("fs");
|
|
384
|
+
return existsSync(path);
|
|
385
|
+
},
|
|
386
|
+
async readdirSync(path) {
|
|
387
|
+
if (isDeno) {
|
|
388
|
+
const entries = [];
|
|
389
|
+
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
390
|
+
entries.push(entry.name);
|
|
391
|
+
}
|
|
392
|
+
return entries;
|
|
393
|
+
}
|
|
394
|
+
const { readdirSync } = await import("fs");
|
|
395
|
+
return readdirSync(path);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var pathHelpers = {
|
|
399
|
+
join(...paths) {
|
|
400
|
+
if (isDeno) {
|
|
401
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
402
|
+
}
|
|
403
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
404
|
+
},
|
|
405
|
+
relative(from, to) {
|
|
406
|
+
const fromParts = from.split("/").filter((p) => p);
|
|
407
|
+
const toParts = to.split("/").filter((p) => p);
|
|
408
|
+
let i = 0;
|
|
409
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
410
|
+
i++;
|
|
411
|
+
}
|
|
412
|
+
const upCount = fromParts.length - i;
|
|
413
|
+
const relativeParts = [...Array(upCount).fill(".."), ...toParts.slice(i)];
|
|
414
|
+
return relativeParts.join("/");
|
|
415
|
+
}
|
|
416
|
+
};
|
|
192
417
|
var McpServer = class {
|
|
193
418
|
static {
|
|
194
419
|
__name(this, "McpServer");
|
|
@@ -202,14 +427,14 @@ var McpServer = class {
|
|
|
202
427
|
serverHost;
|
|
203
428
|
serverBaseUrl;
|
|
204
429
|
/**
|
|
205
|
-
* Creates a new MCP server instance with
|
|
430
|
+
* Creates a new MCP server instance with Hono integration
|
|
206
431
|
*
|
|
207
432
|
* Initializes the server with the provided configuration, sets up CORS headers,
|
|
208
433
|
* configures widget serving routes, and creates a proxy that allows direct
|
|
209
|
-
* access to
|
|
434
|
+
* access to Hono methods while preserving MCP server functionality.
|
|
210
435
|
*
|
|
211
436
|
* @param config - Server configuration including name, version, and description
|
|
212
|
-
* @returns A proxied McpServer instance that supports both MCP and
|
|
437
|
+
* @returns A proxied McpServer instance that supports both MCP and Hono methods
|
|
213
438
|
*/
|
|
214
439
|
constructor(config) {
|
|
215
440
|
this.config = config;
|
|
@@ -219,13 +444,13 @@ var McpServer = class {
|
|
|
219
444
|
name: config.name,
|
|
220
445
|
version: config.version
|
|
221
446
|
});
|
|
222
|
-
this.app =
|
|
223
|
-
this.app.use(import_express.default.json());
|
|
447
|
+
this.app = new import_hono.Hono();
|
|
224
448
|
this.app.use(
|
|
225
|
-
|
|
449
|
+
"*",
|
|
450
|
+
(0, import_cors.cors)({
|
|
226
451
|
origin: "*",
|
|
227
|
-
|
|
228
|
-
|
|
452
|
+
allowMethods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
453
|
+
allowHeaders: [
|
|
229
454
|
"Content-Type",
|
|
230
455
|
"Accept",
|
|
231
456
|
"Authorization",
|
|
@@ -236,9 +461,55 @@ var McpServer = class {
|
|
|
236
461
|
]
|
|
237
462
|
})
|
|
238
463
|
);
|
|
239
|
-
this.app.use(requestLogger);
|
|
464
|
+
this.app.use("*", requestLogger);
|
|
240
465
|
return new Proxy(this, {
|
|
241
466
|
get(target, prop) {
|
|
467
|
+
if (prop === "use") {
|
|
468
|
+
return (...args) => {
|
|
469
|
+
const hasPath = typeof args[0] === "string";
|
|
470
|
+
const path = hasPath ? args[0] : "*";
|
|
471
|
+
const handlers = hasPath ? args.slice(1) : args;
|
|
472
|
+
const adaptedHandlers = handlers.map((handler) => {
|
|
473
|
+
if (isExpressMiddleware(handler)) {
|
|
474
|
+
return { __isExpressMiddleware: true, handler, path };
|
|
475
|
+
}
|
|
476
|
+
return handler;
|
|
477
|
+
});
|
|
478
|
+
const hasExpressMiddleware = adaptedHandlers.some(
|
|
479
|
+
(h) => h.__isExpressMiddleware
|
|
480
|
+
);
|
|
481
|
+
if (hasExpressMiddleware) {
|
|
482
|
+
Promise.all(
|
|
483
|
+
adaptedHandlers.map(async (h) => {
|
|
484
|
+
if (h.__isExpressMiddleware) {
|
|
485
|
+
const adapted = await adaptConnectMiddleware(
|
|
486
|
+
h.handler,
|
|
487
|
+
h.path
|
|
488
|
+
);
|
|
489
|
+
if (hasPath) {
|
|
490
|
+
target.app.use(path, adapted);
|
|
491
|
+
} else {
|
|
492
|
+
target.app.use(adapted);
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
if (hasPath) {
|
|
496
|
+
target.app.use(path, h);
|
|
497
|
+
} else {
|
|
498
|
+
target.app.use(h);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
).catch((err) => {
|
|
503
|
+
console.error(
|
|
504
|
+
"[MIDDLEWARE] Failed to adapt Express middleware:",
|
|
505
|
+
err
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
return target;
|
|
509
|
+
}
|
|
510
|
+
return target.app.use(...args);
|
|
511
|
+
};
|
|
512
|
+
}
|
|
242
513
|
if (prop in target) {
|
|
243
514
|
return target[prop];
|
|
244
515
|
}
|
|
@@ -772,7 +1043,7 @@ var McpServer = class {
|
|
|
772
1043
|
* @returns true if in production mode, false otherwise
|
|
773
1044
|
*/
|
|
774
1045
|
isProductionMode() {
|
|
775
|
-
return
|
|
1046
|
+
return getEnv("NODE_ENV") === "production";
|
|
776
1047
|
}
|
|
777
1048
|
/**
|
|
778
1049
|
* Read build manifest file
|
|
@@ -780,14 +1051,14 @@ var McpServer = class {
|
|
|
780
1051
|
* @private
|
|
781
1052
|
* @returns Build manifest or null if not found
|
|
782
1053
|
*/
|
|
783
|
-
readBuildManifest() {
|
|
1054
|
+
async readBuildManifest() {
|
|
784
1055
|
try {
|
|
785
|
-
const manifestPath =
|
|
786
|
-
|
|
1056
|
+
const manifestPath = pathHelpers.join(
|
|
1057
|
+
getCwd(),
|
|
787
1058
|
"dist",
|
|
788
|
-
"
|
|
1059
|
+
"mcp-use.json"
|
|
789
1060
|
);
|
|
790
|
-
const content =
|
|
1061
|
+
const content = await fsHelpers.readFileSync(manifestPath, "utf8");
|
|
791
1062
|
return JSON.parse(content);
|
|
792
1063
|
} catch {
|
|
793
1064
|
return null;
|
|
@@ -805,9 +1076,11 @@ var McpServer = class {
|
|
|
805
1076
|
* @returns Promise that resolves when all widgets are mounted
|
|
806
1077
|
*/
|
|
807
1078
|
async mountWidgets(options) {
|
|
808
|
-
if (this.isProductionMode()) {
|
|
1079
|
+
if (this.isProductionMode() || isDeno) {
|
|
1080
|
+
console.log("[WIDGETS] Mounting widgets in production mode");
|
|
809
1081
|
await this.mountWidgetsProduction(options);
|
|
810
1082
|
} else {
|
|
1083
|
+
console.log("[WIDGETS] Mounting widgets in development mode");
|
|
811
1084
|
await this.mountWidgetsDev(options);
|
|
812
1085
|
}
|
|
813
1086
|
}
|
|
@@ -828,7 +1101,7 @@ var McpServer = class {
|
|
|
828
1101
|
const { promises: fs } = await import("fs");
|
|
829
1102
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
830
1103
|
const resourcesDir = options?.resourcesDir || "resources";
|
|
831
|
-
const srcDir =
|
|
1104
|
+
const srcDir = pathHelpers.join(getCwd(), resourcesDir);
|
|
832
1105
|
try {
|
|
833
1106
|
await fs.access(srcDir);
|
|
834
1107
|
} catch (error) {
|
|
@@ -840,7 +1113,7 @@ var McpServer = class {
|
|
|
840
1113
|
let entries = [];
|
|
841
1114
|
try {
|
|
842
1115
|
const files = await fs.readdir(srcDir);
|
|
843
|
-
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) =>
|
|
1116
|
+
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => pathHelpers.join(srcDir, f));
|
|
844
1117
|
} catch (error) {
|
|
845
1118
|
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
846
1119
|
return;
|
|
@@ -849,12 +1122,32 @@ var McpServer = class {
|
|
|
849
1122
|
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
850
1123
|
return;
|
|
851
1124
|
}
|
|
852
|
-
const tempDir =
|
|
1125
|
+
const tempDir = pathHelpers.join(getCwd(), TMP_MCP_USE_DIR);
|
|
853
1126
|
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
854
1127
|
});
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1128
|
+
let createServer;
|
|
1129
|
+
let react;
|
|
1130
|
+
let tailwindcss;
|
|
1131
|
+
try {
|
|
1132
|
+
const viteModule = await new Function('return import("vite")')();
|
|
1133
|
+
createServer = viteModule.createServer;
|
|
1134
|
+
const reactModule = await new Function(
|
|
1135
|
+
'return import("@vitejs/plugin-react")'
|
|
1136
|
+
)();
|
|
1137
|
+
react = reactModule.default;
|
|
1138
|
+
const tailwindModule = await new Function(
|
|
1139
|
+
'return import("@tailwindcss/vite")'
|
|
1140
|
+
)();
|
|
1141
|
+
tailwindcss = tailwindModule.default;
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
console.error(
|
|
1144
|
+
"[WIDGETS] Dev dependencies not available. Install vite, @vitejs/plugin-react, and @tailwindcss/vite for widget development."
|
|
1145
|
+
);
|
|
1146
|
+
console.error(
|
|
1147
|
+
"[WIDGETS] For production, use 'mcp-use build' to pre-build widgets."
|
|
1148
|
+
);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
858
1151
|
const widgets = entries.map((entry) => {
|
|
859
1152
|
const baseName = entry.split("/").pop()?.replace(/\.tsx?$/, "") || "widget";
|
|
860
1153
|
const widgetName = baseName;
|
|
@@ -865,20 +1158,20 @@ var McpServer = class {
|
|
|
865
1158
|
};
|
|
866
1159
|
});
|
|
867
1160
|
for (const widget of widgets) {
|
|
868
|
-
const widgetTempDir =
|
|
1161
|
+
const widgetTempDir = pathHelpers.join(tempDir, widget.name);
|
|
869
1162
|
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
870
|
-
const resourcesPath =
|
|
871
|
-
const
|
|
872
|
-
const relativeResourcesPath = relative(
|
|
873
|
-
widgetTempDir,
|
|
874
|
-
resourcesPath
|
|
875
|
-
).replace(/\\/g, "/");
|
|
1163
|
+
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
1164
|
+
const relativeResourcesPath = pathHelpers.relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
876
1165
|
const cssContent = `@import "tailwindcss";
|
|
877
1166
|
|
|
878
1167
|
/* Configure Tailwind to scan the resources directory */
|
|
879
1168
|
@source "${relativeResourcesPath}";
|
|
880
1169
|
`;
|
|
881
|
-
await fs.writeFile(
|
|
1170
|
+
await fs.writeFile(
|
|
1171
|
+
pathHelpers.join(widgetTempDir, "styles.css"),
|
|
1172
|
+
cssContent,
|
|
1173
|
+
"utf8"
|
|
1174
|
+
);
|
|
882
1175
|
const entryContent = `import React from 'react'
|
|
883
1176
|
import { createRoot } from 'react-dom/client'
|
|
884
1177
|
import './styles.css'
|
|
@@ -903,12 +1196,12 @@ if (container && Component) {
|
|
|
903
1196
|
</body>
|
|
904
1197
|
</html>`;
|
|
905
1198
|
await fs.writeFile(
|
|
906
|
-
|
|
1199
|
+
pathHelpers.join(widgetTempDir, "entry.tsx"),
|
|
907
1200
|
entryContent,
|
|
908
1201
|
"utf8"
|
|
909
1202
|
);
|
|
910
1203
|
await fs.writeFile(
|
|
911
|
-
|
|
1204
|
+
pathHelpers.join(widgetTempDir, "index.html"),
|
|
912
1205
|
htmlContent,
|
|
913
1206
|
"utf8"
|
|
914
1207
|
);
|
|
@@ -917,13 +1210,13 @@ if (container && Component) {
|
|
|
917
1210
|
console.log(
|
|
918
1211
|
`[WIDGETS] Serving ${entries.length} widget(s) with shared Vite dev server and HMR`
|
|
919
1212
|
);
|
|
920
|
-
const viteServer = await
|
|
1213
|
+
const viteServer = await createServer({
|
|
921
1214
|
root: tempDir,
|
|
922
1215
|
base: baseRoute + "/",
|
|
923
1216
|
plugins: [tailwindcss(), react()],
|
|
924
1217
|
resolve: {
|
|
925
1218
|
alias: {
|
|
926
|
-
"@":
|
|
1219
|
+
"@": pathHelpers.join(getCwd(), resourcesDir)
|
|
927
1220
|
}
|
|
928
1221
|
},
|
|
929
1222
|
server: {
|
|
@@ -931,22 +1224,38 @@ if (container && Component) {
|
|
|
931
1224
|
origin: serverOrigin
|
|
932
1225
|
}
|
|
933
1226
|
});
|
|
934
|
-
this.app.use(baseRoute
|
|
935
|
-
const
|
|
936
|
-
const
|
|
937
|
-
const widgetMatch = pathname.match(/^\/([^/]+)/);
|
|
1227
|
+
this.app.use(`${baseRoute}/*`, async (c, next) => {
|
|
1228
|
+
const url = new URL(c.req.url);
|
|
1229
|
+
const pathname = url.pathname;
|
|
1230
|
+
const widgetMatch = pathname.replace(baseRoute, "").match(/^\/([^/]+)/);
|
|
938
1231
|
if (widgetMatch) {
|
|
939
1232
|
const widgetName = widgetMatch[1];
|
|
940
1233
|
const widget = widgets.find((w) => w.name === widgetName);
|
|
941
1234
|
if (widget) {
|
|
942
|
-
|
|
943
|
-
|
|
1235
|
+
const relativePath = pathname.replace(baseRoute, "");
|
|
1236
|
+
if (relativePath === `/${widgetName}` || relativePath === `/${widgetName}/`) {
|
|
1237
|
+
const newUrl = new URL(c.req.url);
|
|
1238
|
+
newUrl.pathname = `${baseRoute}/${widgetName}/index.html`;
|
|
1239
|
+
const newRequest = new Request(newUrl.toString(), c.req.raw);
|
|
1240
|
+
Object.defineProperty(c, "req", {
|
|
1241
|
+
value: {
|
|
1242
|
+
...c.req,
|
|
1243
|
+
url: newUrl.toString(),
|
|
1244
|
+
raw: newRequest
|
|
1245
|
+
},
|
|
1246
|
+
writable: false,
|
|
1247
|
+
configurable: true
|
|
1248
|
+
});
|
|
944
1249
|
}
|
|
945
1250
|
}
|
|
946
1251
|
}
|
|
947
|
-
next();
|
|
1252
|
+
await next();
|
|
948
1253
|
});
|
|
949
|
-
|
|
1254
|
+
const viteMiddleware = await adaptConnectMiddleware(
|
|
1255
|
+
viteServer.middlewares,
|
|
1256
|
+
`${baseRoute}/*`
|
|
1257
|
+
);
|
|
1258
|
+
this.app.use(`${baseRoute}/*`, viteMiddleware);
|
|
950
1259
|
widgets.forEach((widget) => {
|
|
951
1260
|
console.log(
|
|
952
1261
|
`[WIDGET] ${widget.name} mounted at ${baseRoute}/${widget.name}`
|
|
@@ -982,8 +1291,11 @@ if (container && Component) {
|
|
|
982
1291
|
console.log("[WIDGET dev] Metadata:", metadata);
|
|
983
1292
|
let html = "";
|
|
984
1293
|
try {
|
|
985
|
-
html =
|
|
986
|
-
|
|
1294
|
+
html = await fsHelpers.readFileSync(
|
|
1295
|
+
pathHelpers.join(tempDir, widget.name, "index.html"),
|
|
1296
|
+
"utf8"
|
|
1297
|
+
);
|
|
1298
|
+
const mcpUrl = getEnv("MCP_URL") || "/";
|
|
987
1299
|
if (mcpUrl && html) {
|
|
988
1300
|
const htmlWithoutComments = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
989
1301
|
const baseTagRegex = /<base\s+[^>]*\/?>/i;
|
|
@@ -1083,22 +1395,46 @@ if (container && Component) {
|
|
|
1083
1395
|
*/
|
|
1084
1396
|
async mountWidgetsProduction(options) {
|
|
1085
1397
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
1086
|
-
const widgetsDir =
|
|
1087
|
-
|
|
1398
|
+
const widgetsDir = pathHelpers.join(
|
|
1399
|
+
getCwd(),
|
|
1400
|
+
"dist",
|
|
1401
|
+
"resources",
|
|
1402
|
+
"widgets"
|
|
1403
|
+
);
|
|
1404
|
+
console.log("widgetsDir", widgetsDir);
|
|
1405
|
+
this.setupWidgetRoutes();
|
|
1406
|
+
const manifestPath = pathHelpers.join(getCwd(), "dist", "mcp-use.json");
|
|
1407
|
+
let widgets = [];
|
|
1408
|
+
try {
|
|
1409
|
+
const manifestContent = await fsHelpers.readFileSync(manifestPath, "utf8");
|
|
1410
|
+
const manifest = JSON.parse(manifestContent);
|
|
1411
|
+
if (manifest.widgets && Array.isArray(manifest.widgets)) {
|
|
1412
|
+
widgets = manifest.widgets;
|
|
1413
|
+
console.log(`[WIDGETS] Loaded ${widgets.length} widget(s) from manifest`);
|
|
1414
|
+
} else {
|
|
1415
|
+
console.log("[WIDGETS] No widgets array found in manifest");
|
|
1416
|
+
}
|
|
1417
|
+
} catch (error) {
|
|
1088
1418
|
console.log(
|
|
1089
|
-
"[WIDGETS]
|
|
1419
|
+
"[WIDGETS] Could not read manifest file, falling back to directory listing:",
|
|
1420
|
+
error
|
|
1090
1421
|
);
|
|
1091
|
-
|
|
1422
|
+
try {
|
|
1423
|
+
const allEntries = await fsHelpers.readdirSync(widgetsDir);
|
|
1424
|
+
for (const name of allEntries) {
|
|
1425
|
+
const widgetPath = pathHelpers.join(widgetsDir, name);
|
|
1426
|
+
const indexPath = pathHelpers.join(widgetPath, "index.html");
|
|
1427
|
+
if (await fsHelpers.existsSync(indexPath)) {
|
|
1428
|
+
widgets.push(name);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
} catch (dirError) {
|
|
1432
|
+
console.log("[WIDGETS] Directory listing also failed:", dirError);
|
|
1433
|
+
}
|
|
1092
1434
|
}
|
|
1093
|
-
this.setupWidgetRoutes();
|
|
1094
|
-
const widgets = (0, import_node_fs.readdirSync)(widgetsDir).filter((name) => {
|
|
1095
|
-
const widgetPath = (0, import_node_path.join)(widgetsDir, name);
|
|
1096
|
-
const indexPath = (0, import_node_path.join)(widgetPath, "index.html");
|
|
1097
|
-
return (0, import_node_fs.existsSync)(indexPath);
|
|
1098
|
-
});
|
|
1099
1435
|
if (widgets.length === 0) {
|
|
1100
1436
|
console.log(
|
|
1101
|
-
"[WIDGETS] No built widgets found
|
|
1437
|
+
"[WIDGETS] No built widgets found"
|
|
1102
1438
|
);
|
|
1103
1439
|
return;
|
|
1104
1440
|
}
|
|
@@ -1106,13 +1442,13 @@ if (container && Component) {
|
|
|
1106
1442
|
`[WIDGETS] Serving ${widgets.length} pre-built widget(s) from dist/resources/widgets/`
|
|
1107
1443
|
);
|
|
1108
1444
|
for (const widgetName of widgets) {
|
|
1109
|
-
const widgetPath =
|
|
1110
|
-
const indexPath =
|
|
1111
|
-
const metadataPath =
|
|
1445
|
+
const widgetPath = pathHelpers.join(widgetsDir, widgetName);
|
|
1446
|
+
const indexPath = pathHelpers.join(widgetPath, "index.html");
|
|
1447
|
+
const metadataPath = pathHelpers.join(widgetPath, "metadata.json");
|
|
1112
1448
|
let html = "";
|
|
1113
1449
|
try {
|
|
1114
|
-
html =
|
|
1115
|
-
const mcpUrl =
|
|
1450
|
+
html = await fsHelpers.readFileSync(indexPath, "utf8");
|
|
1451
|
+
const mcpUrl = getEnv("MCP_URL") || "/";
|
|
1116
1452
|
if (mcpUrl && html) {
|
|
1117
1453
|
const htmlWithoutComments = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
1118
1454
|
const baseTagRegex = /<base\s+[^>]*\/?>/i;
|
|
@@ -1159,7 +1495,10 @@ if (container && Component) {
|
|
|
1159
1495
|
let props = {};
|
|
1160
1496
|
let description = `Widget: ${widgetName}`;
|
|
1161
1497
|
try {
|
|
1162
|
-
const metadataContent =
|
|
1498
|
+
const metadataContent = await fsHelpers.readFileSync(
|
|
1499
|
+
metadataPath,
|
|
1500
|
+
"utf8"
|
|
1501
|
+
);
|
|
1163
1502
|
metadata = JSON.parse(metadataContent);
|
|
1164
1503
|
if (metadata.description) {
|
|
1165
1504
|
description = metadata.description;
|
|
@@ -1242,47 +1581,182 @@ if (container && Component) {
|
|
|
1242
1581
|
if (this.mcpMounted) return;
|
|
1243
1582
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1244
1583
|
const endpoint = "/mcp";
|
|
1245
|
-
|
|
1584
|
+
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1585
|
+
const req = c.req.raw;
|
|
1586
|
+
const responseBody = [];
|
|
1587
|
+
let statusCode = 200;
|
|
1588
|
+
const headers = {};
|
|
1589
|
+
let ended = false;
|
|
1590
|
+
let headersSent = false;
|
|
1591
|
+
const expressReq = {
|
|
1592
|
+
...req,
|
|
1593
|
+
url: new URL(req.url).pathname + new URL(req.url).search,
|
|
1594
|
+
originalUrl: req.url,
|
|
1595
|
+
baseUrl: "",
|
|
1596
|
+
path: new URL(req.url).pathname,
|
|
1597
|
+
query: Object.fromEntries(new URL(req.url).searchParams),
|
|
1598
|
+
params: {},
|
|
1599
|
+
body: {},
|
|
1600
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
1601
|
+
method: req.method
|
|
1602
|
+
};
|
|
1603
|
+
const expressRes = {
|
|
1604
|
+
statusCode: 200,
|
|
1605
|
+
headersSent: false,
|
|
1606
|
+
status: /* @__PURE__ */ __name((code) => {
|
|
1607
|
+
statusCode = code;
|
|
1608
|
+
expressRes.statusCode = code;
|
|
1609
|
+
return expressRes;
|
|
1610
|
+
}, "status"),
|
|
1611
|
+
setHeader: /* @__PURE__ */ __name((name, value) => {
|
|
1612
|
+
if (!headersSent) {
|
|
1613
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
1614
|
+
}
|
|
1615
|
+
}, "setHeader"),
|
|
1616
|
+
getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
|
|
1617
|
+
write: /* @__PURE__ */ __name((chunk, encoding, callback) => {
|
|
1618
|
+
if (!ended) {
|
|
1619
|
+
const data = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
1620
|
+
responseBody.push(data);
|
|
1621
|
+
}
|
|
1622
|
+
if (typeof encoding === "function") {
|
|
1623
|
+
encoding();
|
|
1624
|
+
} else if (callback) {
|
|
1625
|
+
callback();
|
|
1626
|
+
}
|
|
1627
|
+
return true;
|
|
1628
|
+
}, "write"),
|
|
1629
|
+
end: /* @__PURE__ */ __name((chunk, encoding, callback) => {
|
|
1630
|
+
if (chunk && !ended) {
|
|
1631
|
+
const data = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
1632
|
+
responseBody.push(data);
|
|
1633
|
+
}
|
|
1634
|
+
ended = true;
|
|
1635
|
+
if (typeof encoding === "function") {
|
|
1636
|
+
encoding();
|
|
1637
|
+
} else if (callback) {
|
|
1638
|
+
callback();
|
|
1639
|
+
}
|
|
1640
|
+
}, "end"),
|
|
1641
|
+
on: /* @__PURE__ */ __name((event, handler) => {
|
|
1642
|
+
if (event === "close") {
|
|
1643
|
+
expressRes._closeHandler = handler;
|
|
1644
|
+
}
|
|
1645
|
+
}, "on"),
|
|
1646
|
+
once: /* @__PURE__ */ __name(() => {
|
|
1647
|
+
}, "once"),
|
|
1648
|
+
removeListener: /* @__PURE__ */ __name(() => {
|
|
1649
|
+
}, "removeListener"),
|
|
1650
|
+
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
1651
|
+
statusCode = code;
|
|
1652
|
+
expressRes.statusCode = code;
|
|
1653
|
+
headersSent = true;
|
|
1654
|
+
if (_headers) {
|
|
1655
|
+
Object.assign(headers, _headers);
|
|
1656
|
+
}
|
|
1657
|
+
return expressRes;
|
|
1658
|
+
}, "writeHead"),
|
|
1659
|
+
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
1660
|
+
headersSent = true;
|
|
1661
|
+
}, "flushHeaders"),
|
|
1662
|
+
send: /* @__PURE__ */ __name((body) => {
|
|
1663
|
+
if (!ended) {
|
|
1664
|
+
expressRes.write(body);
|
|
1665
|
+
expressRes.end();
|
|
1666
|
+
}
|
|
1667
|
+
}, "send")
|
|
1668
|
+
};
|
|
1669
|
+
return {
|
|
1670
|
+
expressReq,
|
|
1671
|
+
expressRes,
|
|
1672
|
+
getResponse: /* @__PURE__ */ __name(() => {
|
|
1673
|
+
if (ended) {
|
|
1674
|
+
if (responseBody.length > 0) {
|
|
1675
|
+
const body = isDeno ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
|
|
1676
|
+
return new Response(body, {
|
|
1677
|
+
status: statusCode,
|
|
1678
|
+
headers
|
|
1679
|
+
});
|
|
1680
|
+
} else {
|
|
1681
|
+
return new Response(null, {
|
|
1682
|
+
status: statusCode,
|
|
1683
|
+
headers
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return null;
|
|
1688
|
+
}, "getResponse")
|
|
1689
|
+
};
|
|
1690
|
+
}, "createExpressLikeObjects");
|
|
1691
|
+
this.app.post(endpoint, async (c) => {
|
|
1692
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1693
|
+
try {
|
|
1694
|
+
expressReq.body = await c.req.json();
|
|
1695
|
+
} catch {
|
|
1696
|
+
expressReq.body = {};
|
|
1697
|
+
}
|
|
1246
1698
|
const transport = new StreamableHTTPServerTransport({
|
|
1247
1699
|
sessionIdGenerator: void 0,
|
|
1248
1700
|
enableJsonResponse: true
|
|
1249
1701
|
});
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1702
|
+
if (expressRes._closeHandler) {
|
|
1703
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1704
|
+
transport.close();
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1253
1707
|
await this.server.connect(transport);
|
|
1254
|
-
await transport.handleRequest(
|
|
1708
|
+
await transport.handleRequest(expressReq, expressRes, expressReq.body);
|
|
1709
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1710
|
+
const response = getResponse();
|
|
1711
|
+
if (response) {
|
|
1712
|
+
return response;
|
|
1713
|
+
}
|
|
1714
|
+
return c.text("", 200);
|
|
1255
1715
|
});
|
|
1256
|
-
this.app.get(endpoint, async (
|
|
1716
|
+
this.app.get(endpoint, async (c) => {
|
|
1717
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1257
1718
|
const transport = new StreamableHTTPServerTransport({
|
|
1258
1719
|
sessionIdGenerator: void 0,
|
|
1259
1720
|
enableJsonResponse: true
|
|
1260
1721
|
});
|
|
1261
|
-
|
|
1722
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1262
1723
|
transport.close();
|
|
1263
1724
|
});
|
|
1264
1725
|
await this.server.connect(transport);
|
|
1265
|
-
await transport.handleRequest(
|
|
1726
|
+
await transport.handleRequest(expressReq, expressRes);
|
|
1727
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1728
|
+
const response = getResponse();
|
|
1729
|
+
if (response) {
|
|
1730
|
+
return response;
|
|
1731
|
+
}
|
|
1732
|
+
return c.text("", 200);
|
|
1266
1733
|
});
|
|
1267
|
-
this.app.delete(endpoint, async (
|
|
1734
|
+
this.app.delete(endpoint, async (c) => {
|
|
1735
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1268
1736
|
const transport = new StreamableHTTPServerTransport({
|
|
1269
1737
|
sessionIdGenerator: void 0,
|
|
1270
1738
|
enableJsonResponse: true
|
|
1271
1739
|
});
|
|
1272
|
-
|
|
1740
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1273
1741
|
transport.close();
|
|
1274
1742
|
});
|
|
1275
1743
|
await this.server.connect(transport);
|
|
1276
|
-
await transport.handleRequest(
|
|
1744
|
+
await transport.handleRequest(expressReq, expressRes);
|
|
1745
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1746
|
+
const response = getResponse();
|
|
1747
|
+
if (response) {
|
|
1748
|
+
return response;
|
|
1749
|
+
}
|
|
1750
|
+
return c.text("", 200);
|
|
1277
1751
|
});
|
|
1278
1752
|
this.mcpMounted = true;
|
|
1279
1753
|
console.log(`[MCP] Server mounted at ${endpoint}`);
|
|
1280
1754
|
}
|
|
1281
1755
|
/**
|
|
1282
|
-
* Start the
|
|
1756
|
+
* Start the Hono server with MCP endpoints
|
|
1283
1757
|
*
|
|
1284
1758
|
* Initiates the server startup process by mounting MCP endpoints, configuring
|
|
1285
|
-
* the inspector UI (if available), and starting the
|
|
1759
|
+
* the inspector UI (if available), and starting the server to listen
|
|
1286
1760
|
* for incoming connections. This is the main entry point for running the server.
|
|
1287
1761
|
*
|
|
1288
1762
|
* The server will be accessible at the specified port with MCP endpoints at /mcp
|
|
@@ -1300,24 +1774,125 @@ if (container && Component) {
|
|
|
1300
1774
|
* ```
|
|
1301
1775
|
*/
|
|
1302
1776
|
async listen(port) {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1777
|
+
const portEnv = getEnv("PORT");
|
|
1778
|
+
this.serverPort = port || (portEnv ? parseInt(portEnv, 10) : 3001);
|
|
1779
|
+
const hostEnv = getEnv("HOST");
|
|
1780
|
+
if (hostEnv) {
|
|
1781
|
+
this.serverHost = hostEnv;
|
|
1306
1782
|
}
|
|
1307
1783
|
await this.mountWidgets({
|
|
1308
1784
|
baseRoute: "/mcp-use/widgets",
|
|
1309
1785
|
resourcesDir: "resources"
|
|
1310
1786
|
});
|
|
1311
1787
|
await this.mountMcp();
|
|
1312
|
-
this.mountInspector();
|
|
1313
|
-
|
|
1788
|
+
await this.mountInspector();
|
|
1789
|
+
if (isDeno) {
|
|
1790
|
+
globalThis.Deno.serve(
|
|
1791
|
+
{ port: this.serverPort, hostname: this.serverHost },
|
|
1792
|
+
this.app.fetch
|
|
1793
|
+
);
|
|
1314
1794
|
console.log(
|
|
1315
1795
|
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
1316
1796
|
);
|
|
1317
1797
|
console.log(
|
|
1318
1798
|
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
1319
1799
|
);
|
|
1800
|
+
} else {
|
|
1801
|
+
const { serve } = await import("@hono/node-server");
|
|
1802
|
+
serve(
|
|
1803
|
+
{
|
|
1804
|
+
fetch: this.app.fetch,
|
|
1805
|
+
port: this.serverPort,
|
|
1806
|
+
hostname: this.serverHost
|
|
1807
|
+
},
|
|
1808
|
+
(_info) => {
|
|
1809
|
+
console.log(
|
|
1810
|
+
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
1811
|
+
);
|
|
1812
|
+
console.log(
|
|
1813
|
+
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Get the fetch handler for the server after mounting all endpoints
|
|
1821
|
+
*
|
|
1822
|
+
* This method prepares the server by mounting MCP endpoints, widgets, and inspector
|
|
1823
|
+
* (if available), then returns the fetch handler. This is useful for integrating
|
|
1824
|
+
* with external server frameworks like Supabase Edge Functions, Cloudflare Workers,
|
|
1825
|
+
* or other platforms that handle the server lifecycle themselves.
|
|
1826
|
+
*
|
|
1827
|
+
* Unlike `listen()`, this method does not start a server - it only prepares the
|
|
1828
|
+
* routes and returns the handler function that can be used with external servers.
|
|
1829
|
+
*
|
|
1830
|
+
* @param options - Optional configuration for the handler
|
|
1831
|
+
* @param options.provider - Platform provider (e.g., 'supabase') to handle platform-specific path rewriting
|
|
1832
|
+
* @returns Promise that resolves to the fetch handler function
|
|
1833
|
+
*
|
|
1834
|
+
* @example
|
|
1835
|
+
* ```typescript
|
|
1836
|
+
* // For Supabase Edge Functions (handles path rewriting automatically)
|
|
1837
|
+
* const server = createMCPServer('my-server');
|
|
1838
|
+
* server.tool({ ... });
|
|
1839
|
+
* const handler = await server.getHandler({ provider: 'supabase' });
|
|
1840
|
+
* Deno.serve(handler);
|
|
1841
|
+
* ```
|
|
1842
|
+
*
|
|
1843
|
+
* @example
|
|
1844
|
+
* ```typescript
|
|
1845
|
+
* // For Cloudflare Workers
|
|
1846
|
+
* const server = createMCPServer('my-server');
|
|
1847
|
+
* server.tool({ ... });
|
|
1848
|
+
* const handler = await server.getHandler();
|
|
1849
|
+
* export default { fetch: handler };
|
|
1850
|
+
* ```
|
|
1851
|
+
*/
|
|
1852
|
+
async getHandler(options) {
|
|
1853
|
+
console.log("[MCP] Mounting widgets");
|
|
1854
|
+
await this.mountWidgets({
|
|
1855
|
+
baseRoute: "/mcp-use/widgets",
|
|
1856
|
+
resourcesDir: "resources"
|
|
1320
1857
|
});
|
|
1858
|
+
console.log("[MCP] Mounted widgets");
|
|
1859
|
+
await this.mountMcp();
|
|
1860
|
+
console.log("[MCP] Mounted MCP");
|
|
1861
|
+
console.log("[MCP] Mounting inspector");
|
|
1862
|
+
await this.mountInspector();
|
|
1863
|
+
console.log("[MCP] Mounted inspector");
|
|
1864
|
+
const fetchHandler = this.app.fetch.bind(this.app);
|
|
1865
|
+
if (options?.provider === "supabase") {
|
|
1866
|
+
return async (req) => {
|
|
1867
|
+
const url = new URL(req.url);
|
|
1868
|
+
const pathname = url.pathname;
|
|
1869
|
+
let newPathname = pathname;
|
|
1870
|
+
const functionsMatch = pathname.match(
|
|
1871
|
+
/^\/functions\/v1\/[^/]+(\/.*)?$/
|
|
1872
|
+
);
|
|
1873
|
+
if (functionsMatch) {
|
|
1874
|
+
newPathname = functionsMatch[1] || "/";
|
|
1875
|
+
} else {
|
|
1876
|
+
const functionNameMatch = pathname.match(/^\/([^/]+)(\/.*)?$/);
|
|
1877
|
+
if (functionNameMatch && functionNameMatch[2]) {
|
|
1878
|
+
newPathname = functionNameMatch[2] || "/";
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
const newUrl = new URL(newPathname + url.search, url.origin);
|
|
1882
|
+
const newReq = new Request(newUrl, {
|
|
1883
|
+
method: req.method,
|
|
1884
|
+
headers: req.headers,
|
|
1885
|
+
body: req.body,
|
|
1886
|
+
redirect: req.redirect
|
|
1887
|
+
});
|
|
1888
|
+
const result = await fetchHandler(newReq);
|
|
1889
|
+
return result;
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
return async (req) => {
|
|
1893
|
+
const result = await fetchHandler(req);
|
|
1894
|
+
return result;
|
|
1895
|
+
};
|
|
1321
1896
|
}
|
|
1322
1897
|
/**
|
|
1323
1898
|
* Mount MCP Inspector UI at /inspector
|
|
@@ -1341,10 +1916,10 @@ if (container && Component) {
|
|
|
1341
1916
|
* - Server continues to function normally
|
|
1342
1917
|
* - No inspector UI available
|
|
1343
1918
|
*/
|
|
1344
|
-
mountInspector() {
|
|
1919
|
+
async mountInspector() {
|
|
1345
1920
|
if (this.inspectorMounted) return;
|
|
1346
1921
|
if (this.isProductionMode()) {
|
|
1347
|
-
const manifest = this.readBuildManifest();
|
|
1922
|
+
const manifest = await this.readBuildManifest();
|
|
1348
1923
|
if (!manifest?.includeInspector) {
|
|
1349
1924
|
console.log(
|
|
1350
1925
|
"[INSPECTOR] Skipped in production (use --with-inspector flag during build)"
|
|
@@ -1352,19 +1927,20 @@ if (container && Component) {
|
|
|
1352
1927
|
return;
|
|
1353
1928
|
}
|
|
1354
1929
|
}
|
|
1355
|
-
|
|
1930
|
+
try {
|
|
1931
|
+
const { mountInspector } = await import("@mcp-use/inspector");
|
|
1356
1932
|
mountInspector(this.app);
|
|
1357
1933
|
this.inspectorMounted = true;
|
|
1358
1934
|
console.log(
|
|
1359
1935
|
`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
|
|
1360
1936
|
);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1937
|
+
} catch {
|
|
1938
|
+
}
|
|
1363
1939
|
}
|
|
1364
1940
|
/**
|
|
1365
1941
|
* Setup default widget serving routes
|
|
1366
1942
|
*
|
|
1367
|
-
* Configures
|
|
1943
|
+
* Configures Hono routes to serve MCP UI widgets and their static assets.
|
|
1368
1944
|
* Widgets are served from the dist/resources/widgets directory and can
|
|
1369
1945
|
* be accessed via HTTP endpoints for embedding in web applications.
|
|
1370
1946
|
*
|
|
@@ -1383,11 +1959,11 @@ if (container && Component) {
|
|
|
1383
1959
|
* - http://localhost:3001/mcp-use/widgets/assets/script.js (auto-discovered)
|
|
1384
1960
|
*/
|
|
1385
1961
|
setupWidgetRoutes() {
|
|
1386
|
-
this.app.get("/mcp-use/widgets/:widget/assets/*", (
|
|
1387
|
-
const widget = req.
|
|
1388
|
-
const assetFile = req.
|
|
1389
|
-
const assetPath =
|
|
1390
|
-
|
|
1962
|
+
this.app.get("/mcp-use/widgets/:widget/assets/*", async (c) => {
|
|
1963
|
+
const widget = c.req.param("widget");
|
|
1964
|
+
const assetFile = c.req.path.split("/assets/")[1];
|
|
1965
|
+
const assetPath = pathHelpers.join(
|
|
1966
|
+
getCwd(),
|
|
1391
1967
|
"dist",
|
|
1392
1968
|
"resources",
|
|
1393
1969
|
"widgets",
|
|
@@ -1395,48 +1971,82 @@ if (container && Component) {
|
|
|
1395
1971
|
"assets",
|
|
1396
1972
|
assetFile
|
|
1397
1973
|
);
|
|
1398
|
-
|
|
1974
|
+
try {
|
|
1975
|
+
if (await fsHelpers.existsSync(assetPath)) {
|
|
1976
|
+
const content = await fsHelpers.readFile(assetPath);
|
|
1977
|
+
const ext = assetFile.split(".").pop()?.toLowerCase();
|
|
1978
|
+
const contentType = ext === "js" ? "application/javascript" : ext === "css" ? "text/css" : ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "svg" ? "image/svg+xml" : "application/octet-stream";
|
|
1979
|
+
return new Response(content, {
|
|
1980
|
+
status: 200,
|
|
1981
|
+
headers: { "Content-Type": contentType }
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
return c.notFound();
|
|
1985
|
+
} catch {
|
|
1986
|
+
return c.notFound();
|
|
1987
|
+
}
|
|
1399
1988
|
});
|
|
1400
|
-
this.app.get("/mcp-use/widgets/assets/*", (
|
|
1401
|
-
const assetFile = req.
|
|
1402
|
-
const widgetsDir =
|
|
1989
|
+
this.app.get("/mcp-use/widgets/assets/*", async (c) => {
|
|
1990
|
+
const assetFile = c.req.path.split("/assets/")[1];
|
|
1991
|
+
const widgetsDir = pathHelpers.join(
|
|
1992
|
+
getCwd(),
|
|
1993
|
+
"dist",
|
|
1994
|
+
"resources",
|
|
1995
|
+
"widgets"
|
|
1996
|
+
);
|
|
1403
1997
|
try {
|
|
1404
|
-
const widgets =
|
|
1998
|
+
const widgets = await fsHelpers.readdirSync(widgetsDir);
|
|
1405
1999
|
for (const widget of widgets) {
|
|
1406
|
-
const assetPath =
|
|
1407
|
-
|
|
1408
|
-
|
|
2000
|
+
const assetPath = pathHelpers.join(
|
|
2001
|
+
widgetsDir,
|
|
2002
|
+
widget,
|
|
2003
|
+
"assets",
|
|
2004
|
+
assetFile
|
|
2005
|
+
);
|
|
2006
|
+
if (await fsHelpers.existsSync(assetPath)) {
|
|
2007
|
+
const content = await fsHelpers.readFile(assetPath);
|
|
2008
|
+
const ext = assetFile.split(".").pop()?.toLowerCase();
|
|
2009
|
+
const contentType = ext === "js" ? "application/javascript" : ext === "css" ? "text/css" : ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "svg" ? "image/svg+xml" : "application/octet-stream";
|
|
2010
|
+
return new Response(content, {
|
|
2011
|
+
status: 200,
|
|
2012
|
+
headers: { "Content-Type": contentType }
|
|
2013
|
+
});
|
|
1409
2014
|
}
|
|
1410
2015
|
}
|
|
1411
|
-
|
|
2016
|
+
return c.notFound();
|
|
1412
2017
|
} catch {
|
|
1413
|
-
|
|
2018
|
+
return c.notFound();
|
|
1414
2019
|
}
|
|
1415
2020
|
});
|
|
1416
|
-
this.app.get("/mcp-use/widgets/:widget", (
|
|
1417
|
-
const
|
|
1418
|
-
|
|
2021
|
+
this.app.get("/mcp-use/widgets/:widget", async (c) => {
|
|
2022
|
+
const widget = c.req.param("widget");
|
|
2023
|
+
const filePath = pathHelpers.join(
|
|
2024
|
+
getCwd(),
|
|
1419
2025
|
"dist",
|
|
1420
2026
|
"resources",
|
|
1421
2027
|
"widgets",
|
|
1422
|
-
|
|
2028
|
+
widget,
|
|
1423
2029
|
"index.html"
|
|
1424
2030
|
);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
2031
|
+
try {
|
|
2032
|
+
let html = await fsHelpers.readFileSync(filePath, "utf8");
|
|
2033
|
+
html = html.replace(
|
|
2034
|
+
/src="\/mcp-use\/widgets\/([^"]+)"/g,
|
|
2035
|
+
`src="${this.serverBaseUrl}/mcp-use/widgets/$1"`
|
|
2036
|
+
);
|
|
2037
|
+
html = html.replace(
|
|
2038
|
+
/href="\/mcp-use\/widgets\/([^"]+)"/g,
|
|
2039
|
+
`href="${this.serverBaseUrl}/mcp-use/widgets/$1"`
|
|
2040
|
+
);
|
|
2041
|
+
html = html.replace(
|
|
2042
|
+
/<head[^>]*>/i,
|
|
2043
|
+
`<head>
|
|
2044
|
+
<script>window.__getFile = (filename) => { return "${this.serverBaseUrl}/mcp-use/widgets/${widget}/"+filename }</script>`
|
|
2045
|
+
);
|
|
2046
|
+
return c.html(html);
|
|
2047
|
+
} catch {
|
|
2048
|
+
return c.notFound();
|
|
2049
|
+
}
|
|
1440
2050
|
});
|
|
1441
2051
|
}
|
|
1442
2052
|
/**
|
|
@@ -1629,12 +2239,3 @@ function createMCPServer(name, config = {}) {
|
|
|
1629
2239
|
return instance;
|
|
1630
2240
|
}
|
|
1631
2241
|
__name(createMCPServer, "createMCPServer");
|
|
1632
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1633
|
-
0 && (module.exports = {
|
|
1634
|
-
buildWidgetUrl,
|
|
1635
|
-
createExternalUrlResource,
|
|
1636
|
-
createMCPServer,
|
|
1637
|
-
createRawHtmlResource,
|
|
1638
|
-
createRemoteDomResource,
|
|
1639
|
-
createUIResourceFromDefinition
|
|
1640
|
-
});
|