@grec0/memory-bank-mcp 0.1.7 → 0.1.8

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.
@@ -302,7 +302,7 @@ export class IndexManager {
302
302
  // Get all chunks for the project
303
303
  const allChunks = await this.vectorStore.getAllChunks(projectId);
304
304
  // Update docs incrementally based on changed files
305
- docsGeneration = await this.projectKnowledgeService.updateDocuments(allChunks, changedFiles);
305
+ docsGeneration = await this.projectKnowledgeService.updateDocuments(projectId, allChunks, changedFiles);
306
306
  console.error(`Docs updated: ${docsGeneration.documentsUpdated.length}`);
307
307
  console.error(`Docs generated: ${docsGeneration.documentsGenerated.length}`);
308
308
  console.error(`Reasoning tokens: ${docsGeneration.totalReasoningTokens}`);
@@ -133,7 +133,7 @@ Generate a markdown document tracking project documentation progress.`,
133
133
  export class ProjectKnowledgeService {
134
134
  client;
135
135
  options;
136
- metadataCache;
136
+ metadataCacheByProject;
137
137
  constructor(apiKey, options) {
138
138
  if (!apiKey) {
139
139
  throw new Error("OpenAI API key is required for Project Knowledge Service");
@@ -142,55 +142,72 @@ export class ProjectKnowledgeService {
142
142
  this.options = {
143
143
  model: options?.model || "gpt-5-mini",
144
144
  reasoningEffort: options?.reasoningEffort || "medium",
145
- docsPath: options?.docsPath || ".memorybank/project-docs",
145
+ storagePath: options?.storagePath || ".memorybank",
146
146
  enableSummary: options?.enableSummary !== undefined ? options.enableSummary : true,
147
147
  maxChunksPerDoc: options?.maxChunksPerDoc || 50,
148
148
  };
149
- this.metadataCache = new Map();
150
- this.ensureDocsDirectory();
151
- this.loadMetadata();
149
+ this.metadataCacheByProject = new Map();
152
150
  }
153
151
  /**
154
- * Ensures the docs directory exists
152
+ * Gets the docs path for a specific project
153
+ */
154
+ getProjectDocsPath(projectId) {
155
+ return path.join(this.options.storagePath, "projects", projectId, "docs");
156
+ }
157
+ /**
158
+ * Ensures the docs directory exists for a project
155
159
  */
156
- ensureDocsDirectory() {
157
- if (!fs.existsSync(this.options.docsPath)) {
158
- fs.mkdirSync(this.options.docsPath, { recursive: true });
159
- console.error(`Created project docs directory: ${this.options.docsPath}`);
160
+ ensureProjectDocsDirectory(projectId) {
161
+ const docsPath = this.getProjectDocsPath(projectId);
162
+ if (!fs.existsSync(docsPath)) {
163
+ fs.mkdirSync(docsPath, { recursive: true });
164
+ console.error(`Created project docs directory: ${docsPath}`);
160
165
  }
166
+ return docsPath;
161
167
  }
162
168
  /**
163
- * Loads metadata for all documents
169
+ * Loads metadata for a specific project
164
170
  */
165
- loadMetadata() {
166
- const metadataPath = path.join(this.options.docsPath, "metadata.json");
171
+ loadProjectMetadata(projectId) {
172
+ if (this.metadataCacheByProject.has(projectId)) {
173
+ return this.metadataCacheByProject.get(projectId);
174
+ }
175
+ const docsPath = this.getProjectDocsPath(projectId);
176
+ const metadataPath = path.join(docsPath, "metadata.json");
177
+ const cache = new Map();
167
178
  try {
168
179
  if (fs.existsSync(metadataPath)) {
169
180
  const data = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
170
181
  for (const [type, metadata] of Object.entries(data)) {
171
- this.metadataCache.set(type, metadata);
182
+ cache.set(type, metadata);
172
183
  }
173
- console.error(`Loaded metadata for ${this.metadataCache.size} project documents`);
184
+ console.error(`Loaded metadata for ${cache.size} documents (project: ${projectId})`);
174
185
  }
175
186
  }
176
187
  catch (error) {
177
- console.error(`Warning: Could not load project docs metadata: ${error}`);
188
+ console.error(`Warning: Could not load project docs metadata for ${projectId}: ${error}`);
178
189
  }
190
+ this.metadataCacheByProject.set(projectId, cache);
191
+ return cache;
179
192
  }
180
193
  /**
181
- * Saves metadata for all documents
194
+ * Saves metadata for a specific project
182
195
  */
183
- saveMetadata() {
184
- const metadataPath = path.join(this.options.docsPath, "metadata.json");
196
+ saveProjectMetadata(projectId) {
197
+ const docsPath = this.ensureProjectDocsDirectory(projectId);
198
+ const metadataPath = path.join(docsPath, "metadata.json");
199
+ const cache = this.metadataCacheByProject.get(projectId);
200
+ if (!cache)
201
+ return;
185
202
  try {
186
203
  const data = {};
187
- for (const [type, metadata] of this.metadataCache) {
204
+ for (const [type, metadata] of cache) {
188
205
  data[type] = metadata;
189
206
  }
190
207
  fs.writeFileSync(metadataPath, JSON.stringify(data, null, 2));
191
208
  }
192
209
  catch (error) {
193
- console.error(`Warning: Could not save project docs metadata: ${error}`);
210
+ console.error(`Warning: Could not save project docs metadata for ${projectId}: ${error}`);
194
211
  }
195
212
  }
196
213
  /**
@@ -315,18 +332,20 @@ ${chunk.content}
315
332
  };
316
333
  }
317
334
  /**
318
- * Generates a single document
335
+ * Generates a single document for a specific project
319
336
  */
320
- async generateDocument(type, chunks, force = false, previousProgress) {
337
+ async generateDocument(projectId, type, chunks, force = false, previousProgress) {
321
338
  const definition = DOC_DEFINITIONS[type];
322
339
  const inputHash = this.hashChunks(chunks);
340
+ const docsPath = this.ensureProjectDocsDirectory(projectId);
341
+ const metadataCache = this.loadProjectMetadata(projectId);
323
342
  // Check if regeneration is needed
324
- const existingMetadata = this.metadataCache.get(type);
343
+ const existingMetadata = metadataCache.get(type);
325
344
  if (!force && existingMetadata && existingMetadata.lastInputHash === inputHash) {
326
345
  console.error(`Skipping ${type}: No changes detected`);
327
346
  return null;
328
347
  }
329
- console.error(`Generating document: ${definition.title}`);
348
+ console.error(`Generating document: ${definition.title} (project: ${projectId})`);
330
349
  console.error(` Input chunks: ${chunks.length}`);
331
350
  // Prepare prompt
332
351
  const chunksText = this.prepareChunksForPrompt(chunks, this.options.maxChunksPerDoc);
@@ -353,18 +372,18 @@ ${chunk.content}
353
372
  },
354
373
  };
355
374
  // Save document
356
- const docPath = path.join(this.options.docsPath, definition.filename);
375
+ const docPath = path.join(docsPath, definition.filename);
357
376
  fs.writeFileSync(docPath, doc.content);
358
377
  // Update metadata
359
- this.metadataCache.set(type, doc.metadata);
360
- this.saveMetadata();
378
+ metadataCache.set(type, doc.metadata);
379
+ this.saveProjectMetadata(projectId);
361
380
  console.error(`Generated ${definition.title} (${result.reasoningTokens} reasoning + ${result.outputTokens} output tokens)`);
362
381
  return doc;
363
382
  }
364
383
  /**
365
384
  * Generates all project documents (in parallel for speed)
366
385
  */
367
- async generateAllDocuments(chunks, force = false) {
386
+ async generateAllDocuments(projectId, chunks, force = false) {
368
387
  const result = {
369
388
  success: true,
370
389
  documentsGenerated: [],
@@ -374,9 +393,11 @@ ${chunk.content}
374
393
  totalOutputTokens: 0,
375
394
  errors: [],
376
395
  };
396
+ const docsPath = this.ensureProjectDocsDirectory(projectId);
397
+ const metadataCache = this.loadProjectMetadata(projectId);
377
398
  // Get previous progress if exists (read before parallel generation)
378
399
  let previousProgress;
379
- const progressPath = path.join(this.options.docsPath, "progress.md");
400
+ const progressPath = path.join(docsPath, "progress.md");
380
401
  if (fs.existsSync(progressPath)) {
381
402
  previousProgress = fs.readFileSync(progressPath, "utf-8");
382
403
  }
@@ -393,15 +414,15 @@ ${chunk.content}
393
414
  "activeContext",
394
415
  "progress",
395
416
  ];
396
- console.error(`\n🚀 Generating ${docTypes.length} documents in PARALLEL...`);
417
+ console.error(`\n🚀 Generating ${docTypes.length} documents in PARALLEL for project: ${projectId}...`);
397
418
  // Generate all documents in parallel
398
419
  const generationPromises = docTypes.map(async (docType) => {
399
420
  try {
400
421
  // For activeContext, use only recent chunks
401
422
  const docChunks = docType === "activeContext" ? recentChunks : chunks;
402
- const existingMetadata = this.metadataCache.get(docType);
423
+ const existingMetadata = metadataCache.get(docType);
403
424
  const isNew = !existingMetadata;
404
- const doc = await this.generateDocument(docType, docChunks, force, docType === "progress" ? previousProgress : undefined);
425
+ const doc = await this.generateDocument(projectId, docType, docChunks, force, docType === "progress" ? previousProgress : undefined);
405
426
  return { docType, doc, isNew, error: null };
406
427
  }
407
428
  catch (error) {
@@ -438,13 +459,15 @@ ${chunk.content}
438
459
  /**
439
460
  * Updates only documents affected by changes
440
461
  */
441
- async updateDocuments(chunks, changedFiles) {
442
- console.error(`updateDocuments called with ${chunks.length} chunks and ${changedFiles.length} changed files`);
462
+ async updateDocuments(projectId, chunks, changedFiles) {
463
+ console.error(`updateDocuments called for project ${projectId} with ${chunks.length} chunks and ${changedFiles.length} changed files`);
443
464
  // Debug: show first chunk if exists
444
465
  if (chunks.length > 0) {
445
466
  const firstChunk = chunks[0];
446
467
  console.error(`First chunk: ${firstChunk.file_path}, content length: ${firstChunk.content?.length || 0}`);
447
468
  }
469
+ const docsPath = this.ensureProjectDocsDirectory(projectId);
470
+ const metadataCache = this.loadProjectMetadata(projectId);
448
471
  // Determine which documents need updating based on changed files
449
472
  const docsToUpdate = [];
450
473
  // Always update activeContext and progress when there are changes
@@ -475,7 +498,7 @@ ${chunk.content}
475
498
  };
476
499
  // Get previous progress (read before parallel generation)
477
500
  let previousProgress;
478
- const progressPath = path.join(this.options.docsPath, "progress.md");
501
+ const progressPath = path.join(docsPath, "progress.md");
479
502
  if (fs.existsSync(progressPath)) {
480
503
  previousProgress = fs.readFileSync(progressPath, "utf-8");
481
504
  }
@@ -483,14 +506,14 @@ ${chunk.content}
483
506
  const recentChunks = [...chunks]
484
507
  .sort((a, b) => b.timestamp - a.timestamp)
485
508
  .slice(0, Math.min(30, chunks.length));
486
- console.error(`\n🚀 Updating ${docsToUpdate.length} documents in PARALLEL...`);
509
+ console.error(`\n🚀 Updating ${docsToUpdate.length} documents in PARALLEL for project: ${projectId}...`);
487
510
  // Generate docs in parallel
488
511
  const updatePromises = docsToUpdate.map(async (docType) => {
489
512
  try {
490
513
  const docChunks = docType === "activeContext" ? recentChunks : chunks;
491
- const existingMetadata = this.metadataCache.get(docType);
514
+ const existingMetadata = metadataCache.get(docType);
492
515
  const isNew = !existingMetadata;
493
- const doc = await this.generateDocument(docType, docChunks, true, // Force update for changed docs
516
+ const doc = await this.generateDocument(projectId, docType, docChunks, true, // Force update for changed docs
494
517
  docType === "progress" ? previousProgress : undefined);
495
518
  return { docType, doc, isNew, error: null };
496
519
  }
@@ -535,16 +558,18 @@ ${chunk.content}
535
558
  return result;
536
559
  }
537
560
  /**
538
- * Reads a project document
561
+ * Reads a project document for a specific project
539
562
  */
540
- getDocument(type) {
563
+ getDocument(projectId, type) {
541
564
  const definition = DOC_DEFINITIONS[type];
542
- const docPath = path.join(this.options.docsPath, definition.filename);
565
+ const docsPath = this.getProjectDocsPath(projectId);
566
+ const docPath = path.join(docsPath, definition.filename);
543
567
  if (!fs.existsSync(docPath)) {
544
568
  return null;
545
569
  }
546
570
  const content = fs.readFileSync(docPath, "utf-8");
547
- const metadata = this.metadataCache.get(type);
571
+ const metadataCache = this.loadProjectMetadata(projectId);
572
+ const metadata = metadataCache.get(type);
548
573
  return {
549
574
  type,
550
575
  content,
@@ -558,12 +583,12 @@ ${chunk.content}
558
583
  };
559
584
  }
560
585
  /**
561
- * Reads all project documents
586
+ * Reads all project documents for a specific project
562
587
  */
563
- getAllDocuments() {
588
+ getAllDocuments(projectId) {
564
589
  const docs = [];
565
590
  for (const type of Object.keys(DOC_DEFINITIONS)) {
566
- const doc = this.getDocument(type);
591
+ const doc = this.getDocument(projectId, type);
567
592
  if (doc) {
568
593
  docs.push(doc);
569
594
  }
@@ -573,8 +598,8 @@ ${chunk.content}
573
598
  /**
574
599
  * Gets a summary of all documents (useful for context loading)
575
600
  */
576
- getDocumentsSummary() {
577
- const docs = this.getAllDocuments();
601
+ getDocumentsSummary(projectId) {
602
+ const docs = this.getAllDocuments(projectId);
578
603
  if (docs.length === 0) {
579
604
  return "No project documentation has been generated yet. Use memorybank_generate_project_docs to generate documentation.";
580
605
  }
@@ -594,21 +619,23 @@ ${chunk.content}
594
619
  return summary;
595
620
  }
596
621
  /**
597
- * Checks if documents exist
622
+ * Checks if documents exist for a project
598
623
  */
599
- hasDocuments() {
600
- return this.metadataCache.size > 0;
624
+ hasDocuments(projectId) {
625
+ const metadataCache = this.loadProjectMetadata(projectId);
626
+ return metadataCache.size > 0;
601
627
  }
602
628
  /**
603
- * Gets statistics about generated documents
629
+ * Gets statistics about generated documents for a project
604
630
  */
605
- getStats() {
631
+ getStats(projectId) {
632
+ const metadataCache = this.loadProjectMetadata(projectId);
606
633
  let totalReasoningTokens = 0;
607
634
  let totalOutputTokens = 0;
608
635
  let lastGenerated = 0;
609
636
  const documents = {};
610
637
  for (const type of Object.keys(DOC_DEFINITIONS)) {
611
- const metadata = this.metadataCache.get(type);
638
+ const metadata = metadataCache.get(type);
612
639
  documents[type] = {
613
640
  exists: !!metadata,
614
641
  lastGenerated: metadata ? new Date(metadata.lastGenerated) : undefined,
@@ -622,7 +649,7 @@ ${chunk.content}
622
649
  }
623
650
  }
624
651
  return {
625
- documentCount: this.metadataCache.size,
652
+ documentCount: metadataCache.size,
626
653
  totalReasoningTokens,
627
654
  totalOutputTokens,
628
655
  lastGenerated: lastGenerated > 0 ? new Date(lastGenerated) : undefined,
@@ -632,13 +659,6 @@ ${chunk.content}
632
659
  // ==========================================
633
660
  // Project-specific document methods (for MCP Resources)
634
661
  // ==========================================
635
- /**
636
- * Gets the docs path for a specific project
637
- */
638
- getProjectDocsPath(projectId) {
639
- const storagePath = process.env.MEMORYBANK_STORAGE_PATH || ".memorybank";
640
- return path.join(storagePath, "projects", projectId, "docs");
641
- }
642
662
  /**
643
663
  * Checks if a project's Memory Bank is initialized
644
664
  */
@@ -716,7 +736,7 @@ export function createProjectKnowledgeService() {
716
736
  const options = {
717
737
  model: process.env.MEMORYBANK_REASONING_MODEL || "gpt-5-mini",
718
738
  reasoningEffort: process.env.MEMORYBANK_REASONING_EFFORT || "medium",
719
- docsPath: path.join(storagePath, "project-docs"),
739
+ storagePath: storagePath,
720
740
  enableSummary: true,
721
741
  };
722
742
  return new ProjectKnowledgeService(apiKey, options);
@@ -33,8 +33,9 @@ export async function generateProjectDocs(params, projectKnowledgeService, vecto
33
33
  };
34
34
  }
35
35
  console.error(`Found ${chunks.length} code chunks to analyze`);
36
- // Generate documents
37
- const result = await projectKnowledgeService.generateAllDocuments(chunks, params.force || false);
36
+ // Generate documents - projectId is required
37
+ const projectId = params.projectId || "default";
38
+ const result = await projectKnowledgeService.generateAllDocuments(projectId, chunks, params.force || false);
38
39
  // Calculate estimated cost (approximate rates for gpt-5-mini)
39
40
  // Reasoning tokens are typically more expensive
40
41
  const reasoningCostPer1K = 0.003; // $0.003 per 1K reasoning tokens
@@ -15,13 +15,14 @@ const VALID_DOC_TYPES = [
15
15
  */
16
16
  export async function getProjectDocs(params, projectKnowledgeService) {
17
17
  try {
18
+ const projectId = params.projectId;
18
19
  const format = params.format || "full";
19
20
  const requestedDoc = params.document?.toLowerCase();
20
- // Check if any documents exist
21
- if (!projectKnowledgeService.hasDocuments()) {
21
+ // Check if any documents exist for this project
22
+ if (!projectKnowledgeService.hasDocuments(projectId)) {
22
23
  return {
23
24
  success: false,
24
- message: "No project documentation has been generated yet. Run memorybank_generate_project_docs first.",
25
+ message: `No project documentation has been generated for project "${projectId}" yet. Run memorybank_generate_project_docs first.`,
25
26
  stats: {
26
27
  documentCount: 0,
27
28
  totalReasoningTokens: 0,
@@ -29,8 +30,8 @@ export async function getProjectDocs(params, projectKnowledgeService) {
29
30
  },
30
31
  };
31
32
  }
32
- // Get stats
33
- const stats = projectKnowledgeService.getStats();
33
+ // Get stats for this project
34
+ const stats = projectKnowledgeService.getStats(projectId);
34
35
  const statsResult = {
35
36
  documentCount: stats.documentCount,
36
37
  totalReasoningTokens: stats.totalReasoningTokens,
@@ -39,20 +40,20 @@ export async function getProjectDocs(params, projectKnowledgeService) {
39
40
  };
40
41
  // Handle summary request
41
42
  if (requestedDoc === "summary" || format === "summary") {
42
- const summary = projectKnowledgeService.getDocumentsSummary();
43
+ const summary = projectKnowledgeService.getDocumentsSummary(projectId);
43
44
  return {
44
45
  success: true,
45
- message: `Retrieved summary of ${stats.documentCount} project documents.`,
46
+ message: `Retrieved summary of ${stats.documentCount} project documents for "${projectId}".`,
46
47
  summary,
47
48
  stats: statsResult,
48
49
  };
49
50
  }
50
51
  // Handle "all" or no specific document
51
52
  if (!requestedDoc || requestedDoc === "all") {
52
- const documents = projectKnowledgeService.getAllDocuments();
53
+ const documents = projectKnowledgeService.getAllDocuments(projectId);
53
54
  return {
54
55
  success: true,
55
- message: `Retrieved ${documents.length} project documents.`,
56
+ message: `Retrieved ${documents.length} project documents for "${projectId}".`,
56
57
  documents,
57
58
  stats: statsResult,
58
59
  };
@@ -67,17 +68,17 @@ export async function getProjectDocs(params, projectKnowledgeService) {
67
68
  stats: statsResult,
68
69
  };
69
70
  }
70
- const document = projectKnowledgeService.getDocument(normalizedDoc);
71
+ const document = projectKnowledgeService.getDocument(projectId, normalizedDoc);
71
72
  if (!document) {
72
73
  return {
73
74
  success: false,
74
- message: `Document "${normalizedDoc}" has not been generated yet.`,
75
+ message: `Document "${normalizedDoc}" has not been generated yet for project "${projectId}".`,
75
76
  stats: statsResult,
76
77
  };
77
78
  }
78
79
  return {
79
80
  success: true,
80
- message: `Retrieved document: ${normalizedDoc}`,
81
+ message: `Retrieved document: ${normalizedDoc} for project "${projectId}"`,
81
82
  documents: [document],
82
83
  stats: statsResult,
83
84
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grec0/memory-bank-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "MCP server for semantic code indexing with Memory Bank - AI-powered codebase understanding",
5
5
  "license": "MIT",
6
6
  "author": "@grec0",