devsh-memory-mcp 0.1.1 → 0.2.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.
@@ -0,0 +1,1307 @@
1
+ // src/index.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import * as crypto from "crypto";
11
+ var DEFAULT_MEMORY_DIR = "/root/lifecycle/memory";
12
+ function createMemoryMcpServer(config) {
13
+ const memoryDir = config?.memoryDir ?? DEFAULT_MEMORY_DIR;
14
+ const resolvedAgentName = config?.agentName ?? process.env.CMUX_AGENT_NAME;
15
+ const agentName = resolvedAgentName ?? "external-client";
16
+ if (!resolvedAgentName) {
17
+ console.error(
18
+ '[devsh-memory-mcp] Warning: no agent identity provided; falling back to "external-client". Pass --agent <name> or set CMUX_AGENT_NAME to preserve mailbox sender identity.'
19
+ );
20
+ }
21
+ const knowledgeDir = path.join(memoryDir, "knowledge");
22
+ const dailyDir = path.join(memoryDir, "daily");
23
+ const orchestrationDir = path.join(memoryDir, "orchestration");
24
+ const mailboxPath = path.join(memoryDir, "MAILBOX.json");
25
+ const tasksPath = path.join(memoryDir, "TASKS.json");
26
+ const planPath = path.join(orchestrationDir, "PLAN.json");
27
+ const agentsPath = path.join(orchestrationDir, "AGENTS.json");
28
+ const eventsPath = path.join(orchestrationDir, "EVENTS.jsonl");
29
+ function readFile(filePath) {
30
+ try {
31
+ if (!fs.existsSync(filePath)) return null;
32
+ return fs.readFileSync(filePath, "utf-8");
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ function writeFile(filePath, content) {
38
+ try {
39
+ fs.writeFileSync(filePath, content, "utf-8");
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ function readTasks() {
46
+ const content = readFile(tasksPath);
47
+ if (!content) return { version: 1, tasks: [] };
48
+ try {
49
+ return JSON.parse(content);
50
+ } catch {
51
+ return { version: 1, tasks: [] };
52
+ }
53
+ }
54
+ function writeTasks(tasks) {
55
+ return writeFile(tasksPath, JSON.stringify(tasks, null, 2));
56
+ }
57
+ function generateTaskId() {
58
+ return "task_" + crypto.randomUUID().replace(/-/g, "").slice(0, 12);
59
+ }
60
+ function getTodayDateString() {
61
+ const iso = (/* @__PURE__ */ new Date()).toISOString();
62
+ return iso.slice(0, iso.indexOf("T"));
63
+ }
64
+ function ensureDir(dirPath) {
65
+ if (!fs.existsSync(dirPath)) {
66
+ fs.mkdirSync(dirPath, { recursive: true });
67
+ }
68
+ }
69
+ function readPlan() {
70
+ const content = readFile(planPath);
71
+ if (!content) return null;
72
+ try {
73
+ return JSON.parse(content);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function writePlan(plan) {
79
+ ensureDir(orchestrationDir);
80
+ plan.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
81
+ return writeFile(planPath, JSON.stringify(plan, null, 2));
82
+ }
83
+ function appendEvent(event) {
84
+ ensureDir(orchestrationDir);
85
+ const line = JSON.stringify(event) + "\n";
86
+ try {
87
+ fs.appendFileSync(eventsPath, line, "utf-8");
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ function readMailbox() {
94
+ const content = readFile(mailboxPath);
95
+ if (!content) return { version: 1, messages: [] };
96
+ try {
97
+ return JSON.parse(content);
98
+ } catch {
99
+ return { version: 1, messages: [] };
100
+ }
101
+ }
102
+ function writeMailbox(mailbox) {
103
+ return writeFile(mailboxPath, JSON.stringify(mailbox, null, 2));
104
+ }
105
+ function generateMessageId() {
106
+ return "msg_" + crypto.randomUUID().replace(/-/g, "").slice(0, 12);
107
+ }
108
+ function listDailyLogs() {
109
+ try {
110
+ if (!fs.existsSync(dailyDir)) return [];
111
+ const files = fs.readdirSync(dailyDir);
112
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", "")).sort().reverse();
113
+ } catch {
114
+ return [];
115
+ }
116
+ }
117
+ function searchMemory(query) {
118
+ const results = [];
119
+ const lowerQuery = query.toLowerCase();
120
+ const knowledge = readFile(path.join(knowledgeDir, "MEMORY.md"));
121
+ if (knowledge?.toLowerCase().includes(lowerQuery)) {
122
+ const lines = knowledge.split("\n");
123
+ for (let i = 0; i < lines.length; i++) {
124
+ if (lines[i].toLowerCase().includes(lowerQuery)) {
125
+ results.push({
126
+ source: "knowledge/MEMORY.md",
127
+ line: i + 1,
128
+ content: lines[i].trim()
129
+ });
130
+ }
131
+ }
132
+ }
133
+ const tasks = readFile(tasksPath);
134
+ if (tasks?.toLowerCase().includes(lowerQuery)) {
135
+ results.push({ source: "TASKS.json", content: "Match found in tasks file" });
136
+ }
137
+ const mailbox = readFile(mailboxPath);
138
+ if (mailbox?.toLowerCase().includes(lowerQuery)) {
139
+ results.push({ source: "MAILBOX.json", content: "Match found in mailbox file" });
140
+ }
141
+ const dailyLogs = listDailyLogs();
142
+ for (const date of dailyLogs.slice(0, 7)) {
143
+ const logContent = readFile(path.join(dailyDir, `${date}.md`));
144
+ if (logContent?.toLowerCase().includes(lowerQuery)) {
145
+ const lines = logContent.split("\n");
146
+ for (let i = 0; i < lines.length; i++) {
147
+ if (lines[i].toLowerCase().includes(lowerQuery)) {
148
+ results.push({
149
+ source: `daily/${date}.md`,
150
+ line: i + 1,
151
+ content: lines[i].trim()
152
+ });
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return results;
158
+ }
159
+ const server = new Server(
160
+ {
161
+ name: "devsh-memory",
162
+ version: "0.1.0"
163
+ },
164
+ {
165
+ capabilities: {
166
+ tools: {}
167
+ }
168
+ }
169
+ );
170
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
171
+ tools: [
172
+ {
173
+ name: "read_memory",
174
+ description: 'Read a memory file. Type can be "knowledge", "tasks", or "mailbox".',
175
+ inputSchema: {
176
+ type: "object",
177
+ properties: {
178
+ type: {
179
+ type: "string",
180
+ enum: ["knowledge", "tasks", "mailbox"],
181
+ description: "The type of memory to read"
182
+ }
183
+ },
184
+ required: ["type"]
185
+ }
186
+ },
187
+ {
188
+ name: "list_daily_logs",
189
+ description: "List available daily log dates (newest first).",
190
+ inputSchema: {
191
+ type: "object",
192
+ properties: {}
193
+ }
194
+ },
195
+ {
196
+ name: "read_daily_log",
197
+ description: "Read a specific daily log by date (YYYY-MM-DD format).",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ date: {
202
+ type: "string",
203
+ description: "The date in YYYY-MM-DD format"
204
+ }
205
+ },
206
+ required: ["date"]
207
+ }
208
+ },
209
+ {
210
+ name: "search_memory",
211
+ description: "Search across all memory files for a query string.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ query: {
216
+ type: "string",
217
+ description: "The search query"
218
+ }
219
+ },
220
+ required: ["query"]
221
+ }
222
+ },
223
+ {
224
+ name: "send_message",
225
+ description: 'Send a message to another agent on the same task. Use "*" to broadcast to all agents.',
226
+ inputSchema: {
227
+ type: "object",
228
+ properties: {
229
+ to: {
230
+ type: "string",
231
+ description: 'Recipient agent name (e.g., "claude/opus-4.5") or "*" for broadcast'
232
+ },
233
+ message: {
234
+ type: "string",
235
+ description: "The message content"
236
+ },
237
+ type: {
238
+ type: "string",
239
+ enum: ["handoff", "request", "status"],
240
+ description: "Message type: handoff (work transfer), request (ask to do something), status (progress update)"
241
+ }
242
+ },
243
+ required: ["to", "message"]
244
+ }
245
+ },
246
+ {
247
+ name: "get_my_messages",
248
+ description: "Get all messages addressed to this agent (including broadcasts). Returns unread messages first.",
249
+ inputSchema: {
250
+ type: "object",
251
+ properties: {
252
+ includeRead: {
253
+ type: "boolean",
254
+ description: "Include messages already marked as read (default: false)"
255
+ }
256
+ }
257
+ }
258
+ },
259
+ {
260
+ name: "mark_read",
261
+ description: "Mark a message as read by its ID.",
262
+ inputSchema: {
263
+ type: "object",
264
+ properties: {
265
+ messageId: {
266
+ type: "string",
267
+ description: "The message ID to mark as read"
268
+ }
269
+ },
270
+ required: ["messageId"]
271
+ }
272
+ },
273
+ // Write tools
274
+ {
275
+ name: "append_daily_log",
276
+ description: "Append content to today's daily log. Creates the file if it doesn't exist.",
277
+ inputSchema: {
278
+ type: "object",
279
+ properties: {
280
+ content: {
281
+ type: "string",
282
+ description: "Content to append to the daily log"
283
+ }
284
+ },
285
+ required: ["content"]
286
+ }
287
+ },
288
+ {
289
+ name: "update_knowledge",
290
+ description: "Update a specific priority section in the knowledge file (MEMORY.md). Appends a new entry with today's date.",
291
+ inputSchema: {
292
+ type: "object",
293
+ properties: {
294
+ section: {
295
+ type: "string",
296
+ enum: ["P0", "P1", "P2"],
297
+ description: "Priority section to update (P0=Core, P1=Active, P2=Reference)"
298
+ },
299
+ content: {
300
+ type: "string",
301
+ description: "Content to add to the section (will be prefixed with today's date)"
302
+ }
303
+ },
304
+ required: ["section", "content"]
305
+ }
306
+ },
307
+ {
308
+ name: "add_task",
309
+ description: "Add a new task to the TASKS.json file.",
310
+ inputSchema: {
311
+ type: "object",
312
+ properties: {
313
+ subject: {
314
+ type: "string",
315
+ description: "Brief title for the task"
316
+ },
317
+ description: {
318
+ type: "string",
319
+ description: "Detailed description of what needs to be done"
320
+ }
321
+ },
322
+ required: ["subject", "description"]
323
+ }
324
+ },
325
+ {
326
+ name: "update_task",
327
+ description: "Update the status of an existing task in TASKS.json.",
328
+ inputSchema: {
329
+ type: "object",
330
+ properties: {
331
+ taskId: {
332
+ type: "string",
333
+ description: "The ID of the task to update"
334
+ },
335
+ status: {
336
+ type: "string",
337
+ enum: ["pending", "in_progress", "completed"],
338
+ description: "New status for the task"
339
+ }
340
+ },
341
+ required: ["taskId", "status"]
342
+ }
343
+ },
344
+ // Orchestration tools
345
+ {
346
+ name: "read_orchestration",
347
+ description: "Read an orchestration file (PLAN.json, AGENTS.json, or EVENTS.jsonl).",
348
+ inputSchema: {
349
+ type: "object",
350
+ properties: {
351
+ type: {
352
+ type: "string",
353
+ enum: ["plan", "agents", "events"],
354
+ description: "Type of orchestration file to read"
355
+ }
356
+ },
357
+ required: ["type"]
358
+ }
359
+ },
360
+ {
361
+ name: "append_event",
362
+ description: "Append an orchestration event to EVENTS.jsonl.",
363
+ inputSchema: {
364
+ type: "object",
365
+ properties: {
366
+ event: {
367
+ type: "string",
368
+ description: "Event type (e.g., agent_spawned, agent_completed, message_sent)"
369
+ },
370
+ message: {
371
+ type: "string",
372
+ description: "Human-readable message describing the event"
373
+ },
374
+ agentName: {
375
+ type: "string",
376
+ description: "Agent name associated with the event (optional)"
377
+ },
378
+ taskRunId: {
379
+ type: "string",
380
+ description: "Task run ID associated with the event (optional)"
381
+ }
382
+ },
383
+ required: ["event", "message"]
384
+ }
385
+ },
386
+ {
387
+ name: "update_plan_task",
388
+ description: "Update the status of a task in the orchestration PLAN.json.",
389
+ inputSchema: {
390
+ type: "object",
391
+ properties: {
392
+ taskId: {
393
+ type: "string",
394
+ description: "The ID of the orchestration task to update"
395
+ },
396
+ status: {
397
+ type: "string",
398
+ description: "New status (pending, assigned, running, completed, failed, cancelled)"
399
+ },
400
+ result: {
401
+ type: "string",
402
+ description: "Result message (for completed tasks)"
403
+ },
404
+ errorMessage: {
405
+ type: "string",
406
+ description: "Error message (for failed tasks)"
407
+ }
408
+ },
409
+ required: ["taskId", "status"]
410
+ }
411
+ },
412
+ {
413
+ name: "pull_orchestration_updates",
414
+ description: "Sync local orchestration state (PLAN.json) with the server. Fetches latest task statuses, messages, and aggregated progress. Requires CMUX_TASK_RUN_JWT environment variable.",
415
+ inputSchema: {
416
+ type: "object",
417
+ properties: {
418
+ orchestrationId: {
419
+ type: "string",
420
+ description: "The orchestration ID to sync. Uses CMUX_ORCHESTRATION_ID env var if not provided."
421
+ }
422
+ }
423
+ }
424
+ },
425
+ {
426
+ name: "spawn_agent",
427
+ description: "Spawn a sub-agent to work on a task. Requires CMUX_TASK_RUN_JWT for authentication. The sub-agent runs in a new sandbox and works on the specified prompt.",
428
+ inputSchema: {
429
+ type: "object",
430
+ properties: {
431
+ prompt: {
432
+ type: "string",
433
+ description: "The task prompt for the sub-agent"
434
+ },
435
+ agentName: {
436
+ type: "string",
437
+ description: "Agent to use (e.g., 'claude/haiku-4.5', 'codex/gpt-5.1-codex-mini')"
438
+ },
439
+ repo: {
440
+ type: "string",
441
+ description: "GitHub repository in owner/repo format (optional)"
442
+ },
443
+ branch: {
444
+ type: "string",
445
+ description: "Base branch to checkout (optional, defaults to main)"
446
+ },
447
+ dependsOn: {
448
+ type: "array",
449
+ items: { type: "string" },
450
+ description: "Array of orchestration task IDs this task depends on (optional)"
451
+ },
452
+ priority: {
453
+ type: "number",
454
+ description: "Task priority (0=highest, 10=lowest, default 5)"
455
+ }
456
+ },
457
+ required: ["prompt", "agentName"]
458
+ }
459
+ },
460
+ {
461
+ name: "get_agent_status",
462
+ description: "Get the status of a spawned sub-agent by orchestration task ID. Returns current status, result, and linked task run info.",
463
+ inputSchema: {
464
+ type: "object",
465
+ properties: {
466
+ orchestrationTaskId: {
467
+ type: "string",
468
+ description: "The orchestration task ID returned from spawn_agent"
469
+ }
470
+ },
471
+ required: ["orchestrationTaskId"]
472
+ }
473
+ },
474
+ {
475
+ name: "wait_for_agent",
476
+ description: "Wait for a spawned sub-agent to reach a terminal state (completed, failed, or cancelled). Polls every 5 seconds until the agent finishes or timeout.",
477
+ inputSchema: {
478
+ type: "object",
479
+ properties: {
480
+ orchestrationTaskId: {
481
+ type: "string",
482
+ description: "The orchestration task ID to wait for"
483
+ },
484
+ timeout: {
485
+ type: "number",
486
+ description: "Maximum wait time in milliseconds (default: 300000 = 5 minutes)"
487
+ }
488
+ },
489
+ required: ["orchestrationTaskId"]
490
+ }
491
+ },
492
+ {
493
+ name: "list_spawned_agents",
494
+ description: "List all sub-agents spawned by this orchestration. Returns status summary of all tasks.",
495
+ inputSchema: {
496
+ type: "object",
497
+ properties: {
498
+ status: {
499
+ type: "string",
500
+ enum: ["pending", "assigned", "running", "completed", "failed", "cancelled"],
501
+ description: "Filter by status (optional)"
502
+ }
503
+ }
504
+ }
505
+ },
506
+ {
507
+ name: "cancel_agent",
508
+ description: "Cancel a running or pending sub-agent. The agent will stop and its status will be set to cancelled.",
509
+ inputSchema: {
510
+ type: "object",
511
+ properties: {
512
+ orchestrationTaskId: {
513
+ type: "string",
514
+ description: "The orchestration task ID to cancel"
515
+ },
516
+ cascade: {
517
+ type: "boolean",
518
+ description: "Also cancel dependent tasks (default: false)"
519
+ }
520
+ },
521
+ required: ["orchestrationTaskId"]
522
+ }
523
+ },
524
+ {
525
+ name: "get_orchestration_summary",
526
+ description: "Get a summary of the current orchestration including task counts by status, active agents, and recent completions.",
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {}
530
+ }
531
+ }
532
+ ]
533
+ }));
534
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
535
+ const { name, arguments: args } = request.params;
536
+ switch (name) {
537
+ case "read_memory": {
538
+ const type = args.type;
539
+ let content = null;
540
+ if (type === "knowledge") {
541
+ content = readFile(path.join(knowledgeDir, "MEMORY.md"));
542
+ } else if (type === "tasks") {
543
+ content = readFile(tasksPath);
544
+ } else if (type === "mailbox") {
545
+ content = readFile(mailboxPath);
546
+ }
547
+ return {
548
+ content: [{ type: "text", text: content ?? `No ${type} content found.` }]
549
+ };
550
+ }
551
+ case "list_daily_logs": {
552
+ const dates = listDailyLogs();
553
+ return {
554
+ content: [{ type: "text", text: dates.length > 0 ? dates.join("\n") : "No daily logs found." }]
555
+ };
556
+ }
557
+ case "read_daily_log": {
558
+ const date = args.date;
559
+ const content = readFile(path.join(dailyDir, `${date}.md`));
560
+ return {
561
+ content: [{ type: "text", text: content ?? `No log found for ${date}.` }]
562
+ };
563
+ }
564
+ case "search_memory": {
565
+ const query = args.query;
566
+ const results = searchMemory(query);
567
+ if (results.length === 0) {
568
+ return { content: [{ type: "text", text: `No results found for "${query}".` }] };
569
+ }
570
+ const formatted = results.map((r) => `[${r.source}${r.line ? `:${r.line}` : ""}] ${r.content}`).join("\n");
571
+ return { content: [{ type: "text", text: formatted }] };
572
+ }
573
+ case "send_message": {
574
+ const { to, message, type } = args;
575
+ const mailbox = readMailbox();
576
+ const newMessage = {
577
+ id: generateMessageId(),
578
+ from: agentName,
579
+ to,
580
+ type: type ?? "request",
581
+ message,
582
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
583
+ read: false
584
+ };
585
+ mailbox.messages.push(newMessage);
586
+ writeMailbox(mailbox);
587
+ return { content: [{ type: "text", text: `Message sent successfully. ID: ${newMessage.id}` }] };
588
+ }
589
+ case "get_my_messages": {
590
+ const includeRead = args.includeRead ?? false;
591
+ const mailbox = readMailbox();
592
+ const myMessages = mailbox.messages.filter(
593
+ (m) => m.to === agentName || m.to === "*"
594
+ );
595
+ const filtered = includeRead ? myMessages : myMessages.filter((m) => !m.read);
596
+ if (filtered.length === 0) {
597
+ return { content: [{ type: "text", text: "No messages for you." }] };
598
+ }
599
+ const formatted = filtered.map((m) => `[${m.id}] ${m.type ?? "message"} from ${m.from}: ${m.message}`).join("\n\n");
600
+ return { content: [{ type: "text", text: formatted }] };
601
+ }
602
+ case "mark_read": {
603
+ const messageId = args.messageId;
604
+ const mailbox = readMailbox();
605
+ const message = mailbox.messages.find((m) => m.id === messageId);
606
+ if (!message) {
607
+ return { content: [{ type: "text", text: `Message ${messageId} not found.` }] };
608
+ }
609
+ message.read = true;
610
+ writeMailbox(mailbox);
611
+ return { content: [{ type: "text", text: `Message ${messageId} marked as read.` }] };
612
+ }
613
+ // Write tool handlers
614
+ case "append_daily_log": {
615
+ const { content } = args;
616
+ const today = getTodayDateString();
617
+ ensureDir(dailyDir);
618
+ const logPath = path.join(dailyDir, `${today}.md`);
619
+ const existing = readFile(logPath) ?? `# Daily Log: ${today}
620
+
621
+ > Session-specific observations. Temporary notes go here.
622
+
623
+ ---
624
+ `;
625
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].split(".")[0];
626
+ const newContent = existing + `
627
+ - [${timestamp}] ${content}`;
628
+ if (writeFile(logPath, newContent)) {
629
+ return { content: [{ type: "text", text: `Appended to daily/${today}.md` }] };
630
+ }
631
+ return { content: [{ type: "text", text: `Failed to append to daily log` }] };
632
+ }
633
+ case "update_knowledge": {
634
+ const { section, content } = args;
635
+ ensureDir(knowledgeDir);
636
+ const knowledgePath = path.join(knowledgeDir, "MEMORY.md");
637
+ let existing = readFile(knowledgePath);
638
+ if (!existing) {
639
+ existing = `# Project Knowledge
640
+
641
+ > Curated insights organized by priority. Add date tags for TTL tracking.
642
+
643
+ ## P0 - Core (Never Expires)
644
+ <!-- Fundamental project facts, configuration, invariants -->
645
+
646
+ ## P1 - Active (90-day TTL)
647
+ <!-- Ongoing work context, current strategies, recent decisions -->
648
+
649
+ ## P2 - Reference (30-day TTL)
650
+ <!-- Temporary findings, debug notes, one-off context -->
651
+
652
+ ---
653
+ *Priority guide: P0 = permanent truth, P1 = active context, P2 = temporary reference*
654
+ *Format: - [YYYY-MM-DD] Your insight here*
655
+ `;
656
+ }
657
+ const today = getTodayDateString();
658
+ const newEntry = `- [${today}] ${content}`;
659
+ const sectionHeaders = {
660
+ P0: "## P0 - Core (Never Expires)",
661
+ P1: "## P1 - Active (90-day TTL)",
662
+ P2: "## P2 - Reference (30-day TTL)"
663
+ };
664
+ const header = sectionHeaders[section];
665
+ const headerIndex = existing.indexOf(header);
666
+ if (headerIndex === -1) {
667
+ return { content: [{ type: "text", text: `Section ${section} not found in MEMORY.md` }] };
668
+ }
669
+ const afterHeader = existing.slice(headerIndex + header.length);
670
+ const nextSectionMatch = afterHeader.match(/\n## /);
671
+ const insertPoint = nextSectionMatch ? headerIndex + header.length + (nextSectionMatch.index ?? afterHeader.length) : existing.length;
672
+ const commentEndMatch = afterHeader.match(/<!--[^>]*-->\n/);
673
+ const commentEnd = commentEndMatch ? headerIndex + header.length + (commentEndMatch.index ?? 0) + commentEndMatch[0].length : headerIndex + header.length + 1;
674
+ const actualInsertPoint = Math.min(commentEnd, insertPoint);
675
+ const updated = existing.slice(0, actualInsertPoint) + newEntry + "\n" + existing.slice(actualInsertPoint);
676
+ if (writeFile(knowledgePath, updated)) {
677
+ return { content: [{ type: "text", text: `Added entry to ${section} section in MEMORY.md` }] };
678
+ }
679
+ return { content: [{ type: "text", text: `Failed to update MEMORY.md` }] };
680
+ }
681
+ case "add_task": {
682
+ const { subject, description } = args;
683
+ const tasks = readTasks();
684
+ const now = (/* @__PURE__ */ new Date()).toISOString();
685
+ const newTask = {
686
+ id: generateTaskId(),
687
+ subject,
688
+ description,
689
+ status: "pending",
690
+ createdAt: now,
691
+ updatedAt: now
692
+ };
693
+ tasks.tasks.push(newTask);
694
+ if (writeTasks(tasks)) {
695
+ return { content: [{ type: "text", text: `Task created with ID: ${newTask.id}` }] };
696
+ }
697
+ return { content: [{ type: "text", text: `Failed to create task` }] };
698
+ }
699
+ case "update_task": {
700
+ const { taskId, status } = args;
701
+ const tasks = readTasks();
702
+ const task = tasks.tasks.find((t) => t.id === taskId);
703
+ if (!task) {
704
+ return { content: [{ type: "text", text: `Task ${taskId} not found` }] };
705
+ }
706
+ task.status = status;
707
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
708
+ if (writeTasks(tasks)) {
709
+ return { content: [{ type: "text", text: `Task ${taskId} updated to status: ${status}` }] };
710
+ }
711
+ return { content: [{ type: "text", text: `Failed to update task` }] };
712
+ }
713
+ // Orchestration tool handlers
714
+ case "read_orchestration": {
715
+ const type = args.type;
716
+ let content = null;
717
+ if (type === "plan") {
718
+ content = readFile(planPath);
719
+ } else if (type === "agents") {
720
+ content = readFile(agentsPath);
721
+ } else if (type === "events") {
722
+ content = readFile(eventsPath);
723
+ }
724
+ return {
725
+ content: [{ type: "text", text: content ?? `No ${type} file found in orchestration directory.` }]
726
+ };
727
+ }
728
+ case "append_event": {
729
+ const { event, message, agentName: agentName2, taskRunId } = args;
730
+ const eventObj = {
731
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
732
+ event,
733
+ message
734
+ };
735
+ if (agentName2) eventObj.agentName = agentName2;
736
+ if (taskRunId) eventObj.taskRunId = taskRunId;
737
+ if (appendEvent(eventObj)) {
738
+ return { content: [{ type: "text", text: `Event appended to EVENTS.jsonl` }] };
739
+ }
740
+ return { content: [{ type: "text", text: `Failed to append event` }] };
741
+ }
742
+ case "update_plan_task": {
743
+ const { taskId, status, result, errorMessage } = args;
744
+ const plan = readPlan();
745
+ if (!plan) {
746
+ return { content: [{ type: "text", text: `No PLAN.json found in orchestration directory` }] };
747
+ }
748
+ const task = plan.tasks.find((t) => t.id === taskId);
749
+ if (!task) {
750
+ return { content: [{ type: "text", text: `Task ${taskId} not found in PLAN.json` }] };
751
+ }
752
+ task.status = status;
753
+ if (result !== void 0) task.result = result;
754
+ if (errorMessage !== void 0) task.errorMessage = errorMessage;
755
+ if (status === "running" && !task.startedAt) {
756
+ task.startedAt = (/* @__PURE__ */ new Date()).toISOString();
757
+ }
758
+ if (status === "completed" || status === "failed" || status === "cancelled") {
759
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
760
+ }
761
+ if (writePlan(plan)) {
762
+ return { content: [{ type: "text", text: `Plan task ${taskId} updated to status: ${status}` }] };
763
+ }
764
+ return { content: [{ type: "text", text: `Failed to update plan task` }] };
765
+ }
766
+ case "pull_orchestration_updates": {
767
+ const { orchestrationId: argOrchId } = args;
768
+ const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
769
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
770
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
771
+ if (!orchestrationId) {
772
+ return {
773
+ content: [{
774
+ type: "text",
775
+ text: "No orchestration ID provided. Pass orchestrationId parameter or set CMUX_ORCHESTRATION_ID env var."
776
+ }]
777
+ };
778
+ }
779
+ if (!jwt) {
780
+ return {
781
+ content: [{
782
+ type: "text",
783
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
784
+ }]
785
+ };
786
+ }
787
+ try {
788
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/${orchestrationId}/sync`;
789
+ const response = await fetch(url, {
790
+ method: "GET",
791
+ headers: {
792
+ "Authorization": `Bearer ${jwt}`,
793
+ "Content-Type": "application/json"
794
+ }
795
+ });
796
+ if (!response.ok) {
797
+ const errorText = await response.text();
798
+ return {
799
+ content: [{
800
+ type: "text",
801
+ text: `Failed to fetch orchestration updates: ${response.status} ${errorText}`
802
+ }]
803
+ };
804
+ }
805
+ const serverData = await response.json();
806
+ let plan = readPlan();
807
+ if (!plan) {
808
+ plan = {
809
+ version: 1,
810
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
811
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
812
+ status: "running",
813
+ headAgent: agentName,
814
+ orchestrationId,
815
+ tasks: []
816
+ };
817
+ }
818
+ for (const serverTask of serverData.tasks) {
819
+ const localTask = plan.tasks.find((t) => t.id === serverTask.id);
820
+ if (localTask) {
821
+ localTask.status = serverTask.status;
822
+ localTask.taskRunId = serverTask.taskRunId;
823
+ localTask.result = serverTask.result;
824
+ localTask.errorMessage = serverTask.errorMessage;
825
+ localTask.startedAt = serverTask.startedAt;
826
+ localTask.completedAt = serverTask.completedAt;
827
+ } else {
828
+ plan.tasks.push(serverTask);
829
+ }
830
+ }
831
+ const agg = serverData.aggregatedStatus;
832
+ if (agg.failed > 0) {
833
+ plan.status = "failed";
834
+ } else if (agg.completed === agg.total && agg.total > 0) {
835
+ plan.status = "completed";
836
+ } else if (agg.running > 0) {
837
+ plan.status = "running";
838
+ } else {
839
+ plan.status = "pending";
840
+ }
841
+ writePlan(plan);
842
+ const mailbox = readMailbox();
843
+ for (const msg of serverData.messages) {
844
+ if (!mailbox.messages.find((m) => m.id === msg.id)) {
845
+ mailbox.messages.push(msg);
846
+ }
847
+ }
848
+ writeMailbox(mailbox);
849
+ appendEvent({
850
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
851
+ event: "orchestration_synced",
852
+ message: `Synced ${serverData.tasks.length} tasks, ${serverData.messages.length} messages`,
853
+ metadata: serverData.aggregatedStatus
854
+ });
855
+ return {
856
+ content: [{
857
+ type: "text",
858
+ text: JSON.stringify({
859
+ synced: true,
860
+ orchestrationId,
861
+ tasks: serverData.tasks.length,
862
+ messages: serverData.messages.length,
863
+ aggregatedStatus: serverData.aggregatedStatus
864
+ }, null, 2)
865
+ }]
866
+ };
867
+ } catch (error) {
868
+ const errorMsg = error instanceof Error ? error.message : String(error);
869
+ return {
870
+ content: [{
871
+ type: "text",
872
+ text: `Error syncing orchestration updates: ${errorMsg}`
873
+ }]
874
+ };
875
+ }
876
+ }
877
+ case "spawn_agent": {
878
+ const { prompt, agentName: spawnAgentName, repo, branch, dependsOn, priority } = args;
879
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
880
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
881
+ const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
882
+ if (!jwt) {
883
+ return {
884
+ content: [{
885
+ type: "text",
886
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
887
+ }]
888
+ };
889
+ }
890
+ try {
891
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/spawn`;
892
+ const response = await fetch(url, {
893
+ method: "POST",
894
+ headers: {
895
+ "Authorization": `Bearer ${jwt}`,
896
+ "Content-Type": "application/json"
897
+ },
898
+ body: JSON.stringify({
899
+ prompt,
900
+ agent: spawnAgentName,
901
+ repo,
902
+ branch,
903
+ dependsOn,
904
+ priority: priority ?? 5,
905
+ orchestrationId
906
+ })
907
+ });
908
+ if (!response.ok) {
909
+ const errorText = await response.text();
910
+ return {
911
+ content: [{
912
+ type: "text",
913
+ text: `Failed to spawn agent: ${response.status} ${errorText}`
914
+ }]
915
+ };
916
+ }
917
+ const result = await response.json();
918
+ let plan = readPlan();
919
+ if (!plan && orchestrationId) {
920
+ plan = {
921
+ version: 1,
922
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
923
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
924
+ status: "running",
925
+ headAgent: agentName,
926
+ orchestrationId,
927
+ tasks: []
928
+ };
929
+ }
930
+ if (plan) {
931
+ plan.tasks.push({
932
+ id: result.orchestrationTaskId,
933
+ prompt,
934
+ agentName: spawnAgentName,
935
+ status: result.status,
936
+ taskRunId: result.taskRunId,
937
+ dependsOn,
938
+ priority: priority ?? 5,
939
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
940
+ });
941
+ writePlan(plan);
942
+ }
943
+ appendEvent({
944
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
945
+ event: "agent_spawned",
946
+ message: `Spawned ${spawnAgentName} for: ${prompt.slice(0, 50)}...`,
947
+ agentName: spawnAgentName,
948
+ taskRunId: result.taskRunId
949
+ });
950
+ return {
951
+ content: [{
952
+ type: "text",
953
+ text: JSON.stringify(result, null, 2)
954
+ }]
955
+ };
956
+ } catch (error) {
957
+ const errorMsg = error instanceof Error ? error.message : String(error);
958
+ return {
959
+ content: [{
960
+ type: "text",
961
+ text: `Error spawning agent: ${errorMsg}`
962
+ }]
963
+ };
964
+ }
965
+ }
966
+ case "get_agent_status": {
967
+ const { orchestrationTaskId } = args;
968
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
969
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
970
+ if (!jwt) {
971
+ return {
972
+ content: [{
973
+ type: "text",
974
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
975
+ }]
976
+ };
977
+ }
978
+ try {
979
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/tasks/${orchestrationTaskId}`;
980
+ const response = await fetch(url, {
981
+ method: "GET",
982
+ headers: {
983
+ "Authorization": `Bearer ${jwt}`,
984
+ "Content-Type": "application/json"
985
+ }
986
+ });
987
+ if (!response.ok) {
988
+ const errorText = await response.text();
989
+ return {
990
+ content: [{
991
+ type: "text",
992
+ text: `Failed to get agent status: ${response.status} ${errorText}`
993
+ }]
994
+ };
995
+ }
996
+ const result = await response.json();
997
+ return {
998
+ content: [{
999
+ type: "text",
1000
+ text: JSON.stringify(result, null, 2)
1001
+ }]
1002
+ };
1003
+ } catch (error) {
1004
+ const errorMsg = error instanceof Error ? error.message : String(error);
1005
+ return {
1006
+ content: [{
1007
+ type: "text",
1008
+ text: `Error getting agent status: ${errorMsg}`
1009
+ }]
1010
+ };
1011
+ }
1012
+ }
1013
+ case "wait_for_agent": {
1014
+ const { orchestrationTaskId, timeout = 3e5 } = args;
1015
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1016
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1017
+ if (!jwt) {
1018
+ return {
1019
+ content: [{
1020
+ type: "text",
1021
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1022
+ }]
1023
+ };
1024
+ }
1025
+ const startTime = Date.now();
1026
+ const pollInterval = 5e3;
1027
+ try {
1028
+ while (Date.now() - startTime < timeout) {
1029
+ const url = `${apiBaseUrl}/api/orchestrate/status/${orchestrationTaskId}`;
1030
+ const response = await fetch(url, {
1031
+ method: "GET",
1032
+ headers: {
1033
+ "X-Task-Run-JWT": jwt,
1034
+ "Content-Type": "application/json"
1035
+ }
1036
+ });
1037
+ if (!response.ok) {
1038
+ const errorText = await response.text();
1039
+ return {
1040
+ content: [{
1041
+ type: "text",
1042
+ text: `Failed to get agent status: ${response.status} ${errorText}`
1043
+ }]
1044
+ };
1045
+ }
1046
+ const result = await response.json();
1047
+ const status = result.task.status;
1048
+ if (status === "completed" || status === "failed" || status === "cancelled") {
1049
+ appendEvent({
1050
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1051
+ event: "agent_wait_completed",
1052
+ message: `Agent ${orchestrationTaskId} reached terminal state: ${status}`,
1053
+ taskRunId: orchestrationTaskId,
1054
+ status
1055
+ });
1056
+ return {
1057
+ content: [{
1058
+ type: "text",
1059
+ text: JSON.stringify({
1060
+ orchestrationTaskId,
1061
+ status,
1062
+ result: result.task.result ?? null,
1063
+ errorMessage: result.task.errorMessage ?? null,
1064
+ waitDuration: Date.now() - startTime
1065
+ }, null, 2)
1066
+ }]
1067
+ };
1068
+ }
1069
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1070
+ }
1071
+ return {
1072
+ content: [{
1073
+ type: "text",
1074
+ text: JSON.stringify({
1075
+ orchestrationTaskId,
1076
+ status: "timeout",
1077
+ message: `Timed out waiting for agent after ${timeout}ms`,
1078
+ waitDuration: Date.now() - startTime
1079
+ }, null, 2)
1080
+ }]
1081
+ };
1082
+ } catch (error) {
1083
+ const errorMsg = error instanceof Error ? error.message : String(error);
1084
+ return {
1085
+ content: [{
1086
+ type: "text",
1087
+ text: `Error waiting for agent: ${errorMsg}`
1088
+ }]
1089
+ };
1090
+ }
1091
+ }
1092
+ case "list_spawned_agents": {
1093
+ const { status: filterStatus } = args;
1094
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1095
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1096
+ const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
1097
+ if (!jwt) {
1098
+ return {
1099
+ content: [{
1100
+ type: "text",
1101
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1102
+ }]
1103
+ };
1104
+ }
1105
+ if (!orchestrationId) {
1106
+ return {
1107
+ content: [{
1108
+ type: "text",
1109
+ text: "CMUX_ORCHESTRATION_ID environment variable not set."
1110
+ }]
1111
+ };
1112
+ }
1113
+ try {
1114
+ let url = `${apiBaseUrl}/api/v1/cmux/orchestration/${orchestrationId}/tasks`;
1115
+ if (filterStatus) {
1116
+ url += `?status=${filterStatus}`;
1117
+ }
1118
+ const response = await fetch(url, {
1119
+ method: "GET",
1120
+ headers: {
1121
+ "Authorization": `Bearer ${jwt}`,
1122
+ "Content-Type": "application/json"
1123
+ }
1124
+ });
1125
+ if (!response.ok) {
1126
+ const errorText = await response.text();
1127
+ return {
1128
+ content: [{
1129
+ type: "text",
1130
+ text: `Failed to list agents: ${response.status} ${errorText}`
1131
+ }]
1132
+ };
1133
+ }
1134
+ const result = await response.json();
1135
+ return {
1136
+ content: [{
1137
+ type: "text",
1138
+ text: JSON.stringify(result, null, 2)
1139
+ }]
1140
+ };
1141
+ } catch (error) {
1142
+ const errorMsg = error instanceof Error ? error.message : String(error);
1143
+ return {
1144
+ content: [{
1145
+ type: "text",
1146
+ text: `Error listing agents: ${errorMsg}`
1147
+ }]
1148
+ };
1149
+ }
1150
+ }
1151
+ case "cancel_agent": {
1152
+ const { orchestrationTaskId, cascade = false } = args;
1153
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1154
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1155
+ if (!jwt) {
1156
+ return {
1157
+ content: [{
1158
+ type: "text",
1159
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1160
+ }]
1161
+ };
1162
+ }
1163
+ try {
1164
+ const url = `${apiBaseUrl}/api/orchestrate/cancel/${orchestrationTaskId}`;
1165
+ const response = await fetch(url, {
1166
+ method: "POST",
1167
+ headers: {
1168
+ "X-Task-Run-JWT": jwt,
1169
+ "Content-Type": "application/json"
1170
+ },
1171
+ body: JSON.stringify({ cascade })
1172
+ });
1173
+ if (!response.ok) {
1174
+ const errorText = await response.text();
1175
+ return {
1176
+ content: [{
1177
+ type: "text",
1178
+ text: `Failed to cancel agent: ${response.status} ${errorText}`
1179
+ }]
1180
+ };
1181
+ }
1182
+ const result = await response.json();
1183
+ appendEvent({
1184
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1185
+ event: "agent_cancelled",
1186
+ message: `Cancelled agent ${orchestrationTaskId}${cascade ? ` (cascade: ${result.cancelledCount} total)` : ""}`,
1187
+ taskRunId: orchestrationTaskId
1188
+ });
1189
+ const plan = readPlan();
1190
+ if (plan) {
1191
+ const task = plan.tasks.find((t) => t.id === orchestrationTaskId);
1192
+ if (task) {
1193
+ task.status = "cancelled";
1194
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1195
+ writePlan(plan);
1196
+ }
1197
+ }
1198
+ return {
1199
+ content: [{
1200
+ type: "text",
1201
+ text: JSON.stringify({
1202
+ ok: true,
1203
+ orchestrationTaskId,
1204
+ cancelled: true,
1205
+ cascade,
1206
+ cancelledCount: result.cancelledCount ?? 1
1207
+ }, null, 2)
1208
+ }]
1209
+ };
1210
+ } catch (error) {
1211
+ const errorMsg = error instanceof Error ? error.message : String(error);
1212
+ return {
1213
+ content: [{
1214
+ type: "text",
1215
+ text: `Error cancelling agent: ${errorMsg}`
1216
+ }]
1217
+ };
1218
+ }
1219
+ }
1220
+ case "get_orchestration_summary": {
1221
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1222
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1223
+ const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
1224
+ if (!jwt) {
1225
+ return {
1226
+ content: [{
1227
+ type: "text",
1228
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1229
+ }]
1230
+ };
1231
+ }
1232
+ if (!orchestrationId) {
1233
+ return {
1234
+ content: [{
1235
+ type: "text",
1236
+ text: "CMUX_ORCHESTRATION_ID environment variable not set."
1237
+ }]
1238
+ };
1239
+ }
1240
+ try {
1241
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/${orchestrationId}/sync`;
1242
+ const response = await fetch(url, {
1243
+ method: "GET",
1244
+ headers: {
1245
+ "Authorization": `Bearer ${jwt}`,
1246
+ "Content-Type": "application/json"
1247
+ }
1248
+ });
1249
+ if (!response.ok) {
1250
+ const errorText = await response.text();
1251
+ return {
1252
+ content: [{
1253
+ type: "text",
1254
+ text: `Failed to get orchestration summary: ${response.status} ${errorText}`
1255
+ }]
1256
+ };
1257
+ }
1258
+ const data = await response.json();
1259
+ const activeAgents = data.tasks.filter((t) => t.status === "running").map((t) => t.agentName);
1260
+ const recentCompletions = data.tasks.filter((t) => t.status === "completed" || t.status === "failed").slice(-5).map((t) => ({
1261
+ id: t.id,
1262
+ status: t.status,
1263
+ prompt: t.prompt.slice(0, 50) + (t.prompt.length > 50 ? "..." : ""),
1264
+ result: t.result?.slice(0, 100),
1265
+ error: t.errorMessage?.slice(0, 100)
1266
+ }));
1267
+ const summary = {
1268
+ orchestrationId,
1269
+ status: data.aggregatedStatus,
1270
+ activeAgents,
1271
+ activeAgentCount: activeAgents.length,
1272
+ recentCompletions,
1273
+ allTasksComplete: data.aggregatedStatus.pending === 0 && data.aggregatedStatus.running === 0,
1274
+ hasFailures: data.aggregatedStatus.failed > 0
1275
+ };
1276
+ return {
1277
+ content: [{
1278
+ type: "text",
1279
+ text: JSON.stringify(summary, null, 2)
1280
+ }]
1281
+ };
1282
+ } catch (error) {
1283
+ const errorMsg = error instanceof Error ? error.message : String(error);
1284
+ return {
1285
+ content: [{
1286
+ type: "text",
1287
+ text: `Error getting orchestration summary: ${errorMsg}`
1288
+ }]
1289
+ };
1290
+ }
1291
+ }
1292
+ default:
1293
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
1294
+ }
1295
+ });
1296
+ return server;
1297
+ }
1298
+ async function runServer(config) {
1299
+ const server = createMemoryMcpServer(config);
1300
+ const transport = new StdioServerTransport();
1301
+ await server.connect(transport);
1302
+ }
1303
+
1304
+ export {
1305
+ createMemoryMcpServer,
1306
+ runServer
1307
+ };