@roadmapperai/mcp 0.6.0 → 0.7.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +42 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roadmapperai/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
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
@@ -1902,7 +1902,14 @@ async function callTool(name, args) {
1902
1902
  session.themesListedAt = Date.now();
1903
1903
  return suggestThemeFor(args, projected);
1904
1904
  case "link_pr":
1905
- return linkPR(args, projected, seed, wsId);
1905
+ // Pass null as the seed reference. linkPR's only use of seed
1906
+ // is to look up legacy seed-bundled PR entries on a task; in
1907
+ // the live path the canonical PRs live on the projected task,
1908
+ // so seed is never the source of truth. Passing the previously-
1909
+ // undefined identifier `seed` raised "seed is not defined" at
1910
+ // runtime for every link_pr call — caught by the live test
1911
+ // drive that exposed bug #5.
1912
+ return linkPR(args, projected, null, wsId);
1906
1913
  case "archive_task":
1907
1914
  return archiveLifecycle("task", "archive", args, wsId);
1908
1915
  case "archive_capability":
@@ -1943,7 +1950,16 @@ async function proposeTask(args, projected, wsId) {
1943
1950
  if (!cap) return errorResult(`Capability ${args.capabilityId} not found.`);
1944
1951
  const titleErr = validateName(args.title, 5);
1945
1952
  if (titleErr) return errorResult(titleErr);
1946
- if (args.effort && !VALID_EFFORTS.has(args.effort))
1953
+ // Effort is required end-to-end: the schema flags it, and the
1954
+ // Postgres RPC refuses without it. We also raise here so older
1955
+ // clients that ignore the schema get a clear error message before
1956
+ // a round-trip.
1957
+ if (!args.effort) {
1958
+ return errorResult(
1959
+ "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)."
1960
+ );
1961
+ }
1962
+ if (!VALID_EFFORTS.has(args.effort))
1947
1963
  return errorResult(`Invalid effort ${args.effort}.`);
1948
1964
  if (args.priority && !VALID_PRIORITIES.has(args.priority))
1949
1965
  return errorResult(`Invalid priority ${args.priority}.`);
@@ -1960,7 +1976,7 @@ async function proposeTask(args, projected, wsId) {
1960
1976
  )
1961
1977
  return errorResult(`expectedScope must be a positive number, got ${args.expectedScope}.`);
1962
1978
 
1963
- const effort = args.effort ?? "M";
1979
+ const effort = args.effort;
1964
1980
  const start = todayISO();
1965
1981
  // Target dates are day-resolution; round up so sub-day estimates
1966
1982
  // (XS=0.25, S=0.5) still nudge the target at least one day out.
@@ -2269,10 +2285,32 @@ function suggestCapabilityFor(args, projected) {
2269
2285
  (c) => effectiveCapabilityStatus(c, projected.tasks) !== "delivered"
2270
2286
  );
2271
2287
  const query = tokenize(desc);
2288
+ // Build a richer haystack per capability: include the parent
2289
+ // theme's name+description (vocabulary often overlaps with the
2290
+ // query) and the titles of attached tasks (the *implementation*
2291
+ // language, which is closer to how engineers describe new work
2292
+ // than the bet-style capability outcome statement). This makes
2293
+ // matches that previously scored near zero hit the 0.2+ band
2294
+ // where the model takes them seriously.
2295
+ const themeById = new Map(
2296
+ (projected.themes ?? []).map((t) => [t.id, t])
2297
+ );
2272
2298
  const ranked = activeCaps
2273
2299
  .map((c) => {
2300
+ const theme = themeById.get(c.pillarId);
2301
+ const taskTitles = (projected.tasks ?? [])
2302
+ .filter((t) => t.capabilityId === c.id)
2303
+ .map((t) => t.title)
2304
+ .join(" ");
2274
2305
  const hay = tokenize(
2275
- `${c.name} ${c.description ?? ""} ${c.outcome ?? ""}`
2306
+ [
2307
+ c.name,
2308
+ c.description ?? "",
2309
+ c.outcome ?? "",
2310
+ theme?.name ?? "",
2311
+ theme?.description ?? "",
2312
+ taskTitles,
2313
+ ].join(" ")
2276
2314
  );
2277
2315
  return { capability: c, score: jaccardScore(query, hay) };
2278
2316
  })