@mushi-mushi/mcp 0.9.0 → 0.10.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/dist/index.js +253 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -217,6 +217,80 @@ var TOOL_CATALOG = [
|
|
|
217
217
|
useCase: "Set up this repo for the Mushi evolution loop in one step."
|
|
218
218
|
}
|
|
219
219
|
];
|
|
220
|
+
var TDD_TOOL_CATALOG = [
|
|
221
|
+
{
|
|
222
|
+
name: "map_user_stories",
|
|
223
|
+
title: "Map user stories from live app",
|
|
224
|
+
description: 'Crawl a live application URL with Firecrawl/Browserbase and ask Claude to draft an inventory.yaml with pages and user stories. Creates a story_map_run row for progress tracking, then writes an inventory_proposals row (source=live_crawl). Optionally dispatches a Cursor Cloud agent to refine the draft and open a PR. Returns { runId, status: "pending" } immediately \u2014 poll get_map_run_status for progress.',
|
|
225
|
+
scope: "mcp:write",
|
|
226
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
227
|
+
useCase: "Map the user stories in my live app automatically without writing YAML by hand."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "get_map_run_status",
|
|
231
|
+
title: "Story map run status",
|
|
232
|
+
description: "Get the status and results of a story_map_run (pending \u2192 running \u2192 completed/failed). Returns pages_crawled, proposal_id (once done), and cursor_pr_url if Cursor Cloud refined the draft.",
|
|
233
|
+
scope: "mcp:read",
|
|
234
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
235
|
+
useCase: "Is my story mapping crawl done yet?"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "generate_tdd_from_story",
|
|
239
|
+
title: "Generate TDD test from user story",
|
|
240
|
+
description: "Given a user story id (from the accepted inventory), ask Claude to write a full Playwright TypeScript test. Inserts a qa_stories row (source=test_gen_from_story) with approval_status driven by automation_mode. Optionally opens a draft GitHub PR. Returns { qaStoryId, prUrl, approvalStatus, needsHumanReview }.",
|
|
241
|
+
scope: "mcp:write",
|
|
242
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
243
|
+
useCase: "Generate a Playwright test for this user story."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "improve_qa_story",
|
|
247
|
+
title: "PDCA auto-improve a failing QA story",
|
|
248
|
+
description: "Trigger the PDCA improver for a specific project. Finds recently failed qa_story_runs and uses Claude to write improved test scripts. New tests are created with source=pdca and approval gated by the original story's automation_mode.",
|
|
249
|
+
scope: "mcp:write",
|
|
250
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
251
|
+
useCase: "Fix my failing QA tests automatically."
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "run_qa_story",
|
|
255
|
+
title: "Trigger a manual QA story run",
|
|
256
|
+
description: 'Queue a manual run for an enabled + approved qa_story. Returns the run id immediately; poll qa_story_runs or use get_report_detail for progress. Equivalent to "Run now" in the console.',
|
|
257
|
+
scope: "mcp:write",
|
|
258
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
259
|
+
useCase: "Run the login flow test right now."
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "list_byok_keys",
|
|
263
|
+
title: "List BYOK API key pool",
|
|
264
|
+
description: "List all BYOK API keys for the project, grouped by provider. Shows label, priority, status, and cooldown. Never returns the raw key value \u2014 only metadata. Use this to see which keys are active or exhausted.",
|
|
265
|
+
scope: "mcp:read",
|
|
266
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
267
|
+
useCase: "Which API keys are active and which are rate-limited?"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "add_byok_key",
|
|
271
|
+
title: "Add a BYOK API key",
|
|
272
|
+
description: "Add a new API key to the project's BYOK pool for a given provider (anthropic, openai, firecrawl, browserbase, cursor). Specify label and priority for ordering. The key is stored encrypted in Supabase Vault.",
|
|
273
|
+
scope: "mcp:write",
|
|
274
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
275
|
+
useCase: "Add a backup Anthropic key to the pool."
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "list_pending_review_stories",
|
|
279
|
+
title: "List QA stories pending review",
|
|
280
|
+
description: "Get the queue of TDD tests that were auto-generated and are waiting for human approval before they run in the QA schedule.",
|
|
281
|
+
scope: "mcp:read",
|
|
282
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
283
|
+
useCase: "What TDD tests need my approval today?"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "approve_qa_story",
|
|
287
|
+
title: "Approve or reject a pending QA story",
|
|
288
|
+
description: "Approve or reject a qa_story that is in pending_review. Approved stories are enabled in the QA schedule immediately.",
|
|
289
|
+
scope: "mcp:write",
|
|
290
|
+
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
291
|
+
useCase: "Approve this auto-generated test."
|
|
292
|
+
}
|
|
293
|
+
];
|
|
220
294
|
|
|
221
295
|
// src/server.ts
|
|
222
296
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -292,8 +366,9 @@ function createMushiServer(config) {
|
|
|
292
366
|
name: "mushi-mushi",
|
|
293
367
|
version
|
|
294
368
|
});
|
|
295
|
-
|
|
296
|
-
|
|
369
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
370
|
+
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
371
|
+
const spec = catalog.find((t) => t.name === name);
|
|
297
372
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
298
373
|
const a = {
|
|
299
374
|
title: spec.title,
|
|
@@ -304,13 +379,13 @@ function createMushiServer(config) {
|
|
|
304
379
|
if (spec.hints.openWorld !== void 0) a.openWorldHint = spec.hints.openWorld;
|
|
305
380
|
return a;
|
|
306
381
|
}
|
|
307
|
-
function descOf(name) {
|
|
308
|
-
const spec =
|
|
382
|
+
function descOf(name, catalog = ALL_TOOL_CATALOG) {
|
|
383
|
+
const spec = catalog.find((t) => t.name === name);
|
|
309
384
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
310
385
|
return spec.description;
|
|
311
386
|
}
|
|
312
|
-
function titleOf(name) {
|
|
313
|
-
const spec =
|
|
387
|
+
function titleOf(name, catalog = ALL_TOOL_CATALOG) {
|
|
388
|
+
const spec = catalog.find((t) => t.name === name);
|
|
314
389
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
315
390
|
return spec.title;
|
|
316
391
|
}
|
|
@@ -1004,10 +1079,181 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
1004
1079
|
}]
|
|
1005
1080
|
})
|
|
1006
1081
|
);
|
|
1082
|
+
server.registerTool(
|
|
1083
|
+
"map_user_stories",
|
|
1084
|
+
{
|
|
1085
|
+
title: titleOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1086
|
+
description: descOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1087
|
+
annotations: annotationsFor("map_user_stories", TDD_TOOL_CATALOG),
|
|
1088
|
+
inputSchema: {
|
|
1089
|
+
projectId: z.string().describe("Project id to map stories for"),
|
|
1090
|
+
baseUrl: z.string().url().describe("Live app URL to crawl"),
|
|
1091
|
+
maxPages: z.number().int().min(1).max(50).optional().describe("Max pages to crawl (default 20)"),
|
|
1092
|
+
provider: z.enum(["firecrawl", "browserbase"]).optional().describe("Crawl provider (default: firecrawl)"),
|
|
1093
|
+
cursorCloudRefine: z.boolean().optional().describe("Dispatch Cursor Cloud agent to refine and open a PR")
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
async ({ projectId: projectId2, baseUrl, maxPages, provider, cursorCloudRefine }) => {
|
|
1097
|
+
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1098
|
+
const data = await apiCall(
|
|
1099
|
+
`/v1/admin/inventory/${projectId2}/map-from-live`,
|
|
1100
|
+
{ method: "POST", body: JSON.stringify({ base_url: baseUrl, max_pages: maxPages, provider, cursor_cloud_refine: cursorCloudRefine }) }
|
|
1101
|
+
);
|
|
1102
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
server.registerTool(
|
|
1106
|
+
"get_map_run_status",
|
|
1107
|
+
{
|
|
1108
|
+
title: titleOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1109
|
+
description: descOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1110
|
+
annotations: annotationsFor("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1111
|
+
inputSchema: {
|
|
1112
|
+
projectId: z.string().describe("Project id")
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
async ({ projectId: projectId2 }) => {
|
|
1116
|
+
const data = await apiCall(`/v1/admin/inventory/${projectId2}/map-runs`);
|
|
1117
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
server.registerTool(
|
|
1121
|
+
"generate_tdd_from_story",
|
|
1122
|
+
{
|
|
1123
|
+
title: titleOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1124
|
+
description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1125
|
+
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1126
|
+
inputSchema: {
|
|
1127
|
+
projectId: z.string().describe("Project id"),
|
|
1128
|
+
storyNodeId: z.string().describe("User story id slug from the accepted inventory"),
|
|
1129
|
+
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Gate mode for the generated test (default: review)"),
|
|
1130
|
+
baseUrl: z.string().url().optional().describe("Override the app base URL"),
|
|
1131
|
+
openPr: z.boolean().optional().describe("Open a draft GitHub PR (default: true)")
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
1135
|
+
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1136
|
+
const data = await apiCall(
|
|
1137
|
+
`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
|
|
1138
|
+
{ method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
|
|
1139
|
+
);
|
|
1140
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
server.registerTool(
|
|
1144
|
+
"improve_qa_story",
|
|
1145
|
+
{
|
|
1146
|
+
title: titleOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1147
|
+
description: descOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1148
|
+
annotations: annotationsFor("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1149
|
+
inputSchema: {
|
|
1150
|
+
projectId: z.string().optional().describe("Project id (omit to run across all projects)")
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
async ({ projectId: projectId2 }) => {
|
|
1154
|
+
const data = await apiCall(
|
|
1155
|
+
"/v1/admin/pdca/improve-qa-stories",
|
|
1156
|
+
{ method: "POST", body: JSON.stringify({ project_id: projectId2 }) }
|
|
1157
|
+
);
|
|
1158
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
server.registerTool(
|
|
1162
|
+
"run_qa_story",
|
|
1163
|
+
{
|
|
1164
|
+
title: titleOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1165
|
+
description: descOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1166
|
+
annotations: annotationsFor("run_qa_story", TDD_TOOL_CATALOG),
|
|
1167
|
+
inputSchema: {
|
|
1168
|
+
projectId: z.string().describe("Project id"),
|
|
1169
|
+
qaStoryId: z.string().describe("qa_story id to run")
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
async ({ projectId: projectId2, qaStoryId }) => {
|
|
1173
|
+
const data = await apiCall(
|
|
1174
|
+
`/v1/admin/projects/${projectId2}/qa-stories/${qaStoryId}/run`,
|
|
1175
|
+
{ method: "POST" }
|
|
1176
|
+
);
|
|
1177
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
server.registerTool(
|
|
1181
|
+
"list_byok_keys",
|
|
1182
|
+
{
|
|
1183
|
+
title: titleOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1184
|
+
description: descOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1185
|
+
annotations: annotationsFor("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
projectId: z.string().describe("Project id")
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
async ({ projectId: projectId2 }) => {
|
|
1191
|
+
const data = await apiCall(`/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`);
|
|
1192
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1193
|
+
}
|
|
1194
|
+
);
|
|
1195
|
+
server.registerTool(
|
|
1196
|
+
"add_byok_key",
|
|
1197
|
+
{
|
|
1198
|
+
title: titleOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1199
|
+
description: descOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1200
|
+
annotations: annotationsFor("add_byok_key", TDD_TOOL_CATALOG),
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
projectId: z.string().describe("Project id"),
|
|
1203
|
+
provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
|
|
1204
|
+
key: z.string().min(10).describe("The API key value to add"),
|
|
1205
|
+
label: z.string().optional().describe("Human-readable label for this key"),
|
|
1206
|
+
priority: z.number().int().min(1).max(999).optional().describe("Priority for ordering (lower = higher priority)")
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
async ({ projectId: projectId2, provider, key, label, priority }) => {
|
|
1210
|
+
const data = await apiCall(
|
|
1211
|
+
"/v1/admin/byok/keys",
|
|
1212
|
+
{ method: "POST", body: JSON.stringify({ project_id: projectId2, provider_slug: provider, key, label, priority }) }
|
|
1213
|
+
);
|
|
1214
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1215
|
+
}
|
|
1216
|
+
);
|
|
1217
|
+
server.registerTool(
|
|
1218
|
+
"list_pending_review_stories",
|
|
1219
|
+
{
|
|
1220
|
+
title: titleOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1221
|
+
description: descOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1222
|
+
annotations: annotationsFor("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1223
|
+
inputSchema: {
|
|
1224
|
+
projectId: z.string().describe("Project id")
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
async ({ projectId: projectId2 }) => {
|
|
1228
|
+
const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/pending-review`);
|
|
1229
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
server.registerTool(
|
|
1233
|
+
"approve_qa_story",
|
|
1234
|
+
{
|
|
1235
|
+
title: titleOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1236
|
+
description: descOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1237
|
+
annotations: annotationsFor("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1238
|
+
inputSchema: {
|
|
1239
|
+
projectId: z.string().describe("Project id"),
|
|
1240
|
+
qaStoryId: z.string().describe("QA story id to approve or reject"),
|
|
1241
|
+
status: z.enum(["approved", "rejected"]).describe("New approval status")
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
async ({ projectId: projectId2, qaStoryId, status }) => {
|
|
1245
|
+
const data = await apiCall(
|
|
1246
|
+
`/v1/admin/inventory/${projectId2}/stories/${qaStoryId}/approval`,
|
|
1247
|
+
{ method: "PATCH", body: JSON.stringify({ status }) }
|
|
1248
|
+
);
|
|
1249
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1007
1252
|
const grantedScopes = config.scopes;
|
|
1008
1253
|
if (grantedScopes !== void 0) {
|
|
1009
1254
|
const toolRegistry = server._registeredTools;
|
|
1010
|
-
|
|
1255
|
+
const allSpecs = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
1256
|
+
for (const spec of allSpecs) {
|
|
1011
1257
|
if (!grantedScopes.includes(spec.scope)) {
|
|
1012
1258
|
toolRegistry[spec.name]?.remove();
|
|
1013
1259
|
}
|