@grec0/memory-bank-mcp 0.0.2

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/dist/index.js ADDED
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview Memory Bank MCP Server
4
+ * Semantic code indexing and retrieval using vector embeddings
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { z } from "zod";
9
+ // Import Memory Bank services
10
+ import { createEmbeddingService } from "./common/embeddingService.js";
11
+ import { createVectorStore } from "./common/vectorStore.js";
12
+ import { createIndexManager } from "./common/indexManager.js";
13
+ // Import tools
14
+ import { indexCode } from "./tools/indexCode.js";
15
+ import { searchMemory } from "./tools/searchMemory.js";
16
+ import { readFile } from "./tools/readFile.js";
17
+ import { writeFile } from "./tools/writeFile.js";
18
+ import { getStats } from "./tools/getStats.js";
19
+ import { analyzeCoverage } from "./tools/analyzeCoverage.js";
20
+ import { VERSION } from "./common/version.js";
21
+ // Global services
22
+ let embeddingService;
23
+ let vectorStore;
24
+ let indexManager;
25
+ let workspaceRoot;
26
+ // Create the MCP Server
27
+ const server = new McpServer({
28
+ name: "memory-bank-mcp-server",
29
+ version: VERSION,
30
+ });
31
+ // Tool: Index Code
32
+ server.tool("memorybank_index_code", "Indexa semánticamente código de un directorio o archivo específico para permitir búsquedas semánticas", {
33
+ path: z
34
+ .string()
35
+ .optional()
36
+ .describe("Ruta relativa o absoluta del directorio/archivo a indexar (por defecto: raíz del workspace)"),
37
+ recursive: z
38
+ .boolean()
39
+ .optional()
40
+ .default(true)
41
+ .describe("Indexar recursivamente subdirectorios"),
42
+ forceReindex: z
43
+ .boolean()
44
+ .optional()
45
+ .default(false)
46
+ .describe("Forzar reindexación completa aunque no haya cambios"),
47
+ }, async (args) => {
48
+ const result = await indexCode({
49
+ path: args.path,
50
+ recursive: args.recursive,
51
+ forceReindex: args.forceReindex,
52
+ }, indexManager, workspaceRoot);
53
+ return {
54
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
55
+ };
56
+ });
57
+ // Tool: Search Memory Bank
58
+ server.tool("memorybank_search", "Busca código relevante mediante búsqueda semántica vectorial. Usa esta herramienta SIEMPRE que necesites información sobre el código", {
59
+ query: z
60
+ .string()
61
+ .describe("Consulta semántica: describe qué estás buscando en lenguaje natural (ej: 'función de autenticación', '¿cómo se validan los emails?')"),
62
+ topK: z
63
+ .number()
64
+ .optional()
65
+ .default(10)
66
+ .describe("Número máximo de resultados a retornar"),
67
+ minScore: z
68
+ .number()
69
+ .optional()
70
+ .default(0.7)
71
+ .describe("Puntuación mínima de similitud (0-1). Valores más altos = resultados más relevantes"),
72
+ filterByFile: z
73
+ .string()
74
+ .optional()
75
+ .describe("Filtrar resultados por patrón de ruta de archivo (ej: 'auth/', 'utils.ts')"),
76
+ filterByLanguage: z
77
+ .string()
78
+ .optional()
79
+ .describe("Filtrar resultados por lenguaje de programación (ej: 'typescript', 'python')"),
80
+ }, async (args) => {
81
+ const result = await searchMemory({
82
+ query: args.query,
83
+ topK: args.topK,
84
+ minScore: args.minScore,
85
+ filterByFile: args.filterByFile,
86
+ filterByLanguage: args.filterByLanguage,
87
+ }, indexManager);
88
+ return {
89
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
90
+ };
91
+ });
92
+ // Tool: Read File
93
+ server.tool("memorybank_read_file", "Lee el contenido de un archivo específico del workspace. Usa esta herramienta para obtener contexto adicional cuando los fragmentos de búsqueda no son suficientes", {
94
+ path: z
95
+ .string()
96
+ .describe("Ruta relativa o absoluta del archivo a leer"),
97
+ startLine: z
98
+ .number()
99
+ .optional()
100
+ .describe("Línea inicial para leer un rango específico (opcional)"),
101
+ endLine: z
102
+ .number()
103
+ .optional()
104
+ .describe("Línea final para leer un rango específico (opcional)"),
105
+ }, async (args) => {
106
+ const result = await readFile({
107
+ path: args.path,
108
+ startLine: args.startLine,
109
+ endLine: args.endLine,
110
+ }, workspaceRoot);
111
+ return {
112
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
113
+ };
114
+ });
115
+ // Tool: Write File
116
+ server.tool("memorybank_write_file", "Escribe o modifica un archivo y automáticamente lo reindexa en el Memory Bank para mantener la consistencia", {
117
+ path: z
118
+ .string()
119
+ .describe("Ruta relativa o absoluta del archivo a escribir"),
120
+ content: z
121
+ .string()
122
+ .describe("Contenido completo del archivo a escribir"),
123
+ autoReindex: z
124
+ .boolean()
125
+ .optional()
126
+ .default(true)
127
+ .describe("Reindexar automáticamente el archivo después de escribirlo"),
128
+ }, async (args) => {
129
+ const result = await writeFile({
130
+ path: args.path,
131
+ content: args.content,
132
+ autoReindex: args.autoReindex,
133
+ }, indexManager, workspaceRoot);
134
+ return {
135
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
136
+ };
137
+ });
138
+ // Tool: Get Statistics
139
+ server.tool("memorybank_get_stats", "Obtiene estadísticas del Memory Bank: archivos indexados, chunks totales, última indexación, etc. Usa esta herramienta al inicio de cada sesión", {}, async () => {
140
+ const result = await getStats(indexManager, embeddingService);
141
+ return {
142
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
143
+ };
144
+ });
145
+ // Tool: Analyze Coverage
146
+ server.tool("memorybank_analyze_coverage", "Analiza la cobertura de indexación del proyecto. Muestra qué carpetas/archivos están indexados, cuáles no, y cuáles tienen cambios pendientes. Perfecto para visualizar el estado del conocimiento del agente sobre el proyecto. NOTA: Puede tardar en workspaces grandes", {}, async () => {
147
+ try {
148
+ const result = await analyzeCoverage(indexManager, vectorStore, workspaceRoot);
149
+ return {
150
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
151
+ };
152
+ }
153
+ catch (error) {
154
+ console.error(`Error in analyze_coverage: ${error}`);
155
+ return {
156
+ content: [{
157
+ type: "text",
158
+ text: JSON.stringify({
159
+ success: false,
160
+ message: `Error al analizar cobertura: ${error}`,
161
+ stats: {
162
+ totalFiles: 0,
163
+ indexedFiles: 0,
164
+ notIndexedFiles: 0,
165
+ pendingReindexFiles: 0,
166
+ ignoredFiles: 0,
167
+ totalSize: 0,
168
+ indexedSize: 0,
169
+ coveragePercentage: 0,
170
+ totalChunks: 0,
171
+ languageBreakdown: {},
172
+ directoryBreakdown: {},
173
+ },
174
+ tree: {
175
+ name: "root",
176
+ path: "",
177
+ type: "directory",
178
+ status: "not_indexed",
179
+ children: [],
180
+ },
181
+ recommendations: ["Error al escanear workspace. Verifica la ruta y permisos."],
182
+ }, null, 2)
183
+ }],
184
+ };
185
+ }
186
+ });
187
+ /**
188
+ * Validates and initializes environment
189
+ */
190
+ async function validateEnvironment() {
191
+ console.error("=== Memory Bank MCP Server ===");
192
+ console.error("Version:", VERSION);
193
+ console.error("");
194
+ // Validate OpenAI API Key
195
+ if (!process.env.OPENAI_API_KEY) {
196
+ console.error("ERROR: OPENAI_API_KEY environment variable is required");
197
+ console.error("Get your API key from: https://platform.openai.com/api-keys");
198
+ throw new Error("Missing OPENAI_API_KEY environment variable");
199
+ }
200
+ console.error("✓ OpenAI API key configured");
201
+ // Get workspace root
202
+ workspaceRoot = process.env.MEMORYBANK_WORKSPACE_ROOT || process.cwd();
203
+ console.error(`✓ Workspace root: ${workspaceRoot}`);
204
+ // Storage path
205
+ const storagePath = process.env.MEMORYBANK_STORAGE_PATH || ".memorybank";
206
+ console.error(`✓ Storage path: ${storagePath}`);
207
+ // Embedding model configuration
208
+ const embeddingModel = process.env.MEMORYBANK_EMBEDDING_MODEL || "text-embedding-3-small";
209
+ const embeddingDimensions = process.env.MEMORYBANK_EMBEDDING_DIMENSIONS || "1536";
210
+ console.error(`✓ Embedding model: ${embeddingModel} (${embeddingDimensions} dimensions)`);
211
+ // Initialize services
212
+ console.error("\nInitializing services...");
213
+ try {
214
+ embeddingService = createEmbeddingService();
215
+ console.error("✓ Embedding service initialized");
216
+ vectorStore = createVectorStore();
217
+ await vectorStore.initialize();
218
+ console.error("✓ Vector store initialized");
219
+ indexManager = createIndexManager(embeddingService, vectorStore);
220
+ console.error("✓ Index manager initialized");
221
+ }
222
+ catch (error) {
223
+ console.error(`ERROR: Failed to initialize services: ${error}`);
224
+ throw error;
225
+ }
226
+ console.error("\n✓ All services ready");
227
+ console.error("");
228
+ }
229
+ /**
230
+ * Starts the stdio server
231
+ */
232
+ async function startStdioServer() {
233
+ try {
234
+ console.error("Starting Memory Bank MCP Server in stdio mode...\n");
235
+ // Validate environment and initialize services
236
+ await validateEnvironment();
237
+ // Create transport
238
+ const transport = new StdioServerTransport();
239
+ console.error("Connecting server to transport...");
240
+ // Connect server to transport
241
+ await server.connect(transport);
242
+ console.error("\n=== MCP Server Ready ===");
243
+ console.error("Available tools:");
244
+ console.error(" - memorybank_index_code: Indexar código semánticamente");
245
+ console.error(" - memorybank_search: Buscar código por similitud semántica");
246
+ console.error(" - memorybank_read_file: Leer archivos del workspace");
247
+ console.error(" - memorybank_write_file: Escribir archivos y reindexar");
248
+ console.error(" - memorybank_get_stats: Obtener estadísticas del índice");
249
+ console.error(" - memorybank_analyze_coverage: Analizar cobertura de indexación");
250
+ console.error("");
251
+ console.error("Ready to accept requests...\n");
252
+ }
253
+ catch (error) {
254
+ console.error("Error starting stdio server:", error);
255
+ console.error("Stack trace:", error.stack);
256
+ process.exit(1);
257
+ }
258
+ }
259
+ /**
260
+ * Main entry point
261
+ */
262
+ async function main() {
263
+ try {
264
+ // For now, only stdio mode is supported
265
+ await startStdioServer();
266
+ }
267
+ catch (error) {
268
+ console.error("Fatal error:", error);
269
+ console.error("Stack trace:", error.stack);
270
+ process.exit(1);
271
+ }
272
+ }
273
+ // Start the server
274
+ main();
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @fileoverview Board membership operations for the MCP Kanban server
3
+ *
4
+ * This module provides functions for managing board memberships in the Planka Kanban system,
5
+ * including creating, retrieving, updating, and deleting board memberships, which control
6
+ * user access and permissions to boards.
7
+ */
8
+ import { z } from "zod";
9
+ import { plankaRequest } from "../common/utils.js";
10
+ // Schema definitions
11
+ /**
12
+ * Schema for creating a new board membership
13
+ * @property {string} boardId - The ID of the board to add the user to
14
+ * @property {string} userId - The ID of the user to add to the board
15
+ * @property {string} role - The role of the user on the board (editor or viewer)
16
+ */
17
+ export const CreateBoardMembershipSchema = z.object({
18
+ boardId: z.string().describe("Board ID"),
19
+ userId: z.string().describe("User ID"),
20
+ role: z.enum(["editor", "viewer"]).describe("Membership role (editor or viewer)"),
21
+ });
22
+ /**
23
+ * Schema for retrieving board memberships
24
+ * @property {string} boardId - The ID of the board to get memberships for
25
+ */
26
+ export const GetBoardMembershipsSchema = z.object({
27
+ boardId: z.string().describe("Board ID"),
28
+ });
29
+ /**
30
+ * Schema for retrieving a specific board membership
31
+ * @property {string} id - The ID of the board membership to retrieve
32
+ */
33
+ export const GetBoardMembershipSchema = z.object({
34
+ id: z.string().describe("Board Membership ID"),
35
+ });
36
+ /**
37
+ * Schema for updating a board membership
38
+ * @property {string} id - The ID of the board membership to update
39
+ * @property {string} role - The new role for the user (editor or viewer)
40
+ * @property {boolean} [canComment] - Whether the user can comment on cards
41
+ */
42
+ export const UpdateBoardMembershipSchema = z.object({
43
+ id: z.string().describe("Board Membership ID"),
44
+ role: z.enum(["editor", "viewer"]).describe("Membership role (editor or viewer)"),
45
+ canComment: z.boolean().optional().describe("Whether the user can comment on cards"),
46
+ });
47
+ /**
48
+ * Schema for deleting a board membership
49
+ * @property {string} id - The ID of the board membership to delete
50
+ */
51
+ export const DeleteBoardMembershipSchema = z.object({
52
+ id: z.string().describe("Board Membership ID"),
53
+ });
54
+ // Board membership schema
55
+ const BoardMembershipSchema = z.object({
56
+ id: z.string(),
57
+ boardId: z.string(),
58
+ userId: z.string(),
59
+ role: z.string(),
60
+ canComment: z.boolean().nullable(),
61
+ createdAt: z.string(),
62
+ updatedAt: z.string().nullable(),
63
+ });
64
+ // Response schemas
65
+ const BoardMembershipsResponseSchema = z.object({
66
+ items: z.array(BoardMembershipSchema),
67
+ included: z.record(z.any()).optional(),
68
+ });
69
+ const BoardMembershipResponseSchema = z.object({
70
+ item: BoardMembershipSchema,
71
+ included: z.record(z.any()).optional(),
72
+ });
73
+ // Function implementations
74
+ /**
75
+ * Creates a new board membership (adds a user to a board with specified permissions)
76
+ *
77
+ * @param {CreateBoardMembershipOptions} options - Options for creating the board membership
78
+ * @param {string} options.boardId - The ID of the board to add the user to
79
+ * @param {string} options.userId - The ID of the user to add to the board
80
+ * @param {string} options.role - The role of the user on the board (editor or viewer)
81
+ * @returns {Promise<object>} The created board membership
82
+ * @throws {Error} If the board membership creation fails
83
+ */
84
+ export async function createBoardMembership(options) {
85
+ try {
86
+ const response = await plankaRequest(`/api/boards/${options.boardId}/memberships`, {
87
+ method: "POST",
88
+ body: {
89
+ userId: options.userId,
90
+ role: options.role,
91
+ },
92
+ });
93
+ const parsedResponse = BoardMembershipResponseSchema.parse(response);
94
+ return parsedResponse.item;
95
+ }
96
+ catch (error) {
97
+ throw new Error(`Failed to create board membership: ${error instanceof Error ? error.message : String(error)}`);
98
+ }
99
+ }
100
+ /**
101
+ * Retrieves all memberships for a specific board
102
+ *
103
+ * @param {string} boardId - The ID of the board to get memberships for
104
+ * @returns {Promise<Array<object>>} Array of board memberships
105
+ * @throws {Error} If retrieving board memberships fails
106
+ */
107
+ export async function getBoardMemberships(boardId) {
108
+ try {
109
+ const response = await plankaRequest(`/api/boards/${boardId}/memberships`);
110
+ try {
111
+ // Try to parse as a BoardMembershipsResponseSchema first
112
+ const parsedResponse = BoardMembershipsResponseSchema.parse(response);
113
+ return parsedResponse.items;
114
+ }
115
+ catch (parseError) {
116
+ // If that fails, try to parse as an array directly
117
+ if (Array.isArray(response)) {
118
+ return z.array(BoardMembershipSchema).parse(response);
119
+ }
120
+ // If we get here, we couldn't parse the response in any expected format
121
+ throw new Error(`Could not parse board memberships response: ${JSON.stringify(response)}`);
122
+ }
123
+ }
124
+ catch (error) {
125
+ // If all else fails, return an empty array
126
+ return [];
127
+ }
128
+ }
129
+ /**
130
+ * Retrieves a specific board membership by ID
131
+ *
132
+ * @param {string} id - The ID of the board membership to retrieve
133
+ * @returns {Promise<object>} The requested board membership
134
+ * @throws {Error} If retrieving the board membership fails
135
+ */
136
+ export async function getBoardMembership(id) {
137
+ try {
138
+ const response = await plankaRequest(`/api/board-memberships/${id}`);
139
+ const parsedResponse = BoardMembershipResponseSchema.parse(response);
140
+ return parsedResponse.item;
141
+ }
142
+ catch (error) {
143
+ throw new Error(`Failed to get board membership: ${error instanceof Error ? error.message : String(error)}`);
144
+ }
145
+ }
146
+ /**
147
+ * Updates a board membership's properties (user permissions on a board)
148
+ *
149
+ * @param {string} id - The ID of the board membership to update
150
+ * @param {Partial<Omit<CreateBoardMembershipOptions, "boardId" | "userId">>} options - The properties to update
151
+ * @param {string} [options.role] - The new role for the user (editor or viewer)
152
+ * @param {boolean} [options.canComment] - Whether the user can comment on cards
153
+ * @returns {Promise<object>} The updated board membership
154
+ * @throws {Error} If updating the board membership fails
155
+ */
156
+ export async function updateBoardMembership(id, options) {
157
+ try {
158
+ const response = await plankaRequest(`/api/board-memberships/${id}`, {
159
+ method: "PATCH",
160
+ body: options,
161
+ });
162
+ const parsedResponse = BoardMembershipResponseSchema.parse(response);
163
+ return parsedResponse.item;
164
+ }
165
+ catch (error) {
166
+ throw new Error(`Failed to update board membership: ${error instanceof Error ? error.message : String(error)}`);
167
+ }
168
+ }
169
+ /**
170
+ * Deletes a board membership by ID (removes a user from a board)
171
+ *
172
+ * @param {string} id - The ID of the board membership to delete
173
+ * @returns {Promise<{success: boolean}>} Success indicator
174
+ * @throws {Error} If deleting the board membership fails
175
+ */
176
+ export async function deleteBoardMembership(id) {
177
+ try {
178
+ await plankaRequest(`/api/board-memberships/${id}`, {
179
+ method: "DELETE",
180
+ });
181
+ return { success: true };
182
+ }
183
+ catch (error) {
184
+ throw new Error(`Failed to delete board membership: ${error instanceof Error ? error.message : String(error)}`);
185
+ }
186
+ }