shell_ps
Display system process status for monitoring and diagnostics. Allows users to view active processes with controlled execution via Shell-MCP server, ensuring secure and efficient command handling.
Instructions
Show process status
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | Command arguments |
Implementation Reference
- src/config/allowlist.ts:43-48 (registration)Registers the 'shell.ps' command configuration. This entry defines the tool 'ps' (after stripping 'shell.') with description, allowed arguments, and timeout. This is the primary registration point for the tool.'shell.ps': { command: 'ps', description: 'Show process status', allowedArgs: ['-e', '-f', '-u', '--help'], timeout: 5000 },
- src/mcp/server.ts:105-147 (schema)Dynamically generates the tool list and schemas for all allowed commands, including 'ps' from 'shell.ps'. Defines the input schema as an object with 'args' array of strings.private processTools(): Tool[] { const tools: Tool[] = []; const processedNames = new Set<string>(); this.logger.debug('Starting to process tool list', { commandCount: Object.keys(allowedCommands).length }); Object.entries(allowedCommands).forEach(([name, config]) => { const toolName = name.replace('shell.', ''); this.logger.debug('Processing command', { originalName: name, toolName, isProcessed: processedNames.has(toolName) }); if (!processedNames.has(toolName)) { processedNames.add(toolName); tools.push({ name: toolName, description: config.description, inputSchema: { type: "object", properties: { args: { type: "array", items: { type: "string" }, description: "Command arguments" } } } }); } }); this.logger.debug('Tool list processing completed', { toolCount: tools.length, processedNames: Array.from(processedNames) }); this.validateToolNames(tools); return tools; }
- src/core/executor.ts:31-139 (handler)Core execution logic for all shell tools, including 'shell.ps'/'ps'. Strips 'shell.' prefix, spawns the 'ps' process with arguments, handles streaming stdout, timeouts, and errors.async execute( command: string, args: string[] = [], options: ExecuteOptions = {} ): Promise<{ stdout: Readable }> { const commandKey = `${command} ${args.join(' ')}`; try { // Check security await this.securityChecker.validateCommand(command, args, options); // Check cache const cached = this.cache.get(commandKey); if (cached) { this.logger.debug('Using cached command result', { command, args }); return this.createStreamFromCache(cached); } // Remove 'shell.' prefix for execution const baseCommand = command.replace('shell.', ''); // Execute command this.logger.debug('Starting command execution', { command, args, options }); const childProcess = spawn(baseCommand, args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: options.timeout, cwd: options.cwd, env: { ...process.env, ...options.env }, signal: options.signal }); this.currentProcess = childProcess; // Error handling childProcess.on('error', (error: Error) => { this.logger.error('Command execution error', { command, args, error: error.message }); throw new ToolError( 'PROCESS_ERROR', 'Command execution error', { command, args, error: error.message } ); }); // Timeout handling if (options.timeout) { setTimeout(() => { if (childProcess.exitCode === null) { this.logger.warn('Command execution timeout', { command, args, timeout: options.timeout }); childProcess.kill(); throw new ToolError( 'TIMEOUT', 'Command execution timeout', { command, args, timeout: options.timeout } ); } }, options.timeout); } if (!childProcess.stdout) { throw new ToolError( 'STREAM_ERROR', 'Unable to get command output stream', { command, args } ); } // Monitor process status childProcess.on('exit', (code, signal) => { this.logger.debug('Command execution completed', { command, args, exitCode: code, signal }); }); return { stdout: childProcess.stdout }; } catch (error) { this.logger.error('Command execution failed', { command, args, error: error instanceof Error ? error.message : String(error) }); throw new ToolError( 'EXECUTION_ERROR', 'Command execution failed', { command, args, error: error instanceof Error ? error.message : String(error) } ); } }
- src/mcp/server.ts:172-247 (handler)MCP CallTool request handler. For tool 'ps', maps to 'shell.ps', retrieves config, validates, executes via CommandExecutor, collects stdout, and returns as text content.this.server.setRequestHandler(CallToolRequestSchema, async (request, extra: unknown) => { const ext = extra as Extra; if (!request.params?.name) { throw new ToolError('MISSING_COMMAND', 'Command name is required'); } const command = String(request.params.name); const fullCommand = command.startsWith('shell.') ? command : `shell.${command}`; if (!(fullCommand in allowedCommands)) { throw new ToolError('COMMAND_NOT_FOUND', 'Command not found', { command }); } const config = allowedCommands[fullCommand]; const args = Array.isArray(request.params.arguments?.args) ? request.params.arguments.args.map(String) : []; const context: CommandContext = { requestId: ext.id || 'unknown', command, args, timeout: config.timeout, workDir: config.workDir, env: config.env }; this.logger.info('Starting command execution', context); try { this.validator.validateCommand(command, args); this.logger.debug('Command validation passed', { ...context, config }); const stream = await this.executor.execute(command, args, { timeout: config.timeout, cwd: config.workDir, env: config.env }); ext.onCancel?.(() => { this.logger.info('Received cancel request', context); this.executor.interrupt(); }); const output = await this.collectOutput(stream); this.logger.info('Command execution completed', { ...context, outputLength: output.length }); return { content: [{ type: "text", text: output }] }; } catch (error) { this.logger.error('Command execution failed', { ...context, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); throw new ToolError( 'EXECUTION_FAILED', `Command execution failed: ${error instanceof Error ? error.message : String(error)}`, context ); } });
- src/core/validator.ts:11-59 (helper)Validates command and arguments against allowlist config. For 'ps', ensures args like '-e', '-f' etc. are allowed, rejects invalid ones.validateCommand( command: string, args: string[] = [], options: CommandOptions = {} ): void { console.log('Validating command:', { command, args, baseCommand: command.replace('shell.', ''), fullCommand: `shell.${command.replace('shell.', '')}`, config: allowedCommands[`shell.${command.replace('shell.', '')}`] }); const baseCommand = command.replace('shell.', ''); if (!(`shell.${baseCommand}` in allowedCommands)) { throw new Error(`Command not allowed: ${command}`); } const config = allowedCommands[`shell.${baseCommand}`]; const allowedArgs = config.allowedArgs || []; console.log('Checking args:', { allowedArgs, hasWildcard: allowedArgs.includes('*') }); args.forEach(arg => { if (arg.startsWith('-')) { if (!allowedArgs.includes(arg)) { console.log('Invalid option:', arg); throw new Error(`Invalid argument: ${arg}`); } } else if (!allowedArgs.includes('*')) { console.log('Path not allowed:', arg); throw new Error(`Invalid argument: ${arg}`); } else { // 檢查路徑參數 this.validatePath(arg); } }); // 檢查超時設定 if (options.timeout && options.timeout > securityConfig.defaultTimeout) { throw new Error(`Timeout exceeds maximum allowed value`); } }