{"version":3,"file":"index.mjs","names":[],"sources":["../src/transport/origins.ts","../src/index.ts"],"sourcesContent":["/**\n * Origins allowed to call the local server from a view.\n *\n * The desktop app's webview always calls the server from its own origin, so a\n * manually started server must allow it without extra configuration. Browsers\n * set `Origin` themselves, and only the Tauri webview serves these origins, so\n * allowing them does not expose the server to arbitrary websites. Requests still\n * need the bearer token whenever authentication is enabled.\n */\nexport const DESKTOP_APP_ORIGINS = [\n  'tauri://localhost',\n  'http://tauri.localhost',\n  'https://tauri.localhost'\n] as const\n\n/** Parse a comma-separated override. Returns null when nothing usable is set. */\nexport function parseCORSOrigins(raw: string | undefined | null): string[] | null {\n  if (raw == null) return null\n  const origins = raw\n    .split(',')\n    .map((origin) => origin.trim())\n    .filter((origin) => origin.length > 0)\n  return origins.length > 0 ? origins : null\n}\n\n/** Explicit configuration wins; otherwise the desktop app origin is allowed. */\nexport function resolveCORSOrigins(raw: string | undefined): readonly string[] {\n  return parseCORSOrigins(raw) ?? DESKTOP_APP_ORIGINS\n}\n","#!/usr/bin/env node\nimport { resolveMCPRoot } from '#mcp/root'\nimport { MCP_VERSION, startServer } from '#mcp/server'\nimport { readToolPolicyFromEnv } from '#mcp/tool/policy'\nimport { resolveCORSOrigins } from '#mcp/transport/origins'\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-http\\n\\n` +\n      `Start the OpenPencil MCP server.\\n\\n` +\n      `On macOS/Linux, the server listens on a Unix domain socket by default\\n` +\n      `with optional TCP for browser clients. On Windows, only TCP is available.\\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      `  PORT                         TCP port (default: 7600, set to 0 to disable TCP)\\n` +\n      `  OPENPENCIL_MCP_SOCKET        Override Unix socket path (recorded in the discovery file)\\n` +\n      `  OPENPENCIL_MCP_DISCOVERY_PATH Override discovery file (mcp.json) location; defaults to the\\n` +\n      `                               platform path. Parent dir created 0o700. Mainly for test isolation.\\n` +\n      `  OPENPENCIL_MCP_TCP           Deprecated — TCP is controlled by PORT (>0 = on, 0 = off)\\n` +\n      `  OPENPENCIL_MCP_AUTH_TOKEN    Bearer token for MCP and RPC auth\\n` +\n      `  OPENPENCIL_MCP_ROOT          Allowed directory for file-scoped tools (default: home directory on Windows, current working directory 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\\n` +\n      `  OPENPENCIL_MCP_SCOPE         \"selection\" to let clients read only the selected layers\\n` +\n      `                               (default: \"document\")\\n` +\n      `  OPENPENCIL_MCP_CORS_ORIGIN   Allowed CORS origins, comma-separated.\\n` +\n      `                               Defaults to the desktop app origin, since the app\\n` +\n      `                               webview is the only view that calls this server.\\n` +\n      `  OPENPENCIL_MCP_APP_TIMEOUT_MS  If set, close the server and remove its discovery\\n` +\n      `                               file after no app is attached for this many ms. The\\n` +\n      `                               grace period starts at startup and after disconnects.\\n` +\n      `                               Unset/0 disables it (default) — do not set this for\\n` +\n      `                               manual/CLI use, since nothing may ever register.\\n`\n  )\n  process.exit(0)\n}\n\nconst rawPortText = (process.env.PORT ?? '7600').trim()\n// Reject non-digit strings (including hex like \"0x50\" and partially-numeric like\n// \"7600abc\") — Number.parseInt would silently parse these, masking misconfig.\nif (!/^\\d+$/.test(rawPortText)) {\n  process.stderr.write(`Error: PORT must be an integer in 0–65535, got \"${process.env.PORT}\"\\n`)\n  process.exit(1)\n}\nconst rawPort = Number.parseInt(rawPortText, 10)\n// Validate PORT: must be an integer in 0–65535. 0 means \"disable TCP\".\nif (rawPort < 0 || rawPort > 65535) {\n  process.stderr.write(`Error: PORT must be an integer in 0–65535, got \"${process.env.PORT}\"\\n`)\n  process.exit(1)\n}\nconst port = rawPort\n// OPENPENCIL_MCP_TCP is accepted for backward compat but has no effect —\n// the PORT value alone determines whether TCP is enabled (PORT=0 is the kill switch).\nconst withTcp = port > 0\n\nconst MAX_APP_TIMEOUT_MS = 2_147_483_647\nconst rawAppTimeoutText = process.env.OPENPENCIL_MCP_APP_TIMEOUT_MS?.trim()\nlet appAttachTimeoutMs: number | undefined\nif (rawAppTimeoutText) {\n  if (!/^\\d+$/.test(rawAppTimeoutText)) {\n    process.stderr.write(\n      `Error: OPENPENCIL_MCP_APP_TIMEOUT_MS must be a non-negative integer, got \"${rawAppTimeoutText}\"\\n`\n    )\n    process.exit(1)\n  }\n  appAttachTimeoutMs = Number.parseInt(rawAppTimeoutText, 10)\n  if (!Number.isSafeInteger(appAttachTimeoutMs) || appAttachTimeoutMs > MAX_APP_TIMEOUT_MS) {\n    process.stderr.write(\n      `Error: OPENPENCIL_MCP_APP_TIMEOUT_MS must be an integer in 0–${MAX_APP_TIMEOUT_MS}, got \"${rawAppTimeoutText}\"\\n`\n    )\n    process.exit(1)\n  }\n}\n\nconst toolPolicy = readToolPolicyFromEnv()\n\nconst handle = await startServer({\n  httpPort: withTcp ? port : 0,\n  withTcp,\n  socketPath: process.env.OPENPENCIL_MCP_SOCKET?.trim() || null,\n  enableEval: toolPolicy.allowEval,\n  disabledTools: toolPolicy.disabledTools,\n  scope: toolPolicy.scope,\n  mcpRoot: resolveMCPRoot(process.env.OPENPENCIL_MCP_ROOT),\n  // Auth token: undefined → auto-generate, empty string → disable auth,\n  // non-empty → use trimmed value. Whitespace-only is rejected to prevent a\n  // silent fallback to an auto-generated token when the operator intended to\n  // set an explicit one.\n  authToken: (() => {\n    const raw = process.env.OPENPENCIL_MCP_AUTH_TOKEN\n    if (raw === undefined) return undefined\n    if (raw === '') return null\n    const trimmed = raw.trim()\n    if (!trimmed) {\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    }\n    return trimmed\n  })(),\n  corsOrigin: resolveCORSOrigins(process.env.OPENPENCIL_MCP_CORS_ORIGIN),\n  appAttachTimeoutMs\n})\n\nconst readyMarker = process.env.OPENPENCIL_MCP_READY_MARKER\nif (readyMarker && /^open-pencil-ready:[a-f0-9-]{36}$/.test(readyMarker)) {\n  process.stderr.write(`${readyMarker}\\n`)\n}\n\nprocess.stderr.write(`OpenPencil MCP server\\n`)\nif (handle.socketPath) process.stderr.write(`  Socket: ${handle.socketPath}\\n`)\nif (handle.httpPort) process.stderr.write(`  HTTP:   http://127.0.0.1:${handle.httpPort}\\n`)\n\n// Graceful shutdown on signals\nconst shutdown = async () => {\n  process.stderr.write('\\nShutting down MCP server...\\n')\n  await handle.close()\n  process.exit(0)\n}\n\nprocess.on('SIGINT', () => void shutdown().catch(() => process.exit(1)))\nprocess.on('SIGTERM', () => void shutdown().catch(() => process.exit(1)))\n"],"mappings":"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"}