MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / handleNode

Method handleNode

src/mcp/tools.ts:3736–3850  ·  view source on GitHub ↗

* Handle codegraph_node

(args: Record<string, unknown>)

Source from the content-addressed store, hash-verified

3734 * Handle codegraph_node
3735 */
3736 private async handleNode(args: Record<string, unknown>): Promise<ToolResult> {
3737 const cg = this.getCodeGraph(args.projectPath as string | undefined);
3738 // Default to false to minimize context usage
3739 const includeCode = args.includeCode === true;
3740 const fileHint = typeof args.file === 'string' && args.file.trim() ? args.file.trim() : undefined;
3741 const lineHint = typeof args.line === 'number' && args.line > 0 ? args.line : undefined;
3742 const offset = typeof args.offset === 'number' && args.offset > 0 ? Math.floor(args.offset) : undefined;
3743 const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.floor(args.limit) : undefined;
3744 const symbolsOnly = args.symbolsOnly === true;
3745 const symbolRaw = typeof args.symbol === 'string' ? args.symbol.trim() : '';
3746
3747 // FILE READ MODE: a `file` with no `symbol` reads that file like the Read
3748 // tool — its current on-disk source with line numbers, narrowable with
3749 // `offset`/`limit` exactly as Read does — PLUS a one-line blast-radius
3750 // header (which files depend on it). `symbolsOnly` returns just the
3751 // structural map instead. Backed by the index: same bytes Read gives you.
3752 if (!symbolRaw && fileHint) {
3753 return this.handleFileView(cg, fileHint, { offset, limit, symbolsOnly });
3754 }
3755
3756 const symbol = this.validateString(args.symbol, 'symbol');
3757 if (typeof symbol !== 'string') return symbol;
3758
3759 let matches = this.findSymbolMatches(cg, symbol);
3760 if (matches.length === 0) {
3761 return this.textResult(`Symbol "${symbol}" not found in the codebase`);
3762 }
3763
3764 // Disambiguate a heavily-overloaded name to a specific definition the caller
3765 // pinned by file/line (the `file:line` a trail or another tool showed it) —
3766 // so it can fetch e.g. `Harness::poll` at harness.rs:153 out of 50+ `poll`s
3767 // instead of Reading. file matches by path suffix/substring; line prefers the
3768 // def whose body contains it, else the nearest start. Only narrows (never
3769 // empties — if a hint matches nothing it's ignored).
3770 if (matches.length > 1 && (fileHint || lineHint !== undefined)) {
3771 const norm = (p: string) => p.replace(/\\/g, '/').toLowerCase();
3772 let narrowed = matches;
3773 if (fileHint) {
3774 const fh = norm(fileHint);
3775 const byFile = narrowed.filter((n) => norm(n.filePath).endsWith(fh) || norm(n.filePath).includes(fh));
3776 if (byFile.length > 0) narrowed = byFile;
3777 }
3778 if (lineHint !== undefined && narrowed.length > 1) {
3779 const containing = narrowed.filter((n) => n.startLine <= lineHint && (n.endLine ?? n.startLine) >= lineHint);
3780 narrowed = containing.length > 0
3781 ? containing
3782 : [...narrowed].sort((a, b) => Math.abs(a.startLine - lineHint) - Math.abs(b.startLine - lineHint)).slice(0, 1);
3783 }
3784 if (narrowed.length > 0) matches = narrowed;
3785 }
3786
3787 // Single definition — the common case.
3788 if (matches.length === 1) {
3789 return this.textResult(this.truncateOutput(await this.renderNodeSection(cg, matches[0]!, includeCode)));
3790 }
3791
3792 // Multiple definitions share this name — overloads, or same-named methods on
3793 // different types (Alamofire `didCompleteTask`/`task`/`validate`, gin

Callers 1

dispatchToolMethod · 0.95

Calls 9

getCodeGraphMethod · 0.95
handleFileViewMethod · 0.95
validateStringMethod · 0.95
findSymbolMatchesMethod · 0.95
textResultMethod · 0.95
truncateOutputMethod · 0.95
renderNodeSectionMethod · 0.95
normFunction · 0.50
joinMethod · 0.45

Tested by

no test coverage detected