Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@executeautomation/playwright-mcp-server",
"version": "0.2.3",
"version": "0.2.4",
"description": "Model Context Protocol servers for Playwright",
"license": "MIT",
"author": "ExecuteAutomation, Ltd (https://executeautomation.com)",
Expand Down Expand Up @@ -30,6 +30,7 @@
},
"keywords": ["playwright", "automation", "AI", "Claude MCP"],
"devDependencies": {
"@types/node": "^20.10.5",
"shx": "^0.3.4",
"typescript": "^5.6.2"
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async function runServer() {
const server = new Server(
{
name: "executeautomation/playwright-mcp-server",
version: "0.2.3",
version: "0.2.4",
},
{
capabilities: {
Expand Down
3 changes: 3 additions & 0 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export function createToolDefinitions(): Tool[] {
selector: { type: "string", description: "CSS selector for element to screenshot" },
width: { type: "number", description: "Width in pixels (default: 800)" },
height: { type: "number", description: "Height in pixels (default: 600)" },
storeBase64: { type: "boolean", description: "Store screenshot in base64 format (default: true)" },
savePng: { type: "boolean", description: "Save screenshot as PNG file (default: false)" },
downloadsDir: { type: "string", description: "Custom downloads directory path (default: user's Downloads folder)" },
},
required: ["name"],
},
Expand Down
54 changes: 39 additions & 15 deletions src/toolsHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { chromium, Browser, Page, request } from "playwright";
import { CallToolResult, TextContent, ImageContent } from "@modelcontextprotocol/sdk/types.js";
import { BROWSER_TOOLS } from "./tools.js";
import fs from 'node:fs';
import * as os from 'os';
import * as path from 'path';

// Global state
let browser: Browser | undefined;
let page: Page | undefined;
const consoleLogs: string[] = [];
const screenshots = new Map<string, string>();
const defaultDownloadsPath = path.join(os.homedir(), 'Downloads');

async function ensureBrowser() {
if (!browser) {
Expand Down Expand Up @@ -113,24 +117,44 @@ export async function handleToolCall(
const screenshot = await page!.screenshot(screenshotOptions);
const base64Screenshot = screenshot.toString('base64');

screenshots.set(args.name, base64Screenshot);
server.notification({
method: "notifications/resources/list_changed",
});
const responseContent: (TextContent | ImageContent)[] = [];

// Handle PNG file saving
if (args.savePng !== false) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `${args.name}-${timestamp}.png`;
const downloadsDir = args.downloadsDir || defaultDownloadsPath;

// Create downloads directory if it doesn't exist
if (!fs.existsSync(downloadsDir)) {
fs.mkdirSync(downloadsDir, { recursive: true });
}

const filePath = path.join(downloadsDir, filename);
await fs.promises.writeFile(filePath, screenshot);
responseContent.push({
type: "text",
text: `Screenshot saved to: ${filePath}`,
} as TextContent);
}

// Handle base64 storage
if (args.storeBase64 !== false) {
screenshots.set(args.name, base64Screenshot);
server.notification({
method: "notifications/resources/list_changed",
});

responseContent.push({
type: "image",
data: base64Screenshot,
mimeType: "image/png",
} as ImageContent);
}

return {
toolResult: {
content: [
{
type: "text",
text: `Screenshot '${args.name}' taken`,
} as TextContent,
{
type: "image",
data: base64Screenshot,
mimeType: `image/${args.type || 'png'}`,
} as ImageContent,
],
content: responseContent,
isError: false,
},
};
Expand Down
Loading