{"version":3,"file":"stdio.mjs","names":[],"sources":["../src/stdio/bridge.ts","../src/stdio.ts"],"sourcesContent":["import { request } from 'node:http'\nimport type { ClientRequest, RequestOptions } from 'node:http'\n\nimport { readDiscoveryFile } from '#mcp/transport/discovery'\nimport { getSocketPath, platformHasUnixSockets } from '#mcp/transport/paths'\n\ntype StdioRPCBridgeOptions = {\n  /** Override socket path (if known). Auto-discovered if omitted. */\n  socketPath?: string | null\n  /** Auth token for /rpc Bearer header. */\n  authToken?: string | null\n  reconnectDelayMs?: number\n  onReady?: () => void\n  onReconnect?: () => void\n}\n\n// 35s gives a 5s margin over the worst case (10s app wait + 20s RPC timeout = 30s)\nconst RPC_TIMEOUT = 35_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\ntype TransportMode = 'socket' | 'tcp'\n\n/**\n * Creates an RPC bridge that connects to the MCP server via HTTP\n * over a Unix domain socket.\n *\n * This replaces the previous WebSocket-based bridge with a simpler\n * HTTP approach that natively supports Unix domain sockets.\n */\nexport function createStdioRPCBridge({\n  socketPath: socketPathOverride,\n  authToken,\n  reconnectDelayMs = 2000,\n  onReady,\n  onReconnect\n}: StdioRPCBridgeOptions) {\n  let resolvedSocketPath: string | null = socketPathOverride ?? null\n  let resolvedHttpPort: number | null = null\n  let transportMode: TransportMode | null = null\n  let ready = false\n  let wasConnected = false\n  let reconnectTimer: ReturnType<typeof setTimeout> | undefined\n  let connectPromise: Promise<void> | null = null\n  let closed = false\n  let authFailure = false\n  let socketFailed = false\n\n  // Socket path resolution has two tiers:\n  //   Explicit: socketPathOverride parameter — authoritative pin, never\n  //             overwritten by discovery during reconnect.\n  //   Hint:     OPENPENCIL_MCP_SOCKET env var — resolved through\n  //             getSocketPath() and readDiscoveryFile(); NOT treated as\n  //             explicit, so the bridge can pick up a changed path from the\n  //             discovery file after a server restart.\n  const hasExplicitSocketPath = socketPathOverride !== undefined && socketPathOverride !== null\n  // Track whether authToken was explicitly provided by the caller.\n  // When false, the token is auto-discovered from the discovery file and\n  // can be refreshed transparently on 401 without surfacing the error.\n  const hasExplicitAuth = authToken !== undefined\n  let resolvedAuthToken: string | null = authToken ?? null\n\n  async function resolveAuthToken(): Promise<string | null> {\n    if (hasExplicitAuth) return resolvedAuthToken\n    if (resolvedAuthToken) return resolvedAuthToken\n    const info = await readDiscoveryFile()\n    if (info?.authToken) {\n      resolvedAuthToken = info.authToken\n    }\n    return resolvedAuthToken\n  }\n\n  async function resolveTransport(): Promise<void> {\n    // Already resolved\n    if (transportMode) return\n\n    // Try reading the discovery file\n    const info = await readDiscoveryFile()\n\n    // When the auth token was not explicitly provided, pick it up from the\n    // discovery file whenever we re-read transport info. This ensures the\n    // bridge picks up a new server-generated token after a restart without\n    // waiting for a 401 response.\n    if (!hasExplicitAuth && info?.authToken) {\n      resolvedAuthToken = info.authToken\n    }\n\n    // When no Unix socket is available, prefer TCP via httpPort\n    if (!platformHasUnixSockets()) {\n      if (info?.httpPort) {\n        resolvedHttpPort = info.httpPort\n        transportMode = 'tcp'\n        return\n      }\n      throw new Error('No MCP server discovery info found')\n    }\n\n    // Unix: prefer socket path from discovery file, fall back to default.\n    // If no socket is available (TCP-only server), use TCP via httpPort.\n    // The socketPath option (when provided directly by the caller) takes\n    // precedence and is never overwritten by discovery.\n    if (hasExplicitSocketPath && resolvedSocketPath) {\n      // Caller explicitly provided a socket path — keep it, never overwrite\n    } else if (info?.socketPath) {\n      resolvedSocketPath = info.socketPath\n    } else if (info?.httpPort) {\n      // Server is TCP-only (no socket listener) — use TCP\n      resolvedHttpPort = info.httpPort\n      transportMode = 'tcp'\n      return\n    } else {\n      resolvedSocketPath = await getSocketPath()\n    }\n\n    // Also capture httpPort as fallback\n    if (info?.httpPort) {\n      resolvedHttpPort = info.httpPort\n    }\n\n    // If the socket previously failed and TCP is available, use TCP\n    // instead of retrying the same stale socket indefinitely.\n    if (!hasExplicitSocketPath && socketFailed && resolvedHttpPort) {\n      transportMode = 'tcp'\n      return\n    }\n\n    transportMode = 'socket'\n  }\n\n  /**\n   * Makes an HTTP request to the MCP server via socket or TCP.\n   */\n  function httpRequest(\n    method: string,\n    path: string,\n    body?: Record<string, unknown>\n  ): Promise<{ status: number; data: unknown; req: ClientRequest }> {\n    return new Promise((resolve, reject) => {\n      const bodyJSON = body ? JSON.stringify(body) : undefined\n      const headers: Record<string, string> = {}\n      if (bodyJSON) headers['Content-Type'] = 'application/json'\n      if (resolvedAuthToken) headers.Authorization = `Bearer ${resolvedAuthToken}`\n\n      let reqOpts: RequestOptions | null = null\n      if (transportMode === 'socket' && resolvedSocketPath) {\n        reqOpts = { socketPath: resolvedSocketPath, path, method, headers }\n      } else if (transportMode === 'tcp' && resolvedHttpPort) {\n        reqOpts = { hostname: '127.0.0.1', port: resolvedHttpPort, path, method, headers }\n      }\n\n      if (!reqOpts) {\n        reject(new Error(DISCONNECTED_MESSAGE))\n        return\n      }\n\n      const req = request(reqOpts, (res) => {\n        const chunks: Buffer[] = []\n        res.on('data', (chunk: Buffer) => chunks.push(chunk))\n        res.on('end', () => {\n          const raw = Buffer.concat(chunks).toString('utf-8')\n          let data: unknown\n          try {\n            data = JSON.parse(raw)\n          } catch {\n            data = raw\n          }\n          resolve({ status: res.statusCode ?? 200, data, req })\n        })\n        res.on('error', reject)\n      })\n\n      req.on('error', reject)\n      // Socket-level idle timeout to prevent indefinite hangs on\n      // unresponsive connections. Set 5s shorter than the outer RPC_TIMEOUT\n      // so this fires deterministically first, producing a clear \"server\n      // unreachable\" signal rather than racing with the outer timer.\n      // If the outer sendRPC timer already rejected, the error handler\n      // is a no-op (settled guard in attempt()).\n      req.setTimeout(RPC_TIMEOUT - 5_000, () => {\n        req.destroy(new Error('Socket timeout'))\n      })\n      if (bodyJSON) req.write(bodyJSON)\n      req.end()\n    })\n  }\n\n  /**\n   * Checks if the server is healthy and the browser is connected.\n   */\n  async function checkHealth(): Promise<{\n    reachable: boolean\n    appConnected: boolean\n    authFailed: boolean\n  }> {\n    try {\n      const { status, data } = await httpRequest('GET', '/health')\n      if (status === 401) return { reachable: true, appConnected: false, authFailed: true }\n      if (status !== 200) return { reachable: false, appConnected: false, authFailed: false }\n      const health = data as { status?: string }\n      return {\n        reachable: health.status === 'ok' || health.status === 'no_app',\n        appConnected: health.status === 'ok',\n        authFailed: false\n      }\n    } catch {\n      if (!hasExplicitSocketPath && transportMode === 'socket') socketFailed = true\n      return { reachable: false, appConnected: false, authFailed: false }\n    }\n  }\n\n  /**\n   * Attempts to connect to the server. Retries with a fixed delay.\n   */\n  async function connect(): Promise<void> {\n    if (closed) return\n    try {\n      await resolveTransport()\n      if (!resolvedAuthToken) await resolveAuthToken()\n    } catch {\n      connectPromise = null\n      scheduleReconnect()\n      return\n    }\n\n    // close() may have been called while we were resolving transport.\n    // oxlint-disable-next-line no-unnecessary-condition\n    if (closed) return\n\n    const { reachable, appConnected, authFailed } = await checkHealth()\n    // oxlint-disable-next-line no-unnecessary-condition\n    if (closed) return\n    if (authFailed) {\n      // Auth token is wrong — surface as an auth error rather than a generic\n      // disconnect so the caller can prompt for a new token or re-read discovery.\n      authFailure = true\n      ready = false\n      connectPromise = null\n      // When using an auto-discovered token, clear it and transportMode so\n      // the next reconnect re-reads the discovery file for a fresh token\n      // instead of reusing the stale one (which would cause another 401 loop).\n      if (!hasExplicitAuth) {\n        resolvedAuthToken = null\n        transportMode = null\n      }\n      scheduleReconnect()\n      return\n    }\n    authFailure = false\n    if (appConnected) {\n      ready = true\n      socketFailed = false\n      connectPromise = null\n      if (wasConnected) {\n        onReconnect?.()\n      } else {\n        wasConnected = true\n        onReady?.()\n      }\n      return\n    }\n\n    if (reachable) {\n      // Server is up — we can send RPC requests and let the server\n      // wait for the desktop app to connect.\n      ready = true\n      socketFailed = false\n      connectPromise = null\n      if (!wasConnected) {\n        wasConnected = true\n        onReady?.()\n      }\n      return\n    }\n\n    transportMode = null\n    if (!hasExplicitSocketPath) resolvedSocketPath = null\n    resolvedHttpPort = null\n    connectPromise = null\n    scheduleReconnect()\n  }\n\n  function scheduleReconnect() {\n    if (closed) return\n    clearTimeout(reconnectTimer)\n    reconnectTimer = setTimeout(() => {\n      connectPromise = connect()\n    }, reconnectDelayMs)\n    reconnectTimer.unref()\n  }\n\n  /**\n   * Sends an RPC request to the MCP server.\n   *\n   * Routes through the /rpc HTTP endpoint, which proxies\n   * the call to the connected browser.\n   *\n   * When using an auto-discovered auth token (hasExplicitAuth === false),\n   * a 401 response triggers a transparent retry: the cached token is cleared,\n   * the discovery file is re-read for a fresh token, and the request is\n   * retried once. Only if the retry also fails is the error surfaced to the\n   * caller. This makes server restarts with a new auto-generated token\n   * seamless to the AI agent.\n   */\n  function sendRPC(body: Record<string, unknown>): Promise<unknown> {\n    // If not ready, await the in-flight connect() promise first — the\n    // bridge may still be resolving transport. Only reject after that\n    // completes and we're still not ready.\n    const awaitReady = async (): Promise<void> => {\n      if (!ready && connectPromise) await connectPromise\n      if (authFailure) throw new Error('Unauthorized: check OPENPENCIL_MCP_AUTH_TOKEN')\n      if (!ready) throw new Error(DISCONNECTED_MESSAGE)\n    }\n\n    return awaitReady().then(\n      () =>\n        new Promise((resolve, reject) => {\n          let settled = false\n          const timer = setTimeout(() => {\n            if (settled) return\n            settled = true\n            reject(new Error(`RPC timeout (${Math.round(RPC_TIMEOUT / 1000)}s)`))\n          }, RPC_TIMEOUT)\n\n          /**\n           * Performs one HTTP request attempt. `allowAuthRetry` controls whether\n           * a 401 with an auto-discovered token triggers a re-read and retry.\n           * It is set to `false` for the second attempt to prevent infinite loops.\n           */\n          function attempt(allowAuthRetry: boolean) {\n            httpRequest('POST', '/rpc', body)\n              .then(({ status, data, req }) => {\n                // If the overall RPC timeout fired while the request was in-flight,\n                // the server may have already executed the tool. Destroying the\n                // request only closes the TCP socket — it does not roll back any\n                // server-side mutation. Callers must not assume that a timeout\n                // means the operation was not executed.\n                if (settled) {\n                  req.destroy()\n                  return undefined\n                }\n\n                if (status === 401) {\n                  if (allowAuthRetry && !hasExplicitAuth) {\n                    // Auto-discovered token may have rotated — re-read the\n                    // discovery file for a fresh token and retry transparently.\n                    // IMPORTANT: Do NOT clear the timer here — the retry must\n                    // still be protected by the overall 35-second timeout.\n                    resolvedAuthToken = null\n                    void readDiscoveryFile()\n                      .then((info) => {\n                        if (settled) return undefined\n                        if (info?.authToken) {\n                          resolvedAuthToken = info.authToken\n                          // Keep existing transport mode (socket path / port\n                          // doesn't change on server restart — only the token\n                          // does). Retry exactly once with the new token.\n                          attempt(false)\n                          return undefined\n                        }\n                        clearTimeout(timer)\n                        authFailure = true\n                        transportMode = null\n                        if (!hasExplicitSocketPath) resolvedSocketPath = null\n                        resolvedHttpPort = null\n                        ready = false\n                        settled = true\n                        scheduleReconnect()\n                        reject(new Error('Unauthorized'))\n                        return undefined\n                      })\n                      .catch(() => {\n                        if (settled) return\n                        clearTimeout(timer)\n                        // Discovery read failed — stale transport state must be\n                        // discarded so the bridge re-resolves on reconnect.\n                        transportMode = null\n                        if (!hasExplicitSocketPath) resolvedSocketPath = null\n                        resolvedHttpPort = null\n                        ready = false\n                        settled = true\n                        scheduleReconnect()\n                        reject(new Error(DISCONNECTED_MESSAGE))\n                      })\n                    return undefined\n                  }\n\n                  // Explicit token was rejected (config error), or auto-retry\n                  // already failed — surface the error immediately.\n                  clearTimeout(timer)\n                  authFailure = true\n                  if (!hasExplicitAuth) {\n                    resolvedAuthToken = null\n                  }\n                  transportMode = null\n                  // Clear auto-discovered transport params so resolveTransport()\n                  // picks up fresh values from the discovery file on reconnect.\n                  if (!hasExplicitSocketPath) resolvedSocketPath = null\n                  resolvedHttpPort = null\n                  ready = false\n                  settled = true\n                  scheduleReconnect()\n                  reject(new Error('Unauthorized: check OPENPENCIL_MCP_AUTH_TOKEN'))\n                  return undefined\n                }\n\n                clearTimeout(timer)\n\n                if (status >= 400) {\n                  const errData = data as { error?: string }\n                  settled = true\n                  reject(new Error(errData.error ?? `RPC failed with status ${status}`))\n                  return undefined\n                }\n                settled = true\n                resolve(data)\n                return undefined\n              })\n              .catch(() => {\n                if (settled) return\n                clearTimeout(timer)\n                if (!hasExplicitSocketPath && transportMode === 'socket') {\n                  socketFailed = true\n                }\n                transportMode = null\n                // Clear auto-discovered transport params so resolveTransport()\n                // picks up fresh values from the discovery file on reconnect.\n                if (!hasExplicitSocketPath) resolvedSocketPath = null\n                resolvedHttpPort = null\n                ready = false\n                settled = true\n                scheduleReconnect()\n                reject(new Error(DISCONNECTED_MESSAGE))\n              })\n          }\n\n          attempt(true)\n        })\n    )\n  }\n\n  /**\n   * Stops the reconnect timer and marks the bridge as not ready.\n   * Call this to cleanly shut down the bridge and prevent timer leaks.\n   */\n  function close(): void {\n    closed = true\n    clearTimeout(reconnectTimer)\n    reconnectTimer = undefined\n    ready = false\n    connectPromise = null\n  }\n\n  // Start connection\n  connectPromise = connect()\n\n  return { sendRPC, close }\n}\n","#!/usr/bin/env node\nimport { randomUUID } from 'node:crypto'\n\nimport { McpServer } from '@modelcontextprotocol/server'\nimport { StdioServerTransport } from '@modelcontextprotocol/server/stdio'\n\nimport { resolveMCPRoot } from '#mcp/root'\nimport { MCP_VERSION, registerTools } from '#mcp/server'\nimport { createStdioRPCBridge } from '#mcp/stdio/bridge'\nimport type { ToolPolicy } from '#mcp/tool/metadata'\nimport { parseDisabledTools } from '#mcp/tool/policy'\nimport { narrowestScope, parseToolScope } from '#mcp/tool/scope'\nimport { readDiscoveryFile } from '#mcp/transport/discovery'\n\nif (process.argv.includes('--version')) {\n  process.stdout.write(`${MCP_VERSION}\\n`)\n  process.exit(0)\n}\n\nif (process.argv.includes('--help') || process.argv.includes('-h')) {\n  process.stdout.write(\n    `openpencil-mcp\\n\\n` +\n      `Start the OpenPencil MCP stdio bridge.\\n\\n` +\n      `Connects to the MCP server via Unix domain socket on macOS/Linux ` +\n      `(with TCP fallback) or via TCP on Windows.\\n` +\n      `The MCP server is started by the OpenPencil\\n` +\n      `desktop app; this bridge only forwards stdio JSON-RPC to it.\\n\\n` +\n      `Options:\\n` +\n      `  --help, -h    Show this help message\\n` +\n      `  --version     Print the version\\n\\n` +\n      `Environment variables:\\n` +\n      `  OPENPENCIL_MCP_SOCKET        Override socket path (auto-discovered from discovery file when unset)\\n` +\n      `  OPENPENCIL_MCP_AUTH_TOKEN    Bearer token for RPC auth\\n` +\n      `  OPENPENCIL_MCP_ROOT          Allowed directory for file-scoped tools\\n` +\n      `                               (default: home directory on Windows, cwd elsewhere)\\n` +\n      `  OPENPENCIL_MCP_EVAL          Set to 1 to enable the eval tool\\n` +\n      `  OPENPENCIL_MCP_DISABLED_TOOLS Comma-separated tool names to omit; defaults to the app setting\\n` +\n      `  OPENPENCIL_MCP_SCOPE         \"selection\" to read only the selected layers; defaults to the app setting\\n`\n  )\n  process.exit(0)\n}\n\nconst discovery = await readDiscoveryFile()\nconst toolPolicy: ToolPolicy = {\n  allowEval: process.env.OPENPENCIL_MCP_EVAL === '1',\n  disabledTools:\n    process.env.OPENPENCIL_MCP_DISABLED_TOOLS === undefined\n      ? (discovery?.disabledTools ?? [])\n      : parseDisabledTools(process.env.OPENPENCIL_MCP_DISABLED_TOOLS),\n  // The server enforces its own scope on every call; this keeps the tool list in step with it,\n  // and lets the client limit itself further.\n  scope: narrowestScope(\n    discovery?.scope ?? 'document',\n    parseToolScope(process.env.OPENPENCIL_MCP_SCOPE)\n  )\n}\nconst mcpRoot = resolveMCPRoot(process.env.OPENPENCIL_MCP_ROOT)\n// Auth token: undefined → auto-discover from discovery file, empty string →\n// disable auth, whitespace-only → reject (same fail-fast as index.ts to catch\n// misconfiguration), otherwise → use the trimmed value.\nconst rawAuthToken = process.env.OPENPENCIL_MCP_AUTH_TOKEN\nif (rawAuthToken !== undefined && rawAuthToken !== '' && rawAuthToken.trim() === '') {\n  process.stderr.write(\n    'Error: OPENPENCIL_MCP_AUTH_TOKEN is whitespace-only. Set a real token, or use an empty string to disable auth.\\n'\n  )\n  process.exit(1)\n}\nfunction resolveAuthToken(raw: string | undefined): string | null | undefined {\n  if (raw === undefined) return undefined\n  if (raw === '') return null\n  return raw.trim()\n}\nconst authToken = resolveAuthToken(rawAuthToken)\n\n// OPENPENCIL_MCP_SOCKET is intentionally NOT forwarded as an explicit socketPath.\n// The bridge reads the socket path from the discovery file (whose `socketPath`\n// field records the override) via auto-discovery. Treating the env var as an\n// explicit pin would prevent the bridge from following discovery updates after\n// a server restart.\nconst bridge = createStdioRPCBridge({\n  authToken,\n  onReady: () => {\n    process.stderr.write(\n      'Connected to OpenPencil MCP server; document availability is checked per tool call\\n'\n    )\n  },\n  onReconnect: () => {\n    process.stderr.write(\n      'Reconnected to OpenPencil MCP server; document availability is checked per tool call\\n'\n    )\n  }\n})\n\nconst mcpServer = new McpServer({ name: 'open-pencil', version: MCP_VERSION })\n// One client per process: its tool calls show as one agent in the app.\nregisterTools(mcpServer, {\n  policy: toolPolicy,\n  mcpRoot,\n  sendRPC: bridge.sendRPC,\n  agentSession: { id: randomUUID(), kind: 'mcp' }\n})\n\nconst transport = new StdioServerTransport()\nmcpServer.connect(transport).catch((err) => {\n  process.stderr.write(`Fatal: MCP connect failed — ${err instanceof Error ? err.message : err}\\n`)\n  process.exit(1)\n})\n\nprocess.on('uncaughtException', (err) => {\n  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : err}\\n`)\n  process.exit(1)\n})\n\nprocess.on('unhandledRejection', (reason: unknown) => {\n  const message = reason instanceof Error ? reason.message : String(reason)\n  process.stderr.write(`Fatal: unhandled rejection — ${message}\\n`)\n  process.exit(1)\n})\n"],"mappings":"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"}