diff --git a/packages/opencode/src/plugin/figma.ts b/packages/opencode/src/plugin/figma.ts new file mode 100644 index 00000000000..f555798b706 --- /dev/null +++ b/packages/opencode/src/plugin/figma.ts @@ -0,0 +1,16 @@ +import type { Hooks, PluginInput } from "@opencode-ai/plugin" + +const CLIENT_ID = "3zVHNs9kINDDrk8loekLZV" + +export async function FigmaPlugin(_input: PluginInput): Promise { + return { + config: async (config) => { + Object.values(config.mcp ?? {}).forEach((server) => { + if (server.type !== "remote" || server.oauth === false) return + if (!URL.canParse(server.url) || new URL(server.url).hostname !== "mcp.figma.com") return + if (server.oauth?.clientId) return + server.oauth = { ...server.oauth, clientId: CLIENT_ID } + }) + }, + } +} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 0f71b39a9d5..53c8384750c 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -20,6 +20,7 @@ import { AzureAuthPlugin } from "./azure" import { DigitalOceanAuthPlugin } from "./digitalocean" import { XaiAuthPlugin } from "./xai" import { SnowflakeCortexAuthPlugin } from "./snowflake-cortex" +import { FigmaPlugin } from "./figma" import { Effect, Layer, Context } from "effect" import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" @@ -78,6 +79,7 @@ function internalPlugins(flags: RuntimeFlags.Info): PluginInstance[] { DigitalOceanAuthPlugin, SnowflakeCortexAuthPlugin, XaiAuthPlugin, + FigmaPlugin, ] } diff --git a/packages/opencode/test/plugin/figma.test.ts b/packages/opencode/test/plugin/figma.test.ts new file mode 100644 index 00000000000..940e1bb648b --- /dev/null +++ b/packages/opencode/test/plugin/figma.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test } from "bun:test" +import { FigmaPlugin } from "@/plugin/figma" + +describe("plugin.figma", () => { + test("adds the OpenCode client ID to configured Figma servers", async () => { + const config = { + mcp: { + figma: { + type: "remote" as const, + url: "https://mcp.figma.com/mcp", + oauth: { scope: "mcp:connect" } as { scope: string; clientId?: string }, + }, + }, + } + const hooks = await FigmaPlugin({} as never) + + await hooks.config!(config as never) + + expect(config.mcp.figma.oauth).toEqual({ clientId: "3zVHNs9kINDDrk8loekLZV", scope: "mcp:connect" }) + }) + + test("preserves an existing client ID", async () => { + const config = { + mcp: { + figma: { + type: "remote" as const, + url: "https://mcp.figma.com/mcp", + oauth: { clientId: "configured-client-id" }, + }, + }, + } + const hooks = await FigmaPlugin({} as never) + + await hooks.config!(config as never) + + expect(config.mcp.figma.oauth.clientId).toBe("configured-client-id") + }) + + test("does not create or enable Figma servers", async () => { + const config = { + mcp: { + disabled: { + type: "remote" as const, + url: "https://mcp.figma.com/mcp", + oauth: false as const, + }, + other: { + type: "remote" as const, + url: "https://mcp.example.com/mcp", + }, + }, + } + const hooks = await FigmaPlugin({} as never) + + await hooks.config!(config as never) + + expect(config.mcp).toEqual({ + disabled: { + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: false, + }, + other: { + type: "remote", + url: "https://mcp.example.com/mcp", + }, + }) + }) +})