Consult Opus Advisor
consult_opusGet strategic advice from Claude Opus for architecture decisions, complex debugging, and code review. Maintains a per-project consultation log for continuity.
Instructions
Consult Claude Opus 4.7 for strategic advice. Opus runs via the Claude Code CLI with your existing subscription — no API key needed. The advisor maintains a per-project consultation log for continuity across calls. History is capped by both entry count (5) and token budget (~6K tokens) to prevent context bloat. Use this for architecture decisions, complex debugging, code review, or any problem that benefits from deeper reasoning.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| question | Yes | The question or problem you need advice on. Be specific about what decision you're facing or what you're stuck on. | |
| context | No | Additional context: relevant code snippets, error messages, constraints, or background. Include enough that the advisor can give specific guidance without needing to read files. | |
| effort | No | Reasoning effort level for Opus. 'low' for quick opinions, 'medium' (default) for thorough advice, 'high' for deep analysis. | medium |
| files | No | File paths (relative to project root) to include as code context. Each file is read and prepended as a labeled code block. Max 50KB per file, 200KB total. Example: ['src/index.ts', 'lib/utils.ts'] | |
| include_history | No | Whether to include prior consultation history for continuity. Defaults to true. Set to false for standalone questions unrelated to prior advice. |
Implementation Reference
- src/index.ts:362-410 (registration)Registration of 'consult_opus' MCP tool with inputSchema, description, title, and annotations.
server.registerTool("consult_opus", { title: "Consult Opus Advisor", description: "Consult Claude Opus 4.7 for strategic advice. Opus runs via the Claude Code CLI " + "with your existing subscription — no API key needed. The advisor maintains a " + "per-project consultation log for continuity across calls. History is capped by " + "both entry count (5) and token budget (~6K tokens) to prevent context bloat. " + "Use this for architecture decisions, complex debugging, code review, or any " + "problem that benefits from deeper reasoning.", inputSchema: { question: z .string() .describe( "The question or problem you need advice on. Be specific about what decision " + "you're facing or what you're stuck on.", ), context: z .string() .optional() .describe( "Additional context: relevant code snippets, error messages, constraints, or " + "background. Include enough that the advisor can give specific guidance without " + "needing to read files.", ), effort: z .enum(["low", "medium", "high"]) .optional() .default("medium") .describe( "Reasoning effort level for Opus. 'low' for quick opinions, 'medium' (default) " + "for thorough advice, 'high' for deep analysis.", ), files: z .array(z.string()) .optional() .describe( "File paths (relative to project root) to include as code context. " + "Each file is read and prepended as a labeled code block. " + "Max 50KB per file, 200KB total. Example: ['src/index.ts', 'lib/utils.ts']", ), include_history: z .boolean() .optional() .default(true) .describe( "Whether to include prior consultation history for continuity. Defaults to true. " + "Set to false for standalone questions unrelated to prior advice.", ), }, - src/index.ts:371-410 (schema)Input schema for consult_opus: question (string, required), context (string, optional), effort (enum: low/medium/high, optional, default medium), files (string array, optional), include_history (boolean, optional, default true).
inputSchema: { question: z .string() .describe( "The question or problem you need advice on. Be specific about what decision " + "you're facing or what you're stuck on.", ), context: z .string() .optional() .describe( "Additional context: relevant code snippets, error messages, constraints, or " + "background. Include enough that the advisor can give specific guidance without " + "needing to read files.", ), effort: z .enum(["low", "medium", "high"]) .optional() .default("medium") .describe( "Reasoning effort level for Opus. 'low' for quick opinions, 'medium' (default) " + "for thorough advice, 'high' for deep analysis.", ), files: z .array(z.string()) .optional() .describe( "File paths (relative to project root) to include as code context. " + "Each file is read and prepended as a labeled code block. " + "Max 50KB per file, 200KB total. Example: ['src/index.ts', 'lib/utils.ts']", ), include_history: z .boolean() .optional() .default(true) .describe( "Whether to include prior consultation history for continuity. Defaults to true. " + "Set to false for standalone questions unrelated to prior advice.", ), }, - src/index.ts:415-518 (handler)Handler function for consult_opus. Builds prompt with optional history, files, and context, calls runClaude() with model='opus', logs the consultation, and returns advice text.
}, async ({ question, context, effort, files, include_history }) => { const startTime = Date.now(); try { await ensureAdvisorDir(); const cwd = process.cwd(); // Build the prompt with optional history, files, and context const parts: string[] = []; let historyTokens = 0; let fileTokens = 0; if (include_history) { const history = await readAdvisorLog(); const recentHistory = getRecentHistory(history, 5, MAX_HISTORY_TOKENS); if (recentHistory) { historyTokens = estimateTokens(recentHistory); parts.push( `## Prior Consultation History (for continuity — ~${historyTokens} tokens)`, recentHistory, "", ); } } // Read and inject file contents if (files && files.length > 0) { const fileResult = await readFilesForContext(files, cwd); if (fileResult.blocks) { fileTokens = estimateTokens(fileResult.blocks); parts.push( `## Files (${fileResult.fileCount} files, ~${fileTokens} tokens)`, fileResult.blocks, "", ); } if (fileResult.errors.length > 0) { parts.push( `> File warnings: ${fileResult.errors.join("; ")}`, "", ); } } parts.push("## Current Question", question); if (context) { parts.push("", "## Context", context); } const fullPrompt = parts.join("\n"); const result = await runClaude(fullPrompt, { model: "opus", effort: effort ?? "medium", systemPrompt: ADVISOR_SYSTEM_PROMPT, }); const latencyMs = Date.now() - startTime; // Log the consultation (include file names in context for the log) const logContext = [ context || "", files && files.length > 0 ? `\nFiles consulted: ${files.join(", ")}` : "", ].filter(Boolean).join("") || undefined; await appendToLog(question, logContext, result.output); // Write structured metadata await appendMetadata({ timestamp: new Date().toISOString(), effort: effort ?? "medium", latencyMs, questionTokens: estimateTokens(question), contextTokens: (context ? estimateTokens(context) : 0) + fileTokens, historyTokens, adviceTokens: estimateTokens(result.output), signal: result.signal, }); return { content: [{ type: "text" as const, text: result.output }], }; } catch (err) { const latencyMs = Date.now() - startTime; const message = err instanceof Error ? err.message : "Unknown error consulting Opus"; // Log failed attempts to metadata too await appendMetadata({ timestamp: new Date().toISOString(), effort: effort ?? "medium", latencyMs, questionTokens: estimateTokens(question), contextTokens: context ? estimateTokens(context) : 0, historyTokens: 0, adviceTokens: 0, signal: message.includes("signal") ? message : null, }).catch(() => {}); return { content: [{ type: "text" as const, text: `Advisor error: ${message}` }], isError: true, }; } }); - src/index.ts:19-28 (helper)getAdvisorDir() - determines project-specific log directory using SHA256 hash of cwd.
function getAdvisorDir(): string { if (process.env.ADVISOR_LOG_DIR) { return process.env.ADVISOR_LOG_DIR; } const home = process.env.HOME || "~"; const cwd = process.cwd(); const projectName = basename(cwd); const projectHash = createHash("sha256").update(cwd).digest("hex").slice(0, 8); return join(home, ".opus-advisor", `${projectName}-${projectHash}`); } - src/index.ts:276-346 (helper)runClaude() - spawns the claude CLI process with -p flag, pipes prompt via stdin, handles output/errors/signals.
function runClaude(prompt: string, options: { model: string; effort: string; systemPrompt: string; maxTurns?: number; cwd?: string; }): Promise<ClaudeResult> { return new Promise((resolve, reject) => { const args = [ "-p", "--model", options.model, "--effort", options.effort, "--system-prompt", options.systemPrompt, "--max-turns", String(options.maxTurns ?? 1), "--no-session-persistence", "--disable-slash-commands", ]; const child = spawn("claude", args, { cwd: options.cwd || process.cwd(), stdio: ["pipe", "pipe", "pipe"], env: { ...process.env }, timeout: 300_000, // 5 min }); let stdout = ""; let stderr = ""; child.stdout.on("data", (data: Buffer) => { stdout += data.toString(); }); child.stderr.on("data", (data: Buffer) => { stderr += data.toString(); }); // Pipe the prompt via stdin, then close it child.stdin.write(prompt); child.stdin.end(); child.on("error", (err) => { reject(new Error(`Failed to spawn claude CLI: ${err.message}`)); }); child.on("close", (code, signal) => { // If killed by signal (timeout, OOM, etc.), discard partial output if (signal) { reject( new Error( `claude CLI killed by signal ${signal}. Partial output discarded.\nstderr: ${stderr.slice(0, 500)}`, ), ); return; } if (code !== 0) { reject( new Error( `claude CLI exited with code ${code}\nstderr: ${stderr.slice(0, 500)}`, ), ); return; } const output = stdout.trim(); if (!output) { reject(new Error("claude CLI returned empty output")); return; } resolve({ output, signal: null }); }); }); }