@wonderwhy-er/desktop-commander 0.1.17 → 0.1.19
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 +18 -6
- package/dist/server.js +3 -2
- package/dist/setup-claude-server.js +2 -2
- package/dist/terminal-manager.d.ts +10 -0
- package/dist/terminal-manager.js +31 -6
- package/dist/tools/progress.d.ts +20 -0
- package/dist/tools/progress.js +59 -0
- package/dist/types.d.ts +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
# Claude
|
|
1
|
+
# Claude Desktop Commander MCP
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
|
|
4
|
+
[](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
|
|
5
|
+
|
|
2
6
|
|
|
3
7
|
Short version. Two key things. Terminal commands and diff based file editing.
|
|
4
8
|
|
|
5
|
-
This server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional file editing capabilities .
|
|
9
|
+
This is server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional search and replace file editing capabilities .
|
|
6
10
|
|
|
7
11
|
## Features
|
|
8
12
|
|
|
@@ -25,14 +29,22 @@ This server that allows Claude desktop app to execute long-running terminal comm
|
|
|
25
29
|
## Installation
|
|
26
30
|
First, ensure you've downloaded and installed the [Claude Desktop app](https://claude.ai/download) and you have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
|
27
31
|
|
|
28
|
-
### Option 1:
|
|
32
|
+
### Option 1: Installing via Smithery
|
|
33
|
+
|
|
34
|
+
To install Desktop Commander for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@wonderwhy-er/desktop-commander):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx -y @smithery/cli install @wonderwhy-er/desktop-commander --client claude
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Option 2: Install trough npx
|
|
29
41
|
Just run this in terminal
|
|
30
42
|
```
|
|
31
43
|
npx @wonderwhy-er/desktop-commander setup
|
|
32
44
|
```
|
|
33
45
|
Restart Claude if running
|
|
34
46
|
|
|
35
|
-
### Option
|
|
47
|
+
### Option 3: Add to claude_desktop_config by hand
|
|
36
48
|
Add this entry to your claude_desktop_config.json (on Mac, found at ~/Library/Application\ Support/Claude/claude_desktop_config.json):
|
|
37
49
|
```json
|
|
38
50
|
{
|
|
@@ -49,7 +61,7 @@ Add this entry to your claude_desktop_config.json (on Mac, found at ~/Library/Ap
|
|
|
49
61
|
```
|
|
50
62
|
Restart Claude if running
|
|
51
63
|
|
|
52
|
-
### Option
|
|
64
|
+
### Option 4: Checkout locally
|
|
53
65
|
1. Clone and build:
|
|
54
66
|
```bash
|
|
55
67
|
git clone https://github.com/wonderwhy-er/ClaudeComputerCommander.git
|
|
@@ -143,4 +155,4 @@ All contributions, big or small, are greatly appreciated!
|
|
|
143
155
|
|
|
144
156
|
## License
|
|
145
157
|
|
|
146
|
-
MIT
|
|
158
|
+
MIT
|
package/dist/server.js
CHANGED
|
@@ -7,9 +7,10 @@ import { executeCommand, readOutput, forceTerminate, listSessions } from './tool
|
|
|
7
7
|
import { listProcesses, killProcess } from './tools/process.js';
|
|
8
8
|
import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js';
|
|
9
9
|
import { parseEditBlock, performSearchReplace } from './tools/edit.js';
|
|
10
|
+
import { VERSION } from './version.js';
|
|
10
11
|
export const server = new Server({
|
|
11
|
-
name: "
|
|
12
|
-
version:
|
|
12
|
+
name: "desktop-commander",
|
|
13
|
+
version: VERSION,
|
|
13
14
|
}, {
|
|
14
15
|
capabilities: {
|
|
15
16
|
tools: {},
|
|
@@ -95,12 +95,12 @@ try {
|
|
|
95
95
|
config.mcpServers.desktopCommander = serverConfig;
|
|
96
96
|
|
|
97
97
|
// Add puppeteer server if not present
|
|
98
|
-
if (!config.mcpServers.puppeteer) {
|
|
98
|
+
/*if (!config.mcpServers.puppeteer) {
|
|
99
99
|
config.mcpServers.puppeteer = {
|
|
100
100
|
"command": "npx",
|
|
101
101
|
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
|
|
102
102
|
};
|
|
103
|
-
}
|
|
103
|
+
}*/
|
|
104
104
|
|
|
105
105
|
// Write the updated config back
|
|
106
106
|
writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { CommandExecutionResult, ActiveSession } from './types.js';
|
|
2
|
+
interface CompletedSession {
|
|
3
|
+
pid: number;
|
|
4
|
+
output: string;
|
|
5
|
+
exitCode: number | null;
|
|
6
|
+
startTime: Date;
|
|
7
|
+
endTime: Date;
|
|
8
|
+
}
|
|
2
9
|
export declare class TerminalManager {
|
|
3
10
|
private sessions;
|
|
11
|
+
private completedSessions;
|
|
4
12
|
executeCommand(command: string, timeoutMs?: number): Promise<CommandExecutionResult>;
|
|
5
13
|
getNewOutput(pid: number): string | null;
|
|
6
14
|
forceTerminate(pid: number): boolean;
|
|
7
15
|
listActiveSessions(): ActiveSession[];
|
|
16
|
+
listCompletedSessions(): CompletedSession[];
|
|
8
17
|
}
|
|
9
18
|
export declare const terminalManager: TerminalManager;
|
|
19
|
+
export {};
|
package/dist/terminal-manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
|
|
|
3
3
|
export class TerminalManager {
|
|
4
4
|
constructor() {
|
|
5
5
|
this.sessions = new Map();
|
|
6
|
+
this.completedSessions = new Map();
|
|
6
7
|
}
|
|
7
8
|
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT) {
|
|
8
9
|
const process = spawn(command, [], { shell: true });
|
|
@@ -38,8 +39,21 @@ export class TerminalManager {
|
|
|
38
39
|
isBlocked: true
|
|
39
40
|
});
|
|
40
41
|
}, timeoutMs);
|
|
41
|
-
process.on('exit', () => {
|
|
42
|
+
process.on('exit', (code) => {
|
|
42
43
|
if (process.pid) {
|
|
44
|
+
// Store completed session before removing active session
|
|
45
|
+
this.completedSessions.set(process.pid, {
|
|
46
|
+
pid: process.pid,
|
|
47
|
+
output: output + session.lastOutput, // Combine all output
|
|
48
|
+
exitCode: code,
|
|
49
|
+
startTime: session.startTime,
|
|
50
|
+
endTime: new Date()
|
|
51
|
+
});
|
|
52
|
+
// Keep only last 100 completed sessions
|
|
53
|
+
if (this.completedSessions.size > 100) {
|
|
54
|
+
const oldestKey = Array.from(this.completedSessions.keys())[0];
|
|
55
|
+
this.completedSessions.delete(oldestKey);
|
|
56
|
+
}
|
|
43
57
|
this.sessions.delete(process.pid);
|
|
44
58
|
}
|
|
45
59
|
resolve({
|
|
@@ -51,13 +65,21 @@ export class TerminalManager {
|
|
|
51
65
|
});
|
|
52
66
|
}
|
|
53
67
|
getNewOutput(pid) {
|
|
68
|
+
// First check active sessions
|
|
54
69
|
const session = this.sessions.get(pid);
|
|
55
|
-
if (
|
|
56
|
-
|
|
70
|
+
if (session) {
|
|
71
|
+
const output = session.lastOutput;
|
|
72
|
+
session.lastOutput = '';
|
|
73
|
+
return output;
|
|
74
|
+
}
|
|
75
|
+
// Then check completed sessions
|
|
76
|
+
const completedSession = this.completedSessions.get(pid);
|
|
77
|
+
if (completedSession) {
|
|
78
|
+
// Format completion message with exit code and runtime
|
|
79
|
+
const runtime = (completedSession.endTime.getTime() - completedSession.startTime.getTime()) / 1000;
|
|
80
|
+
return `Process completed with exit code ${completedSession.exitCode}\nRuntime: ${runtime}s\nFinal output:\n${completedSession.output}`;
|
|
57
81
|
}
|
|
58
|
-
|
|
59
|
-
session.lastOutput = '';
|
|
60
|
-
return output;
|
|
82
|
+
return null;
|
|
61
83
|
}
|
|
62
84
|
forceTerminate(pid) {
|
|
63
85
|
const session = this.sessions.get(pid);
|
|
@@ -86,5 +108,8 @@ export class TerminalManager {
|
|
|
86
108
|
runtime: now.getTime() - session.startTime.getTime()
|
|
87
109
|
}));
|
|
88
110
|
}
|
|
111
|
+
listCompletedSessions() {
|
|
112
|
+
return Array.from(this.completedSessions.values());
|
|
113
|
+
}
|
|
89
114
|
}
|
|
90
115
|
export const terminalManager = new TerminalManager();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const LongRunningTaskSchema: z.ZodObject<{
|
|
3
|
+
total_steps: z.ZodDefault<z.ZodNumber>;
|
|
4
|
+
step_duration: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
should_fail: z.ZodDefault<z.ZodBoolean>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
total_steps: number;
|
|
8
|
+
step_duration: number;
|
|
9
|
+
should_fail: boolean;
|
|
10
|
+
}, {
|
|
11
|
+
total_steps?: number | undefined;
|
|
12
|
+
step_duration?: number | undefined;
|
|
13
|
+
should_fail?: boolean | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function executeLongRunning(args: unknown, server: any, progressToken?: string): Promise<{
|
|
16
|
+
content: {
|
|
17
|
+
type: string;
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const LongRunningTaskSchema = z.object({
|
|
3
|
+
total_steps: z.number().default(5).describe("Total number of steps to execute"),
|
|
4
|
+
step_duration: z.number().default(1000).describe("Duration of each step in milliseconds"),
|
|
5
|
+
should_fail: z.boolean().default(false).describe("Whether the task should fail midway through"),
|
|
6
|
+
});
|
|
7
|
+
export async function executeLongRunning(args, server, progressToken) {
|
|
8
|
+
const parsed = LongRunningTaskSchema.safeParse(args);
|
|
9
|
+
if (!parsed.success) {
|
|
10
|
+
throw new Error(`Invalid arguments for long_running_task: ${parsed.error}`);
|
|
11
|
+
}
|
|
12
|
+
const { total_steps, step_duration, should_fail } = parsed.data;
|
|
13
|
+
try {
|
|
14
|
+
for (let step = 1; step <= total_steps; step++) {
|
|
15
|
+
// Simulate work
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, step_duration));
|
|
17
|
+
// Send progress notification if we have a token
|
|
18
|
+
if (progressToken) {
|
|
19
|
+
await server.notification({
|
|
20
|
+
method: "notifications/progress",
|
|
21
|
+
params: {
|
|
22
|
+
progress: step,
|
|
23
|
+
total: total_steps,
|
|
24
|
+
progressToken,
|
|
25
|
+
detail: `Completed step ${step} of ${total_steps}${should_fail && step === Math.floor(total_steps / 2)
|
|
26
|
+
? ' (about to fail)'
|
|
27
|
+
: ''}`
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// If should_fail is true, fail halfway through
|
|
32
|
+
if (should_fail && step === Math.floor(total_steps / 2)) {
|
|
33
|
+
throw new Error("Task failed halfway through as requested");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
content: [{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `Long running task completed successfully after ${total_steps} steps`
|
|
40
|
+
}]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Send error notification if we have a token
|
|
45
|
+
if (progressToken) {
|
|
46
|
+
await server.notification({
|
|
47
|
+
method: "notifications/progress",
|
|
48
|
+
params: {
|
|
49
|
+
progress: Math.floor(total_steps / 2),
|
|
50
|
+
total: total_steps,
|
|
51
|
+
progressToken,
|
|
52
|
+
detail: error instanceof Error ? error.message : 'Unknown error',
|
|
53
|
+
error: true
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.1.19";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.1.19';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonderwhy-er/desktop-commander",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduards Ruzga",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
+
"sync-version": "node scripts/sync-version.js",
|
|
19
|
+
"bump": "node scripts/sync-version.js --bump",
|
|
20
|
+
"bump:minor": "node scripts/sync-version.js --bump --minor",
|
|
21
|
+
"bump:major": "node scripts/sync-version.js --bump --major",
|
|
18
22
|
"build": "tsc && shx cp setup-claude-server.js dist/ && shx chmod +x dist/*.js",
|
|
19
23
|
"watch": "tsc --watch",
|
|
20
24
|
"start": "node dist/index.js",
|
|
@@ -60,4 +64,4 @@
|
|
|
60
64
|
"shx": "^0.3.4",
|
|
61
65
|
"typescript": "^5.3.3"
|
|
62
66
|
}
|
|
63
|
-
}
|
|
67
|
+
}
|