@mushi-mushi/mcp 0.4.0 → 0.5.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/CONTRIBUTING.md CHANGED
@@ -91,6 +91,33 @@ pnpm changeset
91
91
 
92
92
  Select the affected packages, the semver bump type, and write a summary. The changeset file gets committed with your PR.
93
93
 
94
+ ## Release flow
95
+
96
+ Releases are fully automated. Maintainers don't run `npm publish` by hand.
97
+
98
+ 1. PRs land on `master` with one or more changeset files in `.changeset/`.
99
+ 2. `release.yml` runs on every push to `master`. It opens (or updates) a `chore: version packages` PR that bumps every affected `package.json`, rolls up the changelogs, and deletes the consumed changesets.
100
+ 3. Merging that "Version Packages" PR re-fires `release.yml`. The publish step authenticates to npm via **OpenID Connect (OIDC) Trusted Publishers** — no long-lived `NPM_TOKEN` is exchanged — and every tarball ships with a **Sigstore provenance attestation** uploaded to the public transparency log.
101
+
102
+ If GitHub's anti-loop protection suppresses the auto re-fire (the squash merge can be attributed to `github-actions[bot]`), trigger the workflow manually: **Actions → release → Run workflow → master**.
103
+
104
+ ### Adding a brand-new publishable package
105
+
106
+ Trusted Publisher bindings are configured **per package** on `npmjs.com` and require the package to already exist on the registry. New packages therefore need a one-time bootstrap before OIDC can take over.
107
+
108
+ 1. Add the package under `packages/<name>/` with a real `version`, `files`, `publishConfig.access: "public"`, `LICENSE`, and the standard fields enforced by `pnpm check:publish-readiness`.
109
+ 2. Build it locally: `pnpm install && pnpm -r build`.
110
+ 3. Mint a short-lived granular access token at `https://www.npmjs.com/settings/<your-user>/tokens/granular-access-tokens/new` — **Bypass 2FA: ON**, **Read and write: All packages**, **Expiration: 7 days**.
111
+ 4. Bootstrap-publish:
112
+ ```bash
113
+ NPM_TOKEN=npm_xxx pnpm bootstrap:new-package
114
+ ```
115
+ The script auto-detects which workspace packages are missing on npm and publishes them via `pnpm publish --no-provenance` (so `workspace:^` specifiers get rewritten to real semver in the tarball).
116
+ 5. The script prints one URL per freshly-published package. Open each, click **GitHub Actions** under "Trusted Publisher", confirm the auto-filled fields (`<owner>` / `<repo>` / `release.yml`), and tap your security key.
117
+ 6. Revoke the bootstrap token at `https://www.npmjs.com/settings/<your-user>/tokens`.
118
+
119
+ From the next changeset bump onward, that package publishes through the normal `release.yml` flow with full OIDC provenance — same as the rest.
120
+
94
121
  ## Code Style
95
122
 
96
123
  - **TypeScript strict mode** — no `any` unless absolutely necessary
package/dist/index.js CHANGED
@@ -176,6 +176,31 @@ var TOOL_CATALOG = [
176
176
  // client prompts the user on every call.
177
177
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
178
178
  useCase: "Dismiss this duplicate / mark it fixed."
179
+ },
180
+ // --- Rewards (P3) -------------------------------------------------------
181
+ {
182
+ name: "list_top_contributors",
183
+ title: "Top contributors leaderboard",
184
+ description: "Return the top N contributors for the organization, ranked by points in a time window (30d | 90d | all). Each row includes display name, tier, total points, report count, and anti-fraud flag. Use this to identify your most engaged power users, write them a thank-you message, or decide who deserves a bonus.",
185
+ scope: "mcp:read",
186
+ hints: { readOnly: true, idempotent: true, openWorld: true },
187
+ useCase: "Who are my top 10 contributors this month?"
188
+ },
189
+ {
190
+ name: "award_bonus_points",
191
+ title: "Award bonus points",
192
+ description: "Award ad-hoc bonus points to a contributor by their external user id (as passed to Mushi.identify()). Points are applied server-side, audit-logged, and trigger tier re-evaluation. Requires mcp:write scope. Use this to thank a contributor who found a critical bug or to run a one-off promotional campaign.",
193
+ scope: "mcp:write",
194
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
195
+ useCase: "Give this contributor 500 bonus points for the critical bug they found."
196
+ },
197
+ {
198
+ name: "set_tier",
199
+ title: "Override contributor tier",
200
+ description: `Override a contributor's tier by tier slug (e.g. "champion"). This is an admin escape hatch for manual promotions \u2014 normal tier transitions happen automatically via point thresholds. The override is logged in end_user_activity with action=tier_override_manual. Requires mcp:write scope.`,
201
+ scope: "mcp:write",
202
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
203
+ useCase: "Manually promote this user to Champion tier as a thank-you."
179
204
  }
180
205
  ];
181
206
 
@@ -690,6 +715,82 @@ function createMushiServer(config) {
690
715
  contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
691
716
  })
692
717
  );
718
+ server.registerTool(
719
+ "list_top_contributors",
720
+ {
721
+ title: titleOf("list_top_contributors"),
722
+ description: descOf("list_top_contributors"),
723
+ inputSchema: {
724
+ limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
725
+ range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
726
+ },
727
+ annotations: annotationsFor("list_top_contributors")
728
+ },
729
+ async ({ limit, range }) => ({
730
+ content: [{
731
+ type: "text",
732
+ text: JSON.stringify(
733
+ await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
734
+ null,
735
+ 2
736
+ )
737
+ }]
738
+ })
739
+ );
740
+ server.registerTool(
741
+ "award_bonus_points",
742
+ {
743
+ title: titleOf("award_bonus_points"),
744
+ description: descOf("award_bonus_points"),
745
+ inputSchema: {
746
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
747
+ points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
748
+ reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
749
+ },
750
+ annotations: annotationsFor("award_bonus_points")
751
+ },
752
+ async ({ external_user_id, points, reason }) => ({
753
+ content: [{
754
+ type: "text",
755
+ text: JSON.stringify(
756
+ await apiCall("/v1/admin/rewards/bonus-points", {
757
+ method: "POST",
758
+ headers: { "Content-Type": "application/json" },
759
+ body: JSON.stringify({ external_user_id, points, reason })
760
+ }),
761
+ null,
762
+ 2
763
+ )
764
+ }]
765
+ })
766
+ );
767
+ server.registerTool(
768
+ "set_tier",
769
+ {
770
+ title: titleOf("set_tier"),
771
+ description: descOf("set_tier"),
772
+ inputSchema: {
773
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
774
+ tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
775
+ reason: z.string().max(200).optional().describe("Optional reason for manual override")
776
+ },
777
+ annotations: annotationsFor("set_tier")
778
+ },
779
+ async ({ external_user_id, tier_slug, reason }) => ({
780
+ content: [{
781
+ type: "text",
782
+ text: JSON.stringify(
783
+ await apiCall("/v1/admin/rewards/set-tier", {
784
+ method: "POST",
785
+ headers: { "Content-Type": "application/json" },
786
+ body: JSON.stringify({ external_user_id, tier_slug, reason })
787
+ }),
788
+ null,
789
+ 2
790
+ )
791
+ }]
792
+ })
793
+ );
693
794
  server.resource(
694
795
  "project_integration_health",
695
796
  "project://integration-health",
@@ -810,6 +911,11 @@ async function main() {
810
911
  "[mushi-mcp] MUSHI_API_ENDPOINT is not set. All tool calls will fail.\nSet MUSHI_API_ENDPOINT to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
811
912
  );
812
913
  }
914
+ if (!PROJECT_ID) {
915
+ console.error(
916
+ "[mushi-mcp] MUSHI_PROJECT_ID is not set.\n\nTools that scope to a project (get_recent_reports, get_report_detail,\nsearch_reports, etc.) will require you to pass projectId explicitly on\nevery call. To set it once and never pass it again:\n\n 1. Open the Mushi admin console \u2192 Projects\n (https://your-admin-url/projects)\n 2. Find your project \u2014 the UUID chip below the name is the value to use.\n 3. Copy it and set:\n MUSHI_PROJECT_ID=<paste-uuid-here>\n\nYou can also visit Admin \u2192 MCP for a pre-filled .env.local snippet."
917
+ );
918
+ }
813
919
  log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT || "(unset)", hasProjectId: !!PROJECT_ID });
814
920
  const server = createMushiServer({
815
921
  version: VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "description": "MCP server exposing Mushi Mushi reports to coding agents",
6
6
  "type": "module",
@@ -24,16 +24,16 @@
24
24
  "SECURITY.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@modelcontextprotocol/sdk": "^1.12.1",
28
- "zod": "^4.3.6",
29
- "@mushi-mushi/core": "^1.0.0"
27
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
+ "zod": "^4.4.2",
29
+ "@mushi-mushi/core": "^1.1.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^22.0.0",
33
- "eslint": "^10.2.0",
34
- "tsup": "^8.4.0",
35
- "typescript": "^6.0.2",
36
- "vitest": "^4.1.4",
32
+ "@types/node": "^22.19.17",
33
+ "eslint": "^10.3.0",
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^6.0.3",
36
+ "vitest": "^4.1.5",
37
37
  "@mushi-mushi/eslint-config": "0.0.0"
38
38
  },
39
39
  "repository": {