@agent-plan/mcp 0.2.19-next.18 → 0.2.19-next.19
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 +61 -27
- package/package.json +5 -3
- package/server.json +15 -0
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":";AA2rCA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
-
import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, migrateToGlobalSequence, addChecklistItem, removeChecklistItem, toggleChecklistItem } from "@agent-plan/core";
|
|
7
|
+
import { PlanStore, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, migrateToGlobalSequence, addChecklistItem, removeChecklistItem, toggleChecklistItem, buildPhaseContextBlock } from "@agent-plan/core";
|
|
8
8
|
import { serve } from "@agent-plan/server";
|
|
9
9
|
import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, validateResolvedTarget } from "@agent-plan/core/naming";
|
|
10
10
|
const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
|
|
@@ -560,30 +560,26 @@ server.registerTool("planner-feature-delete", {
|
|
|
560
560
|
return writeAndSummarize(st, `Feature deleted: ${feature.id}${cascade ? `; deleted ${phases.length} phases` : `; unlinked ${phases.length} phases`}`, { deleted: feature.id, affectedPhases: phases.length, cascade: Boolean(cascade) });
|
|
561
561
|
});
|
|
562
562
|
server.registerTool("planner-phase-add", {
|
|
563
|
-
description: "Create a phase with a rich description. REQUIRED: description must include code references (file:line), current implementation state, dependencies, specific files/systems to modify, and behaviors to preserve. The description is the primary context for future agents; one-liners cause misalignment.",
|
|
563
|
+
description: "Create a phase linked to a feature with a rich description. REQUIRED: feature ref and description. The description must include code references (file:line), current implementation state, dependencies, specific files/systems to modify, and behaviors to preserve. The description is the primary context for future agents; one-liners cause misalignment.",
|
|
564
564
|
inputSchema: {
|
|
565
565
|
title: z.string().min(1),
|
|
566
|
-
feature: z.string().optional().describe("Feature ref. Accepts F00x/P00x/T00x composite, bare
|
|
566
|
+
feature: z.string().min(1).optional().describe("Feature ref (required). Accepts F00x/P00x/T00x composite, bare F00x (global), 5-char shortId, UUID, or title."),
|
|
567
567
|
summary: z.string().optional().describe("One-line summary of the phase"),
|
|
568
568
|
description: z.string().min(50, "Description must be at least 50 characters — include code references (file:line), current state, structs/traits involved, concrete work items, behaviors to preserve. Prefix with 'design-only' for pre-implementation design tasks.").describe("Required code references (file:line), current state, structs/traits involved, concrete work items, behaviors to preserve. Not a one-liner."),
|
|
569
569
|
},
|
|
570
570
|
}, async ({ title, feature: featureRef, summary, description }) => {
|
|
571
571
|
const st = await requireStore();
|
|
572
|
+
if (!featureRef?.trim())
|
|
573
|
+
return text("feature is required: a phase must belong to a feature.");
|
|
572
574
|
const featuresDoc = await st.loadFeatures();
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
feature = resolvedFeature.feature;
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
feature = featuresDoc.features[0];
|
|
585
|
-
}
|
|
586
|
-
if (featureRef && !feature)
|
|
575
|
+
const resolvedFeature = resolveFeatureRefStrict(featuresDoc.features, featureRef);
|
|
576
|
+
if (!resolvedFeature.ok)
|
|
577
|
+
return text(resolvedFeature.error);
|
|
578
|
+
const featureValidation = await validateResolvedTarget("feature", resolvedFeature.feature.id, () => st.loadFeatures().then((doc) => doc.features.find((f) => f.id === resolvedFeature.feature.id)).catch(() => undefined));
|
|
579
|
+
if (!featureValidation.ok)
|
|
580
|
+
return text(featureValidation.error);
|
|
581
|
+
const feature = resolvedFeature.feature;
|
|
582
|
+
if (!feature)
|
|
587
583
|
return text(`Resolved feature ${featureRef} no longer exists. Refusing to create phase.`);
|
|
588
584
|
const lockKey = feature?.id ?? "__unscoped__";
|
|
589
585
|
let phase;
|
|
@@ -753,16 +749,31 @@ server.registerTool("planner-phase-delete", {
|
|
|
753
749
|
server.registerTool("planner-task-add", {
|
|
754
750
|
description: "Create a task with a rich description. REQUIRED: description must include code references (file:line), what already exists vs what needs to be built, specific structs/traits/systems to modify, concrete implementation steps, and edge cases to handle. The description is the execution context for agents; one-liners cause misalignment.",
|
|
755
751
|
inputSchema: {
|
|
752
|
+
feature: z.string().min(1).optional().describe("Feature ref the task's phase belongs to. Accepts F00x/P00x/T00x composite, bare F00x (global), 5-char shortId, UUID, or title. REQUIRED."),
|
|
756
753
|
phase: z.string().min(1).describe("Phase ref. Accepts F00x/P00x/T00x composite, bare P00x/T00x (global), 5-char shortId, UUID, or title."),
|
|
757
754
|
title: z.string().min(1),
|
|
758
755
|
description: z.string().min(50, "Description must be at least 50 characters — include code references (file:line), current state vs desired state, structs/traits to modify, concrete implementation steps, edge cases. Prefix with 'design-only' for pre-implementation design tasks.").describe("Required code references (file:line), current state vs desired state, structs/traits to modify, concrete implementation steps, edge cases. Not a one-liner."),
|
|
759
756
|
checklist: z.array(z.string()).optional(),
|
|
760
757
|
},
|
|
761
|
-
}, async ({ phase: ref, title, description, checklist }) => {
|
|
758
|
+
}, async ({ feature: featureRef, phase: ref, title, description, checklist }) => {
|
|
762
759
|
const st = await requireStore();
|
|
763
|
-
const
|
|
760
|
+
const features = (await st.loadFeatures()).features;
|
|
761
|
+
if (!featureRef?.trim())
|
|
762
|
+
return text("feature is required: a task must belong to a feature.");
|
|
763
|
+
const resolvedFeature = resolveFeatureRefStrict(features, featureRef);
|
|
764
|
+
if (!resolvedFeature.ok)
|
|
765
|
+
return text(resolvedFeature.error);
|
|
766
|
+
const featureValidation = await validateResolvedTarget("feature", resolvedFeature.feature.id, () => st.loadFeatures().then((doc) => doc.features.find((f) => f.id === resolvedFeature.feature.id)).catch(() => undefined));
|
|
767
|
+
if (!featureValidation.ok)
|
|
768
|
+
return text(featureValidation.error);
|
|
769
|
+
const feature = resolvedFeature.feature;
|
|
770
|
+
if (!feature)
|
|
771
|
+
return text(`Resolved feature ${featureRef} no longer exists. Refusing to create task.`);
|
|
772
|
+
const found = findPhaseByRef(await st.loadAllPhases(), features, ref);
|
|
764
773
|
if (!found)
|
|
765
774
|
return text(`Phase not found: ${ref}`);
|
|
775
|
+
if (found.featureId !== feature.id)
|
|
776
|
+
return text(`Phase ${formatPhaseRef(found.number, featureNumberOfPhase(found, features))} does not belong to feature ${formatFeatureRef(feature.number)}. Refusing to create task.`);
|
|
766
777
|
const phaseValidation = await validateResolvedTarget("phase", found.id, () => st.loadPhase(found.id).catch(() => undefined));
|
|
767
778
|
if (!phaseValidation.ok)
|
|
768
779
|
return text(phaseValidation.error);
|
|
@@ -801,8 +812,8 @@ server.registerTool("planner-task-add", {
|
|
|
801
812
|
entry.updatedAt = timestamp;
|
|
802
813
|
return entry;
|
|
803
814
|
});
|
|
804
|
-
const
|
|
805
|
-
return writeAndSummarize(st, `✅ Task created: ${taskCompositeRef(task, found,
|
|
815
|
+
const finalFeatures = (await st.loadFeatures()).features;
|
|
816
|
+
return writeAndSummarize(st, `✅ Task created: ${taskCompositeRef(task, found, finalFeatures)} — ${task.title} (planned)${task.shortId ? ` · ${task.shortId}` : ""}`);
|
|
806
817
|
});
|
|
807
818
|
server.registerTool("planner-task-show", {
|
|
808
819
|
description: "Show a task by id or name.",
|
|
@@ -1043,7 +1054,7 @@ server.registerTool("planner-task-start", {
|
|
|
1043
1054
|
let handoffNotice = "";
|
|
1044
1055
|
if ((found.phase.handoff ?? "") !== "") {
|
|
1045
1056
|
await st.clearPhaseHandoff(found.phase.id, "task-started").catch(() => { });
|
|
1046
|
-
handoffNotice = "📦 Archived handoff for this phase (task started) — recover via planner-handoff-list + .planner/handoff-archive/.\n";
|
|
1057
|
+
handoffNotice = "📦 Archived handoff for this phase (task started) — recover via planner-handoff-list + .planner/.local/handoff-archive/.\n";
|
|
1047
1058
|
}
|
|
1048
1059
|
const _otherHandoffs = allHandoffs.filter((h) => h.phaseId !== found.phase.id);
|
|
1049
1060
|
if (_otherHandoffs.length > 0) {
|
|
@@ -1075,7 +1086,9 @@ server.registerTool("planner-task-start", {
|
|
|
1075
1086
|
await st.syncTaskStatusRollup(found.phase.id);
|
|
1076
1087
|
const features = (await st.loadFeatures()).features;
|
|
1077
1088
|
const t = updatedTask ?? found.task;
|
|
1078
|
-
|
|
1089
|
+
const parentFeature = found.phase.featureId ? features.find((f) => f.id === found.phase.featureId) : undefined;
|
|
1090
|
+
const phaseContext = buildPhaseContextBlock(found.phase, parentFeature);
|
|
1091
|
+
return writeAndSummarize(st, `${handoffNotice}✅ Task started: ${taskCompositeRef(t, found.phase, features)} — ${t.title} (in-progress)${t.shortId ? ` · ${t.shortId}` : ""}${phaseContext}`);
|
|
1079
1092
|
});
|
|
1080
1093
|
server.registerTool("planner-task-complete", {
|
|
1081
1094
|
description: "Set a task to done. Fails if checklist is incomplete unless force=true.",
|
|
@@ -1148,23 +1161,44 @@ server.registerTool("planner-handoff-show", {
|
|
|
1148
1161
|
return text(`Handoff for ${r.compositeRef}:\n\n${content}`, { phaseRef: r.compositeRef, phaseId: r.phase.id });
|
|
1149
1162
|
});
|
|
1150
1163
|
server.registerTool("planner-handoff-write", {
|
|
1151
|
-
description: "Write/refresh the entity-scoped handoff on a phase. Pass content (markdown). phaseRef optional (default: current in-progress phase). Captures design context for a resuming agent on that phase.",
|
|
1164
|
+
description: "Write/refresh the entity-scoped handoff on a phase. Pass content (markdown) AND a meaningful `title` summarizing the work (e.g. 'P049 — featureId validation: tests + adapter wiring'). The title becomes the H1 / first line shown in handoff lists; generic titles like 'Handoff' or 'Canonical handoff' are REJECTED. phaseRef optional (default: current in-progress phase). Captures design context for a resuming agent on that phase.",
|
|
1152
1165
|
inputSchema: {
|
|
1153
1166
|
phaseRef: z.string().optional().describe("Phase ref. Default: current in-progress phase."),
|
|
1167
|
+
title: z.string().min(3).optional().describe("Meaningful handoff title summarizing the work (becomes the H1 / first line in lists). REQUIRED if your markdown's first heading is generic. Example: 'P049 — featureId validation: tests + adapter wiring'."),
|
|
1154
1168
|
content: z.string().min(1).describe("Handoff text (markdown)."),
|
|
1155
1169
|
},
|
|
1156
|
-
}, async ({ phaseRef, content }) => {
|
|
1170
|
+
}, async ({ phaseRef, title, content }) => {
|
|
1157
1171
|
const st = await requireStore();
|
|
1172
|
+
let body = content.trim();
|
|
1173
|
+
const firstLine = body.split(/\r?\n/).find((l) => l.trim().length > 0) ?? "";
|
|
1174
|
+
const firstHeadingText = firstLine.replace(/^#+\s*/, "").trim();
|
|
1175
|
+
const isGeneric = !firstHeadingText || /^(handoff|canonical handoff|session handoff)$/i.test(firstHeadingText);
|
|
1176
|
+
if (!title && isGeneric) {
|
|
1177
|
+
return text("❌ Generic handoff title. Provide a meaningful `title` (or start your markdown with a descriptive H1) summarizing the work — e.g. 'P049 — featureId validation: tests + adapter wiring'. Generic titles like 'Handoff' or 'Canonical handoff' are not accepted.");
|
|
1178
|
+
}
|
|
1179
|
+
if (title && title.trim()) {
|
|
1180
|
+
const t = title.trim();
|
|
1181
|
+
const lines = body.split(/\r?\n/);
|
|
1182
|
+
const firstIdx = lines.findIndex((l) => l.trim().length > 0);
|
|
1183
|
+
if (firstIdx !== -1 && /^#+\s/.test(lines[firstIdx] ?? "")) {
|
|
1184
|
+
lines[firstIdx] = `# ${t}`;
|
|
1185
|
+
body = lines.join("\n");
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
body = `# ${t}\n\n${body}`;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1158
1191
|
const r = await resolvePhaseForHandoff(st, phaseRef);
|
|
1159
1192
|
if (!r.ok)
|
|
1160
1193
|
return text(`❌ ${r.error}`);
|
|
1161
|
-
await st.setPhaseHandoff(r.phase.id,
|
|
1194
|
+
await st.setPhaseHandoff(r.phase.id, body);
|
|
1162
1195
|
return text(`✅ Wrote handoff on ${r.compositeRef}.`, { phaseRef: r.compositeRef, phaseId: r.phase.id });
|
|
1163
1196
|
});
|
|
1164
1197
|
server.registerTool("planner-handoff-prepare", {
|
|
1165
1198
|
description: "Return instructions for the agent to prepare a canonical handoff, then call planner-handoff-write (entity-scoped, phase.handoff).",
|
|
1166
1199
|
}, async () => text([
|
|
1167
1200
|
"Prepare the canonical session handoff and write it on the current in-progress phase with planner-handoff-write (omit phaseRef to target the in-progress phase).",
|
|
1201
|
+
"Pass a meaningful `title` summarizing the work (e.g. 'P049 — featureId validation: tests + adapter wiring') — it becomes the H1 / first line in handoff lists. Generic titles like 'Handoff' or 'Canonical handoff' are REJECTED.",
|
|
1168
1202
|
"Required sections: Created at, Updated at, Reason, Current focus, What was being done, How to resume, Files touched, Blockers, Next steps, Recent decisions, Reminder.",
|
|
1169
1203
|
"Stored on the phase.handoff field (entity-scoped). .planner/HANDOFF.md is deprecated.",
|
|
1170
1204
|
].join("\n")));
|
|
@@ -1177,7 +1211,7 @@ server.registerTool("planner-handoff-clear", {
|
|
|
1177
1211
|
if (!r.ok)
|
|
1178
1212
|
return text(`❌ ${r.error}`);
|
|
1179
1213
|
await st.clearPhaseHandoff(r.phase.id, "manual");
|
|
1180
|
-
return text(`✅ Cleared handoff on ${r.compositeRef} (handoffUpdatedAt preserved as audit; archived to .planner/handoff-archive/).`, { phaseRef: r.compositeRef, phaseId: r.phase.id });
|
|
1214
|
+
return text(`✅ Cleared handoff on ${r.compositeRef} (handoffUpdatedAt preserved as audit; archived to .planner/.local/handoff-archive/).`, { phaseRef: r.compositeRef, phaseId: r.phase.id });
|
|
1181
1215
|
});
|
|
1182
1216
|
server.registerTool("planner-web", {
|
|
1183
1217
|
description: "Manage the planner web dashboard (start/status/stop) from MCP stdio. Runs an in-process HTTP+WS server bound to LAN; it lives as long as this MCP process. plan-root comes from AGENT_PLAN_ROOT or cwd()/.planner.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/mcp",
|
|
3
|
-
"version": "0.2.19-next.
|
|
3
|
+
"version": "0.2.19-next.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
"dist/**/*.js",
|
|
18
18
|
"dist/**/*.d.ts",
|
|
19
19
|
"dist/**/*.d.ts.map",
|
|
20
|
+
"server.json",
|
|
20
21
|
"README.md",
|
|
21
22
|
"LICENSE"
|
|
22
23
|
],
|
|
24
|
+
"mcpName": "io.github.ovidius72/agent-plan",
|
|
23
25
|
"publishConfig": {
|
|
24
26
|
"access": "public"
|
|
25
27
|
},
|
|
@@ -41,8 +43,8 @@
|
|
|
41
43
|
"dependencies": {
|
|
42
44
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
43
45
|
"zod": "^3.24.0",
|
|
44
|
-
"@agent-plan/core": "0.2.19-next.
|
|
45
|
-
"@agent-plan/server": "0.2.19-next.
|
|
46
|
+
"@agent-plan/core": "0.2.19-next.19",
|
|
47
|
+
"@agent-plan/server": "0.2.19-next.19"
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|
|
48
50
|
"build": "tsc -p tsconfig.json",
|
package/server.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "io.github.ovidius72/agent-plan",
|
|
3
|
+
"version": "0.2.19-next.18",
|
|
4
|
+
"description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/ovidius72/agent-planner.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/ovidius72/agent-planner#readme",
|
|
11
|
+
"package": {
|
|
12
|
+
"type": "npm",
|
|
13
|
+
"name": "@agent-plan/mcp"
|
|
14
|
+
}
|
|
15
|
+
}
|