Claude Desktop Commander MCP

  • src
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema, ListToolsRequestSchema, type CallToolRequest, } from "@modelcontextprotocol/sdk/types.js"; import { zodToJsonSchema } from "zod-to-json-schema"; import { commandManager } from './command-manager.js'; import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, BlockCommandArgsSchema, UnblockCommandArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, EditBlockArgsSchema, } from './tools/schemas.js'; import { executeCommand, readOutput, forceTerminate, listSessions } from './tools/execute.js'; import { listProcesses, killProcess } from './tools/process.js'; import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js'; import { parseEditBlock, performSearchReplace } from './tools/edit.js'; import { VERSION } from './version.js'; export const server = new Server( { name: "desktop-commander", version: VERSION, }, { capabilities: { tools: {}, }, }, ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Terminal tools { name: "execute_command", description: "Execute a terminal command with timeout. Command will continue running in background if it doesn't complete within timeout.", inputSchema: zodToJsonSchema(ExecuteCommandArgsSchema), }, { name: "read_output", description: "Read new output from a running terminal session.", inputSchema: zodToJsonSchema(ReadOutputArgsSchema), }, { name: "force_terminate", description: "Force terminate a running terminal session.", inputSchema: zodToJsonSchema(ForceTerminateArgsSchema), }, { name: "list_sessions", description: "List all active terminal sessions.", inputSchema: zodToJsonSchema(ListSessionsArgsSchema), }, { name: "list_processes", description: "List all running processes. Returns process information including PID, " + "command name, CPU usage, and memory usage.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "kill_process", description: "Terminate a running process by PID. Use with caution as this will " + "forcefully terminate the specified process.", inputSchema: zodToJsonSchema(KillProcessArgsSchema), }, { name: "block_command", description: "Add a command to the blacklist. Once blocked, the command cannot be executed until unblocked.", inputSchema: zodToJsonSchema(BlockCommandArgsSchema), }, { name: "unblock_command", description: "Remove a command from the blacklist. Once unblocked, the command can be executed normally.", inputSchema: zodToJsonSchema(UnblockCommandArgsSchema), }, { name: "list_blocked_commands", description: "List all currently blocked commands.", inputSchema: { type: "object", properties: {}, required: [], }, }, // Filesystem tools { name: "read_file", description: "Read the complete contents of a file from the file system. " + "Handles various text encodings and provides detailed error messages " + "if the file cannot be read. Only works within allowed directories.", inputSchema: zodToJsonSchema(ReadFileArgsSchema), }, { name: "read_multiple_files", description: "Read the contents of multiple files simultaneously. " + "Each file's content is returned with its path as a reference. " + "Failed reads for individual files won't stop the entire operation. " + "Only works within allowed directories.", inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema), }, { name: "write_file", description: "Completely replace file contents. Best for large changes (>20% of file) or when edit_block fails. " + "Use with caution as it will overwrite existing files. Only works within allowed directories.", inputSchema: zodToJsonSchema(WriteFileArgsSchema), }, { name: "create_directory", description: "Create a new directory or ensure a directory exists. Can create multiple " + "nested directories in one operation. Only works within allowed directories.", inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema), }, { name: "list_directory", description: "Get a detailed listing of all files and directories in a specified path. " + "Results distinguish between files and directories with [FILE] and [DIR] prefixes. " + "Only works within allowed directories.", inputSchema: zodToJsonSchema(ListDirectoryArgsSchema), }, { name: "move_file", description: "Move or rename files and directories. Can move files between directories " + "and rename them in a single operation. Both source and destination must be " + "within allowed directories.", inputSchema: zodToJsonSchema(MoveFileArgsSchema), }, { name: "search_files", description: "Recursively search for files and directories matching a pattern. " + "Searches through all subdirectories from the starting path. " + "Only searches within allowed directories.", inputSchema: zodToJsonSchema(SearchFilesArgsSchema), }, { name: "get_file_info", description: "Retrieve detailed metadata about a file or directory including size, " + "creation time, last modified time, permissions, and type. " + "Only works within allowed directories.", inputSchema: zodToJsonSchema(GetFileInfoArgsSchema), }, { name: "list_allowed_directories", description: "Returns the list of directories that this server is allowed to access.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "edit_block", description: "Apply surgical text replacements to files. Best for small changes (<20% of file size). " + "Multiple blocks can be used for separate changes. Will verify changes after application. " + "Format: filepath, then <<<<<<< SEARCH, content to find, =======, new content, >>>>>>> REPLACE.", inputSchema: zodToJsonSchema(EditBlockArgsSchema), }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { try { const { name, arguments: args } = request.params; switch (name) { // Terminal tools case "execute_command": { const parsed = ExecuteCommandArgsSchema.parse(args); return executeCommand(parsed); } case "read_output": { const parsed = ReadOutputArgsSchema.parse(args); return readOutput(parsed); } case "force_terminate": { const parsed = ForceTerminateArgsSchema.parse(args); return forceTerminate(parsed); } case "list_sessions": return listSessions(); case "list_processes": return listProcesses(); case "kill_process": { const parsed = KillProcessArgsSchema.parse(args); return killProcess(parsed); } case "block_command": { const parsed = BlockCommandArgsSchema.parse(args); const blockResult = await commandManager.blockCommand(parsed.command); return { content: [{ type: "text", text: blockResult }], }; } case "unblock_command": { const parsed = UnblockCommandArgsSchema.parse(args); const unblockResult = await commandManager.unblockCommand(parsed.command); return { content: [{ type: "text", text: unblockResult }], }; } case "list_blocked_commands": { const blockedCommands = await commandManager.listBlockedCommands(); return { content: [{ type: "text", text: blockedCommands.join('\n') }], }; } // Filesystem tools case "edit_block": { const parsed = EditBlockArgsSchema.parse(args); const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent); await performSearchReplace(filePath, searchReplace); return { content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }], }; } case "read_file": { const parsed = ReadFileArgsSchema.parse(args); const content = await readFile(parsed.path); return { content: [{ type: "text", text: content }], }; } case "read_multiple_files": { const parsed = ReadMultipleFilesArgsSchema.parse(args); const results = await readMultipleFiles(parsed.paths); return { content: [{ type: "text", text: results.join("\n---\n") }], }; } case "write_file": { const parsed = WriteFileArgsSchema.parse(args); await writeFile(parsed.path, parsed.content); return { content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }], }; } case "create_directory": { const parsed = CreateDirectoryArgsSchema.parse(args); await createDirectory(parsed.path); return { content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }], }; } case "list_directory": { const parsed = ListDirectoryArgsSchema.parse(args); const entries = await listDirectory(parsed.path); return { content: [{ type: "text", text: entries.join('\n') }], }; } case "move_file": { const parsed = MoveFileArgsSchema.parse(args); await moveFile(parsed.source, parsed.destination); return { content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }], }; } case "search_files": { const parsed = SearchFilesArgsSchema.parse(args); const results = await searchFiles(parsed.path, parsed.pattern); return { content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }], }; } case "get_file_info": { const parsed = GetFileInfoArgsSchema.parse(args); const info = await getFileInfo(parsed.path); return { content: [{ type: "text", text: Object.entries(info) .map(([key, value]) => `${key}: ${value}`) .join('\n') }], }; } case "list_allowed_directories": { const directories = listAllowedDirectories(); return { content: [{ type: "text", text: `Allowed directories:\n${directories.join('\n')}` }], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true, }; } });