@jungjaehoon/mama-server 1.11.1 → 1.12.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +162 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/mama-server",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "MAMA MCP Server - Memory-Augmented MCP Assistant for Claude Code & Desktop",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/server.js CHANGED
@@ -198,19 +198,69 @@ class MAMAServer {
198
198
  ? `${this.getLegacyMigrationNotice()}\n\n`
199
199
  : '';
200
200
 
201
- const legacyTools = [
201
+ const tools = [
202
+ // 1. SAVE — decisions, checkpoints, conversation ingestion
202
203
  {
203
204
  name: 'save',
204
- description: `${legacyNotice}${memoryTools.save_decision.description}\n\nAlso supports type='checkpoint' for session state.`,
205
+ description: `${legacyNotice}Save to MAMA memory. Use type parameter to choose what to save.
206
+
207
+ **type='decision'** — Save architectural decisions, lessons learned, insights.
208
+ Required: topic, decision, reasoning. Optional: confidence, scopes, event_date.
209
+ Triggers: user says "기억해", "remember", "decided". Reuse same topic to create evolution chain.
210
+
211
+ **type='checkpoint'** — Save session state for resumption.
212
+ Required: summary (4-section: Goal, Evidence, Unfinished, Next Briefing).
213
+ Optional: next_steps, open_files. Triggers: session ending, "체크포인트", "save progress".
214
+
215
+ **type='ingest'** — Import conversation messages into memory with optional extraction.
216
+ Required: messages (array of {role, content}). Optional: scopes, session_date, extract.
217
+
218
+ **Scopes**: Isolate memories per project/channel. Example: [{"kind":"project","id":"/my/app"}]
219
+ **event_date**: ISO 8601 date when event occurred (e.g. "2024-01-15"), not when saved.`,
205
220
  inputSchema: {
206
221
  type: 'object',
207
222
  properties: {
208
- ...memoryTools.save_decision.inputSchema.properties,
209
223
  type: {
210
224
  type: 'string',
211
- enum: ['decision', 'checkpoint'],
212
- description: "What to save: 'decision' or 'checkpoint'",
225
+ enum: ['decision', 'checkpoint', 'ingest'],
226
+ description: "What to save: 'decision', 'checkpoint', or 'ingest'",
227
+ },
228
+ // Decision fields
229
+ topic: {
230
+ type: 'string',
231
+ description: '[Decision] Topic identifier. Reuse same topic = supersedes previous.',
232
+ },
233
+ decision: {
234
+ type: 'string',
235
+ description: '[Decision] The decision made.',
236
+ },
237
+ reasoning: {
238
+ type: 'string',
239
+ description: "[Decision] Why. End with 'builds_on: <id>' or 'debates: <id>' to link.",
240
+ },
241
+ confidence: {
242
+ type: 'number',
243
+ description: '[Decision] 0.0-1.0. Default: 0.5',
244
+ minimum: 0,
245
+ maximum: 1,
246
+ },
247
+ scopes: {
248
+ type: 'array',
249
+ items: {
250
+ type: 'object',
251
+ properties: {
252
+ kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
253
+ id: { type: 'string' },
254
+ },
255
+ required: ['kind', 'id'],
256
+ },
257
+ description: 'Memory scopes for isolation.',
213
258
  },
259
+ event_date: {
260
+ type: 'string',
261
+ description: 'ISO 8601 date when event occurred (e.g. "2024-01-15").',
262
+ },
263
+ // Checkpoint fields
214
264
  summary: {
215
265
  type: 'string',
216
266
  description: '[Checkpoint] Session state summary.',
@@ -224,45 +274,110 @@ class MAMAServer {
224
274
  items: { type: 'string' },
225
275
  description: '[Checkpoint] Currently relevant files.',
226
276
  },
277
+ // Ingest fields
278
+ messages: {
279
+ type: 'array',
280
+ items: {
281
+ type: 'object',
282
+ properties: {
283
+ role: { type: 'string', enum: ['user', 'assistant', 'system'] },
284
+ content: { type: 'string' },
285
+ },
286
+ required: ['role', 'content'],
287
+ },
288
+ description: '[Ingest] Conversation messages to import.',
289
+ },
290
+ session_date: {
291
+ type: 'string',
292
+ description: '[Ingest] ISO 8601 date when conversation occurred.',
293
+ },
294
+ extract: {
295
+ type: 'boolean',
296
+ description: '[Ingest] Extract structured memories via LLM. Default: false',
297
+ },
227
298
  },
228
299
  required: ['type'],
229
300
  },
230
301
  },
302
+ // 2. SEARCH — unified search across decisions, checkpoints, load latest checkpoint
231
303
  {
232
304
  name: 'search',
233
- description: memoryTools.suggest_decision.description,
305
+ description: `Search MAMA memory. Returns results ranked by semantic similarity.
306
+
307
+ **With query** — Semantic search across decisions and checkpoints. Cross-lingual (Korean + English).
308
+ Triggers: "뭐였더라", "what did we decide", making architectural choices, debugging.
309
+
310
+ **Without query** — List recent items sorted by time.
311
+
312
+ **Resume session**: type='checkpoint' without query → loads latest checkpoint with full context (narrative, links, next steps).
313
+ Triggers: "이어서", "continue", "where were we", session start.
314
+
315
+ **type parameter**: 'decision' (choices/lessons only), 'checkpoint' (session states / resume), 'all' (both, default).
316
+ **scopes**: Filter by project/channel. Omit for global search.
317
+ **limit**: Max results (default: 10).
318
+
319
+ ⚠️ REQUIRED: Call search BEFORE save to find related decisions and avoid orphans.`,
234
320
  inputSchema: {
235
321
  type: 'object',
236
322
  properties: {
237
323
  query: {
238
324
  type: 'string',
239
- description: 'Search query. Semantic search finds related decisions.',
325
+ description: 'Search query. Omit to list recent items.',
240
326
  },
241
327
  type: {
242
328
  type: 'string',
243
329
  enum: ['all', 'decision', 'checkpoint'],
244
330
  description: "Filter by type. Default: 'all'",
245
331
  },
246
- limit: { type: 'number', description: 'Maximum results. Default: 10' },
247
- scopes: memoryTools.save_decision.inputSchema.properties.scopes,
332
+ limit: { type: 'number', description: 'Max results. Default: 10' },
333
+ scopes: {
334
+ type: 'array',
335
+ items: {
336
+ type: 'object',
337
+ properties: {
338
+ kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
339
+ id: { type: 'string' },
340
+ },
341
+ required: ['kind', 'id'],
342
+ },
343
+ description: 'Filter by scope.',
344
+ },
248
345
  },
249
346
  },
250
347
  },
348
+ // 3. UPDATE — decision outcome tracking
251
349
  {
252
350
  name: 'update',
253
- description: memoryTools.update_outcome.description,
254
- inputSchema: memoryTools.update_outcome.inputSchema,
351
+ description: `Update decision outcome after real-world validation.
352
+
353
+ Triggers: "이거 안됐어", "this worked", days later when issues discovered.
354
+ outcome: 'success', 'failed', 'partial' (case-insensitive).
355
+ After failure → save a NEW decision with same topic to create evolution history.`,
356
+ inputSchema: {
357
+ type: 'object',
358
+ properties: {
359
+ id: { type: 'string', description: 'Decision ID to update.' },
360
+ outcome: {
361
+ type: 'string',
362
+ description: "'success', 'failed', or 'partial' (case-insensitive).",
363
+ },
364
+ reason: {
365
+ type: 'string',
366
+ description: 'Why it succeeded/failed/was partial. Include evidence.',
367
+ },
368
+ },
369
+ required: ['id', 'outcome'],
370
+ },
371
+ },
372
+ // 4. SEARCH_DECISIONS_AND_CONTRACTS — PreToolUse hook RPC
373
+ {
374
+ name: 'search_decisions_and_contracts',
375
+ description: 'Search decisions and contracts for PreToolUse hook injection.',
376
+ inputSchema: memoryTools.search_decisions_and_contracts.inputSchema,
255
377
  },
256
378
  ];
257
379
 
258
- // All tools: legacy wrappers + all v2 tools from src/tools/
259
- const v2Tools = Object.values(memoryTools)
260
- .filter((t) => t.name && t.inputSchema)
261
- .map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));
262
-
263
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
264
- tools: [...legacyTools, ...v2Tools],
265
- }));
380
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
266
381
 
267
382
  // Handle tool execution — legacy wrappers + v2 tools from src/tools/
268
383
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -377,7 +492,11 @@ class MAMAServer {
377
492
  };
378
493
  }
379
494
 
380
- return { success: false, message: "❌ type must be 'decision' or 'checkpoint'" };
495
+ if (type === 'ingest') {
496
+ return await memoryTools.ingest_conversation.handler(args);
497
+ }
498
+
499
+ return { success: false, message: "❌ type must be 'decision', 'checkpoint', or 'ingest'" };
381
500
  }
382
501
 
383
502
  /**
@@ -386,6 +505,11 @@ class MAMAServer {
386
505
  async handleSearch(args) {
387
506
  const { query, type = 'all', limit = 10, scopes } = args;
388
507
 
508
+ // type='checkpoint' without query → load latest checkpoint (resume session)
509
+ if (type === 'checkpoint' && !query) {
510
+ return await memoryTools.load_checkpoint.handler(args);
511
+ }
512
+
389
513
  const results = [];
390
514
 
391
515
  // Search decisions
@@ -400,7 +524,6 @@ class MAMAServer {
400
524
  } else {
401
525
  decisions = await mama.list({ limit, ...(scopes && { scopes }) });
402
526
  }
403
- // Ensure decisions is an array
404
527
  if (Array.isArray(decisions)) {
405
528
  results.push(
406
529
  ...decisions.map((d) => ({
@@ -411,8 +534,24 @@ class MAMAServer {
411
534
  }
412
535
  }
413
536
 
414
- // Search checkpoints
415
- if (type === 'all' || type === 'checkpoint') {
537
+ // Search checkpoints (with query = search, without = handled above as load)
538
+ if ((type === 'all' || type === 'checkpoint') && query) {
539
+ const checkpoints = await mama.listCheckpoints(limit);
540
+ results.push(
541
+ ...checkpoints
542
+ .filter((c) => c.summary && c.summary.toLowerCase().includes(query.toLowerCase()))
543
+ .map((c) => ({
544
+ id: `checkpoint_${c.id}`,
545
+ summary: c.summary,
546
+ next_steps: c.next_steps,
547
+ created_at: c.timestamp,
548
+ _type: 'checkpoint',
549
+ }))
550
+ );
551
+ }
552
+
553
+ // type='all' without query — include recent checkpoints
554
+ if (type === 'all' && !query) {
416
555
  const checkpoints = await mama.listCheckpoints(limit);
417
556
  results.push(
418
557
  ...checkpoints.map((c) => ({