@roadmapperai/mcp 0.2.0 → 0.4.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/README.md +9 -3
- package/package.json +1 -1
- package/server.mjs +124 -40
package/README.md
CHANGED
|
@@ -31,15 +31,21 @@ Add to your `~/.config/claude-code/config.json` (or the equivalent
|
|
|
31
31
|
"command": "npx",
|
|
32
32
|
"args": ["-y", "@roadmapperai/mcp"],
|
|
33
33
|
"env": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
34
|
+
"ROADMAPPER_BACKEND_URL": "https://api.roadmapperai.com",
|
|
35
|
+
"ROADMAPPER_PUBLISHABLE_KEY": "sb_publishable_...",
|
|
36
|
+
"ROADMAPPER_WORKSPACE_ID": "<your workspace id>",
|
|
37
|
+
"ROADMAPPER_API_KEY": "<rmpr_... — optional, enables writes>"
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
The `ROADMAPPER_API_KEY` env var is optional. Without it, the install runs
|
|
45
|
+
read-only (list/get/suggest tools). Mint a key in the dashboard at Settings →
|
|
46
|
+
MCP activity → API keys and add it to the `env` block to unlock write tools
|
|
47
|
+
(propose / link / grade).
|
|
48
|
+
|
|
43
49
|
Get your workspace ID from the dashboard's URL bar
|
|
44
50
|
(`dashboard.roadmapperai.com/settings` → workspace section) or from the
|
|
45
51
|
generated config block in Settings → Connect.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roadmapperai/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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
|
@@ -3,42 +3,50 @@
|
|
|
3
3
|
* Roadmapper MCP server — zero-dependency stdio JSON-RPC.
|
|
4
4
|
*
|
|
5
5
|
* Exposes a planning surface so an agent can read the roadmap and
|
|
6
|
-
* (when authorized) propose tasks or stamp acceptance grades
|
|
6
|
+
* (when authorized) propose tasks or stamp acceptance grades.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Customer-facing env vars (brand-named, no backend disclosure):
|
|
9
|
+
* ROADMAPPER_BACKEND_URL — backend project URL
|
|
10
|
+
* ROADMAPPER_PUBLISHABLE_KEY — public client key (RLS-scoped)
|
|
11
|
+
* ROADMAPPER_WORKSPACE_ID — target workspace
|
|
12
|
+
* ROADMAPPER_API_KEY — write auth (rmpr_… token from
|
|
13
|
+
* Settings → MCP activity → API keys)
|
|
14
|
+
* ROADMAPPER_BROKER_URL — optional override for the write
|
|
15
|
+
* broker; defaults to BACKEND_URL/
|
|
16
|
+
* functions/v1/mcp-broker
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the seed exactly like the app does.
|
|
22
|
-
* 3. Writes require SUPABASE_SERVICE_ROLE_KEY (bypasses RLS). Without
|
|
23
|
-
* it the write tools return an error result and the read tools
|
|
24
|
-
* still work.
|
|
18
|
+
* Legacy operator-only env vars (still accepted for CI / maintainer
|
|
19
|
+
* local installs; not part of the customer-facing config):
|
|
20
|
+
* SUPABASE_URL / SUPABASE_PUBLISHABLE_KEY / SUPABASE_ANON_KEY /
|
|
21
|
+
* SUPABASE_WORKSPACE_ID / SUPABASE_SERVICE_ROLE_KEY ← operator
|
|
22
|
+
* ROADMAPPER_ADMIN_KEY — brand-named alias for SERVICE_ROLE_KEY
|
|
25
23
|
*
|
|
26
|
-
*
|
|
24
|
+
* Customer wire-up (Claude Code / Claude Desktop / Cursor):
|
|
27
25
|
* {
|
|
28
26
|
* "mcpServers": {
|
|
29
27
|
* "roadmapper": {
|
|
30
|
-
* "command": "
|
|
31
|
-
* "args": ["/
|
|
28
|
+
* "command": "npx",
|
|
29
|
+
* "args": ["-y", "@roadmapperai/mcp"],
|
|
32
30
|
* "env": {
|
|
33
|
-
* "
|
|
34
|
-
* "
|
|
35
|
-
* "
|
|
36
|
-
* "
|
|
31
|
+
* "ROADMAPPER_BACKEND_URL": "...",
|
|
32
|
+
* "ROADMAPPER_PUBLISHABLE_KEY": "sb_publishable_...",
|
|
33
|
+
* "ROADMAPPER_WORKSPACE_ID": "...",
|
|
34
|
+
* "ROADMAPPER_API_KEY": "rmpr_..." ← optional, enables writes
|
|
37
35
|
* }
|
|
38
36
|
* }
|
|
39
37
|
* }
|
|
40
38
|
* }
|
|
41
39
|
*
|
|
40
|
+
* Data sources, in order:
|
|
41
|
+
* 1. Local seed at src/data/roadmap.json (dev only; absent in npm
|
|
42
|
+
* installs, where readSeed() falls back to an empty roadmap and
|
|
43
|
+
* data loads from the backend).
|
|
44
|
+
* 2. Workspace edits via REST, when BACKEND_URL + PUBLISHABLE_KEY
|
|
45
|
+
* + WORKSPACE_ID are set.
|
|
46
|
+
* 3. Writes route through ROADMAPPER_API_KEY (customer path,
|
|
47
|
+
* validated server-side at the broker) or ROADMAPPER_ADMIN_KEY
|
|
48
|
+
* (operator path, bypasses RLS).
|
|
49
|
+
*
|
|
42
50
|
* Self-test: `node mcp/server.mjs --selftest` exercises every tool
|
|
43
51
|
* against the local seed and exits 0 on success, 1 on failure. Useful
|
|
44
52
|
* for verifying the install without an MCP client.
|
|
@@ -111,36 +119,101 @@ function readAgentsMd() {
|
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Fetch a workspace-scoped rubric override via the mcp-broker.
|
|
124
|
+
*
|
|
125
|
+
* Returns the workspace's custom rubric content if they've authored
|
|
126
|
+
* one in the dashboard's Settings → Workspace customization tab,
|
|
127
|
+
* null if the workspace is on the default, or null on any error
|
|
128
|
+
* (we fall back to the bundled AGENTS.md rather than refuse to
|
|
129
|
+
* serve get_agents_md).
|
|
130
|
+
*
|
|
131
|
+
* Only called when ROADMAPPER_API_KEY is set — i.e. the customer
|
|
132
|
+
* install path. Operator path (SUPABASE_SERVICE_ROLE_KEY) skips
|
|
133
|
+
* this and always serves the bundled rubric.
|
|
134
|
+
*/
|
|
135
|
+
async function fetchWorkspaceRubric() {
|
|
136
|
+
const { apiKey, brokerUrl } = supabaseConfig();
|
|
137
|
+
if (!apiKey || !brokerUrl) return null;
|
|
138
|
+
try {
|
|
139
|
+
const res = await fetch(brokerUrl, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
Authorization: `Bearer ${apiKey}`,
|
|
143
|
+
"content-type": "application/json",
|
|
144
|
+
Accept: "application/json",
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({ rpc: "get_workspace_rubric", body: {} }),
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok) return null;
|
|
149
|
+
const parsed = await res.json();
|
|
150
|
+
if (typeof parsed === "string" && parsed.length > 0) return parsed;
|
|
151
|
+
return null;
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function readAgentsMdForWorkspace() {
|
|
158
|
+
const custom = await fetchWorkspaceRubric();
|
|
159
|
+
if (custom) return custom;
|
|
160
|
+
return readAgentsMd();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolve a config value from a primary `ROADMAPPER_*` env var,
|
|
165
|
+
* falling back to a legacy `SUPABASE_*` alias when the primary
|
|
166
|
+
* isn't set. The brand-facing customer config block ships with
|
|
167
|
+
* the ROADMAPPER_* names so customers never see "supabase" in
|
|
168
|
+
* their MCP config; the SUPABASE_* aliases stay supported so
|
|
169
|
+
* existing operator / CI installs don't break on upgrade.
|
|
170
|
+
*/
|
|
171
|
+
function envEither(primary, ...fallbacks) {
|
|
172
|
+
for (const k of [primary, ...fallbacks]) {
|
|
173
|
+
const v = process.env[k];
|
|
174
|
+
if (v) return v;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
114
179
|
/**
|
|
115
180
|
* The read key used to fetch the workspace row. Accepts the new
|
|
116
181
|
* publishable key (`sb_publishable_…`) or the legacy `anon`/JWT key.
|
|
117
182
|
*/
|
|
118
183
|
function readKey() {
|
|
119
|
-
return (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
184
|
+
return envEither(
|
|
185
|
+
"ROADMAPPER_PUBLISHABLE_KEY",
|
|
186
|
+
"SUPABASE_PUBLISHABLE_KEY",
|
|
187
|
+
"SUPABASE_ANON_KEY"
|
|
123
188
|
);
|
|
124
189
|
}
|
|
125
190
|
|
|
126
191
|
function supabaseConfig() {
|
|
192
|
+
const url = envEither("ROADMAPPER_BACKEND_URL", "SUPABASE_URL");
|
|
127
193
|
return {
|
|
128
|
-
url
|
|
194
|
+
url,
|
|
129
195
|
readKey: readKey(),
|
|
130
|
-
|
|
131
|
-
|
|
196
|
+
// Operator path: a service-role-equivalent key. ROADMAPPER_ADMIN_KEY
|
|
197
|
+
// is the brand-facing name; SUPABASE_SERVICE_ROLE_KEY is the legacy
|
|
198
|
+
// alias used by CI and the maintainer's local workspace.
|
|
199
|
+
writeKey: envEither(
|
|
200
|
+
"ROADMAPPER_ADMIN_KEY",
|
|
201
|
+
"SUPABASE_SERVICE_ROLE_KEY"
|
|
202
|
+
),
|
|
203
|
+
workspaceId: envEither(
|
|
204
|
+
"ROADMAPPER_WORKSPACE_ID",
|
|
205
|
+
"SUPABASE_WORKSPACE_ID"
|
|
206
|
+
),
|
|
132
207
|
// ROADMAPPER_API_KEY is the customer-facing path: a per-workspace
|
|
133
208
|
// token (rmpr_…) minted from the dashboard. When set, write tools
|
|
134
209
|
// route through the mcp-broker Edge Function instead of needing
|
|
135
210
|
// a service-role key on the customer's machine. The broker URL
|
|
136
|
-
// defaults to the
|
|
137
|
-
//
|
|
211
|
+
// defaults to the project's edge endpoint but is overridable for
|
|
212
|
+
// self-hosted deployments / staging.
|
|
138
213
|
apiKey: process.env.ROADMAPPER_API_KEY || null,
|
|
139
214
|
brokerUrl:
|
|
140
215
|
process.env.ROADMAPPER_BROKER_URL ||
|
|
141
|
-
(
|
|
142
|
-
? `${process.env.SUPABASE_URL.replace(/\/$/, "")}/functions/v1/mcp-broker`
|
|
143
|
-
: null),
|
|
216
|
+
(url ? `${url.replace(/\/$/, "")}/functions/v1/mcp-broker` : null),
|
|
144
217
|
};
|
|
145
218
|
}
|
|
146
219
|
|
|
@@ -367,7 +440,7 @@ async function rpcCall(fn, body) {
|
|
|
367
440
|
// injects it before calling rpcCall so the override path works.
|
|
368
441
|
if (!url || !body?.p_workspace_id) {
|
|
369
442
|
throw new Error(
|
|
370
|
-
"Write tools require
|
|
443
|
+
"Write tools require ROADMAPPER_BACKEND_URL in env and a resolvable workspaceId (either ROADMAPPER_WORKSPACE_ID env or workspaceId arg)."
|
|
371
444
|
);
|
|
372
445
|
}
|
|
373
446
|
|
|
@@ -401,7 +474,7 @@ async function rpcCall(fn, body) {
|
|
|
401
474
|
// workspace; not what customers should ever configure.
|
|
402
475
|
if (!writeKey) {
|
|
403
476
|
throw new Error(
|
|
404
|
-
"Write tools require either ROADMAPPER_API_KEY (customer path) or
|
|
477
|
+
"Write tools require either ROADMAPPER_API_KEY (customer path) or ROADMAPPER_ADMIN_KEY (operator path)."
|
|
405
478
|
);
|
|
406
479
|
}
|
|
407
480
|
const res = await fetch(`${url}/rest/v1/rpc/${fn}`, {
|
|
@@ -1651,7 +1724,11 @@ async function callTool(name, args) {
|
|
|
1651
1724
|
const fresh = session.rubricFetchedAt === null;
|
|
1652
1725
|
session.rubricFetchedAt = Date.now();
|
|
1653
1726
|
if (fresh) recordTelemetry("rubric_fetched", { via: "tool" }, wsId);
|
|
1654
|
-
|
|
1727
|
+
// Customer path (ROADMAPPER_API_KEY set): ask the broker for
|
|
1728
|
+
// a workspace-scoped rubric. Falls back to the bundled
|
|
1729
|
+
// AGENTS.md when the workspace hasn't customized one.
|
|
1730
|
+
const rubric = await readAgentsMdForWorkspace();
|
|
1731
|
+
return textResult(rubric, {
|
|
1655
1732
|
_meta: {
|
|
1656
1733
|
roadmapper: {
|
|
1657
1734
|
reminder:
|
|
@@ -2865,7 +2942,14 @@ async function readResource(uri) {
|
|
|
2865
2942
|
}
|
|
2866
2943
|
return {
|
|
2867
2944
|
contents: [
|
|
2868
|
-
{
|
|
2945
|
+
{
|
|
2946
|
+
uri,
|
|
2947
|
+
mimeType: "text/markdown",
|
|
2948
|
+
// Resource handler is also async, so the workspace
|
|
2949
|
+
// rubric override applies here too — same fallback chain
|
|
2950
|
+
// as the get_agents_md tool.
|
|
2951
|
+
text: await readAgentsMdForWorkspace(),
|
|
2952
|
+
},
|
|
2869
2953
|
],
|
|
2870
2954
|
};
|
|
2871
2955
|
}
|