codebase-brain-mcp 0.1.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/PKG-INFO +117 -22
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/README.md +113 -21
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/pyproject.toml +4 -1
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/pyproject.toml.orig +4 -1
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/analysis.py +18 -6
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/chunking.py +46 -18
- codebase_brain_mcp-0.2.1/src/codebase_brain_mcp/chunking_js.py +255 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/evals.py +5 -2
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/server.py +130 -42
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/sources.py +1 -1
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/store.py +13 -9
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/LICENSE +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/__init__.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/__main__.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/brain.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/config.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/embeddings.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/history.py +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/py.typed +0 -0
- {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/remote.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebase-brain-mcp
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: An MCP server that gives AI agents a cited, searchable memory of a codebase.
|
|
5
5
|
Keywords: mcp,model-context-protocol,rag,code-search,ai-agents,claude,git
|
|
6
6
|
Author: Noman Nazir
|
|
@@ -18,6 +18,9 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
18
18
|
Requires-Dist: fastembed>=0.9.1
|
|
19
19
|
Requires-Dist: mcp>=2.3.0
|
|
20
20
|
Requires-Dist: numpy>=2.5.4
|
|
21
|
+
Requires-Dist: tree-sitter>=0.25.2,<0.26
|
|
22
|
+
Requires-Dist: tree-sitter-javascript>=0.25
|
|
23
|
+
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
21
24
|
Requires-Python: >=3.12
|
|
22
25
|
Project-URL: Homepage, https://github.com/Nomannazir/codebase-brain-mcp
|
|
23
26
|
Project-URL: Repository, https://github.com/Nomannazir/codebase-brain-mcp
|
|
@@ -73,8 +76,10 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
73
76
|
|
|
74
77
|
## Why it's different (technical)
|
|
75
78
|
|
|
76
|
-
- **Code-aware chunking.** Python
|
|
77
|
-
|
|
79
|
+
- **Code-aware chunking.** Python (with `ast`) and JavaScript / TypeScript / JSX / TSX (with tree-sitter) are split
|
|
80
|
+
by function, class, method, arrow function, interface, type and enum, with their doc comments. Markdown is split
|
|
81
|
+
by heading. Each chunk knows its symbol (`TimestampSigner.unsign`, `Ky.#retry`) and exact lines, so citations
|
|
82
|
+
point at real code. Other languages are indexed in overlapping line windows.
|
|
78
83
|
- **Hybrid retrieval.** SQLite FTS5 BM25 (with `camelCase` / `snake_case` identifier splitting) plus local
|
|
79
84
|
embeddings, merged with reciprocal-rank fusion. Exact names and fuzzy questions both work.
|
|
80
85
|
- **Local and free.** Embeddings run on your machine (`fastembed`, ONNX). No API keys, and no code leaves your
|
|
@@ -85,7 +90,8 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
85
90
|
the current lines, and links the pull requests behind each change. So agents can answer *"why is it built this
|
|
86
91
|
way?"*, not just *"what does it do?"*.
|
|
87
92
|
- **Impact analysis.** `impact_of_change` lists everything that uses a function, directly and transitively, plus the
|
|
88
|
-
tests that exercise it, before an agent changes anything.
|
|
93
|
+
tests that exercise it, before an agent changes anything. It works across Python, JavaScript and TypeScript files,
|
|
94
|
+
and follows a constructor to the code that builds its class.
|
|
89
95
|
- **Doc-drift detection.** `find_doc_drift` checks Markdown and reStructuredText docs against the code. It finds
|
|
90
96
|
files, functions, settings and flags that don't exist; examples passing parameters a function doesn't take; imports
|
|
91
97
|
that would fail; and sections last edited long before the code they describe changed. Each finding cites the doc
|
|
@@ -97,7 +103,7 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
97
103
|
|
|
98
104
|
| Tool | What it does |
|
|
99
105
|
|---|---|
|
|
100
|
-
| `index_repo(source, name?)` | Index a local path or an `https://` git URL (
|
|
106
|
+
| `index_repo(source, name?)` | Index a local path or an `https://` git URL (blobless clone with full history). Reports progress. |
|
|
101
107
|
| `ask_codebase(question, repo?, k?)` | The most relevant code and docs as cited sources, plus related commits, with instructions to answer only from them. |
|
|
102
108
|
| `search(query, repo?, k?, path_prefix?, kinds?)` | Raw hybrid search, filterable by path and chunk kind. |
|
|
103
109
|
| `explain_history(symbol? \| path, start_line?, end_line?, repo?)` | Every commit that changed a function or line range, newest first, with the relevant diff, current authorship, and PR titles and descriptions (GitHub). |
|
|
@@ -111,15 +117,67 @@ Also included: the resource `brain://repos`, and the prompt `answer_with_citatio
|
|
|
111
117
|
|
|
112
118
|
## Install
|
|
113
119
|
|
|
114
|
-
Codebase Brain runs on your own machine, so there's no server or account, and it's free.
|
|
115
|
-
[uv](https://docs.astral.sh/uv/):
|
|
120
|
+
Codebase Brain runs on your own machine, so there's no server or account, and it's free. It works with any
|
|
121
|
+
MCP-compatible assistant. All you need is [uv](https://docs.astral.sh/uv/), and every client runs the same command:
|
|
122
|
+
`uvx codebase-brain-mcp`.
|
|
123
|
+
|
|
124
|
+
**Claude Code**
|
|
116
125
|
|
|
117
126
|
```bash
|
|
118
|
-
# Claude Code
|
|
119
127
|
claude mcp add codebase-brain -- uvx codebase-brain-mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Cursor:** `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project)
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Claude Desktop:** Settings → Developer → Edit Config (`claude_desktop_config.json`), then restart the app
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**VS Code (GitHub Copilot agent mode):** `.vscode/mcp.json`
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"servers": {
|
|
155
|
+
"codebase-brain": { "type": "stdio", "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Windsurf:** `~/.codeium/windsurf/mcp_config.json`. **Gemini CLI:** `~/.gemini/settings.json`. Both use the same
|
|
161
|
+
`mcpServers` block as Cursor.
|
|
162
|
+
|
|
163
|
+
**OpenAI Codex CLI**
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
codex mcp add codebase-brain -- uvx codebase-brain-mcp
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> **Tool doesn't start?** Desktop apps often don't see your shell's `PATH`. Run `which uvx` and put that full
|
|
170
|
+
> path in `"command"`, for example `"/Users/you/.local/bin/uvx"`.
|
|
120
171
|
|
|
121
|
-
|
|
122
|
-
|
|
172
|
+
Optional: install the [skill](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/skills/codebase-brain/SKILL.md).
|
|
173
|
+
It tells the agent which tool fits which question, to cite every claim, to check history before removing odd code,
|
|
174
|
+
and to run impact analysis before editing. For Claude Code, run the commands below. Other agents that read
|
|
175
|
+
`SKILL.md` files take the same file in their own skills folder.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
mkdir -p ~/.claude/skills/codebase-brain
|
|
179
|
+
curl -fsSL https://raw.githubusercontent.com/Nomannazir/codebase-brain-mcp/main/skills/codebase-brain/SKILL.md \
|
|
180
|
+
-o ~/.claude/skills/codebase-brain/SKILL.md
|
|
123
181
|
```
|
|
124
182
|
|
|
125
183
|
## Quick start
|
|
@@ -148,7 +206,7 @@ src/itsdangerous/timed.py:72-158
|
|
|
148
206
|
...
|
|
149
207
|
```
|
|
150
208
|
|
|
151
|
-
### Use
|
|
209
|
+
### Use a local checkout instead (for development)
|
|
152
210
|
|
|
153
211
|
```bash
|
|
154
212
|
claude mcp add codebase-brain -- uv run --directory /path/to/codebase-brain-mcp codebase-brain
|
|
@@ -209,10 +267,14 @@ because they're supposed to mention removed things.
|
|
|
209
267
|
## Evaluation
|
|
210
268
|
|
|
211
269
|
Does search actually put the right code in front of the model? `codebase-brain eval` answers that with a benchmark.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
270
|
+
There are two, each with 32 questions pinned to one commit, and each question lists the code or docs a good answer
|
|
271
|
+
must come from. The questions were written by reading the code before any results were measured, and haven't been
|
|
272
|
+
changed since.
|
|
273
|
+
|
|
274
|
+
### Python: pallets/itsdangerous
|
|
275
|
+
|
|
276
|
+
32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous) at commit `672971d`, in
|
|
277
|
+
[`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml).
|
|
216
278
|
|
|
217
279
|
| Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
|
|
218
280
|
|---|---|---|---|---|---|---|---|
|
|
@@ -236,10 +298,40 @@ before any results were measured, and haven't been changed since.
|
|
|
236
298
|
the top 5. They're listed in [`evals/results/itsdangerous.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/itsdangerous.md) and are the next
|
|
237
299
|
thing to improve. A larger, held-out question set will come first, so any tuning isn't fitted to these 32.
|
|
238
300
|
|
|
239
|
-
|
|
301
|
+
### TypeScript: sindresorhus/ky
|
|
302
|
+
|
|
303
|
+
32 questions about [sindresorhus/ky](https://github.com/sindresorhus/ky) at commit `3541888`, in
|
|
304
|
+
[`evals/datasets/ky.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/ky.toml).
|
|
305
|
+
The readme is one 2,000-line file, so docs questions must find the right *section*, not just the file.
|
|
306
|
+
|
|
307
|
+
| Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
|
|
308
|
+
|---|---|---|---|---|---|---|---|
|
|
309
|
+
| **hybrid** (default) | 47% | 66% | 69% | 78% | 0.563 (0.41–0.71) | 0.576 | 6 ms |
|
|
310
|
+
| keyword only (BM25) | 38% | 44% | 53% | 62% | 0.436 (0.29–0.59) | 0.455 | 1 ms |
|
|
311
|
+
| vector only (bge-small) | 47% | **69%** | **75%** | 78% | **0.594** (0.46–0.74) | **0.604** | 5 ms |
|
|
312
|
+
|
|
313
|
+
| Hit@5 by question type | describes behaviour (14) | about the docs (7) | names the code (11) |
|
|
314
|
+
|---|---|---|---|
|
|
315
|
+
| hybrid | 36% | **100%** | 91% |
|
|
316
|
+
| keyword only | 7% | 86% | 91% |
|
|
317
|
+
| vector only | **43%** | **100%** | **100%** |
|
|
318
|
+
|
|
319
|
+
**What this shows:**
|
|
320
|
+
- **Naming the code or asking about the docs works well:** 91–100% in the top 5.
|
|
321
|
+
- **Describing behaviour in plain words is the weak spot (36%).** ky has about 25,000 lines of tests next to
|
|
322
|
+
5,000 lines of source, and the tests describe the same behaviour in plain words, so they outrank the source.
|
|
323
|
+
Every miss is listed in [`evals/results/ky.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/ky.md).
|
|
324
|
+
- **Hybrid is not better than vector-only here.** The intervals overlap heavily (hybrid beats vector on 5
|
|
325
|
+
questions and loses on 9), so neither is proven better. Keyword-only is clearly worst: hybrid beats it on 12
|
|
326
|
+
questions and loses on none.
|
|
327
|
+
- These results are published as measured. Tuning (for example, ranking source above tests) will be checked
|
|
328
|
+
against a new held-out set, so it isn't fitted to these questions.
|
|
329
|
+
|
|
330
|
+
Reproduce either benchmark (the first run downloads the repo and the embedding model):
|
|
240
331
|
|
|
241
332
|
```bash
|
|
242
333
|
uv run codebase-brain eval evals/datasets/itsdangerous.toml --out evals/results
|
|
334
|
+
uv run codebase-brain eval evals/datasets/ky.toml --out evals/results
|
|
243
335
|
```
|
|
244
336
|
|
|
245
337
|
How it's measured:
|
|
@@ -248,14 +340,15 @@ How it's measured:
|
|
|
248
340
|
- **nDCG@10:** each target is credited once, so duplicate chunks don't inflate the score.
|
|
249
341
|
- **Confidence intervals:** 95% bootstrap over questions, seeded.
|
|
250
342
|
- **Relevance:** a result is relevant if it's in the target file and, when the target names a symbol, is that symbol
|
|
251
|
-
or one of its methods.
|
|
343
|
+
or one of its methods. A docs target can name a section (`FAQ > How do I stop retrying early?`), and then that
|
|
344
|
+
section or one of its subsections counts.
|
|
252
345
|
- **Dataset rot:** the harness fails if a target no longer exists at the pinned commit.
|
|
253
346
|
|
|
254
347
|
## How it works
|
|
255
348
|
|
|
256
349
|
```
|
|
257
|
-
source ─► resolve (local dir /
|
|
258
|
-
─► chunk (ast / headings /
|
|
350
|
+
source ─► resolve (local dir / blobless https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
|
|
351
|
+
─► chunk (ast / tree-sitter / headings / windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
|
|
259
352
|
query ─► BM25 top 50 ┐
|
|
260
353
|
─► vector top 50 ┴─► reciprocal-rank fusion ─► filters ─► cited sources
|
|
261
354
|
```
|
|
@@ -274,7 +367,7 @@ query ─► BM25 top 50 ┐
|
|
|
274
367
|
## Development
|
|
275
368
|
|
|
276
369
|
```bash
|
|
277
|
-
uv run pytest #
|
|
370
|
+
uv run pytest # 67 tests, offline (hash embedder, temporary git repos, in-process MCP client)
|
|
278
371
|
uv run ruff check . # lint
|
|
279
372
|
uv run ruff format . # format
|
|
280
373
|
uv run pyright # types
|
|
@@ -285,9 +378,11 @@ uv run pyright # types
|
|
|
285
378
|
- [x] Git history and PR discussions: `explain_history`, `search_history`
|
|
286
379
|
- [x] Symbol references and transitive dependents: `impact_of_change`
|
|
287
380
|
- [x] `find_doc_drift`: Markdown and reStructuredText, signatures, imports, settings, flags, staleness
|
|
288
|
-
- [
|
|
381
|
+
- [x] JavaScript and TypeScript (tree-sitter): chunking, references, impact analysis
|
|
382
|
+
- [ ] More languages (Go, Rust, Java) with tree-sitter
|
|
289
383
|
- [x] Retrieval evals with confidence intervals (`codebase-brain eval`)
|
|
290
384
|
- [ ] Larger held-out eval set, then retrieval tuning (reranking, query expansion)
|
|
291
385
|
- [x] Streamable HTTP with bearer-token auth, Docker image
|
|
292
|
-
- [
|
|
386
|
+
- [x] PyPI package, MCP Registry listing
|
|
387
|
+
- [ ] Hosted demo
|
|
293
388
|
- [ ] OpenTelemetry tracing
|
|
@@ -47,8 +47,10 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
47
47
|
|
|
48
48
|
## Why it's different (technical)
|
|
49
49
|
|
|
50
|
-
- **Code-aware chunking.** Python
|
|
51
|
-
|
|
50
|
+
- **Code-aware chunking.** Python (with `ast`) and JavaScript / TypeScript / JSX / TSX (with tree-sitter) are split
|
|
51
|
+
by function, class, method, arrow function, interface, type and enum, with their doc comments. Markdown is split
|
|
52
|
+
by heading. Each chunk knows its symbol (`TimestampSigner.unsign`, `Ky.#retry`) and exact lines, so citations
|
|
53
|
+
point at real code. Other languages are indexed in overlapping line windows.
|
|
52
54
|
- **Hybrid retrieval.** SQLite FTS5 BM25 (with `camelCase` / `snake_case` identifier splitting) plus local
|
|
53
55
|
embeddings, merged with reciprocal-rank fusion. Exact names and fuzzy questions both work.
|
|
54
56
|
- **Local and free.** Embeddings run on your machine (`fastembed`, ONNX). No API keys, and no code leaves your
|
|
@@ -59,7 +61,8 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
59
61
|
the current lines, and links the pull requests behind each change. So agents can answer *"why is it built this
|
|
60
62
|
way?"*, not just *"what does it do?"*.
|
|
61
63
|
- **Impact analysis.** `impact_of_change` lists everything that uses a function, directly and transitively, plus the
|
|
62
|
-
tests that exercise it, before an agent changes anything.
|
|
64
|
+
tests that exercise it, before an agent changes anything. It works across Python, JavaScript and TypeScript files,
|
|
65
|
+
and follows a constructor to the code that builds its class.
|
|
63
66
|
- **Doc-drift detection.** `find_doc_drift` checks Markdown and reStructuredText docs against the code. It finds
|
|
64
67
|
files, functions, settings and flags that don't exist; examples passing parameters a function doesn't take; imports
|
|
65
68
|
that would fail; and sections last edited long before the code they describe changed. Each finding cites the doc
|
|
@@ -71,7 +74,7 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
|
|
|
71
74
|
|
|
72
75
|
| Tool | What it does |
|
|
73
76
|
|---|---|
|
|
74
|
-
| `index_repo(source, name?)` | Index a local path or an `https://` git URL (
|
|
77
|
+
| `index_repo(source, name?)` | Index a local path or an `https://` git URL (blobless clone with full history). Reports progress. |
|
|
75
78
|
| `ask_codebase(question, repo?, k?)` | The most relevant code and docs as cited sources, plus related commits, with instructions to answer only from them. |
|
|
76
79
|
| `search(query, repo?, k?, path_prefix?, kinds?)` | Raw hybrid search, filterable by path and chunk kind. |
|
|
77
80
|
| `explain_history(symbol? \| path, start_line?, end_line?, repo?)` | Every commit that changed a function or line range, newest first, with the relevant diff, current authorship, and PR titles and descriptions (GitHub). |
|
|
@@ -85,15 +88,67 @@ Also included: the resource `brain://repos`, and the prompt `answer_with_citatio
|
|
|
85
88
|
|
|
86
89
|
## Install
|
|
87
90
|
|
|
88
|
-
Codebase Brain runs on your own machine, so there's no server or account, and it's free.
|
|
89
|
-
[uv](https://docs.astral.sh/uv/):
|
|
91
|
+
Codebase Brain runs on your own machine, so there's no server or account, and it's free. It works with any
|
|
92
|
+
MCP-compatible assistant. All you need is [uv](https://docs.astral.sh/uv/), and every client runs the same command:
|
|
93
|
+
`uvx codebase-brain-mcp`.
|
|
94
|
+
|
|
95
|
+
**Claude Code**
|
|
90
96
|
|
|
91
97
|
```bash
|
|
92
|
-
# Claude Code
|
|
93
98
|
claude mcp add codebase-brain -- uvx codebase-brain-mcp
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Cursor:** `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project)
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Claude Desktop:** Settings → Developer → Edit Config (`claude_desktop_config.json`), then restart the app
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"mcpServers": {
|
|
116
|
+
"codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**VS Code (GitHub Copilot agent mode):** `.vscode/mcp.json`
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"servers": {
|
|
126
|
+
"codebase-brain": { "type": "stdio", "command": "uvx", "args": ["codebase-brain-mcp"] }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Windsurf:** `~/.codeium/windsurf/mcp_config.json`. **Gemini CLI:** `~/.gemini/settings.json`. Both use the same
|
|
132
|
+
`mcpServers` block as Cursor.
|
|
133
|
+
|
|
134
|
+
**OpenAI Codex CLI**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
codex mcp add codebase-brain -- uvx codebase-brain-mcp
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> **Tool doesn't start?** Desktop apps often don't see your shell's `PATH`. Run `which uvx` and put that full
|
|
141
|
+
> path in `"command"`, for example `"/Users/you/.local/bin/uvx"`.
|
|
94
142
|
|
|
95
|
-
|
|
96
|
-
|
|
143
|
+
Optional: install the [skill](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/skills/codebase-brain/SKILL.md).
|
|
144
|
+
It tells the agent which tool fits which question, to cite every claim, to check history before removing odd code,
|
|
145
|
+
and to run impact analysis before editing. For Claude Code, run the commands below. Other agents that read
|
|
146
|
+
`SKILL.md` files take the same file in their own skills folder.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
mkdir -p ~/.claude/skills/codebase-brain
|
|
150
|
+
curl -fsSL https://raw.githubusercontent.com/Nomannazir/codebase-brain-mcp/main/skills/codebase-brain/SKILL.md \
|
|
151
|
+
-o ~/.claude/skills/codebase-brain/SKILL.md
|
|
97
152
|
```
|
|
98
153
|
|
|
99
154
|
## Quick start
|
|
@@ -122,7 +177,7 @@ src/itsdangerous/timed.py:72-158
|
|
|
122
177
|
...
|
|
123
178
|
```
|
|
124
179
|
|
|
125
|
-
### Use
|
|
180
|
+
### Use a local checkout instead (for development)
|
|
126
181
|
|
|
127
182
|
```bash
|
|
128
183
|
claude mcp add codebase-brain -- uv run --directory /path/to/codebase-brain-mcp codebase-brain
|
|
@@ -183,10 +238,14 @@ because they're supposed to mention removed things.
|
|
|
183
238
|
## Evaluation
|
|
184
239
|
|
|
185
240
|
Does search actually put the right code in front of the model? `codebase-brain eval` answers that with a benchmark.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
241
|
+
There are two, each with 32 questions pinned to one commit, and each question lists the code or docs a good answer
|
|
242
|
+
must come from. The questions were written by reading the code before any results were measured, and haven't been
|
|
243
|
+
changed since.
|
|
244
|
+
|
|
245
|
+
### Python: pallets/itsdangerous
|
|
246
|
+
|
|
247
|
+
32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous) at commit `672971d`, in
|
|
248
|
+
[`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml).
|
|
190
249
|
|
|
191
250
|
| Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
|
|
192
251
|
|---|---|---|---|---|---|---|---|
|
|
@@ -210,10 +269,40 @@ before any results were measured, and haven't been changed since.
|
|
|
210
269
|
the top 5. They're listed in [`evals/results/itsdangerous.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/itsdangerous.md) and are the next
|
|
211
270
|
thing to improve. A larger, held-out question set will come first, so any tuning isn't fitted to these 32.
|
|
212
271
|
|
|
213
|
-
|
|
272
|
+
### TypeScript: sindresorhus/ky
|
|
273
|
+
|
|
274
|
+
32 questions about [sindresorhus/ky](https://github.com/sindresorhus/ky) at commit `3541888`, in
|
|
275
|
+
[`evals/datasets/ky.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/ky.toml).
|
|
276
|
+
The readme is one 2,000-line file, so docs questions must find the right *section*, not just the file.
|
|
277
|
+
|
|
278
|
+
| Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
|
|
279
|
+
|---|---|---|---|---|---|---|---|
|
|
280
|
+
| **hybrid** (default) | 47% | 66% | 69% | 78% | 0.563 (0.41–0.71) | 0.576 | 6 ms |
|
|
281
|
+
| keyword only (BM25) | 38% | 44% | 53% | 62% | 0.436 (0.29–0.59) | 0.455 | 1 ms |
|
|
282
|
+
| vector only (bge-small) | 47% | **69%** | **75%** | 78% | **0.594** (0.46–0.74) | **0.604** | 5 ms |
|
|
283
|
+
|
|
284
|
+
| Hit@5 by question type | describes behaviour (14) | about the docs (7) | names the code (11) |
|
|
285
|
+
|---|---|---|---|
|
|
286
|
+
| hybrid | 36% | **100%** | 91% |
|
|
287
|
+
| keyword only | 7% | 86% | 91% |
|
|
288
|
+
| vector only | **43%** | **100%** | **100%** |
|
|
289
|
+
|
|
290
|
+
**What this shows:**
|
|
291
|
+
- **Naming the code or asking about the docs works well:** 91–100% in the top 5.
|
|
292
|
+
- **Describing behaviour in plain words is the weak spot (36%).** ky has about 25,000 lines of tests next to
|
|
293
|
+
5,000 lines of source, and the tests describe the same behaviour in plain words, so they outrank the source.
|
|
294
|
+
Every miss is listed in [`evals/results/ky.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/ky.md).
|
|
295
|
+
- **Hybrid is not better than vector-only here.** The intervals overlap heavily (hybrid beats vector on 5
|
|
296
|
+
questions and loses on 9), so neither is proven better. Keyword-only is clearly worst: hybrid beats it on 12
|
|
297
|
+
questions and loses on none.
|
|
298
|
+
- These results are published as measured. Tuning (for example, ranking source above tests) will be checked
|
|
299
|
+
against a new held-out set, so it isn't fitted to these questions.
|
|
300
|
+
|
|
301
|
+
Reproduce either benchmark (the first run downloads the repo and the embedding model):
|
|
214
302
|
|
|
215
303
|
```bash
|
|
216
304
|
uv run codebase-brain eval evals/datasets/itsdangerous.toml --out evals/results
|
|
305
|
+
uv run codebase-brain eval evals/datasets/ky.toml --out evals/results
|
|
217
306
|
```
|
|
218
307
|
|
|
219
308
|
How it's measured:
|
|
@@ -222,14 +311,15 @@ How it's measured:
|
|
|
222
311
|
- **nDCG@10:** each target is credited once, so duplicate chunks don't inflate the score.
|
|
223
312
|
- **Confidence intervals:** 95% bootstrap over questions, seeded.
|
|
224
313
|
- **Relevance:** a result is relevant if it's in the target file and, when the target names a symbol, is that symbol
|
|
225
|
-
or one of its methods.
|
|
314
|
+
or one of its methods. A docs target can name a section (`FAQ > How do I stop retrying early?`), and then that
|
|
315
|
+
section or one of its subsections counts.
|
|
226
316
|
- **Dataset rot:** the harness fails if a target no longer exists at the pinned commit.
|
|
227
317
|
|
|
228
318
|
## How it works
|
|
229
319
|
|
|
230
320
|
```
|
|
231
|
-
source ─► resolve (local dir /
|
|
232
|
-
─► chunk (ast / headings /
|
|
321
|
+
source ─► resolve (local dir / blobless https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
|
|
322
|
+
─► chunk (ast / tree-sitter / headings / windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
|
|
233
323
|
query ─► BM25 top 50 ┐
|
|
234
324
|
─► vector top 50 ┴─► reciprocal-rank fusion ─► filters ─► cited sources
|
|
235
325
|
```
|
|
@@ -248,7 +338,7 @@ query ─► BM25 top 50 ┐
|
|
|
248
338
|
## Development
|
|
249
339
|
|
|
250
340
|
```bash
|
|
251
|
-
uv run pytest #
|
|
341
|
+
uv run pytest # 67 tests, offline (hash embedder, temporary git repos, in-process MCP client)
|
|
252
342
|
uv run ruff check . # lint
|
|
253
343
|
uv run ruff format . # format
|
|
254
344
|
uv run pyright # types
|
|
@@ -259,9 +349,11 @@ uv run pyright # types
|
|
|
259
349
|
- [x] Git history and PR discussions: `explain_history`, `search_history`
|
|
260
350
|
- [x] Symbol references and transitive dependents: `impact_of_change`
|
|
261
351
|
- [x] `find_doc_drift`: Markdown and reStructuredText, signatures, imports, settings, flags, staleness
|
|
262
|
-
- [
|
|
352
|
+
- [x] JavaScript and TypeScript (tree-sitter): chunking, references, impact analysis
|
|
353
|
+
- [ ] More languages (Go, Rust, Java) with tree-sitter
|
|
263
354
|
- [x] Retrieval evals with confidence intervals (`codebase-brain eval`)
|
|
264
355
|
- [ ] Larger held-out eval set, then retrieval tuning (reranking, query expansion)
|
|
265
356
|
- [x] Streamable HTTP with bearer-token auth, Docker image
|
|
266
|
-
- [
|
|
357
|
+
- [x] PyPI package, MCP Registry listing
|
|
358
|
+
- [ ] Hosted demo
|
|
267
359
|
- [ ] OpenTelemetry tracing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codebase-brain-mcp"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "An MCP server that gives AI agents a cited, searchable memory of a codebase."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -29,6 +29,9 @@ dependencies = [
|
|
|
29
29
|
"fastembed>=0.9.1",
|
|
30
30
|
"mcp>=2.3.0",
|
|
31
31
|
"numpy>=2.5.4",
|
|
32
|
+
"tree-sitter>=0.25.2,<0.26",
|
|
33
|
+
"tree-sitter-javascript>=0.25",
|
|
34
|
+
"tree-sitter-typescript>=0.23.2",
|
|
32
35
|
]
|
|
33
36
|
|
|
34
37
|
[[project.authors]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codebase-brain-mcp"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "An MCP server that gives AI agents a cited, searchable memory of a codebase."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -24,6 +24,9 @@ dependencies = [
|
|
|
24
24
|
"fastembed>=0.9.1",
|
|
25
25
|
"mcp>=2.3.0",
|
|
26
26
|
"numpy>=2.5.4",
|
|
27
|
+
"tree-sitter>=0.25.2,<0.26", # 0.26.0 crashes the process (SIGBUS) while walking big files
|
|
28
|
+
"tree-sitter-javascript>=0.25",
|
|
29
|
+
"tree-sitter-typescript>=0.23.2",
|
|
27
30
|
]
|
|
28
31
|
|
|
29
32
|
[project.urls]
|
|
@@ -13,10 +13,12 @@ from datetime import datetime, timedelta
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
from . import history
|
|
16
|
+
from .chunking import DEFINITION_KINDS
|
|
16
17
|
from .sources import SourceError
|
|
17
18
|
from .store import Store, StoredChunk
|
|
18
19
|
|
|
19
20
|
MAX_PER_LEVEL = 60
|
|
21
|
+
STRUCTURED_SUFFIXES = (".py", ".pyi", ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx")
|
|
20
22
|
STALE_AFTER = timedelta(days=180)
|
|
21
23
|
MAX_STALENESS_CHECKS = 40
|
|
22
24
|
_FENCE = re.compile(r"^(`{3,}|~{3,})\s*([\w+-]*)[^\n]*\n(.*?)^\1\s*$", re.MULTILINE | re.DOTALL)
|
|
@@ -31,7 +33,7 @@ _PATH = re.compile(r"^\.?/?[\w.-]+(/[\w.-]+)+/?$")
|
|
|
31
33
|
_ENV = re.compile(r"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$")
|
|
32
34
|
_FLAG = re.compile(r"^(--[a-z][a-z0-9-]+)(=\S*)?$")
|
|
33
35
|
_SYMBOL = re.compile(r"^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*(\(\))?$")
|
|
34
|
-
_TEST_PATH = re.compile(r"(^|/)(tests?|testing)/|(^|/)test_[^/]+$|_test\.\w
|
|
36
|
+
_TEST_PATH = re.compile(r"(^|/)(tests?|testing|__tests__)/|(^|/)test_[^/]+$|_test\.\w+$|\.(test|spec)\.[jt]sx?$")
|
|
35
37
|
_COMMON = set(dir(builtins)) | set(keyword.kwlist) | {"true", "false", "null", "none", "self", "cls"}
|
|
36
38
|
_SEVERITY_ORDER = {"high": 0, "medium": 1, "low": 2}
|
|
37
39
|
_CHANGELOG = re.compile(
|
|
@@ -57,6 +59,8 @@ def definition_span(matches: list[StoredChunk]) -> tuple[str, int, int, str | No
|
|
|
57
59
|
|
|
58
60
|
# ---- impact_of_change -------------------------------------------------------------------------
|
|
59
61
|
|
|
62
|
+
_CONSTRUCTORS = {"__init__", "__new__", "constructor"}
|
|
63
|
+
|
|
60
64
|
|
|
61
65
|
@dataclass(frozen=True)
|
|
62
66
|
class Dependent:
|
|
@@ -79,13 +83,21 @@ class ImpactReport:
|
|
|
79
83
|
notes: list[str] = field(default_factory=list)
|
|
80
84
|
|
|
81
85
|
|
|
86
|
+
def _used_as(symbol: str) -> str:
|
|
87
|
+
"""The name other code uses to reach ``symbol``: a constructor is reached through its class."""
|
|
88
|
+
owner, _, short = symbol.rpartition(".")
|
|
89
|
+
if short in _CONSTRUCTORS and owner:
|
|
90
|
+
return owner.rsplit(".", 1)[-1]
|
|
91
|
+
return short
|
|
92
|
+
|
|
93
|
+
|
|
82
94
|
def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], depth: int = 2) -> ImpactReport:
|
|
83
95
|
"""Everything that uses ``target`` by name, directly and (up to ``depth``) transitively."""
|
|
84
96
|
symbol = target.symbol or ""
|
|
85
97
|
short = symbol.rsplit(".", 1)[-1]
|
|
86
98
|
excluded = {c.id for c in store.find_symbol(symbol, target.path)}
|
|
87
99
|
dependents: list[Dependent] = []
|
|
88
|
-
frontier = [
|
|
100
|
+
frontier = [_used_as(symbol)]
|
|
89
101
|
for level in range(1, max(1, min(depth, 3)) + 1):
|
|
90
102
|
next_frontier: list[str] = []
|
|
91
103
|
for name in frontier:
|
|
@@ -97,8 +109,8 @@ def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], d
|
|
|
97
109
|
dependents.append(
|
|
98
110
|
Dependent(chunk=chunk, via=name, level=level, is_test=bool(_TEST_PATH.search(chunk.path)))
|
|
99
111
|
)
|
|
100
|
-
if chunk.symbol and chunk.kind in
|
|
101
|
-
next_frontier.append(chunk.symbol
|
|
112
|
+
if chunk.symbol and chunk.kind in DEFINITION_KINDS:
|
|
113
|
+
next_frontier.append(_used_as(chunk.symbol))
|
|
102
114
|
frontier = list(dict.fromkeys(next_frontier))
|
|
103
115
|
if not frontier:
|
|
104
116
|
break
|
|
@@ -114,8 +126,8 @@ def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], d
|
|
|
114
126
|
f"{len(twins)} other definition(s) share the name {short!r}; some dependents may use those "
|
|
115
127
|
"instead. Check the import or receiver before changing anything."
|
|
116
128
|
)
|
|
117
|
-
if not target.path.endswith(
|
|
118
|
-
notes.append("Only Python
|
|
129
|
+
if not target.path.endswith(STRUCTURED_SUFFIXES):
|
|
130
|
+
notes.append("Only Python, JS and TS are analysed structurally; for other languages, also search the name.")
|
|
119
131
|
notes.append("Found by name, so dynamic calls (getattr, string dispatch, callbacks) aren't included.")
|
|
120
132
|
return ImpactReport(
|
|
121
133
|
symbol=symbol,
|