agent-planner-mcp 1.5.10 → 1.5.13

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/SKILL.md CHANGED
@@ -83,6 +83,7 @@ A Workspace is a folder under an Organization that owns goals + plans — a grou
83
83
  - `list_blueprints` — list blueprints visible to user (owned + public/unlisted), filterable by scope
84
84
  - `fork_blueprint` — instantiate a plan-scope blueprint as a new plan in a target workspace
85
85
  - `save_as_blueprint` — snapshot a live plan as a reusable blueprint. Captures structure, agent_instructions, and dependencies; excludes statuses, claims, knowledge episodes, logs, decisions, and agent assignments
86
+ - `delete_blueprint` — delete a blueprint you own (hard delete; already-forked plans are unaffected)
86
87
 
87
88
  ### Utility
88
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-planner-mcp",
3
- "version": "1.5.10",
3
+ "version": "1.5.13",
4
4
  "description": "MCP server for AgentPlanner — AI agent orchestration with planning, dependencies, knowledge graphs, and human oversight",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -7,7 +7,7 @@
7
7
  * directly — no UI round-trip and no forced approval gate.
8
8
  */
9
9
 
10
- const { asOf, formatResponse, errorResponse, safeArray } = require('./_shared');
10
+ const { asOf, formatResponse, errorResponse, safeArray, apiErrorMessage } = require('./_shared');
11
11
 
12
12
  const listGoalsDefinition = {
13
13
  name: 'list_goals',
@@ -110,11 +110,20 @@ async function updateGoalHandler(args, apiClient) {
110
110
  const applied = [];
111
111
  const failures = [];
112
112
 
113
- // Direct field updates
113
+ // Direct field updates. title/description/priority/status share their name
114
+ // with the backend, but success_criteria must be camelCased to successCriteria
115
+ // — the goal update schema is .strict(), so the snake_case key was rejected
116
+ // with a 400 (the one multi-word field, hence "description writes but
117
+ // success_criteria fails"). Match the create shape: wrap a bare array as
118
+ // { criteria: [...] }.
114
119
  const directFields = {};
115
- for (const k of ['title', 'description', 'priority', 'status', 'success_criteria']) {
120
+ for (const k of ['title', 'description', 'priority', 'status']) {
116
121
  if (changes[k] !== undefined) directFields[k] = changes[k];
117
122
  }
123
+ if (changes.success_criteria !== undefined) {
124
+ const sc = changes.success_criteria;
125
+ directFields.successCriteria = Array.isArray(sc) ? { criteria: sc } : sc;
126
+ }
118
127
  // Map the public `committed` boolean onto the backend's commitment write
119
128
  // (the API still accepts the legacy goalType field and translates it to
120
129
  // promoted_at). committed:true ⇒ promoted, false ⇒ aspirational.
@@ -127,21 +136,21 @@ async function updateGoalHandler(args, apiClient) {
127
136
  await apiClient.goals.update(goal_id, directFields);
128
137
  applied.push('direct_fields');
129
138
  } catch (err) {
130
- failures.push({ step: 'direct_fields', error: err.message });
139
+ failures.push({ step: 'direct_fields', error: apiErrorMessage(err) });
131
140
  }
132
141
  }
133
142
 
134
143
  for (const planId of safeArray(changes.add_linked_plans)) {
135
144
  try { await apiClient.goals.linkPlan(goal_id, planId); applied.push(`link_plan:${planId}`); }
136
- catch (err) { failures.push({ step: `link_plan:${planId}`, error: err.message }); }
145
+ catch (err) { failures.push({ step: `link_plan:${planId}`, error: apiErrorMessage(err) }); }
137
146
  }
138
147
  for (const planId of safeArray(changes.remove_linked_plans)) {
139
148
  try { await apiClient.goals.unlinkPlan(goal_id, planId); applied.push(`unlink_plan:${planId}`); }
140
- catch (err) { failures.push({ step: `unlink_plan:${planId}`, error: err.message }); }
149
+ catch (err) { failures.push({ step: `unlink_plan:${planId}`, error: apiErrorMessage(err) }); }
141
150
  }
142
151
  for (const nodeId of safeArray(changes.add_achievers)) {
143
152
  try { await apiClient.goals.addAchiever(goal_id, nodeId); applied.push(`add_achiever:${nodeId}`); }
144
- catch (err) { failures.push({ step: `add_achiever:${nodeId}`, error: err.message }); }
153
+ catch (err) { failures.push({ step: `add_achiever:${nodeId}`, error: apiErrorMessage(err) }); }
145
154
  }
146
155
  for (const nodeId of safeArray(changes.remove_achievers)) {
147
156
  try {
@@ -152,7 +161,7 @@ async function updateGoalHandler(args, apiClient) {
152
161
  applied.push(`remove_achiever:${nodeId}`);
153
162
  }
154
163
  } catch (err) {
155
- failures.push({ step: `remove_achiever:${nodeId}`, error: err.message });
164
+ failures.push({ step: `remove_achiever:${nodeId}`, error: apiErrorMessage(err) });
156
165
  }
157
166
  }
158
167
 
@@ -229,15 +229,21 @@ async function resolveDecisionHandler(args, apiClient) {
229
229
  if (action === 'approve' && decision?.metadata?.proposed_subtasks?.length) {
230
230
  for (const proposal of decision.metadata.proposed_subtasks) {
231
231
  try {
232
+ // createNode's schema is .strict() and has no acceptance_criteria field —
233
+ // sending it 400s the whole subtask. Fold it into the description so the
234
+ // criteria survive instead of being silently dropped on approval.
235
+ const description = [
236
+ proposal.description,
237
+ proposal.acceptance_criteria ? `Acceptance criteria: ${proposal.acceptance_criteria}` : null,
238
+ ].filter(Boolean).join('\n\n') || undefined;
232
239
  const node = await apiClient.nodes.createNode(plan_id, {
233
240
  parent_id: proposal.parent_id,
234
241
  node_type: proposal.node_type || 'task',
235
242
  title: proposal.title,
236
- description: proposal.description,
243
+ description,
237
244
  status: 'not_started',
238
245
  task_mode: proposal.task_mode || 'free',
239
246
  agent_instructions: proposal.agent_instructions,
240
- acceptance_criteria: proposal.acceptance_criteria,
241
247
  });
242
248
  created.push({ id: node.id || node.node?.id, title: proposal.title, parent_id: proposal.parent_id });
243
249
  } catch (err) {
@@ -274,6 +280,12 @@ const STATUS_TO_LOG_TYPE = {
274
280
  plan_ready: 'progress',
275
281
  };
276
282
 
283
+ // The backend log endpoint accepts comment/progress/reasoning/decision/challenge.
284
+ // Two friendly aliases the tool historically advertised are NOT valid there and
285
+ // 400'd the log step — map them to the closest valid type.
286
+ const LOG_TYPE_ALIASES = { blocker: 'challenge', completion: 'progress' };
287
+ const normalizeLogType = (lt) => (lt ? (LOG_TYPE_ALIASES[lt] || lt) : lt);
288
+
277
289
  const updateTaskDefinition = {
278
290
  name: 'update_task',
279
291
  description:
@@ -295,8 +307,9 @@ const updateTaskDefinition = {
295
307
  log_message: { type: 'string', description: 'Optional progress note' },
296
308
  log_type: {
297
309
  type: 'string',
298
- enum: ['progress', 'decision', 'blocker', 'completion', 'challenge'],
299
- description: "Defaults from status: blocked→challenge, others→progress.",
310
+ enum: ['progress', 'reasoning', 'decision', 'challenge', 'comment'],
311
+ description: "Defaults from status: blocked→challenge, others→progress. "
312
+ + "Legacy 'blocker'/'completion' are accepted and mapped to challenge/progress.",
300
313
  },
301
314
  release_claim: {
302
315
  type: 'boolean',
@@ -344,7 +357,7 @@ async function updateTaskHandler(args, apiClient) {
344
357
  const data = await apiClient.v1.updateTask(task_id, {
345
358
  status,
346
359
  log_message,
347
- log_type: args.log_type,
360
+ log_type: normalizeLogType(args.log_type),
348
361
  release_claim,
349
362
  add_learning,
350
363
  });
@@ -390,7 +403,7 @@ async function updateTaskHandler(args, apiClient) {
390
403
 
391
404
  // 2. Log entry
392
405
  if (log_message) {
393
- const logType = args.log_type || STATUS_TO_LOG_TYPE[status] || 'progress';
406
+ const logType = normalizeLogType(args.log_type) || STATUS_TO_LOG_TYPE[status] || 'progress';
394
407
  try {
395
408
  const log = await apiClient.logs.addLogEntry(planId, task_id, {
396
409
  content: log_message,
@@ -718,6 +731,13 @@ async function addLearningHandler(args, apiClient) {
718
731
  plan_id: scope.plan_id,
719
732
  node_id: scope.node_id,
720
733
  entity_type: entry_type,
734
+ // The episodes endpoint only persists {content,name,plan_id,node_id,metadata};
735
+ // the top-level entry_type/source_description were silently dropped. Carry
736
+ // the categorization in metadata so it survives on the stored episode.
737
+ metadata: {
738
+ entry_type,
739
+ source_description: source_description || 'BDI add_learning',
740
+ },
721
741
  });
722
742
  return formatResponse({
723
743
  as_of: asOf(),
@@ -8,7 +8,7 @@
8
8
  * agent-planner/docs/WORKSPACE_BLUEPRINT_SKETCH.md for the design.
9
9
  */
10
10
 
11
- const { asOf, formatResponse, errorResponse, safeArray } = require('./_shared');
11
+ const { asOf, formatResponse, errorResponse, safeArray, apiErrorMessage } = require('./_shared');
12
12
 
13
13
  // ─── Workspaces ──────────────────────────────────────────────────
14
14
 
@@ -197,6 +197,37 @@ async function saveAsBlueprintHandler(args, apiClient) {
197
197
  }
198
198
  }
199
199
 
200
+ const deleteBlueprintDefinition = {
201
+ name: 'delete_blueprint',
202
+ description:
203
+ "Delete a blueprint you own. Hard delete (the snapshot is removed); plans " +
204
+ "already forked from it are unaffected. Owner-only. Completes the blueprint " +
205
+ "lifecycle alongside save_as_blueprint and fork_blueprint.",
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ blueprint_id: { type: 'string' },
210
+ },
211
+ required: ['blueprint_id'],
212
+ },
213
+ };
214
+
215
+ async function deleteBlueprintHandler(args, apiClient) {
216
+ const { blueprint_id } = args;
217
+ if (!blueprint_id) {
218
+ return errorResponse('invalid_arg', 'delete_blueprint requires blueprint_id');
219
+ }
220
+ try {
221
+ await apiClient.blueprints.delete(blueprint_id);
222
+ return formatResponse({ as_of: asOf(), blueprint_id, deleted: true });
223
+ } catch (err) {
224
+ const status = err.response?.status;
225
+ if (status === 404) return errorResponse('not_found', `Blueprint ${blueprint_id} not found`);
226
+ if (status === 403) return errorResponse('forbidden', 'Only the owner can delete this blueprint');
227
+ return errorResponse('upstream_unavailable', `delete_blueprint failed: ${apiErrorMessage(err)}`);
228
+ }
229
+ }
230
+
200
231
  module.exports = {
201
232
  definitions: [
202
233
  listWorkspacesDefinition,
@@ -204,6 +235,7 @@ module.exports = {
204
235
  listBlueprintsDefinition,
205
236
  forkBlueprintDefinition,
206
237
  saveAsBlueprintDefinition,
238
+ deleteBlueprintDefinition,
207
239
  ],
208
240
  handlers: {
209
241
  list_workspaces: listWorkspacesHandler,
@@ -211,5 +243,6 @@ module.exports = {
211
243
  list_blueprints: listBlueprintsHandler,
212
244
  fork_blueprint: forkBlueprintHandler,
213
245
  save_as_blueprint: saveAsBlueprintHandler,
246
+ delete_blueprint: deleteBlueprintHandler,
214
247
  },
215
248
  };