@agimon-ai/browse-tool 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +11 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +23 -23
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{playwright-test-CURPs_G3.cjs → playwright-test-DfpyCIc0.cjs} +2 -2
- package/dist/{playwright-test-CURPs_G3.cjs.map → playwright-test-DfpyCIc0.cjs.map} +1 -1
- package/dist/{streamable-http-Bya6a5df.cjs → streamable-http-O6YYT3Yr.cjs} +11 -11
- package/dist/streamable-http-O6YYT3Yr.cjs.map +1 -0
- package/dist/streamable-http-_UIJOzAB.mjs +15 -0
- package/dist/streamable-http-_UIJOzAB.mjs.map +1 -0
- package/dist/stubs/playwright-test.cjs +1 -1
- package/package.json +4 -2
- package/dist/streamable-http-Bya6a5df.cjs.map +0 -1
- package/dist/streamable-http-CpuMUSOu.mjs +0 -15
- package/dist/streamable-http-CpuMUSOu.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`./streamable-http-
|
|
1
|
+
const e=require(`./streamable-http-O6YYT3Yr.cjs`);require(`./playwright-test-DfpyCIc0.cjs`);let t=require(`@modelcontextprotocol/sdk/server/index.js`),n=require(`@modelcontextprotocol/sdk/types.js`),r=require(`liquidjs`);function i(e){return typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:JSON.stringify(e)}function a(e){if(!e)return;let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=i(r));return Object.keys(t).length>0?t:void 0}function o(){let t=new e.x({serviceName:`browse-tool-mcp`});return{debug(e,n){t.log(`debug`,e,{attributes:a(n)})},info(e,n){t.log(`info`,e,{attributes:a(n)})},warn(e,n){t.log(`warn`,e,{attributes:a(n)})},error(e,n){t.log(`error`,e,{attributes:a(n)})}}}var s=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},c=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function l(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function u(r){let i=r?.container??e.r,a=r?.logger??o(),u=new t.Server({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),d=i.getAll(e.C.Tool),f=new Map;for(let e of d){let t=e.getDefinition();f.set(t.name,e)}return a.info(`MCP server initialized`,{toolCount:d.length}),u.setRequestHandler(n.ListToolsRequestSchema,async()=>(a.debug(`ListTools request received`),{tools:d.map(e=>e.getDefinition())})),u.setRequestHandler(n.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;a.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=f.get(t);if(!r){let e=Array.from(f.keys());throw a.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new s(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof c?e:new c(t,e instanceof Error?e.message:String(e),{cause:e});return a.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),l(n,t)}}),u}var d='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';const f={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},p=new r.Liquid;function m(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:p.parseAndRenderSync(d,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}exports.PLAYWRIGHT_TYPES=e.C,exports.StdioTransportHandler=e.n,exports.StreamableHttpTransportHandler=e.t,exports.ToolExecutionError=c,exports.UnknownToolError=s,exports.container=e.r,exports.createContainer=e.i,exports.createServer=u,exports.customScriptAuthoringPrompt=f,exports.generateCustomScriptAuthoringPrompt=m;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["defaultContainer","Server","PLAYWRIGHT_TYPES","ListToolsRequestSchema","CallToolRequestSchema","Liquid","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport type { Tool } from '../types/index.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}\n\n/**\n * Creates a no-op logger for silent operation.\n * @returns A logger implementation that discards all messages\n */\nfunction createNoopLogger(): Logger {\n const noop = (): void => undefined;\n return { debug: noop, info: noop, warn: noop, error: noop };\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 ?? createNoopLogger();\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(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\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 try {\n return await tool.execute(args);\n } catch (error) {\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 server;\n}\n","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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["attributes: Attributes","TelemetryService","defaultContainer","Server","PLAYWRIGHT_TYPES","ListToolsRequestSchema","CallToolRequestSchema","Liquid","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Attributes, AttributeValue } from '@opentelemetry/api';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.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}\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 };\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(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\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 try {\n return await tool.execute(args);\n } catch (error) {\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 server;\n}\n","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"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{C as e,i as t,n,r,t as i}from"./streamable-http-
|
|
1
|
+
import{C as e,i as t,n,r,t as i,x as a}from"./streamable-http-_UIJOzAB.mjs";import"./playwright-test-yNsP599T.mjs";import{Server as o}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as s,ListToolsRequestSchema as c}from"@modelcontextprotocol/sdk/types.js";import{Liquid as l}from"liquidjs";function u(e){return typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:JSON.stringify(e)}function d(e){if(!e)return;let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=u(r));return Object.keys(t).length>0?t:void 0}function f(){let e=new a({serviceName:`browse-tool-mcp`});return{debug(t,n){e.log(`debug`,t,{attributes:d(n)})},info(t,n){e.log(`info`,t,{attributes:d(n)})},warn(t,n){e.log(`warn`,t,{attributes:d(n)})},error(t,n){e.log(`error`,t,{attributes:d(n)})}}}var p=class extends Error{code=`UNKNOWN_TOOL`;recovery=`Use ListTools to see available tools.`;availableTools;constructor(e,t,n){super(`Unknown tool: ${e}. Available tools: ${t.slice(0,5).join(`, `)}${t.length>5?`, ... (${t.length} total)`:``}. Use ListTools to see all available tools.`,n),this.name=`UnknownToolError`,this.availableTools=t}},m=class extends Error{code=`TOOL_EXECUTION_ERROR`;recovery;toolName;constructor(e,t,n){super(`Tool execution failed for '${e}': ${t}`,n),this.name=`ToolExecutionError`,this.toolName=e,this.recovery=n?.recovery??`Check tool inputs and try again.`}};function h(e,t){let n=e instanceof Error?e.message:`Unknown error occurred`,r={error:{code:e instanceof Error&&`code`in e?e.code:`TOOL_EXECUTION_ERROR`,message:n,toolName:t,recovery:e instanceof Error&&`recovery`in e?e.recovery:`Check tool inputs and try again.`}};return{content:[{type:`text`,text:JSON.stringify(r,null,2)}],isError:!0}}function g(t){let n=t?.container??r,i=t?.logger??f(),a=new o({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),l=n.getAll(e.Tool),u=new Map;for(let e of l){let t=e.getDefinition();u.set(t.name,e)}return i.info(`MCP server initialized`,{toolCount:l.length}),a.setRequestHandler(c,async()=>(i.debug(`ListTools request received`),{tools:l.map(e=>e.getDefinition())})),a.setRequestHandler(s,async e=>{let{name:t,arguments:n}=e.params;i.debug(`Tool call received`,{toolName:t,timestamp:new Date().toISOString()});let r=u.get(t);if(!r){let e=Array.from(u.keys());throw i.warn(`Unknown tool requested`,{toolName:t,availableTools:e,timestamp:new Date().toISOString()}),new p(t,e)}try{return await r.execute(n)}catch(e){let n=e instanceof m?e:new m(t,e instanceof Error?e.message:String(e),{cause:e});return i.error(`Tool execution failed`,{toolName:t,error:n.message,code:n.code,timestamp:new Date().toISOString()}),h(n,t)}}),a}var _='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';const v={name:`custom_script_authoring`,description:`Generate a browse-tool custom tool folder with tools.yaml and TypeScript scripts.`,arguments:[{name:`toolGoal`,description:`What the custom tool should do on the page`,required:!0},{name:`toolName`,description:`Preferred snake_case tool name`,required:!1},{name:`pageContext`,description:`Optional context about the target page or workflow`,required:!1}]},y=new l;function b(e){let t=e.toolName?.trim()||`custom_page_tool`,n=e.pageContext?.trim()||`No extra page context provided.`;return[{role:`user`,content:{type:`text`,text:y.parseAndRenderSync(_,{toolGoal:e.toolGoal,preferredToolName:t,pageContext:n})}}]}export{e as PLAYWRIGHT_TYPES,n as StdioTransportHandler,i as StreamableHttpTransportHandler,m as ToolExecutionError,p as UnknownToolError,r as container,t as createContainer,g as createServer,v as customScriptAuthoringPrompt,b as generateCustomScriptAuthoringPrompt};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["defaultContainer","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport type { Tool } from '../types/index.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}\n\n/**\n * Creates a no-op logger for silent operation.\n * @returns A logger implementation that discards all messages\n */\nfunction createNoopLogger(): Logger {\n const noop = (): void => undefined;\n return { debug: noop, info: noop, warn: noop, error: noop };\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 ?? createNoopLogger();\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(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\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 try {\n return await tool.execute(args);\n } catch (error) {\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 server;\n}\n","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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["attributes: Attributes","defaultContainer","promptTemplate"],"sources":["../src/server/index.ts","../src/prompts/instructions/custom-script-authoring.md?raw","../src/prompts/CustomScriptAuthoringPrompt.ts"],"sourcesContent":["/**\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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport type { Attributes, AttributeValue } from '@opentelemetry/api';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES, container as defaultContainer } from '../container/index.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.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}\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 };\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(ListToolsRequestSchema, async () => {\n logger.debug('ListTools request received');\n return {\n tools: tools.map((tool) => tool.getDefinition()),\n };\n });\n\n // Execute tool by name\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\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 try {\n return await tool.execute(args);\n } catch (error) {\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 server;\n}\n","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"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const e=require(`./streamable-http-
|
|
1
|
+
const e=require(`./streamable-http-O6YYT3Yr.cjs`);let t=require(`node:module`);const n=Symbol.for(`__locatorProxyBrand__`);var r=class e{[n]=!0;constructor(e,t){this.page=e,this.steps=t}getByRole(t,n){return new e(this.page,[...this.steps,{type:`role`,role:t,options:n}])}getByText(t,n){return new e(this.page,[...this.steps,{type:`text`,text:t,options:n}])}getByLabel(t,n){return new e(this.page,[...this.steps,{type:`label`,text:t,options:n}])}getByPlaceholder(t,n){return new e(this.page,[...this.steps,{type:`placeholder`,text:t,options:n}])}getByTestId(t){return new e(this.page,[...this.steps,{type:`testId`,testId:t}])}locator(t){return new e(this.page,[...this.steps,{type:`css`,selector:t}])}filter(t){return new e(this.page,[...this.steps,{type:`filter`,options:t}])}first(){return new e(this.page,[...this.steps,{type:`first`}])}last(){return this.nth(-1)}nth(t){return new e(this.page,[...this.steps,{type:`nth`,index:t}])}async click(e){let t=await this.resolveAndMark(e?.timeout);await this.page.click(t)}async fill(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.fill(n,e)}async type(e,t){let n=await this.resolveAndMark(t?.timeout);await this.page.type(n,e,{delay:t?.delay})}async press(e){let t=await this.resolveAndMark();await this.page.click(t),await this.page.press(e)}async hover(e){let t=await this.resolveAndMark(e?.timeout);await this.page.hover(t)}async selectOption(e){let t=await this.resolveAndMark();await this.page.selectOption(t,e)}async check(){let e=await this.resolveAndMark();await this.page.click(e)}async uncheck(){let e=await this.resolveAndMark();await this.page.click(e)}async isVisible(){let e=this.buildResolveScript(`isVisible`);return await this.page.evaluate(e)??!1}async isHidden(){return!await this.isVisible()}async isEnabled(){return!await this.evaluateProperty(`disabled`)}async isDisabled(){return!!await this.evaluateProperty(`disabled`)}async isChecked(){return!!await this.evaluateProperty(`checked`)}async evaluateProperty(e){let t=this.buildResolveScript(`getProperty`,e);return this.page.evaluate(t)}async textContent(){let e=this.buildResolveScript(`textContent`);return this.page.evaluate(e)}async innerText(){let e=this.buildResolveScript(`innerText`);return await this.page.evaluate(e)??``}async inputValue(){let e=this.buildResolveScript(`inputValue`);return await this.page.evaluate(e)??``}async count(){let e=this.buildResolveScript(`count`);return await this.page.evaluate(e)??0}async waitFor(e){let t=e?.timeout??5e3,n=e?.state??`visible`,r=Date.now();for(;Date.now()-r<t;){if(n===`visible`&&await this.isVisible()||n===`hidden`&&!await this.isVisible()||n===`attached`&&await this.count()>0||n===`detached`&&await this.count()===0)return;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator waitFor("${n}") timed out after ${t}ms`)}async resolveAndMark(e){let t=e??5e3,n=Date.now(),r=`pw-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;for(;Date.now()-n<t;){let e=this.buildResolveScript(`mark`,r);if(await this.page.evaluate(e))return`[data-pw-proxy="${r}"]`;await new Promise(e=>setTimeout(e,100))}throw Error(`Locator could not resolve element within ${t}ms. Steps: ${this.describeSteps()}`)}describeSteps(){return this.steps.map(e=>{switch(e.type){case`role`:return`getByRole("${e.role}"${e.options?.name?`, { name: "${String(e.options.name)}" }`:``})`;case`text`:return`getByText("${String(e.text)}")`;case`label`:return`getByLabel("${String(e.text)}")`;case`placeholder`:return`getByPlaceholder("${String(e.text)}")`;case`testId`:return`getByTestId("${e.testId}")`;case`css`:return`locator("${e.selector}")`;case`filter`:return`filter(...)`;case`first`:return`first()`;case`nth`:return`nth(${e.index})`}}).join(`.`)}serializeSteps(){return this.steps.map(e=>{if(e.type===`text`||e.type===`label`||e.type===`placeholder`)return{...e,text:e.text instanceof RegExp?{__regex__:e.text.source,flags:e.text.flags}:e.text};if(e.type===`role`&&e.options?.name instanceof RegExp)return{...e,options:{...e.options,name:{__regex__:e.options.name.source,flags:e.options.name.flags}}};if(e.type===`filter`){let t={};return e.options.hasText&&(t.hasText=e.options.hasText instanceof RegExp?{__regex__:e.options.hasText.source,flags:e.options.hasText.flags}:e.options.hasText),e.options.has&&(t.hasSteps=e.options.has.serializeSteps()),{type:`filter`,options:t}}return e})}buildResolveScript(e,t){return`(function() {
|
|
2
2
|
var steps = ${JSON.stringify(this.serializeSteps())};
|
|
3
3
|
var action = "${e}";
|
|
4
4
|
var extra = ${t===void 0?`null`:JSON.stringify(t)};
|
|
@@ -250,4 +250,4 @@ const e=require(`./streamable-http-Bya6a5df.cjs`);let t=require(`node:module`);c
|
|
|
250
250
|
|
|
251
251
|
return null;
|
|
252
252
|
})()`}};const i=Symbol.for(`__pageProxyBrand__`),a=100,o=5e3;async function s(e,t,n=5e3){let r=Date.now();for(;Date.now()-r<n;){if(await e())return;await new Promise(e=>setTimeout(e,100))}throw Error(t)}function c(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e===t}function l(e,t){return e==null?!1:t instanceof RegExp?t.test(e):e.includes(t)}function u(e,t){let n=e=>t?!e:e;return{async toBeVisible(r){await s(async()=>n(await e.isVisible()),t?`Expected element to not be visible`:`Expected element to be visible`,r?.timeout)},async toBeHidden(r){await s(async()=>n(!await e.isVisible()),t?`Expected element to not be hidden`:`Expected element to be hidden`,r?.timeout)},async toBeDisabled(r){await s(async()=>n(!!await e.evaluateProperty(`disabled`)),t?`Expected element to not be disabled`:`Expected element to be disabled`,r?.timeout)},async toBeEnabled(r){await s(async()=>n(!await e.evaluateProperty(`disabled`)),t?`Expected element to not be enabled`:`Expected element to be enabled`,r?.timeout)},async toBeChecked(r){await s(async()=>n(!!await e.evaluateProperty(`checked`)),t?`Expected element to not be checked`:`Expected element to be checked`,r?.timeout)},async toHaveValue(r,i){await s(async()=>n(c(await e.evaluateProperty(`value`),r)),t?`Expected element to not have value "${String(r)}"`:`Expected element to have value "${String(r)}"`,i?.timeout)},async toHaveText(r,i){await s(async()=>n(c((await e.textContent())?.trim()??null,r)),t?`Expected element to not have text "${String(r)}"`:`Expected element to have text "${String(r)}"`,i?.timeout)},async toContainText(r,i){await s(async()=>n(l(await e.textContent(),r)),t?`Expected element to not contain text "${String(r)}"`:`Expected element to contain text "${String(r)}"`,i?.timeout)},async toHaveCount(r,i){await s(async()=>n(await e.count()===r),t?`Expected element count to not be ${r}`:`Expected element count to be ${r}`,i?.timeout)},get not(){return u(e,!t)}}}function d(e,t){let n=e=>t?!e:e;return{async toHaveURL(r,i){await s(async()=>n(c(await e.currentUrlAsync(),r)),t?`Expected page to not have URL "${String(r)}"`:`Expected page to have URL "${String(r)}"`,i?.timeout)},async toHaveTitle(r,i){await s(async()=>n(c(await e.title(),r)),t?`Expected page to not have title "${String(r)}"`:`Expected page to have title "${String(r)}"`,i?.timeout)},get not(){return d(e,!t)}}}function f(e){let t=e;if(t[n])return u(e,!1);if(t[i])return d(e,!1);throw Error(`createExtensionExpect called with unsupported target`)}function p(){try{return(0,t.createRequire)(require(`url`).pathToFileURL(__filename).href).resolve(`playwright/test`),require(`url`).pathToFileURL(__filename).href}catch{return typeof __PLAYWRIGHT_MCP_STUB_PATH__==`string`?`file://${__PLAYWRIGHT_MCP_STUB_PATH__}`:require(`url`).pathToFileURL(__filename).href}}const m=process,h=m.__pw_initiator__;m.__pw_initiator__=void 0;const g=(0,t.createRequire)(p())(`playwright/test`),{expect:_}=g;m.__pw_initiator__=h;const{request:v,chromium:y,firefox:b,webkit:x,devices:S,selectors:C,defineConfig:w,mergeExpects:T,mergeTests:E}=g;var D=class e{constructor(e,t=null){this.defs=e,this.parent=t}getAllDefs(){let e=this.parent?.getAllDefs()??new Map;return new Map([...e,...this.defs])}extend(t){return new e(t,this)}},O=class{currentSpecPath=``;rootDescribe;currentDescribe;constructor(){this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}createDescribeBlock(e,t){return{title:e,tests:[],beforeEachHooks:[],afterEachHooks:[],children:[],parent:t,fixtureScope:null}}setSpecPath(e){this.currentSpecPath=e}reset(){this.currentSpecPath=``,this.rootDescribe=this.createDescribeBlock(``,null),this.currentDescribe=this.rootDescribe}addTest(e,t,n=!1,r=!1){let i={title:e,fn:t,fullTitle:this.getFullTitle(e),only:n,skip:r};return this.currentDescribe.tests.push(i),i}ensureFixtureScope(e){if(e.size===0||this.currentDescribe.fixtureScope)return;let t=null,n=this.currentDescribe.parent;for(;n;){if(n.fixtureScope){t=n.fixtureScope;break}n=n.parent}this.currentDescribe.fixtureScope=new D(e,t)}enterDescribe(e){let t=this.createDescribeBlock(e,this.currentDescribe);this.currentDescribe.children.push(t),this.currentDescribe=t}exitDescribe(){this.currentDescribe.parent&&(this.currentDescribe=this.currentDescribe.parent)}addBeforeAll(e){this.currentDescribe.beforeAllHooks||(this.currentDescribe.beforeAllHooks=[]),this.currentDescribe.beforeAllHooks.push(e)}addAfterAll(e){this.currentDescribe.afterAllHooks||(this.currentDescribe.afterAllHooks=[]),this.currentDescribe.afterAllHooks.push(e)}addBeforeEach(e){this.currentDescribe.beforeEachHooks.push(e)}addAfterEach(e){this.currentDescribe.afterEachHooks.push(e)}markCurrentDescribeSkipped(){for(let e of this.currentDescribe.tests)e.skip=!0;for(let e of this.currentDescribe.children)this.markDescribeSkipped(e)}markDescribeSkipped(e){for(let t of e.tests)t.skip=!0;for(let t of e.children)this.markDescribeSkipped(t)}getFullTitle(e){let t=[],n=this.currentDescribe;for(;n;)n.title&&t.unshift(n.title),n=n.parent;return t.push(e),t.join(` > `)}collectFixtureDefs(e){return e.fixtureScope?.getAllDefs()??new Map}flattenTests(e,t,n){let r=[],i=[...t,...e.beforeEachHooks],a=[...e.afterEachHooks,...n],o=e.beforeAllHooks??[],s=e.afterAllHooks??[],c=P(this.collectFixtureDefs(e)),l=!1,u=[];for(let t of e.tests)u.push({title:t.title,fullTitle:t.fullTitle,fn:c(async e=>{if(!l&&o.length>0){l=!0;for(let t of o)await t(e)}for(let t of i)await t(e);await t.fn(e);for(let t of a)await t(e)}),only:t.only,skip:t.skip});for(let t of e.children)u.push(...this.flattenTests(t,i,a));if(s.length>0&&u.length>0){let e=u[u.length-1],t=e.fn;e.fn=async e=>{try{await t(e)}finally{for(let t of s)await t(e)}}}return r.push(...u),r}retrieveAndReset(){let e=this.flattenTests(this.rootDescribe,[],[]),t={specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length};return this.reset(),t}peek(){let e=this.flattenTests(this.rootDescribe,[],[]);return{specPath:this.currentSpecPath,root:this.rootDescribe,allTests:e,testCount:e.length}}},k=class extends Error{isSkip=!0;constructor(e){super(e??`Skipped`),this.name=`SkipTestError`}};const A=`__playwrightMcpTestCollector__`,j=globalThis;j[A]||(j[A]=new O);const M=j[A];function N(e){let t=new Map;for(let[n,r]of Object.entries(e))typeof r==`function`?t.set(n,r):Array.isArray(r)&&r.length===2&&typeof r[1]==`function`&&t.set(n,r[1]);return t}function P(e){if(e.size===0)return e=>e;let t=[...e.entries()];return e=>async n=>{let r={...n};async function i(n){if(n>=t.length){await e(r);return}let[a,o]=t[n];await o(r,async e=>{r[a]=e,await i(n+1)})}await i(0)}}function F(e){function t(){M.ensureFixtureScope(e)}function n(e,n){t(),M.addTest(e,n)}return n.only=function(e,n){t(),M.addTest(e,n,!0,!1)},n.skip=function(e,n){if(e===void 0||e===!0)throw new k(typeof n==`string`?n:`Skipped`);e!==!1&&typeof e==`string`&&typeof n==`function`&&(t(),M.addTest(e,n,!1,!0))},n.describe=function(e,t){M.enterDescribe(e);try{t()}catch(e){if(e instanceof k)M.markCurrentDescribeSkipped();else throw e}M.exitDescribe()},n.beforeAll=function(e){t(),M.addBeforeAll(e)},n.afterAll=function(e){t(),M.addAfterAll(e)},n.beforeEach=function(e){t(),M.addBeforeEach(e)},n.afterEach=function(e){t(),M.addAfterEach(e)},n.extend=function(t){return F(new Map([...e,...N(t)]))},n}function I(e,t){M.addTest(e,t)}function L(e,t){M.addTest(e,t,!0,!1)}function R(e,t){if(e===void 0||e===!0)throw new k(typeof t==`string`?t:`Skipped`);e!==!1&&typeof e==`string`&&typeof t==`function`&&M.addTest(e,t,!1,!0)}function z(e,t){M.enterDescribe(e);try{t()}catch(e){if(e instanceof k)M.markCurrentDescribeSkipped();else throw e}M.exitDescribe()}function B(e){M.addBeforeAll(e)}function V(e){M.addAfterAll(e)}function H(e){M.addBeforeEach(e)}function U(e){M.addAfterEach(e)}I.describe=z,I.beforeAll=B,I.afterAll=V,I.beforeEach=H,I.afterEach=U,I.only=L,I.skip=R,I.extend=function(e){return F(N(e))};function W(e){M.reset(),M.setSpecPath(e)}function G(){return M.retrieveAndReset()}function K(){return M.peek()}function q(){M.reset()}const J=(e=>{let t=e;return t?.[n]||t?.[i]?f(e):_(e)});Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return J}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return r}});
|
|
253
|
-
//# sourceMappingURL=playwright-test-
|
|
253
|
+
//# sourceMappingURL=playwright-test-DfpyCIc0.cjs.map
|