Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ JS source is plain JavaScript (ES modules) in `src/`. No transpilation step. The
| `config.js` | `.codegraphrc.json` loading, env overrides, `apiKeyCommand` secret resolution |
| `constants.js` | `EXTENSIONS` (derived from parser registry) and `IGNORE_DIRS` constants |
| `native.js` | Native napi-rs addon loader with WASM fallback |
| `registry.js` | Global repo registry (`~/.codegraph/registry.json`) for multi-repo MCP |
| `resolve.js` | Import resolution (supports native batch mode) |
| `logger.js` | Structured logging (`warn`, `debug`, `info`, `error`) |

Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ codegraph deps src/index.ts # file-level import/export map
| 📤 | **Export** | DOT (Graphviz), Mermaid, and JSON graph export |
| 🧠 | **Semantic search** | Embeddings-powered natural language search with multi-query RRF ranking |
| 👀 | **Watch mode** | Incrementally update the graph as files change |
| 🤖 | **MCP server** | Model Context Protocol integration for AI assistants |
| 🤖 | **MCP server** | 12-tool MCP server with multi-repo support for AI assistants |
| 🔒 | **Fully local** | No network calls, no data exfiltration, SQLite-backed |

## 📦 Commands
Expand Down Expand Up @@ -213,6 +213,20 @@ A single trailing semicolon is ignored (falls back to single-query mode). The `-

The model used during `embed` is stored in the database, so `search` auto-detects it — no need to pass `--model` when searching.

### Multi-Repo Registry

Manage a global registry of codegraph-enabled projects. AI agents can query any registered repo from a single MCP session using the `repo` parameter.

```bash
codegraph registry list # List all registered repos
codegraph registry list --json # JSON output
codegraph registry add <dir> # Register a project directory
codegraph registry add <dir> -n my-name # Custom name
codegraph registry remove <name> # Unregister
```

`codegraph build` auto-registers the project — no manual setup needed.

### AI Integration

```bash
Expand Down Expand Up @@ -310,12 +324,14 @@ Benchmarked on a ~3,200-file TypeScript project:

### MCP Server

Codegraph includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server, so AI assistants can query your dependency graph directly:
Codegraph includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server with 12 tools, so AI assistants can query your dependency graph directly:

```bash
codegraph mcp
```

All MCP tools accept an optional `repo` parameter to target any registered repository. Use `list_repos` to see available repos. When `repo` is omitted, the local `.codegraph/graph.db` is used (backwards compatible).

### CLAUDE.md / Agent Instructions

Add this to your project's `CLAUDE.md` to help AI agents use codegraph:
Expand Down Expand Up @@ -468,7 +484,7 @@ const { results: fused } = await multiSearchData(
See **[ROADMAP.md](ROADMAP.md)** for the full development roadmap. Current plan:

1. ~~**Rust Core**~~ — **Complete** (v1.3.0) — native tree-sitter parsing via napi-rs, parallel multi-core parsing, incremental re-parsing, import resolution & cycle detection in Rust
2. ~~**Foundation Hardening**~~ — **Complete** (v1.4.0) — parser registry, 11-tool MCP server, test coverage 62%→75%, `apiKeyCommand` secret resolution
2. ~~**Foundation Hardening**~~ — **Complete** (v1.4.0) — parser registry, 12-tool MCP server with multi-repo support, test coverage 62%→75%, `apiKeyCommand` secret resolution, global repo registry
3. **Intelligent Embeddings** — LLM-generated descriptions, hybrid search
4. **Natural Language Queries** — `codegraph ask` command, conversational sessions
5. **Expanded Language Support** — 8 new languages (12 → 20)
Expand Down
27 changes: 18 additions & 9 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Codegraph is a strong local-first code graph CLI. This roadmap describes planned
| Phase | Theme | Key Deliverables | Status |
|-------|-------|-----------------|--------|
| [**1**](#phase-1--rust-core) | Rust Core | Rust parsing engine via napi-rs, parallel parsing, incremental tree-sitter, JS orchestration layer | **Complete** (v1.3.0) |
| [**2**](#phase-2--foundation-hardening) | Foundation Hardening | Parser registry, complete MCP, test coverage, enhanced config, multi-repo MCP | **Partial** — core complete (v1.4.0), 2.5 planned |
| [**2**](#phase-2--foundation-hardening) | Foundation Hardening | Parser registry, complete MCP, test coverage, enhanced config, multi-repo MCP | **Complete** (v1.4.0) |
| [**3**](#phase-3--intelligent-embeddings) | Intelligent Embeddings | LLM-generated descriptions, hybrid search | Planned |
| [**4**](#phase-4--natural-language-queries) | Natural Language Queries | `ask` command, conversational sessions | Planned |
| [**5**](#phase-5--expanded-language-support) | Expanded Language Support | 8 new languages (12 → 20), parser utilities | Planned |
Expand Down Expand Up @@ -171,19 +171,19 @@ New configuration options in `.codegraphrc.json`:

**Affected files:** `src/config.js`

### 2.5 — Multi-Repo MCP
### 2.5 — Multi-Repo MCP

Support querying multiple codebases from a single MCP server instance.

- Registry file at `~/.codegraph/registry.json` mapping repo names to their `.codegraph/graph.db` paths
- Lazy DB connections — only opened when a repo is first queried
- Add optional `repo` parameter to all MCP tools to target a specific repository
- Auto-registration: `codegraph build` adds the current project to the registry
- New CLI commands: `codegraph registry list|add|remove` for manual management
- Default behavior: when `repo` is omitted, use the local `.codegraph/graph.db` (backwards compatible)
- Registry file at `~/.codegraph/registry.json` mapping repo names to their `.codegraph/graph.db` paths
- ✅ Add optional `repo` parameter to all 11 MCP tools to target a specific repository
- ✅ New `list_repos` MCP tool (12th tool) to enumerate registered repositories
- Auto-registration: `codegraph build` adds the current project to the registry
- New CLI commands: `codegraph registry list|add|remove` for manual management
- Default behavior: when `repo` is omitted, use the local `.codegraph/graph.db` (backwards compatible)

**New files:** `src/registry.js`
**Affected files:** `src/mcp.js`, `src/cli.js`, `src/builder.js`
**Affected files:** `src/mcp.js`, `src/cli.js`, `src/builder.js`, `src/index.js`

---

Expand Down Expand Up @@ -481,6 +481,15 @@ codegraph viz

---

## Watch List

Technology changes to monitor that may unlock future improvements.

- **`node:sqlite` (Node.js built-in)** — **primary target.** Zero native dependencies, eliminates C++ addon breakage on Node major releases (`better-sqlite3` already broken on Node 24/25). Currently Stability 1.1 (Active Development) as of Node 25.x. Adopt when it reaches Stability 2, or use as a fallback alongside `better-sqlite3` (dual-engine pattern like native/WASM parsing). Backed by the Node.js project — no startup risk.
- **`libsql` (SQLite fork by Turso)** — monitor only. Drop-in `better-sqlite3` replacement with built-in DiskANN vector search. However, Turso is pivoting engineering focus to Limbo (full Rust SQLite rewrite), leaving libsql as legacy. Pre-1.0 (v0.5.x) with uncertain long-term maintenance. Low switching cost (API-compatible, data is standard SQLite), but not worth adopting until the Turso/Limbo situation clarifies.

---

## Contributing

Want to help? Contributions to any phase are welcome. See [CONTRIBUTING](README.md#-contributing) for setup instructions.
Expand Down
9 changes: 8 additions & 1 deletion src/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';
import { loadConfig } from './config.js';
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
import { initSchema, openDb } from './db.js';
import { warn } from './logger.js';
import { debug, warn } from './logger.js';
import { getActiveEngine, parseFilesAuto } from './parser.js';
import { computeConfidence, resolveImportPath, resolveImportsBatch } from './resolve.js';

Expand Down Expand Up @@ -543,4 +543,11 @@ export async function buildGraph(rootDir, opts = {}) {
console.log(`Graph built: ${nodeCount} nodes, ${edgeCount} edges`);
console.log(`Stored in ${dbPath}`);
db.close();

try {
const { registerRepo } = await import('./registry.js');
registerRepo(rootDir);
} catch (err) {
debug(`Auto-registration failed: ${err.message}`);
}
}
51 changes: 51 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
moduleMap,
queryName,
} from './queries.js';
import { listRepos, REGISTRY_PATH, registerRepo, unregisterRepo } from './registry.js';
import { watchProject } from './watcher.js';

const program = new Command();
Expand Down Expand Up @@ -191,6 +192,56 @@ program
await startMCPServer(opts.db);
});

// ─── Registry commands ──────────────────────────────────────────────────

const registry = program.command('registry').description('Manage the multi-repo project registry');

registry
.command('list')
.description('List all registered repositories')
.option('-j, --json', 'Output as JSON')
.action((opts) => {
const repos = listRepos();
if (opts.json) {
console.log(JSON.stringify(repos, null, 2));
} else if (repos.length === 0) {
console.log(`No repositories registered.\nRegistry: ${REGISTRY_PATH}`);
} else {
console.log(`Registered repositories (${REGISTRY_PATH}):\n`);
for (const r of repos) {
const dbExists = fs.existsSync(r.dbPath);
const status = dbExists ? '' : ' [DB missing]';
console.log(` ${r.name}${status}`);
console.log(` Path: ${r.path}`);
console.log(` DB: ${r.dbPath}`);
console.log();
}
}
});

registry
.command('add <dir>')
.description('Register a project directory')
.option('-n, --name <name>', 'Custom name (defaults to directory basename)')
.action((dir, opts) => {
const absDir = path.resolve(dir);
const { name, entry } = registerRepo(absDir, opts.name);
console.log(`Registered "${name}" → ${entry.path}`);
});

registry
.command('remove <name>')
.description('Unregister a repository by name')
.action((name) => {
const removed = unregisterRepo(name);
if (removed) {
console.log(`Removed "${name}" from registry.`);
} else {
console.error(`Repository "${name}" not found in registry.`);
process.exit(1);
}
});

// ─── Embedding commands ─────────────────────────────────────────────────

program
Expand Down
10 changes: 10 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,15 @@ export {
moduleMapData,
queryNameData,
} from './queries.js';
// Registry (multi-repo)
export {
listRepos,
loadRegistry,
REGISTRY_PATH,
registerRepo,
resolveRepoDbPath,
saveRegistry,
unregisterRepo,
} from './registry.js';
// Watch mode
export { watchProject } from './watcher.js';
46 changes: 44 additions & 2 deletions src/mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { createRequire } from 'node:module';
import { findCycles } from './cycles.js';
import { findDbPath } from './db.js';

const REPO_PROP = {
repo: {
type: 'string',
description: 'Repository name from the registry (omit for local project)',
},
};

const TOOLS = [
{
name: 'query_function',
Expand All @@ -22,6 +29,7 @@ const TOOLS = [
description: 'Traversal depth for transitive callers',
default: 2,
},
...REPO_PROP,
},
required: ['name'],
},
Expand All @@ -33,6 +41,7 @@ const TOOLS = [
type: 'object',
properties: {
file: { type: 'string', description: 'File path (partial match supported)' },
...REPO_PROP,
},
required: ['file'],
},
Expand All @@ -44,6 +53,7 @@ const TOOLS = [
type: 'object',
properties: {
file: { type: 'string', description: 'File path to analyze' },
...REPO_PROP,
},
required: ['file'],
},
Expand All @@ -53,7 +63,9 @@ const TOOLS = [
description: 'Detect circular dependencies in the codebase',
inputSchema: {
type: 'object',
properties: {},
properties: {
...REPO_PROP,
},
},
},
{
Expand All @@ -63,6 +75,7 @@ const TOOLS = [
type: 'object',
properties: {
limit: { type: 'number', description: 'Number of top files to show', default: 20 },
...REPO_PROP,
},
},
},
Expand All @@ -75,6 +88,7 @@ const TOOLS = [
name: { type: 'string', description: 'Function/method/class name (partial match)' },
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
...REPO_PROP,
},
required: ['name'],
},
Expand All @@ -89,6 +103,7 @@ const TOOLS = [
name: { type: 'string', description: 'Function/method/class name (partial match)' },
depth: { type: 'number', description: 'Max traversal depth', default: 5 },
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
...REPO_PROP,
},
required: ['name'],
},
Expand All @@ -103,6 +118,7 @@ const TOOLS = [
ref: { type: 'string', description: 'Git ref to diff against (default: HEAD)' },
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
...REPO_PROP,
},
},
},
Expand All @@ -116,6 +132,7 @@ const TOOLS = [
query: { type: 'string', description: 'Natural language search query' },
limit: { type: 'number', description: 'Max results to return', default: 15 },
min_score: { type: 'number', description: 'Minimum similarity score (0-1)', default: 0.2 },
...REPO_PROP,
},
required: ['query'],
},
Expand All @@ -136,6 +153,7 @@ const TOOLS = [
description: 'File-level graph (true) or function-level (false)',
default: true,
},
...REPO_PROP,
},
required: ['format'],
},
Expand All @@ -150,9 +168,18 @@ const TOOLS = [
file: { type: 'string', description: 'Filter by file path (partial match)' },
pattern: { type: 'string', description: 'Filter by function name (partial match)' },
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
...REPO_PROP,
},
},
},
{
name: 'list_repos',
description: 'List all repositories registered in the codegraph registry',
inputSchema: {
type: 'object',
properties: {},
},
},
];

export { TOOLS };
Expand Down Expand Up @@ -200,9 +227,19 @@ export async function startMCPServer(customDbPath) {

server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
const dbPath = customDbPath || undefined;

try {
let dbPath = customDbPath || undefined;
if (args.repo) {
const { resolveRepoDbPath } = await import('./registry.js');
const resolved = resolveRepoDbPath(args.repo);
if (!resolved)
throw new Error(
`Repository "${args.repo}" not found in registry or its database is missing.`,
);
dbPath = resolved;
}

let result;
switch (name) {
case 'query_function':
Expand Down Expand Up @@ -296,6 +333,11 @@ export async function startMCPServer(customDbPath) {
noTests: args.no_tests,
});
break;
case 'list_repos': {
const { listRepos } = await import('./registry.js');
result = { repos: listRepos() };
break;
}
default:
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
}
Expand Down
Loading
Loading