diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000000..be0404c49b --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "servers": { + "everything": { + "type": "local", + "command": ["npx", "-y", "@modelcontextprotocol/server-everything"], + "disabled": false, + }, + "context7": { + "type": "local", + "command": ["npx", "-y", "@upstash/context7-mcp"], + "disabled": false, + }, + "posthog": { + "type": "remote", + "url": "https://mcp.posthog.com/mcp", + }, + "figma": { + "type": "remote", + "url": "https://mcp.figma.com/mcp", + }, + }, + }, +} diff --git a/packages/core/src/plugin/figma.ts b/packages/core/src/plugin/figma.ts new file mode 100644 index 0000000000..e954430bdb --- /dev/null +++ b/packages/core/src/plugin/figma.ts @@ -0,0 +1,28 @@ +export * as FigmaPlugin from "./figma" + +import { define } from "@opencode-ai/plugin/v2/effect/plugin" +import { Effect } from "effect" +import { Config } from "../config" + +const CLIENT_ID = "3zVHNs9kINDDrk8loekLZV" + +export const Plugin = define({ + id: "opencode.figma", + effect: Effect.fn(function* () { + const config = yield* Config.Service + const entries = yield* config.entries() + entries + .filter((entry): entry is Config.Document => entry.type === "document") + .flatMap((entry) => Object.values(entry.info.mcp?.servers ?? {})) + .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?.client_id) return + if (server.oauth) { + Object.assign(server.oauth, { client_id: CLIENT_ID }) + return + } + Object.assign(server, { oauth: { client_id: CLIENT_ID } }) + }) + }), +}) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 5457a13d3d..f02f76077b 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -48,6 +48,7 @@ import { WellKnown } from "../wellknown" import { WriteTool } from "../tool/write" import { AgentPlugin } from "./agent" import { CommandPlugin } from "./command" +import { FigmaPlugin } from "./figma" import { ModelsDevPlugin } from "./models-dev" import { ProviderPlugins } from "./provider" import { PluginRuntime } from "./runtime" @@ -126,6 +127,7 @@ export type InternalPlugin = Plugin const pre = [ WellKnownPlugin.Plugin, + FigmaPlugin.Plugin, AgentPlugin.Plugin, CommandPlugin.Plugin, SkillPlugin.Plugin, diff --git a/packages/core/test/config/plugin.test.ts b/packages/core/test/config/plugin.test.ts index 2a9dfa81bf..baafd6200d 100644 --- a/packages/core/test/config/plugin.test.ts +++ b/packages/core/test/config/plugin.test.ts @@ -7,6 +7,7 @@ import { Config as ConfigSchema } from "@opencode-ai/schema/config" import { Plugin } from "@opencode-ai/schema/plugin" import { AgentV2 } from "@opencode-ai/core/agent" import { Catalog } from "@opencode-ai/core/catalog" +import { Config } from "@opencode-ai/core/config" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" @@ -258,6 +259,82 @@ describe("PluginSupervisor config", () => { }), ), ) + + it.live("adds the OpenCode client ID to configured Figma servers", () => + withLocation( + { + mcp: { + servers: { + figma: { + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: { scope: "mcp:connect" }, + }, + }, + }, + }, + Effect.gen(function* () { + yield* ready() + const config = yield* Config.Service + expect(Config.latest(yield* config.entries(), "mcp")?.servers?.figma).toMatchObject({ + oauth: { client_id: "3zVHNs9kINDDrk8loekLZV", scope: "mcp:connect" }, + }) + }), + ), + ) + + it.live("preserves an existing Figma client ID", () => + withLocation( + { + mcp: { + servers: { + figma: { + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: { client_id: "configured-client-id" }, + }, + }, + }, + }, + Effect.gen(function* () { + yield* ready() + const config = yield* Config.Service + expect(Config.latest(yield* config.entries(), "mcp")?.servers?.figma).toMatchObject({ + oauth: { client_id: "configured-client-id" }, + }) + }), + ), + ) + + it.live("does not enable OAuth or modify non-Figma servers", () => + withLocation( + { + mcp: { + servers: { + disabled: { + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: false, + }, + other: { + type: "remote", + url: "https://mcp.example.com/mcp", + }, + }, + }, + }, + Effect.gen(function* () { + yield* ready() + const config = yield* Config.Service + const servers = Config.latest(yield* config.entries(), "mcp")?.servers + expect(servers).toMatchObject({ + disabled: { oauth: false }, + other: { type: "remote", url: "https://mcp.example.com/mcp" }, + }) + expect(servers?.other).not.toHaveProperty("oauth") + }), + ), + ) }) const ready = Effect.fnUntraced(function* () {