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
package/dist/src/server/index.js
CHANGED
|
@@ -7,42 +7,9 @@ import {
|
|
|
7
7
|
McpServer as OfficialMcpServer,
|
|
8
8
|
ResourceTemplate
|
|
9
9
|
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { Hono } from "hono";
|
|
11
|
+
import { cors } from "hono/cors";
|
|
10
12
|
import { z } from "zod";
|
|
11
|
-
import express from "express";
|
|
12
|
-
import cors from "cors";
|
|
13
|
-
import { existsSync, readdirSync } from "fs";
|
|
14
|
-
import { join } from "path";
|
|
15
|
-
import { readFileSync } from "fs";
|
|
16
|
-
|
|
17
|
-
// src/server/logging.ts
|
|
18
|
-
function requestLogger(req, res, next) {
|
|
19
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
20
|
-
const method = req.method;
|
|
21
|
-
const url = req.url;
|
|
22
|
-
const originalEnd = res.end.bind(res);
|
|
23
|
-
res.end = function(chunk, encoding, cb) {
|
|
24
|
-
const statusCode = res.statusCode;
|
|
25
|
-
let statusColor = "";
|
|
26
|
-
if (statusCode >= 200 && statusCode < 300) {
|
|
27
|
-
statusColor = "\x1B[32m";
|
|
28
|
-
} else if (statusCode >= 300 && statusCode < 400) {
|
|
29
|
-
statusColor = "\x1B[33m";
|
|
30
|
-
} else if (statusCode >= 400 && statusCode < 500) {
|
|
31
|
-
statusColor = "\x1B[31m";
|
|
32
|
-
} else if (statusCode >= 500) {
|
|
33
|
-
statusColor = "\x1B[35m";
|
|
34
|
-
}
|
|
35
|
-
let logMessage = `[${timestamp}] ${method} \x1B[1m${url}\x1B[0m`;
|
|
36
|
-
if (method === "POST" && url === "/mcp" && req.body?.method) {
|
|
37
|
-
logMessage += ` \x1B[1m[${req.body.method}]\x1B[0m`;
|
|
38
|
-
}
|
|
39
|
-
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
40
|
-
console.log(logMessage);
|
|
41
|
-
return originalEnd(chunk, encoding, cb);
|
|
42
|
-
};
|
|
43
|
-
next();
|
|
44
|
-
}
|
|
45
|
-
__name(requestLogger, "requestLogger");
|
|
46
13
|
|
|
47
14
|
// src/server/adapters/mcp-ui-adapter.ts
|
|
48
15
|
import { createUIResource } from "@mcp-ui/server";
|
|
@@ -151,9 +118,264 @@ function createUIResourceFromDefinition(definition, params, config) {
|
|
|
151
118
|
}
|
|
152
119
|
__name(createUIResourceFromDefinition, "createUIResourceFromDefinition");
|
|
153
120
|
|
|
121
|
+
// src/server/connect-adapter.ts
|
|
122
|
+
function isExpressMiddleware(middleware) {
|
|
123
|
+
if (!middleware || typeof middleware !== "function") {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const paramCount = middleware.length;
|
|
127
|
+
if (paramCount === 3 || paramCount === 4) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (paramCount === 2) {
|
|
131
|
+
const fnString = middleware.toString();
|
|
132
|
+
const expressPatterns = [
|
|
133
|
+
/\bres\.(send|json|status|end|redirect|render|sendFile|download)\b/,
|
|
134
|
+
/\breq\.(body|params|query|cookies|session)\b/,
|
|
135
|
+
/\breq\.get\s*\(/,
|
|
136
|
+
/\bres\.set\s*\(/
|
|
137
|
+
];
|
|
138
|
+
const hasExpressPattern = expressPatterns.some(
|
|
139
|
+
(pattern) => pattern.test(fnString)
|
|
140
|
+
);
|
|
141
|
+
if (hasExpressPattern) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
__name(isExpressMiddleware, "isExpressMiddleware");
|
|
149
|
+
async function adaptMiddleware(middleware, middlewarePath = "*") {
|
|
150
|
+
if (isExpressMiddleware(middleware)) {
|
|
151
|
+
return adaptConnectMiddleware(middleware, middlewarePath);
|
|
152
|
+
}
|
|
153
|
+
return middleware;
|
|
154
|
+
}
|
|
155
|
+
__name(adaptMiddleware, "adaptMiddleware");
|
|
156
|
+
async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
|
|
157
|
+
let createRequest;
|
|
158
|
+
let createResponse;
|
|
159
|
+
try {
|
|
160
|
+
const httpMocks = await import("node-mocks-http");
|
|
161
|
+
createRequest = httpMocks.createRequest;
|
|
162
|
+
createResponse = httpMocks.createResponse;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(
|
|
165
|
+
"[WIDGETS] node-mocks-http not available. Install connect and node-mocks-http for Vite middleware support."
|
|
166
|
+
);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
let normalizedPath = middlewarePath;
|
|
170
|
+
if (normalizedPath.endsWith("*")) {
|
|
171
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
172
|
+
}
|
|
173
|
+
if (normalizedPath.endsWith("/")) {
|
|
174
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
175
|
+
}
|
|
176
|
+
const honoMiddleware = /* @__PURE__ */ __name(async (c, next) => {
|
|
177
|
+
const request = c.req.raw;
|
|
178
|
+
const parsedURL = new URL(request.url, "http://localhost");
|
|
179
|
+
const query = {};
|
|
180
|
+
for (const [key, value] of parsedURL.searchParams.entries()) {
|
|
181
|
+
query[key] = value;
|
|
182
|
+
}
|
|
183
|
+
let middlewarePathname = parsedURL.pathname;
|
|
184
|
+
if (normalizedPath && middlewarePathname.startsWith(normalizedPath)) {
|
|
185
|
+
middlewarePathname = middlewarePathname.substring(normalizedPath.length);
|
|
186
|
+
if (middlewarePathname === "") {
|
|
187
|
+
middlewarePathname = "/";
|
|
188
|
+
} else if (!middlewarePathname.startsWith("/")) {
|
|
189
|
+
middlewarePathname = "/" + middlewarePathname;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const mockRequest = createRequest({
|
|
193
|
+
method: request.method.toUpperCase(),
|
|
194
|
+
url: middlewarePathname + parsedURL.search,
|
|
195
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
196
|
+
query,
|
|
197
|
+
...request.body && { body: request.body }
|
|
198
|
+
});
|
|
199
|
+
const mockResponse = createResponse();
|
|
200
|
+
let responseResolved = false;
|
|
201
|
+
const res = await new Promise((resolve) => {
|
|
202
|
+
const originalEnd = mockResponse.end.bind(mockResponse);
|
|
203
|
+
mockResponse.end = (...args) => {
|
|
204
|
+
const result = originalEnd(...args);
|
|
205
|
+
if (!responseResolved && mockResponse.writableEnded) {
|
|
206
|
+
responseResolved = true;
|
|
207
|
+
const statusCode = mockResponse.statusCode;
|
|
208
|
+
const noBodyStatuses = [204, 304];
|
|
209
|
+
const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
|
|
210
|
+
const connectResponse = new Response(responseBody, {
|
|
211
|
+
status: statusCode,
|
|
212
|
+
statusText: mockResponse.statusMessage,
|
|
213
|
+
headers: mockResponse.getHeaders()
|
|
214
|
+
});
|
|
215
|
+
resolve(connectResponse);
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
};
|
|
219
|
+
connectMiddleware(mockRequest, mockResponse, () => {
|
|
220
|
+
if (!responseResolved && !mockResponse.writableEnded) {
|
|
221
|
+
responseResolved = true;
|
|
222
|
+
const statusCode = mockResponse.statusCode;
|
|
223
|
+
const noBodyStatuses = [204, 304];
|
|
224
|
+
const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
|
|
225
|
+
const preparedHeaders = c.newResponse(null, 204, {}).headers;
|
|
226
|
+
for (const key of [...preparedHeaders.keys()]) {
|
|
227
|
+
if (preparedHeaders.has(key)) {
|
|
228
|
+
c.header(key, void 0);
|
|
229
|
+
}
|
|
230
|
+
if (c.res && c.res.headers.has(key)) {
|
|
231
|
+
c.res.headers.delete(key);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const connectHeaders = mockResponse.getHeaders();
|
|
235
|
+
for (const [key, value] of Object.entries(connectHeaders)) {
|
|
236
|
+
if (value !== void 0) {
|
|
237
|
+
c.header(
|
|
238
|
+
key,
|
|
239
|
+
Array.isArray(value) ? value.join(", ") : String(value)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
c.status(statusCode);
|
|
244
|
+
if (noBodyStatuses.includes(statusCode)) {
|
|
245
|
+
resolve(c.newResponse(null, statusCode));
|
|
246
|
+
} else if (responseBody) {
|
|
247
|
+
resolve(c.body(responseBody));
|
|
248
|
+
} else {
|
|
249
|
+
resolve(void 0);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
if (res) {
|
|
255
|
+
c.res = res;
|
|
256
|
+
return res;
|
|
257
|
+
}
|
|
258
|
+
await next();
|
|
259
|
+
}, "honoMiddleware");
|
|
260
|
+
return honoMiddleware;
|
|
261
|
+
}
|
|
262
|
+
__name(adaptConnectMiddleware, "adaptConnectMiddleware");
|
|
263
|
+
|
|
264
|
+
// src/server/logging.ts
|
|
265
|
+
async function requestLogger(c, next) {
|
|
266
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
267
|
+
const method = c.req.method;
|
|
268
|
+
const url = c.req.url;
|
|
269
|
+
let body = null;
|
|
270
|
+
if (method === "POST" && url.includes("/mcp")) {
|
|
271
|
+
try {
|
|
272
|
+
const clonedRequest = c.req.raw.clone();
|
|
273
|
+
body = await clonedRequest.json().catch(() => null);
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
await next();
|
|
278
|
+
const statusCode = c.res.status;
|
|
279
|
+
let statusColor = "";
|
|
280
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
281
|
+
statusColor = "\x1B[32m";
|
|
282
|
+
} else if (statusCode >= 300 && statusCode < 400) {
|
|
283
|
+
statusColor = "\x1B[33m";
|
|
284
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
285
|
+
statusColor = "\x1B[31m";
|
|
286
|
+
} else if (statusCode >= 500) {
|
|
287
|
+
statusColor = "\x1B[35m";
|
|
288
|
+
}
|
|
289
|
+
let logMessage = `[${timestamp}] ${method} \x1B[1m${new URL(url).pathname}\x1B[0m`;
|
|
290
|
+
if (method === "POST" && url.includes("/mcp") && body?.method) {
|
|
291
|
+
logMessage += ` \x1B[1m[${body.method}]\x1B[0m`;
|
|
292
|
+
}
|
|
293
|
+
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
294
|
+
console.log(logMessage);
|
|
295
|
+
}
|
|
296
|
+
__name(requestLogger, "requestLogger");
|
|
297
|
+
|
|
154
298
|
// src/server/mcp-server.ts
|
|
155
|
-
import { createServer } from "vite";
|
|
156
299
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
300
|
+
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
301
|
+
function getEnv(key) {
|
|
302
|
+
if (isDeno) {
|
|
303
|
+
return globalThis.Deno.env.get(key);
|
|
304
|
+
}
|
|
305
|
+
return process.env[key];
|
|
306
|
+
}
|
|
307
|
+
__name(getEnv, "getEnv");
|
|
308
|
+
function getCwd() {
|
|
309
|
+
if (isDeno) {
|
|
310
|
+
return globalThis.Deno.cwd();
|
|
311
|
+
}
|
|
312
|
+
return process.cwd();
|
|
313
|
+
}
|
|
314
|
+
__name(getCwd, "getCwd");
|
|
315
|
+
var fsHelpers = {
|
|
316
|
+
async readFileSync(path, encoding = "utf8") {
|
|
317
|
+
if (isDeno) {
|
|
318
|
+
return await globalThis.Deno.readTextFile(path);
|
|
319
|
+
}
|
|
320
|
+
const { readFileSync } = await import("fs");
|
|
321
|
+
const result = readFileSync(path, encoding);
|
|
322
|
+
return typeof result === "string" ? result : result.toString(encoding);
|
|
323
|
+
},
|
|
324
|
+
async readFile(path) {
|
|
325
|
+
if (isDeno) {
|
|
326
|
+
const data = await globalThis.Deno.readFile(path);
|
|
327
|
+
return data.buffer;
|
|
328
|
+
}
|
|
329
|
+
const { readFileSync } = await import("fs");
|
|
330
|
+
const buffer = readFileSync(path);
|
|
331
|
+
return buffer.buffer.slice(
|
|
332
|
+
buffer.byteOffset,
|
|
333
|
+
buffer.byteOffset + buffer.byteLength
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
async existsSync(path) {
|
|
337
|
+
if (isDeno) {
|
|
338
|
+
try {
|
|
339
|
+
await globalThis.Deno.stat(path);
|
|
340
|
+
return true;
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const { existsSync } = await import("fs");
|
|
346
|
+
return existsSync(path);
|
|
347
|
+
},
|
|
348
|
+
async readdirSync(path) {
|
|
349
|
+
if (isDeno) {
|
|
350
|
+
const entries = [];
|
|
351
|
+
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
352
|
+
entries.push(entry.name);
|
|
353
|
+
}
|
|
354
|
+
return entries;
|
|
355
|
+
}
|
|
356
|
+
const { readdirSync } = await import("fs");
|
|
357
|
+
return readdirSync(path);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var pathHelpers = {
|
|
361
|
+
join(...paths) {
|
|
362
|
+
if (isDeno) {
|
|
363
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
364
|
+
}
|
|
365
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
366
|
+
},
|
|
367
|
+
relative(from, to) {
|
|
368
|
+
const fromParts = from.split("/").filter((p) => p);
|
|
369
|
+
const toParts = to.split("/").filter((p) => p);
|
|
370
|
+
let i = 0;
|
|
371
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
372
|
+
i++;
|
|
373
|
+
}
|
|
374
|
+
const upCount = fromParts.length - i;
|
|
375
|
+
const relativeParts = [...Array(upCount).fill(".."), ...toParts.slice(i)];
|
|
376
|
+
return relativeParts.join("/");
|
|
377
|
+
}
|
|
378
|
+
};
|
|
157
379
|
var McpServer = class {
|
|
158
380
|
static {
|
|
159
381
|
__name(this, "McpServer");
|
|
@@ -167,14 +389,14 @@ var McpServer = class {
|
|
|
167
389
|
serverHost;
|
|
168
390
|
serverBaseUrl;
|
|
169
391
|
/**
|
|
170
|
-
* Creates a new MCP server instance with
|
|
392
|
+
* Creates a new MCP server instance with Hono integration
|
|
171
393
|
*
|
|
172
394
|
* Initializes the server with the provided configuration, sets up CORS headers,
|
|
173
395
|
* configures widget serving routes, and creates a proxy that allows direct
|
|
174
|
-
* access to
|
|
396
|
+
* access to Hono methods while preserving MCP server functionality.
|
|
175
397
|
*
|
|
176
398
|
* @param config - Server configuration including name, version, and description
|
|
177
|
-
* @returns A proxied McpServer instance that supports both MCP and
|
|
399
|
+
* @returns A proxied McpServer instance that supports both MCP and Hono methods
|
|
178
400
|
*/
|
|
179
401
|
constructor(config) {
|
|
180
402
|
this.config = config;
|
|
@@ -184,13 +406,13 @@ var McpServer = class {
|
|
|
184
406
|
name: config.name,
|
|
185
407
|
version: config.version
|
|
186
408
|
});
|
|
187
|
-
this.app =
|
|
188
|
-
this.app.use(express.json());
|
|
409
|
+
this.app = new Hono();
|
|
189
410
|
this.app.use(
|
|
411
|
+
"*",
|
|
190
412
|
cors({
|
|
191
413
|
origin: "*",
|
|
192
|
-
|
|
193
|
-
|
|
414
|
+
allowMethods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
415
|
+
allowHeaders: [
|
|
194
416
|
"Content-Type",
|
|
195
417
|
"Accept",
|
|
196
418
|
"Authorization",
|
|
@@ -201,9 +423,55 @@ var McpServer = class {
|
|
|
201
423
|
]
|
|
202
424
|
})
|
|
203
425
|
);
|
|
204
|
-
this.app.use(requestLogger);
|
|
426
|
+
this.app.use("*", requestLogger);
|
|
205
427
|
return new Proxy(this, {
|
|
206
428
|
get(target, prop) {
|
|
429
|
+
if (prop === "use") {
|
|
430
|
+
return (...args) => {
|
|
431
|
+
const hasPath = typeof args[0] === "string";
|
|
432
|
+
const path = hasPath ? args[0] : "*";
|
|
433
|
+
const handlers = hasPath ? args.slice(1) : args;
|
|
434
|
+
const adaptedHandlers = handlers.map((handler) => {
|
|
435
|
+
if (isExpressMiddleware(handler)) {
|
|
436
|
+
return { __isExpressMiddleware: true, handler, path };
|
|
437
|
+
}
|
|
438
|
+
return handler;
|
|
439
|
+
});
|
|
440
|
+
const hasExpressMiddleware = adaptedHandlers.some(
|
|
441
|
+
(h) => h.__isExpressMiddleware
|
|
442
|
+
);
|
|
443
|
+
if (hasExpressMiddleware) {
|
|
444
|
+
Promise.all(
|
|
445
|
+
adaptedHandlers.map(async (h) => {
|
|
446
|
+
if (h.__isExpressMiddleware) {
|
|
447
|
+
const adapted = await adaptConnectMiddleware(
|
|
448
|
+
h.handler,
|
|
449
|
+
h.path
|
|
450
|
+
);
|
|
451
|
+
if (hasPath) {
|
|
452
|
+
target.app.use(path, adapted);
|
|
453
|
+
} else {
|
|
454
|
+
target.app.use(adapted);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
if (hasPath) {
|
|
458
|
+
target.app.use(path, h);
|
|
459
|
+
} else {
|
|
460
|
+
target.app.use(h);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
).catch((err) => {
|
|
465
|
+
console.error(
|
|
466
|
+
"[MIDDLEWARE] Failed to adapt Express middleware:",
|
|
467
|
+
err
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
return target;
|
|
471
|
+
}
|
|
472
|
+
return target.app.use(...args);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
207
475
|
if (prop in target) {
|
|
208
476
|
return target[prop];
|
|
209
477
|
}
|
|
@@ -737,7 +1005,7 @@ var McpServer = class {
|
|
|
737
1005
|
* @returns true if in production mode, false otherwise
|
|
738
1006
|
*/
|
|
739
1007
|
isProductionMode() {
|
|
740
|
-
return
|
|
1008
|
+
return getEnv("NODE_ENV") === "production";
|
|
741
1009
|
}
|
|
742
1010
|
/**
|
|
743
1011
|
* Read build manifest file
|
|
@@ -745,14 +1013,14 @@ var McpServer = class {
|
|
|
745
1013
|
* @private
|
|
746
1014
|
* @returns Build manifest or null if not found
|
|
747
1015
|
*/
|
|
748
|
-
readBuildManifest() {
|
|
1016
|
+
async readBuildManifest() {
|
|
749
1017
|
try {
|
|
750
|
-
const manifestPath = join(
|
|
751
|
-
|
|
1018
|
+
const manifestPath = pathHelpers.join(
|
|
1019
|
+
getCwd(),
|
|
752
1020
|
"dist",
|
|
753
|
-
"
|
|
1021
|
+
"mcp-use.json"
|
|
754
1022
|
);
|
|
755
|
-
const content = readFileSync(manifestPath, "utf8");
|
|
1023
|
+
const content = await fsHelpers.readFileSync(manifestPath, "utf8");
|
|
756
1024
|
return JSON.parse(content);
|
|
757
1025
|
} catch {
|
|
758
1026
|
return null;
|
|
@@ -770,9 +1038,11 @@ var McpServer = class {
|
|
|
770
1038
|
* @returns Promise that resolves when all widgets are mounted
|
|
771
1039
|
*/
|
|
772
1040
|
async mountWidgets(options) {
|
|
773
|
-
if (this.isProductionMode()) {
|
|
1041
|
+
if (this.isProductionMode() || isDeno) {
|
|
1042
|
+
console.log("[WIDGETS] Mounting widgets in production mode");
|
|
774
1043
|
await this.mountWidgetsProduction(options);
|
|
775
1044
|
} else {
|
|
1045
|
+
console.log("[WIDGETS] Mounting widgets in development mode");
|
|
776
1046
|
await this.mountWidgetsDev(options);
|
|
777
1047
|
}
|
|
778
1048
|
}
|
|
@@ -793,7 +1063,7 @@ var McpServer = class {
|
|
|
793
1063
|
const { promises: fs } = await import("fs");
|
|
794
1064
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
795
1065
|
const resourcesDir = options?.resourcesDir || "resources";
|
|
796
|
-
const srcDir = join(
|
|
1066
|
+
const srcDir = pathHelpers.join(getCwd(), resourcesDir);
|
|
797
1067
|
try {
|
|
798
1068
|
await fs.access(srcDir);
|
|
799
1069
|
} catch (error) {
|
|
@@ -805,7 +1075,7 @@ var McpServer = class {
|
|
|
805
1075
|
let entries = [];
|
|
806
1076
|
try {
|
|
807
1077
|
const files = await fs.readdir(srcDir);
|
|
808
|
-
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join(srcDir, f));
|
|
1078
|
+
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => pathHelpers.join(srcDir, f));
|
|
809
1079
|
} catch (error) {
|
|
810
1080
|
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
811
1081
|
return;
|
|
@@ -814,12 +1084,32 @@ var McpServer = class {
|
|
|
814
1084
|
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
815
1085
|
return;
|
|
816
1086
|
}
|
|
817
|
-
const tempDir = join(
|
|
1087
|
+
const tempDir = pathHelpers.join(getCwd(), TMP_MCP_USE_DIR);
|
|
818
1088
|
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
819
1089
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1090
|
+
let createServer;
|
|
1091
|
+
let react;
|
|
1092
|
+
let tailwindcss;
|
|
1093
|
+
try {
|
|
1094
|
+
const viteModule = await new Function('return import("vite")')();
|
|
1095
|
+
createServer = viteModule.createServer;
|
|
1096
|
+
const reactModule = await new Function(
|
|
1097
|
+
'return import("@vitejs/plugin-react")'
|
|
1098
|
+
)();
|
|
1099
|
+
react = reactModule.default;
|
|
1100
|
+
const tailwindModule = await new Function(
|
|
1101
|
+
'return import("@tailwindcss/vite")'
|
|
1102
|
+
)();
|
|
1103
|
+
tailwindcss = tailwindModule.default;
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
console.error(
|
|
1106
|
+
"[WIDGETS] Dev dependencies not available. Install vite, @vitejs/plugin-react, and @tailwindcss/vite for widget development."
|
|
1107
|
+
);
|
|
1108
|
+
console.error(
|
|
1109
|
+
"[WIDGETS] For production, use 'mcp-use build' to pre-build widgets."
|
|
1110
|
+
);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
823
1113
|
const widgets = entries.map((entry) => {
|
|
824
1114
|
const baseName = entry.split("/").pop()?.replace(/\.tsx?$/, "") || "widget";
|
|
825
1115
|
const widgetName = baseName;
|
|
@@ -830,20 +1120,20 @@ var McpServer = class {
|
|
|
830
1120
|
};
|
|
831
1121
|
});
|
|
832
1122
|
for (const widget of widgets) {
|
|
833
|
-
const widgetTempDir = join(tempDir, widget.name);
|
|
1123
|
+
const widgetTempDir = pathHelpers.join(tempDir, widget.name);
|
|
834
1124
|
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
835
|
-
const resourcesPath = join(
|
|
836
|
-
const
|
|
837
|
-
const relativeResourcesPath = relative(
|
|
838
|
-
widgetTempDir,
|
|
839
|
-
resourcesPath
|
|
840
|
-
).replace(/\\/g, "/");
|
|
1125
|
+
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
1126
|
+
const relativeResourcesPath = pathHelpers.relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
841
1127
|
const cssContent = `@import "tailwindcss";
|
|
842
1128
|
|
|
843
1129
|
/* Configure Tailwind to scan the resources directory */
|
|
844
1130
|
@source "${relativeResourcesPath}";
|
|
845
1131
|
`;
|
|
846
|
-
await fs.writeFile(
|
|
1132
|
+
await fs.writeFile(
|
|
1133
|
+
pathHelpers.join(widgetTempDir, "styles.css"),
|
|
1134
|
+
cssContent,
|
|
1135
|
+
"utf8"
|
|
1136
|
+
);
|
|
847
1137
|
const entryContent = `import React from 'react'
|
|
848
1138
|
import { createRoot } from 'react-dom/client'
|
|
849
1139
|
import './styles.css'
|
|
@@ -868,12 +1158,12 @@ if (container && Component) {
|
|
|
868
1158
|
</body>
|
|
869
1159
|
</html>`;
|
|
870
1160
|
await fs.writeFile(
|
|
871
|
-
join(widgetTempDir, "entry.tsx"),
|
|
1161
|
+
pathHelpers.join(widgetTempDir, "entry.tsx"),
|
|
872
1162
|
entryContent,
|
|
873
1163
|
"utf8"
|
|
874
1164
|
);
|
|
875
1165
|
await fs.writeFile(
|
|
876
|
-
join(widgetTempDir, "index.html"),
|
|
1166
|
+
pathHelpers.join(widgetTempDir, "index.html"),
|
|
877
1167
|
htmlContent,
|
|
878
1168
|
"utf8"
|
|
879
1169
|
);
|
|
@@ -888,7 +1178,7 @@ if (container && Component) {
|
|
|
888
1178
|
plugins: [tailwindcss(), react()],
|
|
889
1179
|
resolve: {
|
|
890
1180
|
alias: {
|
|
891
|
-
"@": join(
|
|
1181
|
+
"@": pathHelpers.join(getCwd(), resourcesDir)
|
|
892
1182
|
}
|
|
893
1183
|
},
|
|
894
1184
|
server: {
|
|
@@ -896,22 +1186,38 @@ if (container && Component) {
|
|
|
896
1186
|
origin: serverOrigin
|
|
897
1187
|
}
|
|
898
1188
|
});
|
|
899
|
-
this.app.use(baseRoute
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
const widgetMatch = pathname.match(/^\/([^/]+)/);
|
|
1189
|
+
this.app.use(`${baseRoute}/*`, async (c, next) => {
|
|
1190
|
+
const url = new URL(c.req.url);
|
|
1191
|
+
const pathname = url.pathname;
|
|
1192
|
+
const widgetMatch = pathname.replace(baseRoute, "").match(/^\/([^/]+)/);
|
|
903
1193
|
if (widgetMatch) {
|
|
904
1194
|
const widgetName = widgetMatch[1];
|
|
905
1195
|
const widget = widgets.find((w) => w.name === widgetName);
|
|
906
1196
|
if (widget) {
|
|
907
|
-
|
|
908
|
-
|
|
1197
|
+
const relativePath = pathname.replace(baseRoute, "");
|
|
1198
|
+
if (relativePath === `/${widgetName}` || relativePath === `/${widgetName}/`) {
|
|
1199
|
+
const newUrl = new URL(c.req.url);
|
|
1200
|
+
newUrl.pathname = `${baseRoute}/${widgetName}/index.html`;
|
|
1201
|
+
const newRequest = new Request(newUrl.toString(), c.req.raw);
|
|
1202
|
+
Object.defineProperty(c, "req", {
|
|
1203
|
+
value: {
|
|
1204
|
+
...c.req,
|
|
1205
|
+
url: newUrl.toString(),
|
|
1206
|
+
raw: newRequest
|
|
1207
|
+
},
|
|
1208
|
+
writable: false,
|
|
1209
|
+
configurable: true
|
|
1210
|
+
});
|
|
909
1211
|
}
|
|
910
1212
|
}
|
|
911
1213
|
}
|
|
912
|
-
next();
|
|
1214
|
+
await next();
|
|
913
1215
|
});
|
|
914
|
-
|
|
1216
|
+
const viteMiddleware = await adaptConnectMiddleware(
|
|
1217
|
+
viteServer.middlewares,
|
|
1218
|
+
`${baseRoute}/*`
|
|
1219
|
+
);
|
|
1220
|
+
this.app.use(`${baseRoute}/*`, viteMiddleware);
|
|
915
1221
|
widgets.forEach((widget) => {
|
|
916
1222
|
console.log(
|
|
917
1223
|
`[WIDGET] ${widget.name} mounted at ${baseRoute}/${widget.name}`
|
|
@@ -947,8 +1253,11 @@ if (container && Component) {
|
|
|
947
1253
|
console.log("[WIDGET dev] Metadata:", metadata);
|
|
948
1254
|
let html = "";
|
|
949
1255
|
try {
|
|
950
|
-
html = readFileSync(
|
|
951
|
-
|
|
1256
|
+
html = await fsHelpers.readFileSync(
|
|
1257
|
+
pathHelpers.join(tempDir, widget.name, "index.html"),
|
|
1258
|
+
"utf8"
|
|
1259
|
+
);
|
|
1260
|
+
const mcpUrl = getEnv("MCP_URL") || "/";
|
|
952
1261
|
if (mcpUrl && html) {
|
|
953
1262
|
const htmlWithoutComments = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
954
1263
|
const baseTagRegex = /<base\s+[^>]*\/?>/i;
|
|
@@ -1048,22 +1357,46 @@ if (container && Component) {
|
|
|
1048
1357
|
*/
|
|
1049
1358
|
async mountWidgetsProduction(options) {
|
|
1050
1359
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
1051
|
-
const widgetsDir = join(
|
|
1052
|
-
|
|
1360
|
+
const widgetsDir = pathHelpers.join(
|
|
1361
|
+
getCwd(),
|
|
1362
|
+
"dist",
|
|
1363
|
+
"resources",
|
|
1364
|
+
"widgets"
|
|
1365
|
+
);
|
|
1366
|
+
console.log("widgetsDir", widgetsDir);
|
|
1367
|
+
this.setupWidgetRoutes();
|
|
1368
|
+
const manifestPath = pathHelpers.join(getCwd(), "dist", "mcp-use.json");
|
|
1369
|
+
let widgets = [];
|
|
1370
|
+
try {
|
|
1371
|
+
const manifestContent = await fsHelpers.readFileSync(manifestPath, "utf8");
|
|
1372
|
+
const manifest = JSON.parse(manifestContent);
|
|
1373
|
+
if (manifest.widgets && Array.isArray(manifest.widgets)) {
|
|
1374
|
+
widgets = manifest.widgets;
|
|
1375
|
+
console.log(`[WIDGETS] Loaded ${widgets.length} widget(s) from manifest`);
|
|
1376
|
+
} else {
|
|
1377
|
+
console.log("[WIDGETS] No widgets array found in manifest");
|
|
1378
|
+
}
|
|
1379
|
+
} catch (error) {
|
|
1053
1380
|
console.log(
|
|
1054
|
-
"[WIDGETS]
|
|
1381
|
+
"[WIDGETS] Could not read manifest file, falling back to directory listing:",
|
|
1382
|
+
error
|
|
1055
1383
|
);
|
|
1056
|
-
|
|
1384
|
+
try {
|
|
1385
|
+
const allEntries = await fsHelpers.readdirSync(widgetsDir);
|
|
1386
|
+
for (const name of allEntries) {
|
|
1387
|
+
const widgetPath = pathHelpers.join(widgetsDir, name);
|
|
1388
|
+
const indexPath = pathHelpers.join(widgetPath, "index.html");
|
|
1389
|
+
if (await fsHelpers.existsSync(indexPath)) {
|
|
1390
|
+
widgets.push(name);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
} catch (dirError) {
|
|
1394
|
+
console.log("[WIDGETS] Directory listing also failed:", dirError);
|
|
1395
|
+
}
|
|
1057
1396
|
}
|
|
1058
|
-
this.setupWidgetRoutes();
|
|
1059
|
-
const widgets = readdirSync(widgetsDir).filter((name) => {
|
|
1060
|
-
const widgetPath = join(widgetsDir, name);
|
|
1061
|
-
const indexPath = join(widgetPath, "index.html");
|
|
1062
|
-
return existsSync(indexPath);
|
|
1063
|
-
});
|
|
1064
1397
|
if (widgets.length === 0) {
|
|
1065
1398
|
console.log(
|
|
1066
|
-
"[WIDGETS] No built widgets found
|
|
1399
|
+
"[WIDGETS] No built widgets found"
|
|
1067
1400
|
);
|
|
1068
1401
|
return;
|
|
1069
1402
|
}
|
|
@@ -1071,13 +1404,13 @@ if (container && Component) {
|
|
|
1071
1404
|
`[WIDGETS] Serving ${widgets.length} pre-built widget(s) from dist/resources/widgets/`
|
|
1072
1405
|
);
|
|
1073
1406
|
for (const widgetName of widgets) {
|
|
1074
|
-
const widgetPath = join(widgetsDir, widgetName);
|
|
1075
|
-
const indexPath = join(widgetPath, "index.html");
|
|
1076
|
-
const metadataPath = join(widgetPath, "metadata.json");
|
|
1407
|
+
const widgetPath = pathHelpers.join(widgetsDir, widgetName);
|
|
1408
|
+
const indexPath = pathHelpers.join(widgetPath, "index.html");
|
|
1409
|
+
const metadataPath = pathHelpers.join(widgetPath, "metadata.json");
|
|
1077
1410
|
let html = "";
|
|
1078
1411
|
try {
|
|
1079
|
-
html = readFileSync(indexPath, "utf8");
|
|
1080
|
-
const mcpUrl =
|
|
1412
|
+
html = await fsHelpers.readFileSync(indexPath, "utf8");
|
|
1413
|
+
const mcpUrl = getEnv("MCP_URL") || "/";
|
|
1081
1414
|
if (mcpUrl && html) {
|
|
1082
1415
|
const htmlWithoutComments = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
1083
1416
|
const baseTagRegex = /<base\s+[^>]*\/?>/i;
|
|
@@ -1124,7 +1457,10 @@ if (container && Component) {
|
|
|
1124
1457
|
let props = {};
|
|
1125
1458
|
let description = `Widget: ${widgetName}`;
|
|
1126
1459
|
try {
|
|
1127
|
-
const metadataContent = readFileSync(
|
|
1460
|
+
const metadataContent = await fsHelpers.readFileSync(
|
|
1461
|
+
metadataPath,
|
|
1462
|
+
"utf8"
|
|
1463
|
+
);
|
|
1128
1464
|
metadata = JSON.parse(metadataContent);
|
|
1129
1465
|
if (metadata.description) {
|
|
1130
1466
|
description = metadata.description;
|
|
@@ -1207,47 +1543,182 @@ if (container && Component) {
|
|
|
1207
1543
|
if (this.mcpMounted) return;
|
|
1208
1544
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1209
1545
|
const endpoint = "/mcp";
|
|
1210
|
-
|
|
1546
|
+
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1547
|
+
const req = c.req.raw;
|
|
1548
|
+
const responseBody = [];
|
|
1549
|
+
let statusCode = 200;
|
|
1550
|
+
const headers = {};
|
|
1551
|
+
let ended = false;
|
|
1552
|
+
let headersSent = false;
|
|
1553
|
+
const expressReq = {
|
|
1554
|
+
...req,
|
|
1555
|
+
url: new URL(req.url).pathname + new URL(req.url).search,
|
|
1556
|
+
originalUrl: req.url,
|
|
1557
|
+
baseUrl: "",
|
|
1558
|
+
path: new URL(req.url).pathname,
|
|
1559
|
+
query: Object.fromEntries(new URL(req.url).searchParams),
|
|
1560
|
+
params: {},
|
|
1561
|
+
body: {},
|
|
1562
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
1563
|
+
method: req.method
|
|
1564
|
+
};
|
|
1565
|
+
const expressRes = {
|
|
1566
|
+
statusCode: 200,
|
|
1567
|
+
headersSent: false,
|
|
1568
|
+
status: /* @__PURE__ */ __name((code) => {
|
|
1569
|
+
statusCode = code;
|
|
1570
|
+
expressRes.statusCode = code;
|
|
1571
|
+
return expressRes;
|
|
1572
|
+
}, "status"),
|
|
1573
|
+
setHeader: /* @__PURE__ */ __name((name, value) => {
|
|
1574
|
+
if (!headersSent) {
|
|
1575
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
1576
|
+
}
|
|
1577
|
+
}, "setHeader"),
|
|
1578
|
+
getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
|
|
1579
|
+
write: /* @__PURE__ */ __name((chunk, encoding, callback) => {
|
|
1580
|
+
if (!ended) {
|
|
1581
|
+
const data = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
1582
|
+
responseBody.push(data);
|
|
1583
|
+
}
|
|
1584
|
+
if (typeof encoding === "function") {
|
|
1585
|
+
encoding();
|
|
1586
|
+
} else if (callback) {
|
|
1587
|
+
callback();
|
|
1588
|
+
}
|
|
1589
|
+
return true;
|
|
1590
|
+
}, "write"),
|
|
1591
|
+
end: /* @__PURE__ */ __name((chunk, encoding, callback) => {
|
|
1592
|
+
if (chunk && !ended) {
|
|
1593
|
+
const data = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
1594
|
+
responseBody.push(data);
|
|
1595
|
+
}
|
|
1596
|
+
ended = true;
|
|
1597
|
+
if (typeof encoding === "function") {
|
|
1598
|
+
encoding();
|
|
1599
|
+
} else if (callback) {
|
|
1600
|
+
callback();
|
|
1601
|
+
}
|
|
1602
|
+
}, "end"),
|
|
1603
|
+
on: /* @__PURE__ */ __name((event, handler) => {
|
|
1604
|
+
if (event === "close") {
|
|
1605
|
+
expressRes._closeHandler = handler;
|
|
1606
|
+
}
|
|
1607
|
+
}, "on"),
|
|
1608
|
+
once: /* @__PURE__ */ __name(() => {
|
|
1609
|
+
}, "once"),
|
|
1610
|
+
removeListener: /* @__PURE__ */ __name(() => {
|
|
1611
|
+
}, "removeListener"),
|
|
1612
|
+
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
1613
|
+
statusCode = code;
|
|
1614
|
+
expressRes.statusCode = code;
|
|
1615
|
+
headersSent = true;
|
|
1616
|
+
if (_headers) {
|
|
1617
|
+
Object.assign(headers, _headers);
|
|
1618
|
+
}
|
|
1619
|
+
return expressRes;
|
|
1620
|
+
}, "writeHead"),
|
|
1621
|
+
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
1622
|
+
headersSent = true;
|
|
1623
|
+
}, "flushHeaders"),
|
|
1624
|
+
send: /* @__PURE__ */ __name((body) => {
|
|
1625
|
+
if (!ended) {
|
|
1626
|
+
expressRes.write(body);
|
|
1627
|
+
expressRes.end();
|
|
1628
|
+
}
|
|
1629
|
+
}, "send")
|
|
1630
|
+
};
|
|
1631
|
+
return {
|
|
1632
|
+
expressReq,
|
|
1633
|
+
expressRes,
|
|
1634
|
+
getResponse: /* @__PURE__ */ __name(() => {
|
|
1635
|
+
if (ended) {
|
|
1636
|
+
if (responseBody.length > 0) {
|
|
1637
|
+
const body = isDeno ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
|
|
1638
|
+
return new Response(body, {
|
|
1639
|
+
status: statusCode,
|
|
1640
|
+
headers
|
|
1641
|
+
});
|
|
1642
|
+
} else {
|
|
1643
|
+
return new Response(null, {
|
|
1644
|
+
status: statusCode,
|
|
1645
|
+
headers
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return null;
|
|
1650
|
+
}, "getResponse")
|
|
1651
|
+
};
|
|
1652
|
+
}, "createExpressLikeObjects");
|
|
1653
|
+
this.app.post(endpoint, async (c) => {
|
|
1654
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1655
|
+
try {
|
|
1656
|
+
expressReq.body = await c.req.json();
|
|
1657
|
+
} catch {
|
|
1658
|
+
expressReq.body = {};
|
|
1659
|
+
}
|
|
1211
1660
|
const transport = new StreamableHTTPServerTransport({
|
|
1212
1661
|
sessionIdGenerator: void 0,
|
|
1213
1662
|
enableJsonResponse: true
|
|
1214
1663
|
});
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1664
|
+
if (expressRes._closeHandler) {
|
|
1665
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1666
|
+
transport.close();
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1218
1669
|
await this.server.connect(transport);
|
|
1219
|
-
await transport.handleRequest(
|
|
1670
|
+
await transport.handleRequest(expressReq, expressRes, expressReq.body);
|
|
1671
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1672
|
+
const response = getResponse();
|
|
1673
|
+
if (response) {
|
|
1674
|
+
return response;
|
|
1675
|
+
}
|
|
1676
|
+
return c.text("", 200);
|
|
1220
1677
|
});
|
|
1221
|
-
this.app.get(endpoint, async (
|
|
1678
|
+
this.app.get(endpoint, async (c) => {
|
|
1679
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1222
1680
|
const transport = new StreamableHTTPServerTransport({
|
|
1223
1681
|
sessionIdGenerator: void 0,
|
|
1224
1682
|
enableJsonResponse: true
|
|
1225
1683
|
});
|
|
1226
|
-
|
|
1684
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1227
1685
|
transport.close();
|
|
1228
1686
|
});
|
|
1229
1687
|
await this.server.connect(transport);
|
|
1230
|
-
await transport.handleRequest(
|
|
1688
|
+
await transport.handleRequest(expressReq, expressRes);
|
|
1689
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1690
|
+
const response = getResponse();
|
|
1691
|
+
if (response) {
|
|
1692
|
+
return response;
|
|
1693
|
+
}
|
|
1694
|
+
return c.text("", 200);
|
|
1231
1695
|
});
|
|
1232
|
-
this.app.delete(endpoint, async (
|
|
1696
|
+
this.app.delete(endpoint, async (c) => {
|
|
1697
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1233
1698
|
const transport = new StreamableHTTPServerTransport({
|
|
1234
1699
|
sessionIdGenerator: void 0,
|
|
1235
1700
|
enableJsonResponse: true
|
|
1236
1701
|
});
|
|
1237
|
-
|
|
1702
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1238
1703
|
transport.close();
|
|
1239
1704
|
});
|
|
1240
1705
|
await this.server.connect(transport);
|
|
1241
|
-
await transport.handleRequest(
|
|
1706
|
+
await transport.handleRequest(expressReq, expressRes);
|
|
1707
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1708
|
+
const response = getResponse();
|
|
1709
|
+
if (response) {
|
|
1710
|
+
return response;
|
|
1711
|
+
}
|
|
1712
|
+
return c.text("", 200);
|
|
1242
1713
|
});
|
|
1243
1714
|
this.mcpMounted = true;
|
|
1244
1715
|
console.log(`[MCP] Server mounted at ${endpoint}`);
|
|
1245
1716
|
}
|
|
1246
1717
|
/**
|
|
1247
|
-
* Start the
|
|
1718
|
+
* Start the Hono server with MCP endpoints
|
|
1248
1719
|
*
|
|
1249
1720
|
* Initiates the server startup process by mounting MCP endpoints, configuring
|
|
1250
|
-
* the inspector UI (if available), and starting the
|
|
1721
|
+
* the inspector UI (if available), and starting the server to listen
|
|
1251
1722
|
* for incoming connections. This is the main entry point for running the server.
|
|
1252
1723
|
*
|
|
1253
1724
|
* The server will be accessible at the specified port with MCP endpoints at /mcp
|
|
@@ -1265,24 +1736,125 @@ if (container && Component) {
|
|
|
1265
1736
|
* ```
|
|
1266
1737
|
*/
|
|
1267
1738
|
async listen(port) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1739
|
+
const portEnv = getEnv("PORT");
|
|
1740
|
+
this.serverPort = port || (portEnv ? parseInt(portEnv, 10) : 3001);
|
|
1741
|
+
const hostEnv = getEnv("HOST");
|
|
1742
|
+
if (hostEnv) {
|
|
1743
|
+
this.serverHost = hostEnv;
|
|
1271
1744
|
}
|
|
1272
1745
|
await this.mountWidgets({
|
|
1273
1746
|
baseRoute: "/mcp-use/widgets",
|
|
1274
1747
|
resourcesDir: "resources"
|
|
1275
1748
|
});
|
|
1276
1749
|
await this.mountMcp();
|
|
1277
|
-
this.mountInspector();
|
|
1278
|
-
|
|
1750
|
+
await this.mountInspector();
|
|
1751
|
+
if (isDeno) {
|
|
1752
|
+
globalThis.Deno.serve(
|
|
1753
|
+
{ port: this.serverPort, hostname: this.serverHost },
|
|
1754
|
+
this.app.fetch
|
|
1755
|
+
);
|
|
1279
1756
|
console.log(
|
|
1280
1757
|
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
1281
1758
|
);
|
|
1282
1759
|
console.log(
|
|
1283
1760
|
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
1284
1761
|
);
|
|
1762
|
+
} else {
|
|
1763
|
+
const { serve } = await import("@hono/node-server");
|
|
1764
|
+
serve(
|
|
1765
|
+
{
|
|
1766
|
+
fetch: this.app.fetch,
|
|
1767
|
+
port: this.serverPort,
|
|
1768
|
+
hostname: this.serverHost
|
|
1769
|
+
},
|
|
1770
|
+
(_info) => {
|
|
1771
|
+
console.log(
|
|
1772
|
+
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
1773
|
+
);
|
|
1774
|
+
console.log(
|
|
1775
|
+
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Get the fetch handler for the server after mounting all endpoints
|
|
1783
|
+
*
|
|
1784
|
+
* This method prepares the server by mounting MCP endpoints, widgets, and inspector
|
|
1785
|
+
* (if available), then returns the fetch handler. This is useful for integrating
|
|
1786
|
+
* with external server frameworks like Supabase Edge Functions, Cloudflare Workers,
|
|
1787
|
+
* or other platforms that handle the server lifecycle themselves.
|
|
1788
|
+
*
|
|
1789
|
+
* Unlike `listen()`, this method does not start a server - it only prepares the
|
|
1790
|
+
* routes and returns the handler function that can be used with external servers.
|
|
1791
|
+
*
|
|
1792
|
+
* @param options - Optional configuration for the handler
|
|
1793
|
+
* @param options.provider - Platform provider (e.g., 'supabase') to handle platform-specific path rewriting
|
|
1794
|
+
* @returns Promise that resolves to the fetch handler function
|
|
1795
|
+
*
|
|
1796
|
+
* @example
|
|
1797
|
+
* ```typescript
|
|
1798
|
+
* // For Supabase Edge Functions (handles path rewriting automatically)
|
|
1799
|
+
* const server = createMCPServer('my-server');
|
|
1800
|
+
* server.tool({ ... });
|
|
1801
|
+
* const handler = await server.getHandler({ provider: 'supabase' });
|
|
1802
|
+
* Deno.serve(handler);
|
|
1803
|
+
* ```
|
|
1804
|
+
*
|
|
1805
|
+
* @example
|
|
1806
|
+
* ```typescript
|
|
1807
|
+
* // For Cloudflare Workers
|
|
1808
|
+
* const server = createMCPServer('my-server');
|
|
1809
|
+
* server.tool({ ... });
|
|
1810
|
+
* const handler = await server.getHandler();
|
|
1811
|
+
* export default { fetch: handler };
|
|
1812
|
+
* ```
|
|
1813
|
+
*/
|
|
1814
|
+
async getHandler(options) {
|
|
1815
|
+
console.log("[MCP] Mounting widgets");
|
|
1816
|
+
await this.mountWidgets({
|
|
1817
|
+
baseRoute: "/mcp-use/widgets",
|
|
1818
|
+
resourcesDir: "resources"
|
|
1285
1819
|
});
|
|
1820
|
+
console.log("[MCP] Mounted widgets");
|
|
1821
|
+
await this.mountMcp();
|
|
1822
|
+
console.log("[MCP] Mounted MCP");
|
|
1823
|
+
console.log("[MCP] Mounting inspector");
|
|
1824
|
+
await this.mountInspector();
|
|
1825
|
+
console.log("[MCP] Mounted inspector");
|
|
1826
|
+
const fetchHandler = this.app.fetch.bind(this.app);
|
|
1827
|
+
if (options?.provider === "supabase") {
|
|
1828
|
+
return async (req) => {
|
|
1829
|
+
const url = new URL(req.url);
|
|
1830
|
+
const pathname = url.pathname;
|
|
1831
|
+
let newPathname = pathname;
|
|
1832
|
+
const functionsMatch = pathname.match(
|
|
1833
|
+
/^\/functions\/v1\/[^/]+(\/.*)?$/
|
|
1834
|
+
);
|
|
1835
|
+
if (functionsMatch) {
|
|
1836
|
+
newPathname = functionsMatch[1] || "/";
|
|
1837
|
+
} else {
|
|
1838
|
+
const functionNameMatch = pathname.match(/^\/([^/]+)(\/.*)?$/);
|
|
1839
|
+
if (functionNameMatch && functionNameMatch[2]) {
|
|
1840
|
+
newPathname = functionNameMatch[2] || "/";
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
const newUrl = new URL(newPathname + url.search, url.origin);
|
|
1844
|
+
const newReq = new Request(newUrl, {
|
|
1845
|
+
method: req.method,
|
|
1846
|
+
headers: req.headers,
|
|
1847
|
+
body: req.body,
|
|
1848
|
+
redirect: req.redirect
|
|
1849
|
+
});
|
|
1850
|
+
const result = await fetchHandler(newReq);
|
|
1851
|
+
return result;
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
return async (req) => {
|
|
1855
|
+
const result = await fetchHandler(req);
|
|
1856
|
+
return result;
|
|
1857
|
+
};
|
|
1286
1858
|
}
|
|
1287
1859
|
/**
|
|
1288
1860
|
* Mount MCP Inspector UI at /inspector
|
|
@@ -1306,10 +1878,10 @@ if (container && Component) {
|
|
|
1306
1878
|
* - Server continues to function normally
|
|
1307
1879
|
* - No inspector UI available
|
|
1308
1880
|
*/
|
|
1309
|
-
mountInspector() {
|
|
1881
|
+
async mountInspector() {
|
|
1310
1882
|
if (this.inspectorMounted) return;
|
|
1311
1883
|
if (this.isProductionMode()) {
|
|
1312
|
-
const manifest = this.readBuildManifest();
|
|
1884
|
+
const manifest = await this.readBuildManifest();
|
|
1313
1885
|
if (!manifest?.includeInspector) {
|
|
1314
1886
|
console.log(
|
|
1315
1887
|
"[INSPECTOR] Skipped in production (use --with-inspector flag during build)"
|
|
@@ -1317,19 +1889,20 @@ if (container && Component) {
|
|
|
1317
1889
|
return;
|
|
1318
1890
|
}
|
|
1319
1891
|
}
|
|
1320
|
-
|
|
1892
|
+
try {
|
|
1893
|
+
const { mountInspector } = await import("@mcp-use/inspector");
|
|
1321
1894
|
mountInspector(this.app);
|
|
1322
1895
|
this.inspectorMounted = true;
|
|
1323
1896
|
console.log(
|
|
1324
1897
|
`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
|
|
1325
1898
|
);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1899
|
+
} catch {
|
|
1900
|
+
}
|
|
1328
1901
|
}
|
|
1329
1902
|
/**
|
|
1330
1903
|
* Setup default widget serving routes
|
|
1331
1904
|
*
|
|
1332
|
-
* Configures
|
|
1905
|
+
* Configures Hono routes to serve MCP UI widgets and their static assets.
|
|
1333
1906
|
* Widgets are served from the dist/resources/widgets directory and can
|
|
1334
1907
|
* be accessed via HTTP endpoints for embedding in web applications.
|
|
1335
1908
|
*
|
|
@@ -1348,11 +1921,11 @@ if (container && Component) {
|
|
|
1348
1921
|
* - http://localhost:3001/mcp-use/widgets/assets/script.js (auto-discovered)
|
|
1349
1922
|
*/
|
|
1350
1923
|
setupWidgetRoutes() {
|
|
1351
|
-
this.app.get("/mcp-use/widgets/:widget/assets/*", (
|
|
1352
|
-
const widget = req.
|
|
1353
|
-
const assetFile = req.
|
|
1354
|
-
const assetPath = join(
|
|
1355
|
-
|
|
1924
|
+
this.app.get("/mcp-use/widgets/:widget/assets/*", async (c) => {
|
|
1925
|
+
const widget = c.req.param("widget");
|
|
1926
|
+
const assetFile = c.req.path.split("/assets/")[1];
|
|
1927
|
+
const assetPath = pathHelpers.join(
|
|
1928
|
+
getCwd(),
|
|
1356
1929
|
"dist",
|
|
1357
1930
|
"resources",
|
|
1358
1931
|
"widgets",
|
|
@@ -1360,48 +1933,82 @@ if (container && Component) {
|
|
|
1360
1933
|
"assets",
|
|
1361
1934
|
assetFile
|
|
1362
1935
|
);
|
|
1363
|
-
|
|
1936
|
+
try {
|
|
1937
|
+
if (await fsHelpers.existsSync(assetPath)) {
|
|
1938
|
+
const content = await fsHelpers.readFile(assetPath);
|
|
1939
|
+
const ext = assetFile.split(".").pop()?.toLowerCase();
|
|
1940
|
+
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";
|
|
1941
|
+
return new Response(content, {
|
|
1942
|
+
status: 200,
|
|
1943
|
+
headers: { "Content-Type": contentType }
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
return c.notFound();
|
|
1947
|
+
} catch {
|
|
1948
|
+
return c.notFound();
|
|
1949
|
+
}
|
|
1364
1950
|
});
|
|
1365
|
-
this.app.get("/mcp-use/widgets/assets/*", (
|
|
1366
|
-
const assetFile = req.
|
|
1367
|
-
const widgetsDir = join(
|
|
1951
|
+
this.app.get("/mcp-use/widgets/assets/*", async (c) => {
|
|
1952
|
+
const assetFile = c.req.path.split("/assets/")[1];
|
|
1953
|
+
const widgetsDir = pathHelpers.join(
|
|
1954
|
+
getCwd(),
|
|
1955
|
+
"dist",
|
|
1956
|
+
"resources",
|
|
1957
|
+
"widgets"
|
|
1958
|
+
);
|
|
1368
1959
|
try {
|
|
1369
|
-
const widgets = readdirSync(widgetsDir);
|
|
1960
|
+
const widgets = await fsHelpers.readdirSync(widgetsDir);
|
|
1370
1961
|
for (const widget of widgets) {
|
|
1371
|
-
const assetPath = join(
|
|
1372
|
-
|
|
1373
|
-
|
|
1962
|
+
const assetPath = pathHelpers.join(
|
|
1963
|
+
widgetsDir,
|
|
1964
|
+
widget,
|
|
1965
|
+
"assets",
|
|
1966
|
+
assetFile
|
|
1967
|
+
);
|
|
1968
|
+
if (await fsHelpers.existsSync(assetPath)) {
|
|
1969
|
+
const content = await fsHelpers.readFile(assetPath);
|
|
1970
|
+
const ext = assetFile.split(".").pop()?.toLowerCase();
|
|
1971
|
+
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";
|
|
1972
|
+
return new Response(content, {
|
|
1973
|
+
status: 200,
|
|
1974
|
+
headers: { "Content-Type": contentType }
|
|
1975
|
+
});
|
|
1374
1976
|
}
|
|
1375
1977
|
}
|
|
1376
|
-
|
|
1978
|
+
return c.notFound();
|
|
1377
1979
|
} catch {
|
|
1378
|
-
|
|
1980
|
+
return c.notFound();
|
|
1379
1981
|
}
|
|
1380
1982
|
});
|
|
1381
|
-
this.app.get("/mcp-use/widgets/:widget", (
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1983
|
+
this.app.get("/mcp-use/widgets/:widget", async (c) => {
|
|
1984
|
+
const widget = c.req.param("widget");
|
|
1985
|
+
const filePath = pathHelpers.join(
|
|
1986
|
+
getCwd(),
|
|
1384
1987
|
"dist",
|
|
1385
1988
|
"resources",
|
|
1386
1989
|
"widgets",
|
|
1387
|
-
|
|
1990
|
+
widget,
|
|
1388
1991
|
"index.html"
|
|
1389
1992
|
);
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1993
|
+
try {
|
|
1994
|
+
let html = await fsHelpers.readFileSync(filePath, "utf8");
|
|
1995
|
+
html = html.replace(
|
|
1996
|
+
/src="\/mcp-use\/widgets\/([^"]+)"/g,
|
|
1997
|
+
`src="${this.serverBaseUrl}/mcp-use/widgets/$1"`
|
|
1998
|
+
);
|
|
1999
|
+
html = html.replace(
|
|
2000
|
+
/href="\/mcp-use\/widgets\/([^"]+)"/g,
|
|
2001
|
+
`href="${this.serverBaseUrl}/mcp-use/widgets/$1"`
|
|
2002
|
+
);
|
|
2003
|
+
html = html.replace(
|
|
2004
|
+
/<head[^>]*>/i,
|
|
2005
|
+
`<head>
|
|
2006
|
+
<script>window.__getFile = (filename) => { return "${this.serverBaseUrl}/mcp-use/widgets/${widget}/"+filename }</script>`
|
|
2007
|
+
);
|
|
2008
|
+
return c.html(html);
|
|
2009
|
+
} catch {
|
|
2010
|
+
return c.notFound();
|
|
2011
|
+
}
|
|
1405
2012
|
});
|
|
1406
2013
|
}
|
|
1407
2014
|
/**
|
|
@@ -1595,10 +2202,13 @@ function createMCPServer(name, config = {}) {
|
|
|
1595
2202
|
}
|
|
1596
2203
|
__name(createMCPServer, "createMCPServer");
|
|
1597
2204
|
export {
|
|
2205
|
+
adaptConnectMiddleware,
|
|
2206
|
+
adaptMiddleware,
|
|
1598
2207
|
buildWidgetUrl,
|
|
1599
2208
|
createExternalUrlResource,
|
|
1600
2209
|
createMCPServer,
|
|
1601
2210
|
createRawHtmlResource,
|
|
1602
2211
|
createRemoteDomResource,
|
|
1603
|
-
createUIResourceFromDefinition
|
|
2212
|
+
createUIResourceFromDefinition,
|
|
2213
|
+
isExpressMiddleware
|
|
1604
2214
|
};
|