@wonderwhy-er/desktop-commander 0.1.0
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/command-manager.d.ts +11 -0
- package/dist/command-manager.js +54 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/logging.d.ts +2 -0
- package/dist/logging.js +28 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +287 -0
- package/dist/terminal-manager.d.ts +9 -0
- package/dist/terminal-manager.js +90 -0
- package/dist/tools/command-block.d.ts +18 -0
- package/dist/tools/command-block.js +62 -0
- package/dist/tools/edit.d.ts +10 -0
- package/dist/tools/edit.js +33 -0
- package/dist/tools/execute.d.ts +24 -0
- package/dist/tools/execute.js +60 -0
- package/dist/tools/filesystem.d.ts +10 -0
- package/dist/tools/filesystem.js +133 -0
- package/dist/tools/process.d.ts +12 -0
- package/dist/tools/process.js +47 -0
- package/dist/tools/schemas.d.ts +119 -0
- package/dist/tools/schemas.js +54 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Eduard Ruzga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Claude Computer Commander
|
|
2
|
+
|
|
3
|
+
Short version. Two key things. Terminal commands and diff based file editing.
|
|
4
|
+
|
|
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 .
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Execute terminal commands with output streaming
|
|
10
|
+
- Command timeout and background execution support
|
|
11
|
+
- Process management (list and kill processes)
|
|
12
|
+
- Session management for long-running commands
|
|
13
|
+
- Full filesystem operations:
|
|
14
|
+
- Read/write files
|
|
15
|
+
- Create/list directories
|
|
16
|
+
- Move files/directories
|
|
17
|
+
- Search files
|
|
18
|
+
- Get file metadata
|
|
19
|
+
- Code editing capabilities:
|
|
20
|
+
- Surgical text replacements for small changes
|
|
21
|
+
- Full file rewrites for major changes
|
|
22
|
+
- Multiple file support
|
|
23
|
+
- Pattern-based replacements
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
1. Clone and build:
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/wonderwhy-er/ClaudeComputerCommander.git
|
|
30
|
+
cd ClaudeComputerCommander
|
|
31
|
+
npm run setup
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The setup command will:
|
|
35
|
+
- Install dependencies
|
|
36
|
+
- Build the server
|
|
37
|
+
- Configure Claude's desktop app
|
|
38
|
+
- Add MCP servers to Claude's config
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
The server provides these tool categories:
|
|
43
|
+
|
|
44
|
+
### Terminal Tools
|
|
45
|
+
- `execute_command`: Run commands with configurable timeout
|
|
46
|
+
- `read_output`: Get output from long-running commands
|
|
47
|
+
- `force_terminate`: Stop running command sessions
|
|
48
|
+
- `list_sessions`: View active command sessions
|
|
49
|
+
- `list_processes`: View system processes
|
|
50
|
+
- `kill_process`: Terminate processes by PID
|
|
51
|
+
- `block_command`/`unblock_command`: Manage command blacklist
|
|
52
|
+
|
|
53
|
+
### Filesystem Tools
|
|
54
|
+
- `read_file`/`write_file`: File operations
|
|
55
|
+
- `create_directory`/`list_directory`: Directory management
|
|
56
|
+
- `move_file`: Move/rename files
|
|
57
|
+
- `search_files`: Pattern-based file search
|
|
58
|
+
- `get_file_info`: File metadata
|
|
59
|
+
|
|
60
|
+
### Edit Tools
|
|
61
|
+
- `edit_block`: Apply surgical text replacements (best for changes <20% of file size)
|
|
62
|
+
- `write_file`: Complete file rewrites (best for large changes >20% or when edit_block fails)
|
|
63
|
+
|
|
64
|
+
Search/Replace Block Format:
|
|
65
|
+
```
|
|
66
|
+
filepath.ext
|
|
67
|
+
<<<<<<< SEARCH
|
|
68
|
+
existing code to replace
|
|
69
|
+
=======
|
|
70
|
+
new code to insert
|
|
71
|
+
>>>>>>> REPLACE
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
```
|
|
76
|
+
src/main.js
|
|
77
|
+
<<<<<<< SEARCH
|
|
78
|
+
console.log("old message");
|
|
79
|
+
=======
|
|
80
|
+
console.log("new message");
|
|
81
|
+
>>>>>>> REPLACE
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Handling Long-Running Commands
|
|
85
|
+
|
|
86
|
+
For commands that may take a while:
|
|
87
|
+
|
|
88
|
+
1. `execute_command` returns after timeout with initial output
|
|
89
|
+
2. Command continues in background
|
|
90
|
+
3. Use `read_output` with PID to get new output
|
|
91
|
+
4. Use `force_terminate` to stop if needed
|
|
92
|
+
|
|
93
|
+
## Model Context Protocol Integration
|
|
94
|
+
|
|
95
|
+
This project extends the MCP Filesystem Server to enable:
|
|
96
|
+
- Local server support in Claude Desktop
|
|
97
|
+
- Full system command execution
|
|
98
|
+
- Process management
|
|
99
|
+
- File operations
|
|
100
|
+
- Code editing with search/replace blocks
|
|
101
|
+
|
|
102
|
+
Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare class CommandManager {
|
|
2
|
+
private blockedCommands;
|
|
3
|
+
loadBlockedCommands(): Promise<void>;
|
|
4
|
+
saveBlockedCommands(): Promise<void>;
|
|
5
|
+
validateCommand(command: string): boolean;
|
|
6
|
+
blockCommand(command: string): Promise<boolean>;
|
|
7
|
+
unblockCommand(command: string): Promise<boolean>;
|
|
8
|
+
listBlockedCommands(): string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const commandManager: CommandManager;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { CONFIG_FILE } from './config.js';
|
|
3
|
+
class CommandManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.blockedCommands = new Set();
|
|
6
|
+
}
|
|
7
|
+
async loadBlockedCommands() {
|
|
8
|
+
try {
|
|
9
|
+
const configData = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
10
|
+
const config = JSON.parse(configData);
|
|
11
|
+
this.blockedCommands = new Set(config.blockedCommands);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
this.blockedCommands = new Set();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async saveBlockedCommands() {
|
|
18
|
+
try {
|
|
19
|
+
const config = {
|
|
20
|
+
blockedCommands: Array.from(this.blockedCommands)
|
|
21
|
+
};
|
|
22
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// Handle error if needed
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
validateCommand(command) {
|
|
29
|
+
const baseCommand = command.split(' ')[0].toLowerCase().trim();
|
|
30
|
+
return !this.blockedCommands.has(baseCommand);
|
|
31
|
+
}
|
|
32
|
+
async blockCommand(command) {
|
|
33
|
+
command = command.toLowerCase().trim();
|
|
34
|
+
if (this.blockedCommands.has(command)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
this.blockedCommands.add(command);
|
|
38
|
+
await this.saveBlockedCommands();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
async unblockCommand(command) {
|
|
42
|
+
command = command.toLowerCase().trim();
|
|
43
|
+
if (!this.blockedCommands.has(command)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
this.blockedCommands.delete(command);
|
|
47
|
+
await this.saveBlockedCommands();
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
listBlockedCommands() {
|
|
51
|
+
return Array.from(this.blockedCommands).sort();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export const commandManager = new CommandManager();
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import process from 'process';
|
|
3
|
+
export const CONFIG_FILE = path.join(process.cwd(), 'config.json');
|
|
4
|
+
export const LOG_FILE = path.join(process.cwd(), 'server.log');
|
|
5
|
+
export const ERROR_LOG_FILE = path.join(process.cwd(), 'error.log');
|
|
6
|
+
export const DEFAULT_COMMAND_TIMEOUT = 1000; // milliseconds
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { server } from './server.js';
|
|
4
|
+
import { commandManager } from './command-manager.js';
|
|
5
|
+
async function runServer() {
|
|
6
|
+
try {
|
|
7
|
+
// Handle uncaught exceptions
|
|
8
|
+
process.on('uncaughtException', async (error) => {
|
|
9
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
// Handle unhandled rejections
|
|
13
|
+
process.on('unhandledRejection', async (reason) => {
|
|
14
|
+
const errorMessage = reason instanceof Error ? reason.message : String(reason);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
// Load blocked commands from config file
|
|
19
|
+
await commandManager.loadBlockedCommands();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
24
|
+
process.stderr.write(JSON.stringify({
|
|
25
|
+
type: 'error',
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
message: `Failed to start server: ${errorMessage}`
|
|
28
|
+
}) + '\n');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
runServer().catch(async (error) => {
|
|
33
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
34
|
+
process.stderr.write(JSON.stringify({
|
|
35
|
+
type: 'error',
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
message: `Fatal error running server: ${errorMessage}`
|
|
38
|
+
}) + '\n');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { appendFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const LOG_FILE = join(__dirname, '..', 'server.log');
|
|
8
|
+
const ERROR_LOG_FILE = join(__dirname, '..', 'error.log');
|
|
9
|
+
export async function logToFile(message) {
|
|
10
|
+
const timestamp = new Date().toISOString();
|
|
11
|
+
const logMessage = `${timestamp} - ${message}\n`;
|
|
12
|
+
try {
|
|
13
|
+
appendFileSync(LOG_FILE, logMessage);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.error('Failed to write to log file:', err);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function logError(message) {
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
const logMessage = `${timestamp} - ERROR: ${message}\n`;
|
|
22
|
+
try {
|
|
23
|
+
appendFileSync(ERROR_LOG_FILE, logMessage);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error('Failed to write to error log file:', err);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
export declare const server: Server<{
|
|
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;
|
|
13
|
+
}, {
|
|
14
|
+
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">>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4
|
+
import { commandManager } from './command-manager.js';
|
|
5
|
+
import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, BlockCommandArgsSchema, UnblockCommandArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, EditBlockArgsSchema, } from './tools/schemas.js';
|
|
6
|
+
import { executeCommand, readOutput, forceTerminate, listSessions } from './tools/execute.js';
|
|
7
|
+
import { listProcesses, killProcess } from './tools/process.js';
|
|
8
|
+
import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js';
|
|
9
|
+
import { parseEditBlock, performSearchReplace } from './tools/edit.js';
|
|
10
|
+
export const server = new Server({
|
|
11
|
+
name: "secure-terminal-server",
|
|
12
|
+
version: "0.2.0",
|
|
13
|
+
}, {
|
|
14
|
+
capabilities: {
|
|
15
|
+
tools: {},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
19
|
+
return {
|
|
20
|
+
tools: [
|
|
21
|
+
// Terminal tools
|
|
22
|
+
{
|
|
23
|
+
name: "execute_command",
|
|
24
|
+
description: "Execute a terminal command with timeout. Command will continue running in background if it doesn't complete within timeout.",
|
|
25
|
+
inputSchema: zodToJsonSchema(ExecuteCommandArgsSchema),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "read_output",
|
|
29
|
+
description: "Read new output from a running terminal session.",
|
|
30
|
+
inputSchema: zodToJsonSchema(ReadOutputArgsSchema),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "force_terminate",
|
|
34
|
+
description: "Force terminate a running terminal session.",
|
|
35
|
+
inputSchema: zodToJsonSchema(ForceTerminateArgsSchema),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "list_sessions",
|
|
39
|
+
description: "List all active terminal sessions.",
|
|
40
|
+
inputSchema: zodToJsonSchema(ListSessionsArgsSchema),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "list_processes",
|
|
44
|
+
description: "List all running processes. Returns process information including PID, " +
|
|
45
|
+
"command name, CPU usage, and memory usage.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {},
|
|
49
|
+
required: [],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "kill_process",
|
|
54
|
+
description: "Terminate a running process by PID. Use with caution as this will " +
|
|
55
|
+
"forcefully terminate the specified process.",
|
|
56
|
+
inputSchema: zodToJsonSchema(KillProcessArgsSchema),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "block_command",
|
|
60
|
+
description: "Add a command to the blacklist. Once blocked, the command cannot be executed until unblocked.",
|
|
61
|
+
inputSchema: zodToJsonSchema(BlockCommandArgsSchema),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "unblock_command",
|
|
65
|
+
description: "Remove a command from the blacklist. Once unblocked, the command can be executed normally.",
|
|
66
|
+
inputSchema: zodToJsonSchema(UnblockCommandArgsSchema),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "list_blocked_commands",
|
|
70
|
+
description: "List all currently blocked commands.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {},
|
|
74
|
+
required: [],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
// Filesystem tools
|
|
78
|
+
{
|
|
79
|
+
name: "read_file",
|
|
80
|
+
description: "Read the complete contents of a file from the file system. " +
|
|
81
|
+
"Handles various text encodings and provides detailed error messages " +
|
|
82
|
+
"if the file cannot be read. Only works within allowed directories.",
|
|
83
|
+
inputSchema: zodToJsonSchema(ReadFileArgsSchema),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "read_multiple_files",
|
|
87
|
+
description: "Read the contents of multiple files simultaneously. " +
|
|
88
|
+
"Each file's content is returned with its path as a reference. " +
|
|
89
|
+
"Failed reads for individual files won't stop the entire operation. " +
|
|
90
|
+
"Only works within allowed directories.",
|
|
91
|
+
inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "write_file",
|
|
95
|
+
description: "Completely replace file contents. Best for large changes (>20% of file) or when edit_block fails. " +
|
|
96
|
+
"Use with caution as it will overwrite existing files. Only works within allowed directories.",
|
|
97
|
+
inputSchema: zodToJsonSchema(WriteFileArgsSchema),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "create_directory",
|
|
101
|
+
description: "Create a new directory or ensure a directory exists. Can create multiple " +
|
|
102
|
+
"nested directories in one operation. Only works within allowed directories.",
|
|
103
|
+
inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "list_directory",
|
|
107
|
+
description: "Get a detailed listing of all files and directories in a specified path. " +
|
|
108
|
+
"Results distinguish between files and directories with [FILE] and [DIR] prefixes. " +
|
|
109
|
+
"Only works within allowed directories.",
|
|
110
|
+
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "move_file",
|
|
114
|
+
description: "Move or rename files and directories. Can move files between directories " +
|
|
115
|
+
"and rename them in a single operation. Both source and destination must be " +
|
|
116
|
+
"within allowed directories.",
|
|
117
|
+
inputSchema: zodToJsonSchema(MoveFileArgsSchema),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "search_files",
|
|
121
|
+
description: "Recursively search for files and directories matching a pattern. " +
|
|
122
|
+
"Searches through all subdirectories from the starting path. " +
|
|
123
|
+
"Only searches within allowed directories.",
|
|
124
|
+
inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "get_file_info",
|
|
128
|
+
description: "Retrieve detailed metadata about a file or directory including size, " +
|
|
129
|
+
"creation time, last modified time, permissions, and type. " +
|
|
130
|
+
"Only works within allowed directories.",
|
|
131
|
+
inputSchema: zodToJsonSchema(GetFileInfoArgsSchema),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "list_allowed_directories",
|
|
135
|
+
description: "Returns the list of directories that this server is allowed to access.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {},
|
|
139
|
+
required: [],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "edit_block",
|
|
144
|
+
description: "Apply surgical text replacements to files. Best for small changes (<20% of file size). " +
|
|
145
|
+
"Multiple blocks can be used for separate changes. Will verify changes after application. " +
|
|
146
|
+
"Format: filepath, then <<<<<<< SEARCH, content to find, =======, new content, >>>>>>> REPLACE.",
|
|
147
|
+
inputSchema: zodToJsonSchema(EditBlockArgsSchema),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
153
|
+
try {
|
|
154
|
+
const { name, arguments: args } = request.params;
|
|
155
|
+
switch (name) {
|
|
156
|
+
// Terminal tools
|
|
157
|
+
case "execute_command": {
|
|
158
|
+
const parsed = ExecuteCommandArgsSchema.parse(args);
|
|
159
|
+
return executeCommand(parsed);
|
|
160
|
+
}
|
|
161
|
+
case "read_output": {
|
|
162
|
+
const parsed = ReadOutputArgsSchema.parse(args);
|
|
163
|
+
return readOutput(parsed);
|
|
164
|
+
}
|
|
165
|
+
case "force_terminate": {
|
|
166
|
+
const parsed = ForceTerminateArgsSchema.parse(args);
|
|
167
|
+
return forceTerminate(parsed);
|
|
168
|
+
}
|
|
169
|
+
case "list_sessions":
|
|
170
|
+
return listSessions();
|
|
171
|
+
case "list_processes":
|
|
172
|
+
return listProcesses();
|
|
173
|
+
case "kill_process": {
|
|
174
|
+
const parsed = KillProcessArgsSchema.parse(args);
|
|
175
|
+
return killProcess(parsed);
|
|
176
|
+
}
|
|
177
|
+
case "block_command": {
|
|
178
|
+
const parsed = BlockCommandArgsSchema.parse(args);
|
|
179
|
+
const blockResult = await commandManager.blockCommand(parsed.command);
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: blockResult }],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
case "unblock_command": {
|
|
185
|
+
const parsed = UnblockCommandArgsSchema.parse(args);
|
|
186
|
+
const unblockResult = await commandManager.unblockCommand(parsed.command);
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: unblockResult }],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
case "list_blocked_commands": {
|
|
192
|
+
const blockedCommands = await commandManager.listBlockedCommands();
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: "text", text: blockedCommands.join('\n') }],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Filesystem tools
|
|
198
|
+
case "edit_block": {
|
|
199
|
+
const parsed = EditBlockArgsSchema.parse(args);
|
|
200
|
+
const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
|
|
201
|
+
await performSearchReplace(filePath, searchReplace);
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
case "read_file": {
|
|
207
|
+
const parsed = ReadFileArgsSchema.parse(args);
|
|
208
|
+
const content = await readFile(parsed.path);
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: content }],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case "read_multiple_files": {
|
|
214
|
+
const parsed = ReadMultipleFilesArgsSchema.parse(args);
|
|
215
|
+
const results = await readMultipleFiles(parsed.paths);
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: "text", text: results.join("\n---\n") }],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case "write_file": {
|
|
221
|
+
const parsed = WriteFileArgsSchema.parse(args);
|
|
222
|
+
await writeFile(parsed.path, parsed.content);
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
case "create_directory": {
|
|
228
|
+
const parsed = CreateDirectoryArgsSchema.parse(args);
|
|
229
|
+
await createDirectory(parsed.path);
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
case "list_directory": {
|
|
235
|
+
const parsed = ListDirectoryArgsSchema.parse(args);
|
|
236
|
+
const entries = await listDirectory(parsed.path);
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text: entries.join('\n') }],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
case "move_file": {
|
|
242
|
+
const parsed = MoveFileArgsSchema.parse(args);
|
|
243
|
+
await moveFile(parsed.source, parsed.destination);
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
case "search_files": {
|
|
249
|
+
const parsed = SearchFilesArgsSchema.parse(args);
|
|
250
|
+
const results = await searchFiles(parsed.path, parsed.pattern);
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
case "get_file_info": {
|
|
256
|
+
const parsed = GetFileInfoArgsSchema.parse(args);
|
|
257
|
+
const info = await getFileInfo(parsed.path);
|
|
258
|
+
return {
|
|
259
|
+
content: [{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: Object.entries(info)
|
|
262
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
263
|
+
.join('\n')
|
|
264
|
+
}],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
case "list_allowed_directories": {
|
|
268
|
+
const directories = listAllowedDirectories();
|
|
269
|
+
return {
|
|
270
|
+
content: [{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: `Allowed directories:\n${directories.join('\n')}`
|
|
273
|
+
}],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
default:
|
|
277
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
284
|
+
isError: true,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CommandExecutionResult, ActiveSession } from './types.js';
|
|
2
|
+
export declare class TerminalManager {
|
|
3
|
+
private sessions;
|
|
4
|
+
executeCommand(command: string, timeoutMs?: number): Promise<CommandExecutionResult>;
|
|
5
|
+
getNewOutput(pid: number): string | null;
|
|
6
|
+
forceTerminate(pid: number): boolean;
|
|
7
|
+
listActiveSessions(): ActiveSession[];
|
|
8
|
+
}
|
|
9
|
+
export declare const terminalManager: TerminalManager;
|