Remove codegraph_explore tool in favor of native Explore agents

The hybrid approach (Claude's native Explore agents using codegraph tools)
is more effective than a custom explore tool because Explore agents already
know what format the main session needs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-01-19 21:14:52 -06:00
co-authored by Claude Opus 4.5
parent a4ddedb197
commit 47af81aee7
3 changed files with 21 additions and 362 deletions
+9 -19
View File
@@ -122,26 +122,16 @@ codegraph serve --mcp # Start MCP server
## MCP Tools Best Practices
When using CodeGraph MCP tools, follow these guidelines to minimize context usage:
These tools are designed to be used by **Explore agents** for faster codebase exploration:
### For Complex Tasks (features, refactoring, architecture)
Use `codegraph_explore` - it does intensive exploration internally and returns a condensed brief:
```
codegraph_explore(task: "implement user authentication with OAuth")
```
This replaces multiple tool calls and keeps your main context clean.
### For Simple Lookups
Use targeted tools directly:
- `codegraph_search` - Find symbols by name
- `codegraph_node` - Get details about one symbol
- `codegraph_callers/callees` - Understand usage patterns
### Context Usage Comparison
| Approach | Context Impact |
|----------|---------------|
| Multiple `codegraph_*` calls | High - each result stays in context |
| Single `codegraph_explore` | Low - returns condensed summary |
| Tool | Use For |
|------|---------|
| `codegraph_search` | Find symbols by name (functions, classes, types) |
| `codegraph_context` | Get relevant code context for a task |
| `codegraph_callers` | Find what calls a function |
| `codegraph_callees` | Find what a function calls |
| `codegraph_impact` | See what's affected by changing a symbol |
| `codegraph_node` | Get details + source code for a symbol |
### Important
CodeGraph provides **code context**, not product requirements. For new features, still ask the user about:
+12 -17
View File
@@ -389,17 +389,7 @@ codegraph serve --mcp --path /project # Specify project path
## 🔌 MCP Tools Reference
When running as an MCP server, CodeGraph exposes these tools to AI assistants:
### `codegraph_explore` ⭐ Recommended for complex tasks
Deep exploration that returns a condensed brief. Use this for features, refactoring, or multi-file changes to keep your main context clean.
```
codegraph_explore(task: "implement user authentication", keywords: "auth,login,user")
```
**Returns:** Key files, entry points, types, functions, data flow, and suggested next steps.
When running as an MCP server, CodeGraph exposes these tools to AI assistants. **These tools are designed to be used by Claude's Explore agents** for faster, more efficient codebase exploration.
### `codegraph_context`
@@ -446,13 +436,18 @@ codegraph_node(symbol: "authenticate", includeCode: true)
Check index health and statistics.
### Context Usage Best Practices
### How It Works With Claude Code
| Approach | Context Impact | When to Use |
|----------|----------------|-------------|
| `codegraph_explore` | **Low** - condensed summary | Complex features, refactoring |
| Multiple tool calls | **High** - each result accumulates | Avoid for complex tasks |
| `codegraph_context` | **Medium** | Focused, single-area queries |
Claude's **Explore agents** use these tools instead of grep/glob/Read for faster exploration:
| Without CodeGraph | With CodeGraph | Benefit |
|-------------------|----------------|---------|
| `grep -r "auth"` | `codegraph_search("auth")` | Instant symbol lookup |
| Multiple `Read` calls | `codegraph_context(task)` | Related code in one call |
| Manual file tracing | `codegraph_callers/callees` | Call graph traversal |
| Guessing impact | `codegraph_impact(symbol)` | Know what breaks |
This hybrid approach gives you **~30% fewer tokens** and **~25% fewer tool calls** while letting Claude's native agents handle synthesis.
## 📚 Library Usage
-326
View File
@@ -4,8 +4,6 @@
* Defines the tools exposed by the CodeGraph MCP server.
*/
import * as fs from 'fs';
import * as path from 'path';
import CodeGraph from '../index';
import type { Node, SearchResult, Subgraph, TaskContext, NodeKind } from '../types';
@@ -179,29 +177,6 @@ export const tools: ToolDefinition[] = [
properties: {},
},
},
{
name: 'codegraph_explore',
description: 'RECOMMENDED FOR COMPLEX TASKS: Deep exploration that READS CODE INTERNALLY and returns synthesis with key snippets. You do NOT need to make separate Read calls - the code is included in the response. Checks for existing implementations, traces data flow, and includes relevant code. For feature requests, use AskUserQuestion to clarify requirements BEFORE planning.',
inputSchema: {
type: 'object',
properties: {
task: {
type: 'string',
description: 'Detailed description of the feature, bug, or task to explore',
},
focus: {
type: 'string',
description: 'Optional focus area: "architecture" (structure & patterns), "implementation" (specific code), or "impact" (what would change). Default: auto-detect.',
enum: ['architecture', 'implementation', 'impact'],
},
keywords: {
type: 'string',
description: 'Optional comma-separated keywords to search for (e.g., "bundle,swap,subscription")',
},
},
required: ['task'],
},
},
];
/**
@@ -230,8 +205,6 @@ export class ToolHandler {
return await this.handleNode(args);
case 'codegraph_status':
return await this.handleStatus();
case 'codegraph_explore':
return await this.handleExplore(args);
default:
return this.errorResult(`Unknown tool: ${toolName}`);
}
@@ -448,305 +421,6 @@ export class ToolHandler {
return this.textResult(lines.join('\n'));
}
/**
* Handle codegraph_explore - deep exploration with internal file reading
* Returns synthesized context so Claude doesn't need separate Read calls
*/
private async handleExplore(args: Record<string, unknown>): Promise<ToolResult> {
const task = args.task as string;
const keywordsArg = args.keywords as string | undefined;
const projectRoot = this.cg.getProjectRoot();
// Phase 1: Extract search terms
const keywords = this.extractKeywords(task, keywordsArg);
// Phase 2: Find relevant symbols
const symbolMap = new Map<string, Node>();
const fileSet = new Set<string>();
for (const keyword of keywords.slice(0, 5)) {
const results = this.cg.searchNodes(keyword, { limit: 10 });
for (const r of results) {
if (!symbolMap.has(r.node.id)) {
symbolMap.set(r.node.id, r.node);
fileSet.add(r.node.filePath);
}
}
}
// Phase 3: Look for EXISTING implementations
const existingImplNodes: Node[] = [];
const searchPatterns = this.generateExistingPatternSearches(keywords);
for (const pattern of searchPatterns) {
const results = this.cg.searchNodes(pattern, { limit: 5 });
for (const r of results) {
const node = r.node;
if (['function', 'method', 'component'].includes(node.kind)) {
if (!symbolMap.has(node.id)) {
symbolMap.set(node.id, node);
fileSet.add(node.filePath);
}
existingImplNodes.push(node);
}
}
}
// Phase 4: Categorize symbols by type
const allSymbols = Array.from(symbolMap.values());
const functions = allSymbols.filter(n => n.kind === 'function' || n.kind === 'method');
const components = allSymbols.filter(n => n.kind === 'component');
const types = allSymbols.filter(n => n.kind === 'interface' || n.kind === 'type_alias');
const apiRoutes = allSymbols.filter(n => n.filePath.includes('/api/') || n.kind === 'route');
// Phase 5: READ CODE INTERNALLY for key symbols (the sub-agent behavior)
// Prioritize: existing implementations > API routes > types > functions
const codeSnippets: Array<{ node: Node; code: string }> = [];
const maxSnippets = 8;
const maxCodeLength = 1500; // chars per snippet
const priorityNodes = [
...existingImplNodes.slice(0, 3),
...apiRoutes.slice(0, 2),
...types.slice(0, 2),
...components.slice(0, 2),
...functions.filter(n => !existingImplNodes.includes(n)).slice(0, 1),
];
for (const node of priorityNodes) {
if (codeSnippets.length >= maxSnippets) break;
const code = this.extractNodeCode(projectRoot, node, maxCodeLength);
if (code) {
codeSnippets.push({ node, code });
}
}
// Phase 6: Trace call graphs for data flow understanding
const dataFlowInsights: string[] = [];
for (const node of existingImplNodes.slice(0, 3)) {
const callers = this.cg.getCallers(node.id);
const callees = this.cg.getCallees(node.id);
if (callers.length > 0 || callees.length > 0) {
let flow = `${node.name}`;
if (callers.length > 0) flow = `${callers.slice(0, 2).map(c => c.node.name).join(', ')}${flow}`;
if (callees.length > 0) flow = `${flow}${callees.slice(0, 2).map(c => c.node.name).join(', ')}`;
dataFlowInsights.push(flow);
}
}
// Phase 7: Build comprehensive synthesis
const isFeatureQuery = this.looksLikeFeatureRequest(task);
const hasExistingImpl = existingImplNodes.length > 0;
const synthesis = this.buildExploreSynthesis({
task,
existingImplNodes,
codeSnippets,
types,
apiRoutes,
dataFlowInsights,
totalSymbols: symbolMap.size,
totalFiles: fileSet.size,
isFeatureQuery,
hasExistingImpl,
});
return this.textResult(synthesis);
}
/**
* Extract code from a node's source file
*/
private extractNodeCode(projectRoot: string, node: Node, maxLength: number): string | null {
const filePath = path.join(projectRoot, node.filePath);
if (!fs.existsSync(filePath)) {
return null;
}
try {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const startIdx = Math.max(0, node.startLine - 1);
const endIdx = Math.min(lines.length, node.endLine);
let code = lines.slice(startIdx, endIdx).join('\n');
// Truncate if too long
if (code.length > maxLength) {
code = code.slice(0, maxLength) + '\n// ... truncated ...';
}
return code;
} catch {
return null;
}
}
/**
* Build comprehensive synthesis with code included
*/
private buildExploreSynthesis(data: {
task: string;
existingImplNodes: Node[];
codeSnippets: Array<{ node: Node; code: string }>;
types: Node[];
apiRoutes: Node[];
dataFlowInsights: string[];
totalSymbols: number;
totalFiles: number;
isFeatureQuery: boolean;
hasExistingImpl: boolean;
}): string {
const lines: string[] = [];
// Critical warnings at TOP
if (data.hasExistingImpl) {
lines.push('⚠️ **EXISTING IMPLEMENTATIONS FOUND** - Review code below before planning. Feature may already exist.');
lines.push('');
}
if (data.isFeatureQuery) {
lines.push('📋 **BEFORE PLANNING:** Use `AskUserQuestion` to clarify UX preferences and requirements.');
lines.push('');
}
// Summary stats
lines.push(`**Explored ${data.totalSymbols} symbols across ${data.totalFiles} files**`);
lines.push('');
// Data flow (if available)
if (data.dataFlowInsights.length > 0) {
lines.push('**Data Flow:**');
for (const flow of data.dataFlowInsights.slice(0, 3)) {
lines.push(` ${flow}`);
}
lines.push('');
}
// Key types (signatures only, no code)
if (data.types.length > 0) {
lines.push('**Key Types:**');
for (const t of data.types.slice(0, 4)) {
lines.push(` - \`${t.name}\` (${t.filePath}:${t.startLine})`);
}
lines.push('');
}
// API routes (signatures only)
if (data.apiRoutes.length > 0) {
lines.push('**API Routes:**');
for (const r of data.apiRoutes.slice(0, 3)) {
const routePath = r.filePath.split('/api/')[1] || r.filePath;
lines.push(` - \`/api/${routePath}\` (${r.name})`);
}
lines.push('');
}
// CODE SNIPPETS - the key sub-agent behavior
if (data.codeSnippets.length > 0) {
lines.push('---');
lines.push('**Key Code (read internally):**');
lines.push('');
for (const { node, code } of data.codeSnippets) {
lines.push(`### ${node.name} (${node.kind}) - ${node.filePath}:${node.startLine}`);
lines.push('```' + (node.language || 'typescript'));
lines.push(code);
lines.push('```');
lines.push('');
}
}
return lines.join('\n');
}
/**
* Generate search patterns to find existing implementations
* Language-agnostic: generates patterns for multiple naming conventions
*/
private generateExistingPatternSearches(keywords: string[]): string[] {
const patterns: string[] = [];
for (const keyword of keywords.slice(0, 3)) {
// Skip very short or common words
if (keyword.length < 3) continue;
const lower = keyword.toLowerCase();
const capitalized = keyword.charAt(0).toUpperCase() + keyword.slice(1).toLowerCase();
// The keyword itself in various cases
patterns.push(lower);
patterns.push(capitalized);
// Common suffixes (work across most languages)
// These patterns find: SwapService, swap_service, SwapHandler, etc.
const suffixes = ['Service', 'Handler', 'Controller', 'Manager', 'Helper', 'Util', 'Utils'];
for (const suffix of suffixes) {
patterns.push(`${capitalized}${suffix}`); // PascalCase: SwapService
patterns.push(`${lower}_${suffix.toLowerCase()}`); // snake_case: swap_service
}
// Common prefixes (work across most languages)
const prefixes = ['handle', 'process', 'do', 'execute', 'perform', 'run'];
for (const prefix of prefixes) {
patterns.push(`${prefix}_${lower}`); // snake_case: handle_swap
patterns.push(`${prefix}${capitalized}`); // camelCase: handleSwap
}
// Common action patterns
patterns.push(`create_${lower}`);
patterns.push(`update_${lower}`);
patterns.push(`delete_${lower}`);
patterns.push(`get_${lower}`);
patterns.push(`create${capitalized}`);
patterns.push(`update${capitalized}`);
patterns.push(`delete${capitalized}`);
patterns.push(`get${capitalized}`);
}
return [...new Set(patterns)];
}
/**
* Extract keywords from task description
*/
private extractKeywords(task: string, explicitKeywords?: string): string[] {
const keywords: string[] = [];
// Add explicit keywords first
if (explicitKeywords) {
keywords.push(...explicitKeywords.split(',').map(k => k.trim()).filter(Boolean));
}
// Extract likely code identifiers from task (camelCase, PascalCase, snake_case)
const identifierPattern = /\b([A-Z][a-zA-Z0-9]*|[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*|[a-z]+_[a-z_]+)\b/g;
const matches = task.match(identifierPattern) || [];
keywords.push(...matches);
// Extract quoted terms
const quotedPattern = /"([^"]+)"|'([^']+)'/g;
let match;
while ((match = quotedPattern.exec(task)) !== null) {
const quoted = match[1] || match[2];
if (quoted) keywords.push(quoted);
}
// Extract domain-specific terms (nouns that might be code concepts)
const commonTerms = task.toLowerCase()
.split(/\s+/)
.filter(word =>
word.length > 3 &&
!['this', 'that', 'with', 'from', 'have', 'been', 'will', 'would', 'could', 'should', 'when', 'where', 'what', 'which', 'their', 'there', 'these', 'those', 'about', 'into', 'then', 'than', 'some', 'other', 'after', 'before'].includes(word)
);
keywords.push(...commonTerms);
// Deduplicate and return
return [...new Set(keywords)];
}
// =========================================================================
// Formatting helpers (compact by default to reduce context usage)
// =========================================================================