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

Method handleNode

src/mcp/tools.ts:3905–4019  ·  view source on GitHub ↗

* Handle codegraph_node

(args: Record<string, unknown>)

Source from the content-addressed store, hash-verified

3903 * Handle codegraph_node
3904 */
3905 private async handleNode(args: Record<string, unknown>): Promise<ToolResult> {
3906 const cg = this.getCodeGraph(args.projectPath as string | undefined);
3907 // Default to false to minimize context usage
3908 const includeCode = args.includeCode === true;
3909 const fileHint = typeof args.file === 'string' && args.file.trim() ? args.file.trim() : undefined;
3910 const lineHint = typeof args.line === 'number' && args.line > 0 ? args.line : undefined;
3911 const offset = typeof args.offset === 'number' && args.offset > 0 ? Math.floor(args.offset) : undefined;
3912 const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.floor(args.limit) : undefined;
3913 const symbolsOnly = args.symbolsOnly === true;
3914 const symbolRaw = typeof args.symbol === 'string' ? args.symbol.trim() : '';
3915
3916 // FILE READ MODE: a `file` with no `symbol` reads that file like the Read
3917 // tool — its current on-disk source with line numbers, narrowable with
3918 // `offset`/`limit` exactly as Read does — PLUS a one-line blast-radius
3919 // header (which files depend on it). `symbolsOnly` returns just the
3920 // structural map instead. Backed by the index: same bytes Read gives you.
3921 if (!symbolRaw && fileHint) {
3922 return this.handleFileView(cg, fileHint, { offset, limit, symbolsOnly });
3923 }
3924
3925 const symbol = this.validateString(args.symbol, 'symbol');
3926 if (typeof symbol !== 'string') return symbol;
3927
3928 let matches = this.findSymbolMatches(cg, symbol);
3929 if (matches.length === 0) {
3930 return this.textResult(`Symbol "${symbol}" not found in the codebase`);
3931 }
3932
3933 // Disambiguate a heavily-overloaded name to a specific definition the caller
3934 // pinned by file/line (the `file:line` a trail or another tool showed it) —
3935 // so it can fetch e.g. `Harness::poll` at harness.rs:153 out of 50+ `poll`s
3936 // instead of Reading. file matches by path suffix/substring; line prefers the
3937 // def whose body contains it, else the nearest start. Only narrows (never
3938 // empties — if a hint matches nothing it's ignored).
3939 if (matches.length > 1 && (fileHint || lineHint !== undefined)) {
3940 const norm = (p: string) => p.replace(/\\/g, '/').toLowerCase();
3941 let narrowed = matches;
3942 if (fileHint) {
3943 const fh = norm(fileHint);
3944 const byFile = narrowed.filter((n) => norm(n.filePath).endsWith(fh) || norm(n.filePath).includes(fh));
3945 if (byFile.length > 0) narrowed = byFile;
3946 }
3947 if (lineHint !== undefined && narrowed.length > 1) {
3948 const containing = narrowed.filter((n) => n.startLine <= lineHint && (n.endLine ?? n.startLine) >= lineHint);
3949 narrowed = containing.length > 0
3950 ? containing
3951 : [...narrowed].sort((a, b) => Math.abs(a.startLine - lineHint) - Math.abs(b.startLine - lineHint)).slice(0, 1);
3952 }
3953 if (narrowed.length > 0) matches = narrowed;
3954 }
3955
3956 // Single definition — the common case.
3957 if (matches.length === 1) {
3958 return this.textResult(this.truncateOutput(await this.renderNodeSection(cg, matches[0]!, includeCode)));
3959 }
3960
3961 // Multiple definitions share this name — overloads, or same-named methods on
3962 // 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