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
This commit is contained in:
Danila Poyarkov 2026-03-14 19:58:05 +03:00 committed by Anton A S
parent f02bd84aec
commit e79fb5ac5d
5 changed files with 35 additions and 4 deletions

View file

@ -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",

View file

@ -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',

View file

@ -27,10 +27,14 @@ export class ACPChatTransport implements ChatTransport<UIMessage> {
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<UIMessage> {
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 = {

View file

@ -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
}

View file

@ -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'),