diff --git a/apps/browser-rendering/src/tools/browser.tools.ts b/apps/browser-rendering/src/tools/browser.tools.ts index fa42d8ee..3dd044b8 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) { @@ -77,9 +120,7 @@ export function registerBrowserTools(agent: BrowserMCP) { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) const r = (await client.post(`/accounts/${accountId}/browser-rendering/markdown`, { - body: { - url: params.url, - }, + body: { ...browserRequestZodObject.parse(params) }, })) as { result: string } return { @@ -108,15 +149,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 +167,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, }, ],