|
| 1 | +import { z } from "zod"; |
| 2 | +import { ToolDefinition, Context } from "../types.js"; |
| 3 | +import { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 4 | +import { contextProvider } from "../context-provider.js"; |
| 5 | +import { createSimpleResponse, createErrorResponse } from "../responses/index.js"; |
| 6 | +import { BrowserContext, type BrowserOptions } from "../browser-context.js"; |
| 7 | + |
| 8 | +const desiredCapabilitiesSchema = z |
| 9 | + .object({}) |
| 10 | + .catchall(z.unknown()) |
| 11 | + .superRefine((value, ctx) => { |
| 12 | + const browserName = value?.["browserName"]; |
| 13 | + |
| 14 | + if (browserName !== undefined && typeof browserName !== "string") { |
| 15 | + ctx.addIssue({ code: z.ZodIssueCode.custom, message: '"browserName" must be a string' }); |
| 16 | + } |
| 17 | + }) |
| 18 | + .describe( |
| 19 | + 'WebDriver desiredCapabilities that should be used when launching the browser. Example to launch Chrome with mobile emulation: {"browserName":"chrome","goog:chromeOptions":{"mobileEmulation":{"deviceMetrics":{"width":360,"height":800,"pixelRatio":1.0}}}}', |
| 20 | + ); |
| 21 | + |
| 22 | +const windowSizeSchema = z |
| 23 | + .union([ |
| 24 | + z |
| 25 | + .object({ |
| 26 | + width: z.number().int().positive(), |
| 27 | + height: z.number().int().positive(), |
| 28 | + }) |
| 29 | + .strict(), |
| 30 | + z |
| 31 | + .string() |
| 32 | + .trim() |
| 33 | + .regex(/^[0-9]+x[0-9]+$/, { |
| 34 | + message: '"windowSize" should use the format "<width>x<height>" (e.g. "1600x900")', |
| 35 | + }), |
| 36 | + z.null(), |
| 37 | + ]) |
| 38 | + .optional() |
| 39 | + .describe( |
| 40 | + 'Viewport to use for the session. Provide {"width": number, "height": number} or a string like "1280x720"; use null to reset to the default size.', |
| 41 | + ); |
| 42 | + |
| 43 | +export const launchBrowserSchema = { |
| 44 | + desiredCapabilities: desiredCapabilitiesSchema.optional(), |
| 45 | + gridUrl: z |
| 46 | + .string() |
| 47 | + .default("local") |
| 48 | + .describe( |
| 49 | + 'WebDriver endpoint to connect to. "local" (default) lets Testplane MCP manage Chrome and Firefox automatically; set a Selenium grid URL only when you need other browsers.', |
| 50 | + ), |
| 51 | + windowSize: windowSizeSchema, |
| 52 | +}; |
| 53 | + |
| 54 | +const launchBrowserCb: ToolCallback<typeof launchBrowserSchema> = async args => { |
| 55 | + try { |
| 56 | + const context = contextProvider.getContext(); |
| 57 | + const desiredCapabilities = args.desiredCapabilities as BrowserOptions["desiredCapabilities"]; |
| 58 | + const gridUrl = args.gridUrl ?? "local"; |
| 59 | + const windowSizeInput = args.windowSize; |
| 60 | + |
| 61 | + if (await context.browser.isActive()) { |
| 62 | + console.error("Closing existing browser before launching a new one"); |
| 63 | + await context.browser.close(); |
| 64 | + } |
| 65 | + |
| 66 | + const updatedOptions: BrowserOptions = { |
| 67 | + ...context.browser.getOptions(), |
| 68 | + }; |
| 69 | + |
| 70 | + if (Object.prototype.hasOwnProperty.call(args, "desiredCapabilities")) { |
| 71 | + updatedOptions.desiredCapabilities = desiredCapabilities; |
| 72 | + } |
| 73 | + |
| 74 | + if (!gridUrl || gridUrl === "local") { |
| 75 | + delete updatedOptions.gridUrl; |
| 76 | + } else { |
| 77 | + updatedOptions.gridUrl = gridUrl; |
| 78 | + } |
| 79 | + |
| 80 | + if (Object.prototype.hasOwnProperty.call(args, "windowSize")) { |
| 81 | + if (windowSizeInput === null) { |
| 82 | + updatedOptions.windowSize = null; |
| 83 | + } else if (typeof windowSizeInput === "string") { |
| 84 | + const [width, height] = windowSizeInput.split("x").map(value => Number.parseInt(value, 10)); |
| 85 | + updatedOptions.windowSize = { width, height }; |
| 86 | + } else if (windowSizeInput === undefined) { |
| 87 | + delete updatedOptions.windowSize; |
| 88 | + } else { |
| 89 | + updatedOptions.windowSize = windowSizeInput as BrowserOptions["windowSize"]; |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + const browserContext = new BrowserContext(updatedOptions); |
| 94 | + const newContext: Context = { |
| 95 | + browser: browserContext, |
| 96 | + }; |
| 97 | + contextProvider.setContext(newContext); |
| 98 | + |
| 99 | + await browserContext.get(); |
| 100 | + |
| 101 | + return createSimpleResponse("Successfully launched browser session"); |
| 102 | + } catch (error) { |
| 103 | + console.error("Error launching browser:", error); |
| 104 | + return createErrorResponse("Error launching browser", error instanceof Error ? error : undefined); |
| 105 | + } |
| 106 | +}; |
| 107 | + |
| 108 | +export const launchBrowser: ToolDefinition<typeof launchBrowserSchema> = { |
| 109 | + name: "launchBrowser", |
| 110 | + description: |
| 111 | + "Launch a new browser session with custom desired capabilities. Avoid using this tool unless the user explicitly requests a custom browser configuration; browsers are launched automatically for commands like navigate to URL. Testplane MCP can ONLY download Chrome and Firefox automatically, for other browsers you MUST ensure that driver is launched and provide it as custom gridUrl.", |
| 112 | + schema: launchBrowserSchema, |
| 113 | + cb: launchBrowserCb, |
| 114 | +}; |
0 commit comments