init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+48
View File
@@ -0,0 +1,48 @@
import { z } from 'zod';
import { writeBaseline, listBaselines } from '../../lib/baselines.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserScreenshotBaselineTool(server) {
server.registerTool('browser_screenshot_baseline', {
title: 'Save Screenshot Baseline',
description: 'Capture the current page (or a CSS selector) and save it as a PNG baseline under `.web-dev-mcp/baselines/{name}.png` for later visual diffs.',
inputSchema: {
name: z
.string()
.min(1)
.describe('Baseline name (no path separators), e.g. "home" or "login-form".'),
selector: z.string().optional().describe('Optional CSS selector; if omitted, captures the page.'),
fullPage: z
.boolean()
.optional()
.default(false)
.describe('Capture the full scrollable page when selector is omitted.'),
},
}, async ({ name, selector, fullPage }) => {
try {
const session = await browserManager.getSession();
let buffer;
if (selector) {
const element = await session.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 5000 });
buffer = await element.screenshot({ type: 'png' });
}
else {
buffer = await session.page.screenshot({ fullPage, type: 'png' });
}
const saved = writeBaseline(name, buffer);
return jsonResult({
name,
selector: selector ?? null,
fullPage: selector ? false : fullPage,
sizeBytes: buffer.length,
relativePath: saved.relativePath,
baselines: listBaselines(),
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=baseline.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../../src/tools/browser/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,qCAAqC,CAAC,MAAiB;IACrE,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,8IAA8I;QAChJ,WAAW,EAAE;YACX,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,kEAAkE,CAAC;YAC/E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;YACjG,QAAQ,EAAE,CAAC;iBACR,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,4DAA4D,CAAC;SAC1E;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,CAAC;YAClD,IAAI,MAAc,CAAC;YACnB,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC7D,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3D,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,UAAU,CAAC;gBAChB,IAAI;gBACJ,QAAQ,EAAE,QAAQ,IAAI,IAAI;gBAC1B,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;gBACrC,SAAS,EAAE,MAAM,CAAC,MAAM;gBACxB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,SAAS,EAAE,aAAa,EAAE;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+68
View File
@@ -0,0 +1,68 @@
import { chromium } from 'playwright';
import { logger } from '../../lib/logger.js';
class BrowserManager {
session = null;
async getSession(headless = true) {
if (this.session)
return this.session;
try {
const browser = await chromium.launch({ headless });
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
const page = await context.newPage();
const consoleLogs = [];
page.on('console', (msg) => {
const location = msg.location();
consoleLogs.push({
type: msg.type(),
text: msg.text(),
location: `${location.url}:${location.lineNumber}:${location.columnNumber}`,
time: new Date().toISOString(),
});
});
page.on('pageerror', (error) => {
consoleLogs.push({
type: 'error',
text: `Page error: ${error.message}`,
time: new Date().toISOString(),
});
});
page.on('requestfailed', (request) => {
consoleLogs.push({
type: 'error',
text: `Network request failed: ${request.method()} ${request.url()}`,
time: new Date().toISOString(),
});
});
this.session = { browser, context, page, consoleLogs };
return this.session;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("Executable doesn't exist") || message.includes('browserType.launch')) {
throw new Error('Playwright Chromium browser is not installed. ' +
'Install it with: npx playwright install chromium');
}
throw error;
}
}
async close() {
if (!this.session)
return;
try {
await this.session.browser.close();
}
catch (error) {
logger.error('error closing browser', { error: error instanceof Error ? error.message : error });
}
finally {
this.session = null;
}
}
resetLogs() {
if (this.session) {
this.session.consoleLogs.length = 0;
}
}
}
export const browserManager = new BrowserManager();
//# sourceMappingURL=browserManager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"browserManager.js","sourceRoot":"","sources":["../../../src/tools/browser/browserManager.ts"],"names":[],"mappings":"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"}
+40
View File
@@ -0,0 +1,40 @@
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserConsoleLogsTool(server) {
server.registerTool('browser_get_console_logs', {
title: 'Browser Console Logs',
description: 'Return console logs, JavaScript page errors, and failed network requests captured since the last browser_navigate call (or since the browser session started).',
inputSchema: {
level: z
.enum(['all', 'error', 'warn', 'log', 'info', 'debug'])
.optional()
.default('all')
.describe('Filter logs by type.'),
limit: z
.number()
.int()
.positive()
.optional()
.default(100)
.describe('Maximum number of logs to return.'),
},
}, async ({ level, limit }) => {
try {
const session = await browserManager.getSession();
let logs = session.consoleLogs.slice(-limit);
if (level !== 'all') {
logs = logs.filter((log) => log.type === level);
}
return jsonResult({
totalCaptured: session.consoleLogs.length,
returned: logs.length,
logs,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=consoleLogs.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"consoleLogs.js","sourceRoot":"","sources":["../../../src/tools/browser/consoleLogs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAmB,MAAM,qBAAqB,CAAC;AAEtE,MAAM,UAAU,8BAA8B,CAAC,MAAiB;IAC9D,MAAM,CAAC,YAAY,CACjB,0BAA0B,EAC1B;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,gKAAgK;QAClK,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;iBACtD,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,sBAAsB,CAAC;YACnC,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,QAAQ,EAAE;iBACV,OAAO,CAAC,GAAG,CAAC;iBACZ,QAAQ,CAAC,mCAAmC,CAAC;SACjD;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,CAAC;YAClD,IAAI,IAAI,GAAiB,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;YAC3D,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;gBACpB,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,UAAU,CAAC;gBAChB,aAAa,EAAE,OAAO,CAAC,WAAW,CAAC,MAAM;gBACzC,QAAQ,EAAE,IAAI,CAAC,MAAM;gBACrB,IAAI;aACL,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+18
View File
@@ -0,0 +1,18 @@
import { registerBrowserNavigateTool } from './navigate.js';
import { registerBrowserScreenshotTool } from './screenshot.js';
import { registerBrowserConsoleLogsTool } from './consoleLogs.js';
import { registerBrowserInspectDomTool } from './inspectDom.js';
import { registerBrowserInteractTools } from './interact.js';
import { registerBrowserScreenshotBaselineTool } from './baseline.js';
import { registerBrowserVisualDiffTool } from './visualDiff.js';
export function registerBrowserTools(server) {
registerBrowserNavigateTool(server);
registerBrowserScreenshotTool(server);
registerBrowserScreenshotBaselineTool(server);
registerBrowserVisualDiffTool(server);
registerBrowserConsoleLogsTool(server);
registerBrowserInspectDomTool(server);
registerBrowserInteractTools(server);
}
export { browserManager } from './browserManager.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/browser/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,qCAAqC,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACpC,6BAA6B,CAAC,MAAM,CAAC,CAAC;IACtC,qCAAqC,CAAC,MAAM,CAAC,CAAC;IAC9C,6BAA6B,CAAC,MAAM,CAAC,CAAC;IACtC,8BAA8B,CAAC,MAAM,CAAC,CAAC;IACvC,6BAA6B,CAAC,MAAM,CAAC,CAAC;IACtC,4BAA4B,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
+42
View File
@@ -0,0 +1,42 @@
/// <reference lib="dom" />
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserInspectDomTool(server) {
server.registerTool('browser_inspect_dom', {
title: 'Browser Inspect DOM',
description: 'Query the DOM of the current page by CSS selector and return the outerHTML, inner text, and selected computed styles of the first matching element.',
inputSchema: {
selector: z.string().min(1).describe('CSS selector to query.'),
includeStyles: z
.array(z.string())
.optional()
.default(['color', 'backgroundColor', 'fontSize', 'display'])
.describe('Computed CSS properties to return.'),
},
}, async ({ selector, includeStyles }) => {
try {
const session = await browserManager.getSession();
const element = session.page.locator(selector).first();
await element.waitFor({ state: 'attached', timeout: 5000 });
const outerHTML = await element.evaluate((el) => el.outerHTML).catch(() => null);
const text = await element.innerText().catch(() => null);
const styles = await element.evaluate((el, props) => {
const computed = window.getComputedStyle(el);
return Object.fromEntries(props.map((p) => [p, computed.getPropertyValue(p)]));
}, includeStyles);
const count = await session.page.locator(selector).count();
return jsonResult({
selector,
matchCount: count,
outerHTML,
text,
styles,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=inspectDom.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"inspectDom.js","sourceRoot":"","sources":["../../../src/tools/browser/inspectDom.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,qJAAqJ;QACvJ,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YAC9D,aAAa,EAAE,CAAC;iBACb,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,EAAE;iBACV,OAAO,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;iBAC5D,QAAQ,CAAC,oCAAoC,CAAC;SAClD;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,CAAC;YAClD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;YACvD,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAE5D,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACjF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CACnC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;gBACZ,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;gBAC7C,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjF,CAAC,EACD,aAAa,CACd,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;YAE3D,OAAO,UAAU,CAAC;gBAChB,QAAQ;gBACR,UAAU,EAAE,KAAK;gBACjB,SAAS;gBACT,IAAI;gBACJ,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+68
View File
@@ -0,0 +1,68 @@
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserInteractTools(server) {
server.registerTool('browser_click', {
title: 'Browser Click',
description: 'Click the first element matching a CSS selector on the current page.',
inputSchema: {
selector: z.string().min(1).describe('CSS selector of the element to click.'),
},
}, async ({ selector }) => {
try {
const session = await browserManager.getSession();
await session.page.locator(selector).first().click();
return jsonResult({ clicked: true, selector });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('browser_fill', {
title: 'Browser Fill',
description: 'Fill an input/textarea element with the provided text.',
inputSchema: {
selector: z.string().min(1).describe('CSS selector of the input element.'),
value: z.string().describe('Text to type into the element.'),
clearFirst: z
.boolean()
.optional()
.default(true)
.describe('Clear the existing value before filling.'),
},
}, async ({ selector, value, clearFirst }) => {
try {
const session = await browserManager.getSession();
const locator = session.page.locator(selector).first();
if (clearFirst)
await locator.fill(value);
else
await locator.pressSequentially(value);
return jsonResult({ filled: true, selector, value });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('browser_eval', {
title: 'Browser Eval',
description: 'Evaluate a JavaScript function in the context of the current page and return the result. The function is stringified and executed in the browser, so it can access the DOM (document, window, etc.).',
inputSchema: {
script: z
.string()
.min(1)
.describe('JavaScript function body to evaluate. Must be a function expression returning a JSON-serializable value.'),
},
}, async ({ script }) => {
try {
const session = await browserManager.getSession();
const wrapped = `(async () => { ${script} })()`;
const result = await session.page.evaluate(wrapped);
return jsonResult({ result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=interact.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"interact.js","sourceRoot":"","sources":["../../../src/tools/browser/interact.ts"],"names":[],"mappings":"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"}
+41
View File
@@ -0,0 +1,41 @@
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserNavigateTool(server) {
server.registerTool('browser_navigate', {
title: 'Browser Navigate',
description: 'Open a URL in a headless/headed Playwright browser session. Reuses the same session across calls, so later tools (screenshot, inspect_dom, click, etc.) operate on the same page.',
inputSchema: {
url: z.string().url().describe('URL to navigate to, e.g. the local dev server URL from start_dev_server.'),
headless: z.boolean().optional().default(true).describe('Run browser headlessly.'),
waitUntil: z
.enum(['load', 'domcontentloaded', 'networkidle'])
.optional()
.default('load')
.describe('When to consider navigation complete.'),
resetLogs: z
.boolean()
.optional()
.default(true)
.describe('Clear previously captured console logs before navigating.'),
},
}, async ({ url, headless, waitUntil, resetLogs: shouldResetLogs }) => {
try {
const session = await browserManager.getSession(headless);
if (shouldResetLogs)
browserManager.resetLogs();
const response = await session.page.goto(url, { waitUntil });
const title = await session.page.title().catch(() => '');
return jsonResult({
url,
title,
status: response?.status() ?? null,
finalUrl: response?.url() ?? url,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=navigate.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"navigate.js","sourceRoot":"","sources":["../../../src/tools/browser/navigate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,mLAAmL;QACrL,WAAW,EAAE;YACX,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;YAC1G,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;YAClF,SAAS,EAAE,CAAC;iBACT,IAAI,CAAC,CAAC,MAAM,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC;iBACjD,QAAQ,EAAE;iBACV,OAAO,CAAC,MAAM,CAAC;iBACf,QAAQ,CAAC,uCAAuC,CAAC;YACpD,SAAS,EAAE,CAAC;iBACT,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,IAAI,CAAC;iBACb,QAAQ,CAAC,2DAA2D,CAAC;SACzE;KACF,EACD,KAAK,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,EAAE;QACjE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC1D,IAAI,eAAe;gBAAE,cAAc,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACzD,OAAO,UAAU,CAAC;gBAChB,GAAG;gBACH,KAAK;gBACL,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,IAAI;gBAClC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG;aACjC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+45
View File
@@ -0,0 +1,45 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
export function registerBrowserScreenshotTool(server) {
server.registerTool('browser_screenshot', {
title: 'Browser Screenshot',
description: 'Capture a screenshot of the current page or a specific element. Returns the image as a base64 data URI (PNG).',
inputSchema: {
selector: z.string().optional().describe('CSS selector of the element to screenshot. If omitted, captures the full page.'),
fullPage: z
.boolean()
.optional()
.default(false)
.describe('Capture the full scrollable page (only used when selector is omitted).'),
},
}, async ({ selector, fullPage }) => {
try {
const session = await browserManager.getSession();
let buffer;
if (selector) {
const element = await session.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 5000 });
buffer = await element.screenshot({ type: 'png' });
}
else {
buffer = await session.page.screenshot({ fullPage, type: 'png' });
}
const base64 = buffer.toString('base64');
const dataUri = `data:image/png;base64,${base64}`;
return jsonResult({
selector: selector ?? null,
fullPage: selector ? false : fullPage,
sizeBytes: buffer.length,
dataUri,
truncatedDataUri: dataUri.length > config.maxToolOutputChars ? `${dataUri.slice(0, config.maxToolOutputChars)}...` : dataUri,
note: 'The full dataUri is returned above. If it is truncated, use the returned base64 value directly from your client.',
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=screenshot.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../../src/tools/browser/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,+GAA+G;QACjH,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gFAAgF,CAAC;YAC1H,QAAQ,EAAE,CAAC;iBACR,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,wEAAwE,CAAC;SACtF;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,CAAC;YAClD,IAAI,MAAc,CAAC;YACnB,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC7D,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3D,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,yBAAyB,MAAM,EAAE,CAAC;YAClD,OAAO,UAAU,CAAC;gBAChB,QAAQ,EAAE,QAAQ,IAAI,IAAI;gBAC1B,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;gBACrC,SAAS,EAAE,MAAM,CAAC,MAAM;gBACxB,OAAO;gBACP,gBAAgB,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;gBAC5H,IAAI,EAAE,kHAAkH;aACzH,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+89
View File
@@ -0,0 +1,89 @@
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
import { z } from 'zod';
import { config } from '../../config.js';
import { readBaseline, resolveBaselinePath } from '../../lib/baselines.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
function toDataUri(png) {
return `data:image/png;base64,${png.toString('base64')}`;
}
export function comparePngBuffers(baselinePng, currentPng, threshold = 0.1) {
const baseline = PNG.sync.read(baselinePng);
const current = PNG.sync.read(currentPng);
if (baseline.width !== current.width || baseline.height !== current.height) {
throw new Error(`Screenshot dimensions differ from baseline (${baseline.width}x${baseline.height} vs ${current.width}x${current.height}). ` +
'Recapture the baseline at the same viewport/selector size.');
}
const { width, height } = baseline;
const diff = new PNG({ width, height });
const mismatchedPixels = pixelmatch(baseline.data, current.data, diff.data, width, height, {
threshold,
});
const total = width * height;
const mismatchPercent = total === 0 ? 0 : (mismatchedPixels / total) * 100;
return {
width,
height,
mismatchedPixels,
mismatchPercent,
diffPng: PNG.sync.write(diff),
};
}
export function registerBrowserVisualDiffTool(server) {
server.registerTool('browser_visual_diff', {
title: 'Visual Screenshot Diff',
description: 'Capture the current page/element and compare it to a previously saved baseline with pixelmatch. Returns mismatch stats and a diff PNG data URI.',
inputSchema: {
name: z.string().min(1).describe('Baseline name previously saved with browser_screenshot_baseline.'),
selector: z.string().optional().describe('Optional CSS selector to screenshot for comparison.'),
fullPage: z
.boolean()
.optional()
.default(false)
.describe('Capture the full scrollable page when selector is omitted.'),
threshold: z
.number()
.min(0)
.max(1)
.optional()
.default(0.1)
.describe('pixelmatch threshold (01). Lower is stricter.'),
},
}, async ({ name, selector, fullPage, threshold }) => {
try {
const baselinePng = readBaseline(name);
const session = await browserManager.getSession();
let currentPng;
if (selector) {
const element = await session.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 5000 });
currentPng = await element.screenshot({ type: 'png' });
}
else {
currentPng = await session.page.screenshot({ fullPage, type: 'png' });
}
const comparison = comparePngBuffers(baselinePng, currentPng, threshold ?? 0.1);
const diffDataUri = toDataUri(comparison.diffPng);
return jsonResult({
name,
baselinePath: resolveBaselinePath(name),
selector: selector ?? null,
width: comparison.width,
height: comparison.height,
mismatchedPixels: comparison.mismatchedPixels,
mismatchPercent: Number(comparison.mismatchPercent.toFixed(4)),
passed: comparison.mismatchedPixels === 0,
threshold: threshold ?? 0.1,
diffDataUri,
truncatedDiffDataUri: diffDataUri.length > config.maxToolOutputChars
? `${diffDataUri.slice(0, config.maxToolOutputChars)}...`
: diffDataUri,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=visualDiff.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"visualDiff.js","sourceRoot":"","sources":["../../../src/tools/browser/visualDiff.ts"],"names":[],"mappings":"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"}