@wonderwhy-er/desktop-commander 0.2.3 ā 0.2.4
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/README.md +25 -3
- package/dist/config-manager.d.ts +10 -0
- package/dist/config-manager.js +7 -1
- package/dist/handlers/edit-search-handlers.js +25 -6
- package/dist/handlers/terminal-handlers.d.ts +8 -4
- package/dist/handlers/terminal-handlers.js +16 -10
- package/dist/index-dxt.d.ts +2 -0
- package/dist/index-dxt.js +76 -0
- package/dist/index-with-startup-detection.d.ts +5 -0
- package/dist/index-with-startup-detection.js +180 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +343 -42
- package/dist/terminal-manager.d.ts +7 -0
- package/dist/terminal-manager.js +93 -18
- package/dist/tools/client.d.ts +10 -0
- package/dist/tools/client.js +13 -0
- package/dist/tools/config.d.ts +1 -1
- package/dist/tools/config.js +21 -3
- package/dist/tools/edit.js +4 -3
- package/dist/tools/environment.d.ts +55 -0
- package/dist/tools/environment.js +65 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.js +152 -57
- package/dist/tools/improved-process-tools.js +170 -29
- package/dist/tools/schemas.d.ts +20 -2
- package/dist/tools/schemas.js +20 -2
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/utils/capture.js +23 -1
- package/dist/utils/early-logger.d.ts +4 -0
- package/dist/utils/early-logger.js +35 -0
- package/dist/utils/mcp-logger.d.ts +30 -0
- package/dist/utils/mcp-logger.js +59 -0
- package/dist/utils/smithery-detector.d.ts +94 -0
- package/dist/utils/smithery-detector.js +292 -0
- package/dist/utils/startup-detector.d.ts +65 -0
- package/dist/utils/startup-detector.js +390 -0
- package/dist/utils/usageTracker.d.ts +85 -0
- package/dist/utils/usageTracker.js +280 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
|
@@ -3,7 +3,8 @@ import { commandManager } from '../command-manager.js';
|
|
|
3
3
|
import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
4
|
import { capture } from "../utils/capture.js";
|
|
5
5
|
import { analyzeProcessState, cleanProcessOutput, formatProcessStateMessage } from '../utils/process-detection.js';
|
|
6
|
-
import
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import { configManager } from '../config-manager.js';
|
|
7
8
|
/**
|
|
8
9
|
* Start a new process (renamed from execute_command)
|
|
9
10
|
* Includes early detection of process waiting for input
|
|
@@ -36,7 +37,26 @@ export async function startProcess(args) {
|
|
|
36
37
|
isError: true,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
let shellUsed = parsed.data.shell;
|
|
41
|
+
if (!shellUsed) {
|
|
42
|
+
const config = await configManager.getConfig();
|
|
43
|
+
if (config.defaultShell) {
|
|
44
|
+
shellUsed = config.defaultShell;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const isWindows = os.platform() === 'win32';
|
|
48
|
+
if (isWindows && process.env.COMSPEC) {
|
|
49
|
+
shellUsed = process.env.COMSPEC;
|
|
50
|
+
}
|
|
51
|
+
else if (!isWindows && process.env.SHELL) {
|
|
52
|
+
shellUsed = process.env.SHELL;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
shellUsed = isWindows ? 'cmd.exe' : '/bin/sh';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, shellUsed);
|
|
40
60
|
if (result.pid === -1) {
|
|
41
61
|
return {
|
|
42
62
|
content: [{ type: "text", text: result.output }],
|
|
@@ -45,9 +65,6 @@ export async function startProcess(args) {
|
|
|
45
65
|
}
|
|
46
66
|
// Analyze the process state to detect if it's waiting for input
|
|
47
67
|
const processState = analyzeProcessState(result.output, result.pid);
|
|
48
|
-
// Get system info for shell information
|
|
49
|
-
const systemInfo = getSystemInfo();
|
|
50
|
-
const shellUsed = parsed.data.shell || systemInfo.defaultShell;
|
|
51
68
|
let statusMessage = '';
|
|
52
69
|
if (processState.isWaitingForInput) {
|
|
53
70
|
statusMessage = `\nš ${formatProcessStateMessage(processState, result.pid)}`;
|
|
@@ -93,19 +110,27 @@ export async function readProcessOutput(args) {
|
|
|
93
110
|
const outputPromise = new Promise((resolve) => {
|
|
94
111
|
const initialOutput = terminalManager.getNewOutput(pid);
|
|
95
112
|
if (initialOutput && initialOutput.length > 0) {
|
|
113
|
+
// Immediate check on existing output
|
|
114
|
+
const state = analyzeProcessState(initialOutput, pid);
|
|
115
|
+
if (state.isWaitingForInput) {
|
|
116
|
+
earlyExit = true;
|
|
117
|
+
processState = state;
|
|
118
|
+
}
|
|
96
119
|
resolve(initialOutput);
|
|
97
120
|
return;
|
|
98
121
|
}
|
|
99
122
|
let resolved = false;
|
|
100
123
|
let interval = null;
|
|
101
124
|
let timeout = null;
|
|
125
|
+
// Quick prompt patterns for immediate detection
|
|
126
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
102
127
|
const cleanup = () => {
|
|
103
128
|
if (interval)
|
|
104
129
|
clearInterval(interval);
|
|
105
130
|
if (timeout)
|
|
106
131
|
clearTimeout(timeout);
|
|
107
132
|
};
|
|
108
|
-
|
|
133
|
+
let resolveOnce = (value, isTimeout = false) => {
|
|
109
134
|
if (resolved)
|
|
110
135
|
return;
|
|
111
136
|
resolved = true;
|
|
@@ -113,6 +138,43 @@ export async function readProcessOutput(args) {
|
|
|
113
138
|
timeoutReached = isTimeout;
|
|
114
139
|
resolve(value);
|
|
115
140
|
};
|
|
141
|
+
// Monitor for new output with immediate detection
|
|
142
|
+
const session = terminalManager.getSession(pid);
|
|
143
|
+
if (session && session.process && session.process.stdout && session.process.stderr) {
|
|
144
|
+
const immediateDetector = (data) => {
|
|
145
|
+
const text = data.toString();
|
|
146
|
+
// Immediate check for obvious prompts
|
|
147
|
+
if (quickPromptPatterns.test(text)) {
|
|
148
|
+
const newOutput = terminalManager.getNewOutput(pid) || text;
|
|
149
|
+
const state = analyzeProcessState(output + newOutput, pid);
|
|
150
|
+
if (state.isWaitingForInput) {
|
|
151
|
+
earlyExit = true;
|
|
152
|
+
processState = state;
|
|
153
|
+
resolveOnce(newOutput);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
session.process.stdout.on('data', immediateDetector);
|
|
159
|
+
session.process.stderr.on('data', immediateDetector);
|
|
160
|
+
// Cleanup immediate detectors when done
|
|
161
|
+
const originalResolveOnce = resolveOnce;
|
|
162
|
+
const cleanupDetectors = () => {
|
|
163
|
+
if (session.process.stdout) {
|
|
164
|
+
session.process.stdout.removeListener('data', immediateDetector);
|
|
165
|
+
}
|
|
166
|
+
if (session.process.stderr) {
|
|
167
|
+
session.process.stderr.removeListener('data', immediateDetector);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
// Override resolveOnce to include cleanup
|
|
171
|
+
const resolveOnceWithCleanup = (value, isTimeout = false) => {
|
|
172
|
+
cleanupDetectors();
|
|
173
|
+
originalResolveOnce(value, isTimeout);
|
|
174
|
+
};
|
|
175
|
+
// Replace the local resolveOnce reference
|
|
176
|
+
resolveOnce = resolveOnceWithCleanup;
|
|
177
|
+
}
|
|
116
178
|
interval = setInterval(() => {
|
|
117
179
|
const newOutput = terminalManager.getNewOutput(pid);
|
|
118
180
|
if (newOutput && newOutput.length > 0) {
|
|
@@ -209,32 +271,100 @@ export async function interactWithProcess(args) {
|
|
|
209
271
|
}],
|
|
210
272
|
};
|
|
211
273
|
}
|
|
212
|
-
// Smart waiting with
|
|
274
|
+
// Smart waiting with immediate and periodic detection
|
|
213
275
|
let output = "";
|
|
214
|
-
let attempts = 0;
|
|
215
|
-
const maxAttempts = Math.ceil(timeout_ms / 200);
|
|
216
276
|
let processState;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
277
|
+
let earlyExit = false;
|
|
278
|
+
// Quick prompt patterns for immediate detection
|
|
279
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
280
|
+
const waitForResponse = () => {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
let resolved = false;
|
|
283
|
+
let attempts = 0;
|
|
284
|
+
const maxAttempts = Math.ceil(timeout_ms / 200);
|
|
285
|
+
let interval = null;
|
|
286
|
+
let resolveOnce = () => {
|
|
287
|
+
if (resolved)
|
|
288
|
+
return;
|
|
289
|
+
resolved = true;
|
|
290
|
+
if (interval)
|
|
291
|
+
clearInterval(interval);
|
|
292
|
+
resolve();
|
|
293
|
+
};
|
|
294
|
+
// Set up immediate detection on the process streams
|
|
295
|
+
const session = terminalManager.getSession(pid);
|
|
296
|
+
if (session && session.process && session.process.stdout && session.process.stderr) {
|
|
297
|
+
const immediateDetector = (data) => {
|
|
298
|
+
const text = data.toString();
|
|
299
|
+
// Immediate check for obvious prompts
|
|
300
|
+
if (quickPromptPatterns.test(text)) {
|
|
301
|
+
// Get the latest output and analyze
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
304
|
+
if (newOutput) {
|
|
305
|
+
output += newOutput;
|
|
306
|
+
const state = analyzeProcessState(output, pid);
|
|
307
|
+
if (state.isWaitingForInput) {
|
|
308
|
+
processState = state;
|
|
309
|
+
earlyExit = true;
|
|
310
|
+
resolveOnce();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}, 50); // Small delay to ensure output is captured
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
session.process.stdout.on('data', immediateDetector);
|
|
317
|
+
session.process.stderr.on('data', immediateDetector);
|
|
318
|
+
// Cleanup when done
|
|
319
|
+
const cleanupDetectors = () => {
|
|
320
|
+
if (session.process.stdout) {
|
|
321
|
+
session.process.stdout.removeListener('data', immediateDetector);
|
|
322
|
+
}
|
|
323
|
+
if (session.process.stderr) {
|
|
324
|
+
session.process.stderr.removeListener('data', immediateDetector);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
// Override resolveOnce to include cleanup
|
|
328
|
+
const originalResolveOnce = resolveOnce;
|
|
329
|
+
const resolveOnceWithCleanup = () => {
|
|
330
|
+
cleanupDetectors();
|
|
331
|
+
originalResolveOnce();
|
|
332
|
+
};
|
|
333
|
+
// Replace the local resolveOnce reference
|
|
334
|
+
resolveOnce = resolveOnceWithCleanup;
|
|
231
335
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
336
|
+
// Periodic check as fallback
|
|
337
|
+
interval = setInterval(() => {
|
|
338
|
+
if (resolved)
|
|
339
|
+
return;
|
|
340
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
341
|
+
if (newOutput && newOutput.length > 0) {
|
|
342
|
+
output += newOutput;
|
|
343
|
+
// Analyze current state
|
|
344
|
+
processState = analyzeProcessState(output, pid);
|
|
345
|
+
// Exit early if we detect the process is waiting for input
|
|
346
|
+
if (processState.isWaitingForInput) {
|
|
347
|
+
earlyExit = true;
|
|
348
|
+
resolveOnce();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// Also exit if process finished
|
|
352
|
+
if (processState.isFinished) {
|
|
353
|
+
resolveOnce();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
attempts++;
|
|
358
|
+
if (attempts >= maxAttempts) {
|
|
359
|
+
resolveOnce();
|
|
360
|
+
}
|
|
361
|
+
}, 200);
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
await waitForResponse();
|
|
235
365
|
// Clean and format output
|
|
236
366
|
const cleanOutput = cleanProcessOutput(output, input);
|
|
237
|
-
const timeoutReached =
|
|
367
|
+
const timeoutReached = !earlyExit && !processState?.isFinished && !processState?.isWaitingForInput;
|
|
238
368
|
// Determine final state
|
|
239
369
|
if (!processState) {
|
|
240
370
|
processState = analyzeProcessState(output, pid);
|
|
@@ -253,14 +383,25 @@ export async function interactWithProcess(args) {
|
|
|
253
383
|
return {
|
|
254
384
|
content: [{
|
|
255
385
|
type: "text",
|
|
256
|
-
text: `ā
Input executed in process ${pid}.\n(No output produced)${statusMessage}`
|
|
386
|
+
text: `ā
Input executed in process ${pid}.\nš (No output produced)${statusMessage}`
|
|
257
387
|
}],
|
|
258
388
|
};
|
|
259
389
|
}
|
|
390
|
+
// Format response with better structure and consistent emojis
|
|
391
|
+
let responseText = `ā
Input executed in process ${pid}`;
|
|
392
|
+
if (cleanOutput && cleanOutput.trim().length > 0) {
|
|
393
|
+
responseText += `:\n\nš¤ Output:\n${cleanOutput}`;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
responseText += `.\nš (No output produced)`;
|
|
397
|
+
}
|
|
398
|
+
if (statusMessage) {
|
|
399
|
+
responseText += `\n\n${statusMessage}`;
|
|
400
|
+
}
|
|
260
401
|
return {
|
|
261
402
|
content: [{
|
|
262
403
|
type: "text",
|
|
263
|
-
text:
|
|
404
|
+
text: responseText
|
|
264
405
|
}],
|
|
265
406
|
};
|
|
266
407
|
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const SetConfigValueArgsSchema: z.ZodObject<{
|
|
|
11
11
|
value?: any;
|
|
12
12
|
}>;
|
|
13
13
|
export declare const ListProcessesArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
14
|
-
export declare const
|
|
14
|
+
export declare const StartProcessArgsSchema: z.ZodObject<{
|
|
15
15
|
command: z.ZodString;
|
|
16
16
|
timeout_ms: z.ZodNumber;
|
|
17
17
|
shell: z.ZodOptional<z.ZodString>;
|
|
@@ -24,7 +24,7 @@ export declare const ExecuteCommandArgsSchema: z.ZodObject<{
|
|
|
24
24
|
timeout_ms: number;
|
|
25
25
|
shell?: string | undefined;
|
|
26
26
|
}>;
|
|
27
|
-
export declare const
|
|
27
|
+
export declare const ReadProcessOutputArgsSchema: z.ZodObject<{
|
|
28
28
|
pid: z.ZodNumber;
|
|
29
29
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
30
30
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -173,3 +173,21 @@ export declare const EditBlockArgsSchema: z.ZodObject<{
|
|
|
173
173
|
new_string: string;
|
|
174
174
|
expected_replacements?: number | undefined;
|
|
175
175
|
}>;
|
|
176
|
+
export declare const InteractWithProcessArgsSchema: z.ZodObject<{
|
|
177
|
+
pid: z.ZodNumber;
|
|
178
|
+
input: z.ZodString;
|
|
179
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
180
|
+
wait_for_prompt: z.ZodOptional<z.ZodBoolean>;
|
|
181
|
+
}, "strip", z.ZodTypeAny, {
|
|
182
|
+
pid: number;
|
|
183
|
+
input: string;
|
|
184
|
+
timeout_ms?: number | undefined;
|
|
185
|
+
wait_for_prompt?: boolean | undefined;
|
|
186
|
+
}, {
|
|
187
|
+
pid: number;
|
|
188
|
+
input: string;
|
|
189
|
+
timeout_ms?: number | undefined;
|
|
190
|
+
wait_for_prompt?: boolean | undefined;
|
|
191
|
+
}>;
|
|
192
|
+
export declare const GetUsageStatsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
193
|
+
export declare const GiveFeedbackArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -8,12 +8,12 @@ export const SetConfigValueArgsSchema = z.object({
|
|
|
8
8
|
// Empty schemas
|
|
9
9
|
export const ListProcessesArgsSchema = z.object({});
|
|
10
10
|
// Terminal tools schemas
|
|
11
|
-
export const
|
|
11
|
+
export const StartProcessArgsSchema = z.object({
|
|
12
12
|
command: z.string(),
|
|
13
13
|
timeout_ms: z.number(),
|
|
14
14
|
shell: z.string().optional(),
|
|
15
15
|
});
|
|
16
|
-
export const
|
|
16
|
+
export const ReadProcessOutputArgsSchema = z.object({
|
|
17
17
|
pid: z.number(),
|
|
18
18
|
timeout_ms: z.number().optional(),
|
|
19
19
|
});
|
|
@@ -75,3 +75,21 @@ export const EditBlockArgsSchema = z.object({
|
|
|
75
75
|
new_string: z.string(),
|
|
76
76
|
expected_replacements: z.number().optional().default(1),
|
|
77
77
|
});
|
|
78
|
+
// Send input to process schema
|
|
79
|
+
export const InteractWithProcessArgsSchema = z.object({
|
|
80
|
+
pid: z.number(),
|
|
81
|
+
input: z.string(),
|
|
82
|
+
timeout_ms: z.number().optional(),
|
|
83
|
+
wait_for_prompt: z.boolean().optional(),
|
|
84
|
+
});
|
|
85
|
+
// Usage stats schema
|
|
86
|
+
export const GetUsageStatsArgsSchema = z.object({});
|
|
87
|
+
// Feedback tool schema - no pre-filled parameters, all user input
|
|
88
|
+
export const GiveFeedbackArgsSchema = z.object({
|
|
89
|
+
// No parameters needed - form will be filled manually by user
|
|
90
|
+
// Only auto-filled hidden fields remain:
|
|
91
|
+
// - tool_call_count (auto)
|
|
92
|
+
// - days_using (auto)
|
|
93
|
+
// - platform (auto)
|
|
94
|
+
// - client_id (auto)
|
|
95
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { usageTracker } from '../utils/usageTracker.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get usage statistics for debugging and analysis
|
|
4
|
+
*/
|
|
5
|
+
export async function getUsageStats() {
|
|
6
|
+
try {
|
|
7
|
+
const summary = await usageTracker.getUsageSummary();
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: summary
|
|
12
|
+
}]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: `Error retrieving usage stats: ${error instanceof Error ? error.message : String(error)}`
|
|
20
|
+
}],
|
|
21
|
+
isError: true
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/utils/capture.js
CHANGED
|
@@ -77,6 +77,15 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
77
77
|
if (uniqueUserId === 'unknown') {
|
|
78
78
|
uniqueUserId = await getOrCreateUUID();
|
|
79
79
|
}
|
|
80
|
+
// Get current client information for all events
|
|
81
|
+
const currentClient = configManager.getCurrentClientInfo();
|
|
82
|
+
let clientContext = {};
|
|
83
|
+
if (currentClient) {
|
|
84
|
+
clientContext = {
|
|
85
|
+
client_name: currentClient.name,
|
|
86
|
+
client_version: currentClient.version,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
80
89
|
// Create a deep copy of properties to avoid modifying the original objects
|
|
81
90
|
// This ensures we don't alter error objects that are also returned to the AI
|
|
82
91
|
let sanitizedProperties;
|
|
@@ -109,16 +118,29 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
109
118
|
delete sanitizedProperties[key];
|
|
110
119
|
}
|
|
111
120
|
}
|
|
121
|
+
// Is MCP installed with DXT
|
|
122
|
+
let isDXT = 'false';
|
|
123
|
+
if (process.env.MCP_DXT) {
|
|
124
|
+
isDXT = 'true';
|
|
125
|
+
}
|
|
126
|
+
// Is MCP running in a Docker container
|
|
127
|
+
let isDocker = 'false';
|
|
128
|
+
if (process.env.MCP_CLIENT_DOCKER) {
|
|
129
|
+
isDocker = 'true';
|
|
130
|
+
}
|
|
112
131
|
// Prepare standard properties
|
|
113
132
|
const baseProperties = {
|
|
114
133
|
timestamp: new Date().toISOString(),
|
|
115
134
|
platform: platform(),
|
|
135
|
+
isDocker,
|
|
136
|
+
isDXT,
|
|
116
137
|
app_version: VERSION,
|
|
117
138
|
engagement_time_msec: "100"
|
|
118
139
|
};
|
|
119
|
-
// Combine with sanitized properties
|
|
140
|
+
// Combine with sanitized properties and client context
|
|
120
141
|
const eventProperties = {
|
|
121
142
|
...baseProperties,
|
|
143
|
+
...clientContext,
|
|
122
144
|
...sanitizedProperties
|
|
123
145
|
};
|
|
124
146
|
// Prepare GA4 payload
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function setTransportInstance(transport: any): void;
|
|
2
|
+
export declare function logError(message: string, data?: any): void;
|
|
3
|
+
export declare function logInfo(message: string, data?: any): void;
|
|
4
|
+
export declare function logWarning(message: string, data?: any): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Early logger that can work before FilteredStdioServerTransport is ready
|
|
2
|
+
let transportInstance = null;
|
|
3
|
+
export function setTransportInstance(transport) {
|
|
4
|
+
transportInstance = transport;
|
|
5
|
+
}
|
|
6
|
+
export function logError(message, data) {
|
|
7
|
+
if (transportInstance && transportInstance.sendLog) {
|
|
8
|
+
// Use transport's structured logging
|
|
9
|
+
transportInstance.sendLog("error", message, data);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
// Fallback to console.error which will be intercepted later
|
|
13
|
+
console.error(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function logInfo(message, data) {
|
|
17
|
+
if (transportInstance && transportInstance.sendLog) {
|
|
18
|
+
// Use transport's structured logging
|
|
19
|
+
transportInstance.sendLog("info", message, data);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Fallback to console.log which will be intercepted later
|
|
23
|
+
console.log(message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function logWarning(message, data) {
|
|
27
|
+
if (transportInstance && transportInstance.sendLog) {
|
|
28
|
+
// Use transport's structured logging
|
|
29
|
+
transportInstance.sendLog("warning", message, data);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Fallback to console.warn which will be intercepted later
|
|
33
|
+
console.warn(message);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import '../types.js';
|
|
2
|
+
export type LogLevel = "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug";
|
|
3
|
+
/**
|
|
4
|
+
* MCP-compatible logger that sends log messages via JSON-RPC notifications
|
|
5
|
+
* Falls back to stderr if transport is not available
|
|
6
|
+
*/
|
|
7
|
+
export declare class McpLogger {
|
|
8
|
+
private component;
|
|
9
|
+
constructor(component?: string);
|
|
10
|
+
private logMessage;
|
|
11
|
+
emergency(message: string, data?: any): void;
|
|
12
|
+
alert(message: string, data?: any): void;
|
|
13
|
+
critical(message: string, data?: any): void;
|
|
14
|
+
error(message: string, data?: any): void;
|
|
15
|
+
warning(message: string, data?: any): void;
|
|
16
|
+
notice(message: string, data?: any): void;
|
|
17
|
+
info(message: string, data?: any): void;
|
|
18
|
+
debug(message: string, data?: any): void;
|
|
19
|
+
warn(message: string, data?: any): void;
|
|
20
|
+
logInfo(message: string, data?: any): void;
|
|
21
|
+
toolCall(toolName: string, args: any, result?: any, error?: Error): void;
|
|
22
|
+
}
|
|
23
|
+
export declare const logger: McpLogger;
|
|
24
|
+
export declare const mcpConsole: {
|
|
25
|
+
log: (message: string, data?: any) => void;
|
|
26
|
+
info: (message: string, data?: any) => void;
|
|
27
|
+
warn: (message: string, data?: any) => void;
|
|
28
|
+
error: (message: string, data?: any) => void;
|
|
29
|
+
debug: (message: string, data?: any) => void;
|
|
30
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/utils/mcp-logger.ts
|
|
2
|
+
import '../types.js'; // Import for global types
|
|
3
|
+
/**
|
|
4
|
+
* MCP-compatible logger that sends log messages via JSON-RPC notifications
|
|
5
|
+
* Falls back to stderr if transport is not available
|
|
6
|
+
*/
|
|
7
|
+
export class McpLogger {
|
|
8
|
+
constructor(component = 'desktop-commander') {
|
|
9
|
+
this.component = component;
|
|
10
|
+
}
|
|
11
|
+
logMessage(level, message, data) {
|
|
12
|
+
if (global.mcpTransport?.sendLog) {
|
|
13
|
+
// Send via MCP JSON-RPC notification
|
|
14
|
+
global.mcpTransport.sendLog(level, `[${this.component}] ${message}`, data);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Fallback to stderr
|
|
18
|
+
const timestamp = new Date().toISOString();
|
|
19
|
+
const dataStr = data ? ` - ${JSON.stringify(data)}` : '';
|
|
20
|
+
process.stderr.write(`${timestamp} [${level.toUpperCase()}] [${this.component}] ${message}${dataStr}\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
emergency(message, data) { this.logMessage('emergency', message, data); }
|
|
24
|
+
alert(message, data) { this.logMessage('alert', message, data); }
|
|
25
|
+
critical(message, data) { this.logMessage('critical', message, data); }
|
|
26
|
+
error(message, data) { this.logMessage('error', message, data); }
|
|
27
|
+
warning(message, data) { this.logMessage('warning', message, data); }
|
|
28
|
+
notice(message, data) { this.logMessage('notice', message, data); }
|
|
29
|
+
info(message, data) { this.logMessage('info', message, data); }
|
|
30
|
+
debug(message, data) { this.logMessage('debug', message, data); }
|
|
31
|
+
// Convenience methods
|
|
32
|
+
warn(message, data) { this.warning(message, data); }
|
|
33
|
+
logInfo(message, data) { this.info(message, data); }
|
|
34
|
+
// Tool execution logging
|
|
35
|
+
toolCall(toolName, args, result, error) {
|
|
36
|
+
const logData = {
|
|
37
|
+
tool: toolName,
|
|
38
|
+
args,
|
|
39
|
+
...(result && { result }),
|
|
40
|
+
...(error && { error: error.message, stack: error.stack })
|
|
41
|
+
};
|
|
42
|
+
if (error) {
|
|
43
|
+
this.error(`Tool ${toolName} failed`, logData);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.debug(`Tool ${toolName} executed`, logData);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Create a global logger instance
|
|
51
|
+
export const logger = new McpLogger('desktop-commander');
|
|
52
|
+
// Create convenience functions that mimic console methods
|
|
53
|
+
export const mcpConsole = {
|
|
54
|
+
log: (message, data) => logger.info(message, data),
|
|
55
|
+
info: (message, data) => logger.info(message, data),
|
|
56
|
+
warn: (message, data) => logger.warning(message, data),
|
|
57
|
+
error: (message, data) => logger.error(message, data),
|
|
58
|
+
debug: (message, data) => logger.debug(message, data),
|
|
59
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smithery CLI Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Detects when a Node.js MCP server is being run through Smithery CLI
|
|
5
|
+
* which acts as a proxy/wrapper around MCP servers.
|
|
6
|
+
*/
|
|
7
|
+
export interface SmitheryDetectionInfo {
|
|
8
|
+
isSmithery: boolean;
|
|
9
|
+
confidence: number;
|
|
10
|
+
evidence: string[];
|
|
11
|
+
details: {
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
analyticsEnabled?: boolean;
|
|
14
|
+
clientType?: string;
|
|
15
|
+
connectionType?: 'stdio' | 'http' | 'streamable-http';
|
|
16
|
+
qualifiedName?: string;
|
|
17
|
+
profile?: string;
|
|
18
|
+
runtimeEnvironment?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare class SmitheryDetector {
|
|
22
|
+
private static instance;
|
|
23
|
+
private _detectionInfo;
|
|
24
|
+
private constructor();
|
|
25
|
+
static getInstance(): SmitheryDetector;
|
|
26
|
+
/**
|
|
27
|
+
* Get Smithery detection information (cached after first call)
|
|
28
|
+
*/
|
|
29
|
+
getSmitheryInfo(): SmitheryDetectionInfo;
|
|
30
|
+
/**
|
|
31
|
+
* Force re-detection (useful for testing)
|
|
32
|
+
*/
|
|
33
|
+
forceRedetect(): SmitheryDetectionInfo;
|
|
34
|
+
private detectSmithery;
|
|
35
|
+
/**
|
|
36
|
+
* Check for Smithery-specific environment variables
|
|
37
|
+
*/
|
|
38
|
+
private checkSmitheryEnvironmentVars;
|
|
39
|
+
/**
|
|
40
|
+
* Check process arguments for Smithery patterns
|
|
41
|
+
*/
|
|
42
|
+
private checkProcessArguments;
|
|
43
|
+
/**
|
|
44
|
+
* Check parent process for Smithery CLI
|
|
45
|
+
*/
|
|
46
|
+
private checkParentProcess;
|
|
47
|
+
/**
|
|
48
|
+
* Check for Smithery runtime environment patterns
|
|
49
|
+
*/
|
|
50
|
+
private checkRuntimeEnvironment;
|
|
51
|
+
/**
|
|
52
|
+
* Check for analytics and session indicators
|
|
53
|
+
*/
|
|
54
|
+
private checkAnalyticsIndicators;
|
|
55
|
+
/**
|
|
56
|
+
* Check for MCP transport patterns that indicate Smithery
|
|
57
|
+
*/
|
|
58
|
+
private checkTransportPatterns;
|
|
59
|
+
/**
|
|
60
|
+
* Check stdio patterns that suggest Smithery proxying
|
|
61
|
+
*/
|
|
62
|
+
private checkStdioPatterns;
|
|
63
|
+
/**
|
|
64
|
+
* Check if running through Smithery (convenience method)
|
|
65
|
+
*/
|
|
66
|
+
isSmithery(): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get the client type if running through Smithery
|
|
69
|
+
*/
|
|
70
|
+
getClientType(): string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Get the connection type if running through Smithery
|
|
73
|
+
*/
|
|
74
|
+
getConnectionType(): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Get analytics status if running through Smithery
|
|
77
|
+
*/
|
|
78
|
+
isAnalyticsEnabled(): boolean | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Get session ID if running through Smithery
|
|
81
|
+
*/
|
|
82
|
+
getSessionId(): string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Get server qualified name if running through Smithery
|
|
85
|
+
*/
|
|
86
|
+
getQualifiedName(): string | undefined;
|
|
87
|
+
}
|
|
88
|
+
export declare const smitheryDetector: SmitheryDetector;
|
|
89
|
+
export declare const getSmitheryInfo: () => SmitheryDetectionInfo;
|
|
90
|
+
export declare const isSmithery: () => boolean;
|
|
91
|
+
export declare const getSmitheryClientType: () => string | undefined;
|
|
92
|
+
export declare const getSmitheryConnectionType: () => string | undefined;
|
|
93
|
+
export declare const getSmitherySessionId: () => string | undefined;
|
|
94
|
+
export declare const isSmitheryAnalyticsEnabled: () => boolean | undefined;
|