@wonderwhy-er/desktop-commander 0.1.27 → 0.1.29

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 CHANGED
@@ -1,4 +1,5 @@
1
1
  # Desktop Commander MCP
2
+ ### Search, update, manage files and run terminal commands with AI
2
3
 
3
4
  [![npm downloads](https://img.shields.io/npm/dw/@wonderwhy-er/desktop-commander)](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
4
5
  [![smithery badge](https://smithery.ai/badge/@wonderwhy-er/desktop-commander)](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
@@ -8,8 +9,8 @@
8
9
 
9
10
  Short version. Two key things. Terminal commands and diff based file editing.
10
11
 
11
- ![Desktop Commander MCP](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/logo.png)
12
12
 
13
+ ![Desktop Commander MCP](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/header.png)
13
14
  <a href="https://glama.ai/mcp/servers/zempur9oh4">
14
15
  <img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Claude Desktop Commander MCP server" />
15
16
  </a>
@@ -55,6 +56,11 @@ Just run this in terminal
55
56
  ```
56
57
  npx @wonderwhy-er/desktop-commander@latest setup
57
58
  ```
59
+
60
+ For debugging mode (allows Node.js inspector connection):
61
+ ```
62
+ npx @wonderwhy-er/desktop-commander@latest setup --debug
63
+ ```
58
64
  Restart Claude if running
59
65
 
60
66
  ### Option 2: Installing via Smithery
@@ -156,6 +162,40 @@ For commands that may take a while:
156
162
  3. Use `read_output` with PID to get new output
157
163
  4. Use `force_terminate` to stop if needed
158
164
 
165
+ ## Debugging
166
+
167
+ If you need to debug the server, you can install it in debug mode:
168
+
169
+ ```bash
170
+ # Using npx
171
+ npx @wonderwhy-er/desktop-commander@latest setup --debug
172
+
173
+ # Or if installed locally
174
+ npm run setup:debug
175
+ ```
176
+
177
+ This will:
178
+ 1. Configure Claude to use a separate "desktop-commander" server
179
+ 2. Enable Node.js inspector protocol with `--inspect-brk=9229` flag
180
+ 3. Pause execution at the start until a debugger connects
181
+ 4. Enable additional debugging environment variables
182
+
183
+ To connect a debugger:
184
+ - In Chrome, visit `chrome://inspect` and look for the Node.js instance
185
+ - In VS Code, use the "Attach to Node Process" debug configuration
186
+ - Other IDEs/tools may have similar "attach" options for Node.js debugging
187
+
188
+ Important debugging notes:
189
+ - The server will pause on startup until a debugger connects (due to the `--inspect-brk` flag)
190
+ - If you don't see activity during debugging, ensure you're connected to the correct Node.js process
191
+ - Multiple Node processes may be running; connect to the one on port 9229
192
+ - The debug server is identified as "desktop-commander-debug" in Claude's MCP server list
193
+
194
+ Troubleshooting:
195
+ - If Claude times out while trying to use the debug server, your debugger might not be properly connected
196
+ - When properly connected, the process will continue execution after hitting the first breakpoint
197
+ - You can add additional breakpoints in your IDE once connected
198
+
159
199
  ## Model Context Protocol Integration
160
200
 
161
201
  This project extends the MCP Filesystem Server to enable:
package/dist/index.js CHANGED
@@ -80,6 +80,7 @@ async function runServer() {
80
80
  process.stderr.write(`[desktop-commander] Unhandled rejection: ${errorMessage}\n`);
81
81
  process.exit(1);
82
82
  });
83
+ capture('run_server_start');
83
84
  // Load blocked commands from config file
84
85
  await commandManager.loadBlockedCommands();
85
86
  await server.connect(transport);
package/dist/server.js CHANGED
@@ -179,9 +179,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
179
179
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
180
180
  try {
181
181
  const { name, arguments: args } = request.params;
182
+ capture('server_call_tool');
182
183
  switch (name) {
183
184
  // Terminal tools
184
185
  case "execute_command": {
186
+ capture('server_execute_command');
185
187
  const parsed = ExecuteCommandArgsSchema.parse(args);
186
188
  return executeCommand(parsed);
187
189
  }
@@ -232,9 +234,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
232
234
  // Filesystem tools
233
235
  case "edit_block": {
234
236
  capture('server_edit_block');
235
- const parsed = EditBlockArgsSchema.parse(args);
236
- const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
237
237
  try {
238
+ const parsed = EditBlockArgsSchema.parse(args);
239
+ const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
238
240
  await performSearchReplace(filePath, searchReplace);
239
241
  return {
240
242
  content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
@@ -243,81 +245,146 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
243
245
  catch (error) {
244
246
  const errorMessage = error instanceof Error ? error.message : String(error);
245
247
  return {
246
- content: [{ type: "text", text: errorMessage }],
248
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
247
249
  };
248
250
  }
249
251
  }
250
252
  case "read_file": {
251
253
  capture('server_read_file');
252
- const parsed = ReadFileArgsSchema.parse(args);
253
- const content = await readFile(parsed.path);
254
- return {
255
- content: [{ type: "text", text: content }],
256
- };
254
+ try {
255
+ const parsed = ReadFileArgsSchema.parse(args);
256
+ const content = await readFile(parsed.path);
257
+ return {
258
+ content: [{ type: "text", text: content }],
259
+ };
260
+ }
261
+ catch (error) {
262
+ const errorMessage = error instanceof Error ? error.message : String(error);
263
+ return {
264
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
265
+ };
266
+ }
257
267
  }
258
268
  case "read_multiple_files": {
259
269
  capture('server_read_multiple_files');
260
- const parsed = ReadMultipleFilesArgsSchema.parse(args);
261
- const results = await readMultipleFiles(parsed.paths);
262
- return {
263
- content: [{ type: "text", text: results.join("\n---\n") }],
264
- };
270
+ try {
271
+ const parsed = ReadMultipleFilesArgsSchema.parse(args);
272
+ const results = await readMultipleFiles(parsed.paths);
273
+ return {
274
+ content: [{ type: "text", text: results.join("\n---\n") }],
275
+ };
276
+ }
277
+ catch (error) {
278
+ const errorMessage = error instanceof Error ? error.message : String(error);
279
+ return {
280
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
281
+ };
282
+ }
265
283
  }
266
284
  case "write_file": {
267
285
  capture('server_write_file');
268
- const parsed = WriteFileArgsSchema.parse(args);
269
- await writeFile(parsed.path, parsed.content);
270
- return {
271
- content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
272
- };
286
+ try {
287
+ const parsed = WriteFileArgsSchema.parse(args);
288
+ await writeFile(parsed.path, parsed.content);
289
+ return {
290
+ content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
291
+ };
292
+ }
293
+ catch (error) {
294
+ const errorMessage = error instanceof Error ? error.message : String(error);
295
+ return {
296
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
297
+ };
298
+ }
273
299
  }
274
300
  case "create_directory": {
275
301
  capture('server_create_directory');
276
- const parsed = CreateDirectoryArgsSchema.parse(args);
277
- await createDirectory(parsed.path);
278
- return {
279
- content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
280
- };
302
+ try {
303
+ const parsed = CreateDirectoryArgsSchema.parse(args);
304
+ await createDirectory(parsed.path);
305
+ return {
306
+ content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
307
+ };
308
+ }
309
+ catch (error) {
310
+ const errorMessage = error instanceof Error ? error.message : String(error);
311
+ return {
312
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
313
+ };
314
+ }
281
315
  }
282
316
  case "list_directory": {
283
317
  capture('server_list_directory');
284
- const parsed = ListDirectoryArgsSchema.parse(args);
285
- const entries = await listDirectory(parsed.path);
286
- return {
287
- content: [{ type: "text", text: entries.join('\n') }],
288
- };
318
+ try {
319
+ const parsed = ListDirectoryArgsSchema.parse(args);
320
+ const entries = await listDirectory(parsed.path);
321
+ return {
322
+ content: [{ type: "text", text: entries.join('\n') }],
323
+ };
324
+ }
325
+ catch (error) {
326
+ const errorMessage = error instanceof Error ? error.message : String(error);
327
+ return {
328
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
329
+ };
330
+ }
289
331
  }
290
332
  case "move_file": {
291
333
  capture('server_move_file');
292
- const parsed = MoveFileArgsSchema.parse(args);
293
- await moveFile(parsed.source, parsed.destination);
294
- return {
295
- content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
296
- };
334
+ try {
335
+ const parsed = MoveFileArgsSchema.parse(args);
336
+ await moveFile(parsed.source, parsed.destination);
337
+ return {
338
+ content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
339
+ };
340
+ }
341
+ catch (error) {
342
+ const errorMessage = error instanceof Error ? error.message : String(error);
343
+ return {
344
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
345
+ };
346
+ }
297
347
  }
298
348
  case "search_files": {
299
349
  capture('server_search_files');
300
- const parsed = SearchFilesArgsSchema.parse(args);
301
- const results = await searchFiles(parsed.path, parsed.pattern);
302
- return {
303
- content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
304
- };
350
+ try {
351
+ const parsed = SearchFilesArgsSchema.parse(args);
352
+ const results = await searchFiles(parsed.path, parsed.pattern);
353
+ return {
354
+ content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
355
+ };
356
+ }
357
+ catch (error) {
358
+ const errorMessage = error instanceof Error ? error.message : String(error);
359
+ return {
360
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
361
+ };
362
+ }
305
363
  }
306
364
  case "search_code": {
307
365
  capture('server_search_code');
308
- const parsed = SearchCodeArgsSchema.parse(args);
309
- const results = await searchTextInFiles({
310
- rootPath: parsed.path,
311
- pattern: parsed.pattern,
312
- filePattern: parsed.filePattern,
313
- ignoreCase: parsed.ignoreCase,
314
- maxResults: parsed.maxResults,
315
- includeHidden: parsed.includeHidden,
316
- contextLines: parsed.contextLines,
317
- });
318
- if (results.length === 0) {
366
+ let results = [];
367
+ try {
368
+ const parsed = SearchCodeArgsSchema.parse(args);
369
+ results = await searchTextInFiles({
370
+ rootPath: parsed.path,
371
+ pattern: parsed.pattern,
372
+ filePattern: parsed.filePattern,
373
+ ignoreCase: parsed.ignoreCase,
374
+ maxResults: parsed.maxResults,
375
+ includeHidden: parsed.includeHidden,
376
+ contextLines: parsed.contextLines,
377
+ });
378
+ if (results.length === 0) {
379
+ return {
380
+ content: [{ type: "text", text: "No matches found" }],
381
+ };
382
+ }
383
+ }
384
+ catch (error) {
385
+ const errorMessage = error instanceof Error ? error.message : String(error);
319
386
  return {
320
- content: [{ type: "text", text: "No matches found" }],
387
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
321
388
  };
322
389
  }
323
390
  // Format the results in a VS Code-like format
@@ -336,20 +403,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
336
403
  }
337
404
  case "get_file_info": {
338
405
  capture('server_get_file_info');
339
- const parsed = GetFileInfoArgsSchema.parse(args);
340
- const info = await getFileInfo(parsed.path);
341
- return {
342
- content: [{
343
- type: "text",
344
- text: Object.entries(info)
345
- .map(([key, value]) => `${key}: ${value}`)
346
- .join('\n')
347
- }],
348
- };
406
+ try {
407
+ const parsed = GetFileInfoArgsSchema.parse(args);
408
+ const info = await getFileInfo(parsed.path);
409
+ return {
410
+ content: [{
411
+ type: "text",
412
+ text: Object.entries(info)
413
+ .map(([key, value]) => `${key}: ${value}`)
414
+ .join('\n')
415
+ }],
416
+ };
417
+ }
418
+ catch (error) {
419
+ const errorMessage = error instanceof Error ? error.message : String(error);
420
+ return {
421
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
422
+ };
423
+ }
349
424
  }
350
425
  case "list_allowed_directories": {
351
- const directories = listAllowedDirectories();
352
426
  capture('server_list_allowed_directories');
427
+ const directories = listAllowedDirectories();
353
428
  return {
354
429
  content: [{
355
430
  type: "text",
@@ -239,15 +239,16 @@ async function restartClaude() {
239
239
  }
240
240
  } catch {}
241
241
  await new Promise((resolve) => setTimeout(resolve, 3000))
242
-
243
- if (platform === "win32") {
244
- // it will never start claude
245
- // await execAsync(`start "" "Claude.exe"`)
246
- } else if (platform === "darwin") {
247
- await execAsync(`open -a "Claude"`)
248
- } else if (platform === "linux") {
249
- await execAsync(`claude`)
250
- }
242
+ try {
243
+ if (platform === "win32") {
244
+ // it will never start claude
245
+ // await execAsync(`start "" "Claude.exe"`)
246
+ } else if (platform === "darwin") {
247
+ await execAsync(`open -a "Claude"`)
248
+ } else if (platform === "linux") {
249
+ await execAsync(`claude`)
250
+ }
251
+ } catch{}
251
252
 
252
253
  logToFile(`Claude has been restarted.`)
253
254
  } catch (error) {
@@ -287,8 +288,17 @@ if (!existsSync(claudeConfigPath)) {
287
288
  logToFile('Default config file created. Please update it with your Claude API credentials.');
288
289
  }
289
290
 
291
+ // Function to check for debug mode argument
292
+ function isDebugMode() {
293
+ return process.argv.includes('--debug');
294
+ }
295
+
290
296
  // Main function to export for ESM compatibility
291
297
  export default async function setup() {
298
+ const debugMode = isDebugMode();
299
+ if (debugMode) {
300
+ logToFile('Debug mode enabled. Will configure with Node.js inspector options.');
301
+ }
292
302
  try {
293
303
  // Read existing config
294
304
  const configData = readFileSync(claudeConfigPath, 'utf8');
@@ -300,22 +310,67 @@ export default async function setup() {
300
310
 
301
311
  // Fix Windows path handling for npx execution
302
312
  let serverConfig;
303
- if (isNpx) {
304
- serverConfig = {
305
- "command": isWindows ? "npx.cmd" : "npx",
306
- "args": [
307
- "@wonderwhy-er/desktop-commander"
308
- ]
309
- };
313
+
314
+ if (debugMode) {
315
+ // Use Node.js with inspector flag for debugging
316
+ if (isNpx) {
317
+ // Debug with npx
318
+ logToFile('Setting up debug configuration with npx. The process will pause on start until a debugger connects.');
319
+ // Add environment variables to help with debugging
320
+ const debugEnv = {
321
+ "NODE_OPTIONS": "--trace-warnings --trace-exit",
322
+ "DEBUG": "*"
323
+ };
324
+
325
+ serverConfig = {
326
+ "command": isWindows ? "node.exe" : "node",
327
+ "args": [
328
+ "--inspect-brk=9229",
329
+ isWindows ?
330
+ join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
331
+ "$(which npx)",
332
+ "@wonderwhy-er/desktop-commander"
333
+ ],
334
+ "env": debugEnv
335
+ };
336
+ } else {
337
+ // Debug with local installation path
338
+ const indexPath = join(__dirname, 'dist', 'index.js');
339
+ logToFile('Setting up debug configuration with local path. The process will pause on start until a debugger connects.');
340
+ // Add environment variables to help with debugging
341
+ const debugEnv = {
342
+ "NODE_OPTIONS": "--trace-warnings --trace-exit",
343
+ "DEBUG": "*"
344
+ };
345
+
346
+ serverConfig = {
347
+ "command": isWindows ? "node.exe" : "node",
348
+ "args": [
349
+ "--inspect-brk=9229",
350
+ indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
351
+ ],
352
+ "env": debugEnv
353
+ };
354
+ }
310
355
  } else {
311
- // For local installation, use absolute path to handle Windows properly
312
- const indexPath = join(__dirname, 'dist', 'index.js');
313
- serverConfig = {
314
- "command": "node",
315
- "args": [
316
- indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
317
- ]
318
- };
356
+ // Standard configuration without debug
357
+ if (isNpx) {
358
+ serverConfig = {
359
+ "command": isWindows ? "npx.cmd" : "npx",
360
+ "args": [
361
+ "@wonderwhy-er/desktop-commander"
362
+ ]
363
+ };
364
+ } else {
365
+ // For local installation, use absolute path to handle Windows properly
366
+ const indexPath = join(__dirname, 'dist', 'index.js');
367
+ serverConfig = {
368
+ "command": "node",
369
+ "args": [
370
+ indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
371
+ ]
372
+ };
373
+ }
319
374
  }
320
375
 
321
376
  // Initialize mcpServers if it doesn't exist
@@ -337,7 +392,12 @@ export default async function setup() {
337
392
  await trackEvent('npx_setup_update_config');
338
393
  logToFile('Successfully added MCP server to Claude configuration!');
339
394
  logToFile(`Configuration location: ${claudeConfigPath}`);
340
- logToFile('\nTo use the server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander" in Claude\'s MCP server list');
395
+
396
+ if (debugMode) {
397
+ logToFile('\nTo use the debug server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander-debug" in Claude\'s MCP server list\n3. Connect your debugger to port 9229');
398
+ } else {
399
+ logToFile('\nTo use the server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander" in Claude\'s MCP server list');
400
+ }
341
401
 
342
402
  await restartClaude();
343
403
 
@@ -27,16 +27,20 @@ function expandHome(filepath) {
27
27
  export async function validatePath(requestedPath) {
28
28
  // Temporarily allow all paths by just returning the resolved path
29
29
  // TODO: Implement configurable path validation
30
+ // Expand home directory if present
30
31
  const expandedPath = expandHome(requestedPath);
32
+ // Convert to absolute path
31
33
  const absolute = path.isAbsolute(expandedPath)
32
34
  ? path.resolve(expandedPath)
33
35
  : path.resolve(process.cwd(), expandedPath);
34
- // Try to resolve real path for symlinks, but don't enforce restrictions
36
+ // Check if path exists
35
37
  try {
38
+ const stats = await fs.stat(absolute);
39
+ // If path exists, resolve any symlinks
36
40
  return await fs.realpath(absolute);
37
41
  }
38
42
  catch (error) {
39
- // If can't resolve (e.g., file doesn't exist yet), return absolute path
43
+ // return path if it's not exist. This will be used for folder creation and many other file operations
40
44
  return absolute;
41
45
  }
42
46
  /* Original implementation commented out for future reference
@@ -135,6 +139,7 @@ export async function searchFiles(rootPath, pattern) {
135
139
  }
136
140
  }
137
141
  }
142
+ // if path not exist, it will throw an error
138
143
  const validPath = await validatePath(rootPath);
139
144
  await search(validPath);
140
145
  return results;
package/dist/utils.js CHANGED
@@ -6,16 +6,24 @@ let posthog = null;
6
6
  // Try to load PostHog without breaking if it's not available
7
7
  try {
8
8
  // Dynamic imports to prevent crashing if dependencies aren't available
9
- const { PostHog } = require('posthog-node');
10
- const machineId = require('node-machine-id');
11
- uniqueUserId = machineId.machineIdSync();
12
- if (isTrackingEnabled) {
13
- posthog = new PostHog('phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063', {
14
- host: 'https://eu.i.posthog.com',
15
- flushAt: 3, // send all every time
16
- flushInterval: 5 // send always
9
+ import('posthog-node').then((posthogModule) => {
10
+ const PostHog = posthogModule.PostHog;
11
+ import('node-machine-id').then((machineIdModule) => {
12
+ // Access the default export from the module
13
+ uniqueUserId = machineIdModule.default.machineIdSync();
14
+ if (isTrackingEnabled) {
15
+ posthog = new PostHog('phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063', {
16
+ host: 'https://eu.i.posthog.com',
17
+ flushAt: 3, // send all every time
18
+ flushInterval: 5 // send always
19
+ });
20
+ }
21
+ }).catch(() => {
22
+ // Silently fail - we don't want analytics issues to break functionality
17
23
  });
18
- }
24
+ }).catch(() => {
25
+ // Silently fail - we don't want analytics issues to break functionality
26
+ });
19
27
  }
20
28
  catch (error) {
21
29
  //console.log('Analytics module not available - continuing without tracking');
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.1.27";
1
+ export declare const VERSION = "0.1.29";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.1.27';
1
+ export const VERSION = '0.1.29';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "license": "MIT",
6
6
  "author": "Eduards Ruzga",
@@ -27,7 +27,9 @@
27
27
  "build": "tsc && shx cp setup-claude-server.js dist/ && shx chmod +x dist/*.js",
28
28
  "watch": "tsc --watch",
29
29
  "start": "node dist/index.js",
30
+ "start:debug": "node --inspect-brk=9229 dist/index.js",
30
31
  "setup": "npm install && npm run build && node setup-claude-server.js",
32
+ "setup:debug": "npm install && npm run build && node setup-claude-server.js --debug",
31
33
  "prepare": "npm run build",
32
34
  "test": "node test/test.js",
33
35
  "test:watch": "nodemon test/test.js",
@@ -68,6 +70,7 @@
68
70
  },
69
71
  "devDependencies": {
70
72
  "@types/node": "^20.17.24",
73
+ "nexe": "^5.0.0-beta.4",
71
74
  "nodemon": "^3.0.2",
72
75
  "shx": "^0.3.4",
73
76
  "typescript": "^5.3.3"