run_shell_across_list
Execute shell commands across multiple items in parallel batches, substituting $item placeholders for batch processing tasks like linting or compiling files.
Instructions
Executes a shell command for each item in a previously created list. Commands run in batches of 10 parallel processes, with stdout and stderr streamed to separate files.
WHEN TO USE:
Running the same shell command across multiple files (e.g., linting, formatting, compiling)
Batch processing with command-line tools
Any operation where you need to execute shell commands on a collection of items
HOW IT WORKS:
Each item in the list is substituted into the command where $item appears
Commands run in batches of 10 at a time to avoid overwhelming the system
Output streams directly to files as the commands execute
This tool waits for all commands to complete before returning
AFTER COMPLETION:
Read the stdout files to check results
Check stderr files if you encounter errors or unexpected output
Files are named based on the item (e.g., "myfile.ts.stdout.txt")
VARIABLE SUBSTITUTION:
Use $item in your command - it will be replaced with each list item (properly shell-escaped)
Example: "cat $item" becomes "cat 'src/file.ts'" for item "src/file.ts"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| list_id | Yes | The list ID returned by create_list. This identifies which list of items to iterate over. | |
| command | Yes | Shell command to execute for each item. Use $item as a placeholder - it will be replaced with the current item value (properly escaped). Example: 'wc -l $item' or 'cat $item | grep TODO' |
Implementation Reference
- src/index.ts:514-592 (handler)The main execution logic for the tool. It fetches the list by ID, validates existence, creates a unique output directory, iterates over each item substituting $item in the command with shell-escaped item value, generates stdout/stderr file paths using safe filenames, batches and executes the commands via runInBatches, then returns a structured response listing all output file locations.async ({ list_id, command }) => { const items = lists.get(list_id); if (!items) { return { content: [ { type: "text", text: `Error: No list found with ID "${list_id}". Please call create_list first to create a list of items, then use the returned ID with this tool.`, }, ], isError: true, }; } // Create output directory const runId = randomUUID(); const runDir = join(outputDir, runId); await mkdir(runDir, { recursive: true }); const results: Array<{ item: string; files: OutputFiles }> = []; const tasks: Array<{ command: string; stdoutFile: string; stderrFile: string; }> = []; for (let i = 0; i < items.length; i++) { const item = items[i]; // Replace $item with the actual item value (properly escaped) const escapedItem = item.replace(/'/g, "'\\''"); const expandedCommand = command.replace(/\$item/g, `'${escapedItem}'`); const safeFilename = toSafeFilename(item); const stdoutFile = join(runDir, `${safeFilename}.stdout.txt`); const stderrFile = join(runDir, `${safeFilename}.stderr.txt`); tasks.push({ command: expandedCommand, stdoutFile, stderrFile, }); results.push({ item, files: { stdout: stdoutFile, stderr: stderrFile }, }); } // Run commands in batches of 10 await runInBatches(tasks); // Build prose response const fileList = results .map( (r) => `- ${r.item}: stdout at "${r.files.stdout}", stderr at "${r.files.stderr}"`, ) .join("\n"); const numBatches = Math.ceil(items.length / BATCH_SIZE); return { content: [ { type: "text", text: `Completed ${results.length} shell commands in ${numBatches} batch(es) of up to ${BATCH_SIZE} parallel commands each. Output has been streamed to files. OUTPUT FILES: ${fileList} NEXT STEPS: 1. Read the stdout files to check the results of each command 2. If there are errors, check the corresponding stderr files for details All commands have completed and output files are ready to read.`, }, ], }; },
- src/index.ts:501-512 (schema)Zod input schema validating the list_id (string) and command (string) parameters for the tool.inputSchema: { list_id: z .string() .describe( "The list ID returned by create_list. This identifies which list of items to iterate over.", ), command: z .string() .describe( "Shell command to execute for each item. Use $item as a placeholder - it will be replaced with the current item value (properly escaped). Example: 'wc -l $item' or 'cat $item | grep TODO'", ), },
- src/index.ts:476-478 (registration)Registration of the 'run_shell_across_list' tool on the MCP server, including the tool name.// Tool: run_shell_across_list server.registerTool( "run_shell_across_list",
- src/index.ts:114-132 (helper)Helper function that executes shell command tasks in configurable batches (BATCH_SIZE, default 10) using Promise.all for parallelism, calling runCommandToFiles for each to stream output to files with optional timeout.async function runInBatches( tasks: Array<{ command: string; stdoutFile: string; stderrFile: string; timeout?: number; }>, ): Promise<void> { for (let i = 0; i < tasks.length; i += BATCH_SIZE) { const batch = tasks.slice(i, i + BATCH_SIZE); await Promise.all( batch.map((task) => runCommandToFiles(task.command, task.stdoutFile, task.stderrFile, { timeout: task.timeout, }), ), ); } }