@qwickapps/qwickbrain-proxy 1.0.1 → 1.0.3

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.
Files changed (67) hide show
  1. package/.claude/engineering/bugs/BUG-qwickbrain-proxy-cache-and-design.md +840 -0
  2. package/.github/workflows/publish.yml +13 -0
  3. package/CHANGELOG.md +54 -0
  4. package/dist/db/schema.d.ts +63 -6
  5. package/dist/db/schema.d.ts.map +1 -1
  6. package/dist/db/schema.js +17 -2
  7. package/dist/db/schema.js.map +1 -1
  8. package/dist/lib/__tests__/cache-manager.test.js +146 -83
  9. package/dist/lib/__tests__/cache-manager.test.js.map +1 -1
  10. package/dist/lib/__tests__/proxy-server.test.js +16 -44
  11. package/dist/lib/__tests__/proxy-server.test.js.map +1 -1
  12. package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts +2 -0
  13. package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts.map +1 -0
  14. package/dist/lib/__tests__/sse-invalidation-listener.test.js +245 -0
  15. package/dist/lib/__tests__/sse-invalidation-listener.test.js.map +1 -0
  16. package/dist/lib/__tests__/write-queue-manager.test.d.ts +2 -0
  17. package/dist/lib/__tests__/write-queue-manager.test.d.ts.map +1 -0
  18. package/dist/lib/__tests__/write-queue-manager.test.js +291 -0
  19. package/dist/lib/__tests__/write-queue-manager.test.js.map +1 -0
  20. package/dist/lib/cache-manager.d.ts +35 -6
  21. package/dist/lib/cache-manager.d.ts.map +1 -1
  22. package/dist/lib/cache-manager.js +154 -41
  23. package/dist/lib/cache-manager.js.map +1 -1
  24. package/dist/lib/connection-manager.d.ts.map +1 -1
  25. package/dist/lib/connection-manager.js +4 -1
  26. package/dist/lib/connection-manager.js.map +1 -1
  27. package/dist/lib/proxy-server.d.ts +6 -0
  28. package/dist/lib/proxy-server.d.ts.map +1 -1
  29. package/dist/lib/proxy-server.js +182 -87
  30. package/dist/lib/proxy-server.js.map +1 -1
  31. package/dist/lib/qwickbrain-client.d.ts +4 -0
  32. package/dist/lib/qwickbrain-client.d.ts.map +1 -1
  33. package/dist/lib/qwickbrain-client.js +133 -0
  34. package/dist/lib/qwickbrain-client.js.map +1 -1
  35. package/dist/lib/sse-invalidation-listener.d.ts +27 -0
  36. package/dist/lib/sse-invalidation-listener.d.ts.map +1 -0
  37. package/dist/lib/sse-invalidation-listener.js +145 -0
  38. package/dist/lib/sse-invalidation-listener.js.map +1 -0
  39. package/dist/lib/tools.d.ts +21 -0
  40. package/dist/lib/tools.d.ts.map +1 -0
  41. package/dist/lib/tools.js +488 -0
  42. package/dist/lib/tools.js.map +1 -0
  43. package/dist/lib/write-queue-manager.d.ts +88 -0
  44. package/dist/lib/write-queue-manager.d.ts.map +1 -0
  45. package/dist/lib/write-queue-manager.js +191 -0
  46. package/dist/lib/write-queue-manager.js.map +1 -0
  47. package/dist/types/config.d.ts +7 -42
  48. package/dist/types/config.d.ts.map +1 -1
  49. package/dist/types/config.js +1 -6
  50. package/dist/types/config.js.map +1 -1
  51. package/drizzle/0002_lru_cache_migration.sql +94 -0
  52. package/drizzle/meta/_journal.json +7 -0
  53. package/package.json +6 -2
  54. package/scripts/rebuild-sqlite.sh +26 -0
  55. package/src/db/schema.ts +17 -2
  56. package/src/lib/__tests__/cache-manager.test.ts +180 -90
  57. package/src/lib/__tests__/proxy-server.test.ts +16 -51
  58. package/src/lib/__tests__/sse-invalidation-listener.test.ts +326 -0
  59. package/src/lib/__tests__/write-queue-manager.test.ts +383 -0
  60. package/src/lib/cache-manager.ts +198 -46
  61. package/src/lib/connection-manager.ts +4 -1
  62. package/src/lib/proxy-server.ts +222 -90
  63. package/src/lib/qwickbrain-client.ts +145 -1
  64. package/src/lib/sse-invalidation-listener.ts +171 -0
  65. package/src/lib/tools.ts +500 -0
  66. package/src/lib/write-queue-manager.ts +271 -0
  67. package/src/types/config.ts +1 -6
@@ -7,6 +7,9 @@ import {
7
7
  import { ConnectionManager } from './connection-manager.js';
8
8
  import { CacheManager } from './cache-manager.js';
9
9
  import { QwickBrainClient } from './qwickbrain-client.js';
10
+ import { WriteQueueManager } from './write-queue-manager.js';
11
+ import { SSEInvalidationListener } from './sse-invalidation-listener.js';
12
+ import { QWICKBRAIN_TOOLS, requiresConnection } from './tools.js';
10
13
  import type { Config } from '../types/config.js';
11
14
  import type { DB } from '../db/client.js';
12
15
  import type { MCPResponse, MCPResponseMetadata } from '../types/mcp.js';
@@ -17,6 +20,8 @@ export class ProxyServer {
17
20
  private connectionManager: ConnectionManager;
18
21
  private cacheManager: CacheManager;
19
22
  private qwickbrainClient: QwickBrainClient;
23
+ private writeQueueManager: WriteQueueManager;
24
+ private sseInvalidationListener: SSEInvalidationListener | null = null;
20
25
  private config: Config;
21
26
 
22
27
  constructor(db: DB, config: Config) {
@@ -41,6 +46,16 @@ export class ProxyServer {
41
46
  );
42
47
 
43
48
  this.cacheManager = new CacheManager(db, config.cache);
49
+ this.writeQueueManager = new WriteQueueManager(db, this.qwickbrainClient);
50
+
51
+ // Initialize SSE invalidation listener if in SSE mode
52
+ if (config.qwickbrain.mode === 'sse' && config.qwickbrain.url) {
53
+ this.sseInvalidationListener = new SSEInvalidationListener(
54
+ config.qwickbrain.url,
55
+ this.cacheManager,
56
+ config.qwickbrain.apiKey
57
+ );
58
+ }
44
59
 
45
60
  this.setupHandlers();
46
61
  this.setupConnectionListeners();
@@ -57,6 +72,14 @@ export class ProxyServer {
57
72
 
58
73
  this.connectionManager.on('connected', ({ latencyMs }) => {
59
74
  console.error(`Connected to QwickBrain (latency: ${latencyMs}ms)`);
75
+ // Event-driven: trigger background sync when connection restored
76
+ this.onConnectionRestored().catch(err => {
77
+ console.error('Background sync error:', err);
78
+ });
79
+ // Sync pending write operations
80
+ this.syncWriteQueue().catch(err => {
81
+ console.error('Write queue sync error:', err);
82
+ });
60
83
  });
61
84
 
62
85
  this.connectionManager.on('disconnected', ({ error }) => {
@@ -64,61 +87,43 @@ export class ProxyServer {
64
87
  });
65
88
  }
66
89
 
67
- private setupHandlers(): void {
68
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
69
- // Try to fetch tools from upstream if connected
70
- if (this.connectionManager.getState() === 'connected') {
71
- try {
72
- const tools = await this.connectionManager.execute(async () => {
73
- return await this.qwickbrainClient.listTools();
74
- });
75
- return { tools };
76
- } catch (error) {
77
- console.error('Failed to list tools from upstream:', error);
90
+ private async onConnectionRestored(): Promise<void> {
91
+ console.error('Starting background cache sync...');
92
+
93
+ // Preload critical documents in background
94
+ const preloadItems = this.config.cache.preload || [];
95
+ for (const itemType of preloadItems) {
96
+ try {
97
+ if (itemType === 'workflows') {
98
+ // TODO: List and cache all workflows
99
+ console.error('Preloading workflows...');
100
+ } else if (itemType === 'rules') {
101
+ // TODO: List and cache all rules
102
+ console.error('Preloading rules...');
78
103
  }
104
+ } catch (error) {
105
+ console.error(`Failed to preload ${itemType}:`, error);
79
106
  }
107
+ }
80
108
 
81
- // Fallback to minimal tool set when offline or error
82
- return {
83
- tools: [
84
- {
85
- name: 'get_workflow',
86
- description: 'Get a workflow definition by name (cached)',
87
- inputSchema: {
88
- type: 'object',
89
- properties: {
90
- name: { type: 'string', description: 'Workflow name' },
91
- },
92
- required: ['name'],
93
- },
94
- },
95
- {
96
- name: 'get_document',
97
- description: 'Get a document by name and type (cached)',
98
- inputSchema: {
99
- type: 'object',
100
- properties: {
101
- name: { type: 'string', description: 'Document name' },
102
- doc_type: { type: 'string', description: 'Document type (rule, frd, design, etc.)' },
103
- project: { type: 'string', description: 'Project name (optional)' },
104
- },
105
- required: ['name', 'doc_type'],
106
- },
107
- },
108
- {
109
- name: 'get_memory',
110
- description: 'Get a memory/context document by name (cached)',
111
- inputSchema: {
112
- type: 'object',
113
- properties: {
114
- name: { type: 'string', description: 'Memory name' },
115
- project: { type: 'string', description: 'Project name (optional)' },
116
- },
117
- required: ['name'],
118
- },
119
- },
120
- ],
121
- };
109
+ console.error('Background cache sync complete');
110
+ }
111
+
112
+ private async syncWriteQueue(): Promise<void> {
113
+ const pendingCount = await this.writeQueueManager.getPendingCount();
114
+ if (pendingCount === 0) {
115
+ return;
116
+ }
117
+
118
+ console.error(`Syncing ${pendingCount} pending write operations...`);
119
+ const { synced, failed } = await this.writeQueueManager.syncPendingOperations();
120
+ console.error(`Write queue sync complete: ${synced} synced, ${failed} failed`);
121
+ }
122
+
123
+ private setupHandlers(): void {
124
+ // Static tool listing - always returns all tools regardless of connection state
125
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
126
+ return { tools: QWICKBRAIN_TOOLS };
122
127
  });
123
128
 
124
129
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -145,8 +150,54 @@ export class ProxyServer {
145
150
  args?.project as string | undefined
146
151
  );
147
152
  break;
153
+ case 'create_document':
154
+ case 'update_document':
155
+ result = await this.handleCreateDocument(
156
+ args?.doc_type as string,
157
+ args?.name as string,
158
+ args?.content as string,
159
+ args?.project as string | undefined,
160
+ args?.metadata as Record<string, unknown> | undefined
161
+ );
162
+ break;
163
+ case 'set_memory':
164
+ case 'update_memory':
165
+ result = await this.handleSetMemory(
166
+ args?.name as string,
167
+ args?.content as string,
168
+ args?.project as string | undefined,
169
+ args?.metadata as Record<string, unknown> | undefined
170
+ );
171
+ break;
148
172
  default:
149
173
  // Generic forwarding for all other tools (analyze_repository, search_codebase, etc.)
174
+ // Check if tool requires connection
175
+ if (requiresConnection(name) && this.connectionManager.getState() !== 'connected') {
176
+ // Return offline error for non-cacheable tools
177
+ return {
178
+ content: [
179
+ {
180
+ type: 'text',
181
+ text: JSON.stringify({
182
+ error: {
183
+ code: 'OFFLINE',
184
+ message: `QwickBrain offline - "${name}" requires active connection`,
185
+ suggestions: [
186
+ 'Check internet connection',
187
+ 'Wait for automatic reconnection',
188
+ 'Cached tools (get_workflow, get_document, get_memory) work offline',
189
+ ],
190
+ },
191
+ _metadata: {
192
+ source: 'cache',
193
+ status: this.connectionManager.getState(),
194
+ },
195
+ }, null, 2),
196
+ },
197
+ ],
198
+ isError: true,
199
+ };
200
+ }
150
201
  result = await this.handleGenericTool(name, args || {});
151
202
  break;
152
203
  }
@@ -200,17 +251,17 @@ export class ProxyServer {
200
251
  name: string,
201
252
  project?: string
202
253
  ): Promise<MCPResponse> {
203
- // Try cache first
254
+ // Try cache first (LRU cache never expires, always valid if present)
204
255
  const cached = await this.cacheManager.getDocument(docType, name, project);
205
256
 
206
- if (cached && !cached.isExpired) {
257
+ if (cached) {
207
258
  return {
208
259
  data: cached.data,
209
260
  _metadata: this.createMetadata('cache', cached.age),
210
261
  };
211
262
  }
212
263
 
213
- // Try remote if connected
264
+ // Not cached - try remote if connected
214
265
  if (this.connectionManager.getState() === 'connected') {
215
266
  try {
216
267
  const result = await this.connectionManager.execute(async () => {
@@ -232,22 +283,11 @@ export class ProxyServer {
232
283
  };
233
284
  } catch (error) {
234
285
  console.error('Failed to fetch from QwickBrain:', error);
235
- // Fall through to stale cache
286
+ // Fall through to error
236
287
  }
237
288
  }
238
289
 
239
- // Try stale cache
240
- if (cached) {
241
- return {
242
- data: cached.data,
243
- _metadata: {
244
- ...this.createMetadata('stale_cache', cached.age),
245
- warning: `QwickBrain unavailable - serving cached data (${cached.age}s old)`,
246
- },
247
- };
248
- }
249
-
250
- // No cache, no connection
290
+ // No cache and remote failed/unavailable
251
291
  return {
252
292
  error: {
253
293
  code: 'UNAVAILABLE',
@@ -263,16 +303,17 @@ export class ProxyServer {
263
303
  }
264
304
 
265
305
  private async handleGetMemory(name: string, project?: string): Promise<MCPResponse> {
266
- // Similar logic to handleGetDocument but for memories
306
+ // Try cache first (LRU cache never expires, always valid if present)
267
307
  const cached = await this.cacheManager.getMemory(name, project);
268
308
 
269
- if (cached && !cached.isExpired) {
309
+ if (cached) {
270
310
  return {
271
311
  data: cached.data,
272
312
  _metadata: this.createMetadata('cache', cached.age),
273
313
  };
274
314
  }
275
315
 
316
+ // Not cached - try remote if connected
276
317
  if (this.connectionManager.getState() === 'connected') {
277
318
  try {
278
319
  const result = await this.connectionManager.execute(async () => {
@@ -287,19 +328,11 @@ export class ProxyServer {
287
328
  };
288
329
  } catch (error) {
289
330
  console.error('Failed to fetch memory from QwickBrain:', error);
331
+ // Fall through to error
290
332
  }
291
333
  }
292
334
 
293
- if (cached) {
294
- return {
295
- data: cached.data,
296
- _metadata: {
297
- ...this.createMetadata('stale_cache', cached.age),
298
- warning: `QwickBrain unavailable - serving cached memory (${cached.age}s old)`,
299
- },
300
- };
301
- }
302
-
335
+ // No cache and remote failed/unavailable
303
336
  return {
304
337
  error: {
305
338
  code: 'UNAVAILABLE',
@@ -310,6 +343,94 @@ export class ProxyServer {
310
343
  };
311
344
  }
312
345
 
346
+ private async handleCreateDocument(
347
+ docType: string,
348
+ name: string,
349
+ content: string,
350
+ project?: string,
351
+ metadata?: Record<string, unknown>
352
+ ): Promise<MCPResponse> {
353
+ // Always update local cache first
354
+ await this.cacheManager.setDocument(docType, name, content, project, metadata);
355
+
356
+ // If connected, sync immediately
357
+ if (this.connectionManager.getState() === 'connected') {
358
+ try {
359
+ await this.connectionManager.execute(async () => {
360
+ await this.qwickbrainClient.createDocument(docType, name, content, project, metadata);
361
+ });
362
+
363
+ return {
364
+ data: { success: true },
365
+ _metadata: this.createMetadata('live'),
366
+ };
367
+ } catch (error) {
368
+ console.error('Failed to create document on QwickBrain:', error);
369
+ // Fall through to queue
370
+ }
371
+ }
372
+
373
+ // If offline or sync failed, queue for later
374
+ await this.writeQueueManager.queueOperation('create_document', {
375
+ docType,
376
+ name,
377
+ content,
378
+ project,
379
+ metadata,
380
+ });
381
+
382
+ return {
383
+ data: { success: true, queued: true },
384
+ _metadata: {
385
+ ...this.createMetadata('cache'),
386
+ warning: 'Operation queued - will sync when connection restored',
387
+ },
388
+ };
389
+ }
390
+
391
+ private async handleSetMemory(
392
+ name: string,
393
+ content: string,
394
+ project?: string,
395
+ metadata?: Record<string, unknown>
396
+ ): Promise<MCPResponse> {
397
+ // Always update local cache first
398
+ await this.cacheManager.setMemory(name, content, project, metadata);
399
+
400
+ // If connected, sync immediately
401
+ if (this.connectionManager.getState() === 'connected') {
402
+ try {
403
+ await this.connectionManager.execute(async () => {
404
+ await this.qwickbrainClient.setMemory(name, content, project, metadata);
405
+ });
406
+
407
+ return {
408
+ data: { success: true },
409
+ _metadata: this.createMetadata('live'),
410
+ };
411
+ } catch (error) {
412
+ console.error('Failed to set memory on QwickBrain:', error);
413
+ // Fall through to queue
414
+ }
415
+ }
416
+
417
+ // If offline or sync failed, queue for later
418
+ await this.writeQueueManager.queueOperation('set_memory', {
419
+ name,
420
+ content,
421
+ project,
422
+ metadata,
423
+ });
424
+
425
+ return {
426
+ data: { success: true, queued: true },
427
+ _metadata: {
428
+ ...this.createMetadata('cache'),
429
+ warning: 'Operation queued - will sync when connection restored',
430
+ },
431
+ };
432
+ }
433
+
313
434
  private async handleGenericTool(name: string, args: Record<string, unknown>): Promise<MCPResponse> {
314
435
  // Generic tool forwarding - no caching for non-document tools
315
436
  if (this.connectionManager.getState() !== 'connected') {
@@ -362,18 +483,18 @@ export class ProxyServer {
362
483
  }
363
484
 
364
485
  async start(): Promise<void> {
365
- // Clean up expired cache entries on startup
366
- const { documentsDeleted, memoriesDeleted } = await this.cacheManager.cleanupExpiredEntries();
367
- if (documentsDeleted > 0 || memoriesDeleted > 0) {
368
- console.error(`Cache cleanup: removed ${documentsDeleted} documents, ${memoriesDeleted} memories`);
369
- }
486
+ // LRU cache handles eviction automatically when storage limit reached
487
+ // No need for startup cleanup with LRU-based cache
370
488
 
371
- // Connect to QwickBrain
372
- await this.qwickbrainClient.connect();
373
-
374
- // Start connection manager
489
+ // Start connection manager (handles connection gracefully, doesn't throw)
375
490
  await this.connectionManager.start();
376
491
 
492
+ // Start SSE invalidation listener if configured
493
+ if (this.sseInvalidationListener) {
494
+ await this.sseInvalidationListener.start();
495
+ console.error('SSE cache invalidation listener started');
496
+ }
497
+
377
498
  // Start MCP server
378
499
  const transport = new StdioServerTransport();
379
500
  await this.server.connect(transport);
@@ -383,7 +504,18 @@ export class ProxyServer {
383
504
 
384
505
  async stop(): Promise<void> {
385
506
  this.connectionManager.stop();
386
- await this.qwickbrainClient.disconnect();
507
+
508
+ // Stop SSE invalidation listener
509
+ if (this.sseInvalidationListener) {
510
+ this.sseInvalidationListener.stop();
511
+ }
512
+
513
+ try {
514
+ await this.qwickbrainClient.disconnect();
515
+ } catch (error) {
516
+ // Ignore disconnect errors (client may never have connected)
517
+ console.error('Disconnect error (ignoring):', error);
518
+ }
387
519
  await this.server.close();
388
520
  }
389
521
  }
@@ -232,10 +232,14 @@ export class QwickBrainClient {
232
232
  if (this.mode === 'mcp' || this.mode === 'sse') {
233
233
  // For MCP/SSE mode, check if client is connected
234
234
  if (!this.client) {
235
+ // Initialize connection on first health check
235
236
  await this.connect();
237
+ if (!this.client) {
238
+ return false;
239
+ }
236
240
  }
237
241
  // Try listing tools as health check
238
- await this.client!.listTools();
242
+ await this.client.listTools();
239
243
  return true;
240
244
  } else {
241
245
  // For HTTP mode, ping health endpoint
@@ -246,6 +250,9 @@ export class QwickBrainClient {
246
250
  return response.ok;
247
251
  }
248
252
  } catch (error) {
253
+ // Connection dropped - clear client to force reconnection
254
+ this.client = null;
255
+ this.transport = null;
249
256
  return false;
250
257
  }
251
258
  }
@@ -306,6 +313,143 @@ export class QwickBrainClient {
306
313
  }
307
314
  }
308
315
 
316
+ async createDocument(
317
+ docType: string,
318
+ name: string,
319
+ content: string,
320
+ project?: string,
321
+ metadata?: Record<string, unknown>
322
+ ): Promise<void> {
323
+ if (this.mode === 'mcp' || this.mode === 'sse') {
324
+ if (!this.client) {
325
+ throw new Error('MCP client not connected');
326
+ }
327
+ await this.client.callTool({
328
+ name: 'create_document',
329
+ arguments: {
330
+ doc_type: docType,
331
+ name,
332
+ content,
333
+ project,
334
+ metadata,
335
+ },
336
+ });
337
+ } else {
338
+ if (!this.config.url) {
339
+ throw new Error('HTTP mode requires url to be configured');
340
+ }
341
+ const response = await fetch(`${this.config.url}/mcp/document/create`, {
342
+ method: 'POST',
343
+ headers: {
344
+ 'Content-Type': 'application/json',
345
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
346
+ },
347
+ body: JSON.stringify({ doc_type: docType, name, content, project, metadata }),
348
+ });
349
+ if (!response.ok) {
350
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
351
+ }
352
+ }
353
+ }
354
+
355
+ async setMemory(
356
+ name: string,
357
+ content: string,
358
+ project?: string,
359
+ metadata?: Record<string, unknown>
360
+ ): Promise<void> {
361
+ if (this.mode === 'mcp' || this.mode === 'sse') {
362
+ if (!this.client) {
363
+ throw new Error('MCP client not connected');
364
+ }
365
+ await this.client.callTool({
366
+ name: 'set_memory',
367
+ arguments: {
368
+ name,
369
+ content,
370
+ project,
371
+ metadata,
372
+ },
373
+ });
374
+ } else {
375
+ if (!this.config.url) {
376
+ throw new Error('HTTP mode requires url to be configured');
377
+ }
378
+ const response = await fetch(`${this.config.url}/mcp/memory/set`, {
379
+ method: 'POST',
380
+ headers: {
381
+ 'Content-Type': 'application/json',
382
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
383
+ },
384
+ body: JSON.stringify({ name, content, project, metadata }),
385
+ });
386
+ if (!response.ok) {
387
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
388
+ }
389
+ }
390
+ }
391
+
392
+ async deleteDocument(docType: string, name: string, project?: string): Promise<void> {
393
+ if (this.mode === 'mcp' || this.mode === 'sse') {
394
+ if (!this.client) {
395
+ throw new Error('MCP client not connected');
396
+ }
397
+ await this.client.callTool({
398
+ name: 'delete_document',
399
+ arguments: {
400
+ doc_type: docType,
401
+ name,
402
+ project,
403
+ },
404
+ });
405
+ } else {
406
+ if (!this.config.url) {
407
+ throw new Error('HTTP mode requires url to be configured');
408
+ }
409
+ const response = await fetch(`${this.config.url}/mcp/document/delete`, {
410
+ method: 'POST',
411
+ headers: {
412
+ 'Content-Type': 'application/json',
413
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
414
+ },
415
+ body: JSON.stringify({ doc_type: docType, name, project }),
416
+ });
417
+ if (!response.ok) {
418
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
419
+ }
420
+ }
421
+ }
422
+
423
+ async deleteMemory(name: string, project?: string): Promise<void> {
424
+ if (this.mode === 'mcp' || this.mode === 'sse') {
425
+ if (!this.client) {
426
+ throw new Error('MCP client not connected');
427
+ }
428
+ await this.client.callTool({
429
+ name: 'delete_memory',
430
+ arguments: {
431
+ name,
432
+ project,
433
+ },
434
+ });
435
+ } else {
436
+ if (!this.config.url) {
437
+ throw new Error('HTTP mode requires url to be configured');
438
+ }
439
+ const response = await fetch(`${this.config.url}/mcp/memory/delete`, {
440
+ method: 'POST',
441
+ headers: {
442
+ 'Content-Type': 'application/json',
443
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
444
+ },
445
+ body: JSON.stringify({ name, project }),
446
+ });
447
+ if (!response.ok) {
448
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
449
+ }
450
+ }
451
+ }
452
+
309
453
  async disconnect(): Promise<void> {
310
454
  if ((this.mode === 'mcp' || this.mode === 'sse') && this.client && this.transport) {
311
455
  await this.client.close();