fix(browser-tools): restore MCP-compatible outputs
Browser tools were registering union output schemas that the MCP SDK could not validate at call time. Use raw object output shapes and cover the real tools/call boundary.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { realpathSync, rmSync } from "node:fs";
|
import { realpathSync, rmSync } from "node:fs";
|
||||||
import { access, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
import { access, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
||||||
@@ -13,6 +15,7 @@ import { AgentManager, type ManagedAgent } from "./agent-manager.js";
|
|||||||
import { AgentStorage, type StoredAgentRecord } from "./agent-storage.js";
|
import { AgentStorage, type StoredAgentRecord } from "./agent-storage.js";
|
||||||
import { createTestAgentClients } from "../test-utils/fake-agent-client.js";
|
import { createTestAgentClients } from "../test-utils/fake-agent-client.js";
|
||||||
import type { AgentMode, AgentProvider, ProviderSnapshotEntry } from "./agent-sdk-types.js";
|
import type { AgentMode, AgentProvider, ProviderSnapshotEntry } from "./agent-sdk-types.js";
|
||||||
|
import type { ProviderSnapshotManager } from "./provider-snapshot-manager.js";
|
||||||
import { createProviderSnapshotManagerStub } from "../test-utils/session-stubs.js";
|
import { createProviderSnapshotManagerStub } from "../test-utils/session-stubs.js";
|
||||||
import {
|
import {
|
||||||
AgentListItemPayloadSchema,
|
AgentListItemPayloadSchema,
|
||||||
@@ -36,6 +39,8 @@ import { WorkspaceGitServiceImpl } from "../workspace-git-service.js";
|
|||||||
import type { GitHubService } from "../../services/github-service.js";
|
import type { GitHubService } from "../../services/github-service.js";
|
||||||
import type { TerminalManager } from "../../terminal/terminal-manager.js";
|
import type { TerminalManager } from "../../terminal/terminal-manager.js";
|
||||||
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
|
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
|
||||||
|
import type { BrowserToolsBroker, BrowserToolsExecuteInput } from "../browser-tools/broker.js";
|
||||||
|
import type { BrowserToolsResponsePayload } from "../browser-tools/errors.js";
|
||||||
|
|
||||||
const REPO_CWD = resolvePath("/tmp/repo");
|
const REPO_CWD = resolvePath("/tmp/repo");
|
||||||
const TARGET_CWD = resolvePath("/tmp/target");
|
const TARGET_CWD = resolvePath("/tmp/target");
|
||||||
@@ -487,6 +492,77 @@ function createGitHubServiceStub(): GitHubService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FakeBrowserToolsBroker {
|
||||||
|
public readonly calls: BrowserToolsExecuteInput[] = [];
|
||||||
|
|
||||||
|
public constructor(private readonly response: BrowserToolsResponsePayload) {}
|
||||||
|
|
||||||
|
public async execute(input: BrowserToolsExecuteInput): Promise<BrowserToolsResponsePayload> {
|
||||||
|
this.calls.push(input);
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoundaryAgentManagerFake {
|
||||||
|
private readonly agent = createManagedAgent({
|
||||||
|
id: "agent-1",
|
||||||
|
cwd: REPO_CWD,
|
||||||
|
workspaceId: BROWSER_WORKSPACE_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
public getAgent(agentId: string): ManagedAgent | null {
|
||||||
|
return agentId === this.agent.id ? this.agent : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public listAgents(): ManagedAgent[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoundaryAgentStorageFake {
|
||||||
|
public async list(): Promise<StoredAgentRecord[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoundaryProviderSnapshotManagerFake {
|
||||||
|
public listRegisteredProviderIds(): AgentProvider[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasProvider(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProviderLabel(provider: AgentProvider): string {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listProviders(): Promise<ProviderSnapshotEntry[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getProvider(): Promise<ProviderSnapshotEntry> {
|
||||||
|
throw new Error("Provider catalog is not used by this boundary test");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listModels(): Promise<[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listModes(): Promise<[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectInMemoryMcpClient(server: Awaited<ReturnType<typeof createAgentMcpServer>>) {
|
||||||
|
const client = new Client({ name: "paseo-test-client", version: "0.0.0" });
|
||||||
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||||
|
await server.connect(serverTransport);
|
||||||
|
await client.connect(clientTransport);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
function createStoredSchedule(input: CreateScheduleInput): StoredSchedule {
|
function createStoredSchedule(input: CreateScheduleInput): StoredSchedule {
|
||||||
const now = "2026-04-11T00:00:00.000Z";
|
const now = "2026-04-11T00:00:00.000Z";
|
||||||
return {
|
return {
|
||||||
@@ -582,6 +658,74 @@ function createPaseoWorktreeForMcpTest(options: {
|
|||||||
describe("browser MCP tools", () => {
|
describe("browser MCP tools", () => {
|
||||||
const logger = createTestLogger();
|
const logger = createTestLogger();
|
||||||
|
|
||||||
|
it("calls registered tools through the MCP SDK with listed output schemas", async () => {
|
||||||
|
const agentManager = new BoundaryAgentManagerFake();
|
||||||
|
const agentStorage = new BoundaryAgentStorageFake();
|
||||||
|
const broker = new FakeBrowserToolsBroker({
|
||||||
|
requestId: "req-browser-tabs",
|
||||||
|
ok: true,
|
||||||
|
result: { command: "list_tabs", tabs: [] },
|
||||||
|
});
|
||||||
|
const server = await createAgentMcpServer({
|
||||||
|
agentManager: agentManager as AgentManager,
|
||||||
|
agentStorage: agentStorage as AgentStorage,
|
||||||
|
providerSnapshotManager:
|
||||||
|
new BoundaryProviderSnapshotManagerFake() as unknown as ProviderSnapshotManager,
|
||||||
|
browserToolsBroker: broker as BrowserToolsBroker,
|
||||||
|
callerAgentId: "agent-1",
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
const client = await connectInMemoryMcpClient(server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const browserResult = await client.callTool({
|
||||||
|
name: "browser_list_tabs",
|
||||||
|
arguments: {},
|
||||||
|
});
|
||||||
|
const listAgentsResult = await client.callTool({
|
||||||
|
name: "list_agents",
|
||||||
|
arguments: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(broker.calls).toEqual([
|
||||||
|
{
|
||||||
|
agentId: "agent-1",
|
||||||
|
cwd: REPO_CWD,
|
||||||
|
workspaceId: BROWSER_WORKSPACE_ID,
|
||||||
|
command: { command: "list_tabs", args: {} },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(browserResult.isError).not.toBe(true);
|
||||||
|
expect(browserResult.structuredContent).toEqual({
|
||||||
|
ok: true,
|
||||||
|
result: { command: "list_tabs", tabs: [] },
|
||||||
|
context: { agentId: "agent-1", cwd: REPO_CWD, workspaceId: BROWSER_WORKSPACE_ID },
|
||||||
|
});
|
||||||
|
expect(listAgentsResult.isError).not.toBe(true);
|
||||||
|
expect(listAgentsResult.structuredContent).toEqual({
|
||||||
|
agents: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const listedTools = await client.listTools();
|
||||||
|
const toolsByName = new Map(listedTools.tools.map((tool) => [tool.name, tool]));
|
||||||
|
const registeredTools = Reflect.get(server, "_registeredTools") as Record<
|
||||||
|
string,
|
||||||
|
RegisteredMcpTool
|
||||||
|
>;
|
||||||
|
|
||||||
|
for (const [name, tool] of Object.entries(registeredTools)) {
|
||||||
|
if (tool.outputSchema !== undefined) {
|
||||||
|
expect(toolsByName.get(name)?.outputSchema, `${name} outputSchema`).toEqual(
|
||||||
|
expect.objectContaining({ type: "object" }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await client.close();
|
||||||
|
await server.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps browser tools registered when browser tools are disabled", async () => {
|
it("keeps browser tools registered when browser tools are disabled", async () => {
|
||||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||||
spies.agentManager.getAgent.mockReturnValue({
|
spies.agentManager.getAgent.mockReturnValue({
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type {
|
|||||||
ServerNotification,
|
ServerNotification,
|
||||||
ServerRequest,
|
ServerRequest,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createPaseoToolCatalog, type PaseoToolHostDependencies } from "./tools/paseo-tools.js";
|
import { createPaseoToolCatalog, type PaseoToolHostDependencies } from "./tools/paseo-tools.js";
|
||||||
import type { PaseoToolResult } from "./tools/types.js";
|
import type { PaseoToolResult } from "./tools/types.js";
|
||||||
@@ -14,39 +13,6 @@ export type AgentMcpServerOptions = PaseoToolHostDependencies;
|
|||||||
|
|
||||||
type McpToolContext = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
type McpToolContext = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
||||||
|
|
||||||
function isZodSchema(value: unknown): value is z.ZodType {
|
|
||||||
return (
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
typeof (value as { safeParseAsync?: unknown }).safeParseAsync === "function"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function relaxMcpOutputSchema(outputSchema: unknown): unknown {
|
|
||||||
if (!outputSchema) {
|
|
||||||
return outputSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isZodSchema(outputSchema)) {
|
|
||||||
return outputSchema instanceof z.ZodObject ? outputSchema.passthrough() : outputSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
return z.object(outputSchema as z.ZodRawShape).passthrough();
|
|
||||||
}
|
|
||||||
|
|
||||||
function relaxMcpToolOutputSchema<TConfig extends { outputSchema?: unknown }>(
|
|
||||||
config: TConfig,
|
|
||||||
): TConfig {
|
|
||||||
if (config.outputSchema === undefined) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
outputSchema: relaxMcpOutputSchema(config.outputSchema),
|
|
||||||
} as TConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatStructuredContentForModel(structuredContent: unknown): string {
|
function formatStructuredContentForModel(structuredContent: unknown): string {
|
||||||
if (
|
if (
|
||||||
!structuredContent ||
|
!structuredContent ||
|
||||||
@@ -115,12 +81,12 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
for (const tool of catalog.tools.values()) {
|
for (const tool of catalog.tools.values()) {
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
tool.name,
|
tool.name,
|
||||||
relaxMcpToolOutputSchema({
|
{
|
||||||
title: tool.title,
|
title: tool.title,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
inputSchema: tool.inputSchema,
|
inputSchema: tool.inputSchema,
|
||||||
outputSchema: tool.outputSchema,
|
outputSchema: tool.outputSchema,
|
||||||
}),
|
},
|
||||||
async (args: unknown, context?: McpToolContext) =>
|
async (args: unknown, context?: McpToolContext) =>
|
||||||
toMcpToolResult(await catalog.executeTool(tool.name, args, { signal: context?.signal })),
|
toMcpToolResult(await catalog.executeTool(tool.name, args, { signal: context?.signal })),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface PaseoToolConfig {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
inputSchema?: z.ZodRawShape | z.ZodType;
|
inputSchema?: z.ZodRawShape | z.ZodType;
|
||||||
outputSchema?: z.ZodRawShape | z.ZodType;
|
outputSchema?: z.ZodRawShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaseoToolDefinition extends PaseoToolConfig {
|
export interface PaseoToolDefinition extends PaseoToolConfig {
|
||||||
|
|||||||
@@ -68,10 +68,6 @@ class BrowserToolHarness {
|
|||||||
return this.get(name).handler(parsed, {});
|
return this.get(name).handler(parsed, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public outputParses(name: string, output: unknown) {
|
|
||||||
return schemaFor(this.get(name).config.outputSchema).safeParse(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get(name: string): RegisteredTool {
|
private get(name: string): RegisteredTool {
|
||||||
const tool = this.tools.get(name);
|
const tool = this.tools.get(name);
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
@@ -739,53 +735,6 @@ describe("registerBrowserTools", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("new tab declares the shape it returns", async () => {
|
|
||||||
const harness = new BrowserToolHarness();
|
|
||||||
harness.broker.setResponse(newTabPayload());
|
|
||||||
|
|
||||||
const response = await harness.execute("browser_new_tab", {});
|
|
||||||
|
|
||||||
expect(harness.outputParses("browser_new_tab", response.structuredContent)).toEqual({
|
|
||||||
success: true,
|
|
||||||
data: response.structuredContent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("list tabs declares the shape it returns", async () => {
|
|
||||||
const harness = new BrowserToolHarness();
|
|
||||||
|
|
||||||
const response = await harness.execute("browser_list_tabs", {});
|
|
||||||
|
|
||||||
expect(harness.outputParses("browser_list_tabs", response.structuredContent)).toEqual({
|
|
||||||
success: true,
|
|
||||||
data: response.structuredContent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("snapshot declares the shape it returns", async () => {
|
|
||||||
const harness = new BrowserToolHarness();
|
|
||||||
harness.broker.setResponse(snapshotPayload());
|
|
||||||
|
|
||||||
const response = await harness.execute("browser_snapshot", { browserId: BROWSER_ID });
|
|
||||||
|
|
||||||
expect(harness.outputParses("browser_snapshot", response.structuredContent)).toEqual({
|
|
||||||
success: true,
|
|
||||||
data: response.structuredContent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("screenshot declares the shape it returns", async () => {
|
|
||||||
const harness = new BrowserToolHarness();
|
|
||||||
harness.broker.setResponse(screenshotPayload());
|
|
||||||
|
|
||||||
const response = await harness.execute("browser_screenshot", { browserId: BROWSER_ID });
|
|
||||||
|
|
||||||
expect(harness.outputParses("browser_screenshot", response.structuredContent)).toEqual({
|
|
||||||
success: true,
|
|
||||||
data: response.structuredContent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("snapshot rejects calls without a browser id", () => {
|
test("snapshot rejects calls without a browser id", () => {
|
||||||
const harness = new BrowserToolHarness();
|
const harness = new BrowserToolHarness();
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { BrowserAutomationBrowserIdSchema } from "@getpaseo/protocol/browser-automation/rpc-schemas";
|
||||||
BrowserAutomationBackResultSchema,
|
|
||||||
BrowserAutomationBrowserIdSchema,
|
|
||||||
BrowserAutomationCheckResultSchema,
|
|
||||||
BrowserAutomationClearResultSchema,
|
|
||||||
BrowserAutomationClickResultSchema,
|
|
||||||
BrowserAutomationDownloadResultSchema,
|
|
||||||
BrowserAutomationDragResultSchema,
|
|
||||||
BrowserAutomationEnvironmentResultSchema,
|
|
||||||
BrowserAutomationErrorSchema,
|
|
||||||
BrowserAutomationFillResultSchema,
|
|
||||||
BrowserAutomationFocusResultSchema,
|
|
||||||
BrowserAutomationForwardResultSchema,
|
|
||||||
BrowserAutomationFullPageScreenshotResultSchema,
|
|
||||||
BrowserAutomationHoverResultSchema,
|
|
||||||
BrowserAutomationKeypressResultSchema,
|
|
||||||
BrowserAutomationListTabsResultSchema,
|
|
||||||
BrowserAutomationLogsResultSchema,
|
|
||||||
BrowserAutomationNavigateResultSchema,
|
|
||||||
BrowserAutomationNewTabResultSchema,
|
|
||||||
BrowserAutomationPageInfoResultSchema,
|
|
||||||
BrowserAutomationPdfResultSchema,
|
|
||||||
BrowserAutomationReloadResultSchema,
|
|
||||||
BrowserAutomationScreenshotResultSchema,
|
|
||||||
BrowserAutomationSelectResultSchema,
|
|
||||||
BrowserAutomationSetBackgroundResultSchema,
|
|
||||||
BrowserAutomationSnapshotResultSchema,
|
|
||||||
BrowserAutomationStorageResultSchema,
|
|
||||||
BrowserAutomationTypeResultSchema,
|
|
||||||
BrowserAutomationUploadResultSchema,
|
|
||||||
BrowserAutomationWaitResultSchema,
|
|
||||||
} from "@getpaseo/protocol/browser-automation/rpc-schemas";
|
|
||||||
import type { BrowserToolsBroker } from "./broker.js";
|
import type { BrowserToolsBroker } from "./broker.js";
|
||||||
import type { BrowserToolsResponsePayload } from "./errors.js";
|
import type { BrowserToolsResponsePayload } from "./errors.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -66,29 +35,25 @@ const WORKSPACE_CONTEXT_MESSAGE =
|
|||||||
const URL_WHITESPACE_PATTERN = /\s/;
|
const URL_WHITESPACE_PATTERN = /\s/;
|
||||||
const NON_HTTP_EXPLICIT_SCHEME_PATTERN = /^(?!https?:\/\/)[a-z][a-z0-9+.-]*:\/\//i;
|
const NON_HTTP_EXPLICIT_SCHEME_PATTERN = /^(?!https?:\/\/)[a-z][a-z0-9+.-]*:\/\//i;
|
||||||
|
|
||||||
const BrowserToolContextSchema = z.object({
|
const BrowserToolOutputSchema = {
|
||||||
agentId: z.string().optional(),
|
ok: z.boolean(),
|
||||||
cwd: z.string().optional(),
|
result: z.unknown().optional(),
|
||||||
workspaceId: z.string().optional(),
|
error: z
|
||||||
browserId: z.string().optional(),
|
.object({
|
||||||
});
|
code: z.string(),
|
||||||
|
message: z.string(),
|
||||||
const BrowserToolErrorOutputSchema = z.object({
|
retryable: z.boolean(),
|
||||||
ok: z.literal(false),
|
})
|
||||||
error: BrowserAutomationErrorSchema,
|
.optional(),
|
||||||
context: BrowserToolContextSchema,
|
context: z
|
||||||
});
|
.object({
|
||||||
|
agentId: z.string().optional(),
|
||||||
function browserToolOutputSchema<ResultSchema extends z.ZodType>(resultSchema: ResultSchema) {
|
cwd: z.string().optional(),
|
||||||
return z.discriminatedUnion("ok", [
|
workspaceId: z.string().optional(),
|
||||||
z.object({
|
browserId: z.string().optional(),
|
||||||
ok: z.literal(true),
|
})
|
||||||
result: resultSchema,
|
.optional(),
|
||||||
context: BrowserToolContextSchema,
|
};
|
||||||
}),
|
|
||||||
BrowserToolErrorOutputSchema,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const BrowserHttpUrlInputSchema = z
|
const BrowserHttpUrlInputSchema = z
|
||||||
.string()
|
.string()
|
||||||
@@ -116,37 +81,6 @@ const BrowserWaitInputSchema = z
|
|||||||
message: "browser_wait requires exactly one of text or url",
|
message: "browser_wait requires exactly one of text or url",
|
||||||
});
|
});
|
||||||
|
|
||||||
const BrowserToolOutputs = {
|
|
||||||
listTabs: browserToolOutputSchema(BrowserAutomationListTabsResultSchema),
|
|
||||||
newTab: browserToolOutputSchema(BrowserAutomationNewTabResultSchema),
|
|
||||||
pageInfo: browserToolOutputSchema(BrowserAutomationPageInfoResultSchema),
|
|
||||||
snapshot: browserToolOutputSchema(BrowserAutomationSnapshotResultSchema),
|
|
||||||
click: browserToolOutputSchema(BrowserAutomationClickResultSchema),
|
|
||||||
fill: browserToolOutputSchema(BrowserAutomationFillResultSchema),
|
|
||||||
wait: browserToolOutputSchema(BrowserAutomationWaitResultSchema),
|
|
||||||
type: browserToolOutputSchema(BrowserAutomationTypeResultSchema),
|
|
||||||
keypress: browserToolOutputSchema(BrowserAutomationKeypressResultSchema),
|
|
||||||
navigate: browserToolOutputSchema(BrowserAutomationNavigateResultSchema),
|
|
||||||
back: browserToolOutputSchema(BrowserAutomationBackResultSchema),
|
|
||||||
forward: browserToolOutputSchema(BrowserAutomationForwardResultSchema),
|
|
||||||
reload: browserToolOutputSchema(BrowserAutomationReloadResultSchema),
|
|
||||||
screenshot: browserToolOutputSchema(BrowserAutomationScreenshotResultSchema),
|
|
||||||
fullPageScreenshot: browserToolOutputSchema(BrowserAutomationFullPageScreenshotResultSchema),
|
|
||||||
pdf: browserToolOutputSchema(BrowserAutomationPdfResultSchema),
|
|
||||||
download: browserToolOutputSchema(BrowserAutomationDownloadResultSchema),
|
|
||||||
upload: browserToolOutputSchema(BrowserAutomationUploadResultSchema),
|
|
||||||
focus: browserToolOutputSchema(BrowserAutomationFocusResultSchema),
|
|
||||||
clear: browserToolOutputSchema(BrowserAutomationClearResultSchema),
|
|
||||||
check: browserToolOutputSchema(BrowserAutomationCheckResultSchema),
|
|
||||||
select: browserToolOutputSchema(BrowserAutomationSelectResultSchema),
|
|
||||||
hover: browserToolOutputSchema(BrowserAutomationHoverResultSchema),
|
|
||||||
drag: browserToolOutputSchema(BrowserAutomationDragResultSchema),
|
|
||||||
logs: browserToolOutputSchema(BrowserAutomationLogsResultSchema),
|
|
||||||
storage: browserToolOutputSchema(BrowserAutomationStorageResultSchema),
|
|
||||||
environment: browserToolOutputSchema(BrowserAutomationEnvironmentResultSchema),
|
|
||||||
setBackground: browserToolOutputSchema(BrowserAutomationSetBackgroundResultSchema),
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function registerBrowserTools(options: RegisterBrowserToolsOptions): void {
|
export function registerBrowserTools(options: RegisterBrowserToolsOptions): void {
|
||||||
options.registerTool(
|
options.registerTool(
|
||||||
"browser_list_tabs",
|
"browser_list_tabs",
|
||||||
@@ -155,7 +89,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"List open Paseo desktop browser tabs for this agent's workspace. Use returned browserId values with tab-scoped tools.",
|
"List open Paseo desktop browser tabs for this agent's workspace. Use returned browserId values with tab-scoped tools.",
|
||||||
inputSchema: {},
|
inputSchema: {},
|
||||||
outputSchema: BrowserToolOutputs.listTabs,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -185,7 +119,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
url: BrowserHttpUrlInputSchema.optional(),
|
url: BrowserHttpUrlInputSchema.optional(),
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.newTab,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ url }) => {
|
async ({ url }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -215,7 +149,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.pageInfo,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -244,7 +178,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.snapshot,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -274,7 +208,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.click,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, browserId }) => {
|
async ({ ref, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -306,7 +240,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
value: z.string(),
|
value: z.string(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.fill,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, value, browserId }) => {
|
async ({ ref, value, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -335,7 +269,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"Wait until a Paseo desktop browser tab contains text or reaches a URL fragment. Use browserId from browser_new_tab or browser_list_tabs; waits up to 5s by default on the desktop side.",
|
"Wait until a Paseo desktop browser tab contains text or reaches a URL fragment. Use browserId from browser_new_tab or browser_list_tabs; waits up to 5s by default on the desktop side.",
|
||||||
inputSchema: BrowserWaitInputSchema,
|
inputSchema: BrowserWaitInputSchema,
|
||||||
outputSchema: BrowserToolOutputs.wait,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ text, url, timeoutMs, browserId }) => {
|
async ({ text, url, timeoutMs, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -370,7 +304,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
ref: BrowserRefInputSchema.optional(),
|
ref: BrowserRefInputSchema.optional(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.type,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ text, ref, browserId }) => {
|
async ({ text, ref, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -403,7 +337,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
ref: BrowserRefInputSchema.optional(),
|
ref: BrowserRefInputSchema.optional(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.keypress,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ key, ref, browserId }) => {
|
async ({ key, ref, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -432,7 +366,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"Navigate a Paseo desktop browser tab to a URL. 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.",
|
"Navigate a Paseo desktop browser tab to a URL. 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, browserId: BrowserAutomationBrowserIdSchema },
|
inputSchema: { url: BrowserHttpUrlInputSchema, browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs.navigate,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ url, browserId }) => {
|
async ({ url, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -482,7 +416,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
title: toolConfig.title,
|
title: toolConfig.title,
|
||||||
description: toolConfig.description,
|
description: toolConfig.description,
|
||||||
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs[toolConfig.command],
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -510,7 +444,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"Capture a PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
|
"Capture a PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
|
||||||
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs.screenshot,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -537,7 +471,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"Capture a full-page PNG screenshot of a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
|
"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 },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs.fullPageScreenshot,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -568,7 +502,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
landscape: z.boolean().optional(),
|
landscape: z.boolean().optional(),
|
||||||
printBackground: z.boolean().default(true),
|
printBackground: z.boolean().default(true),
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.pdf,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId, landscape, printBackground }) => {
|
async ({ browserId, landscape, printBackground }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -601,7 +535,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
fileName: z.string().min(1).optional(),
|
fileName: z.string().min(1).optional(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.download,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ url, fileName, browserId }) => {
|
async ({ url, fileName, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -634,7 +568,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
filePaths: z.array(z.string().min(1)).min(1),
|
filePaths: z.array(z.string().min(1)).min(1),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.upload,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, filePaths, browserId }) => {
|
async ({ ref, filePaths, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -685,7 +619,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
title: toolConfig.title,
|
title: toolConfig.title,
|
||||||
description: toolConfig.description,
|
description: toolConfig.description,
|
||||||
inputSchema: { ref: BrowserRefInputSchema, browserId: BrowserAutomationBrowserIdSchema },
|
inputSchema: { ref: BrowserRefInputSchema, browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs[toolConfig.command],
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, browserId }) => {
|
async ({ ref, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -718,7 +652,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
checked: z.boolean().default(true),
|
checked: z.boolean().default(true),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.check,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, checked, browserId }) => {
|
async ({ ref, checked, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -751,7 +685,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
value: z.string(),
|
value: z.string(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.select,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, value, browserId }) => {
|
async ({ ref, value, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -784,7 +718,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
targetRef: BrowserRefInputSchema,
|
targetRef: BrowserRefInputSchema,
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.drag,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ sourceRef, targetRef, browserId }) => {
|
async ({ sourceRef, targetRef, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -816,7 +750,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
maxEntries: z.number().int().positive().max(200).optional(),
|
maxEntries: z.number().int().positive().max(200).optional(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.logs,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ maxEntries, browserId }) => {
|
async ({ maxEntries, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -844,7 +778,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
description:
|
description:
|
||||||
"Read cookies plus localStorage and sessionStorage for a Paseo desktop browser tab. Use browserId from browser_new_tab or browser_list_tabs.",
|
"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 },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputs.storage,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -887,7 +821,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
.optional(),
|
.optional(),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.environment,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ viewport, geolocation, browserId }) => {
|
async ({ viewport, geolocation, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
@@ -919,7 +853,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
color: z.string().min(1),
|
color: z.string().min(1),
|
||||||
browserId: BrowserAutomationBrowserIdSchema,
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputs.setBackground,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ color, browserId }) => {
|
async ({ color, browserId }) => {
|
||||||
const context = resolveBrowserToolContext(options);
|
const context = resolveBrowserToolContext(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user