@brainbank/mcp 0.1.3 → 0.2.0

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.
package/README.md CHANGED
@@ -76,26 +76,22 @@ brainbank serve
76
76
  | Variable | Description | Default |
77
77
  |----------|-------------|---------|
78
78
  | `BRAINBANK_REPO` | Default repo path (fallback if `repo` param not provided) | — |
79
- | `BRAINBANK_EMBEDDING` | Embedding provider: `openai` or `local` | `local` |
79
+ | `BRAINBANK_EMBEDDING` | Embedding provider: `local`, `openai`, `perplexity`, `perplexity-context` | `local` |
80
80
  | `OPENAI_API_KEY` | Required when using `openai` embeddings | — |
81
+ | `PERPLEXITY_API_KEY` | Required when using `perplexity` or `perplexity-context` embeddings | — |
81
82
 
82
83
  > The agent passes the `repo` parameter per tool call based on the active workspace — no hardcoded paths needed.
83
84
 
84
- ## Available Tools
85
+ ## Tools (6)
85
86
 
86
87
  | Tool | Description |
87
88
  |------|-------------|
88
- | `brainbank_hybrid_search` | Best quality: vector + BM25 fused search |
89
- | `brainbank_search` | Semantic vector search |
90
- | `brainbank_keyword_search` | Instant BM25 full-text search |
91
- | `brainbank_context` | Formatted context for system prompt injection |
92
- | `brainbank_index` | Trigger incremental code/git indexing |
93
- | `brainbank_stats` | Index statistics (files, commits, chunks) |
89
+ | `brainbank_search` | Unified search — `mode: hybrid` (default), `vector`, or `keyword` |
90
+ | `brainbank_context` | Formatted context block for a task (code + git + co-edits) |
91
+ | `brainbank_index` | Trigger incremental code/git/docs indexing |
92
+ | `brainbank_stats` | Index statistics (files, commits, chunks, collections) |
94
93
  | `brainbank_history` | Git history for a specific file |
95
- | `brainbank_coedits` | Files that frequently change together |
96
- | `brainbank_collection_add` | Add item to a KV collection |
97
- | `brainbank_collection_search` | Semantic search within a collection |
98
- | `brainbank_collection_trim` | Trim a collection to N most recent items |
94
+ | `brainbank_collection` | KV collection ops `action: add`, `search`, or `trim` |
99
95
 
100
96
  ## Multi-Workspace
101
97
 
@@ -103,10 +99,10 @@ The MCP server maintains a pool of BrainBank instances — one per unique `repo`
103
99
 
104
100
  ```typescript
105
101
  // Agent working in one workspace
106
- brainbank_hybrid_search({ query: "login form", repo: "/Users/you/project-a" })
102
+ brainbank_search({ query: "login form", repo: "/Users/you/project-a" })
107
103
 
108
104
  // Agent switches to another project — new instance auto-created
109
- brainbank_hybrid_search({ query: "API routes", repo: "/Users/you/project-b" })
105
+ brainbank_search({ query: "API routes", repo: "/Users/you/project-b" })
110
106
  ```
111
107
 
112
108
  Instances are cached in memory after first initialization (~480ms), so subsequent queries to the same repo are fast.
@@ -117,7 +113,7 @@ Instances are cached in memory after first initialization (~480ms), so subsequen
117
113
  AI Agent ←→ stdio ←→ @brainbank/mcp ←→ BrainBank core ←→ SQLite
118
114
  ```
119
115
 
120
- 1. Agent sends an MCP tool call (e.g., `brainbank_hybrid_search`)
116
+ 1. Agent sends an MCP tool call (e.g., `brainbank_search`)
121
117
  2. Server routes to the correct BrainBank instance (by `repo` path)
122
118
  3. BrainBank executes the search against its local SQLite database
123
119
  4. Results returned as structured JSON to the agent
@@ -29,9 +29,18 @@ async function createEmbeddingProvider() {
29
29
  const { OpenAIEmbedding } = await import("brainbank");
30
30
  return new OpenAIEmbedding();
31
31
  }
32
+ if (embeddingEnv === "perplexity") {
33
+ const { PerplexityEmbedding } = await import("brainbank");
34
+ return new PerplexityEmbedding();
35
+ }
36
+ if (embeddingEnv === "perplexity-context") {
37
+ const { PerplexityContextEmbedding } = await import("brainbank");
38
+ return new PerplexityContextEmbedding();
39
+ }
32
40
  return void 0;
33
41
  }
34
42
  __name(createEmbeddingProvider, "createEmbeddingProvider");
43
+ var MAX_POOL_SIZE = 10;
35
44
  var _pool = /* @__PURE__ */ new Map();
36
45
  var _sharedReranker = void 0;
37
46
  var _sharedEmbedding = void 0;
@@ -51,50 +60,132 @@ async function getBrainBank(targetRepo) {
51
60
  );
52
61
  }
53
62
  const resolved = rp.replace(/\/+$/, "");
54
- if (_pool.has(resolved)) return _pool.get(resolved);
63
+ if (_pool.has(resolved)) {
64
+ const entry = _pool.get(resolved);
65
+ try {
66
+ const codeStats = entry.brain.indexer("code")?.stats?.();
67
+ if (codeStats && codeStats.hnswSize === 0) {
68
+ const dbPath = path.join(resolved, ".brainbank", "brainbank.db");
69
+ const dbSize = fs.existsSync(dbPath) ? fs.statSync(dbPath).size : 0;
70
+ if (dbSize > 1e5) {
71
+ evictPool(resolved);
72
+ } else {
73
+ entry.lastAccess = Date.now();
74
+ return entry.brain;
75
+ }
76
+ } else {
77
+ entry.lastAccess = Date.now();
78
+ return entry.brain;
79
+ }
80
+ } catch {
81
+ entry.lastAccess = Date.now();
82
+ return entry.brain;
83
+ }
84
+ }
55
85
  await ensureShared();
86
+ if (_pool.size >= MAX_POOL_SIZE) {
87
+ let oldest;
88
+ let oldestTime = Infinity;
89
+ for (const [key, entry] of _pool) {
90
+ if (entry.lastAccess < oldestTime) {
91
+ oldestTime = entry.lastAccess;
92
+ oldest = key;
93
+ }
94
+ }
95
+ if (oldest) evictPool(oldest);
96
+ }
97
+ const brain = await _createBrain(resolved);
98
+ _pool.set(resolved, { brain, lastAccess: Date.now() });
99
+ return brain;
100
+ }
101
+ __name(getBrainBank, "getBrainBank");
102
+ async function _createBrain(resolved) {
56
103
  const opts = { repoPath: resolved, reranker: _sharedReranker };
57
104
  if (_sharedEmbedding) {
58
105
  opts.embeddingProvider = _sharedEmbedding;
59
106
  opts.embeddingDims = _sharedEmbedding.dims;
60
107
  }
61
108
  const brain = new BrainBank(opts).use(code({ repoPath: resolved })).use(git({ repoPath: resolved })).use(docs());
62
- await brain.initialize();
63
- _pool.set(resolved, brain);
109
+ try {
110
+ await brain.initialize();
111
+ } catch (err) {
112
+ if (err?.message?.includes("Invalid the given array length")) {
113
+ const dbPath = path.join(resolved, ".brainbank", "brainbank.db");
114
+ try {
115
+ fs.unlinkSync(dbPath);
116
+ } catch {
117
+ }
118
+ try {
119
+ fs.unlinkSync(dbPath + "-wal");
120
+ } catch {
121
+ }
122
+ try {
123
+ fs.unlinkSync(dbPath + "-shm");
124
+ } catch {
125
+ }
126
+ const fresh = new BrainBank(opts).use(code({ repoPath: resolved })).use(git({ repoPath: resolved })).use(docs());
127
+ await fresh.initialize();
128
+ return fresh;
129
+ }
130
+ throw err;
131
+ }
64
132
  return brain;
65
133
  }
66
- __name(getBrainBank, "getBrainBank");
134
+ __name(_createBrain, "_createBrain");
135
+ function evictPool(resolved) {
136
+ const entry = _pool.get(resolved);
137
+ if (entry) {
138
+ try {
139
+ entry.brain.close();
140
+ } catch {
141
+ }
142
+ _pool.delete(resolved);
143
+ }
144
+ }
145
+ __name(evictPool, "evictPool");
67
146
  var server = new McpServer({
68
147
  name: "brainbank",
69
- version: "0.1.0"
148
+ version: "0.2.0"
70
149
  });
71
150
  server.registerTool(
72
151
  "brainbank_search",
73
152
  {
74
153
  title: "BrainBank Search",
75
- description: "Semantic search across indexed code and git commits. Returns the most relevant results sorted by similarity score.",
154
+ description: "Search indexed code and git commits. Supports three modes:\n- hybrid (default): vector + BM25 fused with RRF \u2014 best quality\n- vector: semantic similarity only\n- keyword: instant BM25 for exact terms, function names, error messages",
76
155
  inputSchema: z.object({
77
- query: z.string().describe("Natural language search query describing what you are looking for"),
78
- codeK: z.number().optional().default(6).describe("Max code results to return"),
79
- gitK: z.number().optional().default(5).describe("Max git commit results to return"),
80
- minScore: z.number().optional().default(0.25).describe("Minimum similarity score threshold (0-1)"),
81
- repo: z.string().optional().describe("Repository path to search (default: BRAINBANK_REPO)")
156
+ query: z.string().describe("Search query \u2014 works with both keywords and natural language"),
157
+ mode: z.enum(["hybrid", "vector", "keyword"]).optional().default("hybrid").describe("Search strategy"),
158
+ codeK: z.number().optional().default(8).describe("Max code results"),
159
+ gitK: z.number().optional().default(5).describe("Max git results"),
160
+ minScore: z.number().optional().default(0.25).describe("Minimum similarity score (0-1), only for vector mode"),
161
+ collections: z.record(z.string(), z.number()).optional().describe(
162
+ 'Max results per source. Reserved: "code", "git", "docs". Any other key = KV collection.'
163
+ ),
164
+ repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
82
165
  })
83
166
  },
84
- async ({ query, codeK, gitK, minScore, repo }) => {
167
+ async ({ query, mode, codeK, gitK, minScore, collections, repo }) => {
85
168
  const brainbank = await getBrainBank(repo);
86
- const results = await brainbank.search(query, { codeK, gitK, minScore });
169
+ let results;
170
+ if (mode === "keyword") {
171
+ results = await brainbank.searchBM25(query, { codeK, gitK });
172
+ } else if (mode === "vector") {
173
+ results = await brainbank.search(query, { codeK, gitK, minScore });
174
+ } else {
175
+ results = await brainbank.hybridSearch(query, { codeK, gitK, collections });
176
+ }
87
177
  if (results.length === 0) {
88
- return { content: [{ type: "text", text: "No results found for this query." }] };
178
+ return { content: [{ type: "text", text: "No results found." }] };
89
179
  }
90
- return { content: [{ type: "text", text: formatResults(results, "Semantic Search") }] };
180
+ const modeLabel = mode === "keyword" ? "Keyword (BM25)" : mode === "vector" ? "Vector" : "Hybrid (Vector + BM25 \u2192 RRF)";
181
+ return { content: [{ type: "text", text: formatResults(results, modeLabel) }] };
91
182
  }
92
183
  );
93
184
  server.registerTool(
94
185
  "brainbank_context",
95
186
  {
96
187
  title: "BrainBank Context",
97
- description: "Get a formatted knowledge context block for a task. Returns relevant code snippets, git history, co-edit patterns as markdown. Perfect for enriching your understanding before working on a task.",
188
+ description: "Get a formatted knowledge context block for a task. Returns relevant code, git history, and co-edit patterns as markdown.",
98
189
  inputSchema: z.object({
99
190
  task: z.string().describe("Description of the task you need context for"),
100
191
  affectedFiles: z.array(z.string()).optional().default([]).describe("Files you plan to modify (improves co-edit suggestions)"),
@@ -117,25 +208,45 @@ server.registerTool(
117
208
  "brainbank_index",
118
209
  {
119
210
  title: "BrainBank Index",
120
- description: "Index (or re-index) the repository code and git history. Run this before searching if the codebase has changed. Indexing is incremental \u2014 only changed files are processed.",
211
+ description: "Index (or re-index) code, git history, and docs. Incremental \u2014 only changed files are processed.",
121
212
  inputSchema: z.object({
122
- forceReindex: z.boolean().optional().default(false).describe("Force re-index of all files, even unchanged ones"),
213
+ modules: z.array(z.enum(["code", "git", "docs"])).optional().describe("Which modules to index (default: all)"),
214
+ docsPath: z.string().optional().describe("Path to a docs folder to register and index"),
215
+ forceReindex: z.boolean().optional().default(false).describe("Force re-index of all files"),
123
216
  gitDepth: z.number().optional().default(500).describe("Number of git commits to index"),
124
- repo: z.string().optional().describe("Repository path to index (default: BRAINBANK_REPO)")
217
+ repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
125
218
  })
126
219
  },
127
- async ({ forceReindex, gitDepth, repo }) => {
220
+ async ({ modules, docsPath, forceReindex, gitDepth, repo }) => {
128
221
  const brainbank = await getBrainBank(repo);
129
- const result = await brainbank.index({ forceReindex, gitDepth });
222
+ if (docsPath) {
223
+ const absPath = path.resolve(docsPath);
224
+ const collName = path.basename(absPath);
225
+ try {
226
+ brainbank.addCollection({
227
+ name: collName,
228
+ path: absPath,
229
+ pattern: "**/*.md",
230
+ ignore: ["deprecated/**", "node_modules/**"]
231
+ });
232
+ } catch {
233
+ }
234
+ }
235
+ const result = await brainbank.index({ modules, forceReindex, gitDepth });
130
236
  const lines = [
131
237
  "## Indexing Complete",
132
238
  "",
133
239
  `**Code**: ${result.code?.indexed ?? 0} files indexed, ${result.code?.skipped ?? 0} skipped, ${result.code?.chunks ?? 0} chunks`,
134
240
  `**Git**: ${result.git?.indexed ?? 0} commits indexed, ${result.git?.skipped ?? 0} skipped`
135
241
  ];
242
+ if (result.docs) {
243
+ for (const [name, stat] of Object.entries(result.docs)) {
244
+ lines.push(`**Docs [${name}]**: ${stat.indexed} indexed, ${stat.skipped} skipped, ${stat.chunks} chunks`);
245
+ }
246
+ }
136
247
  const stats = brainbank.stats();
137
248
  lines.push("");
138
- lines.push(`**Totals**: ${stats.code?.chunks ?? 0} code chunks, ${stats.git?.commits ?? 0} commits`);
249
+ lines.push(`**Totals**: ${stats.code?.chunks ?? 0} code chunks, ${stats.git?.commits ?? 0} commits, ${stats.documents?.documents ?? 0} docs`);
139
250
  return { content: [{ type: "text", text: lines.join("\n") }] };
140
251
  }
141
252
  );
@@ -143,7 +254,7 @@ server.registerTool(
143
254
  "brainbank_stats",
144
255
  {
145
256
  title: "BrainBank Stats",
146
- description: "Get statistics about the indexed knowledge base: file count, code chunks, git commits, HNSW index sizes, and KV collections.",
257
+ description: "Get index statistics: file count, code chunks, git commits, HNSW sizes, KV collections.",
147
258
  inputSchema: z.object({
148
259
  repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
149
260
  })
@@ -151,35 +262,20 @@ server.registerTool(
151
262
  async ({ repo }) => {
152
263
  const brainbank = await getBrainBank(repo);
153
264
  const s = brainbank.stats();
154
- const lines = [
155
- "## BrainBank Knowledge Base Stats",
156
- ""
157
- ];
265
+ const lines = ["## BrainBank Stats", ""];
158
266
  if (s.code) {
159
- lines.push("### Code");
160
- lines.push(`- Files indexed: ${s.code.files}`);
161
- lines.push(`- Code chunks: ${s.code.chunks}`);
162
- lines.push(`- HNSW vectors: ${s.code.hnswSize}`);
163
- lines.push("");
267
+ lines.push(`**Code**: ${s.code.files} files, ${s.code.chunks} chunks, ${s.code.hnswSize} vectors`);
164
268
  }
165
269
  if (s.git) {
166
- lines.push("### Git History");
167
- lines.push(`- Commits indexed: ${s.git.commits}`);
168
- lines.push(`- Files tracked: ${s.git.filesTracked}`);
169
- lines.push(`- Co-edit pairs: ${s.git.coEdits}`);
170
- lines.push(`- HNSW vectors: ${s.git.hnswSize}`);
171
- lines.push("");
270
+ lines.push(`**Git**: ${s.git.commits} commits, ${s.git.filesTracked} files, ${s.git.coEdits} co-edit pairs`);
172
271
  }
173
272
  if (s.documents) {
174
- lines.push("### Documents");
175
- lines.push(`- Collections: ${s.documents.collections}`);
176
- lines.push(`- Documents: ${s.documents.documents}`);
177
- lines.push(`- HNSW vectors: ${s.documents.hnswSize}`);
178
- lines.push("");
273
+ lines.push(`**Docs**: ${s.documents.collections} collections, ${s.documents.documents} documents`);
179
274
  }
180
275
  const kvNames = brainbank.listCollectionNames();
181
276
  if (kvNames.length > 0) {
182
- lines.push("### KV Collections");
277
+ lines.push("");
278
+ lines.push("**KV Collections**:");
183
279
  for (const name of kvNames) {
184
280
  const coll = brainbank.collection(name);
185
281
  lines.push(`- ${name}: ${coll.count()} items`);
@@ -192,9 +288,9 @@ server.registerTool(
192
288
  "brainbank_history",
193
289
  {
194
290
  title: "BrainBank File History",
195
- description: "Get the git commit history for a specific file. Shows recent changes, authors, and line counts.",
291
+ description: "Get git commit history for a file. Shows changes, authors, and line counts.",
196
292
  inputSchema: z.object({
197
- filePath: z.string().describe('File path (relative or partial match, e.g. "auth.ts" or "src/core/agent.ts")'),
293
+ filePath: z.string().describe('File path (relative or partial, e.g. "auth.ts")'),
198
294
  limit: z.number().optional().default(20).describe("Max commits to return"),
199
295
  repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
200
296
  })
@@ -213,155 +309,56 @@ server.registerTool(
213
309
  }
214
310
  );
215
311
  server.registerTool(
216
- "brainbank_coedits",
217
- {
218
- title: "BrainBank Co-Edits",
219
- description: "Find files that historically change together with a given file. Useful for understanding dependencies and ensuring you do not miss related changes.",
220
- inputSchema: z.object({
221
- filePath: z.string().describe("File path to check co-edit relationships for"),
222
- limit: z.number().optional().default(5).describe("Max co-edit suggestions"),
223
- repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
224
- })
225
- },
226
- async ({ filePath, limit, repo }) => {
227
- const brainbank = await getBrainBank(repo);
228
- const suggestions = brainbank.coEdits(filePath, limit);
229
- if (suggestions.length === 0) {
230
- return { content: [{ type: "text", text: `No co-edit patterns found for "${filePath}"` }] };
231
- }
232
- const lines = [`## Co-Edits for: ${filePath}`, ""];
233
- lines.push("Files that frequently change together:");
234
- for (const s of suggestions) {
235
- lines.push(`- **${s.file}** (changed together ${s.count} times)`);
236
- }
237
- return { content: [{ type: "text", text: lines.join("\n") }] };
238
- }
239
- );
240
- server.registerTool(
241
- "brainbank_hybrid_search",
312
+ "brainbank_collection",
242
313
  {
243
- title: "BrainBank Hybrid Search",
244
- description: "Best quality search: combines semantic vector search + BM25 keyword search using Reciprocal Rank Fusion. Catches both exact keyword matches AND conceptual similarities. Use this by default for all searches.",
314
+ title: "BrainBank Collection",
315
+ description: "Operate on KV collections (auto-created). Actions:\n- add: store content with optional metadata\n- search: hybrid vector + keyword search\n- trim: keep only N most recent items",
245
316
  inputSchema: z.object({
246
- query: z.string().describe("Search query \u2014 works with both keywords and natural language"),
247
- codeK: z.number().optional().default(8).describe("Max code results (shorthand for collections.code)"),
248
- gitK: z.number().optional().default(5).describe("Max git results (shorthand for collections.git)"),
249
- collections: z.record(z.string(), z.number()).optional().describe(
250
- 'Max results per source. Reserved keys: "code", "git", "docs" control built-in indexers. Any other key is a KV collection. Example: { "code": 8, "git": 5, "errors": 3, "slack": 2 }'
251
- ),
252
- repo: z.string().optional().describe("Repository path to search (default: BRAINBANK_REPO)")
253
- })
254
- },
255
- async ({ query, codeK, gitK, collections, repo }) => {
256
- const t0 = performance.now();
257
- const brainbank = await getBrainBank(repo);
258
- const t1 = performance.now();
259
- const results = await brainbank.hybridSearch(query, { codeK, gitK, collections });
260
- const t2 = performance.now();
261
- const timing = `
262
-
263
- \u23F1 getBrainBank: ${(t1 - t0).toFixed(0)}ms | hybridSearch: ${(t2 - t1).toFixed(0)}ms | total: ${(t2 - t0).toFixed(0)}ms`;
264
- if (results.length === 0) {
265
- return { content: [{ type: "text", text: "No results found for this query." + timing }] };
266
- }
267
- return { content: [{ type: "text", text: formatResults(results, "Hybrid Search (Vector + BM25 \u2192 RRF)") + timing }] };
268
- }
269
- );
270
- server.registerTool(
271
- "brainbank_keyword_search",
272
- {
273
- title: "BrainBank Keyword Search",
274
- description: "Instant BM25 keyword search (no embedding computation needed). Best for exact terms, function names, variable names, error messages, and specific identifiers.",
275
- inputSchema: z.object({
276
- query: z.string().describe("Keywords to search for"),
277
- codeK: z.number().optional().default(8).describe("Max code results to return"),
278
- gitK: z.number().optional().default(5).describe("Max git commit results to return"),
279
- repo: z.string().optional().describe("Repository path to search (default: BRAINBANK_REPO)")
280
- })
281
- },
282
- async ({ query, codeK, gitK, repo }) => {
283
- const brainbank = await getBrainBank(repo);
284
- const results = brainbank.searchBM25(query, { codeK, gitK });
285
- if (results.length === 0) {
286
- return { content: [{ type: "text", text: "No keyword matches found." }] };
287
- }
288
- return { content: [{ type: "text", text: formatResults(results, "Keyword Search (BM25)") }] };
289
- }
290
- );
291
- server.registerTool(
292
- "brainbank_collection_add",
293
- {
294
- title: "BrainBank Collection Add",
295
- description: "Add an item to a dynamic KV collection. Collections are created automatically. Use this to store any structured data: errors, decisions, notes, context, etc.",
296
- inputSchema: z.object({
297
- collection: z.string().describe('Collection name (e.g. "errors", "decisions", "context")'),
298
- content: z.string().describe("Content to store"),
299
- metadata: z.record(z.any()).optional().default({}).describe("Optional metadata object"),
317
+ action: z.enum(["add", "search", "trim"]).describe("Operation to perform"),
318
+ collection: z.string().describe('Collection name (e.g. "errors", "decisions")'),
319
+ content: z.string().optional().describe("Content to store (required for add)"),
320
+ query: z.string().optional().describe("Search query (required for search)"),
321
+ metadata: z.record(z.any()).optional().default({}).describe("Metadata for add"),
322
+ k: z.number().optional().default(5).describe("Max results for search"),
323
+ keep: z.number().optional().describe("Items to keep for trim"),
300
324
  repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
301
325
  })
302
326
  },
303
- async ({ collection, content, metadata, repo }) => {
327
+ async ({ action, collection, content, query, metadata, k, keep, repo }) => {
304
328
  const brainbank = await getBrainBank(repo);
305
329
  const coll = brainbank.collection(collection);
306
- const id = await coll.add(content, metadata);
307
- return {
308
- content: [{ type: "text", text: `\u2713 Item #${id} added to '${collection}' (${coll.count()} total)` }]
309
- };
310
- }
311
- );
312
- server.registerTool(
313
- "brainbank_collection_search",
314
- {
315
- title: "BrainBank Collection Search",
316
- description: "Search a dynamic KV collection using hybrid vector + keyword search. Returns semantically similar items.",
317
- inputSchema: z.object({
318
- collection: z.string().describe("Collection name to search"),
319
- query: z.string().describe("Search query"),
320
- k: z.number().optional().default(5).describe("Max results"),
321
- mode: z.enum(["hybrid", "vector", "keyword"]).optional().default("hybrid").describe("Search mode"),
322
- repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
323
- })
324
- },
325
- async ({ collection, query, k, mode, repo }) => {
326
- const brainbank = await getBrainBank(repo);
327
- const coll = brainbank.collection(collection);
328
- const results = await coll.search(query, { k, mode });
329
- if (results.length === 0) {
330
- return { content: [{ type: "text", text: `No results in '${collection}' for "${query}"` }] };
330
+ if (action === "add") {
331
+ if (!content) throw new Error("BrainBank: content is required for add action.");
332
+ const id = await coll.add(content, metadata);
333
+ return {
334
+ content: [{ type: "text", text: `\u2713 Item #${id} added to '${collection}' (${coll.count()} total)` }]
335
+ };
331
336
  }
332
- const lines = [`## Collection: ${collection} \u2014 "${query}"`, ""];
333
- for (const r of results) {
334
- const score = Math.round((r.score ?? 0) * 100);
335
- lines.push(`[${score}%] ${r.content}`);
336
- if (Object.keys(r.metadata).length > 0) {
337
- lines.push(` ${JSON.stringify(r.metadata)}`);
337
+ if (action === "search") {
338
+ if (!query) throw new Error("BrainBank: query is required for search action.");
339
+ const results = await coll.search(query, { k });
340
+ if (results.length === 0) {
341
+ return { content: [{ type: "text", text: `No results in '${collection}' for "${query}"` }] };
338
342
  }
339
- lines.push("");
343
+ const lines = [`## Collection: ${collection}`, ""];
344
+ for (const r of results) {
345
+ const score = Math.round((r.score ?? 0) * 100);
346
+ lines.push(`[${score}%] ${r.content}`);
347
+ if (Object.keys(r.metadata).length > 0) {
348
+ lines.push(` ${JSON.stringify(r.metadata)}`);
349
+ }
350
+ lines.push("");
351
+ }
352
+ return { content: [{ type: "text", text: lines.join("\n") }] };
340
353
  }
341
- return { content: [{ type: "text", text: lines.join("\n") }] };
342
- }
343
- );
344
- server.registerTool(
345
- "brainbank_collection_trim",
346
- {
347
- title: "BrainBank Collection Trim",
348
- description: "Trim a dynamic collection to keep only the N most recent items. Use this to prevent collections from growing unbounded.",
349
- inputSchema: z.object({
350
- collection: z.string().describe("Collection name"),
351
- keep: z.number().describe("Number of most recent items to keep"),
352
- repo: z.string().optional().describe("Repository path (default: BRAINBANK_REPO)")
353
- })
354
- },
355
- async ({ collection, keep, repo }) => {
356
- const brainbank = await getBrainBank(repo);
357
- const coll = brainbank.collection(collection);
358
- const result = await coll.trim({ keep });
359
- return {
360
- content: [{
361
- type: "text",
362
- text: `\u2713 Trimmed ${result.removed} items from '${collection}' (kept ${keep})`
363
- }]
364
- };
354
+ if (action === "trim") {
355
+ if (keep == null) throw new Error("BrainBank: keep is required for trim action.");
356
+ const result = await coll.trim({ keep });
357
+ return {
358
+ content: [{ type: "text", text: `\u2713 Trimmed ${result.removed} items from '${collection}' (kept ${keep})` }]
359
+ };
360
+ }
361
+ throw new Error(`BrainBank: Unknown action "${action}".`);
365
362
  }
366
363
  );
367
364
  function formatResults(results, mode) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp-server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * BrainBank — MCP Server\n * \n * Exposes BrainBank as an MCP server via stdio transport.\n * Works with Google Antigravity, Claude Desktop, and any MCP-compatible client.\n * \n * Usage in Antigravity mcp_config.json:\n * {\n * \"mcpServers\": {\n * \"brainbank\": {\n * \"command\": \"npx\",\n * \"args\": [\"tsx\", \"/path/to/brainbank/src/integrations/mcp-server.ts\"],\n * \"env\": { \"BRAINBANK_REPO\": \"/path/to/your/repo\" }\n * }\n * }\n * }\n * \n * Tools exposed:\n * brainbank_search — Semantic search across code, commits\n * brainbank_hybrid_search — Best quality: vector + BM25 fused\n * brainbank_keyword_search — Instant BM25 full-text\n * brainbank_context — Get formatted context for a task\n * brainbank_index — Trigger code/git indexing\n * brainbank_stats — Get index statistics\n * brainbank_history — Git history for a specific file\n * brainbank_coedits — Files that frequently change together\n * brainbank_collection_add — Add item to a dynamic collection\n * brainbank_collection_search — Search a dynamic collection\n * brainbank_collection_trim — Trim a dynamic collection\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v3';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { BrainBank } from 'brainbank';\nimport { code } from 'brainbank/code';\nimport { git } from 'brainbank/git';\nimport { docs } from 'brainbank/docs';\n\n// ── Configuration from env ──────────────────────────\n\n/**\n * Detect repo root by walking up from startDir until we find `.git/`.\n * Returns startDir itself if no `.git/` is found (mono-repo or non-git project).\n */\nfunction findRepoRoot(startDir: string): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, '.git'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) break; // filesystem root\n dir = parent;\n }\n return path.resolve(startDir); // fallback: use startDir as-is\n}\n\nconst defaultRepoPath = process.env.BRAINBANK_REPO || undefined;\n\n// ── Reranker (default: qwen3, set BRAINBANK_RERANKER=none to disable) ──\n\nasync function createReranker() {\n const rerankerEnv = process.env.BRAINBANK_RERANKER ?? 'none';\n if (rerankerEnv === 'none') return undefined;\n if (rerankerEnv === 'qwen3') {\n const { Qwen3Reranker } = await import('@brainbank/reranker');\n return new Qwen3Reranker();\n }\n return undefined;\n}\n\n// ── Embedding Provider (default: local, set BRAINBANK_EMBEDDING=openai) ──\n\nasync function createEmbeddingProvider() {\n const embeddingEnv = process.env.BRAINBANK_EMBEDDING ?? 'local';\n if (embeddingEnv === 'openai') {\n const { OpenAIEmbedding } = await import('brainbank');\n return new OpenAIEmbedding();\n }\n return undefined; // BrainBank defaults to local WASM\n}\n\n// ── Multi-Workspace BrainBank Pool ─────────────────────\n// Reranker + embedding provider are shared; each repo gets its own DB.\n\nconst _pool = new Map<string, BrainBank>();\nlet _sharedReranker: any = undefined;\nlet _sharedEmbedding: any = undefined;\nlet _sharedReady = false;\n\nasync function ensureShared() {\n if (_sharedReady) return;\n _sharedReranker = await createReranker();\n _sharedEmbedding = await createEmbeddingProvider();\n _sharedReady = true;\n}\n\nasync function getBrainBank(targetRepo?: string): Promise<BrainBank> {\n const rp = targetRepo ?? defaultRepoPath;\n if (!rp) {\n throw new Error(\n 'No repository specified. Pass the `repo` parameter with the workspace path, ' +\n 'or set BRAINBANK_REPO environment variable.'\n );\n }\n const resolved = rp.replace(/\\/+$/, ''); // normalize\n\n if (_pool.has(resolved)) return _pool.get(resolved)!;\n\n await ensureShared();\n\n const opts: Record<string, any> = { repoPath: resolved, reranker: _sharedReranker };\n if (_sharedEmbedding) {\n opts.embeddingProvider = _sharedEmbedding;\n opts.embeddingDims = _sharedEmbedding.dims;\n }\n const brain = new BrainBank(opts)\n .use(code({ repoPath: resolved }))\n .use(git({ repoPath: resolved }))\n .use(docs());\n await brain.initialize();\n\n _pool.set(resolved, brain);\n return brain;\n}\n\n// ── MCP Server Setup ────────────────────────────────\n\nconst server = new McpServer({\n name: 'brainbank',\n version: '0.1.0',\n});\n\n// ── Tool: brainbank_search ─────────────────────────────\n\nserver.registerTool(\n 'brainbank_search',\n {\n title: 'BrainBank Search',\n description: 'Semantic search across indexed code and git commits. Returns the most relevant results sorted by similarity score.',\n inputSchema: z.object({\n query: z.string().describe('Natural language search query describing what you are looking for'),\n codeK: z.number().optional().default(6).describe('Max code results to return'),\n gitK: z.number().optional().default(5).describe('Max git commit results to return'),\n minScore: z.number().optional().default(0.25).describe('Minimum similarity score threshold (0-1)'),\n repo: z.string().optional().describe('Repository path to search (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ query, codeK, gitK, minScore, repo }) => {\n const brainbank = await getBrainBank(repo);\n const results = await brainbank.search(query, { codeK, gitK, minScore });\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: 'No results found for this query.' }] };\n }\n\n return { content: [{ type: 'text', text: formatResults(results, 'Semantic Search') }] };\n },\n);\n\n// ── Tool: brainbank_context ────────────────────────────\n\nserver.registerTool(\n 'brainbank_context',\n {\n title: 'BrainBank Context',\n description: 'Get a formatted knowledge context block for a task. Returns relevant code snippets, git history, co-edit patterns as markdown. Perfect for enriching your understanding before working on a task.',\n inputSchema: z.object({\n task: z.string().describe('Description of the task you need context for'),\n affectedFiles: z.array(z.string()).optional().default([]).describe('Files you plan to modify (improves co-edit suggestions)'),\n codeResults: z.number().optional().default(6).describe('Max code results'),\n gitResults: z.number().optional().default(5).describe('Max git commit results'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ task, affectedFiles, codeResults, gitResults, repo }) => {\n const brainbank = await getBrainBank(repo);\n const context = await brainbank.getContext(task, {\n affectedFiles,\n codeResults,\n gitResults,\n });\n\n return { content: [{ type: 'text', text: context }] };\n },\n);\n\n// ── Tool: brainbank_index ──────────────────────────────\n\nserver.registerTool(\n 'brainbank_index',\n {\n title: 'BrainBank Index',\n description: 'Index (or re-index) the repository code and git history. Run this before searching if the codebase has changed. Indexing is incremental — only changed files are processed.',\n inputSchema: z.object({\n forceReindex: z.boolean().optional().default(false).describe('Force re-index of all files, even unchanged ones'),\n gitDepth: z.number().optional().default(500).describe('Number of git commits to index'),\n repo: z.string().optional().describe('Repository path to index (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ forceReindex, gitDepth, repo }) => {\n const brainbank = await getBrainBank(repo);\n const result = await brainbank.index({ forceReindex, gitDepth });\n\n const lines = [\n '## Indexing Complete',\n '',\n `**Code**: ${result.code?.indexed ?? 0} files indexed, ${result.code?.skipped ?? 0} skipped, ${result.code?.chunks ?? 0} chunks`,\n `**Git**: ${result.git?.indexed ?? 0} commits indexed, ${result.git?.skipped ?? 0} skipped`,\n ];\n\n const stats = brainbank.stats();\n lines.push('');\n lines.push(`**Totals**: ${stats.code?.chunks ?? 0} code chunks, ${stats.git?.commits ?? 0} commits`);\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_stats ──────────────────────────────\n\nserver.registerTool(\n 'brainbank_stats',\n {\n title: 'BrainBank Stats',\n description: 'Get statistics about the indexed knowledge base: file count, code chunks, git commits, HNSW index sizes, and KV collections.',\n inputSchema: z.object({\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ repo }) => {\n const brainbank = await getBrainBank(repo);\n const s = brainbank.stats();\n\n const lines = [\n '## BrainBank Knowledge Base Stats',\n '',\n ];\n\n if (s.code) {\n lines.push('### Code');\n lines.push(`- Files indexed: ${s.code.files}`);\n lines.push(`- Code chunks: ${s.code.chunks}`);\n lines.push(`- HNSW vectors: ${s.code.hnswSize}`);\n lines.push('');\n }\n\n if (s.git) {\n lines.push('### Git History');\n lines.push(`- Commits indexed: ${s.git.commits}`);\n lines.push(`- Files tracked: ${s.git.filesTracked}`);\n lines.push(`- Co-edit pairs: ${s.git.coEdits}`);\n lines.push(`- HNSW vectors: ${s.git.hnswSize}`);\n lines.push('');\n }\n\n if (s.documents) {\n lines.push('### Documents');\n lines.push(`- Collections: ${s.documents.collections}`);\n lines.push(`- Documents: ${s.documents.documents}`);\n lines.push(`- HNSW vectors: ${s.documents.hnswSize}`);\n lines.push('');\n }\n\n const kvNames = brainbank.listCollectionNames();\n if (kvNames.length > 0) {\n lines.push('### KV Collections');\n for (const name of kvNames) {\n const coll = brainbank.collection(name);\n lines.push(`- ${name}: ${coll.count()} items`);\n }\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_history ────────────────────────────\n\nserver.registerTool(\n 'brainbank_history',\n {\n title: 'BrainBank File History',\n description: 'Get the git commit history for a specific file. Shows recent changes, authors, and line counts.',\n inputSchema: z.object({\n filePath: z.string().describe('File path (relative or partial match, e.g. \"auth.ts\" or \"src/core/agent.ts\")'),\n limit: z.number().optional().default(20).describe('Max commits to return'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ filePath, limit, repo }) => {\n const brainbank = await getBrainBank(repo);\n const history = await brainbank.fileHistory(filePath, limit);\n\n if (history.length === 0) {\n return { content: [{ type: 'text', text: `No git history found for \"${filePath}\"` }] };\n }\n\n const lines = [`## Git History: ${filePath}`, ''];\n for (const h of history as any[]) {\n lines.push(`**[${h.short_hash}]** ${h.message} *(${h.author}, +${h.additions}/-${h.deletions})*`);\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_coedits ────────────────────────────\n\nserver.registerTool(\n 'brainbank_coedits',\n {\n title: 'BrainBank Co-Edits',\n description: 'Find files that historically change together with a given file. Useful for understanding dependencies and ensuring you do not miss related changes.',\n inputSchema: z.object({\n filePath: z.string().describe('File path to check co-edit relationships for'),\n limit: z.number().optional().default(5).describe('Max co-edit suggestions'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ filePath, limit, repo }) => {\n const brainbank = await getBrainBank(repo);\n const suggestions = brainbank.coEdits(filePath, limit);\n\n if (suggestions.length === 0) {\n return { content: [{ type: 'text', text: `No co-edit patterns found for \"${filePath}\"` }] };\n }\n\n const lines = [`## Co-Edits for: ${filePath}`, ''];\n lines.push('Files that frequently change together:');\n for (const s of suggestions) {\n lines.push(`- **${s.file}** (changed together ${s.count} times)`);\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_hybrid_search ─────────────────────\n\nserver.registerTool(\n 'brainbank_hybrid_search',\n {\n title: 'BrainBank Hybrid Search',\n description: 'Best quality search: combines semantic vector search + BM25 keyword search using Reciprocal Rank Fusion. Catches both exact keyword matches AND conceptual similarities. Use this by default for all searches.',\n inputSchema: z.object({\n query: z.string().describe('Search query — works with both keywords and natural language'),\n codeK: z.number().optional().default(8).describe('Max code results (shorthand for collections.code)'),\n gitK: z.number().optional().default(5).describe('Max git results (shorthand for collections.git)'),\n collections: z.record(z.string(), z.number()).optional().describe(\n 'Max results per source. Reserved keys: \"code\", \"git\", \"docs\" control built-in indexers. ' +\n 'Any other key is a KV collection. Example: { \"code\": 8, \"git\": 5, \"errors\": 3, \"slack\": 2 }'\n ),\n repo: z.string().optional().describe('Repository path to search (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ query, codeK, gitK, collections, repo }) => {\n const t0 = performance.now();\n const brainbank = await getBrainBank(repo);\n const t1 = performance.now();\n const results = await brainbank.hybridSearch(query, { codeK, gitK, collections });\n const t2 = performance.now();\n\n const timing = `\\n\\n⏱ getBrainBank: ${(t1 - t0).toFixed(0)}ms | hybridSearch: ${(t2 - t1).toFixed(0)}ms | total: ${(t2 - t0).toFixed(0)}ms`;\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: 'No results found for this query.' + timing }] };\n }\n\n return { content: [{ type: 'text', text: formatResults(results, 'Hybrid Search (Vector + BM25 → RRF)') + timing }] };\n },\n);\n\n// ── Tool: brainbank_keyword_search ────────────────────\n\nserver.registerTool(\n 'brainbank_keyword_search',\n {\n title: 'BrainBank Keyword Search',\n description: 'Instant BM25 keyword search (no embedding computation needed). Best for exact terms, function names, variable names, error messages, and specific identifiers.',\n inputSchema: z.object({\n query: z.string().describe('Keywords to search for'),\n codeK: z.number().optional().default(8).describe('Max code results to return'),\n gitK: z.number().optional().default(5).describe('Max git commit results to return'),\n repo: z.string().optional().describe('Repository path to search (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ query, codeK, gitK, repo }) => {\n const brainbank = await getBrainBank(repo);\n const results = brainbank.searchBM25(query, { codeK, gitK });\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: 'No keyword matches found.' }] };\n }\n\n return { content: [{ type: 'text', text: formatResults(results, 'Keyword Search (BM25)') }] };\n },\n);\n\n// ── Tool: brainbank_collection_add ────────────────────\n\nserver.registerTool(\n 'brainbank_collection_add',\n {\n title: 'BrainBank Collection Add',\n description: 'Add an item to a dynamic KV collection. Collections are created automatically. Use this to store any structured data: errors, decisions, notes, context, etc.',\n inputSchema: z.object({\n collection: z.string().describe('Collection name (e.g. \"errors\", \"decisions\", \"context\")'),\n content: z.string().describe('Content to store'),\n metadata: z.record(z.any()).optional().default({}).describe('Optional metadata object'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ collection, content, metadata, repo }) => {\n const brainbank = await getBrainBank(repo);\n const coll = brainbank.collection(collection);\n const id = await coll.add(content, metadata);\n\n return {\n content: [{ type: 'text', text: `✓ Item #${id} added to '${collection}' (${coll.count()} total)` }],\n };\n },\n);\n\n// ── Tool: brainbank_collection_search ──────────────────\n\nserver.registerTool(\n 'brainbank_collection_search',\n {\n title: 'BrainBank Collection Search',\n description: 'Search a dynamic KV collection using hybrid vector + keyword search. Returns semantically similar items.',\n inputSchema: z.object({\n collection: z.string().describe('Collection name to search'),\n query: z.string().describe('Search query'),\n k: z.number().optional().default(5).describe('Max results'),\n mode: z.enum(['hybrid', 'vector', 'keyword']).optional().default('hybrid').describe('Search mode'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ collection, query, k, mode, repo }) => {\n const brainbank = await getBrainBank(repo);\n const coll = brainbank.collection(collection);\n const results = await coll.search(query, { k, mode: mode as any });\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: `No results in '${collection}' for \"${query}\"` }] };\n }\n\n const lines = [`## Collection: ${collection} — \"${query}\"`, ''];\n for (const r of results) {\n const score = Math.round((r.score ?? 0) * 100);\n lines.push(`[${score}%] ${r.content}`);\n if (Object.keys(r.metadata).length > 0) {\n lines.push(` ${JSON.stringify(r.metadata)}`);\n }\n lines.push('');\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_collection_trim ────────────────────\n\nserver.registerTool(\n 'brainbank_collection_trim',\n {\n title: 'BrainBank Collection Trim',\n description: 'Trim a dynamic collection to keep only the N most recent items. Use this to prevent collections from growing unbounded.',\n inputSchema: z.object({\n collection: z.string().describe('Collection name'),\n keep: z.number().describe('Number of most recent items to keep'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ collection, keep, repo }) => {\n const brainbank = await getBrainBank(repo);\n const coll = brainbank.collection(collection);\n const result = await coll.trim({ keep });\n\n return {\n content: [{\n type: 'text',\n text: `✓ Trimmed ${result.removed} items from '${collection}' (kept ${keep})`,\n }],\n };\n },\n);\n\n// ── Shared result formatter ────────────────────────\n\nfunction formatResults(results: any[], mode: string): string {\n const lines: string[] = [`## ${mode}`, ''];\n for (const r of results) {\n const score = Math.round(r.score * 100);\n if (r.type === 'code') {\n const m = r.metadata;\n lines.push(`[CODE ${score}%] ${r.filePath} — ${m.name || m.chunkType} (L${m.startLine}-${m.endLine})`);\n lines.push(r.content);\n lines.push('');\n } else if (r.type === 'commit') {\n const m = r.metadata;\n lines.push(`[COMMIT ${score}%] ${m.shortHash} — ${r.content} (${m.author})`);\n if (m.files?.length) lines.push(` Files: ${m.files.join(', ')}`);\n lines.push('');\n } else if (r.type === 'document') {\n const ctx = r.context ? ` — ${r.context}` : '';\n lines.push(`[DOC ${score}%] ${r.filePath} [${r.metadata.collection}]${ctx}`);\n lines.push(r.content);\n lines.push('');\n } else if (r.type === 'collection') {\n const col = r.metadata?.collection ?? 'unknown';\n lines.push(`[COLLECTION ${score}%] [${col}]`);\n lines.push(r.content);\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\n// ── Start Server ────────────────────────────────────\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch(err => {\n console.error(`BrainBank MCP Server Error: ${err.message}`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mcp-server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * BrainBank — MCP Server\n * \n * Exposes BrainBank as an MCP server via stdio transport.\n * Works with Google Antigravity, Claude Desktop, and any MCP-compatible client.\n * \n * Usage in Antigravity mcp_config.json:\n * {\n * \"mcpServers\": {\n * \"brainbank\": {\n * \"command\": \"npx\",\n * \"args\": [\"tsx\", \"/path/to/brainbank/packages/mcp/src/mcp-server.ts\"],\n * \"env\": { \"BRAINBANK_REPO\": \"/path/to/your/repo\" }\n * }\n * }\n * }\n * \n * Tools (6):\n * brainbank_search — Unified search (hybrid, vector, or keyword mode)\n * brainbank_context — Formatted knowledge context for a task\n * brainbank_index — Trigger code/git/docs indexing\n * brainbank_stats — Index statistics\n * brainbank_history — Git history for a specific file\n * brainbank_collection — KV collection operations (add, search, trim)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v3';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { BrainBank } from 'brainbank';\nimport { code } from 'brainbank/code';\nimport { git } from 'brainbank/git';\nimport { docs } from 'brainbank/docs';\n\n// ── Configuration from env ──────────────────────────\n\n/**\n * Detect repo root by walking up from startDir until we find `.git/`.\n * Returns startDir itself if no `.git/` is found (mono-repo or non-git project).\n */\nfunction findRepoRoot(startDir: string): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, '.git'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) break; // filesystem root\n dir = parent;\n }\n return path.resolve(startDir); // fallback: use startDir as-is\n}\n\nconst defaultRepoPath = process.env.BRAINBANK_REPO || undefined;\n\n// ── Reranker (default: none, set BRAINBANK_RERANKER=qwen3 to enable) ──\n\nasync function createReranker() {\n const rerankerEnv = process.env.BRAINBANK_RERANKER ?? 'none';\n if (rerankerEnv === 'none') return undefined;\n if (rerankerEnv === 'qwen3') {\n const { Qwen3Reranker } = await import('@brainbank/reranker');\n return new Qwen3Reranker();\n }\n return undefined;\n}\n\n// ── Embedding Provider (default: local) ──\n\nasync function createEmbeddingProvider() {\n const embeddingEnv = process.env.BRAINBANK_EMBEDDING ?? 'local';\n if (embeddingEnv === 'openai') {\n const { OpenAIEmbedding } = await import('brainbank');\n return new OpenAIEmbedding();\n }\n if (embeddingEnv === 'perplexity') {\n const { PerplexityEmbedding } = await import('brainbank');\n return new PerplexityEmbedding();\n }\n if (embeddingEnv === 'perplexity-context') {\n const { PerplexityContextEmbedding } = await import('brainbank');\n return new PerplexityContextEmbedding();\n }\n return undefined; // BrainBank defaults to local WASM\n}\n\n// ── Multi-Workspace BrainBank Pool ─────────────────────\n\nconst MAX_POOL_SIZE = 10;\n\ninterface PoolEntry {\n brain: BrainBank;\n lastAccess: number;\n}\n\nconst _pool = new Map<string, PoolEntry>();\nlet _sharedReranker: any = undefined;\nlet _sharedEmbedding: any = undefined;\nlet _sharedReady = false;\n\nasync function ensureShared() {\n if (_sharedReady) return;\n _sharedReranker = await createReranker();\n _sharedEmbedding = await createEmbeddingProvider();\n _sharedReady = true;\n}\n\nasync function getBrainBank(targetRepo?: string): Promise<BrainBank> {\n const rp = targetRepo ?? defaultRepoPath;\n if (!rp) {\n throw new Error(\n 'No repository specified. Pass the `repo` parameter with the workspace path, ' +\n 'or set BRAINBANK_REPO environment variable.'\n );\n }\n const resolved = rp.replace(/\\/+$/, '');\n\n if (_pool.has(resolved)) {\n const entry = _pool.get(resolved)!;\n try {\n const codeStats = entry.brain.indexer('code')?.stats?.();\n if (codeStats && codeStats.hnswSize === 0) {\n const dbPath = path.join(resolved, '.brainbank', 'brainbank.db');\n const dbSize = fs.existsSync(dbPath) ? fs.statSync(dbPath).size : 0;\n if (dbSize > 100_000) {\n evictPool(resolved);\n } else {\n entry.lastAccess = Date.now();\n return entry.brain;\n }\n } else {\n entry.lastAccess = Date.now();\n return entry.brain;\n }\n } catch {\n entry.lastAccess = Date.now();\n return entry.brain;\n }\n }\n\n await ensureShared();\n\n if (_pool.size >= MAX_POOL_SIZE) {\n let oldest: string | undefined;\n let oldestTime = Infinity;\n for (const [key, entry] of _pool) {\n if (entry.lastAccess < oldestTime) {\n oldestTime = entry.lastAccess;\n oldest = key;\n }\n }\n if (oldest) evictPool(oldest);\n }\n\n const brain = await _createBrain(resolved);\n _pool.set(resolved, { brain, lastAccess: Date.now() });\n return brain;\n}\n\nasync function _createBrain(resolved: string): Promise<BrainBank> {\n const opts: Record<string, any> = { repoPath: resolved, reranker: _sharedReranker };\n if (_sharedEmbedding) {\n opts.embeddingProvider = _sharedEmbedding;\n opts.embeddingDims = _sharedEmbedding.dims;\n }\n const brain = new BrainBank(opts)\n .use(code({ repoPath: resolved }))\n .use(git({ repoPath: resolved }))\n .use(docs());\n\n try {\n await brain.initialize();\n } catch (err: any) {\n if (err?.message?.includes('Invalid the given array length')) {\n const dbPath = path.join(resolved, '.brainbank', 'brainbank.db');\n try { fs.unlinkSync(dbPath); } catch {}\n try { fs.unlinkSync(dbPath + '-wal'); } catch {}\n try { fs.unlinkSync(dbPath + '-shm'); } catch {}\n\n const fresh = new BrainBank(opts)\n .use(code({ repoPath: resolved }))\n .use(git({ repoPath: resolved }))\n .use(docs());\n await fresh.initialize();\n return fresh;\n }\n throw err;\n }\n\n return brain;\n}\n\nfunction evictPool(resolved: string) {\n const entry = _pool.get(resolved);\n if (entry) {\n try { entry.brain.close(); } catch {}\n _pool.delete(resolved);\n }\n}\n\n// ── MCP Server Setup ────────────────────────────────\n\nconst server = new McpServer({\n name: 'brainbank',\n version: '0.2.0',\n});\n\n// ── Tool: brainbank_search ──────────────────────────\n// Replaces: brainbank_search, brainbank_hybrid_search, brainbank_keyword_search\n\nserver.registerTool(\n 'brainbank_search',\n {\n title: 'BrainBank Search',\n description:\n 'Search indexed code and git commits. Supports three modes:\\n' +\n '- hybrid (default): vector + BM25 fused with RRF — best quality\\n' +\n '- vector: semantic similarity only\\n' +\n '- keyword: instant BM25 for exact terms, function names, error messages',\n inputSchema: z.object({\n query: z.string().describe('Search query — works with both keywords and natural language'),\n mode: z.enum(['hybrid', 'vector', 'keyword']).optional().default('hybrid').describe('Search strategy'),\n codeK: z.number().optional().default(8).describe('Max code results'),\n gitK: z.number().optional().default(5).describe('Max git results'),\n minScore: z.number().optional().default(0.25).describe('Minimum similarity score (0-1), only for vector mode'),\n collections: z.record(z.string(), z.number()).optional().describe(\n 'Max results per source. Reserved: \"code\", \"git\", \"docs\". Any other key = KV collection.'\n ),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ query, mode, codeK, gitK, minScore, collections, repo }) => {\n const brainbank = await getBrainBank(repo);\n\n let results;\n if (mode === 'keyword') {\n results = await brainbank.searchBM25(query, { codeK, gitK });\n } else if (mode === 'vector') {\n results = await brainbank.search(query, { codeK, gitK, minScore });\n } else {\n results = await brainbank.hybridSearch(query, { codeK, gitK, collections });\n }\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: 'No results found.' }] };\n }\n\n const modeLabel = mode === 'keyword' ? 'Keyword (BM25)' : mode === 'vector' ? 'Vector' : 'Hybrid (Vector + BM25 → RRF)';\n return { content: [{ type: 'text', text: formatResults(results, modeLabel) }] };\n },\n);\n\n// ── Tool: brainbank_context ─────────────────────────\n\nserver.registerTool(\n 'brainbank_context',\n {\n title: 'BrainBank Context',\n description: 'Get a formatted knowledge context block for a task. Returns relevant code, git history, and co-edit patterns as markdown.',\n inputSchema: z.object({\n task: z.string().describe('Description of the task you need context for'),\n affectedFiles: z.array(z.string()).optional().default([]).describe('Files you plan to modify (improves co-edit suggestions)'),\n codeResults: z.number().optional().default(6).describe('Max code results'),\n gitResults: z.number().optional().default(5).describe('Max git commit results'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ task, affectedFiles, codeResults, gitResults, repo }) => {\n const brainbank = await getBrainBank(repo);\n const context = await brainbank.getContext(task, {\n affectedFiles,\n codeResults,\n gitResults,\n });\n\n return { content: [{ type: 'text', text: context }] };\n },\n);\n\n// ── Tool: brainbank_index ───────────────────────────\n\nserver.registerTool(\n 'brainbank_index',\n {\n title: 'BrainBank Index',\n description: 'Index (or re-index) code, git history, and docs. Incremental — only changed files are processed.',\n inputSchema: z.object({\n modules: z.array(z.enum(['code', 'git', 'docs'])).optional().describe('Which modules to index (default: all)'),\n docsPath: z.string().optional().describe('Path to a docs folder to register and index'),\n forceReindex: z.boolean().optional().default(false).describe('Force re-index of all files'),\n gitDepth: z.number().optional().default(500).describe('Number of git commits to index'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ modules, docsPath, forceReindex, gitDepth, repo }) => {\n const brainbank = await getBrainBank(repo);\n\n if (docsPath) {\n const absPath = path.resolve(docsPath);\n const collName = path.basename(absPath);\n try {\n brainbank.addCollection({\n name: collName,\n path: absPath,\n pattern: '**/*.md',\n ignore: ['deprecated/**', 'node_modules/**'],\n });\n } catch {\n // docs module not loaded\n }\n }\n\n const result = await brainbank.index({ modules, forceReindex, gitDepth });\n\n const lines = [\n '## Indexing Complete',\n '',\n `**Code**: ${result.code?.indexed ?? 0} files indexed, ${result.code?.skipped ?? 0} skipped, ${result.code?.chunks ?? 0} chunks`,\n `**Git**: ${result.git?.indexed ?? 0} commits indexed, ${result.git?.skipped ?? 0} skipped`,\n ];\n\n if (result.docs) {\n for (const [name, stat] of Object.entries(result.docs)) {\n lines.push(`**Docs [${name}]**: ${stat.indexed} indexed, ${stat.skipped} skipped, ${stat.chunks} chunks`);\n }\n }\n\n const stats = brainbank.stats();\n lines.push('');\n lines.push(`**Totals**: ${stats.code?.chunks ?? 0} code chunks, ${stats.git?.commits ?? 0} commits, ${stats.documents?.documents ?? 0} docs`);\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_stats ───────────────────────────\n\nserver.registerTool(\n 'brainbank_stats',\n {\n title: 'BrainBank Stats',\n description: 'Get index statistics: file count, code chunks, git commits, HNSW sizes, KV collections.',\n inputSchema: z.object({\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ repo }) => {\n const brainbank = await getBrainBank(repo);\n const s = brainbank.stats();\n\n const lines = ['## BrainBank Stats', ''];\n\n if (s.code) {\n lines.push(`**Code**: ${s.code.files} files, ${s.code.chunks} chunks, ${s.code.hnswSize} vectors`);\n }\n if (s.git) {\n lines.push(`**Git**: ${s.git.commits} commits, ${s.git.filesTracked} files, ${s.git.coEdits} co-edit pairs`);\n }\n if (s.documents) {\n lines.push(`**Docs**: ${s.documents.collections} collections, ${s.documents.documents} documents`);\n }\n\n const kvNames = brainbank.listCollectionNames();\n if (kvNames.length > 0) {\n lines.push('');\n lines.push('**KV Collections**:');\n for (const name of kvNames) {\n const coll = brainbank.collection(name);\n lines.push(`- ${name}: ${coll.count()} items`);\n }\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_history ─────────────────────────\n\nserver.registerTool(\n 'brainbank_history',\n {\n title: 'BrainBank File History',\n description: 'Get git commit history for a file. Shows changes, authors, and line counts.',\n inputSchema: z.object({\n filePath: z.string().describe('File path (relative or partial, e.g. \"auth.ts\")'),\n limit: z.number().optional().default(20).describe('Max commits to return'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ filePath, limit, repo }) => {\n const brainbank = await getBrainBank(repo);\n const history = await brainbank.fileHistory(filePath, limit);\n\n if (history.length === 0) {\n return { content: [{ type: 'text', text: `No git history found for \"${filePath}\"` }] };\n }\n\n const lines = [`## Git History: ${filePath}`, ''];\n for (const h of history as any[]) {\n lines.push(`**[${h.short_hash}]** ${h.message} *(${h.author}, +${h.additions}/-${h.deletions})*`);\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n },\n);\n\n// ── Tool: brainbank_collection ──────────────────────\n// Replaces: brainbank_collection_add, brainbank_collection_search, brainbank_collection_trim\n\nserver.registerTool(\n 'brainbank_collection',\n {\n title: 'BrainBank Collection',\n description:\n 'Operate on KV collections (auto-created). Actions:\\n' +\n '- add: store content with optional metadata\\n' +\n '- search: hybrid vector + keyword search\\n' +\n '- trim: keep only N most recent items',\n inputSchema: z.object({\n action: z.enum(['add', 'search', 'trim']).describe('Operation to perform'),\n collection: z.string().describe('Collection name (e.g. \"errors\", \"decisions\")'),\n content: z.string().optional().describe('Content to store (required for add)'),\n query: z.string().optional().describe('Search query (required for search)'),\n metadata: z.record(z.any()).optional().default({}).describe('Metadata for add'),\n k: z.number().optional().default(5).describe('Max results for search'),\n keep: z.number().optional().describe('Items to keep for trim'),\n repo: z.string().optional().describe('Repository path (default: BRAINBANK_REPO)'),\n }),\n },\n async ({ action, collection, content, query, metadata, k, keep, repo }) => {\n const brainbank = await getBrainBank(repo);\n const coll = brainbank.collection(collection);\n\n if (action === 'add') {\n if (!content) throw new Error('BrainBank: content is required for add action.');\n const id = await coll.add(content, metadata);\n return {\n content: [{ type: 'text', text: `✓ Item #${id} added to '${collection}' (${coll.count()} total)` }],\n };\n }\n\n if (action === 'search') {\n if (!query) throw new Error('BrainBank: query is required for search action.');\n const results = await coll.search(query, { k });\n\n if (results.length === 0) {\n return { content: [{ type: 'text', text: `No results in '${collection}' for \"${query}\"` }] };\n }\n\n const lines = [`## Collection: ${collection}`, ''];\n for (const r of results) {\n const score = Math.round((r.score ?? 0) * 100);\n lines.push(`[${score}%] ${r.content}`);\n if (Object.keys(r.metadata).length > 0) {\n lines.push(` ${JSON.stringify(r.metadata)}`);\n }\n lines.push('');\n }\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n }\n\n if (action === 'trim') {\n if (keep == null) throw new Error('BrainBank: keep is required for trim action.');\n const result = await coll.trim({ keep });\n return {\n content: [{ type: 'text', text: `✓ Trimmed ${result.removed} items from '${collection}' (kept ${keep})` }],\n };\n }\n\n throw new Error(`BrainBank: Unknown action \"${action}\".`);\n },\n);\n\n// ── Shared result formatter ─────────────────────────\n\nfunction formatResults(results: any[], mode: string): string {\n const lines: string[] = [`## ${mode}`, ''];\n for (const r of results) {\n const score = Math.round(r.score * 100);\n if (r.type === 'code') {\n const m = r.metadata;\n lines.push(`[CODE ${score}%] ${r.filePath} — ${m.name || m.chunkType} (L${m.startLine}-${m.endLine})`);\n lines.push(r.content);\n lines.push('');\n } else if (r.type === 'commit') {\n const m = r.metadata;\n lines.push(`[COMMIT ${score}%] ${m.shortHash} — ${r.content} (${m.author})`);\n if (m.files?.length) lines.push(` Files: ${m.files.join(', ')}`);\n lines.push('');\n } else if (r.type === 'document') {\n const ctx = r.context ? ` — ${r.context}` : '';\n lines.push(`[DOC ${score}%] ${r.filePath} [${r.metadata.collection}]${ctx}`);\n lines.push(r.content);\n lines.push('');\n } else if (r.type === 'collection') {\n const col = r.metadata?.collection ?? 'unknown';\n lines.push(`[COLLECTION ${score}%] [${col}]`);\n lines.push(r.content);\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\n// ── Start Server ────────────────────────────────────\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch(err => {\n console.error(`BrainBank MCP Server Error: ${err.message}`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@brainbank/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "MCP (Model Context Protocol) server for BrainBank",
5
5
  "type": "module",
6
6
  "main": "dist/mcp-server.js",
7
7
  "bin": "dist/mcp-server.js",
8
- "files": ["dist/"],
9
- "scripts": { "build": "tsup" },
8
+ "files": [
9
+ "dist/"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup"
13
+ },
10
14
  "dependencies": {
11
- "brainbank": ">=0.1.0",
15
+ "brainbank": ">=0.2.2",
12
16
  "@modelcontextprotocol/sdk": "^1.27.1",
13
17
  "zod": "^4.3.6"
14
18
  },