diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index b0f7d59447d..09cf52e1589 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -12,7 +12,12 @@ "description": "Contains opencode logs and data", }, }, - "mcp": {}, + "mcp": { + "figma": { + "type": "remote", + "url": "https://mcp.figma.com/mcp", + }, + }, "tools": { "github-triage": false, "github-pr-search": false, diff --git a/opencode.jsonc b/opencode.jsonc deleted file mode 100644 index be0404c49b3..00000000000 --- a/opencode.jsonc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$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/mcp/index.ts b/packages/core/src/mcp/index.ts index 225919c27f4..cd39797e1cf 100644 --- a/packages/core/src/mcp/index.ts +++ b/packages/core/src/mcp/index.ts @@ -15,6 +15,7 @@ import { Integration } from "../integration" import { IntegrationConnection } from "../integration/connection" import { KeyedMutex } from "../effect/keyed-mutex" import { Location } from "../location" +import { FigmaPlugin } from "../plugin/figma" import { waitForAbort } from "@opencode-ai/util/process" import { State } from "../state" import { MCPClient } from "./client" @@ -194,6 +195,7 @@ export const layer = (options?: Options) => Layer.effect( const urlElicitations = new Map() for (const entry of documents) { for (const [name, server] of Object.entries(entry.info.mcp?.servers ?? {})) { + FigmaPlugin.apply(server) runtime.set(ServerName.make(name), { config: { ...server, timeout: { ...timeout, ...server.timeout } }, status: { status: "pending" }, @@ -622,6 +624,7 @@ export const layer = (options?: Options) => Layer.effect( if (previous.integrationID) owned.delete(previous.integrationID) if (previous.registration) yield* previous.registration.dispose } + FigmaPlugin.apply(config) const entry: ServerEntry = { config: { ...config, timeout: { ...timeout, ...config.timeout } }, status: { status: "pending" }, diff --git a/packages/core/src/plugin/figma.ts b/packages/core/src/plugin/figma.ts index e954430bdb5..94874fdb330 100644 --- a/packages/core/src/plugin/figma.ts +++ b/packages/core/src/plugin/figma.ts @@ -1,28 +1,17 @@ export * as FigmaPlugin from "./figma" -import { define } from "@opencode-ai/plugin/v2/effect/plugin" -import { Effect } from "effect" -import { Config } from "../config" +import { ConfigMCP } from "../config/mcp" 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 } }) - }) - }), -}) +export function apply(server: typeof ConfigMCP.Server.Type) { + if (server.type !== "remote" || server.oauth === false) return server + if (!URL.canParse(server.url) || new URL(server.url).hostname !== "mcp.figma.com") return server + if (server.oauth?.client_id) return server + if (server.oauth) { + Object.assign(server.oauth, { client_id: CLIENT_ID }) + return server + } + Object.assign(server, { oauth: { client_id: CLIENT_ID } }) + return server +} diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index f02f76077b1..5457a13d3da 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -48,7 +48,6 @@ 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" @@ -127,7 +126,6 @@ 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 baafd6200d0..4b5853a6d56 100644 --- a/packages/core/test/config/plugin.test.ts +++ b/packages/core/test/config/plugin.test.ts @@ -7,7 +7,6 @@ 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" @@ -260,81 +259,6 @@ 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* () { diff --git a/packages/core/test/plugin/figma.test.ts b/packages/core/test/plugin/figma.test.ts new file mode 100644 index 00000000000..794a31e988f --- /dev/null +++ b/packages/core/test/plugin/figma.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test" +import { ConfigMCP } from "@opencode-ai/core/config/mcp" +import { FigmaPlugin } from "@opencode-ai/core/plugin/figma" + +describe("plugin.figma", () => { + test("adds the OpenCode client ID to configured Figma servers", () => { + const server = new ConfigMCP.Remote({ + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: new ConfigMCP.OAuth({ scope: "mcp:connect" }), + }) + + FigmaPlugin.apply(server) + + expect(server.oauth).toEqual({ client_id: "3zVHNs9kINDDrk8loekLZV", scope: "mcp:connect" }) + }) + + test("preserves an existing client ID", () => { + const server = new ConfigMCP.Remote({ + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: new ConfigMCP.OAuth({ client_id: "configured-client-id" }), + }) + + FigmaPlugin.apply(server) + + expect(server.oauth).toEqual({ client_id: "configured-client-id" }) + }) + + test("does not enable OAuth or modify non-Figma servers", () => { + const disabled = new ConfigMCP.Remote({ + type: "remote", + url: "https://mcp.figma.com/mcp", + oauth: false, + }) + const other = new ConfigMCP.Remote({ + type: "remote", + url: "https://mcp.example.com/mcp", + }) + + FigmaPlugin.apply(disabled) + FigmaPlugin.apply(other) + + expect(disabled.oauth).toBe(false) + expect(other.oauth).toBeUndefined() + }) +})