execute-command
Run shell commands directly from LLMs with explicit user permission via PermShell MCP. Specify command, directory, timeout, and input for secure execution.
Instructions
Execute a shell command
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | The shell command to execute | |
| cwd | No | Directory to run the command in (defaults to system temp directory) | |
| stdin | No | Optional input to provide to the command's standard input | |
| timeout | No | Timeout in milliseconds after which the process is killed |
Implementation Reference
- src/tools/shell-tools.ts:19-99 (handler)The async handler function that implements the execute-command tool logic: sanitizes the command, requests user permission via notifications (retry up to 5 times), executes the command using execAsync with options (cwd, timeout, stdin), and returns formatted text content with stdout/stderr or error details.async ({ command, cwd, timeout, stdin }) => { try { // Sanitize command for display const sanitizedCommand = sanitizeCommand(command); // Determine execution directory const execDir = cwd ? path.resolve(cwd) : os.tmpdir(); // Request permission with meaningful context let permissionMessage = `Execute: ${sanitizedCommand} (in ${execDir})`; if (stdin !== undefined) { permissionMessage += " with provided standard input"; } // Ask for permission - retry up to 5 times let permitted = false; let tries = 0; while (!permitted && tries < 5) { tries++; permitted = await askPermission(permissionMessage); if (!permitted && tries >= 5) { return { isError: true, content: [{ type: "text" as const, text: "Permission denied by user after multiple attempts" }] }; } } // Set up execution options const execOptions: ExecOptionsWithInput = { cwd: execDir, timeout: timeout || 30000, // Default 30 seconds timeout }; // Add stdin if provided if (stdin !== undefined) { execOptions.input = stdin; } // Execute the command const { stdout, stderr } = await execAsync(command, execOptions); return { content: [ { type: "text" as const, text: stdout || "Command executed successfully with no output" }, ...(stderr ? [{ type: "text" as const, text: `Standard Error:\n${stderr}` }] : []) ] }; } catch (error) { // Handle execution errors const execError = error as any; const stdout = execError.stdout || ''; const stderr = execError.stderr || ''; const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [ ...(stdout ? [{ type: "text" as const, text: stdout }] : []), { type: "text" as const, text: `Error executing command: ${errorMessage}\n${stderr}` } ] }; } }
- src/tools/shell-tools.ts:13-18 (schema)Zod schema defining input parameters: command (required string), cwd (optional string), timeout (optional number), stdin (optional string).{ command: z.string().describe("The shell command to execute"), cwd: z.string().optional().describe("Directory to run the command in (defaults to system temp directory)"), timeout: z.number().optional().describe("Timeout in milliseconds after which the process is killed"), stdin: z.string().optional().describe("Optional input to provide to the command's standard input") },
- src/tools/shell-tools.ts:10-100 (registration)Registers the 'execute-command' tool on the MCP server using server.tool(), including description, input schema, and inline handler.server.tool( "execute-command", "Execute a shell command", { command: z.string().describe("The shell command to execute"), cwd: z.string().optional().describe("Directory to run the command in (defaults to system temp directory)"), timeout: z.number().optional().describe("Timeout in milliseconds after which the process is killed"), stdin: z.string().optional().describe("Optional input to provide to the command's standard input") }, async ({ command, cwd, timeout, stdin }) => { try { // Sanitize command for display const sanitizedCommand = sanitizeCommand(command); // Determine execution directory const execDir = cwd ? path.resolve(cwd) : os.tmpdir(); // Request permission with meaningful context let permissionMessage = `Execute: ${sanitizedCommand} (in ${execDir})`; if (stdin !== undefined) { permissionMessage += " with provided standard input"; } // Ask for permission - retry up to 5 times let permitted = false; let tries = 0; while (!permitted && tries < 5) { tries++; permitted = await askPermission(permissionMessage); if (!permitted && tries >= 5) { return { isError: true, content: [{ type: "text" as const, text: "Permission denied by user after multiple attempts" }] }; } } // Set up execution options const execOptions: ExecOptionsWithInput = { cwd: execDir, timeout: timeout || 30000, // Default 30 seconds timeout }; // Add stdin if provided if (stdin !== undefined) { execOptions.input = stdin; } // Execute the command const { stdout, stderr } = await execAsync(command, execOptions); return { content: [ { type: "text" as const, text: stdout || "Command executed successfully with no output" }, ...(stderr ? [{ type: "text" as const, text: `Standard Error:\n${stderr}` }] : []) ] }; } catch (error) { // Handle execution errors const execError = error as any; const stdout = execError.stdout || ''; const stderr = execError.stderr || ''; const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [ ...(stdout ? [{ type: "text" as const, text: stdout }] : []), { type: "text" as const, text: `Error executing command: ${errorMessage}\n${stderr}` } ] }; } } );
- src/index.ts:14-15 (registration)In main server initialization, calls registerShellTools(server), which registers shell tools including 'execute-command'.// Register tools registerShellTools(server);
- src/utils/helpers.ts:6-7 (helper)execAsync: Promisified child_process.exec function used by the handler to execute shell commands asynchronously.// Promisify exec for async/await usage export const execAsync = promisify(exec);