@jungjaehoon/mama-server 1.12.1 → 1.13.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/README.md +20 -18
- package/package.json +3 -2
- package/src/server.js +10 -88
- package/src/tools/case-timeline-range.js +77 -0
- package/src/tools/index.js +7 -0
- package/src/tools/search-decisions-and-contracts.js +109 -0
package/README.md
CHANGED
|
@@ -75,23 +75,25 @@ Any MCP-compatible client can use MAMA with:
|
|
|
75
75
|
npx -y @jungjaehoon/mama-server
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
## Available Tools (v1.
|
|
79
|
-
|
|
80
|
-
The MCP server exposes
|
|
81
|
-
|
|
82
|
-
| Tool
|
|
83
|
-
|
|
|
84
|
-
| `save_decision`
|
|
85
|
-
| `recall_decision`
|
|
86
|
-
| `suggest_decision`
|
|
87
|
-
| `list_decisions`
|
|
88
|
-
| `update_outcome`
|
|
89
|
-
| `search_narrative`
|
|
90
|
-
| `ingest_conversation`
|
|
91
|
-
| `save_checkpoint`
|
|
92
|
-
| `load_checkpoint`
|
|
93
|
-
| `generate_quality_report`
|
|
94
|
-
| `get_restart_metrics`
|
|
78
|
+
## Available Tools (v1.13)
|
|
79
|
+
|
|
80
|
+
The MCP server exposes 13 tools:
|
|
81
|
+
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
| -------------------------------- | ----------------------------------------------------------------------- |
|
|
84
|
+
| `save_decision` | Save decision with optional scopes and event_date for temporal tracking |
|
|
85
|
+
| `recall_decision` | Recall decision history by topic, scope-filtered via recallMemory v2 |
|
|
86
|
+
| `suggest_decision` | Semantic search for relevant past decisions, scope-aware |
|
|
87
|
+
| `list_decisions` | List recent decisions, scope-filterable |
|
|
88
|
+
| `update_outcome` | Update decision outcome (case-insensitive: success/failed/partial) |
|
|
89
|
+
| `search_narrative` | Narrative search with link expansion (depth 0-2) |
|
|
90
|
+
| `ingest_conversation` | Ingest conversation messages into memory with optional LLM extraction |
|
|
91
|
+
| `save_checkpoint` | Save session checkpoint for later resumption |
|
|
92
|
+
| `load_checkpoint` | Resume previous session |
|
|
93
|
+
| `generate_quality_report` | Quality metrics and observability report |
|
|
94
|
+
| `get_restart_metrics` | Restart success rate and latency monitoring |
|
|
95
|
+
| `search_decisions_and_contracts` | Decision + contract lookup for tooling and hook pipelines |
|
|
96
|
+
| `case_timeline_range` | Read bounded case timeline windows for case-first workflows |
|
|
95
97
|
|
|
96
98
|
### Edge Types
|
|
97
99
|
|
|
@@ -261,4 +263,4 @@ MAMA was inspired by [mem0](https://github.com/mem0ai/mem0) (Apache 2.0). While
|
|
|
261
263
|
---
|
|
262
264
|
|
|
263
265
|
**Author:** SpineLift Team
|
|
264
|
-
**Version:** 1.
|
|
266
|
+
**Version:** 1.13.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "MAMA MCP Server - Memory-Augmented MCP Assistant for Claude Code & Desktop",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
"node": ">=22.13.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jungjaehoon/mama-core": "^1.
|
|
42
|
+
"@jungjaehoon/mama-core": "^1.5.0",
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.0.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"better-sqlite3": "^12.8.0",
|
|
46
47
|
"vitest": "^1.0.0"
|
|
47
48
|
},
|
|
48
49
|
"files": [
|
package/src/server.js
CHANGED
|
@@ -26,7 +26,6 @@ const {
|
|
|
26
26
|
CallToolRequestSchema,
|
|
27
27
|
ListToolsRequestSchema,
|
|
28
28
|
} = require('@modelcontextprotocol/sdk/types.js');
|
|
29
|
-
const path = require('path');
|
|
30
29
|
|
|
31
30
|
// Import all MAMA tools from src/tools/ — single source of truth for tool definitions
|
|
32
31
|
const { createMemoryTools } = require('./tools/index.js');
|
|
@@ -35,8 +34,6 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
|
|
|
35
34
|
|
|
36
35
|
// Import core modules from mama-core
|
|
37
36
|
const { initDB } = require('@jungjaehoon/mama-core/db-manager');
|
|
38
|
-
const { generateEmbedding } = require('@jungjaehoon/mama-core/embeddings');
|
|
39
|
-
const { vectorSearch } = require('@jungjaehoon/mama-core/memory-store');
|
|
40
37
|
const embeddingServer = require('@jungjaehoon/mama-core/embedding-server');
|
|
41
38
|
const http = require('http');
|
|
42
39
|
|
|
@@ -369,24 +366,17 @@ After failure → save a NEW decision with same topic to create evolution histor
|
|
|
369
366
|
required: ['id', 'outcome'],
|
|
370
367
|
},
|
|
371
368
|
},
|
|
372
|
-
// 4. SEARCH_DECISIONS_AND_CONTRACTS — PreToolUse hook RPC
|
|
369
|
+
// 4. SEARCH_DECISIONS_AND_CONTRACTS — PreToolUse hook RPC (defined in src/tools/)
|
|
373
370
|
{
|
|
374
|
-
name:
|
|
375
|
-
description:
|
|
376
|
-
inputSchema:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
contractLimit: { type: 'number', description: 'Max contracts (default: 3).' },
|
|
384
|
-
similarityThreshold: {
|
|
385
|
-
type: 'number',
|
|
386
|
-
description: 'Similarity threshold (default: 0.7).',
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
},
|
|
371
|
+
name: memoryTools.search_decisions_and_contracts.name,
|
|
372
|
+
description: memoryTools.search_decisions_and_contracts.description,
|
|
373
|
+
inputSchema: memoryTools.search_decisions_and_contracts.inputSchema,
|
|
374
|
+
},
|
|
375
|
+
// 5. CASE_TIMELINE_RANGE — Phase 3 case timeline RPC (defined in src/tools/)
|
|
376
|
+
{
|
|
377
|
+
name: memoryTools.case_timeline_range.name,
|
|
378
|
+
description: memoryTools.case_timeline_range.description,
|
|
379
|
+
inputSchema: memoryTools.case_timeline_range.inputSchema,
|
|
390
380
|
},
|
|
391
381
|
];
|
|
392
382
|
|
|
@@ -592,74 +582,6 @@ After failure → save a NEW decision with same topic to create evolution histor
|
|
|
592
582
|
};
|
|
593
583
|
}
|
|
594
584
|
|
|
595
|
-
/**
|
|
596
|
-
* Handle PreToolUse search for decisions + contracts
|
|
597
|
-
*/
|
|
598
|
-
async handleSearchDecisionsAndContracts(args = {}) {
|
|
599
|
-
const {
|
|
600
|
-
query = '',
|
|
601
|
-
filePath = '',
|
|
602
|
-
toolName = '',
|
|
603
|
-
decisionLimit = 5,
|
|
604
|
-
contractLimit = 3,
|
|
605
|
-
similarityThreshold = 0.7,
|
|
606
|
-
} = args;
|
|
607
|
-
|
|
608
|
-
await initDB();
|
|
609
|
-
|
|
610
|
-
let decisionResults = [];
|
|
611
|
-
let contractResults = [];
|
|
612
|
-
|
|
613
|
-
// Decision search
|
|
614
|
-
if (decisionLimit > 0 && query) {
|
|
615
|
-
try {
|
|
616
|
-
const queryEmbedding = await generateEmbedding(query);
|
|
617
|
-
const results = await vectorSearch(queryEmbedding, decisionLimit, similarityThreshold);
|
|
618
|
-
if (Array.isArray(results)) {
|
|
619
|
-
decisionResults = results.slice(0, decisionLimit);
|
|
620
|
-
}
|
|
621
|
-
} catch (err) {
|
|
622
|
-
console.error('[MAMA MCP] Decision search failed:', err.message);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Contract search (file-specific)
|
|
627
|
-
const contractTools = ['Edit', 'Write', 'apply_patch'];
|
|
628
|
-
const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java'];
|
|
629
|
-
const ext = filePath ? path.extname(filePath) : '';
|
|
630
|
-
|
|
631
|
-
if (
|
|
632
|
-
contractLimit > 0 &&
|
|
633
|
-
filePath &&
|
|
634
|
-
contractTools.includes(toolName) &&
|
|
635
|
-
codeExtensions.includes(ext)
|
|
636
|
-
) {
|
|
637
|
-
const basename = path.basename(filePath, ext);
|
|
638
|
-
const keywords = basename.split(/[-_]/).filter(Boolean);
|
|
639
|
-
const contractQuery = `contract api ${keywords.join(' ')}`.trim();
|
|
640
|
-
|
|
641
|
-
if (contractQuery) {
|
|
642
|
-
try {
|
|
643
|
-
const contractEmbedding = await generateEmbedding(contractQuery);
|
|
644
|
-
const contractMatches = await vectorSearch(contractEmbedding, 10, similarityThreshold);
|
|
645
|
-
if (Array.isArray(contractMatches)) {
|
|
646
|
-
contractResults = contractMatches
|
|
647
|
-
.filter((r) => r.topic && r.topic.startsWith('contract_'))
|
|
648
|
-
.slice(0, contractLimit);
|
|
649
|
-
}
|
|
650
|
-
} catch (err) {
|
|
651
|
-
console.error('[MAMA MCP] Contract search failed:', err.message);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return {
|
|
657
|
-
success: true,
|
|
658
|
-
decisionResults,
|
|
659
|
-
contractResults,
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
|
|
663
585
|
/**
|
|
664
586
|
* Handle update (decision outcome)
|
|
665
587
|
* Story 3.1: Case-insensitive outcome support
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: case_timeline_range
|
|
3
|
+
*
|
|
4
|
+
* Thin MCP wrapper around mama-core caseTimelineRange().
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { caseTimelineRange } = require('@jungjaehoon/mama-core');
|
|
8
|
+
const { initDB, getAdapter } = require('@jungjaehoon/mama-core/db-manager');
|
|
9
|
+
|
|
10
|
+
let adapterOverrideForTest = null;
|
|
11
|
+
|
|
12
|
+
const caseTimelineRangeTool = {
|
|
13
|
+
name: 'case_timeline_range',
|
|
14
|
+
description:
|
|
15
|
+
'Return a bounded, chronological timeline for a case. Includes decision, event, observation, and artifact memberships resolved through canonical case chains.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
case_id: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Case UUID to read. Merged cases resolve through their canonical case chain.',
|
|
22
|
+
},
|
|
23
|
+
from: {
|
|
24
|
+
oneOf: [{ type: 'string' }, { type: 'number' }],
|
|
25
|
+
description: 'Optional inclusive lower date bound. ISO 8601 string or epoch milliseconds.',
|
|
26
|
+
},
|
|
27
|
+
to: {
|
|
28
|
+
oneOf: [{ type: 'string' }, { type: 'number' }],
|
|
29
|
+
description: 'Optional inclusive upper date bound. ISO 8601 string or epoch milliseconds.',
|
|
30
|
+
},
|
|
31
|
+
order: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
enum: ['asc', 'desc'],
|
|
34
|
+
description: "Timeline order. Default: 'asc'.",
|
|
35
|
+
},
|
|
36
|
+
limit: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
minimum: 0,
|
|
39
|
+
maximum: 500,
|
|
40
|
+
description: 'Maximum items to return. Default: 100. Maximum: 500.',
|
|
41
|
+
},
|
|
42
|
+
include_connector_enrichments: {
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
description: 'Include connector event snapshots for observations/artifacts when available.',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['case_id'],
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async handler(args) {
|
|
51
|
+
const adapter = await getCaseTimelineRangeAdapter();
|
|
52
|
+
return caseTimelineRange(adapter, args || {});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async function getCaseTimelineRangeAdapter() {
|
|
57
|
+
if (adapterOverrideForTest) {
|
|
58
|
+
return adapterOverrideForTest;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await initDB();
|
|
62
|
+
return getAdapter();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setCaseTimelineRangeAdapterForTest(adapter) {
|
|
66
|
+
adapterOverrideForTest = adapter;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resetCaseTimelineRangeAdapterForTest() {
|
|
70
|
+
adapterOverrideForTest = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
caseTimelineRangeTool,
|
|
75
|
+
setCaseTimelineRangeAdapterForTest,
|
|
76
|
+
resetCaseTimelineRangeAdapterForTest,
|
|
77
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - generate_quality_report: Generate coverage and quality metrics report ✅
|
|
17
17
|
* - get_restart_metrics: Get restart success rate and latency metrics ✅
|
|
18
18
|
* - ingest_conversation: Ingest conversation messages into memory ✅
|
|
19
|
+
* - case_timeline_range: Read bounded case timeline ranges ✅
|
|
19
20
|
*
|
|
20
21
|
* @module tools
|
|
21
22
|
*/
|
|
@@ -29,6 +30,8 @@ const { saveCheckpointTool, loadCheckpointTool } = require('./checkpoint-tools.j
|
|
|
29
30
|
const { searchNarrativeTool } = require('./search-narrative.js');
|
|
30
31
|
const { generateQualityReportTool, getRestartMetricsTool } = require('./quality-metrics-tools.js');
|
|
31
32
|
const { ingestConversationTool } = require('./ingest-conversation.js');
|
|
33
|
+
const { searchDecisionsAndContractsTool } = require('./search-decisions-and-contracts.js');
|
|
34
|
+
const { caseTimelineRangeTool } = require('./case-timeline-range.js');
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* Create all MAMA memory tools
|
|
@@ -50,6 +53,8 @@ function createMemoryTools() {
|
|
|
50
53
|
generate_quality_report: generateQualityReportTool,
|
|
51
54
|
get_restart_metrics: getRestartMetricsTool,
|
|
52
55
|
ingest_conversation: ingestConversationTool,
|
|
56
|
+
search_decisions_and_contracts: searchDecisionsAndContractsTool,
|
|
57
|
+
case_timeline_range: caseTimelineRangeTool,
|
|
53
58
|
};
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -67,4 +72,6 @@ module.exports = {
|
|
|
67
72
|
generateQualityReportTool,
|
|
68
73
|
getRestartMetricsTool,
|
|
69
74
|
ingestConversationTool,
|
|
75
|
+
searchDecisionsAndContractsTool,
|
|
76
|
+
caseTimelineRangeTool,
|
|
70
77
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: search_decisions_and_contracts
|
|
3
|
+
*
|
|
4
|
+
* PreToolUse hook RPC — searches decisions and contract-specific memories
|
|
5
|
+
* for file-aware context injection before Edit/Write/apply_patch tool calls.
|
|
6
|
+
*
|
|
7
|
+
* @module search-decisions-and-contracts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { initDB } = require('@jungjaehoon/mama-core/db-manager');
|
|
12
|
+
const { generateEmbedding } = require('@jungjaehoon/mama-core/embeddings');
|
|
13
|
+
const { vectorSearch } = require('@jungjaehoon/mama-core/memory-store');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* search_decisions_and_contracts tool definition
|
|
17
|
+
*/
|
|
18
|
+
const searchDecisionsAndContractsTool = {
|
|
19
|
+
name: 'search_decisions_and_contracts',
|
|
20
|
+
description: 'Search decisions and contracts for PreToolUse hook injection.',
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
query: { type: 'string', description: 'Search query for decisions.' },
|
|
25
|
+
filePath: { type: 'string', description: 'File path context.' },
|
|
26
|
+
toolName: { type: 'string', description: 'Tool name (Edit/Write/apply_patch).' },
|
|
27
|
+
decisionLimit: { type: 'number', description: 'Max decisions (default: 5).' },
|
|
28
|
+
contractLimit: { type: 'number', description: 'Max contracts (default: 3).' },
|
|
29
|
+
similarityThreshold: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
description: 'Similarity threshold (default: 0.7).',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async handler(args = {}) {
|
|
37
|
+
try {
|
|
38
|
+
const {
|
|
39
|
+
query = '',
|
|
40
|
+
filePath = '',
|
|
41
|
+
toolName = '',
|
|
42
|
+
decisionLimit = 5,
|
|
43
|
+
contractLimit = 3,
|
|
44
|
+
similarityThreshold = 0.7,
|
|
45
|
+
} = args;
|
|
46
|
+
|
|
47
|
+
await initDB();
|
|
48
|
+
|
|
49
|
+
let decisionResults = [];
|
|
50
|
+
let contractResults = [];
|
|
51
|
+
|
|
52
|
+
// Decision search
|
|
53
|
+
if (decisionLimit > 0 && query) {
|
|
54
|
+
try {
|
|
55
|
+
const queryEmbedding = await generateEmbedding(query);
|
|
56
|
+
const results = await vectorSearch(queryEmbedding, decisionLimit, similarityThreshold);
|
|
57
|
+
if (Array.isArray(results)) {
|
|
58
|
+
decisionResults = results.slice(0, decisionLimit);
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error('[MAMA MCP] Decision search failed:', err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Contract search (file-specific)
|
|
66
|
+
const contractTools = ['Edit', 'Write', 'apply_patch'];
|
|
67
|
+
const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java'];
|
|
68
|
+
const ext = filePath ? path.extname(filePath) : '';
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
contractLimit > 0 &&
|
|
72
|
+
filePath &&
|
|
73
|
+
contractTools.includes(toolName) &&
|
|
74
|
+
codeExtensions.includes(ext)
|
|
75
|
+
) {
|
|
76
|
+
const basename = path.basename(filePath, ext);
|
|
77
|
+
const keywords = basename.split(/[-_]/).filter(Boolean);
|
|
78
|
+
const contractQuery = `contract api ${keywords.join(' ')}`.trim();
|
|
79
|
+
|
|
80
|
+
if (contractQuery) {
|
|
81
|
+
try {
|
|
82
|
+
const contractEmbedding = await generateEmbedding(contractQuery);
|
|
83
|
+
const contractMatches = await vectorSearch(contractEmbedding, 10, similarityThreshold);
|
|
84
|
+
if (Array.isArray(contractMatches)) {
|
|
85
|
+
contractResults = contractMatches
|
|
86
|
+
.filter((r) => r.topic && r.topic.startsWith('contract_'))
|
|
87
|
+
.slice(0, contractLimit);
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error('[MAMA MCP] Contract search failed:', err.message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
decisionResults,
|
|
98
|
+
contractResults,
|
|
99
|
+
};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
error: err instanceof Error ? err.message : String(err),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = { searchDecisionsAndContractsTool };
|