codebridge-mcp
v5.0.0
Published
AI-friendly MCP server for navigating any TypeScript or JavaScript codebase
Readme
Codebridge
An MCP (Model Context Protocol) server that gives AI assistants a structured, queryable view of any TypeScript or JavaScript project — single-package repos or large monorepos — without needing to read thousands of files.
Installation
npm install -g codebridge-mcpThen build the index for your repo and connect it to Claude Code — see Setup below.
The Problem
Large codebases have tens of thousands of source files and hundreds of thousands of symbols. Asking an AI to "understand the codebase" by reading files is slow, expensive, and context-limited. The Codebase Interface Layer solves this by pre-indexing the repo into four JSON files and exposing them as tools Claude can call directly.
Works with any JS/TS project: single-package repos, monorepos (Yarn/npm/pnpm workspaces, Lerna, Turborepo, Nx), or any custom layout. Both ESM and CommonJS are supported.
Why MCP tools beat grep
The fundamental difference: grep treats code as text. MCP treats code as a graph.
Code is a graph of relationships — functions call functions, types reference types, modules import modules, packages depend on packages. grep is blind to this graph.
1. Understands re-exports and aliases
A symbol has one identity but potentially many names across the codebase:
// pkg-types/index.ts
export { FindRecipientPersonaType } from './types/FindRecipientPersonaType';
// pkg-ui/index.ts
export { FindRecipientPersonaType } from 'pkg-types';
// consumer file
import { FindRecipientPersonaType } from 'pkg-ui'; // same type, different import pathgrep finds the string FindRecipientPersonaType. MCP finds the symbol — regardless of which barrel it came through.
2. Returns structured results, not raw text
find_call_sites gives you file + line number + code snippet for every real invocation — already filtered to actual call sites (fn(, <Component, new Foo(). grep returns every line matching a pattern, including comments, imports, type annotations, and strings — you sift through noise.
3. Semantic symbol resolution
get_type_definition resolves a type name across the entire monorepo index in one call. With grep you'd search for the definition, find 10 matches (re-exports, type aliases, interface merges), and manually determine which is canonical.
4. Transitive impact analysis
grep can find direct usages. It cannot answer: "if I change this type's shape, which packages break downstream?"
Type T (pkg-types)
└─ used by PackageA
└─ re-exported by PackageB
└─ consumed by PackageC ← grep never finds thisfind_impact walks the dependency graph. grep only sees one hop.
5. Package-boundary awareness
find_file_package, search_packages, get_package_info understand the monorepo's workspace graph. grep has no concept of package ownership — you'd have to infer it from directory paths and traverse package.json files manually.
6. Natural language queries
search_description lets you ask "where is the zero-query search logic?" without knowing any symbol names. grep requires you to already know what you're looking for.
7. Eliminates the cold-start problem
When entering an unfamiliar area, get_file_context gives you a curated map of a file's symbols, imports, and importers in one call. With grep you'd spend multiple rounds building a mental model manually.
8. Indexed, not scanned
The MCP tools query a pre-built semantic index (like a language server). grep does a full file scan every time — slower on a large monorepo, and blind to semantics.
When grep/Glob is still the right tool
| Situation | Use | |---|---| | Content-level searches (specific string literal, regex pattern) | grep | | Searching comments or log messages | grep | | You know the exact file and just need a line number | grep | | MCP returns no results (private/protected members, instance method calls) | grep | | Searching across file types MCP doesn't index | grep |
Architecture
┌──────────────────────────────────────────────────────────────────────────┐
│ monorepo root │
│ │
│ packages/*/package.json ──┐ │
│ packages/*/src/index.ts ──┤ │
│ packages/*/**/*.ts(x) ──┤──► indexBuilder.ts │
│ apps/*/**/*.ts(x) ──┤ │ │
│ libs/*/**/*.js(x) ──┘ ▼ │
│ ┌────────────────┐ │
│ │ data/ │ │
│ │ graph.json │ package dependency │
│ │ symbols.json │ package-level exports│
│ │ files.json │ file-level index │
│ │ types.json │ type definition bodies│
│ └───────┬────────┘ │
│ │ │
│ store.ts (load/cache) │
│ │ │
│ ┌──────────┬──────────┬─────────┼─────────┬──────────┬────────┐ │
│ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │
│ buildIndex searchPkgs getPkgInfo searchSym findImpact searchFile ... │
│ │ │
│ index.ts (MCP server) │
│ stdio transport │
└─────────────────────────────────────────┼────────────────────────────────┘
│
Claude Code
(calls tools by name)Four-phase index build (run once)
npm run build-index walks your workspace package directories and writes four files:
| File | Contents |
| ------------------- | ---------------------------------------------------------------- |
| data/graph.json | All packages, dependency edges, reverse dependents map |
| data/symbols.json | Symbols exported from each package's barrel file (index.ts) |
| data/files.json | Every source file, symbols, import graph, reverse importedBy |
| data/types.json | Full declaration bodies for every interface/type/enum/schema |
All query tools load these files once into memory (cached). Subsequent calls are in-process lookups — no file scanning.
Setup
1. Install
# Recommended — global install from npm
npm install -g codebridge-mcp
# Or build from source
git clone https://github.com/your-org/codebridge-mcp
cd codebridge-mcp && npm install && npm run build2. Configure for your monorepo
The indexer auto-detects your workspace structure with no configuration required for most setups. Configuration is resolved in this priority order:
Auto-detection (zero config)
Single-package repos are detected automatically — if no workspace packages are found, the repo root package.json is treated as the single package and all source files under the root are indexed.
Monorepos are detected by reading your existing package manager config:
| Config file | Field read |
|---|---|
| package.json | workspaces (Yarn / npm workspaces) |
| pnpm-workspace.yaml | packages list |
| lerna.json | packages field |
| (fallback) | ['packages', 'shared'] |
For barrel export detection (which file in each package is the public API), the indexer reads each package's package.json main, module, exports, and types fields to derive the source path, then falls back to src/index.ts, index.ts, src/index.js, index.js.
Examples that work with zero config:
# Yarn / npm workspaces in package.json:
{ "workspaces": ["packages/*", "apps/*"] }
# pnpm-workspace.yaml:
packages:
- 'packages/*'
- 'apps/*'
- 'libs/*'
# lerna.json:
{ "packages": ["packages/*", "apps/*"] }Explicit override — .codebase-index.json
Place this file in your monorepo root to override any auto-detected setting:
{
"packageDirs": ["packages", "apps", "libs", "services"],
"indexFiles": ["src/index.ts", "src/main.ts", "index.ts"],
"skipDirs": ["coverage", "fixtures", "e2e"]
}| Field | Default | Description |
|---|---|---|
| packageDirs | auto-detected | Directories under repo root that contain workspace packages |
| indexFiles | auto-detected per package | Barrel export filenames to scan for public API symbols |
| skipDirs | [] | Additional directory names to skip when walking the file tree (added to the built-in skip list) |
The built-in skip list already excludes: node_modules, .git, dist, build, lib, obj, .yarn, .cache, coverage, .turbo, .nx, .next, .nuxt, out, storybook-static.
3. Build the index
Full index (entire monorepo)
# Run from the monorepo root
npm run build-index
# Run from anywhere — point at the repo with --root
npm run build-index -- --root /path/to/my-monorepo
# Or via environment variable
CODEBASE_ROOT=/path/to/my-monorepo npm run build-indexScoped index (one root package + its transitive deps)
Use --scope to limit indexing to a single package and everything it depends on. Useful for large monorepos where a full build takes too long.
# by exact package name
npm run build-index -- --scope my-ui-package
# by file path — resolves to the owning package automatically
npm run build-index -- --scope packages/ui/src/components/Button.tsx
# by directory path inside the package
npm run build-index -- --scope packages/ui/src/components
# include devDependencies in the transitive closure (default: runtime deps only)
npm run build-index -- --scope my-ui-package --dev-deps
# scoped build against a specific repo root
npm run build-index -- --root /path/to/my-monorepo --scope my-package-nameRun npm run build-index at any time to restore the full index.
Flag reference
| Flag | Description |
| ----------------- | --------------------------------------------------------------------------------------------------- |
| --root <path> | Target repo directory. Defaults to CODEBASE_ROOT env var, then process.cwd() |
| --scope <value> | Limit to one root package + its transitive deps. Accepts package name, file path, or directory path |
| --dev-deps | Also follow devDependencies when collecting the transitive closure (only with --scope) |
4. Connect to Claude Code
If installed globally via npm, use codebridge-mcp as the command directly:
{
"mcpServers": {
"codebridge-mcp": {
"command": "codebridge-mcp",
"env": {
"CODEBASE_ROOT": "/path/to/my-repo"
}
}
}
}If running from a local build, point at the compiled file:
{
"mcpServers": {
"codebridge-mcp": {
"command": "node",
"args": ["/path/to/codebridge-mcp/build/index.js"],
"env": {
"CODEBASE_ROOT": "/path/to/my-repo"
}
}
}
}Restart Claude Code to pick it up. Approve the server when prompted.
Language support
The indexer extracts symbols and imports from:
| Language | Extensions | Module formats |
|---|---|---|
| TypeScript | .ts, .tsx | ESM (import/export) |
| JavaScript | .js, .jsx, .mjs, .cjs | ESM + CommonJS (require/module.exports) |
The following file types are tracked in the index (path only, no symbol extraction):
.scss, .css, .less, .json, .md, .mdx, .svg, .graphql, .gql
Data Model
graph.json — package dependency graph
{
"packages": {
"my-utils": {
"name": "my-utils",
"path": "packages/utils/my-utils",
"dependencies": ["my-types", "my-config"],
"devDependencies": []
}
},
"dependents": {
"my-utils": ["my-ui", "my-services"]
},
"builtAt": "2026-01-01T10:00:00.000Z"
}symbols.json — package-level exported symbols
Only covers symbols re-exported from barrel files (index.ts, index.js, or whatever is detected per package).
{
"bySymbol": {
"Button": ["my-ui"],
"formatDate": ["my-utils"]
},
"byPackage": {
"my-utils": [
{ "name": "formatDate", "isType": false },
{ "name": "DateRange", "isType": true }
]
},
"builtAt": "..."
}files.json — file-level index
{
"files": {
"packages/utils/my-utils/src/formatDate.ts": {
"path": "packages/utils/my-utils/src/formatDate.ts",
"package": "my-utils",
"description": "Date formatting utilities.",
"symbols": ["formatDate", "DateRange"],
"symbolKinds": {
"formatDate": "function",
"DateRange": "type"
},
"symbolDocs": {
"formatDate": "Formats a Date into a locale string."
},
"pkgImports": ["date-fns"],
"fileImports": ["../utils/locale"]
}
},
"bySymbol": { "formatDate": ["packages/utils/my-utils/src/formatDate.ts"] },
"byPackage": { "my-utils": ["packages/utils/my-utils/src/formatDate.ts", ...] },
"importedBy": { "packages/utils/my-utils/src/formatDate.ts": ["packages/ui/my-ui/src/DatePicker.tsx"] },
"builtAt": "..."
}Symbol kind classification
| Kind | Covers |
| ----------------------- | ---------------------------------------------------------------------------------------- |
| schema | const whose name ends in Schema/Contract/Validator/Payload/Request/Response, or whose value uses Zod/Yup calls |
| interface | TypeScript interface declarations |
| type | TypeScript type aliases |
| enum | enum / const enum |
| class | class / abstract class |
| function | function / async function |
| const / let / var | Everything else |
| method | Class member method (all visibilities: public, protected, private) |
| property | Class member property |
| getter / setter | Class accessor methods |
Class members are indexed with qualified names ClassName.memberName.
MCP Tools (14 total)
Package-level tools
build_index
Scans the entire repo and writes all four data files. Automatically skipped when a full index already exists — only re-run after package changes.
build_index()
→ Index built successfully
Packages indexed : 312
Package-level symbols : 4821
Source files indexed : 8103
File-level symbols : 11294
Type definitions : 3840build_scoped_index
Build a focused index for one package and all its transitive dependencies. Accepts a package name, a file path, or a directory path — all resolve to the owning package automatically.
build_scoped_index({ packageName: "my-ui-package" })
→ Scoped index built for "my-ui-package"
Root package : my-ui-package
Total packages : 87 (transitive closure)
Source files : 2341
Symbols : 5102
All query tools now operate on this scoped dataset.
Run build_index to restore the full repo index.search_packages
Fuzzy-search packages by name or path fragment.
search_packages({ query: "button" })
→ my-button path: packages/ui/my-button
→ my-icon-button path: packages/ui/my-icon-buttonget_package_info
Full details for one package: path, deps, dependents, exported symbols.
get_package_info({ packageName: "my-utils" })
→ Package: my-utils
Path: packages/utils/my-utils
Used by: 24 packages
Exports: formatDate, parseDate, DateRange, ...search_symbol
Find which package publicly exports a symbol (via its barrel file).
search_symbol({ symbol: "Button", exact: true })
→ Button [class] from: my-uifind_impact
Blast-radius analysis — all packages that transitively depend on a given package.
find_impact({ packageName: "my-utils" })
→ Total impacted: 24 | Direct: 8 | Indirect: 16find_file_package
Given any file path, identify its owning package. Walks up the directory tree — no index required.
find_file_package({ filePath: "packages/utils/my-utils/src/formatDate.ts" })
→ Package: my-utils | Used by 24 packagesFile-level tools
search_file
Find any source file by filename or path fragment.
search_file({ query: "formatDate" })
→ packages/utils/my-utils/src/formatDate.ts
package: my-utils
symbols: formatDate, DateRangesearch_deep_symbol
Find the exact file that defines any symbol — including internal helpers never re-exported from index.ts.
search_deep_symbol({ symbol: "useButtonState" })
→ useButtonState
packages/ui/my-button/src/hooks/useButtonState.ts [my-button]get_type_definition
Look up the full declaration body of any TypeScript interface, type alias, enum, or Zod/Yup schema.
get_type_definition({ query: "ButtonProps" })
→ ── ButtonProps (interface) [my-button]
packages/ui/my-button/src/types/ButtonProps.ts
export interface ButtonProps {
label: string;
onClick?: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary';
}search_description
Find components and files by describing what they do in plain English.
search_description({ query: "formats date for display" })
→ packages/utils/my-utils/src/formatDate.ts (score: 34)
summary : Utilities for formatting dates for UI rendering.
symbols : formatDate, parseDate, DateRangeget_file_context
Full context for a single file: symbols it defines, what it imports, and what imports it.
get_file_context({ filePath: "formatDate" })
→ File: packages/utils/my-utils/src/formatDate.ts
Package: my-utils
Symbols defined (2):
formatDate, DateRange
Imports from packages (1):
date-fns
Imported by (3 files):
packages/ui/my-ui/src/DatePicker.tsx [my-ui]
...find_usages
Find every file that imports a specific symbol. Traverses barrel re-export chains.
find_usages({ symbol: "Button" })
→ Found 18 file(s) importing "Button"
my-app (3 files):
packages/app/my-app/src/pages/Home.tsx
packages/app/my-app/src/components/Toolbar.tsx
...find_call_sites
Find files that actually use a specific function, component, or class — not just files that import it. Returns real usage lines with line numbers.
find_call_sites({ symbol: "formatDate" })
→ Found 7 file(s) calling "formatDate":
my-ui (2 files):
packages/ui/my-ui/src/DatePicker.tsx
L42: const display = formatDate(value, 'MMM d, yyyy');
packages/ui/my-ui/src/Calendar.tsx
L88: label={formatDate(date)}Typical agent workflow
Tracing a change's blast radius
User: "I need to change the DateRange type"
Claude:
1. search_symbol("DateRange")
→ my-utils
2. find_impact("my-utils")
→ 24 packages affected
3. get_type_definition("DateRange")
→ full type body
4. find_usages("DateRange")
→ every file that imports it
5. Edit the type, fix downstream type errorsExploring an unfamiliar area
User: "Find the button hover animation logic"
Claude:
1. search_description({ query: "button hover animation" })
→ packages/ui/my-button/src/hooks/useButtonAnimation.ts
2. get_file_context("useButtonAnimation.ts")
→ imports, importers, symbols
3. Read file and editScoped work on one package
User: "I'm working on my-checkout-flow"
Claude:
1. build_scoped_index({ packageName: "my-checkout-flow" })
→ 87 packages, 2341 files (vs full repo)
2. All subsequent tools operate on this smaller datasetRe-indexing
The data/ files are gitignored — each developer builds their own local index. Re-run npm run build-index after:
- Adding or removing packages
- Renaming a package in
package.json - Significant changes to barrel exports
Compatibility
| Repo type | Auto-detected? | Notes |
|---|---|---|
| Single-package TS/JS repo | ✓ | Root package.json treated as the one package |
| Yarn workspaces (classic & berry) | ✓ | Reads package.json workspaces |
| npm workspaces | ✓ | Reads package.json workspaces |
| pnpm | ✓ | Reads pnpm-workspace.yaml |
| Lerna | ✓ | Reads lerna.json packages |
| Turborepo | ✓ | Reads package.json workspaces (Turbo uses standard workspaces) |
| Nx | Manual | Use .codebase-index.json packageDirs |
| Custom layout | Manual | Use .codebase-index.json packageDirs |
| CommonJS projects | ✓ | require(), module.exports, exports.Foo all indexed |
| ESM projects | ✓ | import/export fully indexed |
