@mcp-use/cli 2.1.20-canary.0 → 2.1.20
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/index.js +162 -23
- package/dist/index.mjs +162 -23
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -56,8 +56,8 @@ async function findAvailablePort(startPort, host = "localhost") {
|
|
|
56
56
|
async function waitForServer(port, host = "localhost", maxAttempts = 30) {
|
|
57
57
|
for (let i = 0; i < maxAttempts; i++) {
|
|
58
58
|
try {
|
|
59
|
-
const response = await fetch(`http://${host}:${port}/
|
|
60
|
-
if (response.
|
|
59
|
+
const response = await fetch(`http://${host}:${port}/mcp`);
|
|
60
|
+
if (response.status !== 404) {
|
|
61
61
|
return true;
|
|
62
62
|
}
|
|
63
63
|
} catch {
|
|
@@ -66,23 +66,76 @@ async function waitForServer(port, host = "localhost", maxAttempts = 30) {
|
|
|
66
66
|
}
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
|
-
function runCommand(command, args, cwd, env) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
function runCommand(command, args, cwd, env, filterStderr = false) {
|
|
70
|
+
const proc = (0, import_node_child_process.spawn)(command, args, {
|
|
71
|
+
cwd,
|
|
72
|
+
stdio: filterStderr ? ["inherit", "inherit", "pipe"] : "inherit",
|
|
73
|
+
shell: false,
|
|
74
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
75
|
+
});
|
|
76
|
+
if (filterStderr && proc.stderr) {
|
|
77
|
+
proc.stderr.on("data", (data) => {
|
|
78
|
+
const text = data.toString();
|
|
79
|
+
if (!text.includes("Previous process hasn't exited yet") && !text.includes("Force killing")) {
|
|
80
|
+
process.stderr.write(data);
|
|
81
|
+
}
|
|
76
82
|
});
|
|
83
|
+
}
|
|
84
|
+
const promise = new Promise((resolve, reject) => {
|
|
77
85
|
proc.on("error", reject);
|
|
78
86
|
proc.on("exit", (code) => {
|
|
79
|
-
if (code === 0) {
|
|
87
|
+
if (code === 0 || code === 130 || code === 143) {
|
|
80
88
|
resolve();
|
|
81
89
|
} else {
|
|
82
90
|
reject(new Error(`Command failed with exit code ${code}`));
|
|
83
91
|
}
|
|
84
92
|
});
|
|
85
93
|
});
|
|
94
|
+
return { promise, process: proc };
|
|
95
|
+
}
|
|
96
|
+
async function startTunnel(port) {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
console.log(import_chalk.default.gray(`Starting tunnel for port ${port}...`));
|
|
99
|
+
const proc = (0, import_node_child_process.spawn)("npx", ["--yes", "@mcp-use/tunnel", String(port)], {
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
shell: false
|
|
102
|
+
});
|
|
103
|
+
let resolved = false;
|
|
104
|
+
proc.stdout?.on("data", (data) => {
|
|
105
|
+
const text = data.toString();
|
|
106
|
+
process.stdout.write(text);
|
|
107
|
+
const urlMatch = text.match(/https?:\/\/([a-z0-9-]+\.[a-z0-9.-]+)/i);
|
|
108
|
+
if (urlMatch && !resolved) {
|
|
109
|
+
const url = urlMatch[0];
|
|
110
|
+
const subdomain = url;
|
|
111
|
+
resolved = true;
|
|
112
|
+
clearTimeout(setupTimeout);
|
|
113
|
+
console.log(import_chalk.default.green.bold(`\u2713 Tunnel established: ${url}/mcp`));
|
|
114
|
+
resolve({ url, subdomain, process: proc });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
proc.stderr?.on("data", (data) => {
|
|
118
|
+
process.stderr.write(data);
|
|
119
|
+
});
|
|
120
|
+
proc.on("error", (error) => {
|
|
121
|
+
if (!resolved) {
|
|
122
|
+
clearTimeout(setupTimeout);
|
|
123
|
+
reject(new Error(`Failed to start tunnel: ${error.message}`));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
proc.on("exit", (code) => {
|
|
127
|
+
if (code !== 0 && !resolved) {
|
|
128
|
+
clearTimeout(setupTimeout);
|
|
129
|
+
reject(new Error(`Tunnel process exited with code ${code}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const setupTimeout = setTimeout(() => {
|
|
133
|
+
if (!resolved) {
|
|
134
|
+
proc.kill();
|
|
135
|
+
reject(new Error("Tunnel setup timed out"));
|
|
136
|
+
}
|
|
137
|
+
}, 3e4);
|
|
138
|
+
});
|
|
86
139
|
}
|
|
87
140
|
async function findServerFile(projectPath) {
|
|
88
141
|
const candidates = ["index.ts", "src/index.ts", "server.ts", "src/server.ts"];
|
|
@@ -166,7 +219,7 @@ if (container && Component) {
|
|
|
166
219
|
await fs.writeFile(import_node_path.default.join(tempDir, "entry.tsx"), entryContent, "utf8");
|
|
167
220
|
await fs.writeFile(import_node_path.default.join(tempDir, "index.html"), htmlContent, "utf8");
|
|
168
221
|
const outDir = import_node_path.default.join(projectPath, "dist", "resources", "widgets", widgetName);
|
|
169
|
-
const baseUrl =
|
|
222
|
+
const baseUrl = `/mcp-use/widgets/${widgetName}/`;
|
|
170
223
|
let widgetMetadata = {};
|
|
171
224
|
try {
|
|
172
225
|
const metadataTempDir = import_node_path.default.join(projectPath, ".mcp-use", `${widgetName}-metadata`);
|
|
@@ -226,6 +279,15 @@ if (container && Component) {
|
|
|
226
279
|
root: tempDir,
|
|
227
280
|
base: baseUrl,
|
|
228
281
|
plugins: [tailwindcss(), react()],
|
|
282
|
+
experimental: {
|
|
283
|
+
renderBuiltUrl: (filename, { hostType }) => {
|
|
284
|
+
if (["js", "css"].includes(hostType)) {
|
|
285
|
+
return { runtime: `window.__getFile(${JSON.stringify(filename)})` };
|
|
286
|
+
} else {
|
|
287
|
+
return { relative: true };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
229
291
|
resolve: {
|
|
230
292
|
alias: {
|
|
231
293
|
"@": resourcesDir
|
|
@@ -292,25 +354,36 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
292
354
|
console.log(import_chalk.default.green.bold(`\u2713 Using port ${availablePort} instead`));
|
|
293
355
|
port = availablePort;
|
|
294
356
|
}
|
|
357
|
+
let mcpUrl;
|
|
295
358
|
const serverFile = await findServerFile(projectPath);
|
|
296
359
|
const processes = [];
|
|
297
|
-
const
|
|
360
|
+
const env = {
|
|
298
361
|
PORT: String(port),
|
|
299
362
|
HOST: host,
|
|
300
363
|
NODE_ENV: "development"
|
|
301
|
-
}
|
|
302
|
-
|
|
364
|
+
};
|
|
365
|
+
if (mcpUrl) {
|
|
366
|
+
env.MCP_URL = mcpUrl;
|
|
367
|
+
}
|
|
368
|
+
const serverCommand = runCommand("npx", ["tsx", "watch", serverFile], projectPath, env, true);
|
|
369
|
+
processes.push(serverCommand.process);
|
|
303
370
|
if (options.open !== false) {
|
|
304
371
|
const startTime = Date.now();
|
|
305
372
|
const ready = await waitForServer(port, host);
|
|
306
373
|
if (ready) {
|
|
307
|
-
const
|
|
308
|
-
|
|
374
|
+
const mcpEndpoint = `http://${host}:${port}/mcp`;
|
|
375
|
+
let inspectorUrl = `http://${host}:${port}/inspector?autoConnect=${encodeURIComponent(mcpEndpoint)}`;
|
|
376
|
+
if (mcpUrl) {
|
|
377
|
+
inspectorUrl += `&tunnelUrl=${encodeURIComponent(mcpUrl)}`;
|
|
378
|
+
}
|
|
309
379
|
const readyTime = Date.now() - startTime;
|
|
310
380
|
console.log(import_chalk.default.green.bold(`\u2713 Ready in ${readyTime}ms`));
|
|
311
381
|
console.log(import_chalk.default.whiteBright(`Local: http://${host}:${port}`));
|
|
312
382
|
console.log(import_chalk.default.whiteBright(`Network: http://${host}:${port}`));
|
|
313
|
-
|
|
383
|
+
if (mcpUrl) {
|
|
384
|
+
console.log(import_chalk.default.whiteBright(`Tunnel: ${mcpUrl}`));
|
|
385
|
+
}
|
|
386
|
+
console.log(import_chalk.default.whiteBright(`MCP: ${mcpEndpoint}`));
|
|
314
387
|
console.log(import_chalk.default.whiteBright(`Inspector: ${inspectorUrl}
|
|
315
388
|
`));
|
|
316
389
|
await (0, import_open.default)(inspectorUrl);
|
|
@@ -318,8 +391,30 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
318
391
|
}
|
|
319
392
|
const cleanup = () => {
|
|
320
393
|
console.log(import_chalk.default.gray("\n\nShutting down..."));
|
|
321
|
-
|
|
322
|
-
|
|
394
|
+
const processesToKill = processes.length;
|
|
395
|
+
let killedCount = 0;
|
|
396
|
+
const checkAndExit = () => {
|
|
397
|
+
killedCount++;
|
|
398
|
+
if (killedCount >= processesToKill) {
|
|
399
|
+
process.exit(0);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
processes.forEach((proc) => {
|
|
403
|
+
if (proc && typeof proc.kill === "function") {
|
|
404
|
+
proc.on("exit", checkAndExit);
|
|
405
|
+
proc.kill("SIGINT");
|
|
406
|
+
} else {
|
|
407
|
+
checkAndExit();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
processes.forEach((proc) => {
|
|
412
|
+
if (proc && typeof proc.kill === "function" && proc.exitCode === null) {
|
|
413
|
+
proc.kill("SIGKILL");
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}, 1e3);
|
|
323
418
|
};
|
|
324
419
|
process.on("SIGINT", cleanup);
|
|
325
420
|
process.on("SIGTERM", cleanup);
|
|
@@ -330,12 +425,24 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
330
425
|
process.exit(1);
|
|
331
426
|
}
|
|
332
427
|
});
|
|
333
|
-
program.command("start").description("Start production server").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--port <port>", "Server port", "3000").action(async (options) => {
|
|
428
|
+
program.command("start").description("Start production server").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--port <port>", "Server port", "3000").option("--tunnel", "Expose server through a tunnel").action(async (options) => {
|
|
334
429
|
try {
|
|
335
430
|
const projectPath = import_node_path.default.resolve(options.path);
|
|
336
431
|
const port = parseInt(options.port, 10);
|
|
337
432
|
console.log(`\x1B[36m\x1B[1mmcp-use\x1B[0m \x1B[90mVersion: ${packageJson.version}\x1B[0m
|
|
338
433
|
`);
|
|
434
|
+
let mcpUrl;
|
|
435
|
+
let tunnelProcess = void 0;
|
|
436
|
+
if (options.tunnel) {
|
|
437
|
+
try {
|
|
438
|
+
const tunnelInfo = await startTunnel(port);
|
|
439
|
+
mcpUrl = tunnelInfo.subdomain;
|
|
440
|
+
tunnelProcess = tunnelInfo.process;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error(import_chalk.default.red("Failed to start tunnel:"), error);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
339
446
|
let serverFile = "dist/index.js";
|
|
340
447
|
try {
|
|
341
448
|
await (0, import_promises.access)(import_node_path.default.join(projectPath, serverFile));
|
|
@@ -343,15 +450,47 @@ program.command("start").description("Start production server").option("-p, --pa
|
|
|
343
450
|
serverFile = "dist/server.js";
|
|
344
451
|
}
|
|
345
452
|
console.log("Starting production server...");
|
|
453
|
+
const env = {
|
|
454
|
+
...process.env,
|
|
455
|
+
PORT: String(port),
|
|
456
|
+
NODE_ENV: "production"
|
|
457
|
+
};
|
|
458
|
+
if (mcpUrl) {
|
|
459
|
+
env.MCP_URL = mcpUrl;
|
|
460
|
+
console.log(import_chalk.default.whiteBright(`Tunnel: ${mcpUrl}`));
|
|
461
|
+
}
|
|
346
462
|
const serverProc = (0, import_node_child_process.spawn)("node", [serverFile], {
|
|
347
463
|
cwd: projectPath,
|
|
348
464
|
stdio: "inherit",
|
|
349
|
-
env
|
|
465
|
+
env
|
|
350
466
|
});
|
|
351
467
|
const cleanup = () => {
|
|
352
468
|
console.log("\n\nShutting down...");
|
|
353
|
-
|
|
354
|
-
|
|
469
|
+
const processesToKill = 1 + (tunnelProcess ? 1 : 0);
|
|
470
|
+
let killedCount = 0;
|
|
471
|
+
const checkAndExit = () => {
|
|
472
|
+
killedCount++;
|
|
473
|
+
if (killedCount >= processesToKill) {
|
|
474
|
+
process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
serverProc.on("exit", checkAndExit);
|
|
478
|
+
serverProc.kill("SIGTERM");
|
|
479
|
+
if (tunnelProcess && typeof tunnelProcess.kill === "function") {
|
|
480
|
+
tunnelProcess.on("exit", checkAndExit);
|
|
481
|
+
tunnelProcess.kill("SIGTERM");
|
|
482
|
+
} else {
|
|
483
|
+
checkAndExit();
|
|
484
|
+
}
|
|
485
|
+
setTimeout(() => {
|
|
486
|
+
if (serverProc.exitCode === null) {
|
|
487
|
+
serverProc.kill("SIGKILL");
|
|
488
|
+
}
|
|
489
|
+
if (tunnelProcess && tunnelProcess.exitCode === null) {
|
|
490
|
+
tunnelProcess.kill("SIGKILL");
|
|
491
|
+
}
|
|
492
|
+
process.exit(0);
|
|
493
|
+
}, 1e3);
|
|
355
494
|
};
|
|
356
495
|
process.on("SIGINT", cleanup);
|
|
357
496
|
process.on("SIGTERM", cleanup);
|
package/dist/index.mjs
CHANGED
|
@@ -33,8 +33,8 @@ async function findAvailablePort(startPort, host = "localhost") {
|
|
|
33
33
|
async function waitForServer(port, host = "localhost", maxAttempts = 30) {
|
|
34
34
|
for (let i = 0; i < maxAttempts; i++) {
|
|
35
35
|
try {
|
|
36
|
-
const response = await fetch(`http://${host}:${port}/
|
|
37
|
-
if (response.
|
|
36
|
+
const response = await fetch(`http://${host}:${port}/mcp`);
|
|
37
|
+
if (response.status !== 404) {
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
} catch {
|
|
@@ -43,23 +43,76 @@ async function waitForServer(port, host = "localhost", maxAttempts = 30) {
|
|
|
43
43
|
}
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
-
function runCommand(command, args, cwd, env) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
function runCommand(command, args, cwd, env, filterStderr = false) {
|
|
47
|
+
const proc = spawn(command, args, {
|
|
48
|
+
cwd,
|
|
49
|
+
stdio: filterStderr ? ["inherit", "inherit", "pipe"] : "inherit",
|
|
50
|
+
shell: false,
|
|
51
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
52
|
+
});
|
|
53
|
+
if (filterStderr && proc.stderr) {
|
|
54
|
+
proc.stderr.on("data", (data) => {
|
|
55
|
+
const text = data.toString();
|
|
56
|
+
if (!text.includes("Previous process hasn't exited yet") && !text.includes("Force killing")) {
|
|
57
|
+
process.stderr.write(data);
|
|
58
|
+
}
|
|
53
59
|
});
|
|
60
|
+
}
|
|
61
|
+
const promise = new Promise((resolve, reject) => {
|
|
54
62
|
proc.on("error", reject);
|
|
55
63
|
proc.on("exit", (code) => {
|
|
56
|
-
if (code === 0) {
|
|
64
|
+
if (code === 0 || code === 130 || code === 143) {
|
|
57
65
|
resolve();
|
|
58
66
|
} else {
|
|
59
67
|
reject(new Error(`Command failed with exit code ${code}`));
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
});
|
|
71
|
+
return { promise, process: proc };
|
|
72
|
+
}
|
|
73
|
+
async function startTunnel(port) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
console.log(chalk.gray(`Starting tunnel for port ${port}...`));
|
|
76
|
+
const proc = spawn("npx", ["--yes", "@mcp-use/tunnel", String(port)], {
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
shell: false
|
|
79
|
+
});
|
|
80
|
+
let resolved = false;
|
|
81
|
+
proc.stdout?.on("data", (data) => {
|
|
82
|
+
const text = data.toString();
|
|
83
|
+
process.stdout.write(text);
|
|
84
|
+
const urlMatch = text.match(/https?:\/\/([a-z0-9-]+\.[a-z0-9.-]+)/i);
|
|
85
|
+
if (urlMatch && !resolved) {
|
|
86
|
+
const url = urlMatch[0];
|
|
87
|
+
const subdomain = url;
|
|
88
|
+
resolved = true;
|
|
89
|
+
clearTimeout(setupTimeout);
|
|
90
|
+
console.log(chalk.green.bold(`\u2713 Tunnel established: ${url}/mcp`));
|
|
91
|
+
resolve({ url, subdomain, process: proc });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
proc.stderr?.on("data", (data) => {
|
|
95
|
+
process.stderr.write(data);
|
|
96
|
+
});
|
|
97
|
+
proc.on("error", (error) => {
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
clearTimeout(setupTimeout);
|
|
100
|
+
reject(new Error(`Failed to start tunnel: ${error.message}`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
proc.on("exit", (code) => {
|
|
104
|
+
if (code !== 0 && !resolved) {
|
|
105
|
+
clearTimeout(setupTimeout);
|
|
106
|
+
reject(new Error(`Tunnel process exited with code ${code}`));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const setupTimeout = setTimeout(() => {
|
|
110
|
+
if (!resolved) {
|
|
111
|
+
proc.kill();
|
|
112
|
+
reject(new Error("Tunnel setup timed out"));
|
|
113
|
+
}
|
|
114
|
+
}, 3e4);
|
|
115
|
+
});
|
|
63
116
|
}
|
|
64
117
|
async function findServerFile(projectPath) {
|
|
65
118
|
const candidates = ["index.ts", "src/index.ts", "server.ts", "src/server.ts"];
|
|
@@ -143,7 +196,7 @@ if (container && Component) {
|
|
|
143
196
|
await fs.writeFile(path.join(tempDir, "entry.tsx"), entryContent, "utf8");
|
|
144
197
|
await fs.writeFile(path.join(tempDir, "index.html"), htmlContent, "utf8");
|
|
145
198
|
const outDir = path.join(projectPath, "dist", "resources", "widgets", widgetName);
|
|
146
|
-
const baseUrl =
|
|
199
|
+
const baseUrl = `/mcp-use/widgets/${widgetName}/`;
|
|
147
200
|
let widgetMetadata = {};
|
|
148
201
|
try {
|
|
149
202
|
const metadataTempDir = path.join(projectPath, ".mcp-use", `${widgetName}-metadata`);
|
|
@@ -203,6 +256,15 @@ if (container && Component) {
|
|
|
203
256
|
root: tempDir,
|
|
204
257
|
base: baseUrl,
|
|
205
258
|
plugins: [tailwindcss(), react()],
|
|
259
|
+
experimental: {
|
|
260
|
+
renderBuiltUrl: (filename, { hostType }) => {
|
|
261
|
+
if (["js", "css"].includes(hostType)) {
|
|
262
|
+
return { runtime: `window.__getFile(${JSON.stringify(filename)})` };
|
|
263
|
+
} else {
|
|
264
|
+
return { relative: true };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
206
268
|
resolve: {
|
|
207
269
|
alias: {
|
|
208
270
|
"@": resourcesDir
|
|
@@ -269,25 +331,36 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
269
331
|
console.log(chalk.green.bold(`\u2713 Using port ${availablePort} instead`));
|
|
270
332
|
port = availablePort;
|
|
271
333
|
}
|
|
334
|
+
let mcpUrl;
|
|
272
335
|
const serverFile = await findServerFile(projectPath);
|
|
273
336
|
const processes = [];
|
|
274
|
-
const
|
|
337
|
+
const env = {
|
|
275
338
|
PORT: String(port),
|
|
276
339
|
HOST: host,
|
|
277
340
|
NODE_ENV: "development"
|
|
278
|
-
}
|
|
279
|
-
|
|
341
|
+
};
|
|
342
|
+
if (mcpUrl) {
|
|
343
|
+
env.MCP_URL = mcpUrl;
|
|
344
|
+
}
|
|
345
|
+
const serverCommand = runCommand("npx", ["tsx", "watch", serverFile], projectPath, env, true);
|
|
346
|
+
processes.push(serverCommand.process);
|
|
280
347
|
if (options.open !== false) {
|
|
281
348
|
const startTime = Date.now();
|
|
282
349
|
const ready = await waitForServer(port, host);
|
|
283
350
|
if (ready) {
|
|
284
|
-
const
|
|
285
|
-
|
|
351
|
+
const mcpEndpoint = `http://${host}:${port}/mcp`;
|
|
352
|
+
let inspectorUrl = `http://${host}:${port}/inspector?autoConnect=${encodeURIComponent(mcpEndpoint)}`;
|
|
353
|
+
if (mcpUrl) {
|
|
354
|
+
inspectorUrl += `&tunnelUrl=${encodeURIComponent(mcpUrl)}`;
|
|
355
|
+
}
|
|
286
356
|
const readyTime = Date.now() - startTime;
|
|
287
357
|
console.log(chalk.green.bold(`\u2713 Ready in ${readyTime}ms`));
|
|
288
358
|
console.log(chalk.whiteBright(`Local: http://${host}:${port}`));
|
|
289
359
|
console.log(chalk.whiteBright(`Network: http://${host}:${port}`));
|
|
290
|
-
|
|
360
|
+
if (mcpUrl) {
|
|
361
|
+
console.log(chalk.whiteBright(`Tunnel: ${mcpUrl}`));
|
|
362
|
+
}
|
|
363
|
+
console.log(chalk.whiteBright(`MCP: ${mcpEndpoint}`));
|
|
291
364
|
console.log(chalk.whiteBright(`Inspector: ${inspectorUrl}
|
|
292
365
|
`));
|
|
293
366
|
await open(inspectorUrl);
|
|
@@ -295,8 +368,30 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
295
368
|
}
|
|
296
369
|
const cleanup = () => {
|
|
297
370
|
console.log(chalk.gray("\n\nShutting down..."));
|
|
298
|
-
|
|
299
|
-
|
|
371
|
+
const processesToKill = processes.length;
|
|
372
|
+
let killedCount = 0;
|
|
373
|
+
const checkAndExit = () => {
|
|
374
|
+
killedCount++;
|
|
375
|
+
if (killedCount >= processesToKill) {
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
processes.forEach((proc) => {
|
|
380
|
+
if (proc && typeof proc.kill === "function") {
|
|
381
|
+
proc.on("exit", checkAndExit);
|
|
382
|
+
proc.kill("SIGINT");
|
|
383
|
+
} else {
|
|
384
|
+
checkAndExit();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
setTimeout(() => {
|
|
388
|
+
processes.forEach((proc) => {
|
|
389
|
+
if (proc && typeof proc.kill === "function" && proc.exitCode === null) {
|
|
390
|
+
proc.kill("SIGKILL");
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
process.exit(0);
|
|
394
|
+
}, 1e3);
|
|
300
395
|
};
|
|
301
396
|
process.on("SIGINT", cleanup);
|
|
302
397
|
process.on("SIGTERM", cleanup);
|
|
@@ -307,12 +402,24 @@ program.command("dev").description("Run development server with auto-reload and
|
|
|
307
402
|
process.exit(1);
|
|
308
403
|
}
|
|
309
404
|
});
|
|
310
|
-
program.command("start").description("Start production server").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--port <port>", "Server port", "3000").action(async (options) => {
|
|
405
|
+
program.command("start").description("Start production server").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--port <port>", "Server port", "3000").option("--tunnel", "Expose server through a tunnel").action(async (options) => {
|
|
311
406
|
try {
|
|
312
407
|
const projectPath = path.resolve(options.path);
|
|
313
408
|
const port = parseInt(options.port, 10);
|
|
314
409
|
console.log(`\x1B[36m\x1B[1mmcp-use\x1B[0m \x1B[90mVersion: ${packageJson.version}\x1B[0m
|
|
315
410
|
`);
|
|
411
|
+
let mcpUrl;
|
|
412
|
+
let tunnelProcess = void 0;
|
|
413
|
+
if (options.tunnel) {
|
|
414
|
+
try {
|
|
415
|
+
const tunnelInfo = await startTunnel(port);
|
|
416
|
+
mcpUrl = tunnelInfo.subdomain;
|
|
417
|
+
tunnelProcess = tunnelInfo.process;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error(chalk.red("Failed to start tunnel:"), error);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
316
423
|
let serverFile = "dist/index.js";
|
|
317
424
|
try {
|
|
318
425
|
await access(path.join(projectPath, serverFile));
|
|
@@ -320,15 +427,47 @@ program.command("start").description("Start production server").option("-p, --pa
|
|
|
320
427
|
serverFile = "dist/server.js";
|
|
321
428
|
}
|
|
322
429
|
console.log("Starting production server...");
|
|
430
|
+
const env = {
|
|
431
|
+
...process.env,
|
|
432
|
+
PORT: String(port),
|
|
433
|
+
NODE_ENV: "production"
|
|
434
|
+
};
|
|
435
|
+
if (mcpUrl) {
|
|
436
|
+
env.MCP_URL = mcpUrl;
|
|
437
|
+
console.log(chalk.whiteBright(`Tunnel: ${mcpUrl}`));
|
|
438
|
+
}
|
|
323
439
|
const serverProc = spawn("node", [serverFile], {
|
|
324
440
|
cwd: projectPath,
|
|
325
441
|
stdio: "inherit",
|
|
326
|
-
env
|
|
442
|
+
env
|
|
327
443
|
});
|
|
328
444
|
const cleanup = () => {
|
|
329
445
|
console.log("\n\nShutting down...");
|
|
330
|
-
|
|
331
|
-
|
|
446
|
+
const processesToKill = 1 + (tunnelProcess ? 1 : 0);
|
|
447
|
+
let killedCount = 0;
|
|
448
|
+
const checkAndExit = () => {
|
|
449
|
+
killedCount++;
|
|
450
|
+
if (killedCount >= processesToKill) {
|
|
451
|
+
process.exit(0);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
serverProc.on("exit", checkAndExit);
|
|
455
|
+
serverProc.kill("SIGTERM");
|
|
456
|
+
if (tunnelProcess && typeof tunnelProcess.kill === "function") {
|
|
457
|
+
tunnelProcess.on("exit", checkAndExit);
|
|
458
|
+
tunnelProcess.kill("SIGTERM");
|
|
459
|
+
} else {
|
|
460
|
+
checkAndExit();
|
|
461
|
+
}
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
if (serverProc.exitCode === null) {
|
|
464
|
+
serverProc.kill("SIGKILL");
|
|
465
|
+
}
|
|
466
|
+
if (tunnelProcess && tunnelProcess.exitCode === null) {
|
|
467
|
+
tunnelProcess.kill("SIGKILL");
|
|
468
|
+
}
|
|
469
|
+
process.exit(0);
|
|
470
|
+
}, 1e3);
|
|
332
471
|
};
|
|
333
472
|
process.on("SIGINT", cleanup);
|
|
334
473
|
process.on("SIGTERM", cleanup);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-use/cli",
|
|
3
|
-
"version": "2.1.20
|
|
3
|
+
"version": "2.1.20",
|
|
4
4
|
"description": "Build tool for MCP UI widgets - bundles React components into standalone HTML pages for Model Context Protocol servers",
|
|
5
5
|
"author": "mcp-use, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"tsx": "^4.0.0",
|
|
45
45
|
"vite": "^6.0.0",
|
|
46
46
|
"ws": "^8.18.0",
|
|
47
|
-
"@mcp-use/inspector": "0.4.8
|
|
48
|
-
"mcp-use": "1.1.8
|
|
47
|
+
"@mcp-use/inspector": "0.4.8",
|
|
48
|
+
"mcp-use": "1.1.8"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^20.0.0",
|