Skip to main content
Glama
Leviathangk

Playwright MCP Server

by Leviathangk
index.ts24.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { parseConfig } from './config.js'; import { SessionManager } from './session-manager.js'; import { handleCreateSession, handleCloseSession, handleNavigate, handleClick, handleType, handleSearchRequests, handleGetRequests, handleGetRequestDetail, handleClearRequests, handleGetPageStructure, handleFindElementByText, handleScreenshot, handleWaitForElement, handleGetTextContent, handleQuerySelector, handleGetPageContent, handleScroll, handleExecuteScript, handleInstallBrowser, handleGetPages, handleNewPage, handleSwitchPage, handleClosePage, } from './tools/index.js'; /** * Main entry point */ async function main() { // Parse configuration from command line arguments const config = parseConfig(process.argv.slice(2)); console.error('Starting Playwright MCP Server with config:', config); // Create session manager const sessionManager = new SessionManager(config); // Initialize browser await sessionManager.initialize(); // Create MCP server const server = new Server( { name: 'playwright-mcp-server', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); // Register tool list handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'browser_install', description: 'Install Playwright browsers. This allows you to install chromium, firefox, webkit, or all browsers without manually running playwright install.', inputSchema: { type: 'object', properties: { browser: { type: 'string', enum: ['chromium', 'firefox', 'webkit', 'all'], description: 'Which browser to install', default: 'chromium', }, withDeps: { type: 'boolean', description: 'Whether to install system dependencies (Linux only)', default: false, }, }, required: [], }, }, { name: 'browser_create_session', description: 'Create a new browser session. Returns a sessionId that must be used for all subsequent operations.', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'browser_close_session', description: 'Close an existing browser session and release resources.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID to close', }, }, required: ['sessionId'], }, }, { name: 'browser_navigate', description: 'Navigate to a URL in the specified session.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, url: { type: 'string', description: 'The URL to navigate to', }, waitUntil: { type: 'string', description: 'When to consider navigation successful', enum: ['load', 'domcontentloaded', 'networkidle'], default: 'load', }, timeout: { type: 'number', description: 'Navigation timeout in milliseconds', }, }, required: ['sessionId', 'url'], }, }, { name: 'browser_click', description: 'Click an element on the page.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'CSS selector or XPath of the element to click', }, timeout: { type: 'number', description: 'Timeout in milliseconds', }, force: { type: 'boolean', description: 'Whether to force the click even if the element is not actionable', }, clickCount: { type: 'number', description: 'Number of times to click', default: 1, }, }, required: ['sessionId', 'selector'], }, }, { name: 'browser_type', description: 'Type text into an input element.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'CSS selector or XPath of the input element', }, text: { type: 'string', description: 'The text to type', }, delay: { type: 'number', description: 'Delay between key presses in milliseconds', }, timeout: { type: 'number', description: 'Timeout in milliseconds', }, clear: { type: 'boolean', description: 'Whether to clear the input before typing', default: false, }, }, required: ['sessionId', 'selector', 'text'], }, }, { name: 'browser_search_requests', description: 'Search captured network requests by keyword (supports regex). Useful for finding API endpoints that contain specific data.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, keyword: { type: 'string', description: 'Keyword to search for', }, searchIn: { type: 'array', items: { type: 'string', enum: ['url', 'request', 'response'], }, description: 'Where to search: url, request body, or response body', default: ['url', 'response'], }, isRegex: { type: 'boolean', description: 'Whether the keyword is a regular expression', default: false, }, limit: { type: 'number', description: 'Maximum number of results to return', default: 10, }, }, required: ['sessionId', 'keyword'], }, }, { name: 'browser_get_requests', description: 'Get all captured network requests with optional filtering.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, filter: { type: 'object', properties: { method: { type: 'string', description: 'Filter by HTTP method (GET, POST, etc.)', }, urlContains: { type: 'string', description: 'Filter by URL containing this string', }, resourceType: { type: 'string', description: 'Filter by resource type (xhr, fetch, document, etc.)', }, statusCode: { type: 'number', description: 'Filter by HTTP status code', }, }, }, limit: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['sessionId'], }, }, { name: 'browser_get_request_detail', description: 'Get detailed information about a specific request, including curl command.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, requestId: { type: 'string', description: 'The request ID', }, }, required: ['sessionId', 'requestId'], }, }, { name: 'browser_clear_requests', description: 'Clear all captured network requests for a session.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, }, required: ['sessionId'], }, }, { name: 'browser_get_page_structure', description: 'Get the structure of interactive elements on the page. Returns clickable elements like links, buttons, and inputs with their text and selectors.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'Optional CSS selector to analyze only a specific region of the page', }, includeHidden: { type: 'boolean', description: 'Whether to include hidden elements', default: false, }, maxElements: { type: 'number', description: 'Maximum number of elements to return', default: 100, }, }, required: ['sessionId'], }, }, { name: 'browser_find_element_by_text', description: 'Find an element on the page by its text content. Returns the selector that can be used with click or type tools.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, text: { type: 'string', description: 'The text to search for', }, exact: { type: 'boolean', description: 'Whether to match the text exactly', default: false, }, elementType: { type: 'string', enum: ['link', 'button', 'any'], description: 'Type of element to search for', default: 'any', }, }, required: ['sessionId', 'text'], }, }, { name: 'browser_screenshot', description: 'Take a screenshot of the page or a specific element and save it to a file.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, path: { type: 'string', description: 'File path where the screenshot will be saved (e.g., "screenshots/page.png")', }, selector: { type: 'string', description: 'Optional CSS selector to screenshot only a specific element', }, fullPage: { type: 'boolean', description: 'Whether to take a full page screenshot', default: false, }, }, required: ['sessionId', 'path'], }, }, { name: 'browser_wait_for_element', description: 'Wait for an element to appear on the page. Useful for waiting for dynamic content to load.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'CSS selector of the element to wait for', }, timeout: { type: 'number', description: 'Maximum time to wait in milliseconds', default: 30000, }, state: { type: 'string', enum: ['attached', 'detached', 'visible', 'hidden'], description: 'Wait for element to reach this state', default: 'visible', }, }, required: ['sessionId', 'selector'], }, }, { name: 'browser_get_text_content', description: 'Get the text content of a specific element on the page.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'CSS selector of the element', }, }, required: ['sessionId', 'selector'], }, }, { name: 'browser_query_selector', description: 'Query elements using CSS selector and get detailed information including text, attributes, position, and visibility.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, selector: { type: 'string', description: 'CSS selector to query', }, multiple: { type: 'boolean', description: 'Whether to return all matching elements or just the first one', default: false, }, includeAttributes: { type: 'boolean', description: 'Whether to include element attributes in the result', default: true, }, }, required: ['sessionId', 'selector'], }, }, { name: 'browser_get_page_content', description: 'Get the content of the page or a specific element in different formats (html, text, markdown).', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, format: { type: 'string', enum: ['html', 'text', 'markdown'], description: 'Format of the content to return', default: 'html', }, selector: { type: 'string', description: 'Optional CSS selector to get content from a specific element', }, }, required: ['sessionId'], }, }, { name: 'browser_scroll', description: 'Scroll the page to a specific position, element, or direction. Useful for loading lazy-loaded content.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, target: { type: 'string', enum: ['top', 'bottom', 'element'], description: 'Where to scroll: top, bottom, or to a specific element', default: 'bottom', }, selector: { type: 'string', description: 'CSS selector of element to scroll to (required when target is "element")', }, x: { type: 'number', description: 'Horizontal scroll position in pixels', }, y: { type: 'number', description: 'Vertical scroll position in pixels', }, smooth: { type: 'boolean', description: 'Whether to use smooth scrolling', default: true, }, }, required: ['sessionId'], }, }, { name: 'browser_execute_script', description: 'Execute custom JavaScript code in the browser context. Returns the result of the script execution.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, script: { type: 'string', description: 'JavaScript code to execute', }, args: { type: 'array', description: 'Optional arguments to pass to the script', default: [], }, }, required: ['sessionId', 'script'], }, }, { name: 'browser_get_pages', description: 'Get a list of all open pages (tabs) in the session, including which page is currently active.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, }, required: ['sessionId'], }, }, { name: 'browser_new_page', description: 'Create a new page (tab) in the session and optionally navigate to a URL.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, url: { type: 'string', description: 'Optional URL to navigate to in the new page', }, }, required: ['sessionId'], }, }, { name: 'browser_switch_page', description: 'Switch to a different page (tab) by its index. Use browser_get_pages to see available pages.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, pageIndex: { type: 'number', description: 'The index of the page to switch to (0-based)', }, }, required: ['sessionId', 'pageIndex'], }, }, { name: 'browser_close_page', description: 'Close a specific page (tab) by its index. Cannot close the last remaining page.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The session ID', }, pageIndex: { type: 'number', description: 'The index of the page to close (0-based)', }, }, required: ['sessionId', 'pageIndex'], }, }, ], }; }); // Register tool call handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'browser_install': return await handleInstallBrowser(args); case 'browser_create_session': return await handleCreateSession(sessionManager); case 'browser_close_session': return await handleCloseSession(sessionManager, args); case 'browser_navigate': return await handleNavigate(sessionManager, args); case 'browser_click': return await handleClick(sessionManager, args); case 'browser_type': return await handleType(sessionManager, args); case 'browser_search_requests': return await handleSearchRequests(sessionManager, args); case 'browser_get_requests': return await handleGetRequests(sessionManager, args); case 'browser_get_request_detail': return await handleGetRequestDetail(sessionManager, args); case 'browser_clear_requests': return await handleClearRequests(sessionManager, args); case 'browser_get_page_structure': return await handleGetPageStructure(sessionManager, args); case 'browser_find_element_by_text': return await handleFindElementByText(sessionManager, args); case 'browser_screenshot': return await handleScreenshot(sessionManager, args); case 'browser_wait_for_element': return await handleWaitForElement(sessionManager, args); case 'browser_get_text_content': return await handleGetTextContent(sessionManager, args); case 'browser_query_selector': return await handleQuerySelector(sessionManager, args); case 'browser_get_page_content': return await handleGetPageContent(sessionManager, args); case 'browser_scroll': return await handleScroll(sessionManager, args); case 'browser_execute_script': return await handleExecuteScript(sessionManager, args); case 'browser_get_pages': return await handleGetPages(sessionManager, args); case 'browser_new_page': return await handleNewPage(sessionManager, args); case 'browser_switch_page': return await handleSwitchPage(sessionManager, args); case 'browser_close_page': return await handleClosePage(sessionManager, args); default: return { content: [ { type: 'text', text: JSON.stringify({ errorCode: 'INVALID_TOOL', message: `Unknown tool: ${name}`, }), }, ], isError: true, }; } } catch (error: any) { console.error(`Error handling tool ${name}:`, error); return { content: [ { type: 'text', text: JSON.stringify({ errorCode: 'INTERNAL_ERROR', message: error.message || 'Internal server error', details: error.stack, }), }, ], isError: true, }; } }); // Create stdio transport const transport = new StdioServerTransport(); // Connect server to transport await server.connect(transport); console.error('Playwright MCP Server started successfully'); // Handle shutdown process.on('SIGINT', async () => { console.error('Shutting down...'); await sessionManager.shutdown(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('Shutting down...'); await sessionManager.shutdown(); process.exit(0); }); } // Run the server main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });

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/Leviathangk/PlaywrightMCPForCrawler'

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