@astramindapp/mcp-server 0.17.0 → 0.19.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/dist/mind-client.d.ts +130 -5
- package/dist/mind-client.d.ts.map +1 -1
- package/dist/mind-client.js +80 -13
- package/dist/mind-client.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +399 -23
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -293,15 +293,15 @@ function createMindMcpServer(client) {
|
|
|
293
293
|
// ─── mind_folders ───────────────────────────────────────
|
|
294
294
|
// Organize documents into folders. Folders are a presentation layer — the
|
|
295
295
|
// knowledge graph still indexes every document regardless of folder.
|
|
296
|
-
server.tool("mind_folders", "Organize your MIND documents into folders. Folders are a presentation layer — the knowledge graph still indexes and retrieves across every document regardless of folder. Use mind_remember to create a document, then mind_folders move_documents to file it. Actions: list, create, rename, move, delete, move_documents.", {
|
|
296
|
+
server.tool("mind_folders", "Organize your MIND documents into folders. Folders are a presentation layer — the knowledge graph still indexes and retrieves across every document regardless of folder. Use mind_remember to create a document, then mind_folders move_documents to file it. Actions: list, create, rename, move, delete, move_documents, set_hint (set the agent-routing hint read by mind_folder_suggest).", {
|
|
297
297
|
action: zod_1.z
|
|
298
|
-
.enum(["list", "create", "rename", "move", "delete", "move_documents"])
|
|
299
|
-
.describe("list (all folders + counts), create (new folder), rename (change a folder's name), move (re-nest a folder), delete (remove a folder — its documents and subfolders move up a level, nothing is deleted), move_documents (file documents into a folder)"),
|
|
298
|
+
.enum(["list", "create", "rename", "move", "delete", "move_documents", "set_hint"])
|
|
299
|
+
.describe("list (all folders + counts + routing hints), create (new folder; pass routing_hint to enable agent-routing), rename (change a folder's name), move (re-nest a folder), delete (remove a folder — its documents and subfolders move up a level, nothing is deleted), move_documents (file documents into a folder), set_hint (write/clear the routing_hint that mind_folder_suggest reads)"),
|
|
300
300
|
name: zod_1.z.string().optional().describe("Folder name — required for create and rename"),
|
|
301
301
|
folder_id: zod_1.z
|
|
302
302
|
.string()
|
|
303
303
|
.optional()
|
|
304
|
-
.describe("Folder id — the folder to rename/move/delete, or the destination for move_documents (omit or pass 'root' to file documents at the top level)"),
|
|
304
|
+
.describe("Folder id — the folder to rename/move/delete/set_hint, or the destination for move_documents (omit or pass 'root' to file documents at the top level)"),
|
|
305
305
|
parent_id: zod_1.z
|
|
306
306
|
.string()
|
|
307
307
|
.optional()
|
|
@@ -310,7 +310,11 @@ function createMindMcpServer(client) {
|
|
|
310
310
|
.array(zod_1.z.string())
|
|
311
311
|
.optional()
|
|
312
312
|
.describe("Document ids to file — required for move_documents"),
|
|
313
|
-
|
|
313
|
+
routing_hint: zod_1.z
|
|
314
|
+
.string()
|
|
315
|
+
.optional()
|
|
316
|
+
.describe("Free-text instruction answering \"when should MIND save things to this folder?\". Read by mind_folder_suggest to LLM-route new content. Pass on create to set up-front; pass on set_hint to update. Pass empty string to clear (disables the folder from auto-routing)."),
|
|
317
|
+
}, async ({ action, name, folder_id, parent_id, doc_ids, routing_hint }) => {
|
|
314
318
|
try {
|
|
315
319
|
switch (action) {
|
|
316
320
|
case "list": {
|
|
@@ -328,7 +332,9 @@ function createMindMcpServer(client) {
|
|
|
328
332
|
const byId = new Map(res.folders.map((f) => [f.id, f]));
|
|
329
333
|
const lines = res.folders.map((f) => {
|
|
330
334
|
const parent = f.parent_id ? byId.get(f.parent_id)?.name ?? "?" : "(top level)";
|
|
331
|
-
|
|
335
|
+
const hint = (f.routing_hint || "").trim();
|
|
336
|
+
const hintTail = hint ? ` · hint: ${hint.length > 80 ? hint.slice(0, 80) + "…" : hint}` : "";
|
|
337
|
+
return `• ${f.name} — ${f.document_count ?? 0} doc(s) · id: ${f.id} · parent: ${parent}${hintTail}`;
|
|
332
338
|
});
|
|
333
339
|
return {
|
|
334
340
|
content: [
|
|
@@ -346,10 +352,10 @@ function createMindMcpServer(client) {
|
|
|
346
352
|
case "create": {
|
|
347
353
|
if (!name)
|
|
348
354
|
throw new Error("'name' is required to create a folder");
|
|
349
|
-
const res = await client.createFolder(name, parent_id ?? null);
|
|
355
|
+
const res = await client.createFolder(name, parent_id ?? null, routing_hint);
|
|
350
356
|
return {
|
|
351
357
|
content: [
|
|
352
|
-
{ type: "text", text: `Created folder "${res.folder.name}" (id: ${res.folder.id})` },
|
|
358
|
+
{ type: "text", text: `Created folder "${res.folder.name}" (id: ${res.folder.id})${routing_hint ? " · routing hint set" : ""}` },
|
|
353
359
|
],
|
|
354
360
|
};
|
|
355
361
|
}
|
|
@@ -404,6 +410,24 @@ function createMindMcpServer(client) {
|
|
|
404
410
|
],
|
|
405
411
|
};
|
|
406
412
|
}
|
|
413
|
+
case "set_hint": {
|
|
414
|
+
if (!folder_id)
|
|
415
|
+
throw new Error("'folder_id' is required to set a routing hint");
|
|
416
|
+
if (routing_hint === undefined)
|
|
417
|
+
throw new Error("'routing_hint' is required for set_hint (pass empty string to clear)");
|
|
418
|
+
const res = await client.updateFolder(folder_id, { routing_hint });
|
|
419
|
+
const cleared = !routing_hint.trim();
|
|
420
|
+
return {
|
|
421
|
+
content: [
|
|
422
|
+
{
|
|
423
|
+
type: "text",
|
|
424
|
+
text: cleared
|
|
425
|
+
? `Cleared routing hint on "${res.folder.name}" — folder is no longer auto-routed.`
|
|
426
|
+
: `Routing hint set on "${res.folder.name}". mind_folder_suggest will now consider this folder.`,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
407
431
|
default:
|
|
408
432
|
throw new Error(`Unknown action: ${action}`);
|
|
409
433
|
}
|
|
@@ -415,6 +439,154 @@ function createMindMcpServer(client) {
|
|
|
415
439
|
};
|
|
416
440
|
}
|
|
417
441
|
});
|
|
442
|
+
// ─── mind_folder_routes ─────────────────────────────────
|
|
443
|
+
// Deterministic per-source_type system-folder routing. Configure once
|
|
444
|
+
// and every life_item / crm / chat-save / etc. lands in its designated
|
|
445
|
+
// folder automatically. ``apply_recommended`` is the one-tap setup.
|
|
446
|
+
server.tool("mind_folder_routes", "Configure deterministic system-folder routing — which folder each kind of system-generated doc (life items, CRM, chat saves, tasks, training, reasoning trajectories, playbooks, trader signals, cloud imports) is filed into automatically. Actions: list, set, clear, apply_recommended (idempotent one-tap setup that creates Life/CRM/Chats/Reasoning/Trader folders and wires every source_type to its destination). User uploads are unaffected — this is system writes only.", {
|
|
447
|
+
action: zod_1.z
|
|
448
|
+
.enum(["list", "set", "clear", "apply_recommended"])
|
|
449
|
+
.describe("list (current routing table + every supported source_type with label/description), set (route one source_type to a folder), clear (remove a route — that source_type goes to the top level again), apply_recommended (create Life/CRM/Chats/Reasoning/Trader folders if missing and wire all routes; idempotent and safe to re-run)"),
|
|
450
|
+
source_type: zod_1.z
|
|
451
|
+
.string()
|
|
452
|
+
.optional()
|
|
453
|
+
.describe("Required for set and clear. Canonical values: life_item, task, crm, chat, training, trajectory, playbook, trader, document. Custom values accepted (the route will fire whenever save_document_record is called with that source_type)."),
|
|
454
|
+
folder_id: zod_1.z
|
|
455
|
+
.string()
|
|
456
|
+
.optional()
|
|
457
|
+
.describe("Required for set — the folder id to route this source_type into."),
|
|
458
|
+
}, async ({ action, source_type, folder_id }) => {
|
|
459
|
+
try {
|
|
460
|
+
switch (action) {
|
|
461
|
+
case "list": {
|
|
462
|
+
const res = await client.listFolderRoutes();
|
|
463
|
+
const folders = await client.listFolders();
|
|
464
|
+
const folderById = new Map(folders.folders.map((f) => [f.id, f]));
|
|
465
|
+
const lines = res.categories.map((cat) => {
|
|
466
|
+
const fid = res.routes[cat.source_type];
|
|
467
|
+
const folder = fid ? folderById.get(fid) : null;
|
|
468
|
+
const dest = folder
|
|
469
|
+
? `→ ${folder.name} (id: ${fid})`
|
|
470
|
+
: "→ (unrouted — files at top level)";
|
|
471
|
+
return `• ${cat.label} [${cat.source_type}] ${dest}\n ${cat.description}`;
|
|
472
|
+
});
|
|
473
|
+
const configured = Object.keys(res.routes).length;
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: [
|
|
479
|
+
`${configured}/${res.categories.length} routes configured.`,
|
|
480
|
+
"",
|
|
481
|
+
...lines,
|
|
482
|
+
].join("\n"),
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
case "set": {
|
|
488
|
+
if (!source_type)
|
|
489
|
+
throw new Error("'source_type' is required");
|
|
490
|
+
if (!folder_id)
|
|
491
|
+
throw new Error("'folder_id' is required for set");
|
|
492
|
+
await client.setFolderRoute(source_type, folder_id);
|
|
493
|
+
return {
|
|
494
|
+
content: [
|
|
495
|
+
{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: `Routed source_type "${source_type}" → folder ${folder_id}. Future writes of that type will file there automatically.`,
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
case "clear": {
|
|
503
|
+
if (!source_type)
|
|
504
|
+
throw new Error("'source_type' is required");
|
|
505
|
+
await client.clearFolderRoute(source_type);
|
|
506
|
+
return {
|
|
507
|
+
content: [
|
|
508
|
+
{
|
|
509
|
+
type: "text",
|
|
510
|
+
text: `Cleared route for "${source_type}". Future writes of that type land at the top level.`,
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
case "apply_recommended": {
|
|
516
|
+
const res = await client.applyRecommendedFolders();
|
|
517
|
+
const lines = [];
|
|
518
|
+
if (res.created.length)
|
|
519
|
+
lines.push(`Created: ${res.created.join(", ")}`);
|
|
520
|
+
if (res.reused.length)
|
|
521
|
+
lines.push(`Reused: ${res.reused.join(", ")}`);
|
|
522
|
+
if (res.hint_updated.length)
|
|
523
|
+
lines.push(`Hints filled: ${res.hint_updated.join(", ")}`);
|
|
524
|
+
lines.push("");
|
|
525
|
+
lines.push(`${res.routes_set.length} routes set:`);
|
|
526
|
+
for (const r of res.routes_set) {
|
|
527
|
+
lines.push(` • ${r.source_type} → ${r.folder_name}`);
|
|
528
|
+
}
|
|
529
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
530
|
+
}
|
|
531
|
+
default:
|
|
532
|
+
throw new Error(`Unknown action: ${action}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
return {
|
|
537
|
+
content: [
|
|
538
|
+
{ type: "text", text: `Error managing folder routes: ${err}` },
|
|
539
|
+
],
|
|
540
|
+
isError: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
// ─── mind_folder_suggest ─────────────────────────────────
|
|
545
|
+
// LLM-driven content-aware folder picker. Reads every folder's
|
|
546
|
+
// routing_hint and chooses the best match (or null when nothing fits).
|
|
547
|
+
server.tool("mind_folder_suggest", "Ask MIND which folder a piece of content belongs in. Reads every folder's routing_hint (set via mind_folders set_hint) and uses a cheap LLM to pick the best match. Returns folder_id=null when no folder's hint clearly applies — in that case leave the doc unfiled rather than guess. Typical agent flow: suggest → mind_remember (create the doc) → mind_folders move_documents (file it to the suggested folder).", {
|
|
548
|
+
content: zod_1.z
|
|
549
|
+
.string()
|
|
550
|
+
.describe("The text of the new document you're about to file. The picker reads it against every folder's routing_hint."),
|
|
551
|
+
title: zod_1.z
|
|
552
|
+
.string()
|
|
553
|
+
.optional()
|
|
554
|
+
.describe("Optional title — helps the picker disambiguate short content."),
|
|
555
|
+
}, async ({ content, title }) => {
|
|
556
|
+
try {
|
|
557
|
+
const res = await client.suggestFolder(content, title);
|
|
558
|
+
if (!res.folder_id) {
|
|
559
|
+
return {
|
|
560
|
+
content: [
|
|
561
|
+
{
|
|
562
|
+
type: "text",
|
|
563
|
+
text: `No folder match — ${res.reason}\n\nLeave the doc unfiled or call mind_folder_routes apply_recommended to set up the default folder layout.`,
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
content: [
|
|
570
|
+
{
|
|
571
|
+
type: "text",
|
|
572
|
+
text: [
|
|
573
|
+
`Suggested folder: ${res.folder_name} (id: ${res.folder_id})`,
|
|
574
|
+
`Confidence: ${Math.round(res.confidence * 100)}%`,
|
|
575
|
+
`Reason: ${res.reason}`,
|
|
576
|
+
].join("\n"),
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
return {
|
|
583
|
+
content: [
|
|
584
|
+
{ type: "text", text: `Error suggesting folder: ${err}` },
|
|
585
|
+
],
|
|
586
|
+
isError: true,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
});
|
|
418
590
|
// ─── mind_context ───────────────────────────────────────
|
|
419
591
|
// Get structured always-loaded context.
|
|
420
592
|
// Equivalent to OpenClaw's SOUL.md + USER.md + AGENTS.md bootstrap injection,
|
|
@@ -453,7 +625,7 @@ function createMindMcpServer(client) {
|
|
|
453
625
|
// ─── mind_life ──────────────────────────────────────────
|
|
454
626
|
// Read/write goals, projects, tasks, calendar events, stats.
|
|
455
627
|
// Enhanced: calendar CRUD, stats, move, get.
|
|
456
|
-
server.tool("mind_life", "Manage
|
|
628
|
+
server.tool("mind_life", "Manage Focus → Project → Outcome hierarchy + calendar in MIND Life. Items are either projects (top-level domain/client buckets that live inside a Focus) or outcomes (deliverables that live under a parent project). Use `item_type` + `parent_id` + `focus_id` on create/update to wire the hierarchy. Use list filters (item_type, parent_id, focus_id, top_level_only) to slice the board.", {
|
|
457
629
|
action: zod_1.z
|
|
458
630
|
.enum([
|
|
459
631
|
"list", "create", "update", "complete", "delete", "bulk_delete", "move", "get",
|
|
@@ -484,17 +656,61 @@ function createMindMcpServer(client) {
|
|
|
484
656
|
.optional()
|
|
485
657
|
.describe("Left-border color stripe on the board card. Use to group items (e.g. Astra=red, Atlas=blue)."),
|
|
486
658
|
target_date: zod_1.z.string().optional().describe("Target completion date in YYYY-MM-DD format — when the project should be done. Powers the countdown badge on the card."),
|
|
487
|
-
|
|
659
|
+
item_type: zod_1.z
|
|
660
|
+
.enum(["project", "outcome"])
|
|
661
|
+
.optional()
|
|
662
|
+
.describe("'project' (top-level domain/client bucket; lives inside a Focus) or 'outcome' (deliverable under a parent project). Defaults to 'outcome' on create. Pass on list to filter."),
|
|
663
|
+
parent_id: zod_1.z
|
|
664
|
+
.string()
|
|
665
|
+
.optional()
|
|
666
|
+
.describe("For outcomes — the item_id of the parent project. Pass empty string on update to detach (make top-level). On list, pass an item_id to fetch its outcomes, or 'none' for items with no parent."),
|
|
667
|
+
focus_id: zod_1.z
|
|
668
|
+
.string()
|
|
669
|
+
.optional()
|
|
670
|
+
.describe("For projects — the focus_id this project belongs to. Pass empty string on update to detach (No Focus). On list, pass a focus_id to filter to that focus, or 'none' for unfiled projects."),
|
|
671
|
+
agent_ids: zod_1.z.array(zod_1.z.string()).optional().describe("Agents bound to this project (project only). On update, REPLACES the existing list."),
|
|
672
|
+
workflow_ids: zod_1.z.array(zod_1.z.string()).optional().describe("Workflows bound to this project (project only). On update, REPLACES the existing list."),
|
|
673
|
+
top_level_only: zod_1.z.boolean().optional().describe("On list — true returns only items with no parent (board roots: projects + unassigned outcomes)."),
|
|
674
|
+
include_completed: zod_1.z.boolean().optional().describe("On list — false hides completed items. Defaults true."),
|
|
675
|
+
limit: zod_1.z.number().int().min(1).max(500).optional().describe("On list — max items to return (1–500, default 30)."),
|
|
676
|
+
}, async ({ action, title, description, status, priority, due_date, item_id, item_ids, start_time, end_time, event_id, all_day, tags, color, target_date, item_type, parent_id, focus_id, agent_ids, workflow_ids, top_level_only, include_completed, limit }) => {
|
|
488
677
|
try {
|
|
489
678
|
switch (action) {
|
|
490
679
|
case "list": {
|
|
491
|
-
const result = await client.listLifeItems(
|
|
680
|
+
const result = await client.listLifeItems({
|
|
681
|
+
status,
|
|
682
|
+
item_type,
|
|
683
|
+
parent_id,
|
|
684
|
+
focus_id,
|
|
685
|
+
top_level_only,
|
|
686
|
+
include_completed,
|
|
687
|
+
limit,
|
|
688
|
+
});
|
|
492
689
|
const items = result.items ?? [];
|
|
493
690
|
if (items.length === 0) {
|
|
494
691
|
return { content: [{ type: "text", text: "No life items found." }] };
|
|
495
692
|
}
|
|
496
|
-
const lines = items.map((i) =>
|
|
497
|
-
|
|
693
|
+
const lines = items.map((i) => {
|
|
694
|
+
const tag = i.item_type === "project" ? "📁 project" : "🎯 outcome";
|
|
695
|
+
const parts = [`[${i.priority ?? "—"}] ${tag} ${i.title}`];
|
|
696
|
+
parts.push(`status: ${i.status ?? "—"}`);
|
|
697
|
+
if (i.due_date)
|
|
698
|
+
parts.push(`due: ${i.due_date}`);
|
|
699
|
+
if (i.item_type === "project") {
|
|
700
|
+
if (i.focus_id)
|
|
701
|
+
parts.push(`focus: ${i.focus_id}`);
|
|
702
|
+
if (typeof i.subtask_count === "number")
|
|
703
|
+
parts.push(`outcomes: ${i.subtasks_completed ?? 0}/${i.subtask_count}`);
|
|
704
|
+
}
|
|
705
|
+
else if (i.parent_id) {
|
|
706
|
+
parts.push(`parent: ${i.parent_id}`);
|
|
707
|
+
}
|
|
708
|
+
return `• ${parts.join(" · ")} — id: ${i.item_id}`;
|
|
709
|
+
});
|
|
710
|
+
const header = typeof result.total === "number" ? `Showing ${items.length} of ${result.total} items.` : null;
|
|
711
|
+
return {
|
|
712
|
+
content: [{ type: "text", text: [header, ...lines].filter(Boolean).join("\n") }],
|
|
713
|
+
};
|
|
498
714
|
}
|
|
499
715
|
case "create": {
|
|
500
716
|
if (!title) {
|
|
@@ -512,8 +728,19 @@ function createMindMcpServer(client) {
|
|
|
512
728
|
tags,
|
|
513
729
|
color,
|
|
514
730
|
target_date,
|
|
731
|
+
item_type,
|
|
732
|
+
parent_id,
|
|
733
|
+
focus_id,
|
|
734
|
+
agent_ids,
|
|
735
|
+
workflow_ids,
|
|
515
736
|
});
|
|
516
737
|
const extras = [];
|
|
738
|
+
if (result.item_type)
|
|
739
|
+
extras.push(`type: ${result.item_type}`);
|
|
740
|
+
if (result.parent_id)
|
|
741
|
+
extras.push(`parent: ${result.parent_id}`);
|
|
742
|
+
if (result.focus_id)
|
|
743
|
+
extras.push(`focus: ${result.focus_id}`);
|
|
517
744
|
if (result.tags && result.tags.length > 0)
|
|
518
745
|
extras.push(`tags: ${result.tags.join(", ")}`);
|
|
519
746
|
if (result.color && result.color !== "default")
|
|
@@ -539,22 +766,39 @@ function createMindMcpServer(client) {
|
|
|
539
766
|
const patch = {};
|
|
540
767
|
if (title)
|
|
541
768
|
patch.title = title;
|
|
542
|
-
if (description)
|
|
769
|
+
if (description !== undefined)
|
|
543
770
|
patch.description = description;
|
|
544
771
|
if (status)
|
|
545
772
|
patch.status = status;
|
|
546
773
|
if (priority)
|
|
547
774
|
patch.priority = priority;
|
|
548
|
-
if (due_date)
|
|
775
|
+
if (due_date !== undefined)
|
|
549
776
|
patch.due_date = due_date;
|
|
550
777
|
if (tags)
|
|
551
778
|
patch.tags = tags;
|
|
552
779
|
if (color)
|
|
553
780
|
patch.color = color;
|
|
554
|
-
if (target_date)
|
|
781
|
+
if (target_date !== undefined)
|
|
555
782
|
patch.target_date = target_date;
|
|
783
|
+
if (item_type !== undefined)
|
|
784
|
+
patch.item_type = item_type;
|
|
785
|
+
// parent_id / focus_id: pass empty string explicitly to clear; pass undefined to leave unchanged
|
|
786
|
+
if (parent_id !== undefined)
|
|
787
|
+
patch.parent_id = parent_id;
|
|
788
|
+
if (focus_id !== undefined)
|
|
789
|
+
patch.focus_id = focus_id;
|
|
790
|
+
if (agent_ids !== undefined)
|
|
791
|
+
patch.agent_ids = agent_ids;
|
|
792
|
+
if (workflow_ids !== undefined)
|
|
793
|
+
patch.workflow_ids = workflow_ids;
|
|
556
794
|
const result = await client.updateLifeItem(item_id, patch);
|
|
557
795
|
const extras = [];
|
|
796
|
+
if (result.item_type)
|
|
797
|
+
extras.push(`type: ${result.item_type}`);
|
|
798
|
+
if (result.parent_id)
|
|
799
|
+
extras.push(`parent: ${result.parent_id}`);
|
|
800
|
+
if (result.focus_id)
|
|
801
|
+
extras.push(`focus: ${result.focus_id}`);
|
|
558
802
|
if (result.tags && result.tags.length > 0)
|
|
559
803
|
extras.push(`tags: ${result.tags.join(", ")}`);
|
|
560
804
|
if (result.color && result.color !== "default")
|
|
@@ -625,16 +869,25 @@ function createMindMcpServer(client) {
|
|
|
625
869
|
};
|
|
626
870
|
}
|
|
627
871
|
const result = await client.getLifeItem(item_id);
|
|
872
|
+
const r = result;
|
|
628
873
|
const lines = [
|
|
629
|
-
`**${result.title}
|
|
874
|
+
`**${result.title}** (id: ${result.item_id})`,
|
|
630
875
|
`Status: ${result.status ?? "—"} | Priority: ${result.priority ?? "—"} | Due: ${result.due_date ?? "none"}`,
|
|
876
|
+
`Type: ${r.item_type ?? "outcome"}${r.parent_id ? ` | Parent: ${r.parent_id}` : ""}${r.focus_id ? ` | Focus: ${r.focus_id}` : ""}`,
|
|
631
877
|
];
|
|
632
|
-
if (
|
|
633
|
-
lines.push(`
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
878
|
+
if (r.item_type === "project" && typeof r.subtask_count === "number") {
|
|
879
|
+
lines.push(`Outcomes: ${r.subtasks_completed ?? 0}/${r.subtask_count} complete`);
|
|
880
|
+
}
|
|
881
|
+
if (r.agent_ids && r.agent_ids.length > 0)
|
|
882
|
+
lines.push(`Agents: ${r.agent_ids.join(", ")}`);
|
|
883
|
+
if (r.workflow_ids && r.workflow_ids.length > 0)
|
|
884
|
+
lines.push(`Workflows: ${r.workflow_ids.join(", ")}`);
|
|
885
|
+
if (r.color && r.color !== "default")
|
|
886
|
+
lines.push(`Color: ${r.color}`);
|
|
887
|
+
if (r.target_date)
|
|
888
|
+
lines.push(`Target: ${r.target_date}`);
|
|
889
|
+
if (r.tags && r.tags.length > 0)
|
|
890
|
+
lines.push(`Tags: ${r.tags.join(", ")}`);
|
|
638
891
|
if (result.description)
|
|
639
892
|
lines.push(`\n${result.description}`);
|
|
640
893
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
@@ -729,6 +982,129 @@ function createMindMcpServer(client) {
|
|
|
729
982
|
};
|
|
730
983
|
}
|
|
731
984
|
});
|
|
985
|
+
// ─── mind_focuses ──────────────────────────────────────
|
|
986
|
+
// Top of the Focus → Project → Outcome → Task hierarchy.
|
|
987
|
+
// A Focus is a per-user bucket that groups multiple Life Projects, and
|
|
988
|
+
// can carry shared agents/workflows that apply to every project inside.
|
|
989
|
+
server.tool("mind_focuses", "Manage Focuses — the top-level buckets that group Life Projects. Every project lives inside one focus (or 'No Focus'). Use list to see all focuses + their project counts, create to add a new bucket (e.g. 'Astra', 'Atlas', 'Fundraising'), update/delete to maintain them. Pass the returned focus_id to mind_life when creating projects.", {
|
|
990
|
+
action: zod_1.z
|
|
991
|
+
.enum(["list", "get", "create", "update", "delete"])
|
|
992
|
+
.describe("list/get/create/update/delete focuses. 'delete' soft-archives by default; pass hard=true to remove permanently."),
|
|
993
|
+
focus_id: zod_1.z.string().optional().describe("Focus id — required for get/update/delete"),
|
|
994
|
+
name: zod_1.z.string().optional().describe("Display name — required for create"),
|
|
995
|
+
description: zod_1.z.string().optional().describe("Long-form description"),
|
|
996
|
+
color: zod_1.z
|
|
997
|
+
.enum(["default", "red", "orange", "yellow", "green", "blue", "purple", "pink"])
|
|
998
|
+
.optional()
|
|
999
|
+
.describe("Color stripe used in the UI"),
|
|
1000
|
+
icon: zod_1.z.string().optional().describe("Lucide icon name (e.g. 'briefcase', 'rocket'). Defaults to 'folder'."),
|
|
1001
|
+
position: zod_1.z.number().int().optional().describe("Sort position (lower = earlier)"),
|
|
1002
|
+
agent_ids: zod_1.z.array(zod_1.z.string()).optional().describe("Agents bound to every project inside this focus. On update, REPLACES the existing list."),
|
|
1003
|
+
workflow_ids: zod_1.z.array(zod_1.z.string()).optional().describe("Workflows bound to every project inside this focus. On update, REPLACES the existing list."),
|
|
1004
|
+
include_archived: zod_1.z.boolean().optional().describe("On list — true to include archived focuses (default false)"),
|
|
1005
|
+
hard: zod_1.z.boolean().optional().describe("On delete — true to remove permanently (default false = soft archive)"),
|
|
1006
|
+
}, async ({ action, focus_id, name, description, color, icon, position, agent_ids, workflow_ids, include_archived, hard }) => {
|
|
1007
|
+
try {
|
|
1008
|
+
switch (action) {
|
|
1009
|
+
case "list": {
|
|
1010
|
+
const result = await client.listFocuses(include_archived ?? false);
|
|
1011
|
+
const focuses = result.focuses ?? [];
|
|
1012
|
+
if (focuses.length === 0 && (result.unfiled_project_count ?? 0) === 0) {
|
|
1013
|
+
return { content: [{ type: "text", text: "No focuses yet. Create one with action='create'." }] };
|
|
1014
|
+
}
|
|
1015
|
+
const lines = focuses.map((f) => {
|
|
1016
|
+
const c = f.color && f.color !== "default" ? ` (${f.color})` : "";
|
|
1017
|
+
const counts = `${f.project_count_active ?? 0} active / ${f.project_count ?? 0} total`;
|
|
1018
|
+
return `• 📁 ${f.name}${c} — ${counts} projects — id: ${f.focus_id}`;
|
|
1019
|
+
});
|
|
1020
|
+
if (typeof result.unfiled_project_count === "number" && result.unfiled_project_count > 0) {
|
|
1021
|
+
lines.push(`• 🗂 No Focus — ${result.unfiled_project_count} unfiled project(s)`);
|
|
1022
|
+
}
|
|
1023
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1024
|
+
}
|
|
1025
|
+
case "get": {
|
|
1026
|
+
if (!focus_id) {
|
|
1027
|
+
return { content: [{ type: "text", text: "Error: focus_id is required for get" }], isError: true };
|
|
1028
|
+
}
|
|
1029
|
+
const f = await client.getFocus(focus_id);
|
|
1030
|
+
const lines = [
|
|
1031
|
+
`**${f.name}** (id: ${f.focus_id})`,
|
|
1032
|
+
`Color: ${f.color ?? "default"} | Icon: ${f.icon ?? "folder"} | Position: ${f.position ?? 0}`,
|
|
1033
|
+
`Projects: ${f.project_count_active ?? 0} active / ${f.project_count_completed ?? 0} completed / ${f.project_count ?? 0} total`,
|
|
1034
|
+
];
|
|
1035
|
+
if (f.agent_ids && f.agent_ids.length > 0)
|
|
1036
|
+
lines.push(`Agents: ${f.agent_ids.join(", ")}`);
|
|
1037
|
+
if (f.workflow_ids && f.workflow_ids.length > 0)
|
|
1038
|
+
lines.push(`Workflows: ${f.workflow_ids.join(", ")}`);
|
|
1039
|
+
if (f.archived_at)
|
|
1040
|
+
lines.push(`Archived at: ${f.archived_at}`);
|
|
1041
|
+
if (f.description)
|
|
1042
|
+
lines.push(`\n${f.description}`);
|
|
1043
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1044
|
+
}
|
|
1045
|
+
case "create": {
|
|
1046
|
+
if (!name) {
|
|
1047
|
+
return { content: [{ type: "text", text: "Error: name is required to create a focus" }], isError: true };
|
|
1048
|
+
}
|
|
1049
|
+
const f = await client.createFocus({
|
|
1050
|
+
name,
|
|
1051
|
+
description,
|
|
1052
|
+
color,
|
|
1053
|
+
icon,
|
|
1054
|
+
position,
|
|
1055
|
+
agent_ids,
|
|
1056
|
+
workflow_ids,
|
|
1057
|
+
});
|
|
1058
|
+
return {
|
|
1059
|
+
content: [
|
|
1060
|
+
{
|
|
1061
|
+
type: "text",
|
|
1062
|
+
text: `✅ Created focus: "${f.name}" (id: ${f.focus_id}, color: ${f.color ?? "default"}, icon: ${f.icon ?? "folder"})`,
|
|
1063
|
+
},
|
|
1064
|
+
],
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
case "update": {
|
|
1068
|
+
if (!focus_id) {
|
|
1069
|
+
return { content: [{ type: "text", text: "Error: focus_id is required for update" }], isError: true };
|
|
1070
|
+
}
|
|
1071
|
+
const patch = {};
|
|
1072
|
+
if (name !== undefined)
|
|
1073
|
+
patch.name = name;
|
|
1074
|
+
if (description !== undefined)
|
|
1075
|
+
patch.description = description;
|
|
1076
|
+
if (color !== undefined)
|
|
1077
|
+
patch.color = color;
|
|
1078
|
+
if (icon !== undefined)
|
|
1079
|
+
patch.icon = icon;
|
|
1080
|
+
if (position !== undefined)
|
|
1081
|
+
patch.position = position;
|
|
1082
|
+
if (agent_ids !== undefined)
|
|
1083
|
+
patch.agent_ids = agent_ids;
|
|
1084
|
+
if (workflow_ids !== undefined)
|
|
1085
|
+
patch.workflow_ids = workflow_ids;
|
|
1086
|
+
const f = await client.updateFocus(focus_id, patch);
|
|
1087
|
+
return {
|
|
1088
|
+
content: [{ type: "text", text: `✅ Updated focus: "${f.name}" (id: ${f.focus_id})` }],
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
case "delete": {
|
|
1092
|
+
if (!focus_id) {
|
|
1093
|
+
return { content: [{ type: "text", text: "Error: focus_id is required for delete" }], isError: true };
|
|
1094
|
+
}
|
|
1095
|
+
const r = await client.deleteFocus(focus_id, hard ?? false);
|
|
1096
|
+
const verb = r.deleted ? "Deleted" : "Archived";
|
|
1097
|
+
return { content: [{ type: "text", text: `✅ ${verb} focus ${r.focus_id} (projects inside detached to 'No Focus').` }] };
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
catch (err) {
|
|
1102
|
+
return {
|
|
1103
|
+
content: [{ type: "text", text: `Error in mind_focuses: ${err}` }],
|
|
1104
|
+
isError: true,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
732
1108
|
// ─── mind_tasks ─────────────────────────────────────────
|
|
733
1109
|
// Site-wide tasks: assignable, completable work items that attach to a
|
|
734
1110
|
// Life project, a CRM contact, an agent, or stand alone.
|