@geoql/mdr 0.0.1 → 1.0.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 CHANGED
@@ -15,9 +15,9 @@
15
15
  * - expand_conversation: Load full context from a session
16
16
  */
17
17
 
18
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
- import { z } from "zod";
18
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
+ import { z } from 'zod';
21
21
  import {
22
22
  existsSync,
23
23
  readFileSync,
@@ -25,22 +25,22 @@ import {
25
25
  appendFileSync,
26
26
  mkdirSync,
27
27
  readdirSync,
28
- } from "fs";
29
- import { join } from "path";
28
+ } from 'fs';
29
+ import { join } from 'path';
30
30
  import {
31
31
  searchMemory as doSearchMemory,
32
32
  indexJournalEntry,
33
33
  rebuildIndex,
34
34
  getIndexStats,
35
35
  type MemoryItemType,
36
- } from "./indexer.js";
36
+ } from './indexer.js';
37
37
  import {
38
38
  searchConversations,
39
39
  expandConversation,
40
40
  rebuildConversationIndex,
41
41
  updateConversationIndex,
42
42
  getConversationIndexStats,
43
- } from "./conversations.js";
43
+ } from './conversations.js';
44
44
  import {
45
45
  getStateRoot,
46
46
  getStateDir,
@@ -48,8 +48,8 @@ import {
48
48
  getJournalDir,
49
49
  getIndexDir,
50
50
  getRemindersDir,
51
- } from "./config.js";
52
- import { unlinkSync } from "fs";
51
+ } from './config.js';
52
+ import { unlinkSync } from 'fs';
53
53
 
54
54
  // Types
55
55
  interface JournalEntry {
@@ -64,11 +64,11 @@ interface JournalEntry {
64
64
 
65
65
  interface Schedule {
66
66
  id: string;
67
- type: "cron" | "once";
67
+ type: 'cron' | 'once';
68
68
  expression: string;
69
69
  description: string;
70
70
  payload: string;
71
- agent?: "opencode" | "claude"; // Which agent CLI to trigger
71
+ agent?: 'opencode' | 'claude'; // Which agent CLI to trigger
72
72
  model?: string; // Optional model override (e.g., "anthropic/claude-opus-4-6")
73
73
  createdAt: string;
74
74
  }
@@ -80,8 +80,8 @@ function ensureDirectories() {
80
80
  getStateRoot(),
81
81
  getStateDir(),
82
82
  entitiesDir,
83
- join(entitiesDir, "people"),
84
- join(entitiesDir, "projects"),
83
+ join(entitiesDir, 'people'),
84
+ join(entitiesDir, 'projects'),
85
85
  getJournalDir(),
86
86
  getIndexDir(),
87
87
  ];
@@ -99,10 +99,10 @@ function loadAllSchedules(): Schedule[] {
99
99
  try {
100
100
  if (!existsSync(remindersDir)) return schedules;
101
101
 
102
- const files = readdirSync(remindersDir).filter((f) => f.endsWith(".json"));
102
+ const files = readdirSync(remindersDir).filter((f) => f.endsWith('.json'));
103
103
  for (const file of files) {
104
104
  try {
105
- const content = readFileSync(join(remindersDir, file), "utf-8");
105
+ const content = readFileSync(join(remindersDir, file), 'utf-8');
106
106
  schedules.push(JSON.parse(content));
107
107
  } catch {
108
108
  // Skip malformed files
@@ -140,7 +140,7 @@ function deleteScheduleFile(id: string) {
140
140
  }
141
141
 
142
142
  function getTodayJournalPath(): string {
143
- const today = new Date().toISOString().split("T")[0];
143
+ const today = new Date().toISOString().split('T')[0];
144
144
  return join(getJournalDir(), `${today}.jsonl`);
145
145
  }
146
146
 
@@ -152,15 +152,15 @@ function getRecentJournalEntries(count: number): JournalEntry[] {
152
152
  if (!existsSync(journalDir)) return entries;
153
153
 
154
154
  const files = readdirSync(journalDir)
155
- .filter((f: string) => f.endsWith(".jsonl"))
155
+ .filter((f: string) => f.endsWith('.jsonl'))
156
156
  .sort()
157
157
  .reverse();
158
158
 
159
159
  for (const file of files) {
160
160
  if (entries.length >= count) break;
161
161
 
162
- const content = readFileSync(join(journalDir, file), "utf-8");
163
- const lines = content.trim().split("\n").filter(Boolean);
162
+ const content = readFileSync(join(journalDir, file), 'utf-8');
163
+ const lines = content.trim().split('\n').filter(Boolean);
164
164
 
165
165
  for (const line of lines.reverse()) {
166
166
  if (entries.length >= count) break;
@@ -178,19 +178,19 @@ function getRecentJournalEntries(count: number): JournalEntry[] {
178
178
  // Create MCP server with every tool registered.
179
179
  export function createServer(): McpServer {
180
180
  const server = new McpServer({
181
- name: "macrodata-local",
182
- version: "0.1.0",
181
+ name: 'macrodata-local',
182
+ version: '0.1.0',
183
183
  });
184
184
 
185
185
  // Tool: log_journal
186
186
  server.tool(
187
- "log_journal",
188
- "Append a timestamped entry to the journal",
187
+ 'log_journal',
188
+ 'Append a timestamped entry to the journal',
189
189
  {
190
- topic: z.string().describe("Category or tag for this entry"),
191
- content: z.string().describe("The actual note or observation"),
192
- source: z.string().optional().describe("Where this came from (conversation, cron, etc.)"),
193
- intent: z.string().optional().describe("What you were doing when logging this"),
190
+ topic: z.string().describe('Category or tag for this entry'),
191
+ content: z.string().describe('The actual note or observation'),
192
+ source: z.string().optional().describe('Where this came from (conversation, cron, etc.)'),
193
+ intent: z.string().optional().describe('What you were doing when logging this'),
194
194
  },
195
195
  async ({ topic, content, source, intent }) => {
196
196
  ensureDirectories();
@@ -206,19 +206,19 @@ export function createServer(): McpServer {
206
206
  };
207
207
 
208
208
  const journalPath = getTodayJournalPath();
209
- appendFileSync(journalPath, JSON.stringify(entry) + "\n");
209
+ appendFileSync(journalPath, JSON.stringify(entry) + '\n');
210
210
 
211
211
  // Index the entry for semantic search
212
212
  try {
213
213
  await indexJournalEntry(entry);
214
214
  } catch (err) {
215
- console.error("[log_journal] Failed to index entry:", err);
215
+ console.error('[log_journal] Failed to index entry:', err);
216
216
  }
217
217
 
218
218
  return {
219
219
  content: [
220
220
  {
221
- type: "text" as const,
221
+ type: 'text' as const,
222
222
  text: `Logged to journal: ${topic}`,
223
223
  },
224
224
  ],
@@ -228,11 +228,11 @@ export function createServer(): McpServer {
228
228
 
229
229
  // Tool: get_recent_journal
230
230
  server.tool(
231
- "get_recent_journal",
232
- "Get the N most recent journal entries, optionally filtered by topic",
231
+ 'get_recent_journal',
232
+ 'Get the N most recent journal entries, optionally filtered by topic',
233
233
  {
234
- count: z.number().default(10).describe("Number of entries to retrieve"),
235
- topic: z.string().optional().describe("Filter by specific topic"),
234
+ count: z.number().default(10).describe('Number of entries to retrieve'),
235
+ topic: z.string().optional().describe('Filter by specific topic'),
236
236
  },
237
237
  async ({ count, topic }) => {
238
238
  let entries = getRecentJournalEntries(Math.min(count * 2, 100)); // Get more to filter
@@ -246,7 +246,7 @@ export function createServer(): McpServer {
246
246
  return {
247
247
  content: [
248
248
  {
249
- type: "text" as const,
249
+ type: 'text' as const,
250
250
  text: JSON.stringify(entries, null, 2),
251
251
  },
252
252
  ],
@@ -256,22 +256,22 @@ export function createServer(): McpServer {
256
256
 
257
257
  // Tool: search_memory
258
258
  server.tool(
259
- "search_memory",
260
- "Semantic search across journal entries and entity files. Returns ranked results.",
259
+ 'search_memory',
260
+ 'Semantic search across journal entries and entity files. Returns ranked results.',
261
261
  {
262
- query: z.string().describe("Natural language search query"),
262
+ query: z.string().describe('Natural language search query'),
263
263
  type: z
264
- .enum(["journal", "person", "project", "all"])
265
- .default("all")
266
- .describe("Filter by content type"),
267
- since: z.string().optional().describe("Only include items after this ISO date"),
268
- limit: z.number().default(5).describe("Maximum results to return"),
264
+ .enum(['journal', 'person', 'project', 'all'])
265
+ .default('all')
266
+ .describe('Filter by content type'),
267
+ since: z.string().optional().describe('Only include items after this ISO date'),
268
+ limit: z.number().default(5).describe('Maximum results to return'),
269
269
  },
270
270
  async ({ query, type, since, limit }) => {
271
271
  try {
272
272
  const results = await doSearchMemory(query, {
273
273
  limit,
274
- type: type === "all" ? undefined : (type as MemoryItemType),
274
+ type: type === 'all' ? undefined : (type as MemoryItemType),
275
275
  since,
276
276
  });
277
277
 
@@ -279,8 +279,8 @@ export function createServer(): McpServer {
279
279
  return {
280
280
  content: [
281
281
  {
282
- type: "text" as const,
283
- text: "(no matches found)",
282
+ type: 'text' as const,
283
+ text: '(no matches found)',
284
284
  },
285
285
  ],
286
286
  };
@@ -288,18 +288,18 @@ export function createServer(): McpServer {
288
288
 
289
289
  const formatted = results
290
290
  .map((r, i) => {
291
- const header = `[${i + 1}] ${r.type}${r.section ? ` / ${r.section}` : ""} (score: ${r.score.toFixed(3)})`;
292
- const meta = r.timestamp ? ` Date: ${r.timestamp}` : "";
291
+ const header = `[${i + 1}] ${r.type}${r.section ? ` / ${r.section}` : ''} (score: ${r.score.toFixed(3)})`;
292
+ const meta = r.timestamp ? ` Date: ${r.timestamp}` : '';
293
293
  const source = ` Source: ${r.source}`;
294
- const content = r.content.slice(0, 500) + (r.content.length > 500 ? "..." : "");
295
- return [header, meta, source, "", content].filter(Boolean).join("\n");
294
+ const content = r.content.slice(0, 500) + (r.content.length > 500 ? '...' : '');
295
+ return [header, meta, source, '', content].filter(Boolean).join('\n');
296
296
  })
297
- .join("\n\n---\n\n");
297
+ .join('\n\n---\n\n');
298
298
 
299
299
  return {
300
300
  content: [
301
301
  {
302
- type: "text" as const,
302
+ type: 'text' as const,
303
303
  text: formatted,
304
304
  },
305
305
  ],
@@ -308,7 +308,7 @@ export function createServer(): McpServer {
308
308
  return {
309
309
  content: [
310
310
  {
311
- type: "text" as const,
311
+ type: 'text' as const,
312
312
  text: `Search error: ${String(err)}`,
313
313
  },
314
314
  ],
@@ -319,25 +319,25 @@ export function createServer(): McpServer {
319
319
 
320
320
  // Tool: manage_index
321
321
  server.tool(
322
- "manage_index",
322
+ 'manage_index',
323
323
  "Manage search indexes. Target 'memory' for journal/entities, 'conversations' for past Claude Code sessions.",
324
324
  {
325
- target: z.enum(["memory", "conversations"]).describe("Which index to manage"),
325
+ target: z.enum(['memory', 'conversations']).describe('Which index to manage'),
326
326
  action: z
327
- .enum(["rebuild", "update", "stats"])
327
+ .enum(['rebuild', 'update', 'stats'])
328
328
  .describe(
329
329
  "'rebuild' to reindex from scratch, 'update' for incremental (conversations only), 'stats' to get counts",
330
330
  ),
331
331
  },
332
332
  async ({ target, action }) => {
333
333
  try {
334
- if (target === "memory") {
335
- if (action === "rebuild" || action === "update") {
334
+ if (target === 'memory') {
335
+ if (action === 'rebuild' || action === 'update') {
336
336
  const result = await rebuildIndex();
337
337
  return {
338
338
  content: [
339
339
  {
340
- type: "text" as const,
340
+ type: 'text' as const,
341
341
  text: `Memory index rebuilt. Indexed ${result.itemCount} items.`,
342
342
  },
343
343
  ],
@@ -346,12 +346,12 @@ export function createServer(): McpServer {
346
346
  const stats = await getIndexStats();
347
347
  return {
348
348
  content: [
349
- { type: "text" as const, text: `Memory index contains ${stats.itemCount} items.` },
349
+ { type: 'text' as const, text: `Memory index contains ${stats.itemCount} items.` },
350
350
  ],
351
351
  };
352
352
  }
353
353
  } else {
354
- if (action === "rebuild") {
354
+ if (action === 'rebuild') {
355
355
  // Run in background - don't wait
356
356
  rebuildConversationIndex()
357
357
  .then((result) =>
@@ -365,12 +365,12 @@ export function createServer(): McpServer {
365
365
  return {
366
366
  content: [
367
367
  {
368
- type: "text" as const,
368
+ type: 'text' as const,
369
369
  text: `Conversation index rebuild started in background.`,
370
370
  },
371
371
  ],
372
372
  };
373
- } else if (action === "update") {
373
+ } else if (action === 'update') {
374
374
  // Incremental update - also background
375
375
  updateConversationIndex()
376
376
  .then((result) =>
@@ -383,7 +383,7 @@ export function createServer(): McpServer {
383
383
  );
384
384
  return {
385
385
  content: [
386
- { type: "text" as const, text: `Conversation index update started in background.` },
386
+ { type: 'text' as const, text: `Conversation index update started in background.` },
387
387
  ],
388
388
  };
389
389
  } else {
@@ -391,7 +391,7 @@ export function createServer(): McpServer {
391
391
  return {
392
392
  content: [
393
393
  {
394
- type: "text" as const,
394
+ type: 'text' as const,
395
395
  text: `Conversation index contains ${stats.exchangeCount} exchanges.`,
396
396
  },
397
397
  ],
@@ -401,7 +401,7 @@ export function createServer(): McpServer {
401
401
  } catch (err) {
402
402
  return {
403
403
  content: [
404
- { type: "text" as const, text: `Failed to ${action} ${target} index: ${String(err)}` },
404
+ { type: 'text' as const, text: `Failed to ${action} ${target} index: ${String(err)}` },
405
405
  ],
406
406
  };
407
407
  }
@@ -410,18 +410,18 @@ export function createServer(): McpServer {
410
410
 
411
411
  // Tool: schedule
412
412
  server.tool(
413
- "schedule",
413
+ 'schedule',
414
414
  "Create a reminder. Use type 'cron' for recurring (expression is cron syntax) or 'once' for one-shot (expression is ISO datetime).",
415
415
  {
416
- type: z.enum(["cron", "once"]).describe("'cron' for recurring, 'once' for one-shot"),
417
- id: z.string().describe("Unique identifier for this reminder"),
416
+ type: z.enum(['cron', 'once']).describe("'cron' for recurring, 'once' for one-shot"),
417
+ id: z.string().describe('Unique identifier for this reminder'),
418
418
  expression: z
419
419
  .string()
420
420
  .describe(
421
421
  "Cron expression (e.g., '0 9 * * *') or ISO datetime (e.g., '2026-01-31T10:00:00')",
422
422
  ),
423
- description: z.string().describe("What this reminder is for"),
424
- payload: z.string().describe("Message to process when reminder fires"),
423
+ description: z.string().describe('What this reminder is for'),
424
+ payload: z.string().describe('Message to process when reminder fires'),
425
425
  model: z
426
426
  .string()
427
427
  .optional()
@@ -434,7 +434,7 @@ export function createServer(): McpServer {
434
434
  expression,
435
435
  description,
436
436
  payload,
437
- agent: "claude",
437
+ agent: 'claude',
438
438
  model,
439
439
  createdAt: new Date().toISOString(),
440
440
  };
@@ -442,12 +442,12 @@ export function createServer(): McpServer {
442
442
  // Save to individual file (overwrites if exists)
443
443
  saveSchedule(schedule);
444
444
 
445
- const typeLabel = type === "cron" ? "recurring" : "one-shot";
445
+ const typeLabel = type === 'cron' ? 'recurring' : 'one-shot';
446
446
  return {
447
447
  content: [
448
448
  {
449
- type: "text" as const,
450
- text: `Created ${typeLabel} reminder: ${id} (${expression})${model ? ` with model ${model}` : ""}`,
449
+ type: 'text' as const,
450
+ text: `Created ${typeLabel} reminder: ${id} (${expression})${model ? ` with model ${model}` : ''}`,
451
451
  },
452
452
  ],
453
453
  };
@@ -455,13 +455,13 @@ export function createServer(): McpServer {
455
455
  );
456
456
 
457
457
  // Tool: list_reminders
458
- server.tool("list_reminders", "List all active scheduled reminders", {}, async () => {
458
+ server.tool('list_reminders', 'List all active scheduled reminders', {}, async () => {
459
459
  const schedules = loadAllSchedules();
460
460
 
461
461
  return {
462
462
  content: [
463
463
  {
464
- type: "text" as const,
464
+ type: 'text' as const,
465
465
  text: JSON.stringify(schedules, null, 2),
466
466
  },
467
467
  ],
@@ -470,10 +470,10 @@ export function createServer(): McpServer {
470
470
 
471
471
  // Tool: remove_reminder
472
472
  server.tool(
473
- "remove_reminder",
474
- "Remove a scheduled reminder",
473
+ 'remove_reminder',
474
+ 'Remove a scheduled reminder',
475
475
  {
476
- id: z.string().describe("ID of the reminder to remove"),
476
+ id: z.string().describe('ID of the reminder to remove'),
477
477
  },
478
478
  async ({ id }) => {
479
479
  const removed = deleteScheduleFile(id);
@@ -481,7 +481,7 @@ export function createServer(): McpServer {
481
481
  return {
482
482
  content: [
483
483
  {
484
- type: "text" as const,
484
+ type: 'text' as const,
485
485
  text: removed ? `Removed reminder: ${id}` : `Reminder not found: ${id}`,
486
486
  },
487
487
  ],
@@ -491,49 +491,49 @@ export function createServer(): McpServer {
491
491
 
492
492
  // Tool: save_conversation_summary
493
493
  server.tool(
494
- "save_conversation_summary",
495
- "Save a summary of the current conversation for context recovery in future sessions",
494
+ 'save_conversation_summary',
495
+ 'Save a summary of the current conversation for context recovery in future sessions',
496
496
  {
497
- summary: z.string().describe("Brief summary of what was discussed/accomplished"),
498
- keyDecisions: z.array(z.string()).optional().describe("Important decisions made"),
499
- openThreads: z.array(z.string()).optional().describe("Topics to follow up on"),
497
+ summary: z.string().describe('Brief summary of what was discussed/accomplished'),
498
+ keyDecisions: z.array(z.string()).optional().describe('Important decisions made'),
499
+ openThreads: z.array(z.string()).optional().describe('Topics to follow up on'),
500
500
  learnedPatterns: z
501
501
  .array(z.string())
502
502
  .optional()
503
- .describe("New patterns learned about the user"),
504
- notes: z.string().optional().describe("Freeform notes"),
503
+ .describe('New patterns learned about the user'),
504
+ notes: z.string().optional().describe('Freeform notes'),
505
505
  },
506
506
  async ({ summary, keyDecisions, openThreads, learnedPatterns, notes }) => {
507
507
  ensureDirectories();
508
508
 
509
509
  const parts = [summary];
510
- if (keyDecisions?.length) parts.push(`Decisions: ${keyDecisions.join(", ")}`);
511
- if (openThreads?.length) parts.push(`Open threads: ${openThreads.join(", ")}`);
512
- if (learnedPatterns?.length) parts.push(`Learned: ${learnedPatterns.join(", ")}`);
510
+ if (keyDecisions?.length) parts.push(`Decisions: ${keyDecisions.join(', ')}`);
511
+ if (openThreads?.length) parts.push(`Open threads: ${openThreads.join(', ')}`);
512
+ if (learnedPatterns?.length) parts.push(`Learned: ${learnedPatterns.join(', ')}`);
513
513
  if (notes) parts.push(`Notes: ${notes}`);
514
514
 
515
515
  const entry: JournalEntry = {
516
516
  timestamp: new Date().toISOString(),
517
- topic: "conversation-summary",
518
- content: parts.join("\n"),
519
- metadata: { source: "conversation" },
517
+ topic: 'conversation-summary',
518
+ content: parts.join('\n'),
519
+ metadata: { source: 'conversation' },
520
520
  };
521
521
 
522
522
  const journalPath = getTodayJournalPath();
523
- appendFileSync(journalPath, JSON.stringify(entry) + "\n");
523
+ appendFileSync(journalPath, JSON.stringify(entry) + '\n');
524
524
 
525
525
  // Index for semantic search
526
526
  try {
527
527
  await indexJournalEntry(entry);
528
528
  } catch (err) {
529
- console.error("[save_conversation_summary] Failed to index:", err);
529
+ console.error('[save_conversation_summary] Failed to index:', err);
530
530
  }
531
531
 
532
532
  return {
533
533
  content: [
534
534
  {
535
- type: "text" as const,
536
- text: "Conversation summary saved.",
535
+ type: 'text' as const,
536
+ text: 'Conversation summary saved.',
537
537
  },
538
538
  ],
539
539
  };
@@ -542,22 +542,22 @@ export function createServer(): McpServer {
542
542
 
543
543
  // Tool: get_recent_summaries
544
544
  server.tool(
545
- "get_recent_summaries",
546
- "Get recent conversation summaries for context recovery",
545
+ 'get_recent_summaries',
546
+ 'Get recent conversation summaries for context recovery',
547
547
  {
548
- count: z.number().default(7).describe("Number of summaries to retrieve"),
548
+ count: z.number().default(7).describe('Number of summaries to retrieve'),
549
549
  },
550
550
  async ({ count }) => {
551
551
  // Get recent journal entries filtered by topic
552
552
  let entries = getRecentJournalEntries(count * 3);
553
- entries = entries.filter((e) => e.topic === "conversation-summary").slice(0, count);
553
+ entries = entries.filter((e) => e.topic === 'conversation-summary').slice(0, count);
554
554
 
555
555
  if (entries.length === 0) {
556
556
  return {
557
557
  content: [
558
558
  {
559
- type: "text" as const,
560
- text: "No conversation summaries yet.",
559
+ type: 'text' as const,
560
+ text: 'No conversation summaries yet.',
561
561
  },
562
562
  ],
563
563
  };
@@ -566,7 +566,7 @@ export function createServer(): McpServer {
566
566
  return {
567
567
  content: [
568
568
  {
569
- type: "text" as const,
569
+ type: 'text' as const,
570
570
  text: JSON.stringify(entries, null, 2),
571
571
  },
572
572
  ],
@@ -576,8 +576,8 @@ export function createServer(): McpServer {
576
576
 
577
577
  // Tool: search_conversations
578
578
  server.tool(
579
- "search_conversations",
580
- "Search past Claude Code conversations for similar problems/solutions. By default searches current project first, with recent conversations weighted higher.",
579
+ 'search_conversations',
580
+ 'Search past Claude Code conversations for similar problems/solutions. By default searches current project first, with recent conversations weighted higher.',
581
581
  {
582
582
  query: z
583
583
  .string()
@@ -587,8 +587,8 @@ export function createServer(): McpServer {
587
587
  projectOnly: z
588
588
  .boolean()
589
589
  .default(false)
590
- .describe("Only search current project (default: search all but boost current)"),
591
- limit: z.number().default(5).describe("Maximum results to return"),
590
+ .describe('Only search current project (default: search all but boost current)'),
591
+ limit: z.number().default(5).describe('Maximum results to return'),
592
592
  },
593
593
  async ({ query, projectOnly, limit }) => {
594
594
  try {
@@ -605,7 +605,7 @@ export function createServer(): McpServer {
605
605
  return {
606
606
  content: [
607
607
  {
608
- type: "text" as const,
608
+ type: 'text' as const,
609
609
  text: "No matching conversations found. Try manage_index(target: 'conversations', action: 'update').",
610
610
  },
611
611
  ],
@@ -616,17 +616,17 @@ export function createServer(): McpServer {
616
616
  const formatted = results
617
617
  .map((r, i) => {
618
618
  const date = new Date(r.exchange.timestamp).toLocaleDateString();
619
- const branch = r.exchange.branch ? ` (${r.exchange.branch})` : "";
619
+ const branch = r.exchange.branch ? ` (${r.exchange.branch})` : '';
620
620
  return `[${i + 1}] ${r.exchange.project}${branch} - ${date}
621
- "${r.exchange.userPrompt.slice(0, 200)}${r.exchange.userPrompt.length > 200 ? "..." : ""}"
621
+ "${r.exchange.userPrompt.slice(0, 200)}${r.exchange.userPrompt.length > 200 ? '...' : ''}"
622
622
  Session: ${r.exchange.sessionId}`;
623
623
  })
624
- .join("\n\n");
624
+ .join('\n\n');
625
625
 
626
626
  return {
627
627
  content: [
628
628
  {
629
- type: "text" as const,
629
+ type: 'text' as const,
630
630
  text: `Found ${results.length} relevant conversation(s):\n\n${formatted}\n\nUse expand_conversation to see full context.`,
631
631
  },
632
632
  ],
@@ -635,7 +635,7 @@ export function createServer(): McpServer {
635
635
  return {
636
636
  content: [
637
637
  {
638
- type: "text" as const,
638
+ type: 'text' as const,
639
639
  text: `Search error: ${String(err)}`,
640
640
  },
641
641
  ],
@@ -646,45 +646,45 @@ export function createServer(): McpServer {
646
646
 
647
647
  // Tool: expand_conversation
648
648
  server.tool(
649
- "expand_conversation",
650
- "Load full context from a past conversation. Use after search_conversations to see the complete exchange.",
649
+ 'expand_conversation',
650
+ 'Load full context from a past conversation. Use after search_conversations to see the complete exchange.',
651
651
  {
652
- sessionPath: z.string().describe("Session file path from search results"),
653
- messageUuid: z.string().optional().describe("Specific message UUID to center on"),
654
- contextMessages: z.number().default(10).describe("Number of messages to include"),
652
+ sessionPath: z.string().describe('Session file path from search results'),
653
+ messageUuid: z.string().optional().describe('Specific message UUID to center on'),
654
+ contextMessages: z.number().default(10).describe('Number of messages to include'),
655
655
  },
656
656
  async ({ sessionPath, messageUuid, contextMessages }) => {
657
657
  try {
658
658
  // Resolve session path if only ID given
659
659
  let fullPath = sessionPath;
660
- if (!sessionPath.startsWith("/")) {
660
+ if (!sessionPath.startsWith('/')) {
661
661
  // Assume it's a session ID, need to find the file
662
662
  // For now, require full path
663
663
  return {
664
664
  content: [
665
665
  {
666
- type: "text" as const,
667
- text: "Please provide the full session path from search results.",
666
+ type: 'text' as const,
667
+ text: 'Please provide the full session path from search results.',
668
668
  },
669
669
  ],
670
670
  };
671
671
  }
672
672
 
673
- const result = await expandConversation(fullPath, messageUuid || "", contextMessages);
673
+ const result = await expandConversation(fullPath, messageUuid || '', contextMessages);
674
674
 
675
675
  const formatted = result.messages
676
676
  .map((m) => {
677
- const prefix = m.role === "user" ? "User" : "Assistant";
677
+ const prefix = m.role === 'user' ? 'User' : 'Assistant';
678
678
  return `**${prefix}**: ${m.content}`;
679
679
  })
680
- .join("\n\n---\n\n");
680
+ .join('\n\n---\n\n');
681
681
 
682
- const header = `Project: ${result.project}${result.branch ? ` (${result.branch})` : ""}\n\n`;
682
+ const header = `Project: ${result.project}${result.branch ? ` (${result.branch})` : ''}\n\n`;
683
683
 
684
684
  return {
685
685
  content: [
686
686
  {
687
- type: "text" as const,
687
+ type: 'text' as const,
688
688
  text: header + formatted,
689
689
  },
690
690
  ],
@@ -693,7 +693,7 @@ export function createServer(): McpServer {
693
693
  return {
694
694
  content: [
695
695
  {
696
- type: "text" as const,
696
+ type: 'text' as const,
697
697
  text: `Failed to expand conversation: ${String(err)}`,
698
698
  },
699
699
  ],