@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.
- package/package.json +1 -1
- package/server.mjs +37 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roadmapperai/mcp",
|
|
3
|
-
"version": "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
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
})
|