{"version":3,"file":"scope-Ddp8rHsr.mjs","names":[],"sources":["../src/tool/scope.ts"],"sourcesContent":["import type { RPCJSONObject } from '#mcp/json'\n\n/**\n * What MCP clients can reach: the whole document, or only the layers the user selected. In\n * selection scope the server exposes these read tools, stamps every call it sends to the app with\n * the scope, and the app rejects node IDs outside the selected layers and their descendants.\n */\nexport const MCP_TOOL_SCOPES = ['document', 'selection'] as const\nexport type MCPToolScope = (typeof MCP_TOOL_SCOPES)[number]\n\n/** The tools selection scope keeps, each with the arguments that name the nodes it reads. */\nexport const SELECTION_SCOPE_TOOLS: Readonly<Record<string, readonly string[]>> = {\n  get_selection: [],\n  get_node: ['id'],\n  get_page_tree: ['root_id'],\n  describe: ['id', 'ids'],\n  export_image: ['ids']\n}\n\nexport function isSelectionScopeTool(name: string): boolean {\n  return Object.hasOwn(SELECTION_SCOPE_TOOLS, name)\n}\n\nexport function parseToolScope(value: string | undefined): MCPToolScope {\n  const scope = value?.trim() || 'document'\n  if (scope === 'document' || scope === 'selection') return scope\n  throw new Error('OPENPENCIL_MCP_SCOPE must be \"document\" or \"selection\"')\n}\n\n/** The narrower of the scopes, so a client never offers more than the server it talks to. */\nexport function narrowestScope(...scopes: MCPToolScope[]): MCPToolScope {\n  return scopes.includes('selection') ? 'selection' : 'document'\n}\n\n/** Why a selection-scoped call cannot write files, such as an export with a `path`. */\nexport const SELECTION_SCOPE_FILE_OUTPUT_ERROR =\n  'Writing files is unavailable while OpenPencil shares only the selection; omit path'\n\n/** Commands the app may receive in selection scope besides calls of the tools above. */\nconst SELECTION_SCOPE_COMMANDS: ReadonlySet<string> = new Set(['agent_session_closed'])\n\ntype RPCSend = (msg: RPCJSONObject) => Promise<unknown>\n\nfunction toolName(msg: RPCJSONObject): string | null {\n  const args = msg.args\n  if (!args || typeof args !== 'object' || Array.isArray(args)) return null\n  const name = Reflect.get(args, 'name')\n  return typeof name === 'string' ? name : null\n}\n\n/**\n * Sends to the app within the scope: in selection scope, only the selection tools and session\n * notices go through, each tool call carrying the scope however the client called it, so neither\n * `/rpc` nor a stale client can reach the rest of the document or the settings.\n */\nexport function scopeRPC(send: RPCSend, scope: MCPToolScope): RPCSend {\n  if (scope === 'document') return send\n  return async (msg) => {\n    const command = typeof msg.command === 'string' ? msg.command : ''\n    if (SELECTION_SCOPE_COMMANDS.has(command)) return send(msg)\n    const name = command === 'tool' ? toolName(msg) : null\n    if (!name || !isSelectionScopeTool(name)) {\n      return {\n        ok: false,\n        error: `${name ?? command} is unavailable while OpenPencil shares only the selection with MCP clients`\n      }\n    }\n    const args = msg.args as RPCJSONObject\n    return send({ ...msg, args: { ...args, scope } })\n  }\n}\n"],"mappings":";;;;;;AAOA,MAAa,kBAAkB,CAAC,YAAY,WAAW;;AAIvD,MAAa,wBAAqE;CAChF,eAAe,CAAC;CAChB,UAAU,CAAC,IAAI;CACf,eAAe,CAAC,SAAS;CACzB,UAAU,CAAC,MAAM,KAAK;CACtB,cAAc,CAAC,KAAK;AACtB;AAEA,SAAgB,qBAAqB,MAAuB;CAC1D,OAAO,OAAO,OAAO,uBAAuB,IAAI;AAClD;AAEA,SAAgB,eAAe,OAAyC;CACtE,MAAM,QAAQ,OAAO,KAAK,KAAK;CAC/B,IAAI,UAAU,cAAc,UAAU,aAAa,OAAO;CAC1D,MAAM,IAAI,MAAM,4DAAwD;AAC1E;;AAGA,SAAgB,eAAe,GAAG,QAAsC;CACtE,OAAO,OAAO,SAAS,WAAW,IAAI,cAAc;AACtD;;AAGA,MAAa,oCACX;;AAGF,MAAM,2CAAgD,IAAI,IAAI,CAAC,sBAAsB,CAAC;AAItF,SAAS,SAAS,KAAmC;CACnD,MAAM,OAAO,IAAI;CACjB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG,OAAO;CACrE,MAAM,OAAO,QAAQ,IAAI,MAAM,MAAM;CACrC,OAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;;;;;;AAOA,SAAgB,SAAS,MAAe,OAA8B;CACpE,IAAI,UAAU,YAAY,OAAO;CACjC,OAAO,OAAO,QAAQ;EACpB,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;EAChE,IAAI,yBAAyB,IAAI,OAAO,GAAG,OAAO,KAAK,GAAG;EAC1D,MAAM,OAAO,YAAY,SAAS,SAAS,GAAG,IAAI;EAClD,IAAI,CAAC,QAAQ,CAAC,qBAAqB,IAAI,GACrC,OAAO;GACL,IAAI;GACJ,OAAO,GAAG,QAAQ,QAAQ;EAC5B;EAEF,MAAM,OAAO,IAAI;EACjB,OAAO,KAAK;GAAE,GAAG;GAAK,MAAM;IAAE,GAAG;IAAM;GAAM;EAAE,CAAC;CAClD;AACF"}