Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"dependencies": {
"core-js": "3.46.0",
"debug": "4.4.3",
"puppeteer-core": "^24.24.1",
"yargs": "18.0.0"
},
"devDependencies": {
Expand Down
50 changes: 38 additions & 12 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,31 @@ import license from 'rollup-plugin-license';

const isProduction = process.env.NODE_ENV === 'production';

/** @type {import('rollup').RollupOptions} */
const sdk = {
input: './build/src/third_party/modelcontextprotocol-sdk/index.js',
const allowedLicenses = [
'MIT',
'Apache 2.0',
'Apache-2.0',
'BSD-3-Clause',
'BSD-2-Clause',
'ISC',
'0BSD',
];

/**
* @param {string} wrapperIndexPath
* @param {import('rollup').OutputOptions} [extraOutputOptions={}]
* @param {string[]} [external=[]]
* @returns {import('rollup').RollupOptions}
*/
const bundleDependency = (
wrapperIndexPath,
extraOutputOptions = {},
external = [],
) => ({
input: wrapperIndexPath,
output: {
file: './build/src/third_party/modelcontextprotocol-sdk/index.js',
...extraOutputOptions,
file: wrapperIndexPath,
sourcemap: !isProduction,
format: 'esm',
},
Expand All @@ -48,18 +68,14 @@ const sdk = {
thirdParty: {
allow: {
test: dependency => {
let allowed_licenses = ['MIT', 'Apache 2.0', 'BSD-2-Clause', 'ISC'];
return allowed_licenses.includes(dependency.license);
return allowedLicenses.includes(dependency.license);
},
failOnUnlicensed: true,
failOnViolation: true,
},
output: {
file: path.join(
'build',
'src',
'third_party',
'modelcontextprotocol-sdk',
path.dirname(wrapperIndexPath),
'THIRD_PARTY_NOTICES',
),
template(dependencies) {
Expand Down Expand Up @@ -90,6 +106,16 @@ const sdk = {
json(),
nodeResolve(),
],
};
external,
});

export default [sdk];
export default [
bundleDependency('./build/src/third_party/modelcontextprotocol-sdk/index.js'),
bundleDependency(
'./build/src/third_party/puppeteer-core/index.js',
{
inlineDynamicImports: true,
},
['./bidi.js', '../bidi/bidi.js'],
),
];
4 changes: 2 additions & 2 deletions src/DevToolsConnectionAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {type ConnectionTransport} from 'puppeteer-core';

import {Connection} from '../node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js';

import {type ConnectionTransport} from './third_party/puppeteer-core/index.js';

/**
* Allows a puppeteer {@link ConnectionTransport} to act like a DevTools {@link Connection}.
*/
Expand Down
51 changes: 44 additions & 7 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import os from 'node:os';
import path from 'node:path';

import type {Debugger} from 'debug';

import type {ListenerMap} from './PageCollector.js';
import {NetworkCollector, PageCollector} from './PageCollector.js';
import {Locator} from './third_party/puppeteer-core/index.js';
import type {
Browser,
ConsoleMessage,
Expand All @@ -17,10 +21,7 @@ import type {
Page,
SerializedAXNode,
PredefinedNetworkConditions,
} from 'puppeteer-core';

import type {ListenerMap} from './PageCollector.js';
import {NetworkCollector, PageCollector} from './PageCollector.js';
} from './third_party/puppeteer-core/index.js';
import {listPages} from './tools/pages.js';
import {takeSnapshot} from './tools/snapshot.js';
import {CLOSE_PAGE_ERROR} from './tools/ToolDefinition.js';
Expand Down Expand Up @@ -91,9 +92,16 @@ export class McpContext implements Context {
#nextSnapshotId = 1;
#traceResults: TraceResult[] = [];

private constructor(browser: Browser, logger: Debugger) {
#locatorClass: typeof Locator;

private constructor(
browser: Browser,
logger: Debugger,
locatorClass: typeof Locator,
) {
this.browser = browser;
this.logger = logger;
this.#locatorClass = locatorClass;

this.#networkCollector = new NetworkCollector(this.browser);

Expand Down Expand Up @@ -122,8 +130,13 @@ export class McpContext implements Context {
await this.#consoleCollector.init();
}

static async from(browser: Browser, logger: Debugger) {
const context = new McpContext(browser, logger);
static async from(
browser: Browser,
logger: Debugger,
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
locatorClass: typeof Locator = Locator,
) {
const context = new McpContext(browser, logger, locatorClass);
await context.#init();
return context;
}
Expand Down Expand Up @@ -428,4 +441,28 @@ export class McpContext implements Context {
getNetworkRequestStableId(request: HTTPRequest): number {
return this.#networkCollector.getIdForResource(request);
}

waitForTextOnPage({
text,
timeout,
}: {
text: string;
timeout?: number | undefined;
}): Promise<Element> {
const page = this.getSelectedPage();
const frames = page.frames();

const locator = this.#locatorClass.race(
frames.flatMap(frame => [
frame.locator(`aria/${text}`),
frame.locator(`text/${text}`),
]),
);

if (timeout) {
locator.setTimeout(timeout);
}

return locator.wait();
}
}
6 changes: 4 additions & 2 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {ConsoleMessage, ResourceType} from 'puppeteer-core';

import type {ConsoleMessageData} from './formatters/consoleFormatter.js';
import {
formatConsoleEventShort,
Expand All @@ -23,6 +21,10 @@ import type {
ImageContent,
TextContent,
} from './third_party/modelcontextprotocol-sdk/index.js';
import type {
ConsoleMessage,
ResourceType,
} from './third_party/puppeteer-core/index.js';
import {handleDialog} from './tools/pages.js';
import type {ImageContentData, Response} from './tools/ToolDefinition.js';
import {paginate} from './utils/pagination.js';
Expand Down
2 changes: 1 addition & 1 deletion src/PageCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type HTTPRequest,
type Page,
type PageEvents,
} from 'puppeteer-core';
} from './third_party/puppeteer-core/index.js';

export type ListenerMap<EventMap extends PageEvents = PageEvents> = {
[K in keyof EventMap]?: (event: EventMap[K]) => void;
Expand Down
2 changes: 1 addition & 1 deletion src/WaitForHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {Page, Protocol} from 'puppeteer-core';
import type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js';

import {logger} from './logger.js';
import type {Page, Protocol} from './third_party/puppeteer-core/index.js';

export class WaitForHelper {
#abortController = new AbortController();
Expand Down
4 changes: 2 additions & 2 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import type {
ChromeReleaseChannel,
LaunchOptions,
Target,
} from 'puppeteer-core';
import puppeteer from 'puppeteer-core';
} from './third_party/puppeteer-core/index.js';
import {puppeteer} from './third_party/puppeteer-core/index.js';

let browser: Browser | undefined;

Expand Down
5 changes: 4 additions & 1 deletion src/formatters/networkFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

import {isUtf8} from 'node:buffer';

import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
import type {
HTTPRequest,
HTTPResponse,
} from '../third_party/puppeteer-core/index.js';

const BODY_CONTEXT_SIZE_LIMIT = 10000;

Expand Down
9 changes: 9 additions & 0 deletions src/third_party/puppeteer-core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

export {Locator, PredefinedNetworkConditions} from 'puppeteer-core';
export {default as puppeteer} from 'puppeteer-core';
export type * from 'puppeteer-core';
11 changes: 9 additions & 2 deletions src/tools/ToolDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {Dialog, ElementHandle, Page} from 'puppeteer-core';

import type {TextSnapshotNode} from '../McpContext.js';
import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {
Dialog,
ElementHandle,
Page,
} from '../third_party/puppeteer-core/index.js';
import type {TraceResult} from '../trace-processing/parse.js';
import type {PaginationOptions} from '../utils/types.js';

Expand Down Expand Up @@ -93,6 +96,10 @@ export type Context = Readonly<{
filename: string,
): Promise<{filename: string}>;
waitForEventsAfterAction(action: () => Promise<unknown>): Promise<void>;
waitForTextOnPage(params: {
text: string;
timeout?: number | undefined;
}): Promise<Element>;
}>;

export function defineTool<Schema extends zod.ZodRawShape>(
Expand Down
3 changes: 1 addition & 2 deletions src/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {ConsoleMessageType} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {ConsoleMessageType} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
3 changes: 1 addition & 2 deletions src/tools/emulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {PredefinedNetworkConditions} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import {PredefinedNetworkConditions} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
3 changes: 1 addition & 2 deletions src/tools/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {ElementHandle} from 'puppeteer-core';

import type {McpContext, TextSnapshotNode} from '../McpContext.js';
import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {ElementHandle} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
3 changes: 1 addition & 2 deletions src/tools/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {ResourceType} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {ResourceType} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
3 changes: 1 addition & 2 deletions src/tools/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {Page} from 'puppeteer-core';

import {logger} from '../logger.js';
import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {Page} from '../third_party/puppeteer-core/index.js';
import type {InsightName} from '../trace-processing/parse.js';
import {
getInsightOutput,
Expand Down
3 changes: 1 addition & 2 deletions src/tools/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {ElementHandle, Page} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {ElementHandle, Page} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
6 changes: 5 additions & 1 deletion src/tools/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {Frame, JSHandle, Page} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';
import type {
Frame,
JSHandle,
Page,
} from '../third_party/puppeteer-core/index.js';

import {ToolCategories} from './categories.js';
import {defineTool} from './ToolDefinition.js';
Expand Down
18 changes: 1 addition & 17 deletions src/tools/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {Locator} from 'puppeteer-core';

import {zod} from '../third_party/modelcontextprotocol-sdk/index.js';

import {ToolCategories} from './categories.js';
Expand Down Expand Up @@ -44,21 +42,7 @@ export const waitFor = defineTool({
...timeoutSchema,
},
handler: async (request, response, context) => {
const page = context.getSelectedPage();
const frames = page.frames();

const locator = Locator.race(
frames.flatMap(frame => [
frame.locator(`aria/${request.params.text}`),
frame.locator(`text/${request.params.text}`),
]),
);

if (request.params.timeout) {
locator.setTimeout(request.params.timeout);
}

await locator.wait();
await context.waitForTextOnPage(request.params);

response.appendResponseLine(
`Element with text "${request.params.text}" found.`,
Expand Down
Loading