{"version":3,"file":"index.mjs","names":["promptTemplate","defaultContainer"],"sources":["../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts","../src/server/index.ts"],"sourcesContent":["export default \"Create a browse-tool custom tool folder for use with `browse-tool mcp-serve --custom-tools <dir>`.\\n\\nGoal: {{ toolGoal }}\\nPreferred tool name: {{ preferredToolName }}\\nPage context: {{ pageContext }}\\n\\nRequirements:\\n- Produce a `tools.yaml` file with a top-level `tools` array.\\n- Each tool entry must include `name`, `description`, `script`, `capabilities`, and `inputSchema`.\\n- Each tool entry may optionally include `suggestionActions` as a short text string describing the recommended next step after the tool succeeds.\\n- `inputSchema` must be JSON Schema with `type: object`.\\n- `inputSchema` must define at least one execution target field: `pageId` and/or `browserId`, each as `type: string` when present.\\n- The script file must be `.ts` and export a named `run` function with the shape `export const run = async ({ page, browser, input, logger }) => { ... }`.\\n- The `run` export runs in Node.js, not inside the page.\\n- Use the provided `page` object for browser interaction. It will be a Playwright page in playwright mode or an extension page proxy in extension mode.\\n- Use the provided `browser` helper when the tool needs to list pages, open a new page, or work from `browserId` instead of an existing `pageId`.\\n- `browser` is not a raw Playwright browser. It exposes `browserId`, `mode`, `listPages()`, `getPage(pageId)`, `getCurrentPage()`, and `newPage({ url?, setAsCurrent? })`.\\n- Use the provided `logger` object for instrumentation. It exposes `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, and logs automatically attach to the active custom-tool telemetry context.\\n- The logger also exposes `getTraceContext()` so a tool can return the active `traceId` and `spanId` when needed for downstream log inspection.\\n- If you need DOM access, call `page.evaluate(...)` from the `run` export instead of assuming browser globals are available at module scope.\\n- Keep the script self-contained and avoid relative imports.\\n- Return either a plain object, a string, or an MCP-style `CallToolResult`.\\n- Prefer read-only behavior unless the goal explicitly requires mutation.\\n\\nOutput format:\\n- Show the full `tools.yaml` content.\\n- Show the full `{{ preferredToolName }}.ts` content.\\n- If the tool needs additional inputs beyond `pageId` or `browserId`, define them in `inputSchema` and read them from `input`.\\n- Filesystem access is allowed because the script runs in Node.js.\\n\\nExample `tools.yaml` shape:\\n```yaml\\ntools:\\n  - name: get_post\\n    description: Get the current post from the open feed page\\n    script: get_post.ts\\n    suggestionActions: Open the author profile and review whether the post is worth engaging with\\n    capabilities:\\n      readOnlyHint: true\\n      openWorldHint: false\\n    inputSchema:\\n      type: object\\n      properties:\\n        pageId:\\n          type: string\\n        browserId:\\n          type: string\\n        selector:\\n          type: string\\n      required:\\n        - pageId\\n```\\n\\nExample script shape:\\n```ts\\nexport const run = async ({ page, browser, input, logger }) => {\\n  const traceContext = logger.getTraceContext();\\n  logger.info(\\\"reading post\\\", { attributes: { selector: input.selector ?? \\\"body\\\", browserId: browser.browserId } });\\n  const selector = input.selector ?? \\\"body\\\";\\n  const text = await page.evaluate((currentSelector) => {\\n    return document.querySelector(currentSelector)?.textContent ?? \\\"\\\";\\n  }, selector);\\n  return {\\n    pageUrl: page.url(),\\n    traceContext,\\n    text,\\n  };\\n};\\n```\\n\"","/**\n * CustomScriptAuthoringPrompt\n *\n * Guides an AI assistant to create browse-tool custom tool folders that work\n * with the `mcp-serve --custom-tools <dir>` flow.\n */\nimport { Liquid } from 'liquidjs';\nimport promptTemplate from './instructions/custom-script-authoring.md?raw';\n\nexport const customScriptAuthoringPrompt = {\n  name: 'custom_script_authoring',\n  description: 'Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.',\n  arguments: [\n    {\n      name: 'toolGoal',\n      description: 'What the custom tool should do on the page',\n      required: true,\n    },\n    {\n      name: 'toolName',\n      description: 'Preferred snake_case tool name',\n      required: false,\n    },\n    {\n      name: 'pageContext',\n      description: 'Optional context about the target page or workflow',\n      required: false,\n    },\n  ],\n};\n\ninterface GenerateCustomScriptAuthoringPromptArgs {\n  toolGoal: string;\n  toolName?: string;\n  pageContext?: string;\n}\n\nconst liquid = new Liquid();\n\nexport function generateCustomScriptAuthoringPrompt(\n  args: GenerateCustomScriptAuthoringPromptArgs,\n): Array<{ role: string; content: { type: string; text: string } }> {\n  const preferredToolName = args.toolName?.trim() || 'custom_page_tool';\n  const pageContext = args.pageContext?.trim() || 'No extra page context provided.';\n  const text = liquid.parseAndRenderSync(promptTemplate, {\n    toolGoal: args.toolGoal,\n    preferredToolName,\n    pageContext,\n  });\n\n  return [\n    {\n      role: 'user',\n      content: {\n        type: 'text',\n        text,\n      },\n    },\n  ];\n}\n","/**\n * MCP Server Setup\n *\n * DESIGN PATTERNS:\n * - Factory pattern for server creation with IoC container\n * - Tool registry pattern for fast lookup\n * - Structured error handling with custom error classes\n *\n * CODING STANDARDS:\n * - Tools are loaded from IoC container on server creation\n * - Proper error handling in all request handlers with custom error classes\n * - Errors include codes, recovery suggestions, and available tools list\n *\n * AVOID:\n * - Hardcoded tool lists (use IoC container)\n * - Missing error handling in handlers\n * - Generic error messages without recovery suggestions\n */\n\nimport { coerceArgs, formatZodError } from '@agimon-ai/foundation-validator';\nimport { type CallToolResult, Server } from '@modelcontextprotocol/server';\nimport type { Attributes, AttributeValue, Span } from '@opentelemetry/api';\nimport type { Container } from 'inversify';\nimport { z } from 'zod';\nimport { container as defaultContainer, PLAYWRIGHT_TYPES } from '../container/index.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\nimport { toMcpListTool } from '../utils/mcpToolDefinition.js';\n\n// ============================================================================\n// Logger Interface\n// ============================================================================\n\n/** Logger interface for dependency injection */\nexport interface Logger {\n  debug(message: string, context?: Record<string, unknown>): void;\n  info(message: string, context?: Record<string, unknown>): void;\n  warn(message: string, context?: Record<string, unknown>): void;\n  error(message: string, context?: Record<string, unknown>): void;\n  runInSpan?<T>(\n    name: string,\n    context: Record<string, unknown>,\n    callback: (span: Span | undefined) => Promise<T> | T,\n  ): Promise<T>;\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue {\n  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n    return value;\n  }\n\n  return JSON.stringify(value);\n}\n\nfunction toAttributes(context?: Record<string, unknown>): Attributes | undefined {\n  if (!context) {\n    return undefined;\n  }\n\n  const attributes: Attributes = {};\n  for (const [key, value] of Object.entries(context)) {\n    if (value === undefined || value === null) {\n      continue;\n    }\n\n    attributes[key] = toAttributeValue(value);\n  }\n\n  return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction createTelemetryLogger(): Logger {\n  const telemetry = new TelemetryService({\n    serviceName: 'browse-tool-mcp',\n  });\n\n  return {\n    debug(message, context) {\n      telemetry.log('debug', message, { attributes: toAttributes(context) });\n    },\n    info(message, context) {\n      telemetry.log('info', message, { attributes: toAttributes(context) });\n    },\n    warn(message, context) {\n      telemetry.log('warn', message, { attributes: toAttributes(context) });\n    },\n    error(message, context) {\n      telemetry.log('error', message, { attributes: toAttributes(context) });\n    },\n    runInSpan(name, context, callback) {\n      return telemetry.runInSpan(name, { attributes: toAttributes(context) }, callback);\n    },\n  };\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\n/**\n * Error thrown when an unknown tool is requested.\n * Provides error code, recovery suggestion, and available tools list.\n */\nexport class UnknownToolError extends Error {\n  readonly code = 'UNKNOWN_TOOL';\n  readonly recovery = 'Use ListTools to see available tools.';\n  readonly availableTools: string[];\n\n  constructor(toolName: string, availableTools: string[], options?: ErrorOptions) {\n    super(\n      `Unknown tool: ${toolName}. Available tools: ${availableTools.slice(0, 5).join(', ')}${availableTools.length > 5 ? `, ... (${availableTools.length} total)` : ''}. Use ListTools to see all available tools.`,\n      options,\n    );\n    this.name = 'UnknownToolError';\n    this.availableTools = availableTools;\n  }\n}\n\n/**\n * Error thrown when tool execution fails.\n * Provides error code, tool name context, and recovery suggestion.\n */\nexport class ToolExecutionError extends Error {\n  readonly code = 'TOOL_EXECUTION_ERROR';\n  readonly recovery: string;\n  readonly toolName: string;\n\n  constructor(toolName: string, message: string, options?: ErrorOptions & { recovery?: string }) {\n    super(`Tool execution failed for '${toolName}': ${message}`, options);\n    this.name = 'ToolExecutionError';\n    this.toolName = toolName;\n    this.recovery = options?.recovery ?? 'Check tool inputs and try again.';\n  }\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration options for the MCP server.\n */\nexport interface ServerConfig {\n  /** Optional IoC container (defaults to the shared container) */\n  container?: Container;\n  /** Optional logger for debugging and error tracking */\n  logger?: Logger;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Creates a structured error response following MCP conventions.\n * Includes error codes, recovery suggestions, and context metadata.\n * @param error - The error that occurred\n * @param toolName - Name of the tool that failed\n * @returns CallToolResult with isError flag and structured error content\n */\nfunction createErrorResponse(error: unknown, toolName: string): CallToolResult {\n  const message = error instanceof Error ? error.message : 'Unknown error occurred';\n  const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : 'TOOL_EXECUTION_ERROR';\n  const recovery =\n    error instanceof Error && 'recovery' in error\n      ? (error as { recovery: string }).recovery\n      : 'Check tool inputs and try again.';\n\n  const errorResponse = {\n    error: {\n      code,\n      message,\n      toolName,\n      recovery,\n    },\n  };\n\n  return {\n    content: [\n      {\n        type: 'text',\n        text: JSON.stringify(errorResponse, null, 2),\n      },\n    ],\n    isError: true,\n  };\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Creates a new MCP server instance with tools from the IoC container.\n * @param config - Optional server configuration\n * @returns Configured MCP Server instance\n */\nexport function createServer(config?: ServerConfig): Server {\n  const iocContainer = config?.container ?? defaultContainer;\n  const logger = config?.logger ?? createTelemetryLogger();\n\n  const server = new Server(\n    {\n      name: 'browse-tool',\n      version: '0.1.0',\n    },\n    {\n      capabilities: {\n        tools: {},\n      },\n    },\n  );\n\n  // Get all tools from the container\n  const tools = iocContainer.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n  // Build tool map for fast lookup\n  const toolMap = new Map<string, Tool>();\n  for (const tool of tools) {\n    const def = tool.getDefinition();\n    toolMap.set(def.name, tool);\n  }\n\n  logger.info('MCP server initialized', { toolCount: tools.length });\n\n  // List all available tools\n  server.setRequestHandler('tools/list', async () => {\n    const execute = async () => {\n      logger.debug('ListTools request received');\n      return {\n        tools: tools.map((tool) => toMcpListTool(tool.getDefinition())),\n      };\n    };\n\n    return await (logger.runInSpan?.('mcp.server.list_tools', { 'mcp.service': 'browse-tool' }, execute) ?? execute());\n  });\n\n  // Execute tool by name\n  server.setRequestHandler('tools/call', async (request) => {\n    const { name, arguments: args } = request.params;\n\n    const execute = async (): Promise<CallToolResult> => {\n      logger.debug('Tool call received', { toolName: name, timestamp: new Date().toISOString() });\n\n      const tool = toolMap.get(name);\n\n      if (!tool) {\n        const availableTools = Array.from(toolMap.keys());\n        logger.warn('Unknown tool requested', { toolName: name, availableTools, timestamp: new Date().toISOString() });\n        throw new UnknownToolError(name, availableTools);\n      }\n\n      const coerced = coerceArgs(args ?? {}, tool.getInputSchema());\n\n      try {\n        // Validate with Zod before executing\n        const parsed = tool.getInputSchema().parse(coerced);\n        return await tool.execute(parsed as Record<string, unknown>);\n      } catch (error) {\n        if (error instanceof z.ZodError) {\n          logger.warn('Tool input validation failed', { toolName: name, timestamp: new Date().toISOString() });\n          return {\n            content: [\n              { type: 'text', text: formatZodError(error, { schemaName: name, schema: tool.getInputSchema() }) },\n            ],\n            isError: true,\n          };\n        }\n\n        // Wrap in ToolExecutionError for consistent error handling\n        const toolError =\n          error instanceof ToolExecutionError\n            ? error\n            : new ToolExecutionError(name, error instanceof Error ? error.message : String(error), { cause: error });\n\n        logger.error('Tool execution failed', {\n          toolName: name,\n          error: toolError.message,\n          code: toolError.code,\n          timestamp: new Date().toISOString(),\n        });\n        return createErrorResponse(toolError, name);\n      }\n    };\n\n    return await (logger.runInSpan?.(\n      'mcp.server.call_tool',\n      { 'mcp.service': 'browse-tool', 'mcp.tool.name': name },\n      execute,\n    ) ?? execute());\n  });\n\n  return server;\n}\n"],"mappings":"8RAAA,IAAA,EAAe,y7GCSf,MAAa,EAA8B,CACzC,KAAM,0BACN,YAAa,oFACb,UAAW,CACT,CACE,KAAM,WACN,YAAa,6CACb,SAAU,GACX,CACD,CACE,KAAM,WACN,YAAa,iCACb,SAAU,GACX,CACD,CACE,KAAM,cACN,YAAa,qDACb,SAAU,GACX,CACF,CACF,CAQK,EAAS,IAAI,EAEnB,SAAgB,EACd,EACkE,CAClE,IAAM,EAAoB,EAAK,UAAU,MAAM,EAAI,mBAC7C,EAAc,EAAK,aAAa,MAAM,EAAI,kCAOhD,MAAO,CACL,CACE,KAAM,OACN,QAAS,CACP,KAAM,OACN,KAXO,EAAO,mBAAmBA,EAAgB,CACrD,SAAU,EAAK,SACf,oBACA,cACD,CAOS,CACL,CACF,CACF,CCZH,SAAS,EAAiB,EAAgC,CAKxD,OAJI,OAAO,GAAU,UAAY,OAAO,GAAU,UAAY,OAAO,GAAU,UACtE,EAGF,KAAK,UAAU,EAAM,CAG9B,SAAS,EAAa,EAA2D,CAC/E,GAAI,CAAC,EACH,OAGF,IAAM,EAAyB,EAAE,CACjC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAQ,CAC5C,GAAiC,OAIrC,EAAW,GAAO,EAAiB,EAAM,EAG3C,OAAO,OAAO,KAAK,EAAW,CAAC,OAAS,EAAI,EAAa,IAAA,GAG3D,SAAS,GAAgC,CACvC,IAAM,EAAY,IAAI,EAAiB,CACrC,YAAa,kBACd,CAAC,CAEF,MAAO,CACL,MAAM,EAAS,EAAS,CACtB,EAAU,IAAI,QAAS,EAAS,CAAE,WAAY,EAAa,EAAQ,CAAE,CAAC,EAExE,KAAK,EAAS,EAAS,CACrB,EAAU,IAAI,OAAQ,EAAS,CAAE,WAAY,EAAa,EAAQ,CAAE,CAAC,EAEvE,KAAK,EAAS,EAAS,CACrB,EAAU,IAAI,OAAQ,EAAS,CAAE,WAAY,EAAa,EAAQ,CAAE,CAAC,EAEvE,MAAM,EAAS,EAAS,CACtB,EAAU,IAAI,QAAS,EAAS,CAAE,WAAY,EAAa,EAAQ,CAAE,CAAC,EAExE,UAAU,EAAM,EAAS,EAAU,CACjC,OAAO,EAAU,UAAU,EAAM,CAAE,WAAY,EAAa,EAAQ,CAAE,CAAE,EAAS,EAEpF,CAWH,IAAa,EAAb,cAAsC,KAAM,CAC1C,KAAgB,eAChB,SAAoB,wCACpB,eAEA,YAAY,EAAkB,EAA0B,EAAwB,CAC9E,MACE,iBAAiB,EAAS,qBAAqB,EAAe,MAAM,EAAG,EAAE,CAAC,KAAK,KAAK,GAAG,EAAe,OAAS,EAAI,UAAU,EAAe,OAAO,SAAW,GAAG,6CACjK,EACD,CACD,KAAK,KAAO,mBACZ,KAAK,eAAiB,IAQb,EAAb,cAAwC,KAAM,CAC5C,KAAgB,uBAChB,SACA,SAEA,YAAY,EAAkB,EAAiB,EAAgD,CAC7F,MAAM,8BAA8B,EAAS,KAAK,IAAW,EAAQ,CACrE,KAAK,KAAO,qBACZ,KAAK,SAAW,EAChB,KAAK,SAAW,GAAS,UAAY,qCA6BzC,SAAS,EAAoB,EAAgB,EAAkC,CAC7E,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,yBAOnD,EAAgB,CACpB,MAAO,CACL,KARS,aAAiB,OAAS,SAAU,EAAS,EAA2B,KAAO,uBASxF,UACA,WACA,SATF,aAAiB,OAAS,aAAc,EACnC,EAA+B,SAChC,mCAQH,CACF,CAED,MAAO,CACL,QAAS,CACP,CACE,KAAM,OACN,KAAM,KAAK,UAAU,EAAe,KAAM,EAAE,CAC7C,CACF,CACD,QAAS,GACV,CAYH,SAAgB,EAAa,EAA+B,CAC1D,IAAM,EAAe,GAAQ,WAAaC,EACpC,EAAS,GAAQ,QAAU,GAAuB,CAElD,EAAS,IAAI,EACjB,CACE,KAAM,cACN,QAAS,QACV,CACD,CACE,aAAc,CACZ,MAAO,EAAE,CACV,CACF,CACF,CAGK,EAAQ,EAAa,OAAa,EAAiB,KAAK,CAGxD,EAAU,IAAI,IACpB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAK,eAAe,CAChC,EAAQ,IAAI,EAAI,KAAM,EAAK,CAwE7B,OArEA,EAAO,KAAK,yBAA0B,CAAE,UAAW,EAAM,OAAQ,CAAC,CAGlE,EAAO,kBAAkB,aAAc,SAAY,CACjD,IAAM,EAAU,UACd,EAAO,MAAM,6BAA6B,CACnC,CACL,MAAO,EAAM,IAAK,GAAS,EAAc,EAAK,eAAe,CAAC,CAAC,CAChE,EAGH,OAAO,MAAO,EAAO,YAAY,wBAAyB,CAAE,cAAe,cAAe,CAAE,EAAQ,EAAI,GAAS,GACjH,CAGF,EAAO,kBAAkB,aAAc,KAAO,IAAY,CACxD,GAAM,CAAE,OAAM,UAAW,GAAS,EAAQ,OAEpC,EAAU,SAAqC,CACnD,EAAO,MAAM,qBAAsB,CAAE,SAAU,EAAM,UAAW,IAAI,MAAM,CAAC,aAAa,CAAE,CAAC,CAE3F,IAAM,EAAO,EAAQ,IAAI,EAAK,CAE9B,GAAI,CAAC,EAAM,CACT,IAAM,EAAiB,MAAM,KAAK,EAAQ,MAAM,CAAC,CAEjD,MADA,EAAO,KAAK,yBAA0B,CAAE,SAAU,EAAM,iBAAgB,UAAW,IAAI,MAAM,CAAC,aAAa,CAAE,CAAC,CACxG,IAAI,EAAiB,EAAM,EAAe,CAGlD,IAAM,EAAU,EAAW,GAAQ,EAAE,CAAE,EAAK,gBAAgB,CAAC,CAE7D,GAAI,CAEF,IAAM,EAAS,EAAK,gBAAgB,CAAC,MAAM,EAAQ,CACnD,OAAO,MAAM,EAAK,QAAQ,EAAkC,OACrD,EAAO,CACd,GAAI,aAAiB,EAAE,SAErB,OADA,EAAO,KAAK,+BAAgC,CAAE,SAAU,EAAM,UAAW,IAAI,MAAM,CAAC,aAAa,CAAE,CAAC,CAC7F,CACL,QAAS,CACP,CAAE,KAAM,OAAQ,KAAM,EAAe,EAAO,CAAE,WAAY,EAAM,OAAQ,EAAK,gBAAgB,CAAE,CAAC,CAAE,CACnG,CACD,QAAS,GACV,CAIH,IAAM,EACJ,aAAiB,EACb,EACA,IAAI,EAAmB,EAAM,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAE,CAAE,MAAO,EAAO,CAAC,CAQ5G,OANA,EAAO,MAAM,wBAAyB,CACpC,SAAU,EACV,MAAO,EAAU,QACjB,KAAM,EAAU,KAChB,UAAW,IAAI,MAAM,CAAC,aAAa,CACpC,CAAC,CACK,EAAoB,EAAW,EAAK,GAI/C,OAAO,MAAO,EAAO,YACnB,uBACA,CAAE,cAAe,cAAe,gBAAiB,EAAM,CACvD,EACD,EAAI,GAAS,GACd,CAEK"}