claude_code
Send a task to an autonomous code agent that reads/writes files, runs shell commands, and searches codebases to handle complex software engineering tasks end-to-end.
Instructions
Send a task to an autonomous Claude Code agent. It reads/writes files, runs shell commands, searches codebases, and handles complex software engineering tasks end-to-end. Returns the result text plus a session_id for follow-ups via claude_code_reply.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | Task or question for Claude Code | |
| run_id | No | Workflow run ID — groups related agent calls into one timeline. Auto-generated if omitted; returned in every response for propagation. | |
| cwd | No | Working directory (overrides CLAUDE_CWD) | |
| model | No | Model override (e.g. "sonnet", "opus", "haiku") | |
| tools | No | Restrict available tools to this list (intersects with server-level restriction) | |
| disallowedTools | No | Additional tools to block (unions with server-level blacklist) | |
| additionalDirs | No | Extra directories the agent can access for this invocation | |
| plugins | No | Additional plugin paths to load for this invocation (unions with server-level plugins) | |
| effort | No | Thinking effort override | |
| permissionMode | No | Permission mode override (can only tighten, never loosen) | |
| maxTurns | No | Max conversation turns | |
| maxBudgetUsd | No | Max spend in USD | |
| systemPrompt | No | Additional system prompt (appended to server default) |
Implementation Reference
- src/index.ts:42-108 (registration)Entry point that registers the 'claude_code' tool (and derived tools like _reply, _timeline, _transcript, _report) on the MCP server. The tool name defaults to 'claude_code' when neither CLAUDE_TOOL_NAME env var is set nor sanitizeToolName empties it.
function startMcpServer() { const CONFIG = buildOctopusConfig(); const TOOL_NAME = sanitizeToolName(envStr("CLAUDE_TOOL_NAME") || "claude_code"); const REPLY_TOOL_NAME = `${TOOL_NAME}_reply`; const TIMELINE_TOOL_NAME = `${TOOL_NAME}_timeline`; const SERVER_NAME = envStr("CLAUDE_SERVER_NAME") || "claude-octopus"; const FACTORY_ONLY = envBool("CLAUDE_FACTORY_ONLY", false); const DEFAULT_DESCRIPTION = [ "Send a task to an autonomous Claude Code agent.", "It reads/writes files, runs shell commands, searches codebases,", "and handles complex software engineering tasks end-to-end.", `Returns the result text plus a session_id for follow-ups via ${REPLY_TOOL_NAME}.`, ].join(" "); const TOOL_DESCRIPTION = envStr("CLAUDE_DESCRIPTION") || DEFAULT_DESCRIPTION; const server = new McpServer({ name: SERVER_NAME, version: PKG_VERSION }); if (!FACTORY_ONLY) { registerQueryTools( server, CONFIG.sdkOptions, TOOL_NAME, TOOL_DESCRIPTION, SERVER_NAME, CONFIG.timeline, ); registerTimelineTool( server, TOOL_NAME, CONFIG.timeline, CONFIG.sdkOptions.persistSession !== false, ); registerReportTool( server, TOOL_NAME, CONFIG.timeline, CONFIG.sdkOptions.persistSession !== false, ); } if (FACTORY_ONLY) { registerFactoryTool(server); } async function main() { const transport = new StdioServerTransport(); await server.connect(transport); const toolList = FACTORY_ONLY ? ["create_claude_code_mcp"] : [ TOOL_NAME, ...(CONFIG.sdkOptions.persistSession !== false ? [REPLY_TOOL_NAME] : []), TIMELINE_TOOL_NAME, ...(CONFIG.sdkOptions.persistSession !== false ? [`${TOOL_NAME}_transcript`] : []), `${TOOL_NAME}_report`, ]; console.error(`${SERVER_NAME}: running on stdio (tools: ${toolList.join(", ")})`); } main().catch((error) => { console.error(`${SERVER_NAME}: fatal:`, error); process.exit(1); }); } - src/tools/query.ts:32-123 (handler)The actual handler for the 'claude_code' tool (and its _reply variant). registerQueryTools registers the main query tool (named by toolName param, default 'claude_code') with z.inputSchema and an async handler that calls runQuery() from query-helpers.ts.
export function registerQueryTools( server: McpServer, baseOptions: Options, toolName: string, toolDescription: string, agentName: string, timelineConfig: TimelineConfig, ) { const replyToolName = `${toolName}_reply`; server.registerTool(toolName, { description: toolDescription, inputSchema: z.object({ prompt: z.string().describe("Task or question for Claude Code"), run_id: z.string().optional().describe("Workflow run ID — groups related agent calls into one timeline. Auto-generated if omitted; returned in every response for propagation."), cwd: z.string().optional().describe("Working directory (overrides CLAUDE_CWD)"), model: z.string().optional().describe('Model override (e.g. "sonnet", "opus", "haiku")'), tools: z.array(z.string()).optional().describe("Restrict available tools to this list (intersects with server-level restriction)"), disallowedTools: z.array(z.string()).optional().describe("Additional tools to block (unions with server-level blacklist)"), additionalDirs: z.array(z.string()).optional().describe("Extra directories the agent can access for this invocation"), plugins: z.array(z.string()).optional().describe("Additional plugin paths to load for this invocation (unions with server-level plugins)"), effort: z.enum(["low", "medium", "high", "max"]).optional().describe("Thinking effort override"), permissionMode: z.enum(["default", "acceptEdits", "plan"]).optional().describe("Permission mode override (can only tighten, never loosen)"), maxTurns: z.number().int().positive().optional().describe("Max conversation turns"), maxBudgetUsd: z.number().positive().optional().describe("Max spend in USD"), systemPrompt: z.string().optional().describe("Additional system prompt (appended to server default)"), }), }, async ({ prompt, run_id, cwd, model, tools, disallowedTools, additionalDirs, plugins, effort, permissionMode, maxTurns, maxBudgetUsd, systemPrompt }) => { const runId = run_id || randomUUID(); const t0 = new Date().toISOString(); const baseCwd = baseOptions.cwd || process.cwd(); const effectiveCwd = cwd ? (isAbsolute(cwd) || isDescendantPath(cwd, baseCwd)) ? resolve(baseCwd, cwd) : baseCwd : baseCwd; try { const result = await runQuery(prompt, { cwd, model, tools, disallowedTools, additionalDirs, plugins, effort, permissionMode, maxTurns, maxBudgetUsd, systemPrompt, }, baseOptions); await recordTimeline(agentName, prompt, runId, t0, result, effectiveCwd, timelineConfig); return formatResult(result, runId); } catch (error) { await appendTimeline({ run_id: runId, agent: agentName, session_id: "", t0, t1: new Date().toISOString(), cost_usd: 0, turns: 0, is_error: true, subtype: "error_thrown", prompt_excerpt: prompt.slice(0, 200), cwd: effectiveCwd, }, timelineConfig.dir); return formatError(error, runId); } }); if (baseOptions.persistSession !== false) { server.registerTool(replyToolName, { description: [ `Continue a previous ${toolName} conversation by session ID.`, "Use this for follow-up questions, iterative refinement,", "or multi-step workflows that build on prior context.", ].join(" "), inputSchema: z.object({ session_id: z.string().describe(`Session ID from a prior ${toolName} response`), prompt: z.string().describe("Follow-up instruction or question"), run_id: z.string().optional().describe("Workflow run ID — pass the same run_id from the original call to keep entries grouped."), cwd: z.string().optional().describe("Working directory override"), model: z.string().optional().describe("Model override"), maxTurns: z.number().int().positive().optional().describe("Max conversation turns"), maxBudgetUsd: z.number().positive().optional().describe("Max spend in USD"), }), }, async ({ session_id, prompt, run_id, cwd, model, maxTurns, maxBudgetUsd }) => { const runId = run_id || randomUUID(); const t0 = new Date().toISOString(); const baseCwd = baseOptions.cwd || process.cwd(); const effectiveCwd = cwd ? (isAbsolute(cwd) || isDescendantPath(cwd, baseCwd)) ? resolve(baseCwd, cwd) : baseCwd : baseCwd; try { const result = await runQuery(prompt, { cwd, model, maxTurns, maxBudgetUsd, resumeSessionId: session_id, }, baseOptions); await recordTimeline(agentName, prompt, runId, t0, result, effectiveCwd, timelineConfig); return formatResult(result, runId); } catch (error) { await appendTimeline({ run_id: runId, agent: agentName, session_id: session_id || "", t0, t1: new Date().toISOString(), cost_usd: 0, turns: 0, is_error: true, subtype: "error_thrown", prompt_excerpt: prompt.slice(0, 200), cwd: effectiveCwd, }, timelineConfig.dir); return formatError(error, runId); } }); } } - src/query-helpers.ts:136-159 (handler)runQuery function called by the tool handler. Uses the Claude Agent SDK's query() with options that default to 'claude_code' preset in the system prompt when using preset mode.
if (overrides.systemPrompt) { if ( typeof baseOptions.systemPrompt === "object" && baseOptions.systemPrompt?.type === "preset" ) { const baseAppend = baseOptions.systemPrompt.append || ""; options.systemPrompt = { type: "preset", preset: "claude_code", append: [baseAppend, overrides.systemPrompt].filter(Boolean).join("\n"), }; } else if (typeof baseOptions.systemPrompt === "string") { options.systemPrompt = baseOptions.systemPrompt; options.extraArgs = { ...options.extraArgs, "append-system-prompt": overrides.systemPrompt, }; } else { options.systemPrompt = { type: "preset", preset: "claude_code", append: overrides.systemPrompt, }; } - src/config.ts:66-76 (helper)buildBaseOptions configures the 'claude_code' preset in system prompt when CLAUDE_APPEND_PROMPT is set (no full CLAUDE_SYSTEM_PROMPT override).
const sysPrompt = envStr("CLAUDE_SYSTEM_PROMPT"); const appendPrompt = envStr("CLAUDE_APPEND_PROMPT"); if (sysPrompt) { opts.systemPrompt = sysPrompt; } else if (appendPrompt) { opts.systemPrompt = { type: "preset", preset: "claude_code", append: appendPrompt, }; } - src/templates.ts:173-185 (helper)The 'solo-agent' template defines a default agent with toolName: 'claude_code', which is used by the claude-octopus init command.
{ id: "solo-agent", name: "Solo Agent", summary: "Single Claude Code agent with sensible defaults", agents: [ { serverName: "claude", toolName: "claude_code", description: "Send a task to an autonomous Claude Code agent. Reads/writes files, runs commands, handles complex engineering tasks.", permissionMode: "bypassPermissions", }, ], },