@flemist/mcp-project-tools 1.0.2 → 3.0.0
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/build/cli.d.ts +1 -0
- package/build/cli.js +92 -54
- package/build/index.d.ts +91 -0
- package/build/index.js +1 -2
- package/build/startMcpServer-BOhpWAg9.js +3475 -0
- package/package.json +12 -6
- package/build/cli.js.map +0 -1
- package/build/index.js.map +0 -1
- package/build/startMcpServer-B-xjEzCx.js +0 -1692
- package/build/startMcpServer-B-xjEzCx.js.map +0 -1
|
@@ -1,1692 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import * as cors from "cors";
|
|
3
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { randomBytes } from "crypto";
|
|
5
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
-
import * as fs from "fs";
|
|
7
|
-
import fs__default from "fs";
|
|
8
|
-
import * as path from "path";
|
|
9
|
-
import path__default from "path";
|
|
10
|
-
import { spawn } from "child_process";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import treeKill from "tree-kill";
|
|
13
|
-
import { Pool, poolRunWait } from "@flemist/time-limits";
|
|
14
|
-
import { priorityCreate } from "@flemist/priority-queue";
|
|
15
|
-
import { useAbortController, combineAbortSignals } from "@flemist/async-utils";
|
|
16
|
-
import os from "node:os";
|
|
17
|
-
import picomatch from "picomatch";
|
|
18
|
-
function createAuthMiddleware(options) {
|
|
19
|
-
const { authToken } = options;
|
|
20
|
-
return function authMiddleware(req, res, next) {
|
|
21
|
-
const token = req.query.token || req.headers.authorization?.replace("Bearer ", "");
|
|
22
|
-
if (token !== authToken) {
|
|
23
|
-
res.status(401).json({ error: "Unauthorized" });
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
next();
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
async function logToFile(options) {
|
|
30
|
-
const { logFilePath, message, data } = options;
|
|
31
|
-
try {
|
|
32
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[TZ]/g, " ").trim();
|
|
33
|
-
const dataStr = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
34
|
-
const logData = `[${timestamp}] ${message}
|
|
35
|
-
${dataStr}
|
|
36
|
-
|
|
37
|
-
`;
|
|
38
|
-
await fs.promises.mkdir(path.dirname(logFilePath), { recursive: true });
|
|
39
|
-
await fs.promises.appendFile(logFilePath, logData);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.error(`Failed to log "${message}":`, err);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const sessionsTransports = /* @__PURE__ */ new Map();
|
|
45
|
-
function createStreamableHttpHandler(mcpServer, options) {
|
|
46
|
-
return async function streamableHttpHandler(req, res) {
|
|
47
|
-
const sessionId = req.headers["mcp-session-id"] || req.headers["x-session-id"] || req.query.token;
|
|
48
|
-
let transport;
|
|
49
|
-
if (req.method === "POST") {
|
|
50
|
-
await logToFile({
|
|
51
|
-
logFilePath: options.logFilePath,
|
|
52
|
-
message: "REQUEST",
|
|
53
|
-
data: req.body
|
|
54
|
-
});
|
|
55
|
-
if (sessionId && sessionsTransports.has(sessionId)) {
|
|
56
|
-
transport = sessionsTransports.get(sessionId);
|
|
57
|
-
} else {
|
|
58
|
-
transport = new StreamableHTTPServerTransport({
|
|
59
|
-
sessionIdGenerator: () => sessionId || randomBytes(16).toString("hex"),
|
|
60
|
-
onsessioninitialized: (id) => {
|
|
61
|
-
sessionsTransports.set(id, transport);
|
|
62
|
-
console.log(`Session initialized: ${id}`);
|
|
63
|
-
},
|
|
64
|
-
enableJsonResponse: options.enableJsonResponse || false
|
|
65
|
-
});
|
|
66
|
-
transport.onclose = () => {
|
|
67
|
-
if (transport.sessionId) {
|
|
68
|
-
sessionsTransports.delete(transport.sessionId);
|
|
69
|
-
console.log(`Session closed: ${transport.sessionId}`);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
await mcpServer.connect(transport);
|
|
73
|
-
if (sessionId) {
|
|
74
|
-
sessionsTransports.set(sessionId, transport);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
await transport.handleRequest(req, res, req.body);
|
|
78
|
-
} else if (req.method === "GET") {
|
|
79
|
-
if (sessionId && sessionsTransports.has(sessionId)) {
|
|
80
|
-
transport = sessionsTransports.get(sessionId);
|
|
81
|
-
await transport.handleRequest(req, res);
|
|
82
|
-
} else {
|
|
83
|
-
res.status(400).json({ error: "No valid session" });
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
res.status(405).json({ error: "Method not allowed" });
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function extractCommand(options) {
|
|
91
|
-
const { commandLine } = options;
|
|
92
|
-
const match = commandLine.match(/^"([^"]+)"|^(\S+)/);
|
|
93
|
-
return match ? match[1] || match[2] : commandLine.split(" ")[0];
|
|
94
|
-
}
|
|
95
|
-
const processes = /* @__PURE__ */ new Map();
|
|
96
|
-
let nextProcessId = 0;
|
|
97
|
-
const MAX_PROCESSES = 10;
|
|
98
|
-
const PROCESS_CLEANUP_TIME = 30 * 60 * 1e3;
|
|
99
|
-
const OUTPUT_LIMIT = 2e3;
|
|
100
|
-
const OUTPUT_THROTTLE_TIME = 500;
|
|
101
|
-
const KILL_TIMEOUT = 5e3;
|
|
102
|
-
function isCommandAllowed(options) {
|
|
103
|
-
const { commandLine, allowedCommands } = options;
|
|
104
|
-
const command = extractCommand({ commandLine });
|
|
105
|
-
return allowedCommands.some(
|
|
106
|
-
(allowed) => command === allowed || command.toLowerCase() === allowed.toLowerCase()
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
function getNextProcessId() {
|
|
110
|
-
return ++nextProcessId;
|
|
111
|
-
}
|
|
112
|
-
let autoKillInitialized = false;
|
|
113
|
-
function autoKillChildProcesses() {
|
|
114
|
-
if (autoKillInitialized) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
autoKillInitialized = true;
|
|
118
|
-
const kill = () => {
|
|
119
|
-
console.log("Auto-killing all child processes...");
|
|
120
|
-
for (const [id, process2] of Array.from(processes.entries())) {
|
|
121
|
-
if (process2.isRunning && process2.pid) {
|
|
122
|
-
try {
|
|
123
|
-
treeKill(process2.pid, "SIGKILL");
|
|
124
|
-
} catch (err) {
|
|
125
|
-
console.error(`Error killing process ${id}:`, err);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
process.exit(0);
|
|
130
|
-
};
|
|
131
|
-
process.on("SIGINT", kill);
|
|
132
|
-
process.on("SIGTERM", kill);
|
|
133
|
-
}
|
|
134
|
-
function cleanupOldProcesses() {
|
|
135
|
-
const now = Date.now();
|
|
136
|
-
const toRemove = [];
|
|
137
|
-
for (const [id, process2] of Array.from(processes.entries())) {
|
|
138
|
-
if (!process2.isRunning && process2.endTime) {
|
|
139
|
-
const timeSinceEnd = now - process2.endTime.getTime();
|
|
140
|
-
if (timeSinceEnd > PROCESS_CLEANUP_TIME) {
|
|
141
|
-
toRemove.push(id);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
for (const id of toRemove) {
|
|
146
|
-
processes.delete(id);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function updateProcessOutput(options) {
|
|
150
|
-
const { process: process2 } = options;
|
|
151
|
-
const now = Date.now();
|
|
152
|
-
const timeSinceLastUpdate = now - process2.lastOutputTime.getTime();
|
|
153
|
-
if (timeSinceLastUpdate >= OUTPUT_THROTTLE_TIME) {
|
|
154
|
-
process2.output += process2.localOutput;
|
|
155
|
-
process2.localOutput = "";
|
|
156
|
-
process2.lastOutputTime = new Date(now);
|
|
157
|
-
if (process2.output.length > OUTPUT_LIMIT) {
|
|
158
|
-
const removed = process2.output.length - OUTPUT_LIMIT;
|
|
159
|
-
const trimMessage = `
|
|
160
|
-
... [${removed} characters trimmed] ...
|
|
161
|
-
`;
|
|
162
|
-
const availableSpace = OUTPUT_LIMIT - trimMessage.length;
|
|
163
|
-
if (availableSpace > 0) {
|
|
164
|
-
const half = Math.floor(availableSpace / 2);
|
|
165
|
-
process2.output = process2.output.substring(0, half) + trimMessage + process2.output.substring(
|
|
166
|
-
process2.output.length - (availableSpace - half)
|
|
167
|
-
);
|
|
168
|
-
} else {
|
|
169
|
-
process2.output = process2.output.substring(0, OUTPUT_LIMIT);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function cropText(text, options) {
|
|
175
|
-
const limit = options.limit - 35;
|
|
176
|
-
if (text.length <= limit) return text;
|
|
177
|
-
const removedLength = text.length - limit;
|
|
178
|
-
const trimMessage = `
|
|
179
|
-
... [${removedLength} characters trimmed] ...
|
|
180
|
-
`;
|
|
181
|
-
const availableLength = limit - trimMessage.length;
|
|
182
|
-
if (availableLength <= 0) {
|
|
183
|
-
return text.substring(0, limit);
|
|
184
|
-
}
|
|
185
|
-
const half = Math.floor(availableLength / 2);
|
|
186
|
-
return text.substring(0, half) + trimMessage + text.substring(text.length - (availableLength - half));
|
|
187
|
-
}
|
|
188
|
-
const StatusSchema = z.object({
|
|
189
|
-
id: z.number().describe(
|
|
190
|
-
"Process ID to get detailed status information for. Get process IDs using process-list. Works for both running and completed processes. Examples: 1, 42, 123."
|
|
191
|
-
),
|
|
192
|
-
outputLimit: z.number().max(OUTPUT_LIMIT).default(OUTPUT_LIMIT).describe(
|
|
193
|
-
`Maximum number of output characters to return from the process. Output exceeding this limit will be cropped with beginning/end preserved. Maximum: ${OUTPUT_LIMIT} characters. Default: ${OUTPUT_LIMIT}.`
|
|
194
|
-
)
|
|
195
|
-
});
|
|
196
|
-
async function commandStatus(args, options) {
|
|
197
|
-
cleanupOldProcesses();
|
|
198
|
-
const parsedArgs = StatusSchema.parse(args);
|
|
199
|
-
const { id, outputLimit } = parsedArgs;
|
|
200
|
-
const process2 = processes.get(id);
|
|
201
|
-
if (!process2) {
|
|
202
|
-
return {
|
|
203
|
-
error: `Process ${id} not found. The process may have already completed and been cleaned up after 30 minutes, or the ID may be incorrect. Use process-list to see available processes and their current status.`
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
updateProcessOutput({ process: process2 });
|
|
207
|
-
const fullOutput = process2.output + process2.localOutput;
|
|
208
|
-
const formattedOutput = cropText(fullOutput, { limit: outputLimit });
|
|
209
|
-
process2.output = "";
|
|
210
|
-
process2.localOutput = "";
|
|
211
|
-
return {
|
|
212
|
-
id: process2.id,
|
|
213
|
-
cwd: path.relative(options.workingDir || "", process2.cwd),
|
|
214
|
-
commandLine: process2.commandLine,
|
|
215
|
-
pid: process2.pid,
|
|
216
|
-
startTime: process2.startTime.toISOString().replace(/[TZ]/g, " ").trim(),
|
|
217
|
-
endTime: process2.endTime?.toISOString().replace(/[TZ]/g, " ").trim(),
|
|
218
|
-
exitCode: process2.exitCode,
|
|
219
|
-
isRunning: process2.isRunning,
|
|
220
|
-
output: formattedOutput,
|
|
221
|
-
error: process2.error
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function mcpRegisterToolProcessStatus(mcpRegisterTool, options) {
|
|
225
|
-
mcpRegisterTool(
|
|
226
|
-
"process-status",
|
|
227
|
-
{
|
|
228
|
-
title: "Get Host Machine Process Status",
|
|
229
|
-
description: "Get detailed status information about a process on the host machine, including execution details, captured output, exit code, and runtime status. Returns comprehensive process information for both running and completed processes. The output is cleared after retrieval to prevent duplication in subsequent calls. Useful for checking command execution results and monitoring process progress.",
|
|
230
|
-
inputSchema: StatusSchema.shape
|
|
231
|
-
},
|
|
232
|
-
async (args) => {
|
|
233
|
-
const result = await commandStatus(args, options);
|
|
234
|
-
const output = result.output || "";
|
|
235
|
-
delete result.output;
|
|
236
|
-
return `Method: process-status(${JSON.stringify(args)})
|
|
237
|
-
${JSON.stringify(result, null, 2)}
|
|
238
|
-
|
|
239
|
-
Output:
|
|
240
|
-
${output}`.trim();
|
|
241
|
-
}
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
function killProcessById(pid) {
|
|
245
|
-
treeKill(pid, "SIGTERM", (err) => {
|
|
246
|
-
if (err && !err.message.includes("not found")) {
|
|
247
|
-
console.error(`Error sending SIGTERM to process ${pid}:`, err);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
setTimeout(() => {
|
|
251
|
-
treeKill(pid, "SIGKILL", (err) => {
|
|
252
|
-
if (err && !err.message.includes("not found")) {
|
|
253
|
-
console.error(`Error sending SIGKILL to process ${pid}:`, err);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}, KILL_TIMEOUT);
|
|
257
|
-
}
|
|
258
|
-
const WaitSchema = z.object({
|
|
259
|
-
id: z.number().describe(
|
|
260
|
-
"Process ID to wait for completion. Get process IDs using process-list. The process can be running or already completed. Examples: 1, 42, 123."
|
|
261
|
-
),
|
|
262
|
-
waitTime: z.number().optional().describe(
|
|
263
|
-
"Maximum time to wait in seconds for process completion. If omitted, waits indefinitely until process completes. If process is already completed, returns immediately. Examples: 30 (wait up to 30 seconds), 300 (wait up to 5 minutes)."
|
|
264
|
-
),
|
|
265
|
-
autoKill: z.boolean().default(false).describe(
|
|
266
|
-
"Automatically terminate the process if waitTime expires and it is still running. Only applies when waitTime is specified. Default: false (let process continue running after timeout). Set to true for processes that should not run indefinitely."
|
|
267
|
-
),
|
|
268
|
-
outputLimit: z.number().max(OUTPUT_LIMIT).default(OUTPUT_LIMIT).describe(
|
|
269
|
-
`Maximum number of output characters to capture and return from the process. Output exceeding this limit will be trimmed. Maximum: ${OUTPUT_LIMIT} characters. Default: ${OUTPUT_LIMIT}.`
|
|
270
|
-
)
|
|
271
|
-
});
|
|
272
|
-
async function commandWait(args, options) {
|
|
273
|
-
const parsedArgs = WaitSchema.parse(args);
|
|
274
|
-
const { id, waitTime, autoKill, outputLimit } = parsedArgs;
|
|
275
|
-
const process2 = processes.get(id);
|
|
276
|
-
if (!process2) {
|
|
277
|
-
return {
|
|
278
|
-
error: `Process ${id} not found. The process may have already completed and been cleaned up after 30 minutes, or the ID may be incorrect. Use process-list to see available processes and their current status.`
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
const startWait = Date.now();
|
|
282
|
-
let waitTimeExceeded = false;
|
|
283
|
-
let autoKillExecuted = false;
|
|
284
|
-
if (waitTime !== void 0) {
|
|
285
|
-
const waitPromise = new Promise((resolve) => {
|
|
286
|
-
const checkInterval = setInterval(() => {
|
|
287
|
-
if (!process2.isRunning) {
|
|
288
|
-
clearInterval(checkInterval);
|
|
289
|
-
resolve();
|
|
290
|
-
} else if (Date.now() - startWait >= waitTime * 1e3) {
|
|
291
|
-
clearInterval(checkInterval);
|
|
292
|
-
waitTimeExceeded = true;
|
|
293
|
-
if (autoKill && process2.pid) {
|
|
294
|
-
killProcessById(process2.pid);
|
|
295
|
-
autoKillExecuted = true;
|
|
296
|
-
}
|
|
297
|
-
resolve();
|
|
298
|
-
}
|
|
299
|
-
}, 100);
|
|
300
|
-
});
|
|
301
|
-
await waitPromise;
|
|
302
|
-
}
|
|
303
|
-
const waitDuration = (Date.now() - startWait) / 1e3;
|
|
304
|
-
const status = await commandStatus(
|
|
305
|
-
{ id, outputLimit },
|
|
306
|
-
{ workingDir: options.workingDir }
|
|
307
|
-
);
|
|
308
|
-
return {
|
|
309
|
-
...status,
|
|
310
|
-
waitDuration,
|
|
311
|
-
waitTimeExceeded,
|
|
312
|
-
autoKillExecuted
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
function mcpRegisterToolProcessWait(mcpRegisterTool, options) {
|
|
316
|
-
mcpRegisterTool(
|
|
317
|
-
"process-wait",
|
|
318
|
-
{
|
|
319
|
-
title: "Wait for Host Machine Process",
|
|
320
|
-
description: "Wait for a host machine process to complete execution, with optional timeout and auto-kill functionality. Useful for long-running commands like builds, installs, or tests where you need to wait for completion. Returns the final process status along with wait duration and timeout information. Can automatically terminate processes that exceed the wait time limit.",
|
|
321
|
-
inputSchema: WaitSchema.shape
|
|
322
|
-
},
|
|
323
|
-
async (args) => {
|
|
324
|
-
const result = await commandWait(args, options);
|
|
325
|
-
const output = result.output || "";
|
|
326
|
-
delete result.output;
|
|
327
|
-
return `Method: process-wait(${JSON.stringify(args)})
|
|
328
|
-
${JSON.stringify(result, null, 2)}
|
|
329
|
-
|
|
330
|
-
Output:
|
|
331
|
-
${output}`.trim();
|
|
332
|
-
}
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
const RunSchema = z.object({
|
|
336
|
-
cwd: z.string().optional().describe(
|
|
337
|
-
'Working directory for command execution, resolved relative to the current working directory. Leave empty to use current directory. Examples: "src" (run in src/ subdirectory), "../parent" (run in parent directory), "build/output" (run in nested subdirectory). Directory must exist.'
|
|
338
|
-
),
|
|
339
|
-
commandLine: z.string().describe(
|
|
340
|
-
'Complete command line to execute on the host machine. Include all arguments and options. Examples: "npm install", "pnpm build", "node script.js --verbose", "git status". Command must be in the allowed commands list for security.'
|
|
341
|
-
),
|
|
342
|
-
waitTime: z.number().optional().describe(
|
|
343
|
-
"Time to wait in seconds for process completion before returning results. If specified, will wait this long then return final status. If omitted, returns immediately with initial status. Use process-wait or process-status to check progress later. Examples: 30 (wait 30 seconds), 120 (wait 2 minutes)."
|
|
344
|
-
),
|
|
345
|
-
autoKill: z.boolean().default(false).describe(
|
|
346
|
-
"Automatically kill the process if waitTime expires and it is still running. Only applies when waitTime is specified. Default: false (let process continue running). Set to true for commands that should not run indefinitely."
|
|
347
|
-
),
|
|
348
|
-
outputLimit: z.number().max(OUTPUT_LIMIT).default(OUTPUT_LIMIT).describe(
|
|
349
|
-
`Maximum number of output characters to capture and return from the process. Output exceeding this limit will be trimmed with beginning/end preserved and middle removed. Maximum: ${OUTPUT_LIMIT} characters. Default: ${OUTPUT_LIMIT}.`
|
|
350
|
-
)
|
|
351
|
-
});
|
|
352
|
-
async function commandRun(args, options) {
|
|
353
|
-
cleanupOldProcesses();
|
|
354
|
-
const parsedArgs = RunSchema.parse(args);
|
|
355
|
-
const { commandLine, waitTime, autoKill, outputLimit } = parsedArgs;
|
|
356
|
-
const { allowedCommands } = options;
|
|
357
|
-
const cwd = path.resolve(options.workingDir || "", parsedArgs.cwd || "");
|
|
358
|
-
if (!isCommandAllowed({ commandLine, allowedCommands })) {
|
|
359
|
-
return {
|
|
360
|
-
error: `Command not allowed: "${commandLine}". For security, only whitelisted commands can be executed on the host machine. Allowed commands: ${allowedCommands.join(", ")}. The command "${extractCommand({ commandLine })}" is not in the allowed list. To use this command, ask the user to add it to the allowedCommands configuration.`
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
const activeProcesses = Array.from(processes.values()).filter(
|
|
364
|
-
(o) => o.isRunning
|
|
365
|
-
);
|
|
366
|
-
if (activeProcesses.length >= MAX_PROCESSES) {
|
|
367
|
-
return {
|
|
368
|
-
error: `Maximum concurrent process limit reached (${MAX_PROCESSES} processes). Cannot start new process until existing processes complete. Use process-list to see active processes, or process-kill to terminate unnecessary processes.`
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
const id = getNextProcessId();
|
|
372
|
-
const processInfo = {
|
|
373
|
-
id,
|
|
374
|
-
cwd,
|
|
375
|
-
commandLine,
|
|
376
|
-
startTime: /* @__PURE__ */ new Date(),
|
|
377
|
-
isRunning: true,
|
|
378
|
-
output: "",
|
|
379
|
-
localOutput: "",
|
|
380
|
-
lastOutputTime: /* @__PURE__ */ new Date()
|
|
381
|
-
};
|
|
382
|
-
processes.set(id, processInfo);
|
|
383
|
-
try {
|
|
384
|
-
const child = spawn(commandLine, [], {
|
|
385
|
-
shell: true,
|
|
386
|
-
cwd,
|
|
387
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
388
|
-
});
|
|
389
|
-
processInfo.pid = child.pid;
|
|
390
|
-
const handleOutput = (data) => {
|
|
391
|
-
const text = data.toString();
|
|
392
|
-
processInfo.localOutput += text;
|
|
393
|
-
updateProcessOutput({ process: processInfo });
|
|
394
|
-
console.log(text);
|
|
395
|
-
};
|
|
396
|
-
child.stdout?.on("data", handleOutput);
|
|
397
|
-
child.stderr?.on("data", handleOutput);
|
|
398
|
-
child.on("close", (code) => {
|
|
399
|
-
processInfo.isRunning = false;
|
|
400
|
-
processInfo.endTime = /* @__PURE__ */ new Date();
|
|
401
|
-
processInfo.exitCode = code !== null ? code : void 0;
|
|
402
|
-
processInfo.output += processInfo.localOutput;
|
|
403
|
-
processInfo.localOutput = "";
|
|
404
|
-
if (processInfo.output.length > OUTPUT_LIMIT) {
|
|
405
|
-
const removed = processInfo.output.length - OUTPUT_LIMIT;
|
|
406
|
-
const trimMessage = `
|
|
407
|
-
... [${removed} characters trimmed] ...
|
|
408
|
-
`;
|
|
409
|
-
const availableSpace = OUTPUT_LIMIT - trimMessage.length;
|
|
410
|
-
if (availableSpace > 0) {
|
|
411
|
-
const half = Math.floor(availableSpace / 2);
|
|
412
|
-
processInfo.output = processInfo.output.substring(0, half) + trimMessage + processInfo.output.substring(
|
|
413
|
-
processInfo.output.length - (availableSpace - half)
|
|
414
|
-
);
|
|
415
|
-
} else {
|
|
416
|
-
processInfo.output = processInfo.output.substring(0, OUTPUT_LIMIT);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
console.log(`Process ${id} (${commandLine}) exited with code ${code}`);
|
|
420
|
-
});
|
|
421
|
-
child.on("error", (err) => {
|
|
422
|
-
processInfo.isRunning = false;
|
|
423
|
-
processInfo.endTime = /* @__PURE__ */ new Date();
|
|
424
|
-
processInfo.error = err.message;
|
|
425
|
-
console.error(`Process ${id} error:`, err.message);
|
|
426
|
-
});
|
|
427
|
-
if (waitTime !== void 0) {
|
|
428
|
-
return commandWait(
|
|
429
|
-
{ id, waitTime, autoKill, outputLimit },
|
|
430
|
-
{ workingDir: options.workingDir }
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
return commandStatus(
|
|
434
|
-
{ id, outputLimit },
|
|
435
|
-
{ workingDir: options.workingDir }
|
|
436
|
-
);
|
|
437
|
-
} catch (err) {
|
|
438
|
-
processInfo.isRunning = false;
|
|
439
|
-
processInfo.endTime = /* @__PURE__ */ new Date();
|
|
440
|
-
processInfo.error = err instanceof Error ? err.message : "Unknown error";
|
|
441
|
-
return { error: processInfo.error };
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
function mcpRegisterToolProcessRun(mcpRegisterTool, options) {
|
|
445
|
-
mcpRegisterTool(
|
|
446
|
-
"process-run",
|
|
447
|
-
{
|
|
448
|
-
title: "Execute Command on Host Machine",
|
|
449
|
-
description: `Execute commands on the host machine. This tool allows running system commands that require host environment access, such as package managers (npm, pnpm, yarn), build tools, git operations, and other system utilities. Commands are executed in shell with full argument support. Returns process status and captured output. If waitTime is specified, waits for completion; otherwise returns immediately and you can check progress with process-wait or process-status. Security: Only whitelisted commands are allowed. Current allowed commands: ${options.allowedCommands.join(", ")}.`,
|
|
450
|
-
inputSchema: RunSchema.shape
|
|
451
|
-
},
|
|
452
|
-
async (args) => {
|
|
453
|
-
const result = await commandRun(args, options);
|
|
454
|
-
const output = result.output || "";
|
|
455
|
-
delete result.output;
|
|
456
|
-
return `Method: process-run(${JSON.stringify(args)})
|
|
457
|
-
${JSON.stringify(result, null, 2)}
|
|
458
|
-
|
|
459
|
-
Output:
|
|
460
|
-
${output}`.trim();
|
|
461
|
-
}
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
const ListSchema$1 = z.object({
|
|
465
|
-
minOpenDateTime: z.string().optional().describe(
|
|
466
|
-
'Filter to processes started after this datetime. Accepts ISO format or space-separated format. Examples: "2024-01-15T10:30:00Z", "2024-01-15 10:30:00". Underscores and spaces are converted to standard ISO format internally.'
|
|
467
|
-
),
|
|
468
|
-
minCloseDateTime: z.string().optional().describe(
|
|
469
|
-
'Filter to processes that finished after this datetime. Only applies to completed processes. Accepts ISO format or space-separated format. Examples: "2024-01-15T14:30:00Z", "2024-01-15 14:30:00". Useful for finding recently completed processes.'
|
|
470
|
-
),
|
|
471
|
-
activeOnly: z.boolean().default(false).describe(
|
|
472
|
-
"Show only currently running processes. Set to true to exclude completed processes, false to show all processes (running and completed). Default: false (show all)."
|
|
473
|
-
),
|
|
474
|
-
fields: z.array(z.string()).optional().describe(
|
|
475
|
-
'Specific process data fields to include in the response. If omitted, returns all available fields. Available fields: id, cwd, commandLine, pid, startTime, endTime, exitCode, isRunning, output, error. Examples: ["id", "commandLine", "isRunning"] for minimal info, ["id", "output", "exitCode"] for execution results.'
|
|
476
|
-
)
|
|
477
|
-
});
|
|
478
|
-
async function commandList$1(args, options) {
|
|
479
|
-
cleanupOldProcesses();
|
|
480
|
-
const parsedArgs = ListSchema$1.parse(args);
|
|
481
|
-
const { minOpenDateTime, minCloseDateTime, activeOnly, fields } = parsedArgs;
|
|
482
|
-
let processList = Array.from(processes.values());
|
|
483
|
-
if (minOpenDateTime) {
|
|
484
|
-
const minDate = new Date(minOpenDateTime.replace(/[_\s]/g, "T"));
|
|
485
|
-
processList = processList.filter((p) => p.startTime >= minDate);
|
|
486
|
-
}
|
|
487
|
-
if (minCloseDateTime) {
|
|
488
|
-
const minDate = new Date(minCloseDateTime.replace(/[_\s]/g, "T"));
|
|
489
|
-
processList = processList.filter((p) => p.endTime && p.endTime >= minDate);
|
|
490
|
-
}
|
|
491
|
-
if (activeOnly) {
|
|
492
|
-
processList = processList.filter((p) => p.isRunning);
|
|
493
|
-
}
|
|
494
|
-
const result = processList.map((process2) => {
|
|
495
|
-
updateProcessOutput({ process: process2 });
|
|
496
|
-
let processData = {
|
|
497
|
-
id: process2.id,
|
|
498
|
-
cwd: path__default.relative(options.workingDir || "", process2.cwd),
|
|
499
|
-
commandLine: process2.commandLine,
|
|
500
|
-
pid: process2.pid,
|
|
501
|
-
startTime: process2.startTime.toISOString().replace(/[TZ]/g, " ").trim(),
|
|
502
|
-
endTime: process2.endTime?.toISOString().replace(/[TZ]/g, " ").trim(),
|
|
503
|
-
exitCode: process2.exitCode,
|
|
504
|
-
isRunning: process2.isRunning,
|
|
505
|
-
output: process2.output + process2.localOutput,
|
|
506
|
-
error: process2.error
|
|
507
|
-
};
|
|
508
|
-
if (fields) {
|
|
509
|
-
const filtered = {};
|
|
510
|
-
for (const field of fields) {
|
|
511
|
-
if (field in processData) {
|
|
512
|
-
filtered[field] = processData[field];
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
processData = filtered;
|
|
516
|
-
}
|
|
517
|
-
return processData;
|
|
518
|
-
});
|
|
519
|
-
return { processes: result };
|
|
520
|
-
}
|
|
521
|
-
function mcpRegisterToolProcessList(mcpRegisterTool, options) {
|
|
522
|
-
mcpRegisterTool(
|
|
523
|
-
"process-list",
|
|
524
|
-
{
|
|
525
|
-
title: "List Host Machine Processes",
|
|
526
|
-
description: "List all processes that have been executed on the host machine with filtering and field selection options. Shows both currently running and completed processes with their execution details, output, and status. Useful for monitoring command execution history, checking active processes, and retrieving results from completed commands. Supports datetime filtering to find processes within specific time ranges.",
|
|
527
|
-
inputSchema: ListSchema$1.shape
|
|
528
|
-
},
|
|
529
|
-
async (args) => {
|
|
530
|
-
const result = await commandList$1(args, options);
|
|
531
|
-
const output = result.output || "";
|
|
532
|
-
delete result.output;
|
|
533
|
-
return `Method: process-list(${JSON.stringify(args)})
|
|
534
|
-
${JSON.stringify(result, null, 2)}
|
|
535
|
-
|
|
536
|
-
Output:
|
|
537
|
-
${output}`.trim();
|
|
538
|
-
}
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
const KillSchema = z.object({
|
|
542
|
-
id: z.number().describe(
|
|
543
|
-
"Process ID of the process to terminate. Get process IDs using process-list. The process must be currently running. Examples: 1, 42, 123."
|
|
544
|
-
)
|
|
545
|
-
});
|
|
546
|
-
function commandKill(args) {
|
|
547
|
-
const parsedArgs = KillSchema.parse(args);
|
|
548
|
-
const { id } = parsedArgs;
|
|
549
|
-
const process2 = processes.get(id);
|
|
550
|
-
if (!process2) {
|
|
551
|
-
return {
|
|
552
|
-
error: `Process ${id} not found. The process may have already completed, been cleaned up after 30 minutes, or the ID may be incorrect. Use process-list to see available processes and their current status.`
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
if (!process2.isRunning) {
|
|
556
|
-
return {
|
|
557
|
-
error: `Process ${id} is not currently running. The process has already completed or was previously terminated. Use process-list to see the process status and process-status to get final execution details.`
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (!process2.pid) {
|
|
561
|
-
return {
|
|
562
|
-
error: `Process ${id} has no system process ID (PID). This indicates the process failed to start properly or the system was unable to assign a PID. Check process-status for error details.`
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
try {
|
|
566
|
-
killProcessById(process2.pid);
|
|
567
|
-
return { success: true, message: `Kill signal sent to process ${id}` };
|
|
568
|
-
} catch (err) {
|
|
569
|
-
return {
|
|
570
|
-
error: `Failed to terminate process ${id}: ${err instanceof Error ? err.message : "Unknown error"}. The process may have already exited, be protected by the system, or there may be insufficient permissions. Use process-status to check current process state.`
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function mcpRegisterToolProcessKill(mcpRegisterTool) {
|
|
575
|
-
mcpRegisterTool(
|
|
576
|
-
"process-kill",
|
|
577
|
-
{
|
|
578
|
-
title: "Kill Host Machine Process",
|
|
579
|
-
description: "Forcibly terminate a running process on the host machine. Sends SIGTERM signal first for graceful shutdown, then SIGKILL after 5 seconds if the process is still running. Use this to stop runaway processes, hung commands, or long-running tasks that need to be cancelled. The process must be currently running to be terminated.",
|
|
580
|
-
inputSchema: KillSchema.shape
|
|
581
|
-
},
|
|
582
|
-
async (args) => {
|
|
583
|
-
const result = commandKill(args);
|
|
584
|
-
const output = result.output || "";
|
|
585
|
-
delete result.output;
|
|
586
|
-
return `Method: process-kill(${JSON.stringify(args)})
|
|
587
|
-
${JSON.stringify(result, null, 2)}
|
|
588
|
-
|
|
589
|
-
Output:
|
|
590
|
-
${output}`.trim();
|
|
591
|
-
}
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
function mcpRegisterProcessManager(mcpRegisterTool, options) {
|
|
595
|
-
autoKillChildProcesses();
|
|
596
|
-
mcpRegisterToolProcessRun(mcpRegisterTool, {
|
|
597
|
-
workingDir: options.workingDir,
|
|
598
|
-
...options.run
|
|
599
|
-
});
|
|
600
|
-
mcpRegisterToolProcessStatus(mcpRegisterTool, {
|
|
601
|
-
workingDir: options.workingDir
|
|
602
|
-
});
|
|
603
|
-
mcpRegisterToolProcessWait(mcpRegisterTool, {
|
|
604
|
-
workingDir: options.workingDir
|
|
605
|
-
});
|
|
606
|
-
mcpRegisterToolProcessList(mcpRegisterTool, {
|
|
607
|
-
workingDir: options.workingDir
|
|
608
|
-
});
|
|
609
|
-
mcpRegisterToolProcessKill(mcpRegisterTool);
|
|
610
|
-
console.log(
|
|
611
|
-
`Process manager:
|
|
612
|
-
- Working directory: ${path.resolve(options.workingDir || "")}
|
|
613
|
-
- Allowed commands: ${options.run.allowedCommands.join(", ")}
|
|
614
|
-
`
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
function createMcpRegisterToolFunc(server, options) {
|
|
618
|
-
return function mcpRegisterTool(name, config, callback) {
|
|
619
|
-
const callbackNative = async (...args) => {
|
|
620
|
-
await logToFile({
|
|
621
|
-
logFilePath: options.logFilePath,
|
|
622
|
-
message: "REQUEST",
|
|
623
|
-
data: { name, args }
|
|
624
|
-
});
|
|
625
|
-
const result = await callback(...args);
|
|
626
|
-
await logToFile({
|
|
627
|
-
logFilePath: options.logFilePath,
|
|
628
|
-
message: "RESPONSE",
|
|
629
|
-
data: result
|
|
630
|
-
});
|
|
631
|
-
return {
|
|
632
|
-
content: [
|
|
633
|
-
{
|
|
634
|
-
type: "text",
|
|
635
|
-
text: result
|
|
636
|
-
}
|
|
637
|
-
]
|
|
638
|
-
};
|
|
639
|
-
};
|
|
640
|
-
return server.registerTool(
|
|
641
|
-
name,
|
|
642
|
-
config,
|
|
643
|
-
callbackNative
|
|
644
|
-
);
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
function getDrive(path2) {
|
|
648
|
-
return path2.match(/^[/\\]?[^/\\]+/)[0];
|
|
649
|
-
}
|
|
650
|
-
function getFileId(path2, stat) {
|
|
651
|
-
return getDrive(path2) + "|" + stat.ino;
|
|
652
|
-
}
|
|
653
|
-
function pathResolve(_path) {
|
|
654
|
-
if (_path.endsWith(":")) {
|
|
655
|
-
_path += "/";
|
|
656
|
-
}
|
|
657
|
-
return path.resolve(_path);
|
|
658
|
-
}
|
|
659
|
-
const poolFs = new Pool(os.cpus().length);
|
|
660
|
-
function addStats(totalStat, itemStat) {
|
|
661
|
-
totalStat.totalSize += itemStat.totalSize;
|
|
662
|
-
totalStat.maxFileDateModified = Math.max(
|
|
663
|
-
totalStat.maxFileDateModified,
|
|
664
|
-
itemStat.maxFileDateModified
|
|
665
|
-
);
|
|
666
|
-
totalStat.countFiles += itemStat.countFiles;
|
|
667
|
-
totalStat.countDirs += itemStat.countDirs;
|
|
668
|
-
totalStat.countLinks += itemStat.countLinks;
|
|
669
|
-
}
|
|
670
|
-
const walkPathHandleErrorDefault = function walkPathHandleErrorDefault2(err) {
|
|
671
|
-
if (err.code === "ENOENT") {
|
|
672
|
-
return true;
|
|
673
|
-
}
|
|
674
|
-
return false;
|
|
675
|
-
};
|
|
676
|
-
function _walkPaths(options) {
|
|
677
|
-
const items = options.paths;
|
|
678
|
-
if (!items || items.length === 0) {
|
|
679
|
-
return Promise.resolve({
|
|
680
|
-
totalSize: 0,
|
|
681
|
-
maxFileDateModified: 0,
|
|
682
|
-
countFiles: 0,
|
|
683
|
-
countDirs: 0,
|
|
684
|
-
countLinks: 0
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
const level = options.level ?? 0;
|
|
688
|
-
const walkedIds = options.walkedIds ?? /* @__PURE__ */ new Set();
|
|
689
|
-
const abortSignal = options.abortSignal;
|
|
690
|
-
const pool = options.pool ?? poolFs;
|
|
691
|
-
const handleError = options.handleError;
|
|
692
|
-
const priority = options.priority ?? priorityCreate(0);
|
|
693
|
-
const walkLinks = options.walkLinks ?? false;
|
|
694
|
-
const logOptions = options.log;
|
|
695
|
-
const handlePath = options.handlePath;
|
|
696
|
-
const matchPath = options.matchPath;
|
|
697
|
-
async function _handleError(err) {
|
|
698
|
-
if (handleError) {
|
|
699
|
-
if (await handleError(err)) {
|
|
700
|
-
return void 0;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if (walkPathHandleErrorDefault(err)) {
|
|
704
|
-
return void 0;
|
|
705
|
-
}
|
|
706
|
-
throw err;
|
|
707
|
-
}
|
|
708
|
-
function canLog(itemSize) {
|
|
709
|
-
if (!logOptions) {
|
|
710
|
-
return false;
|
|
711
|
-
}
|
|
712
|
-
if (logOptions.minTotalContentSize != null && itemSize < logOptions.minTotalContentSize) {
|
|
713
|
-
return false;
|
|
714
|
-
}
|
|
715
|
-
if (logOptions.maxNestedLevel != null && level > logOptions.maxNestedLevel) {
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
return true;
|
|
719
|
-
}
|
|
720
|
-
return useAbortController(async (_abortSignal) => {
|
|
721
|
-
const abortSignalCombined = combineAbortSignals(abortSignal, _abortSignal);
|
|
722
|
-
const totalStat = {
|
|
723
|
-
totalSize: 0,
|
|
724
|
-
maxFileDateModified: 0,
|
|
725
|
-
countFiles: 0,
|
|
726
|
-
countDirs: 0,
|
|
727
|
-
countLinks: 0
|
|
728
|
-
};
|
|
729
|
-
function logItem(itemPath, itemStat) {
|
|
730
|
-
if (canLog(itemStat.totalSize)) {
|
|
731
|
-
const sizeStr = itemStat.totalSize.toLocaleString("en-US").replace(/,/g, " ").padStart(19);
|
|
732
|
-
const message = `${sizeStr}: ${itemPath}`;
|
|
733
|
-
if (logOptions?.handleLog) {
|
|
734
|
-
logOptions.handleLog(message);
|
|
735
|
-
} else {
|
|
736
|
-
console.log(message);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
async function _handlePath(itemPath, stat, itemStat, priority2) {
|
|
741
|
-
if (!handlePath) {
|
|
742
|
-
return true;
|
|
743
|
-
}
|
|
744
|
-
return await poolRunWait({
|
|
745
|
-
pool,
|
|
746
|
-
func: async () => {
|
|
747
|
-
try {
|
|
748
|
-
return await handlePath({
|
|
749
|
-
level,
|
|
750
|
-
path: itemPath,
|
|
751
|
-
stat,
|
|
752
|
-
itemStat,
|
|
753
|
-
totalStat,
|
|
754
|
-
abortSignal: abortSignalCombined
|
|
755
|
-
});
|
|
756
|
-
} catch (err) {
|
|
757
|
-
await _handleError(err);
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
|
-
},
|
|
761
|
-
count: 1,
|
|
762
|
-
priority: priority2,
|
|
763
|
-
abortSignal: abortSignalCombined
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
async function processItem(itemPath, index, matchResult) {
|
|
767
|
-
const stat = await poolRunWait({
|
|
768
|
-
pool,
|
|
769
|
-
func: () => fs__default.promises.lstat(itemPath).catch(_handleError),
|
|
770
|
-
count: 1,
|
|
771
|
-
priority: priorityCreate(index, priorityCreate(1, priority)),
|
|
772
|
-
abortSignal: abortSignalCombined
|
|
773
|
-
});
|
|
774
|
-
if (!stat) {
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
if (!matchResult && stat.isFile()) {
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
const itemId = getFileId(itemPath, stat);
|
|
781
|
-
if (walkedIds.has(itemId)) {
|
|
782
|
-
return null;
|
|
783
|
-
}
|
|
784
|
-
walkedIds.add(itemId);
|
|
785
|
-
let itemStat = {
|
|
786
|
-
totalSize: stat.size,
|
|
787
|
-
maxFileDateModified: stat.isDirectory() ? 0 : stat.mtimeMs,
|
|
788
|
-
countFiles: 0,
|
|
789
|
-
countDirs: 0,
|
|
790
|
-
countLinks: 0
|
|
791
|
-
};
|
|
792
|
-
const _priority = priorityCreate(
|
|
793
|
-
index,
|
|
794
|
-
priorityCreate(stat.isDirectory() ? 2 : 3, priority)
|
|
795
|
-
);
|
|
796
|
-
if (stat.isSymbolicLink()) {
|
|
797
|
-
if (walkLinks) {
|
|
798
|
-
const link = await poolRunWait({
|
|
799
|
-
pool,
|
|
800
|
-
func: () => fs__default.promises.readlink(itemPath).catch(_handleError).then((link2) => link2 ?? null),
|
|
801
|
-
count: 1,
|
|
802
|
-
priority: _priority,
|
|
803
|
-
abortSignal: abortSignalCombined
|
|
804
|
-
});
|
|
805
|
-
if (link) {
|
|
806
|
-
const linkedItemStat = await processItem(link, index, matchResult);
|
|
807
|
-
if (linkedItemStat) {
|
|
808
|
-
itemStat = linkedItemStat;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
itemStat.countLinks = 1;
|
|
813
|
-
if (matchResult || itemStat.countFiles + itemStat.countDirs + itemStat.countLinks > 1) {
|
|
814
|
-
const shouldInclude = await _handlePath(
|
|
815
|
-
itemPath,
|
|
816
|
-
stat,
|
|
817
|
-
itemStat,
|
|
818
|
-
_priority
|
|
819
|
-
);
|
|
820
|
-
if (shouldInclude) {
|
|
821
|
-
addStats(totalStat, itemStat);
|
|
822
|
-
logItem(itemPath, itemStat);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return itemStat;
|
|
826
|
-
} else if (stat.isDirectory()) {
|
|
827
|
-
const dirItems = await poolRunWait({
|
|
828
|
-
pool,
|
|
829
|
-
func: () => fs__default.promises.readdir(itemPath).catch(_handleError),
|
|
830
|
-
count: 1,
|
|
831
|
-
priority,
|
|
832
|
-
abortSignal: abortSignalCombined
|
|
833
|
-
});
|
|
834
|
-
if (dirItems) {
|
|
835
|
-
for (let i = 0, len = dirItems.length; i < len; i++) {
|
|
836
|
-
dirItems[i] = path.join(itemPath, dirItems[i]);
|
|
837
|
-
}
|
|
838
|
-
itemStat = await _walkPaths({
|
|
839
|
-
...options,
|
|
840
|
-
paths: dirItems,
|
|
841
|
-
abortSignal: abortSignalCombined,
|
|
842
|
-
priority: _priority,
|
|
843
|
-
level: level + 1,
|
|
844
|
-
walkedIds
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
itemStat.countDirs += 1;
|
|
848
|
-
} else if (stat.isFile()) {
|
|
849
|
-
itemStat.countFiles += 1;
|
|
850
|
-
}
|
|
851
|
-
if (matchResult || itemStat.countFiles + itemStat.countDirs + itemStat.countLinks > 1) {
|
|
852
|
-
const shouldInclude = await _handlePath(
|
|
853
|
-
itemPath,
|
|
854
|
-
stat,
|
|
855
|
-
itemStat,
|
|
856
|
-
_priority
|
|
857
|
-
);
|
|
858
|
-
if (shouldInclude) {
|
|
859
|
-
addStats(totalStat, itemStat);
|
|
860
|
-
logItem(itemPath, itemStat);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return itemStat;
|
|
864
|
-
}
|
|
865
|
-
const promises = [];
|
|
866
|
-
for (let i = 0, len = items.length; i < len; i++) {
|
|
867
|
-
const itemPath = pathResolve(items[i]);
|
|
868
|
-
const matchResult = matchPath ? matchPath(itemPath) : true;
|
|
869
|
-
if (matchResult === false) {
|
|
870
|
-
continue;
|
|
871
|
-
}
|
|
872
|
-
promises.push(processItem(itemPath, i, matchResult));
|
|
873
|
-
}
|
|
874
|
-
await Promise.all(promises);
|
|
875
|
-
return totalStat;
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
function walkPaths(options) {
|
|
879
|
-
return _walkPaths(options);
|
|
880
|
-
}
|
|
881
|
-
function globToRelative(glob, relativePath) {
|
|
882
|
-
if (!relativePath || relativePath === ".") {
|
|
883
|
-
return glob;
|
|
884
|
-
}
|
|
885
|
-
const exclude = glob.startsWith("^");
|
|
886
|
-
if (exclude) {
|
|
887
|
-
glob = glob.substring(1);
|
|
888
|
-
}
|
|
889
|
-
const negative = glob.startsWith("!");
|
|
890
|
-
if (negative) {
|
|
891
|
-
glob = glob.substring(1);
|
|
892
|
-
}
|
|
893
|
-
if (glob.startsWith("/")) {
|
|
894
|
-
if (relativePath.endsWith("/")) {
|
|
895
|
-
relativePath = relativePath.substring(0, relativePath.length - 1);
|
|
896
|
-
}
|
|
897
|
-
glob = relativePath + glob;
|
|
898
|
-
} else {
|
|
899
|
-
if (!relativePath.endsWith("/")) {
|
|
900
|
-
relativePath += "/";
|
|
901
|
-
}
|
|
902
|
-
if (glob.startsWith("./")) {
|
|
903
|
-
glob = relativePath + glob.substring(2);
|
|
904
|
-
} else if (glob.startsWith("../")) {
|
|
905
|
-
glob = relativePath + glob;
|
|
906
|
-
} else {
|
|
907
|
-
if (relativePath.startsWith("..")) {
|
|
908
|
-
relativePath = "";
|
|
909
|
-
}
|
|
910
|
-
if (glob.startsWith("**")) {
|
|
911
|
-
glob = relativePath + glob;
|
|
912
|
-
} else {
|
|
913
|
-
glob = relativePath + "**/" + glob;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
glob = path.normalize(glob).replace(/\\/g, "/");
|
|
918
|
-
if (negative) {
|
|
919
|
-
glob = "!" + glob;
|
|
920
|
-
}
|
|
921
|
-
if (exclude) {
|
|
922
|
-
glob = "^" + glob;
|
|
923
|
-
}
|
|
924
|
-
return glob;
|
|
925
|
-
}
|
|
926
|
-
function globGitIgnoreToPicomatch(glob) {
|
|
927
|
-
const negative = glob.startsWith("!");
|
|
928
|
-
if (negative) {
|
|
929
|
-
glob = glob.substring(1);
|
|
930
|
-
}
|
|
931
|
-
if (glob.startsWith("/")) {
|
|
932
|
-
glob = glob.substring(1);
|
|
933
|
-
} else if (!glob.startsWith("**") && !glob.startsWith("../")) {
|
|
934
|
-
glob = `**/${glob}`;
|
|
935
|
-
}
|
|
936
|
-
if (negative) {
|
|
937
|
-
glob = "!" + glob;
|
|
938
|
-
}
|
|
939
|
-
return glob;
|
|
940
|
-
}
|
|
941
|
-
function globExclude(glob) {
|
|
942
|
-
return "^" + glob;
|
|
943
|
-
}
|
|
944
|
-
async function loadGlobsFromFile(filePath) {
|
|
945
|
-
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
946
|
-
const lines = content.split("\n");
|
|
947
|
-
const globs = [];
|
|
948
|
-
lines.forEach((line) => {
|
|
949
|
-
line = line.trim();
|
|
950
|
-
if (!line || line.startsWith("#")) {
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
globs.push(line);
|
|
954
|
-
});
|
|
955
|
-
return globs;
|
|
956
|
-
}
|
|
957
|
-
async function loadGlobs(options) {
|
|
958
|
-
const rootDir = options.rootDir ?? ".";
|
|
959
|
-
const result = [];
|
|
960
|
-
if (!options.globs?.length) {
|
|
961
|
-
return result;
|
|
962
|
-
}
|
|
963
|
-
const filesContainsGlobs = [];
|
|
964
|
-
options.globs.forEach((glob) => {
|
|
965
|
-
if (!glob.value) {
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
if (glob.valueType === "file-contains-patterns") {
|
|
969
|
-
filesContainsGlobs.push(glob);
|
|
970
|
-
} else if (glob.valueType === "pattern") {
|
|
971
|
-
result.push(glob.exclude ? globExclude(glob.value) : glob.value);
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
if (filesContainsGlobs.length) {
|
|
975
|
-
await Promise.all(
|
|
976
|
-
filesContainsGlobs.map(async (glob) => {
|
|
977
|
-
await poolRunWait({
|
|
978
|
-
pool: poolFs,
|
|
979
|
-
count: 1,
|
|
980
|
-
func: async () => {
|
|
981
|
-
const filePath = path.resolve(rootDir, glob.value);
|
|
982
|
-
const globs = await loadGlobsFromFile(filePath);
|
|
983
|
-
const relativePath = path.relative(rootDir, path.dirname(filePath));
|
|
984
|
-
globs.forEach((globValue) => {
|
|
985
|
-
globValue = globGitIgnoreToPicomatch(globValue);
|
|
986
|
-
globValue = globToRelative(globValue, relativePath);
|
|
987
|
-
result.push(glob.exclude ? globExclude(globValue) : globValue);
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
})
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
return result;
|
|
995
|
-
}
|
|
996
|
-
function createMatchPath({
|
|
997
|
-
globs,
|
|
998
|
-
rootDir,
|
|
999
|
-
noCase
|
|
1000
|
-
}) {
|
|
1001
|
-
const conditions = [];
|
|
1002
|
-
globs.forEach((glob) => {
|
|
1003
|
-
glob = glob.replace(/\\/g, "/").trim();
|
|
1004
|
-
const exclude = glob.startsWith("^");
|
|
1005
|
-
if (exclude) {
|
|
1006
|
-
glob = glob.substring(1).trim();
|
|
1007
|
-
}
|
|
1008
|
-
const negative = glob.startsWith("!");
|
|
1009
|
-
if (negative) {
|
|
1010
|
-
glob = glob.substring(1).trim();
|
|
1011
|
-
}
|
|
1012
|
-
if (glob.startsWith("!") || glob.startsWith("^")) {
|
|
1013
|
-
throw new Error(
|
|
1014
|
-
`Invalid glob pattern: "${glob}". The syntax '${glob.substring(0, 2)}' is not supported. Valid glob patterns use: * (match any characters), ** (match any directories), ? (match single character), [abc] (character class), ! (negate pattern), ^ (exclude if included). Examples of valid patterns: "*.js", "src/**/*.ts", "!node_modules", "^dist". Avoid starting with '!' after '^' or multiple special prefixes.`
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
if (glob.startsWith("/")) {
|
|
1018
|
-
glob = "." + glob;
|
|
1019
|
-
}
|
|
1020
|
-
const globAbsolute = rootDir ? path.resolve(rootDir, glob).replace(/\\/g, "/") : glob;
|
|
1021
|
-
if (!globAbsolute) {
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
let matcher;
|
|
1025
|
-
try {
|
|
1026
|
-
matcher = picomatch(globAbsolute, {
|
|
1027
|
-
nocase: noCase ?? false,
|
|
1028
|
-
dot: true,
|
|
1029
|
-
strictBrackets: true
|
|
1030
|
-
// Validate bracket balance for patterns like "["
|
|
1031
|
-
});
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
throw new Error(
|
|
1034
|
-
`Invalid glob pattern: "${glob}". ${error instanceof Error ? error.message : "Unknown error"}. Valid glob patterns use: * (match any characters), ** (match any directories), ? (match single character), [abc] (character class with balanced brackets), ! (negate pattern), ^ (exclude if included). Examples: "*.js", "src/**/*.ts", "!node_modules", "[abc]def.txt". Ensure all brackets [ ] are properly closed and balanced.`
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
conditions.push({
|
|
1038
|
-
exclude,
|
|
1039
|
-
negative,
|
|
1040
|
-
debugInfo: globAbsolute,
|
|
1041
|
-
match: matcher
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
return function matchPath(_path) {
|
|
1045
|
-
_path = _path.replace(/\\/g, "/");
|
|
1046
|
-
let include = null;
|
|
1047
|
-
let exclude = false;
|
|
1048
|
-
for (let i = 0, len = conditions.length; i < len; i++) {
|
|
1049
|
-
const condition = conditions[i];
|
|
1050
|
-
const conditionResult = condition.match(_path);
|
|
1051
|
-
if (conditionResult) {
|
|
1052
|
-
if (condition.exclude) {
|
|
1053
|
-
exclude = !condition.negative;
|
|
1054
|
-
} else {
|
|
1055
|
-
include = !condition.negative;
|
|
1056
|
-
exclude = false;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
return exclude ? false : include;
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
async function getPathList(options) {
|
|
1064
|
-
const rootDir = options.rootDir ?? ".";
|
|
1065
|
-
const items = [];
|
|
1066
|
-
const totals = {};
|
|
1067
|
-
if (options.result.countFiles) {
|
|
1068
|
-
totals.countFiles = 0;
|
|
1069
|
-
}
|
|
1070
|
-
if (options.result.size) {
|
|
1071
|
-
totals.size = 0;
|
|
1072
|
-
}
|
|
1073
|
-
const globs = await loadGlobs({
|
|
1074
|
-
rootDir,
|
|
1075
|
-
globs: options.globs
|
|
1076
|
-
});
|
|
1077
|
-
await walkPaths({
|
|
1078
|
-
paths: [rootDir],
|
|
1079
|
-
walkLinks: true,
|
|
1080
|
-
matchPath: createMatchPath({
|
|
1081
|
-
globs,
|
|
1082
|
-
rootDir,
|
|
1083
|
-
noCase: true
|
|
1084
|
-
}),
|
|
1085
|
-
handlePath: async ({ path: itemPath, stat, itemStat }) => {
|
|
1086
|
-
const relativePath = path.relative(rootDir, itemPath);
|
|
1087
|
-
const isDir = stat.isDirectory();
|
|
1088
|
-
const isFile = stat.isFile();
|
|
1089
|
-
if (!isDir && !isFile) {
|
|
1090
|
-
return true;
|
|
1091
|
-
}
|
|
1092
|
-
const _path = (relativePath || ".").replace(/\\/g, "/");
|
|
1093
|
-
const type = isDir ? "dir" : "file";
|
|
1094
|
-
const dateModified = isDir ? itemStat.maxFileDateModified || null : stat.mtimeMs;
|
|
1095
|
-
const size = isDir ? itemStat.totalSize : stat.size;
|
|
1096
|
-
const countFiles = isDir ? itemStat.countFiles : null;
|
|
1097
|
-
const item = {
|
|
1098
|
-
path: _path,
|
|
1099
|
-
type
|
|
1100
|
-
};
|
|
1101
|
-
if (options.result.dateModified) {
|
|
1102
|
-
item.dateModified = dateModified;
|
|
1103
|
-
}
|
|
1104
|
-
if (options.result.size) {
|
|
1105
|
-
item.size = size;
|
|
1106
|
-
}
|
|
1107
|
-
if (options.result.countFiles) {
|
|
1108
|
-
item.countFiles = countFiles;
|
|
1109
|
-
}
|
|
1110
|
-
if (options.dateModified && dateModified != null) {
|
|
1111
|
-
const [minDate, maxDate] = options.dateModified;
|
|
1112
|
-
if (minDate != null && dateModified < minDate || maxDate != null && dateModified > maxDate) {
|
|
1113
|
-
return false;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
if (options.totalSize && size != null) {
|
|
1117
|
-
const [minSize, maxSize] = options.totalSize;
|
|
1118
|
-
if (minSize != null && size < minSize || maxSize != null && size > maxSize) {
|
|
1119
|
-
return false;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
if (type === "file") {
|
|
1123
|
-
totals.countFiles = (totals.countFiles ?? 0) + 1;
|
|
1124
|
-
}
|
|
1125
|
-
if (type === "file" && size != null) {
|
|
1126
|
-
totals.size = (totals.size ?? 0) + size;
|
|
1127
|
-
}
|
|
1128
|
-
if (dateModified != null && (totals.dateModified == null || dateModified > totals.dateModified)) {
|
|
1129
|
-
totals.dateModified = dateModified;
|
|
1130
|
-
}
|
|
1131
|
-
if (isDir && !options.result.dirs) {
|
|
1132
|
-
return true;
|
|
1133
|
-
}
|
|
1134
|
-
if (isFile && !options.result.files) {
|
|
1135
|
-
return true;
|
|
1136
|
-
}
|
|
1137
|
-
items.push(item);
|
|
1138
|
-
return true;
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
return { items, totals };
|
|
1142
|
-
}
|
|
1143
|
-
const UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
1144
|
-
const UNIT_SIZE = 1024;
|
|
1145
|
-
function fileSizeFormat(bytes) {
|
|
1146
|
-
if (bytes == null) return "-";
|
|
1147
|
-
let value = bytes ?? 0;
|
|
1148
|
-
let unitIndex = 0;
|
|
1149
|
-
while (value >= UNIT_SIZE && unitIndex < UNITS.length - 1) {
|
|
1150
|
-
value /= UNIT_SIZE;
|
|
1151
|
-
unitIndex++;
|
|
1152
|
-
}
|
|
1153
|
-
const formatted = unitIndex === 0 ? value.toString() : value.toFixed(2);
|
|
1154
|
-
return `${formatted}${UNITS[unitIndex]}`;
|
|
1155
|
-
}
|
|
1156
|
-
function timeAgoFormat(timestamp) {
|
|
1157
|
-
const now = Date.now();
|
|
1158
|
-
const diffMs = now - timestamp;
|
|
1159
|
-
if (diffMs < 0) return "0s";
|
|
1160
|
-
const diffSeconds = Math.floor(diffMs / 1e3);
|
|
1161
|
-
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
1162
|
-
const diffHours = Math.floor(diffMinutes / 60);
|
|
1163
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
1164
|
-
const diffWeeks = Math.floor(diffDays / 7);
|
|
1165
|
-
const diffMonths = Math.floor(diffDays / 30);
|
|
1166
|
-
const diffYears = Math.floor(diffDays / 365);
|
|
1167
|
-
if (diffYears > 0) return `${diffYears}Y`;
|
|
1168
|
-
if (diffMonths > 0) return `${diffMonths}M`;
|
|
1169
|
-
if (diffWeeks > 0) return `${diffWeeks}w`;
|
|
1170
|
-
if (diffDays > 0) return `${diffDays}d`;
|
|
1171
|
-
if (diffHours > 0) return `${diffHours}h`;
|
|
1172
|
-
if (diffMinutes > 0) return `${diffMinutes}m`;
|
|
1173
|
-
return `${diffSeconds}s`;
|
|
1174
|
-
}
|
|
1175
|
-
function pathListSort(pathList, sort) {
|
|
1176
|
-
if (!sort?.length) return pathList;
|
|
1177
|
-
return [...pathList].sort((a, b) => {
|
|
1178
|
-
for (let i = 0, len = sort.length; i < len; i++) {
|
|
1179
|
-
const sortConfig = sort[i];
|
|
1180
|
-
let aValue;
|
|
1181
|
-
let bValue;
|
|
1182
|
-
switch (sortConfig.field) {
|
|
1183
|
-
case "type":
|
|
1184
|
-
aValue = a.type;
|
|
1185
|
-
bValue = b.type;
|
|
1186
|
-
break;
|
|
1187
|
-
case "path":
|
|
1188
|
-
aValue = a.path;
|
|
1189
|
-
bValue = b.path;
|
|
1190
|
-
break;
|
|
1191
|
-
case "dateModified":
|
|
1192
|
-
aValue = a.dateModified;
|
|
1193
|
-
bValue = b.dateModified;
|
|
1194
|
-
break;
|
|
1195
|
-
case "size":
|
|
1196
|
-
aValue = a.size;
|
|
1197
|
-
bValue = b.size;
|
|
1198
|
-
break;
|
|
1199
|
-
case "countFiles":
|
|
1200
|
-
aValue = a.countFiles;
|
|
1201
|
-
bValue = b.countFiles;
|
|
1202
|
-
break;
|
|
1203
|
-
}
|
|
1204
|
-
if (aValue == null) {
|
|
1205
|
-
if (bValue == null) {
|
|
1206
|
-
continue;
|
|
1207
|
-
}
|
|
1208
|
-
return 1;
|
|
1209
|
-
}
|
|
1210
|
-
if (bValue == null) {
|
|
1211
|
-
return -1;
|
|
1212
|
-
}
|
|
1213
|
-
const comparison = aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
|
|
1214
|
-
if (comparison !== 0) {
|
|
1215
|
-
return sortConfig.desc ? -comparison : comparison;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
return 0;
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
function pathListToString(pathListResult, options) {
|
|
1222
|
-
const sortedList = pathListSort(pathListResult.items, options.sort ?? []);
|
|
1223
|
-
const fields = options.fields && options.fields.length > 0 ? options.fields : [];
|
|
1224
|
-
let result = "";
|
|
1225
|
-
if (sortedList.length > 0 && fields.length > 0) {
|
|
1226
|
-
for (let i = 0, len = fields.length; i < len; i++) {
|
|
1227
|
-
const field = fields[i];
|
|
1228
|
-
if (i > 0) result += " | ";
|
|
1229
|
-
switch (field) {
|
|
1230
|
-
case "dateModified":
|
|
1231
|
-
result += "Time ago (s/m/h/d/w/M/Y)";
|
|
1232
|
-
break;
|
|
1233
|
-
case "size":
|
|
1234
|
-
result += "Size (B/KB/MB/TB)";
|
|
1235
|
-
break;
|
|
1236
|
-
case "type":
|
|
1237
|
-
result += "Type";
|
|
1238
|
-
break;
|
|
1239
|
-
case "path":
|
|
1240
|
-
result += "Path";
|
|
1241
|
-
break;
|
|
1242
|
-
case "countFiles":
|
|
1243
|
-
result += "Files Count";
|
|
1244
|
-
break;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
for (let i = 0, len = sortedList.length; i < len; i++) {
|
|
1248
|
-
const item = sortedList[i];
|
|
1249
|
-
result += "\n";
|
|
1250
|
-
for (let j = 0, fieldsLen = fields.length; j < fieldsLen; j++) {
|
|
1251
|
-
const field = fields[j];
|
|
1252
|
-
if (j > 0) result += " | ";
|
|
1253
|
-
switch (field) {
|
|
1254
|
-
case "dateModified":
|
|
1255
|
-
result += item.dateModified ? timeAgoFormat(item.dateModified) : "-";
|
|
1256
|
-
break;
|
|
1257
|
-
case "size":
|
|
1258
|
-
result += fileSizeFormat(item.size);
|
|
1259
|
-
break;
|
|
1260
|
-
case "type":
|
|
1261
|
-
result += item.type;
|
|
1262
|
-
break;
|
|
1263
|
-
case "path":
|
|
1264
|
-
result += item.type === "dir" ? `${item.path}/` : item.path;
|
|
1265
|
-
break;
|
|
1266
|
-
case "countFiles":
|
|
1267
|
-
if (item.type === "dir") {
|
|
1268
|
-
result += item.countFiles != null ? item.countFiles.toString() : "-";
|
|
1269
|
-
} else {
|
|
1270
|
-
result += "-";
|
|
1271
|
-
}
|
|
1272
|
-
break;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
{
|
|
1278
|
-
if (result.length > 0) {
|
|
1279
|
-
result += "\n---\n";
|
|
1280
|
-
}
|
|
1281
|
-
const totalSizeFormatted = fileSizeFormat(pathListResult.totals.size ?? 0);
|
|
1282
|
-
const lastModifiedPart = pathListResult.totals.dateModified ? `, last modified ${timeAgoFormat(pathListResult.totals.dateModified)} ago` : "";
|
|
1283
|
-
result += `Totals: ${pathListResult.totals.countFiles ?? 0} files in dirs, ${totalSizeFormatted}${lastModifiedPart}`;
|
|
1284
|
-
}
|
|
1285
|
-
return result;
|
|
1286
|
-
}
|
|
1287
|
-
const SERVER_NAME = "project-tools";
|
|
1288
|
-
const SERVER_VERSION = "1.0.0";
|
|
1289
|
-
const AUTH_TOKEN = "d00f70240703039df14c76176a055bce6b5484d2b552ba2c89820f03b8e5e60d";
|
|
1290
|
-
const MAX_FS_LIST_PATHS = 25e3;
|
|
1291
|
-
function parseTimeAgo(timeAgoStr) {
|
|
1292
|
-
const match = timeAgoStr.match(
|
|
1293
|
-
/^\s*(\d+(?:\.\d+)?)\s*([smhdwMY]|sec(onds?)?|min(utes?)?|hours?|days?|weeks?|months?|years?)\s*$/i
|
|
1294
|
-
);
|
|
1295
|
-
if (!match) {
|
|
1296
|
-
throw new Error(
|
|
1297
|
-
`Invalid time ago format: "${timeAgoStr}". Must be a number followed by a time unit. Valid units: s/sec/seconds (seconds), m/min/minutes (minutes), h/hours (hours), d/days (days), w/weeks (weeks), M/months (months), Y/years (years). Examples: "30s", "0.5h", "1.5d", "7d", "3w", "6M", "1Y". Format is case-insensitive except M (months) must be uppercase to distinguish from m (minutes).`
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
const value = parseFloat(match[1]);
|
|
1301
|
-
let unit = match[2];
|
|
1302
|
-
if (unit !== "M") {
|
|
1303
|
-
unit = unit.toLowerCase();
|
|
1304
|
-
if (unit.startsWith("month")) {
|
|
1305
|
-
unit = "M";
|
|
1306
|
-
} else if (unit.length > 1) {
|
|
1307
|
-
unit = unit[0];
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
switch (unit) {
|
|
1311
|
-
case "s":
|
|
1312
|
-
return value * 1e3;
|
|
1313
|
-
case "m":
|
|
1314
|
-
return value * 60 * 1e3;
|
|
1315
|
-
case "h":
|
|
1316
|
-
return value * 60 * 60 * 1e3;
|
|
1317
|
-
case "d":
|
|
1318
|
-
return value * 24 * 60 * 60 * 1e3;
|
|
1319
|
-
case "w":
|
|
1320
|
-
return value * 7 * 24 * 60 * 60 * 1e3;
|
|
1321
|
-
case "M":
|
|
1322
|
-
return value * 30 * 24 * 60 * 60 * 1e3;
|
|
1323
|
-
case "y":
|
|
1324
|
-
return value * 365 * 24 * 60 * 60 * 1e3;
|
|
1325
|
-
default:
|
|
1326
|
-
throw new Error(
|
|
1327
|
-
`Unknown time unit: "${unit}". Valid time units are: s (seconds), m (minutes), h (hours), d (days), w (weeks), M (months), Y (years). Use uppercase M for months to distinguish from lowercase m for minutes. Examples: "30s", "5m", "2h", "7d", "3w", "6M", "1Y".`
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
function parseSize(sizeStr) {
|
|
1332
|
-
const match = sizeStr.match(/^\s*(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)\s*$/i);
|
|
1333
|
-
if (!match) {
|
|
1334
|
-
throw new Error(
|
|
1335
|
-
`Invalid size format: "${sizeStr}". Must be a number (integer or decimal) followed by a size unit. Valid units: B (bytes), KB (kilobytes), MB (megabytes), GB (gigabytes), TB (terabytes). Uses binary units (1024-based). Examples: "1B", "2.5KB", "100MB", "1.2GB", "0.5TB". Units are case-insensitive.`
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
1338
|
-
const value = parseFloat(match[1]);
|
|
1339
|
-
const unit = match[2].toUpperCase();
|
|
1340
|
-
switch (unit) {
|
|
1341
|
-
case "B":
|
|
1342
|
-
return value;
|
|
1343
|
-
case "KB":
|
|
1344
|
-
return value * 1024;
|
|
1345
|
-
case "MB":
|
|
1346
|
-
return value * 1024 * 1024;
|
|
1347
|
-
case "GB":
|
|
1348
|
-
return value * 1024 * 1024 * 1024;
|
|
1349
|
-
case "TB":
|
|
1350
|
-
return value * 1024 * 1024 * 1024 * 1024;
|
|
1351
|
-
default:
|
|
1352
|
-
throw new Error(
|
|
1353
|
-
`Unknown size unit: "${unit}". Valid size units are: B (bytes), KB (kilobytes), MB (megabytes), GB (gigabytes), TB (terabytes). Uses binary calculation (1KB = 1024 bytes). Examples: "500B", "2KB", "100MB", "1.5GB", "2TB". Units are case-insensitive.`
|
|
1354
|
-
);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
const ListSchema = z.object({
|
|
1358
|
-
rootDir: z.string().optional().describe(
|
|
1359
|
-
'Root directory to list files from, resolved relative to the current working directory. Leave empty to use current directory. Examples: "src" (list src/ subdirectory), "../parent" (list parent directory), "docs/api" (list nested subdirectory). Path must exist and be accessible.'
|
|
1360
|
-
),
|
|
1361
|
-
globs: z.array(z.string()).optional().describe(
|
|
1362
|
-
'Glob patterns to filter which files/directories to include. Add leading ** to match files and dirs in subdirectories. Examples: ["**/*.js"] (JavaScript files), ["src/**/*.ts"] (TypeScript files in src), ["**/dir/"] (all directories named "dir"), ["!node_modules"] (exclude {rootDir}/node_modules). If omitted, includes all files matching other criteria. Supports standard glob syntax: * (any chars), ** (any dirs), ? (single char), [abc] (char class).'
|
|
1363
|
-
),
|
|
1364
|
-
showFiles: z.boolean().optional().describe(
|
|
1365
|
-
"Whether to show regular files in the report table. Set to true to show files, false to hide them from the table. When both showFiles and showDirs are false, nothing will be shown in the table. It Does not affect totals. Default is false (do not show files in the table)."
|
|
1366
|
-
),
|
|
1367
|
-
showDirs: z.boolean().optional().describe(
|
|
1368
|
-
"Whether to show directories in the report table. Set to true to show directories, false to hide them from the table. When both showFiles and showDirs are false, nothing will be shown in the table. It Does not affect totals. Default is true (show directories in the table)."
|
|
1369
|
-
),
|
|
1370
|
-
sortBy: z.array(
|
|
1371
|
-
z.object({
|
|
1372
|
-
field: z.enum(["type", "path", "lastModified", "size", "totalCountFiles"]).describe(
|
|
1373
|
-
'Field to sort results by. "type" sorts files before directories. "path" sorts alphabetically by file/directory name. "lastModified" sorts by modification time (newest first when desc=true). "size" sorts by file/directory size (largest first when desc=true). "totalCountFiles" sorts by total files count (highest first when desc=true, directories only).'
|
|
1374
|
-
),
|
|
1375
|
-
desc: z.boolean().optional().describe("Sort in descending order (largest/newest first).")
|
|
1376
|
-
})
|
|
1377
|
-
).optional().describe(
|
|
1378
|
-
'Multi-level sorting configuration. Sorts are applied in array order - first sort is primary, second is secondary, etc. Example: [{field: "type", desc: false}, {field: "size", desc: true}] sorts by type ascending, then by size descending within each type.'
|
|
1379
|
-
),
|
|
1380
|
-
fields: z.array(z.enum(["type", "path", "lastModified", "size", "totalCountFiles"])).optional().describe(
|
|
1381
|
-
'Which data fields to include in the formatted table output. "type" shows file/directory indicator. "path" shows relative file/directory path. "lastModified" shows last modification time as time-ago format (5m, 2h, 3d, etc). "size" shows file/directory size in human-readable format (KB, MB, GB). "totalCountFiles" shows total files count for directories (displays "-" for files). Adding lastModified, size, or totalCountFiles fields increases processing time. Do not set fields if you want to show only totals summary.'
|
|
1382
|
-
),
|
|
1383
|
-
minTimeAgo: z.string().optional().describe(
|
|
1384
|
-
'Filter files/directories modified at least this long ago. Only items older than this duration will be included. Format: number + unit (s/m/h/d/w/M/Y). Examples: "1h" (modified more than 1 hour ago), "7d" (modified more than 7 days ago), "6M" (modified more than 6 months ago).'
|
|
1385
|
-
),
|
|
1386
|
-
maxTimeAgo: z.string().optional().describe(
|
|
1387
|
-
'Filter files/directories modified at most this long ago. Only items newer than this duration will be included. Format: number + unit (s/m/h/d/w/M/Y). Examples: "1h" (modified within last hour), "7d" (modified within last 7 days), "1M" (modified within last month). Combine with minTimeAgo for date ranges.'
|
|
1388
|
-
),
|
|
1389
|
-
minTotalSize: z.string().optional().describe(
|
|
1390
|
-
'Filter files/directories with total size at least this large. Only items with size >= this value will be included. For directories, uses total size of all contents. Format: number + unit (B/KB/MB/GB/TB). Examples: "1KB" (at least 1 kilobyte), "100MB" (at least 100 megabytes), "1.5GB" (at least 1.5 gigabytes).'
|
|
1391
|
-
),
|
|
1392
|
-
maxTotalSize: z.string().optional().describe(
|
|
1393
|
-
'Filter files/directories with total size at most this large. Only items with size <= this value will be included. For directories, uses total size of all contents. Format: number + unit (B/KB/MB/GB/TB). Examples: "1MB" (up to 1 megabyte), "500KB" (up to 500 kilobytes), "10GB" (up to 10 gigabytes). Combine with minTotalSize for size ranges.'
|
|
1394
|
-
)
|
|
1395
|
-
});
|
|
1396
|
-
async function commandList(args, options) {
|
|
1397
|
-
const parsedArgs = ListSchema.parse(args);
|
|
1398
|
-
const {
|
|
1399
|
-
globs,
|
|
1400
|
-
showFiles,
|
|
1401
|
-
showDirs,
|
|
1402
|
-
sortBy,
|
|
1403
|
-
minTimeAgo,
|
|
1404
|
-
maxTimeAgo,
|
|
1405
|
-
minTotalSize,
|
|
1406
|
-
maxTotalSize
|
|
1407
|
-
} = parsedArgs;
|
|
1408
|
-
if (parsedArgs.fields && parsedArgs.fields.length > 0 && !parsedArgs.fields.includes("path")) {
|
|
1409
|
-
return {
|
|
1410
|
-
error: 'Fields array must include "path" field when fields are specified. The "path" field is required to identify files and directories in the output.'
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
const fields = parsedArgs.fields ? parsedArgs.fields.map((o) => {
|
|
1414
|
-
if (o === "totalCountFiles") {
|
|
1415
|
-
return "countFiles";
|
|
1416
|
-
}
|
|
1417
|
-
if (o === "lastModified") {
|
|
1418
|
-
return "dateModified";
|
|
1419
|
-
}
|
|
1420
|
-
return o;
|
|
1421
|
-
}) : [];
|
|
1422
|
-
let sort = parsedArgs.sortBy?.map((o) => {
|
|
1423
|
-
let field = o.field;
|
|
1424
|
-
if (field === "totalCountFiles") {
|
|
1425
|
-
field = "countFiles";
|
|
1426
|
-
}
|
|
1427
|
-
if (field === "lastModified") {
|
|
1428
|
-
field = "dateModified";
|
|
1429
|
-
}
|
|
1430
|
-
return {
|
|
1431
|
-
field,
|
|
1432
|
-
desc: o.desc ?? false
|
|
1433
|
-
// Default to ascending if not specified
|
|
1434
|
-
};
|
|
1435
|
-
}) || null;
|
|
1436
|
-
if (!sort || sort.length === 0) {
|
|
1437
|
-
sort = [{ field: "path", desc: false }];
|
|
1438
|
-
}
|
|
1439
|
-
const sortFields = sort?.map((o) => o.field) || [];
|
|
1440
|
-
const rootDir = path.resolve(
|
|
1441
|
-
options.workingDir || "",
|
|
1442
|
-
parsedArgs.rootDir || ""
|
|
1443
|
-
);
|
|
1444
|
-
try {
|
|
1445
|
-
try {
|
|
1446
|
-
await fs.promises.access(rootDir, fs.constants.F_OK);
|
|
1447
|
-
} catch (err) {
|
|
1448
|
-
if (err.code === "ENOENT") {
|
|
1449
|
-
return {
|
|
1450
|
-
error: `Directory does not exist: "${rootDir}". Verify the path is correct and accessible. If using rootDir parameter, ensure it exists relative to the current working directory. Use fs-list without rootDir to list the current directory, or check parent directories first.`
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
throw err;
|
|
1454
|
-
}
|
|
1455
|
-
const userGlobs = globs && globs.length > 0 ? globs.map((g) => ({
|
|
1456
|
-
value: g,
|
|
1457
|
-
valueType: "pattern",
|
|
1458
|
-
exclude: false
|
|
1459
|
-
})) : [{ value: "**", valueType: "pattern", exclude: false }];
|
|
1460
|
-
const excludeGlobs = options.globsExclude || [];
|
|
1461
|
-
const globsConverted = [...userGlobs, ...excludeGlobs];
|
|
1462
|
-
const result = {
|
|
1463
|
-
files: showFiles ?? false,
|
|
1464
|
-
dirs: showDirs ?? false,
|
|
1465
|
-
dateModified: fields.includes("dateModified") || sortFields.includes("dateModified"),
|
|
1466
|
-
size: fields.includes("size") || sortFields.includes("size"),
|
|
1467
|
-
countFiles: fields.includes("countFiles") || sortFields.includes("countFiles")
|
|
1468
|
-
};
|
|
1469
|
-
let dateModifiedRange = null;
|
|
1470
|
-
let totalSizeRange = null;
|
|
1471
|
-
if (minTimeAgo || maxTimeAgo) {
|
|
1472
|
-
try {
|
|
1473
|
-
const now = Date.now();
|
|
1474
|
-
const minDate = maxTimeAgo ? now - parseTimeAgo(maxTimeAgo) : null;
|
|
1475
|
-
const maxDate = minTimeAgo ? now - parseTimeAgo(minTimeAgo) : null;
|
|
1476
|
-
dateModifiedRange = [minDate, maxDate];
|
|
1477
|
-
} catch (err) {
|
|
1478
|
-
return {
|
|
1479
|
-
error: err instanceof Error ? err.message : "Unknown error parsing time ago filter"
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
if (minTotalSize || maxTotalSize) {
|
|
1484
|
-
try {
|
|
1485
|
-
const minTotalSizeBytes = minTotalSize ? parseSize(minTotalSize) : null;
|
|
1486
|
-
const maxTotalSizeBytes = maxTotalSize ? parseSize(maxTotalSize) : null;
|
|
1487
|
-
totalSizeRange = [minTotalSizeBytes, maxTotalSizeBytes];
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
return {
|
|
1490
|
-
error: err instanceof Error ? err.message : "Unknown error parsing size filter"
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
const pathListResult = await getPathList({
|
|
1495
|
-
rootDir: rootDir || null,
|
|
1496
|
-
globs: globsConverted,
|
|
1497
|
-
result,
|
|
1498
|
-
dateModified: dateModifiedRange,
|
|
1499
|
-
totalSize: totalSizeRange
|
|
1500
|
-
});
|
|
1501
|
-
if (pathListResult.items.length > MAX_FS_LIST_PATHS) {
|
|
1502
|
-
throw new Error(
|
|
1503
|
-
`Number of paths (${pathListResult.items.length}) exceeds maximum allowed (${MAX_FS_LIST_PATHS}). Consider using more specific glob patterns or filters to reduce the result set.`
|
|
1504
|
-
);
|
|
1505
|
-
}
|
|
1506
|
-
const formattedOutput = pathListToString(pathListResult, {
|
|
1507
|
-
sort,
|
|
1508
|
-
fields,
|
|
1509
|
-
totals: true
|
|
1510
|
-
});
|
|
1511
|
-
return {
|
|
1512
|
-
output: formattedOutput
|
|
1513
|
-
};
|
|
1514
|
-
} catch (err) {
|
|
1515
|
-
return { error: err instanceof Error ? err.message : "Unknown error" };
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
function mcpRegisterToolFsList(mcpRegisterTool, options) {
|
|
1519
|
-
mcpRegisterTool(
|
|
1520
|
-
"fs-list",
|
|
1521
|
-
{
|
|
1522
|
-
title: "List Filesystem Paths",
|
|
1523
|
-
description: `List files and directories with advanced filtering, sorting and formatting options. Features include: glob pattern filtering (gitignore-style syntax), date modification filters (modified within/before time ranges), size filters (binary units B/KB/MB/GB/TB), multi-field sorting (type/path/date/size/totalCountFiles), custom field selection, and formatted table output. Returns structured table text with pipe-separated columns. Use showFiles/showDirs to control what types are shown in report table. The totalCountFiles field shows total files count for directories. Supports complex queries like "all TypeScript files larger than 1KB modified in the last week".`,
|
|
1524
|
-
inputSchema: ListSchema.shape
|
|
1525
|
-
},
|
|
1526
|
-
async (args) => {
|
|
1527
|
-
const result = await commandList(args, options);
|
|
1528
|
-
if (result.error) {
|
|
1529
|
-
return `Method: fs-list(${JSON.stringify(args)})
|
|
1530
|
-
Error: ${result.error}`;
|
|
1531
|
-
}
|
|
1532
|
-
return `Method: fs-list(${JSON.stringify(args)})
|
|
1533
|
-
${result.output || JSON.stringify(result, null, 2)}`;
|
|
1534
|
-
}
|
|
1535
|
-
);
|
|
1536
|
-
}
|
|
1537
|
-
function mcpRegisterFsManager(mcpRegisterTool, options) {
|
|
1538
|
-
mcpRegisterToolFsList(mcpRegisterTool, {
|
|
1539
|
-
workingDir: options.workingDir || null,
|
|
1540
|
-
...options.list
|
|
1541
|
-
});
|
|
1542
|
-
console.log(
|
|
1543
|
-
`File manager:
|
|
1544
|
-
- Working directory: ${path.resolve(options.workingDir || "")}
|
|
1545
|
-
`
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
function createErrorMiddleware(options) {
|
|
1549
|
-
const { logFilePath } = options;
|
|
1550
|
-
return async function errorErrorMiddleware(err, req, res, next) {
|
|
1551
|
-
await logToFile({
|
|
1552
|
-
logFilePath,
|
|
1553
|
-
message: "ERROR",
|
|
1554
|
-
data: {
|
|
1555
|
-
request: {
|
|
1556
|
-
url: req.url,
|
|
1557
|
-
method: req.method,
|
|
1558
|
-
headers: req.headers,
|
|
1559
|
-
body: req.body
|
|
1560
|
-
},
|
|
1561
|
-
error: err.message,
|
|
1562
|
-
stack: err.stack
|
|
1563
|
-
}
|
|
1564
|
-
});
|
|
1565
|
-
res.status(500).send({
|
|
1566
|
-
error: "Internal Server Error",
|
|
1567
|
-
message: err.stack || err.message || err.toString()
|
|
1568
|
-
});
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
function createLogFileName() {
|
|
1572
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(0, 19).replace(/T/, "_").replace(/:/g, "-");
|
|
1573
|
-
return `mcp_${timestamp}.log`;
|
|
1574
|
-
}
|
|
1575
|
-
function createServer(options) {
|
|
1576
|
-
const app = express();
|
|
1577
|
-
const baseRouter = createBaseRouter();
|
|
1578
|
-
app.use(baseRouter);
|
|
1579
|
-
return app;
|
|
1580
|
-
}
|
|
1581
|
-
function createMcpServer(options) {
|
|
1582
|
-
const mcpServer = new McpServer({
|
|
1583
|
-
name: options.name,
|
|
1584
|
-
version: options.version,
|
|
1585
|
-
title: "Project Tools"
|
|
1586
|
-
});
|
|
1587
|
-
const mcpRouter = createMcpRouter(mcpServer, options);
|
|
1588
|
-
return {
|
|
1589
|
-
mcpServer,
|
|
1590
|
-
mcpRouter
|
|
1591
|
-
};
|
|
1592
|
-
}
|
|
1593
|
-
function createBaseRouter(options) {
|
|
1594
|
-
const router = express.Router();
|
|
1595
|
-
router.use(
|
|
1596
|
-
cors.default({
|
|
1597
|
-
origin: "*",
|
|
1598
|
-
credentials: true,
|
|
1599
|
-
allowedHeaders: [
|
|
1600
|
-
"Content-Type",
|
|
1601
|
-
"Authorization",
|
|
1602
|
-
"X-Session-Id",
|
|
1603
|
-
"mcp-session-id"
|
|
1604
|
-
],
|
|
1605
|
-
exposedHeaders: ["mcp-session-id"]
|
|
1606
|
-
})
|
|
1607
|
-
);
|
|
1608
|
-
router.use(express.json());
|
|
1609
|
-
return router;
|
|
1610
|
-
}
|
|
1611
|
-
function createMcpRouter(mcpServer, options) {
|
|
1612
|
-
const router = express.Router();
|
|
1613
|
-
router.use(createAuthMiddleware({ authToken: options.authToken }));
|
|
1614
|
-
router.all("/mcp", createStreamableHttpHandler(mcpServer, options));
|
|
1615
|
-
return router;
|
|
1616
|
-
}
|
|
1617
|
-
function startServer(app, options) {
|
|
1618
|
-
app.use(createErrorMiddleware({ logFilePath: options.logFilePath }));
|
|
1619
|
-
return new Promise((resolve, reject) => {
|
|
1620
|
-
let httpServer;
|
|
1621
|
-
const callback = () => {
|
|
1622
|
-
resolve(httpServer);
|
|
1623
|
-
};
|
|
1624
|
-
try {
|
|
1625
|
-
httpServer = options.host ? app.listen(options.port ?? 0, options.host, callback) : app.listen(options.port ?? 0, callback);
|
|
1626
|
-
httpServer.addListener("error", (err) => {
|
|
1627
|
-
reject(err);
|
|
1628
|
-
});
|
|
1629
|
-
} catch (error) {
|
|
1630
|
-
reject(error);
|
|
1631
|
-
}
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
function getMcpServerDescription(httpServer, options) {
|
|
1635
|
-
const family = httpServer.address().family;
|
|
1636
|
-
const port = httpServer.address().port;
|
|
1637
|
-
let host = httpServer.address().address;
|
|
1638
|
-
if (host === "::") {
|
|
1639
|
-
host = "localhost";
|
|
1640
|
-
} else if (family === "IPv6") {
|
|
1641
|
-
host = `[${host}]`;
|
|
1642
|
-
}
|
|
1643
|
-
const serverUrl = `http://${family === "IPv6" ? `[${host}]` : host}:${port}`;
|
|
1644
|
-
return `Project Tools - MCP Server Started
|
|
1645
|
-
|
|
1646
|
-
Server Name: ${options.name}
|
|
1647
|
-
Server Version: ${options.version}
|
|
1648
|
-
Server URL: ${serverUrl}/mcp
|
|
1649
|
-
SSE Endpoint: ${serverUrl}/sse
|
|
1650
|
-
|
|
1651
|
-
Log File: ${path.resolve(options.logFilePath)}`;
|
|
1652
|
-
}
|
|
1653
|
-
async function startMcpServer(options) {
|
|
1654
|
-
const logFilePath = path.join(options.logDir, createLogFileName());
|
|
1655
|
-
const app = createServer();
|
|
1656
|
-
const { mcpServer, mcpRouter } = createMcpServer({
|
|
1657
|
-
...options,
|
|
1658
|
-
logFilePath
|
|
1659
|
-
});
|
|
1660
|
-
app.use(mcpRouter);
|
|
1661
|
-
const mcpRegisterTool = createMcpRegisterToolFunc(mcpServer, {
|
|
1662
|
-
logFilePath
|
|
1663
|
-
});
|
|
1664
|
-
if (options.tools.processManager) {
|
|
1665
|
-
mcpRegisterProcessManager(mcpRegisterTool, options.tools?.processManager);
|
|
1666
|
-
}
|
|
1667
|
-
if (options.tools.fsManager) {
|
|
1668
|
-
mcpRegisterFsManager(mcpRegisterTool, options.tools.fsManager);
|
|
1669
|
-
}
|
|
1670
|
-
const httpServer = await startServer(app, {
|
|
1671
|
-
host: options.host,
|
|
1672
|
-
port: options.port,
|
|
1673
|
-
logFilePath
|
|
1674
|
-
});
|
|
1675
|
-
console.log(
|
|
1676
|
-
getMcpServerDescription(httpServer, {
|
|
1677
|
-
name: options.name,
|
|
1678
|
-
version: options.version,
|
|
1679
|
-
authToken: options.authToken,
|
|
1680
|
-
logFilePath,
|
|
1681
|
-
enableJsonResponse: options.enableJsonResponse
|
|
1682
|
-
})
|
|
1683
|
-
);
|
|
1684
|
-
return httpServer;
|
|
1685
|
-
}
|
|
1686
|
-
export {
|
|
1687
|
-
AUTH_TOKEN as A,
|
|
1688
|
-
SERVER_VERSION as S,
|
|
1689
|
-
SERVER_NAME as a,
|
|
1690
|
-
startMcpServer as s
|
|
1691
|
-
};
|
|
1692
|
-
//# sourceMappingURL=startMcpServer-B-xjEzCx.js.map
|