@jungjaehoon/mama-server 1.11.1 → 1.12.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +175 -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.1",
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.',
258
+ },
259
+ event_date: {
260
+ type: 'string',
261
+ description: 'ISO 8601 date when event occurred (e.g. "2024-01-15").',
213
262
  },
263
+ // Checkpoint fields
214
264
  summary: {
215
265
  type: 'string',
216
266
  description: '[Checkpoint] Session state summary.',
@@ -224,45 +274,123 @@ 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: {
377
+ type: 'object',
378
+ properties: {
379
+ query: { type: 'string', description: 'Search query for decisions.' },
380
+ filePath: { type: 'string', description: 'File path context.' },
381
+ toolName: { type: 'string', description: 'Tool name (Edit/Write/apply_patch).' },
382
+ decisionLimit: { type: 'number', description: 'Max decisions (default: 5).' },
383
+ contractLimit: { type: 'number', description: 'Max contracts (default: 3).' },
384
+ similarityThreshold: {
385
+ type: 'number',
386
+ description: 'Similarity threshold (default: 0.7).',
387
+ },
388
+ },
389
+ },
255
390
  },
256
391
  ];
257
392
 
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
- }));
393
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
266
394
 
267
395
  // Handle tool execution — legacy wrappers + v2 tools from src/tools/
268
396
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -377,7 +505,11 @@ class MAMAServer {
377
505
  };
378
506
  }
379
507
 
380
- return { success: false, message: "❌ type must be 'decision' or 'checkpoint'" };
508
+ if (type === 'ingest') {
509
+ return await memoryTools.ingest_conversation.handler(args);
510
+ }
511
+
512
+ return { success: false, message: "❌ type must be 'decision', 'checkpoint', or 'ingest'" };
381
513
  }
382
514
 
383
515
  /**
@@ -386,6 +518,11 @@ class MAMAServer {
386
518
  async handleSearch(args) {
387
519
  const { query, type = 'all', limit = 10, scopes } = args;
388
520
 
521
+ // type='checkpoint' without query → load latest checkpoint (resume session)
522
+ if (type === 'checkpoint' && !query) {
523
+ return await memoryTools.load_checkpoint.handler(args);
524
+ }
525
+
389
526
  const results = [];
390
527
 
391
528
  // Search decisions
@@ -400,7 +537,6 @@ class MAMAServer {
400
537
  } else {
401
538
  decisions = await mama.list({ limit, ...(scopes && { scopes }) });
402
539
  }
403
- // Ensure decisions is an array
404
540
  if (Array.isArray(decisions)) {
405
541
  results.push(
406
542
  ...decisions.map((d) => ({
@@ -411,8 +547,24 @@ class MAMAServer {
411
547
  }
412
548
  }
413
549
 
414
- // Search checkpoints
415
- if (type === 'all' || type === 'checkpoint') {
550
+ // Search checkpoints (with query = search, without = handled above as load)
551
+ if ((type === 'all' || type === 'checkpoint') && query) {
552
+ const checkpoints = await mama.listCheckpoints(limit);
553
+ results.push(
554
+ ...checkpoints
555
+ .filter((c) => c.summary && c.summary.toLowerCase().includes(query.toLowerCase()))
556
+ .map((c) => ({
557
+ id: `checkpoint_${c.id}`,
558
+ summary: c.summary,
559
+ next_steps: c.next_steps,
560
+ created_at: c.timestamp,
561
+ _type: 'checkpoint',
562
+ }))
563
+ );
564
+ }
565
+
566
+ // type='all' without query — include recent checkpoints
567
+ if (type === 'all' && !query) {
416
568
  const checkpoints = await mama.listCheckpoints(limit);
417
569
  results.push(
418
570
  ...checkpoints.map((c) => ({