agent-planner-mcp 1.5.3 → 1.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-planner-mcp",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
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": {
@@ -26,6 +26,24 @@ function safeArray(value) {
26
26
  return Array.isArray(value) ? value : [];
27
27
  }
28
28
 
29
+ /**
30
+ * The web app origin (where /app/plans/:id lives), for building shareable plan
31
+ * links agents can post (e.g. to Slack). Derived from API_URL — the web app
32
+ * shares the origin and the API sits under /api behind nginx — with an explicit
33
+ * AGENTPLANNER_WEB_URL override for local/self-hosted setups where the UI is on
34
+ * a different host/port.
35
+ */
36
+ function webOrigin() {
37
+ if (process.env.AGENTPLANNER_WEB_URL) return process.env.AGENTPLANNER_WEB_URL.replace(/\/+$/, '');
38
+ const api = process.env.API_URL || 'https://agentplanner.io/api';
39
+ return api.replace(/\/+$/, '').replace(/\/api$/, '') || 'https://agentplanner.io';
40
+ }
41
+
42
+ /** Shareable web link to a plan (set the plan's visibility to unlisted/public for a rich unfurl). */
43
+ function planUrl(planId) {
44
+ return planId ? `${webOrigin()}/app/plans/${planId}` : null;
45
+ }
46
+
29
47
  /**
30
48
  * True when an error means the backend has no /v1 surface (pre-consolidation
31
49
  * self-hosted API). Express returns a default 404 with no structured body for
@@ -38,4 +56,4 @@ function isV1Unavailable(err) {
38
56
  return !(body && typeof body === 'object' && body.error);
39
57
  }
40
58
 
41
- module.exports = { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable };
59
+ module.exports = { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable, webOrigin, planUrl };
@@ -5,7 +5,7 @@
5
5
  * plan_analysis. Each answers one whole agentic question and returns `as_of`.
6
6
  */
7
7
 
8
- const { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable } = require('./_shared');
8
+ const { asOf, formatResponse, errorResponse, safeArray, isV1Unavailable, planUrl } = require('./_shared');
9
9
 
10
10
  // ─────────────────────────────────────────────────────────────────────────
11
11
  // briefing — bundled mission control state. Replaces 4 round trips.
@@ -486,6 +486,7 @@ async function listPlansHandler(args, apiClient) {
486
486
  summary,
487
487
  plans: page.map((p) => ({
488
488
  id: p.id,
489
+ url: planUrl(p.id),
489
490
  title: p.title,
490
491
  status: p.status,
491
492
  visibility: p.visibility,
@@ -13,7 +13,7 @@
13
13
  * See ../../../docs/MCP_v1.0_FULL_SURFACE.md for design rationale.
14
14
  */
15
15
 
16
- const { asOf, formatResponse, errorResponse, isV1Unavailable } = require('./_shared');
16
+ const { asOf, formatResponse, errorResponse, isV1Unavailable, planUrl } = require('./_shared');
17
17
  const { version: PKG_VERSION } = require('../../../package.json');
18
18
 
19
19
  // Provenance tag stamped onto every plan this server creates, so a plan stays
@@ -854,16 +854,18 @@ async function formIntentionHandler(args, apiClient) {
854
854
  tree,
855
855
  client_version: CLIENT_TAG,
856
856
  });
857
+ const facadePlanId = result.plan?.id || result.plan_id;
857
858
  return formatResponse({
858
859
  ...result,
859
- plan_id: result.plan?.id || result.plan_id,
860
+ plan_id: facadePlanId,
861
+ url: planUrl(facadePlanId),
860
862
  goal_id,
861
863
  status: result.plan?.status || status,
862
864
  is_draft: (result.plan?.status || status) === 'draft',
863
865
  nodes_created: Array.isArray(result.tree) ? result.tree.length : undefined,
864
866
  next_step: (result.plan?.status || status) === 'draft'
865
867
  ? "Plan created as draft. Will surface in dashboard pending for human review. Auto-promotes to active when first task moves to in_progress."
866
- : "Plan active. Claim a task with claim_next_task({plan_id}) to begin work.",
868
+ : `Plan active. Claim a task with claim_next_task({plan_id}) to begin work. Shareable link: ${planUrl(facadePlanId)} (set visibility:'unlisted' for a rich Slack/social preview).`,
867
869
  });
868
870
  } catch {
869
871
  // Fall through to the legacy multi-call path for older/self-hosted APIs.
@@ -938,7 +940,10 @@ async function formIntentionHandler(args, apiClient) {
938
940
  continue;
939
941
  }
940
942
  try {
941
- await apiClient.axiosInstance.post('/dependencies', {
943
+ // Plan-scoped route — the bare /dependencies path is unmounted (only
944
+ // /dependencies/cross-plan + /external exist), so it 404s and silently
945
+ // dropped every inline edge. POST /plans/:id/dependencies is canonical.
946
+ await apiClient.axiosInstance.post(`/plans/${plan.id}/dependencies`, {
942
947
  source_node_id: sourceId,
943
948
  target_node_id: intent.targetId,
944
949
  dependency_type: 'blocks',
@@ -963,6 +968,7 @@ async function formIntentionHandler(args, apiClient) {
963
968
  const response = {
964
969
  as_of: asOf(),
965
970
  plan_id: plan.id,
971
+ url: planUrl(plan.id),
966
972
  goal_id,
967
973
  status: plan.status,
968
974
  is_draft: plan.status === 'draft',
@@ -972,7 +978,7 @@ async function formIntentionHandler(args, apiClient) {
972
978
  structure,
973
979
  next_step: plan.status === 'draft'
974
980
  ? "Plan created as draft. Will surface in dashboard pending for human review. Auto-promotes to active when first task moves to in_progress."
975
- : "Plan active. Claim a task with claim_next_task({plan_id}) to begin work.",
981
+ : `Plan active. Claim a task with claim_next_task({plan_id}) to begin work. Shareable link: ${planUrl(plan.id)} (set visibility:'unlisted' for a rich Slack/social preview).`,
976
982
  };
977
983
  if (createdWithoutDependencies) {
978
984
  response.warning =
@@ -1133,7 +1139,9 @@ async function proposeResearchChainHandler(args, apiClient) {
1133
1139
  [created.plan.id, created.implement.id],
1134
1140
  ]) {
1135
1141
  try {
1136
- await apiClient.axiosInstance.post('/dependencies', {
1142
+ // Plan-scoped route — bare /dependencies 404s (unmounted), which silently
1143
+ // dropped both chain edges and left 3 orphan tasks.
1144
+ await apiClient.axiosInstance.post(`/plans/${plan_id}/dependencies`, {
1137
1145
  source_node_id: from,
1138
1146
  target_node_id: to,
1139
1147
  dependency_type: 'blocks',
@@ -1144,9 +1152,11 @@ async function proposeResearchChainHandler(args, apiClient) {
1144
1152
  }
1145
1153
  }
1146
1154
 
1155
+ const edgeFailures = failures.filter((f) => f.step === 'create_edge');
1147
1156
  return formatResponse({
1148
1157
  as_of: asOf(),
1149
1158
  plan_id,
1159
+ url: planUrl(plan_id),
1150
1160
  parent_id,
1151
1161
  rationale,
1152
1162
  research: { id: created.research.id, title: created.research.title },
@@ -1154,6 +1164,11 @@ async function proposeResearchChainHandler(args, apiClient) {
1154
1164
  implement: { id: created.implement.id, title: created.implement.title },
1155
1165
  edges,
1156
1166
  failures,
1167
+ // Fail loudly: don't let the caller believe the chain is wired when the
1168
+ // blocking edges didn't get created.
1169
+ ...(edgeFailures.length
1170
+ ? { warning: `Chain tasks created but ${edgeFailures.length} blocking edge(s) FAILED — the Research→Plan→Implement ordering is NOT wired: ${edgeFailures.map((f) => f.error).join('; ')}` }
1171
+ : {}),
1157
1172
  next_step: "Claim the Research task with claim_next_task({plan_id}) to begin investigation.",
1158
1173
  });
1159
1174
  }