ucn

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, Python, Go, Rust, Java, and HTML inline scripts with tree-sitter, then exposes 28 navigation commands as a CLI tool, MCP server, or agent skill.

UCN - Universal Code Navigator

UCN 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, Python, Go, Rust, Java, and HTML inline scripts with tree-sitter, then exposes 28 navigation commands as a CLI tool, MCP server, or agent skill.

Designed for large codebases where agents waste context on reading large files. UCN's surgical output means agents spend tokens on reasoning, not on ingesting thousands of lines to find three callers, discourages agents from cutting corners, as without UCN, agents working with large codebases tend to skip parts of the code structure, assuming they have "enough data".

Everything runs locally on your machine and nothing leaves your project. The ucn mcp is kept light, as all 28 commands ship as a single MCP tool, under 2k tokens total.


What UCN does

Precise answers without reading files.

  TASK                                    COMMAND
  ─────────────────────                   ─────────────────────

  Pull one function from                  $ ucn fn handleRequest
  a 2000-line file                        → 20 lines, just that function

  Who calls this? Will they               $ ucn impact handleRequest
  break if I change it?                   → 8 call sites, with arguments

  What happens when                       $ ucn trace main --depth=3
  main() runs?                            → full call tree, no file reads

  What can I safely delete?               $ ucn deadcode
                                          → unused functions, AST-verified

  What depends on this file               $ ucn graph src/routes.ts
  before I move it?                       → imports and importers tree

Three Ways to it: ucn mcp, ucn skill, ucn cli

  ┌──────────────────────────────────────────────────────────────────────┐
  │                                                                      │
  │   1. CLI                    Use it directly from the terminal.       │
  │      $ ucn about myFunc     Works standalone, no agent required.     │
  │                                                                      │
  │   2. MCP Server             Any MCP-compatible AI agent connects     │
  │      $ ucn --mcp            and gets 28 commands automatically.      │
  │                                                                      │
  │   3. Agent Skill            Drop-in skill for Claude Code and        │
  │      /ucn about myFunc      OpenAI Codex CLI. No server needed.      │
  │                                                                      │
  └──────────────────────────────────────────────────────────────────────┘

How agents understand code today

AI agents working with code typically do this:

  grep "functionName"      →  47 matches, 23 files
       │
       ▼
  read file1.ts            →  2000 lines... wrong function
       │
       ▼
  read file2.ts            →  1500 lines... still not it
       │
       ▼
  read file3.ts            →  found it, finally
       │
       ▼
  grep "whoCallsThis"      →  start over
       │
       ▼
  ┌─────────────────────────────────────────┐
  │  Half the context window is gone.       │
  │  The agent hasn't changed a single line.│
  └─────────────────────────────────────────┘

How UCN works: tree-sitter, locally

  ┌──────────────────────────────────────────────┐
  │              Any AI Agent                    │
  │  Claude Code · Cursor · Windsurf · Copilot   │
  └───────────────────────┬──────────────────────┘
                          │
                         MCP
                          │
                          ▼
                 ┌───────────────────┐
                 │   UCN MCP Server  │
                 │   28 commands     │
                 │   runs locally    │
                 └────────┬──────────┘
                          │
                    tree-sitter AST
                          │
        ┌─────────────────┴───────────────────┐
        │          Supported Languages        │
        │ JS/TS, Python, Go, Rust, Java, HTML │
        └─────────────────────────────────────┘

Before and after UCN

  WITHOUT UCN                              WITH UCN
  ──────────────────────                   ──────────────────────

  grep "processOrder"                      ucn impact "processOrder"
       │                                        │
       ▼                                        ▼
  34 matches, mostly noise                 8 call sites, grouped by file,
       │                                   with actual arguments passed
       ▼                                        │
  read service.ts  (800 lines)                  │
       │                                        │
       ▼                                        │
  read handler.ts  (600 lines)             ucn smart "processOrder"
       │                                        │
       ▼                                        ▼
  read batch.ts    (400 lines)             function + all dependencies
       │                                   expanded inline
       ▼                                        │
  read orders.test (500 lines)                  │
       │                                        ▼
       ▼                                   Done. Full picture.
  grep "import.*processOrder"              Ready to make the change.
       │
       ▼
  read routes.ts   (300 lines)
       │
       ▼
  ... still not sure about full impact


  8+ tool calls                            2 tool calls
  Reads thousands of lines                 Reads zero full files
  Context spent on file contents           Context spent on reasoning

After editing code, before committing:

  WITHOUT UCN                              WITH UCN
  ──────────────────────                   ──────────────────────

  git diff                                 ucn diff_impact
       │                                        │
       ▼                                        ▼
  see changed lines, but which             13 modified functions
  functions do they belong to?             8 new functions
       │                                   22 call sites across 9 files
       ▼                                        │
  read each file to map hunks                   ▼
  to function boundaries                   Each function shown with:
       │                                     • which lines changed
       ▼                                     • every downstream caller
  ucn impact on each function                • caller context
  you identified (repeat 5-10x)                 │
       │                                        ▼
       ▼                                   Done. Full blast radius.
  hope you didn't miss one                 One command.


  10+ tool calls                            1 tool call

Text search vs AST

  Code: processOrder(items, user)


  ┌─────────────────────────────────────────────────────────────────┐
  │  grep "processOrder"                                            │
  │                                                                 │
  │    ✓  processOrder(items, user)          ← the actual call      │
  │    ✗  // TODO: refactor processOrder     ← comment, not a call  │
  │    ✗  const processOrder = "label"       ← string, not a call   │
  │    ✗  order.processOrder()               ← different class      │
  │    ✗  import { processOrder }            ← import, not a call   │
  │                                                                 │
  │  5 results. 1 is what you wanted.                               │
  └─────────────────────────────────────────────────────────────────┘


  ┌─────────────────────────────────────────────────────────────────┐
  │  ucn context "processOrder"                                     │
  │                                                                 │
  │    Callers:                                                     │
  │      handleCheckout    src/api/checkout.ts:45                   │
  │      batchProcess      src/workers/batch.ts:12                  │
  │      runDailyOrders    src/jobs/daily.ts:88                     │
  │                                                                 │
  │    Callees:                                                     │
  │      validateItems     src/orders/validate.ts:20                │
  │      calculateTotal    src/orders/pricing.ts:55                 │
  │      saveOrder         src/db/orders.ts:30                      │
  │                                                                 │
  │  3 callers, 3 callees. Verified from the AST.                   │
  └─────────────────────────────────────────────────────────────────┘

The tradeoff: text search works on any language and any text. UCN only works on 5 languages + HTML, but gives structural understanding within those.


UCN commands in action

Extract a function from a large file without reading it:

$ ucn fn expandGlob

core/discovery.js:135
[ 135- 166] expandGlob(pattern, options = {})
────────────────────────────────────────────────────────────
function expandGlob(pattern, options = {}) {
    const root = path.resolve(options.root || process.cwd());
    const ignores = options.ignores || DEFAULT_IGNORES;
    ...
    return files.sort(compareNames);
}

See who calls it and what it calls:

$ ucn context expandGlob

Context for expandGlob:
════════════════════════════════════════════════════════════

CALLERS (7):
  [1] cli/index.js:1847 [runGlobCommand]
    const files = expandGlob(pattern);
  [2] core/project.js:81
    const files = expandGlob(pattern, {
  [3] core/project.js:3434
    const currentFiles = expandGlob(pattern, { root: this.root });
  ...

CALLEES (2):
  [8] parseGlobPattern [utility] - core/discovery.js:171
  [9] walkDir [utility] - core/discovery.js:227

See what breaks if you change it:

$ ucn impact shouldIgnore

Impact analysis for shouldIgnore
════════════════════════════════════════════════════════════
core/discovery.js:289
shouldIgnore (name, ignores, parentDir)

CALL SITES: 2
  Files affected: 1

BY FILE:

core/discovery.js (2 calls)
  :255 [walkDir]
    if (shouldIgnore(entry.name, options.ignores, dir)) continue;
    args: entry.name, options.ignores, dir
  :373 [detectProjectPattern]
    !shouldIgnore(entry.name, DEFAULT_IGNORES)) {
    args: entry.name, DEFAULT_IGNORES

Get a function with all its dependencies inline:

$ ucn smart shouldIgnore

shouldIgnore (core/discovery.js:289)
════════════════════════════════════════════════════════════
function shouldIgnore(name, ignores, parentDir) {
    for (const pattern of ignores) {
        if (pattern.includes('*')) {
            const regex = globToRegex(pattern);
            ...
        }
    }
    ...
}

─── DEPENDENCIES ───

// globToRegex [utility] (core/discovery.js:208)
function globToRegex(glob) {
    let regex = glob.replace(/[.+^$[\]\\]/g, '\\$&');
    ...
    return new RegExp('^' + regex + '$');
}

Trace the call tree:

$ ucn trace expandGlob --depth=2

Call tree for expandGlob
════════════════════════════════════════════════════════════

expandGlob
├── parseGlobPattern (core/discovery.js:171) [utility] 1x
│   └── globToRegex (core/discovery.js:208) [utility] 1x
└── walkDir (core/discovery.js:227) [utility] 1x
    └── shouldIgnore (core/discovery.js:289) [utility] 1x

See the impact of your recent edits:

$ ucn diff-impact --base=HEAD~1

Diff Impact Analysis (vs HEAD~1)
════════════════════════════════════════════════════════════
3 modified, 1 new, 12 call sites across 4 files

MODIFIED FUNCTIONS:

  processOrder
  src/orders/service.ts:45
  processOrder (items: Item[], user: User): Promise<Order>
  Lines added: 48-52
  Lines deleted: 49
  Callers (3):
    src/api/checkout.ts:89 [handleCheckout]
      await processOrder(cart.items, req.user)
    src/workers/batch.ts:12 [batchProcess]
      processOrder(order.items, systemUser)
    src/jobs/daily.ts:88 [runDailyOrders]
      results.push(await processOrder(items, admin))

  validateItems
  src/orders/validate.ts:20
  validateItems (items: Item[]): ValidationResult
  Lines added: 25-30
  Callers (2):
    src/orders/service.ts:46 [processOrder]
      const valid = validateItems(items)
    src/api/admin.ts:55 [bulkValidate]
      return items.map(i => validateItems([i]))

NEW FUNCTIONS:
  calculateShipping — src/orders/shipping.ts:10
  calculateShipping (items: Item[], region: Region): number

MODULE-LEVEL CHANGES:
  src/orders/service.ts: +5 lines, -1 lines

Scoped to staged changes or a specific file:

$ ucn diff-impact --staged                 # Only what's staged for commit
$ ucn diff-impact --base=main              # Everything since branching from main
$ ucn diff-impact --file=src/orders        # Only changes in this path

Find unused code:

$ ucn deadcode

Dead code: 15 unused symbol(s)

cli/index.js
  [1649-1654] extractFunctionNameFromContent (function)
core/project.js
  [1664-1694] findReExportsOf (method)
  [1998-2020] withCompleteness (method)
...

Install

npm install -g ucn

As an MCP Server

One-line setup for supported clients:

# 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"]}'

Or add to your client's MCP config file manually:

{
  "mcpServers": {
    "ucn": {
      "command": "npx",
      "args": ["-y", "ucn", "--mcp"]
    }
  }
}
{
  "servers": {
    "ucn": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "ucn", "--mcp"]
    }
  }
}

As a Claude Code / Codex Skill

When MCP server is not needed, drop it in as a native skill:

# 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/

As a CLI Tool

Works standalone from the terminal — no agent required:

ucn toc                             # Project overview
ucn about handleRequest             # Understand a function
ucn impact handleRequest            # Before modifying
ucn fn handleRequest --file api     # Extract specific function
ucn --interactive                   # Multiple queries, index stays in memory

UCN workflows

Investigating a bug:

ucn about problematic_function            # Understand it fully
ucn trace problematic_function --depth=2  # See what it calls

Before modifying a function:

ucn impact the_function                   # Who will break?
ucn smart the_function                    # See it + its helpers
# ... make your changes ...
ucn verify the_function                   # Did all call sites survive?

Before committing:

ucn diff-impact                           # What did I change + who calls it?
ucn diff-impact --base=main               # Full branch impact vs main
ucn diff-impact --staged                  # Only staged changes

Periodic cleanup:

ucn deadcode --exclude=test               # What can be deleted?
ucn toc                                   # Project overview

Limitations

  ┌──────────────────────────┬──────────────────────────────────────────┐
  │  Limitation              │  What happens                            │
  ├──────────────────────────┼──────────────────────────────────────────┤
  │                          │                                          │
  │  5 languages + HTML      │  JS/TS, Python, Go, Rust, Java.          │
  │  (no C, Ruby, PHP, etc.) │  Agents fall back to text search for     │
  │                          │  the rest. UCN complements, doesn't      │
  │                          │  replace.                                │
  │                          │                                          │
  ├──────────────────────────┼──────────────────────────────────────────┤
  │                          │                                          │
  │  Dynamic dispatch        │  getattr(), reflection, eval() — UCN     │
  │                          │  does static analysis and can't follow   │
  │                          │  calls that only exist at runtime.       │
  │                          │                                          │
  ├──────────────────────────┼──────────────────────────────────────────┤
  │                          │                                          │
  │  Duck-typed methods      │  obj.method() in JS/TS/Python — when     │
  │                          │  the receiver type is ambiguous, results │
  │                          │  are marked "uncertain" so the agent     │
  │                          │  knows to verify. Go/Rust/Java resolve   │
  │                          │  with high confidence.                   │
  │                          │                                          │
  ├──────────────────────────┼──────────────────────────────────────────┤
  │                          │                                          │
  │  Single project scope    │  UCN follows imports within the project  │
  │                          │  but stops at the boundary — no tracing  │
  │                          │  into node_modules or site-packages.     │
  │                          │                                          │
  ├──────────────────────────┼──────────────────────────────────────────┤
  │                          │                                          │
  │  First-query index time  │  Tree-sitter index is built on first     │
  │                          │  query. A few seconds on large projects. │
  │                          │  Cached and incrementally updated —      │
  │                          │  only changed files are re-indexed.      │
  │                          │                                          │
  └──────────────────────────┴──────────────────────────────────────────┘

All 28 Commands

All commands are accessible through a single ucn MCP tool with a command parameter.

  UNDERSTAND                          MODIFY SAFELY
  ─────────────────────               ─────────────────────
  about         everything in one     impact          all call sites
                call: definition,                     with arguments
                callers, callees,
                tests, source         diff_impact     what changed in a
                                                      git diff + who
  context       callers + callees                     calls it
                (quick overview)
                                      verify          check all sites
  smart         function + helpers                    match signature
                expanded inline
                                      plan            preview a refactor
  trace         call tree — map                       before doing it
                a whole pipeline


  FIND & NAVIGATE                     ARCHITECTURE
  ─────────────────────               ─────────────────────
  find          locate definitions    imports         file dependencies
  usages        all occurrences       exporters       who depends on it
  fn            extract a function    graph           dependency tree
  class         extract a class       related         sibling functions
  toc           project overview      tests           find tests
  deadcode      unused functions      stacktrace      error trace context
  search        text search           api             public API surface
  example       best usage example    typedef         type definitions
  lines         extract line range    file_exports    file's exports
  expand        drill into context    stats           project size stats

License

MIT

UCN - Universal Code Navigator

Related Servers