@wonderwhy-er/desktop-commander 0.1.23 → 0.1.25

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
@@ -67,7 +67,12 @@ npx @wonderwhy-er/desktop-commander setup
67
67
  Restart Claude if running
68
68
 
69
69
  ### Option 3: Add to claude_desktop_config by hand
70
- Add this entry to your claude_desktop_config.json (on Mac, found at ~/Library/Application\ Support/Claude/claude_desktop_config.json):
70
+ Add this entry to your claude_desktop_config.json:
71
+
72
+ - On Mac: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
73
+ - On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
74
+ - On Linux: `~/.config/Claude/claude_desktop_config.json`
75
+
71
76
  ```json
72
77
  {
73
78
  "mcpServers": {
@@ -164,6 +169,7 @@ This project extends the MCP Filesystem Server to enable:
164
169
  Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
165
170
 
166
171
  ## DONE
172
+ - **28-03-2025 Fixed "Watching /" JSON error** - Implemented custom stdio transport to handle non-JSON messages and prevent server crashes
167
173
  - **25-03-2025 Better code search** ([merged](https://github.com/wonderwhy-er/ClaudeDesktopCommander/pull/17)) - Enhanced code exploration with context-aware results
168
174
 
169
175
  ## Work in Progress and TODOs
@@ -195,7 +201,7 @@ Learn more about this project through these resources:
195
201
  This Developer Ditched Windsurf, Cursor Using Claude with MCPs](https://analyticsindiamag.com/ai-features/this-developer-ditched-windsurf-cursor-using-claude-with-mcps/)
196
202
 
197
203
  ### Community
198
- Join our [Discord server](https://discord.gg/7cbccwRp) to get help, share feedback, and connect with other users.
204
+ Join our [Discord server](https://discord.gg/kQ27sNnZr7) to get help, share feedback, and connect with other users.
199
205
 
200
206
  ## Testimonials
201
207
 
@@ -0,0 +1,8 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ /**
3
+ * Extended StdioServerTransport that filters out non-JSON messages.
4
+ * This prevents the "Watching /" error from crashing the server.
5
+ */
6
+ export declare class FilteredStdioServerTransport extends StdioServerTransport {
7
+ constructor();
8
+ }
@@ -0,0 +1,22 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import process from "node:process";
3
+ /**
4
+ * Extended StdioServerTransport that filters out non-JSON messages.
5
+ * This prevents the "Watching /" error from crashing the server.
6
+ */
7
+ export class FilteredStdioServerTransport extends StdioServerTransport {
8
+ constructor() {
9
+ // Create a proxy for stdout that only allows valid JSON to pass through
10
+ const originalStdoutWrite = process.stdout.write;
11
+ process.stdout.write = function (buffer) {
12
+ // Only intercept string output that doesn't look like JSON
13
+ if (typeof buffer === 'string' && !buffer.trim().startsWith('{')) {
14
+ return true; //process.stderr.write(buffer);
15
+ }
16
+ return originalStdoutWrite.apply(process.stdout, arguments);
17
+ };
18
+ super();
19
+ // Log initialization to stderr to avoid polluting the JSON stream
20
+ process.stderr.write(`[desktop-commander] Initialized FilteredStdioServerTransport\n`);
21
+ }
22
+ }
package/dist/index.js CHANGED
@@ -1,20 +1,51 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { FilteredStdioServerTransport } from './custom-stdio.js';
3
3
  import { server } from './server.js';
4
4
  import { commandManager } from './command-manager.js';
5
5
  import { join, dirname } from 'path';
6
- import { fileURLToPath } from 'url';
6
+ import { fileURLToPath, pathToFileURL } from 'url';
7
+ import { platform } from 'os';
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = dirname(__filename);
10
+ const isWindows = platform() === 'win32';
11
+ // Helper function to properly convert file paths to URLs, especially for Windows
12
+ function createFileURL(filePath) {
13
+ if (isWindows) {
14
+ // Ensure path uses forward slashes for URL format
15
+ const normalizedPath = filePath.replace(/\\/g, '/');
16
+ // Ensure path has proper file:// prefix
17
+ if (normalizedPath.startsWith('/')) {
18
+ return new URL(`file://${normalizedPath}`);
19
+ }
20
+ else {
21
+ return new URL(`file:///${normalizedPath}`);
22
+ }
23
+ }
24
+ else {
25
+ // For non-Windows, we can use the built-in function
26
+ return pathToFileURL(filePath);
27
+ }
28
+ }
9
29
  async function runSetup() {
10
- const setupScript = join(__dirname, 'setup-claude-server.js');
11
- const { default: setupModule } = await import(setupScript);
12
- if (typeof setupModule === 'function') {
13
- await setupModule();
30
+ try {
31
+ // Fix for Windows ESM path issue
32
+ const setupScriptPath = join(__dirname, 'setup-claude-server.js');
33
+ const setupScriptUrl = createFileURL(setupScriptPath);
34
+ // Now import using the URL format
35
+ const { default: setupModule } = await import(setupScriptUrl.href);
36
+ if (typeof setupModule === 'function') {
37
+ await setupModule();
38
+ }
39
+ }
40
+ catch (error) {
41
+ console.error('Error running setup:', error);
42
+ process.exit(1);
14
43
  }
15
44
  }
16
45
  async function runServer() {
17
46
  try {
47
+ const transport = new FilteredStdioServerTransport();
48
+ console.log("start");
18
49
  // Check if first argument is "setup"
19
50
  if (process.argv[2] === 'setup') {
20
51
  await runSetup();
@@ -23,14 +54,25 @@ async function runServer() {
23
54
  // Handle uncaught exceptions
24
55
  process.on('uncaughtException', async (error) => {
25
56
  const errorMessage = error instanceof Error ? error.message : String(error);
57
+ // If this is a JSON parsing error, log it to stderr but don't crash
58
+ if (errorMessage.includes('JSON') && errorMessage.includes('Unexpected token')) {
59
+ process.stderr.write(`[desktop-commander] JSON parsing error: ${errorMessage}\n`);
60
+ return; // Don't exit on JSON parsing errors
61
+ }
62
+ process.stderr.write(`[desktop-commander] Uncaught exception: ${errorMessage}\n`);
26
63
  process.exit(1);
27
64
  });
28
65
  // Handle unhandled rejections
29
66
  process.on('unhandledRejection', async (reason) => {
30
67
  const errorMessage = reason instanceof Error ? reason.message : String(reason);
68
+ // If this is a JSON parsing error, log it to stderr but don't crash
69
+ if (errorMessage.includes('JSON') && errorMessage.includes('Unexpected token')) {
70
+ process.stderr.write(`[desktop-commander] JSON parsing rejection: ${errorMessage}\n`);
71
+ return; // Don't exit on JSON parsing errors
72
+ }
73
+ process.stderr.write(`[desktop-commander] Unhandled rejection: ${errorMessage}\n`);
31
74
  process.exit(1);
32
75
  });
33
- const transport = new StdioServerTransport();
34
76
  // Load blocked commands from config file
35
77
  await commandManager.loadBlockedCommands();
36
78
  await server.connect(transport);
package/dist/server.d.ts CHANGED
@@ -1,20 +1,24 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  export declare const server: Server<{
3
3
  method: string;
4
- params?: import("zod").objectOutputType<{
5
- _meta: import("zod").ZodOptional<import("zod").ZodObject<{
6
- progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
7
- }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
8
- progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
9
- }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
10
- progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
11
- }, import("zod").ZodTypeAny, "passthrough">>>;
12
- }, import("zod").ZodTypeAny, "passthrough"> | undefined;
4
+ params?: {
5
+ [x: string]: unknown;
6
+ _meta?: {
7
+ [x: string]: unknown;
8
+ progressToken?: string | number | undefined;
9
+ } | undefined;
10
+ } | undefined;
13
11
  }, {
14
12
  method: string;
15
- params?: import("zod").objectOutputType<{
16
- _meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
17
- }, import("zod").ZodTypeAny, "passthrough"> | undefined;
18
- }, import("zod").objectOutputType<{
19
- _meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
20
- }, import("zod").ZodTypeAny, "passthrough">>;
13
+ params?: {
14
+ [x: string]: unknown;
15
+ _meta?: {
16
+ [x: string]: unknown;
17
+ } | undefined;
18
+ } | undefined;
19
+ }, {
20
+ [x: string]: unknown;
21
+ _meta?: {
22
+ [x: string]: unknown;
23
+ } | undefined;
24
+ }>;
@@ -3,15 +3,31 @@ import { join } from 'path';
3
3
  import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname } from 'path';
6
+ import { exec } from "node:child_process";
6
7
 
8
+ // Fix for Windows ESM path resolution
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = dirname(__filename);
9
11
 
10
- // Determine OS and set appropriate config path and command
11
- const isWindows = platform() === 'win32';
12
- const claudeConfigPath = isWindows
13
- ? join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json')
14
- : join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
12
+ // Determine OS and set appropriate config path
13
+ const os = platform();
14
+ const isWindows = os === 'win32'; // Define isWindows variable
15
+ let claudeConfigPath;
16
+
17
+ switch (os) {
18
+ case 'win32':
19
+ claudeConfigPath = join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json');
20
+ break;
21
+ case 'darwin':
22
+ claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
23
+ break;
24
+ case 'linux':
25
+ claudeConfigPath = join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
26
+ break;
27
+ default:
28
+ // Fallback for other platforms
29
+ claudeConfigPath = join(homedir(), '.claude_desktop_config.json');
30
+ }
15
31
 
16
32
  // Setup logging
17
33
  const LOG_FILE = join(__dirname, 'setup.log');
@@ -38,6 +54,64 @@ function logToFile(message, isError = false) {
38
54
  }
39
55
  }
40
56
 
57
+ async function execAsync(command) {
58
+ return new Promise((resolve, reject) => {
59
+ // Use PowerShell on Windows for better Unicode support and consistency
60
+ const actualCommand = isWindows
61
+ ? `cmd.exe /c ${command}`
62
+ : command;
63
+
64
+ exec(actualCommand, (error, stdout, stderr) => {
65
+ if (error) {
66
+ reject(error);
67
+ return;
68
+ }
69
+ resolve({ stdout, stderr });
70
+ });
71
+ });
72
+ }
73
+
74
+ async function restartClaude() {
75
+ try {
76
+ const platform = process.platform
77
+ switch (platform) {
78
+ case "win32":
79
+ // ignore errors on windows when claude is not running.
80
+ // just silently kill the process
81
+ try {
82
+ await execAsync(
83
+ `taskkill /F /IM "Claude.exe"`,
84
+ )
85
+ } catch {}
86
+ break;
87
+ case "darwin":
88
+ await execAsync(
89
+ `killall "Claude"`,
90
+ )
91
+ break;
92
+ case "linux":
93
+ await execAsync(
94
+ `pkill -f "claude"`,
95
+ )
96
+ break;
97
+ }
98
+ await new Promise((resolve) => setTimeout(resolve, 3000))
99
+
100
+ if (platform === "win32") {
101
+ // it will never start claude
102
+ // await execAsync(`start "" "Claude.exe"`)
103
+ } else if (platform === "darwin") {
104
+ await execAsync(`open -a "Claude"`)
105
+ } else if (platform === "linux") {
106
+ await execAsync(`claude`)
107
+ }
108
+
109
+ logToFile(`Claude has been restarted.`)
110
+ } catch (error) {
111
+ logToFile(`Failed to restart Claude: ${error}`, true)
112
+ }
113
+ }
114
+
41
115
  // Check if config file exists and create default if not
42
116
  if (!existsSync(claudeConfigPath)) {
43
117
  logToFile(`Claude config file not found at: ${claudeConfigPath}`);
@@ -49,7 +123,7 @@ if (!existsSync(claudeConfigPath)) {
49
123
  import('fs').then(fs => fs.mkdirSync(configDir, { recursive: true }));
50
124
  }
51
125
 
52
- // Create default config
126
+ // Create default config with shell based on platform
53
127
  const defaultConfig = {
54
128
  "serverConfig": isWindows
55
129
  ? {
@@ -66,49 +140,69 @@ if (!existsSync(claudeConfigPath)) {
66
140
  logToFile('Default config file created. Please update it with your Claude API credentials.');
67
141
  }
68
142
 
69
- try {
70
- // Read existing config
71
- const configData = readFileSync(claudeConfigPath, 'utf8');
72
- const config = JSON.parse(configData);
73
-
74
- // Prepare the new server config based on OS
75
- // Determine if running through npx or locally
76
- const isNpx = import.meta.url.endsWith('dist/setup-claude-server.js');
77
-
78
- const serverConfig = isNpx ? {
79
- "command": "npx",
80
- "args": [
81
- "@wonderwhy-er/desktop-commander"
82
- ]
83
- } : {
84
- "command": "node",
85
- "args": [
86
- join(__dirname, 'dist', 'index.js')
87
- ]
88
- };
143
+ // Main function to export for ESM compatibility
144
+ export default async function setup() {
145
+ try {
146
+ // Read existing config
147
+ const configData = readFileSync(claudeConfigPath, 'utf8');
148
+ const config = JSON.parse(configData);
89
149
 
90
- // Initialize mcpServers if it doesn't exist
91
- if (!config.mcpServers) {
92
- config.mcpServers = {};
93
- }
94
-
95
- // Check if the old "desktopCommander" exists and remove it
96
- if (config.mcpServers.desktopCommander) {
97
- logToFile('Found old "desktopCommander" installation. Removing it...');
98
- delete config.mcpServers.desktopCommander;
150
+ // Prepare the new server config based on OS
151
+ // Determine if running through npx or locally
152
+ const isNpx = import.meta.url.includes('node_modules');
153
+
154
+ // Fix Windows path handling for npx execution
155
+ let serverConfig;
156
+ if (isNpx) {
157
+ serverConfig = {
158
+ "command": isWindows ? "npx.cmd" : "npx",
159
+ "args": [
160
+ "@wonderwhy-er/desktop-commander"
161
+ ]
162
+ };
163
+ } else {
164
+ // For local installation, use absolute path to handle Windows properly
165
+ const indexPath = join(__dirname, 'dist', 'index.js');
166
+ serverConfig = {
167
+ "command": "node",
168
+ "args": [
169
+ indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
170
+ ]
171
+ };
172
+ }
173
+
174
+ // Initialize mcpServers if it doesn't exist
175
+ if (!config.mcpServers) {
176
+ config.mcpServers = {};
177
+ }
178
+
179
+ // Check if the old "desktopCommander" exists and remove it
180
+ if (config.mcpServers.desktopCommander) {
181
+ logToFile('Found old "desktopCommander" installation. Removing it...');
182
+ delete config.mcpServers.desktopCommander;
183
+ }
184
+
185
+ // Add or update the terminal server config with the proper name "desktop-commander"
186
+ config.mcpServers["desktop-commander"] = serverConfig;
187
+
188
+ // Write the updated config back
189
+ writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
190
+
191
+ logToFile('Successfully added MCP server to Claude configuration!');
192
+ logToFile(`Configuration location: ${claudeConfigPath}`);
193
+ 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');
194
+
195
+ await restartClaude();
196
+ } catch (error) {
197
+ logToFile(`Error updating Claude configuration: ${error}`, true);
198
+ process.exit(1);
99
199
  }
100
-
101
- // Add or update the terminal server config with the proper name "desktop-commander"
102
- config.mcpServers["desktop-commander"] = serverConfig;
200
+ }
103
201
 
104
- // Write the updated config back
105
- writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
106
-
107
- logToFile('Successfully added MCP server to Claude configuration!');
108
- logToFile(`Configuration location: ${claudeConfigPath}`);
109
- 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');
110
-
111
- } catch (error) {
112
- logToFile(`Error updating Claude configuration: ${error}`, true);
113
- process.exit(1);
202
+ // Allow direct execution
203
+ if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.url)) {
204
+ setup().catch(error => {
205
+ logToFile(`Fatal error: ${error}`, true);
206
+ process.exit(1);
207
+ });
114
208
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.1.23";
1
+ export declare const VERSION = "0.1.25";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.1.23';
1
+ export const VERSION = '0.1.25';
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "license": "MIT",
6
6
  "author": "Eduards Ruzga",
7
7
  "homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP",
8
8
  "bugs": "https://github.com/wonderwhy-er/DesktopCommanderMCP/issues",
9
9
  "type": "module",
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
10
13
  "bin": {
11
14
  "desktop-commander": "dist/index.js",
12
15
  "setup": "dist/setup-claude-server.js"
@@ -56,14 +59,14 @@
56
59
  "file-operations"
57
60
  ],
58
61
  "dependencies": {
59
- "@modelcontextprotocol/sdk": "1.0.1",
62
+ "@modelcontextprotocol/sdk": "^1.8.0",
60
63
  "@vscode/ripgrep": "^1.15.9",
61
64
  "glob": "^10.3.10",
62
65
  "zod": "^3.24.1",
63
66
  "zod-to-json-schema": "^3.23.5"
64
67
  },
65
68
  "devDependencies": {
66
- "@types/node": "^20.11.0",
69
+ "@types/node": "^20.17.24",
67
70
  "nodemon": "^3.0.2",
68
71
  "shx": "^0.3.4",
69
72
  "typescript": "^5.3.3"