shell_nslookup
Query DNS records efficiently using command arguments. Integrated with Shell-MCP for secure execution, enabling precise DNS lookups with controlled resources and timeouts.
Instructions
Query DNS records
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | Command arguments |
Input Schema (JSON Schema)
{
"properties": {
"args": {
"description": "Command arguments",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
Implementation Reference
- src/config/allowlist.ts:160-169 (schema)Configuration schema defining the nslookup command, its description, allowed arguments for input validation, and execution timeout.'shell.nslookup': { command: 'nslookup', description: 'Query DNS records', allowedArgs: [ '-type=*', // record type '-query=*',// query type '*' // domain names ], timeout: 5000 },
- src/mcp/server.ts:105-147 (registration)Dynamically generates the list of available tools, including 'nslookup' from 'shell.nslookup' config, with standardized input schema.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/mcp/server.ts:172-248 (handler)MCP CallTool request handler that dispatches the tool call (e.g., nslookup) to the appropriate shell command using the allowlist configuration, handles validation and execution.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/executor.ts:31-122 (handler)Core execution logic that spawns the actual shell process for commands like nslookup, handling streams, caching, security checks, and timeouts.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) {
- src/core/validator.ts:11-59 (helper)Validates command name and arguments against the allowlist before execution, ensuring only permitted options and paths are used.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`); } }