@roadmapperai/mcp 0.5.0 → 0.7.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +37 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roadmapperai/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Roadmapper AI MCP server — exposes a planning surface (themes, capabilities, tasks, sprints, PRs) to coding agents via stdio JSON-RPC. Pairs with the Roadmapper AI workspace at dashboard.roadmapperai.com.",
5
5
  "keywords": [
6
6
  "mcp",
package/server.mjs CHANGED
@@ -1090,7 +1090,8 @@ const TOOLS = [
1090
1090
  "USE WHEN: the user asks to plan features, design new work, sketch a roadmap, file a TODO that should persist beyond this conversation, or break a capability into deliverables.\n" +
1091
1091
  "PREREQUISITE: get_agents_md once this session (the server enforces this and returns an error with a `fix` field if missing). Call suggest_capability_for first to find the right parent capability — do not invent a new one.\n" +
1092
1092
  "ANTI-PATTERN: do not call to track in-progress work within a single conversation — use the harness TodoWrite tool. Do not call to log a bug discovered during implementation — file in the issue tracker, not roadmapper. Do not call when you don't know which capability the task belongs under; resolve that first.\n" +
1093
- "EXAMPLE: propose_task({ capabilityId: 'CAP-XXX', title: 'Drag-and-drop block reorder', acceptance: ['Block can be dragged with mouse + keyboard', 'Order persists across reloads'], idempotencyKey: 'session-1-task-3' })\n\n" +
1093
+ "REQUIRED FIELDS: capabilityId, title, effort. Always size the task XS (≤2h) / S (≤1d) / M (~1-3d) / L (~1-2w) / XL (>2w). Effort drives capability % roll-up weighting; do not omit.\n" +
1094
+ "EXAMPLE: propose_task({ capabilityId: 'CAP-XXX', title: 'Drag-and-drop block reorder', effort: 'M', acceptance: ['Block can be dragged with mouse + keyboard', 'Order persists across reloads'], idempotencyKey: 'session-1-task-3' })\n\n" +
1094
1095
  "Requires SUPABASE_SERVICE_ROLE_KEY. Pass idempotencyKey so retries don't duplicate. Pass dryRun: true to validate without writing. Pass workspaceId to target a workspace other than the env default.",
1095
1096
  inputSchema: {
1096
1097
  type: "object",
@@ -1118,7 +1119,7 @@ const TOOLS = [
1118
1119
  dryRun: { type: "boolean" },
1119
1120
  workspaceId: { type: "string" },
1120
1121
  },
1121
- required: ["capabilityId", "title"],
1122
+ required: ["capabilityId", "title", "effort"],
1122
1123
  additionalProperties: false,
1123
1124
  },
1124
1125
  },
@@ -1942,7 +1943,16 @@ async function proposeTask(args, projected, wsId) {
1942
1943
  if (!cap) return errorResult(`Capability ${args.capabilityId} not found.`);
1943
1944
  const titleErr = validateName(args.title, 5);
1944
1945
  if (titleErr) return errorResult(titleErr);
1945
- if (args.effort && !VALID_EFFORTS.has(args.effort))
1946
+ // Effort is required end-to-end: the schema flags it, and the
1947
+ // Postgres RPC refuses without it. We also raise here so older
1948
+ // clients that ignore the schema get a clear error message before
1949
+ // a round-trip.
1950
+ if (!args.effort) {
1951
+ return errorResult(
1952
+ "effort is required (one of XS, S, M, L, XL). Always size the task — XS (≤2h), S (≤1d), M (~1-3d), L (~1-2w), XL (>2w)."
1953
+ );
1954
+ }
1955
+ if (!VALID_EFFORTS.has(args.effort))
1946
1956
  return errorResult(`Invalid effort ${args.effort}.`);
1947
1957
  if (args.priority && !VALID_PRIORITIES.has(args.priority))
1948
1958
  return errorResult(`Invalid priority ${args.priority}.`);
@@ -1959,7 +1969,7 @@ async function proposeTask(args, projected, wsId) {
1959
1969
  )
1960
1970
  return errorResult(`expectedScope must be a positive number, got ${args.expectedScope}.`);
1961
1971
 
1962
- const effort = args.effort ?? "M";
1972
+ const effort = args.effort;
1963
1973
  const start = todayISO();
1964
1974
  // Target dates are day-resolution; round up so sub-day estimates
1965
1975
  // (XS=0.25, S=0.5) still nudge the target at least one day out.
@@ -2268,10 +2278,32 @@ function suggestCapabilityFor(args, projected) {
2268
2278
  (c) => effectiveCapabilityStatus(c, projected.tasks) !== "delivered"
2269
2279
  );
2270
2280
  const query = tokenize(desc);
2281
+ // Build a richer haystack per capability: include the parent
2282
+ // theme's name+description (vocabulary often overlaps with the
2283
+ // query) and the titles of attached tasks (the *implementation*
2284
+ // language, which is closer to how engineers describe new work
2285
+ // than the bet-style capability outcome statement). This makes
2286
+ // matches that previously scored near zero hit the 0.2+ band
2287
+ // where the model takes them seriously.
2288
+ const themeById = new Map(
2289
+ (projected.themes ?? []).map((t) => [t.id, t])
2290
+ );
2271
2291
  const ranked = activeCaps
2272
2292
  .map((c) => {
2293
+ const theme = themeById.get(c.pillarId);
2294
+ const taskTitles = (projected.tasks ?? [])
2295
+ .filter((t) => t.capabilityId === c.id)
2296
+ .map((t) => t.title)
2297
+ .join(" ");
2273
2298
  const hay = tokenize(
2274
- `${c.name} ${c.description ?? ""} ${c.outcome ?? ""}`
2299
+ [
2300
+ c.name,
2301
+ c.description ?? "",
2302
+ c.outcome ?? "",
2303
+ theme?.name ?? "",
2304
+ theme?.description ?? "",
2305
+ taskTitles,
2306
+ ].join(" ")
2275
2307
  );
2276
2308
  return { capability: c, score: jaccardScore(query, hay) };
2277
2309
  })