browser-devtools-mcp 0.5.1 → 0.5.3
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/README.md +18 -13
- package/dist/cli/runner.js +22 -7
- package/dist/core-P5HBF7J2.js +17 -0
- package/dist/core-UCG5EXSY.js +36 -0
- package/dist/{core-PKYX4YOY.js → core-VG5O43O7.js} +76 -76
- package/dist/{core-5DYSM5YY.js → core-X7CLDI7H.js} +1 -1
- package/dist/daemon-server.js +1 -1
- package/dist/index.js +2 -2
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/types.d.ts +11 -0
- package/package.json +5 -5
- package/dist/core-3YBKJFSF.js +0 -17
- package/dist/core-UNMLWWDL.js +0 -36
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,BROWSER_CDP_CONNECT_URL,BROWSER_CDP_ENDPOINT_EXPLICIT,BROWSER_CDP_OPEN_INSPECT,BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE,BROWSER_EXECUTABLE_PATH,BROWSER_HEADLESS_ENABLE,BROWSER_HTTP_REQUESTS_BUFFER_SIZE,BROWSER_LOCALE,BROWSER_PERSISTENT_ENABLE,BROWSER_PERSISTENT_USER_DATA_DIR,BROWSER_POLICY_UI_DEBUGGING_ENABLE,BROWSER_SERVER_INSTRUCTIONS_ENABLE,BROWSER_USE_INSTALLED_ON_SYSTEM,ConsoleMessageLevel,ConsoleMessageLevelName,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,NODE_CONSOLE_MESSAGES_BUFFER_SIZE,NODE_INSPECTOR_HOST,NODE_POLICY_DEBUGGING_ENABLE,NODE_SERVER_INSTRUCTIONS_ENABLE,OTEL_ASSETS_DIR,OTEL_ENABLE,OTEL_EXPORTER_HTTP_HEADERS,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION,PLATFORM,SourceMapResolver,V8Api,addWatchExpression,clearWatchExpressions,createProbe,debug,detachDebugging,enableDebugging,getConsoleMessages,getExceptionBreakpoint,getSnapshotStats,getSnapshots,getSnapshotsByProbe,getStoreKey,hasSourceMaps,isDebuggingEnabled,listProbes,listWatchExpressions,removeProbe,removeWatchExpression,resolveSourceLocation,setExceptionBreakpoint,toJson,warn}from"./core-
|
|
1
|
+
import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,BROWSER_CDP_CONNECT_URL,BROWSER_CDP_ENDPOINT_EXPLICIT,BROWSER_CDP_OPEN_INSPECT,BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE,BROWSER_EXECUTABLE_PATH,BROWSER_HEADLESS_ENABLE,BROWSER_HTTP_REQUESTS_BUFFER_SIZE,BROWSER_LOCALE,BROWSER_PERSISTENT_ENABLE,BROWSER_PERSISTENT_USER_DATA_DIR,BROWSER_POLICY_UI_DEBUGGING_ENABLE,BROWSER_SERVER_INSTRUCTIONS_ENABLE,BROWSER_USE_INSTALLED_ON_SYSTEM,ConsoleMessageLevel,ConsoleMessageLevelName,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,NODE_CONSOLE_MESSAGES_BUFFER_SIZE,NODE_INSPECTOR_HOST,NODE_POLICY_DEBUGGING_ENABLE,NODE_SERVER_INSTRUCTIONS_ENABLE,OTEL_ASSETS_DIR,OTEL_ENABLE,OTEL_EXPORTER_HTTP_HEADERS,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION,PLATFORM,SourceMapResolver,TOOL_INPUT_METADATA_ENABLE,V8Api,addWatchExpression,clearWatchExpressions,createProbe,debug,denormalizeToolName,detachDebugging,enableDebugging,getConsoleMessages,getExceptionBreakpoint,getSnapshotStats,getSnapshots,getSnapshotsByProbe,getStoreKey,hasSourceMaps,isDebuggingEnabled,listProbes,listWatchExpressions,removeProbe,removeWatchExpression,resolveSourceLocation,setExceptionBreakpoint,toJson,warn}from"./core-P5HBF7J2.js";var R_DISCOVERY_REMOTE="Check BROWSER_CDP_ENDPOINT_URL, host reachability, and firewall.",R_CONNECT_GENERIC="Allow the Chrome remote-debugging prompt if shown. If attach still fails, open chrome://inspect/#remote-debugging and confirm debugging is on.",CdpDiscoveryFailedError=class extends Error{constructor(detailLine,loopbackAttach){super(`[CDP discovery] ${detailLine}`);this.detailLine=detailLine;this.loopbackAttach=loopbackAttach;this.name="CdpDiscoveryFailedError"}};function cdpDiscoveryAttachHint(chromeRunning,openedInspectPage){return chromeRunning?`Chrome is running but remote debugging is not active on the probed port(s). ${openedInspectPage?"Opened chrome://inspect/#remote-debugging \u2014 enable remote debugging, then retry.":"Open chrome://inspect/#remote-debugging in Chrome, enable remote debugging, then retry."} Alternatively start Chrome with --remote-debugging-port=9222 or set BROWSER_CDP_ENDPOINT_URL.`:"Google Chrome does not appear to be running. Start Chrome, then enable remote debugging at chrome://inspect/#remote-debugging or launch with --remote-debugging-port=9222, then retry."}function cdpConnectAttachHint(chromeRunning,openedInspectPage){return chromeRunning?openedInspectPage?"If chrome://inspect/#remote-debugging opened in Chrome, enable remote debugging and retry. Approve any remote-debugging prompt.":"Open chrome://inspect/#remote-debugging, enable remote debugging, then retry. Or start Chrome with --remote-debugging-port=9222.":"Google Chrome does not appear to be running. Start Chrome, then enable remote debugging (chrome://inspect/#remote-debugging or --remote-debugging-port=9222), then retry."}function cdpDiscoveryError(detail){return new Error(`[CDP discovery] ${detail}
|
|
2
2
|
\u2192 ${R_DISCOVERY_REMOTE}`)}function cdpConnectError(endpointSummary,cause,chromeRunning,openedInspectPage){let hint=chromeRunning===void 0?R_CONNECT_GENERIC:cdpConnectAttachHint(chromeRunning,openedInspectPage??!1);return new Error(`[CDP connect] ${cause}
|
|
3
3
|
\u2192 endpoint: ${endpointSummary}
|
|
4
4
|
\u2192 ${hint}`)}import net from"node:net";import{execSync}from"node:child_process";function hostPortFromCdpConnectRef(urlStr){let raw=typeof urlStr=="string"?urlStr.trim():"";if(!raw)throw cdpDiscoveryError("Connect URL is empty after endpoint resolution.");let withScheme=raw.startsWith("ws:")||raw.startsWith("wss:")?`http${raw.slice(raw.indexOf(":"))}`:raw.startsWith("http")?raw:`http://${raw}`,u;try{u=new URL(withScheme)}catch{throw cdpDiscoveryError(`Invalid URL "${raw.slice(0,120)}". Expected http://host:port or ws://host:port/...`)}let port=u.port?parseInt(u.port,10):u.protocol==="https:"?443:80;return{host:u.hostname,port}}function isLoopbackHost(host){let h=host.toLowerCase();return h==="localhost"||h==="127.0.0.1"||h==="::1"||h==="[::1]"}function isChromeRunning(){try{return process.platform==="win32"?execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH',{stdio:"pipe",encoding:"utf8"}).toLowerCase().includes("chrome.exe"):execSync("pgrep -x 'Google Chrome' || pgrep -x chrome || pgrep -x chromium || true",{stdio:"pipe",encoding:"utf8"}).trim().length>0}catch{return!1}}function openChromeRemoteDebuggingPage(){let url="chrome://inspect/#remote-debugging";try{if(process.platform==="darwin")execSync(`open -a "Google Chrome" "${url}"`,{stdio:"pipe",timeout:5e3});else if(process.platform==="win32")execSync(`start chrome "${url}"`,{stdio:"pipe",timeout:5e3});else try{execSync("google-chrome chrome://inspect/#remote-debugging",{stdio:"pipe",timeout:5e3})}catch{execSync("chromium chrome://inspect/#remote-debugging",{stdio:"pipe",timeout:5e3})}return!0}catch{return!1}}var DEFAULT_TIMEOUT_MS=5e3,CDP_PROBE_PORTS=[9222,9229];function _bracketIpv6(host){return host.includes(":")&&!host.startsWith("[")?`[${host}]`:host}function _rewriteWsHost(wsUrl,host,port){try{let u=new URL(wsUrl);return u.hostname=host.includes(":")?_bracketIpv6(host):host,u.port=String(port),u.toString()}catch{return wsUrl}}function _parseHttpCdpRoot(urlStr){try{let u=new URL(urlStr);if(u.protocol!=="http:"&&u.protocol!=="https:")return null;let port=u.port?parseInt(u.port,10):u.protocol==="https:"?443:80;return{host:u.hostname,port}}catch{return null}}async function _fetchText(url,timeoutMs){let ctrl=new AbortController,id=setTimeout(()=>{ctrl.abort()},timeoutMs);try{return await(await fetch(url,{signal:ctrl.signal})).text()}finally{clearTimeout(id)}}function _browserWsUrl(host,port){return`ws://${_bracketIpv6(host)}:${port}/devtools/browser`}function _tcpPortOpen(host,port,timeoutMs){return new Promise(resolve=>{let settled=!1,done=ok=>{settled||(settled=!0,clearTimeout(timer),resolve(ok))},sock=net.createConnection({host,port},()=>{sock.end(),done(!0)});sock.on("error",()=>{done(!1)});let timer=setTimeout(()=>{sock.destroy(),done(!1)},timeoutMs)})}async function _httpDiscoverWsUrl(host,port,timeoutMs){let b=_bracketIpv6(host);try{let body=await _fetchText(`http://${b}:${port}/json/version`,timeoutMs),info=JSON.parse(body);if(info.webSocketDebuggerUrl)return _rewriteWsHost(info.webSocketDebuggerUrl,host,port)}catch{}try{let body=await _fetchText(`http://${b}:${port}/json/list`,timeoutMs),targets=JSON.parse(body),ws=(targets.find(t=>t.type==="browser")??targets[0])?.webSocketDebuggerUrl;if(ws)return _rewriteWsHost(ws,host,port)}catch{}return null}async function _connectUrlForHostPort(host,port,timeoutMs=DEFAULT_TIMEOUT_MS){let fromHttp=await _httpDiscoverWsUrl(host,port,timeoutMs);return fromHttp||_browserWsUrl(host,port)}async function _tryLoopbackProbePorts(timeoutMs=DEFAULT_TIMEOUT_MS){let host="127.0.0.1";for(let port of CDP_PROBE_PORTS){let fromHttp=await _httpDiscoverWsUrl(host,port,timeoutMs);if(fromHttp)return{connectUrl:fromHttp,port}}for(let port of CDP_PROBE_PORTS)if(await _tcpPortOpen(host,port,timeoutMs))return{connectUrl:_browserWsUrl(host,port),port};return null}async function resolveCdpConnectEndpoint(options){let configuredHttpOrWsUrl=options.configuredHttpOrWsUrl;if(!options.explicitEndpointUrl){let found=await _tryLoopbackProbePorts();if(found)return{connectUrl:found.connectUrl,cacheKey:`cdp:127.0.0.1:${found.port}`};throw new CdpDiscoveryFailedError("No CDP on 127.0.0.1:9222 or :9229.",!0)}let url=configuredHttpOrWsUrl;if(url.startsWith("ws://")||url.startsWith("wss://"))return{connectUrl:url,cacheKey:url};let parsed=_parseHttpCdpRoot(url);if(!parsed)throw cdpDiscoveryError(`BROWSER_CDP_ENDPOINT_URL must be http(s)://host:port or ws(s)://\u2026, got: ${configuredHttpOrWsUrl}`);try{return{connectUrl:await _connectUrlForHostPort(parsed.host,parsed.port),cacheKey:url}}catch(e){let detail=e instanceof Error?e.message:String(e),line=`No DevTools at ${parsed.host}:${parsed.port}. ${detail}`;throw isLoopbackHost(parsed.host)?new CdpDiscoveryFailedError(line,!0):cdpDiscoveryError(line)}}import fs from"node:fs";import{chromium,firefox,webkit}from"playwright";var DEFAULT_BROWSER_TYPE="chromium",browsers=new Map,persistenceBrowserContexts=new Map,cdpByEndpoint=new Map;function _browserKey(browserOptions){return JSON.stringify(browserOptions)}function _browserLaunchOptions(browserOptions){let launchOptions={headless:browserOptions.headless,executablePath:browserOptions.executablePath,handleSIGINT:!1,handleSIGTERM:!1};if(browserOptions.useInstalledOnSystem)if(browserOptions.browserType==="chromium")launchOptions.channel="chrome",launchOptions.args=["--disable-blink-features=AutomationControlled"],launchOptions.ignoreDefaultArgs=["--disable-extensions"];else throw new Error(`Browser type ${browserOptions.browserType} is not supported to be used from the one installed on the system`);return launchOptions}async function _createBrowser(browserOptions){let browserInstance;switch(browserOptions.browserType){case"firefox":browserInstance=firefox;break;case"webkit":browserInstance=webkit;break;default:browserInstance=chromium;break}return browserInstance.launch(_browserLaunchOptions(browserOptions))}async function _getBrowser(browserOptions){let browserKey=_browserKey(browserOptions),browserInstance=browsers.get(browserKey);if(browserInstance&&!browserInstance.isConnected()){try{await browserInstance.close().catch(()=>{})}catch{}browserInstance=void 0}return browserInstance||(browserInstance=await _createBrowser(browserOptions),browsers.set(browserKey,browserInstance)),browserInstance}function _persistentBrowserContextKey(browserContextOptions){return browserContextOptions.persistent.userDataDir}function _persistentBrowserContextLaunchOptions(browserContextOptions){let browserOptions=browserContextOptions.browserOptions,launchOptions={headless:browserOptions.headless,executablePath:browserOptions.executablePath,bypassCSP:!0,viewport:browserOptions.headless?void 0:null,locale:browserContextOptions.locale};if(browserOptions.useInstalledOnSystem)if(browserOptions.browserType==="chromium")launchOptions.channel="chrome",launchOptions.args=["--disable-blink-features=AutomationControlled"],launchOptions.ignoreDefaultArgs=["--disable-extensions"];else throw new Error(`Browser type ${browserOptions.browserType} is not supported to be used from the one installed on the system`);return launchOptions}async function _createPersistentBrowserContext(browserContextOptions){let browserInstance;switch(browserContextOptions.browserOptions.browserType){case"firefox":browserInstance=firefox;break;case"webkit":browserInstance=webkit;break;default:browserInstance=chromium;break}let userDataDir=browserContextOptions.persistent.userDataDir;fs.mkdirSync(userDataDir,{recursive:!0});let browserContext=await browserInstance.launchPersistentContext(userDataDir,_persistentBrowserContextLaunchOptions(browserContextOptions));for(let p of browserContext.pages())try{await p.close()}catch{}return browserContext}async function _getPersistentBrowserContext(browserContextOptions){let persistentBrowserContextKey=_persistentBrowserContextKey(browserContextOptions),browserContext=persistenceBrowserContexts.get(persistentBrowserContextKey);if(browserContext&&!browserContext.browser()?.isConnected()){try{await browserContext.close().catch(()=>{})}catch{}browserContext=void 0}if(!browserContext)browserContext=await _createPersistentBrowserContext(browserContextOptions),persistenceBrowserContexts.set(persistentBrowserContextKey,browserContext);else throw new Error(`There is already active persistent browser context in the user data directory: ${browserContextOptions.persistent?.userDataDir}`);return browserContext}function _openInspectIfChromeRunningOnLoopback(endpointHint){if(!BROWSER_CDP_OPEN_INSPECT||!isChromeRunning())return!1;try{let{host}=hostPortFromCdpConnectRef(endpointHint);if(isLoopbackHost(host))return openChromeRemoteDebuggingPage()}catch{return openChromeRemoteDebuggingPage()}return!1}async function _createCdpBrowserContext(connectUrl){let browser=await chromium.connectOverCDP(connectUrl),contexts=browser.contexts();if(contexts.length===0){let opened=_openInspectIfChromeRunningOnLoopback(connectUrl),running=isChromeRunning();throw cdpConnectError(connectUrl,"Attached but browser reported zero contexts (close other debug clients or re-enable remote debugging).",running,opened)}return{browser,context:contexts[0]}}async function _getCdpBrowserContext(){let resolvedRef,resolvedCacheKey;try{let r=await resolveCdpConnectEndpoint({configuredHttpOrWsUrl:BROWSER_CDP_CONNECT_URL,explicitEndpointUrl:BROWSER_CDP_ENDPOINT_EXPLICIT});resolvedRef=r.connectUrl,resolvedCacheKey=r.cacheKey}catch(e){if(e instanceof CdpDiscoveryFailedError&&e.loopbackAttach){let running=isChromeRunning(),openedInspect=BROWSER_CDP_OPEN_INSPECT&&running&&openChromeRemoteDebuggingPage();throw new Error(`${e.message}
|
|
@@ -22,13 +22,13 @@ ARIA snapshot of the page or a scoped element. Returns a tree with refs (e1, e2,
|
|
|
22
22
|
Use refs in interaction tools: selector "e1" or "@e1" to click/fill that element. Output includes URL, title, and YAML tree.
|
|
23
23
|
Refs are valid until next snapshot or navigation. interactiveOnly: only interactive elements get refs; omit for content roles (headings, etc.) too.
|
|
24
24
|
cursorInteractive: true adds refs for clickable elements without ARIA (e.g. div with cursor:pointer/onclick).
|
|
25
|
-
Use with a11y_take-ax-tree-snapshot for full UI analysis.
|
|
25
|
+
Use with <a11y_take-ax-tree-snapshot> for full UI analysis.
|
|
26
26
|
`.trim()}inputSchema(){return{selector:z.string().optional().describe("Scope to this element; omit for full page."),interactiveOnly:z.boolean().optional().describe("Only interactive elements get refs."),maxDepth:z.number().int().min(0).optional().describe("Max tree depth; 0 = root only."),compact:z.boolean().optional().describe("Omit structural nodes without content."),cursorInteractive:z.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements.")}}outputSchema(){return{output:z.string().describe("Includes the page URL, title, and a YAML-formatted accessibility tree with [ref=e1] etc."),refs:z.record(z.string(),refMapEntrySchema).describe('Map of ref id to { role, name?, selector, nth? }. Use selector "e1" or "@e1" in interaction tools.')}}async handle(context,args){return takeAriaSnapshotWithRefs(context,{selector:args.selector,interactiveOnly:args.interactiveOnly,maxDepth:args.maxDepth,compact:args.compact,cursorInteractive:args.cursorInteractive})}};import{z as z2}from"zod";var DEFAULT_INCLUDE_STYLES=!0,DEFAULT_INCLUDE_RUNTIME_VISUAL=!0,DEFAULT_CHECK_OCCLUSION=!1,DEFAULT_ONLY_VISIBLE=!1,DEFAULT_ONLY_IN_VIEWPORT=!1,DEFAULT_TEXT_PREVIEW_MAX_LENGTH=80,DEFAULT_STYLE_PROPERTIES=["display","visibility","opacity","pointer-events","position","z-index","color","background-color","border-color","border-width","border-style","font-family","font-size","font-weight","line-height","letter-spacing","text-align","text-decoration-line","white-space","overflow","overflow-x","overflow-y","transform","cursor"],DEFAULT_INTERESTING_ROLES=new Set(["button","link","textbox","textField","checkbox","radio","combobox","switch","tab","tabList","tabPanel","menuitem","menu","menubar","dialog","alertDialog","heading","staticText","text","listbox","listitem","list","option","image","img","search","searchBox","navigation","main","banner","contentInfo","complementary","region","article","form","group","progressIndicator","slider","spinButton","status","alert","log","row","columnHeader","rowHeader","cell","grid","table","tooltip","figure","figcaption"]),INTERNAL_CONCURRENCY=12,INTERNAL_SAFETY_CAP=2e3;function attrsToObj(attrs){let result={};if(!attrs)return result;for(let i=0;i<attrs.length;i+=2){let key=String(attrs[i]),value=String(attrs[i+1]??"");result[key]=value}return result}var TakeAxTreeSnapshot=class{name(){return"a11y_take-ax-tree-snapshot"}description(){return`
|
|
27
27
|
Combines Chromium AX tree with runtime visual diagnostics (bounding box, visibility, viewport).
|
|
28
28
|
Use to detect: elements with role/name but hidden or off-screen; layout/geometry issues; overlap/occlusion (enable checkOcclusion).
|
|
29
29
|
When investigating UI/layout or when clicks fail on seemingly visible elements, set checkOcclusion:true\u2014it uses elementFromPoint()
|
|
30
30
|
at center+corners to find what is actually on top. boundingBox is from getBoundingClientRect() (viewport coords; layout box only).
|
|
31
|
-
selectorHint is best-effort (data-testid/data-selector/id). Use with a11y_take-aria-snapshot for full UI analysis.
|
|
31
|
+
selectorHint is best-effort (data-testid/data-selector/id). Use with <a11y_take-aria-snapshot> for full UI analysis.
|
|
32
32
|
`.trim()}inputSchema(){return{roles:z2.array(z2.string().min(1)).optional().describe('ARIA/AX role names to include; omit for default set. Standard roles: ARIA https://w3c.github.io/aria/#role_definitions, Chromium AX https://chromium.googlesource.com/chromium/src/+/main/ui/accessibility/ax_enum_util.cc (e.g. button, heading, staticText, image, main, navigation). Matching is case-insensitive; "text" also matches staticText, "img" matches image.'),includeStyles:z2.boolean().optional().default(DEFAULT_INCLUDE_STYLES),includeRuntimeVisual:z2.boolean().optional().default(DEFAULT_INCLUDE_RUNTIME_VISUAL).describe("Include bounding box and visibility."),checkOcclusion:z2.boolean().optional().default(DEFAULT_CHECK_OCCLUSION).describe("Use elementFromPoint to detect what is on top."),onlyVisible:z2.boolean().optional().default(DEFAULT_ONLY_VISIBLE),onlyInViewport:z2.boolean().optional().default(DEFAULT_ONLY_IN_VIEWPORT),textPreviewMaxLength:z2.number().int().positive().optional().default(DEFAULT_TEXT_PREVIEW_MAX_LENGTH),styleProperties:z2.array(z2.string()).optional().default([...DEFAULT_STYLE_PROPERTIES]).describe("CSS property names when includeStyles.")}}outputSchema(){return{url:z2.string().describe("The current page URL at the time the AX snapshot was captured."),title:z2.string().describe("The document title of the page at the time of the snapshot."),axNodeCount:z2.number().int().nonnegative().describe("Total number of nodes returned by Chromium Accessibility.getFullAXTree before filtering."),candidateCount:z2.number().int().nonnegative().describe("Number of DOM-backed AX nodes that passed role filtering before enrichment."),enrichedCount:z2.number().int().nonnegative().describe("Number of nodes included in the final enriched snapshot output."),truncatedBySafetyCap:z2.boolean().describe("Indicates whether the result set was truncated by an internal safety cap to prevent excessive output size."),nodes:z2.array(z2.object({axNodeId:z2.string().describe("Unique identifier of the accessibility node within the AX tree."),parentAxNodeId:z2.string().nullable().describe("Parent AX node id in the full AX tree. Null if this node is a root."),childAxNodeIds:z2.array(z2.string()).describe("Child AX node ids in the full AX tree (may include nodes not present in the filtered output)."),role:z2.string().nullable().describe("ARIA role of the accessibility node (e.g. button, link, textbox)."),name:z2.string().nullable().describe("Accessible name computed by the browser accessibility engine."),ignored:z2.boolean().nullable().describe("Whether the accessibility node is marked as ignored."),backendDOMNodeId:z2.number().int().describe("Chromium backend DOM node identifier used to map AX nodes to DOM elements."),domNodeId:z2.number().int().nullable().describe("Resolved DOM nodeId from CDP if available; may be null because nodeId is not guaranteed to be stable/resolved."),frameId:z2.string().nullable().describe("Frame identifier if the node belongs to an iframe or subframe."),localName:z2.string().nullable().describe("Lowercased DOM tag name of the mapped element (e.g. div, button, input)."),id:z2.string().nullable().describe("DOM id attribute of the mapped element."),className:z2.string().nullable().describe("DOM class attribute of the mapped element."),selectorHint:z2.string().nullable().describe("Best-effort selector hint for targeting this element (prefers data-testid/data-selector/id)."),textPreview:z2.string().nullable().describe("Short preview of rendered text content or aria-label, truncated to the configured maximum length."),value:z2.any().nullable().describe("Raw AX value payload associated with the node, if present."),description:z2.any().nullable().describe("Raw AX description payload associated with the node, if present."),properties:z2.array(z2.any()).nullable().describe("Additional AX properties exposed by the accessibility tree."),styles:z2.record(z2.string(),z2.string()).optional().describe("Subset of computed CSS styles for the element as string key-value pairs."),runtime:z2.object({boundingBox:z2.object({x:z2.number().describe("X coordinate of the element relative to the viewport."),y:z2.number().describe("Y coordinate of the element relative to the viewport."),width:z2.number().describe("Width of the element in CSS pixels."),height:z2.number().describe("Height of the element in CSS pixels.")}).nullable().describe("Bounding box computed at runtime using getBoundingClientRect."),isVisible:z2.boolean().describe("Whether the element is considered visually visible (display, visibility, opacity, and size)."),isInViewport:z2.boolean().describe("Whether the element intersects the current viewport."),occlusion:z2.object({samplePoints:z2.array(z2.object({x:z2.number().describe("Sample point X (viewport coordinates) used for occlusion testing."),y:z2.number().describe("Sample point Y (viewport coordinates) used for occlusion testing."),hit:z2.boolean().describe("True if elementFromPoint at this point returned a different element that is not a descendant.")})).describe("Sample points used for occlusion detection (center + corners)."),isOccluded:z2.boolean().describe("True if at least one sample point is covered by another element."),topElement:z2.object({localName:z2.string().nullable().describe("Tag name of the occluding element."),id:z2.string().nullable().describe("DOM id of the occluding element (may be null if none)."),className:z2.string().nullable().describe("DOM class of the occluding element (may be null if none)."),selectorHint:z2.string().nullable().describe("Best-effort selector hint for the occluding element."),boundingBox:z2.object({x:z2.number().describe("X coordinate of the occluding element bounding box."),y:z2.number().describe("Y coordinate of the occluding element bounding box."),width:z2.number().describe("Width of the occluding element bounding box."),height:z2.number().describe("Height of the occluding element bounding box.")}).nullable().describe("Bounding box of the occluding element (if available).")}).nullable().describe("Identity and geometry of the occluding element. Null when not occluded."),intersection:z2.object({x:z2.number().describe("Intersection rect X."),y:z2.number().describe("Intersection rect Y."),width:z2.number().describe("Intersection rect width."),height:z2.number().describe("Intersection rect height."),area:z2.number().describe("Intersection rect area in CSS pixels squared.")}).nullable().describe("Intersection box between this element and the occluding element. Null if not occluded or cannot compute.")}).optional().describe("Occlusion detection results. Only present when checkOcclusion=true.")}).optional().describe("Runtime-derived visual information representing how the element is actually rendered.")})).describe("List of enriched DOM-backed AX nodes combining accessibility metadata with visual diagnostics.")}}async handle(context,args){let page=context.page,includeRuntimeVisual=args.includeRuntimeVisual??DEFAULT_INCLUDE_RUNTIME_VISUAL,includeStyles=args.includeStyles??DEFAULT_INCLUDE_STYLES,checkOcclusion=args.checkOcclusion??DEFAULT_CHECK_OCCLUSION,onlyVisible=args.onlyVisible??DEFAULT_ONLY_VISIBLE,onlyInViewport=args.onlyInViewport??DEFAULT_ONLY_IN_VIEWPORT;if((onlyVisible||onlyInViewport)&&!includeRuntimeVisual)throw new Error("onlyVisible/onlyInViewport require includeRuntimeVisual=true.");if(checkOcclusion&&!includeRuntimeVisual)throw new Error("checkOcclusion requires includeRuntimeVisual=true.");let textMax=args.textPreviewMaxLength??DEFAULT_TEXT_PREVIEW_MAX_LENGTH,stylePropsRaw=args.styleProperties&&args.styleProperties.length>0?args.styleProperties:DEFAULT_STYLE_PROPERTIES,stylePropsLower=Array.from(stylePropsRaw).map(p=>p.toLowerCase()),roleAllow=args.roles&&args.roles.length>0?new Set(args.roles.flatMap(r=>{let lower=r.toLowerCase();return lower==="text"?["text","statictext"]:lower==="img"?["img","image"]:[lower]})):new Set(Array.from(DEFAULT_INTERESTING_ROLES).map(r=>r.toLowerCase())),cdp=await page.context().newCDPSession(page);try{await cdp.send("DOM.enable"),await cdp.send("Accessibility.enable"),includeRuntimeVisual&&await cdp.send("Runtime.enable");let axResponse=await cdp.send("Accessibility.getFullAXTree"),axNodes=axResponse.nodes??axResponse,parentByChildId=new Map,childIdsByNodeId=new Map;for(let n of axNodes){let nodeIdStr=String(n.nodeId),childIds=(n.childIds??[]).map(cid=>String(cid));childIdsByNodeId.set(nodeIdStr,childIds);for(let childIdStr of childIds)parentByChildId.set(childIdStr,nodeIdStr)}let candidates=axNodes.filter(n=>{if(typeof n.backendDOMNodeId!="number")return!1;let roleValue=n.role?.value??null;if(!roleValue)return!1;let roleStr=String(roleValue);return roleAllow.has(roleStr)||roleAllow.has(roleStr.toLowerCase())}),candidateCount=candidates.length,truncatedBySafetyCap=candidates.length>INTERNAL_SAFETY_CAP;truncatedBySafetyCap&&(candidates=candidates.slice(0,INTERNAL_SAFETY_CAP));let queue=[...candidates],nodesOut=[],objectIds=[],worker=async()=>{for(;queue.length>0;){let ax=queue.shift();if(!ax)return;let axNodeIdStr=String(ax.nodeId),parentAxNodeId=parentByChildId.get(axNodeIdStr)??null,childAxNodeIds=childIdsByNodeId.get(axNodeIdStr)??[],backendDOMNodeId=ax.backendDOMNodeId,domNodeId=null,localName=null,id=null,className=null,objectId=null;try{let node=(await cdp.send("DOM.describeNode",{backendNodeId:backendDOMNodeId}))?.node;if(node){let nodeIdCandidate=node.nodeId;typeof nodeIdCandidate=="number"&&nodeIdCandidate>0?domNodeId=nodeIdCandidate:domNodeId=null,localName=node.localName??(node.nodeName?String(node.nodeName).toLowerCase():null);let attrObj=attrsToObj(node.attributes);id=attrObj.id??null,className=attrObj.class??null}}catch{}if(includeRuntimeVisual)try{objectId=(await cdp.send("DOM.resolveNode",{backendNodeId:backendDOMNodeId}))?.object?.objectId??null}catch{}let roleStr=ax.role?.value?String(ax.role.value):null,nameStr=ax.name?.value?String(ax.name.value):null,ignoredVal=typeof ax.ignored=="boolean"?ax.ignored:null,item={axNodeId:axNodeIdStr,parentAxNodeId,childAxNodeIds,role:roleStr,name:nameStr,ignored:ignoredVal,backendDOMNodeId,domNodeId,frameId:ax.frameId??null,localName,id,className,selectorHint:null,textPreview:null,value:ax.value??null,description:ax.description??null,properties:Array.isArray(ax.properties)?ax.properties:null},index=nodesOut.push(item)-1;objectIds[index]=objectId}},workers=Array.from({length:INTERNAL_CONCURRENCY},()=>worker());if(await Promise.all(workers),includeRuntimeVisual){let globalObjectId=(await cdp.send("Runtime.evaluate",{expression:"globalThis",returnByValue:!1}))?.result?.objectId;if(globalObjectId){let runtimeArgs=objectIds.map(oid=>oid?{objectId:oid}:{value:null}),values=(await cdp.send("Runtime.callFunctionOn",{objectId:globalObjectId,returnByValue:!0,functionDeclaration:`
|
|
33
33
|
function(textMax, includeStyles, styleProps, checkOcclusion, ...els) {
|
|
34
34
|
function selectorHintFor(el) {
|
|
@@ -410,7 +410,7 @@ How to use it effectively:
|
|
|
410
410
|
- Notes explain which signals were used or skipped; skipped signals usually mean missing cloud configuration (e.g. AWS_REGION, inference profile, etc).
|
|
411
411
|
|
|
412
412
|
This tool is designed for UI regression checks, design parity checks, and "does this page still match the intended layout?" validation.
|
|
413
|
-
`.trim()}isEnabled(){return!!FIGMA_ACCESS_TOKEN?.trim()}inputSchema(){return{figmaFileKey:z19.string().min(1).describe("Figma file key (from URL: part after /file/)."),figmaNodeId:z19.string().min(1).describe("Figma node id (frame/component, e.g. 12:34)."),selector:z19.string().optional().describe("Compare only this region; omit for full page."),fullPage:z19.boolean().optional().default(DEFAULT_FULL_PAGE).describe("Full scrollable page; ignored when selector set."),figmaScale:z19.number().int().positive().optional().describe("Scale for Figma raster (e.g. 1, 2)."),figmaFormat:z19.enum(["png","jpg"]).optional(),weights:z19.object({mssim:z19.number().positive().optional(),imageEmbedding:z19.number().positive().optional(),textEmbedding:z19.number().positive().optional()}).optional().describe("Weights for combining signals."),mssimMode:z19.enum(["raw","semantic"]).optional().default(DEFAULT_MSSIM_MODE).describe("semantic = more robust for real data vs design."),maxDim:z19.number().int().positive().optional().describe("Max dimension for comparison."),jpegQuality:z19.number().int().min(50).max(100).optional().describe("JPEG quality 50\u2013100.")}}outputSchema(){return{score:z19.number().describe("Combined similarity score in the range [0..1]. Higher means more similar."),notes:z19.array(z19.string()).describe("Human-readable notes explaining which signals were used and their individual scores."),meta:z19.object({pageUrl:z19.string().describe("URL of the page that was compared."),pageTitle:z19.string().describe("Title of the page that was compared."),figmaFileKey:z19.string().describe("Figma file key used for the design snapshot."),figmaNodeId:z19.string().describe("Figma node id used for the design snapshot."),selector:z19.string().nullable().describe("Selector used for page screenshot, if any. Null means full page."),fullPage:z19.boolean().describe("Whether the page screenshot was full-page."),pageImageType:z19.enum(["png","jpeg"]).describe("Image type of the captured page screenshot."),figmaImageType:z19.enum(["png","jpeg"]).describe("Image type of the captured Figma snapshot.")}).describe("Metadata about what was compared.")}}async handle(context,args){let pageUrl=String(context.page.url()),pageTitle=String(await context.page.title()),figmaFormat=args.figmaFormat??"png",figmaScale=typeof args.figmaScale=="number"?args.figmaScale:void 0,figmaSnapshot=await getFigmaDesignScreenshot({fileKey:args.figmaFileKey,nodeId:args.figmaNodeId,format:figmaFormat,scale:figmaScale}),pagePng;if(typeof args.selector=="string"&&args.selector.trim()){let selector=args.selector.trim(),locator=context.page.locator(selector);if(await locator.count()===0)throw new Error(`Element not found for selector: ${selector}`);pagePng=await locator.first().screenshot({type:DEFAULT_SCREENSHOT_TYPE2})}else{let fullPage=args.fullPage!==!1;pagePng=await context.page.screenshot({type:DEFAULT_SCREENSHOT_TYPE2,fullPage})}let pageSs={image:pagePng,type:"png",name:"page"},figmaSs={image:figmaSnapshot.image,type:figmaSnapshot.type==="jpeg"?"jpeg":"png",name:"figma"},result=await compareWithNotes(pageSs,figmaSs,{weights:args.weights?{mssim:args.weights.mssim,vectorEmbedding:args.weights.imageEmbedding,textEmbedding:args.weights.textEmbedding}:void 0,mssim:{mode:args.mssimMode??DEFAULT_MSSIM_MODE},imageEmbedding:{maxDim:args.maxDim,jpegQuality:typeof args.jpegQuality=="number"?args.jpegQuality:void 0},textEmbedding:{maxDim:args.maxDim,jpegQuality:typeof args.jpegQuality=="number"?args.jpegQuality:void 0}});return{score:result.score,notes:result.notes,meta:{pageUrl,pageTitle,figmaFileKey:args.figmaFileKey,figmaNodeId:args.figmaNodeId,selector:typeof args.selector=="string"&&args.selector.trim()?args.selector.trim():null,fullPage:typeof args.selector=="string"&&args.selector.trim()?!1:args.fullPage!==!1,pageImageType:"png",figmaImageType:figmaSnapshot.type}}}};var tools4=[new ComparePageWithDesign];async function waitForNavigationAndNetworkIdle(context,navPromise,triggerPromise,options){let timeoutMs=options.timeoutMs,deadlineMs=Date.now()+timeoutMs;try{await Promise.all([navPromise,triggerPromise])}catch(err){let message=err instanceof Error?err.message:String(err);if(/timeout|Timeout|TIMEOUT/.test(message)||message.includes("Navigation")){let hint=options.timeoutHint??"Ensure the action triggers a page load (e.g. link or submit).";throw new Error(`Navigation timed out (${timeoutMs}ms). ${hint} Original: ${message}`)}throw err}let stabilizationMs=Math.min(150,Math.max(0,deadlineMs-Date.now()));stabilizationMs>0&&await new Promise(resolve=>{setTimeout(resolve,stabilizationMs)});let lastNotIdleMs=Date.now();for(;;){let nowMs=Date.now();if(context.numOfInFlightRequests()>0&&(lastNotIdleMs=nowMs),nowMs-lastNotIdleMs>=500||nowMs>=deadlineMs)break;await new Promise(resolve=>{setTimeout(resolve,50)})}}async function waitForNetworkIdle(context,options){let timeoutMs=options.timeoutMs,deadlineMs=Date.now()+timeoutMs,stabilizationMs=Math.min(150,Math.max(0,deadlineMs-Date.now()));stabilizationMs>0&&await new Promise(resolve=>{setTimeout(resolve,stabilizationMs)});let lastNotIdleMs=Date.now();for(;;){let nowMs=Date.now();if(context.numOfInFlightRequests()>0&&(lastNotIdleMs=nowMs),nowMs-lastNotIdleMs>=500)break;let latestNetworkActivity=context.lastNetworkActivityTimestamp();if(nowMs-latestNetworkActivity>5e3||nowMs>=deadlineMs)break;await new Promise(resolve=>{setTimeout(resolve,50)})}}import{z as z20}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS=1e4,DEFAULT_WAIT_FOR_TIMEOUT_MS=3e4,Click=class{name(){return"interaction_click"}description(){return"Clicks an element. Accepts selector or ref (e.g. e1, @e1). Set waitForNavigation: true when the click opens a new page \u2014 waits for navigation then for network idle so snapshot/screenshot see full content."}inputSchema(){return{selector:z20.string().describe("CSS selector or ref from a11y snapshot (e.g. e1, @e1)."),timeoutMs:z20.number().int().positive().optional().describe("Wait for element, ms. Default 10000."),waitForNavigation:z20.boolean().optional().default(!1).describe("Wait for navigation triggered by click (parallel with click). Use when click opens a new page."),waitForTimeoutMs:z20.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS).describe("Timeout for navigation and for network idle wait (ms). Only when waitForNavigation is true. Default 30000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,REF_ROLES_INTERACTIVE,"click","Use a ref for an interactive element (button, link, etc.) from the latest a11y_take-aria-snapshot. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS,locator=resolveSelectorOrRef(context,args.selector);if(args.waitForNavigation){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS,navPromise=context.page.waitForNavigation({waitUntil:"load",timeout:waitForTimeoutMs}),triggerPromise=locator.click({timeout});await waitForNavigationAndNetworkIdle(context,navPromise,triggerPromise,{timeoutMs:waitForTimeoutMs,timeoutHint:"Ensure the click triggers a page load (e.g. link or submit)."})}else await locator.click({timeout});return{}}};import{z as z21}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS2=1e4,Drag=class{name(){return"interaction_drag"}description(){return"Drags an element to a target location. Accepts CSS selectors or refs (e.g. e1, @e1) from the last ARIA snapshot."}inputSchema(){return{sourceSelector:z21.string().describe("CSS selector or ref for the element to drag."),targetSelector:z21.string().describe("CSS selector or ref for the drop target."),timeoutMs:z21.number().int().positive().optional().describe("Wait for elements, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.sourceSelector,REF_ROLES_INTERACTIVE,"drag (source)","Use a ref for a draggable element from the latest a11y_take-aria-snapshot. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS2,sourceLocator=resolveSelectorOrRef(context,args.sourceSelector),targetLocator=resolveSelectorOrRef(context,args.targetSelector),sourceBound=await sourceLocator.boundingBox({timeout}),targetBound=await targetLocator.boundingBox({timeout});if(!sourceBound||!targetBound)throw new Error("Could not get element positions for drag operation");return await context.page.mouse.move(sourceBound.x+sourceBound.width/2,sourceBound.y+sourceBound.height/2),await context.page.mouse.down(),await context.page.mouse.move(targetBound.x+targetBound.width/2,targetBound.y+targetBound.height/2),await context.page.mouse.up(),{}}};import{z as z22}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS3=1e4,FILLABLE_ROLES=new Set(["textbox","searchbox","spinbutton","combobox"]),Fill=class{name(){return"interaction_fill"}description(){return"Fills out an input field. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z22.string().describe("CSS selector or ref from a11y snapshot for the input."),value:z22.string(),timeoutMs:z22.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,FILLABLE_ROLES,"fill","Use a ref for a textbox, searchbox, or input (e.g. from the latest a11y_take-aria-snapshot on this page). Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS3;return await resolveSelectorOrRef(context,args.selector).fill(args.value,{timeout}),{}}};import{z as z23}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS4=1e4,Hover=class{name(){return"interaction_hover"}description(){return"Hovers an element on the page. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z23.string().describe("CSS selector or ref from a11y snapshot."),timeoutMs:z23.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,REF_ROLES_INTERACTIVE,"hover","Use a ref for an interactive element from the latest a11y_take-aria-snapshot. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS4;return await resolveSelectorOrRef(context,args.selector).hover({timeout}),{}}};import{z as z24}from"zod";var DEFAULT_REPEAT_INTERVAL_MS=50,MIN_REPEAT_INTERVAL_MS=10,DEFAULT_SELECTOR_TIMEOUT_MS5=1e4,PressKey=class{name(){return"interaction_press-key"}description(){return`
|
|
413
|
+
`.trim()}isEnabled(){return!!FIGMA_ACCESS_TOKEN?.trim()}inputSchema(){return{figmaFileKey:z19.string().min(1).describe("Figma file key (from URL: part after /file/)."),figmaNodeId:z19.string().min(1).describe("Figma node id (frame/component, e.g. 12:34)."),selector:z19.string().optional().describe("Compare only this region; omit for full page."),fullPage:z19.boolean().optional().default(DEFAULT_FULL_PAGE).describe("Full scrollable page; ignored when selector set."),figmaScale:z19.number().int().positive().optional().describe("Scale for Figma raster (e.g. 1, 2)."),figmaFormat:z19.enum(["png","jpg"]).optional(),weights:z19.object({mssim:z19.number().positive().optional(),imageEmbedding:z19.number().positive().optional(),textEmbedding:z19.number().positive().optional()}).optional().describe("Weights for combining signals."),mssimMode:z19.enum(["raw","semantic"]).optional().default(DEFAULT_MSSIM_MODE).describe("semantic = more robust for real data vs design."),maxDim:z19.number().int().positive().optional().describe("Max dimension for comparison."),jpegQuality:z19.number().int().min(50).max(100).optional().describe("JPEG quality 50\u2013100.")}}outputSchema(){return{score:z19.number().describe("Combined similarity score in the range [0..1]. Higher means more similar."),notes:z19.array(z19.string()).describe("Human-readable notes explaining which signals were used and their individual scores."),meta:z19.object({pageUrl:z19.string().describe("URL of the page that was compared."),pageTitle:z19.string().describe("Title of the page that was compared."),figmaFileKey:z19.string().describe("Figma file key used for the design snapshot."),figmaNodeId:z19.string().describe("Figma node id used for the design snapshot."),selector:z19.string().nullable().describe("Selector used for page screenshot, if any. Null means full page."),fullPage:z19.boolean().describe("Whether the page screenshot was full-page."),pageImageType:z19.enum(["png","jpeg"]).describe("Image type of the captured page screenshot."),figmaImageType:z19.enum(["png","jpeg"]).describe("Image type of the captured Figma snapshot.")}).describe("Metadata about what was compared.")}}async handle(context,args){let pageUrl=String(context.page.url()),pageTitle=String(await context.page.title()),figmaFormat=args.figmaFormat??"png",figmaScale=typeof args.figmaScale=="number"?args.figmaScale:void 0,figmaSnapshot=await getFigmaDesignScreenshot({fileKey:args.figmaFileKey,nodeId:args.figmaNodeId,format:figmaFormat,scale:figmaScale}),pagePng;if(typeof args.selector=="string"&&args.selector.trim()){let selector=args.selector.trim(),locator=context.page.locator(selector);if(await locator.count()===0)throw new Error(`Element not found for selector: ${selector}`);pagePng=await locator.first().screenshot({type:DEFAULT_SCREENSHOT_TYPE2})}else{let fullPage=args.fullPage!==!1;pagePng=await context.page.screenshot({type:DEFAULT_SCREENSHOT_TYPE2,fullPage})}let pageSs={image:pagePng,type:"png",name:"page"},figmaSs={image:figmaSnapshot.image,type:figmaSnapshot.type==="jpeg"?"jpeg":"png",name:"figma"},result=await compareWithNotes(pageSs,figmaSs,{weights:args.weights?{mssim:args.weights.mssim,vectorEmbedding:args.weights.imageEmbedding,textEmbedding:args.weights.textEmbedding}:void 0,mssim:{mode:args.mssimMode??DEFAULT_MSSIM_MODE},imageEmbedding:{maxDim:args.maxDim,jpegQuality:typeof args.jpegQuality=="number"?args.jpegQuality:void 0},textEmbedding:{maxDim:args.maxDim,jpegQuality:typeof args.jpegQuality=="number"?args.jpegQuality:void 0}});return{score:result.score,notes:result.notes,meta:{pageUrl,pageTitle,figmaFileKey:args.figmaFileKey,figmaNodeId:args.figmaNodeId,selector:typeof args.selector=="string"&&args.selector.trim()?args.selector.trim():null,fullPage:typeof args.selector=="string"&&args.selector.trim()?!1:args.fullPage!==!1,pageImageType:"png",figmaImageType:figmaSnapshot.type}}}};var tools4=[new ComparePageWithDesign];async function waitForNavigationAndNetworkIdle(context,navPromise,triggerPromise,options){let timeoutMs=options.timeoutMs,deadlineMs=Date.now()+timeoutMs;try{await Promise.all([navPromise,triggerPromise])}catch(err){let message=err instanceof Error?err.message:String(err);if(/timeout|Timeout|TIMEOUT/.test(message)||message.includes("Navigation")){let hint=options.timeoutHint??"Ensure the action triggers a page load (e.g. link or submit).";throw new Error(`Navigation timed out (${timeoutMs}ms). ${hint} Original: ${message}`)}throw err}let stabilizationMs=Math.min(150,Math.max(0,deadlineMs-Date.now()));stabilizationMs>0&&await new Promise(resolve=>{setTimeout(resolve,stabilizationMs)});let lastNotIdleMs=Date.now();for(;;){let nowMs=Date.now();if(context.numOfInFlightRequests()>0&&(lastNotIdleMs=nowMs),nowMs-lastNotIdleMs>=500||nowMs>=deadlineMs)break;await new Promise(resolve=>{setTimeout(resolve,50)})}}async function waitForNetworkIdle(context,options){let timeoutMs=options.timeoutMs,deadlineMs=Date.now()+timeoutMs,stabilizationMs=Math.min(150,Math.max(0,deadlineMs-Date.now()));stabilizationMs>0&&await new Promise(resolve=>{setTimeout(resolve,stabilizationMs)});let lastNotIdleMs=Date.now();for(;;){let nowMs=Date.now();if(context.numOfInFlightRequests()>0&&(lastNotIdleMs=nowMs),nowMs-lastNotIdleMs>=500)break;let latestNetworkActivity=context.lastNetworkActivityTimestamp();if(nowMs-latestNetworkActivity>5e3||nowMs>=deadlineMs)break;await new Promise(resolve=>{setTimeout(resolve,50)})}}import{z as z20}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS=1e4,DEFAULT_WAIT_FOR_TIMEOUT_MS=3e4,Click=class{name(){return"interaction_click"}description(){return"Clicks an element. Accepts selector or ref (e.g. e1, @e1). Set waitForNavigation: true when the click opens a new page \u2014 waits for navigation then for network idle so snapshot/screenshot see full content."}inputSchema(){return{selector:z20.string().describe("CSS selector or ref from a11y snapshot (e.g. e1, @e1)."),timeoutMs:z20.number().int().positive().optional().describe("Wait for element, ms. Default 10000."),waitForNavigation:z20.boolean().optional().default(!1).describe("Wait for navigation triggered by click (parallel with click). Use when click opens a new page."),waitForTimeoutMs:z20.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS).describe("Timeout for navigation and for network idle wait (ms). Only when waitForNavigation is true. Default 30000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,REF_ROLES_INTERACTIVE,"click","Use a ref for an interactive element (button, link, etc.) from the latest <a11y_take-aria-snapshot>. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS,locator=resolveSelectorOrRef(context,args.selector);if(args.waitForNavigation){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS,navPromise=context.page.waitForNavigation({waitUntil:"load",timeout:waitForTimeoutMs}),triggerPromise=locator.click({timeout});await waitForNavigationAndNetworkIdle(context,navPromise,triggerPromise,{timeoutMs:waitForTimeoutMs,timeoutHint:"Ensure the click triggers a page load (e.g. link or submit)."})}else await locator.click({timeout});return{}}};import{z as z21}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS2=1e4,Drag=class{name(){return"interaction_drag"}description(){return"Drags an element to a target location. Accepts CSS selectors or refs (e.g. e1, @e1) from the last ARIA snapshot."}inputSchema(){return{sourceSelector:z21.string().describe("CSS selector or ref for the element to drag."),targetSelector:z21.string().describe("CSS selector or ref for the drop target."),timeoutMs:z21.number().int().positive().optional().describe("Wait for elements, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.sourceSelector,REF_ROLES_INTERACTIVE,"drag (source)","Use a ref for a draggable element from the latest <a11y_take-aria-snapshot>. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS2,sourceLocator=resolveSelectorOrRef(context,args.sourceSelector),targetLocator=resolveSelectorOrRef(context,args.targetSelector),sourceBound=await sourceLocator.boundingBox({timeout}),targetBound=await targetLocator.boundingBox({timeout});if(!sourceBound||!targetBound)throw new Error("Could not get element positions for drag operation");return await context.page.mouse.move(sourceBound.x+sourceBound.width/2,sourceBound.y+sourceBound.height/2),await context.page.mouse.down(),await context.page.mouse.move(targetBound.x+targetBound.width/2,targetBound.y+targetBound.height/2),await context.page.mouse.up(),{}}};import{z as z22}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS3=1e4,FILLABLE_ROLES=new Set(["textbox","searchbox","spinbutton","combobox"]),Fill=class{name(){return"interaction_fill"}description(){return"Fills out an input field. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z22.string().describe("CSS selector or ref from a11y snapshot for the input."),value:z22.string(),timeoutMs:z22.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,FILLABLE_ROLES,"fill","Use a ref for a textbox, searchbox, or input (e.g. from the latest <a11y_take-aria-snapshot> on this page). Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS3;return await resolveSelectorOrRef(context,args.selector).fill(args.value,{timeout}),{}}};import{z as z23}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS4=1e4,Hover=class{name(){return"interaction_hover"}description(){return"Hovers an element on the page. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z23.string().describe("CSS selector or ref from a11y snapshot."),timeoutMs:z23.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,REF_ROLES_INTERACTIVE,"hover","Use a ref for an interactive element from the latest <a11y_take-aria-snapshot>. Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS4;return await resolveSelectorOrRef(context,args.selector).hover({timeout}),{}}};import{z as z24}from"zod";var DEFAULT_REPEAT_INTERVAL_MS=50,MIN_REPEAT_INTERVAL_MS=10,DEFAULT_SELECTOR_TIMEOUT_MS5=1e4,PressKey=class{name(){return"interaction_press-key"}description(){return`
|
|
414
414
|
Presses a keyboard key with optional "hold" and auto-repeat behavior.
|
|
415
415
|
|
|
416
416
|
Key facts:
|
|
@@ -447,7 +447,7 @@ so the page layout follows the OS window size.
|
|
|
447
447
|
Important:
|
|
448
448
|
- If Playwright viewport emulation is enabled (viewport is NOT null), resizing the OS window may not change page layout.
|
|
449
449
|
- On non-Chromium browsers (Firefox/WebKit), CDP is not available and this tool will fail.
|
|
450
|
-
`.trim()}inputSchema(){return{width:z26.number().int().min(MIN_WINDOW_WIDTH).optional().describe("Required when state=normal."),height:z26.number().int().min(MIN_WINDOW_HEIGHT).optional().describe("Required when state=normal."),state:z26.enum(["normal","maximized","minimized","fullscreen"]).optional().default("normal").describe("When not normal, width/height may be ignored.")}}outputSchema(){return{requested:z26.object({width:z26.number().int().nullable().describe("Requested window width (pixels). Null if not provided."),height:z26.number().int().nullable().describe("Requested window height (pixels). Null if not provided."),state:z26.enum(["normal","maximized","minimized","fullscreen"]).describe("Requested window state.")}).describe("Requested window change parameters."),before:z26.object({windowId:z26.number().int().describe("CDP window id for the current target."),state:z26.string().nullable().describe("Window state before resizing."),left:z26.number().int().nullable().describe("Window left position before resizing."),top:z26.number().int().nullable().describe("Window top position before resizing."),width:z26.number().int().nullable().describe("Window width before resizing."),height:z26.number().int().nullable().describe("Window height before resizing.")}).describe("Window bounds before resizing."),after:z26.object({windowId:z26.number().int().describe("CDP window id for the current target."),state:z26.string().nullable().describe("Window state after resizing."),left:z26.number().int().nullable().describe("Window left position after resizing."),top:z26.number().int().nullable().describe("Window top position after resizing."),width:z26.number().int().nullable().describe("Window width after resizing."),height:z26.number().int().nullable().describe("Window height after resizing.")}).describe("Window bounds after resizing."),viewport:z26.object({innerWidth:z26.number().int().describe("window.innerWidth after resizing (CSS pixels)."),innerHeight:z26.number().int().describe("window.innerHeight after resizing (CSS pixels)."),outerWidth:z26.number().int().describe("window.outerWidth after resizing (CSS pixels)."),outerHeight:z26.number().int().describe("window.outerHeight after resizing (CSS pixels)."),devicePixelRatio:z26.number().describe("window.devicePixelRatio after resizing.")}).describe("Page viewport metrics after resizing (helps verify responsive behavior).")}}async handle(context,args){let state=args.state??"normal",width=args.width,height=args.height;if(state==="normal"&&(typeof width!="number"||typeof height!="number"))throw new Error('state="normal" requires both width and height.');let page=context.page,cdp=await page.context().newCDPSession(page);try{let info=await cdp.send("Browser.getWindowForTarget",{}),windowId=Number(info.windowId),beforeBounds=info.bounds??{},before={windowId,state:typeof beforeBounds.windowState=="string"?beforeBounds.windowState:null,left:typeof beforeBounds.left=="number"?beforeBounds.left:null,top:typeof beforeBounds.top=="number"?beforeBounds.top:null,width:typeof beforeBounds.width=="number"?beforeBounds.width:null,height:typeof beforeBounds.height=="number"?beforeBounds.height:null},boundsToSet={};state!=="normal"?boundsToSet.windowState=state:(boundsToSet.windowState="normal",boundsToSet.width=width,boundsToSet.height=height),await cdp.send("Browser.setWindowBounds",{windowId,bounds:boundsToSet});let afterBounds=(await cdp.send("Browser.getWindowForTarget",{})).bounds??{},after={windowId,state:typeof afterBounds.windowState=="string"?afterBounds.windowState:null,left:typeof afterBounds.left=="number"?afterBounds.left:null,top:typeof afterBounds.top=="number"?afterBounds.top:null,width:typeof afterBounds.width=="number"?afterBounds.width:null,height:typeof afterBounds.height=="number"?afterBounds.height:null},metrics=await page.evaluate(()=>({innerWidth:window.innerWidth,innerHeight:window.innerHeight,outerWidth:window.outerWidth,outerHeight:window.outerHeight,devicePixelRatio:window.devicePixelRatio})),viewport={innerWidth:Number(metrics.innerWidth),innerHeight:Number(metrics.innerHeight),outerWidth:Number(metrics.outerWidth),outerHeight:Number(metrics.outerHeight),devicePixelRatio:Number(metrics.devicePixelRatio)};return{requested:{width:typeof width=="number"?width:null,height:typeof height=="number"?height:null,state},before,after,viewport}}catch(e){let msg=String(e?.message??e);throw new Error(`Failed to resize real browser window via CDP. This tool works best on Chromium-based browsers. Original error: ${msg}`)}finally{await cdp.detach().catch(()=>{})}}};import{z as z27}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS6=1e4,SELECTABLE_ROLES=new Set(["listbox","combobox"]),Select=class{name(){return"interaction_select"}description(){return"Select an option in a dropdown. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z27.string().describe("CSS selector or ref from a11y snapshot for the dropdown."),value:z27.string(),timeoutMs:z27.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,SELECTABLE_ROLES,"select","Use a ref for a listbox or combobox (e.g. from the latest a11y_take-aria-snapshot). Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS6;return await resolveSelectorOrRef(context,args.selector).selectOption(args.value,{timeout}),{}}};import{z as z28}from"zod";var DEFAULT_BEHAVIOR="auto",DEFAULT_MODE="by",Scroll=class{name(){return"interaction_scroll"}description(){return`
|
|
450
|
+
`.trim()}inputSchema(){return{width:z26.number().int().min(MIN_WINDOW_WIDTH).optional().describe("Required when state=normal."),height:z26.number().int().min(MIN_WINDOW_HEIGHT).optional().describe("Required when state=normal."),state:z26.enum(["normal","maximized","minimized","fullscreen"]).optional().default("normal").describe("When not normal, width/height may be ignored.")}}outputSchema(){return{requested:z26.object({width:z26.number().int().nullable().describe("Requested window width (pixels). Null if not provided."),height:z26.number().int().nullable().describe("Requested window height (pixels). Null if not provided."),state:z26.enum(["normal","maximized","minimized","fullscreen"]).describe("Requested window state.")}).describe("Requested window change parameters."),before:z26.object({windowId:z26.number().int().describe("CDP window id for the current target."),state:z26.string().nullable().describe("Window state before resizing."),left:z26.number().int().nullable().describe("Window left position before resizing."),top:z26.number().int().nullable().describe("Window top position before resizing."),width:z26.number().int().nullable().describe("Window width before resizing."),height:z26.number().int().nullable().describe("Window height before resizing.")}).describe("Window bounds before resizing."),after:z26.object({windowId:z26.number().int().describe("CDP window id for the current target."),state:z26.string().nullable().describe("Window state after resizing."),left:z26.number().int().nullable().describe("Window left position after resizing."),top:z26.number().int().nullable().describe("Window top position after resizing."),width:z26.number().int().nullable().describe("Window width after resizing."),height:z26.number().int().nullable().describe("Window height after resizing.")}).describe("Window bounds after resizing."),viewport:z26.object({innerWidth:z26.number().int().describe("window.innerWidth after resizing (CSS pixels)."),innerHeight:z26.number().int().describe("window.innerHeight after resizing (CSS pixels)."),outerWidth:z26.number().int().describe("window.outerWidth after resizing (CSS pixels)."),outerHeight:z26.number().int().describe("window.outerHeight after resizing (CSS pixels)."),devicePixelRatio:z26.number().describe("window.devicePixelRatio after resizing.")}).describe("Page viewport metrics after resizing (helps verify responsive behavior).")}}async handle(context,args){let state=args.state??"normal",width=args.width,height=args.height;if(state==="normal"&&(typeof width!="number"||typeof height!="number"))throw new Error('state="normal" requires both width and height.');let page=context.page,cdp=await page.context().newCDPSession(page);try{let info=await cdp.send("Browser.getWindowForTarget",{}),windowId=Number(info.windowId),beforeBounds=info.bounds??{},before={windowId,state:typeof beforeBounds.windowState=="string"?beforeBounds.windowState:null,left:typeof beforeBounds.left=="number"?beforeBounds.left:null,top:typeof beforeBounds.top=="number"?beforeBounds.top:null,width:typeof beforeBounds.width=="number"?beforeBounds.width:null,height:typeof beforeBounds.height=="number"?beforeBounds.height:null},boundsToSet={};state!=="normal"?boundsToSet.windowState=state:(boundsToSet.windowState="normal",boundsToSet.width=width,boundsToSet.height=height),await cdp.send("Browser.setWindowBounds",{windowId,bounds:boundsToSet});let afterBounds=(await cdp.send("Browser.getWindowForTarget",{})).bounds??{},after={windowId,state:typeof afterBounds.windowState=="string"?afterBounds.windowState:null,left:typeof afterBounds.left=="number"?afterBounds.left:null,top:typeof afterBounds.top=="number"?afterBounds.top:null,width:typeof afterBounds.width=="number"?afterBounds.width:null,height:typeof afterBounds.height=="number"?afterBounds.height:null},metrics=await page.evaluate(()=>({innerWidth:window.innerWidth,innerHeight:window.innerHeight,outerWidth:window.outerWidth,outerHeight:window.outerHeight,devicePixelRatio:window.devicePixelRatio})),viewport={innerWidth:Number(metrics.innerWidth),innerHeight:Number(metrics.innerHeight),outerWidth:Number(metrics.outerWidth),outerHeight:Number(metrics.outerHeight),devicePixelRatio:Number(metrics.devicePixelRatio)};return{requested:{width:typeof width=="number"?width:null,height:typeof height=="number"?height:null,state},before,after,viewport}}catch(e){let msg=String(e?.message??e);throw new Error(`Failed to resize real browser window via CDP. This tool works best on Chromium-based browsers. Original error: ${msg}`)}finally{await cdp.detach().catch(()=>{})}}};import{z as z27}from"zod";var DEFAULT_SELECTOR_TIMEOUT_MS6=1e4,SELECTABLE_ROLES=new Set(["listbox","combobox"]),Select=class{name(){return"interaction_select"}description(){return"Select an option in a dropdown. Accepts a CSS selector or a ref from the last ARIA snapshot (e.g. e1, @e1)."}inputSchema(){return{selector:z27.string().describe("CSS selector or ref from a11y snapshot for the dropdown."),value:z27.string(),timeoutMs:z27.number().int().positive().optional().describe("Wait for element, ms. Default 10000.")}}outputSchema(){return{}}async handle(context,args){assertRefRole(context,args.selector,SELECTABLE_ROLES,"select","Use a ref for a listbox or combobox (e.g. from the latest <a11y_take-aria-snapshot>). Refs are page-specific; re-snapshot after navigation.");let timeout=args.timeoutMs??DEFAULT_SELECTOR_TIMEOUT_MS6;return await resolveSelectorOrRef(context,args.selector).selectOption(args.value,{timeout}),{}}};import{z as z28}from"zod";var DEFAULT_BEHAVIOR="auto",DEFAULT_MODE="by",Scroll=class{name(){return"interaction_scroll"}description(){return`
|
|
451
451
|
Scrolls the page viewport or a specific scrollable element.
|
|
452
452
|
|
|
453
453
|
Modes:
|
|
@@ -463,7 +463,7 @@ Use this tool to:
|
|
|
463
463
|
- Jump to the top/bottom without knowing exact positions
|
|
464
464
|
- Bring elements into view before clicking
|
|
465
465
|
- Inspect lazy-loaded content that appears on scroll
|
|
466
|
-
`.trim()}inputSchema(){return{mode:z28.enum(["by","to","top","bottom","left","right"]).optional().default(DEFAULT_MODE).describe("by=dx,dy; to=x,y; or edge (top/bottom/left/right)."),selector:z28.string().optional().describe("Scroll this container; omit for viewport."),dx:z28.number().optional(),dy:z28.number().optional(),x:z28.number().optional(),y:z28.number().optional(),behavior:z28.enum(["auto","smooth"]).optional().default(DEFAULT_BEHAVIOR)}}outputSchema(){return{mode:z28.enum(["by","to","top","bottom","left","right"]).describe("The scroll mode used."),selector:z28.string().nullable().describe("The selector of the scroll container if provided; otherwise null (document viewport)."),behavior:z28.enum(["auto","smooth"]).describe("The scroll behavior used."),before:z28.object({x:z28.number().describe("ScrollLeft before scrolling."),y:z28.number().describe("ScrollTop before scrolling."),scrollWidth:z28.number().describe("Total scrollable width before scrolling."),scrollHeight:z28.number().describe("Total scrollable height before scrolling."),clientWidth:z28.number().describe("Viewport/container client width before scrolling."),clientHeight:z28.number().describe("Viewport/container client height before scrolling.")}).describe("Scroll metrics before the scroll action."),after:z28.object({x:z28.number().describe("ScrollLeft after scrolling."),y:z28.number().describe("ScrollTop after scrolling."),scrollWidth:z28.number().describe("Total scrollable width after scrolling."),scrollHeight:z28.number().describe("Total scrollable height after scrolling."),clientWidth:z28.number().describe("Viewport/container client width after scrolling."),clientHeight:z28.number().describe("Viewport/container client height after scrolling.")}).describe("Scroll metrics after the scroll action."),canScrollX:z28.boolean().describe("Whether horizontal scrolling is possible (scrollWidth > clientWidth)."),canScrollY:z28.boolean().describe("Whether vertical scrolling is possible (scrollHeight > clientHeight)."),maxScrollX:z28.number().describe("Maximum horizontal scrollLeft (scrollWidth - clientWidth)."),maxScrollY:z28.number().describe("Maximum vertical scrollTop (scrollHeight - clientHeight)."),isAtLeft:z28.boolean().describe("Whether the scroll position is at the far left."),isAtRight:z28.boolean().describe("Whether the scroll position is at the far right."),isAtTop:z28.boolean().describe("Whether the scroll position is at the very top."),isAtBottom:z28.boolean().describe("Whether the scroll position is at the very bottom.")}}async handle(context,args){let mode=args.mode??DEFAULT_MODE,selector=args.selector,behavior=args.behavior??DEFAULT_BEHAVIOR,dx=args.dx??0,dy=args.dy??0,x=args.x,y=args.y;if(mode==="to"&&typeof x!="number"&&typeof y!="number")throw new Error('mode="to" requires at least one of x or y.');if(mode==="by"&&dx===0&&dy===0)throw new Error('mode="by" requires dx and/or dy to be non-zero.');let params={modeEval:mode,selectorEval:selector,dxEval:dx,dyEval:dy,xEval:x,yEval:y,behaviorEval:behavior},result;return selector?result=await resolveSelectorOrRef(context,selector).evaluate((el,p)=>{let before={x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight},readMetrics=elem2=>({x:elem2.scrollLeft,y:elem2.scrollTop,scrollWidth:elem2.scrollWidth,scrollHeight:elem2.scrollHeight,clientWidth:elem2.clientWidth,clientHeight:elem2.clientHeight}),clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),elem=el,maxX=Math.max(0,elem.scrollWidth-elem.clientWidth),maxY=Math.max(0,elem.scrollHeight-elem.clientHeight);p.modeEval==="by"?elem.scrollTo({left:clamp(elem.scrollLeft+p.dxEval,0,maxX),top:clamp(elem.scrollTop+p.dyEval,0,maxY),behavior:p.behaviorEval}):p.modeEval==="to"?elem.scrollTo({left:typeof p.xEval=="number"?clamp(p.xEval,0,maxX):elem.scrollLeft,top:typeof p.yEval=="number"?clamp(p.yEval,0,maxY):elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="top"?elem.scrollTo({top:0,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="bottom"?elem.scrollTo({top:maxY,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="left"?elem.scrollTo({left:0,top:elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="right"&&elem.scrollTo({left:maxX,top:elem.scrollTop,behavior:p.behaviorEval});let after=readMetrics(elem);return{before,after,canScrollX:after.scrollWidth>after.clientWidth,canScrollY:after.scrollHeight>after.clientHeight,maxScrollX:Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY:Math.max(0,after.scrollHeight-after.clientHeight),isAtLeft:after.x<=1,isAtRight:after.x>=Math.max(0,after.scrollWidth-after.clientWidth)-1,isAtTop:after.y<=1,isAtBottom:after.y>=Math.max(0,after.scrollHeight-after.clientHeight)-1}},params):result=await context.page.evaluate(params2=>{let modeEval=params2.modeEval,selectorEval=params2.selectorEval,dxEval=params2.dxEval,dyEval=params2.dyEval,xEval=params2.xEval,yEval=params2.yEval,behaviorEval=params2.behaviorEval,getTarget=()=>{if(selectorEval){let el=document.querySelector(selectorEval);if(!el)throw new Error(`Element with selector "${selectorEval}" not found`);return el}let scrolling=document.scrollingElement||document.documentElement||document.body;if(!scrolling)throw new Error("No scrolling element available.");return scrolling},readMetrics=el=>({x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight}),clamp=(v,min,max)=>v<min?min:v>max?max:v,doScroll=el=>{let maxX=Math.max(0,el.scrollWidth-el.clientWidth),maxY=Math.max(0,el.scrollHeight-el.clientHeight);if(modeEval==="by"){let nextX=clamp(el.scrollLeft+dxEval,0,maxX),nextY=clamp(el.scrollTop+dyEval,0,maxY);el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="to"){let nextX=typeof xEval=="number"?clamp(xEval,0,maxX):el.scrollLeft,nextY=typeof yEval=="number"?clamp(yEval,0,maxY):el.scrollTop;el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="top"){el.scrollTo({top:0,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="bottom"){el.scrollTo({top:maxY,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="left"){el.scrollTo({left:0,top:el.scrollTop,behavior:behaviorEval});return}if(modeEval==="right"){el.scrollTo({left:maxX,top:el.scrollTop,behavior:behaviorEval});return}},target=getTarget(),before=readMetrics(target);doScroll(target);let after=readMetrics(target),maxScrollX=Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY=Math.max(0,after.scrollHeight-after.clientHeight),canScrollX=after.scrollWidth>after.clientWidth,canScrollY=after.scrollHeight>after.clientHeight,eps=1,isAtLeft=after.x<=eps,isAtRight=after.x>=maxScrollX-eps,isAtTop=after.y<=eps,isAtBottom=after.y>=maxScrollY-eps;return{before,after,canScrollX,canScrollY,maxScrollX,maxScrollY,isAtLeft,isAtRight,isAtTop,isAtBottom}},params),{mode,selector:selector??null,behavior,before:result.before,after:result.after,canScrollX:result.canScrollX,canScrollY:result.canScrollY,maxScrollX:result.maxScrollX,maxScrollY:result.maxScrollY,isAtLeft:result.isAtLeft,isAtRight:result.isAtRight,isAtTop:result.isAtTop,isAtBottom:result.isAtBottom}}};var tools5=[new Click,new Drag,new Fill,new Hover,new PressKey,new ResizeViewport,new ResizeWindow,new Select,new Scroll];import fs4 from"node:fs/promises";import os3 from"node:os";import path4 from"node:path";import{z as z29}from"zod";var DEFAULT_SCREENSHOT_NAME2="screenshot",DEFAULT_TIMEOUT_MS2=0,DEFAULT_WAIT_FOR_TIMEOUT_MS2=3e4,DEFAULT_WAIT_UNTIL="load",DIRECTION_VALUES=["back","forward"],refMapEntrySchema2=z29.object({role:z29.string(),name:z29.string().optional(),selector:z29.string(),nth:z29.number().optional()}),GoBackOrForward=class{name(){return"navigation_go-back-or-forward"}description(){return'\nNavigates to the previous or next page in history.\n- `direction: "back"` \u2014 previous page in history.\n- `direction: "forward"` \u2014 next page in history.\n\nIn case of multiple redirects, the navigation will resolve with the response of the last redirect.\nIf cannot go back/forward, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as a11y_take-aria-snapshot.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as `content_take-screenshot`). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{direction:z29.enum(DIRECTION_VALUES),timeout:z29.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS2).describe("Max wait ms. 0=no timeout."),waitUntil:z29.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after history navigation."),waitForNavigation:z29.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z29.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS2).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z29.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z29.object({interactiveOnly:z29.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z29.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as a11y_take-aria-snapshot."),includeScreenshot:z29.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z29.object({outputPath:z29.string().optional().default(os3.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z29.string().optional().default(DEFAULT_SCREENSHOT_NAME2).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z29.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z29.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z29.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z29.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. outputPath/name default to tmp and "screenshot".')}}outputSchema(){return{url:z29.string().describe("Contains the URL of the navigated page.").optional(),status:z29.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z29.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z29.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z29.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z29.string().optional(),refs:z29.record(z29.string(),refMapEntrySchema2).optional(),image:z29.object({data:z29.any().describe("Base64-encoded image data."),mimeType:z29.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=args.direction==="back"?await context.page.goBack({timeout:args.timeout,waitUntil:args.waitUntil}):await context.page.goForward({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS2;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os3.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME2;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path4.resolve(outputPath,filename);await fs4.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs5 from"node:fs/promises";import os4 from"node:os";import path5 from"node:path";import{z as z30}from"zod";var DEFAULT_SCREENSHOT_NAME3="screenshot",DEFAULT_TIMEOUT_MS3=0,DEFAULT_WAIT_FOR_TIMEOUT_MS3=3e4,DEFAULT_WAIT_UNTIL2="load",refMapEntrySchema3=z30.object({role:z30.string(),name:z30.string().optional(),selector:z30.string(),nth:z30.number().optional()}),GoTo=class{name(){return"navigation_go-to"}description(){return'\nNavigates to the given URL.\n**NOTE**: The tool either throws an error or returns a main resource response. \nThe only exceptions are navigation to `about:blank` or navigation to the same URL with a different hash, \nwhich would succeed and return empty response.\n\n**By default** (`includeSnapshot: true`), an ARIA snapshot with refs is taken after navigation and returned in `output` and `refs`; you can use refs (e1, e2, ...) in interaction tools without calling `a11y_take-aria-snapshot` separately. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false). Set `includeSnapshot: false` to get only url/status/ok.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as `content_take-screenshot`). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{url:z30.string(),timeout:z30.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS3).describe("Max wait ms. 0=no timeout."),waitUntil:z30.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL2).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after navigation."),waitForNavigation:z30.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z30.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS3).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z30.boolean().optional().default(!0).describe("Return ARIA snapshot with refs after nav."),snapshotOptions:z30.object({interactiveOnly:z30.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z30.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as a11y_take-aria-snapshot."),includeScreenshot:z30.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z30.object({outputPath:z30.string().optional().default(os4.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z30.string().optional().default(DEFAULT_SCREENSHOT_NAME3).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z30.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z30.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z30.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z30.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. outputPath/name default to tmp and "screenshot".')}}outputSchema(){return{url:z30.string().describe("Contains the URL of the navigated page.").optional(),status:z30.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z30.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z30.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z30.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z30.string().optional().describe("When includeSnapshot is true: page URL, title, and ARIA tree with refs."),refs:z30.record(z30.string(),refMapEntrySchema3).optional().describe("When includeSnapshot is true: map of ref id (e1, e2, ...) to role/name/selector for use in interaction tools."),image:z30.object({data:z30.any().describe("Base64-encoded image data."),mimeType:z30.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.goto(args.url,{timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS3;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os4.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME3;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path5.resolve(outputPath,filename);await fs5.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs6 from"node:fs/promises";import os5 from"node:os";import path6 from"node:path";import{z as z31}from"zod";var DEFAULT_SCREENSHOT_NAME4="screenshot",DEFAULT_TIMEOUT_MS4=0,DEFAULT_WAIT_FOR_TIMEOUT_MS4=3e4,DEFAULT_WAIT_UNTIL3="load",refMapEntrySchema4=z31.object({role:z31.string(),name:z31.string().optional(),selector:z31.string(),nth:z31.number().optional()}),Reload=class{name(){return"navigation_reload"}description(){return'\nReloads the current page.\nIn case of multiple redirects, the navigation resolves with the response of the last redirect.\nIf the reload does not produce a response, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as a11y_take-aria-snapshot.\n\nWhen `includeScreenshot: true`, the screenshot is saved to disk; `screenshotFilePath` is returned. Default path/name: OS temp dir and "screenshot". Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the path.\n '}inputSchema(){return{timeout:z31.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS4).describe("Max wait ms."),waitUntil:z31.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL3).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after reload."),waitForNavigation:z31.boolean().optional().default(!0).describe("Wait for reload then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z31.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS4).describe("Timeout for reload and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z31.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z31.object({interactiveOnly:z31.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z31.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as a11y_take-aria-snapshot."),includeScreenshot:z31.boolean().optional().default(!1).describe("Take a screenshot after reload; saved to disk (default: OS temp dir)."),screenshotOptions:z31.object({outputPath:z31.string().optional().default(os5.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z31.string().optional().default(DEFAULT_SCREENSHOT_NAME4).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z31.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z31.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z31.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z31.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. outputPath/name default to tmp and "screenshot".')}}outputSchema(){return{url:z31.string().describe("Contains the URL of the reloaded page.").optional(),status:z31.number().int().positive().describe("Contains the status code of the reloaded page (e.g., 200 for a success).").optional(),statusText:z31.string().describe('Contains the status text of the reloaded page (e.g. usually an "OK" for a success).').optional(),ok:z31.boolean().describe("Contains a boolean stating whether the reloaded page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z31.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z31.string().optional(),refs:z31.record(z31.string(),refMapEntrySchema4).optional(),image:z31.object({data:z31.any().describe("Base64-encoded image data."),mimeType:z31.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.reload({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS4;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os5.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME4;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path6.resolve(outputPath,filename);await fs6.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};var tools6=[new GoBackOrForward,new GoTo,new Reload];import{z as z32}from"zod";var GetConsoleMessages=class{name(){return"o11y_get-console-messages"}description(){return"Retrieves console messages/logs from the browser with filtering options."}inputSchema(){return{type:z32.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z32.string().optional().describe("Filter by message text."),timestamp:z32.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z32.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z32.object({count:z32.number().int().nonnegative().default(100),from:z32.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).default({count:100,from:"end"}).optional()}}outputSchema(){return{messages:z32.array(z32.object({type:z32.string().describe("Type of the console message."),text:z32.string().describe("Text of the console message."),location:z32.object({url:z32.string().describe("URL of the resource."),lineNumber:z32.number().nonnegative().describe("0-based line number in the resource."),columnNumber:z32.number().nonnegative().describe("0-based column number in the resource.")}).describe("Location of the console message in the resource.").optional(),timestamp:z32.number().int().nonnegative().describe("Unix epoch timestamp (in milliseconds) of the console message."),sequenceNumber:z32.number().int().nonnegative().describe(`
|
|
466
|
+
`.trim()}inputSchema(){return{mode:z28.enum(["by","to","top","bottom","left","right"]).optional().default(DEFAULT_MODE).describe("by=dx,dy; to=x,y; or edge (top/bottom/left/right)."),selector:z28.string().optional().describe("Scrollable container: ref (e.g. from latest <a11y_take-aria-snapshot>), getBy\u2026 expression, or CSS. Omit for viewport."),dx:z28.number().optional(),dy:z28.number().optional(),x:z28.number().optional(),y:z28.number().optional(),behavior:z28.enum(["auto","smooth"]).optional().default(DEFAULT_BEHAVIOR)}}outputSchema(){return{mode:z28.enum(["by","to","top","bottom","left","right"]).describe("The scroll mode used."),selector:z28.string().nullable().describe("The selector of the scroll container if provided; otherwise null (document viewport)."),behavior:z28.enum(["auto","smooth"]).describe("The scroll behavior used."),before:z28.object({x:z28.number().describe("ScrollLeft before scrolling."),y:z28.number().describe("ScrollTop before scrolling."),scrollWidth:z28.number().describe("Total scrollable width before scrolling."),scrollHeight:z28.number().describe("Total scrollable height before scrolling."),clientWidth:z28.number().describe("Viewport/container client width before scrolling."),clientHeight:z28.number().describe("Viewport/container client height before scrolling.")}).describe("Scroll metrics before the scroll action."),after:z28.object({x:z28.number().describe("ScrollLeft after scrolling."),y:z28.number().describe("ScrollTop after scrolling."),scrollWidth:z28.number().describe("Total scrollable width after scrolling."),scrollHeight:z28.number().describe("Total scrollable height after scrolling."),clientWidth:z28.number().describe("Viewport/container client width after scrolling."),clientHeight:z28.number().describe("Viewport/container client height after scrolling.")}).describe("Scroll metrics after the scroll action."),canScrollX:z28.boolean().describe("Whether horizontal scrolling is possible (scrollWidth > clientWidth)."),canScrollY:z28.boolean().describe("Whether vertical scrolling is possible (scrollHeight > clientHeight)."),maxScrollX:z28.number().describe("Maximum horizontal scrollLeft (scrollWidth - clientWidth)."),maxScrollY:z28.number().describe("Maximum vertical scrollTop (scrollHeight - clientHeight)."),isAtLeft:z28.boolean().describe("Whether the scroll position is at the far left."),isAtRight:z28.boolean().describe("Whether the scroll position is at the far right."),isAtTop:z28.boolean().describe("Whether the scroll position is at the very top."),isAtBottom:z28.boolean().describe("Whether the scroll position is at the very bottom.")}}async handle(context,args){let mode=args.mode??DEFAULT_MODE,selector=args.selector,behavior=args.behavior??DEFAULT_BEHAVIOR,dx=args.dx??0,dy=args.dy??0,x=args.x,y=args.y;if(mode==="to"&&typeof x!="number"&&typeof y!="number")throw new Error('mode="to" requires at least one of x or y.');if(mode==="by"&&dx===0&&dy===0)throw new Error('mode="by" requires dx and/or dy to be non-zero.');let params={modeEval:mode,selectorEval:selector,dxEval:dx,dyEval:dy,xEval:x,yEval:y,behaviorEval:behavior},result;return selector?result=await resolveSelectorOrRef(context,selector).evaluate((el,p)=>{let before={x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight},readMetrics=elem2=>({x:elem2.scrollLeft,y:elem2.scrollTop,scrollWidth:elem2.scrollWidth,scrollHeight:elem2.scrollHeight,clientWidth:elem2.clientWidth,clientHeight:elem2.clientHeight}),clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),elem=el,maxX=Math.max(0,elem.scrollWidth-elem.clientWidth),maxY=Math.max(0,elem.scrollHeight-elem.clientHeight);p.modeEval==="by"?elem.scrollTo({left:clamp(elem.scrollLeft+p.dxEval,0,maxX),top:clamp(elem.scrollTop+p.dyEval,0,maxY),behavior:p.behaviorEval}):p.modeEval==="to"?elem.scrollTo({left:typeof p.xEval=="number"?clamp(p.xEval,0,maxX):elem.scrollLeft,top:typeof p.yEval=="number"?clamp(p.yEval,0,maxY):elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="top"?elem.scrollTo({top:0,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="bottom"?elem.scrollTo({top:maxY,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="left"?elem.scrollTo({left:0,top:elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="right"&&elem.scrollTo({left:maxX,top:elem.scrollTop,behavior:p.behaviorEval});let after=readMetrics(elem);return{before,after,canScrollX:after.scrollWidth>after.clientWidth,canScrollY:after.scrollHeight>after.clientHeight,maxScrollX:Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY:Math.max(0,after.scrollHeight-after.clientHeight),isAtLeft:after.x<=1,isAtRight:after.x>=Math.max(0,after.scrollWidth-after.clientWidth)-1,isAtTop:after.y<=1,isAtBottom:after.y>=Math.max(0,after.scrollHeight-after.clientHeight)-1}},params):result=await context.page.evaluate(params2=>{let modeEval=params2.modeEval,selectorEval=params2.selectorEval,dxEval=params2.dxEval,dyEval=params2.dyEval,xEval=params2.xEval,yEval=params2.yEval,behaviorEval=params2.behaviorEval,getTarget=()=>{if(selectorEval){let el=document.querySelector(selectorEval);if(!el)throw new Error(`Element with selector "${selectorEval}" not found`);return el}let scrolling=document.scrollingElement||document.documentElement||document.body;if(!scrolling)throw new Error("No scrolling element available.");return scrolling},readMetrics=el=>({x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight}),clamp=(v,min,max)=>v<min?min:v>max?max:v,doScroll=el=>{let maxX=Math.max(0,el.scrollWidth-el.clientWidth),maxY=Math.max(0,el.scrollHeight-el.clientHeight);if(modeEval==="by"){let nextX=clamp(el.scrollLeft+dxEval,0,maxX),nextY=clamp(el.scrollTop+dyEval,0,maxY);el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="to"){let nextX=typeof xEval=="number"?clamp(xEval,0,maxX):el.scrollLeft,nextY=typeof yEval=="number"?clamp(yEval,0,maxY):el.scrollTop;el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="top"){el.scrollTo({top:0,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="bottom"){el.scrollTo({top:maxY,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="left"){el.scrollTo({left:0,top:el.scrollTop,behavior:behaviorEval});return}if(modeEval==="right"){el.scrollTo({left:maxX,top:el.scrollTop,behavior:behaviorEval});return}},target=getTarget(),before=readMetrics(target);doScroll(target);let after=readMetrics(target),maxScrollX=Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY=Math.max(0,after.scrollHeight-after.clientHeight),canScrollX=after.scrollWidth>after.clientWidth,canScrollY=after.scrollHeight>after.clientHeight,eps=1,isAtLeft=after.x<=eps,isAtRight=after.x>=maxScrollX-eps,isAtTop=after.y<=eps,isAtBottom=after.y>=maxScrollY-eps;return{before,after,canScrollX,canScrollY,maxScrollX,maxScrollY,isAtLeft,isAtRight,isAtTop,isAtBottom}},params),{mode,selector:selector??null,behavior,before:result.before,after:result.after,canScrollX:result.canScrollX,canScrollY:result.canScrollY,maxScrollX:result.maxScrollX,maxScrollY:result.maxScrollY,isAtLeft:result.isAtLeft,isAtRight:result.isAtRight,isAtTop:result.isAtTop,isAtBottom:result.isAtBottom}}};var tools5=[new Click,new Drag,new Fill,new Hover,new PressKey,new ResizeViewport,new ResizeWindow,new Select,new Scroll];import fs4 from"node:fs/promises";import os3 from"node:os";import path4 from"node:path";import{z as z29}from"zod";var DEFAULT_SCREENSHOT_NAME2="screenshot",DEFAULT_TIMEOUT_MS2=0,DEFAULT_WAIT_FOR_TIMEOUT_MS2=3e4,DEFAULT_WAIT_UNTIL="load",DIRECTION_VALUES=["back","forward"],refMapEntrySchema2=z29.object({role:z29.string(),name:z29.string().optional(),selector:z29.string(),nth:z29.number().optional()}),GoBackOrForward=class{name(){return"navigation_go-back-or-forward"}description(){return'\nNavigates to the previous or next page in history.\n- `direction: "back"` \u2014 previous page in history.\n- `direction: "forward"` \u2014 next page in history.\n\nIn case of multiple redirects, the navigation will resolve with the response of the last redirect.\nIf cannot go back/forward, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{direction:z29.enum(DIRECTION_VALUES),timeout:z29.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS2).describe("Max wait ms. 0=no timeout."),waitUntil:z29.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after history navigation."),waitForNavigation:z29.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z29.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS2).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z29.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z29.object({interactiveOnly:z29.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z29.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z29.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z29.object({outputPath:z29.string().optional().default(os3.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z29.string().optional().default(DEFAULT_SCREENSHOT_NAME2).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z29.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z29.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z29.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z29.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z29.string().describe("Contains the URL of the navigated page.").optional(),status:z29.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z29.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z29.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z29.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z29.string().optional(),refs:z29.record(z29.string(),refMapEntrySchema2).optional(),image:z29.object({data:z29.any().describe("Base64-encoded image data."),mimeType:z29.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=args.direction==="back"?await context.page.goBack({timeout:args.timeout,waitUntil:args.waitUntil}):await context.page.goForward({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS2;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os3.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME2;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path4.resolve(outputPath,filename);await fs4.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs5 from"node:fs/promises";import os4 from"node:os";import path5 from"node:path";import{z as z30}from"zod";var DEFAULT_SCREENSHOT_NAME3="screenshot",DEFAULT_TIMEOUT_MS3=0,DEFAULT_WAIT_FOR_TIMEOUT_MS3=3e4,DEFAULT_WAIT_UNTIL2="load",refMapEntrySchema3=z30.object({role:z30.string(),name:z30.string().optional(),selector:z30.string(),nth:z30.number().optional()}),GoTo=class{name(){return"navigation_go-to"}description(){return'\nNavigates to the given URL.\n**NOTE**: The tool either throws an error or returns a main resource response. \nThe only exceptions are navigation to `about:blank` or navigation to the same URL with a different hash, \nwhich would succeed and return empty response.\n\n**By default** (`includeSnapshot: true`), an ARIA snapshot with refs is taken after navigation and returned in `output` and `refs`; you can use refs (e1, e2, ...) in interaction tools without calling <a11y_take-aria-snapshot> separately. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false). Set `includeSnapshot: false` to get only url/status/ok.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{url:z30.string(),timeout:z30.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS3).describe("Max wait ms. 0=no timeout."),waitUntil:z30.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL2).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after navigation."),waitForNavigation:z30.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z30.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS3).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z30.boolean().optional().default(!0).describe("Return ARIA snapshot with refs after nav."),snapshotOptions:z30.object({interactiveOnly:z30.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z30.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z30.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z30.object({outputPath:z30.string().optional().default(os4.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z30.string().optional().default(DEFAULT_SCREENSHOT_NAME3).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z30.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z30.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z30.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z30.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z30.string().describe("Contains the URL of the navigated page.").optional(),status:z30.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z30.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z30.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z30.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z30.string().optional().describe("When includeSnapshot is true: page URL, title, and ARIA tree with refs."),refs:z30.record(z30.string(),refMapEntrySchema3).optional().describe("When includeSnapshot is true: map of ref id (e1, e2, ...) to role/name/selector for use in interaction tools."),image:z30.object({data:z30.any().describe("Base64-encoded image data."),mimeType:z30.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.goto(args.url,{timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS3;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os4.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME3;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path5.resolve(outputPath,filename);await fs5.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs6 from"node:fs/promises";import os5 from"node:os";import path6 from"node:path";import{z as z31}from"zod";var DEFAULT_SCREENSHOT_NAME4="screenshot",DEFAULT_TIMEOUT_MS4=0,DEFAULT_WAIT_FOR_TIMEOUT_MS4=3e4,DEFAULT_WAIT_UNTIL3="load",refMapEntrySchema4=z31.object({role:z31.string(),name:z31.string().optional(),selector:z31.string(),nth:z31.number().optional()}),Reload=class{name(){return"navigation_reload"}description(){return'\nReloads the current page.\nIn case of multiple redirects, the navigation resolves with the response of the last redirect.\nIf the reload does not produce a response, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is saved to disk; `screenshotFilePath` is returned. Default path/name: OS temp dir and "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the path.\n '}inputSchema(){return{timeout:z31.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS4).describe("Max wait ms."),waitUntil:z31.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL3).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after reload."),waitForNavigation:z31.boolean().optional().default(!0).describe("Wait for reload then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z31.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS4).describe("Timeout for reload and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z31.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z31.object({interactiveOnly:z31.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z31.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z31.boolean().optional().default(!1).describe("Take a screenshot after reload; saved to disk (default: OS temp dir)."),screenshotOptions:z31.object({outputPath:z31.string().optional().default(os5.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z31.string().optional().default(DEFAULT_SCREENSHOT_NAME4).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z31.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z31.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z31.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z31.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z31.string().describe("Contains the URL of the reloaded page.").optional(),status:z31.number().int().positive().describe("Contains the status code of the reloaded page (e.g., 200 for a success).").optional(),statusText:z31.string().describe('Contains the status text of the reloaded page (e.g. usually an "OK" for a success).').optional(),ok:z31.boolean().describe("Contains a boolean stating whether the reloaded page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z31.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z31.string().optional(),refs:z31.record(z31.string(),refMapEntrySchema4).optional(),image:z31.object({data:z31.any().describe("Base64-encoded image data."),mimeType:z31.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.reload({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS4;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os5.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME4;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path6.resolve(outputPath,filename);await fs6.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};var tools6=[new GoBackOrForward,new GoTo,new Reload];import{z as z32}from"zod";var GetConsoleMessages=class{name(){return"o11y_get-console-messages"}description(){return"Retrieves console messages/logs from the browser with filtering options."}inputSchema(){return{type:z32.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z32.string().optional().describe("Filter by message text."),timestamp:z32.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z32.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z32.object({count:z32.number().int().nonnegative().default(100),from:z32.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).default({count:100,from:"end"}).optional()}}outputSchema(){return{messages:z32.array(z32.object({type:z32.string().describe("Type of the console message."),text:z32.string().describe("Text of the console message."),location:z32.object({url:z32.string().describe("URL of the resource."),lineNumber:z32.number().nonnegative().describe("0-based line number in the resource."),columnNumber:z32.number().nonnegative().describe("0-based column number in the resource.")}).describe("Location of the console message in the resource.").optional(),timestamp:z32.number().int().nonnegative().describe("Unix epoch timestamp (in milliseconds) of the console message."),sequenceNumber:z32.number().int().nonnegative().describe(`
|
|
467
467
|
A monotonically increasing sequence number assigned to each console message.
|
|
468
468
|
It reflects the order in which messages were captured and can be used by clients
|
|
469
469
|
to retrieve messages incrementally by requesting only those with a higher sequence
|
|
@@ -531,13 +531,13 @@ Notes:
|
|
|
531
531
|
`.trim()}inputSchema(){return{pattern:z43.string().describe("URL glob."),response:z43.object({action:z43.enum(["fulfill","abort"]).optional().default("fulfill"),status:z43.number().int().min(100).max(599).optional(),headers:z43.record(z43.string(),z43.string()).optional(),body:z43.union([z43.string(),z43.record(z43.string(),z43.any()),z43.array(z43.any())]).optional(),abortErrorCode:z43.string().optional().describe("Error code when action=abort.")}).describe("Response."),delayMs:z43.number().int().nonnegative().optional(),times:z43.number().int().optional().describe("Max times to apply; -1=infinite."),chance:z43.number().min(0).max(1).optional().describe("Probability 0\u20131; omit=always.")}}outputSchema(){return{stubId:z43.string().describe("Unique id of the installed stub (use it to clear later)."),kind:z43.literal("mock_http_response").describe("Stub kind."),pattern:z43.string().describe("Glob pattern."),enabled:z43.boolean().describe("Whether the stub is enabled."),delayMs:z43.number().int().describe("Applied artificial delay in milliseconds."),times:z43.number().int().describe("Max applications (-1 means infinite)."),chance:z43.number().optional().describe("Apply probability (omit means always)."),action:z43.enum(["fulfill","abort"]).describe("Applied action."),status:z43.number().int().optional().describe("HTTP status (present when action=fulfill).")}}async handle(context,args){await ensureRoutingInstalled(context.browserContext);let action=args.response.action??"fulfill",delayMs=normalizeDelayMs(args.delayMs),times=normalizeTimesPublic(args.times),chance=normalizeChance(args.chance),status=args.response.status,headers=normalizeHeaders(args.response.headers),body=normalizeBody(args.response.body),abortErrorCode=normalizeAbortCode(args.response.abortErrorCode),stub=addMockHttpResponseStub(context.browserContext,{enabled:!0,pattern:args.pattern,action,status,headers,body,abortErrorCode,delayMs,times,chance}),out={stubId:stub.id,kind:"mock_http_response",pattern:stub.pattern,enabled:stub.enabled,delayMs:stub.delayMs,times:stub.times,action:stub.action};return typeof stub.chance=="number"&&(out.chance=stub.chance),stub.action==="fulfill"&&(out.status=typeof stub.status=="number"?stub.status:200),out}};var tools9=[new Clear,new InterceptHttpRequest,new List,new MockHttpResponse];import{z as z44}from"zod";var DEFAULT_TIMEOUT_MS5=3e4,DEFAULT_IDLE_TIME_MS=500,DEFAULT_MAX_CONNECTIONS=0,DEFAULT_POLL_INTERVAL_MS=50,WaitForNetworkIdle=class{name(){return"sync_wait-for-network-idle"}description(){return`
|
|
532
532
|
Waits until the page is network-idle: in-flight requests <= maxConnections for at least idleTimeMs (server-side tracking, no page globals).
|
|
533
533
|
Use before SPAs, screenshots, or AX snapshots for stable results. With long-polling, increase maxConnections or use shorter idleTimeMs.
|
|
534
|
-
`.trim()}inputSchema(){return{timeoutMs:z44.number().int().min(0).optional().default(DEFAULT_TIMEOUT_MS5).describe("Max wait ms before failing."),idleTimeMs:z44.number().int().min(0).optional().default(DEFAULT_IDLE_TIME_MS).describe("Network must stay idle for this many ms to resolve."),maxConnections:z44.number().int().min(0).optional().default(DEFAULT_MAX_CONNECTIONS).describe("Idle when in-flight requests <= this."),pollIntervalMs:z44.number().int().min(10).optional().default(DEFAULT_POLL_INTERVAL_MS).describe("Polling interval ms.")}}outputSchema(){return{waitedMs:z44.number().int().nonnegative().describe("Total time waited until the network became idle or the tool timed out (milliseconds)."),idleTimeMs:z44.number().int().nonnegative().describe("Idle duration required for success (milliseconds)."),timeoutMs:z44.number().int().nonnegative().describe("Maximum allowed wait time (milliseconds)."),maxConnections:z44.number().int().nonnegative().describe("Idle threshold used: in-flight requests must be <= this value."),pollIntervalMs:z44.number().int().nonnegative().describe("Polling interval used to sample the in-flight request count (milliseconds)."),finalInFlightRequests:z44.number().int().nonnegative().describe("The last observed number of in-flight requests at the moment the tool returned."),observedIdleMs:z44.number().int().nonnegative().describe("How long the in-flight request count stayed <= maxConnections right before returning (milliseconds).")}}async handle(context,args){let timeoutMs=args.timeoutMs??DEFAULT_TIMEOUT_MS5,idleTimeMs=args.idleTimeMs??DEFAULT_IDLE_TIME_MS,maxConnections=args.maxConnections??DEFAULT_MAX_CONNECTIONS,pollIntervalMs=args.pollIntervalMs??DEFAULT_POLL_INTERVAL_MS,startMs=Date.now(),deadlineMs=startMs+timeoutMs,lastNotIdleMs=startMs,lastInFlight=0;for(;;){let nowMs=Date.now();lastInFlight=context.numOfInFlightRequests(),lastInFlight>maxConnections&&(lastNotIdleMs=nowMs);let observedIdleMs=nowMs-lastNotIdleMs;if(observedIdleMs>=idleTimeMs)return{waitedMs:nowMs-startMs,idleTimeMs,timeoutMs,maxConnections,pollIntervalMs,finalInFlightRequests:lastInFlight,observedIdleMs};if(nowMs>=deadlineMs){let waitedMs=nowMs-startMs;throw new Error(`Timed out after ${waitedMs}ms waiting for network idle (idleTimeMs=${idleTimeMs}, maxConnections=${maxConnections}, inFlight=${lastInFlight}).`)}await this.sleep(pollIntervalMs)}}async sleep(ms){await new Promise(resolve=>{setTimeout(()=>resolve(),ms)})}};var tools10=[new WaitForNetworkIdle];var tools11=[...tools,...tools2,...tools3,...tools4,...tools5,...tools6,...tools7,...tools8,...tools9,...tools10];async function createToolSessionContext(sessionIdProvider){let sessionId=sessionIdProvider(),browserContextInfo=await newBrowserContext(),page=await newPage(browserContextInfo.browserContext),context=new BrowserToolSessionContext(sessionId,browserContextInfo.browserContext,page,{otelEnable:OTEL_ENABLE,dontCloseBrowserContext:browserContextInfo.dontCloseBrowserContext??!1});return await context.init(),debug(`Created session context for the session with id ${context.sessionId()}`),context}function createToolExecutor(){return new BrowserToolExecutor}import{Option}from"commander";var BrowserCliProvider=class{platform="browser";cliName="browser-devtools-cli";tools=tools11;sessionDescription="Manage browser sessions";cliExamples=["browser-devtools-cli --no-headless interactive","browser-devtools-cli --persistent --no-headless interactive"];bashCompletionOptions="--headless --no-headless --persistent --no-persistent --user-data-dir --use-system-browser --browser-path";bashCompletionCommands="daemon session tools config completion interactive navigation content interaction a11y accessibility o11y react
|
|
534
|
+
`.trim()}inputSchema(){return{timeoutMs:z44.number().int().min(0).optional().default(DEFAULT_TIMEOUT_MS5).describe("Max wait ms before failing."),idleTimeMs:z44.number().int().min(0).optional().default(DEFAULT_IDLE_TIME_MS).describe("Network must stay idle for this many ms to resolve."),maxConnections:z44.number().int().min(0).optional().default(DEFAULT_MAX_CONNECTIONS).describe("Idle when in-flight requests <= this."),pollIntervalMs:z44.number().int().min(10).optional().default(DEFAULT_POLL_INTERVAL_MS).describe("Polling interval ms.")}}outputSchema(){return{waitedMs:z44.number().int().nonnegative().describe("Total time waited until the network became idle or the tool timed out (milliseconds)."),idleTimeMs:z44.number().int().nonnegative().describe("Idle duration required for success (milliseconds)."),timeoutMs:z44.number().int().nonnegative().describe("Maximum allowed wait time (milliseconds)."),maxConnections:z44.number().int().nonnegative().describe("Idle threshold used: in-flight requests must be <= this value."),pollIntervalMs:z44.number().int().nonnegative().describe("Polling interval used to sample the in-flight request count (milliseconds)."),finalInFlightRequests:z44.number().int().nonnegative().describe("The last observed number of in-flight requests at the moment the tool returned."),observedIdleMs:z44.number().int().nonnegative().describe("How long the in-flight request count stayed <= maxConnections right before returning (milliseconds).")}}async handle(context,args){let timeoutMs=args.timeoutMs??DEFAULT_TIMEOUT_MS5,idleTimeMs=args.idleTimeMs??DEFAULT_IDLE_TIME_MS,maxConnections=args.maxConnections??DEFAULT_MAX_CONNECTIONS,pollIntervalMs=args.pollIntervalMs??DEFAULT_POLL_INTERVAL_MS,startMs=Date.now(),deadlineMs=startMs+timeoutMs,lastNotIdleMs=startMs,lastInFlight=0;for(;;){let nowMs=Date.now();lastInFlight=context.numOfInFlightRequests(),lastInFlight>maxConnections&&(lastNotIdleMs=nowMs);let observedIdleMs=nowMs-lastNotIdleMs;if(observedIdleMs>=idleTimeMs)return{waitedMs:nowMs-startMs,idleTimeMs,timeoutMs,maxConnections,pollIntervalMs,finalInFlightRequests:lastInFlight,observedIdleMs};if(nowMs>=deadlineMs){let waitedMs=nowMs-startMs;throw new Error(`Timed out after ${waitedMs}ms waiting for network idle (idleTimeMs=${idleTimeMs}, maxConnections=${maxConnections}, inFlight=${lastInFlight}).`)}await this.sleep(pollIntervalMs)}}async sleep(ms){await new Promise(resolve=>{setTimeout(()=>resolve(),ms)})}};var tools10=[new WaitForNetworkIdle];var tools11=[...tools,...tools2,...tools3,...tools4,...tools5,...tools6,...tools7,...tools8,...tools9,...tools10];async function createToolSessionContext(sessionIdProvider){let sessionId=sessionIdProvider(),browserContextInfo=await newBrowserContext(),page=await newPage(browserContextInfo.browserContext),context=new BrowserToolSessionContext(sessionId,browserContextInfo.browserContext,page,{otelEnable:OTEL_ENABLE,dontCloseBrowserContext:browserContextInfo.dontCloseBrowserContext??!1});return await context.init(),debug(`Created session context for the session with id ${context.sessionId()}`),context}function createToolExecutor(){return new BrowserToolExecutor}import{Option}from"commander";var BrowserCliProvider=class{platform="browser";cliName="browser-devtools-cli";tools=tools11;sessionDescription="Manage browser sessions";cliExamples=["browser-devtools-cli --no-headless interactive","browser-devtools-cli --persistent --no-headless interactive"];bashCompletionOptions="--headless --no-headless --persistent --no-persistent --user-data-dir --use-system-browser --browser-path";bashCompletionCommands="daemon session tools config completion interactive run navigation content interaction a11y accessibility o11y react stub sync figma debug";zshCompletionOptions=` '--headless[Run in headless mode]' \\
|
|
535
535
|
'--no-headless[Run in headful mode]' \\
|
|
536
536
|
'--persistent[Use persistent context]' \\
|
|
537
537
|
'--no-persistent[Use ephemeral context]' \\
|
|
538
538
|
'--user-data-dir[User data directory]:path:_files -/' \\
|
|
539
539
|
'--use-system-browser[Use system browser]' \\
|
|
540
|
-
'--browser-path[Browser executable path]:path:_files' \\`;zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage browser sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"navigation",description:"Navigation commands"},{name:"content",description:"Content extraction commands"},{name:"interaction",description:"Interaction commands"},{name:"a11y",description:"Accessibility commands"},{name:"accessibility",description:"Extended accessibility commands"},{name:"o11y",description:"Observability commands"},{name:"react",description:"React debugging commands"},{name:"run",description:"Script execution commands"},{name:"stub",description:"HTTP stubbing commands"},{name:"sync",description:"Synchronization commands"},{name:"figma",description:"Figma integration commands"},{name:"debug",description:"Non-blocking debugging commands"}];replPrompt="browser> ";cliDescription="Browser DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(opts){let env={...process.env};return opts.headless!==void 0&&(env.BROWSER_HEADLESS_ENABLE=String(opts.headless)),opts.persistent!==void 0&&(env.BROWSER_PERSISTENT_ENABLE=String(opts.persistent)),opts.userDataDir!==void 0&&(env.BROWSER_PERSISTENT_USER_DATA_DIR=opts.userDataDir),opts.useSystemBrowser!==void 0&&(env.BROWSER_USE_INSTALLED_ON_SYSTEM=String(opts.useSystemBrowser)),opts.browserPath!==void 0&&(env.BROWSER_EXECUTABLE_PATH=opts.browserPath),env}addOptions(cmd){return cmd.addOption(new Option("--headless","Run browser in headless mode (no visible window)").default(BROWSER_HEADLESS_ENABLE)).addOption(new Option("--no-headless","Run browser in headful mode (visible window)")).addOption(new Option("--persistent","Use persistent browser context (preserves cookies, localStorage)").default(BROWSER_PERSISTENT_ENABLE)).addOption(new Option("--no-persistent","Use ephemeral browser context (cleared on session end)")).addOption(new Option("--user-data-dir <path>","Directory for persistent browser context user data").default(BROWSER_PERSISTENT_USER_DATA_DIR)).addOption(new Option("--use-system-browser","Use system-installed Chrome instead of bundled browser").default(BROWSER_USE_INSTALLED_ON_SYSTEM)).addOption(new Option("--browser-path <path>","Custom browser executable path"))}getConfig(){return{headless:BROWSER_HEADLESS_ENABLE,persistent:BROWSER_PERSISTENT_ENABLE,userDataDir:BROWSER_PERSISTENT_USER_DATA_DIR,useSystemBrowser:BROWSER_USE_INSTALLED_ON_SYSTEM,executablePath:BROWSER_EXECUTABLE_PATH,locale:BROWSER_LOCALE}}formatConfig(configValues){let lines=[" Browser:",` Headless: ${configValues.headless}`,` Persistent: ${configValues.persistent}`,` User Data Dir: ${configValues.userDataDir||"(default)"}`,` Use System Browser: ${configValues.useSystemBrowser}`,` Executable Path: ${configValues.executablePath||"(bundled)"}`];return configValues.locale&&lines.push(` Locale: ${configValues.locale}`),lines.join(`
|
|
540
|
+
'--browser-path[Browser executable path]:path:_files' \\`;zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage browser sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"navigation",description:"Navigation commands"},{name:"content",description:"Content extraction commands"},{name:"interaction",description:"Interaction commands"},{name:"a11y",description:"Accessibility commands"},{name:"accessibility",description:"Extended accessibility commands"},{name:"o11y",description:"Observability commands"},{name:"react",description:"React debugging commands"},{name:"run",description:"Script execution commands"},{name:"stub",description:"HTTP stubbing commands"},{name:"sync",description:"Synchronization commands"},{name:"figma",description:"Figma integration commands"},{name:"debug",description:"Non-blocking debugging commands"}];replPrompt="browser> ";cliDescription="Browser DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(opts){let env={...process.env};return env.PLATFORM="browser",opts.headless!==void 0&&(env.BROWSER_HEADLESS_ENABLE=String(opts.headless)),opts.persistent!==void 0&&(env.BROWSER_PERSISTENT_ENABLE=String(opts.persistent)),opts.userDataDir!==void 0&&(env.BROWSER_PERSISTENT_USER_DATA_DIR=opts.userDataDir),opts.useSystemBrowser!==void 0&&(env.BROWSER_USE_INSTALLED_ON_SYSTEM=String(opts.useSystemBrowser)),opts.browserPath!==void 0&&(env.BROWSER_EXECUTABLE_PATH=opts.browserPath),env}addOptions(cmd){return cmd.addOption(new Option("--headless","Run browser in headless mode (no visible window)").default(BROWSER_HEADLESS_ENABLE)).addOption(new Option("--no-headless","Run browser in headful mode (visible window)")).addOption(new Option("--persistent","Use persistent browser context (preserves cookies, localStorage)").default(BROWSER_PERSISTENT_ENABLE)).addOption(new Option("--no-persistent","Use ephemeral browser context (cleared on session end)")).addOption(new Option("--user-data-dir <path>","Directory for persistent browser context user data").default(BROWSER_PERSISTENT_USER_DATA_DIR)).addOption(new Option("--use-system-browser","Use system-installed Chrome instead of bundled browser").default(BROWSER_USE_INSTALLED_ON_SYSTEM)).addOption(new Option("--browser-path <path>","Custom browser executable path"))}getConfig(){return{headless:BROWSER_HEADLESS_ENABLE,persistent:BROWSER_PERSISTENT_ENABLE,userDataDir:BROWSER_PERSISTENT_USER_DATA_DIR,useSystemBrowser:BROWSER_USE_INSTALLED_ON_SYSTEM,executablePath:BROWSER_EXECUTABLE_PATH,locale:BROWSER_LOCALE}}formatConfig(configValues){let lines=[" Browser:",` Headless: ${configValues.headless}`,` Persistent: ${configValues.persistent}`,` User Data Dir: ${configValues.userDataDir||"(default)"}`,` Use System Browser: ${configValues.useSystemBrowser}`,` Executable Path: ${configValues.executablePath||"(bundled)"}`];return configValues.locale&&lines.push(` Locale: ${configValues.locale}`),lines.join(`
|
|
541
541
|
`)}formatConfigForRepl(opts){return[` headless = ${opts.headless??BROWSER_HEADLESS_ENABLE}`,` persistent = ${opts.persistent??BROWSER_PERSISTENT_ENABLE}`,` user-data-dir = ${opts.userDataDir||BROWSER_PERSISTENT_USER_DATA_DIR||"(default)"}`,` use-system-browser = ${opts.useSystemBrowser??BROWSER_USE_INSTALLED_ON_SYSTEM}`,` browser-path = ${opts.browserPath||"(auto)"}`].join(`
|
|
542
542
|
`)}},cliProvider=new BrowserCliProvider;var SERVER_INSTRUCTIONS=`
|
|
543
543
|
This MCP server exposes a Playwright-powered browser runtime to AI agents,
|
|
@@ -547,7 +547,7 @@ It supports both visual understanding and code-level inspection of browser state
|
|
|
547
547
|
with a focus on AI-driven exploration, diagnosis, and action.
|
|
548
548
|
|
|
549
549
|
Capabilities include navigation, ARIA/AX snapshots, screenshots, element interaction (click, fill, hover, select, drag),
|
|
550
|
-
the execute tool for batching multiple steps in one call, and observability (console, HTTP, Web Vitals, stubs, non-blocking debug probes).
|
|
550
|
+
the <execute> tool for batching multiple steps in one call, and observability (console, HTTP, Web Vitals, stubs, non-blocking debug probes).
|
|
551
551
|
|
|
552
552
|
**Important rules for AI agents:**
|
|
553
553
|
|
|
@@ -556,12 +556,12 @@ the execute tool for batching multiple steps in one call, and observability (con
|
|
|
556
556
|
|
|
557
557
|
Use ARIA snapshot first, then AX tree if needed. Use screenshot only when necessary.
|
|
558
558
|
|
|
559
|
-
After navigating to a page, your FIRST inspection call MUST be
|
|
560
|
-
NOT
|
|
559
|
+
After navigating to a page, your FIRST inspection call MUST be <a11y_take-aria-snapshot> (or <a11y_take-ax-tree-snapshot>),
|
|
560
|
+
NOT <content_take-screenshot>.
|
|
561
561
|
DO NOT take a screenshot just to "see the page" or understand what is on it.
|
|
562
|
-
1. Call
|
|
563
|
-
2. If you need layout, bounding boxes, or occlusion: call
|
|
564
|
-
3. Call
|
|
562
|
+
1. Call <a11y_take-aria-snapshot> FIRST for structure, roles, and semantics.
|
|
563
|
+
2. If you need layout, bounding boxes, or occlusion: call <a11y_take-ax-tree-snapshot>.
|
|
564
|
+
3. Call <content_take-screenshot> ONLY when you need to verify how something looks visually
|
|
565
565
|
(e.g. design check, visual bug, or when ARIA/AX are insufficient). Do not use screenshot to understand page structure.
|
|
566
566
|
---
|
|
567
567
|
|
|
@@ -572,9 +572,9 @@ the execute tool for batching multiple steps in one call, and observability (con
|
|
|
572
572
|
DO NOT GUESS SELECTORS FROM SCREENSHOTS.
|
|
573
573
|
|
|
574
574
|
Take a snapshot first to get real selectors and refs:
|
|
575
|
-
- Call
|
|
575
|
+
- Call <a11y_take-aria-snapshot>, <a11y_take-ax-tree-snapshot>, or <content_get-as-html> (e.g. selector "form")
|
|
576
576
|
to see actual ids, roles, and markup.
|
|
577
|
-
-
|
|
577
|
+
- <a11y_take-aria-snapshot> returns a tree with refs (e1, e2, ...) and stores them in context. In click, fill, hover, select, drag, scroll use the selector as:
|
|
578
578
|
a ref ("e1", "@e1", "ref=e1"), a Playwright-style expression (e.g. getByRole('button', { name: 'Login' }), getByLabel('Email'),
|
|
579
579
|
getByText('Register'), getByPlaceholder('\u2026'), getByTitle('\u2026'), getByAltText('\u2026'), getByTestId('\u2026')), or a CSS selector.
|
|
580
580
|
Refs are valid until the next snapshot or navigation; re-snapshot after page/DOM changes.
|
|
@@ -582,7 +582,7 @@ the execute tool for batching multiple steps in one call, and observability (con
|
|
|
582
582
|
type="text", wrappers, or different attributes.
|
|
583
583
|
|
|
584
584
|
SNAPSHOT FIRST, THEN INTERACT.
|
|
585
|
-
Before inspection: use sync_wait-for-network-idle for SPA/async content; use interaction_click with waitForNavigation: true
|
|
585
|
+
Before inspection: use <sync_wait-for-network-idle> for SPA/async content; use <interaction_click> with waitForNavigation: true
|
|
586
586
|
when the click navigates to a new page.
|
|
587
587
|
---
|
|
588
588
|
|
|
@@ -593,17 +593,17 @@ the execute tool for batching multiple steps in one call, and observability (con
|
|
|
593
593
|
1. NAVIGATE \u2014 go to a page; the response includes an ARIA snapshot with refs.
|
|
594
594
|
2. PLAN \u2014 read the snapshot to understand the page structure, then decide the next steps (e.g. fill fields, click buttons,
|
|
595
595
|
take a snapshot).
|
|
596
|
-
3. BATCH \u2014 use the
|
|
596
|
+
3. BATCH \u2014 use the <execute> tool to run all planned steps in a SINGLE call via callTool().
|
|
597
597
|
|
|
598
598
|
This is the PREFERRED way to perform multi-step interactions. Instead of making
|
|
599
599
|
separate tool calls for each fill/click/select, batch them together:
|
|
600
600
|
|
|
601
|
-
// execute tool code (e.g. form submit then new page):
|
|
602
|
-
await callTool('interaction_fill', { selector: 'e3', value: 'user@test.com' });
|
|
603
|
-
await callTool('interaction_fill', { selector: 'e5', value: 'secret123' });
|
|
604
|
-
await callTool('interaction_click', { selector: 'e7', waitForNavigation: true });
|
|
605
|
-
await callTool('a11y_take-aria-snapshot', {}, true);
|
|
606
|
-
await callTool('content_take-screenshot', {}, true);
|
|
601
|
+
// <execute> tool code (e.g. form submit then new page):
|
|
602
|
+
await callTool('<interaction_fill>', { selector: 'e3', value: 'user@test.com' });
|
|
603
|
+
await callTool('<interaction_fill>', { selector: 'e5', value: 'secret123' });
|
|
604
|
+
await callTool('<interaction_click>', { selector: 'e7', waitForNavigation: true });
|
|
605
|
+
await callTool('<a11y_take-aria-snapshot>', {}, true);
|
|
606
|
+
await callTool('<content_take-screenshot>', {}, true);
|
|
607
607
|
|
|
608
608
|
Benefits: fewer round-trips, lower token usage, faster execution.
|
|
609
609
|
Use individual tool calls only when you need to inspect the result before deciding the next step.
|
|
@@ -612,19 +612,19 @@ the execute tool for batching multiple steps in one call, and observability (con
|
|
|
612
612
|
<ui_debugging_policy>
|
|
613
613
|
When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follow this policy:
|
|
614
614
|
|
|
615
|
-
1. **Synchronization**: When a click navigates to a new page (e.g. submit), use
|
|
615
|
+
1. **Synchronization**: When a click navigates to a new page (e.g. submit), use <interaction_click> with waitForNavigation: true \u2014
|
|
616
616
|
this waits for the new page in parallel with the click (race-free). If the page loads content asynchronously (SPA/XHR without navigation),
|
|
617
|
-
call
|
|
617
|
+
call <sync_wait-for-network-idle> after the click to ensure the page is stable before inspection.
|
|
618
618
|
|
|
619
619
|
2. **To understand page structure (mandatory order\u2014do not default to screenshot):**
|
|
620
|
-
- Call
|
|
621
|
-
- If you need layout/occlusion/visibility: call
|
|
622
|
-
- Call
|
|
620
|
+
- Call <a11y_take-aria-snapshot> FIRST for structure, roles, and semantics.
|
|
621
|
+
- If you need layout/occlusion/visibility: call <a11y_take-ax-tree-snapshot> (with "checkOcclusion:true" when relevant).
|
|
622
|
+
- Call <content_take-screenshot> ONLY when you need to verify how something looks visually (e.g. design check, visual bug) or when ARIA/AX are insufficient.
|
|
623
623
|
Do NOT use screenshot to "see the page" or understand structure.
|
|
624
624
|
|
|
625
625
|
3. **Before any element interaction** (click, fill, hover, select, drag): Take a snapshot first to get real selectors and refs.
|
|
626
|
-
Call
|
|
627
|
-
or
|
|
626
|
+
Call <a11y_take-aria-snapshot> (returns refs e1, e2, ... usable as selector "e1", "@e1", or "ref=e1"), <a11y_take-ax-tree-snapshot>,
|
|
627
|
+
or <content_get-as-html> (e.g. selector "form") to see actual ids, roles, and markup. You can also use Playwright-style expressions
|
|
628
628
|
as selector: getByRole('button', { name: 'Login' }), getByLabel('Email'), getByText('Register'), getByPlaceholder('\u2026'),
|
|
629
629
|
getByTitle('\u2026'), getByAltText('\u2026'), getByTestId('\u2026'), or CSS. Refs are valid until next snapshot or navigation.
|
|
630
630
|
Do NOT guess selectors from screenshots\u2014real markup often differs (e.g. type="text" instead of type="email"). Snapshot first, then interact.
|
|
@@ -632,13 +632,13 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
632
632
|
4. **Screenshot usage**: When you do take a screenshot, do NOT set "includeBase64" to true unless the assistant cannot read the returned
|
|
633
633
|
file path (e.g. remote/container). The image is always saved to disk.
|
|
634
634
|
|
|
635
|
-
5. **Accessibility Tree Analysis**: Call
|
|
635
|
+
5. **Accessibility Tree Analysis**: Call <a11y_take-ax-tree-snapshot> tool with "checkOcclusion:true"
|
|
636
636
|
- Provides precise bounding boxes, runtime visual data, and occlusion detection
|
|
637
637
|
- Best for detecting overlaps and measuring exact positions
|
|
638
638
|
- Use "onlyVisible:true" or "onlyInViewport:true" to filter results
|
|
639
639
|
- Set "includeStyles:true" to analyze computed CSS properties
|
|
640
640
|
|
|
641
|
-
6. **ARIA Snapshot**: Call
|
|
641
|
+
6. **ARIA Snapshot**: Call <a11y_take-aria-snapshot> tool (full page or specific selector)
|
|
642
642
|
- Provides semantic structure and accessibility roles; returns refs (e1, e2, ...) for use in interaction tools (selector "e1", "@e1",
|
|
643
643
|
"ref=e1", or getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId, or CSS)
|
|
644
644
|
- Set cursorInteractive: true to also get refs for custom clickable elements (e.g. div/span with cursor:pointer or onclick)
|
|
@@ -647,7 +647,7 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
647
647
|
- Refs are valid until next snapshot or navigation; re-snapshot after page/DOM changes
|
|
648
648
|
- Use in combination with AX tree snapshot for comprehensive analysis
|
|
649
649
|
|
|
650
|
-
7. **Design Comparison** (if Figma design is available): Call
|
|
650
|
+
7. **Design Comparison** (if Figma design is available): Call <figma_compare-page-with-design> tool
|
|
651
651
|
- Compares live page UI against Figma design snapshot
|
|
652
652
|
- Returns combined similarity score using multiple signals (MSSIM, image embedding, text embedding)
|
|
653
653
|
- Use "semantic" mode for real data vs design data comparisons
|
|
@@ -655,24 +655,24 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
655
655
|
- Notes explain which signals were used or skipped
|
|
656
656
|
|
|
657
657
|
8. **React Component Inspection** (if page uses React): Use React tools to understand component structure
|
|
658
|
-
- Call
|
|
659
|
-
- Call
|
|
658
|
+
- Call <react_get-component-for-element> with selector or (x,y) to find React component for a DOM element
|
|
659
|
+
- Call <react_get-element-for-component> to find DOM elements rendered by a React component
|
|
660
660
|
- **Important:** These tools require persistent browser context (BROWSER_PERSISTENT_ENABLE=true)
|
|
661
661
|
- React DevTools extension must be manually installed in the browser profile for optimal reliability
|
|
662
662
|
- Without extension, tools use best-effort DOM scanning (less reliable)
|
|
663
663
|
- Component names and debug source info are best-effort and may vary by build (dev/prod)
|
|
664
664
|
|
|
665
|
-
9. **Performance Check** (optional but recommended): Call
|
|
665
|
+
9. **Performance Check** (optional but recommended): Call <o11y_get-web-vitals> to assess page performance
|
|
666
666
|
- Identifies performance issues that may affect user experience
|
|
667
667
|
- Provides actionable recommendations based on Google's thresholds
|
|
668
668
|
|
|
669
669
|
10. **Console & Network Inspection**: Check for errors and failed requests
|
|
670
|
-
- Call
|
|
671
|
-
- Call
|
|
670
|
+
- Call <o11y_get-console-messages> with "type:ERROR" to find JavaScript errors
|
|
671
|
+
- Call <o11y_get-http-requests> with "ok:false" to find failed network requests
|
|
672
672
|
- If network issues are suspected or testing error scenarios, use stub tools:
|
|
673
|
-
- Use
|
|
674
|
-
- Use
|
|
675
|
-
- Use
|
|
673
|
+
- Use <stub_mock-http-response> to simulate error responses (e.g., 500, 404, timeout) to test UI error handling
|
|
674
|
+
- Use <stub_intercept-http-request> to modify requests (e.g., inject headers) to test different scenarios
|
|
675
|
+
- Use <stub_list> to verify active stubs and <stub_clear> to remove them after testing
|
|
676
676
|
|
|
677
677
|
11. **Manual Verification**: Calculate bounding box overlaps:
|
|
678
678
|
- Horizontal: (element1.x + element1.width) \u2264 element2.x
|
|
@@ -682,36 +682,36 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
682
682
|
accessibility problems, semantic structure issues, design parity issues (if compared with Figma),
|
|
683
683
|
React component structure issues (if inspected), performance problems, console errors, failed requests
|
|
684
684
|
|
|
685
|
-
13. **JavaScript Execution** (when needed): Use the
|
|
685
|
+
13. **JavaScript Execution** (when needed): Use the <execute> tool; the VM receives \`page\` (Playwright Page) from the session context.
|
|
686
686
|
Use \`page.evaluate()\` to run script in the browser, or the Playwright API (e.g. \`page.locator()\`, \`page.goto()\`) for automation.
|
|
687
687
|
|
|
688
688
|
14. **Non-Blocking JavaScript Debugging** (for deep code investigation):
|
|
689
|
-
- Use
|
|
690
|
-
- Use
|
|
691
|
-
- Use
|
|
692
|
-
- Use
|
|
693
|
-
- Use
|
|
694
|
-
- Use
|
|
695
|
-
- Use
|
|
696
|
-
- Retrieve snapshots with
|
|
689
|
+
- Use <debug_put-tracepoint> to set breakpoints that capture call stack and local variables without pausing
|
|
690
|
+
- Use <debug_put-logpoint> for lightweight logging at specific code locations
|
|
691
|
+
- Use <debug_put-exceptionpoint> with state "uncaught" or "all" to capture exception snapshots
|
|
692
|
+
- Use <debug_add-watch> to add expressions evaluated at every tracepoint hit
|
|
693
|
+
- Use <debug_list-probes> to list tracepoints, logpoints, and watch expressions (optional types)
|
|
694
|
+
- Use <debug_remove-probe> (type + id) to remove a single probe or watch
|
|
695
|
+
- Use <debug_clear-probes> to clear tracepoints, logpoints, and/or watches (optional types)
|
|
696
|
+
- Retrieve snapshots with <debug_get-probe-snapshots> (response: tracepointSnapshots, logpointSnapshots, exceptionpointSnapshots;
|
|
697
697
|
optional types, probeId, fromSequence, limit)
|
|
698
|
-
- Use
|
|
698
|
+
- Use <debug_clear-probe-snapshots> to clear captured snapshots (optional types, probeId)
|
|
699
699
|
- Use "fromSequence" parameter to poll only new snapshots since last retrieval
|
|
700
700
|
- Probes support source maps: specify original source file paths for bundled applications
|
|
701
|
-
- Use
|
|
701
|
+
- Use <debug_status> to check current debugging state and probe counts
|
|
702
702
|
|
|
703
703
|
**Batch Execution \u2014 STRONGLY RECOMMENDED:**
|
|
704
704
|
- After navigating to a page and reading the ARIA snapshot (which comes with the navigation response), plan all the interactions you need,
|
|
705
|
-
then execute them in a single
|
|
705
|
+
then execute them in a single <execute> tool call using callTool().
|
|
706
706
|
- Example workflow:
|
|
707
|
-
1. Call
|
|
707
|
+
1. Call <navigation_go-to> \u2192 read the returned ARIA snapshot and refs.
|
|
708
708
|
2. Identify the steps: fill email, fill password, click submit.
|
|
709
|
-
3. Use
|
|
710
|
-
await callTool('interaction_fill', { selector: 'e3', value: 'user@test.com' });
|
|
711
|
-
await callTool('interaction_fill', { selector: 'e5', value: 'secret123' });
|
|
712
|
-
await callTool('interaction_click', { selector: 'e7', waitForNavigation: true });
|
|
713
|
-
await callTool('a11y_take-aria-snapshot', {}, true);
|
|
714
|
-
await callTool('content_take-screenshot', {}, true);
|
|
709
|
+
3. Use <execute> to batch all steps in one call:
|
|
710
|
+
await callTool('<interaction_fill>', { selector: 'e3', value: 'user@test.com' });
|
|
711
|
+
await callTool('<interaction_fill>', { selector: 'e5', value: 'secret123' });
|
|
712
|
+
await callTool('<interaction_click>', { selector: 'e7', waitForNavigation: true });
|
|
713
|
+
await callTool('<a11y_take-aria-snapshot>', {}, true);
|
|
714
|
+
await callTool('<content_take-screenshot>', {}, true);
|
|
715
715
|
4. Read the batched result and plan the next batch if needed.
|
|
716
716
|
- This dramatically reduces tool-call round-trips, saves tokens, and speeds up execution.
|
|
717
717
|
- Use individual (non-batched) tool calls only when you must inspect the result before deciding the next step.
|
|
@@ -725,8 +725,8 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
725
725
|
- Do not set includeBase64 on screenshot unless the assistant cannot read the file path.
|
|
726
726
|
- AX tree: Technical measurements, occlusion, precise positioning, visual diagnostics
|
|
727
727
|
- ARIA snapshot: Semantic understanding, accessibility structure, role hierarchy
|
|
728
|
-
- interaction_click waitForNavigation: true \u2014 when click opens a new page, waits for navigation in parallel (race-free)
|
|
729
|
-
- sync_wait-for-network-idle
|
|
728
|
+
- <interaction_click> waitForNavigation: true \u2014 when click opens a new page, waits for navigation in parallel (race-free)
|
|
729
|
+
- <sync_wait-for-network-idle>: configurable idle for SPAs and async content
|
|
730
730
|
- Web Vitals: Performance context for UI issues
|
|
731
731
|
- Tracepoints: Deep code investigation with call stack and variables (non-blocking)
|
|
732
732
|
- Logpoints: Lightweight logging at specific code locations
|
|
@@ -736,18 +736,18 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
|
|
|
736
736
|
- Never assume "looks good visually" = "no problems". Overlaps and accessibility issues
|
|
737
737
|
can be functionally broken while appearing visually correct.
|
|
738
738
|
- Always check occlusion when interactions fail or elements appear misaligned.
|
|
739
|
-
- Use
|
|
740
|
-
- For responsive issues, use
|
|
739
|
+
- Use <interaction_scroll> if elements are below the fold before inspection.
|
|
740
|
+
- For responsive issues, use <interaction_resize-viewport> or <interaction_resize-window> tools to test different sizes.
|
|
741
741
|
</ui_debugging_policy>
|
|
742
|
-
`;var EXECUTE_IMPORTANT_DESCRIPTION_BROWSER="\n**IMPORTANT:** \n- `page` (Playwright Page) is available in the VM \u2014 use it for navigation or `page.evaluate()`. \n- Prefer interaction tools with refs (e1, e2 from
|
|
742
|
+
`;var EXECUTE_IMPORTANT_DESCRIPTION_BROWSER="\n**IMPORTANT:** \n- `page` (Playwright Page) is available in the VM \u2014 use it for navigation or `page.evaluate()`. \n- Prefer interaction tools with refs (e1, e2 from <a11y_take-aria-snapshot>); use raw Playwright only as last resort.\n- `document`/`window` are not in the VM \u2014 use `page.evaluate(() => { ... })` to run code in the browser.\n- Use `waitForNavigation: true` on <interaction_click> when the click navigates. \n- After navigation, do not continue with refs from the previous page \u2014 take fresh refs with <a11y_take-aria-snapshot> first.\n".trim(),EXECUTE_DESCRIPTION_BROWSER=`
|
|
743
743
|
**Example** \u2014 fill form, submit (with navigation wait), then snapshot and screenshot:
|
|
744
|
-
await callTool('interaction_fill', { selector: 'e3', value: 'user@test.com' });
|
|
745
|
-
await callTool('interaction_fill', { selector: 'e5', value: 'secret123' });
|
|
746
|
-
await callTool('interaction_click', { selector: 'e7', waitForNavigation: true });
|
|
744
|
+
await callTool('<interaction_fill>', { selector: 'e3', value: 'user@test.com' });
|
|
745
|
+
await callTool('<interaction_fill>', { selector: 'e5', value: 'secret123' });
|
|
746
|
+
await callTool('<interaction_click>', { selector: 'e7', waitForNavigation: true });
|
|
747
747
|
// Or with page.locator: await page.locator('button').click();
|
|
748
748
|
// Or with page.evaluate: await page.evaluate(() => document.querySelector('button').click());
|
|
749
|
-
await callTool('a11y_take-aria-snapshot', {}, true);
|
|
750
|
-
await callTool('content_take-screenshot', {}, true);
|
|
749
|
+
await callTool('<a11y_take-aria-snapshot>', {}, true);
|
|
750
|
+
await callTool('<content_take-screenshot>', {}, true);
|
|
751
751
|
`.trim(),platformInfo={serverInfo:{instructions:BROWSER_SERVER_INSTRUCTIONS_ENABLE?SERVER_INSTRUCTIONS:void 0,policies:BROWSER_POLICY_UI_DEBUGGING_ENABLE?[UI_DEBUGGING_POLICY]:[]},toolsInfo:{tools:tools11,createToolSessionContext,createToolExecutor,executeImportantDescription:EXECUTE_IMPORTANT_DESCRIPTION_BROWSER,executeDescription:EXECUTE_DESCRIPTION_BROWSER},cliInfo:{cliProvider}};import{EventEmitter}from"node:events";import{WebSocket}from"ws";var NodeCDPSession=class extends EventEmitter{ws=null;reqId=0;pending=new Map;connected=!1;wsUrl;connectOptions;constructor(wsUrl,connectOptions){super(),this.wsUrl=wsUrl,this.connectOptions=connectOptions}async connect(){if(this.connected)return;let wsOptions=this.connectOptions?.headers?{headers:this.connectOptions.headers}:void 0;return new Promise((resolve,reject)=>{this.ws=new WebSocket(this.wsUrl,wsOptions);let timeout=setTimeout(()=>{reject(new Error(`Connection timeout to ${this.wsUrl}`)),this.ws?.close()},1e4);this.ws.on("open",()=>{clearTimeout(timeout),this.connected=!0,resolve()}),this.ws.on("error",err=>{clearTimeout(timeout),this.connected||reject(err)}),this.ws.on("close",()=>{this.connected=!1;for(let[id,handler]of this.pending)handler.reject(new Error("CDP session closed"));this.pending.clear()}),this.ws.on("message",data=>{try{let msg=JSON.parse(data.toString());this._handleMessage(msg)}catch{}})})}async send(method,params){if(!this.ws||!this.connected)throw new Error("CDP session is not connected");let id=++this.reqId;return new Promise((resolve,reject)=>{this.pending.set(id,{resolve,reject});let message=JSON.stringify({id,method,params:params||{}});this.ws.send(message,err=>{err&&(this.pending.delete(id),reject(err))}),setTimeout(()=>{this.pending.has(id)&&(this.pending.delete(id),reject(new Error(`CDP command timeout: ${method}`)))},3e4)})}async detach(){if(this.ws){this.connected=!1,this.ws.close(),this.ws=null;for(let[,handler]of this.pending)handler.reject(new Error("CDP session detached"));this.pending.clear()}}isConnected(){return this.connected}_handleMessage(msg){if(msg.id!==void 0){let handler=this.pending.get(msg.id);handler&&(this.pending.delete(msg.id),msg.error?handler.reject(new Error(`CDP error: ${msg.error.message||JSON.stringify(msg.error)}`)):handler.resolve(msg.result||{}))}else msg.method&&this.emit(msg.method,msg.params)}};async function createNodeCDPSession(wsUrl,connectOptions){let session=new NodeCDPSession(wsUrl,connectOptions);return await session.connect(),session}import{execSync as execSync2}from"node:child_process";import*as http from"node:http";var DEFAULT_HOST="127.0.0.1",DEFAULT_PORT=9229,DEFAULT_TIMEOUT_MS6=1e4,ACTIVATION_POLL_INTERVAL_MS=200,MAX_INSPECTOR_SCAN_PORTS=20;function buildInspectorWebSocketUrl(host,port,targetWebSocketDebuggerUrl){let path7=new URL(targetWebSocketDebuggerUrl).pathname;return`ws://${host}:${port}${path7}`}var DISCOVERY_HOST_HEADER="127.0.0.1:9229";function inspectorWsConnectOptions(wsUrl){try{let hostname=new URL(wsUrl).hostname.toLowerCase();return hostname==="127.0.0.1"||hostname==="localhost"?void 0:{headers:{Host:DISCOVERY_HOST_HEADER}}}catch{return}}async function discoverInspectorTargets(host=DEFAULT_HOST,port=DEFAULT_PORT,timeoutMs=5e3){return new Promise((resolve,reject)=>{let timeout=setTimeout(()=>{reject(new Error(`Inspector discovery timeout on ${host}:${port}`))},timeoutMs),req=http.get({host,port,path:"/json/list",...host!==DEFAULT_HOST&&host!=="localhost"&&{headers:{Host:DISCOVERY_HOST_HEADER}}},res=>{let data="";res.on("data",chunk=>{data+=chunk}),res.on("end",()=>{clearTimeout(timeout);try{let targets=JSON.parse(data);resolve(targets)}catch{reject(new Error(`Invalid response from inspector at ${host}:${port}`))}})});req.on("error",err=>{clearTimeout(timeout),reject(new Error(`Cannot reach inspector at ${host}:${port}: ${err.message}`))}),req.end()})}async function scanForInspector(host=DEFAULT_HOST,startPort=DEFAULT_PORT,maxPorts=MAX_INSPECTOR_SCAN_PORTS){for(let port=startPort;port<startPort+maxPorts;port++)try{let targets=await discoverInspectorTargets(host,port,1e3);if(targets.length>0)return{port,targets}}catch{}return null}function findNodeProcesses(namePattern){try{let platform=process.platform,output;platform==="win32"?output=execSync2(`wmic process where "name like '%node%'" get ProcessId,CommandLine /format:csv`,{encoding:"utf-8",timeout:5e3}):output=execSync2('ps aux | grep -E "[n]ode|[t]s-node|[n]px"',{encoding:"utf-8",timeout:5e3});let processes=[];for(let line of output.split(`
|
|
752
752
|
`)){let trimmed=line.trim();if(trimmed)if(platform==="win32"){let parts=trimmed.split(",");if(parts.length>=3){let pid=parseInt(parts[parts.length-1],10),command=parts.slice(1,-1).join(",");!isNaN(pid)&&pid>0&&processes.push({pid,command})}}else{let parts=trimmed.split(/\s+/);if(parts.length>=11){let pid=parseInt(parts[1],10),command=parts.slice(10).join(" ");!isNaN(pid)&&pid>0&&processes.push({pid,command})}}}if(namePattern){let regex=new RegExp(namePattern,"i");return processes.filter(p=>regex.test(p.command))}return processes}catch{return[]}}async function activateInspector(pid,host=DEFAULT_HOST,timeoutMs=DEFAULT_TIMEOUT_MS6){if(process.platform==="win32")throw new Error("SIGUSR1 activation is not supported on Windows. Start the Node.js process with --inspect flag or NODE_OPTIONS=--inspect instead.");try{process.kill(pid,0)}catch{throw new Error(`Process ${pid} does not exist or is not accessible`)}debug(`Sending SIGUSR1 to process ${pid} to activate V8 Inspector...`),process.kill(pid,"SIGUSR1");let startTime=Date.now();for(;Date.now()-startTime<timeoutMs;){let result=await scanForInspector(host,DEFAULT_PORT,10);if(result)return debug(`Inspector activated on port ${result.port} for process ${pid}`),result;await new Promise(resolve=>setTimeout(resolve,ACTIVATION_POLL_INTERVAL_MS))}throw new Error(`Timeout waiting for inspector to activate on process ${pid}`)}function resolveContainerId(containerName){try{let ids=execSync2(`docker ps -q -f name=${containerName}`,{encoding:"utf-8",timeout:5e3}).trim().split(`
|
|
753
753
|
`).filter(Boolean);return ids.length===0?null:(ids.length>1&&debug(`Multiple containers match "${containerName}", using first: ${ids[0]}`),ids[0])}catch(err){if(/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??""))throw new Error(`${err.message} When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.`);return null}}async function activateInspectorInContainer(containerId,host=DEFAULT_HOST,port=DEFAULT_PORT,timeoutMs=DEFAULT_TIMEOUT_MS6){if(process.platform==="win32")throw new Error("Docker container activation may have limitations on Windows. Ensure the container has port 9229 exposed and Node is started with --inspect.");let hostPid="";try{let lines=execSync2(`docker top ${containerId}`,{encoding:"utf-8",timeout:5e3}).trim().split(`
|
|
@@ -764,7 +764,7 @@ Retrieves snapshots captured by tracepoints, logpoints, and/or exceptionpoints.
|
|
|
764
764
|
Optional \`types\`: array of \`tracepoint\`, \`logpoint\`, \`exceptionpoint\`. If omitted or empty, returns all.
|
|
765
765
|
Response: tracepointSnapshots, logpointSnapshots, exceptionpointSnapshots. Optional probeId, fromSequence, limit apply per type.
|
|
766
766
|
Output trimming: by default only the top ${DEFAULT_TRIM_OPTIONS.maxCallStackDepth} call stack frames are returned, only \`${NODE_DEFAULT_INCLUDE_SCOPES.join(", ")}\` scope(s) are included, and variables per scope are capped at ${DEFAULT_TRIM_OPTIONS.maxVariablesPerScope}. Override with maxCallStackDepth, includeScopes, maxVariablesPerScope.
|
|
767
|
-
`.trim()}inputSchema(){return{types:z47.array(z47.enum(GET_SNAPSHOT_TYPES)).optional().describe("Which snapshot types to return. If omitted or empty, all are returned."),probeId:z47.string().optional().describe("Filter tracepoint or logpoint snapshots by this probe ID"),fromSequence:z47.number().int().nonnegative().optional().describe("Return snapshots with sequence number > fromSequence (per type)."),limit:z47.number().int().positive().optional().describe("Maximum number of snapshots per type."),maxCallStackDepth:z47.number().int().positive().optional().default(DEFAULT_TRIM_OPTIONS.maxCallStackDepth).describe(`Max call stack frames per snapshot. Default ${DEFAULT_TRIM_OPTIONS.maxCallStackDepth}.`),includeScopes:z47.array(z47.enum(SCOPE_TYPE_VALUES2)).optional().default([...NODE_DEFAULT_INCLUDE_SCOPES]).describe(`Scope types to include. Default [${NODE_DEFAULT_INCLUDE_SCOPES.join(", ")}] (local only to keep payload small).`),maxVariablesPerScope:z47.number().int().positive().optional().default(DEFAULT_TRIM_OPTIONS.maxVariablesPerScope).describe(`Max variables per scope. Default ${DEFAULT_TRIM_OPTIONS.maxVariablesPerScope}.`)}}outputSchema(){return{tracepointSnapshots:z47.array(z47.any()).describe("Tracepoint snapshots"),logpointSnapshots:z47.array(z47.any()).describe("Logpoint snapshots"),exceptionpointSnapshots:z47.array(z47.any()).describe("Exceptionpoint snapshots")}}async handle(context,args){let getTracepoint=!args.types?.length||args.types.includes("tracepoint"),getLogpoint=!args.types?.length||args.types.includes("logpoint"),getExceptionpoint=!args.types?.length||args.types.includes("exceptionpoint"),trimOpts={maxCallStackDepth:args.maxCallStackDepth,includeScopes:args.includeScopes,maxVariablesPerScope:args.maxVariablesPerScope},tracepointSnapshots=[],logpointSnapshots=[],exceptionpointSnapshots=[],allSnapshots=getSnapshots(context.storeKey),probes=listProbes(context.storeKey);if(args.probeId&&(getTracepoint||getLogpoint)){let raw=getSnapshotsByProbe(context.storeKey,args.probeId),filtered=applySnapshotFilters(raw,args.fromSequence,args.limit),probe=probes.find(p=>p.id===args.probeId);probe?.kind==="tracepoint"&&getTracepoint?tracepointSnapshots=filtered:probe?.kind==="logpoint"&&getLogpoint&&(logpointSnapshots=filtered)}else{if(getTracepoint){let tracepointIds=new Set(probes.filter(p=>p.kind==="tracepoint").map(p=>p.id)),raw=allSnapshots.filter(s=>tracepointIds.has(s.probeId));tracepointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}if(getLogpoint){let logpointIds=new Set(probes.filter(p=>p.kind==="logpoint").map(p=>p.id)),raw=allSnapshots.filter(s=>logpointIds.has(s.probeId));logpointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}}if(getExceptionpoint){let raw=allSnapshots.filter(s=>s.probeId==="__exception__");exceptionpointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}return{tracepointSnapshots:trimSnapshots(tracepointSnapshots,trimOpts),logpointSnapshots:trimSnapshots(logpointSnapshots,trimOpts),exceptionpointSnapshots:trimSnapshots(exceptionpointSnapshots,trimOpts)}}},SNAPSHOT_TYPES3=["tracepoint","logpoint","exceptionpoint"],ClearProbeSnapshots2=class{name(){return"debug_clear-probe-snapshots"}description(){return"\nClears snapshots captured by tracepoints, logpoints, and/or exceptionpoints.\nOptional `types`: array of `tracepoint`, `logpoint`, `exceptionpoint`. If omitted or empty, clears all.\nOptional `probeId`: clear only snapshots for this probe (tracepoint/logpoint).\n ".trim()}inputSchema(){return{types:z47.array(z47.enum(SNAPSHOT_TYPES3)).optional().describe("Which snapshot types to clear. If omitted or empty, all are cleared."),probeId:z47.string().optional().describe("Clear only snapshots for this probe ID (tracepoint or logpoint). Ignored for exceptionpoint.")}}outputSchema(){return{tracepointCleared:z47.number().describe("Tracepoint snapshots cleared"),logpointCleared:z47.number().describe("Logpoint snapshots cleared"),exceptionpointCleared:z47.number().describe("Exceptionpoint snapshots cleared"),message:z47.string().describe("Status message")}}async handle(context,args){let{clearSnapshotsByProbe:clearSnapshotsByProbe2,listProbes:listProbes3}=await import("./core-5DYSM5YY.js"),clearTracepoint=!args.types?.length||args.types.includes("tracepoint"),clearLogpoint=!args.types?.length||args.types.includes("logpoint"),clearExceptionpoint=!args.types?.length||args.types.includes("exceptionpoint"),tracepointCleared=0,logpointCleared=0,exceptionpointCleared=0;if(args.probeId&&(clearTracepoint||clearLogpoint)){let n=clearSnapshotsByProbe2(context.storeKey,args.probeId),p=listProbes3(context.storeKey).find(x=>x.id===args.probeId);clearTracepoint&&clearLogpoint?p?.kind==="tracepoint"?tracepointCleared=n:p?.kind==="logpoint"&&(logpointCleared=n):clearTracepoint?tracepointCleared=n:logpointCleared=n}else{if(clearTracepoint)for(let p of listProbes3(context.storeKey).filter(x=>x.kind==="tracepoint"))tracepointCleared+=clearSnapshotsByProbe2(context.storeKey,p.id);if(clearLogpoint)for(let p of listProbes3(context.storeKey).filter(x=>x.kind==="logpoint"))logpointCleared+=clearSnapshotsByProbe2(context.storeKey,p.id);clearExceptionpoint&&(exceptionpointCleared=clearSnapshotsByProbe2(context.storeKey,"__exception__"))}let parts=[tracepointCleared&&`${tracepointCleared} tracepoint snapshot(s)`,logpointCleared&&`${logpointCleared} logpoint snapshot(s)`,exceptionpointCleared&&`${exceptionpointCleared} exceptionpoint snapshot(s)`].filter(Boolean),message=parts.length>0?`Cleared: ${parts.join(", ")}`:"Nothing to clear";return{tracepointCleared,logpointCleared,exceptionpointCleared,message}}};import{z as z48}from"zod";var GetLogs=class{name(){return"debug_get-logs"}description(){return"Retrieves console messages/logs from the Node.js process with filtering options."}inputSchema(){return{type:z48.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z48.string().optional().describe("Filter by message text."),timestamp:z48.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z48.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z48.object({count:z48.number().int().nonnegative().default(0).describe("0 = no limit."),from:z48.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).optional()}}outputSchema(){return{messages:z48.array(z48.object({type:z48.string().describe("Type of the console message."),text:z48.string().describe("Text of the console message."),location:z48.object({url:z48.string().describe("URL of the resource."),lineNumber:z48.number().nonnegative().describe("0-based line number."),columnNumber:z48.number().nonnegative().describe("0-based column number.")}).describe("Location of the console message.").optional(),timestamp:z48.number().int().nonnegative().describe("Unix epoch timestamp (ms)."),sequenceNumber:z48.number().int().nonnegative().describe("Monotonic sequence number.")})).describe("Retrieved console messages.")}}async handle(context,args){let levelCodeThreshold=args.type?ConsoleMessageLevel[args.type]?.code:void 0,filtered=getConsoleMessages(context.storeKey).filter(msg=>!(levelCodeThreshold!==void 0&&msg.level.code<levelCodeThreshold||args.timestamp&&msg.timestamp<args.timestamp||args.sequenceNumber&&msg.sequenceNumber<=args.sequenceNumber||args.search&&!msg.text.includes(args.search)));return{messages:(args.limit?.count?args.limit.from==="start"?filtered.slice(0,args.limit.count):filtered.slice(-args.limit.count):filtered).map(msg=>({type:msg.type,text:msg.text,location:msg.location?{url:msg.location.url,lineNumber:msg.location.lineNumber,columnNumber:msg.location.columnNumber}:void 0,timestamp:msg.timestamp,sequenceNumber:msg.sequenceNumber}))}}};import{z as z49}from"zod";var REMOVE_TYPES2=["tracepoint","logpoint","watch"],RemoveProbe2=class{name(){return"debug_remove-probe"}description(){return"Removes a tracepoint, logpoint, or watch expression by ID. `type`: tracepoint, logpoint, or watch. `id`: the probe or watch ID."}inputSchema(){return{type:z49.enum(REMOVE_TYPES2).describe("Kind of probe to remove: tracepoint, logpoint, or watch"),id:z49.string().describe("Probe or watch expression ID to remove")}}outputSchema(){return{removed:z49.boolean().describe("Whether the probe or watch was removed"),message:z49.string().describe("Status message")}}async handle(context,args){if(args.type==="watch"){let removed2=removeWatchExpression(context.storeKey,args.id);return{removed:removed2,message:removed2?`Watch expression ${args.id} removed`:`Watch expression ${args.id} not found`}}let removed=await removeProbe(context.storeKey,args.id),label=args.type==="tracepoint"?"Tracepoint":"Logpoint";return{removed,message:removed?`${label} ${args.id} removed`:`Failed to remove ${args.type}`}}},LIST_TYPES2=["tracepoint","logpoint","watch"],ListProbes2=class{name(){return"debug_list-probes"}description(){return"Lists tracepoints, logpoints, and/or watch expressions. Optional `types`: array of `tracepoint`, `logpoint`, `watch`. If omitted or empty, returns all."}inputSchema(){return{types:z49.array(z49.enum(LIST_TYPES2)).optional().describe("Which probe types to list. If omitted or empty, all are returned.")}}outputSchema(){return{tracepoints:z49.array(z49.any()).describe("Tracepoints"),logpoints:z49.array(z49.any()).describe("Logpoints"),watches:z49.array(z49.any()).describe("Watch expressions")}}async handle(context,args){let listTracepoints=!args.types?.length||args.types.includes("tracepoint"),listLogpoints=!args.types?.length||args.types.includes("logpoint"),listWatches=!args.types?.length||args.types.includes("watch"),probes=listProbes(context.storeKey),tracepoints=listTracepoints?probes.filter(p=>p.kind==="tracepoint").map(p=>({id:p.id,urlPattern:p.urlPattern,lineNumber:p.lineNumber,columnNumber:p.columnNumber,condition:p.condition,hitCondition:p.hitCondition,hitCount:p.hitCount,resolvedLocations:p.resolvedLocations,enabled:p.enabled})):[],logpoints=listLogpoints?probes.filter(p=>p.kind==="logpoint").map(p=>({id:p.id,urlPattern:p.urlPattern,lineNumber:p.lineNumber,logExpression:p.logExpression,condition:p.condition,hitCondition:p.hitCondition,hitCount:p.hitCount,resolvedLocations:p.resolvedLocations,enabled:p.enabled})):[],watches=listWatches?listWatchExpressions(context.storeKey):[];return{tracepoints,logpoints,watches}}},PROBE_TYPES2=["tracepoint","logpoint","watches"],ClearProbes2=class{name(){return"debug_clear-probes"}description(){return"Removes tracepoints, logpoints, and/or watch expressions. Optional `types`: array of `tracepoint`, `logpoint`, `watches`. If omitted or empty, clears all."}inputSchema(){return{types:z49.array(z49.enum(PROBE_TYPES2)).optional().describe("Which probe types to clear. If omitted or empty, all are cleared.")}}outputSchema(){return{tracepointsCleared:z49.number().describe("Number of tracepoints cleared"),logpointsCleared:z49.number().describe("Number of logpoints cleared"),watchesCleared:z49.number().describe("Number of watch expressions cleared"),message:z49.string().describe("Status message")}}async handle(context,args){let clearTracepoints=!args.types?.length||args.types.includes("tracepoint"),clearLogpoints=!args.types?.length||args.types.includes("logpoint"),clearWatches=!args.types?.length||args.types.includes("watches"),tracepointsCleared=0,logpointsCleared=0,watchesCleared=0;if(clearTracepoints||clearLogpoints){let probes=listProbes(context.storeKey);for(let p of probes)p.kind==="tracepoint"&&clearTracepoints?await removeProbe(context.storeKey,p.id)&&tracepointsCleared++:p.kind==="logpoint"&&clearLogpoints&&await removeProbe(context.storeKey,p.id)&&logpointsCleared++}clearWatches&&(watchesCleared=clearWatchExpressions(context.storeKey));let parts=[];tracepointsCleared&&parts.push(`${tracepointsCleared} tracepoint(s)`),logpointsCleared&&parts.push(`${logpointsCleared} logpoint(s)`),watchesCleared&&parts.push(`${watchesCleared} watch(es)`);let message=parts.length>0?`Cleared: ${parts.join(", ")}`:"Nothing to clear";return{tracepointsCleared,logpointsCleared,watchesCleared,message}}};import{z as z50}from"zod";var PutExceptionpoint2=class{name(){return"debug_put-exceptionpoint"}description(){return`
|
|
767
|
+
`.trim()}inputSchema(){return{types:z47.array(z47.enum(GET_SNAPSHOT_TYPES)).optional().describe("Which snapshot types to return. If omitted or empty, all are returned."),probeId:z47.string().optional().describe("Filter tracepoint or logpoint snapshots by this probe ID"),fromSequence:z47.number().int().nonnegative().optional().describe("Return snapshots with sequence number > fromSequence (per type)."),limit:z47.number().int().positive().optional().describe("Maximum number of snapshots per type."),maxCallStackDepth:z47.number().int().positive().optional().default(DEFAULT_TRIM_OPTIONS.maxCallStackDepth).describe(`Max call stack frames per snapshot. Default ${DEFAULT_TRIM_OPTIONS.maxCallStackDepth}.`),includeScopes:z47.array(z47.enum(SCOPE_TYPE_VALUES2)).optional().default([...NODE_DEFAULT_INCLUDE_SCOPES]).describe(`Scope types to include. Default [${NODE_DEFAULT_INCLUDE_SCOPES.join(", ")}] (local only to keep payload small).`),maxVariablesPerScope:z47.number().int().positive().optional().default(DEFAULT_TRIM_OPTIONS.maxVariablesPerScope).describe(`Max variables per scope. Default ${DEFAULT_TRIM_OPTIONS.maxVariablesPerScope}.`)}}outputSchema(){return{tracepointSnapshots:z47.array(z47.any()).describe("Tracepoint snapshots"),logpointSnapshots:z47.array(z47.any()).describe("Logpoint snapshots"),exceptionpointSnapshots:z47.array(z47.any()).describe("Exceptionpoint snapshots")}}async handle(context,args){let getTracepoint=!args.types?.length||args.types.includes("tracepoint"),getLogpoint=!args.types?.length||args.types.includes("logpoint"),getExceptionpoint=!args.types?.length||args.types.includes("exceptionpoint"),trimOpts={maxCallStackDepth:args.maxCallStackDepth,includeScopes:args.includeScopes,maxVariablesPerScope:args.maxVariablesPerScope},tracepointSnapshots=[],logpointSnapshots=[],exceptionpointSnapshots=[],allSnapshots=getSnapshots(context.storeKey),probes=listProbes(context.storeKey);if(args.probeId&&(getTracepoint||getLogpoint)){let raw=getSnapshotsByProbe(context.storeKey,args.probeId),filtered=applySnapshotFilters(raw,args.fromSequence,args.limit),probe=probes.find(p=>p.id===args.probeId);probe?.kind==="tracepoint"&&getTracepoint?tracepointSnapshots=filtered:probe?.kind==="logpoint"&&getLogpoint&&(logpointSnapshots=filtered)}else{if(getTracepoint){let tracepointIds=new Set(probes.filter(p=>p.kind==="tracepoint").map(p=>p.id)),raw=allSnapshots.filter(s=>tracepointIds.has(s.probeId));tracepointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}if(getLogpoint){let logpointIds=new Set(probes.filter(p=>p.kind==="logpoint").map(p=>p.id)),raw=allSnapshots.filter(s=>logpointIds.has(s.probeId));logpointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}}if(getExceptionpoint){let raw=allSnapshots.filter(s=>s.probeId==="__exception__");exceptionpointSnapshots=applySnapshotFilters(raw,args.fromSequence,args.limit)}return{tracepointSnapshots:trimSnapshots(tracepointSnapshots,trimOpts),logpointSnapshots:trimSnapshots(logpointSnapshots,trimOpts),exceptionpointSnapshots:trimSnapshots(exceptionpointSnapshots,trimOpts)}}},SNAPSHOT_TYPES3=["tracepoint","logpoint","exceptionpoint"],ClearProbeSnapshots2=class{name(){return"debug_clear-probe-snapshots"}description(){return"\nClears snapshots captured by tracepoints, logpoints, and/or exceptionpoints.\nOptional `types`: array of `tracepoint`, `logpoint`, `exceptionpoint`. If omitted or empty, clears all.\nOptional `probeId`: clear only snapshots for this probe (tracepoint/logpoint).\n ".trim()}inputSchema(){return{types:z47.array(z47.enum(SNAPSHOT_TYPES3)).optional().describe("Which snapshot types to clear. If omitted or empty, all are cleared."),probeId:z47.string().optional().describe("Clear only snapshots for this probe ID (tracepoint or logpoint). Ignored for exceptionpoint.")}}outputSchema(){return{tracepointCleared:z47.number().describe("Tracepoint snapshots cleared"),logpointCleared:z47.number().describe("Logpoint snapshots cleared"),exceptionpointCleared:z47.number().describe("Exceptionpoint snapshots cleared"),message:z47.string().describe("Status message")}}async handle(context,args){let{clearSnapshotsByProbe:clearSnapshotsByProbe2,listProbes:listProbes3}=await import("./core-X7CLDI7H.js"),clearTracepoint=!args.types?.length||args.types.includes("tracepoint"),clearLogpoint=!args.types?.length||args.types.includes("logpoint"),clearExceptionpoint=!args.types?.length||args.types.includes("exceptionpoint"),tracepointCleared=0,logpointCleared=0,exceptionpointCleared=0;if(args.probeId&&(clearTracepoint||clearLogpoint)){let n=clearSnapshotsByProbe2(context.storeKey,args.probeId),p=listProbes3(context.storeKey).find(x=>x.id===args.probeId);clearTracepoint&&clearLogpoint?p?.kind==="tracepoint"?tracepointCleared=n:p?.kind==="logpoint"&&(logpointCleared=n):clearTracepoint?tracepointCleared=n:logpointCleared=n}else{if(clearTracepoint)for(let p of listProbes3(context.storeKey).filter(x=>x.kind==="tracepoint"))tracepointCleared+=clearSnapshotsByProbe2(context.storeKey,p.id);if(clearLogpoint)for(let p of listProbes3(context.storeKey).filter(x=>x.kind==="logpoint"))logpointCleared+=clearSnapshotsByProbe2(context.storeKey,p.id);clearExceptionpoint&&(exceptionpointCleared=clearSnapshotsByProbe2(context.storeKey,"__exception__"))}let parts=[tracepointCleared&&`${tracepointCleared} tracepoint snapshot(s)`,logpointCleared&&`${logpointCleared} logpoint snapshot(s)`,exceptionpointCleared&&`${exceptionpointCleared} exceptionpoint snapshot(s)`].filter(Boolean),message=parts.length>0?`Cleared: ${parts.join(", ")}`:"Nothing to clear";return{tracepointCleared,logpointCleared,exceptionpointCleared,message}}};import{z as z48}from"zod";var GetLogs=class{name(){return"debug_get-logs"}description(){return"Retrieves console messages/logs from the Node.js process with filtering options."}inputSchema(){return{type:z48.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z48.string().optional().describe("Filter by message text."),timestamp:z48.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z48.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z48.object({count:z48.number().int().nonnegative().default(0).describe("0 = no limit."),from:z48.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).optional()}}outputSchema(){return{messages:z48.array(z48.object({type:z48.string().describe("Type of the console message."),text:z48.string().describe("Text of the console message."),location:z48.object({url:z48.string().describe("URL of the resource."),lineNumber:z48.number().nonnegative().describe("0-based line number."),columnNumber:z48.number().nonnegative().describe("0-based column number.")}).describe("Location of the console message.").optional(),timestamp:z48.number().int().nonnegative().describe("Unix epoch timestamp (ms)."),sequenceNumber:z48.number().int().nonnegative().describe("Monotonic sequence number.")})).describe("Retrieved console messages.")}}async handle(context,args){let levelCodeThreshold=args.type?ConsoleMessageLevel[args.type]?.code:void 0,filtered=getConsoleMessages(context.storeKey).filter(msg=>!(levelCodeThreshold!==void 0&&msg.level.code<levelCodeThreshold||args.timestamp&&msg.timestamp<args.timestamp||args.sequenceNumber&&msg.sequenceNumber<=args.sequenceNumber||args.search&&!msg.text.includes(args.search)));return{messages:(args.limit?.count?args.limit.from==="start"?filtered.slice(0,args.limit.count):filtered.slice(-args.limit.count):filtered).map(msg=>({type:msg.type,text:msg.text,location:msg.location?{url:msg.location.url,lineNumber:msg.location.lineNumber,columnNumber:msg.location.columnNumber}:void 0,timestamp:msg.timestamp,sequenceNumber:msg.sequenceNumber}))}}};import{z as z49}from"zod";var REMOVE_TYPES2=["tracepoint","logpoint","watch"],RemoveProbe2=class{name(){return"debug_remove-probe"}description(){return"Removes a tracepoint, logpoint, or watch expression by ID. `type`: tracepoint, logpoint, or watch. `id`: the probe or watch ID."}inputSchema(){return{type:z49.enum(REMOVE_TYPES2).describe("Kind of probe to remove: tracepoint, logpoint, or watch"),id:z49.string().describe("Probe or watch expression ID to remove")}}outputSchema(){return{removed:z49.boolean().describe("Whether the probe or watch was removed"),message:z49.string().describe("Status message")}}async handle(context,args){if(args.type==="watch"){let removed2=removeWatchExpression(context.storeKey,args.id);return{removed:removed2,message:removed2?`Watch expression ${args.id} removed`:`Watch expression ${args.id} not found`}}let removed=await removeProbe(context.storeKey,args.id),label=args.type==="tracepoint"?"Tracepoint":"Logpoint";return{removed,message:removed?`${label} ${args.id} removed`:`Failed to remove ${args.type}`}}},LIST_TYPES2=["tracepoint","logpoint","watch"],ListProbes2=class{name(){return"debug_list-probes"}description(){return"Lists tracepoints, logpoints, and/or watch expressions. Optional `types`: array of `tracepoint`, `logpoint`, `watch`. If omitted or empty, returns all."}inputSchema(){return{types:z49.array(z49.enum(LIST_TYPES2)).optional().describe("Which probe types to list. If omitted or empty, all are returned.")}}outputSchema(){return{tracepoints:z49.array(z49.any()).describe("Tracepoints"),logpoints:z49.array(z49.any()).describe("Logpoints"),watches:z49.array(z49.any()).describe("Watch expressions")}}async handle(context,args){let listTracepoints=!args.types?.length||args.types.includes("tracepoint"),listLogpoints=!args.types?.length||args.types.includes("logpoint"),listWatches=!args.types?.length||args.types.includes("watch"),probes=listProbes(context.storeKey),tracepoints=listTracepoints?probes.filter(p=>p.kind==="tracepoint").map(p=>({id:p.id,urlPattern:p.urlPattern,lineNumber:p.lineNumber,columnNumber:p.columnNumber,condition:p.condition,hitCondition:p.hitCondition,hitCount:p.hitCount,resolvedLocations:p.resolvedLocations,enabled:p.enabled})):[],logpoints=listLogpoints?probes.filter(p=>p.kind==="logpoint").map(p=>({id:p.id,urlPattern:p.urlPattern,lineNumber:p.lineNumber,logExpression:p.logExpression,condition:p.condition,hitCondition:p.hitCondition,hitCount:p.hitCount,resolvedLocations:p.resolvedLocations,enabled:p.enabled})):[],watches=listWatches?listWatchExpressions(context.storeKey):[];return{tracepoints,logpoints,watches}}},PROBE_TYPES2=["tracepoint","logpoint","watches"],ClearProbes2=class{name(){return"debug_clear-probes"}description(){return"Removes tracepoints, logpoints, and/or watch expressions. Optional `types`: array of `tracepoint`, `logpoint`, `watches`. If omitted or empty, clears all."}inputSchema(){return{types:z49.array(z49.enum(PROBE_TYPES2)).optional().describe("Which probe types to clear. If omitted or empty, all are cleared.")}}outputSchema(){return{tracepointsCleared:z49.number().describe("Number of tracepoints cleared"),logpointsCleared:z49.number().describe("Number of logpoints cleared"),watchesCleared:z49.number().describe("Number of watch expressions cleared"),message:z49.string().describe("Status message")}}async handle(context,args){let clearTracepoints=!args.types?.length||args.types.includes("tracepoint"),clearLogpoints=!args.types?.length||args.types.includes("logpoint"),clearWatches=!args.types?.length||args.types.includes("watches"),tracepointsCleared=0,logpointsCleared=0,watchesCleared=0;if(clearTracepoints||clearLogpoints){let probes=listProbes(context.storeKey);for(let p of probes)p.kind==="tracepoint"&&clearTracepoints?await removeProbe(context.storeKey,p.id)&&tracepointsCleared++:p.kind==="logpoint"&&clearLogpoints&&await removeProbe(context.storeKey,p.id)&&logpointsCleared++}clearWatches&&(watchesCleared=clearWatchExpressions(context.storeKey));let parts=[];tracepointsCleared&&parts.push(`${tracepointsCleared} tracepoint(s)`),logpointsCleared&&parts.push(`${logpointsCleared} logpoint(s)`),watchesCleared&&parts.push(`${watchesCleared} watch(es)`);let message=parts.length>0?`Cleared: ${parts.join(", ")}`:"Nothing to clear";return{tracepointsCleared,logpointsCleared,watchesCleared,message}}};import{z as z50}from"zod";var PutExceptionpoint2=class{name(){return"debug_put-exceptionpoint"}description(){return`
|
|
768
768
|
Sets the exception tracepoint state for a Node.js process:
|
|
769
769
|
- "none": Don't capture on exceptions
|
|
770
770
|
- "uncaught": Capture only on uncaught exceptions
|
|
@@ -803,7 +803,7 @@ If 0, the pattern didn't match any loaded scripts.
|
|
|
803
803
|
Resolves a generated/bundled code location to its original source via source maps.
|
|
804
804
|
Useful for translating minified stack traces or bundle line numbers to original TypeScript/JavaScript source.
|
|
805
805
|
|
|
806
|
-
Requires an active Node.js debug connection (call debug_connect first).
|
|
806
|
+
Requires an active Node.js debug connection (call <debug_connect> first).
|
|
807
807
|
Input: generated script URL (e.g. file path), line, column (1-based).
|
|
808
808
|
Output: original source path, line, column when a source map is available.
|
|
809
809
|
`}inputSchema(){return{url:z53.string().describe("Generated script URL (e.g. file:///path/to/dist/app.js)"),line:z53.number().int().positive().describe("Line number in generated code (1-based)"),column:z53.number().int().nonnegative().describe("Column number in generated code (1-based, default 1)").optional()}}outputSchema(){return{resolved:z53.boolean().describe("Whether the location was resolved to original source"),source:z53.string().optional().describe("Original source file path"),line:z53.number().optional().describe("Line number in original source (1-based)"),column:z53.number().optional().describe("Column number in original source (1-based)"),name:z53.string().optional().describe("Original identifier name if available")}}async handle(context,args){let resolved=await resolveSourceLocation(context.storeKey,args.url,args.line,args.column??1);return resolved?{resolved:!0,source:resolved.source,line:resolved.line,column:resolved.column,name:resolved.name}:{resolved:!1}}};import{z as z54}from"zod";var Status2=class{name(){return"debug_status"}description(){return`
|
|
@@ -818,7 +818,7 @@ Adds a watch expression to be evaluated at every tracepoint hit on the Node.js p
|
|
|
818
818
|
Watch expression results are included in the snapshot's watchResults field.
|
|
819
819
|
|
|
820
820
|
Examples: "user.name", "this.state", "items.length", "JSON.stringify(config)"
|
|
821
|
-
`}inputSchema(){return{expression:z55.string().describe("JavaScript expression to watch")}}outputSchema(){return{id:z55.string().describe("Watch expression ID"),expression:z55.string().describe("The expression")}}async handle(context,args){let watch=addWatchExpression(context.storeKey,args.expression);return{id:watch.id,expression:watch.expression}}};var tools12=[new Connect,new Disconnect,new Status2,new PutTracepoint2,new RemoveProbe2,new ListProbes2,new ClearProbes2,new PutLogpoint2,new PutExceptionpoint2,new GetProbeSnapshots2,new ClearProbeSnapshots2,new GetLogs,new ResolveSourceLocation2,new AddWatch2];var tools13=[...tools12];async function createToolSessionContext2(sessionIdProvider){let sessionId=sessionIdProvider();return debug(`Created session context for the session with id ${sessionId}`),new NodeToolSessionContext(sessionId)}function createToolExecutor2(){return new NodeToolExecutor}var NodeCliProvider=class{platform="node";cliName="node-devtools-cli";tools=tools13;sessionDescription="Manage Node.js debugging sessions";cliExamples=["node-devtools-cli interactive","node-devtools-cli connect --pid 12345"];bashCompletionOptions="";bashCompletionCommands="daemon session tools config completion interactive connect disconnect status tracepoint logpoint exceptionpoint watch";zshCompletionOptions="";zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage Node.js debug sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"connect",description:"Connect to a Node.js process"},{name:"disconnect",description:"Disconnect from current process"},{name:"status",description:"Show connection status"},{name:"tracepoint",description:"Tracepoint commands"},{name:"logpoint",description:"Logpoint commands"},{name:"exceptionpoint",description:"Exceptionpoint commands"},{name:"watch",description:"Watch expression commands"}];replPrompt="node> ";cliDescription="Node.js DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(_opts){
|
|
821
|
+
`}inputSchema(){return{expression:z55.string().describe("JavaScript expression to watch")}}outputSchema(){return{id:z55.string().describe("Watch expression ID"),expression:z55.string().describe("The expression")}}async handle(context,args){let watch=addWatchExpression(context.storeKey,args.expression);return{id:watch.id,expression:watch.expression}}};var tools12=[new Connect,new Disconnect,new Status2,new PutTracepoint2,new RemoveProbe2,new ListProbes2,new ClearProbes2,new PutLogpoint2,new PutExceptionpoint2,new GetProbeSnapshots2,new ClearProbeSnapshots2,new GetLogs,new ResolveSourceLocation2,new AddWatch2];var tools13=[...tools12];async function createToolSessionContext2(sessionIdProvider){let sessionId=sessionIdProvider();return debug(`Created session context for the session with id ${sessionId}`),new NodeToolSessionContext(sessionId)}function createToolExecutor2(){return new NodeToolExecutor}var NodeCliProvider=class{platform="node";cliName="node-devtools-cli";tools=tools13;sessionDescription="Manage Node.js debugging sessions";cliExamples=["node-devtools-cli interactive","node-devtools-cli connect --pid 12345"];bashCompletionOptions="";bashCompletionCommands="daemon session tools config completion interactive run connect disconnect status tracepoint logpoint exceptionpoint watch";zshCompletionOptions="";zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage Node.js debug sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"run",description:"Script execution commands"},{name:"connect",description:"Connect to a Node.js process"},{name:"disconnect",description:"Disconnect from current process"},{name:"status",description:"Show connection status"},{name:"tracepoint",description:"Tracepoint commands"},{name:"logpoint",description:"Logpoint commands"},{name:"exceptionpoint",description:"Exceptionpoint commands"},{name:"watch",description:"Watch expression commands"}];replPrompt="node> ";cliDescription="Node.js DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(_opts){let env={...process.env};return env.PLATFORM="node",env}addOptions(cmd){return cmd}getConfig(){return{consoleMessagesBufferSize:NODE_CONSOLE_MESSAGES_BUFFER_SIZE,nodeInspectorHost:NODE_INSPECTOR_HOST}}formatConfig(configValues){return[" Node.js:",` Console Messages Buffer: ${configValues.consoleMessagesBufferSize??NODE_CONSOLE_MESSAGES_BUFFER_SIZE}`,` Inspector Host (NODE_INSPECTOR_HOST): ${configValues.nodeInspectorHost??"(default 127.0.0.1)"}`].join(`
|
|
822
822
|
`)}formatConfigForRepl(_opts){return[` console-messages-buffer = ${NODE_CONSOLE_MESSAGES_BUFFER_SIZE}`,` node-inspector-host = ${NODE_INSPECTOR_HOST??"127.0.0.1"}`].join(`
|
|
823
823
|
`)}},cliProvider2=new NodeCliProvider;var SERVER_INSTRUCTIONS2=`
|
|
824
824
|
Node.js Backend Debugging Platform
|
|
@@ -846,7 +846,7 @@ and provides the same debugging capabilities as browser debugging.
|
|
|
846
846
|
- Same V8 protocol as browser debugging - consistent experience across platforms
|
|
847
847
|
|
|
848
848
|
**Usage Flow:**
|
|
849
|
-
1. Connect to a Node.js process using debug_connect (by PID, name, port, or WebSocket URL)
|
|
849
|
+
1. Connect to a Node.js process using <debug_connect> (by PID, name, port, or WebSocket URL)
|
|
850
850
|
2. Set tracepoints/logpoints at code locations of interest
|
|
851
851
|
3. Trigger the code paths (e.g., make API requests)
|
|
852
852
|
4. Retrieve captured snapshots with call stack, variables, and watch results
|
|
@@ -859,4 +859,4 @@ Node.js Debugging Best Practices:
|
|
|
859
859
|
- Use source maps when debugging TypeScript or compiled code
|
|
860
860
|
- Disconnect cleanly when done to avoid leaving inspector sessions open
|
|
861
861
|
- Use exception breakpoints to catch unhandled errors
|
|
862
|
-
`;var nodePlatformInfo={serverInfo:{instructions:NODE_SERVER_INSTRUCTIONS_ENABLE?SERVER_INSTRUCTIONS2:void 0,policies:NODE_POLICY_DEBUGGING_ENABLE?[NODE_DEBUGGING_POLICY]:[]},toolsInfo:{tools:tools13,createToolSessionContext:createToolSessionContext2,createToolExecutor:createToolExecutor2},cliInfo:{cliProvider:cliProvider2}};function _resolvePlatformInfo(){return PLATFORM==="node"?nodePlatformInfo:platformInfo}var platformInfo2=_resolvePlatformInfo();import{z as z56,ZodError}from"zod";function formatZodError(err){return"Validation failed: "+err.issues.map(issue=>(issue.path.length?issue.path.join(".")+": ":"")+issue.message).join("; ")}var ToolRegistry=class{tools=new Map;addTool(tool){if(this.tools.has(tool.name()))throw new Error(`Tool already registered: ${tool.name()}`);this.tools.set(tool.name(),{tool,inputSchema:
|
|
862
|
+
`;var nodePlatformInfo={serverInfo:{instructions:NODE_SERVER_INSTRUCTIONS_ENABLE?SERVER_INSTRUCTIONS2:void 0,policies:NODE_POLICY_DEBUGGING_ENABLE?[NODE_DEBUGGING_POLICY]:[]},toolsInfo:{tools:tools13,createToolSessionContext:createToolSessionContext2,createToolExecutor:createToolExecutor2},cliInfo:{cliProvider:cliProvider2}};function _resolvePlatformInfo(){return PLATFORM==="node"?nodePlatformInfo:platformInfo}var platformInfo2=_resolvePlatformInfo();import{z as z56}from"zod";function augmentToolInputSchema(shape){if(!TOOL_INPUT_METADATA_ENABLE)return shape;let extra={};return Object.prototype.hasOwnProperty.call(shape,"_metadata")||(extra._metadata=z56.object({sessionId:z56.string().optional(),traceId:z56.string().optional()}).catchall(z56.unknown()).optional()),Object.keys(extra).length===0?shape:{...shape,...extra}}import{z as z57,ZodError}from"zod";function formatZodError(err){return"Validation failed: "+err.issues.map(issue=>(issue.path.length?issue.path.join(".")+": ":"")+issue.message).join("; ")}var ToolRegistry=class{tools=new Map;addTool(tool){if(this.tools.has(tool.name()))throw new Error(`Tool already registered: ${tool.name()}`);this.tools.set(tool.name(),{tool,inputSchema:z57.object(augmentToolInputSchema(tool.inputSchema())).strict(),outputSchema:z57.object(tool.outputSchema()).strict()})}async runTool(context,toolName,toolArgs){let canonicalKey=denormalizeToolName(toolName),toolDef=this.tools.get(canonicalKey);if(!toolDef)throw new Error(`Tool not found: ${toolName}`);let parsedToolArgs;try{parsedToolArgs=await toolDef.inputSchema.parseAsync(toolArgs)}catch(e){throw e instanceof ZodError?new Error(formatZodError(e)):e}let rawToolResult=await toolDef.tool.handle(context,parsedToolArgs);return await toolDef.outputSchema.parseAsync(rawToolResult)}};function isToolEnabled(tool){return tool.isEnabled?.()??!0}export{platformInfo2 as platformInfo,augmentToolInputSchema,ToolRegistry,isToolEnabled};
|