From e79fb5ac5dbcce7131f97c637257764b162fa297 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 14 Mar 2026 19:58:05 +0300 Subject: [PATCH] Connect ACP agents to OpenPencil MCP tools Pass openpencil-mcp as a stdio MCP server to the ACP session so agents get all design tools (create shapes, render JSX, export images, etc.) instead of just plain conversation. - Add makeFigma option to createServer for external FigmaAPI injection - Skip file lifecycle tools (open/save/new) when using external FigmaAPI - Pass MCP server config in ACP newSession mcpServers - Add bun to Tauri shell scope for MCP server subprocess --- desktop/capabilities/default.json | 3 ++- packages/mcp/src/server.ts | 7 +++++++ src/ai/acp-transport.ts | 12 ++++++++++-- src/composables/use-chat.ts | 14 +++++++++++++- vite.config.ts | 3 +++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/desktop/capabilities/default.json b/desktop/capabilities/default.json index 996d19f91..43f9a654f 100644 --- a/desktop/capabilities/default.json +++ b/desktop/capabilities/default.json @@ -24,7 +24,8 @@ "allow": [ { "name": "claude-code-acp", "cmd": "claude-code-acp", "args": true }, { "name": "codex-acp", "cmd": "codex-acp", "args": true }, - { "name": "gemini", "cmd": "gemini", "args": true } + { "name": "gemini", "cmd": "gemini", "args": true }, + { "name": "bun", "cmd": "bun", "args": true } ] }, "shell:allow-stdin-write", diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 94b1b4229..125aca47a 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -22,6 +22,7 @@ type McpResult = { content: McpContent[]; isError?: boolean } export interface CreateServerOptions { enableEval?: boolean fileRoot?: string | null + makeFigma?: () => FigmaAPI } function ok(data: unknown): McpResult { @@ -61,10 +62,13 @@ export function createServer(version: string, options: CreateServerOptions = {}) ? null : resolve(options.fileRoot) + const externalMakeFigma = options.makeFigma ?? null + let graph: SceneGraph | null = null let currentPageId: string | null = null function makeFigma(): FigmaAPI { + if (externalMakeFigma) return externalMakeFigma() if (!graph) throw new Error('No document loaded. Use open_file or new_document first.') const g = graph const api = new FigmaAPI(g) @@ -112,6 +116,8 @@ export function createServer(version: string, options: CreateServerOptions = {}) } const register = server.registerTool.bind(server) as (...args: unknown[]) => void + + if (!externalMakeFigma) { register( 'open_file', { @@ -170,6 +176,7 @@ export function createServer(version: string, options: CreateServerOptions = {}) } } ) + } // end if (!externalMakeFigma) register( 'export_image_file', diff --git a/src/ai/acp-transport.ts b/src/ai/acp-transport.ts index 06b6548e2..ed20cf238 100644 --- a/src/ai/acp-transport.ts +++ b/src/ai/acp-transport.ts @@ -27,10 +27,14 @@ export class ACPChatTransport implements ChatTransport { private session: ACPSession | null = null private agentDef: ACPAgentDef private cwd: string + private mcpCommand?: string + private mcpArgs: string[] - constructor(options: { agentDef: ACPAgentDef; cwd?: string }) { + constructor(options: { agentDef: ACPAgentDef; cwd?: string; mcpCommand?: string; mcpArgs?: string[] }) { this.agentDef = options.agentDef this.cwd = options.cwd ?? '.' + this.mcpCommand = options.mcpCommand + this.mcpArgs = options.mcpArgs ?? [] } async sendMessages({ @@ -199,9 +203,13 @@ export class ACPChatTransport implements ChatTransport { clientCapabilities: {} }) + const mcpServers = this.mcpCommand + ? [{ name: 'open-pencil', command: this.mcpCommand, args: this.mcpArgs, env: [] }] + : [] + const sessionResult = await connection.newSession({ cwd: this.cwd, - mcpServers: [] + mcpServers }) const session: ACPSession = { diff --git a/src/composables/use-chat.ts b/src/composables/use-chat.ts index 83f366152..104165a5e 100644 --- a/src/composables/use-chat.ts +++ b/src/composables/use-chat.ts @@ -209,8 +209,20 @@ async function createACPTransport() { if (!agentDef) throw new Error(`Unknown ACP agent: ${agentId}`) const { ACPChatTransport } = await import('@/ai/acp-transport') + const { homeDir } = await import('@tauri-apps/api/path') await acpTransportInstance?.destroy() - const transport = new ACPChatTransport({ agentDef }) + + const mcpCommand = import.meta.env.DEV ? 'bun' : 'npx' + const mcpArgs = import.meta.env.DEV + ? [import.meta.env.VITE_PROJECT_ROOT + '/packages/mcp/src/index.ts'] + : ['-y', '@open-pencil/mcp'] + + const transport = new ACPChatTransport({ + agentDef, + cwd: await homeDir(), + mcpCommand, + mcpArgs + }) acpTransportInstance = transport return transport } diff --git a/vite.config.ts b/vite.config.ts index 002397a42..48132f6e3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,6 +15,9 @@ import { automationPlugin } from './src/automation/vite-plugin' const host = process.env.TAURI_DEV_HOST export default defineConfig(async () => ({ + define: { + 'import.meta.env.VITE_PROJECT_ROOT': JSON.stringify(__dirname) + }, resolve: { alias: { '@': resolve(__dirname, 'src'),