diff --git a/package.json b/package.json index a54543a6..f31e95be 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,7 @@ "core-js": "3.46.0", "debug": "4.4.3", "puppeteer-core": "^24.24.1", - "yargs": "18.0.0", - "zod": "^3.25.76" + "yargs": "18.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", diff --git a/src/third_party/modelcontextprotocol-sdk/index.ts b/src/third_party/modelcontextprotocol-sdk/index.ts index 3c3d05bf..6366490f 100644 --- a/src/third_party/modelcontextprotocol-sdk/index.ts +++ b/src/third_party/modelcontextprotocol-sdk/index.ts @@ -12,3 +12,4 @@ export { type ImageContent, type TextContent, } from '@modelcontextprotocol/sdk/types.js'; +export {z as zod} from 'zod'; diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index f5cb454b..7c3b379f 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -5,15 +5,17 @@ */ import type {Dialog, ElementHandle, Page} from 'puppeteer-core'; -import z from 'zod'; import type {TextSnapshotNode} from '../McpContext.js'; +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import type {TraceResult} from '../trace-processing/parse.js'; import type {PaginationOptions} from '../utils/types.js'; import type {ToolCategories} from './categories.js'; -export interface ToolDefinition { +export interface ToolDefinition< + Schema extends zod.ZodRawShape = zod.ZodRawShape, +> { name: string; description: string; annotations: { @@ -32,8 +34,8 @@ export interface ToolDefinition { ) => Promise; } -export interface Request { - params: z.objectOutputType; +export interface Request { + params: zod.objectOutputType; } export interface ImageContentData { @@ -92,7 +94,7 @@ export type Context = Readonly<{ waitForEventsAfterAction(action: () => Promise): Promise; }>; -export function defineTool( +export function defineTool( definition: ToolDefinition, ) { return definition; @@ -102,7 +104,7 @@ export const CLOSE_PAGE_ERROR = 'The last open page cannot be closed. It is fine to keep it open.'; export const timeoutSchema = { - timeout: z + timeout: zod .number() .int() .optional() diff --git a/src/tools/console.ts b/src/tools/console.ts index 5b049111..91ac20d4 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -5,7 +5,8 @@ */ import type {ConsoleMessageType} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -44,7 +45,7 @@ export const consoleTool = defineTool({ readOnlyHint: true, }, schema: { - pageSize: z + pageSize: zod .number() .int() .positive() @@ -52,7 +53,7 @@ export const consoleTool = defineTool({ .describe( 'Maximum number of messages to return. When omitted, returns all requests.', ), - pageIdx: z + pageIdx: zod .number() .int() .min(0) @@ -60,8 +61,8 @@ export const consoleTool = defineTool({ .describe( 'Page number to return (0-based). When omitted, returns the first page.', ), - types: z - .array(z.enum(FILTERABLE_MESSAGE_TYPES)) + types: zod + .array(zod.enum(FILTERABLE_MESSAGE_TYPES)) .optional() .describe( 'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.', diff --git a/src/tools/emulation.ts b/src/tools/emulation.ts index 92b949e9..d65ac5e8 100644 --- a/src/tools/emulation.ts +++ b/src/tools/emulation.ts @@ -5,7 +5,8 @@ */ import {PredefinedNetworkConditions} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -24,7 +25,7 @@ export const emulateNetwork = defineTool({ readOnlyHint: false, }, schema: { - throttlingOption: z + throttlingOption: zod .enum(throttlingOptions) .describe( `The network throttling option to emulate. Available throttling options are: ${throttlingOptions.join(', ')}. Set to "No emulation" to disable. Set to "Offline" to simulate offline network conditions.`, @@ -70,7 +71,7 @@ export const emulateCpu = defineTool({ readOnlyHint: false, }, schema: { - throttlingRate: z + throttlingRate: zod .number() .min(1) .max(20) diff --git a/src/tools/input.ts b/src/tools/input.ts index 02bb8a0f..ffb1cbb7 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -5,9 +5,9 @@ */ import type {ElementHandle} from 'puppeteer-core'; -import z from 'zod'; import type {McpContext, TextSnapshotNode} from '../McpContext.js'; +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -20,12 +20,12 @@ export const click = defineTool({ readOnlyHint: false, }, schema: { - uid: z + uid: zod .string() .describe( 'The uid of an element on the page from the page content snapshot', ), - dblClick: z + dblClick: zod .boolean() .optional() .describe('Set to true for double clicks. Default is false.'), @@ -59,7 +59,7 @@ export const hover = defineTool({ readOnlyHint: false, }, schema: { - uid: z + uid: zod .string() .describe( 'The uid of an element on the page from the page content snapshot', @@ -143,12 +143,12 @@ export const fill = defineTool({ readOnlyHint: false, }, schema: { - uid: z + uid: zod .string() .describe( 'The uid of an element on the page from the page content snapshot', ), - value: z.string().describe('The value to fill in'), + value: zod.string().describe('The value to fill in'), }, handler: async (request, response, context) => { await context.waitForEventsAfterAction(async () => { @@ -171,8 +171,8 @@ export const drag = defineTool({ readOnlyHint: false, }, schema: { - from_uid: z.string().describe('The uid of the element to drag'), - to_uid: z.string().describe('The uid of the element to drop into'), + from_uid: zod.string().describe('The uid of the element to drag'), + to_uid: zod.string().describe('The uid of the element to drop into'), }, handler: async (request, response, context) => { const fromHandle = await context.getElementByUid(request.params.from_uid); @@ -200,11 +200,11 @@ export const fillForm = defineTool({ readOnlyHint: false, }, schema: { - elements: z + elements: zod .array( - z.object({ - uid: z.string().describe('The uid of the element to fill out'), - value: z.string().describe('Value for the element'), + zod.object({ + uid: zod.string().describe('The uid of the element to fill out'), + value: zod.string().describe('Value for the element'), }), ) .describe('Elements from snapshot to fill out.'), @@ -232,12 +232,12 @@ export const uploadFile = defineTool({ readOnlyHint: false, }, schema: { - uid: z + uid: zod .string() .describe( 'The uid of the file input element or an element that will open file chooser on the page from the page content snapshot', ), - filePath: z.string().describe('The local path of the file to upload'), + filePath: zod.string().describe('The local path of the file to upload'), }, handler: async (request, response, context) => { const {uid, filePath} = request.params; diff --git a/src/tools/network.ts b/src/tools/network.ts index 23b2c1f6..0dbc7f7e 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -5,7 +5,8 @@ */ import type {ResourceType} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -40,7 +41,7 @@ export const listNetworkRequests = defineTool({ readOnlyHint: true, }, schema: { - pageSize: z + pageSize: zod .number() .int() .positive() @@ -48,7 +49,7 @@ export const listNetworkRequests = defineTool({ .describe( 'Maximum number of requests to return. When omitted, returns all requests.', ), - pageIdx: z + pageIdx: zod .number() .int() .min(0) @@ -56,8 +57,8 @@ export const listNetworkRequests = defineTool({ .describe( 'Page number to return (0-based). When omitted, returns the first page.', ), - resourceTypes: z - .array(z.enum(FILTERABLE_RESOURCE_TYPES)) + resourceTypes: zod + .array(zod.enum(FILTERABLE_RESOURCE_TYPES)) .optional() .describe( 'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.', @@ -80,7 +81,7 @@ export const getNetworkRequest = defineTool({ readOnlyHint: true, }, schema: { - reqid: z + reqid: zod .number() .describe( 'The reqid of a request on the page from the listed network requests', diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 7643881d..ac6756d2 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import z from 'zod'; - import {logger} from '../logger.js'; +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {CLOSE_PAGE_ERROR, defineTool, timeoutSchema} from './ToolDefinition.js'; @@ -32,7 +31,7 @@ export const selectPage = defineTool({ readOnlyHint: true, }, schema: { - pageIdx: z + pageIdx: zod .number() .describe( 'The index of the page to select. Call list_pages to list pages.', @@ -54,7 +53,7 @@ export const closePage = defineTool({ readOnlyHint: false, }, schema: { - pageIdx: z + pageIdx: zod .number() .describe( 'The index of the page to close. Call list_pages to list pages.', @@ -82,7 +81,7 @@ export const newPage = defineTool({ readOnlyHint: false, }, schema: { - url: z.string().describe('URL to load in a new page.'), + url: zod.string().describe('URL to load in a new page.'), ...timeoutSchema, }, handler: async (request, response, context) => { @@ -106,7 +105,7 @@ export const navigatePage = defineTool({ readOnlyHint: false, }, schema: { - url: z.string().describe('URL to navigate the page to'), + url: zod.string().describe('URL to navigate the page to'), ...timeoutSchema, }, handler: async (request, response, context) => { @@ -130,7 +129,7 @@ export const navigatePageHistory = defineTool({ readOnlyHint: false, }, schema: { - navigate: z + navigate: zod .enum(['back', 'forward']) .describe( 'Whether to navigate back or navigate forward in the selected pages history', @@ -167,8 +166,8 @@ export const resizePage = defineTool({ readOnlyHint: false, }, schema: { - width: z.number().describe('Page width'), - height: z.number().describe('Page height'), + width: zod.number().describe('Page width'), + height: zod.number().describe('Page height'), }, handler: async (request, response, context) => { const page = context.getSelectedPage(); @@ -191,10 +190,10 @@ export const handleDialog = defineTool({ readOnlyHint: false, }, schema: { - action: z + action: zod .enum(['accept', 'dismiss']) .describe('Whether to dismiss or accept the dialog'), - promptText: z + promptText: zod .string() .optional() .describe('Optional prompt text to enter into the dialog.'), diff --git a/src/tools/performance.ts b/src/tools/performance.ts index 9a6a3b95..6c172fbb 100644 --- a/src/tools/performance.ts +++ b/src/tools/performance.ts @@ -5,9 +5,9 @@ */ import type {Page} from 'puppeteer-core'; -import z from 'zod'; import {logger} from '../logger.js'; +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import type {InsightName} from '../trace-processing/parse.js'; import { getInsightOutput, @@ -29,12 +29,12 @@ export const startTrace = defineTool({ readOnlyHint: true, }, schema: { - reload: z + reload: zod .boolean() .describe( 'Determines if, once tracing has started, the page should be automatically reloaded.', ), - autoStop: z + autoStop: zod .boolean() .describe( 'Determines if the trace recording should be automatically stopped.', @@ -128,7 +128,7 @@ export const analyzeInsight = defineTool({ readOnlyHint: true, }, schema: { - insightName: z + insightName: zod .string() .describe( 'The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"', diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index 901a880d..991fbd2b 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -5,7 +5,8 @@ */ import type {ElementHandle, Page} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -18,11 +19,11 @@ export const screenshot = defineTool({ readOnlyHint: true, }, schema: { - format: z + format: zod .enum(['png', 'jpeg', 'webp']) .default('png') .describe('Type of format to save the screenshot as. Default is "png"'), - quality: z + quality: zod .number() .min(0) .max(100) @@ -30,19 +31,19 @@ export const screenshot = defineTool({ .describe( 'Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format.', ), - uid: z + uid: zod .string() .optional() .describe( 'The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.', ), - fullPage: z + fullPage: zod .boolean() .optional() .describe( 'If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid.', ), - filePath: z + filePath: zod .string() .optional() .describe( diff --git a/src/tools/script.ts b/src/tools/script.ts index be6349d1..beaef94b 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -4,7 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ import type {JSHandle} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -18,7 +19,7 @@ so returned values have to JSON-serializable.`, readOnlyHint: false, }, schema: { - function: z.string().describe( + function: zod.string().describe( `A JavaScript function declaration to be executed by the tool in the currently selected page. Example without arguments: \`() => { return document.title @@ -30,10 +31,10 @@ Example with arguments: \`(el) => { }\` `, ), - args: z + args: zod .array( - z.object({ - uid: z + zod.object({ + uid: zod .string() .describe( 'The uid of an element on the page from the page content snapshot', diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index 75ab1d0e..fb774f78 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -5,7 +5,8 @@ */ import {Locator} from 'puppeteer-core'; -import z from 'zod'; + +import {zod} from '../third_party/modelcontextprotocol-sdk/index.js'; import {ToolCategories} from './categories.js'; import {defineTool, timeoutSchema} from './ToolDefinition.js'; @@ -19,7 +20,7 @@ identifier (uid). Always use the latest snapshot. Prefer taking a snapshot over readOnlyHint: true, }, schema: { - verbose: z + verbose: zod .boolean() .optional() .describe( @@ -39,7 +40,7 @@ export const waitFor = defineTool({ readOnlyHint: true, }, schema: { - text: z.string().describe('Text to appear on the page'), + text: zod.string().describe('Text to appear on the page'), ...timeoutSchema, }, handler: async (request, response, context) => {