@mushi-mushi/mcp 0.22.1 → 0.23.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 CHANGED
@@ -132,7 +132,7 @@ The Mushi backend now exposes the same tool catalog over the **Streamable HTTP**
132
132
 
133
133
  ### Feature groups (`?features=` / `MUSHI_FEATURES`)
134
134
 
135
- Full catalog is **76 tools** (stdio / `MUSHI_FEATURES=all`). The hosted HTTP
135
+ Full catalog is **80 tools** (stdio / `MUSHI_FEATURES=all`). The hosted HTTP
136
136
  endpoint exposes a leaner subset filtered by feature groups (see
137
137
  `mcp-hosted-tool-manifest.json`). New installs now default to the lean
138
138
  `triage,fixes,inventory,setup,docs` set automatically (stdio: when
@@ -399,7 +399,7 @@ Spawns `dist/index.js` with a dummy unreachable endpoint and confirms it adverti
399
399
  pnpm --filter @mushi-mushi/mcp build
400
400
  pnpm --filter @mushi-mushi/mcp test:smoke
401
401
  # OK; prints live tool/resource/prompt counts from the catalog
402
- # (see packages/mcp/src/catalog.ts — currently 76 tools / 8 resources / 4 prompts)
402
+ # (see packages/mcp/src/catalog.ts — currently 80 tools / 8 resources / 4 prompts)
403
403
  ```
404
404
 
405
405
  ### Layer 3; Full localhost E2E (real binary + real backend behaviour)
@@ -493,4 +493,4 @@ MIT
493
493
  <!-- mushi-readme-stats-footer -->
494
494
  ---
495
495
 
496
- <sub>Monorepo scale (July 2026): 59 edge functions · 376 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
496
+ <sub>Monorepo scale (July 2026): 60 edge functions · 383 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
package/dist/catalog.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  TOOL_CATALOG,
9
9
  USE_MUSHI_INTENTS,
10
10
  routeUseMushiIntent
11
- } from "./chunk-RPSPV4FC.js";
11
+ } from "./chunk-EFENR3EA.js";
12
12
  export {
13
13
  ALL_SCOPES,
14
14
  CODEBASE_TOOL_CATALOG,
@@ -40,6 +40,7 @@ var TOOL_FEATURE_MAP = {
40
40
  run_nl_query: "triage",
41
41
  get_report_evidence: "triage",
42
42
  triage_issue: "triage",
43
+ import_sentry_issues: "triage",
43
44
  query_lessons: "triage",
44
45
  list_lessons: "triage",
45
46
  suggest_fix: "triage",
@@ -64,6 +65,9 @@ var TOOL_FEATURE_MAP = {
64
65
  refresh_ci: "fixes",
65
66
  reopen_report: "fixes",
66
67
  reply_to_reporter: "fixes",
68
+ request_reporter_info: "fixes",
69
+ list_reporter_outbox: "fixes",
70
+ release_reporter_update: "fixes",
67
71
  // rewards
68
72
  list_top_contributors: "rewards",
69
73
  award_bonus_points: "rewards",
@@ -213,7 +213,7 @@ var TOOL_CATALOG = [
213
213
  {
214
214
  name: "transition_status",
215
215
  title: "Move report between states",
216
- description: "Move a report to a new workflow state, enforcing the same transition rules as the admin UI. Valid targets: classified, grouped, fixing, fixed, verified, reopened, dismissed. Returns { report } with the updated status. Write; idempotent (setting the current status is a no-op); rejects illegal transitions. Use to dismiss a duplicate or mark fixed; use merge_fix to mark fixed via a merged PR, or reopen_report for the reopened path.",
216
+ description: "Move a report to a new workflow state, enforcing the same transition rules as the admin UI. Valid targets: classified, grouped, fixing, fixed, verified, reopened, dismissed. When dismissing, closedReason (duplicate, not_reproducible, wont_fix, working_as_intended, spam) sets what the reporter is told \u2014 spam closes silently, and duplicate needs the report grouped under another one first; reporterMessage posts your note to the reporter verbatim. Returns { ok }. Write; idempotent (setting the current status is a no-op); rejects illegal transitions. Use to dismiss a duplicate or mark fixed; use merge_fix to mark fixed via a merged PR, or reopen_report for the reopened path.",
217
217
  scope: "mcp:write",
218
218
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
219
219
  useCase: "Dismiss this duplicate / mark it fixed."
@@ -319,6 +319,14 @@ var TOOL_CATALOG = [
319
319
  returnsUntrusted: true,
320
320
  useCase: "Analyze this bug report end-to-end and tell me what to do."
321
321
  },
322
+ {
323
+ name: "import_sentry_issues",
324
+ title: "Import Sentry issues",
325
+ description: "Pull existing Sentry issues into Mushi's report queue: by issueIds (numeric ids or short ids like WEB-12, at most 10) or by a Sentry search query (default is:unresolved) within the project's configured Sentry project, limit 1-10. Each issue goes through the same path as a Sentry webhook delivery: deduped per issue, linked, classified. An issue already linked to a report answers linked and is never reopened. Returns { items: [{ input, issueId, shortId, outcome, reportId, error? }], created, linked, failed, indexing }. Write; idempotent; spends classification budget per new report. Needs the Sentry org slug, project slug and an auth token (event:read, project:read) in Integrations \u2192 Sentry. Use for issues that existed before the webhook was wired; then call triage_issue on a created report.",
326
+ scope: "mcp:write",
327
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
328
+ useCase: "Pull my open Sentry issues into Mushi so I can triage and fix them."
329
+ },
322
330
  {
323
331
  name: "triage_next_steps",
324
332
  title: "What should I work on now?",
@@ -563,6 +571,31 @@ var TDD_TOOL_CATALOG = [
563
571
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
564
572
  useCase: "Reply to a reporter asking for more info or confirming a fix."
565
573
  },
574
+ {
575
+ name: "request_reporter_info",
576
+ title: "Ask the reporter a question",
577
+ description: 'Ask the end-user who filed a report a question: it appears in their in-app "Your reports" thread, marks the report "Waiting on you", and the waiting state clears when they answer (their reply reaches Slack and the console). Returns { comment_id, awaiting_reporter }. Write; messages a real end user and is NOT idempotent \u2014 each call posts another question, so confirm the wording with the user first. Fails with NO_REPORTER for reports that came from an integration. Use when you need reproduction details; use reply_to_reporter for a plain reply.',
578
+ scope: "mcp:write",
579
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
580
+ useCase: "Ask the reporter which page or device this happened on."
581
+ },
582
+ {
583
+ name: "list_reporter_outbox",
584
+ title: "List held reporter updates",
585
+ description: 'List pipeline messages waiting for review before they reach reporters (projects in review mode hold "fix in progress", "fixed", "shipped in vX" and "closed" messages). Each row has { id, report_id, report_title, notification_type, text, body_override, created_at }; text is exactly what the reporter will see. Read-only. Use before release_reporter_update; an empty list means nothing is waiting (or the project sends updates automatically).',
586
+ scope: "mcp:read",
587
+ hints: { readOnly: true, idempotent: true, openWorld: false },
588
+ returnsUntrusted: true,
589
+ useCase: "What reporter updates are waiting for my review?"
590
+ },
591
+ {
592
+ name: "release_reporter_update",
593
+ title: "Release or discard a held reporter update",
594
+ description: "Send (action=release, the default) or drop (action=discard) one held reporter message from list_reporter_outbox; bodyOverride replaces its text before sending. Returns { delivered, skipped, failed }. Write; messages a real end user \u2014 confirm with the user first. Idempotent in effect: a message that is no longer held returns CONFLICT instead of sending twice.",
595
+ scope: "mcp:write",
596
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: false },
597
+ useCase: 'Send the "fixed in v1.4" update to the reporter.'
598
+ },
566
599
  {
567
600
  name: "get_two_way_comms_health",
568
601
  title: "Two-way communication health",
package/dist/clients.d.ts CHANGED
@@ -46,7 +46,7 @@ import { FeatureGroup } from './feature-groups.js';
46
46
  * editor launch. Synced to package.json by `scripts/sync-mcp-pin.mjs` — never
47
47
  * hand-edit the version.
48
48
  */
49
- declare const MCP_PIN_SPEC = "@mushi-mushi/mcp@0.22.1";
49
+ declare const MCP_PIN_SPEC = "@mushi-mushi/mcp@0.23.0";
50
50
  interface StdioConfig {
51
51
  command: string;
52
52
  args: string[];
package/dist/clients.js CHANGED
@@ -5,10 +5,10 @@ import {
5
5
  DEFAULT_FEATURE_GROUPS,
6
6
  appendFeaturesToUrl,
7
7
  featuresQueryString
8
- } from "./chunk-LFSAKI6Z.js";
8
+ } from "./chunk-CXSLU5V5.js";
9
9
 
10
10
  // src/clients.ts
11
- var MCP_PIN_SPEC = "@mushi-mushi/mcp@0.22.1";
11
+ var MCP_PIN_SPEC = "@mushi-mushi/mcp@0.23.0";
12
12
  function projectServerName(projectId, projectName) {
13
13
  const nameSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 22);
14
14
  const idSuffix = projectId.replace(/-/g, "").slice(0, 6);
@@ -9,7 +9,7 @@ import {
9
9
  parseFeaturesCsv,
10
10
  parseFeaturesParam,
11
11
  toolMatchesFeatures
12
- } from "./chunk-LFSAKI6Z.js";
12
+ } from "./chunk-CXSLU5V5.js";
13
13
  export {
14
14
  ALL_FEATURE_GROUPS,
15
15
  DEFAULT_FEATURE_GROUPS,
package/dist/index.js CHANGED
@@ -289,7 +289,7 @@ var TOOL_CATALOG = [
289
289
  {
290
290
  name: "transition_status",
291
291
  title: "Move report between states",
292
- description: "Move a report to a new workflow state, enforcing the same transition rules as the admin UI. Valid targets: classified, grouped, fixing, fixed, verified, reopened, dismissed. Returns { report } with the updated status. Write; idempotent (setting the current status is a no-op); rejects illegal transitions. Use to dismiss a duplicate or mark fixed; use merge_fix to mark fixed via a merged PR, or reopen_report for the reopened path.",
292
+ description: "Move a report to a new workflow state, enforcing the same transition rules as the admin UI. Valid targets: classified, grouped, fixing, fixed, verified, reopened, dismissed. When dismissing, closedReason (duplicate, not_reproducible, wont_fix, working_as_intended, spam) sets what the reporter is told \u2014 spam closes silently, and duplicate needs the report grouped under another one first; reporterMessage posts your note to the reporter verbatim. Returns { ok }. Write; idempotent (setting the current status is a no-op); rejects illegal transitions. Use to dismiss a duplicate or mark fixed; use merge_fix to mark fixed via a merged PR, or reopen_report for the reopened path.",
293
293
  scope: "mcp:write",
294
294
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
295
295
  useCase: "Dismiss this duplicate / mark it fixed."
@@ -395,6 +395,14 @@ var TOOL_CATALOG = [
395
395
  returnsUntrusted: true,
396
396
  useCase: "Analyze this bug report end-to-end and tell me what to do."
397
397
  },
398
+ {
399
+ name: "import_sentry_issues",
400
+ title: "Import Sentry issues",
401
+ description: "Pull existing Sentry issues into Mushi's report queue: by issueIds (numeric ids or short ids like WEB-12, at most 10) or by a Sentry search query (default is:unresolved) within the project's configured Sentry project, limit 1-10. Each issue goes through the same path as a Sentry webhook delivery: deduped per issue, linked, classified. An issue already linked to a report answers linked and is never reopened. Returns { items: [{ input, issueId, shortId, outcome, reportId, error? }], created, linked, failed, indexing }. Write; idempotent; spends classification budget per new report. Needs the Sentry org slug, project slug and an auth token (event:read, project:read) in Integrations \u2192 Sentry. Use for issues that existed before the webhook was wired; then call triage_issue on a created report.",
402
+ scope: "mcp:write",
403
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
404
+ useCase: "Pull my open Sentry issues into Mushi so I can triage and fix them."
405
+ },
398
406
  {
399
407
  name: "triage_next_steps",
400
408
  title: "What should I work on now?",
@@ -555,6 +563,31 @@ var TDD_TOOL_CATALOG = [
555
563
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
556
564
  useCase: "Reply to a reporter asking for more info or confirming a fix."
557
565
  },
566
+ {
567
+ name: "request_reporter_info",
568
+ title: "Ask the reporter a question",
569
+ description: 'Ask the end-user who filed a report a question: it appears in their in-app "Your reports" thread, marks the report "Waiting on you", and the waiting state clears when they answer (their reply reaches Slack and the console). Returns { comment_id, awaiting_reporter }. Write; messages a real end user and is NOT idempotent \u2014 each call posts another question, so confirm the wording with the user first. Fails with NO_REPORTER for reports that came from an integration. Use when you need reproduction details; use reply_to_reporter for a plain reply.',
570
+ scope: "mcp:write",
571
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
572
+ useCase: "Ask the reporter which page or device this happened on."
573
+ },
574
+ {
575
+ name: "list_reporter_outbox",
576
+ title: "List held reporter updates",
577
+ description: 'List pipeline messages waiting for review before they reach reporters (projects in review mode hold "fix in progress", "fixed", "shipped in vX" and "closed" messages). Each row has { id, report_id, report_title, notification_type, text, body_override, created_at }; text is exactly what the reporter will see. Read-only. Use before release_reporter_update; an empty list means nothing is waiting (or the project sends updates automatically).',
578
+ scope: "mcp:read",
579
+ hints: { readOnly: true, idempotent: true, openWorld: false },
580
+ returnsUntrusted: true,
581
+ useCase: "What reporter updates are waiting for my review?"
582
+ },
583
+ {
584
+ name: "release_reporter_update",
585
+ title: "Release or discard a held reporter update",
586
+ description: "Send (action=release, the default) or drop (action=discard) one held reporter message from list_reporter_outbox; bodyOverride replaces its text before sending. Returns { delivered, skipped, failed }. Write; messages a real end user \u2014 confirm with the user first. Idempotent in effect: a message that is no longer held returns CONFLICT instead of sending twice.",
587
+ scope: "mcp:write",
588
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: false },
589
+ useCase: 'Send the "fixed in v1.4" update to the reporter.'
590
+ },
558
591
  {
559
592
  name: "get_two_way_comms_health",
560
593
  title: "Two-way communication health",
@@ -865,6 +898,7 @@ var TOOL_FEATURE_MAP = {
865
898
  run_nl_query: "triage",
866
899
  get_report_evidence: "triage",
867
900
  triage_issue: "triage",
901
+ import_sentry_issues: "triage",
868
902
  query_lessons: "triage",
869
903
  list_lessons: "triage",
870
904
  suggest_fix: "triage",
@@ -889,6 +923,9 @@ var TOOL_FEATURE_MAP = {
889
923
  refresh_ci: "fixes",
890
924
  reopen_report: "fixes",
891
925
  reply_to_reporter: "fixes",
926
+ request_reporter_info: "fixes",
927
+ list_reporter_outbox: "fixes",
928
+ release_reporter_update: "fixes",
892
929
  // rewards
893
930
  list_top_contributors: "rewards",
894
931
  award_bonus_points: "rewards",
@@ -1016,7 +1053,7 @@ var MUSHI_DOCS_INDEX = [
1016
1053
  route: "/",
1017
1054
  markdown: "index.md",
1018
1055
  keywords: ["know", "ai-built", "app", "broke", "fix", "ready"],
1019
- headings: ["See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
1056
+ headings: ["This is what a diagnosis looks like", "See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
1020
1057
  excerpt: "Your AI shipped it. Mushi tells you why it broke: a plain-English diagnosis and a ready fix in your editor. Open source, Sentry optional."
1021
1058
  },
1022
1059
  {
@@ -2027,6 +2064,15 @@ var MUSHI_DOCS_INDEX = [
2027
2064
  headings: ["What changes (high level)", "API mapping (Mushi-specific)", "Migration checklist", "Common gotchas", "Mushi behaviour during the migration", "References"],
2028
2065
  excerpt: "Upgrade a Vue 2 app to Vue 3 and move from the vanilla Mushi web SDK to the @mushi-mushi/vue plugin, with an interactive migration checklist."
2029
2066
  },
2067
+ {
2068
+ title: "Open metrics",
2069
+ url: docUrl("/open"),
2070
+ route: "/open",
2071
+ markdown: "open.md",
2072
+ keywords: ["open", "metrics"],
2073
+ headings: ["What is not on this page, and why", "Where the numbers come from"],
2074
+ excerpt: "Mushi Mushi's public numbers (releases, commits, contributors, npm downloads), fetched from public APIs at build time, with what each one does not mean."
2075
+ },
2030
2076
  {
2031
2077
  title: "Operating (maintainers)",
2032
2078
  url: docUrl("/operating"),
@@ -2591,7 +2637,7 @@ var MUSHI_DOCS_INDEX = [
2591
2637
  route: "/sdks/react-native",
2592
2638
  markdown: "sdks/react-native.md",
2593
2639
  keywords: ["sdks", "react-native", "react", "native", "@mushi-mushi/react-native"],
2594
- headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "Manual breadcrumbs", "Reporter identity contract"],
2640
+ headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "Theming the report sheet", "Manual breadcrumbs"],
2595
2641
  excerpt: "Reference for @mushi-mushi/react-native \u2014 MushiProvider, hooks, shake-to-report, screenshots, identity and the offline queue for React Native and Expo."
2596
2642
  },
2597
2643
  {
@@ -4839,6 +4885,44 @@ function createMushiServer(config) {
4839
4885
  return jsonResult({ report });
4840
4886
  }
4841
4887
  );
4888
+ server.registerTool(
4889
+ "import_sentry_issues",
4890
+ {
4891
+ title: titleOf("import_sentry_issues"),
4892
+ description: descOf("import_sentry_issues"),
4893
+ annotations: annotationsFor("import_sentry_issues"),
4894
+ inputSchema: z.object({
4895
+ issueIds: z.array(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/)).max(10).optional().describe('Sentry issue ids or short ids (e.g. "4501", "WEB-12"), at most 10'),
4896
+ query: z.string().max(200).optional().describe("Sentry search query within the configured Sentry project (default is:unresolved). Not with issueIds."),
4897
+ limit: z.number().int().min(1).max(10).optional().describe("How many issues a query imports (1-10, default 5)"),
4898
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
4899
+ }),
4900
+ outputSchema: z.object({
4901
+ items: z.array(
4902
+ z.looseObject({
4903
+ input: z.string(),
4904
+ issueId: z.string().nullable(),
4905
+ shortId: z.string().nullable(),
4906
+ outcome: z.string().describe("created | linked | deduped | ignored | error"),
4907
+ reportId: z.string().nullable(),
4908
+ error: z.string().optional()
4909
+ })
4910
+ ).describe("One entry per requested or matched issue"),
4911
+ created: z.array(z.string().nullable()).describe("Report ids created by this import"),
4912
+ linked: z.array(z.string().nullable()).describe("Report ids that already existed for these issues"),
4913
+ failed: z.number().describe("Issues that could not be imported"),
4914
+ indexing: z.looseObject({ queued: z.boolean(), paths: z.number() }).describe("Whether stack-frame files were queued for codebase indexing")
4915
+ })
4916
+ },
4917
+ async (args) => {
4918
+ const pid = await resolveProjectId(args.projectId);
4919
+ const data = await apiCall(`/v1/admin/projects/${pid}/sentry/import`, {
4920
+ method: "POST",
4921
+ body: JSON.stringify({ issueIds: args.issueIds, query: args.query, limit: args.limit })
4922
+ });
4923
+ return jsonResult(data);
4924
+ }
4925
+ );
4842
4926
  server.registerTool(
4843
4927
  "transition_status",
4844
4928
  {
@@ -4863,13 +4947,20 @@ function createMushiServer(config) {
4863
4947
  "reopened",
4864
4948
  "dismissed"
4865
4949
  ]).describe("Target status (resolved is stored as fixed)"),
4866
- reason: z.string().optional().describe("Reason for the transition (audit trail)")
4950
+ reason: z.string().optional().describe("Reason for the transition (audit trail)"),
4951
+ closedReason: z.enum(["duplicate", "not_reproducible", "wont_fix", "working_as_intended", "spam"]).optional().describe("When dismissing: what the reporter is told (spam closes silently)"),
4952
+ reporterMessage: z.string().max(1e4).optional().describe("Optional note posted to the reporter verbatim")
4867
4953
  })
4868
4954
  },
4869
4955
  async (args) => {
4870
4956
  const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
4871
4957
  method: "PATCH",
4872
- body: JSON.stringify({ status: args.status, reason: args.reason })
4958
+ body: JSON.stringify({
4959
+ status: args.status,
4960
+ reason: args.reason,
4961
+ closed_reason: args.closedReason,
4962
+ reporter_message: args.reporterMessage
4963
+ })
4873
4964
  });
4874
4965
  return jsonText(data);
4875
4966
  }
@@ -5613,6 +5704,65 @@ Be specific. No generic advice.`
5613
5704
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
5614
5705
  }
5615
5706
  );
5707
+ server.registerTool(
5708
+ "request_reporter_info",
5709
+ {
5710
+ title: titleOf("request_reporter_info", TDD_TOOL_CATALOG),
5711
+ description: descOf("request_reporter_info", TDD_TOOL_CATALOG),
5712
+ annotations: annotationsFor("request_reporter_info", TDD_TOOL_CATALOG),
5713
+ inputSchema: z.object({
5714
+ reportId: z.string().describe("Report id to ask about"),
5715
+ question: z.string().min(1).max(2e3).describe("The question, shown to the reporter verbatim"),
5716
+ authorName: z.string().optional().describe('Display name for the sender (default: "Developer")')
5717
+ })
5718
+ },
5719
+ async ({ reportId, question, authorName }) => {
5720
+ const data = await apiCall(`/v1/admin/reports/${reportId}/request-info`, {
5721
+ method: "POST",
5722
+ body: JSON.stringify({ question, author_name: authorName })
5723
+ });
5724
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
5725
+ }
5726
+ );
5727
+ server.registerTool(
5728
+ "list_reporter_outbox",
5729
+ {
5730
+ title: titleOf("list_reporter_outbox", TDD_TOOL_CATALOG),
5731
+ description: descOf("list_reporter_outbox", TDD_TOOL_CATALOG),
5732
+ annotations: annotationsFor("list_reporter_outbox", TDD_TOOL_CATALOG),
5733
+ inputSchema: z.object({
5734
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
5735
+ })
5736
+ },
5737
+ async ({ projectId: pid }) => {
5738
+ const scoped = pid ?? config.projectId;
5739
+ const data = await apiCall(
5740
+ `/v1/admin/reporter-outbox${scoped ? `?project_id=${encodeURIComponent(scoped)}` : ""}`
5741
+ );
5742
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
5743
+ }
5744
+ );
5745
+ server.registerTool(
5746
+ "release_reporter_update",
5747
+ {
5748
+ title: titleOf("release_reporter_update", TDD_TOOL_CATALOG),
5749
+ description: descOf("release_reporter_update", TDD_TOOL_CATALOG),
5750
+ annotations: annotationsFor("release_reporter_update", TDD_TOOL_CATALOG),
5751
+ inputSchema: z.object({
5752
+ messageId: z.string().describe("Held message id from list_reporter_outbox"),
5753
+ action: z.enum(["release", "discard"]).optional().describe("release (default) sends it; discard drops it"),
5754
+ bodyOverride: z.string().min(1).max(1e3).optional().describe("Replacement text to send instead")
5755
+ })
5756
+ },
5757
+ async ({ messageId, action, bodyOverride }) => {
5758
+ const verb = action === "discard" ? "discard" : "release";
5759
+ const data = await apiCall(`/v1/admin/reporter-outbox/${messageId}/${verb}`, {
5760
+ method: "POST",
5761
+ body: JSON.stringify(verb === "release" && bodyOverride ? { body_override: bodyOverride } : {})
5762
+ });
5763
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
5764
+ }
5765
+ );
5616
5766
  server.registerTool(
5617
5767
  "list_qa_story_runs",
5618
5768
  {
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  TDD_TOOL_CATALOG,
5
5
  TOOL_CATALOG,
6
6
  routeUseMushiIntent
7
- } from "./chunk-RPSPV4FC.js";
7
+ } from "./chunk-EFENR3EA.js";
8
8
  import {
9
9
  MUSHI_SERVER_METADATA
10
10
  } from "./chunk-FJRPC4VR.js";
@@ -12,7 +12,7 @@ import {
12
12
  DEPRECATED_TOOL_ALIASES,
13
13
  TOOL_FEATURE_MAP,
14
14
  toolMatchesFeatures
15
- } from "./chunk-LFSAKI6Z.js";
15
+ } from "./chunk-CXSLU5V5.js";
16
16
 
17
17
  // src/server.ts
18
18
  import { AsyncLocalStorage } from "async_hooks";
@@ -32,7 +32,7 @@ var MUSHI_DOCS_INDEX = [
32
32
  route: "/",
33
33
  markdown: "index.md",
34
34
  keywords: ["know", "ai-built", "app", "broke", "fix", "ready"],
35
- headings: ["See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
35
+ headings: ["This is what a diagnosis looks like", "See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
36
36
  excerpt: "Your AI shipped it. Mushi tells you why it broke: a plain-English diagnosis and a ready fix in your editor. Open source, Sentry optional."
37
37
  },
38
38
  {
@@ -1043,6 +1043,15 @@ var MUSHI_DOCS_INDEX = [
1043
1043
  headings: ["What changes (high level)", "API mapping (Mushi-specific)", "Migration checklist", "Common gotchas", "Mushi behaviour during the migration", "References"],
1044
1044
  excerpt: "Upgrade a Vue 2 app to Vue 3 and move from the vanilla Mushi web SDK to the @mushi-mushi/vue plugin, with an interactive migration checklist."
1045
1045
  },
1046
+ {
1047
+ title: "Open metrics",
1048
+ url: docUrl("/open"),
1049
+ route: "/open",
1050
+ markdown: "open.md",
1051
+ keywords: ["open", "metrics"],
1052
+ headings: ["What is not on this page, and why", "Where the numbers come from"],
1053
+ excerpt: "Mushi Mushi's public numbers (releases, commits, contributors, npm downloads), fetched from public APIs at build time, with what each one does not mean."
1054
+ },
1046
1055
  {
1047
1056
  title: "Operating (maintainers)",
1048
1057
  url: docUrl("/operating"),
@@ -1607,7 +1616,7 @@ var MUSHI_DOCS_INDEX = [
1607
1616
  route: "/sdks/react-native",
1608
1617
  markdown: "sdks/react-native.md",
1609
1618
  keywords: ["sdks", "react-native", "react", "native", "@mushi-mushi/react-native"],
1610
- headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "Manual breadcrumbs", "Reporter identity contract"],
1619
+ headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "Theming the report sheet", "Manual breadcrumbs"],
1611
1620
  excerpt: "Reference for @mushi-mushi/react-native \u2014 MushiProvider, hooks, shake-to-report, screenshots, identity and the offline queue for React Native and Expo."
1612
1621
  },
1613
1622
  {
@@ -3921,6 +3930,44 @@ function createMushiServer(config) {
3921
3930
  return jsonResult({ report });
3922
3931
  }
3923
3932
  );
3933
+ server.registerTool(
3934
+ "import_sentry_issues",
3935
+ {
3936
+ title: titleOf("import_sentry_issues"),
3937
+ description: descOf("import_sentry_issues"),
3938
+ annotations: annotationsFor("import_sentry_issues"),
3939
+ inputSchema: z.object({
3940
+ issueIds: z.array(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/)).max(10).optional().describe('Sentry issue ids or short ids (e.g. "4501", "WEB-12"), at most 10'),
3941
+ query: z.string().max(200).optional().describe("Sentry search query within the configured Sentry project (default is:unresolved). Not with issueIds."),
3942
+ limit: z.number().int().min(1).max(10).optional().describe("How many issues a query imports (1-10, default 5)"),
3943
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
3944
+ }),
3945
+ outputSchema: z.object({
3946
+ items: z.array(
3947
+ z.looseObject({
3948
+ input: z.string(),
3949
+ issueId: z.string().nullable(),
3950
+ shortId: z.string().nullable(),
3951
+ outcome: z.string().describe("created | linked | deduped | ignored | error"),
3952
+ reportId: z.string().nullable(),
3953
+ error: z.string().optional()
3954
+ })
3955
+ ).describe("One entry per requested or matched issue"),
3956
+ created: z.array(z.string().nullable()).describe("Report ids created by this import"),
3957
+ linked: z.array(z.string().nullable()).describe("Report ids that already existed for these issues"),
3958
+ failed: z.number().describe("Issues that could not be imported"),
3959
+ indexing: z.looseObject({ queued: z.boolean(), paths: z.number() }).describe("Whether stack-frame files were queued for codebase indexing")
3960
+ })
3961
+ },
3962
+ async (args) => {
3963
+ const pid = await resolveProjectId(args.projectId);
3964
+ const data = await apiCall(`/v1/admin/projects/${pid}/sentry/import`, {
3965
+ method: "POST",
3966
+ body: JSON.stringify({ issueIds: args.issueIds, query: args.query, limit: args.limit })
3967
+ });
3968
+ return jsonResult(data);
3969
+ }
3970
+ );
3924
3971
  server.registerTool(
3925
3972
  "transition_status",
3926
3973
  {
@@ -3945,13 +3992,20 @@ function createMushiServer(config) {
3945
3992
  "reopened",
3946
3993
  "dismissed"
3947
3994
  ]).describe("Target status (resolved is stored as fixed)"),
3948
- reason: z.string().optional().describe("Reason for the transition (audit trail)")
3995
+ reason: z.string().optional().describe("Reason for the transition (audit trail)"),
3996
+ closedReason: z.enum(["duplicate", "not_reproducible", "wont_fix", "working_as_intended", "spam"]).optional().describe("When dismissing: what the reporter is told (spam closes silently)"),
3997
+ reporterMessage: z.string().max(1e4).optional().describe("Optional note posted to the reporter verbatim")
3949
3998
  })
3950
3999
  },
3951
4000
  async (args) => {
3952
4001
  const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
3953
4002
  method: "PATCH",
3954
- body: JSON.stringify({ status: args.status, reason: args.reason })
4003
+ body: JSON.stringify({
4004
+ status: args.status,
4005
+ reason: args.reason,
4006
+ closed_reason: args.closedReason,
4007
+ reporter_message: args.reporterMessage
4008
+ })
3955
4009
  });
3956
4010
  return jsonText(data);
3957
4011
  }
@@ -4695,6 +4749,65 @@ Be specific. No generic advice.`
4695
4749
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
4696
4750
  }
4697
4751
  );
4752
+ server.registerTool(
4753
+ "request_reporter_info",
4754
+ {
4755
+ title: titleOf("request_reporter_info", TDD_TOOL_CATALOG),
4756
+ description: descOf("request_reporter_info", TDD_TOOL_CATALOG),
4757
+ annotations: annotationsFor("request_reporter_info", TDD_TOOL_CATALOG),
4758
+ inputSchema: z.object({
4759
+ reportId: z.string().describe("Report id to ask about"),
4760
+ question: z.string().min(1).max(2e3).describe("The question, shown to the reporter verbatim"),
4761
+ authorName: z.string().optional().describe('Display name for the sender (default: "Developer")')
4762
+ })
4763
+ },
4764
+ async ({ reportId, question, authorName }) => {
4765
+ const data = await apiCall(`/v1/admin/reports/${reportId}/request-info`, {
4766
+ method: "POST",
4767
+ body: JSON.stringify({ question, author_name: authorName })
4768
+ });
4769
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
4770
+ }
4771
+ );
4772
+ server.registerTool(
4773
+ "list_reporter_outbox",
4774
+ {
4775
+ title: titleOf("list_reporter_outbox", TDD_TOOL_CATALOG),
4776
+ description: descOf("list_reporter_outbox", TDD_TOOL_CATALOG),
4777
+ annotations: annotationsFor("list_reporter_outbox", TDD_TOOL_CATALOG),
4778
+ inputSchema: z.object({
4779
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
4780
+ })
4781
+ },
4782
+ async ({ projectId: pid }) => {
4783
+ const scoped = pid ?? config.projectId;
4784
+ const data = await apiCall(
4785
+ `/v1/admin/reporter-outbox${scoped ? `?project_id=${encodeURIComponent(scoped)}` : ""}`
4786
+ );
4787
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
4788
+ }
4789
+ );
4790
+ server.registerTool(
4791
+ "release_reporter_update",
4792
+ {
4793
+ title: titleOf("release_reporter_update", TDD_TOOL_CATALOG),
4794
+ description: descOf("release_reporter_update", TDD_TOOL_CATALOG),
4795
+ annotations: annotationsFor("release_reporter_update", TDD_TOOL_CATALOG),
4796
+ inputSchema: z.object({
4797
+ messageId: z.string().describe("Held message id from list_reporter_outbox"),
4798
+ action: z.enum(["release", "discard"]).optional().describe("release (default) sends it; discard drops it"),
4799
+ bodyOverride: z.string().min(1).max(1e3).optional().describe("Replacement text to send instead")
4800
+ })
4801
+ },
4802
+ async ({ messageId, action, bodyOverride }) => {
4803
+ const verb = action === "discard" ? "discard" : "release";
4804
+ const data = await apiCall(`/v1/admin/reporter-outbox/${messageId}/${verb}`, {
4805
+ method: "POST",
4806
+ body: JSON.stringify(verb === "release" && bodyOverride ? { body_override: bodyOverride } : {})
4807
+ });
4808
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
4809
+ }
4810
+ );
4698
4811
  server.registerTool(
4699
4812
  "list_qa_story_runs",
4700
4813
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
3
  "mcpName": "io.github.kensaurus/mushi-mushi",
4
- "version": "0.22.1",
4
+ "version": "0.23.0",
5
5
  "license": "MIT",
6
6
  "description": "MCP server for Cursor, Claude Code and Copilot. The bug mediator for AI-built apps: plain-English diagnosis + a ready fix, in your editor.",
7
7
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "@modelcontextprotocol/core": "^2.0.0",
46
46
  "@modelcontextprotocol/server": "^2.0.0",
47
47
  "zod": "^4.4.3",
48
- "@mushi-mushi/core": "^1.29.0"
48
+ "@mushi-mushi/core": "^1.30.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@sentry/node": "^10.57.0"