@contextstream/mcp-server 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.ts DELETED
@@ -1,764 +0,0 @@
1
- import { z } from 'zod';
2
- import type { Config } from './config.js';
3
- import { request } from './http.js';
4
- import { readFilesFromDirectory, readAllFilesInBatches } from './files.js';
5
- import {
6
- resolveWorkspace,
7
- readLocalConfig,
8
- writeLocalConfig,
9
- addGlobalMapping,
10
- type WorkspaceConfig
11
- } from './workspace-config.js';
12
-
13
- const uuidSchema = z.string().uuid();
14
-
15
- export class ContextStreamClient {
16
- constructor(private config: Config) {}
17
-
18
- private withDefaults<T extends { workspace_id?: string; project_id?: string }>(
19
- input: T
20
- ): T {
21
- const { defaultWorkspaceId, defaultProjectId } = this.config;
22
- return {
23
- ...input,
24
- workspace_id: input.workspace_id || defaultWorkspaceId,
25
- project_id: input.project_id || defaultProjectId,
26
- } as T;
27
- }
28
-
29
- // Auth
30
- me() {
31
- return request(this.config, '/auth/me');
32
- }
33
-
34
- // Workspaces & Projects
35
- listWorkspaces(params?: { page?: number; page_size?: number }) {
36
- const query = new URLSearchParams();
37
- if (params?.page) query.set('page', String(params.page));
38
- if (params?.page_size) query.set('page_size', String(params.page_size));
39
- const suffix = query.toString() ? `?${query.toString()}` : '';
40
- return request(this.config, `/workspaces${suffix}`);
41
- }
42
-
43
- createWorkspace(input: { name: string; description?: string; visibility?: string }) {
44
- return request(this.config, '/workspaces', { body: input });
45
- }
46
-
47
- listProjects(params?: { workspace_id?: string; page?: number; page_size?: number }) {
48
- const withDefaults = this.withDefaults(params || {});
49
- const query = new URLSearchParams();
50
- if (withDefaults.workspace_id) query.set('workspace_id', withDefaults.workspace_id);
51
- if (params?.page) query.set('page', String(params.page));
52
- if (params?.page_size) query.set('page_size', String(params.page_size));
53
- const suffix = query.toString() ? `?${query.toString()}` : '';
54
- return request(this.config, `/projects${suffix}`);
55
- }
56
-
57
- createProject(input: { name: string; description?: string; workspace_id?: string }) {
58
- const payload = this.withDefaults(input);
59
- return request(this.config, '/projects', { body: payload });
60
- }
61
-
62
- indexProject(projectId: string) {
63
- uuidSchema.parse(projectId);
64
- return request(this.config, `/projects/${projectId}/index`, { body: {} });
65
- }
66
-
67
- // Search
68
- searchSemantic(body: { query: string; workspace_id?: string; project_id?: string; limit?: number }) {
69
- return request(this.config, '/search/semantic', { body: this.withDefaults(body) });
70
- }
71
-
72
- searchHybrid(body: { query: string; workspace_id?: string; project_id?: string; limit?: number }) {
73
- return request(this.config, '/search/hybrid', { body: this.withDefaults(body) });
74
- }
75
-
76
- searchKeyword(body: { query: string; workspace_id?: string; project_id?: string; limit?: number }) {
77
- return request(this.config, '/search/keyword', { body: this.withDefaults(body) });
78
- }
79
-
80
- searchPattern(body: { query: string; workspace_id?: string; project_id?: string; limit?: number }) {
81
- return request(this.config, '/search/pattern', { body: this.withDefaults(body) });
82
- }
83
-
84
- // Memory / Knowledge
85
- createMemoryEvent(body: {
86
- workspace_id?: string;
87
- project_id?: string;
88
- event_type: string;
89
- title: string;
90
- content: string;
91
- metadata?: Record<string, any>;
92
- }) {
93
- return request(this.config, '/memory/events', { body: this.withDefaults(body) });
94
- }
95
-
96
- bulkIngestEvents(body: { workspace_id?: string; project_id?: string; events: any[] }) {
97
- return request(this.config, '/memory/events/ingest', { body: this.withDefaults(body) });
98
- }
99
-
100
- listMemoryEvents(params?: { workspace_id?: string; project_id?: string; limit?: number }) {
101
- const withDefaults = this.withDefaults(params || {});
102
- if (!withDefaults.workspace_id) {
103
- throw new Error('workspace_id is required for listing memory events');
104
- }
105
- const query = new URLSearchParams();
106
- if (params?.limit) query.set('limit', String(params.limit));
107
- if (withDefaults.project_id) query.set('project_id', withDefaults.project_id);
108
- const suffix = query.toString() ? `?${query.toString()}` : '';
109
- return request(this.config, `/memory/events/workspace/${withDefaults.workspace_id}${suffix}`, { method: 'GET' });
110
- }
111
-
112
- createKnowledgeNode(body: {
113
- workspace_id?: string;
114
- project_id?: string;
115
- node_type: string;
116
- title: string;
117
- content: string;
118
- relations?: Array<{ type: string; target_id: string }>;
119
- }) {
120
- return request(this.config, '/memory/nodes', { body: this.withDefaults(body) });
121
- }
122
-
123
- listKnowledgeNodes(params?: { workspace_id?: string; project_id?: string; limit?: number }) {
124
- const withDefaults = this.withDefaults(params || {});
125
- if (!withDefaults.workspace_id) {
126
- throw new Error('workspace_id is required for listing knowledge nodes');
127
- }
128
- const query = new URLSearchParams();
129
- if (params?.limit) query.set('limit', String(params.limit));
130
- if (withDefaults.project_id) query.set('project_id', withDefaults.project_id);
131
- const suffix = query.toString() ? `?${query.toString()}` : '';
132
- return request(this.config, `/memory/nodes/workspace/${withDefaults.workspace_id}${suffix}`, { method: 'GET' });
133
- }
134
-
135
- memorySearch(body: { query: string; workspace_id?: string; project_id?: string; limit?: number }) {
136
- return request(this.config, '/memory/search', { body: this.withDefaults(body) });
137
- }
138
-
139
- memoryDecisions(params?: { workspace_id?: string; project_id?: string; limit?: number }) {
140
- const query = new URLSearchParams();
141
- const withDefaults = this.withDefaults(params || {});
142
- if (withDefaults.workspace_id) query.set('workspace_id', withDefaults.workspace_id);
143
- if (withDefaults.project_id) query.set('project_id', withDefaults.project_id);
144
- if (params?.limit) query.set('limit', String(params.limit));
145
- const suffix = query.toString() ? `?${query.toString()}` : '';
146
- return request(this.config, `/memory/search/decisions${suffix}`, { method: 'GET' });
147
- }
148
-
149
- // Graph
150
- graphRelated(body: { workspace_id?: string; project_id?: string; node_id: string; limit?: number }) {
151
- return request(this.config, '/graph/knowledge/related', { body: this.withDefaults(body) });
152
- }
153
-
154
- graphPath(body: { workspace_id?: string; project_id?: string; source_id: string; target_id: string }) {
155
- return request(this.config, '/graph/knowledge/path', { body: this.withDefaults(body) });
156
- }
157
-
158
- graphDecisions(body?: { workspace_id?: string; project_id?: string; limit?: number }) {
159
- return request(this.config, '/graph/knowledge/decisions', { body: this.withDefaults(body || {}) });
160
- }
161
-
162
- graphDependencies(body: { target: { type: string; id: string }; max_depth?: number; include_transitive?: boolean }) {
163
- return request(this.config, '/graph/dependencies', { body });
164
- }
165
-
166
- graphCallPath(body: { source: { type: string; id: string }; target: { type: string; id: string }; max_depth?: number }) {
167
- return request(this.config, '/graph/call-paths', { body });
168
- }
169
-
170
- graphImpact(body: { target: { type: string; id: string }; max_depth?: number }) {
171
- return request(this.config, '/graph/impact-analysis', { body });
172
- }
173
-
174
- // AI
175
- aiContext(body: {
176
- query: string;
177
- workspace_id?: string;
178
- project_id?: string;
179
- include_code?: boolean;
180
- include_docs?: boolean;
181
- include_memory?: boolean;
182
- limit?: number;
183
- }) {
184
- return request(this.config, '/ai/context', { body: this.withDefaults(body) });
185
- }
186
-
187
- aiEmbeddings(body: { text: string }) {
188
- return request(this.config, '/ai/embeddings', { body });
189
- }
190
-
191
- aiPlan(body: { description: string; project_id?: string; complexity?: string }) {
192
- return request(this.config, '/ai/plan/generate', { body: this.withDefaults(body) });
193
- }
194
-
195
- aiTasks(body: { plan_id?: string; description?: string; project_id?: string; granularity?: string }) {
196
- return request(this.config, '/ai/tasks/generate', { body: this.withDefaults(body) });
197
- }
198
-
199
- aiEnhancedContext(body: {
200
- query: string;
201
- workspace_id?: string;
202
- project_id?: string;
203
- include_code?: boolean;
204
- include_docs?: boolean;
205
- include_memory?: boolean;
206
- limit?: number;
207
- }) {
208
- return request(this.config, '/ai/context/enhanced', { body: this.withDefaults(body) });
209
- }
210
-
211
- // Project extended operations
212
- getProject(projectId: string) {
213
- uuidSchema.parse(projectId);
214
- return request(this.config, `/projects/${projectId}`, { method: 'GET' });
215
- }
216
-
217
- projectOverview(projectId: string) {
218
- uuidSchema.parse(projectId);
219
- return request(this.config, `/projects/${projectId}/overview`, { method: 'GET' });
220
- }
221
-
222
- projectStatistics(projectId: string) {
223
- uuidSchema.parse(projectId);
224
- return request(this.config, `/projects/${projectId}/statistics`, { method: 'GET' });
225
- }
226
-
227
- projectFiles(projectId: string) {
228
- uuidSchema.parse(projectId);
229
- return request(this.config, `/projects/${projectId}/files`, { method: 'GET' });
230
- }
231
-
232
- projectIndexStatus(projectId: string) {
233
- uuidSchema.parse(projectId);
234
- return request(this.config, `/projects/${projectId}/index/status`, { method: 'GET' });
235
- }
236
-
237
- /**
238
- * Ingest files for indexing
239
- * This uploads files to the API for indexing
240
- */
241
- ingestFiles(projectId: string, files: Array<{ path: string; content: string; language?: string }>) {
242
- uuidSchema.parse(projectId);
243
- return request(this.config, `/projects/${projectId}/files/ingest`, {
244
- body: { files },
245
- });
246
- }
247
-
248
- // Workspace extended operations
249
- getWorkspace(workspaceId: string) {
250
- uuidSchema.parse(workspaceId);
251
- return request(this.config, `/workspaces/${workspaceId}`, { method: 'GET' });
252
- }
253
-
254
- workspaceOverview(workspaceId: string) {
255
- uuidSchema.parse(workspaceId);
256
- return request(this.config, `/workspaces/${workspaceId}/overview`, { method: 'GET' });
257
- }
258
-
259
- workspaceAnalytics(workspaceId: string) {
260
- uuidSchema.parse(workspaceId);
261
- return request(this.config, `/workspaces/${workspaceId}/analytics`, { method: 'GET' });
262
- }
263
-
264
- workspaceContent(workspaceId: string) {
265
- uuidSchema.parse(workspaceId);
266
- return request(this.config, `/workspaces/${workspaceId}/content`, { method: 'GET' });
267
- }
268
-
269
- // Memory extended operations
270
- getMemoryEvent(eventId: string) {
271
- uuidSchema.parse(eventId);
272
- return request(this.config, `/memory/events/${eventId}`, { method: 'GET' });
273
- }
274
-
275
- updateMemoryEvent(eventId: string, body: { title?: string; content?: string; metadata?: Record<string, any> }) {
276
- uuidSchema.parse(eventId);
277
- return request(this.config, `/memory/events/${eventId}`, { method: 'PUT', body });
278
- }
279
-
280
- deleteMemoryEvent(eventId: string) {
281
- uuidSchema.parse(eventId);
282
- return request(this.config, `/memory/events/${eventId}`, { method: 'DELETE' });
283
- }
284
-
285
- distillMemoryEvent(eventId: string) {
286
- uuidSchema.parse(eventId);
287
- return request(this.config, `/memory/events/${eventId}/distill`, { body: {} });
288
- }
289
-
290
- getKnowledgeNode(nodeId: string) {
291
- uuidSchema.parse(nodeId);
292
- return request(this.config, `/memory/nodes/${nodeId}`, { method: 'GET' });
293
- }
294
-
295
- updateKnowledgeNode(nodeId: string, body: { title?: string; content?: string; relations?: Array<{ type: string; target_id: string }> }) {
296
- uuidSchema.parse(nodeId);
297
- return request(this.config, `/memory/nodes/${nodeId}`, { method: 'PUT', body });
298
- }
299
-
300
- deleteKnowledgeNode(nodeId: string) {
301
- uuidSchema.parse(nodeId);
302
- return request(this.config, `/memory/nodes/${nodeId}`, { method: 'DELETE' });
303
- }
304
-
305
- supersedeKnowledgeNode(nodeId: string, body: { new_content: string; reason?: string }) {
306
- uuidSchema.parse(nodeId);
307
- return request(this.config, `/memory/nodes/${nodeId}/supersede`, { body });
308
- }
309
-
310
- memoryTimeline(workspaceId: string) {
311
- uuidSchema.parse(workspaceId);
312
- return request(this.config, `/memory/search/timeline/${workspaceId}`, { method: 'GET' });
313
- }
314
-
315
- memorySummary(workspaceId: string) {
316
- uuidSchema.parse(workspaceId);
317
- return request(this.config, `/memory/search/summary/${workspaceId}`, { method: 'GET' });
318
- }
319
-
320
- // Graph extended operations
321
- findCircularDependencies(projectId: string) {
322
- uuidSchema.parse(projectId);
323
- return request(this.config, `/graph/circular-dependencies/${projectId}`, { method: 'GET' });
324
- }
325
-
326
- findUnusedCode(projectId: string) {
327
- uuidSchema.parse(projectId);
328
- return request(this.config, `/graph/unused-code/${projectId}`, { method: 'GET' });
329
- }
330
-
331
- findContradictions(nodeId: string) {
332
- uuidSchema.parse(nodeId);
333
- return request(this.config, `/graph/knowledge/contradictions/${nodeId}`, { method: 'GET' });
334
- }
335
-
336
- // Search suggestions
337
- searchSuggestions(body: { query: string; workspace_id?: string; project_id?: string }) {
338
- return request(this.config, '/search/suggest', { body: this.withDefaults(body) });
339
- }
340
-
341
- // ============================================
342
- // Session & Auto-Context Initialization
343
- // ============================================
344
-
345
- /**
346
- * Initialize a conversation session and retrieve relevant context automatically.
347
- * This is the key tool for AI assistants to get context at the start of a conversation.
348
- *
349
- * Discovery chain:
350
- * 1. Check local .contextstream/config.json in repo root
351
- * 2. Check parent folder heuristic mappings (~/.contextstream-mappings.json)
352
- * 3. If ambiguous, return workspace candidates for user/agent selection
353
- *
354
- * Once workspace is resolved, loads WORKSPACE-LEVEL context (not just project),
355
- * ensuring cross-project decisions and memory are available.
356
- */
357
- async initSession(params: {
358
- workspace_id?: string;
359
- project_id?: string;
360
- session_id?: string;
361
- context_hint?: string;
362
- include_recent_memory?: boolean;
363
- include_decisions?: boolean;
364
- include_user_preferences?: boolean;
365
- auto_index?: boolean;
366
- }, ideRoots: string[] = []) {
367
- let workspaceId = params.workspace_id || this.config.defaultWorkspaceId;
368
- let projectId = params.project_id || this.config.defaultProjectId;
369
- let workspaceName: string | undefined;
370
-
371
- // Build comprehensive initial context
372
- const context: Record<string, unknown> = {
373
- session_id: params.session_id || crypto.randomUUID(),
374
- initialized_at: new Date().toISOString(),
375
- };
376
-
377
- const rootPath = ideRoots.length > 0 ? ideRoots[0] : undefined;
378
-
379
- // ========================================
380
- // STEP 1: Workspace Discovery Chain
381
- // ========================================
382
- if (!workspaceId && rootPath) {
383
- // Try local config and parent mappings first
384
- const resolved = resolveWorkspace(rootPath);
385
-
386
- if (resolved.config) {
387
- workspaceId = resolved.config.workspace_id;
388
- workspaceName = resolved.config.workspace_name;
389
- projectId = resolved.config.project_id || projectId;
390
- context.workspace_source = resolved.source;
391
- context.workspace_resolved_from = resolved.source === 'local_config'
392
- ? `${rootPath}/.contextstream/config.json`
393
- : 'parent_folder_mapping';
394
- } else {
395
- // Ambiguous - need user selection
396
- // Fetch all workspaces as candidates
397
- try {
398
- const workspaces = await this.listWorkspaces({ page_size: 50 }) as {
399
- items?: Array<{ id: string; name: string; description?: string }>
400
- };
401
-
402
- if (workspaces.items && workspaces.items.length > 0) {
403
- // Return ambiguous status with candidates
404
- context.status = 'requires_workspace_selection';
405
- context.workspace_candidates = workspaces.items.map(w => ({
406
- id: w.id,
407
- name: w.name,
408
- description: w.description,
409
- }));
410
- context.message = `New folder detected: "${rootPath?.split('/').pop()}". Please select which workspace this belongs to, or create a new one.`;
411
- context.ide_roots = ideRoots;
412
- context.folder_name = rootPath?.split('/').pop();
413
-
414
- // Still return early - agent needs to ask user
415
- return context;
416
- } else {
417
- // No workspaces exist - create default
418
- const newWorkspace = await this.createWorkspace({
419
- name: 'My Workspace',
420
- description: 'Default workspace created by ContextStream',
421
- visibility: 'private',
422
- }) as { id?: string; name?: string };
423
- if (newWorkspace.id) {
424
- workspaceId = newWorkspace.id;
425
- workspaceName = newWorkspace.name;
426
- context.workspace_source = 'auto_created';
427
- context.workspace_created = true;
428
-
429
- // Save to local config for next time
430
- writeLocalConfig(rootPath, {
431
- workspace_id: newWorkspace.id,
432
- workspace_name: newWorkspace.name,
433
- associated_at: new Date().toISOString(),
434
- });
435
- }
436
- }
437
- } catch (e) {
438
- context.workspace_error = String(e);
439
- }
440
- }
441
- }
442
-
443
- // Fallback: if still no workspace and no IDE roots, pick first available
444
- if (!workspaceId && !rootPath) {
445
- try {
446
- const workspaces = await this.listWorkspaces({ page_size: 1 }) as { items?: Array<{ id: string; name: string }> };
447
- if (workspaces.items && workspaces.items.length > 0) {
448
- workspaceId = workspaces.items[0].id;
449
- workspaceName = workspaces.items[0].name;
450
- context.workspace_source = 'fallback_first';
451
- }
452
- } catch (e) {
453
- context.workspace_error = String(e);
454
- }
455
- }
456
-
457
- // ========================================
458
- // STEP 2: Project Discovery
459
- // ========================================
460
- if (!projectId && workspaceId && rootPath && params.auto_index !== false) {
461
- const projectName = rootPath.split('/').pop() || 'My Project';
462
-
463
- try {
464
- // Check if a project with this name already exists in this workspace
465
- const projects = await this.listProjects({ workspace_id: workspaceId }) as { items?: Array<{ id: string; name: string }> };
466
- const existingProject = projects.items?.find(p => p.name === projectName);
467
-
468
- if (existingProject) {
469
- projectId = existingProject.id;
470
- context.project_source = 'existing';
471
- } else {
472
- // Create project from IDE root
473
- const newProject = await this.createProject({
474
- name: projectName,
475
- description: `Auto-created from ${rootPath}`,
476
- workspace_id: workspaceId,
477
- }) as { id?: string };
478
-
479
- if (newProject.id) {
480
- projectId = newProject.id;
481
- context.project_source = 'auto_created';
482
- context.project_created = true;
483
- context.project_path = rootPath;
484
- }
485
- }
486
-
487
- // Update local config with project info
488
- if (projectId) {
489
- const existingConfig = readLocalConfig(rootPath);
490
- if (existingConfig || workspaceId) {
491
- writeLocalConfig(rootPath, {
492
- workspace_id: workspaceId!,
493
- workspace_name: workspaceName,
494
- project_id: projectId,
495
- project_name: projectName,
496
- associated_at: existingConfig?.associated_at || new Date().toISOString(),
497
- });
498
- }
499
- }
500
-
501
- // Ingest files if auto_index is enabled (default: true)
502
- // Runs in BACKGROUND - does not block session_init
503
- if (projectId && (params.auto_index === undefined || params.auto_index === true)) {
504
- context.indexing_status = 'started';
505
-
506
- // Fire-and-forget: start indexing in background
507
- const projectIdCopy = projectId;
508
- const rootPathCopy = rootPath;
509
- (async () => {
510
- try {
511
- for await (const batch of readAllFilesInBatches(rootPathCopy, { batchSize: 50 })) {
512
- await this.ingestFiles(projectIdCopy, batch);
513
- }
514
- console.error(`[ContextStream] Background indexing completed for ${rootPathCopy}`);
515
- } catch (e) {
516
- console.error(`[ContextStream] Background indexing failed:`, e);
517
- }
518
- })();
519
- }
520
- } catch (e) {
521
- context.project_error = String(e);
522
- }
523
- }
524
-
525
- context.status = 'connected';
526
- context.workspace_id = workspaceId;
527
- context.workspace_name = workspaceName;
528
- context.project_id = projectId;
529
- context.ide_roots = ideRoots;
530
-
531
- // ========================================
532
- // STEP 3: Load WORKSPACE-LEVEL Context
533
- // (Cross-project memories and decisions)
534
- // ========================================
535
- if (workspaceId) {
536
- try {
537
- context.workspace = await this.workspaceOverview(workspaceId);
538
- } catch { /* optional */ }
539
- }
540
-
541
- if (projectId) {
542
- try {
543
- context.project = await this.projectOverview(projectId);
544
- } catch { /* optional */ }
545
- }
546
-
547
- // Fetch WORKSPACE-level memory (not filtered by project)
548
- // This ensures cross-project context is available
549
- if (params.include_recent_memory !== false && workspaceId) {
550
- try {
551
- context.recent_memory = await this.listMemoryEvents({
552
- workspace_id: workspaceId,
553
- // NOTE: Intentionally NOT filtering by project_id
554
- // to get workspace-wide context
555
- limit: 10,
556
- });
557
- } catch { /* optional */ }
558
- }
559
-
560
- // Fetch WORKSPACE-level decisions
561
- if (params.include_decisions !== false && workspaceId) {
562
- try {
563
- context.recent_decisions = await this.memoryDecisions({
564
- workspace_id: workspaceId,
565
- // NOTE: Intentionally NOT filtering by project_id
566
- limit: 5,
567
- });
568
- } catch { /* optional */ }
569
- }
570
-
571
- // Search for context hint if provided
572
- if (params.context_hint && workspaceId) {
573
- try {
574
- context.relevant_context = await this.memorySearch({
575
- query: params.context_hint,
576
- workspace_id: workspaceId,
577
- // Search workspace-wide first
578
- limit: 5,
579
- });
580
- } catch { /* optional */ }
581
- }
582
-
583
- return context;
584
- }
585
-
586
- /**
587
- * Associate a folder with a workspace (called after user selects from candidates).
588
- * Persists the selection to .contextstream/config.json for future sessions.
589
- */
590
- async associateWorkspace(params: {
591
- folder_path: string;
592
- workspace_id: string;
593
- workspace_name?: string;
594
- create_parent_mapping?: boolean; // Also create a parent folder mapping
595
- }) {
596
- const { folder_path, workspace_id, workspace_name, create_parent_mapping } = params;
597
-
598
- // Save local config
599
- const saved = writeLocalConfig(folder_path, {
600
- workspace_id,
601
- workspace_name,
602
- associated_at: new Date().toISOString(),
603
- });
604
-
605
- // Optionally create parent folder mapping (e.g., /home/user/dev/company/* -> workspace)
606
- if (create_parent_mapping) {
607
- const parentDir = folder_path.split('/').slice(0, -1).join('/');
608
- addGlobalMapping({
609
- pattern: `${parentDir}/*`,
610
- workspace_id,
611
- workspace_name: workspace_name || 'Unknown',
612
- });
613
- }
614
-
615
- return {
616
- success: saved,
617
- config_path: `${folder_path}/.contextstream/config.json`,
618
- workspace_id,
619
- workspace_name,
620
- parent_mapping_created: create_parent_mapping || false,
621
- };
622
- }
623
-
624
- /**
625
- * Get user preferences and persona from memory.
626
- * Useful for AI to understand user's coding style, preferences, etc.
627
- */
628
- async getUserContext(params: { workspace_id?: string }) {
629
- const withDefaults = this.withDefaults(params);
630
-
631
- if (!withDefaults.workspace_id) {
632
- throw new Error('workspace_id is required for getUserContext');
633
- }
634
-
635
- const context: Record<string, unknown> = {};
636
-
637
- // Search for user preferences
638
- try {
639
- context.preferences = await this.memorySearch({
640
- query: 'user preferences coding style settings',
641
- workspace_id: withDefaults.workspace_id,
642
- limit: 10,
643
- });
644
- } catch { /* optional */ }
645
-
646
- // Get memory summary for overall context
647
- try {
648
- context.summary = await this.memorySummary(withDefaults.workspace_id);
649
- } catch { /* optional */ }
650
-
651
- return context;
652
- }
653
-
654
- /**
655
- * Capture and store conversation context automatically.
656
- * Call this to persist important context from the current conversation.
657
- */
658
- async captureContext(params: {
659
- workspace_id?: string;
660
- project_id?: string;
661
- session_id?: string;
662
- event_type: 'conversation' | 'decision' | 'insight' | 'preference' | 'task' | 'bug' | 'feature';
663
- title: string;
664
- content: string;
665
- tags?: string[];
666
- importance?: 'low' | 'medium' | 'high' | 'critical';
667
- }) {
668
- const withDefaults = this.withDefaults(params);
669
-
670
- // Map high-level types to API EventType
671
- let apiEventType = 'manual_note';
672
- const tags = params.tags || [];
673
-
674
- switch (params.event_type) {
675
- case 'conversation':
676
- apiEventType = 'chat';
677
- break;
678
- case 'task':
679
- apiEventType = 'task_created';
680
- break;
681
- case 'bug':
682
- case 'feature':
683
- apiEventType = 'ticket';
684
- tags.push(params.event_type);
685
- break;
686
- case 'decision':
687
- case 'insight':
688
- case 'preference':
689
- apiEventType = 'manual_note';
690
- tags.push(params.event_type);
691
- break;
692
- default:
693
- apiEventType = 'manual_note';
694
- tags.push(params.event_type);
695
- }
696
-
697
- return this.createMemoryEvent({
698
- workspace_id: withDefaults.workspace_id,
699
- project_id: withDefaults.project_id,
700
- event_type: apiEventType,
701
- title: params.title,
702
- content: params.content,
703
- metadata: {
704
- original_type: params.event_type,
705
- session_id: params.session_id,
706
- tags: tags,
707
- importance: params.importance || 'medium',
708
- captured_at: new Date().toISOString(),
709
- source: 'mcp_auto_capture',
710
- },
711
- });
712
- }
713
-
714
- /**
715
- * Search memory with automatic context enrichment.
716
- * Returns both direct matches and related context.
717
- */
718
- async smartSearch(params: {
719
- query: string;
720
- workspace_id?: string;
721
- project_id?: string;
722
- include_related?: boolean;
723
- include_decisions?: boolean;
724
- }) {
725
- const withDefaults = this.withDefaults(params);
726
-
727
- const results: Record<string, unknown> = {};
728
-
729
- // Primary memory search
730
- try {
731
- results.memory_results = await this.memorySearch({
732
- query: params.query,
733
- workspace_id: withDefaults.workspace_id,
734
- project_id: withDefaults.project_id,
735
- limit: 10,
736
- });
737
- } catch { /* optional */ }
738
-
739
- // Semantic code search if project specified
740
- if (withDefaults.project_id) {
741
- try {
742
- results.code_results = await this.searchSemantic({
743
- query: params.query,
744
- workspace_id: withDefaults.workspace_id,
745
- project_id: withDefaults.project_id,
746
- limit: 5,
747
- });
748
- } catch { /* optional */ }
749
- }
750
-
751
- // Include related decisions
752
- if (params.include_decisions !== false && withDefaults.workspace_id) {
753
- try {
754
- results.related_decisions = await this.memoryDecisions({
755
- workspace_id: withDefaults.workspace_id,
756
- project_id: withDefaults.project_id,
757
- limit: 3,
758
- });
759
- } catch { /* optional */ }
760
- }
761
-
762
- return results;
763
- }
764
- }