Skip to main content
Glama
jomon003

PlayMCP Browser Automation Server

by jomon003
server.ts27.2 kB
#!/usr/bin/env node import { Server } from './mcp/server.js'; import { Tool } from './mcp/types.js'; import { playwrightController } from './controllers/playwright.js'; const OPEN_BROWSER_TOOL: Tool = { name: "openBrowser", description: "Launch a new browser instance", inputSchema: { type: "object", properties: { headless: { type: "boolean" }, debug: { type: "boolean" } }, required: [] } }; const NAVIGATE_TOOL: Tool = { name: "navigate", description: "Navigate to a URL", inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] } }; const TYPE_TOOL: Tool = { name: "type", description: "Type text into an element", inputSchema: { type: "object", properties: { selector: { type: "string" }, text: { type: "string" } }, required: ["selector", "text"] } }; const CLICK_TOOL: Tool = { name: "click", description: "Click an element", inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] } }; const MOVE_MOUSE_TOOL: Tool = { name: "moveMouse", description: "Move mouse to coordinates", inputSchema: { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"] } }; const SCREENSHOT_TOOL: Tool = { name: "screenshot", description: "Take a screenshot", inputSchema: { type: "object", properties: { path: { type: "string" }, type: { type: "string", enum: ["viewport", "element", "page"] }, selector: { type: "string" } }, required: ["path"] } }; const GET_PAGE_SOURCE_TOOL: Tool = { name: "getPageSource", description: "Get the HTML source code of the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_PAGE_TEXT_TOOL: Tool = { name: "getPageText", description: "Get the text content of the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_PAGE_TITLE_TOOL: Tool = { name: "getPageTitle", description: "Get the title of the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_PAGE_URL_TOOL: Tool = { name: "getPageUrl", description: "Get the URL of the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_SCRIPTS_TOOL: Tool = { name: "getScripts", description: "Get all JavaScript code from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_STYLESHEETS_TOOL: Tool = { name: "getStylesheets", description: "Get all CSS stylesheets from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_META_TAGS_TOOL: Tool = { name: "getMetaTags", description: "Get all meta tags from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_LINKS_TOOL: Tool = { name: "getLinks", description: "Get all links from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_IMAGES_TOOL: Tool = { name: "getImages", description: "Get all images from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_FORMS_TOOL: Tool = { name: "getForms", description: "Get all forms from the current page", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_ELEMENT_CONTENT_TOOL: Tool = { name: "getElementContent", description: "Get the HTML and text content of a specific element", inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] } }; const EXECUTE_JAVASCRIPT_TOOL: Tool = { name: "executeJavaScript", description: "Execute arbitrary JavaScript code on the current page and return the result", inputSchema: { type: "object", properties: { script: { type: "string", description: "The JavaScript code to execute on the page. Can be expressions or statements." } }, required: ["script"] } }; const SCROLL_TOOL: Tool = { name: "scroll", description: "Scroll the page by specified amounts with enhanced feedback", inputSchema: { type: "object", properties: { x: { type: "number", description: "Horizontal scroll amount in pixels (positive = right, negative = left)" }, y: { type: "number", description: "Vertical scroll amount in pixels (positive = down, negative = up)" }, smooth: { type: "boolean", description: "Whether to use smooth scrolling animation (default: false)" } }, required: ["x", "y"] } }; const GET_ELEMENT_HIERARCHY_TOOL: Tool = { name: "getElementHierarchy", description: "Get the hierarchical structure of page elements with parent-child relationships", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for root element (default: 'body')" }, maxDepth: { type: "number", description: "Maximum depth to traverse (-1 for unlimited, default: 3)" }, includeText: { type: "boolean", description: "Include text content of elements (default: false)" }, includeAttributes: { type: "boolean", description: "Include element attributes (default: false)" } }, required: [] } }; const GO_FORWARD_TOOL: Tool = { name: "goForward", description: "Navigate forward to the next page in history", inputSchema: { type: "object", properties: {}, required: [] } }; const HOVER_TOOL: Tool = { name: "hover", description: "Hover over an element on the page", inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] } }; const DRAG_AND_DROP_TOOL: Tool = { name: "dragAndDrop", description: "Drag and drop from one element to another", inputSchema: { type: "object", properties: { sourceSelector: { type: "string" }, targetSelector: { type: "string" } }, required: ["sourceSelector", "targetSelector"] } }; const SELECT_OPTION_TOOL: Tool = { name: "selectOption", description: "Select option(s) in a dropdown or select element", inputSchema: { type: "object", properties: { selector: { type: "string" }, values: { type: "array", items: { type: "string" }, description: "Array of values to select" } }, required: ["selector", "values"] } }; const PRESS_KEY_TOOL: Tool = { name: "pressKey", description: "Press a key on the keyboard", inputSchema: { type: "object", properties: { key: { type: "string", description: "Key to press (e.g., 'Enter', 'Escape', 'ArrowDown', etc.)" } }, required: ["key"] } }; const WAIT_FOR_TEXT_TOOL: Tool = { name: "waitForText", description: "Wait for specific text to appear on the page", inputSchema: { type: "object", properties: { text: { type: "string" }, timeout: { type: "number", description: "Timeout in milliseconds (default: 30000)" } }, required: ["text"] } }; const WAIT_FOR_SELECTOR_TOOL: Tool = { name: "waitForSelector", description: "Wait for a specific selector to appear on the page", inputSchema: { type: "object", properties: { selector: { type: "string" }, timeout: { type: "number", description: "Timeout in milliseconds (default: 30000)" } }, required: ["selector"] } }; const RESIZE_TOOL: Tool = { name: "resize", description: "Resize the browser viewport", inputSchema: { type: "object", properties: { width: { type: "number" }, height: { type: "number" } }, required: ["width", "height"] } }; const HANDLE_DIALOG_TOOL: Tool = { name: "handleDialog", description: "Handle browser dialogs (alerts, confirms, prompts)", inputSchema: { type: "object", properties: { accept: { type: "boolean", description: "Whether to accept (true) or dismiss (false) the dialog" }, promptText: { type: "string", description: "Text to enter in prompt dialogs (optional)" } }, required: ["accept"] } }; const GET_CONSOLE_MESSAGES_TOOL: Tool = { name: "getConsoleMessages", description: "Get console messages from the browser", inputSchema: { type: "object", properties: {}, required: [] } }; const GET_NETWORK_REQUESTS_TOOL: Tool = { name: "getNetworkRequests", description: "Get network requests made by the page", inputSchema: { type: "object", properties: {}, required: [] } }; const UPLOAD_FILES_TOOL: Tool = { name: "uploadFiles", description: "Upload files through a file input element", inputSchema: { type: "object", properties: { selector: { type: "string" }, filePaths: { type: "array", items: { type: "string" }, description: "Array of absolute file paths to upload" } }, required: ["selector", "filePaths"] } }; const EVALUATE_WITH_RETURN_TOOL: Tool = { name: "evaluateWithReturn", description: "Execute JavaScript code and return the result", inputSchema: { type: "object", properties: { script: { type: "string", description: "JavaScript code to execute" } }, required: ["script"] } }; const TAKE_SCREENSHOT_TOOL: Tool = { name: "takeScreenshot", description: "Take a screenshot of the page or specific element", inputSchema: { type: "object", properties: { path: { type: "string" }, fullPage: { type: "boolean", description: "Whether to capture the full scrollable page" }, element: { type: "string", description: "CSS selector for element screenshot" } }, required: ["path"] } }; const MOUSE_MOVE_TOOL: Tool = { name: "mouseMove", description: "Move mouse to specific coordinates", inputSchema: { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"] } }; const MOUSE_CLICK_TOOL: Tool = { name: "mouseClick", description: "Click at specific coordinates", inputSchema: { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"] } }; const MOUSE_DRAG_TOOL: Tool = { name: "mouseDrag", description: "Drag from one coordinate to another", inputSchema: { type: "object", properties: { startX: { type: "number" }, startY: { type: "number" }, endX: { type: "number" }, endY: { type: "number" } }, required: ["startX", "startY", "endX", "endY"] } }; const CLOSE_BROWSER_TOOL: Tool = { name: "closeBrowser", description: "Close the browser", inputSchema: { type: "object", properties: {}, required: [] } }; const tools = { openBrowser: OPEN_BROWSER_TOOL, navigate: NAVIGATE_TOOL, type: TYPE_TOOL, click: CLICK_TOOL, moveMouse: MOVE_MOUSE_TOOL, scroll: SCROLL_TOOL, screenshot: SCREENSHOT_TOOL, getPageSource: GET_PAGE_SOURCE_TOOL, getPageText: GET_PAGE_TEXT_TOOL, getPageTitle: GET_PAGE_TITLE_TOOL, getPageUrl: GET_PAGE_URL_TOOL, getScripts: GET_SCRIPTS_TOOL, getStylesheets: GET_STYLESHEETS_TOOL, getMetaTags: GET_META_TAGS_TOOL, getLinks: GET_LINKS_TOOL, getImages: GET_IMAGES_TOOL, getForms: GET_FORMS_TOOL, getElementContent: GET_ELEMENT_CONTENT_TOOL, getElementHierarchy: GET_ELEMENT_HIERARCHY_TOOL, executeJavaScript: EXECUTE_JAVASCRIPT_TOOL, goForward: GO_FORWARD_TOOL, hover: HOVER_TOOL, dragAndDrop: DRAG_AND_DROP_TOOL, selectOption: SELECT_OPTION_TOOL, pressKey: PRESS_KEY_TOOL, waitForText: WAIT_FOR_TEXT_TOOL, waitForSelector: WAIT_FOR_SELECTOR_TOOL, resize: RESIZE_TOOL, handleDialog: HANDLE_DIALOG_TOOL, getConsoleMessages: GET_CONSOLE_MESSAGES_TOOL, getNetworkRequests: GET_NETWORK_REQUESTS_TOOL, uploadFiles: UPLOAD_FILES_TOOL, evaluateWithReturn: EVALUATE_WITH_RETURN_TOOL, takeScreenshot: TAKE_SCREENSHOT_TOOL, mouseMove: MOUSE_MOVE_TOOL, mouseClick: MOUSE_CLICK_TOOL, mouseDrag: MOUSE_DRAG_TOOL, closeBrowser: CLOSE_BROWSER_TOOL }; const server = new Server( { name: "playmcp-browser", version: "1.0.0", }, { capabilities: { tools, }, } ); server.setRequestHandler('listTools', async () => ({ tools: Object.values(tools) })); server.setRequestHandler('callTool', async (request) => { const { name, arguments: args = {} } = request.params; try { switch (name) { case 'openBrowser': { await playwrightController.openBrowser( args.headless as boolean, args.debug as boolean ); return { content: [{ type: "text", text: "Browser opened successfully" }] }; } case 'navigate': { if (!args.url || typeof args.url !== 'string') { return { content: [{ type: "text", text: "URL is required" }], isError: true }; } await playwrightController.navigate(args.url); return { content: [{ type: "text", text: "Navigation successful" }] }; } case 'type': { if (!args.selector || !args.text) { return { content: [{ type: "text", text: "Selector and text are required" }], isError: true }; } await playwrightController.type(args.selector as string, args.text as string); return { content: [{ type: "text", text: "Text entered successfully" }] }; } case 'click': { if (!args.selector) { return { content: [{ type: "text", text: "Selector is required" }], isError: true }; } await playwrightController.click(args.selector as string); return { content: [{ type: "text", text: "Click successful" }] }; } case 'moveMouse': { if (typeof args.x !== 'number' || typeof args.y !== 'number') { return { content: [{ type: "text", text: "X and Y coordinates are required" }], isError: true }; } await playwrightController.moveMouse(args.x, args.y); return { content: [{ type: "text", text: "Mouse moved successfully" }] }; } case 'scroll': { if (typeof args.x !== 'number' || typeof args.y !== 'number') { return { content: [{ type: "text", text: "X and Y scroll amounts are required" }], isError: true }; } const scrollResult = await playwrightController.scroll( args.x, args.y, args.smooth as boolean || false ); return { content: [{ type: "text", text: JSON.stringify({ message: "Page scrolled successfully", before: scrollResult.before, after: scrollResult.after, scrolled: { x: scrollResult.after.x - scrollResult.before.x, y: scrollResult.after.y - scrollResult.before.y } }, null, 2) }] }; } case 'screenshot': { if (!args.path) { return { content: [{ type: "text", text: "Path is required" }], isError: true }; } await playwrightController.screenshot(args as any); return { content: [{ type: "text", text: "Screenshot taken successfully" }] }; } case 'getPageSource': { const source = await playwrightController.getPageSource(); return { content: [{ type: "text", text: source }] }; } case 'getPageText': { const text = await playwrightController.getPageText(); return { content: [{ type: "text", text }] }; } case 'getPageTitle': { const title = await playwrightController.getPageTitle(); return { content: [{ type: "text", text: title }] }; } case 'getPageUrl': { const url = await playwrightController.getPageUrl(); return { content: [{ type: "text", text: url }] }; } case 'getScripts': { const scripts = await playwrightController.getScripts(); return { content: [{ type: "text", text: scripts.join('\n') }] }; } case 'getStylesheets': { const stylesheets = await playwrightController.getStylesheets(); return { content: [{ type: "text", text: stylesheets.join('\n') }] }; } case 'getMetaTags': { const metaTags = await playwrightController.getMetaTags(); return { content: [{ type: "text", text: JSON.stringify(metaTags, null, 2) }] }; } case 'getLinks': { const links = await playwrightController.getLinks(); return { content: [{ type: "text", text: JSON.stringify(links, null, 2) }] }; } case 'getImages': { const images = await playwrightController.getImages(); return { content: [{ type: "text", text: JSON.stringify(images, null, 2) }] }; } case 'getForms': { const forms = await playwrightController.getForms(); return { content: [{ type: "text", text: JSON.stringify(forms, null, 2) }] }; } case 'getElementContent': { if (!args.selector) { return { content: [{ type: "text", text: "Selector is required" }], isError: true }; } const content = await playwrightController.getElementContent(args.selector as string); return { content: [{ type: "text", text: JSON.stringify(content, null, 2) }] }; } case 'getElementHierarchy': { const hierarchy = await playwrightController.getElementHierarchy( args.selector as string || 'body', args.maxDepth as number || 3, args.includeText as boolean || false, args.includeAttributes as boolean || false ); return { content: [{ type: "text", text: JSON.stringify(hierarchy, null, 2) }] }; } case 'executeJavaScript': { if (!args.script || typeof args.script !== 'string') { return { content: [{ type: "text", text: "JavaScript script is required" }], isError: true }; } const result = await playwrightController.executeJavaScript(args.script); return { content: [{ type: "text", text: result !== undefined ? JSON.stringify(result, null, 2) : "Script executed successfully (no return value)" }] }; } case 'goForward': { await playwrightController.goForward(); return { content: [{ type: "text", text: "Navigated forward successfully" }] }; } case 'hover': { if (!args.selector) { return { content: [{ type: "text", text: "Selector is required" }], isError: true }; } await playwrightController.hover(args.selector as string); return { content: [{ type: "text", text: "Hover completed successfully" }] }; } case 'dragAndDrop': { if (!args.sourceSelector || !args.targetSelector) { return { content: [{ type: "text", text: "Source and target selectors are required" }], isError: true }; } await playwrightController.dragAndDrop(args.sourceSelector as string, args.targetSelector as string); return { content: [{ type: "text", text: "Drag and drop completed successfully" }] }; } case 'selectOption': { if (!args.selector || !args.values) { return { content: [{ type: "text", text: "Selector and values are required" }], isError: true }; } await playwrightController.selectOption(args.selector as string, args.values as string[]); return { content: [{ type: "text", text: "Option selected successfully" }] }; } case 'pressKey': { if (!args.key) { return { content: [{ type: "text", text: "Key is required" }], isError: true }; } await playwrightController.pressKey(args.key as string); return { content: [{ type: "text", text: "Key pressed successfully" }] }; } case 'waitForText': { if (!args.text) { return { content: [{ type: "text", text: "Text is required" }], isError: true }; } await playwrightController.waitForText(args.text as string, args.timeout as number); return { content: [{ type: "text", text: "Text found successfully" }] }; } case 'waitForSelector': { if (!args.selector) { return { content: [{ type: "text", text: "Selector is required" }], isError: true }; } await playwrightController.waitForSelector(args.selector as string, args.timeout as number); return { content: [{ type: "text", text: "Selector found successfully" }] }; } case 'resize': { if (typeof args.width !== 'number' || typeof args.height !== 'number') { return { content: [{ type: "text", text: "Width and height are required" }], isError: true }; } await playwrightController.resize(args.width, args.height); return { content: [{ type: "text", text: "Browser resized successfully" }] }; } case 'handleDialog': { if (typeof args.accept !== 'boolean') { return { content: [{ type: "text", text: "Accept parameter is required" }], isError: true }; } await playwrightController.handleDialog(args.accept, args.promptText as string); return { content: [{ type: "text", text: "Dialog handler set successfully" }] }; } case 'getConsoleMessages': { const messages = await playwrightController.getConsoleMessages(); return { content: [{ type: "text", text: JSON.stringify(messages, null, 2) }] }; } case 'getNetworkRequests': { const requests = await playwrightController.getNetworkRequests(); return { content: [{ type: "text", text: JSON.stringify(requests, null, 2) }] }; } case 'uploadFiles': { if (!args.selector || !args.filePaths) { return { content: [{ type: "text", text: "Selector and file paths are required" }], isError: true }; } await playwrightController.uploadFiles(args.selector as string, args.filePaths as string[]); return { content: [{ type: "text", text: "Files uploaded successfully" }] }; } case 'evaluateWithReturn': { if (!args.script || typeof args.script !== 'string') { return { content: [{ type: "text", text: "JavaScript script is required" }], isError: true }; } const result = await playwrightController.evaluateWithReturn(args.script); return { content: [{ type: "text", text: result !== undefined ? JSON.stringify(result, null, 2) : "null" }] }; } case 'takeScreenshot': { if (!args.path) { return { content: [{ type: "text", text: "Path is required" }], isError: true }; } await playwrightController.takeScreenshot(args.path as string, { fullPage: args.fullPage as boolean, element: args.element as string }); return { content: [{ type: "text", text: "Screenshot taken successfully" }] }; } case 'mouseMove': { if (typeof args.x !== 'number' || typeof args.y !== 'number') { return { content: [{ type: "text", text: "X and Y coordinates are required" }], isError: true }; } await playwrightController.mouseMove(args.x, args.y); return { content: [{ type: "text", text: "Mouse moved successfully" }] }; } case 'mouseClick': { if (typeof args.x !== 'number' || typeof args.y !== 'number') { return { content: [{ type: "text", text: "X and Y coordinates are required" }], isError: true }; } await playwrightController.mouseClick(args.x, args.y); return { content: [{ type: "text", text: "Mouse clicked successfully" }] }; } case 'mouseDrag': { if (typeof args.startX !== 'number' || typeof args.startY !== 'number' || typeof args.endX !== 'number' || typeof args.endY !== 'number') { return { content: [{ type: "text", text: "Start and end coordinates are required" }], isError: true }; } await playwrightController.mouseDrag(args.startX, args.startY, args.endX, args.endY); return { content: [{ type: "text", text: "Mouse drag completed successfully" }] }; } case 'closeBrowser': { await playwrightController.closeBrowser(); return { content: [{ type: "text", text: "Browser closed successfully" }] }; } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true }; } } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}${error.suggestion ? `\nSuggestion: ${error.suggestion}` : ''}` }], isError: true }; } }); async function runServer() { console.error("Browser Automation MCP Server starting..."); await server.connect(); } // Handle process exit process.on('SIGINT', async () => { try { await playwrightController.closeBrowser(); } catch (error) { // Ignore errors during cleanup } process.exit(0); }); runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jomon003/PlayMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server