@mcp-use/cli 2.5.6 → 2.6.0-canary.11
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/commands/auth.d.ts.map +1 -1
- package/dist/commands/client.d.ts +95 -0
- package/dist/commands/client.d.ts.map +1 -0
- package/dist/index.cjs +4087 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +3101 -1029
- package/dist/index.js.map +1 -0
- package/dist/utils/format.d.ts +58 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/session-storage.d.ts +65 -0
- package/dist/utils/session-storage.d.ts.map +1 -0
- package/package.json +20 -8
- package/dist/index.mjs +0 -1976
package/dist/index.mjs
DELETED
|
@@ -1,1976 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/index.ts
|
|
4
|
-
import chalk3 from "chalk";
|
|
5
|
-
import { Command } from "commander";
|
|
6
|
-
import "dotenv/config";
|
|
7
|
-
import { spawn } from "child_process";
|
|
8
|
-
import { readFileSync } from "fs";
|
|
9
|
-
import { access, mkdir, readFile, writeFile } from "fs/promises";
|
|
10
|
-
import path3 from "path";
|
|
11
|
-
import open3 from "open";
|
|
12
|
-
|
|
13
|
-
// src/commands/auth.ts
|
|
14
|
-
import chalk from "chalk";
|
|
15
|
-
import {
|
|
16
|
-
createServer
|
|
17
|
-
} from "http";
|
|
18
|
-
import open from "open";
|
|
19
|
-
|
|
20
|
-
// src/utils/config.ts
|
|
21
|
-
import { promises as fs } from "fs";
|
|
22
|
-
import path from "path";
|
|
23
|
-
import os from "os";
|
|
24
|
-
var CONFIG_DIR = path.join(os.homedir(), ".mcp-use");
|
|
25
|
-
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
26
|
-
var DEFAULT_API_URL = process.env.MCP_API_URL ? process.env.MCP_API_URL.replace(/\/api\/v1$/, "") + "/api/v1" : "https://cloud.mcp-use.com/api/v1";
|
|
27
|
-
var DEFAULT_WEB_URL = process.env.MCP_WEB_URL ? process.env.MCP_WEB_URL : "https://mcp-use.com";
|
|
28
|
-
async function ensureConfigDir() {
|
|
29
|
-
try {
|
|
30
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
31
|
-
} catch (error) {
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async function readConfig() {
|
|
35
|
-
try {
|
|
36
|
-
const content = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
37
|
-
return JSON.parse(content);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function writeConfig(config) {
|
|
43
|
-
await ensureConfigDir();
|
|
44
|
-
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
45
|
-
}
|
|
46
|
-
async function deleteConfig() {
|
|
47
|
-
try {
|
|
48
|
-
await fs.unlink(CONFIG_FILE);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function getApiUrl() {
|
|
53
|
-
const config = await readConfig();
|
|
54
|
-
return config.apiUrl || DEFAULT_API_URL;
|
|
55
|
-
}
|
|
56
|
-
async function getApiKey() {
|
|
57
|
-
const config = await readConfig();
|
|
58
|
-
return config.apiKey || null;
|
|
59
|
-
}
|
|
60
|
-
async function isLoggedIn() {
|
|
61
|
-
const apiKey = await getApiKey();
|
|
62
|
-
return !!apiKey;
|
|
63
|
-
}
|
|
64
|
-
async function getWebUrl() {
|
|
65
|
-
return DEFAULT_WEB_URL;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/utils/api.ts
|
|
69
|
-
var McpUseAPI = class _McpUseAPI {
|
|
70
|
-
baseUrl;
|
|
71
|
-
apiKey;
|
|
72
|
-
constructor(baseUrl, apiKey) {
|
|
73
|
-
this.baseUrl = baseUrl || "";
|
|
74
|
-
this.apiKey = apiKey;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Initialize API client with config
|
|
78
|
-
*/
|
|
79
|
-
static async create() {
|
|
80
|
-
const baseUrl = await getApiUrl();
|
|
81
|
-
const apiKey = await getApiKey();
|
|
82
|
-
return new _McpUseAPI(baseUrl, apiKey ?? void 0);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Make authenticated request
|
|
86
|
-
*/
|
|
87
|
-
async request(endpoint, options = {}) {
|
|
88
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
89
|
-
const headers = {
|
|
90
|
-
"Content-Type": "application/json",
|
|
91
|
-
...options.headers || {}
|
|
92
|
-
};
|
|
93
|
-
if (this.apiKey) {
|
|
94
|
-
headers["x-api-key"] = this.apiKey;
|
|
95
|
-
}
|
|
96
|
-
const response = await fetch(url, {
|
|
97
|
-
...options,
|
|
98
|
-
headers
|
|
99
|
-
});
|
|
100
|
-
if (!response.ok) {
|
|
101
|
-
const error = await response.text();
|
|
102
|
-
throw new Error(`API request failed: ${response.status} ${error}`);
|
|
103
|
-
}
|
|
104
|
-
return response.json();
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Create API key using JWT token
|
|
108
|
-
*/
|
|
109
|
-
async createApiKey(jwtToken, name = "CLI") {
|
|
110
|
-
const url = `${this.baseUrl}/api-key`;
|
|
111
|
-
const response = await fetch(url, {
|
|
112
|
-
method: "POST",
|
|
113
|
-
headers: {
|
|
114
|
-
"Content-Type": "application/json",
|
|
115
|
-
Authorization: `Bearer ${jwtToken}`
|
|
116
|
-
},
|
|
117
|
-
body: JSON.stringify({ name })
|
|
118
|
-
});
|
|
119
|
-
if (!response.ok) {
|
|
120
|
-
const error = await response.text();
|
|
121
|
-
throw new Error(`Failed to create API key: ${response.status} ${error}`);
|
|
122
|
-
}
|
|
123
|
-
return response.json();
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Test authentication
|
|
127
|
-
*/
|
|
128
|
-
async testAuth() {
|
|
129
|
-
return this.request("/test-auth");
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Create deployment
|
|
133
|
-
*/
|
|
134
|
-
async createDeployment(request) {
|
|
135
|
-
return this.request("/deployments", {
|
|
136
|
-
method: "POST",
|
|
137
|
-
body: JSON.stringify(request)
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Get deployment by ID
|
|
142
|
-
*/
|
|
143
|
-
async getDeployment(deploymentId) {
|
|
144
|
-
return this.request(`/deployments/${deploymentId}`);
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Stream deployment logs
|
|
148
|
-
*/
|
|
149
|
-
async *streamDeploymentLogs(deploymentId) {
|
|
150
|
-
const url = `${this.baseUrl}/deployments/${deploymentId}/logs/stream`;
|
|
151
|
-
const headers = {};
|
|
152
|
-
if (this.apiKey) {
|
|
153
|
-
headers["x-api-key"] = this.apiKey;
|
|
154
|
-
}
|
|
155
|
-
const response = await fetch(url, { headers });
|
|
156
|
-
if (!response.ok) {
|
|
157
|
-
throw new Error(`Failed to stream logs: ${response.status}`);
|
|
158
|
-
}
|
|
159
|
-
if (!response.body) {
|
|
160
|
-
throw new Error("Response body is null");
|
|
161
|
-
}
|
|
162
|
-
const reader = response.body.getReader();
|
|
163
|
-
const decoder = new TextDecoder();
|
|
164
|
-
let buffer = "";
|
|
165
|
-
try {
|
|
166
|
-
while (true) {
|
|
167
|
-
const { done, value } = await reader.read();
|
|
168
|
-
if (done) break;
|
|
169
|
-
buffer += decoder.decode(value, { stream: true });
|
|
170
|
-
const lines = buffer.split("\n");
|
|
171
|
-
buffer = lines.pop() || "";
|
|
172
|
-
for (const line of lines) {
|
|
173
|
-
if (line.startsWith("data: ")) {
|
|
174
|
-
const data = line.slice(6);
|
|
175
|
-
try {
|
|
176
|
-
const parsed = JSON.parse(data);
|
|
177
|
-
if (parsed.log) {
|
|
178
|
-
yield parsed.log;
|
|
179
|
-
} else if (parsed.error) {
|
|
180
|
-
throw new Error(parsed.error);
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} finally {
|
|
188
|
-
reader.releaseLock();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Create deployment with source code upload
|
|
193
|
-
*/
|
|
194
|
-
async createDeploymentWithUpload(request, filePath) {
|
|
195
|
-
const { readFile: readFile2 } = await import("fs/promises");
|
|
196
|
-
const { basename } = await import("path");
|
|
197
|
-
const { stat } = await import("fs/promises");
|
|
198
|
-
const stats = await stat(filePath);
|
|
199
|
-
const maxSize = 2 * 1024 * 1024;
|
|
200
|
-
if (stats.size > maxSize) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
`File size (${(stats.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum of 2MB`
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
const fileBuffer = await readFile2(filePath);
|
|
206
|
-
const filename = basename(filePath);
|
|
207
|
-
const formData = new FormData();
|
|
208
|
-
const blob = new Blob([fileBuffer], { type: "application/gzip" });
|
|
209
|
-
formData.append("source_file", blob, filename);
|
|
210
|
-
formData.append("name", request.name);
|
|
211
|
-
formData.append("source_type", "upload");
|
|
212
|
-
if (request.source.type === "upload") {
|
|
213
|
-
formData.append("runtime", request.source.runtime || "node");
|
|
214
|
-
formData.append("port", String(request.source.port || 3e3));
|
|
215
|
-
if (request.source.startCommand) {
|
|
216
|
-
formData.append("startCommand", request.source.startCommand);
|
|
217
|
-
}
|
|
218
|
-
if (request.source.buildCommand) {
|
|
219
|
-
formData.append("buildCommand", request.source.buildCommand);
|
|
220
|
-
}
|
|
221
|
-
if (request.source.env && Object.keys(request.source.env).length > 0) {
|
|
222
|
-
formData.append("env", JSON.stringify(request.source.env));
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (request.customDomain) {
|
|
226
|
-
formData.append("customDomain", request.customDomain);
|
|
227
|
-
}
|
|
228
|
-
if (request.healthCheckPath) {
|
|
229
|
-
formData.append("healthCheckPath", request.healthCheckPath);
|
|
230
|
-
}
|
|
231
|
-
const url = `${this.baseUrl}/deployments`;
|
|
232
|
-
const headers = {};
|
|
233
|
-
if (this.apiKey) {
|
|
234
|
-
headers["x-api-key"] = this.apiKey;
|
|
235
|
-
}
|
|
236
|
-
const response = await fetch(url, {
|
|
237
|
-
method: "POST",
|
|
238
|
-
headers,
|
|
239
|
-
body: formData
|
|
240
|
-
});
|
|
241
|
-
if (!response.ok) {
|
|
242
|
-
const error = await response.text();
|
|
243
|
-
throw new Error(`Deployment failed: ${error}`);
|
|
244
|
-
}
|
|
245
|
-
return response.json();
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// src/commands/auth.ts
|
|
250
|
-
var LOGIN_TIMEOUT = 3e5;
|
|
251
|
-
async function findAvailablePort(startPort = 8765) {
|
|
252
|
-
for (let port = startPort; port < startPort + 100; port++) {
|
|
253
|
-
try {
|
|
254
|
-
await new Promise((resolve, reject) => {
|
|
255
|
-
const server = createServer();
|
|
256
|
-
server.once("error", reject);
|
|
257
|
-
server.once("listening", () => {
|
|
258
|
-
server.close();
|
|
259
|
-
resolve();
|
|
260
|
-
});
|
|
261
|
-
server.listen(port);
|
|
262
|
-
});
|
|
263
|
-
return port;
|
|
264
|
-
} catch {
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
throw new Error("No available ports found");
|
|
269
|
-
}
|
|
270
|
-
async function startCallbackServer(port) {
|
|
271
|
-
return new Promise((resolve, reject) => {
|
|
272
|
-
let tokenResolver = null;
|
|
273
|
-
const tokenPromise = new Promise((res) => {
|
|
274
|
-
tokenResolver = res;
|
|
275
|
-
});
|
|
276
|
-
const server = createServer((req, res) => {
|
|
277
|
-
if (req.url?.startsWith("/callback")) {
|
|
278
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
279
|
-
const token = url.searchParams.get("token");
|
|
280
|
-
if (token && tokenResolver) {
|
|
281
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
282
|
-
res.end(`
|
|
283
|
-
<!DOCTYPE html>
|
|
284
|
-
<html>
|
|
285
|
-
<head>
|
|
286
|
-
<title>Login Successful</title>
|
|
287
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
288
|
-
<style>
|
|
289
|
-
* {
|
|
290
|
-
margin: 0;
|
|
291
|
-
padding: 0;
|
|
292
|
-
box-sizing: border-box;
|
|
293
|
-
}
|
|
294
|
-
body {
|
|
295
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
296
|
-
display: flex;
|
|
297
|
-
justify-content: center;
|
|
298
|
-
align-items: center;
|
|
299
|
-
min-height: 100vh;
|
|
300
|
-
background: #000;
|
|
301
|
-
padding: 1rem;
|
|
302
|
-
}
|
|
303
|
-
.container {
|
|
304
|
-
width: 100%;
|
|
305
|
-
max-width: 28rem;
|
|
306
|
-
padding: 3rem;
|
|
307
|
-
text-align: center;
|
|
308
|
-
-webkit-backdrop-filter: blur(40px);
|
|
309
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
310
|
-
border-radius: 1.5rem;
|
|
311
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
312
|
-
}
|
|
313
|
-
.icon-container {
|
|
314
|
-
display: inline-flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
justify-content: center;
|
|
317
|
-
width: 6rem;
|
|
318
|
-
height: 6rem;
|
|
319
|
-
margin-bottom: 2rem;
|
|
320
|
-
background: rgba(255, 255, 255, 0.1);
|
|
321
|
-
backdrop-filter: blur(10px);
|
|
322
|
-
-webkit-backdrop-filter: blur(10px);
|
|
323
|
-
border-radius: 50%;
|
|
324
|
-
}
|
|
325
|
-
.checkmark {
|
|
326
|
-
font-size: 4rem;
|
|
327
|
-
color: #fff;
|
|
328
|
-
line-height: 1;
|
|
329
|
-
animation: scaleIn 0.5s ease-out;
|
|
330
|
-
}
|
|
331
|
-
@keyframes scaleIn {
|
|
332
|
-
from {
|
|
333
|
-
transform: scale(0);
|
|
334
|
-
opacity: 0;
|
|
335
|
-
}
|
|
336
|
-
to {
|
|
337
|
-
transform: scale(1);
|
|
338
|
-
opacity: 1;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
h1 {
|
|
342
|
-
color: #fff;
|
|
343
|
-
margin: 0 0 1rem 0;
|
|
344
|
-
font-size: 2.5rem;
|
|
345
|
-
font-weight: 700;
|
|
346
|
-
letter-spacing: -0.025em;
|
|
347
|
-
}
|
|
348
|
-
p {
|
|
349
|
-
color: rgba(255, 255, 255, 0.8);
|
|
350
|
-
margin: 0 0 2rem 0;
|
|
351
|
-
font-size: 1.125rem;
|
|
352
|
-
line-height: 1.5;
|
|
353
|
-
}
|
|
354
|
-
.spinner {
|
|
355
|
-
display: inline-block;
|
|
356
|
-
width: 2rem;
|
|
357
|
-
height: 2rem;
|
|
358
|
-
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
359
|
-
border-top-color: #fff;
|
|
360
|
-
border-radius: 50%;
|
|
361
|
-
animation: spin 0.8s linear infinite;
|
|
362
|
-
}
|
|
363
|
-
@keyframes spin {
|
|
364
|
-
to { transform: rotate(360deg); }
|
|
365
|
-
}
|
|
366
|
-
.footer {
|
|
367
|
-
margin-top: 2rem;
|
|
368
|
-
color: rgba(255, 255, 255, 0.6);
|
|
369
|
-
font-size: 0.875rem;
|
|
370
|
-
}
|
|
371
|
-
</style>
|
|
372
|
-
</head>
|
|
373
|
-
<body>
|
|
374
|
-
<div class="container">
|
|
375
|
-
<h1>Authentication Successful!</h1>
|
|
376
|
-
<p>You can now close this window and return to the CLI.</p>
|
|
377
|
-
</div>
|
|
378
|
-
</body>
|
|
379
|
-
</html>
|
|
380
|
-
`);
|
|
381
|
-
tokenResolver(token);
|
|
382
|
-
} else {
|
|
383
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
384
|
-
res.end(`
|
|
385
|
-
<!DOCTYPE html>
|
|
386
|
-
<html>
|
|
387
|
-
<head>
|
|
388
|
-
<title>Login Failed</title>
|
|
389
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
390
|
-
<style>
|
|
391
|
-
* {
|
|
392
|
-
margin: 0;
|
|
393
|
-
padding: 0;
|
|
394
|
-
box-sizing: border-box;
|
|
395
|
-
}
|
|
396
|
-
body {
|
|
397
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
398
|
-
display: flex;
|
|
399
|
-
justify-content: center;
|
|
400
|
-
align-items: center;
|
|
401
|
-
min-height: 100vh;
|
|
402
|
-
background: #000;
|
|
403
|
-
padding: 1rem;
|
|
404
|
-
}
|
|
405
|
-
.container {
|
|
406
|
-
width: 100%;
|
|
407
|
-
max-width: 28rem;
|
|
408
|
-
padding: 3rem;
|
|
409
|
-
text-align: center;
|
|
410
|
-
background: rgba(255, 255, 255, 0.1);
|
|
411
|
-
backdrop-filter: blur(40px);
|
|
412
|
-
-webkit-backdrop-filter: blur(40px);
|
|
413
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
414
|
-
border-radius: 1.5rem;
|
|
415
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
416
|
-
}
|
|
417
|
-
.icon-container {
|
|
418
|
-
display: inline-flex;
|
|
419
|
-
align-items: center;
|
|
420
|
-
justify-content: center;
|
|
421
|
-
width: 6rem;
|
|
422
|
-
height: 6rem;
|
|
423
|
-
margin-bottom: 2rem;
|
|
424
|
-
background: rgba(255, 255, 255, 0.1);
|
|
425
|
-
backdrop-filter: blur(10px);
|
|
426
|
-
-webkit-backdrop-filter: blur(10px);
|
|
427
|
-
border-radius: 50%;
|
|
428
|
-
}
|
|
429
|
-
.cross {
|
|
430
|
-
font-size: 4rem;
|
|
431
|
-
color: #fff;
|
|
432
|
-
line-height: 1;
|
|
433
|
-
}
|
|
434
|
-
h1 {
|
|
435
|
-
color: #fff;
|
|
436
|
-
margin: 0 0 1rem 0;
|
|
437
|
-
font-size: 2.5rem;
|
|
438
|
-
font-weight: 700;
|
|
439
|
-
letter-spacing: -0.025em;
|
|
440
|
-
}
|
|
441
|
-
p {
|
|
442
|
-
color: rgba(255, 255, 255, 0.8);
|
|
443
|
-
margin: 0;
|
|
444
|
-
font-size: 1.125rem;
|
|
445
|
-
line-height: 1.5;
|
|
446
|
-
}
|
|
447
|
-
</style>
|
|
448
|
-
</head>
|
|
449
|
-
<body>
|
|
450
|
-
<div class="container">
|
|
451
|
-
<div class="icon-container">
|
|
452
|
-
<div class="cross">\u2717</div>
|
|
453
|
-
</div>
|
|
454
|
-
<h1>Login Failed</h1>
|
|
455
|
-
<p>No token received. Please try again.</p>
|
|
456
|
-
</div>
|
|
457
|
-
</body>
|
|
458
|
-
</html>
|
|
459
|
-
`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
server.listen(port, () => {
|
|
464
|
-
resolve({ server, token: tokenPromise });
|
|
465
|
-
});
|
|
466
|
-
server.on("error", reject);
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
async function loginCommand() {
|
|
470
|
-
try {
|
|
471
|
-
if (await isLoggedIn()) {
|
|
472
|
-
console.log(
|
|
473
|
-
chalk.yellow(
|
|
474
|
-
"\u26A0\uFE0F You are already logged in. Run 'mcp-use logout' first if you want to login with a different account."
|
|
475
|
-
)
|
|
476
|
-
);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
console.log(chalk.cyan.bold("\u{1F510} Logging in to mcp-use cloud...\n"));
|
|
480
|
-
const port = await findAvailablePort();
|
|
481
|
-
const redirectUri = `http://localhost:${port}/callback`;
|
|
482
|
-
console.log(chalk.gray(`Starting local server on port ${port}...`));
|
|
483
|
-
const { server, token } = await startCallbackServer(port);
|
|
484
|
-
const webUrl = await getWebUrl();
|
|
485
|
-
const loginUrl = `${webUrl}/auth/cli?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
486
|
-
console.log(chalk.gray(`Opening browser to ${webUrl}/auth/cli...
|
|
487
|
-
`));
|
|
488
|
-
console.log(
|
|
489
|
-
chalk.white(
|
|
490
|
-
"If the browser doesn't open automatically, please visit:\n" + chalk.cyan(loginUrl)
|
|
491
|
-
)
|
|
492
|
-
);
|
|
493
|
-
await open(loginUrl);
|
|
494
|
-
console.log(
|
|
495
|
-
chalk.gray("\nWaiting for authentication... (this may take a moment)")
|
|
496
|
-
);
|
|
497
|
-
const jwtToken = await Promise.race([
|
|
498
|
-
token,
|
|
499
|
-
new Promise(
|
|
500
|
-
(_, reject) => setTimeout(
|
|
501
|
-
() => reject(new Error("Login timeout - please try again")),
|
|
502
|
-
LOGIN_TIMEOUT
|
|
503
|
-
)
|
|
504
|
-
)
|
|
505
|
-
]);
|
|
506
|
-
server.close();
|
|
507
|
-
console.log(
|
|
508
|
-
chalk.gray("Received authentication token, creating API key...")
|
|
509
|
-
);
|
|
510
|
-
const api = await McpUseAPI.create();
|
|
511
|
-
const apiKeyResponse = await api.createApiKey(jwtToken, "CLI");
|
|
512
|
-
await writeConfig({
|
|
513
|
-
apiKey: apiKeyResponse.api_key
|
|
514
|
-
});
|
|
515
|
-
console.log(chalk.green.bold("\n\u2713 Successfully logged in!"));
|
|
516
|
-
console.log(
|
|
517
|
-
chalk.gray(
|
|
518
|
-
`
|
|
519
|
-
Your API key has been saved to ${chalk.white("~/.mcp-use/config.json")}`
|
|
520
|
-
)
|
|
521
|
-
);
|
|
522
|
-
console.log(
|
|
523
|
-
chalk.gray(
|
|
524
|
-
"You can now deploy your MCP servers with " + chalk.white("mcp-use deploy")
|
|
525
|
-
)
|
|
526
|
-
);
|
|
527
|
-
process.exit(0);
|
|
528
|
-
} catch (error) {
|
|
529
|
-
console.error(
|
|
530
|
-
chalk.red.bold("\n\u2717 Login failed:"),
|
|
531
|
-
chalk.red(error instanceof Error ? error.message : "Unknown error")
|
|
532
|
-
);
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
async function logoutCommand() {
|
|
537
|
-
try {
|
|
538
|
-
if (!await isLoggedIn()) {
|
|
539
|
-
console.log(chalk.yellow("\u26A0\uFE0F You are not logged in."));
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
console.log(chalk.cyan.bold("\u{1F513} Logging out...\n"));
|
|
543
|
-
await deleteConfig();
|
|
544
|
-
console.log(chalk.green.bold("\u2713 Successfully logged out!"));
|
|
545
|
-
console.log(
|
|
546
|
-
chalk.gray(
|
|
547
|
-
"\nYour local config has been deleted. The API key will remain active until revoked from the web interface."
|
|
548
|
-
)
|
|
549
|
-
);
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error(
|
|
552
|
-
chalk.red.bold("\n\u2717 Logout failed:"),
|
|
553
|
-
chalk.red(error instanceof Error ? error.message : "Unknown error")
|
|
554
|
-
);
|
|
555
|
-
process.exit(1);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
async function whoamiCommand() {
|
|
559
|
-
try {
|
|
560
|
-
if (!await isLoggedIn()) {
|
|
561
|
-
console.log(chalk.yellow("\u26A0\uFE0F You are not logged in."));
|
|
562
|
-
console.log(
|
|
563
|
-
chalk.gray("Run " + chalk.white("mcp-use login") + " to get started.")
|
|
564
|
-
);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
console.log(chalk.cyan.bold("\u{1F464} Current user:\n"));
|
|
568
|
-
const api = await McpUseAPI.create();
|
|
569
|
-
const authInfo = await api.testAuth();
|
|
570
|
-
console.log(chalk.white("Email: ") + chalk.cyan(authInfo.email));
|
|
571
|
-
console.log(chalk.white("User ID: ") + chalk.gray(authInfo.user_id));
|
|
572
|
-
const apiKey = await getApiKey();
|
|
573
|
-
if (apiKey) {
|
|
574
|
-
const masked = apiKey.substring(0, 8) + "..." + apiKey.substring(apiKey.length - 4);
|
|
575
|
-
console.log(chalk.white("API Key: ") + chalk.gray(masked));
|
|
576
|
-
}
|
|
577
|
-
} catch (error) {
|
|
578
|
-
console.error(
|
|
579
|
-
chalk.red.bold("\n\u2717 Failed to get user info:"),
|
|
580
|
-
chalk.red(error instanceof Error ? error.message : "Unknown error")
|
|
581
|
-
);
|
|
582
|
-
process.exit(1);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// src/commands/deploy.ts
|
|
587
|
-
import chalk2 from "chalk";
|
|
588
|
-
import { promises as fs2 } from "fs";
|
|
589
|
-
import path2 from "path";
|
|
590
|
-
import os2 from "os";
|
|
591
|
-
import { exec as exec2 } from "child_process";
|
|
592
|
-
import { promisify as promisify2 } from "util";
|
|
593
|
-
|
|
594
|
-
// src/utils/git.ts
|
|
595
|
-
import { exec } from "child_process";
|
|
596
|
-
import { promisify } from "util";
|
|
597
|
-
var execAsync = promisify(exec);
|
|
598
|
-
async function gitCommand(command, cwd = process.cwd()) {
|
|
599
|
-
try {
|
|
600
|
-
const { stdout } = await execAsync(command, { cwd });
|
|
601
|
-
return stdout.trim();
|
|
602
|
-
} catch (error) {
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
async function isGitRepo(cwd = process.cwd()) {
|
|
607
|
-
const result = await gitCommand("git rev-parse --is-inside-work-tree", cwd);
|
|
608
|
-
return result === "true";
|
|
609
|
-
}
|
|
610
|
-
async function getRemoteUrl(cwd = process.cwd()) {
|
|
611
|
-
return gitCommand("git config --get remote.origin.url", cwd);
|
|
612
|
-
}
|
|
613
|
-
function parseGitHubUrl(url) {
|
|
614
|
-
const sshMatch = url.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
615
|
-
const httpsMatch = url.match(
|
|
616
|
-
/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/
|
|
617
|
-
);
|
|
618
|
-
const match = sshMatch || httpsMatch;
|
|
619
|
-
if (!match) return null;
|
|
620
|
-
return {
|
|
621
|
-
owner: match[1],
|
|
622
|
-
repo: match[2]
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
async function getCurrentBranch(cwd = process.cwd()) {
|
|
626
|
-
return gitCommand("git rev-parse --abbrev-ref HEAD", cwd);
|
|
627
|
-
}
|
|
628
|
-
async function getCommitSha(cwd = process.cwd()) {
|
|
629
|
-
return gitCommand("git rev-parse HEAD", cwd);
|
|
630
|
-
}
|
|
631
|
-
async function getCommitMessage(cwd = process.cwd()) {
|
|
632
|
-
return gitCommand("git log -1 --pretty=%B", cwd);
|
|
633
|
-
}
|
|
634
|
-
async function getGitInfo(cwd = process.cwd()) {
|
|
635
|
-
const isRepo = await isGitRepo(cwd);
|
|
636
|
-
if (!isRepo) {
|
|
637
|
-
return { isGitRepo: false };
|
|
638
|
-
}
|
|
639
|
-
const remoteUrl = await getRemoteUrl(cwd);
|
|
640
|
-
const branch = await getCurrentBranch(cwd);
|
|
641
|
-
const commitSha = await getCommitSha(cwd);
|
|
642
|
-
const commitMessage = await getCommitMessage(cwd);
|
|
643
|
-
let owner;
|
|
644
|
-
let repo;
|
|
645
|
-
if (remoteUrl) {
|
|
646
|
-
const parsed = parseGitHubUrl(remoteUrl);
|
|
647
|
-
if (parsed) {
|
|
648
|
-
owner = parsed.owner;
|
|
649
|
-
repo = parsed.repo;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return {
|
|
653
|
-
isGitRepo: true,
|
|
654
|
-
remoteUrl: remoteUrl || void 0,
|
|
655
|
-
owner,
|
|
656
|
-
repo,
|
|
657
|
-
branch: branch || void 0,
|
|
658
|
-
commitSha: commitSha || void 0,
|
|
659
|
-
commitMessage: commitMessage || void 0
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
function isGitHubUrl(url) {
|
|
663
|
-
return url.includes("github.com");
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// src/commands/deploy.ts
|
|
667
|
-
import open2 from "open";
|
|
668
|
-
var execAsync2 = promisify2(exec2);
|
|
669
|
-
async function isMcpProject(cwd = process.cwd()) {
|
|
670
|
-
try {
|
|
671
|
-
const packageJsonPath = path2.join(cwd, "package.json");
|
|
672
|
-
const content = await fs2.readFile(packageJsonPath, "utf-8");
|
|
673
|
-
const packageJson2 = JSON.parse(content);
|
|
674
|
-
const hasMcpDeps = packageJson2.dependencies?.["mcp-use"] || packageJson2.dependencies?.["@modelcontextprotocol/sdk"] || packageJson2.devDependencies?.["mcp-use"] || packageJson2.devDependencies?.["@modelcontextprotocol/sdk"];
|
|
675
|
-
const hasMcpScripts = packageJson2.scripts?.mcp || packageJson2.scripts?.["mcp:dev"];
|
|
676
|
-
return !!(hasMcpDeps || hasMcpScripts);
|
|
677
|
-
} catch {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
async function getProjectName(cwd = process.cwd()) {
|
|
682
|
-
try {
|
|
683
|
-
const packageJsonPath = path2.join(cwd, "package.json");
|
|
684
|
-
const content = await fs2.readFile(packageJsonPath, "utf-8");
|
|
685
|
-
const packageJson2 = JSON.parse(content);
|
|
686
|
-
if (packageJson2.name) {
|
|
687
|
-
return packageJson2.name;
|
|
688
|
-
}
|
|
689
|
-
} catch {
|
|
690
|
-
}
|
|
691
|
-
return path2.basename(cwd);
|
|
692
|
-
}
|
|
693
|
-
async function detectBuildCommand(cwd = process.cwd()) {
|
|
694
|
-
try {
|
|
695
|
-
const packageJsonPath = path2.join(cwd, "package.json");
|
|
696
|
-
const content = await fs2.readFile(packageJsonPath, "utf-8");
|
|
697
|
-
const packageJson2 = JSON.parse(content);
|
|
698
|
-
if (packageJson2.scripts?.build) {
|
|
699
|
-
return "npm run build";
|
|
700
|
-
}
|
|
701
|
-
} catch {
|
|
702
|
-
}
|
|
703
|
-
return void 0;
|
|
704
|
-
}
|
|
705
|
-
async function detectStartCommand(cwd = process.cwd()) {
|
|
706
|
-
try {
|
|
707
|
-
const packageJsonPath = path2.join(cwd, "package.json");
|
|
708
|
-
const content = await fs2.readFile(packageJsonPath, "utf-8");
|
|
709
|
-
const packageJson2 = JSON.parse(content);
|
|
710
|
-
if (packageJson2.scripts?.start) {
|
|
711
|
-
return "npm start";
|
|
712
|
-
}
|
|
713
|
-
if (packageJson2.main) {
|
|
714
|
-
return `node ${packageJson2.main}`;
|
|
715
|
-
}
|
|
716
|
-
} catch {
|
|
717
|
-
}
|
|
718
|
-
return void 0;
|
|
719
|
-
}
|
|
720
|
-
async function detectRuntime(cwd = process.cwd()) {
|
|
721
|
-
try {
|
|
722
|
-
const pythonFiles = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
723
|
-
for (const file of pythonFiles) {
|
|
724
|
-
try {
|
|
725
|
-
await fs2.access(path2.join(cwd, file));
|
|
726
|
-
return "python";
|
|
727
|
-
} catch {
|
|
728
|
-
continue;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
try {
|
|
732
|
-
await fs2.access(path2.join(cwd, "package.json"));
|
|
733
|
-
return "node";
|
|
734
|
-
} catch {
|
|
735
|
-
}
|
|
736
|
-
} catch {
|
|
737
|
-
}
|
|
738
|
-
return "node";
|
|
739
|
-
}
|
|
740
|
-
async function prompt(question, defaultValue = "n") {
|
|
741
|
-
const readline = await import("readline");
|
|
742
|
-
const rl = readline.createInterface({
|
|
743
|
-
input: process.stdin,
|
|
744
|
-
output: process.stdout
|
|
745
|
-
});
|
|
746
|
-
const defaultIndicator = defaultValue === "y" ? "Y/n" : "y/N";
|
|
747
|
-
const questionWithDefault = question.replace(
|
|
748
|
-
/(\(y\/n\):)/,
|
|
749
|
-
`(${defaultIndicator}):`
|
|
750
|
-
);
|
|
751
|
-
return new Promise((resolve) => {
|
|
752
|
-
rl.question(questionWithDefault, (answer) => {
|
|
753
|
-
rl.close();
|
|
754
|
-
const trimmedAnswer = answer.trim().toLowerCase();
|
|
755
|
-
if (trimmedAnswer === "") {
|
|
756
|
-
resolve(defaultValue === "y");
|
|
757
|
-
} else {
|
|
758
|
-
resolve(trimmedAnswer === "y" || trimmedAnswer === "yes");
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
async function createTarball(cwd) {
|
|
764
|
-
const tmpDir = os2.tmpdir();
|
|
765
|
-
const tarballPath = path2.join(tmpDir, `mcp-deploy-${Date.now()}.tar.gz`);
|
|
766
|
-
const excludePatterns = [
|
|
767
|
-
"node_modules",
|
|
768
|
-
".git",
|
|
769
|
-
"dist",
|
|
770
|
-
"build",
|
|
771
|
-
".next",
|
|
772
|
-
".venv",
|
|
773
|
-
"__pycache__",
|
|
774
|
-
"*.pyc",
|
|
775
|
-
".DS_Store",
|
|
776
|
-
"._*",
|
|
777
|
-
// macOS resource fork files
|
|
778
|
-
".mcp-use",
|
|
779
|
-
// Build artifacts directory
|
|
780
|
-
".env",
|
|
781
|
-
".env.local",
|
|
782
|
-
"*.log"
|
|
783
|
-
];
|
|
784
|
-
const excludeFlags = excludePatterns.map((pattern) => `--exclude=${pattern}`).join(" ");
|
|
785
|
-
const command = `tar ${excludeFlags} -czf "${tarballPath}" -C "${cwd}" . 2>&1 || true`;
|
|
786
|
-
try {
|
|
787
|
-
await execAsync2(command);
|
|
788
|
-
return tarballPath;
|
|
789
|
-
} catch (error) {
|
|
790
|
-
throw new Error(
|
|
791
|
-
`Failed to create tarball: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
function formatFileSize(bytes) {
|
|
796
|
-
if (bytes === 0) return "0 B";
|
|
797
|
-
const k = 1024;
|
|
798
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
799
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
800
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
801
|
-
}
|
|
802
|
-
async function displayDeploymentProgress(api, deployment) {
|
|
803
|
-
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
804
|
-
let frameIndex = 0;
|
|
805
|
-
let spinnerInterval = null;
|
|
806
|
-
let lastStep = "";
|
|
807
|
-
const startSpinner = (message) => {
|
|
808
|
-
if (spinnerInterval) {
|
|
809
|
-
clearInterval(spinnerInterval);
|
|
810
|
-
}
|
|
811
|
-
process.stdout.write("\r\x1B[K");
|
|
812
|
-
spinnerInterval = setInterval(() => {
|
|
813
|
-
const frame = frames[frameIndex];
|
|
814
|
-
frameIndex = (frameIndex + 1) % frames.length;
|
|
815
|
-
process.stdout.write(
|
|
816
|
-
"\r" + chalk2.cyan(frame) + " " + chalk2.gray(message)
|
|
817
|
-
);
|
|
818
|
-
}, 80);
|
|
819
|
-
};
|
|
820
|
-
const stopSpinner = () => {
|
|
821
|
-
if (spinnerInterval) {
|
|
822
|
-
clearInterval(spinnerInterval);
|
|
823
|
-
spinnerInterval = null;
|
|
824
|
-
process.stdout.write("\r\x1B[K");
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
console.log();
|
|
828
|
-
startSpinner("Deploying...");
|
|
829
|
-
try {
|
|
830
|
-
for await (const log of api.streamDeploymentLogs(deployment.id)) {
|
|
831
|
-
try {
|
|
832
|
-
const logData = JSON.parse(log);
|
|
833
|
-
if (logData.step && logData.step !== lastStep) {
|
|
834
|
-
lastStep = logData.step;
|
|
835
|
-
const stepMessages = {
|
|
836
|
-
clone: "Preparing source code...",
|
|
837
|
-
analyze: "Analyzing project...",
|
|
838
|
-
build: "Building container image...",
|
|
839
|
-
deploy: "Deploying to cloud..."
|
|
840
|
-
};
|
|
841
|
-
const message = stepMessages[logData.step] || "Deploying...";
|
|
842
|
-
startSpinner(message);
|
|
843
|
-
}
|
|
844
|
-
if (logData.line) {
|
|
845
|
-
stopSpinner();
|
|
846
|
-
const levelColor = logData.level === "error" ? chalk2.red : logData.level === "warn" ? chalk2.yellow : chalk2.gray;
|
|
847
|
-
const stepPrefix = logData.step ? chalk2.cyan(`[${logData.step}]`) + " " : "";
|
|
848
|
-
console.log(stepPrefix + levelColor(logData.line));
|
|
849
|
-
}
|
|
850
|
-
} catch {
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
} catch (error) {
|
|
854
|
-
stopSpinner();
|
|
855
|
-
}
|
|
856
|
-
let checkCount = 0;
|
|
857
|
-
const maxChecks = 60;
|
|
858
|
-
let delay = 3e3;
|
|
859
|
-
const maxDelay = 1e4;
|
|
860
|
-
let lastDisplayedLogLength = 0;
|
|
861
|
-
while (checkCount < maxChecks) {
|
|
862
|
-
const currentDelay = delay;
|
|
863
|
-
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
864
|
-
const finalDeployment = await api.getDeployment(deployment.id);
|
|
865
|
-
if (finalDeployment.buildLogs && finalDeployment.buildLogs.length > lastDisplayedLogLength) {
|
|
866
|
-
const newLogs = finalDeployment.buildLogs.substring(
|
|
867
|
-
lastDisplayedLogLength
|
|
868
|
-
);
|
|
869
|
-
const logLines = newLogs.split("\n").filter((l) => l.trim());
|
|
870
|
-
for (const line of logLines) {
|
|
871
|
-
try {
|
|
872
|
-
const logData = JSON.parse(line);
|
|
873
|
-
if (logData.line) {
|
|
874
|
-
stopSpinner();
|
|
875
|
-
const levelColor = logData.level === "error" ? chalk2.red : logData.level === "warn" ? chalk2.yellow : chalk2.gray;
|
|
876
|
-
const stepPrefix = logData.step ? chalk2.cyan(`[${logData.step}]`) + " " : "";
|
|
877
|
-
console.log(stepPrefix + levelColor(logData.line));
|
|
878
|
-
}
|
|
879
|
-
} catch {
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
lastDisplayedLogLength = finalDeployment.buildLogs.length;
|
|
883
|
-
}
|
|
884
|
-
if (finalDeployment.status === "running") {
|
|
885
|
-
const mcpUrl = `https://${finalDeployment.domain}/mcp`;
|
|
886
|
-
const inspectorUrl = `https://inspector.mcp-use.com/inspector?autoConnect=${encodeURIComponent(mcpUrl)}`;
|
|
887
|
-
console.log(chalk2.green.bold("\u2713 Deployment successful!\n"));
|
|
888
|
-
console.log(chalk2.white("\u{1F310} MCP Server URL:"));
|
|
889
|
-
console.log(chalk2.cyan.bold(` ${mcpUrl}
|
|
890
|
-
`));
|
|
891
|
-
console.log(chalk2.white("\u{1F50D} Inspector URL:"));
|
|
892
|
-
console.log(chalk2.cyan.bold(` ${inspectorUrl}
|
|
893
|
-
`));
|
|
894
|
-
if (finalDeployment.customDomain) {
|
|
895
|
-
const customMcpUrl = `https://${finalDeployment.customDomain}/mcp`;
|
|
896
|
-
const customInspectorUrl = `https://inspector.mcp-use.com/inspect?autoConnect=${encodeURIComponent(customMcpUrl)}`;
|
|
897
|
-
console.log(chalk2.white("\u{1F517} Custom Domain:"));
|
|
898
|
-
console.log(chalk2.cyan.bold(` ${customMcpUrl}
|
|
899
|
-
`));
|
|
900
|
-
console.log(chalk2.white("\u{1F50D} Custom Inspector:"));
|
|
901
|
-
console.log(chalk2.cyan.bold(` ${customInspectorUrl}
|
|
902
|
-
`));
|
|
903
|
-
}
|
|
904
|
-
console.log(
|
|
905
|
-
chalk2.gray("Deployment ID: ") + chalk2.white(finalDeployment.id)
|
|
906
|
-
);
|
|
907
|
-
return;
|
|
908
|
-
} else if (finalDeployment.status === "failed") {
|
|
909
|
-
console.log(chalk2.red.bold("\u2717 Deployment failed\n"));
|
|
910
|
-
if (finalDeployment.error) {
|
|
911
|
-
console.log(chalk2.red("Error: ") + finalDeployment.error);
|
|
912
|
-
}
|
|
913
|
-
if (finalDeployment.buildLogs) {
|
|
914
|
-
console.log(chalk2.gray("\nBuild logs:"));
|
|
915
|
-
try {
|
|
916
|
-
const logs = finalDeployment.buildLogs.split("\n").filter((l) => l.trim());
|
|
917
|
-
for (const log of logs) {
|
|
918
|
-
try {
|
|
919
|
-
const logData = JSON.parse(log);
|
|
920
|
-
if (logData.line) {
|
|
921
|
-
console.log(chalk2.gray(` ${logData.line}`));
|
|
922
|
-
}
|
|
923
|
-
} catch {
|
|
924
|
-
console.log(chalk2.gray(` ${log}`));
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
} catch {
|
|
928
|
-
console.log(chalk2.gray(finalDeployment.buildLogs));
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
process.exit(1);
|
|
932
|
-
} else if (finalDeployment.status === "building") {
|
|
933
|
-
startSpinner("Building and deploying...");
|
|
934
|
-
checkCount++;
|
|
935
|
-
delay = Math.min(delay * 1.2, maxDelay);
|
|
936
|
-
} else {
|
|
937
|
-
console.log(
|
|
938
|
-
chalk2.yellow("\u26A0\uFE0F Deployment status: ") + finalDeployment.status
|
|
939
|
-
);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
stopSpinner();
|
|
944
|
-
console.log(chalk2.yellow("\u26A0\uFE0F Deployment is taking longer than expected."));
|
|
945
|
-
console.log(
|
|
946
|
-
chalk2.gray("Check status with: ") + chalk2.white(`mcp-use status ${deployment.id}`)
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
async function deployCommand(options) {
|
|
950
|
-
try {
|
|
951
|
-
const cwd = process.cwd();
|
|
952
|
-
if (!await isLoggedIn()) {
|
|
953
|
-
console.log(chalk2.red("\u2717 You are not logged in."));
|
|
954
|
-
console.log(
|
|
955
|
-
chalk2.gray("Run " + chalk2.white("mcp-use login") + " to get started.")
|
|
956
|
-
);
|
|
957
|
-
process.exit(1);
|
|
958
|
-
}
|
|
959
|
-
console.log(chalk2.cyan.bold("\u{1F680} Deploying to mcp-use cloud...\n"));
|
|
960
|
-
const isMcp = await isMcpProject(cwd);
|
|
961
|
-
if (!isMcp) {
|
|
962
|
-
console.log(
|
|
963
|
-
chalk2.yellow(
|
|
964
|
-
"\u26A0\uFE0F This doesn't appear to be an MCP server project (no mcp-use or @modelcontextprotocol/sdk dependency found)."
|
|
965
|
-
)
|
|
966
|
-
);
|
|
967
|
-
const shouldContinue = await prompt(
|
|
968
|
-
chalk2.white("Continue anyway? (y/n): ")
|
|
969
|
-
);
|
|
970
|
-
if (!shouldContinue) {
|
|
971
|
-
console.log(chalk2.gray("Deployment cancelled."));
|
|
972
|
-
process.exit(0);
|
|
973
|
-
}
|
|
974
|
-
console.log();
|
|
975
|
-
}
|
|
976
|
-
const gitInfo = await getGitInfo(cwd);
|
|
977
|
-
if (!options.fromSource && gitInfo.isGitRepo && gitInfo.remoteUrl && isGitHubUrl(gitInfo.remoteUrl)) {
|
|
978
|
-
if (!gitInfo.owner || !gitInfo.repo) {
|
|
979
|
-
console.log(
|
|
980
|
-
chalk2.red(
|
|
981
|
-
"\u2717 Could not parse GitHub repository information from remote URL."
|
|
982
|
-
)
|
|
983
|
-
);
|
|
984
|
-
process.exit(1);
|
|
985
|
-
}
|
|
986
|
-
console.log(chalk2.white("GitHub repository detected:"));
|
|
987
|
-
console.log(
|
|
988
|
-
chalk2.gray(` Repository: `) + chalk2.cyan(`${gitInfo.owner}/${gitInfo.repo}`)
|
|
989
|
-
);
|
|
990
|
-
console.log(
|
|
991
|
-
chalk2.gray(` Branch: `) + chalk2.cyan(gitInfo.branch || "main")
|
|
992
|
-
);
|
|
993
|
-
if (gitInfo.commitSha) {
|
|
994
|
-
console.log(
|
|
995
|
-
chalk2.gray(` Commit: `) + chalk2.gray(gitInfo.commitSha.substring(0, 7))
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
if (gitInfo.commitMessage) {
|
|
999
|
-
console.log(
|
|
1000
|
-
chalk2.gray(` Message: `) + chalk2.gray(gitInfo.commitMessage.split("\n")[0])
|
|
1001
|
-
);
|
|
1002
|
-
}
|
|
1003
|
-
console.log();
|
|
1004
|
-
const shouldDeploy = await prompt(
|
|
1005
|
-
chalk2.white(
|
|
1006
|
-
`Deploy from GitHub repository ${gitInfo.owner}/${gitInfo.repo}? (y/n): `
|
|
1007
|
-
)
|
|
1008
|
-
);
|
|
1009
|
-
if (!shouldDeploy) {
|
|
1010
|
-
console.log(chalk2.gray("Deployment cancelled."));
|
|
1011
|
-
process.exit(0);
|
|
1012
|
-
}
|
|
1013
|
-
const projectName = options.name || await getProjectName(cwd);
|
|
1014
|
-
const runtime = options.runtime || await detectRuntime(cwd);
|
|
1015
|
-
const port = options.port || 3e3;
|
|
1016
|
-
const buildCommand = await detectBuildCommand(cwd);
|
|
1017
|
-
const startCommand = await detectStartCommand(cwd);
|
|
1018
|
-
console.log();
|
|
1019
|
-
console.log(chalk2.white("Deployment configuration:"));
|
|
1020
|
-
console.log(chalk2.gray(` Name: `) + chalk2.cyan(projectName));
|
|
1021
|
-
console.log(chalk2.gray(` Runtime: `) + chalk2.cyan(runtime));
|
|
1022
|
-
console.log(chalk2.gray(` Port: `) + chalk2.cyan(port));
|
|
1023
|
-
if (buildCommand) {
|
|
1024
|
-
console.log(chalk2.gray(` Build command: `) + chalk2.cyan(buildCommand));
|
|
1025
|
-
}
|
|
1026
|
-
if (startCommand) {
|
|
1027
|
-
console.log(chalk2.gray(` Start command: `) + chalk2.cyan(startCommand));
|
|
1028
|
-
}
|
|
1029
|
-
console.log();
|
|
1030
|
-
const deploymentRequest = {
|
|
1031
|
-
name: projectName,
|
|
1032
|
-
source: {
|
|
1033
|
-
type: "github",
|
|
1034
|
-
repo: `${gitInfo.owner}/${gitInfo.repo}`,
|
|
1035
|
-
branch: gitInfo.branch || "main",
|
|
1036
|
-
runtime,
|
|
1037
|
-
port,
|
|
1038
|
-
buildCommand,
|
|
1039
|
-
startCommand
|
|
1040
|
-
},
|
|
1041
|
-
healthCheckPath: "/healthz"
|
|
1042
|
-
};
|
|
1043
|
-
console.log(chalk2.gray("Creating deployment..."));
|
|
1044
|
-
const api = await McpUseAPI.create();
|
|
1045
|
-
const deployment = await api.createDeployment(deploymentRequest);
|
|
1046
|
-
console.log(
|
|
1047
|
-
chalk2.green("\u2713 Deployment created: ") + chalk2.gray(deployment.id)
|
|
1048
|
-
);
|
|
1049
|
-
await displayDeploymentProgress(api, deployment);
|
|
1050
|
-
if (options.open && deployment.domain) {
|
|
1051
|
-
console.log();
|
|
1052
|
-
console.log(chalk2.gray("Opening deployment in browser..."));
|
|
1053
|
-
await open2(`https://${deployment.domain}`);
|
|
1054
|
-
}
|
|
1055
|
-
} else {
|
|
1056
|
-
if (options.fromSource) {
|
|
1057
|
-
console.log(
|
|
1058
|
-
chalk2.white("\u{1F4E6} Deploying from local source code (--from-source)...")
|
|
1059
|
-
);
|
|
1060
|
-
} else {
|
|
1061
|
-
console.log(
|
|
1062
|
-
chalk2.yellow(
|
|
1063
|
-
"\u26A0\uFE0F This is not a GitHub repository or no remote is configured."
|
|
1064
|
-
)
|
|
1065
|
-
);
|
|
1066
|
-
console.log(chalk2.white("Deploying from local source code instead..."));
|
|
1067
|
-
}
|
|
1068
|
-
console.log();
|
|
1069
|
-
const projectName = options.name || await getProjectName(cwd);
|
|
1070
|
-
const runtime = options.runtime || await detectRuntime(cwd);
|
|
1071
|
-
const port = options.port || 3e3;
|
|
1072
|
-
const buildCommand = await detectBuildCommand(cwd);
|
|
1073
|
-
const startCommand = await detectStartCommand(cwd);
|
|
1074
|
-
console.log(chalk2.white("Deployment configuration:"));
|
|
1075
|
-
console.log(chalk2.gray(` Name: `) + chalk2.cyan(projectName));
|
|
1076
|
-
console.log(chalk2.gray(` Runtime: `) + chalk2.cyan(runtime));
|
|
1077
|
-
console.log(chalk2.gray(` Port: `) + chalk2.cyan(port));
|
|
1078
|
-
if (buildCommand) {
|
|
1079
|
-
console.log(chalk2.gray(` Build command: `) + chalk2.cyan(buildCommand));
|
|
1080
|
-
}
|
|
1081
|
-
if (startCommand) {
|
|
1082
|
-
console.log(chalk2.gray(` Start command: `) + chalk2.cyan(startCommand));
|
|
1083
|
-
}
|
|
1084
|
-
console.log();
|
|
1085
|
-
const shouldDeploy = await prompt(
|
|
1086
|
-
chalk2.white("Deploy from local source? (y/n): "),
|
|
1087
|
-
"y"
|
|
1088
|
-
);
|
|
1089
|
-
if (!shouldDeploy) {
|
|
1090
|
-
console.log(chalk2.gray("Deployment cancelled."));
|
|
1091
|
-
process.exit(0);
|
|
1092
|
-
}
|
|
1093
|
-
console.log();
|
|
1094
|
-
console.log(chalk2.gray("Packaging source code..."));
|
|
1095
|
-
const tarballPath = await createTarball(cwd);
|
|
1096
|
-
const stats = await fs2.stat(tarballPath);
|
|
1097
|
-
console.log(
|
|
1098
|
-
chalk2.green("\u2713 Packaged: ") + chalk2.gray(formatFileSize(stats.size))
|
|
1099
|
-
);
|
|
1100
|
-
const maxSize = 2 * 1024 * 1024;
|
|
1101
|
-
if (stats.size > maxSize) {
|
|
1102
|
-
console.log(
|
|
1103
|
-
chalk2.red(
|
|
1104
|
-
`\u2717 File size (${formatFileSize(stats.size)}) exceeds maximum of 2MB`
|
|
1105
|
-
)
|
|
1106
|
-
);
|
|
1107
|
-
await fs2.unlink(tarballPath);
|
|
1108
|
-
process.exit(1);
|
|
1109
|
-
}
|
|
1110
|
-
const deploymentRequest = {
|
|
1111
|
-
name: projectName,
|
|
1112
|
-
source: {
|
|
1113
|
-
type: "upload",
|
|
1114
|
-
runtime,
|
|
1115
|
-
port,
|
|
1116
|
-
buildCommand,
|
|
1117
|
-
startCommand
|
|
1118
|
-
},
|
|
1119
|
-
healthCheckPath: "/healthz"
|
|
1120
|
-
};
|
|
1121
|
-
console.log(chalk2.gray("Creating deployment..."));
|
|
1122
|
-
const api = await McpUseAPI.create();
|
|
1123
|
-
const deployment = await api.createDeploymentWithUpload(
|
|
1124
|
-
deploymentRequest,
|
|
1125
|
-
tarballPath
|
|
1126
|
-
);
|
|
1127
|
-
await fs2.unlink(tarballPath);
|
|
1128
|
-
console.log(
|
|
1129
|
-
chalk2.green("\u2713 Deployment created: ") + chalk2.gray(deployment.id)
|
|
1130
|
-
);
|
|
1131
|
-
await displayDeploymentProgress(api, deployment);
|
|
1132
|
-
if (options.open && deployment.domain) {
|
|
1133
|
-
console.log();
|
|
1134
|
-
console.log(chalk2.gray("Opening deployment in browser..."));
|
|
1135
|
-
await open2(`https://${deployment.domain}`);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
} catch (error) {
|
|
1139
|
-
console.error(
|
|
1140
|
-
chalk2.red.bold("\n\u2717 Deployment failed:"),
|
|
1141
|
-
chalk2.red(error instanceof Error ? error.message : "Unknown error")
|
|
1142
|
-
);
|
|
1143
|
-
process.exit(1);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// src/index.ts
|
|
1148
|
-
var program = new Command();
|
|
1149
|
-
var packageContent = readFileSync(
|
|
1150
|
-
path3.join(__dirname, "../package.json"),
|
|
1151
|
-
"utf-8"
|
|
1152
|
-
);
|
|
1153
|
-
var packageJson = JSON.parse(packageContent);
|
|
1154
|
-
var packageVersion = packageJson.version || "unknown";
|
|
1155
|
-
program.name("mcp-use").description("Create and run MCP servers with ui resources widgets").version(packageVersion);
|
|
1156
|
-
async function isPortAvailable(port, host = "localhost") {
|
|
1157
|
-
try {
|
|
1158
|
-
await fetch(`http://${host}:${port}`);
|
|
1159
|
-
return false;
|
|
1160
|
-
} catch {
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
async function findAvailablePort2(startPort, host = "localhost") {
|
|
1165
|
-
for (let port = startPort; port < startPort + 100; port++) {
|
|
1166
|
-
if (await isPortAvailable(port, host)) {
|
|
1167
|
-
return port;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
throw new Error("No available ports found");
|
|
1171
|
-
}
|
|
1172
|
-
async function waitForServer(port, host = "localhost", maxAttempts = 30) {
|
|
1173
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1174
|
-
const controller = new AbortController();
|
|
1175
|
-
try {
|
|
1176
|
-
const response = await fetch(`http://${host}:${port}/inspector/health`, {
|
|
1177
|
-
signal: controller.signal
|
|
1178
|
-
});
|
|
1179
|
-
if (response.ok) {
|
|
1180
|
-
return true;
|
|
1181
|
-
}
|
|
1182
|
-
} catch {
|
|
1183
|
-
} finally {
|
|
1184
|
-
controller.abort();
|
|
1185
|
-
}
|
|
1186
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1187
|
-
}
|
|
1188
|
-
return false;
|
|
1189
|
-
}
|
|
1190
|
-
function runCommand(command, args, cwd, env, filterStderr = false) {
|
|
1191
|
-
const proc = spawn(command, args, {
|
|
1192
|
-
cwd,
|
|
1193
|
-
stdio: filterStderr ? ["inherit", "inherit", "pipe"] : "inherit",
|
|
1194
|
-
shell: false,
|
|
1195
|
-
env: env ? { ...process.env, ...env } : process.env
|
|
1196
|
-
});
|
|
1197
|
-
if (filterStderr && proc.stderr) {
|
|
1198
|
-
proc.stderr.on("data", (data) => {
|
|
1199
|
-
const text = data.toString();
|
|
1200
|
-
if (!text.includes("Previous process hasn't exited yet") && !text.includes("Force killing")) {
|
|
1201
|
-
process.stderr.write(data);
|
|
1202
|
-
}
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
const promise = new Promise((resolve, reject) => {
|
|
1206
|
-
proc.on("error", reject);
|
|
1207
|
-
proc.on("exit", (code) => {
|
|
1208
|
-
if (code === 0 || code === 130 || code === 143) {
|
|
1209
|
-
resolve();
|
|
1210
|
-
} else {
|
|
1211
|
-
reject(new Error(`Command failed with exit code ${code}`));
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
});
|
|
1215
|
-
return { promise, process: proc };
|
|
1216
|
-
}
|
|
1217
|
-
async function startTunnel(port, subdomain) {
|
|
1218
|
-
return new Promise((resolve, reject) => {
|
|
1219
|
-
console.log(chalk3.gray(`Starting tunnel for port ${port}...`));
|
|
1220
|
-
const tunnelArgs = ["--yes", "@mcp-use/tunnel", String(port)];
|
|
1221
|
-
if (subdomain) {
|
|
1222
|
-
tunnelArgs.push("--subdomain", subdomain);
|
|
1223
|
-
}
|
|
1224
|
-
const proc = spawn("npx", tunnelArgs, {
|
|
1225
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1226
|
-
shell: false
|
|
1227
|
-
});
|
|
1228
|
-
let resolved = false;
|
|
1229
|
-
let isShuttingDown = false;
|
|
1230
|
-
proc.stdout?.on("data", (data) => {
|
|
1231
|
-
const text = data.toString();
|
|
1232
|
-
const isShutdownMessage = text.includes("Shutting down") || text.includes("\u{1F6D1}");
|
|
1233
|
-
if (!isShuttingDown && !isShutdownMessage) {
|
|
1234
|
-
process.stdout.write(text);
|
|
1235
|
-
}
|
|
1236
|
-
const urlMatch = text.match(/https?:\/\/([a-z0-9-]+\.[a-z0-9.-]+)/i);
|
|
1237
|
-
if (urlMatch && !resolved) {
|
|
1238
|
-
const url = urlMatch[0];
|
|
1239
|
-
const fullDomain = urlMatch[1];
|
|
1240
|
-
const subdomainMatch = fullDomain.match(/^([a-z0-9-]+)\./i);
|
|
1241
|
-
let extractedSubdomain = subdomainMatch ? subdomainMatch[1] : fullDomain.split(".")[0];
|
|
1242
|
-
if (!/^[a-z0-9-]+$/i.test(extractedSubdomain)) {
|
|
1243
|
-
console.warn(
|
|
1244
|
-
chalk3.yellow(
|
|
1245
|
-
`Warning: Extracted subdomain "${extractedSubdomain}" does not match expected format.`
|
|
1246
|
-
)
|
|
1247
|
-
);
|
|
1248
|
-
extractedSubdomain = "";
|
|
1249
|
-
}
|
|
1250
|
-
resolved = true;
|
|
1251
|
-
clearTimeout(setupTimeout);
|
|
1252
|
-
console.log(chalk3.green.bold(`\u2713 Tunnel established: ${url}/mcp`));
|
|
1253
|
-
resolve({ url, subdomain: extractedSubdomain, process: proc });
|
|
1254
|
-
}
|
|
1255
|
-
});
|
|
1256
|
-
proc.stderr?.on("data", (data) => {
|
|
1257
|
-
const text = data.toString();
|
|
1258
|
-
if (!isShuttingDown && !text.includes("INFO") && !text.includes("bore_cli") && !text.includes("Shutting down")) {
|
|
1259
|
-
process.stderr.write(data);
|
|
1260
|
-
}
|
|
1261
|
-
});
|
|
1262
|
-
proc.on("error", (error) => {
|
|
1263
|
-
if (!resolved) {
|
|
1264
|
-
clearTimeout(setupTimeout);
|
|
1265
|
-
reject(new Error(`Failed to start tunnel: ${error.message}`));
|
|
1266
|
-
}
|
|
1267
|
-
});
|
|
1268
|
-
proc.on("exit", (code) => {
|
|
1269
|
-
if (code !== 0 && !resolved) {
|
|
1270
|
-
clearTimeout(setupTimeout);
|
|
1271
|
-
reject(new Error(`Tunnel process exited with code ${code}`));
|
|
1272
|
-
}
|
|
1273
|
-
});
|
|
1274
|
-
proc.markShutdown = () => {
|
|
1275
|
-
isShuttingDown = true;
|
|
1276
|
-
};
|
|
1277
|
-
const setupTimeout = setTimeout(() => {
|
|
1278
|
-
if (!resolved) {
|
|
1279
|
-
proc.kill();
|
|
1280
|
-
reject(new Error("Tunnel setup timed out"));
|
|
1281
|
-
}
|
|
1282
|
-
}, 3e4);
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
async function findServerFile(projectPath) {
|
|
1286
|
-
const candidates = ["index.ts", "src/index.ts", "server.ts", "src/server.ts"];
|
|
1287
|
-
for (const candidate of candidates) {
|
|
1288
|
-
try {
|
|
1289
|
-
await access(path3.join(projectPath, candidate));
|
|
1290
|
-
return candidate;
|
|
1291
|
-
} catch {
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
throw new Error("No server file found");
|
|
1296
|
-
}
|
|
1297
|
-
async function buildWidgets(projectPath) {
|
|
1298
|
-
const { promises: fs3 } = await import("fs");
|
|
1299
|
-
const { build } = await import("vite");
|
|
1300
|
-
const resourcesDir = path3.join(projectPath, "resources");
|
|
1301
|
-
const mcpUrl = process.env.MCP_URL;
|
|
1302
|
-
try {
|
|
1303
|
-
await access(resourcesDir);
|
|
1304
|
-
} catch {
|
|
1305
|
-
console.log(
|
|
1306
|
-
chalk3.gray("No resources/ directory found - skipping widget build")
|
|
1307
|
-
);
|
|
1308
|
-
return [];
|
|
1309
|
-
}
|
|
1310
|
-
const entries = [];
|
|
1311
|
-
try {
|
|
1312
|
-
const files = await fs3.readdir(resourcesDir, { withFileTypes: true });
|
|
1313
|
-
for (const dirent of files) {
|
|
1314
|
-
if (dirent.name.startsWith("._") || dirent.name.startsWith(".DS_Store")) {
|
|
1315
|
-
continue;
|
|
1316
|
-
}
|
|
1317
|
-
if (dirent.isFile() && (dirent.name.endsWith(".tsx") || dirent.name.endsWith(".ts"))) {
|
|
1318
|
-
entries.push({
|
|
1319
|
-
name: dirent.name.replace(/\.tsx?$/, ""),
|
|
1320
|
-
path: path3.join(resourcesDir, dirent.name)
|
|
1321
|
-
});
|
|
1322
|
-
} else if (dirent.isDirectory()) {
|
|
1323
|
-
const widgetPath = path3.join(resourcesDir, dirent.name, "widget.tsx");
|
|
1324
|
-
try {
|
|
1325
|
-
await fs3.access(widgetPath);
|
|
1326
|
-
entries.push({
|
|
1327
|
-
name: dirent.name,
|
|
1328
|
-
path: widgetPath
|
|
1329
|
-
});
|
|
1330
|
-
} catch {
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
console.log(chalk3.gray("No widgets found in resources/ directory"));
|
|
1336
|
-
return [];
|
|
1337
|
-
}
|
|
1338
|
-
if (entries.length === 0) {
|
|
1339
|
-
console.log(chalk3.gray("No widgets found in resources/ directory"));
|
|
1340
|
-
return [];
|
|
1341
|
-
}
|
|
1342
|
-
console.log(chalk3.gray(`Building ${entries.length} widget(s)...`));
|
|
1343
|
-
const react = (await import("@vitejs/plugin-react")).default;
|
|
1344
|
-
const tailwindcss = (await import("@tailwindcss/vite")).default;
|
|
1345
|
-
const builtWidgets = [];
|
|
1346
|
-
for (const entry of entries) {
|
|
1347
|
-
const widgetName = entry.name;
|
|
1348
|
-
const entryPath = entry.path;
|
|
1349
|
-
console.log(chalk3.gray(` - Building ${widgetName}...`));
|
|
1350
|
-
const tempDir = path3.join(projectPath, ".mcp-use", widgetName);
|
|
1351
|
-
await fs3.mkdir(tempDir, { recursive: true });
|
|
1352
|
-
const relativeResourcesPath = path3.relative(tempDir, resourcesDir).replace(/\\/g, "/");
|
|
1353
|
-
const cssContent = `@import "tailwindcss";
|
|
1354
|
-
|
|
1355
|
-
/* Configure Tailwind to scan the resources directory */
|
|
1356
|
-
@source "${relativeResourcesPath}";
|
|
1357
|
-
`;
|
|
1358
|
-
await fs3.writeFile(path3.join(tempDir, "styles.css"), cssContent, "utf8");
|
|
1359
|
-
const entryContent = `import React from 'react'
|
|
1360
|
-
import { createRoot } from 'react-dom/client'
|
|
1361
|
-
import './styles.css'
|
|
1362
|
-
import Component from '${entryPath}'
|
|
1363
|
-
|
|
1364
|
-
const container = document.getElementById('widget-root')
|
|
1365
|
-
if (container && Component) {
|
|
1366
|
-
const root = createRoot(container)
|
|
1367
|
-
root.render(<Component />)
|
|
1368
|
-
}
|
|
1369
|
-
`;
|
|
1370
|
-
const htmlContent = `<!doctype html>
|
|
1371
|
-
<html lang="en">
|
|
1372
|
-
<head>
|
|
1373
|
-
<meta charset="UTF-8" />
|
|
1374
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
1375
|
-
<title>${widgetName} Widget</title>
|
|
1376
|
-
</head>
|
|
1377
|
-
<body>
|
|
1378
|
-
<div id="widget-root"></div>
|
|
1379
|
-
<script type="module" src="/entry.tsx"></script>
|
|
1380
|
-
</body>
|
|
1381
|
-
</html>`;
|
|
1382
|
-
await fs3.writeFile(path3.join(tempDir, "entry.tsx"), entryContent, "utf8");
|
|
1383
|
-
await fs3.writeFile(path3.join(tempDir, "index.html"), htmlContent, "utf8");
|
|
1384
|
-
const outDir = path3.join(
|
|
1385
|
-
projectPath,
|
|
1386
|
-
"dist",
|
|
1387
|
-
"resources",
|
|
1388
|
-
"widgets",
|
|
1389
|
-
widgetName
|
|
1390
|
-
);
|
|
1391
|
-
const baseUrl = mcpUrl ? `${mcpUrl}/${widgetName}/` : `/mcp-use/widgets/${widgetName}/`;
|
|
1392
|
-
let widgetMetadata = {};
|
|
1393
|
-
try {
|
|
1394
|
-
const metadataTempDir = path3.join(
|
|
1395
|
-
projectPath,
|
|
1396
|
-
".mcp-use",
|
|
1397
|
-
`${widgetName}-metadata`
|
|
1398
|
-
);
|
|
1399
|
-
await fs3.mkdir(metadataTempDir, { recursive: true });
|
|
1400
|
-
const { createServer: createServer2 } = await import("vite");
|
|
1401
|
-
const nodeStubsPlugin = {
|
|
1402
|
-
name: "node-stubs",
|
|
1403
|
-
enforce: "pre",
|
|
1404
|
-
resolveId(id) {
|
|
1405
|
-
if (id === "posthog-node" || id.startsWith("posthog-node/")) {
|
|
1406
|
-
return "\0virtual:posthog-node-stub";
|
|
1407
|
-
}
|
|
1408
|
-
return null;
|
|
1409
|
-
},
|
|
1410
|
-
load(id) {
|
|
1411
|
-
if (id === "\0virtual:posthog-node-stub") {
|
|
1412
|
-
return `
|
|
1413
|
-
export class PostHog {
|
|
1414
|
-
constructor() {}
|
|
1415
|
-
capture() {}
|
|
1416
|
-
identify() {}
|
|
1417
|
-
alias() {}
|
|
1418
|
-
flush() { return Promise.resolve(); }
|
|
1419
|
-
shutdown() { return Promise.resolve(); }
|
|
1420
|
-
}
|
|
1421
|
-
export default PostHog;
|
|
1422
|
-
`;
|
|
1423
|
-
}
|
|
1424
|
-
return null;
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
const metadataServer = await createServer2({
|
|
1428
|
-
root: metadataTempDir,
|
|
1429
|
-
cacheDir: path3.join(metadataTempDir, ".vite-cache"),
|
|
1430
|
-
plugins: [nodeStubsPlugin, tailwindcss(), react()],
|
|
1431
|
-
resolve: {
|
|
1432
|
-
alias: {
|
|
1433
|
-
"@": resourcesDir
|
|
1434
|
-
}
|
|
1435
|
-
},
|
|
1436
|
-
server: {
|
|
1437
|
-
middlewareMode: true
|
|
1438
|
-
},
|
|
1439
|
-
optimizeDeps: {
|
|
1440
|
-
// Exclude Node.js-only packages from browser bundling
|
|
1441
|
-
exclude: ["posthog-node"]
|
|
1442
|
-
},
|
|
1443
|
-
ssr: {
|
|
1444
|
-
// Force Vite to transform these packages in SSR instead of using external requires
|
|
1445
|
-
noExternal: ["@openai/apps-sdk-ui", "react-router"],
|
|
1446
|
-
// Mark Node.js-only packages as external in SSR mode
|
|
1447
|
-
external: ["posthog-node"]
|
|
1448
|
-
},
|
|
1449
|
-
define: {
|
|
1450
|
-
// Define process.env for SSR context
|
|
1451
|
-
"process.env.NODE_ENV": JSON.stringify(
|
|
1452
|
-
process.env.NODE_ENV || "development"
|
|
1453
|
-
),
|
|
1454
|
-
"import.meta.env.DEV": true,
|
|
1455
|
-
"import.meta.env.PROD": false,
|
|
1456
|
-
"import.meta.env.MODE": JSON.stringify("development"),
|
|
1457
|
-
"import.meta.env.SSR": true
|
|
1458
|
-
},
|
|
1459
|
-
clearScreen: false,
|
|
1460
|
-
logLevel: "silent",
|
|
1461
|
-
customLogger: {
|
|
1462
|
-
info: () => {
|
|
1463
|
-
},
|
|
1464
|
-
warn: () => {
|
|
1465
|
-
},
|
|
1466
|
-
error: () => {
|
|
1467
|
-
},
|
|
1468
|
-
clearScreen: () => {
|
|
1469
|
-
},
|
|
1470
|
-
hasErrorLogged: () => false,
|
|
1471
|
-
hasWarned: false,
|
|
1472
|
-
warnOnce: () => {
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
try {
|
|
1477
|
-
const mod = await metadataServer.ssrLoadModule(entryPath);
|
|
1478
|
-
if (mod.widgetMetadata) {
|
|
1479
|
-
widgetMetadata = {
|
|
1480
|
-
...mod.widgetMetadata,
|
|
1481
|
-
title: mod.widgetMetadata.title || widgetName,
|
|
1482
|
-
description: mod.widgetMetadata.description,
|
|
1483
|
-
inputs: mod.widgetMetadata.inputs?.shape || {}
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1487
|
-
} catch (error) {
|
|
1488
|
-
console.warn(
|
|
1489
|
-
chalk3.yellow(` \u26A0 Could not extract metadata for ${widgetName}`)
|
|
1490
|
-
);
|
|
1491
|
-
} finally {
|
|
1492
|
-
await metadataServer.close();
|
|
1493
|
-
try {
|
|
1494
|
-
await fs3.rm(metadataTempDir, { recursive: true, force: true });
|
|
1495
|
-
} catch {
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
} catch (error) {
|
|
1499
|
-
}
|
|
1500
|
-
try {
|
|
1501
|
-
const buildNodeStubsPlugin = {
|
|
1502
|
-
name: "node-stubs-build",
|
|
1503
|
-
enforce: "pre",
|
|
1504
|
-
resolveId(id) {
|
|
1505
|
-
if (id === "posthog-node" || id.startsWith("posthog-node/")) {
|
|
1506
|
-
return "\0virtual:posthog-node-stub";
|
|
1507
|
-
}
|
|
1508
|
-
if (id === "path" || id === "node:path") {
|
|
1509
|
-
return "\0virtual:path-stub";
|
|
1510
|
-
}
|
|
1511
|
-
return null;
|
|
1512
|
-
},
|
|
1513
|
-
load(id) {
|
|
1514
|
-
if (id === "\0virtual:posthog-node-stub") {
|
|
1515
|
-
return `
|
|
1516
|
-
export class PostHog {
|
|
1517
|
-
constructor() {}
|
|
1518
|
-
capture() {}
|
|
1519
|
-
identify() {}
|
|
1520
|
-
alias() {}
|
|
1521
|
-
flush() { return Promise.resolve(); }
|
|
1522
|
-
shutdown() { return Promise.resolve(); }
|
|
1523
|
-
}
|
|
1524
|
-
export default PostHog;
|
|
1525
|
-
`;
|
|
1526
|
-
}
|
|
1527
|
-
if (id === "\0virtual:path-stub") {
|
|
1528
|
-
return `
|
|
1529
|
-
export function join(...paths) {
|
|
1530
|
-
return paths.filter(Boolean).join("/").replace(/\\/\\//g, "/").replace(/\\/$/, "");
|
|
1531
|
-
}
|
|
1532
|
-
export function resolve(...paths) {
|
|
1533
|
-
return join(...paths);
|
|
1534
|
-
}
|
|
1535
|
-
export function dirname(filepath) {
|
|
1536
|
-
const parts = filepath.split("/");
|
|
1537
|
-
parts.pop();
|
|
1538
|
-
return parts.join("/") || "/";
|
|
1539
|
-
}
|
|
1540
|
-
export function basename(filepath, ext) {
|
|
1541
|
-
const parts = filepath.split("/");
|
|
1542
|
-
let name = parts[parts.length - 1] || "";
|
|
1543
|
-
if (ext && name.endsWith(ext)) {
|
|
1544
|
-
name = name.slice(0, -ext.length);
|
|
1545
|
-
}
|
|
1546
|
-
return name;
|
|
1547
|
-
}
|
|
1548
|
-
export function extname(filepath) {
|
|
1549
|
-
const name = basename(filepath);
|
|
1550
|
-
const index = name.lastIndexOf(".");
|
|
1551
|
-
return index > 0 ? name.slice(index) : "";
|
|
1552
|
-
}
|
|
1553
|
-
export function normalize(filepath) {
|
|
1554
|
-
return filepath.replace(/\\/\\//g, "/");
|
|
1555
|
-
}
|
|
1556
|
-
export function isAbsolute(filepath) {
|
|
1557
|
-
return filepath.startsWith("/");
|
|
1558
|
-
}
|
|
1559
|
-
export const sep = "/";
|
|
1560
|
-
export const delimiter = ":";
|
|
1561
|
-
export const posix = {
|
|
1562
|
-
join,
|
|
1563
|
-
resolve,
|
|
1564
|
-
dirname,
|
|
1565
|
-
basename,
|
|
1566
|
-
extname,
|
|
1567
|
-
normalize,
|
|
1568
|
-
isAbsolute,
|
|
1569
|
-
sep,
|
|
1570
|
-
delimiter,
|
|
1571
|
-
};
|
|
1572
|
-
export default {
|
|
1573
|
-
join,
|
|
1574
|
-
resolve,
|
|
1575
|
-
dirname,
|
|
1576
|
-
basename,
|
|
1577
|
-
extname,
|
|
1578
|
-
normalize,
|
|
1579
|
-
isAbsolute,
|
|
1580
|
-
sep,
|
|
1581
|
-
delimiter,
|
|
1582
|
-
posix,
|
|
1583
|
-
};
|
|
1584
|
-
`;
|
|
1585
|
-
}
|
|
1586
|
-
return null;
|
|
1587
|
-
}
|
|
1588
|
-
};
|
|
1589
|
-
await build({
|
|
1590
|
-
root: tempDir,
|
|
1591
|
-
base: baseUrl,
|
|
1592
|
-
plugins: [buildNodeStubsPlugin, tailwindcss(), react()],
|
|
1593
|
-
experimental: {
|
|
1594
|
-
renderBuiltUrl: (filename, { hostType }) => {
|
|
1595
|
-
if (["js", "css"].includes(hostType)) {
|
|
1596
|
-
return {
|
|
1597
|
-
runtime: `window.__getFile(${JSON.stringify(filename)})`
|
|
1598
|
-
};
|
|
1599
|
-
} else {
|
|
1600
|
-
return { relative: true };
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
},
|
|
1604
|
-
resolve: {
|
|
1605
|
-
alias: {
|
|
1606
|
-
"@": resourcesDir
|
|
1607
|
-
}
|
|
1608
|
-
},
|
|
1609
|
-
optimizeDeps: {
|
|
1610
|
-
// Exclude Node.js-only packages from browser bundling
|
|
1611
|
-
exclude: ["posthog-node"]
|
|
1612
|
-
},
|
|
1613
|
-
build: {
|
|
1614
|
-
outDir,
|
|
1615
|
-
emptyOutDir: true,
|
|
1616
|
-
rollupOptions: {
|
|
1617
|
-
input: path3.join(tempDir, "index.html"),
|
|
1618
|
-
external: (id) => {
|
|
1619
|
-
return false;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
const mcpServerUrl = process.env.MCP_SERVER_URL;
|
|
1625
|
-
if (mcpServerUrl) {
|
|
1626
|
-
try {
|
|
1627
|
-
const htmlPath = path3.join(outDir, "index.html");
|
|
1628
|
-
let html = await fs3.readFile(htmlPath, "utf8");
|
|
1629
|
-
const injectionScript = `<script>window.__getFile = (filename) => { return "${mcpUrl}/${widgetName}/"+filename }; window.__mcpPublicUrl = "${mcpServerUrl}/mcp-use/public"; window.__mcpPublicAssetsUrl = "${mcpUrl}/public";</script>`;
|
|
1630
|
-
if (!html.includes("window.__mcpPublicUrl")) {
|
|
1631
|
-
html = html.replace(
|
|
1632
|
-
/<head[^>]*>/i,
|
|
1633
|
-
`<head>
|
|
1634
|
-
${injectionScript}`
|
|
1635
|
-
);
|
|
1636
|
-
}
|
|
1637
|
-
if (/<base\s+[^>]*\/?>/i.test(html)) {
|
|
1638
|
-
html = html.replace(
|
|
1639
|
-
/<base\s+[^>]*\/?>/i,
|
|
1640
|
-
`<base href="${mcpServerUrl}">`
|
|
1641
|
-
);
|
|
1642
|
-
} else {
|
|
1643
|
-
html = html.replace(
|
|
1644
|
-
injectionScript,
|
|
1645
|
-
`${injectionScript}
|
|
1646
|
-
<base href="${mcpServerUrl}">`
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1649
|
-
await fs3.writeFile(htmlPath, html, "utf8");
|
|
1650
|
-
console.log(
|
|
1651
|
-
chalk3.gray(` \u2192 Injected MCP_SERVER_URL into ${widgetName}`)
|
|
1652
|
-
);
|
|
1653
|
-
} catch (error) {
|
|
1654
|
-
console.warn(
|
|
1655
|
-
chalk3.yellow(
|
|
1656
|
-
` \u26A0 Failed to post-process HTML for ${widgetName}:`,
|
|
1657
|
-
error
|
|
1658
|
-
)
|
|
1659
|
-
);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
builtWidgets.push({
|
|
1663
|
-
name: widgetName,
|
|
1664
|
-
metadata: widgetMetadata
|
|
1665
|
-
});
|
|
1666
|
-
console.log(chalk3.green(` \u2713 Built ${widgetName}`));
|
|
1667
|
-
} catch (error) {
|
|
1668
|
-
console.error(chalk3.red(` \u2717 Failed to build ${widgetName}:`), error);
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
return builtWidgets;
|
|
1672
|
-
}
|
|
1673
|
-
program.command("build").description("Build TypeScript and MCP UI widgets").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--with-inspector", "Include inspector in production build").action(async (options) => {
|
|
1674
|
-
try {
|
|
1675
|
-
const projectPath = path3.resolve(options.path);
|
|
1676
|
-
const { promises: fs3 } = await import("fs");
|
|
1677
|
-
console.log(chalk3.cyan.bold(`mcp-use v${packageJson.version}`));
|
|
1678
|
-
const builtWidgets = await buildWidgets(projectPath);
|
|
1679
|
-
console.log(chalk3.gray("Building TypeScript..."));
|
|
1680
|
-
await runCommand("npx", ["tsc"], projectPath);
|
|
1681
|
-
console.log(chalk3.green("\u2713 TypeScript build complete!"));
|
|
1682
|
-
const publicDir = path3.join(projectPath, "public");
|
|
1683
|
-
try {
|
|
1684
|
-
await fs3.access(publicDir);
|
|
1685
|
-
console.log(chalk3.gray("Copying public assets..."));
|
|
1686
|
-
await fs3.cp(publicDir, path3.join(projectPath, "dist", "public"), {
|
|
1687
|
-
recursive: true
|
|
1688
|
-
});
|
|
1689
|
-
console.log(chalk3.green("\u2713 Public assets copied"));
|
|
1690
|
-
} catch {
|
|
1691
|
-
}
|
|
1692
|
-
const manifestPath = path3.join(projectPath, "dist", "mcp-use.json");
|
|
1693
|
-
let existingManifest = {};
|
|
1694
|
-
try {
|
|
1695
|
-
const existingContent = await fs3.readFile(manifestPath, "utf-8");
|
|
1696
|
-
existingManifest = JSON.parse(existingContent);
|
|
1697
|
-
} catch {
|
|
1698
|
-
}
|
|
1699
|
-
const widgetsData = {};
|
|
1700
|
-
for (const widget of builtWidgets) {
|
|
1701
|
-
widgetsData[widget.name] = widget.metadata;
|
|
1702
|
-
}
|
|
1703
|
-
const includeInspector = !!options.withInspector;
|
|
1704
|
-
const buildTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1705
|
-
const { createHash } = await import("crypto");
|
|
1706
|
-
const buildId = createHash("sha256").update(buildTime + Math.random().toString()).digest("hex").substring(0, 16);
|
|
1707
|
-
const manifest = {
|
|
1708
|
-
...existingManifest,
|
|
1709
|
-
// Preserve existing fields like tunnel
|
|
1710
|
-
includeInspector,
|
|
1711
|
-
buildTime,
|
|
1712
|
-
buildId,
|
|
1713
|
-
widgets: widgetsData
|
|
1714
|
-
};
|
|
1715
|
-
await fs3.mkdir(path3.dirname(manifestPath), { recursive: true });
|
|
1716
|
-
await fs3.writeFile(
|
|
1717
|
-
manifestPath,
|
|
1718
|
-
JSON.stringify(manifest, null, 2),
|
|
1719
|
-
"utf8"
|
|
1720
|
-
);
|
|
1721
|
-
console.log(chalk3.green("\u2713 Build manifest created"));
|
|
1722
|
-
console.log(chalk3.green.bold(`
|
|
1723
|
-
\u2713 Build complete!`));
|
|
1724
|
-
if (builtWidgets.length > 0) {
|
|
1725
|
-
console.log(chalk3.gray(` ${builtWidgets.length} widget(s) built`));
|
|
1726
|
-
}
|
|
1727
|
-
if (options.withInspector) {
|
|
1728
|
-
console.log(chalk3.gray(" Inspector included"));
|
|
1729
|
-
}
|
|
1730
|
-
} catch (error) {
|
|
1731
|
-
console.error(chalk3.red("Build failed:"), error);
|
|
1732
|
-
process.exit(1);
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
program.command("dev").description("Run development server with auto-reload and inspector").option("-p, --path <path>", "Path to project directory", process.cwd()).option("--port <port>", "Server port", "3000").option("--host <host>", "Server host", "localhost").option("--no-open", "Do not auto-open inspector").action(async (options) => {
|
|
1736
|
-
try {
|
|
1737
|
-
const projectPath = path3.resolve(options.path);
|
|
1738
|
-
let port = parseInt(options.port, 10);
|
|
1739
|
-
const host = options.host;
|
|
1740
|
-
console.log(chalk3.cyan.bold(`mcp-use v${packageJson.version}`));
|
|
1741
|
-
if (!await isPortAvailable(port, host)) {
|
|
1742
|
-
console.log(chalk3.yellow.bold(`\u26A0\uFE0F Port ${port} is already in use`));
|
|
1743
|
-
const availablePort = await findAvailablePort2(port, host);
|
|
1744
|
-
console.log(chalk3.green.bold(`\u2713 Using port ${availablePort} instead`));
|
|
1745
|
-
port = availablePort;
|
|
1746
|
-
}
|
|
1747
|
-
const serverFile = await findServerFile(projectPath);
|
|
1748
|
-
const processes = [];
|
|
1749
|
-
const env = {
|
|
1750
|
-
PORT: String(port),
|
|
1751
|
-
HOST: host,
|
|
1752
|
-
NODE_ENV: "development"
|
|
1753
|
-
};
|
|
1754
|
-
const serverCommand = runCommand(
|
|
1755
|
-
"npx",
|
|
1756
|
-
["tsx", "watch", serverFile],
|
|
1757
|
-
projectPath,
|
|
1758
|
-
env,
|
|
1759
|
-
true
|
|
1760
|
-
);
|
|
1761
|
-
processes.push(serverCommand.process);
|
|
1762
|
-
if (options.open !== false) {
|
|
1763
|
-
const startTime = Date.now();
|
|
1764
|
-
const ready = await waitForServer(port, host);
|
|
1765
|
-
if (ready) {
|
|
1766
|
-
const mcpEndpoint = `http://${host}:${port}/mcp`;
|
|
1767
|
-
const inspectorUrl = `http://${host}:${port}/inspector?autoConnect=${encodeURIComponent(mcpEndpoint)}`;
|
|
1768
|
-
const readyTime = Date.now() - startTime;
|
|
1769
|
-
console.log(chalk3.green.bold(`\u2713 Ready in ${readyTime}ms`));
|
|
1770
|
-
console.log(chalk3.whiteBright(`Local: http://${host}:${port}`));
|
|
1771
|
-
console.log(chalk3.whiteBright(`Network: http://${host}:${port}`));
|
|
1772
|
-
console.log(chalk3.whiteBright(`MCP: ${mcpEndpoint}`));
|
|
1773
|
-
console.log(chalk3.whiteBright(`Inspector: ${inspectorUrl}
|
|
1774
|
-
`));
|
|
1775
|
-
await open3(inspectorUrl);
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
const cleanup = () => {
|
|
1779
|
-
console.log(chalk3.gray("\n\nShutting down..."));
|
|
1780
|
-
const processesToKill = processes.length;
|
|
1781
|
-
let killedCount = 0;
|
|
1782
|
-
const checkAndExit = () => {
|
|
1783
|
-
killedCount++;
|
|
1784
|
-
if (killedCount >= processesToKill) {
|
|
1785
|
-
process.exit(0);
|
|
1786
|
-
}
|
|
1787
|
-
};
|
|
1788
|
-
processes.forEach((proc) => {
|
|
1789
|
-
if (proc && typeof proc.kill === "function") {
|
|
1790
|
-
proc.on("exit", checkAndExit);
|
|
1791
|
-
proc.kill("SIGINT");
|
|
1792
|
-
} else {
|
|
1793
|
-
checkAndExit();
|
|
1794
|
-
}
|
|
1795
|
-
});
|
|
1796
|
-
setTimeout(() => {
|
|
1797
|
-
processes.forEach((proc) => {
|
|
1798
|
-
if (proc && typeof proc.kill === "function" && proc.exitCode === null) {
|
|
1799
|
-
proc.kill("SIGKILL");
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
process.exit(0);
|
|
1803
|
-
}, 1e3);
|
|
1804
|
-
};
|
|
1805
|
-
process.on("SIGINT", cleanup);
|
|
1806
|
-
process.on("SIGTERM", cleanup);
|
|
1807
|
-
await new Promise(() => {
|
|
1808
|
-
});
|
|
1809
|
-
} catch (error) {
|
|
1810
|
-
console.error(chalk3.red("Dev mode failed:"), error);
|
|
1811
|
-
process.exit(1);
|
|
1812
|
-
}
|
|
1813
|
-
});
|
|
1814
|
-
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) => {
|
|
1815
|
-
try {
|
|
1816
|
-
const projectPath = path3.resolve(options.path);
|
|
1817
|
-
const port = parseInt(options.port, 10);
|
|
1818
|
-
console.log(
|
|
1819
|
-
`\x1B[36m\x1B[1mmcp-use\x1B[0m \x1B[90mVersion: ${packageJson.version}\x1B[0m
|
|
1820
|
-
`
|
|
1821
|
-
);
|
|
1822
|
-
let mcpUrl;
|
|
1823
|
-
let tunnelProcess = void 0;
|
|
1824
|
-
let tunnelSubdomain = void 0;
|
|
1825
|
-
if (options.tunnel) {
|
|
1826
|
-
try {
|
|
1827
|
-
const manifestPath = path3.join(projectPath, "dist", "mcp-use.json");
|
|
1828
|
-
let existingSubdomain;
|
|
1829
|
-
try {
|
|
1830
|
-
const manifestContent = await readFile(manifestPath, "utf-8");
|
|
1831
|
-
const manifest = JSON.parse(manifestContent);
|
|
1832
|
-
existingSubdomain = manifest.tunnel?.subdomain;
|
|
1833
|
-
if (existingSubdomain) {
|
|
1834
|
-
console.log(
|
|
1835
|
-
chalk3.gray(`Found existing subdomain: ${existingSubdomain}`)
|
|
1836
|
-
);
|
|
1837
|
-
}
|
|
1838
|
-
} catch (error) {
|
|
1839
|
-
console.debug(
|
|
1840
|
-
chalk3.gray(
|
|
1841
|
-
`Debug: Failed to read or parse mcp-use.json: ${error instanceof Error ? error.message : String(error)}`
|
|
1842
|
-
)
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
const tunnelInfo = await startTunnel(port, existingSubdomain);
|
|
1846
|
-
mcpUrl = tunnelInfo.url;
|
|
1847
|
-
tunnelProcess = tunnelInfo.process;
|
|
1848
|
-
const subdomain = tunnelInfo.subdomain;
|
|
1849
|
-
tunnelSubdomain = subdomain;
|
|
1850
|
-
try {
|
|
1851
|
-
let manifest = {};
|
|
1852
|
-
try {
|
|
1853
|
-
const manifestContent = await readFile(manifestPath, "utf-8");
|
|
1854
|
-
manifest = JSON.parse(manifestContent);
|
|
1855
|
-
} catch {
|
|
1856
|
-
}
|
|
1857
|
-
if (!manifest.tunnel) {
|
|
1858
|
-
manifest.tunnel = {};
|
|
1859
|
-
}
|
|
1860
|
-
manifest.tunnel.subdomain = subdomain;
|
|
1861
|
-
await mkdir(path3.dirname(manifestPath), { recursive: true });
|
|
1862
|
-
await writeFile(
|
|
1863
|
-
manifestPath,
|
|
1864
|
-
JSON.stringify(manifest, null, 2),
|
|
1865
|
-
"utf-8"
|
|
1866
|
-
);
|
|
1867
|
-
} catch (error) {
|
|
1868
|
-
console.warn(
|
|
1869
|
-
chalk3.yellow(
|
|
1870
|
-
`\u26A0\uFE0F Failed to save subdomain to mcp-use.json: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1871
|
-
)
|
|
1872
|
-
);
|
|
1873
|
-
}
|
|
1874
|
-
} catch (error) {
|
|
1875
|
-
console.error(chalk3.red("Failed to start tunnel:"), error);
|
|
1876
|
-
process.exit(1);
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
let serverFile = "dist/index.js";
|
|
1880
|
-
try {
|
|
1881
|
-
await access(path3.join(projectPath, serverFile));
|
|
1882
|
-
} catch {
|
|
1883
|
-
serverFile = "dist/server.js";
|
|
1884
|
-
}
|
|
1885
|
-
console.log("Starting production server...");
|
|
1886
|
-
const env = {
|
|
1887
|
-
...process.env,
|
|
1888
|
-
PORT: String(port),
|
|
1889
|
-
NODE_ENV: "production"
|
|
1890
|
-
};
|
|
1891
|
-
if (mcpUrl) {
|
|
1892
|
-
env.MCP_URL = mcpUrl;
|
|
1893
|
-
console.log(chalk3.whiteBright(`Tunnel: ${mcpUrl}/mcp`));
|
|
1894
|
-
}
|
|
1895
|
-
const serverProc = spawn("node", [serverFile], {
|
|
1896
|
-
cwd: projectPath,
|
|
1897
|
-
stdio: "inherit",
|
|
1898
|
-
env
|
|
1899
|
-
});
|
|
1900
|
-
let cleanupInProgress = false;
|
|
1901
|
-
const cleanup = async () => {
|
|
1902
|
-
if (cleanupInProgress) {
|
|
1903
|
-
return;
|
|
1904
|
-
}
|
|
1905
|
-
cleanupInProgress = true;
|
|
1906
|
-
console.log(chalk3.gray("\n\nShutting down..."));
|
|
1907
|
-
if (tunnelProcess && typeof tunnelProcess.markShutdown === "function") {
|
|
1908
|
-
tunnelProcess.markShutdown();
|
|
1909
|
-
}
|
|
1910
|
-
if (tunnelSubdomain) {
|
|
1911
|
-
try {
|
|
1912
|
-
const apiBase = process.env.MCP_USE_API || "https://local.mcp-use.run";
|
|
1913
|
-
await fetch(`${apiBase}/api/tunnels/${tunnelSubdomain}`, {
|
|
1914
|
-
method: "DELETE"
|
|
1915
|
-
});
|
|
1916
|
-
} catch (err) {
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
const processesToKill = 1 + (tunnelProcess ? 1 : 0);
|
|
1920
|
-
let killedCount = 0;
|
|
1921
|
-
const checkAndExit = () => {
|
|
1922
|
-
killedCount++;
|
|
1923
|
-
if (killedCount >= processesToKill) {
|
|
1924
|
-
process.exit(0);
|
|
1925
|
-
}
|
|
1926
|
-
};
|
|
1927
|
-
serverProc.on("exit", checkAndExit);
|
|
1928
|
-
serverProc.kill("SIGTERM");
|
|
1929
|
-
if (tunnelProcess && typeof tunnelProcess.kill === "function") {
|
|
1930
|
-
tunnelProcess.on("exit", checkAndExit);
|
|
1931
|
-
tunnelProcess.kill("SIGINT");
|
|
1932
|
-
} else {
|
|
1933
|
-
checkAndExit();
|
|
1934
|
-
}
|
|
1935
|
-
setTimeout(() => {
|
|
1936
|
-
if (serverProc.exitCode === null) {
|
|
1937
|
-
serverProc.kill("SIGKILL");
|
|
1938
|
-
}
|
|
1939
|
-
if (tunnelProcess && tunnelProcess.exitCode === null) {
|
|
1940
|
-
tunnelProcess.kill("SIGKILL");
|
|
1941
|
-
}
|
|
1942
|
-
process.exit(0);
|
|
1943
|
-
}, 2e3);
|
|
1944
|
-
};
|
|
1945
|
-
process.on("SIGINT", cleanup);
|
|
1946
|
-
process.on("SIGTERM", cleanup);
|
|
1947
|
-
serverProc.on("exit", (code) => {
|
|
1948
|
-
process.exit(code || 0);
|
|
1949
|
-
});
|
|
1950
|
-
} catch (error) {
|
|
1951
|
-
console.error("Start failed:", error);
|
|
1952
|
-
process.exit(1);
|
|
1953
|
-
}
|
|
1954
|
-
});
|
|
1955
|
-
program.command("login").description("Login to mcp-use cloud").action(async () => {
|
|
1956
|
-
await loginCommand();
|
|
1957
|
-
});
|
|
1958
|
-
program.command("logout").description("Logout from mcp-use cloud").action(async () => {
|
|
1959
|
-
await logoutCommand();
|
|
1960
|
-
});
|
|
1961
|
-
program.command("whoami").description("Show current user information").action(async () => {
|
|
1962
|
-
await whoamiCommand();
|
|
1963
|
-
});
|
|
1964
|
-
program.command("deploy").description("Deploy MCP server to mcp-use cloud").option("--open", "Open deployment in browser after successful deploy").option("--name <name>", "Custom deployment name").option("--port <port>", "Server port", "3000").option("--runtime <runtime>", "Runtime (node or python)").option(
|
|
1965
|
-
"--from-source",
|
|
1966
|
-
"Deploy from local source code (even for GitHub repos)"
|
|
1967
|
-
).action(async (options) => {
|
|
1968
|
-
await deployCommand({
|
|
1969
|
-
open: options.open,
|
|
1970
|
-
name: options.name,
|
|
1971
|
-
port: options.port ? parseInt(options.port, 10) : void 0,
|
|
1972
|
-
runtime: options.runtime,
|
|
1973
|
-
fromSource: options.fromSource
|
|
1974
|
-
});
|
|
1975
|
-
});
|
|
1976
|
-
program.parse();
|