Skip to main content
Glama
xiaolai

claude-octopus

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

TableJSON Schema
NameRequiredDescriptionDefault
promptYesTask or question for Claude Code
run_idNoWorkflow run ID — groups related agent calls into one timeline. Auto-generated if omitted; returned in every response for propagation.
cwdNoWorking directory (overrides CLAUDE_CWD)
modelNoModel override (e.g. "sonnet", "opus", "haiku")
toolsNoRestrict available tools to this list (intersects with server-level restriction)
disallowedToolsNoAdditional tools to block (unions with server-level blacklist)
additionalDirsNoExtra directories the agent can access for this invocation
pluginsNoAdditional plugin paths to load for this invocation (unions with server-level plugins)
effortNoThinking effort override
permissionModeNoPermission mode override (can only tighten, never loosen)
maxTurnsNoMax conversation turns
maxBudgetUsdNoMax spend in USD
systemPromptNoAdditional 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);
      });
    }
  • 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);
          }
        });
      }
    }
  • 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,
        };
      }
  • 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,
      };
    }
  • 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",
        },
      ],
    },
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden. It honestly describes the agent's autonomous capabilities (read/write files, run shell commands), implying potential side effects. However, it does not disclose safety constraints, permission requirements, or destructive potential beyond what is implied.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first states purpose and capabilities, the second explains return value and follow-up mechanism. Every sentence adds value, no redundancy, and key information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

While the tool has many parameters and no output schema, the description covers the essential outcome (result text + session_id) and follow-up usage. For a complex tool, it provides sufficient context, though more detail on return format or behavior with various parameters could improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, meaning all 13 parameters have descriptions in the schema. The description does not add additional parameter semantics beyond what the schema already provides. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool sends tasks to an autonomous Claude Code agent and lists specific capabilities (reads/writes files, runs shell commands, searches codebases). It distinguishes from sibling tools by noting it returns a session_id for follow-ups via claude_code_reply.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implicitly provides usage guidelines by stating return of session_id for follow-ups, indicating when to use this tool (initial task) vs claude_code_reply (follow-up). No explicit when-not or alternatives, but context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaolai/claude-octopus'

If you have feedback or need assistance with the MCP directory API, please join our Discord server