@mycontxt/mcp 0.1.1

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/server.js ADDED
@@ -0,0 +1,988 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ // src/tools/index.ts
12
+ import { SQLiteDatabase as SQLiteDatabase3 } from "@mycontxt/adapters/sqlite";
13
+ import { MemoryEngine } from "@mycontxt/core";
14
+
15
+ // src/utils/project.ts
16
+ import { join } from "path";
17
+ function getContxtDir(cwd) {
18
+ return join(cwd, ".contxt");
19
+ }
20
+ function getDbPath(cwd) {
21
+ return join(getContxtDir(cwd), "local.db");
22
+ }
23
+
24
+ // src/tools/suggest-context.ts
25
+ import { SQLiteDatabase } from "@mycontxt/adapters/sqlite";
26
+ import { rankEntries, fitToBudget, buildContext } from "@mycontxt/core";
27
+ async function suggestContext(args) {
28
+ const projectPath = args.projectPath || process.cwd();
29
+ const dbPath = getDbPath(projectPath);
30
+ const db = new SQLiteDatabase(dbPath);
31
+ await db.initialize();
32
+ try {
33
+ const project = await db.getProjectByPath(projectPath);
34
+ if (!project) {
35
+ return "No MemoCore project found. Run `memocore init` to initialize.";
36
+ }
37
+ const activeBranch = await db.getActiveBranch(project.id);
38
+ const entries = await db.listEntries({
39
+ projectId: project.id,
40
+ branch: activeBranch,
41
+ isArchived: false
42
+ });
43
+ if (entries.length === 0) {
44
+ return "No memory entries found. Add some decisions, patterns, or context to get started.";
45
+ }
46
+ const options = {
47
+ projectId: project.id,
48
+ taskDescription: args.taskDescription,
49
+ activeFiles: args.activeFiles,
50
+ maxTokens: args.maxTokens || 4e3,
51
+ minRelevance: args.minRelevance || 0.3
52
+ };
53
+ const ranked = rankEntries(entries, options);
54
+ if (ranked.length === 0) {
55
+ return "No relevant entries found for the given task.";
56
+ }
57
+ const fitted = fitToBudget(ranked, options.maxTokens);
58
+ const context = buildContext(fitted, {
59
+ includeReasons: true,
60
+ includeStats: true
61
+ });
62
+ return context;
63
+ } finally {
64
+ await db.close();
65
+ }
66
+ }
67
+
68
+ // src/tools/auto-capture.ts
69
+ import { SQLiteDatabase as SQLiteDatabase2 } from "@mycontxt/adapters/sqlite";
70
+
71
+ // src/utils/usage-gate.ts
72
+ import { existsSync, readFileSync } from "fs";
73
+ import { homedir } from "os";
74
+ import { join as join2 } from "path";
75
+ import { UsageGate } from "@mycontxt/core/engine/usage";
76
+ import { resolveUserPlan } from "@mycontxt/core/engine/plan-resolver";
77
+ import { SupabaseDatabase } from "@mycontxt/adapters/supabase";
78
+ var AUTH_FILE = join2(homedir(), ".contxt", "auth.json");
79
+ function getCurrentUserId() {
80
+ if (!existsSync(AUTH_FILE)) return null;
81
+ try {
82
+ const content = readFileSync(AUTH_FILE, "utf-8");
83
+ const auth = JSON.parse(content);
84
+ return auth.userId;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function getRemoteDb() {
90
+ if (!existsSync(AUTH_FILE)) return null;
91
+ try {
92
+ const content = readFileSync(AUTH_FILE, "utf-8");
93
+ const auth = JSON.parse(content);
94
+ return new SupabaseDatabase({
95
+ url: process.env.SUPABASE_URL || "",
96
+ anonKey: process.env.SUPABASE_ANON_KEY || "",
97
+ accessToken: auth.accessToken
98
+ });
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+ async function getUsageCounts(localDb, userId, projectId) {
104
+ const projectCountQuery = "SELECT COUNT(*) as count FROM projects";
105
+ const projectRow = localDb.db.prepare(projectCountQuery).get();
106
+ const totalProjects = projectRow.count;
107
+ const totalEntriesQuery = "SELECT COUNT(*) as count FROM memory_entries WHERE is_archived = 0";
108
+ const totalEntriesRow = localDb.db.prepare(totalEntriesQuery).get();
109
+ const totalEntries = totalEntriesRow.count;
110
+ let entriesInProject = 0;
111
+ if (projectId) {
112
+ const projectEntriesQuery = "SELECT COUNT(*) as count FROM memory_entries WHERE project_id = ? AND is_archived = 0";
113
+ const projectEntriesRow = localDb.db.prepare(projectEntriesQuery).get(projectId);
114
+ entriesInProject = projectEntriesRow.count;
115
+ }
116
+ return {
117
+ totalProjects,
118
+ totalEntries,
119
+ entriesInProject,
120
+ totalSeats: 1
121
+ };
122
+ }
123
+ async function checkEntryAllowed(localDb, projectId) {
124
+ try {
125
+ const userId = getCurrentUserId();
126
+ const remoteDb = getRemoteDb();
127
+ const planId = await resolveUserPlan(localDb, remoteDb, userId);
128
+ const gate = new UsageGate(planId, () => getUsageCounts(localDb, userId, projectId));
129
+ const result = await gate.checkEntryCreate();
130
+ if (!result.allowed) {
131
+ return {
132
+ allowed: false,
133
+ message: `
134
+
135
+ ---
136
+ \u{1F4A1} ${result.reason}
137
+ ${result.upgradeHint}`
138
+ };
139
+ }
140
+ return { allowed: true };
141
+ } catch (error) {
142
+ console.error("Usage gate error:", error);
143
+ return { allowed: true };
144
+ }
145
+ }
146
+
147
+ // src/tools/auto-capture.ts
148
+ async function autoCaptureDecision(args) {
149
+ const projectPath = args.projectPath || process.cwd();
150
+ const dbPath = getDbPath(projectPath);
151
+ const db = new SQLiteDatabase2(dbPath);
152
+ await db.initialize();
153
+ try {
154
+ const project = await db.getProjectByPath(projectPath);
155
+ if (!project) {
156
+ return JSON.stringify({ status: "error", message: "No Contxt project found." });
157
+ }
158
+ const gateCheck = await checkEntryAllowed(db, project.id);
159
+ if (!gateCheck.allowed) {
160
+ return JSON.stringify({
161
+ status: "limit_reached",
162
+ message: `Decision noted (not saved - usage limit reached).${gateCheck.message}`
163
+ });
164
+ }
165
+ const entry = await db.createEntry({
166
+ projectId: project.id,
167
+ type: "decision",
168
+ title: args.decision,
169
+ content: args.rationale,
170
+ metadata: {
171
+ source: "mcp:auto",
172
+ category: args.category,
173
+ alternatives: args.alternatives,
174
+ decisionStatus: args.status || "active",
175
+ conversationId: args.conversationId,
176
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
177
+ },
178
+ status: "draft"
179
+ });
180
+ return JSON.stringify({
181
+ status: "captured",
182
+ id: entry.id,
183
+ message: `Decision captured: ${args.decision} (draft \u2014 run \`contxt review\` to confirm)`
184
+ });
185
+ } finally {
186
+ await db.close();
187
+ }
188
+ }
189
+ async function autoCapturePattern(args) {
190
+ const projectPath = args.projectPath || process.cwd();
191
+ const dbPath = getDbPath(projectPath);
192
+ const db = new SQLiteDatabase2(dbPath);
193
+ await db.initialize();
194
+ try {
195
+ const project = await db.getProjectByPath(projectPath);
196
+ if (!project) {
197
+ return JSON.stringify({ status: "error", message: "No Contxt project found." });
198
+ }
199
+ const gateCheck = await checkEntryAllowed(db, project.id);
200
+ if (!gateCheck.allowed) {
201
+ return JSON.stringify({
202
+ status: "limit_reached",
203
+ message: `Pattern noted (not saved - usage limit reached).${gateCheck.message}`
204
+ });
205
+ }
206
+ const entry = await db.createEntry({
207
+ projectId: project.id,
208
+ type: "pattern",
209
+ title: args.pattern,
210
+ content: args.description,
211
+ metadata: {
212
+ source: "mcp:auto",
213
+ category: args.category,
214
+ when: args.when,
215
+ conversationId: args.conversationId,
216
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
217
+ },
218
+ status: "draft"
219
+ });
220
+ return JSON.stringify({
221
+ status: "captured",
222
+ id: entry.id,
223
+ message: `Pattern captured: ${args.pattern} (draft \u2014 run \`contxt review\` to confirm)`
224
+ });
225
+ } finally {
226
+ await db.close();
227
+ }
228
+ }
229
+ async function captureDiscussion(args) {
230
+ const projectPath = args.projectPath || process.cwd();
231
+ const dbPath = getDbPath(projectPath);
232
+ const db = new SQLiteDatabase2(dbPath);
233
+ await db.initialize();
234
+ try {
235
+ const project = await db.getProjectByPath(projectPath);
236
+ if (!project) {
237
+ return JSON.stringify({ status: "error", message: "No Contxt project found." });
238
+ }
239
+ const gateCheck = await checkEntryAllowed(db, project.id);
240
+ if (!gateCheck.allowed) {
241
+ return JSON.stringify({
242
+ status: "limit_reached",
243
+ message: `Discussion noted (not saved - usage limit reached).${gateCheck.message}`
244
+ });
245
+ }
246
+ const content = [
247
+ args.summary,
248
+ "",
249
+ `**Context:** ${args.context}`,
250
+ args.rejected ? `**Rejected:** ${args.rejected}` : ""
251
+ ].filter(Boolean).join("\n");
252
+ const entry = await db.createEntry({
253
+ projectId: project.id,
254
+ type: args.type,
255
+ title: args.title,
256
+ content,
257
+ metadata: {
258
+ source: "mcp:discussion",
259
+ context: args.context,
260
+ rejected: args.rejected,
261
+ conversationId: args.conversationId,
262
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
263
+ },
264
+ status: "draft"
265
+ });
266
+ return JSON.stringify({
267
+ status: "captured",
268
+ id: entry.id,
269
+ message: `Discussion captured: ${args.title} (draft \u2014 run \`contxt review\` to confirm)`
270
+ });
271
+ } finally {
272
+ await db.close();
273
+ }
274
+ }
275
+ async function updateSession(args) {
276
+ const projectPath = args.projectPath || process.cwd();
277
+ const dbPath = getDbPath(projectPath);
278
+ const db = new SQLiteDatabase2(dbPath);
279
+ await db.initialize();
280
+ try {
281
+ const project = await db.getProjectByPath(projectPath);
282
+ if (!project) {
283
+ return JSON.stringify({ status: "error", message: "No Contxt project found." });
284
+ }
285
+ const branch = await db.getActiveBranch(project.id);
286
+ const entries = await db.listEntries({
287
+ projectId: project.id,
288
+ branch,
289
+ type: "context"
290
+ });
291
+ const activeContext = entries.find((e) => e.status === "active");
292
+ if (activeContext) {
293
+ await db.updateEntry(activeContext.id, {
294
+ metadata: {
295
+ ...activeContext.metadata,
296
+ lastSessionSummary: args.summary,
297
+ lastSessionFiles: args.filesChanged,
298
+ lastSessionDecisions: args.decisions,
299
+ lastSessionAt: (/* @__PURE__ */ new Date()).toISOString()
300
+ }
301
+ });
302
+ } else {
303
+ const gateCheck = await checkEntryAllowed(db, project.id);
304
+ if (!gateCheck.allowed) {
305
+ return JSON.stringify({
306
+ status: "limit_reached",
307
+ message: `Session noted (not saved - usage limit reached).${gateCheck.message}`
308
+ });
309
+ }
310
+ await db.createEntry({
311
+ projectId: project.id,
312
+ type: "session",
313
+ title: `Session: ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
314
+ content: args.summary,
315
+ metadata: {
316
+ source: "mcp:auto",
317
+ filesChanged: args.filesChanged,
318
+ decisions: args.decisions,
319
+ conversationId: args.conversationId,
320
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
321
+ },
322
+ status: "draft"
323
+ });
324
+ }
325
+ return JSON.stringify({
326
+ status: "updated",
327
+ message: "Session summary updated."
328
+ });
329
+ } finally {
330
+ await db.close();
331
+ }
332
+ }
333
+ async function getDrafts(args) {
334
+ const projectPath = args.projectPath || process.cwd();
335
+ const dbPath = getDbPath(projectPath);
336
+ const db = new SQLiteDatabase2(dbPath);
337
+ await db.initialize();
338
+ try {
339
+ const project = await db.getProjectByPath(projectPath);
340
+ if (!project) {
341
+ return JSON.stringify({ status: "error", message: "No Contxt project found." });
342
+ }
343
+ const branch = await db.getActiveBranch(project.id);
344
+ const allEntries = await db.listEntries({ projectId: project.id, branch });
345
+ let drafts = allEntries.filter((e) => e.status === "draft");
346
+ if (args.source) {
347
+ const sourceFilter = `mcp:${args.source}`;
348
+ drafts = drafts.filter((e) => e.metadata.source?.includes(sourceFilter));
349
+ }
350
+ if (drafts.length === 0) {
351
+ return JSON.stringify({ status: "ok", count: 0, message: "No drafts pending review." });
352
+ }
353
+ const summary = drafts.map((d) => ({
354
+ id: d.id,
355
+ type: d.type,
356
+ title: d.title,
357
+ source: d.metadata.source,
358
+ conversationId: d.metadata.conversationId,
359
+ capturedAt: d.metadata.capturedAt || d.createdAt
360
+ }));
361
+ return JSON.stringify({
362
+ status: "ok",
363
+ count: drafts.length,
364
+ drafts: summary,
365
+ message: `${drafts.length} draft${drafts.length !== 1 ? "s" : ""} pending. Run \`contxt review\` to confirm them.`
366
+ });
367
+ } finally {
368
+ await db.close();
369
+ }
370
+ }
371
+ async function confirmDraft(args) {
372
+ const projectPath = args.projectPath || process.cwd();
373
+ const dbPath = getDbPath(projectPath);
374
+ const db = new SQLiteDatabase2(dbPath);
375
+ await db.initialize();
376
+ try {
377
+ const entry = await db.getEntry(args.id);
378
+ if (!entry) {
379
+ return JSON.stringify({ status: "error", message: `Draft ${args.id} not found.` });
380
+ }
381
+ if (entry.status !== "draft") {
382
+ return JSON.stringify({ status: "error", message: `Entry ${args.id} is not a draft.` });
383
+ }
384
+ await db.updateEntry(args.id, { status: "active" });
385
+ return JSON.stringify({
386
+ status: "confirmed",
387
+ id: args.id,
388
+ message: `"${entry.title}" confirmed and added to memory.`
389
+ });
390
+ } finally {
391
+ await db.close();
392
+ }
393
+ }
394
+
395
+ // src/tools/index.ts
396
+ async function getProjectContext(args) {
397
+ const projectPath = args.projectPath || process.cwd();
398
+ const dbPath = getDbPath(projectPath);
399
+ const db = new SQLiteDatabase3(dbPath);
400
+ await db.initialize();
401
+ try {
402
+ const project = await db.getProjectByPath(projectPath);
403
+ if (!project) {
404
+ return "No MemoCore project found.";
405
+ }
406
+ const engine = new MemoryEngine(db);
407
+ const activeBranch = await db.getActiveBranch(project.id);
408
+ const context = await engine.getContext(project.id);
409
+ const decisions = await engine.listDecisions(project.id);
410
+ const patterns = await engine.listPatterns(project.id);
411
+ const lines = [];
412
+ lines.push(`# Project: ${project.name}`);
413
+ lines.push("");
414
+ lines.push(`**Branch:** ${activeBranch}`);
415
+ lines.push(`**Stack:** ${project.stack?.join(", ") || "Not specified"}`);
416
+ lines.push("");
417
+ if (context) {
418
+ lines.push("## Current Context");
419
+ if (context.metadata.feature) {
420
+ lines.push(`**Feature:** ${context.metadata.feature}`);
421
+ }
422
+ if (context.metadata.blockers?.length > 0) {
423
+ lines.push(`**Blockers:** ${context.metadata.blockers.join(", ")}`);
424
+ }
425
+ lines.push("");
426
+ }
427
+ lines.push(`## Summary`);
428
+ lines.push(`- ${decisions.length} decisions`);
429
+ lines.push(`- ${patterns.length} patterns`);
430
+ return lines.join("\n");
431
+ } finally {
432
+ await db.close();
433
+ }
434
+ }
435
+ async function getDecisions(args) {
436
+ const projectPath = args.projectPath || process.cwd();
437
+ const dbPath = getDbPath(projectPath);
438
+ const db = new SQLiteDatabase3(dbPath);
439
+ await db.initialize();
440
+ try {
441
+ const project = await db.getProjectByPath(projectPath);
442
+ if (!project) {
443
+ return "No MemoCore project found.";
444
+ }
445
+ const engine = new MemoryEngine(db);
446
+ const decisions = await engine.listDecisions(project.id);
447
+ const limited = args.limit ? decisions.slice(0, args.limit) : decisions;
448
+ if (limited.length === 0) {
449
+ return "No decisions found.";
450
+ }
451
+ const lines = [];
452
+ lines.push("# Architectural Decisions");
453
+ lines.push("");
454
+ for (const decision of limited) {
455
+ lines.push(`## ${decision.title}`);
456
+ lines.push("");
457
+ lines.push(decision.content);
458
+ if (decision.metadata.alternatives?.length > 0) {
459
+ lines.push("");
460
+ lines.push(`**Alternatives considered:** ${decision.metadata.alternatives.join(", ")}`);
461
+ }
462
+ lines.push("");
463
+ lines.push("---");
464
+ lines.push("");
465
+ }
466
+ return lines.join("\n");
467
+ } finally {
468
+ await db.close();
469
+ }
470
+ }
471
+ async function getPatterns(args) {
472
+ const projectPath = args.projectPath || process.cwd();
473
+ const dbPath = getDbPath(projectPath);
474
+ const db = new SQLiteDatabase3(dbPath);
475
+ await db.initialize();
476
+ try {
477
+ const project = await db.getProjectByPath(projectPath);
478
+ if (!project) {
479
+ return "No MemoCore project found.";
480
+ }
481
+ const engine = new MemoryEngine(db);
482
+ const patterns = await engine.listPatterns(project.id);
483
+ const limited = args.limit ? patterns.slice(0, args.limit) : patterns;
484
+ if (limited.length === 0) {
485
+ return "No patterns found.";
486
+ }
487
+ const lines = [];
488
+ lines.push("# Code Patterns & Conventions");
489
+ lines.push("");
490
+ for (const pattern of limited) {
491
+ lines.push(`## ${pattern.title}`);
492
+ lines.push("");
493
+ lines.push(pattern.content);
494
+ lines.push("");
495
+ lines.push("---");
496
+ lines.push("");
497
+ }
498
+ return lines.join("\n");
499
+ } finally {
500
+ await db.close();
501
+ }
502
+ }
503
+ async function searchMemory(args) {
504
+ const projectPath = args.projectPath || process.cwd();
505
+ const dbPath = getDbPath(projectPath);
506
+ const db = new SQLiteDatabase3(dbPath);
507
+ await db.initialize();
508
+ try {
509
+ const project = await db.getProjectByPath(projectPath);
510
+ if (!project) {
511
+ return "No MemoCore project found.";
512
+ }
513
+ const engine = new MemoryEngine(db);
514
+ const results = await engine.searchEntries(project.id, args.query);
515
+ const limited = args.limit ? results.slice(0, args.limit) : results;
516
+ if (limited.length === 0) {
517
+ return `No results found for "${args.query}".`;
518
+ }
519
+ const lines = [];
520
+ lines.push(`# Search Results for "${args.query}"`);
521
+ lines.push("");
522
+ for (const entry of limited) {
523
+ lines.push(`## ${entry.type.toUpperCase()}: ${entry.title}`);
524
+ lines.push("");
525
+ lines.push(entry.content);
526
+ lines.push("");
527
+ lines.push("---");
528
+ lines.push("");
529
+ }
530
+ return lines.join("\n");
531
+ } finally {
532
+ await db.close();
533
+ }
534
+ }
535
+ async function logDecision(args) {
536
+ const projectPath = args.projectPath || process.cwd();
537
+ const dbPath = getDbPath(projectPath);
538
+ const db = new SQLiteDatabase3(dbPath);
539
+ await db.initialize();
540
+ try {
541
+ const project = await db.getProjectByPath(projectPath);
542
+ if (!project) {
543
+ return "No MemoCore project found.";
544
+ }
545
+ const engine = new MemoryEngine(db);
546
+ const input = {
547
+ title: args.title,
548
+ rationale: args.rationale,
549
+ alternatives: args.alternatives
550
+ };
551
+ const entry = await engine.addDecision(project.id, input);
552
+ return `Decision "${entry.title}" logged successfully.`;
553
+ } finally {
554
+ await db.close();
555
+ }
556
+ }
557
+ async function updateContext(args) {
558
+ const projectPath = args.projectPath || process.cwd();
559
+ const dbPath = getDbPath(projectPath);
560
+ const db = new SQLiteDatabase3(dbPath);
561
+ await db.initialize();
562
+ try {
563
+ const project = await db.getProjectByPath(projectPath);
564
+ if (!project) {
565
+ return "No MemoCore project found.";
566
+ }
567
+ const engine = new MemoryEngine(db);
568
+ const input = {
569
+ feature: args.feature,
570
+ blockers: args.blockers,
571
+ nextSteps: args.nextSteps
572
+ };
573
+ await engine.setContext(project.id, input);
574
+ return "Project context updated successfully.";
575
+ } finally {
576
+ await db.close();
577
+ }
578
+ }
579
+ async function savePattern(args) {
580
+ const projectPath = args.projectPath || process.cwd();
581
+ const dbPath = getDbPath(projectPath);
582
+ const db = new SQLiteDatabase3(dbPath);
583
+ await db.initialize();
584
+ try {
585
+ const project = await db.getProjectByPath(projectPath);
586
+ if (!project) {
587
+ return "No MemoCore project found.";
588
+ }
589
+ const engine = new MemoryEngine(db);
590
+ const input = {
591
+ title: args.title,
592
+ content: args.content,
593
+ category: args.category
594
+ };
595
+ const entry = await engine.addPattern(project.id, input);
596
+ return `Pattern "${entry.title}" saved successfully.`;
597
+ } finally {
598
+ await db.close();
599
+ }
600
+ }
601
+
602
+ // src/server.ts
603
+ var server = new Server(
604
+ {
605
+ name: "contxt",
606
+ version: "0.1.0"
607
+ },
608
+ {
609
+ capabilities: {
610
+ tools: {}
611
+ }
612
+ }
613
+ );
614
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
615
+ return {
616
+ tools: [
617
+ {
618
+ name: "suggest_context",
619
+ description: "Smart context retrieval - returns relevant memory entries based on task description and active files. This is the primary tool for getting project context.",
620
+ inputSchema: {
621
+ type: "object",
622
+ properties: {
623
+ taskDescription: {
624
+ type: "string",
625
+ description: "Description of the task or feature being worked on"
626
+ },
627
+ activeFiles: {
628
+ type: "array",
629
+ items: { type: "string" },
630
+ description: "List of files currently being worked on"
631
+ },
632
+ maxTokens: {
633
+ type: "number",
634
+ description: "Maximum tokens for context (default: 4000)"
635
+ },
636
+ projectPath: {
637
+ type: "string",
638
+ description: "Path to project directory (default: current directory)"
639
+ }
640
+ }
641
+ }
642
+ },
643
+ {
644
+ name: "get_project_context",
645
+ description: "Get project overview including stack, configuration, active context, and summary statistics",
646
+ inputSchema: {
647
+ type: "object",
648
+ properties: {
649
+ projectPath: {
650
+ type: "string",
651
+ description: "Path to project directory (default: current directory)"
652
+ }
653
+ }
654
+ }
655
+ },
656
+ {
657
+ name: "get_decisions",
658
+ description: "Get architectural decisions with optional limit",
659
+ inputSchema: {
660
+ type: "object",
661
+ properties: {
662
+ limit: {
663
+ type: "number",
664
+ description: "Maximum number of decisions to return"
665
+ },
666
+ projectPath: {
667
+ type: "string",
668
+ description: "Path to project directory (default: current directory)"
669
+ }
670
+ }
671
+ }
672
+ },
673
+ {
674
+ name: "get_patterns",
675
+ description: "Get code patterns and conventions with optional limit",
676
+ inputSchema: {
677
+ type: "object",
678
+ properties: {
679
+ limit: {
680
+ type: "number",
681
+ description: "Maximum number of patterns to return"
682
+ },
683
+ projectPath: {
684
+ type: "string",
685
+ description: "Path to project directory (default: current directory)"
686
+ }
687
+ }
688
+ }
689
+ },
690
+ {
691
+ name: "search_memory",
692
+ description: "Search across all memory entries using full-text search",
693
+ inputSchema: {
694
+ type: "object",
695
+ properties: {
696
+ query: {
697
+ type: "string",
698
+ description: "Search query"
699
+ },
700
+ limit: {
701
+ type: "number",
702
+ description: "Maximum number of results"
703
+ },
704
+ projectPath: {
705
+ type: "string",
706
+ description: "Path to project directory (default: current directory)"
707
+ }
708
+ },
709
+ required: ["query"]
710
+ }
711
+ },
712
+ {
713
+ name: "log_decision",
714
+ description: "Log a new architectural decision",
715
+ inputSchema: {
716
+ type: "object",
717
+ properties: {
718
+ title: {
719
+ type: "string",
720
+ description: "Decision title"
721
+ },
722
+ rationale: {
723
+ type: "string",
724
+ description: "Rationale for the decision"
725
+ },
726
+ alternatives: {
727
+ type: "array",
728
+ items: { type: "string" },
729
+ description: "Alternative approaches considered"
730
+ },
731
+ projectPath: {
732
+ type: "string",
733
+ description: "Path to project directory (default: current directory)"
734
+ }
735
+ },
736
+ required: ["title", "rationale"]
737
+ }
738
+ },
739
+ {
740
+ name: "update_context",
741
+ description: "Update the current working context",
742
+ inputSchema: {
743
+ type: "object",
744
+ properties: {
745
+ feature: {
746
+ type: "string",
747
+ description: "Feature being worked on"
748
+ },
749
+ blockers: {
750
+ type: "array",
751
+ items: { type: "string" },
752
+ description: "Current blockers"
753
+ },
754
+ nextSteps: {
755
+ type: "array",
756
+ items: { type: "string" },
757
+ description: "Next steps to take"
758
+ },
759
+ projectPath: {
760
+ type: "string",
761
+ description: "Path to project directory (default: current directory)"
762
+ }
763
+ }
764
+ }
765
+ },
766
+ {
767
+ name: "save_pattern",
768
+ description: "Save a new code pattern or convention",
769
+ inputSchema: {
770
+ type: "object",
771
+ properties: {
772
+ title: {
773
+ type: "string",
774
+ description: "Pattern title"
775
+ },
776
+ content: {
777
+ type: "string",
778
+ description: "Pattern content/code"
779
+ },
780
+ category: {
781
+ type: "string",
782
+ description: "Pattern category"
783
+ },
784
+ projectPath: {
785
+ type: "string",
786
+ description: "Path to project directory (default: current directory)"
787
+ }
788
+ },
789
+ required: ["title", "content"]
790
+ }
791
+ },
792
+ // ── Auto-capture tools (called by AI silently during conversation) ──
793
+ {
794
+ name: "contxt_auto_capture_decision",
795
+ description: "Silently capture an architectural decision made during the conversation. Call this when the developer chooses one technology/approach over another. Saves as a draft for later review.",
796
+ inputSchema: {
797
+ type: "object",
798
+ properties: {
799
+ decision: { type: "string", description: "The decision title" },
800
+ rationale: { type: "string", description: "Why this decision was made" },
801
+ category: { type: "string", description: "Category (infrastructure, frontend, backend, etc.)" },
802
+ alternatives: { type: "string", description: "Alternatives considered" },
803
+ conversationId: { type: "string", description: "ID to group entries from the same conversation" },
804
+ projectPath: { type: "string", description: "Project directory path" }
805
+ },
806
+ required: ["decision", "rationale"]
807
+ }
808
+ },
809
+ {
810
+ name: "contxt_auto_capture_pattern",
811
+ description: "Silently capture a reusable code pattern or convention. Call this when the developer establishes a pattern that should be followed consistently. Saves as a draft.",
812
+ inputSchema: {
813
+ type: "object",
814
+ properties: {
815
+ pattern: { type: "string", description: "Pattern name" },
816
+ description: { type: "string", description: "What the pattern does and when to use it" },
817
+ category: { type: "string", description: "Pattern category (api, component, testing, etc.)" },
818
+ when: { type: "string", description: "When to apply this pattern" },
819
+ conversationId: { type: "string", description: "ID to group entries from the same conversation" },
820
+ projectPath: { type: "string", description: "Project directory path" }
821
+ },
822
+ required: ["pattern", "description"]
823
+ }
824
+ },
825
+ {
826
+ name: "contxt_capture_discussion",
827
+ description: "Capture an insight that emerged from conversation \u2014 richer than auto_capture_decision. Use when a decision or pattern came out of back-and-forth; captures context, what was rejected, and why. Saves as a draft.",
828
+ inputSchema: {
829
+ type: "object",
830
+ properties: {
831
+ type: { type: "string", enum: ["decision", "pattern", "context"], description: "Entry type" },
832
+ title: { type: "string", description: "Short title for the decision or pattern" },
833
+ summary: { type: "string", description: "Full statement of the decision or pattern" },
834
+ context: { type: "string", description: "What problem or question prompted this" },
835
+ rejected: { type: "string", description: "What was considered and rejected, and why" },
836
+ conversationId: { type: "string", description: "ID to group entries from the same conversation" },
837
+ projectPath: { type: "string", description: "Project directory path" }
838
+ },
839
+ required: ["type", "title", "summary", "context"]
840
+ }
841
+ },
842
+ {
843
+ name: "contxt_update_session",
844
+ description: "Log what was accomplished in this AI conversation session. Call at the end of a session.",
845
+ inputSchema: {
846
+ type: "object",
847
+ properties: {
848
+ summary: { type: "string", description: "Summary of what was accomplished" },
849
+ filesChanged: { type: "array", items: { type: "string" }, description: "Files modified" },
850
+ decisions: { type: "array", items: { type: "string" }, description: "Key decisions made" },
851
+ conversationId: { type: "string", description: "ID to group entries from the same conversation" },
852
+ projectPath: { type: "string", description: "Project directory path" }
853
+ },
854
+ required: ["summary"]
855
+ }
856
+ },
857
+ {
858
+ name: "contxt_get_drafts",
859
+ description: "Get pending draft entries awaiting developer review. Show when asked what was captured.",
860
+ inputSchema: {
861
+ type: "object",
862
+ properties: {
863
+ source: { type: "string", description: "Filter by source (auto, hooks, scan)" },
864
+ projectPath: { type: "string", description: "Project directory path" }
865
+ }
866
+ }
867
+ },
868
+ {
869
+ name: "contxt_confirm_draft",
870
+ description: "Confirm and activate a draft entry when the developer explicitly approves it.",
871
+ inputSchema: {
872
+ type: "object",
873
+ properties: {
874
+ id: { type: "string", description: "Draft entry ID to confirm" },
875
+ projectPath: { type: "string", description: "Project directory path" }
876
+ },
877
+ required: ["id"]
878
+ }
879
+ }
880
+ ]
881
+ };
882
+ });
883
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
884
+ const { name, arguments: args } = request.params;
885
+ try {
886
+ let result;
887
+ switch (name) {
888
+ case "suggest_context":
889
+ result = await suggestContext(args || {});
890
+ break;
891
+ case "get_project_context":
892
+ result = await getProjectContext(args || {});
893
+ break;
894
+ case "get_decisions":
895
+ result = await getDecisions(args || {});
896
+ break;
897
+ case "get_patterns":
898
+ result = await getPatterns(args || {});
899
+ break;
900
+ case "search_memory":
901
+ if (!args?.query) {
902
+ throw new Error("query parameter is required");
903
+ }
904
+ result = await searchMemory(args);
905
+ break;
906
+ case "log_decision":
907
+ if (!args?.title || !args?.rationale) {
908
+ throw new Error("title and rationale parameters are required");
909
+ }
910
+ result = await logDecision(args);
911
+ break;
912
+ case "update_context":
913
+ result = await updateContext(args || {});
914
+ break;
915
+ case "save_pattern":
916
+ if (!args?.title || !args?.content) {
917
+ throw new Error("title and content parameters are required");
918
+ }
919
+ result = await savePattern(args);
920
+ break;
921
+ // Auto-capture tools
922
+ case "contxt_capture_discussion":
923
+ if (!args?.type || !args?.title || !args?.summary || !args?.context) {
924
+ throw new Error("type, title, summary, and context parameters are required");
925
+ }
926
+ result = await captureDiscussion(args);
927
+ break;
928
+ case "contxt_auto_capture_decision":
929
+ if (!args?.decision || !args?.rationale) {
930
+ throw new Error("decision and rationale parameters are required");
931
+ }
932
+ result = await autoCaptureDecision(args);
933
+ break;
934
+ case "contxt_auto_capture_pattern":
935
+ if (!args?.pattern || !args?.description) {
936
+ throw new Error("pattern and description parameters are required");
937
+ }
938
+ result = await autoCapturePattern(args);
939
+ break;
940
+ case "contxt_update_session":
941
+ if (!args?.summary) {
942
+ throw new Error("summary parameter is required");
943
+ }
944
+ result = await updateSession(args);
945
+ break;
946
+ case "contxt_get_drafts":
947
+ result = await getDrafts(args || {});
948
+ break;
949
+ case "contxt_confirm_draft":
950
+ if (!args?.id) {
951
+ throw new Error("id parameter is required");
952
+ }
953
+ result = await confirmDraft(args);
954
+ break;
955
+ default:
956
+ throw new Error(`Unknown tool: ${name}`);
957
+ }
958
+ return {
959
+ content: [
960
+ {
961
+ type: "text",
962
+ text: result
963
+ }
964
+ ]
965
+ };
966
+ } catch (error) {
967
+ const errorMessage = error instanceof Error ? error.message : String(error);
968
+ return {
969
+ content: [
970
+ {
971
+ type: "text",
972
+ text: `Error: ${errorMessage}`
973
+ }
974
+ ],
975
+ isError: true
976
+ };
977
+ }
978
+ });
979
+ async function main() {
980
+ const transport = new StdioServerTransport();
981
+ await server.connect(transport);
982
+ console.error("Contxt MCP Server running on stdio");
983
+ }
984
+ main().catch((error) => {
985
+ console.error("Fatal error:", error);
986
+ process.exit(1);
987
+ });
988
+ //# sourceMappingURL=server.js.map