@mushi-mushi/mcp 0.11.0 → 0.13.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/dist/index.js +225 -6
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -183,14 +183,35 @@ var TOOL_CATALOG = [
183
183
  {
184
184
  name: "transition_status",
185
185
  title: "Move report between states",
186
- description: "Move a report between workflow states (new \u2192 classified \u2192 grouped \u2192 fixing \u2192 fixed \u2192 dismissed). Enforces the same transition rules as the admin UI.",
186
+ description: "Move a report between workflow states (new \u2192 classified \u2192 grouped \u2192 fixing \u2192 fixed \u2192 verified \u2192 reopened \u2192 dismissed). Enforces the same transition rules as the admin UI.",
187
187
  scope: "mcp:write",
188
- // Transitioning to `dismissed` is destructive by intent — it removes the
189
- // report from triage queues. Flag the whole tool as destructive so the
190
- // client prompts the user on every call.
191
188
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
192
189
  useCase: "Dismiss this duplicate / mark it fixed."
193
190
  },
191
+ {
192
+ name: "merge_fix",
193
+ title: "Merge fix PR",
194
+ description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
195
+ scope: "mcp:write",
196
+ hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
197
+ useCase: "Merge the draft PR and notify the reporter."
198
+ },
199
+ {
200
+ name: "refresh_ci",
201
+ title: "Refresh fix CI status",
202
+ description: "Pull the latest GitHub check-run status for a fix attempt.",
203
+ scope: "mcp:read",
204
+ hints: { readOnly: true, idempotent: true, openWorld: true },
205
+ useCase: "Check whether CI is green before merging."
206
+ },
207
+ {
208
+ name: "reopen_report",
209
+ title: "Reopen report (operator)",
210
+ description: "Move a report to reopened for regression triage.",
211
+ scope: "mcp:write",
212
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
213
+ useCase: "Reopen a regression the reporter flagged as not fixed."
214
+ },
194
215
  // --- Rewards (P3) -------------------------------------------------------
195
216
  {
196
217
  name: "list_top_contributors",
@@ -330,6 +351,47 @@ var TDD_TOOL_CATALOG = [
330
351
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
331
352
  useCase: 'Did "Add to Slack" work? Send a test ping to confirm.'
332
353
  },
354
+ // ── Skill Pipeline tools ─────────────────────────────────────────────────────
355
+ {
356
+ name: "list_skills",
357
+ title: "List agent skills",
358
+ description: "List the agent skills available in the catalog, optionally filtered by category. Returns slug, title, description, category, and chain_slugs for each skill. Use before start_skill_pipeline to find the right skill slug for a given type of work.",
359
+ scope: "mcp:read",
360
+ hints: { readOnly: true, idempotent: true, openWorld: true },
361
+ useCase: "What skills are available for debugging production errors?"
362
+ },
363
+ {
364
+ name: "get_skill",
365
+ title: "Get skill detail",
366
+ description: "Fetch the full detail for a single agent skill by slug, including the complete SKILL.md body and resolved chain. Use before executing a step to understand what the skill expects you to do.",
367
+ scope: "mcp:read",
368
+ hints: { readOnly: true, idempotent: true, openWorld: true },
369
+ useCase: "What does workflow-fix-and-ship actually instruct me to do?"
370
+ },
371
+ {
372
+ name: "start_skill_pipeline",
373
+ title: "Start a skill pipeline",
374
+ description: "Start a new skill pipeline run for a report. Pass root_skill_slug and optionally report_id. Returns run_id, context_packet (full instructions + report context), and step list. Read the context_packet \u2014 it contains skill instructions plus full report context (repro steps, root cause, RAG files). After executing each step, call checkin_pipeline_step. The PM watching the console sees progress live.",
375
+ scope: "mcp:write",
376
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
377
+ useCase: "Start the workflow-fix-and-ship pipeline for this bug report."
378
+ },
379
+ {
380
+ name: "get_pipeline_run",
381
+ title: "Get pipeline run detail",
382
+ description: "Fetch the full detail for a skill pipeline run: status, context_packet, and all step statuses. Call to retrieve the context_packet after a pipeline was started from the console or another agent.",
383
+ scope: "mcp:read",
384
+ hints: { readOnly: true, idempotent: true, openWorld: true },
385
+ useCase: "Give me the context packet and step status for this pipeline run."
386
+ },
387
+ {
388
+ name: "checkin_pipeline_step",
389
+ title: "Check in a pipeline step",
390
+ description: "Report the completion status of a pipeline step (passed, failed, running, or skipped). Optionally include notes, a PR URL, or the Cursor agentId. Updates the live React Flow canvas in the Mushi console so PMs see real-time progress.",
391
+ scope: "mcp:write",
392
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
393
+ useCase: "I just opened the PR \u2014 mark step 2 as passed and link the PR."
394
+ },
333
395
  // ── Full-Stack Audit tools (Phase 5) ────────────────────────────────────────
334
396
  {
335
397
  name: "run_fullstack_audit",
@@ -795,12 +857,15 @@ function createMushiServer(config) {
795
857
  prUrl: z.string().optional().describe("GitHub PR URL"),
796
858
  filesChanged: z.array(z.string()).describe("Files modified"),
797
859
  linesChanged: z.number().describe("Total lines changed"),
798
- summary: z.string().describe("Fix summary")
860
+ summary: z.string().describe("Fix summary"),
861
+ idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
799
862
  }
800
863
  },
801
864
  async (args) => {
865
+ const idemKey = args.idempotencyKey ?? crypto.randomUUID();
802
866
  const created = await apiCall("/v1/admin/fixes", {
803
867
  method: "POST",
868
+ headers: { "Idempotency-Key": idemKey },
804
869
  body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
805
870
  });
806
871
  await apiCall(`/v1/admin/fixes/${created.fixId}`, {
@@ -906,6 +971,59 @@ function createMushiServer(config) {
906
971
  return jsonText(data);
907
972
  }
908
973
  );
974
+ server.registerTool(
975
+ "merge_fix",
976
+ {
977
+ title: "Merge fix PR",
978
+ description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
979
+ annotations: annotationsFor("merge_fix"),
980
+ inputSchema: {
981
+ fixId: z.string().describe("Fix attempt UUID"),
982
+ mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method")
983
+ }
984
+ },
985
+ async (args) => {
986
+ const data = await apiCall(`/v1/admin/fixes/${args.fixId}/merge`, {
987
+ method: "POST",
988
+ body: JSON.stringify({ mergeMethod: args.mergeMethod ?? "squash" })
989
+ });
990
+ return jsonText(data);
991
+ }
992
+ );
993
+ server.registerTool(
994
+ "refresh_ci",
995
+ {
996
+ title: "Refresh fix CI status",
997
+ description: "Pull the latest GitHub check-run status for a fix attempt.",
998
+ annotations: annotationsFor("refresh_ci"),
999
+ inputSchema: {
1000
+ fixId: z.string().describe("Fix attempt UUID")
1001
+ }
1002
+ },
1003
+ async (args) => {
1004
+ const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
1005
+ return jsonText(data);
1006
+ }
1007
+ );
1008
+ server.registerTool(
1009
+ "reopen_report",
1010
+ {
1011
+ title: "Reopen report (operator)",
1012
+ description: "Operator alias to move a report back to new for regression triage.",
1013
+ annotations: annotationsFor("reopen_report"),
1014
+ inputSchema: {
1015
+ reportId: z.string().describe("Report UUID"),
1016
+ note: z.string().optional().describe("Triage note")
1017
+ }
1018
+ },
1019
+ async (args) => {
1020
+ const data = await apiCall(`/v1/sync/reports/${args.reportId}`, {
1021
+ method: "PATCH",
1022
+ body: JSON.stringify({ status: "reopened", note: args.note })
1023
+ });
1024
+ return jsonText(data);
1025
+ }
1026
+ );
909
1027
  server.registerTool(
910
1028
  "transition_status",
911
1029
  {
@@ -914,7 +1032,7 @@ function createMushiServer(config) {
914
1032
  annotations: annotationsFor("transition_status"),
915
1033
  inputSchema: {
916
1034
  reportId: z.string().describe("Report UUID"),
917
- status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "dismissed"]).describe("Target status"),
1035
+ status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "resolved", "verified", "reopened", "dismissed"]).describe("Target status"),
918
1036
  reason: z.string().optional().describe("Reason for the transition (audit trail)")
919
1037
  }
920
1038
  },
@@ -1468,6 +1586,107 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1468
1586
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1469
1587
  }
1470
1588
  );
1589
+ server.registerTool(
1590
+ "list_skills",
1591
+ {
1592
+ title: titleOf("list_skills"),
1593
+ description: descOf("list_skills"),
1594
+ annotations: annotationsFor("list_skills"),
1595
+ inputSchema: {
1596
+ category: z.string().optional().describe("Filter by category: workflow, debug, test, audit, enhance, \u2026"),
1597
+ search: z.string().optional().describe("Free-text search across slug, title, description"),
1598
+ page: z.number().optional().describe("Page number (default 1)"),
1599
+ limit: z.number().optional().describe("Max results per page (default 200, max 200)")
1600
+ }
1601
+ },
1602
+ async (args) => {
1603
+ const qs = new URLSearchParams();
1604
+ if (args.category) qs.set("category", args.category);
1605
+ if (args.search) qs.set("q", args.search);
1606
+ if (args.page) qs.set("page", String(args.page));
1607
+ qs.set("limit", String(Math.min(args.limit ?? 200, 200)));
1608
+ const data = await apiCall(`/v1/admin/skills?${qs}`);
1609
+ const skills = Array.isArray(data) ? data : [];
1610
+ return jsonText({ skills, count: skills.length });
1611
+ }
1612
+ );
1613
+ server.registerTool(
1614
+ "get_skill",
1615
+ {
1616
+ title: titleOf("get_skill"),
1617
+ description: descOf("get_skill"),
1618
+ annotations: annotationsFor("get_skill"),
1619
+ inputSchema: {
1620
+ slug: z.string().describe('Skill slug, e.g. "workflow-fix-and-ship"')
1621
+ }
1622
+ },
1623
+ async (args) => {
1624
+ const data = await apiCall(`/v1/admin/skills/${args.slug}`);
1625
+ return jsonText(data);
1626
+ }
1627
+ );
1628
+ server.registerTool(
1629
+ "start_skill_pipeline",
1630
+ {
1631
+ title: titleOf("start_skill_pipeline"),
1632
+ description: descOf("start_skill_pipeline"),
1633
+ annotations: annotationsFor("start_skill_pipeline"),
1634
+ inputSchema: {
1635
+ root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
1636
+ report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
1637
+ mode: z.enum(["handoff", "cloud"]).optional().describe("handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."),
1638
+ project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
1639
+ }
1640
+ },
1641
+ async (args) => {
1642
+ const resolvedProjectId = args.project_id ?? projectId;
1643
+ if (!resolvedProjectId) return jsonText({ error: "No project_id provided or configured." });
1644
+ const data = await apiCall(
1645
+ "/v1/admin/skills/pipelines",
1646
+ { method: "POST", body: JSON.stringify({ ...args, project_id: resolvedProjectId }) }
1647
+ );
1648
+ return jsonText(data);
1649
+ }
1650
+ );
1651
+ server.registerTool(
1652
+ "get_pipeline_run",
1653
+ {
1654
+ title: titleOf("get_pipeline_run"),
1655
+ description: descOf("get_pipeline_run"),
1656
+ annotations: annotationsFor("get_pipeline_run"),
1657
+ inputSchema: {
1658
+ run_id: z.string().describe("Pipeline run UUID")
1659
+ }
1660
+ },
1661
+ async (args) => {
1662
+ const data = await apiCall(`/v1/admin/skills/pipelines/${args.run_id}`);
1663
+ return jsonText(data);
1664
+ }
1665
+ );
1666
+ server.registerTool(
1667
+ "checkin_pipeline_step",
1668
+ {
1669
+ title: titleOf("checkin_pipeline_step"),
1670
+ description: descOf("checkin_pipeline_step"),
1671
+ annotations: annotationsFor("checkin_pipeline_step"),
1672
+ inputSchema: {
1673
+ run_id: z.string().describe("Pipeline run UUID"),
1674
+ step_index: z.number().describe("Step index (0-based)"),
1675
+ status: z.enum(["running", "passed", "failed", "skipped"]).describe("Step status"),
1676
+ notes: z.string().optional().describe("Optional notes or output summary"),
1677
+ pr_url: z.string().optional().describe("PR URL opened during this step"),
1678
+ agent_ref: z.string().optional().describe("Cursor agentId or external agent reference")
1679
+ }
1680
+ },
1681
+ async (args) => {
1682
+ const { run_id, step_index, ...body } = args;
1683
+ await apiCall(
1684
+ `/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`,
1685
+ { method: "POST", body: JSON.stringify(body) }
1686
+ );
1687
+ return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
1688
+ }
1689
+ );
1471
1690
  const grantedScopes = config.scopes;
1472
1691
  if (grantedScopes !== void 0) {
1473
1692
  const toolRegistry = server._registeredTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "description": "MCP server exposing Mushi Mushi reports to coding agents",
6
6
  "type": "module",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "@mushi-mushi/core": "^1.9.0",
28
+ "@mushi-mushi/core": "^1.11.0",
29
29
  "zod": "^4.4.2"
30
30
  },
31
31
  "devDependencies": {