decision-os-mcp 0.3.2 → 0.4.0

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/index.ts DELETED
@@ -1,932 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- Tool,
9
- } from "@modelcontextprotocol/sdk/types.js";
10
- import { ZodError } from "zod";
11
- import { HierarchicalDecisionOSStorage, createHierarchicalStorage } from "./hierarchical-storage.js";
12
- import {
13
- LogPressureInput,
14
- CreateCaseInput,
15
- CloseCaseInput,
16
- GetFoundationsInput,
17
- PromoteToFoundationInput,
18
- ElevateFoundationInput,
19
- ValidateFoundationInput,
20
- CheckPolicyInput,
21
- SearchPressuresInput,
22
- SetActiveCaseInput,
23
- QuickPressureInput,
24
- } from "./schemas.js";
25
-
26
- // ============================================================================
27
- // CONFIGURATION
28
- // ============================================================================
29
-
30
- // NOTE:
31
- // - Prefer explicit DECISION_OS_PATH (Cursor config sets this per-project)
32
- // - Fall back to process.cwd() and walk up to find .decision-os
33
- // - The hierarchical storage will discover GLOBAL (~/.decision-os) automatically
34
- const DEFAULT_WORKSPACE_PATH =
35
- process.env.DECISION_OS_PATH ??
36
- (process.env.PWD ?? process.cwd());
37
-
38
- // Cache of storage instances per workspace path
39
- const storageCache = new Map<string, HierarchicalDecisionOSStorage>();
40
-
41
- /**
42
- * Get or create a hierarchical storage instance for the given workspace path.
43
- */
44
- async function getStorage(workspacePath?: string): Promise<HierarchicalDecisionOSStorage> {
45
- const path = workspacePath ?? DEFAULT_WORKSPACE_PATH;
46
-
47
- if (!storageCache.has(path)) {
48
- const storage = createHierarchicalStorage(path);
49
- await storage.initialize();
50
- storageCache.set(path, storage);
51
- }
52
-
53
- return storageCache.get(path)!;
54
- }
55
-
56
- // ============================================================================
57
- // TOOL DEFINITIONS
58
- // ============================================================================
59
-
60
- // Common workspace_path property for all tools
61
- const WORKSPACE_PATH_PROP = {
62
- workspace_path: {
63
- type: "string",
64
- description: "Path to workspace (optional, auto-detected from DECISION_OS_PATH or cwd)",
65
- },
66
- } as const;
67
-
68
- const TOOLS: Tool[] = [
69
- {
70
- name: "get_context",
71
- description: `Get the current Decision OS context. Call this at the start of a task to understand:
72
- - Active case (if any)
73
- - Recent pressure events
74
- - Relevant foundations (from both project and global scopes)
75
- - Any conflicts between project and global foundations
76
-
77
- Returns the project name, active case details, applicable foundations with source scope, and detected conflicts.`,
78
- inputSchema: {
79
- type: "object",
80
- properties: {
81
- ...WORKSPACE_PATH_PROP,
82
- },
83
- },
84
- annotations: {
85
- title: "Get Context",
86
- readOnlyHint: true,
87
- destructiveHint: false,
88
- idempotentHint: true,
89
- openWorldHint: false,
90
- },
91
- },
92
- {
93
- name: "log_pressure",
94
- description: `Log a pressure event when reality differs from expectation.
95
-
96
- Call this IMMEDIATELY when you encounter something unexpected:
97
- - Something failed that you predicted would work
98
- - You had to change approach mid-implementation
99
- - You discovered a constraint not in your initial context
100
-
101
- This is the primary learning mechanism. Be specific about what you expected vs what happened.`,
102
- inputSchema: {
103
- type: "object",
104
- properties: {
105
- case_id: {
106
- type: "string",
107
- description: "Case ID (uses active case if not specified)",
108
- },
109
- expected: {
110
- type: "string",
111
- description: "What you assumed would happen",
112
- },
113
- actual: {
114
- type: "string",
115
- description: "What actually happened",
116
- },
117
- adaptation: {
118
- type: "string",
119
- description: "What you changed in response",
120
- },
121
- remember: {
122
- type: "string",
123
- description:
124
- "One-liner summary for future reference (potential foundation)",
125
- },
126
- pressure_type: {
127
- type: "string",
128
- enum: [
129
- "CHANGE",
130
- "IRREVERSIBILITY",
131
- "COGNITIVE",
132
- "COUPLING",
133
- "OPERATIONAL",
134
- "EXTERNAL",
135
- ],
136
- description: "Category of pressure",
137
- },
138
- context_tags: {
139
- type: "array",
140
- items: { type: "string" },
141
- description:
142
- "Tags for context (e.g., BACKEND, SUPABASE, AUTH)",
143
- },
144
- },
145
- required: ["expected", "actual", "adaptation", "remember"],
146
- },
147
- annotations: {
148
- title: "Log Pressure Event",
149
- readOnlyHint: false,
150
- destructiveHint: true,
151
- idempotentHint: false,
152
- openWorldHint: false,
153
- },
154
- },
155
- {
156
- name: "quick_pressure",
157
- description: `Quick-capture a pressure event with minimal friction.
158
-
159
- Use this when you want to capture a surprise fast without filling all fields.
160
- Only requires expected and actual — adaptation and remember are optional
161
- (remember is auto-generated from expected vs actual if omitted).
162
-
163
- Prefer this over log_pressure when in the middle of debugging or rapid iteration.
164
- Capturing too much is better than missing surprises.`,
165
- inputSchema: {
166
- type: "object",
167
- properties: {
168
- case_id: {
169
- type: "string",
170
- description: "Case ID (uses active case if not specified)",
171
- },
172
- expected: {
173
- type: "string",
174
- description: "What you assumed would happen",
175
- },
176
- actual: {
177
- type: "string",
178
- description: "What actually happened",
179
- },
180
- remember: {
181
- type: "string",
182
- description: "One-liner summary (auto-generated if omitted)",
183
- },
184
- adaptation: {
185
- type: "string",
186
- description: "What you changed (optional for quick capture)",
187
- },
188
- pressure_type: {
189
- type: "string",
190
- enum: [
191
- "CHANGE",
192
- "IRREVERSIBILITY",
193
- "COGNITIVE",
194
- "COUPLING",
195
- "OPERATIONAL",
196
- "EXTERNAL",
197
- ],
198
- description: "Category of pressure (optional)",
199
- },
200
- context_tags: {
201
- type: "array",
202
- items: { type: "string" },
203
- description: "Tags for context (optional)",
204
- },
205
- },
206
- required: ["expected", "actual"],
207
- },
208
- annotations: {
209
- title: "Quick Pressure Capture",
210
- readOnlyHint: false,
211
- destructiveHint: true,
212
- idempotentHint: false,
213
- openWorldHint: false,
214
- },
215
- },
216
- {
217
- name: "create_case",
218
- description: `Create a new case (unit of work) and set it as active.
219
-
220
- A case represents a bounded piece of work: feature, bugfix, refactor, spike.
221
- Creating a case provides context for logging pressure events.`,
222
- inputSchema: {
223
- type: "object",
224
- properties: {
225
- title: {
226
- type: "string",
227
- description: "Short descriptive title",
228
- },
229
- goal: {
230
- type: "string",
231
- description: "What success looks like",
232
- },
233
- signals: {
234
- type: "object",
235
- description: "Context signals (risk_level, reversibility, etc.)",
236
- properties: {
237
- risk_level: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
238
- reversibility: { type: "string", enum: ["EASY", "MEDIUM", "HARD"] },
239
- change_frequency: {
240
- type: "string",
241
- enum: ["RARE", "OCCASIONAL", "FREQUENT"],
242
- },
243
- affected_surface: {
244
- type: "array",
245
- items: { type: "string" },
246
- },
247
- novelty: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
248
- repo_scope: { type: "string" },
249
- },
250
- },
251
- touched_areas: {
252
- type: "array",
253
- items: { type: "string" },
254
- description: "Areas of codebase being touched",
255
- },
256
- },
257
- required: ["title"],
258
- },
259
- annotations: {
260
- title: "Create Case",
261
- readOnlyHint: false,
262
- destructiveHint: true,
263
- idempotentHint: false,
264
- openWorldHint: false,
265
- },
266
- },
267
- {
268
- name: "close_case",
269
- description: `Close a case with outcome signals.
270
-
271
- Call this when work is complete. The regret score (0-3) is critical:
272
- - 0: Would choose the same approach again
273
- - 1: Minor improvements possible
274
- - 2: Significant regret, different approach likely better
275
- - 3: Strong regret, wrong posture/approach`,
276
- inputSchema: {
277
- type: "object",
278
- properties: {
279
- case_id: {
280
- type: "string",
281
- description: "Case ID (uses active case if not specified)",
282
- },
283
- regret: {
284
- type: ["string", "number"],
285
- enum: ["0", "1", "2", "3", 0, 1, 2, 3],
286
- description: "0=would choose same, 3=strong regret",
287
- },
288
- notes: {
289
- type: "string",
290
- description: "Outcome notes, lessons learned",
291
- },
292
- regressions: {
293
- type: "string",
294
- enum: ["NONE", "MINOR", "MAJOR"],
295
- description: "Any regressions introduced",
296
- },
297
- },
298
- required: ["regret"],
299
- },
300
- annotations: {
301
- title: "Close Case",
302
- readOnlyHint: false,
303
- destructiveHint: true,
304
- idempotentHint: false,
305
- openWorldHint: false,
306
- },
307
- },
308
- {
309
- name: "set_active_case",
310
- description: `Set the active case for the current session.
311
-
312
- Pressure events will be logged to the active case by default.`,
313
- inputSchema: {
314
- type: "object",
315
- properties: {
316
- case_id: {
317
- type: "string",
318
- description: "Case ID to set as active",
319
- },
320
- },
321
- required: ["case_id"],
322
- },
323
- annotations: {
324
- title: "Set Active Case",
325
- readOnlyHint: false,
326
- destructiveHint: true,
327
- idempotentHint: true,
328
- openWorldHint: false,
329
- },
330
- },
331
- {
332
- name: "get_foundations",
333
- description: `Get project foundations (compressed learnings).
334
-
335
- Foundations are promoted from repeated pressure events. They represent
336
- project-specific defaults with confidence levels (0-3).
337
-
338
- Query by context tags or minimum confidence to get relevant foundations.`,
339
- inputSchema: {
340
- type: "object",
341
- properties: {
342
- context_tags: {
343
- type: "array",
344
- items: { type: "string" },
345
- description: "Filter by context tags",
346
- },
347
- min_confidence: {
348
- type: "number",
349
- minimum: 0,
350
- maximum: 3,
351
- description: "Minimum confidence level (0-3)",
352
- },
353
- },
354
- },
355
- annotations: {
356
- title: "Get Foundations",
357
- readOnlyHint: true,
358
- destructiveHint: false,
359
- idempotentHint: true,
360
- openWorldHint: false,
361
- },
362
- },
363
- {
364
- name: "search_pressures",
365
- description: `Search past pressure events.
366
-
367
- Use this to find relevant past learnings before implementing something.
368
- Searches across expected, actual, adaptation, remember, and context_tags.`,
369
- inputSchema: {
370
- type: "object",
371
- properties: {
372
- query: {
373
- type: "string",
374
- description: "Search query",
375
- },
376
- },
377
- required: ["query"],
378
- },
379
- annotations: {
380
- title: "Search Pressure Events",
381
- readOnlyHint: true,
382
- destructiveHint: false,
383
- idempotentHint: true,
384
- openWorldHint: false,
385
- },
386
- },
387
- {
388
- name: "check_policy",
389
- description: `Check what policy requires for given signals.
390
-
391
- Returns:
392
- - Whether MINIMAL vs ROBUST options comparison is required
393
- - Required validation level (BASIC/STANDARD/STRICT)
394
- - Any warnings or requirements
395
-
396
- Call this before implementation to understand constraints.`,
397
- inputSchema: {
398
- type: "object",
399
- properties: {
400
- signals: {
401
- type: "object",
402
- description: "Context signals to check",
403
- properties: {
404
- risk_level: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
405
- reversibility: { type: "string", enum: ["EASY", "MEDIUM", "HARD"] },
406
- repo_scope: { type: "string" },
407
- affected_surface: {
408
- type: "array",
409
- items: { type: "string" },
410
- },
411
- uncertainty: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
412
- },
413
- },
414
- },
415
- required: ["signals"],
416
- },
417
- annotations: {
418
- title: "Check Policy",
419
- readOnlyHint: true,
420
- destructiveHint: false,
421
- idempotentHint: true,
422
- openWorldHint: false,
423
- },
424
- },
425
- {
426
- name: "promote_to_foundation",
427
- description: `Promote pressure events to a foundation.
428
-
429
- When you see a pattern across multiple pressure events, promote them
430
- to a foundation. Foundations start with confidence 1/3 and evolve
431
- based on future outcomes.
432
-
433
- Use scope: "GLOBAL" to create a universal foundation in ~/.decision-os
434
- that applies across all projects. Default is "PROJECT" (local only).`,
435
- inputSchema: {
436
- type: "object",
437
- properties: {
438
- ...WORKSPACE_PATH_PROP,
439
- title: {
440
- type: "string",
441
- description: "Foundation title",
442
- },
443
- default_behavior: {
444
- type: "string",
445
- description: "What to do when this applies",
446
- },
447
- context_tags: {
448
- type: "array",
449
- items: { type: "string" },
450
- description: "When this foundation applies",
451
- },
452
- counter_contexts: {
453
- type: "array",
454
- items: { type: "string" },
455
- description: "When this foundation does NOT apply",
456
- },
457
- source_pressures: {
458
- type: "array",
459
- items: { type: "string" },
460
- description: "PE-IDs that led to this foundation",
461
- },
462
- exit_criteria: {
463
- type: "string",
464
- description: "When to reconsider this foundation",
465
- },
466
- scope: {
467
- type: "string",
468
- enum: ["GLOBAL", "PROJECT"],
469
- description: "GLOBAL (universal) or PROJECT (local). Default: PROJECT",
470
- },
471
- },
472
- required: ["title", "default_behavior", "context_tags", "source_pressures"],
473
- },
474
- annotations: {
475
- title: "Promote to Foundation",
476
- readOnlyHint: false,
477
- destructiveHint: true,
478
- idempotentHint: false,
479
- openWorldHint: false,
480
- },
481
- },
482
- {
483
- name: "elevate_foundation",
484
- description: `Elevate a project foundation to global scope.
485
-
486
- Use when a project-specific learning has proven universal.
487
- The foundation will be copied to ~/.decision-os with a GF- prefix.
488
-
489
- Strong signal for elevation:
490
- - Foundation has confidence 2+
491
- - Pattern has been validated across multiple implementations
492
- - Learning applies regardless of tech stack`,
493
- inputSchema: {
494
- type: "object",
495
- properties: {
496
- ...WORKSPACE_PATH_PROP,
497
- foundation_id: {
498
- type: "string",
499
- description: "ID of the foundation to elevate (e.g., F-0001)",
500
- },
501
- reason: {
502
- type: "string",
503
- description: "Why this foundation is universal",
504
- },
505
- },
506
- required: ["foundation_id"],
507
- },
508
- annotations: {
509
- title: "Elevate Foundation to Global",
510
- readOnlyHint: false,
511
- destructiveHint: true,
512
- idempotentHint: false,
513
- openWorldHint: false,
514
- },
515
- },
516
- {
517
- name: "validate_foundation",
518
- description: `Validate that a global foundation applies in the current project.
519
-
520
- Increases confidence and adds current project to validated_in list.
521
- Strong signal for keeping foundation at global scope.
522
-
523
- Use when you observe a global foundation being correct in a new project context.`,
524
- inputSchema: {
525
- type: "object",
526
- properties: {
527
- ...WORKSPACE_PATH_PROP,
528
- foundation_id: {
529
- type: "string",
530
- description: "ID of the foundation to validate",
531
- },
532
- validation_notes: {
533
- type: "string",
534
- description: "Notes on how this applies in current project",
535
- },
536
- },
537
- required: ["foundation_id"],
538
- },
539
- annotations: {
540
- title: "Validate Foundation",
541
- readOnlyHint: false,
542
- destructiveHint: true,
543
- idempotentHint: true,
544
- openWorldHint: false,
545
- },
546
- },
547
- {
548
- name: "suggest_review",
549
- description: `Review the project for unextracted learnings and forgetting opportunities.
550
-
551
- Call this periodically (e.g., after closing several cases) to:
552
- - Find clusters of unpromoted pressure events that could become foundations
553
- - Identify cases blocking forgetting (regret 0 but unpromoted PEs remain — promote or discard to unblock)
554
- - Flag high-regret cases with no PEs (possible missed captures)
555
-
556
- This is the retrospective mechanism. Knowledge lives in foundations, not cases.`,
557
- inputSchema: {
558
- type: "object",
559
- properties: {
560
- ...WORKSPACE_PATH_PROP,
561
- },
562
- },
563
- annotations: {
564
- title: "Suggest Review",
565
- readOnlyHint: true,
566
- destructiveHint: false,
567
- idempotentHint: true,
568
- openWorldHint: false,
569
- },
570
- },
571
- {
572
- name: "list_cases",
573
- description: `List all cases in the project.
574
-
575
- Returns all cases with their status, useful for finding past work
576
- or setting an active case.`,
577
- inputSchema: {
578
- type: "object",
579
- properties: {},
580
- },
581
- annotations: {
582
- title: "List Cases",
583
- readOnlyHint: true,
584
- destructiveHint: false,
585
- idempotentHint: true,
586
- openWorldHint: false,
587
- },
588
- },
589
- ];
590
-
591
- // ============================================================================
592
- // SERVER
593
- // ============================================================================
594
-
595
- async function main() {
596
- // Pre-initialize default storage to fail fast if config is broken
597
- try {
598
- await getStorage();
599
- console.error(`Decision OS initialized from ${DEFAULT_WORKSPACE_PATH}`);
600
- } catch (error) {
601
- console.error(
602
- `Failed to initialize Decision OS from ${DEFAULT_WORKSPACE_PATH}:`,
603
- error
604
- );
605
- console.error(
606
- "Set DECISION_OS_PATH environment variable to your .decision-os folder, " +
607
- "or ensure ~/.decision-os exists for global foundations."
608
- );
609
- process.exit(1);
610
- }
611
-
612
- const server = new Server(
613
- {
614
- name: "decision-os",
615
- version: "0.1.0",
616
- },
617
- {
618
- capabilities: {
619
- tools: {},
620
- },
621
- }
622
- );
623
-
624
- // List tools handler
625
- server.setRequestHandler(ListToolsRequestSchema, async () => {
626
- return { tools: TOOLS };
627
- });
628
-
629
- // Call tool handler
630
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
631
- const { name, arguments: args } = request.params;
632
- const rawArgs = (args ?? {}) as Record<string, unknown>;
633
- const workspacePath = rawArgs.workspace_path as string | undefined;
634
-
635
- try {
636
- const storage = await getStorage(workspacePath);
637
-
638
- switch (name) {
639
- case "get_context": {
640
- const context = await storage.getContext();
641
-
642
- // Format conflicts for visibility
643
- let conflictWarning = "";
644
- if (context.conflicts.length > 0) {
645
- conflictWarning = "\n\n⚠️ FOUNDATION CONFLICTS DETECTED:\n" +
646
- context.conflicts.map(c =>
647
- `- ${c.title}: ${c.recommendation}`
648
- ).join("\n");
649
- }
650
-
651
- // Annotate foundations with scope
652
- const annotatedFoundations = context.relevant_foundations.map(f => ({
653
- ...f,
654
- scope_indicator: f._source_scope === "GLOBAL" ? "🌐 GLOBAL" : "📁 PROJECT",
655
- }));
656
-
657
- return {
658
- content: [
659
- {
660
- type: "text",
661
- text: JSON.stringify({
662
- ...context,
663
- relevant_foundations: annotatedFoundations,
664
- }, null, 2) + conflictWarning,
665
- },
666
- ],
667
- };
668
- }
669
-
670
- case "log_pressure": {
671
- const input = LogPressureInput.parse(rawArgs);
672
- const pressure = await storage.logPressure(input);
673
- return {
674
- content: [
675
- {
676
- type: "text",
677
- text: `Logged pressure event ${pressure.id}:\n${JSON.stringify(pressure, null, 2)}`,
678
- },
679
- ],
680
- };
681
- }
682
-
683
- case "quick_pressure": {
684
- const input = QuickPressureInput.parse(rawArgs);
685
- const remember = input.remember ??
686
- `Expected: ${input.expected.slice(0, 40)}… but: ${input.actual.slice(0, 40)}…`;
687
- const adaptation = input.adaptation ?? "(captured for review)";
688
- const pressure = await storage.logPressure({
689
- case_id: input.case_id,
690
- expected: input.expected,
691
- actual: input.actual,
692
- adaptation,
693
- remember,
694
- pressure_type: input.pressure_type,
695
- context_tags: input.context_tags,
696
- });
697
- return {
698
- content: [
699
- {
700
- type: "text",
701
- text: `⚡ Quick-captured pressure event ${pressure.id}:\n${JSON.stringify(pressure, null, 2)}`,
702
- },
703
- ],
704
- };
705
- }
706
-
707
- case "create_case": {
708
- const input = CreateCaseInput.parse(rawArgs);
709
- const caseData = await storage.createCase({
710
- title: input.title,
711
- goal: input.goal,
712
- signals: input.signals ? { context: input.signals } : undefined,
713
- touched_areas: input.touched_areas,
714
- });
715
- return {
716
- content: [
717
- {
718
- type: "text",
719
- text: `Created case ${caseData.id} (now active):\n${JSON.stringify(caseData, null, 2)}`,
720
- },
721
- ],
722
- };
723
- }
724
-
725
- case "close_case": {
726
- const input = CloseCaseInput.parse(rawArgs);
727
- const caseId = input.case_id || storage.getActiveCase();
728
- if (!caseId) {
729
- throw new Error("No active case. Specify case_id.");
730
- }
731
- const result = await storage.closeCase(caseId, {
732
- regret: input.regret,
733
- notes: input.notes,
734
- regressions: input.regressions,
735
- });
736
- const forgottenMsg = result.forgotten
737
- ? `\n\n🧹 Case forgotten — no novel pressure retained. Knowledge lives in foundations.`
738
- : "";
739
- return {
740
- content: [
741
- {
742
- type: "text",
743
- text: `Closed case ${result.case.id}:\n${JSON.stringify(result.case, null, 2)}${forgottenMsg}`,
744
- },
745
- ],
746
- };
747
- }
748
-
749
- case "set_active_case": {
750
- const input = SetActiveCaseInput.parse(rawArgs);
751
- const caseData = await storage.getCase(input.case_id);
752
- if (!caseData) {
753
- throw new Error(`Case not found: ${input.case_id}`);
754
- }
755
- await storage.setActiveCase(input.case_id);
756
- return {
757
- content: [
758
- {
759
- type: "text",
760
- text: `Set active case to ${input.case_id}: "${caseData.title}"`,
761
- },
762
- ],
763
- };
764
- }
765
-
766
- case "get_foundations": {
767
- const input = GetFoundationsInput.parse(rawArgs);
768
- const foundations = await storage.getFoundations(input);
769
-
770
- // Annotate with scope indicator
771
- const annotated = foundations.map(f => ({
772
- ...f,
773
- scope_indicator: f._source_scope === "GLOBAL" ? "🌐 GLOBAL" : "📁 PROJECT",
774
- }));
775
-
776
- return {
777
- content: [
778
- {
779
- type: "text",
780
- text:
781
- annotated.length > 0
782
- ? JSON.stringify(annotated, null, 2)
783
- : "No foundations found matching criteria.",
784
- },
785
- ],
786
- };
787
- }
788
-
789
- case "search_pressures": {
790
- const input = SearchPressuresInput.parse(rawArgs);
791
- const pressures = await storage.searchPressures(input.query);
792
- return {
793
- content: [
794
- {
795
- type: "text",
796
- text:
797
- pressures.length > 0
798
- ? `Found ${pressures.length} pressure events:\n${JSON.stringify(pressures, null, 2)}`
799
- : "No pressure events found matching query.",
800
- },
801
- ],
802
- };
803
- }
804
-
805
- case "check_policy": {
806
- const input = CheckPolicyInput.parse(rawArgs);
807
- const result = storage.checkPolicy(input.signals);
808
- return {
809
- content: [
810
- {
811
- type: "text",
812
- text: JSON.stringify(result, null, 2),
813
- },
814
- ],
815
- };
816
- }
817
-
818
- case "promote_to_foundation": {
819
- const input = PromoteToFoundationInput.parse(rawArgs);
820
- const config = await storage.getConfig();
821
- const foundation = await storage.promoteToFoundation({
822
- ...input,
823
- origin_project: config.project,
824
- });
825
-
826
- const scopeLabel = foundation.scope === "GLOBAL" ? "🌐 GLOBAL" : "📁 PROJECT";
827
- return {
828
- content: [
829
- {
830
- type: "text",
831
- text: `Created ${scopeLabel} foundation ${foundation.id} (confidence: 1/3):\n${JSON.stringify(foundation, null, 2)}`,
832
- },
833
- ],
834
- };
835
- }
836
-
837
- case "elevate_foundation": {
838
- const input = ElevateFoundationInput.parse(rawArgs);
839
- const previousId = input.foundation_id;
840
- const foundation = await storage.elevateFoundation(input);
841
- return {
842
- content: [
843
- {
844
- type: "text",
845
- text: `🌐 Elevated to GLOBAL foundation ${foundation.id}:\n${JSON.stringify(foundation, null, 2)}\n\n` +
846
- `Project copy ${previousId} retired — knowledge now lives in global scope.\n` +
847
- `This foundation will now apply across all projects.`,
848
- },
849
- ],
850
- };
851
- }
852
-
853
- case "validate_foundation": {
854
- const input = ValidateFoundationInput.parse(rawArgs);
855
- const foundation = await storage.validateFoundation(input);
856
- return {
857
- content: [
858
- {
859
- type: "text",
860
- text: `✓ Validated foundation ${foundation.id} in this project.\n` +
861
- `Validated in ${foundation.validated_in?.length ?? 0} project(s): ${foundation.validated_in?.join(", ") ?? "none"}\n` +
862
- `Confidence: ${foundation.confidence}/3\n\n${JSON.stringify(foundation, null, 2)}`,
863
- },
864
- ],
865
- };
866
- }
867
-
868
- case "suggest_review": {
869
- const review = await storage.suggestReview();
870
- return {
871
- content: [
872
- {
873
- type: "text",
874
- text: `Review Summary: ${review.summary}\n\n${JSON.stringify(review, null, 2)}`,
875
- },
876
- ],
877
- };
878
- }
879
-
880
- case "list_cases": {
881
- const cases = await storage.listCases();
882
- const activeCase = storage.getActiveCase();
883
- const summary = cases.map((c) => ({
884
- id: c.id,
885
- title: c.title,
886
- status: c.status,
887
- active: c.id === activeCase,
888
- }));
889
- return {
890
- content: [
891
- {
892
- type: "text",
893
- text:
894
- cases.length > 0
895
- ? JSON.stringify(summary, null, 2)
896
- : "No cases found.",
897
- },
898
- ],
899
- };
900
- }
901
-
902
- default:
903
- throw new Error(`Unknown tool: ${name}`);
904
- }
905
- } catch (error) {
906
- const message =
907
- error instanceof ZodError
908
- ? `Invalid input: ${error.issues
909
- .map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
910
- .join("; ")}`
911
- : error instanceof Error
912
- ? error.message
913
- : String(error);
914
- return {
915
- content: [
916
- {
917
- type: "text",
918
- text: `Error: ${message}`,
919
- },
920
- ],
921
- isError: true,
922
- };
923
- }
924
- });
925
-
926
- // Start server
927
- const transport = new StdioServerTransport();
928
- await server.connect(transport);
929
- console.error("Decision OS MCP server running");
930
- }
931
-
932
- main().catch(console.error);