Path: blob/main/test/mcp/src/automationTools/windows.ts
5260 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';6import { z } from 'zod';7import { ApplicationService } from '../application';89/**10* Create a standardized text response for window tools11*/12function textResponse(text: string) {13return {14content: [{ type: 'text' as const, text }]15};16}1718/**19* Window Management Tools for multi-window support.20* These tools provide Playwright-based window interactions through the automation driver.21*/22export function applyWindowTools(server: McpServer, appService: ApplicationService): RegisteredTool[] {23const tools: RegisteredTool[] = [];2425tools.push(server.tool(26'vscode_automation_list_windows',27'List all open VS Code windows with their index and URL',28async () => {29const app = await appService.getOrCreateApplication();30const windowInfo = app.code.driver.getWindowsInfo();31return textResponse(`Open windows (${windowInfo.length}):\n${JSON.stringify(windowInfo, null, 2)}`);32}33));3435tools.push(server.tool(36'vscode_automation_switch_window',37'Switch to a different VS Code window by index or URL pattern (e.g., "agent.html")',38{39indexOrUrl: z.union([z.number(), z.string()]).describe('Window index (0-based) or URL pattern to match (e.g., "agent.html", "workbench")')40},41async ({ indexOrUrl }) => {42const app = await appService.getOrCreateApplication();43const switched = app.code.driver.switchToWindow(indexOrUrl);4445if (switched) {46return textResponse(`Switched to window (URL: ${switched.url()})`);47}4849const windowInfo = app.code.driver.getWindowsInfo();50const availableWindows = windowInfo.map(w => ` ${w.index}: ${w.url}`).join('\n');51return textResponse(`Failed to switch window. Window not found for: ${indexOrUrl}\n\nAvailable windows:\n${availableWindows}`);52}53));5455tools.push(server.tool(56'vscode_automation_get_current_window',57'Get information about the currently active window',58async () => {59const app = await appService.getOrCreateApplication();60const driver = app.code.driver;61const windowInfo = driver.getWindowsInfo();62const current = windowInfo.find(w => w.isCurrent);63return textResponse(`Current window:\nIndex: ${current?.index ?? -1}\nURL: ${current?.url ?? 'Unknown'}`);64}65));6667tools.push(server.tool(68'vscode_automation_window_screenshot',69'Take a screenshot of the current window (respects the window set by vscode_automation_switch_window)',70{71fullPage: z.boolean().optional().describe('When true, takes a screenshot of the full scrollable page')72},73async ({ fullPage }) => {74const app = await appService.getOrCreateApplication();75const driver = app.code.driver;76const screenshotBuffer = await driver.screenshotBuffer(fullPage ?? false);77const url = driver.currentPage.url();7879return {80content: [81{ type: 'text' as const, text: `Screenshot (URL: ${url})` },82{ type: 'image' as const, data: screenshotBuffer.toString('base64'), mimeType: 'image/png' }83]84};85}86));8788tools.push(server.tool(89'vscode_automation_window_snapshot',90'Capture accessibility snapshot of the current window. Returns the page structure with element references that can be used for interactions.',91async () => {92const app = await appService.getOrCreateApplication();93const driver = app.code.driver;94const snapshot = await driver.getAccessibilitySnapshot();95const url = driver.currentPage.url();9697return textResponse(`Page snapshot (URL: ${url}):\n\n${JSON.stringify(snapshot, null, 2)}`);98}99));100101tools.push(server.tool(102'vscode_automation_window_click',103'Click on an element in the current window using a CSS selector',104{105selector: z.string().describe('CSS selector for the element to click'),106button: z.enum(['left', 'right', 'middle']).optional().describe('Mouse button to click'),107clickCount: z.number().optional().describe('Number of clicks (1 for single, 2 for double)')108},109async ({ selector, button, clickCount }) => {110const app = await appService.getOrCreateApplication();111const driver = app.code.driver;112await driver.clickSelector(selector, { button, clickCount });113return textResponse(`Clicked "${selector}"`);114}115));116117tools.push(server.tool(118'vscode_automation_window_type',119'Type text into an element in the current window',120{121selector: z.string().describe('CSS selector for the element to type into'),122text: z.string().describe('Text to type'),123slowly: z.boolean().optional().describe('Whether to type one character at a time (useful for triggering key handlers)')124},125async ({ selector, text, slowly }) => {126const app = await appService.getOrCreateApplication();127const driver = app.code.driver;128await driver.typeText(selector, text, slowly ?? false);129return textResponse(`Typed "${text}" into "${selector}"`);130}131));132133tools.push(server.tool(134'vscode_automation_window_evaluate',135'Evaluate JavaScript in the current window',136{137expression: z.string().describe('JavaScript expression to evaluate')138},139async ({ expression }) => {140const app = await appService.getOrCreateApplication();141const driver = app.code.driver;142const result = await driver.evaluateExpression(expression);143return textResponse(`Result:\n${JSON.stringify(result, null, 2)}`);144}145));146147tools.push(server.tool(148'vscode_automation_window_locator',149'Get information about elements matching a selector in the current window',150{151selector: z.string().describe('CSS selector to find elements'),152action: z.enum(['count', 'textContent', 'innerHTML', 'boundingBox', 'isVisible']).optional().describe('Action to perform on matched elements')153},154async ({ selector, action }) => {155const app = await appService.getOrCreateApplication();156const driver = app.code.driver;157const result = await driver.getLocatorInfo(selector, action);158return textResponse(`Locator "${selector}":\n${JSON.stringify(result, null, 2)}`);159}160));161162tools.push(server.tool(163'vscode_automation_window_wait_for_selector',164'Wait for an element to appear in the current window',165{166selector: z.string().describe('CSS selector to wait for'),167state: z.enum(['attached', 'detached', 'visible', 'hidden']).optional().describe('State to wait for'),168timeout: z.number().optional().describe('Timeout in milliseconds')169},170async ({ selector, state, timeout }) => {171const app = await appService.getOrCreateApplication();172const driver = app.code.driver;173await driver.waitForElement(selector, { state, timeout });174return textResponse(`Element "${selector}" is now ${state ?? 'visible'}`);175}176));177178tools.push(server.tool(179'vscode_automation_window_hover',180'Hover over an element in the current window',181{182selector: z.string().describe('CSS selector for the element to hover over')183},184async ({ selector }) => {185const app = await appService.getOrCreateApplication();186const driver = app.code.driver;187await driver.hoverSelector(selector);188return textResponse(`Hovered over "${selector}"`);189}190));191192tools.push(server.tool(193'vscode_automation_window_drag',194'Drag from one element to another in the current window',195{196sourceSelector: z.string().describe('CSS selector for the source element'),197targetSelector: z.string().describe('CSS selector for the target element')198},199async ({ sourceSelector, targetSelector }) => {200const app = await appService.getOrCreateApplication();201const driver = app.code.driver;202await driver.dragSelector(sourceSelector, targetSelector);203return textResponse(`Dragged from "${sourceSelector}" to "${targetSelector}"`);204}205));206207tools.push(server.tool(208'vscode_automation_window_press_key',209'Press a key or key combination in the current window',210{211key: z.string().describe('Key to press (e.g., "Enter", "Tab", "Control+c", "Meta+v")')212},213async ({ key }) => {214const app = await appService.getOrCreateApplication();215const driver = app.code.driver;216await driver.pressKey(key);217return textResponse(`Pressed key "${key}"`);218}219));220221tools.push(server.tool(222'vscode_automation_window_mouse_move',223'Move mouse to a specific position in the current window',224{225x: z.number().describe('X coordinate'),226y: z.number().describe('Y coordinate')227},228async ({ x, y }) => {229const app = await appService.getOrCreateApplication();230const driver = app.code.driver;231await driver.mouseMove(x, y);232return textResponse(`Moved mouse to (${x}, ${y})`);233}234));235236tools.push(server.tool(237'vscode_automation_window_mouse_click',238'Click at a specific position in the current window',239{240x: z.number().describe('X coordinate'),241y: z.number().describe('Y coordinate'),242button: z.enum(['left', 'right', 'middle']).optional().describe('Mouse button to click'),243clickCount: z.number().optional().describe('Number of clicks (1 for single, 2 for double)')244},245async ({ x, y, button, clickCount }) => {246const app = await appService.getOrCreateApplication();247const driver = app.code.driver;248await driver.mouseClick(x, y, { button, clickCount });249return textResponse(`Clicked at (${x}, ${y})`);250}251));252253tools.push(server.tool(254'vscode_automation_window_mouse_drag',255'Drag from one position to another in the current window',256{257startX: z.number().describe('Starting X coordinate'),258startY: z.number().describe('Starting Y coordinate'),259endX: z.number().describe('Ending X coordinate'),260endY: z.number().describe('Ending Y coordinate')261},262async ({ startX, startY, endX, endY }) => {263const app = await appService.getOrCreateApplication();264const driver = app.code.driver;265await driver.mouseDrag(startX, startY, endX, endY);266return textResponse(`Dragged from (${startX}, ${startY}) to (${endX}, ${endY})`);267}268));269270tools.push(server.tool(271'vscode_automation_window_select_option',272'Select an option in a dropdown in the current window',273{274selector: z.string().describe('CSS selector for the select element'),275value: z.union([z.string(), z.array(z.string())]).describe('Value(s) to select')276},277async ({ selector, value }) => {278const app = await appService.getOrCreateApplication();279const driver = app.code.driver;280const selected = await driver.selectOption(selector, value);281return textResponse(`Selected "${selected.join(', ')}" in "${selector}"`);282}283));284285tools.push(server.tool(286'vscode_automation_window_fill_form',287'Fill multiple form fields at once in the current window',288{289fields: z.array(z.object({290selector: z.string().describe('CSS selector for the form field'),291value: z.string().describe('Value to fill')292})).describe('Array of fields to fill')293},294async ({ fields }) => {295const app = await appService.getOrCreateApplication();296const driver = app.code.driver;297await driver.fillForm(fields);298return textResponse(`Filled ${fields.length} form field(s)`);299}300));301302tools.push(server.tool(303'vscode_automation_window_console_messages',304'Get console messages from the current window',305async () => {306const app = await appService.getOrCreateApplication();307const driver = app.code.driver;308const messages = await driver.getConsoleMessages();309return textResponse(`Console messages (${messages.length}):\n${JSON.stringify(messages, null, 2)}`);310}311));312313tools.push(server.tool(314'vscode_automation_window_wait_for_text',315'Wait for text to appear or disappear in the current window',316{317text: z.string().optional().describe('Text to wait for to appear'),318textGone: z.string().optional().describe('Text to wait for to disappear'),319timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)')320},321async ({ text, textGone, timeout }) => {322const app = await appService.getOrCreateApplication();323const driver = app.code.driver;324await driver.waitForText({ text, textGone, timeout });325return textResponse(`Waited for ${text ? `"${text}" to appear` : ''}${textGone ? `"${textGone}" to disappear` : ''}`);326}327));328329tools.push(server.tool(330'vscode_automation_window_wait_for_time',331'Wait for a specified time in the current window',332{333seconds: z.number().describe('Time to wait in seconds')334},335async ({ seconds }) => {336const app = await appService.getOrCreateApplication();337const driver = app.code.driver;338await driver.waitForTime(seconds * 1000);339return textResponse(`Waited for ${seconds} second(s)`);340}341));342343tools.push(server.tool(344'vscode_automation_window_verify_element_visible',345'Verify an element is visible in the current window',346{347selector: z.string().describe('CSS selector for the element to verify')348},349async ({ selector }) => {350const app = await appService.getOrCreateApplication();351const driver = app.code.driver;352const isVisible = await driver.verifyElementVisible(selector);353return textResponse(isVisible ? `✓ Element "${selector}" is visible` : `✗ Element "${selector}" is NOT visible`);354}355));356357tools.push(server.tool(358'vscode_automation_window_verify_text_visible',359'Verify text is visible in the current window',360{361text: z.string().describe('Text to verify is visible')362},363async ({ text }) => {364const app = await appService.getOrCreateApplication();365const driver = app.code.driver;366const isVisible = await driver.verifyTextVisible(text);367return textResponse(isVisible ? `✓ Text "${text}" is visible` : `✗ Text "${text}" is NOT visible`);368}369));370371tools.push(server.tool(372'vscode_automation_window_get_input_value',373'Get the value of an input element in the current window',374{375selector: z.string().describe('CSS selector for the input element')376},377async ({ selector }) => {378const app = await appService.getOrCreateApplication();379const driver = app.code.driver;380const value = await driver.getInputValue(selector);381return textResponse(`Input "${selector}" value: "${value}"`);382}383));384385return tools;386}387388389