{"version":3,"file":"stdio.mjs","names":[],"sources":["../src/stdio-bridge.ts","../src/stdio.ts"],"sourcesContent":["import { WebSocket } from 'ws'\n\nimport type { PendingRequest } from '#mcp/rpc-types'\n\ntype StdioRpcBridgeOptions = {\n  wsUrl: string\n  reconnectDelayMs?: number\n  onOpen?: () => void\n  onMalformedMessage?: () => void\n}\n\nconst RPC_TIMEOUT = 30_000\nconst DISCONNECTED_MESSAGE =\n  'OpenPencil app is not connected. ' +\n  'STOP and tell the user: \"The OpenPencil desktop app is not running or no document is open. ' +\n  'Please start the app and open a document, then try again.\" ' +\n  'Do NOT attempt to start the app yourself or retry automatically.'\n\nfunction wsMessageToString(raw: WebSocket.RawData): string {\n  if (Buffer.isBuffer(raw)) return raw.toString('utf8')\n  if (Array.isArray(raw)) return Buffer.concat(raw).toString('utf8')\n  return Buffer.from(raw).toString('utf8')\n}\n\nexport function createStdioRpcBridge({\n  wsUrl,\n  reconnectDelayMs = 2000,\n  onOpen,\n  onMalformedMessage\n}: StdioRpcBridgeOptions) {\n  const pending = new Map<string, PendingRequest>()\n  let ws: WebSocket | null = null\n  let registered = false\n\n  function rejectAllPending(reason: string) {\n    for (const [id, req] of pending) {\n      clearTimeout(req.timer)\n      req.reject(new Error(reason))\n      pending.delete(id)\n    }\n  }\n\n  function connect() {\n    ws = new WebSocket(wsUrl)\n\n    ws.on('open', () => {\n      onOpen?.()\n    })\n\n    ws.on('message', (raw) => {\n      try {\n        const msg = JSON.parse(wsMessageToString(raw)) as {\n          type: string\n          id?: string\n          token?: string\n          result?: unknown\n          error?: string\n          ok?: boolean\n        }\n        if (msg.type === 'register' && msg.token) {\n          registered = true\n          return\n        }\n        if (msg.type === 'response' && msg.id) {\n          const req = pending.get(msg.id)\n          if (!req) return\n          pending.delete(msg.id)\n          clearTimeout(req.timer)\n          if (msg.ok === false) req.reject(new Error(msg.error ?? 'RPC failed'))\n          else {\n            const { type: _, id: __, ...payload } = msg\n            req.resolve(payload)\n          }\n        }\n      } catch {\n        onMalformedMessage?.()\n      }\n    })\n\n    ws.on('close', () => {\n      registered = false\n      rejectAllPending('WebSocket closed')\n      setTimeout(connect, reconnectDelayMs)\n    })\n\n    ws.on('error', () => {\n      ws?.close()\n    })\n  }\n\n  function sendRpc(body: Record<string, unknown>): Promise<unknown> {\n    return new Promise((resolve, reject) => {\n      if (!ws || ws.readyState !== WebSocket.OPEN || !registered) {\n        reject(new Error(DISCONNECTED_MESSAGE))\n        return\n      }\n      const id = crypto.randomUUID()\n      const timer = setTimeout(() => {\n        pending.delete(id)\n        reject(new Error('RPC timeout (30s)'))\n      }, RPC_TIMEOUT)\n      pending.set(id, { resolve, reject, timer })\n      ws.send(JSON.stringify({ type: 'request', id, ...body }))\n    })\n  }\n\n  return { connect, sendRpc }\n}\n","#!/usr/bin/env node\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\n\nimport { MCP_VERSION, registerTools } from './server.js'\nimport { createStdioRpcBridge } from './stdio-bridge.js'\n\nif (process.argv.includes('--help') || process.argv.includes('-h')) {\n  process.stdout.write(\n    `openpencil-mcp\\n\\nStart the OpenPencil MCP stdio bridge.\\n\\nOptions:\\n  --help, -h    Show this help message\\n`\n  )\n  process.exit(0)\n}\n\nconst wsPort = Number.parseInt(process.env.WS_PORT ?? '7601', 10)\nconst wsHost = process.env.HOST ?? '127.0.0.1'\nconst enableEval = process.env.OPENPENCIL_MCP_EVAL === '1'\nconst mcpRoot = process.env.OPENPENCIL_MCP_ROOT?.trim() || process.cwd()\n\nconst wsUrl = `ws://${wsHost}:${wsPort}`\nconst bridge = createStdioRpcBridge({\n  wsUrl,\n  onOpen: () => {\n    process.stderr.write(`Connected to OpenPencil app at ${wsUrl}\\n`)\n  },\n  onMalformedMessage: () => {\n    process.stderr.write('Malformed WS message\\n')\n  }\n})\n\nbridge.connect()\n\nconst mcpServer = new McpServer({ name: 'open-pencil', version: MCP_VERSION })\nregisterTools(mcpServer, { enableEval, mcpRoot, sendRpc: bridge.sendRpc })\n\nconst transport = new StdioServerTransport()\nvoid mcpServer.connect(transport)\n"],"mappings":"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"}