ucn

by mleoca

Not rated
GitHub

Description

Universal Code Navigator - a lightweight MCP server that gives AI agents call-graph-level understanding of code. Instead of reading entire files, agents ask structural questions like: "who calls this function", "what breaks if I change it", "what's unused", and get precise…

About

Universal Code Navigator - a lightweight MCP server that gives AI agents call-graph-level understanding of code. Instead of reading entire files, agents ask structural questions like: "who calls this function", "what breaks if I change it", "what's unused", and get precise, AST-verified answers. UCN parses JS/TS…

Details

Author
mleoca
Categories
Developer Tools, Other

Setup

Install ucn in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/mleoca/ucn

Follow the installation instructions in the repository README, then restart your MCP client.

If you work with AI Agents, add UCN as aSkill or MCP tool. One tool gives the agent compact, source-linked answers to caller, impact, and test questions, with uncertainty labeled instead of guessed.

Find symbols, trace callers, check impact, pick the right tests, extract exact source, and spot dead code - from your terminal or your AI agent.

Supports JavaScript, TypeScript, JSX/TSX, Python, Go, Rust, Java, C, C++, C#, and HTML inline scripts. All commands, one engine, three ways to use it:

Terminal AI Agents Agent Skills │ │ │ CLI MCP Skill └────────────────────┼────────────────────┘ │ ┌──────┴──────┐ │ UCN Engine │ │ commands │ │ tree-sitter │ └─────────────┘

Your tools can already find text. UCN findsthe function- its definition, its callers, its blast radius, its tests - and tells you how sure it is. It parses code the way a compiler does (tree-sitter ASTs, not regex) and answers the questions you actually have: who calls this? what breaks if I change it? which tests should I run? is this dead?

- No required background process- the CLI parses on demand, answers, and exits. MCP stays warm only when you choose to run it.
- No language servers, no compilation- tree-sitter does the analysis without building the project.
- No config- point it at a directory and ask.

And it's built for auditable trust. grep hands you raw matches to sift yourself; UCN separates proven edges from possible ones, explains every exclusion, and reconciles every occurrence of the name it searched. It never turns a zero into a deletion claim. CI re-derives its answers from real compilers and language servers (ts-morph, Pyright, gopls, rust-analyzer, JDT LS, Roslyn, clangd) on pinned production repositories. SeeAnswers you can trust.

Real output: oneucn showonripgrep- signature, 123 proven callers with their evidence, and an account of every occurrence of the name. No files opened.

npm install -g ucn # Node.js 20+ cd your-project ucn repo # what is this codebase? ucn find handleRequest # exact definitions, stable handles ucn show src/server.ts:42:handleRequest # the full picture ucn trace src/server.ts:42:handleRequest --direction=callers ucn impact src/server.ts:42:handleRequest # every call site, with evidence ucn tests src/server.ts:42:handleRequest --depth=3 # which tests to run

The first command builds an incremental index; the rest reuse it. The cache lives outside your project directory, so there's nothing to gitignore.

What does this function do, who calls it, and how sure is the answer?ucn showgathers everything useful about one symbol: signature, source, callers, callees, tests, types, dependencies, examples. Project it down to just the sections you need:

$ ucn show detectLanguage --sections=summary,callers,callees --compact SUMMARY ─────── detectLanguage(filePath: string, projectRoot = null): string|null languages/index.js:420-428 (9 lines) handle: languages/index.js:420:detectLanguage "Detect language from file path" async: no | side_effects: [none] | complexity: branches=1, depth=1 RELATIONSHIPS ───────────── CALLERS — CONFIRMED (51, 30 prod + 21 test): evidence: scope-match (all) [1] cli/index.js:604 [runFileCommand]: const language = detectLanguage(filePath); [7] core/build-worker.js:39 [processFile]: const language = detectLanguage(filePath, rootDir); [17] core/project.js:472 [build]: const language = detectLanguage(filePath, this.root); [34] test/parser-unit.test.js:19: assert.strictEqual(detectLanguage('file.js'), 'javascript'); ... 47 more callers CALLEES (1): evidence: exact-binding (all) [52] detectHeaderLanguage {fs} - core/compilation-database.js:217 CALLEES — UNVERIFIED (1) — call syntax, receiver/binding unresolved: toLowerCase ×1 — possible-dispatch L422 ACCOUNT: "detectLanguage" occurs on 79 lines in 20 files: 51 confirmed, 0 unverified, 28 non-call (18 import, 1 definition, 3 reference, 6 other-text), 0 other-target, 0 unaccounted CONTRACT: literal-name text partition complete; semantic completeness is not claimed (aliases, indirect calls, generated code, and runtime dispatch may exist).

findreturns stable handles infile:line:nameform. Pass a handle to any command to pin the answer to one definition, even when several files or classes reuse the same name.

$ ucn trace build --depth=2 build ├── compareNames (core/discovery.js:293) [regular] 3x ├── recordDiscoveryIssue (core/project.js:346) 2x │ └── [unverified] push — method-ambiguous L351 ├── detectProjectPattern (core/discovery.js:760) [utility] 1x ├── parseGitignore (core/discovery.js:253) [utility] 1x │ ├── gitignoreFiles (core/discovery.js:234) [utility] 1x │ ├── compareNames (core/discovery.js:293) [utility] 1x (see above) │ └── parseGitignoreFile (core/discovery.js:152) [utility] 1x ├── gitTrackedPaths (core/discovery.js:266) [utility] 1x │ ├── hasGitMetadata (core/discovery.js:224) [utility] 1x │ └── [unverified] dirname — method-ambiguous L281,L284 └── ... more callees CALLEE ACCOUNT: 11 nodes expanded · 210 call sites = 31 confirmed + 33 unverified (25 method-ambiguous, 1 possible-dispatch, 7 uncertain-receiver) + 86 external/builtin + 60 excluded

tracewalks callees, callers, or callers all the way up to runtime entry points (--direction=callers --to=entrypoints). Proven edges form the tree; calls UCN can't prove a receiver for show up as[unverified]leaves with a reason. The account line reconciles every call site in the expanded tree, so unresolved dispatch stays visible and counted instead of quietly vanishing.

UCN doesn't turn every matching name into a semantic claim. Watch it work through a name with two definitions and a pile of ambiguous method calls:

$ ucn impact saveCache Impact analysis for saveCache core/cache.js:610 Note: Found 2 definitions for "saveCache". Using core/cache.js:610. Also in: core/project.js:2380. Use file= to disambiguate. CALL SITES: 5 confirmed + 15 unverified Files affected: 3 BY FILE: core/project.js:2380 [saveCache]: saveCache(cachePath) { return indexCache.saveCache(this, cachePath); } test/prerelease-audit.test.js:1493: saveCache(built, cacheFile); ... (3 more) UNVERIFIED CALL SITES (15) — call syntax, no binding/receiver evidence: mcp/server.js:517: try { index.saveCache(); } catch (_) { / best-effort / } (possible-dispatch via local receiver) test/cache.test.js:124: index.saveCache(); (possible-dispatch via local receiver) (+13 more) ACCOUNT: "saveCache" occurs on 68 lines in 11 files: 5 confirmed, 15 unverified, 12 non-call (3 import, 1 definition, 1 reference, 7 other-text), 36 other-target, 0 unaccounted CONTRACT: literal-name text partition complete; semantic completeness is not claimed (aliases, indirect calls, generated code, and runtime dispatch may exist).

UCN sorted all 68 places the name appears:

- 5 confirmed- call sites it canproveresolve to thissaveCache, via a binding, import, receiver type, qualified path, or same-class evidence.
- 15 unverified- real call syntax it refuses to claim.index.saveCache()sits on an untyped receiver, so the site stays visible with its reason (possible-dispatch via local receiver) instead of being guessed or dropped.
- 36 other-target- occurrences that belong to theothersaveCache, kept out of the answer instead of quietly inflating it.
- 12 non-call- imports, the definition, comments, strings.
- 0 unaccounted- every observed line landed in exactly one bucket.

That's the payoff: an answer you (or your agent) can audit, instead of an opaque match count. A confirmed edge is evidence about the pinned target. An unverified edge is a review item with a stated reason. And a clean zero is anobserved-textzero, not a safe-to-delete claim: aliases, generated code, reflection, runtime registration, and external consumers can live beyond the indexed evidence, anducn repo --sections=health --deepreports exactly those blind spots. Even when output is truncated to fit an agent's budget, the ACCOUNT, CONTRACT, and WARNING lines survive the cut.

Don't take the tiers on faith. Release gates re-derive UCN's answers from real compilers and language servers on a ten-repository board of pinned production codebases, and publishing is blocked unless they pass. The latest full release-board run (2026-08-11):

On the same run:zeroin-scope oracle call edges missing from the answer (the release gate) on every repository,zerofalse-deaddeadcodeclaims in the oracle-visible sample,8,000 / 8,000cross-command consistency comparisons in agreement,10 / 10repositories inside the performance budget (slowest median cold build 15.4K lines/second by wall time, worst query p95 73.8 ms, highest peak RSS 771 MB), and 3,383 automated tests with no failures or skips. The same gates run in CI (the scheduledEval workflowand every release tag), andnpm run trust:gatereproduces the release board locally. Pinned sources:eval/lib/repos.js.

Semantic runs draw a deterministic, reference-stratified sample of up to 50 compiler/LSP symbols per repository, then check caller identity, callee identity, account conservation, review burden, and the public commandsfind,show,source,trace,impact,usages, andtestsagainst that same external population. Unverified precision is reported separately and is intentionally much lower on dispatch-heavy code: those entries are review candidates, never confirmed claims.

Beyond the publish gate, a scheduled board re-checks 22 pinned repositories across every supported oracle language (zod, express, hono, zustand, fastify, rich, click, grpc-go, chi, cursive, gson, jsoup, and friends), plus a rotating fresh-repo arm of codebases the engine was never tuned on. Repositories that expose a gap stay on the board; they don't get removed to keep a table pretty. These are measured results on pinned code, not a claim of universal program understanding or identical performance on every machine.

Will this change break a call site you've never seen? Check before you edit:

$ ucn check expandGlob Verification: expandGlob ════════════════════════════════════════════════════════════ core/discovery.js:314 expandGlob (pattern: string, options: number = {}) : string[] Expected arguments: 1-2 STATUS: ✓ All calls valid Total calls: 7 Valid: 7 Mismatches: 0 Uncertain: 0 Patterns: 4 in try, 4 in callback ACCOUNT: "expandGlob" occurs on 14 lines in 6 files: 7 confirmed, 0 unverified, 7 non-call (4 import, 1 definition, 2 reference, 0 other-text), 0 other-target, 0 unaccounted

ThePatterns:line classifies call-site structure (inLoop,inTry,inCallback,awaited) so risky sites stand out. Then preview the refactor. UCN shows exactly what would need to change and where:

$ ucn plan expandGlob --rename-to=expandGlobPattern Refactoring plan: rename ════════════════════════════════════════════════════════════ core/discovery.js:314 SIGNATURE CHANGE: Before: expandGlob (pattern: string, options: number = {}) : string[] After: expandGlobPattern (pattern: string, options: number = {}) : string[] CHANGES NEEDED: 12 Files affected: 5 Definition 1, calls/references 7, imports 4, exports 0; manual review required for 0 of these changes BY FILE: cli/index.js (2 changes) :771 [call] const files = expandGlob(pattern); → Rename to: const files = expandGlobPattern(pattern); :15 [import] const { expandGlob, findProjectRoot } = require('../core/discovery'); → Update import: const { expandGlobPattern, findProjectRoot } = require('../core/discovery'); ... (more changes in core/discovery.js, core/cache.js, core/project.js, test/integration.test.js)

Anythingplancan't represent safely is markedneedsReviewinstead of being silently rewritten. Before committing, point the same machinery at your Git diff:

ucn impact --staged # what did I change, and who depends on it? ucn check --staged # signature drift, orphaned functions, tests to run

Which tests actually exercise this function, directly or three hops away?

$ ucn tests expandGlob --depth=3 affected-tests: expandGlob ════════════════════════════════════════════════════════════ core/discovery.js:314 1 function changed → 12 functions affected (depth 3) Test files to run (30): test/integration.test.js (links: expandGlob, build, idx, setupProject) L169: const files = expandGlob('/.go', { root: tmpDir }); [call] test/prerelease-audit.test.js (links: isCacheStale, runInteractive, build, idx) L39: const index = idx(dir); [call] ... Summary: 12 affected → 30 statically linked test files, 5/12 functions linked (42%) · 1 possibly affected (unverified chains)

testsreports static call/reference linkage, not runtime coverage. Functions reached only through unverified edges are listed separately aspossibly affected, and empty results warn about subprocess tests, reflection, and external harnesses that may still exercise the target.

One command answers "what is this codebase?" Here it is on ripgrep:

$ ucn repo PROJECT ORIENTATION — ripgrep ════════════════════════════════════════════════════════════ 100 files · 4755 symbols · language mix by symbols: rust 100% TOP DIRS (by symbols): crates/core/flags 1510 symbols · 6 file(s) crates/printer/src 677 symbols · 11 file(s) crates/ignore/src 607 symbols · 8 file(s) crates/globset/src 331 symbols · 5 file(s) HOT (most-called production functions, top 8 of 2238 raw candidates): parse_low_raw — 545 call(s) · crates/core/flags/parse.rs:139 SearcherBuilder.build — 123 call(s) · crates/searcher/src/searcher/mod.rs:315 Searcher.search_reader — 123 call(s) · crates/searcher/src/searcher/mod.rs:727 RegexMatcher.new — 100 call(s) · crates/regex/src/matcher.rs:385 ... ENTRY POINTS: 426 — test 421, runtime 5 TRUST: PARTIAL — 48 glob import(s), 5 unsupported source file(s) (ucn repo --sections=health --deep for detail) SKIPPED SOURCE: 5 file(s) (Shell 4, Ruby 1) — use grep/ripgrep plus a language-native analyzer. Next: ucn show parse_low_raw · ucn repo --sections=files --detailed · ucn repo --sections=health --deep

Size, layout, hot spots, entry points, and an honest trust line. Note theSKIPPED SOURCEhandoff: when a repo mixes in languages UCN can't parse, it says so and points you at the right tool, instead of presenting a clean-looking answer over a partial index.

$ ucn deadcode --exclude=test # run on ripgrep Dead code: 3 unused symbol(s) crates/globset/src/serde_impl.rs [ 38- 42] Glob.deserialize (method) [ 70- 74] GlobSet.deserialize (method) crates/matcher/src/lib.rs [ 397- 399] Captures.as_match (method) 33 decorated/annotated symbol(s) hidden (framework-registered). Use --include-decorated to include them. 903 exported symbol(s) excluded from the audit (public API may have external callers). Use --include-exported to audit them. WARNING: source coverage is incomplete (5 unsupported-language); 17 candidate name(s) found in skipped source were suppressed.

Three claims, and every one is re-checked against rust-analyzer in CI: a default-audit claim with an oracle-visible reference fails the build. Notice what itdidn'tclaim: exported API that external code may call, framework-registered symbols, and anything whose name appears in files UCN couldn't parse.deadcodeis deliberately a candidate generator. Before deleting, corroborate withusages,impact,api, and your compiler and tests.

For missing-await bugs,ucn audit-asynclists async calls inside async functions that lackawait(JS/TS/Python).

ucn deps src/server.ts --direction=imports --detailed ucn deps src/server.ts --direction=importers --depth=3 ucn deps --cycles # circular imports ucn api # public surface of the project ucn entrypoints --type=http # runtime and framework roots ucn endpoints --bridge --unmatched # server routes with no client, and vice versa

endpoints --bridgematches server routes to client requests across languages: Express/Fastify/Koa/NestJS/Next.js, Flask/FastAPI, Spring/JAX-RS, Go net/http (Gin/Echo/Chi/Fiber), axum/actix-web, and ASP.NET on the server side; fetch/axios, requests/httpx, RestTemplate/WebClient, reqwest, and .NET HttpClient on the client side. Exact, partial, and uncertain matches stay in separate tiers.

Extract and search without opening whole files

ucn source core/discovery.js:314:expandGlob # exactly one function ucn source core/discovery.js --range=314-364 # exactly one range ucn search '$scope.$apply' # literal by default ucn search 'TODO|FIXME' --regex # regex is explicit ucn search --type=call --receiver=client # structural search ucn usages expandGlob --include-tests # every occurrence, classified

usagesis the escape hatch: the complete literal-name inventory (calls, definitions, imports, references, comments, strings), for when you want everything the text contains, not just what the engine can prove. Regex search runs on an RE2-compatible linear-time engine; hostile nested repetition is rejected up front instead of hanging your terminal.

Runucn --helpfor every flag. Related modes live behind parameters rather than extra verbs:tracehandles down, up, and to-entry-points;impact/checkhandle a symbol or the current Git diff;showprojects any subset of sections. Flags that don't apply to a command produce an explicit warning instead of silently changing the task.

CLI, MCP, file mode, project mode, glob mode, and interactive mode resolve commands through the same registry, handlers, index, cache, and formatters: same answers everywhere, different delivery.

- The CLI prints readable text;--jsonreturns a stable machine envelope.
- MCP exposes exactly one tool nameducn. Itscommandenum lists the 18 tasks, snake_cased where needed (audit_async,project_dir,class_name). A persistent MCP process keeps the index warm across calls.
- Targeted text answers default to a 10K-character budget, broad ones to 3K, ceiling 100K (--max-chars/max_chars). Truncation preserves ACCOUNT, CONTRACT, and WARNING lines; JSON is never text-truncated.

{ "meta": { "command": "audit-async", "canonicalCommand": "auditAsync", "ok": true, "contract": {} }, "data": {} }

Failures keep the envelope:meta.ok: false,data: null, and anerrorstring, with the command contract when known.

The incremental index lives under your user cache root, not in the repo:UCN_CACHE_DIRif set, else$XDG_CACHE_HOME/ucn,~/Library/Caches/ucn(macOS),%LOCALAPPDATA%/ucn/cache(Windows), or~/.cache/ucn. Canonical path hashes keep same-named checkouts separate.--no-cachebypasses,--clear-cacheclears the current project,--clear-cache --allclears every bounded UCN cache. Old in-project.ucn-cachedirectories are migrated out automatically on first use.

All parsers feed the same versioned language IR and index path, and sequential and worker builds are tested to produce identical symbols, calls, imports, and evidence.

- JavaScript / TypeScript / JSX / TSX- functions, classes, imports/exports, typed receivers, aliases, callbacks, async flow, framework roots.
-
Python- functions, classes, annotations, decorators, imports, comprehensions, context-manager bindings, async flow, framework roots.
-
Go, Rust, Java- nominal receivers, methods, inheritance/traits/interfaces, package and path ownership, overload/arity discipline, framework roots.
-
C- functions, structs, macros, includes, calls, entry points, API analysis.
-
C++- C coverage plus classes, methods, constructors, inheritance, namespaces, overloads, templates, typed field receivers.
-
C#- namespaces, classes/interfaces/records, fields/properties, attributes, overload-aware calls, async flow, top-level programs, .NET stack frames, ASP.NET/HttpClient endpoints.
-
HTML- inline JavaScript andon
event handlers.

For C and C++, acompile_commands.jsonimproves header-language, include-path, and ownership context when available. UCN keeps AST-proven definitions from recoverable preprocessor branches without claiming which branch a particular build activates.

- Regression discipline- every fixed defect gets a focused test.
-
Surface coverage- all 18 commands run through CLI text, CLI JSON, and MCP; parity between them is guarded by architecture tests.
-
External ground truth- real compilers and language servers adjudicate caller, callee, command, and dead-code claims on pinned repositories (see the board).
-
Release-blocking budgets*- publishing requires 100% in-scope semantic recall, ≥98% confirmed precision, a conserved account for every sample, zero cross-command disagreements, zero default-arm false-dead claims, and the performance gate (≥10K lines/second cold build by wall time and ≥3K by CPU time, query p50 ≤75 ms, p95 ≤250 ms, bounded peak RSS), all on the actual release board.

npm run verify # lint + full test suite npm run trust:gate # the release board: semantic, dead-code, consistency, performance

Gate runs write their reports undereval/reports/as local run artifacts; the pinned manifest iseval/lib/repos.js. Before a tag, the Eval workflow's pre-tag dry run must pass on the actual CI runner.

One tool, 18 commands, compact source-linked answers that keep their trust metadata even when truncated.

# Claude Code claude mcp add ucn -- npx -y ucn --mcp # OpenAI Codex CLI codex mcp add ucn -- npx -y ucn --mcp # VS Code Copilot code --add-mcp '{"name":"ucn","command":"npx","args":["-y","ucn","--mcp"]}'
{ "mcpServers": { "ucn": { "command": "npx", "args": ["-y", "ucn", "--mcp"] } } }
{ "servers": { "ucn": { "type": "stdio", "command": "npx", "args": ["-y", "ucn", "--mcp"] } } }
# Claude Code mkdir -p ~/.claude/skills cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.claude/skills/ # OpenAI Codex CLI mkdir -p ~/.agents/skills cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.agents/skills/
$npmRoot = npm root -g New-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\skills" Copy-Item -Recurse "$npmRoot\ucn\.claude\skills\ucn" "$env:USERPROFILE\.claude\skills\" New-Item -ItemType Directory -Force "$env:USERPROFILE\.agents\skills" Copy-Item -Recurse "$npmRoot\ucn\.claude\skills\ucn" "$env:USERPROFILE\.agents\skills\"

The skill teaches an agent how to orient, pin symbols, choose the smallest useful command, interpret the evidence tiers, and recover from incomplete answers. It's guidance over the same engine, not a second implementation.

- Static, single-project analysis - dependencies likenode_modulesandsite-packagesaren't indexed, and nothing is executed.
- Reflection, generated code, runtime registration, dynamic property access, and external consumers can be invisible. UCN reports these blind spots (repo --sections=health --deep) rather than pretending they don't exist.
- Interface, trait, template, overload, and untyped-receiver dispatch may stay in the UNVERIFIED tier with a reason instead of being guessed. When same-name definitions compete,showlists their stable handles once so agents can see exactly what needs disambiguation.
- C/C++ analysis doesn't run the preprocessor or compiler; build-specific branches, advanced templates, and macro expansion can remain unresolved. C# analysis doesn't run Roslyn; source generators and external assembly semantics stay outside the index.
- HTML has regression coverage but no compiler/LSP real-repository oracle.
- Large repos take a few seconds on the first query, then use the cache.

If a decision needs compiler completeness or runtime truth, use the compiler, the type checker, the test runner, or a profiler. Those are different tools for different jobs. UCN's job is getting you to the right code fast, with answers you can audit.

This is a web browser that enables your coding agent, such as Claude Code, to visit websites on your behalf and assist you in identifying bugs or creating UI test cases.

Fast lightweight Java MCP server framework - Build Model Context Protocol servers with minimal boilerplate and full TypeScript SDK compatibility

A Java plugin that exposes the Jadx decompiler API over HTTP for interaction with MCP clients.

Specialized tools for analyzing and migrating Java applications from Java EE 8 (javax.) to Jakarta EE 9+ (jakarta.*).

Java Archive Reader Protocol MCP server - Give AI agents X-ray vision into compiled Java code by decompiling JAR/WAR/EAR files and Maven/Gradle dependencies

A Model Context Protocol (MCP) server for searching Java documentation. This server enables AI assistants to search and retrieve Java API documentation from JSON files.

Allows AI assistants to remotely drive the JetBrains debugger via MCP, including breakpoints, stepping, and variable inspection.

Resolves your Gradle project’s real classpath and returns Java source, method signatures, and class structure for any dependency class—using the version your build actually uses, not random files from ~/.gradle/caches.

Legacy Java to Microservices Refactoring

A community gateway to migrate legacy Jakarta EE monoliths into Spring Boot using AST parsing.

Search for and retrieve detailed information, including READMEs and metadata, for Maven packages from Maven Central.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.