@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 +8 -2
- package/dist/custom-stdio.d.ts +8 -0
- package/dist/custom-stdio.js +22 -0
- package/dist/index.js +49 -7
- package/dist/server.d.ts +19 -15
- package/dist/setup-claude-server.js +142 -48
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -3
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
|
|
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/
|
|
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 {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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?:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
progressToken
|
|
9
|
-
}
|
|
10
|
-
|
|
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?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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.
|
|
1
|
+
export declare const VERSION = "0.1.25";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
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.
|
|
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
|
|
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.
|
|
69
|
+
"@types/node": "^20.17.24",
|
|
67
70
|
"nodemon": "^3.0.2",
|
|
68
71
|
"shx": "^0.3.4",
|
|
69
72
|
"typescript": "^5.3.3"
|