由于playwright mcp 缺少get_by_role(),get_by_text()等用法,于是让Cursor 生成了一个playwright mcp tools,代码如下:
from mcp.server.fastmcp import FastMCP
from playwright.async_api import async_playwright, TimeoutError, Error
from typing import Optional, Dict
import asyncio
class PlaywrightController:
def __init__(self):
self.playwright = None
self.browser = None
self.context = None
self.page = None
async def _initialize(self):
"""Initialize playwright instance and browser"""
if self.playwright is None:
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch(headless=False)
self.context = await self.browser.new_context()
self.page = await self.context.new_page()
async def navigate(self, url: str, wait_until: Optional[str] = None, timeout: Optional[int] = None):
"""Navigate to a URL"""
await self._initialize()
try:
await self.page.goto(url, wait_until=wait_until, timeout=timeout)
return {"status": "success", "url": url}
except TimeoutError:
return {"status": "error", "message": f"Navigation timeout: {url}"}
except Exception as e:
return {"status": "error", "message": str(e)}
async def click(self, selector: str, index: int = 0, strict: bool = False):
"""Click an element"""
await self._initialize()
try:
await self.page.locator(selector).nth(index).click()
return {"status": "success", "action": "click", "selector": selector}
except Error as e:
if "strict mode violation" in str(e):
try:
# Try with exact text match
await self.page.get_by_text(selector, exact=True).click()
return {"status": "success", "action": "click", "selector": f"text={selector}"}
except:
pass
try:
# Try with role
await self.page.get_by_role("button", name=selector).nth(index).click()
return {"status": "success", "action": "click", "selector": f"role=button,name={selector}","idnex":index}
except:
pass
return {"status": "error", "message": str(e)}
async def fill(self, selector: str, index: int, value: str, strict: bool = False):
"""Fill a form field"""
await self._initialize()
try:
await self.page.locator(selector).nth(index).fill(value)
return {"status": "success", "action": "fill", "selector": selector, "index":index,"value": value}
except Error as e:
if "strict mode violation" in str(e):
try:
# Try with placeholder
await self.page.get_by_placeholder(selector).nth(index).fill(value)
return {"status": "success", "action": "fill", "selector": f"placeholder={selector}","index":index}
except:
pass
try:
# Try with label
await self.page.get_by_label(selector).nth(index).fill(value)
return {"status": "success", "action": "fill", "selector": f"label={selector}","index":index}
except:
pass
return {"status": "error", "message": str(e)}
async def click_by_text(self, text: str, index: int = 0, exact: bool = False):
"""Click element by text content"""
await self._initialize()
try:
await self.page.get_by_text(text, exact=exact).nth(index).click()
return {"status": "success", "action": "click_by_text", "text": text,"index":index}
except Exception as e:
return {"status": "error", "message": str(e)}
async def click_by_role(self, role: str, name: str, exact: bool = False, index: int = 0):
"""Click element by role and name"""
await self._initialize()
try:
await self.page.get_by_role(role, name=name, exact=exact).nth(index).click()
return {"status": "success", "action": "click_by_role", "role": role, "name": name, "index": index}
except Exception as e:
return {"status": "error", "message": str(e)}
async def wait_for_selector(self, selector: str, timeout: Optional[float] = None, strict: bool = False):
"""Wait for an element to be present"""
await self._initialize()
try:
await self.page.wait_for_selector(selector, timeout=timeout)
return {"status": "success", "action": "wait_for_selector", "selector": selector}
except Exception as e:
return {"status": "error", "message": str(e)}
async def close(self):
"""Close all resources"""
if self.page:
await self.page.close()
if self.context:
await self.context.close()
if self.browser:
await self.browser.close()
if self.playwright:
await self.playwright.stop()
# Global controller instance
controller = PlaywrightController()
mcp = FastMCP("Playwright Server")
@mcp.tool()
async def playwright_navigate(url: str, wait_until: Optional[str] = None, timeout: Optional[int] = None) -> Dict:
"""Navigate to a URL
Args:
url: The URL to navigate to
wait_until: Optional navigation wait condition ('load'|'domcontentloaded'|'networkidle'|'commit')
timeout: Maximum navigation time in milliseconds
"""
return await controller.navigate(url, wait_until=wait_until, timeout=timeout)
@mcp.tool()
async def playwright_click(selector: str, strict: bool = False) -> Dict:
"""Click an element by selector
Args:
selector: CSS or XPath selector
strict: Whether to enable strict mode (exact match)
"""
return await controller.click(selector, strict=strict)
@mcp.tool()
async def playwright_fill(selector: str, index: int,value: str, strict: bool = False) -> Dict:
"""Fill a form field
Args:
selector: CSS or XPath selector
value: Text to fill
strict: Whether to enable strict mode (exact match)
"""
return await controller.fill(selector, index, value,strict=strict)
@mcp.tool()
async def playwright_click_by_text(text: str, exact: bool = False) -> Dict:
"""Click element by text content
Args:
text: Text content to match
exact: Whether to require exact text match
"""
return await controller.click_by_text(text, exact=exact)
@mcp.tool()
async def playwright_click_by_role(role: str, name: str, exact: bool = False, index: int = 0) -> Dict:
"""Click element by role and name
Args:
role: ARIA role
name: Element name or label
exact: Whether to require exact name match
index: Index if multiple elements match
"""
return await controller.click_by_role(role, name, exact=exact, index=index)
@mcp.tool()
async def playwright_wait_for_selector(selector: str, timeout: Optional[float] = None, strict: bool = False) -> Dict:
"""Wait for an element to be present
Args:
selector: CSS or XPath selector
timeout: Maximum time to wait in milliseconds
strict: Whether to enable strict mode (exact match)
"""
return await controller.wait_for_selector(selector, timeout=timeout, strict=strict)
@mcp.tool()
async def playwright_close() -> Dict:
"""Close all resources"""
await controller.close()
return {"status": "success", "action": "close"}
if __name__ == "__main__":
try:
mcp.run()
except KeyboardInterrupt:
asyncio.run(controller.close())
finally:
asyncio.run(controller.close())
- 目录结构如下:
2. 调试mcp tools执行命令: mcp dev playwright_server.py, 然后在UI界面中单步调试;
- tools 页面如下:
4. tools 调试成功后就可以在Claude Desktop 中进行配置了;
claude-desktop.json内容如下:
{ "mcpServers": {
"my_playwright": {
"command": "uv",
"args": [
"--directory",
"/Users/xxx/MCPUse/my-playwright-mcp",
"run",
"playwright_server.py"
] }
}
}
5. 在Claude desktop 配置完成后重启Claude Desktop;
若无法执行脚本,在macos下需要先安装uv, brew install uv;