@agent-plan/mcp 0.2.10 → 0.2.16
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/index.d.ts.map +1 -1
- package/dist/index.js +79 -13
- package/package.json +3 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA21BA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { existsSync } from "node:fs";
|
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { PlanStore, ExportService, withFeatureLock, needsMotivation } from "@agent-plan/core";
|
|
9
9
|
import { serve } from "@agent-plan/server";
|
|
10
|
-
import { createChecklistItemId, createFeatureId, createPhaseId, createTaskId, normalizeSlug } from "@agent-plan/core/naming";
|
|
10
|
+
import { createChecklistItemId, createFeatureId, createPhaseId, createTaskId, clampSlug, normalizeSlug } from "@agent-plan/core/naming";
|
|
11
11
|
const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
|
|
12
12
|
const PHASE_STATUS_VALUES = ["draft", "discovery", ...STATUS_VALUES];
|
|
13
13
|
function text(textValue, structuredContent) {
|
|
@@ -30,9 +30,67 @@ async function requireStore() {
|
|
|
30
30
|
throw new Error(`No .planner/ found at ${st.root}. Use planner-init first.`);
|
|
31
31
|
return st;
|
|
32
32
|
}
|
|
33
|
-
// In-process web server handle, managed by the planner-web tool
|
|
34
|
-
// as this MCP stdio process (i.e. the host session). Null when not running.
|
|
33
|
+
// In-process web server handle, managed by the planner-web tool and planner-load.
|
|
34
|
+
// Lives as long as this MCP stdio process (i.e. the host session). Null when not running.
|
|
35
35
|
let webHandle = null;
|
|
36
|
+
/** Start the web dashboard on LAN (0.0.0.0:0, OS-assigned port) if not already
|
|
37
|
+
* running. Returns the local/LAN URLs. No-op (empty url) when no .planner/ exists. */
|
|
38
|
+
async function ensureWebStarted() {
|
|
39
|
+
if (webHandle)
|
|
40
|
+
return { localUrl: webHandle.localUrl, lanUrl: webHandle.lanUrl, mode: webHandle.mode };
|
|
41
|
+
const root = planRoot();
|
|
42
|
+
const st = new PlanStore(root);
|
|
43
|
+
if (!(await st.exists()))
|
|
44
|
+
return { localUrl: "" };
|
|
45
|
+
try {
|
|
46
|
+
webHandle = await serve({ planRoot: root, host: "0.0.0.0", port: 0, quiet: true });
|
|
47
|
+
return { localUrl: webHandle.localUrl, lanUrl: webHandle.lanUrl, mode: webHandle.mode };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { localUrl: "" };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Build a consolidated planner recap (project state + active work + pending
|
|
54
|
+
* handoff + web URL). Harness-agnostic equivalent of Pi's buildStartupResumeSummary. */
|
|
55
|
+
async function buildRecapText(st, web) {
|
|
56
|
+
const [plan, resume, handoff] = await Promise.all([st.loadAll(), st.loadResume(), st.loadHandoff()]);
|
|
57
|
+
const allTasks = plan.phases.flatMap((p) => p.tasks.map((t) => ({ phase: p, task: t })));
|
|
58
|
+
const totalF = plan.features.features.length;
|
|
59
|
+
const doneF = plan.features.features.filter((f) => f.status === "done").length;
|
|
60
|
+
const activeF = plan.features.features.filter((f) => f.status === "in-progress").length;
|
|
61
|
+
const totalP = plan.phases.length;
|
|
62
|
+
const doneP = plan.phases.filter((p) => p.status === "done").length;
|
|
63
|
+
const activeP = plan.phases.filter((p) => p.status === "in-progress" || p.status === "discovery").length;
|
|
64
|
+
const totalT = allTasks.length;
|
|
65
|
+
const doneT = allTasks.filter(({ task }) => task.status === "done").length;
|
|
66
|
+
const activeT = allTasks.filter(({ task }) => task.status === "in-progress").length;
|
|
67
|
+
const inProgress = allTasks.filter(({ task }) => task.status === "in-progress");
|
|
68
|
+
const focus = inProgress[0];
|
|
69
|
+
const focusFeature = focus ? plan.features.features.find((f) => f.id === focus.phase.featureId) : undefined;
|
|
70
|
+
const lines = [];
|
|
71
|
+
lines.push("## Planner recap");
|
|
72
|
+
const name = plan.project.name || "(unnamed project)";
|
|
73
|
+
lines.push(`Project: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
|
|
74
|
+
lines.push(`Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active)`);
|
|
75
|
+
if (focus) {
|
|
76
|
+
lines.push(`Current focus: ${focusFeature?.name ?? "?"} / ${focus.phase.title} / ${focus.task.title} (in-progress)`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
lines.push("Current focus: no active task — review the plan and pick the next concrete task");
|
|
80
|
+
}
|
|
81
|
+
if (resume?.nextSteps?.length)
|
|
82
|
+
lines.push(`Next step: ${resume.nextSteps[0]}`);
|
|
83
|
+
if (handoff) {
|
|
84
|
+
lines.push("", "## Pending handoff (read me first)", handoff.content || "(empty handoff)", "", "→ After processing the handoff above, call planner-handoff-clear to remove it.");
|
|
85
|
+
}
|
|
86
|
+
else if (activeT === 0) {
|
|
87
|
+
lines.push("", "No handoff pending and no task in-progress. Use planner-task-add / planner-task-start to begin work.");
|
|
88
|
+
}
|
|
89
|
+
if (web.localUrl) {
|
|
90
|
+
lines.push("", "## Web UI", `🌐 ${web.localUrl}${web.lanUrl ? " (LAN: " + web.lanUrl + ")" : ""}`);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
36
94
|
function findFeatureByRef(features, ref) {
|
|
37
95
|
const normalized = ref.trim().toLowerCase();
|
|
38
96
|
return features.find((feature) => feature.id.toLowerCase() === normalized)
|
|
@@ -508,7 +566,7 @@ server.registerTool("planner-task-add", {
|
|
|
508
566
|
id: taskId,
|
|
509
567
|
phaseId: found.id,
|
|
510
568
|
number: found.tasks.length + 1,
|
|
511
|
-
shortName:
|
|
569
|
+
shortName: clampSlug(title, 30, `task-${Date.now().toString(36)}`),
|
|
512
570
|
title: title.trim(),
|
|
513
571
|
status: "planned",
|
|
514
572
|
description: description?.trim() ?? "",
|
|
@@ -644,12 +702,13 @@ server.registerTool("planner-task-start", {
|
|
|
644
702
|
inputSchema: { task: z.string().min(1) },
|
|
645
703
|
}, async ({ task: ref }) => {
|
|
646
704
|
const st = await requireStore();
|
|
647
|
-
// Hygiene
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
705
|
+
// Hygiene notice (non-blocking): surface an existing handoff but never block
|
|
706
|
+
// task_start — the handoff is a captured-context artifact, not a lock (e.g. a
|
|
707
|
+
// small modification after writing a handoff should start without forcing
|
|
708
|
+
// deletion, which would lose context).
|
|
709
|
+
const handoffNotice = existsSync(join(st.root, "HANDOFF.md"))
|
|
710
|
+
? "ℹ️ A handoff exists at .planner/HANDOFF.md — read it for context, then delete it with planner-handoff-delete when no longer needed. Proceeding with task start.\n"
|
|
711
|
+
: "";
|
|
653
712
|
const found = findTaskByRef(await st.loadAllPhases(), ref);
|
|
654
713
|
if (!found)
|
|
655
714
|
return text(`Task not found: ${ref}`);
|
|
@@ -677,7 +736,7 @@ server.registerTool("planner-task-start", {
|
|
|
677
736
|
return phase;
|
|
678
737
|
});
|
|
679
738
|
await st.syncTaskStatusRollup(found.phase.id);
|
|
680
|
-
return writeAndSummarize(st,
|
|
739
|
+
return writeAndSummarize(st, `${handoffNotice}✅ Task started: ${found.task.id}`, { task: updatedTask ?? found.task });
|
|
681
740
|
});
|
|
682
741
|
server.registerTool("planner-task-complete", {
|
|
683
742
|
description: "Set a task to done. Fails if checklist is incomplete unless force=true.",
|
|
@@ -798,8 +857,15 @@ server.registerTool("planner-web", {
|
|
|
798
857
|
return text("planner-web not running. Use planner-web with action=start to start the dashboard.");
|
|
799
858
|
});
|
|
800
859
|
server.registerTool("planner-load", {
|
|
801
|
-
description: "MCP
|
|
802
|
-
}, async () =>
|
|
860
|
+
description: "Load/refresh the planner on explicit user request (NOT automatic): starts the web dashboard on LAN and returns a consolidated recap (project state, active task, pending handoff, web URL). This is the MCP equivalent of Pi /planner load. Call it ONLY when the user runs /planner load or /planner recap (or asks to load the planner). Present the recap to the user in that reply, including the web URL on a final prominent line. If a pending handoff is included, read it, summarize it to the user, then call planner-handoff-clear. Do NOT start the planner/web or show the web URL unless the user explicitly asks (load/recap/web status).",
|
|
861
|
+
}, async () => {
|
|
862
|
+
const st = store();
|
|
863
|
+
if (!(await st.exists()))
|
|
864
|
+
return text("No .planner/ found at " + planRoot() + ". Run planner-init first.");
|
|
865
|
+
const web = await ensureWebStarted();
|
|
866
|
+
const recap = await buildRecapText(st, web);
|
|
867
|
+
return text(recap);
|
|
868
|
+
});
|
|
803
869
|
server.registerTool("planner-disable", {
|
|
804
870
|
description: "MCP no-op equivalent of Pi /planner disable. Stop the MCP process from the host to disable it.",
|
|
805
871
|
}, async () => text("To disable planner MCP, remove/disable this MCP server from the host config or stop the process."));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
43
43
|
"zod": "^3.24.0",
|
|
44
|
-
"@agent-plan/core": "0.2.
|
|
45
|
-
"@agent-plan/server": "0.2.
|
|
44
|
+
"@agent-plan/core": "0.2.16",
|
|
45
|
+
"@agent-plan/server": "0.2.16"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.json",
|