feat: add preview tool — navigate + screenshot in one call with optional POST
New MCP tool `preview` combines goto + screenshot with viewport control. Optionally POSTs result to any HTTP endpoint (e.g. HUD/visor) via previewUrl. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import type {
|
||||
MetricsData,
|
||||
NetworkEntry,
|
||||
PageError,
|
||||
PreviewCommand,
|
||||
StorageCommand,
|
||||
} from './types.js';
|
||||
|
||||
@@ -878,6 +879,62 @@ export class ClaudeBrowser {
|
||||
}
|
||||
}
|
||||
|
||||
private async handlePreviewCommand(cmd: PreviewCommand): Promise<CommandResponse> {
|
||||
const page = this.ensurePage();
|
||||
|
||||
// Resize viewport if dimensions specified
|
||||
if (cmd.width || cmd.height) {
|
||||
const current = page.viewportSize();
|
||||
const width = cmd.width || current?.width || 1280;
|
||||
const height = cmd.height || current?.height || 800;
|
||||
await page.setViewportSize({ width, height });
|
||||
}
|
||||
|
||||
// Navigate
|
||||
const nav = await this.goto(cmd.url);
|
||||
|
||||
// Screenshot
|
||||
const outputPath = resolve(cmd.output || '/tmp/preview.png');
|
||||
await page.screenshot({ path: outputPath, fullPage: cmd.fullPage || false });
|
||||
|
||||
// Optional: POST to preview endpoint
|
||||
let posted = false;
|
||||
if (cmd.previewUrl) {
|
||||
posted = await this.postPreview(outputPath, cmd.previewUrl, cmd.title, cmd.caption);
|
||||
}
|
||||
|
||||
return { ok: true, path: outputPath, url: nav.url, title: nav.title, posted };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST a screenshot to a preview endpoint.
|
||||
* Payload: { source: "file:///path", title, caption }
|
||||
* Silent failure — returns false if endpoint is unreachable.
|
||||
*/
|
||||
private async postPreview(
|
||||
imagePath: string,
|
||||
previewUrl: string,
|
||||
title?: string,
|
||||
caption?: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const payload = JSON.stringify({
|
||||
source: `file://${resolve(imagePath)}`,
|
||||
title: title || null,
|
||||
caption: caption || null,
|
||||
});
|
||||
const res = await fetch(previewUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: payload,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleImportCommand(cmd: ImportCommand): Promise<CommandResponse> {
|
||||
const context = this.getContext();
|
||||
if (!context) throw new Error('Browser not launched');
|
||||
@@ -1139,6 +1196,8 @@ export class ClaudeBrowser {
|
||||
}
|
||||
case 'import':
|
||||
return this.handleImportCommand(cmd);
|
||||
case 'preview':
|
||||
return this.handlePreviewCommand(cmd);
|
||||
default: {
|
||||
const _exhaustive: never = cmd;
|
||||
return { ok: false, error: `Unknown command: ${(_exhaustive as { cmd: string }).cmd}` };
|
||||
|
||||
+34
@@ -255,6 +255,40 @@ server.tool(
|
||||
})
|
||||
);
|
||||
|
||||
// Preview — navigate + screenshot in one call, optional POST to preview endpoint
|
||||
server.tool(
|
||||
'preview',
|
||||
'Navigate to a URL and take a screenshot in one call. Optionally POST the result to a preview endpoint.',
|
||||
{
|
||||
url: z.string().describe('URL or file:///path to preview'),
|
||||
width: z.number().optional().default(1280).describe('Viewport width'),
|
||||
height: z.number().optional().default(800).describe('Viewport height'),
|
||||
fullPage: z.boolean().optional().default(false),
|
||||
output: z.string().optional().default('/tmp/preview.png').describe('Screenshot output path'),
|
||||
previewUrl: z.string().optional().describe('HTTP endpoint to POST screenshot result to'),
|
||||
title: z.string().optional().describe('Title sent with preview POST'),
|
||||
caption: z.string().optional().describe('Caption sent with preview POST'),
|
||||
},
|
||||
withLogging(
|
||||
'preview',
|
||||
async ({ url, width, height, fullPage, output, previewUrl, title, caption }) => {
|
||||
await ensureLaunched();
|
||||
const result = await browser.executeCommand({
|
||||
cmd: 'preview',
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
fullPage,
|
||||
output,
|
||||
previewUrl,
|
||||
title,
|
||||
caption,
|
||||
});
|
||||
return textResult(JSON.stringify(result));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Eval
|
||||
server.tool(
|
||||
'eval',
|
||||
|
||||
+16
-1
@@ -347,7 +347,20 @@ export type BrowserCommand =
|
||||
| ScrollCommand
|
||||
| ViewportCommand
|
||||
| EmulateCommand
|
||||
| ImportCommand;
|
||||
| ImportCommand
|
||||
| PreviewCommand;
|
||||
|
||||
export interface PreviewCommand {
|
||||
cmd: 'preview';
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fullPage?: boolean;
|
||||
output?: string;
|
||||
previewUrl?: string;
|
||||
title?: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
// Response types
|
||||
export interface SuccessResponse {
|
||||
@@ -359,6 +372,8 @@ export interface SuccessResponse {
|
||||
count?: number;
|
||||
elements?: ElementInfo[];
|
||||
result?: unknown;
|
||||
// Preview fields
|
||||
posted?: boolean;
|
||||
// Image processing fields
|
||||
files?: string[];
|
||||
outputDir?: string;
|
||||
|
||||
Reference in New Issue
Block a user