From 9982355b39f46992a06df6acd27b965e3e545d75 Mon Sep 17 00:00:00 2001 From: Maksim Kachurin Date: Thu, 27 Aug 2026 17:38:15 -0300 Subject: [PATCH 1/2] feat: ship OAuth MCPs, drop Cartesia --- README.md | 3 +-- marketplace.json | 20 +++++-------------- .../cartesia/.reforma-plugin/plugin.json | 9 --------- marketplace/media/cartesia/mcp.json | 7 ------- 4 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 marketplace/media/cartesia/.reforma-plugin/plugin.json delete mode 100644 marketplace/media/cartesia/mcp.json diff --git a/README.md b/README.md index 3afbaea..72d607c 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ Marketplace plugins for Reforma. Each plugin is a folder with `.reforma-plugin/p | heygen | [HeyGen](marketplace/media/heygen) | HeyGen | Media | Generate avatar videos with HeyGen. | | fal | [fal](marketplace/media/fal) | fal | Media | Generate images, video, and audio from fal models. Needs an API key. | | elevenlabs | [ElevenLabs](marketplace/media/elevenlabs) | ElevenLabs | Media | Generate speech and manage voices in ElevenLabs. | -| cartesia | [Cartesia](marketplace/media/cartesia) | Cartesia | Media | Generate speech with Cartesia voices. | | sentry | [Sentry](https://github.com/getsentry/plugin-cursor) | Sentry | Analytics | Errors and traces from the Sentry org. | | gsap | [GSAP](https://github.com/greensock/gsap-skills) | GreenSock | UI | Animation timelines, ScrollTrigger, and GSAP plugins. | | motion | [Motion](https://github.com/motiondivision/cursor-plugin) | Motion | UI | Web animation, springs, and Motion docs. | @@ -78,7 +77,7 @@ Author is `plugin.json` `author.name` when present, else Reforma. Shelf is the p ## Repository structure -Root `marketplace.json` lists plugins under `categories[].plugins`. `source` is a path in this repo or a GitHub `tree/…` URL. `"disabled": true` skips pack (folder stays); delete that line to ship after the vendor allowlists us. +Root `marketplace.json` lists plugins under `categories[].plugins`. `source` is a path in this repo or a GitHub `tree/…` URL. `"disabled": true` skips pack (folder stays) when the hosted MCP only accepts catalog clients (Figma waitlist). OAuth apps you register yourself (Drive, Dropbox, Slack, GitHub) stay enabled — credentials go in IN → OAuth clients, not here. Local plugins live under `marketplace///`. Origin (first-party vs vendored) is `plugin.json` `author`, not the folder tree. Remote pins stay as GitHub URLs. Array order is section order; empty shelves omit `plugins`. diff --git a/marketplace.json b/marketplace.json index 6d34079..124d089 100644 --- a/marketplace.json +++ b/marketplace.json @@ -142,8 +142,7 @@ "plugins": [ { "name": "clerk", - "source": "./marketplace/auth/clerk", - "disabled": true + "source": "./marketplace/auth/clerk" }, { "name": "auth0", @@ -203,13 +202,11 @@ "plugins": [ { "name": "google-drive", - "source": "./marketplace/files/google-drive", - "disabled": true + "source": "./marketplace/files/google-drive" }, { "name": "dropbox", - "source": "https://github.com/dropbox/dropbox-ai-plugins/tree/4900430d6afa76fc2504e00e006895de424f3637/codex", - "disabled": true + "source": "https://github.com/dropbox/dropbox-ai-plugins/tree/4900430d6afa76fc2504e00e006895de424f3637/codex" }, { "name": "cloudinary", @@ -281,11 +278,6 @@ { "name": "elevenlabs", "source": "./marketplace/media/elevenlabs" - }, - { - "name": "cartesia", - "source": "./marketplace/media/cartesia", - "disabled": true } ] }, @@ -335,13 +327,11 @@ }, { "name": "slack", - "source": "./marketplace/workspace/slack", - "disabled": true + "source": "./marketplace/workspace/slack" }, { "name": "github-mcp", - "source": "./marketplace/workspace/github-mcp", - "disabled": true + "source": "./marketplace/workspace/github-mcp" } ] }, diff --git a/marketplace/media/cartesia/.reforma-plugin/plugin.json b/marketplace/media/cartesia/.reforma-plugin/plugin.json deleted file mode 100644 index b2dc360..0000000 --- a/marketplace/media/cartesia/.reforma-plugin/plugin.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "cartesia", - "description": "Generate speech with Cartesia voices.", - "author": { - "name": "Cartesia", - "url": "https://cartesia.ai" - }, - "mcpServers": "./mcp.json" -} diff --git a/marketplace/media/cartesia/mcp.json b/marketplace/media/cartesia/mcp.json deleted file mode 100644 index 3482136..0000000 --- a/marketplace/media/cartesia/mcp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mcpServers": { - "cartesia": { - "url": "https://mcp.cartesia.ai/mcp" - } - } -} From 4f473fdb51135e35f1f24b80cb7e570c48243691 Mon Sep 17 00:00:00 2001 From: Maksim Kachurin Date: Thu, 27 Aug 2026 17:46:02 -0300 Subject: [PATCH 2/2] chore: Fix pack --- scripts/pack/content.test.ts | 26 ++++++++++++++++++++++++++ scripts/pack/content.ts | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/scripts/pack/content.test.ts b/scripts/pack/content.test.ts index 0285e31..a8c33d9 100644 --- a/scripts/pack/content.test.ts +++ b/scripts/pack/content.test.ts @@ -99,6 +99,32 @@ describe("normalizePackedMcpServerId", () => { }); }); + it("lowercases every server key when a plugin ships several", () => { + const dir = mkdtempSync(join(tmpdir(), "motion-")); + const pluginDir = join(dir, "motion"); + + dirs.push(dir); + mkdirSync(pluginDir, { recursive: true }); + writeFileSync( + join(pluginDir, "mcp.json"), + `${JSON.stringify({ + mcpServers: { + Motion: { url: "https://mcp.motion.dev" }, + "Motion+": { url: "https://mcp.motion.dev/plus" }, + }, + })}\n`, + ); + + normalizePackedMcpServerId(pluginDir); + + expect(JSON.parse(readFileSync(join(pluginDir, "mcp.json"), "utf8"))).toEqual({ + mcpServers: { + motion: { url: "https://mcp.motion.dev" }, + "motion+": { url: "https://mcp.motion.dev/plus" }, + }, + }); + }); + it("wraps a Cursor top-level map and renames the lone server key", () => { const dir = mkdtempSync(join(tmpdir(), "notion-")); const pluginDir = join(dir, "notion-workspace"); diff --git a/scripts/pack/content.ts b/scripts/pack/content.ts index 83e3dd1..931fdb7 100644 --- a/scripts/pack/content.ts +++ b/scripts/pack/content.ts @@ -145,6 +145,11 @@ function isMcpServerEntry(value: unknown): value is Record { return url.length > 0 !== command.length > 0; } +/** Same rule as reforma `normalizeMcpServerId` — trim, lowercase, spaces → `_`. */ +function normalizeMcpServerId(raw: string): string { + return raw.trim().toLowerCase().replace(/\s+/g, "_"); +} + function renameLoneKey( servers: Record, pluginName: string, @@ -164,6 +169,35 @@ function renameLoneKey( return { next: { [pluginName]: servers[from] }, from }; } +function normalizeServerKeys( + servers: Record, + pluginName: string, +): Record { + const next: Record = {}; + + for (const [id, entry] of Object.entries(servers)) { + const key = normalizeMcpServerId(id); + + if (!key) { + throw new Error(`${pluginName}: mcp server id is empty`); + } + + if (key in next) { + throw new Error( + `${pluginName}: mcp server id ${key} collides after normalize`, + ); + } + + if (key !== id) { + console.log(` mcp ${id} → ${key}`); + } + + next[key] = entry; + } + + return next; +} + /** One MCP server → marketplace plugin name. Vendor keys like `chatgpt_app_mcp` go away. */ export function renameLoneMcpServerKey( raw: unknown, @@ -239,6 +273,8 @@ export function normalizePackedMcpServerId(pluginDir: string): void { console.log(` mcp ${renamed.from} → ${pluginName}`); } + servers = normalizeServerKeys(servers, pluginName); + const next = { mcpServers: servers }; const current = isRecord(raw) && isRecord(raw.mcpServers) ? raw : undefined;