#!/usr/bin/env node
/**
* VSCode Automation MCP Server
*
* A Model Context Protocol (MCP) server that enables AI agents to automate
* and test VSCode extensions using vscode-extension-tester.
*
* @author Sukarth Acharya
* @license MIT
* @repository https://github.com/sukarth/vscode-automation-mcp
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// Import tool implementations
import {
executeCommand,
listCommands,
executeCommandInputSchema,
listCommandsInputSchema,
} from './tools/commands.js';
import {
clickElement,
typeText,
openFile,
clickElementInputSchema,
typeTextInputSchema,
openFileInputSchema,
} from './tools/ui-actions.js';
import {
takeScreenshot,
getElement,
openWebview,
getDiagnostics,
getDom,
getUiStructure,
queryElements,
getAccessibilityTree,
getElementChildren,
getElementParents,
getElementSiblings,
findInteractiveElements,
dumpDomToFile,
searchDom,
takeScreenshotInputSchema,
getElementInputSchema,
openWebviewInputSchema,
getDiagnosticsInputSchema,
getDomInputSchema,
getUiStructureInputSchema,
queryElementsInputSchema,
getAccessibilityTreeInputSchema,
getElementChildrenInputSchema,
getElementParentsInputSchema,
getElementSiblingsInputSchema,
findInteractiveElementsInputSchema,
dumpDomToFileInputSchema,
searchDomInputSchema,
executeScript,
querySelector,
getElementById,
getElementsByClass,
getElementsByTag,
executeScriptInputSchema,
querySelectorInputSchema,
getElementByIdInputSchema,
getElementsByClassInputSchema,
getElementsByTagInputSchema,
} from './tools/inspection.js';
import {
getEditorContent,
verifyElement,
assertText,
checkFileOpen,
getEditorContentInputSchema,
verifyElementInputSchema,
assertTextInputSchema,
checkFileOpenInputSchema,
} from './tools/testing.js';
import {
pressKeys,
focusElement,
scroll,
dragDrop,
hover,
pressKeysInputSchema,
focusElementInputSchema,
scrollInputSchema,
dragDropInputSchema,
hoverInputSchema,
} from './tools/keyboard.js';
import {
waitForElement,
waitForText,
waitForIdle,
waitForElementInputSchema,
waitForTextInputSchema,
waitForIdleInputSchema,
} from './tools/wait.js';
import {
getNotifications,
dismissNotification,
handleDialog,
getQuickPickItems,
selectQuickPickItem,
getNotificationsInputSchema,
dismissNotificationInputSchema,
handleDialogInputSchema,
getQuickPickItemsInputSchema,
selectQuickPickItemInputSchema,
} from './tools/notifications.js';
import {
triggerHover,
openContextMenu,
getMenuItems,
clickMenuItem,
getTooltip,
triggerCompletion,
getCompletionItems,
selectCompletionItem,
getProblemsPanel,
goToDefinition,
triggerSignatureHelp,
triggerHoverInputSchema,
openContextMenuInputSchema,
getMenuItemsInputSchema,
clickMenuItemInputSchema,
getTooltipInputSchema,
triggerCompletionInputSchema,
getCompletionItemsInputSchema,
selectCompletionItemInputSchema,
getProblemsPanelInputSchema,
goToDefinitionInputSchema,
triggerSignatureHelpInputSchema,
} from './tools/extension-testing.js';
import {
getConsoleLogs,
clearConsole,
getOutputChannels,
getOutputChannelContent,
getPerformanceMetrics,
getExtensionLogs,
getDevToolsInfo,
getConsoleLogsInputSchema,
clearConsoleInputSchema,
getOutputChannelsInputSchema,
getOutputChannelContentInputSchema,
getPerformanceMetricsInputSchema,
getExtensionLogsInputSchema,
getDevToolsInfoInputSchema,
} from './tools/debug.js';
import { getVSCodeDriver, VSCodeDriver } from './vscode-driver.js';
// Server information
const SERVER_NAME = 'vscode-automation-mcp';
const SERVER_VERSION = '1.0.0';
/**
* Create and configure the MCP server
*/
function createServer(): McpServer {
const server = new McpServer({
name: SERVER_NAME,
version: SERVER_VERSION,
});
// ===========================
// Command Tools
// ===========================
server.tool(
'vscode_execute_command',
'Execute a VSCode command programmatically. Use this to trigger any command available in the Command Palette.',
executeCommandInputSchema,
async (input) => {
return executeCommand(input);
}
);
server.tool(
'vscode_list_commands',
'List available VSCode commands. Optionally filter by a search string to find specific commands.',
listCommandsInputSchema,
async (input) => {
return listCommands(input);
}
);
// ===========================
// UI Action Tools
// ===========================
server.tool(
'vscode_click_element',
'Click a UI element in VSCode by CSS selector, XPath, accessibility label, or text content. Supports single click, double click, and right click.',
clickElementInputSchema,
async (input) => {
return clickElement(input);
}
);
server.tool(
'vscode_type_text',
'Type text into VSCode. Can type into the currently focused element or target a specific input by selector.',
typeTextInputSchema,
async (input) => {
return typeText(input);
}
);
server.tool(
'vscode_open_file',
'Open a file in the VSCode editor. Optionally navigate to a specific line and column position.',
openFileInputSchema,
async (input) => {
return openFile(input);
}
);
// ===========================
// Inspection Tools
// ===========================
server.tool(
'vscode_take_screenshot',
'Take a screenshot of the VSCode window. The screenshot is saved to a file and optionally returned as base64 data.',
takeScreenshotInputSchema,
async (input) => {
return takeScreenshot(input);
}
);
server.tool(
'vscode_get_element',
'Get detailed information about a UI element including its text, visibility, enabled state, position, size, and attributes.',
getElementInputSchema,
async (input) => {
return getElement(input);
}
);
server.tool(
'vscode_open_webview',
'Open a webview panel by its title or command. Webviews are used by extensions for custom UI.',
openWebviewInputSchema,
async (input) => {
return openWebview(input);
}
);
server.tool(
'vscode_get_diagnostics',
'Get diagnostic messages (errors, warnings, info) from the Problems panel. Useful for checking compilation errors and linting issues.',
getDiagnosticsInputSchema,
async (input) => {
return getDiagnostics(input);
}
);
// ===========================
// DOM Inspection Tools
// ===========================
server.tool(
'vscode_get_dom',
'Get the full DOM structure of the VSCode window or a specific element. Returns the DOM tree in a format suitable for understanding the UI structure. Use this to discover element selectors for automation.',
getDomInputSchema,
async (input) => {
return getDom(input);
}
);
server.tool(
'vscode_get_ui_structure',
'Get the structure of a specific VSCode UI region (sidebar, editor, panel, etc.). Provides a focused, clean view of the requested UI area with semantic information.',
getUiStructureInputSchema,
async (input) => {
return getUiStructure(input);
}
);
server.tool(
'vscode_query_elements',
'Find all elements matching a CSS selector and get information about each. Useful for discovering interactive elements, understanding repeated patterns, and building automation selectors.',
queryElementsInputSchema,
async (input) => {
return queryElements(input);
}
);
server.tool(
'vscode_get_accessibility_tree',
'Get the accessibility tree of the VSCode window. Provides a semantic, role-based view of the UI that is often cleaner and more meaningful than raw DOM. Includes ARIA labels, roles, and states.',
getAccessibilityTreeInputSchema,
async (input) => {
return getAccessibilityTree(input);
}
);
// ===========================
// DOM Navigation Tools
// ===========================
server.tool(
'vscode_get_element_children',
'Get the direct children (or descendants) of an element. Use this for incremental DOM exploration - start with a parent, then drill down into specific children.',
getElementChildrenInputSchema,
async (input) => {
return getElementChildren(input);
}
);
server.tool(
'vscode_get_element_parents',
'Get the parent chain (ancestors) of an element up to the body. Useful for understanding element context and building more specific selectors.',
getElementParentsInputSchema,
async (input) => {
return getElementParents(input);
}
);
server.tool(
'vscode_get_element_siblings',
'Get sibling elements at the same level as the target element. Useful for understanding list items, tabs, or other repeated elements.',
getElementSiblingsInputSchema,
async (input) => {
return getElementSiblings(input);
}
);
server.tool(
'vscode_find_interactive_elements',
'Find all interactive elements (buttons, inputs, links, tabs, etc.) within a container. Great for discovering what actions can be performed in a UI area.',
findInteractiveElementsInputSchema,
async (input) => {
return findInteractiveElements(input);
}
);
server.tool(
'vscode_search_dom',
'Search the DOM for elements by text content, ID, class, aria-label, title, or role. Returns matching elements with their selectors.',
searchDomInputSchema,
async (input) => {
return searchDom(input);
}
);
server.tool(
'vscode_dump_dom_to_file',
'Dump the full DOM structure to a file (JSON, HTML, or tree format). The AI can then read sections of this file incrementally to explore large DOMs without overwhelming context.',
dumpDomToFileInputSchema,
async (input) => {
return dumpDomToFile(input);
}
);
// ===========================
// JavaScript Execution & DOM Query Tools
// ===========================
server.tool(
'vscode_execute_script',
'Execute arbitrary JavaScript code in the VSCode window context (like DevTools console). Has full access to document, window, and all globals. Returns the result as JSON.',
executeScriptInputSchema,
async (input) => {
return executeScript(input);
}
);
server.tool(
'vscode_query_selector',
'Direct wrapper for document.querySelector() or querySelectorAll(). Simple and fast element lookup by CSS selector.',
querySelectorInputSchema,
async (input) => {
return querySelector(input);
}
);
server.tool(
'vscode_get_element_by_id',
'Get an element by its ID (document.getElementById wrapper). Fast lookup for elements with known IDs.',
getElementByIdInputSchema,
async (input) => {
return getElementById(input);
}
);
server.tool(
'vscode_get_elements_by_class',
'Get all elements with a specific class name (document.getElementsByClassName wrapper).',
getElementsByClassInputSchema,
async (input) => {
return getElementsByClass(input);
}
);
server.tool(
'vscode_get_elements_by_tag',
'Get all elements of a specific tag type like "button", "input", "div" (document.getElementsByTagName wrapper).',
getElementsByTagInputSchema,
async (input) => {
return getElementsByTag(input);
}
);
// ===========================
// Testing Tools
// ===========================
server.tool(
'vscode_get_editor_content',
'Get the full text content of the currently active editor along with metadata like file name, line count, and dirty state.',
getEditorContentInputSchema,
async (input) => {
return getEditorContent(input);
}
);
server.tool(
'vscode_verify_element',
'Verify the presence and state of a UI element. Check if it exists, is visible, is enabled, or contains specific text.',
verifyElementInputSchema,
async (input) => {
return verifyElement(input);
}
);
server.tool(
'vscode_assert_text',
'Assert that specific text appears in the editor or a UI element. Supports exact match or contains check.',
assertTextInputSchema,
async (input) => {
return assertText(input);
}
);
server.tool(
'vscode_check_file_open',
'Check if a file with the specified name is currently open in the editor.',
checkFileOpenInputSchema,
async (input) => {
return checkFileOpen(input);
}
);
// ===========================
// Keyboard & Input Tools
// ===========================
server.tool(
'vscode_press_keys',
'Press keyboard keys or key combinations (e.g., "ctrl+s", "ctrl+shift+p", "Enter", "Escape"). Supports modifiers and special keys.',
pressKeysInputSchema,
async (input) => {
return pressKeys(input);
}
);
server.tool(
'vscode_focus_element',
'Focus a UI element by CSS selector. Optionally scrolls the element into view first.',
focusElementInputSchema,
async (input) => {
return focusElement(input);
}
);
server.tool(
'vscode_scroll',
'Scroll an element or the page in a specified direction or to a position (top/bottom).',
scrollInputSchema,
async (input) => {
return scroll(input);
}
);
server.tool(
'vscode_drag_drop',
'Drag an element and drop it onto another element. Useful for reordering tabs, tree items, etc.',
dragDropInputSchema,
async (input) => {
return dragDrop(input);
}
);
server.tool(
'vscode_hover',
'Hover over an element for a specified duration to trigger hover effects.',
hoverInputSchema,
async (input) => {
return hover(input);
}
);
// ===========================
// Wait & Sync Tools
// ===========================
server.tool(
'vscode_wait_for_element',
'Wait for an element to appear or disappear. Useful for waiting for UI updates after actions.',
waitForElementInputSchema,
async (input) => {
return waitForElement(input);
}
);
server.tool(
'vscode_wait_for_text',
'Wait for specific text to appear or disappear in an element or the page.',
waitForTextInputSchema,
async (input) => {
return waitForText(input);
}
);
server.tool(
'vscode_wait_for_idle',
'Wait for VSCode to become idle (no pending operations, document ready, no busy indicators).',
waitForIdleInputSchema,
async (input) => {
return waitForIdle(input);
}
);
// ===========================
// Notification & Dialog Tools
// ===========================
server.tool(
'vscode_get_notifications',
'Get all visible notifications with their type, message, actions, and source.',
getNotificationsInputSchema,
async (input) => {
return getNotifications(input);
}
);
server.tool(
'vscode_dismiss_notification',
'Dismiss a notification by clicking its close button. Can target by index or message text.',
dismissNotificationInputSchema,
async (input) => {
return dismissNotification(input);
}
);
server.tool(
'vscode_handle_dialog',
'Handle modal dialogs like InputBox, QuickPick, or confirmation dialogs. Can submit text or dismiss.',
handleDialogInputSchema,
async (input) => {
return handleDialog(input);
}
);
server.tool(
'vscode_get_quick_pick_items',
'Get items from the currently visible QuickPick/Command Palette.',
getQuickPickItemsInputSchema,
async (input) => {
return getQuickPickItems(input);
}
);
server.tool(
'vscode_select_quick_pick_item',
'Select an item from the QuickPick by text or index.',
selectQuickPickItemInputSchema,
async (input) => {
return selectQuickPickItem(input);
}
);
// ===========================
// Extension Testing Tools
// ===========================
server.tool(
'vscode_trigger_hover',
'Trigger hover on an element and optionally wait for tooltip to appear. Returns tooltip content if found.',
triggerHoverInputSchema,
async (input) => {
return triggerHover(input);
}
);
server.tool(
'vscode_open_context_menu',
'Open a context menu (right-click menu) on an element.',
openContextMenuInputSchema,
async (input) => {
return openContextMenu(input);
}
);
server.tool(
'vscode_get_menu_items',
'Get items from a visible menu (context menu or dropdown).',
getMenuItemsInputSchema,
async (input) => {
return getMenuItems(input);
}
);
server.tool(
'vscode_click_menu_item',
'Click a menu item by its text label.',
clickMenuItemInputSchema,
async (input) => {
return clickMenuItem(input);
}
);
server.tool(
'vscode_get_tooltip',
'Get currently visible tooltip content. Optionally hover over an element first.',
getTooltipInputSchema,
async (input) => {
return getTooltip(input);
}
);
server.tool(
'vscode_trigger_completion',
'Trigger IntelliSense/code completion (Ctrl+Space) and optionally wait for items.',
triggerCompletionInputSchema,
async (input) => {
return triggerCompletion(input);
}
);
server.tool(
'vscode_get_completion_items',
'Get items from the currently visible IntelliSense/completion widget.',
getCompletionItemsInputSchema,
async (input) => {
return getCompletionItems(input);
}
);
server.tool(
'vscode_select_completion_item',
'Select and accept a completion item by its label.',
selectCompletionItemInputSchema,
async (input) => {
return selectCompletionItem(input);
}
);
server.tool(
'vscode_get_problems_panel',
'Get problems/diagnostics from the Problems panel with severity, message, source, and location.',
getProblemsPanelInputSchema,
async () => {
return getProblemsPanel({});
}
);
server.tool(
'vscode_go_to_definition',
'Trigger Go to Definition (F12) on the current cursor position.',
goToDefinitionInputSchema,
async () => {
return goToDefinition({});
}
);
server.tool(
'vscode_trigger_signature_help',
'Trigger signature help (Ctrl+Shift+Space) and get parameter info.',
triggerSignatureHelpInputSchema,
async () => {
return triggerSignatureHelp({});
}
);
// ===========================
// Debug & Performance Tools
// ===========================
server.tool(
'vscode_get_console_logs',
'Get captured console logs (log, info, warn, error, debug). Logs are captured after first call.',
getConsoleLogsInputSchema,
async (input) => {
return getConsoleLogs(input);
}
);
server.tool(
'vscode_clear_console',
'Clear captured console logs.',
clearConsoleInputSchema,
async () => {
return clearConsole({});
}
);
server.tool(
'vscode_get_output_channels',
'Get list of available output channels in VSCode.',
getOutputChannelsInputSchema,
async () => {
return getOutputChannels({});
}
);
server.tool(
'vscode_get_output_channel_content',
'Get content from a specific output channel.',
getOutputChannelContentInputSchema,
async (input) => {
return getOutputChannelContent(input);
}
);
server.tool(
'vscode_get_performance_metrics',
'Get performance metrics including memory, timing, DOM stats, and resource loading.',
getPerformanceMetricsInputSchema,
async () => {
return getPerformanceMetrics({});
}
);
server.tool(
'vscode_get_extension_logs',
'Get extension-related console logs. Can filter by extension ID.',
getExtensionLogsInputSchema,
async (input) => {
return getExtensionLogs(input);
}
);
server.tool(
'vscode_get_devtools_info',
'Get DevTools-style information about the window, document, navigator, and VSCode-specific APIs.',
getDevToolsInfoInputSchema,
async () => {
return getDevToolsInfo({});
}
);
// ===========================
// Utility Tools
// ===========================
server.tool(
'vscode_initialize',
'Initialize the VSCode automation driver. This is called automatically on first tool use, but can be called explicitly to pre-warm the connection.',
{},
async () => {
const driver = getVSCodeDriver();
const result = await driver.initialize();
return {
content: [{
type: 'text',
text: JSON.stringify({
success: result.success,
message: result.message || result.error,
state: driver.getState(),
}, null, 2),
}],
};
}
);
server.tool(
'vscode_get_status',
'Get the current status of the VSCode automation driver.',
{},
async () => {
const driver = getVSCodeDriver();
return {
content: [{
type: 'text',
text: JSON.stringify({
state: driver.getState(),
isReady: driver.isReady(),
serverName: SERVER_NAME,
serverVersion: SERVER_VERSION,
}, null, 2),
}],
};
}
);
return server;
}
/**
* Main entry point
*/
async function main(): Promise<void> {
console.error(`Starting ${SERVER_NAME} v${SERVER_VERSION}...`);
const server = createServer();
const transport = new StdioServerTransport();
// Handle graceful shutdown
const shutdown = async (): Promise<void> => {
console.error('Shutting down...');
try {
VSCodeDriver.resetInstance();
} catch (error) {
console.error('Error during shutdown:', error);
}
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
// Handle uncaught errors
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
});
// Connect and start serving
await server.connect(transport);
console.error(`${SERVER_NAME} is running on stdio transport`);
}
// Run the server
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});