import { z } from 'zod';
import { exec, spawn } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* Shell Tools
* Provides unrestricted shell command execution with full system access
* Supports sudo, background processes, environment variables, and streaming output
*/
// ========== TOOL SCHEMAS ==========
export const executeCommandSchema = z.object({
command: z.string().describe('The shell command to execute'),
cwd: z.string().optional().describe('Working directory for the command (defaults to current directory)'),
env: z.record(z.string()).optional().describe('Environment variables to set for the command'),
timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000ms)'),
shell: z.string().optional().describe('Shell to use (default: /bin/bash)'),
sudo: z.boolean().default(false).describe('Execute with sudo privileges')
});
export const executeStreamingCommandSchema = z.object({
command: z.string().describe('The shell command to execute with streaming output'),
cwd: z.string().optional().describe('Working directory for the command'),
env: z.record(z.string()).optional().describe('Environment variables to set for the command'),
shell: z.string().optional().describe('Shell to use (default: /bin/bash)'),
sudo: z.boolean().default(false).describe('Execute with sudo privileges')
});
// ========== TOOL IMPLEMENTATIONS ==========
type ToolResponse = {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
};
export async function executeCommand(args: z.infer<typeof executeCommandSchema>): Promise<ToolResponse> {
try {
// For sudo commands, wrap the entire command in a shell to ensure all parts run with sudo
const command = args.sudo
? `sudo bash -c ${JSON.stringify(args.command)}`
: args.command;
const timeout = args.timeout || 30000;
const { stdout, stderr } = await execAsync(command, {
cwd: args.cwd,
env: args.env ? { ...process.env, ...args.env } : process.env,
shell: args.shell || '/bin/bash',
timeout: timeout,
maxBuffer: 10 * 1024 * 1024 // 10MB buffer
});
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
success: true,
command: command,
stdout: stdout,
stderr: stderr,
exitCode: 0
}, null, 2)
}
]
};
} catch (error: any) {
// Even if command fails, return the output
const command = args.sudo
? `sudo bash -c ${JSON.stringify(args.command)}`
: args.command;
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
success: false,
command: command,
stdout: error.stdout || '',
stderr: error.stderr || error.message,
exitCode: error.code || 1,
signal: error.signal,
timedOut: error.killed && error.signal === 'SIGTERM'
}, null, 2)
}
],
isError: true
};
}
}
export async function executeStreamingCommand(args: z.infer<typeof executeStreamingCommandSchema>): Promise<ToolResponse> {
try {
// For sudo commands, wrap the entire command in a shell to ensure all parts run with sudo
const command = args.sudo
? `sudo bash -c ${JSON.stringify(args.command)}`
: args.command;
return new Promise((resolve) => {
const child = spawn(command, {
cwd: args.cwd,
env: args.env ? { ...process.env, ...args.env } : process.env,
shell: args.shell || '/bin/bash',
stdio: ['ignore', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
child.stdout?.on('data', (data) => {
stdout += data.toString();
});
child.stderr?.on('data', (data) => {
stderr += data.toString();
});
child.on('close', (code, signal) => {
resolve({
content: [
{
type: "text" as const,
text: JSON.stringify({
success: code === 0,
command: command,
stdout: stdout,
stderr: stderr,
exitCode: code,
signal: signal
}, null, 2)
}
],
isError: code !== 0
});
});
child.on('error', (error) => {
resolve({
content: [
{
type: "text" as const,
text: JSON.stringify({
success: false,
command: command,
stdout: stdout,
stderr: stderr + '\n' + error.message,
error: error.message
}, null, 2)
}
],
isError: true
});
});
});
} catch (error) {
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}
],
isError: true
};
}
}
// ========== TOOL DEFINITIONS FOR MCP ==========
export const shellTools = [
{
name: 'shell_execute',
description: `Execute a shell command with full system access. Supports sudo for privileged operations.
Use this for:
- Running system commands (apt, brew, yum, dnf, pacman, etc.)
- Installing packages and dependencies
- Managing services (systemctl, service, etc.)
- File operations via shell utilities
- Network operations (curl, wget, ssh, scp, etc.)
- Git operations
- Docker commands
- Any other shell command
The command runs in /bin/bash by default. Output is captured and returned after completion.
For long-running commands, use shell_execute_streaming instead.`,
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The shell command to execute'
},
cwd: {
type: 'string',
description: 'Working directory for the command (defaults to current directory)'
},
env: {
type: 'object',
additionalProperties: { type: 'string' },
description: 'Environment variables to set for the command'
},
timeout: {
type: 'number',
description: 'Timeout in milliseconds (default: 30000ms)'
},
shell: {
type: 'string',
description: 'Shell to use (default: /bin/bash)'
},
sudo: {
type: 'boolean',
default: false,
description: 'Execute with sudo privileges. Use this for system-level operations like installing packages.'
}
},
required: ['command']
}
},
{
name: 'shell_execute_streaming',
description: `Execute a long-running shell command with streaming output support. Captures output as it's produced.
Use this for:
- Build processes (npm build, cargo build, etc.)
- Long-running scripts
- Services that produce continuous output
- Commands that take significant time to complete
The command runs in /bin/bash by default. Output is streamed and returned when complete.`,
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'The shell command to execute with streaming output'
},
cwd: {
type: 'string',
description: 'Working directory for the command'
},
env: {
type: 'object',
additionalProperties: { type: 'string' },
description: 'Environment variables to set for the command'
},
shell: {
type: 'string',
description: 'Shell to use (default: /bin/bash)'
},
sudo: {
type: 'boolean',
default: false,
description: 'Execute with sudo privileges'
}
},
required: ['command']
}
}
];