feat(browser-tools): cut low-value tools

This commit is contained in:
Mohamed Boudra
2026-07-02 23:56:20 +02:00
parent b2714ccd89
commit 2ba3ad53ab
13 changed files with 119 additions and 1796 deletions
@@ -753,7 +753,7 @@ describe("browser MCP tools", () => {
const response = await tool.handler({});
expect(lookupTool(server, "browser_page_info")).not.toBeUndefined();
expect(lookupTool(server, "browser_snapshot")).not.toBeUndefined();
expect(execute).toHaveBeenCalledWith({
agentId: "agent-1",
cwd: REPO_CWD,
@@ -45,8 +45,8 @@ function createBroker(options: { enabled: boolean; timeoutMs?: number }): Browse
});
}
function pageInfoCommand(): BrowserAutomationCommand {
return { command: "page_info", args: { browserId: BROWSER_ID } };
function snapshotCommand(): BrowserAutomationCommand {
return { command: "snapshot", args: { browserId: BROWSER_ID } };
}
describe("BrowserToolsBroker", () => {
@@ -57,7 +57,7 @@ describe("BrowserToolsBroker", () => {
test("disabled returns browser_disabled", async () => {
const broker = createBroker({ enabled: false });
await expect(broker.execute({ command: pageInfoCommand() })).resolves.toEqual({
await expect(broker.execute({ command: snapshotCommand() })).resolves.toEqual({
requestId: "req-1",
ok: false,
error: {
@@ -71,7 +71,7 @@ describe("BrowserToolsBroker", () => {
test("no capable desktop returns browser_no_desktop", async () => {
const broker = createBroker({ enabled: true });
await expect(broker.execute({ command: pageInfoCommand() })).resolves.toEqual({
await expect(broker.execute({ command: snapshotCommand() })).resolves.toEqual({
requestId: "req-1",
ok: false,
error: {
@@ -68,6 +68,10 @@ class BrowserToolHarness {
return this.get(name).handler(parsed, {});
}
public toolNames(): string[] {
return Array.from(this.tools.keys());
}
private get(name: string): RegisteredTool {
const tool = this.tools.get(name);
if (!tool) {
@@ -150,18 +154,6 @@ function screenshotPayload(): Extract<BrowserToolsResponsePayload, { ok: true }>
}
const routedToolCases = [
{
name: "set background",
toolName: "browser_set_background",
input: { browserId: BROWSER_ID, color: "red" },
command: { command: "set_background", args: { browserId: BROWSER_ID, color: "red" } },
payload: {
requestId: "req-set-background",
ok: true,
result: { command: "set_background", browserId: BROWSER_ID, color: "red" },
},
content: [{ type: "text", text: "Set browser page background to red." }],
},
{
name: "click",
toolName: "browser_click",
@@ -226,7 +218,7 @@ const routedToolCases = [
name: "screenshot",
toolName: "browser_screenshot",
input: { browserId: BROWSER_ID },
command: { command: "screenshot", args: { browserId: BROWSER_ID } },
command: { command: "screenshot", args: { browserId: BROWSER_ID, fullPage: false } },
payload: screenshotPayload(),
content: [
{ type: "text", text: "Captured browser screenshot (800x600)." },
@@ -257,65 +249,16 @@ const routedToolCases = [
},
content: [{ type: "text", text: "Read 1 console log and 1 network entry." }],
},
{
name: "storage",
toolName: "browser_storage",
input: { browserId: BROWSER_ID },
command: { command: "storage", args: { browserId: BROWSER_ID } },
payload: {
requestId: "req-storage",
ok: true,
result: {
command: "storage",
browserId: BROWSER_ID,
url: "https://example.com",
cookies: [{ name: "theme", value: "dark" }],
localStorage: [{ key: "token", value: "abc" }],
sessionStorage: [{ key: "tab", value: "1" }],
},
},
content: [
{ type: "text", text: "Read 1 cookie, 1 localStorage entry, and 1 sessionStorage entry." },
],
},
{
name: "environment",
toolName: "browser_environment",
input: {
browserId: BROWSER_ID,
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
},
command: {
command: "environment",
args: {
browserId: BROWSER_ID,
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
},
},
payload: {
requestId: "req-environment",
ok: true,
result: {
command: "environment",
browserId: BROWSER_ID,
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
},
},
content: [{ type: "text", text: "Browser environment viewport is 390x844." }],
},
{
name: "full page screenshot",
toolName: "browser_full_page_screenshot",
input: { browserId: BROWSER_ID },
command: { command: "full_page_screenshot", args: { browserId: BROWSER_ID } },
toolName: "browser_screenshot",
input: { browserId: BROWSER_ID, fullPage: true },
command: { command: "screenshot", args: { browserId: BROWSER_ID, fullPage: true } },
payload: {
requestId: "req-full-page",
ok: true,
result: {
command: "full_page_screenshot",
command: "screenshot",
browserId: BROWSER_ID,
mimeType: "image/png",
dataBase64: "iVBORw0KGgo=",
@@ -324,56 +267,10 @@ const routedToolCases = [
},
},
content: [
{ type: "text", text: "Captured full-page browser screenshot (390x1200)." },
{ type: "text", text: "Captured browser screenshot (390x1200)." },
{ type: "image", data: "iVBORw0KGgo=", mimeType: "image/png" },
],
},
{
name: "pdf",
toolName: "browser_pdf",
input: { browserId: BROWSER_ID, landscape: true },
command: {
command: "pdf",
args: { browserId: BROWSER_ID, landscape: true, printBackground: true },
},
payload: {
requestId: "req-pdf",
ok: true,
result: {
command: "pdf",
browserId: BROWSER_ID,
mimeType: "application/pdf",
dataBase64: "JVBERg==",
},
},
content: [{ type: "text", text: "Exported browser page PDF." }],
},
{
name: "download",
toolName: "browser_download",
input: { browserId: BROWSER_ID, url: "https://example.com/file.txt", fileName: "file.txt" },
command: {
command: "download",
args: {
browserId: BROWSER_ID,
url: "https://example.com/file.txt",
fileName: "file.txt",
},
},
payload: {
requestId: "req-download",
ok: true,
result: {
command: "download",
browserId: BROWSER_ID,
url: "https://example.com/file.txt",
filePath: "/tmp/file.txt",
totalBytes: 5,
state: "completed",
},
},
content: [{ type: "text", text: "Downloaded browser file to /tmp/file.txt." }],
},
{
name: "upload",
toolName: "browser_upload",
@@ -394,42 +291,6 @@ const routedToolCases = [
},
content: [{ type: "text", text: "Uploaded 1 file to browser element @e1." }],
},
{
name: "focus",
toolName: "browser_focus",
input: { browserId: BROWSER_ID, ref: "@e1" },
command: { command: "focus", args: { browserId: BROWSER_ID, ref: "@e1" } },
payload: {
requestId: "req-focus",
ok: true,
result: { command: "focus", browserId: BROWSER_ID, ref: "@e1" },
},
content: [{ type: "text", text: "Focused browser element @e1." }],
},
{
name: "clear",
toolName: "browser_clear",
input: { browserId: BROWSER_ID, ref: "@e1" },
command: { command: "clear", args: { browserId: BROWSER_ID, ref: "@e1" } },
payload: {
requestId: "req-clear",
ok: true,
result: { command: "clear", browserId: BROWSER_ID, ref: "@e1" },
},
content: [{ type: "text", text: "Cleared browser element @e1." }],
},
{
name: "check",
toolName: "browser_check",
input: { browserId: BROWSER_ID, ref: "@e2", checked: false },
command: { command: "check", args: { browserId: BROWSER_ID, ref: "@e2", checked: false } },
payload: {
requestId: "req-check",
ok: true,
result: { command: "check", browserId: BROWSER_ID, ref: "@e2", checked: false },
},
content: [{ type: "text", text: "Unchecked browser element @e2." }],
},
{
name: "select",
toolName: "browser_select",
@@ -502,7 +363,7 @@ const brokerErrorCases = [
},
{
name: "typed timeout errors",
toolName: "browser_page_info",
toolName: "browser_snapshot",
input: { browserId: BROWSER_ID },
payload: {
requestId: "req-timeout",
@@ -562,6 +423,31 @@ const brokerErrorCases = [
}>;
describe("registerBrowserTools", () => {
test("registers the kept browser automation tools only", () => {
const harness = new BrowserToolHarness();
expect(harness.toolNames()).toEqual([
"browser_list_tabs",
"browser_new_tab",
"browser_snapshot",
"browser_click",
"browser_fill",
"browser_wait",
"browser_type",
"browser_keypress",
"browser_navigate",
"browser_back",
"browser_forward",
"browser_reload",
"browser_screenshot",
"browser_upload",
"browser_hover",
"browser_select",
"browser_drag",
"browser_logs",
]);
});
test("list tabs sends workspace in the request envelope", async () => {
const harness = new BrowserToolHarness();
@@ -612,12 +498,6 @@ describe("registerBrowserTools", () => {
input: { browserId: BROWSER_ID, url: "localhost:3000" },
expected: { browserId: BROWSER_ID, url: "http://localhost:3000" },
},
{
name: "download accepts an IP host without a scheme as http",
toolName: "browser_download",
input: { browserId: BROWSER_ID, url: "127.0.0.1:8081/admin" },
expected: { browserId: BROWSER_ID, url: "http://127.0.0.1:8081/admin" },
},
{
name: "new tab accepts a domain path without a scheme as http",
toolName: "browser_new_tab",
@@ -662,11 +542,6 @@ describe("registerBrowserTools", () => {
toolName: "browser_navigate",
input: { browserId: BROWSER_ID, url: "file:///tmp/index.html" },
},
{
name: "download rejects file URLs",
toolName: "browser_download",
input: { browserId: BROWSER_ID, url: "file:///tmp/archive.zip" },
},
{
name: "new tab rejects file URLs",
toolName: "browser_new_tab",
@@ -905,38 +780,16 @@ describe("registerBrowserTools", () => {
test("tab tools keep empty context when there is no caller agent", async () => {
const harness = new BrowserToolHarness(null, null);
harness.broker.setResponse({
requestId: "req-page-info",
ok: true,
result: {
command: "page_info",
tab: {
browserId: BROWSER_ID,
url: "https://example.com",
title: "Example",
isActive: false,
isLoading: false,
},
},
});
harness.broker.setResponse(snapshotPayload());
const response = await harness.execute("browser_page_info", { browserId: BROWSER_ID });
const response = await harness.execute("browser_snapshot", { browserId: BROWSER_ID });
expect(harness.broker.calls).toEqual([
{ command: { command: "page_info", args: { browserId: BROWSER_ID } } },
{ command: { command: "snapshot", args: { browserId: BROWSER_ID } } },
]);
expect(response.structuredContent).toEqual({
ok: true,
result: {
command: "page_info",
tab: {
browserId: BROWSER_ID,
url: "https://example.com",
title: "Example",
isActive: false,
isLoading: false,
},
},
result: snapshotPayload().result,
context: { browserId: BROWSER_ID },
});
});
@@ -140,35 +140,6 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
},
);
options.registerTool(
"browser_page_info",
{
title: "Get browser page info",
description:
"Get page info for a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: {
browserId: BrowserAutomationBrowserIdSchema,
},
outputSchema: BrowserToolOutputSchema,
},
async ({ browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "page_info",
args: {
browserId,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_snapshot",
{
@@ -442,11 +413,14 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
{
title: "Capture browser screenshot",
description:
"Capture a PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
"Capture a PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs. Set fullPage to true to capture the full page.",
inputSchema: {
browserId: BrowserAutomationBrowserIdSchema,
fullPage: z.boolean().default(false),
},
outputSchema: BrowserToolOutputSchema,
},
async ({ browserId }) => {
async ({ browserId, fullPage }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
@@ -457,99 +431,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
command: "screenshot",
args: {
browserId,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_full_page_screenshot",
{
title: "Capture full-page browser screenshot",
description:
"Capture a full-page PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
outputSchema: BrowserToolOutputSchema,
},
async ({ browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "full_page_screenshot",
args: {
browserId,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_pdf",
{
title: "Export browser page PDF",
description:
"Export a Paseo desktop browser tab as a PDF. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: {
browserId: BrowserAutomationBrowserIdSchema,
landscape: z.boolean().optional(),
printBackground: z.boolean().default(true),
},
outputSchema: BrowserToolOutputSchema,
},
async ({ browserId, landscape, printBackground }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "pdf",
args: {
browserId,
...(landscape !== undefined ? { landscape } : {}),
printBackground: printBackground ?? true,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_download",
{
title: "Download file in browser",
description:
"Download a URL through a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs; pass an http(s) URL or a scheme-less host URL, which is treated as http.",
inputSchema: {
url: BrowserHttpUrlInputSchema,
fileName: z.string().min(1).optional(),
browserId: BrowserAutomationBrowserIdSchema,
},
outputSchema: BrowserToolOutputSchema,
},
async ({ url, fileName, browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "download",
args: {
browserId,
url,
...(fileName ? { fileName } : {}),
fullPage: fullPage ?? false,
},
},
});
@@ -591,20 +473,6 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
);
for (const toolConfig of [
{
name: "browser_focus",
command: "focus",
title: "Focus browser element",
description:
"Focus an element in a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs; refs come from the latest browser_snapshot of the same tab and expire when the page changes.",
},
{
name: "browser_clear",
command: "clear",
title: "Clear browser element",
description:
"Clear an input-like element in a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs; refs come from the latest browser_snapshot of the same tab and expire when the page changes.",
},
{
name: "browser_hover",
command: "hover",
@@ -641,39 +509,6 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
);
}
options.registerTool(
"browser_check",
{
title: "Check browser control",
description:
"Set a checkbox or radio control in a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs; refs come from the latest browser_snapshot of the same tab and expire when the page changes.",
inputSchema: {
ref: BrowserRefInputSchema,
checked: z.boolean().default(true),
browserId: BrowserAutomationBrowserIdSchema,
},
outputSchema: BrowserToolOutputSchema,
},
async ({ ref, checked, browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "check",
args: {
browserId,
ref,
checked,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_select",
{
@@ -770,109 +605,6 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_storage",
{
title: "Read browser storage",
description:
"Read cookies plus localStorage and sessionStorage for a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
outputSchema: BrowserToolOutputSchema,
},
async ({ browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "storage",
args: {
browserId,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_environment",
{
title: "Set/read browser environment",
description:
"Set or read viewport and geolocation for a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: {
viewport: z
.object({
width: z.number().int().positive(),
height: z.number().int().positive(),
deviceScaleFactor: z.number().positive().optional(),
})
.optional(),
geolocation: z
.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
accuracy: z.number().positive().optional(),
})
.optional(),
browserId: BrowserAutomationBrowserIdSchema,
},
outputSchema: BrowserToolOutputSchema,
},
async ({ viewport, geolocation, browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "environment",
args: {
browserId,
...(viewport ? { viewport } : {}),
...(geolocation ? { geolocation } : {}),
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
options.registerTool(
"browser_set_background",
{
title: "Set browser background",
description:
"Set the current page background color in a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
inputSchema: {
color: z.string().min(1),
browserId: BrowserAutomationBrowserIdSchema,
},
outputSchema: BrowserToolOutputSchema,
},
async ({ color, browserId }) => {
const context = resolveBrowserToolContext(options);
const payload = await options.broker.execute({
agentId: context.agentId,
cwd: context.cwd,
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
command: {
command: "set_background",
args: {
browserId,
color,
},
},
});
return browserToolResult({ payload, context: { ...context, browserId } });
},
);
}
function resolveBrowserToolContext(options: RegisterBrowserToolsOptions): {
@@ -975,7 +707,7 @@ function browserToolSuccessContent(
function browserToolImageContent(
result: Extract<BrowserToolsResponsePayload, { ok: true }>["result"],
): PaseoToolResult["content"][number] | null {
if (result.command !== "screenshot" && result.command !== "full_page_screenshot") {
if (result.command !== "screenshot") {
return null;
}
@@ -1004,16 +736,6 @@ function summarizeBrowserSuccess(
return diagnosticsSummary;
}
const storageSummary = summarizeBrowserStorageSuccess(payload.result);
if (storageSummary) {
return storageSummary;
}
const environmentSummary = summarizeBrowserEnvironmentSuccess(payload.result);
if (environmentSummary) {
return environmentSummary;
}
const keyboardSummary = summarizeBrowserKeyboardSuccess(payload.result);
if (keyboardSummary) {
return keyboardSummary;
@@ -1057,10 +779,6 @@ function summarizeBrowserSuccess(
return `Browser wait matched ${payload.result.matched}.`;
}
if (payload.result.command === "page_info") {
return `Current page browserId=${payload.result.tab.browserId}: ${payload.result.tab.title || "Untitled"} — ${payload.result.tab.url}`;
}
return `Browser ${payload.result.command} complete.`;
}
@@ -1070,15 +788,6 @@ function summarizeBrowserMediaSuccess(
if (result.command === "screenshot") {
return `Captured browser screenshot (${result.width}x${result.height}).`;
}
if (result.command === "full_page_screenshot") {
return `Captured full-page browser screenshot (${result.width}x${result.height}).`;
}
if (result.command === "pdf") {
return "Exported browser page PDF.";
}
if (result.command === "download") {
return `Downloaded browser file to ${result.filePath}.`;
}
if (result.command === "upload") {
const count = result.filePaths.length;
return `Uploaded ${count} file${count === 1 ? "" : "s"} to browser element ${result.ref}.`;
@@ -1129,24 +838,6 @@ function summarizeBrowserDiagnosticsSuccess(
return `Read ${consoleCount} console log${consoleCount === 1 ? "" : "s"} and ${networkCount} network entr${networkCount === 1 ? "y" : "ies"}.`;
}
function summarizeBrowserStorageSuccess(
result: Extract<BrowserToolsResponsePayload, { ok: true }>["result"],
): string | null {
if (result.command !== "storage") {
return null;
}
return `Read ${result.cookies.length} cookie${result.cookies.length === 1 ? "" : "s"}, ${result.localStorage.length} localStorage entr${result.localStorage.length === 1 ? "y" : "ies"}, and ${result.sessionStorage.length} sessionStorage entr${result.sessionStorage.length === 1 ? "y" : "ies"}.`;
}
function summarizeBrowserEnvironmentSuccess(
result: Extract<BrowserToolsResponsePayload, { ok: true }>["result"],
): string | null {
if (result.command !== "environment") {
return null;
}
return `Browser environment viewport is ${result.viewport.width}x${result.viewport.height}.`;
}
function summarizeBrowserRefActionSuccess(
result: Extract<BrowserToolsResponsePayload, { ok: true }>["result"],
): string | null {
@@ -1164,18 +855,6 @@ function summarizeBrowserRefActionSuccess(
function summarizeBrowserControlSuccess(
result: Extract<BrowserToolsResponsePayload, { ok: true }>["result"],
): string | null {
if (result.command === "focus") {
return `Focused browser element ${result.ref}.`;
}
if (result.command === "clear") {
return `Cleared browser element ${result.ref}.`;
}
if (result.command === "check") {
return `${result.checked ? "Checked" : "Unchecked"} browser element ${result.ref}.`;
}
if (result.command === "select") {
return `Selected ${result.value} in browser element ${result.ref}.`;
}
@@ -1188,10 +867,6 @@ function summarizeBrowserControlSuccess(
return `Dragged browser element ${result.sourceRef} to ${result.targetRef}.`;
}
if (result.command === "set_background") {
return `Set browser page background to ${result.color}.`;
}
return null;
}