@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/.memoryignore.example +76 -0
- package/README.md +425 -0
- package/dist/common/chunker.js +407 -0
- package/dist/common/embeddingService.js +302 -0
- package/dist/common/errors.js +71 -0
- package/dist/common/fileScanner.js +261 -0
- package/dist/common/indexManager.js +332 -0
- package/dist/common/setup.js +49 -0
- package/dist/common/types.js +115 -0
- package/dist/common/utils.js +215 -0
- package/dist/common/vectorStore.js +332 -0
- package/dist/common/version.js +2 -0
- package/dist/index.js +274 -0
- package/dist/operations/boardMemberships.js +186 -0
- package/dist/operations/boards.js +268 -0
- package/dist/operations/cards.js +426 -0
- package/dist/operations/comments.js +249 -0
- package/dist/operations/labels.js +258 -0
- package/dist/operations/lists.js +157 -0
- package/dist/operations/projects.js +102 -0
- package/dist/operations/tasks.js +238 -0
- package/dist/tools/analyzeCoverage.js +316 -0
- package/dist/tools/board-summary.js +151 -0
- package/dist/tools/card-details.js +106 -0
- package/dist/tools/create-card-with-tasks.js +81 -0
- package/dist/tools/getStats.js +59 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/indexCode.js +53 -0
- package/dist/tools/readFile.js +69 -0
- package/dist/tools/searchMemory.js +60 -0
- package/dist/tools/workflow-actions.js +145 -0
- package/dist/tools/writeFile.js +66 -0
- package/package.json +58 -0
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
|
+
}
|