@grec0/memory-bank-mcp 0.1.16 → 0.1.18

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.
@@ -10,6 +10,81 @@ export class AgentBoard {
10
10
  this.projectId = projectId;
11
11
  this.lockManager = new LockManager(basePath);
12
12
  }
13
+ async getBoardContent() {
14
+ await this.ensureBoardExists();
15
+ return await fs.readFile(this.getBoardPath(), 'utf-8');
16
+ }
17
+ // --- Task Management Methods ---
18
+ async createTask(title, fromAgentId, assignedTo, description) {
19
+ const taskId = `TASK-${Date.now().toString().slice(-6)}`;
20
+ await this.updateBoard((content) => {
21
+ const tasks = this.parseTable(content, 'Pending Tasks');
22
+ const now = new Date().toISOString();
23
+ // Columns: ID, Title, Assigned To, From, Status, Created At
24
+ tasks.push([taskId, title, assignedTo, fromAgentId, 'PENDING', now]);
25
+ return this.updateTable(content, 'Pending Tasks', ['ID', 'Title', 'Assigned To', 'From', 'Status', 'Created At'], tasks);
26
+ });
27
+ await this.logMessage(fromAgentId, `Created task ${taskId}: ${title}`);
28
+ return taskId;
29
+ }
30
+ async createExternalTask(title, fromProject, context) {
31
+ const taskId = `EXT-${Date.now().toString().slice(-6)}`;
32
+ await this.updateBoard((content) => {
33
+ const requests = this.parseTable(content, 'External Requests');
34
+ const now = new Date().toISOString();
35
+ // Columns: ID, Title, From Project, Context, Status, Received At
36
+ requests.push([taskId, title, fromProject, context, 'PENDING', now]);
37
+ return this.updateTable(content, 'External Requests', ['ID', 'Title', 'From Project', 'Context', 'Status', 'Received At'], requests);
38
+ });
39
+ await this.logMessage('SYSTEM', `Received external prompt from project ${fromProject}: ${title}`);
40
+ return taskId;
41
+ }
42
+ async completeTask(taskId, agentId) {
43
+ await this.updateBoard((content) => {
44
+ let tasks = this.parseTable(content, 'Pending Tasks');
45
+ const initialCount = tasks.length;
46
+ tasks = tasks.filter(t => t[0] !== taskId);
47
+ if (tasks.length < initialCount) {
48
+ return this.updateTable(content, 'Pending Tasks', ['ID', 'Title', 'Assigned To', 'From', 'Status', 'Created At'], tasks);
49
+ }
50
+ // Check external requests if not found in pending
51
+ let requests = this.parseTable(content, 'External Requests');
52
+ requests = requests.filter(t => t[0] !== taskId);
53
+ return this.updateTable(content, 'External Requests', ['ID', 'Title', 'From Project', 'Context', 'Status', 'Received At'], requests);
54
+ });
55
+ await this.logMessage(agentId, `Completed task ${taskId}`);
56
+ }
57
+ async logMessage(agentId, message) {
58
+ await this.updateBoard((content) => {
59
+ const lines = content.split('\n');
60
+ let msgSectionIdx = -1;
61
+ for (let i = 0; i < lines.length; i++) {
62
+ if (lines[i].trim().startsWith('## Agent Messages')) {
63
+ msgSectionIdx = i;
64
+ break;
65
+ }
66
+ }
67
+ const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
68
+ const msgLine = `- [${timestamp}] **${agentId}**: ${message}`;
69
+ if (msgSectionIdx !== -1) {
70
+ // Determine insertion point (after header)
71
+ lines.splice(msgSectionIdx + 1, 0, msgLine);
72
+ // Truncate logs if too long (keep last 20)
73
+ let nextHeaderIdx = -1;
74
+ for (let j = msgSectionIdx + 2; j < lines.length; j++) {
75
+ if (lines[j].startsWith('## ')) {
76
+ nextHeaderIdx = j;
77
+ break;
78
+ }
79
+ }
80
+ const endOfMessages = nextHeaderIdx === -1 ? lines.length : nextHeaderIdx;
81
+ if (endOfMessages - (msgSectionIdx + 1) > 20) {
82
+ lines.splice(endOfMessages - 1, 1);
83
+ }
84
+ }
85
+ return lines.join('\n');
86
+ });
87
+ }
13
88
  getBoardPath() {
14
89
  return path.join(this.basePath, '.memorybank', 'projects', this.projectId, 'docs', 'agentBoard.md');
15
90
  }
@@ -22,8 +97,16 @@ export class AgentBoard {
22
97
  const initialContent = `# Multi-Agent Board
23
98
 
24
99
  ## Active Agents
25
- | Agent ID | Status | Current Focus | Last Heartbeat |
26
- |---|---|---|---|
100
+ | Agent ID | Status | Current Focus | Session ID | Last Heartbeat |
101
+ |---|---|---|---|---|
102
+
103
+ ## Pending Tasks
104
+ | ID | Title | Assigned To | From | Status | Created At |
105
+ |---|---|---|---|---|---|
106
+
107
+ ## External Requests
108
+ | ID | Title | From Project | Context | Status | Received At |
109
+ |---|---|---|---|---|---|
27
110
 
28
111
  ## File Locks
29
112
  | File Pattern | Claimed By | Since |
@@ -36,34 +119,64 @@ export class AgentBoard {
36
119
  await fs.writeFile(boardPath, initialContent, 'utf-8');
37
120
  }
38
121
  }
39
- async registerAgent(agentId) {
122
+ async registerAgent(agentId, sessionId) {
40
123
  await this.updateBoard((content) => {
41
124
  const agents = this.parseTable(content, 'Active Agents');
42
125
  const existing = agents.findIndex(a => a[0]?.trim() === agentId);
43
126
  const now = new Date().toISOString();
127
+ const session = sessionId || (existing >= 0 ? (agents[existing][3] || '') : ''); // Use col 3 as SessionID based on previous read
44
128
  if (existing >= 0) {
45
- agents[existing] = [agentId, 'ACTIVE', '-', now];
129
+ // Check if it's already 5 cols or 4
130
+ if (agents[existing].length < 5) {
131
+ agents[existing] = [agentId, 'ACTIVE', '-', session, now];
132
+ }
133
+ else {
134
+ // preserve existing fields if needed, but update timestamp
135
+ agents[existing] = [agentId, 'ACTIVE', '-', session, now];
136
+ }
46
137
  }
47
138
  else {
48
- agents.push([agentId, 'ACTIVE', '-', now]);
139
+ agents.push([agentId, 'ACTIVE', '-', session, now]);
49
140
  }
50
- return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Last Heartbeat'], agents);
141
+ return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
51
142
  });
52
143
  }
53
144
  async updateStatus(agentId, status, focus) {
54
145
  await this.updateBoard((content) => {
55
- const agents = this.parseTable(content, 'Active Agents');
146
+ let agents = this.parseTable(content, 'Active Agents');
147
+ // Migration: Ensure 5 columns
148
+ agents = agents.map(row => {
149
+ if (row.length === 4) {
150
+ return [row[0], row[1], row[2], '', row[3]];
151
+ }
152
+ return row;
153
+ });
56
154
  const idx = agents.findIndex(a => a[0]?.trim() === agentId);
57
155
  const now = new Date().toISOString();
58
156
  if (idx >= 0) {
59
- agents[idx] = [agentId, status, focus, now];
157
+ // Keep existing session ID
158
+ const currentSession = agents[idx][3] || '';
159
+ agents[idx] = [agentId, status, focus, currentSession, now];
60
160
  }
61
161
  else {
62
- agents.push([agentId, status, focus, now]);
162
+ agents.push([agentId, status, focus, '', now]);
63
163
  }
64
- return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Last Heartbeat'], agents);
164
+ return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
65
165
  });
66
166
  }
167
+ async getSessionId(agentId) {
168
+ const content = await this.getBoardContent();
169
+ const agents = this.parseTable(content, 'Active Agents');
170
+ const agent = agents.find(a => a[0]?.trim() === agentId);
171
+ if (agent) {
172
+ // Handle 5 cols
173
+ if (agent.length >= 5)
174
+ return agent[3].trim();
175
+ // Handle 4 cols (legacy) - no session ID
176
+ return undefined;
177
+ }
178
+ return undefined;
179
+ }
67
180
  async claimResource(agentId, resource) {
68
181
  let success = false;
69
182
  await this.updateBoard((content) => {
@@ -95,10 +208,7 @@ export class AgentBoard {
95
208
  return this.updateTable(content, 'File Locks', ['File Pattern', 'Claimed By', 'Since'], locks);
96
209
  });
97
210
  }
98
- async getBoardContent() {
99
- await this.ensureBoardExists();
100
- return await fs.readFile(this.getBoardPath(), 'utf-8');
101
- }
211
+ // [Duplicate getBoardContent removed]
102
212
  // --- Helpers ---
103
213
  async updateBoard(mutator) {
104
214
  await this.ensureBoardExists();
@@ -0,0 +1,56 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ export class RegistryManager {
5
+ globalPath;
6
+ constructor() {
7
+ this.globalPath = path.join(os.homedir(), '.memorybank', 'global_registry.json');
8
+ }
9
+ async ensureRegistry() {
10
+ try {
11
+ await fs.mkdir(path.dirname(this.globalPath), { recursive: true });
12
+ const content = await fs.readFile(this.globalPath, 'utf-8');
13
+ return JSON.parse(content);
14
+ }
15
+ catch {
16
+ return { projects: [] };
17
+ }
18
+ }
19
+ async saveRegistry(registry) {
20
+ await fs.writeFile(this.globalPath, JSON.stringify(registry, null, 2), 'utf-8');
21
+ }
22
+ async registerProject(projectId, workspacePath, description, keywords = []) {
23
+ const registry = await this.ensureRegistry();
24
+ const idx = registry.projects.findIndex(p => p.projectId === projectId);
25
+ // Preserve existing description/keywords if not provided
26
+ const existing = idx >= 0 ? registry.projects[idx] : null;
27
+ const card = {
28
+ projectId,
29
+ path: workspacePath,
30
+ description: description || existing?.description || '',
31
+ keywords: keywords.length > 0 ? keywords : (existing?.keywords || []),
32
+ lastActive: new Date().toISOString(),
33
+ status: 'ACTIVE'
34
+ };
35
+ if (idx >= 0) {
36
+ registry.projects[idx] = card;
37
+ }
38
+ else {
39
+ registry.projects.push(card);
40
+ }
41
+ await this.saveRegistry(registry);
42
+ }
43
+ async discoverProjects(query) {
44
+ const registry = await this.ensureRegistry();
45
+ if (!query || query.trim() === '')
46
+ return registry.projects;
47
+ const q = query.toLowerCase();
48
+ return registry.projects.filter(p => p.projectId.toLowerCase().includes(q) ||
49
+ (p.description && p.description.toLowerCase().includes(q)) ||
50
+ p.keywords.some(k => k.toLowerCase().includes(q)));
51
+ }
52
+ async getProject(projectId) {
53
+ const registry = await this.ensureRegistry();
54
+ return registry.projects.find(p => p.projectId === projectId);
55
+ }
56
+ }
@@ -0,0 +1,45 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ export class SessionLogger {
4
+ projectPath;
5
+ constructor(projectPath) {
6
+ this.projectPath = projectPath;
7
+ }
8
+ getSessionDir(projectId) {
9
+ return path.join(this.projectPath, '.memorybank', 'projects', projectId, 'sessions');
10
+ }
11
+ getSessionFilePath(projectId, sessionId) {
12
+ return path.join(this.getSessionDir(projectId), `${sessionId}.jsonl`);
13
+ }
14
+ async logSessionEvent(projectId, sessionId, event) {
15
+ if (!sessionId)
16
+ return; // No logging if no session ID provided
17
+ const sessionDir = this.getSessionDir(projectId);
18
+ try {
19
+ await fs.mkdir(sessionDir, { recursive: true });
20
+ const filePath = this.getSessionFilePath(projectId, sessionId);
21
+ // Append line to JSONL file
22
+ const line = JSON.stringify(event) + '\n';
23
+ await fs.appendFile(filePath, line, 'utf-8');
24
+ }
25
+ catch (error) {
26
+ console.error(`[SessionLogger] Error logging event for session ${sessionId}:`, error);
27
+ }
28
+ }
29
+ async getSessionHistory(projectId, sessionId) {
30
+ const filePath = this.getSessionFilePath(projectId, sessionId);
31
+ try {
32
+ const content = await fs.readFile(filePath, 'utf-8');
33
+ return content.split('\n')
34
+ .filter(line => line.trim().length > 0)
35
+ .map(line => JSON.parse(line));
36
+ }
37
+ catch (error) {
38
+ if (error.code === 'ENOENT') {
39
+ return [];
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+ }
45
+ export const sessionLogger = new SessionLogger(process.cwd());
@@ -1,2 +1,2 @@
1
1
  // Version of the MCP Kanban server
2
- export const VERSION = "0.1.15";
2
+ export const VERSION = "0.1.18";
package/dist/index.js CHANGED
@@ -80,12 +80,16 @@ import { updateContext, updateContextToolDefinition } from "./tools/updateContex
80
80
  import { recordDecision, recordDecisionToolDefinition } from "./tools/recordDecision.js";
81
81
  import { trackProgress, trackProgressToolDefinition } from "./tools/trackProgress.js";
82
82
  import { manageAgentsTool, manageAgentsToolDefinition } from "./tools/manageAgents.js";
83
+ import { discoverProjectsTool, discoverProjectsToolDefinition } from "./tools/discoverProjects.js";
84
+ import { delegateTaskTool, delegateTaskToolDefinition } from "./tools/delegateTask.js";
85
+ import { RegistryManager } from "./common/registryManager.js";
83
86
  import { VERSION } from "./common/version.js";
84
87
  // Global services
85
88
  let embeddingService;
86
89
  let vectorStore;
87
90
  let indexManager;
88
91
  let projectKnowledgeService;
92
+ let registryManager;
89
93
  let workspaceRoot;
90
94
  // Create the MCP Server
91
95
  const server = new McpServer({
@@ -155,6 +159,10 @@ server.tool("memorybank_search", "Busca código relevante mediante búsqueda sem
155
159
  .string()
156
160
  .optional()
157
161
  .describe("Filtrar resultados por lenguaje de programación (ej: 'typescript', 'python')"),
162
+ agentId: z
163
+ .string()
164
+ .optional()
165
+ .describe("Identificador del agente (para logging de sesión)"),
158
166
  }, async (args) => {
159
167
  const result = await searchMemory({
160
168
  projectId: args.projectId,
@@ -163,7 +171,8 @@ server.tool("memorybank_search", "Busca código relevante mediante búsqueda sem
163
171
  minScore: args.minScore,
164
172
  filterByFile: args.filterByFile,
165
173
  filterByLanguage: args.filterByLanguage,
166
- }, indexManager);
174
+ agentId: args.agentId,
175
+ }, indexManager, workspaceRoot);
167
176
  return {
168
177
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
169
178
  };
@@ -494,6 +503,7 @@ server.tool(manageAgentsToolDefinition.name, manageAgentsToolDefinition.descript
494
503
  projectId: z.string().describe("Identificador único del proyecto (OBLIGATORIO)"),
495
504
  action: z.enum(["register", "update_status", "claim_resource", "release_resource", "get_board"]).describe("Acción a realizar"),
496
505
  agentId: z.string().optional().describe("Identificador del agente (ej: 'dev-agent-1'). Requerido para escrituras."),
506
+ sessionId: z.string().optional().describe("UUID de sesión del agente para tracking de contexto."),
497
507
  status: z.string().optional().describe("Estado del agente (para update_status)."),
498
508
  focus: z.string().optional().describe("Tarea o fichero en el que se enfoca (para update_status)."),
499
509
  resource: z.string().optional().describe("Identificador del recurso a bloquear (ej: 'src/auth/')."),
@@ -506,6 +516,7 @@ server.tool(manageAgentsToolDefinition.name, manageAgentsToolDefinition.descript
506
516
  projectId: args.projectId,
507
517
  action: args.action,
508
518
  agentId: args.agentId,
519
+ sessionId: args.sessionId,
509
520
  status: args.status,
510
521
  focus: args.focus,
511
522
  resource: args.resource
@@ -753,6 +764,18 @@ async function validateEnvironment() {
753
764
  indexManager.setProjectKnowledgeService(projectKnowledgeService);
754
765
  indexManager.setAutoUpdateDocs(autoUpdateDocs);
755
766
  console.error("✓ Project Knowledge service connected to Index Manager");
767
+ // Initialize Registry Manager and register current project
768
+ try {
769
+ registryManager = new RegistryManager();
770
+ // Assuming user defines project ID in env or generic default
771
+ // We will try to read it from an existing .memorybank config if possible, or pass it later
772
+ // But here we can't easily know the projectId if it hasn't been initialized by the user.
773
+ // However, if the user calls initialize_memorybank, we should register it then.
774
+ console.error("✓ Registry manager initialized");
775
+ }
776
+ catch (regError) {
777
+ console.error(`⚠ Warning: Registry Manager service not available: ${regError}`);
778
+ }
756
779
  }
757
780
  catch (error) {
758
781
  console.error(`⚠ Warning: Project Knowledge service not available: ${error}`);
@@ -766,6 +789,36 @@ async function validateEnvironment() {
766
789
  console.error("\n✓ All services ready");
767
790
  console.error("");
768
791
  }
792
+ // Tool: Discover Projects
793
+ server.tool(discoverProjectsToolDefinition.name, discoverProjectsToolDefinition.description, {
794
+ query: z.string().optional().describe("Término de búsqueda (por ID, descripción o keywords)")
795
+ }, async (args) => {
796
+ const result = await discoverProjectsTool({
797
+ query: args.query
798
+ });
799
+ return {
800
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
801
+ };
802
+ });
803
+ // Tool: Delegate Task
804
+ server.tool(delegateTaskToolDefinition.name, delegateTaskToolDefinition.description, {
805
+ projectId: z.string().describe("ID del proyecto origen (quien pide)"),
806
+ targetProjectId: z.string().describe("ID del proyecto destino (quien debe hacer el trabajo)"),
807
+ title: z.string().describe("Título corto de la tarea"),
808
+ description: z.string().describe("Descripción detallada de lo que se necesita"),
809
+ context: z.string().optional().describe("Contexto técnico adicional para que el agente receptor entienda la tarea")
810
+ }, async (args) => {
811
+ const result = await delegateTaskTool({
812
+ projectId: args.projectId,
813
+ targetProjectId: args.targetProjectId,
814
+ title: args.title,
815
+ description: args.description,
816
+ context: args.context || ''
817
+ });
818
+ return {
819
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
820
+ };
821
+ });
769
822
  /**
770
823
  * Starts the stdio server
771
824
  */
@@ -0,0 +1,59 @@
1
+ import { RegistryManager } from '../common/registryManager.js';
2
+ import { AgentBoard } from '../common/agentBoard.js';
3
+ export async function delegateTaskTool(params) {
4
+ const registryManager = new RegistryManager();
5
+ const targetProject = await registryManager.getProject(params.targetProjectId);
6
+ if (!targetProject) {
7
+ return {
8
+ success: false,
9
+ message: `Target project '${params.targetProjectId}' not found in global registry.`
10
+ };
11
+ }
12
+ try {
13
+ // Initialize board for the TARGET project path
14
+ // We use targetProject.path as the workspace root for that board
15
+ const targetBoard = new AgentBoard(targetProject.path, targetProject.projectId);
16
+ const taskId = await targetBoard.createExternalTask(params.title, params.projectId, `${params.description}\n\nContext:\n${params.context}`);
17
+ return {
18
+ success: true,
19
+ taskId,
20
+ message: `Task successfully delegated to project '${params.targetProjectId}' (Task ID: ${taskId})`
21
+ };
22
+ }
23
+ catch (error) {
24
+ return {
25
+ success: false,
26
+ message: `Failed to delegate task: ${error.message}`
27
+ };
28
+ }
29
+ }
30
+ export const delegateTaskToolDefinition = {
31
+ name: "memorybank_delegate_task",
32
+ description: "Delega una tarea a otro proyecto del ecosistema. Crea una petición externa en el tablero del proyecto destino.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ projectId: {
37
+ type: "string",
38
+ description: "ID del proyecto origen (quien pide)"
39
+ },
40
+ targetProjectId: {
41
+ type: "string",
42
+ description: "ID del proyecto destino (quien debe hacer el trabajo)"
43
+ },
44
+ title: {
45
+ type: "string",
46
+ description: "Título corto de la tarea"
47
+ },
48
+ description: {
49
+ type: "string",
50
+ description: "Descripción detallada de lo que se necesita"
51
+ },
52
+ context: {
53
+ type: "string",
54
+ description: "Contexto técnico adicional para que el agente receptor entienda la tarea"
55
+ }
56
+ },
57
+ required: ["projectId", "targetProjectId", "title", "description"]
58
+ }
59
+ };
@@ -0,0 +1,23 @@
1
+ import { RegistryManager } from '../common/registryManager.js';
2
+ export async function discoverProjectsTool(params) {
3
+ const registryManager = new RegistryManager();
4
+ const projects = await registryManager.discoverProjects(params.query);
5
+ return {
6
+ success: true,
7
+ projectCount: projects.length,
8
+ projects: projects
9
+ };
10
+ }
11
+ export const discoverProjectsToolDefinition = {
12
+ name: "memorybank_discover_projects",
13
+ description: "Descubre otros proyectos indexados en el ecosistema Memory Bank local. Útil para coordinar tareas entre proyectos.",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ query: {
18
+ type: "string",
19
+ description: "Término de búsqueda (por ID, descripción o keywords)"
20
+ }
21
+ }
22
+ }
23
+ };
@@ -2,6 +2,8 @@
2
2
  * @fileoverview Tool for reading project documentation
3
3
  * Retrieves generated markdown documents for project context
4
4
  */
5
+ import { AgentBoard } from "../common/agentBoard.js";
6
+ import { sessionLogger } from "../common/sessionLogger.js";
5
7
  const VALID_DOC_TYPES = [
6
8
  "projectBrief",
7
9
  "productContext",
@@ -13,7 +15,30 @@ const VALID_DOC_TYPES = [
13
15
  /**
14
16
  * Retrieves project documentation
15
17
  */
16
- export async function getProjectDocs(params, projectKnowledgeService) {
18
+ export async function getProjectDocs(params, projectKnowledgeService, workspaceRoot) {
19
+ const logSession = async (outputSummary) => {
20
+ if (params.agentId && workspaceRoot) {
21
+ try {
22
+ const board = new AgentBoard(workspaceRoot, params.projectId);
23
+ const sessionId = await board.getSessionId(params.agentId);
24
+ if (sessionId) {
25
+ // Use singleton sessionLogger
26
+ await sessionLogger.logSessionEvent(params.projectId, sessionId, {
27
+ timestamp: new Date().toISOString(),
28
+ type: 'read_doc',
29
+ data: {
30
+ document: params.document || 'all',
31
+ format: params.format || 'full',
32
+ summary: outputSummary
33
+ }
34
+ });
35
+ }
36
+ }
37
+ catch (error) {
38
+ console.error(`Failed to log doc session: ${error}`);
39
+ }
40
+ }
41
+ };
17
42
  try {
18
43
  const projectId = params.projectId;
19
44
  const format = params.format || "full";
@@ -41,6 +66,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
41
66
  // Handle summary request
42
67
  if (requestedDoc === "summary" || format === "summary") {
43
68
  const summary = projectKnowledgeService.getDocumentsSummary(projectId);
69
+ await logSession("Summary of all documents");
44
70
  return {
45
71
  success: true,
46
72
  message: `Retrieved summary of ${stats.documentCount} project documents for "${projectId}".`,
@@ -51,6 +77,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
51
77
  // Handle "all" or no specific document
52
78
  if (!requestedDoc || requestedDoc === "all") {
53
79
  const documents = projectKnowledgeService.getAllDocuments(projectId);
80
+ await logSession(`All ${documents.length} documents`);
54
81
  return {
55
82
  success: true,
56
83
  message: `Retrieved ${documents.length} project documents for "${projectId}".`,
@@ -76,6 +103,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
76
103
  stats: statsResult,
77
104
  };
78
105
  }
106
+ await logSession(`Document: ${normalizedDoc}`);
79
107
  return {
80
108
  success: true,
81
109
  message: `Retrieved document: ${normalizedDoc} for project "${projectId}"`,
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
+ import { RegistryManager } from "../common/registryManager.js";
7
8
  /**
8
9
  * Document templates for initialization
9
10
  */
@@ -295,6 +296,15 @@ export async function initializeMemoryBank(params, storagePath = ".memorybank")
295
296
  // Create directory structure
296
297
  console.error(`Creating directory: ${docsPath}`);
297
298
  fs.mkdirSync(docsPath, { recursive: true });
299
+ // Register project globally
300
+ try {
301
+ const registry = new RegistryManager();
302
+ await registry.registerProject(projectId, projectPath, description, projectName ? [projectName] : []);
303
+ console.error(` Global registry updated.`);
304
+ }
305
+ catch (regErr) {
306
+ console.error(` Failed to update global registry: ${regErr}`);
307
+ }
298
308
  // Create all template documents
299
309
  const documentsCreated = [];
300
310
  const documents = {
@@ -1,15 +1,23 @@
1
1
  import { AgentBoard } from '../common/agentBoard.js';
2
+ import * as crypto from 'crypto';
2
3
  const WORKSPACE_ROOT = process.cwd(); // Will be overridden by actual workspace logic if passed
3
4
  export async function manageAgentsTool(params, workspaceRoot = WORKSPACE_ROOT) {
4
- const { projectId, action, agentId, status, focus, resource } = params;
5
+ const { projectId, action, agentId, sessionId, status, focus, resource } = params;
5
6
  const board = new AgentBoard(workspaceRoot, projectId);
6
7
  try {
7
8
  switch (action) {
8
9
  case 'register':
9
10
  if (!agentId)
10
11
  throw new Error('agentId is required for register');
11
- await board.registerAgent(agentId);
12
- return { success: true, message: `Agent ${agentId} registered` };
12
+ // Auto-generate session ID if not provided.
13
+ // This abstracts the session management from the agent.
14
+ const effectiveSessionId = sessionId || crypto.randomUUID();
15
+ await board.registerAgent(agentId, effectiveSessionId);
16
+ return {
17
+ success: true,
18
+ message: `Agent ${agentId} registered`,
19
+ sessionId: effectiveSessionId
20
+ };
13
21
  case 'update_status':
14
22
  if (!agentId)
15
23
  throw new Error('agentId is required for update_status');
@@ -63,6 +71,10 @@ export const manageAgentsToolDefinition = {
63
71
  type: "string",
64
72
  description: "Identificador del agente (ej: 'dev-agent-1'). Requerido para escrituras.",
65
73
  },
74
+ sessionId: {
75
+ type: "string",
76
+ description: "Identificador de sesión (opcional, para registro)",
77
+ },
66
78
  status: {
67
79
  type: "string",
68
80
  description: "Estado del agente (para update_status).",
@@ -2,10 +2,12 @@
2
2
  * @fileoverview Search memory tool for Memory Bank
3
3
  * Searches code semantically using vector similarity
4
4
  */
5
+ import { AgentBoard } from "../common/agentBoard.js";
6
+ import { sessionLogger } from "../common/sessionLogger.js";
5
7
  /**
6
8
  * Searches the Memory Bank for relevant code
7
9
  */
8
- export async function searchMemory(params, indexManager) {
10
+ export async function searchMemory(params, indexManager, workspaceRoot) {
9
11
  try {
10
12
  if (!params.query || params.query.trim() === "") {
11
13
  return {
@@ -34,6 +36,31 @@ export async function searchMemory(params, indexManager) {
34
36
  filterByFile: params.filterByFile,
35
37
  filterByLanguage: params.filterByLanguage,
36
38
  });
39
+ // Session Logging
40
+ if (params.agentId && workspaceRoot) {
41
+ try {
42
+ const board = new AgentBoard(workspaceRoot, params.projectId);
43
+ const sessionId = await board.getSessionId(params.agentId);
44
+ if (sessionId) {
45
+ await sessionLogger.logSessionEvent(params.projectId, sessionId, {
46
+ timestamp: new Date().toISOString(),
47
+ type: 'search',
48
+ data: {
49
+ query: params.query,
50
+ filters: {
51
+ file: params.filterByFile,
52
+ lang: params.filterByLanguage
53
+ },
54
+ resultCount: results.length,
55
+ topResults: results.slice(0, 5).map(r => ({ path: r.filePath, score: r.score }))
56
+ }
57
+ });
58
+ }
59
+ }
60
+ catch (logError) {
61
+ console.error(`Failed to log search session: ${logError}`);
62
+ }
63
+ }
37
64
  console.error(`Found ${results.length} result(s)`);
38
65
  // Format message
39
66
  let message = `Found ${results.length} result(s)`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grec0/memory-bank-mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "MCP server for semantic code indexing with Memory Bank - AI-powered codebase understanding",
5
5
  "license": "MIT",
6
6
  "author": "@grec0",