From c3cd57611db7d0e8218f3c6b8ec25282af137daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=20=E5=A5=88=E6=9C=88?= <43605695+akinazuki@users.noreply.github.com> Date: Tue, 17 Mar 2026 04:27:06 +0900 Subject: [PATCH 1/2] Add Zod schemas and screenshot handling Introduce reusable Zod schemas (browserRequestZodObject and screenshotRequestZodObject) and use them to validate params for get_url_html_content, get_url_markdown and get_url_screenshot tools. Replace inline param shapes with parsed schema spreads in requests, add logging for markdown requests, and improve screenshot handling by computing mime type from screenshotOptions.type and supporting base64-encoded responses when requested. Also include richer screenshot options and defaults for viewport/goto/screenshot settings. --- .../src/tools/browser.tools.ts | 92 ++++++++++++++----- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/apps/browser-rendering/src/tools/browser.tools.ts b/apps/browser-rendering/src/tools/browser.tools.ts index fa42d8ee..f60b55f7 100644 --- a/apps/browser-rendering/src/tools/browser.tools.ts +++ b/apps/browser-rendering/src/tools/browser.tools.ts @@ -5,13 +5,58 @@ import { getProps } from '@repo/mcp-common/src/get-props' import type { BrowserMCP } from '../browser.app' +const browserRequestZodObject = z.object({ + url: z.string().url(), + authenticate: z.object({ + username: z.string(), + password: z.string(), + }).optional(), + gotoOptions: z.object({ + referer: z.string().url().optional(), + waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).default('networkidle2'), + timeout: z.number().default(30000), + }).optional(), + cookies: z.array( + z.object({ + name: z.string(), + value: z.string(), + }) + ).optional(), + userAgent: z.string().optional(), + setExtraHTTPHeaders: z.record(z.string()).optional(), +}) + +const browserRequestSchema = browserRequestZodObject.shape + +const screenshotRequestZodObject = browserRequestZodObject.extend({ + viewport: z.object({ + height: z.number().default(600), + width: z.number().default(800), + }).optional(), + screenshotOptions: z.object({ + captureBeyondViewport: z.boolean().optional(), + clip: z.object({ + height: z.number(), + width: z.number(), + x: z.number(), + y: z.number(), + scale: z.number().optional(), + }).optional(), + encoding: z.enum(['binary', 'base64']).default('binary').optional(), + fromSurface: z.boolean().optional(), + fullPage: z.boolean().optional(), + omitBackground: z.boolean().optional(), + optimizeForSpeed: z.boolean().optional(), + quality: z.number().optional(), + type: z.enum(['png', 'jpeg', 'webp']).default('png').optional(), + }).optional(), +}) + export function registerBrowserTools(agent: BrowserMCP) { agent.server.tool( 'get_url_html_content', 'Get page HTML content', - { - url: z.string().url(), - }, + browserRequestSchema, async (params) => { const accountId = await agent.getActiveAccountId() if (!accountId) { @@ -29,7 +74,7 @@ export function registerBrowserTools(agent: BrowserMCP) { const client = getCloudflareClient(props.accessToken) const r = await client.browserRendering.content.create({ account_id: accountId, - url: params.url, + ...browserRequestZodObject.parse(params), }) return { @@ -58,9 +103,7 @@ export function registerBrowserTools(agent: BrowserMCP) { agent.server.tool( 'get_url_markdown', 'Get page converted into Markdown', - { - url: z.string().url(), - }, + browserRequestSchema, async (params) => { const accountId = await agent.getActiveAccountId() if (!accountId) { @@ -76,10 +119,9 @@ export function registerBrowserTools(agent: BrowserMCP) { try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) + console.log('Requesting markdown rendering with params:', params) const r = (await client.post(`/accounts/${accountId}/browser-rendering/markdown`, { - body: { - url: params.url, - }, + body: { ...browserRequestZodObject.parse(params) }, })) as { result: string } return { @@ -108,15 +150,7 @@ export function registerBrowserTools(agent: BrowserMCP) { agent.server.tool( 'get_url_screenshot', 'Get page screenshot', - { - url: z.string().url(), - viewport: z - .object({ - height: z.number().default(600), - width: z.number().default(800), - }) - .optional(), - }, + screenshotRequestZodObject.shape, async (params) => { const accountId = await agent.getActiveAccountId() if (!accountId) { @@ -134,22 +168,32 @@ export function registerBrowserTools(agent: BrowserMCP) { const client = getCloudflareClient(props.accessToken) const r = await client .post(`/accounts/${accountId}/browser-rendering/screenshot`, { - body: { - url: params.url, - viewport: params.viewport, - }, + body: { ...screenshotRequestZodObject.parse(params) }, __binaryResponse: true, }) .asResponse() const arrayBuffer = await r.arrayBuffer() const base64Image = Buffer.from(arrayBuffer).toString('base64') + const imageType = params.screenshotOptions?.type ?? 'png' + const mimeType = `image/${imageType}` as 'image/png' | 'image/jpeg' | 'image/webp' + + if (params.screenshotOptions?.encoding === 'base64') { + return { + content: [ + { + type: 'text', + text: base64Image, + }, + ], + } + } return { content: [ { type: 'image', - mimeType: 'image/png', + mimeType, data: base64Image, }, ], From fadc5b63f6cd47cdf6c32c75c54cea66e9ded8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=20=E5=A5=88=E6=9C=88?= <43605695+akinazuki@users.noreply.github.com> Date: Tue, 17 Mar 2026 04:28:07 +0900 Subject: [PATCH 2/2] remove log --- apps/browser-rendering/src/tools/browser.tools.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/browser-rendering/src/tools/browser.tools.ts b/apps/browser-rendering/src/tools/browser.tools.ts index f60b55f7..3dd044b8 100644 --- a/apps/browser-rendering/src/tools/browser.tools.ts +++ b/apps/browser-rendering/src/tools/browser.tools.ts @@ -119,7 +119,6 @@ export function registerBrowserTools(agent: BrowserMCP) { try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) - console.log('Requesting markdown rendering with params:', params) const r = (await client.post(`/accounts/${accountId}/browser-rendering/markdown`, { body: { ...browserRequestZodObject.parse(params) }, })) as { result: string }