@agent-plan/mcp 0.3.0 → 0.3.1
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 +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -38
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
type ToolResult = {
|
|
3
|
+
content: Array<{
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}>;
|
|
7
|
+
structuredContent?: Record<string, unknown>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code gives the agent only `structuredContent` when a result has
|
|
12
|
+
* one, and drops `content[].text`. Anything a tool says in text — a handoff
|
|
13
|
+
* body, a phase work map, task-start context, an advisory — would never
|
|
14
|
+
* reach the agent. So every result with a structured part also carries its
|
|
15
|
+
* text there, as `text`. `content` keeps the text for clients that read
|
|
16
|
+
* only that. Applied once, to every tool, by the registerTool wrapper below;
|
|
17
|
+
* no handler needs to know.
|
|
18
|
+
*/
|
|
19
|
+
export declare function foldTextIntoStructured<T extends ToolResult>(result: T): T;
|
|
2
20
|
export declare function startStdioServer(): Promise<void>;
|
|
21
|
+
export {};
|
|
3
22
|
//# sourceMappingURL=index.d.ts.map
|
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":";AA4BA,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA2LrI;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAUzE;AAs2FD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as z from "zod/v4";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
|
-
import { PlanStore, PlanStoreError, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, runtimeCapabilities, runtimePackagesDiagnostic, markCanonicalFullReadForSessionId, contextReadEligibilityForSession, requirementReadEligibilityForSession, hasValidSessionAttestation, markRequirementReadForSessionId, startReadSession, invalidateReads, taskStartDenied, taskStartSucceeded, taskStartGateState, taskStartGateStateLine, noMutableFieldsReceived, normalizeDescriptionRef, projectGuidelinesReadStateForSession, reconcileRequirementMacroTasks, RequirementMacroTaskError, HANDOFF_COMPLETENESS_AUDIT_VERSION, HANDOFF_COMPLETENESS_CATEGORIES, HANDOFF_COLD_START_INVENTORY_VERSION, HANDOFF_COLD_START_INVENTORY_CATEGORIES, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildMutationReply, longTextFieldLimitNotice, buildRecommendationReply, buildTaskOrderContext, taskOrderContextLine, taskCreatedPriorityFragment, findHigherPriorityOpenPhase, higherPriorityOpenPhaseAdvisory, listOpenPhaseWork, openPhaseWorkLines, boundedOpenPhaseWork, deleteFeatureCascade, evaluateTaskCompletionGate, taskCompletionChecklistRefusal, taskCompletionForceMotivationRequired, taskCompletionOverrideNote, taskCompletionMismatchLine, boundedPage, nowISO, resolveFeatureRefStrict, resolveAcceptedDecisionTarget, applyTaskLifecycleDates, handoffContractFailureReply, derivedStatusReadOnlyReply, acceptedDecisionMutationFailureReply, legacyContextMigrationSummary } from "@agent-plan/core";
|
|
8
|
+
import { PlanStore, PlanStoreError, ExportService, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, findIdeaByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, replaceChecklist, buildPhaseContextBlock, buildPhaseWorkMap, buildBoundedAcceptedDecisionContext, renderAcceptedDecisionsSection, checkExplicitTaskStart, recommendNextTask, recommendNextWork, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, createStaleProcessCheck, runtimeCapabilities, runtimePackagesDiagnostic, markCanonicalFullReadForSessionId, contextReadEligibilityForSession, requirementReadEligibilityForSession, hasValidSessionAttestation, markRequirementReadForSessionId, startReadSession, invalidateReads, taskStartDenied, taskStartSucceeded, taskStartGateState, taskStartGateStateLine, noMutableFieldsReceived, normalizeDescriptionRef, projectGuidelinesReadStateForSession, reconcileRequirementMacroTasks, RequirementMacroTaskError, HANDOFF_COMPLETENESS_AUDIT_VERSION, HANDOFF_COMPLETENESS_CATEGORIES, HANDOFF_COLD_START_INVENTORY_VERSION, HANDOFF_COLD_START_INVENTORY_CATEGORIES, HandoffContractError, buildHandoffShowReply, buildHandoffPrepareReply, buildProjectContextLoadReply, buildPlannerLoadReply, buildScopedDecisionContext, DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS, projectContextStaleAdvisory, LEGACY_DECISIONS_ARRAY_READ_ONLY_MESSAGE, LEGACY_DECISIONS_ARRAY_READ_ONLY_ERROR_CODE, buildMutationReply, longTextFieldLimitNotice, buildRecommendationReply, buildTaskOrderContext, taskOrderContextLine, taskCreatedPriorityFragment, findHigherPriorityOpenPhase, higherPriorityOpenPhaseAdvisory, listOpenPhaseWork, openPhaseWorkLines, boundedOpenPhaseWork, deleteFeatureCascade, evaluateTaskCompletionGate, taskCompletionChecklistRefusal, taskCompletionForceMotivationRequired, taskCompletionOverrideNote, taskCompletionMismatchLine, boundedPage, nowISO, resolveFeatureRefStrict, resolveAcceptedDecisionTarget, applyTaskLifecycleDates, handoffContractFailureReply, derivedStatusReadOnlyReply, acceptedDecisionMutationFailureReply, legacyContextMigrationSummary } from "@agent-plan/core";
|
|
9
9
|
import { serve } from "@agent-plan/server";
|
|
10
10
|
import { createChecklistItemId, createFeatureId, createPhaseId, createRequirementId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, formatIdeaRef, featureNumberOfPhase, validateResolvedTarget } from "@agent-plan/core/naming";
|
|
11
11
|
const STATUS_VALUES = ["planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting"];
|
|
@@ -83,6 +83,7 @@ function mcpContextReadActions(eligibility, refs, projectGuidelinesReadRequired,
|
|
|
83
83
|
}
|
|
84
84
|
const MCP_PACKAGE = packageVersionFromModule(import.meta.url, "@agent-plan/mcp");
|
|
85
85
|
const CORE_PACKAGE = resolvedPackageVersion("@agent-plan/core", import.meta.url);
|
|
86
|
+
const staleServerNotice = createStaleProcessCheck(MCP_PACKAGE);
|
|
86
87
|
function planRoot() {
|
|
87
88
|
return process.env.AGENT_PLAN_ROOT || join(process.cwd(), ".planner");
|
|
88
89
|
}
|
|
@@ -169,6 +170,44 @@ const server = new McpServer({
|
|
|
169
170
|
name: "agent-plan-planner",
|
|
170
171
|
version: MCP_PACKAGE.version,
|
|
171
172
|
});
|
|
173
|
+
/**
|
|
174
|
+
* Claude Code gives the agent only `structuredContent` when a result has
|
|
175
|
+
* one, and drops `content[].text`. Anything a tool says in text — a handoff
|
|
176
|
+
* body, a phase work map, task-start context, an advisory — would never
|
|
177
|
+
* reach the agent. So every result with a structured part also carries its
|
|
178
|
+
* text there, as `text`. `content` keeps the text for clients that read
|
|
179
|
+
* only that. Applied once, to every tool, by the registerTool wrapper below;
|
|
180
|
+
* no handler needs to know.
|
|
181
|
+
*/
|
|
182
|
+
export function foldTextIntoStructured(result) {
|
|
183
|
+
if (!result?.structuredContent)
|
|
184
|
+
return result;
|
|
185
|
+
const replyText = result.content
|
|
186
|
+
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
187
|
+
.map((part) => part.text)
|
|
188
|
+
.join("\n\n");
|
|
189
|
+
// A reply that already uses `text` for something else is left as it is;
|
|
190
|
+
// the MCP tests fail on that, so it never ships.
|
|
191
|
+
if (!replyText || "text" in result.structuredContent)
|
|
192
|
+
return result;
|
|
193
|
+
return { ...result, structuredContent: { text: replyText, ...result.structuredContent } };
|
|
194
|
+
}
|
|
195
|
+
/** Add one line to a reply when this server is older than what is now
|
|
196
|
+
* installed (see createStaleProcessCheck). */
|
|
197
|
+
function withStaleServerNotice(result) {
|
|
198
|
+
const notice = staleServerNotice();
|
|
199
|
+
if (!notice || !result?.content)
|
|
200
|
+
return result;
|
|
201
|
+
return { ...result, content: [...result.content, { type: "text", text: notice }] };
|
|
202
|
+
}
|
|
203
|
+
{
|
|
204
|
+
const registerToolUnwrapped = server.registerTool.bind(server);
|
|
205
|
+
server.registerTool = (...args) => {
|
|
206
|
+
const handler = args[args.length - 1];
|
|
207
|
+
const wrapped = async (...handlerArgs) => foldTextIntoStructured(withStaleServerNotice(await handler(...handlerArgs)));
|
|
208
|
+
return registerToolUnwrapped(...args.slice(0, -1), wrapped);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
172
211
|
async function resolveRequirementPhaseRefs(st, refs) {
|
|
173
212
|
if (!refs || refs.length === 0)
|
|
174
213
|
return { ok: false, error: "linkedPhaseIds must contain at least one phase reference." };
|
|
@@ -235,7 +274,7 @@ server.registerTool("planner-export", {
|
|
|
235
274
|
return text(`Project export generated. Summary results:\n\n${markdown.slice(0, 1000)}${markdown.length > 1000 ? "... (full report in .planner/EXPORT.md)" : ""}`);
|
|
236
275
|
});
|
|
237
276
|
server.registerTool("planner-authorize-bypass", {
|
|
238
|
-
description: "Authorize a temporary guard bypass (default 15 minutes)
|
|
277
|
+
description: "Authorize a temporary guard bypass (default 15 minutes) that silences the no-task warning. Edits are never blocked; this only stops the agent being warned when it changes project code with no task in-progress. Use ONLY after the user explicitly authorizes working without a task. Harness-agnostic: stored in resume.json so all adapters (Pi, Claude Code, Codex, ...) respect it.",
|
|
239
278
|
inputSchema: {
|
|
240
279
|
durationMinutes: z.number().optional().describe("Bypass window in minutes. Default 15."),
|
|
241
280
|
},
|
|
@@ -3079,7 +3118,7 @@ server.registerTool("planner-web", {
|
|
|
3079
3118
|
return text("planner-web not running. Use planner-web with action=start to start the dashboard.", { running: false, address: "", localUrl: "", lanUrl: "", host: "", port: 0, mode: "" });
|
|
3080
3119
|
});
|
|
3081
3120
|
server.registerTool("planner-load", {
|
|
3082
|
-
description: "Load/refresh the planner on explicit user request (NOT automatic): starts the web dashboard on LAN and returns a
|
|
3121
|
+
description: "Load/refresh the planner on explicit user request (NOT automatic): starts the web dashboard on LAN and returns one text with, in order: a recap under '## Recap — show this to the user verbatim', then agent-only context under '## Agent-only context' (the complete project-level context — description, goal, scope, out-of-scope, technologies, tools, language preferences, Project Guidelines, every Requirement, every project Accepted Decision — then Accepted Decisions of features, phases and tasks, then where the Agent Plan operating guide lives). Call it ONLY when the user runs /planner load or /planner recap (or asks to load the planner). Show the user ONLY the recap section, verbatim, including its final Web UI line; never quote the agent-only context. If structuredContent.projectContext.contextComplete is false, the project-level context was NOT delivered — follow structuredContent.projectContext.nextActions and retry before relying on it. A pending handoff is read-only context: NEVER call planner-handoff-show or planner-handoff-clear as part of load/recap. Archive it only when every phase task is done/canceled, when replacing it with a new handoff, or after an explicit user handoff-clear request. Do NOT start the planner/web or show the web URL unless the user explicitly asks (load/recap/web status).",
|
|
3083
3122
|
inputSchema: {
|
|
3084
3123
|
maxChars: z.number().int().positive().optional().describe(`Single-response bound (chars) for the project-context delivery. Default ${DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS}. Raise this and retry if a prior call returned projectContext.contextComplete=false.`),
|
|
3085
3124
|
},
|
|
@@ -3104,50 +3143,29 @@ server.registerTool("planner-load", {
|
|
|
3104
3143
|
const web = await ensureWebStarted();
|
|
3105
3144
|
const recap = await buildRecap(st, web, { harness: "mcp" });
|
|
3106
3145
|
const plan = await st.loadAll();
|
|
3107
|
-
const acceptedDecisionContext = buildBoundedAcceptedDecisionContext([
|
|
3108
|
-
{ scope: "project", decisions: plan.project.acceptedDecisions },
|
|
3109
|
-
...plan.features.features.map((feature) => ({ scope: `feature ${formatFeatureRef(feature.number)}`, decisions: feature.acceptedDecisions })),
|
|
3110
|
-
...plan.phases.map((phase) => ({ scope: `phase ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, plan.features.features))}`, decisions: phase.acceptedDecisions })),
|
|
3111
|
-
...plan.phases.flatMap((phase) => phase.tasks.map((task) => ({
|
|
3112
|
-
scope: `task ${taskCompositeRef(task, phase, plan.features.features)}`,
|
|
3113
|
-
decisions: task.acceptedDecisions,
|
|
3114
|
-
}))),
|
|
3115
|
-
]);
|
|
3116
3146
|
// Explicit planner load must deliver the complete project-level context
|
|
3117
3147
|
// (P102(F005)/T404), attested through the same lossless contract used
|
|
3118
3148
|
// everywhere else: loadProjectContextDelivery + recordProjectContextRead.
|
|
3119
|
-
// buildProjectContextLoadReply is the single shared shaping for this reply
|
|
3120
|
-
// (Pi's planner-load and /planner load use the identical builder).
|
|
3121
3149
|
const boundedMaxChars = maxChars ?? DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS;
|
|
3122
3150
|
const projectContextDelivery = await st.loadProjectContextDelivery(boundedMaxChars);
|
|
3123
3151
|
const projectContextReply = buildProjectContextLoadReply(projectContextDelivery, boundedMaxChars);
|
|
3124
3152
|
if (projectContextReply.structured.contextComplete) {
|
|
3125
3153
|
await st.recordProjectContextRead({ sessionId: plannerSessionId, chunks: projectContextDelivery.chunks });
|
|
3126
3154
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
content: plannerSkill.content,
|
|
3142
|
-
status: plannerSkill.status,
|
|
3143
|
-
customized: plannerSkill.customized,
|
|
3144
|
-
message: plannerSkill.message,
|
|
3145
|
-
instruction: "Read and retain this project-local planner usage skill, project context, and Accepted Decision context. Do not quote it, the project context, or the Accepted Decision context in the human-facing recap.",
|
|
3146
|
-
projectContext: { text: projectContextReply.text },
|
|
3147
|
-
acceptedDecisions: acceptedDecisionContext,
|
|
3148
|
-
},
|
|
3149
|
-
},
|
|
3150
|
-
};
|
|
3155
|
+
// One text, each piece once (shared with Pi via buildPlannerLoadReply).
|
|
3156
|
+
// The guide body is not resent: the /planner command already carries it,
|
|
3157
|
+
// and a bare planner-load call is pointed at the project-local copy.
|
|
3158
|
+
const reply = buildPlannerLoadReply({
|
|
3159
|
+
recap: preparation.changed ? `${preparation.legacyProjectContext.summary}\n\n${recap}` : recap,
|
|
3160
|
+
projectContext: projectContextReply,
|
|
3161
|
+
scopedDecisions: buildScopedDecisionContext(plan),
|
|
3162
|
+
plannerGuide: { mode: "pointer", path: ".planner/SKILL.md", customized: plannerSkill.customized, message: plannerSkill.message },
|
|
3163
|
+
});
|
|
3164
|
+
return text(reply.text, {
|
|
3165
|
+
...reply.structured,
|
|
3166
|
+
webUi: plannerWebMetadata(web),
|
|
3167
|
+
preparation: preparation.legacyProjectContext,
|
|
3168
|
+
});
|
|
3151
3169
|
});
|
|
3152
3170
|
server.registerTool("planner-disable", {
|
|
3153
3171
|
description: "MCP no-op equivalent of Pi /planner disable. Stop the MCP process from the host to disable it.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server for Agent Plan, exposing planner-* tools over stdio.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
45
45
|
"zod": "^3.24.0",
|
|
46
|
-
"@agent-plan/core": "0.3.
|
|
47
|
-
"@agent-plan/server": "0.3.
|
|
46
|
+
"@agent-plan/core": "0.3.1",
|
|
47
|
+
"@agent-plan/server": "0.3.1"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsc -p tsconfig.json",
|