@jungjaehoon/mama-server 1.9.2 → 1.10.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 CHANGED
@@ -75,16 +75,23 @@ Any MCP-compatible client can use MAMA with:
75
75
  npx -y @jungjaehoon/mama-server
76
76
  ```
77
77
 
78
- ## Available Tools (v1.3)
79
-
80
- The MCP server exposes 4 core tools:
81
-
82
- | Tool | Description |
83
- | ----------------- | --------------------------------------------------------------------- |
84
- | `save` | Save decision (`type='decision'`) or checkpoint (`type='checkpoint'`) |
85
- | `search` | Semantic search (with `query`) or list recent items (without `query`) |
86
- | `update` | Update decision outcome (case-insensitive: success/failed/partial) |
87
- | `load_checkpoint` | Resume previous session |
78
+ ## Available Tools (v1.9)
79
+
80
+ The MCP server exposes 11 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 |
88
95
 
89
96
  ### Edge Types
90
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/mama-server",
3
- "version": "1.9.2",
3
+ "version": "1.10.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,7 +39,7 @@
39
39
  "node": ">=22.13.0"
40
40
  },
41
41
  "dependencies": {
42
- "@jungjaehoon/mama-core": "^1.3.2",
42
+ "@jungjaehoon/mama-core": "^1.4.0",
43
43
  "@modelcontextprotocol/sdk": "^1.0.1"
44
44
  },
45
45
  "devDependencies": {
package/src/server.js CHANGED
@@ -415,6 +415,8 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
415
415
  // Handle tool execution - 4 core tools only
416
416
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
417
417
  const { name, arguments: args } = request.params;
418
+ const toolStart = Date.now();
419
+ console.error(`[MAMA MCP] Tool start: ${name}`);
418
420
 
419
421
  try {
420
422
  let result;
@@ -446,6 +448,8 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
446
448
  this.legacyNoticeEmittedInToolResponse = true;
447
449
  }
448
450
 
451
+ console.error(`[MAMA MCP] Tool done: ${name} (${Date.now() - toolStart}ms)`);
452
+
449
453
  return {
450
454
  content: [
451
455
  {
@@ -464,6 +468,7 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
464
468
  ],
465
469
  };
466
470
  } catch (error) {
471
+ console.error(`[MAMA MCP] Tool failed: ${name} (${Date.now() - toolStart}ms)`);
467
472
  console.error('[MAMA MCP] Tool execution error:', error);
468
473
  return {
469
474
  content: [
@@ -489,7 +494,7 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
489
494
  if (!topic || !decision || !reasoning) {
490
495
  return { success: false, message: '❌ Decision requires: topic, decision, reasoning' };
491
496
  }
492
- const id = await mama.save({ topic, decision, reasoning, confidence });
497
+ const id = await mama.save({ type: 'user_decision', topic, decision, reasoning, confidence });
493
498
  return {
494
499
  success: true,
495
500
  id,
@@ -235,10 +235,6 @@ const loadCheckpointTool = {
235
235
  inputSchema: {
236
236
  type: 'object',
237
237
  properties: {
238
- session_id: {
239
- type: 'string',
240
- description: 'Optional session ID. If not provided, loads the most recent checkpoint.',
241
- },
242
238
  include_narrative: {
243
239
  type: 'boolean',
244
240
  description: 'Include related narrative/decisions (default: true)',
@@ -258,8 +254,7 @@ const loadCheckpointTool = {
258
254
  },
259
255
  handler: async (args = {}) => {
260
256
  const start = Date.now();
261
- // eslint-disable-next-line no-unused-vars
262
- const { session_id, include_narrative = true, include_links = true, link_depth = 1 } = args;
257
+ const { include_narrative = true, include_links = true, link_depth = 1 } = args;
263
258
 
264
259
  try {
265
260
  const adapter = getAdapter();
@@ -338,7 +333,7 @@ const loadCheckpointTool = {
338
333
  const formattedResponse = formatRestart(checkpoint, narrative, links);
339
334
 
340
335
  // BMad Workflow Integration: Add Story context
341
- const currentStory = inferCurrentStory(checkpoint.summary);
336
+ const currentStory = include_narrative ? inferCurrentStory(checkpoint.summary) : null;
342
337
  let bmadWorkflowContext = '';
343
338
 
344
339
  if (currentStory && currentStory.details) {
@@ -15,6 +15,7 @@
15
15
  * - search_narrative: Semantic search with link expansion ✅
16
16
  * - generate_quality_report: Generate coverage and quality metrics report ✅
17
17
  * - get_restart_metrics: Get restart success rate and latency metrics ✅
18
+ * - ingest_conversation: Ingest conversation messages into memory ✅
18
19
  *
19
20
  * @module tools
20
21
  */
@@ -27,6 +28,7 @@ const { updateOutcomeTool } = require('./update-outcome.js');
27
28
  const { saveCheckpointTool, loadCheckpointTool } = require('./checkpoint-tools.js');
28
29
  const { searchNarrativeTool } = require('./search-narrative.js');
29
30
  const { generateQualityReportTool, getRestartMetricsTool } = require('./quality-metrics-tools.js');
31
+ const { ingestConversationTool } = require('./ingest-conversation.js');
30
32
 
31
33
  /**
32
34
  * Create all MAMA memory tools
@@ -47,6 +49,7 @@ function createMemoryTools() {
47
49
  search_narrative: searchNarrativeTool,
48
50
  generate_quality_report: generateQualityReportTool,
49
51
  get_restart_metrics: getRestartMetricsTool,
52
+ ingest_conversation: ingestConversationTool,
50
53
  };
51
54
  }
52
55
 
@@ -63,4 +66,5 @@ module.exports = {
63
66
  searchNarrativeTool,
64
67
  generateQualityReportTool,
65
68
  getRestartMetricsTool,
69
+ ingestConversationTool,
66
70
  };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * MCP Tool: ingest_conversation
3
+ *
4
+ * Ingests conversation messages into MAMA memory with optional extraction.
5
+ *
6
+ * @module ingest-conversation
7
+ */
8
+
9
+ const { ingestConversation } = require('@jungjaehoon/mama-core');
10
+
11
+ const createIngestConversationTool = (mamaApi) => ({
12
+ name: 'ingest_conversation',
13
+ description:
14
+ "Ingest a conversation into MAMA's memory. Stores the raw conversation and optionally extracts structured memories (decisions, facts, preferences) using LLM. Use this to import past conversations or chat logs into memory.",
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ messages: {
19
+ type: 'array',
20
+ items: {
21
+ type: 'object',
22
+ properties: {
23
+ role: { type: 'string', enum: ['user', 'assistant', 'system'] },
24
+ content: { type: 'string' },
25
+ },
26
+ required: ['role', 'content'],
27
+ },
28
+ description: 'Conversation messages to ingest.',
29
+ },
30
+ scopes: {
31
+ type: 'array',
32
+ items: {
33
+ type: 'object',
34
+ properties: {
35
+ kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
36
+ id: { type: 'string' },
37
+ },
38
+ required: ['kind', 'id'],
39
+ },
40
+ description: 'Memory scopes for isolation.',
41
+ },
42
+ session_date: {
43
+ type: 'string',
44
+ description: 'ISO 8601 date when the conversation occurred (e.g., "2024-01-15").',
45
+ },
46
+ extract: {
47
+ type: 'boolean',
48
+ description: 'Whether to extract structured memories from the conversation. Default: false',
49
+ },
50
+ },
51
+ required: ['messages'],
52
+ },
53
+
54
+ async handler(params, _context) {
55
+ const { messages, scopes, session_date, extract = false } = params || {};
56
+
57
+ try {
58
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
59
+ return {
60
+ success: false,
61
+ message: '❌ Validation error: messages must be a non-empty array',
62
+ };
63
+ }
64
+
65
+ const ingestFn = mamaApi.ingestConversation || ingestConversation;
66
+ const result = await ingestFn({
67
+ messages,
68
+ scopes: scopes || [],
69
+ source: {
70
+ package: 'mcp-server',
71
+ source_type: 'mcp_ingest_conversation',
72
+ },
73
+ ...(session_date && { sessionDate: session_date }),
74
+ ...(extract && { extract: { enabled: true } }),
75
+ });
76
+
77
+ return {
78
+ success: true,
79
+ raw_id: result.rawId,
80
+ extracted_memories: result.extractedMemories || [],
81
+ message: `✅ Conversation ingested (ID: ${result.rawId})${
82
+ result.extractedMemories?.length
83
+ ? `, extracted ${result.extractedMemories.length} memories`
84
+ : ''
85
+ }`,
86
+ };
87
+ } catch (error) {
88
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
89
+ return {
90
+ success: false,
91
+ message: `❌ Failed to ingest conversation: ${errorMessage}`,
92
+ };
93
+ }
94
+ },
95
+ });
96
+
97
+ const ingestConversationTool = createIngestConversationTool({});
98
+
99
+ module.exports = { ingestConversationTool, createIngestConversationTool };
@@ -26,7 +26,7 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
26
26
  const listDecisionsTool = {
27
27
  name: 'list_decisions',
28
28
  description:
29
- 'List recent decisions in chronological order. Returns formatted list showing time, type (user/assistant), topic, preview, confidence, and status. Use this to see recent activity or find decisions by browsing.',
29
+ 'List recent decisions in chronological order. Returns formatted list showing time, type (user/assistant), topic, preview, confidence, and status. Use this to see recent activity or find decisions by browsing. Use scopes to filter by project/channel.',
30
30
  inputSchema: {
31
31
  type: 'object',
32
32
  properties: {
@@ -36,12 +36,24 @@ const listDecisionsTool = {
36
36
  minimum: 1,
37
37
  maximum: 100,
38
38
  },
39
+ scopes: {
40
+ type: 'array',
41
+ items: {
42
+ type: 'object',
43
+ properties: {
44
+ kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
45
+ id: { type: 'string' },
46
+ },
47
+ required: ['kind', 'id'],
48
+ },
49
+ description: 'Filter list by scope. If omitted, lists all scopes.',
50
+ },
39
51
  },
40
52
  required: [],
41
53
  },
42
54
 
43
55
  async handler(params, _context) {
44
- const { limit = 20 } = params || {};
56
+ const { limit = 20, scopes } = params || {};
45
57
 
46
58
  try {
47
59
  // Validation: Limit range check
@@ -52,9 +64,7 @@ const listDecisionsTool = {
52
64
  };
53
65
  }
54
66
 
55
- // Call MAMA API with markdown format for human display
56
- // mama.list() defaults to JSON (LLM-first), but we need markdown for user display
57
- const list = await mama.list({ limit, format: 'markdown' });
67
+ const list = await mama.list({ limit, format: 'markdown', ...(scopes && { scopes }) });
58
68
 
59
69
  // Return success response with formatted list
60
70
  return {
@@ -4,15 +4,8 @@
4
4
  * Story M1.3: MCP Tool - recall_decision (ported from mcp-server)
5
5
  * Priority: P1 (Core Feature)
6
6
  *
7
- * Recalls full decision history for a specific topic.
8
- * This is a wrapper around the existing mama.recall() API.
9
- *
10
- * Flow:
11
- * 1. User (via Claude Desktop): "Recall my decision about auth strategy"
12
- * 2. Claude: Calls recall_decision MCP tool
13
- * 3. Tool: Validates input, calls mama.recall()
14
- * 4. mama.recall(): Queries decision history + formats as markdown
15
- * 5. Tool: Returns formatted markdown response
7
+ * Recalls decision history for a specific topic using v2 recallMemory API.
8
+ * Supports scope-based filtering.
16
9
  *
17
10
  * @module recall-decision
18
11
  */
@@ -20,26 +13,51 @@
20
13
  const mama = require('@jungjaehoon/mama-core/mama-api');
21
14
 
22
15
  /**
23
- * Recall decision tool definition
16
+ * Create recall decision tool with dependencies
17
+ * @param {Object} mamaApi - MAMA API instance
24
18
  */
25
- const recallDecisionTool = {
19
+ const createRecallDecisionTool = (mamaApi) => ({
26
20
  name: 'recall_decision',
27
21
  description:
28
- 'Recall full decision history for a specific topic. Returns all past decisions on this topic in chronological order with reasoning, confidence, and outcomes. Use this when you need to review previous decisions, understand decision evolution, or check current position on a topic.\n\n⚡ GRAPH TRAVERSAL: When the same topic is reused across multiple decisions, this tool automatically shows the decision evolution chain (supersedes graph), enabling Learn/Unlearn/Relearn workflows.',
22
+ 'Recall decision history for a topic using semantic search. Returns past decisions filtered by scope if provided. Use this when you need to review previous decisions, understand decision evolution, or check current position on a topic.\n\n⚡ GRAPH TRAVERSAL: When the same topic is reused across multiple decisions, this tool automatically shows the decision evolution chain (supersedes graph), enabling Learn/Unlearn/Relearn workflows.',
29
23
  inputSchema: {
30
24
  type: 'object',
31
25
  properties: {
32
26
  topic: {
33
27
  type: 'string',
34
28
  description:
35
- "Decision topic to recall (e.g., 'auth_strategy', 'mesh_detail_choice'). Use the EXACT SAME topic name used in save_decision to see full decision evolution graph. Different topic names will show separate, disconnected decisions.",
29
+ "Decision topic to recall (e.g., 'auth_strategy', 'mesh_detail_choice'). Use the EXACT SAME topic name used in save_decision to see full decision evolution graph.",
30
+ },
31
+ format: {
32
+ type: 'string',
33
+ enum: ['markdown', 'json'],
34
+ description: "Output format. Default: 'markdown'",
35
+ },
36
+ scopes: {
37
+ type: 'array',
38
+ items: {
39
+ type: 'object',
40
+ properties: {
41
+ kind: {
42
+ type: 'string',
43
+ enum: ['global', 'user', 'channel', 'project'],
44
+ description: 'Scope type',
45
+ },
46
+ id: {
47
+ type: 'string',
48
+ description: 'Scope identifier (e.g., project path, channel ID)',
49
+ },
50
+ },
51
+ required: ['kind', 'id'],
52
+ },
53
+ description: 'Filter recall results by scope. If omitted, returns all scopes.',
36
54
  },
37
55
  },
38
56
  required: ['topic'],
39
57
  },
40
58
 
41
59
  async handler(params, _context) {
42
- const { topic } = params || {};
60
+ const { topic, format = 'markdown', scopes } = params || {};
43
61
 
44
62
  try {
45
63
  // Validation: Non-empty string check
@@ -50,26 +68,53 @@ const recallDecisionTool = {
50
68
  };
51
69
  }
52
70
 
53
- // Call MAMA API with markdown format for human display
54
- // mama.recall() defaults to JSON (LLM-first), but we need markdown for user display
55
- const history = await mama.recall(topic, { format: 'markdown' });
71
+ if (scopes && scopes.length > 0) {
72
+ // Use v2 recallMemory for scope-aware semantic recall
73
+ const bundle = await mamaApi.recallMemory(topic, {
74
+ scopes,
75
+ includeHistory: true,
76
+ });
77
+
78
+ if (format === 'json') {
79
+ return { success: true, history: bundle, message: bundle };
80
+ }
81
+
82
+ const memories = bundle.memories || [];
83
+ let md = `🧠 **Recall: ${topic}** (${memories.length} results)\n\n`;
84
+ for (const m of memories) {
85
+ md += `### ${m.topic}\n`;
86
+ md += `${m.summary}\n`;
87
+ if (m.details && m.details !== m.summary) {
88
+ md += `> ${m.details}\n`;
89
+ }
90
+ md += `- Confidence: ${m.confidence} | Status: ${m.status}`;
91
+ if (m.event_date) {
92
+ md += ` | Event: ${m.event_date}`;
93
+ }
94
+ md += '\n\n';
95
+ }
96
+ return { success: true, history: md, message: md };
97
+ }
98
+
99
+ // Legacy path: topic-exact-match recall (no scopes)
100
+ const history = await mamaApi.recall(topic, { format });
56
101
 
57
- // Return success response with formatted history
58
102
  return {
59
103
  success: true,
60
104
  history,
61
- message: history, // For backward compatibility with MCP response format
105
+ message: history,
62
106
  };
63
107
  } catch (error) {
64
- // Error handling: Return user-friendly message
65
108
  const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
66
-
67
109
  return {
68
110
  success: false,
69
111
  message: `❌ Failed to recall decisions: ${errorMessage}`,
70
112
  };
71
113
  }
72
114
  },
73
- };
115
+ });
116
+
117
+ // Default instance with real dependency
118
+ const recallDecisionTool = createRecallDecisionTool(mama);
74
119
 
75
- module.exports = { recallDecisionTool };
120
+ module.exports = { recallDecisionTool, createRecallDecisionTool };
@@ -149,6 +149,10 @@ When you find similar past decisions (returned in similar_decisions), choose you
149
149
  - **debate**: Present a counter-argument with evidence. Explain why the prior decision may be wrong.
150
150
  - **synthesize**: Merge multiple decisions into a new unified approach.
151
151
 
152
+ **SCOPES & TEMPORAL:**
153
+ - Use 'scopes' to isolate decisions per project/channel/user (e.g., [{"kind": "project", "id": "/my/app"}])
154
+ - Use 'event_date' (ISO 8601) to record when events actually happened vs. when they were saved
155
+
152
156
  **5-LAYER REASONING (CoT Guide):**
153
157
  Structure your reasoning with these layers for maximum value:
154
158
  1. **Context**: What problem/situation prompted this decision?
@@ -211,6 +215,31 @@ Structure your reasoning with these layers for maximum value:
211
215
  type: 'string',
212
216
  description: 'Potential risks or downsides (Tension layer).',
213
217
  },
218
+ scopes: {
219
+ type: 'array',
220
+ items: {
221
+ type: 'object',
222
+ properties: {
223
+ kind: {
224
+ type: 'string',
225
+ enum: ['global', 'user', 'channel', 'project'],
226
+ description: 'Scope type',
227
+ },
228
+ id: {
229
+ type: 'string',
230
+ description: 'Scope identifier (e.g., project path, channel ID)',
231
+ },
232
+ },
233
+ required: ['kind', 'id'],
234
+ },
235
+ description:
236
+ 'Memory scopes for isolation. Decisions are stored per-scope. If omitted, decision is unscoped (global). Example: [{"kind": "project", "id": "/path/to/project"}]',
237
+ },
238
+ event_date: {
239
+ type: 'string',
240
+ description:
241
+ 'ISO 8601 date when the event actually occurred (e.g., "2024-01-15"). If omitted, defaults to current time. Use this when recording decisions about past events.',
242
+ },
214
243
  },
215
244
  required: ['topic', 'decision', 'reasoning'],
216
245
  },
@@ -226,6 +255,8 @@ Structure your reasoning with these layers for maximum value:
226
255
  evidence,
227
256
  alternatives,
228
257
  risks,
258
+ scopes,
259
+ event_date,
229
260
  } = params || {};
230
261
 
231
262
  try {
@@ -305,6 +336,8 @@ Structure your reasoning with these layers for maximum value:
305
336
  alternatives,
306
337
  risks,
307
338
  trust_context: trustContext,
339
+ ...(scopes && { scopes }),
340
+ ...(event_date && { event_date }),
308
341
  });
309
342
 
310
343
  // Story 1.2: Return enhanced response with collaborative fields
@@ -17,7 +17,7 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
17
17
  const suggestDecisionTool = {
18
18
  name: 'suggest_decision',
19
19
  description:
20
- "Auto-suggest relevant past decisions based on user's question. Uses semantic search to find decisions related to the current context. Returns null if no relevant decisions found. Supports multilingual queries (English, Korean, etc.).",
20
+ "Auto-suggest relevant past decisions based on user's question. Uses semantic search to find decisions related to the current context. Returns null if no relevant decisions found. Supports multilingual queries (English, Korean, etc.). Use 'scopes' to filter by project/channel.",
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -48,12 +48,24 @@ const suggestDecisionTool = {
48
48
  description: 'Optional: Disable recency weighting entirely. Default: false',
49
49
  default: false,
50
50
  },
51
+ scopes: {
52
+ type: 'array',
53
+ items: {
54
+ type: 'object',
55
+ properties: {
56
+ kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
57
+ id: { type: 'string' },
58
+ },
59
+ required: ['kind', 'id'],
60
+ },
61
+ description: 'Filter suggestions by scope. If omitted, searches all scopes.',
62
+ },
51
63
  },
52
64
  required: ['userQuestion'],
53
65
  },
54
66
 
55
67
  async handler(params, _context) {
56
- const { userQuestion, recencyWeight, recencyScale, recencyDecay, disableRecency } =
68
+ const { userQuestion, recencyWeight, recencyScale, recencyDecay, disableRecency, scopes } =
57
69
  params || {};
58
70
 
59
71
  try {
@@ -65,13 +77,13 @@ const suggestDecisionTool = {
65
77
  };
66
78
  }
67
79
 
68
- // Call MAMA API with markdown format
69
80
  const suggestions = await mama.suggest(userQuestion, {
70
81
  format: 'markdown',
71
82
  recencyWeight,
72
83
  recencyScale,
73
84
  recencyDecay,
74
85
  disableRecency,
86
+ ...(scopes && { scopes }),
75
87
  });
76
88
 
77
89
  if (!suggestions) {