Skip to content

Commit 43a79ee

Browse files
authored
Merge pull request #16 from optave/feat/multi-repo-mcp
feat: multi-repo MCP with global registry (Phase 2.5)
2 parents 75a39fa + 54ea9f6 commit 43a79ee

11 files changed

Lines changed: 643 additions & 15 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ JS source is plain JavaScript (ES modules) in `src/`. No transpilation step. The
5252
| `config.js` | `.codegraphrc.json` loading, env overrides, `apiKeyCommand` secret resolution |
5353
| `constants.js` | `EXTENSIONS` (derived from parser registry) and `IGNORE_DIRS` constants |
5454
| `native.js` | Native napi-rs addon loader with WASM fallback |
55+
| `registry.js` | Global repo registry (`~/.codegraph/registry.json`) for multi-repo MCP |
5556
| `resolve.js` | Import resolution (supports native batch mode) |
5657
| `logger.js` | Structured logging (`warn`, `debug`, `info`, `error`) |
5758

README.md

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

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

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

216+
### Multi-Repo Registry
217+
218+
Manage a global registry of codegraph-enabled projects. AI agents can query any registered repo from a single MCP session using the `repo` parameter.
219+
220+
```bash
221+
codegraph registry list # List all registered repos
222+
codegraph registry list --json # JSON output
223+
codegraph registry add <dir> # Register a project directory
224+
codegraph registry add <dir> -n my-name # Custom name
225+
codegraph registry remove <name> # Unregister
226+
```
227+
228+
`codegraph build` auto-registers the project — no manual setup needed.
229+
216230
### AI Integration
217231

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

311325
### MCP Server
312326

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

315329
```bash
316330
codegraph mcp
317331
```
318332

333+
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).
334+
319335
### CLAUDE.md / Agent Instructions
320336

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

470486
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
471-
2. ~~**Foundation Hardening**~~**Complete** (v1.4.0) — parser registry, 11-tool MCP server, test coverage 62%→75%, `apiKeyCommand` secret resolution
487+
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
472488
3. **Intelligent Embeddings** — LLM-generated descriptions, hybrid search
473489
4. **Natural Language Queries**`codegraph ask` command, conversational sessions
474490
5. **Expanded Language Support** — 8 new languages (12 → 20)

ROADMAP.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Codegraph is a strong local-first code graph CLI. This roadmap describes planned
1313
| Phase | Theme | Key Deliverables | Status |
1414
|-------|-------|-----------------|--------|
1515
| [**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) |
16-
| [**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 |
16+
| [**2**](#phase-2--foundation-hardening) | Foundation Hardening | Parser registry, complete MCP, test coverage, enhanced config, multi-repo MCP | **Complete** (v1.4.0) |
1717
| [**3**](#phase-3--intelligent-embeddings) | Intelligent Embeddings | LLM-generated descriptions, hybrid search | Planned |
1818
| [**4**](#phase-4--natural-language-queries) | Natural Language Queries | `ask` command, conversational sessions | Planned |
1919
| [**5**](#phase-5--expanded-language-support) | Expanded Language Support | 8 new languages (12 → 20), parser utilities | Planned |
@@ -171,19 +171,19 @@ New configuration options in `.codegraphrc.json`:
171171

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

174-
### 2.5 — Multi-Repo MCP
174+
### 2.5 — Multi-Repo MCP
175175

176176
Support querying multiple codebases from a single MCP server instance.
177177

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

185185
**New files:** `src/registry.js`
186-
**Affected files:** `src/mcp.js`, `src/cli.js`, `src/builder.js`
186+
**Affected files:** `src/mcp.js`, `src/cli.js`, `src/builder.js`, `src/index.js`
187187

188188
---
189189

@@ -481,6 +481,15 @@ codegraph viz
481481

482482
---
483483

484+
## Watch List
485+
486+
Technology changes to monitor that may unlock future improvements.
487+
488+
- **`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.
489+
- **`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.
490+
491+
---
492+
484493
## Contributing
485494

486495
Want to help? Contributions to any phase are welcome. See [CONTRIBUTING](README.md#-contributing) for setup instructions.

src/builder.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path from 'node:path';
44
import { loadConfig } from './config.js';
55
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
66
import { initSchema, openDb } from './db.js';
7-
import { warn } from './logger.js';
7+
import { debug, warn } from './logger.js';
88
import { getActiveEngine, parseFilesAuto } from './parser.js';
99
import { computeConfidence, resolveImportPath, resolveImportsBatch } from './resolve.js';
1010

@@ -543,4 +543,11 @@ export async function buildGraph(rootDir, opts = {}) {
543543
console.log(`Graph built: ${nodeCount} nodes, ${edgeCount} edges`);
544544
console.log(`Stored in ${dbPath}`);
545545
db.close();
546+
547+
try {
548+
const { registerRepo } = await import('./registry.js');
549+
registerRepo(rootDir);
550+
} catch (err) {
551+
debug(`Auto-registration failed: ${err.message}`);
552+
}
546553
}

src/cli.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
moduleMap,
2020
queryName,
2121
} from './queries.js';
22+
import { listRepos, REGISTRY_PATH, registerRepo, unregisterRepo } from './registry.js';
2223
import { watchProject } from './watcher.js';
2324

2425
const program = new Command();
@@ -191,6 +192,56 @@ program
191192
await startMCPServer(opts.db);
192193
});
193194

195+
// ─── Registry commands ──────────────────────────────────────────────────
196+
197+
const registry = program.command('registry').description('Manage the multi-repo project registry');
198+
199+
registry
200+
.command('list')
201+
.description('List all registered repositories')
202+
.option('-j, --json', 'Output as JSON')
203+
.action((opts) => {
204+
const repos = listRepos();
205+
if (opts.json) {
206+
console.log(JSON.stringify(repos, null, 2));
207+
} else if (repos.length === 0) {
208+
console.log(`No repositories registered.\nRegistry: ${REGISTRY_PATH}`);
209+
} else {
210+
console.log(`Registered repositories (${REGISTRY_PATH}):\n`);
211+
for (const r of repos) {
212+
const dbExists = fs.existsSync(r.dbPath);
213+
const status = dbExists ? '' : ' [DB missing]';
214+
console.log(` ${r.name}${status}`);
215+
console.log(` Path: ${r.path}`);
216+
console.log(` DB: ${r.dbPath}`);
217+
console.log();
218+
}
219+
}
220+
});
221+
222+
registry
223+
.command('add <dir>')
224+
.description('Register a project directory')
225+
.option('-n, --name <name>', 'Custom name (defaults to directory basename)')
226+
.action((dir, opts) => {
227+
const absDir = path.resolve(dir);
228+
const { name, entry } = registerRepo(absDir, opts.name);
229+
console.log(`Registered "${name}" → ${entry.path}`);
230+
});
231+
232+
registry
233+
.command('remove <name>')
234+
.description('Unregister a repository by name')
235+
.action((name) => {
236+
const removed = unregisterRepo(name);
237+
if (removed) {
238+
console.log(`Removed "${name}" from registry.`);
239+
} else {
240+
console.error(`Repository "${name}" not found in registry.`);
241+
process.exit(1);
242+
}
243+
});
244+
194245
// ─── Embedding commands ─────────────────────────────────────────────────
195246

196247
program

src/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,15 @@ export {
4646
moduleMapData,
4747
queryNameData,
4848
} from './queries.js';
49+
// Registry (multi-repo)
50+
export {
51+
listRepos,
52+
loadRegistry,
53+
REGISTRY_PATH,
54+
registerRepo,
55+
resolveRepoDbPath,
56+
saveRegistry,
57+
unregisterRepo,
58+
} from './registry.js';
4959
// Watch mode
5060
export { watchProject } from './watcher.js';

src/mcp.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import { createRequire } from 'node:module';
99
import { findCycles } from './cycles.js';
1010
import { findDbPath } from './db.js';
1111

12+
const REPO_PROP = {
13+
repo: {
14+
type: 'string',
15+
description: 'Repository name from the registry (omit for local project)',
16+
},
17+
};
18+
1219
const TOOLS = [
1320
{
1421
name: 'query_function',
@@ -22,6 +29,7 @@ const TOOLS = [
2229
description: 'Traversal depth for transitive callers',
2330
default: 2,
2431
},
32+
...REPO_PROP,
2533
},
2634
required: ['name'],
2735
},
@@ -33,6 +41,7 @@ const TOOLS = [
3341
type: 'object',
3442
properties: {
3543
file: { type: 'string', description: 'File path (partial match supported)' },
44+
...REPO_PROP,
3645
},
3746
required: ['file'],
3847
},
@@ -44,6 +53,7 @@ const TOOLS = [
4453
type: 'object',
4554
properties: {
4655
file: { type: 'string', description: 'File path to analyze' },
56+
...REPO_PROP,
4757
},
4858
required: ['file'],
4959
},
@@ -53,7 +63,9 @@ const TOOLS = [
5363
description: 'Detect circular dependencies in the codebase',
5464
inputSchema: {
5565
type: 'object',
56-
properties: {},
66+
properties: {
67+
...REPO_PROP,
68+
},
5769
},
5870
},
5971
{
@@ -63,6 +75,7 @@ const TOOLS = [
6375
type: 'object',
6476
properties: {
6577
limit: { type: 'number', description: 'Number of top files to show', default: 20 },
78+
...REPO_PROP,
6679
},
6780
},
6881
},
@@ -75,6 +88,7 @@ const TOOLS = [
7588
name: { type: 'string', description: 'Function/method/class name (partial match)' },
7689
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
7790
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
91+
...REPO_PROP,
7892
},
7993
required: ['name'],
8094
},
@@ -89,6 +103,7 @@ const TOOLS = [
89103
name: { type: 'string', description: 'Function/method/class name (partial match)' },
90104
depth: { type: 'number', description: 'Max traversal depth', default: 5 },
91105
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
106+
...REPO_PROP,
92107
},
93108
required: ['name'],
94109
},
@@ -103,6 +118,7 @@ const TOOLS = [
103118
ref: { type: 'string', description: 'Git ref to diff against (default: HEAD)' },
104119
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
105120
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
121+
...REPO_PROP,
106122
},
107123
},
108124
},
@@ -116,6 +132,7 @@ const TOOLS = [
116132
query: { type: 'string', description: 'Natural language search query' },
117133
limit: { type: 'number', description: 'Max results to return', default: 15 },
118134
min_score: { type: 'number', description: 'Minimum similarity score (0-1)', default: 0.2 },
135+
...REPO_PROP,
119136
},
120137
required: ['query'],
121138
},
@@ -136,6 +153,7 @@ const TOOLS = [
136153
description: 'File-level graph (true) or function-level (false)',
137154
default: true,
138155
},
156+
...REPO_PROP,
139157
},
140158
required: ['format'],
141159
},
@@ -150,9 +168,18 @@ const TOOLS = [
150168
file: { type: 'string', description: 'Filter by file path (partial match)' },
151169
pattern: { type: 'string', description: 'Filter by function name (partial match)' },
152170
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
171+
...REPO_PROP,
153172
},
154173
},
155174
},
175+
{
176+
name: 'list_repos',
177+
description: 'List all repositories registered in the codegraph registry',
178+
inputSchema: {
179+
type: 'object',
180+
properties: {},
181+
},
182+
},
156183
];
157184

158185
export { TOOLS };
@@ -200,9 +227,19 @@ export async function startMCPServer(customDbPath) {
200227

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

205231
try {
232+
let dbPath = customDbPath || undefined;
233+
if (args.repo) {
234+
const { resolveRepoDbPath } = await import('./registry.js');
235+
const resolved = resolveRepoDbPath(args.repo);
236+
if (!resolved)
237+
throw new Error(
238+
`Repository "${args.repo}" not found in registry or its database is missing.`,
239+
);
240+
dbPath = resolved;
241+
}
242+
206243
let result;
207244
switch (name) {
208245
case 'query_function':
@@ -296,6 +333,11 @@ export async function startMCPServer(customDbPath) {
296333
noTests: args.no_tests,
297334
});
298335
break;
336+
case 'list_repos': {
337+
const { listRepos } = await import('./registry.js');
338+
result = { repos: listRepos() };
339+
break;
340+
}
299341
default:
300342
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
301343
}

0 commit comments

Comments
 (0)