#
tokens: 7752/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── image
│   ├── AvailableMCPTools.png
│   └── UsingMCPServer.png
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   ├── requestHandler.ts
│   ├── tools.ts
│   └── toolsHandler.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | dist
3 | 
4 | .DS_Store
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCP Browser Automation
 2 | 
 3 | This is demo project to practice Model Context Protocol based server implemenation for automating browsing with Playwright. It interacts with a Claude Desktop client to accept user prompts and use server to control browser.
 4 | 
 5 | <a href="https://glama.ai/mcp/servers/hokppvk1dy"><img width="380" height="200" src="https://glama.ai/mcp/servers/hokppvk1dy/badge" alt="Browser Automation Server MCP server" /></a>
 6 | 
 7 | ## Pre-requisites
 8 | 
 9 | - [Playwright](https://playwright.dev/)
10 | - [Claude Desktop](https://claude.ai/download)
11 | - [Node.js](https://nodejs.org/en/download/)
12 | 
13 | ## Building
14 | 
15 | 1. Clone the repository: `git clone https://github.com/hrmeetsingh/mcp-browser-automation.git`
16 | 2. Install dependencies: `npm install`
17 | 3. Verify the output executables are present in `dist` folder
18 | 
19 | ## Integration
20 | 
21 | 1. Create a configuration file in `~/Application\ Support/Claude/claude_desktop_config.json` (This is for macOS)
22 | 2. Copy the following to the file:
23 | ```json
24 | {
25 |   "mcpServers": {
26 |     "mcp-browser-automation": {
27 |       "command": "node",
28 |       "args": ["/path/to/mcp-browser-automation/dist/index.js"]
29 |     }
30 |   }
31 | }
32 | ```
33 | 3. Start Claude Desktop
34 | 
35 | ## Usage
36 | 
37 | 1. Open Claude Desktop
38 | 2. Start a new conversation to open a browser and navigate to a URL
39 | 
40 | ## Example
41 | 
42 | - Added MCP Server options
43 | ![Added MCP Server options](./image/AvailableMCPTools.png)
44 | 
45 | - Navigating to a URL and doing actions with playwright
46 | ![Navigating to a URL and entering text](./image/UsingMCPServer.png)
47 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2020",
 4 |       "module": "ES2022",
 5 |       "moduleResolution": "bundler",
 6 |       "outDir": "./dist",
 7 |       "strict": true,
 8 |       "esModuleInterop": true,
 9 |       "skipLibCheck": true,
10 |       "declaration": true
11 |     },
12 |     "include": ["src/**/*"],
13 |     "exclude": ["node_modules", "dist"]
14 |   }
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 5 | import { createToolDefinitions } from "./tools.js";
 6 | import { setupRequestHandlers } from "./requestHandler.js";
 7 | 
 8 | async function runServer() {
 9 |   const server = new Server(
10 |     {
11 |       name: "executeautomation/mcp-browser-automation",
12 |       version: "0.2.6",
13 |     },
14 |     {
15 |       capabilities: {
16 |         resources: {},
17 |         tools: {},
18 |       },
19 |     }
20 |   );
21 | 
22 |   // Create tool definitions
23 |   const TOOLS = createToolDefinitions();
24 | 
25 |   // Setup request handlers
26 |   setupRequestHandlers(server, TOOLS);
27 | 
28 |   // Create transport and connect
29 |   const transport = new StdioServerTransport();
30 |   await server.connect(transport);
31 | }
32 | 
33 | runServer().catch(console.error);
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-browser-automation",
 3 |   "version": "1.0.0",
 4 |   "description": "This is demo project to practice Model Context Protocol implemenation for automating browsing",
 5 |   "author": "Harmeet Singh",
 6 |   "types": "dist/index.d.ts",
 7 |   "type": "module",
 8 |   "bin": {
 9 |     "mcp-browser-automation": "dist/index.js"
10 |   },
11 |   "files": [
12 |     "dist"
13 |   ],
14 |   "scripts": {
15 |     "build": "tsc && shx chmod +x dist/*.js",
16 |     "prepare": "npm run build",
17 |     "watch": "tsc --watch"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "1.0.3",
21 |     "playwright": "1.49.1",
22 |     "@playwright/browser-chromium": "1.49.1"
23 |   },
24 |   "keywords": ["playwright", "automation", "AI", "Claude", "Model Context Protocol"],
25 |   "devDependencies": {
26 |     "@types/node": "^20.10.5",
27 |     "shx": "^0.3.4",
28 |     "typescript": "^5.6.2"
29 |   },
30 |   "license": "MIT"
31 | }
32 | 
```

--------------------------------------------------------------------------------
/src/requestHandler.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 2 | import { 
 3 |   ListResourcesRequestSchema, 
 4 |   ReadResourceRequestSchema, 
 5 |   ListToolsRequestSchema, 
 6 |   CallToolRequestSchema,
 7 |   Tool
 8 | } from "@modelcontextprotocol/sdk/types.js";
 9 | import { handleToolCall, getConsoleLogs, getScreenshots } from "./toolsHandler.js";
10 | 
11 | export function setupRequestHandlers(server: Server, tools: Tool[]) {
12 |   // List resources handler
13 |   server.setRequestHandler(ListResourcesRequestSchema, async () => ({
14 |     resources: [
15 |       {
16 |         uri: "console://logs",
17 |         mimeType: "text/plain",
18 |         name: "Browser console logs",
19 |       },
20 |       ...Array.from(getScreenshots().keys()).map(name => ({
21 |         uri: `screenshot://${name}`,
22 |         mimeType: "image/png",
23 |         name: `Screenshot: ${name}`,
24 |       })),
25 |     ],
26 |   }));
27 | 
28 |   // Read resource handler
29 |   server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
30 |     const uri = request.params.uri.toString();
31 | 
32 |     if (uri === "console://logs") {
33 |       return {
34 |         contents: [{
35 |           uri,
36 |           mimeType: "text/plain",
37 |           text: getConsoleLogs().join("\n"),
38 |         }],
39 |       };
40 |     }
41 | 
42 |     if (uri.startsWith("screenshot://")) {
43 |       const name = uri.split("://")[1];
44 |       const screenshot = getScreenshots().get(name);
45 |       if (screenshot) {
46 |         return {
47 |           contents: [{
48 |             uri,
49 |             mimeType: "image/png",
50 |             blob: screenshot,
51 |           }],
52 |         };
53 |       }
54 |     }
55 | 
56 |     throw new Error(`Resource not found: ${uri}`);
57 |   });
58 | 
59 |   // List tools handler
60 |   server.setRequestHandler(ListToolsRequestSchema, async () => ({
61 |     tools: tools,
62 |   }));
63 | 
64 |   // Call tool handler
65 |   server.setRequestHandler(CallToolRequestSchema, async (request) =>
66 |     handleToolCall(request.params.name, request.params.arguments ?? {}, server)
67 |   );
68 | }
```

--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Tool } from "@modelcontextprotocol/sdk/types.js";
  2 | 
  3 | export function createToolDefinitions(): Tool[] {
  4 |   return [
  5 |     {
  6 |       name: "playwright_navigate",
  7 |       description: "Navigate to a URL",
  8 |       inputSchema: {
  9 |         type: "object",
 10 |         properties: {
 11 |           url: { type: "string" },
 12 |         },
 13 |         required: ["url"],
 14 |       },
 15 |     },
 16 |     {
 17 |       name: "playwright_screenshot",
 18 |       description: "Take a screenshot of the current page or a specific element",
 19 |       inputSchema: {
 20 |         type: "object",
 21 |         properties: {
 22 |           name: { type: "string", description: "Name for the screenshot" },
 23 |           selector: { type: "string", description: "CSS selector for element to screenshot" },
 24 |           width: { type: "number", description: "Width in pixels (default: 800)" },
 25 |           height: { type: "number", description: "Height in pixels (default: 600)" },
 26 |           storeBase64: { type: "boolean", description: "Store screenshot in base64 format (default: true)" },
 27 |           savePng: { type: "boolean", description: "Save screenshot as PNG file (default: false)" },
 28 |           downloadsDir: { type: "string", description: "Custom downloads directory path (default: user's Downloads folder)" },
 29 |         },
 30 |         required: ["name"],
 31 |       },
 32 |     },
 33 |     {
 34 |       name: "playwright_click",
 35 |       description: "Click an element on the page",
 36 |       inputSchema: {
 37 |         type: "object",
 38 |         properties: {
 39 |           selector: { type: "string", description: "CSS selector for element to click" },
 40 |         },
 41 |         required: ["selector"],
 42 |       },
 43 |     },
 44 |     {
 45 |       name: "playwright_fill",
 46 |       description: "fill out an input field",
 47 |       inputSchema: {
 48 |         type: "object",
 49 |         properties: {
 50 |           selector: { type: "string", description: "CSS selector for input field" },
 51 |           value: { type: "string", description: "Value to fill" },
 52 |         },
 53 |         required: ["selector", "value"],
 54 |       },
 55 |     },
 56 |     {
 57 |       name: "playwright_select",
 58 |       description: "Select an element on the page with Select tag",
 59 |       inputSchema: {
 60 |         type: "object",
 61 |         properties: {
 62 |           selector: { type: "string", description: "CSS selector for element to select" },
 63 |           value: { type: "string", description: "Value to select" },
 64 |         },
 65 |         required: ["selector", "value"],
 66 |       },
 67 |     },
 68 |     {
 69 |       name: "playwright_hover",
 70 |       description: "Hover an element on the page",
 71 |       inputSchema: {
 72 |         type: "object",
 73 |         properties: {
 74 |           selector: { type: "string", description: "CSS selector for element to hover" },
 75 |         },
 76 |         required: ["selector"],
 77 |       },
 78 |     },
 79 |     {
 80 |       name: "playwright_evaluate",
 81 |       description: "Execute JavaScript in the browser console",
 82 |       inputSchema: {
 83 |         type: "object",
 84 |         properties: {
 85 |           script: { type: "string", description: "JavaScript code to execute" },
 86 |         },
 87 |         required: ["script"],
 88 |       },
 89 |     },
 90 |     {
 91 |       name: "playwright_get",
 92 |       description: "Perform an HTTP GET request",
 93 |       inputSchema: {
 94 |         type: "object",
 95 |         properties: {
 96 |           url: { type: "string", description: "URL to perform GET operation" }
 97 |         },
 98 |         required: ["url"],
 99 |       },
100 |     },
101 |     {
102 |       name: "playwright_post",
103 |       description: "Perform an HTTP POST request",
104 |       inputSchema: {
105 |         type: "object",
106 |         properties: {
107 |           url: { type: "string", description: "URL to perform POST operation" },
108 |           value: { type: "string", description: "Data to post in the body" },
109 |         },
110 |         required: ["url", "value"],
111 |       },
112 |     },
113 |     {
114 |       name: "playwright_put",
115 |       description: "Perform an HTTP PUT request",
116 |       inputSchema: {
117 |         type: "object",
118 |         properties: {
119 |           url: { type: "string", description: "URL to perform PUT operation" },
120 |           value: { type: "string", description: "Data to PUT in the body" },
121 |         },
122 |         required: ["url", "value"],
123 |       },
124 |     },
125 |     {
126 |       name: "playwright_patch",
127 |       description: "Perform an HTTP PATCH request",
128 |       inputSchema: {
129 |         type: "object",
130 |         properties: {
131 |           url: { type: "string", description: "URL to perform PUT operation" },
132 |           value: { type: "string", description: "Data to PATCH in the body" },
133 |         },
134 |         required: ["url", "value"],
135 |       },
136 |     },
137 |     {
138 |       name: "playwright_delete",
139 |       description: "Perform an HTTP DELETE request",
140 |       inputSchema: {
141 |         type: "object",
142 |         properties: {
143 |           url: { type: "string", description: "URL to perform DELETE operation" }
144 |         },
145 |         required: ["url"],
146 |       },
147 |     },
148 |   ];
149 | }
150 | 
151 | // Browser-requiring tools for conditional browser launch
152 | export const BROWSER_TOOLS = [
153 |   "playwright_navigate",
154 |   "playwright_screenshot",
155 |   "playwright_click",
156 |   "playwright_fill",
157 |   "playwright_select",
158 |   "playwright_hover",
159 |   "playwright_evaluate"
160 | ];
161 | 
162 | 
163 | // API Request tools for conditional launch
164 | export const API_TOOLS = [
165 |   "playwright_get",
166 |   "playwright_post",
167 |   "playwright_put",
168 |   "playwright_delete",
169 |   "playwright_patch"
170 | ];
```

--------------------------------------------------------------------------------
/src/toolsHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { chromium, Browser, Page, request, APIRequest, APIRequestContext } from "playwright";
  2 | import { CallToolResult, TextContent, ImageContent } from "@modelcontextprotocol/sdk/types.js";
  3 | import { BROWSER_TOOLS, API_TOOLS } from "./tools.js";
  4 | import fs from 'node:fs';
  5 | import * as os from 'os';
  6 | import * as path from 'path';
  7 | 
  8 | // Global state
  9 | let browser: Browser | undefined;
 10 | let page: Page | undefined;
 11 | const consoleLogs: string[] = [];
 12 | const screenshots = new Map<string, string>();
 13 | const defaultDownloadsPath = path.join(os.homedir(), 'Downloads');
 14 | 
 15 | async function ensureBrowser() {
 16 |   if (!browser) {
 17 |     browser = await chromium.launch({ headless: false });
 18 |     const context = await browser.newContext({
 19 |       viewport: { width: 1920, height: 1080 },
 20 |       deviceScaleFactor: 1,
 21 |     });
 22 | 
 23 |     page = await context.newPage();
 24 | 
 25 |     page.on("console", (msg) => {
 26 |       const logEntry = `[${msg.type()}] ${msg.text()}`;
 27 |       consoleLogs.push(logEntry);
 28 |       // Note: server.notification is assumed to be passed in from the main server
 29 |     });
 30 |   }
 31 |   return page!;
 32 | }
 33 | 
 34 | async function ensureApiContext(url: string) {
 35 |   return await request.newContext({
 36 |     baseURL: url,
 37 |   });
 38 | }
 39 | 
 40 | export async function handleToolCall(
 41 |   name: string,
 42 |   args: any,
 43 |   server: any
 44 | ): Promise<{ toolResult: CallToolResult }> {
 45 |   // Check if the tool requires browser interaction
 46 |   const requiresBrowser = BROWSER_TOOLS.includes(name);
 47 |   // Check if the tool requires api interaction
 48 |   const requiresApi = API_TOOLS.includes(name);
 49 |   let page: Page | undefined;
 50 |   let apiContext: APIRequestContext;
 51 | 
 52 |   // Only launch browser if the tool requires browser interaction
 53 |   if (requiresBrowser) {
 54 |     page = await ensureBrowser();
 55 |   }
 56 | 
 57 |   // Set up API context for API-related operations
 58 |   if (requiresApi) {
 59 |     apiContext = await ensureApiContext(args.url);
 60 |   }
 61 | 
 62 |   switch (name) {
 63 |     case "playwright_navigate":
 64 |       try {
 65 |         await page!.goto(args.url, {
 66 |           timeout: args.timeout || 30000,
 67 |           waitUntil: args.waitUntil || "load"
 68 |         });
 69 |         return {
 70 |           toolResult: {
 71 |             content: [{
 72 |               type: "text",
 73 |               text: `Navigated to ${args.url} with ${args.waitUntil || "load"} wait`,
 74 |             }],
 75 |             isError: false,
 76 |           },
 77 |         };
 78 |       } catch (error) {
 79 |         return {
 80 |           toolResult: {
 81 |             content: [{
 82 |               type: "text",
 83 |               text: `Navigation failed: ${(error as Error).message}`,
 84 |             }],
 85 |             isError: true,
 86 |           },
 87 |         };
 88 |       }
 89 | 
 90 |     case "playwright_screenshot": {
 91 |       try {
 92 |         const screenshotOptions: any = {
 93 |           type: args.type || "png",
 94 |           fullPage: !!args.fullPage
 95 |         };
 96 | 
 97 |         if (args.selector) {
 98 |           const element = await page!.$(args.selector);
 99 |           if (!element) {
100 |             return {
101 |               toolResult: {
102 |                 content: [{
103 |                   type: "text",
104 |                   text: `Element not found: ${args.selector}`,
105 |                 }],
106 |                 isError: true,
107 |               },
108 |             };
109 |           }
110 |           screenshotOptions.element = element;
111 |         }
112 | 
113 |         if (args.mask) {
114 |           screenshotOptions.mask = await Promise.all(
115 |             args.mask.map(async (selector: string) => await page!.$(selector))
116 |           );
117 |         }
118 | 
119 |         const screenshot = await page!.screenshot(screenshotOptions);
120 |         const base64Screenshot = screenshot.toString('base64');
121 | 
122 |         const responseContent: (TextContent | ImageContent)[] = [];
123 | 
124 |         // Handle PNG file saving
125 |         if (args.savePng !== false) {
126 |           const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
127 |           const filename = `${args.name}-${timestamp}.png`;
128 |           const downloadsDir = args.downloadsDir || defaultDownloadsPath;
129 | 
130 |           // Create downloads directory if it doesn't exist
131 |           if (!fs.existsSync(downloadsDir)) {
132 |             fs.mkdirSync(downloadsDir, { recursive: true });
133 |           }
134 | 
135 |           const filePath = path.join(downloadsDir, filename);
136 |           await fs.promises.writeFile(filePath, screenshot);
137 |           responseContent.push({
138 |             type: "text",
139 |             text: `Screenshot saved to: ${filePath}`,
140 |           } as TextContent);
141 |         }
142 | 
143 |         // Handle base64 storage
144 |         if (args.storeBase64 !== false) {
145 |           screenshots.set(args.name, base64Screenshot);
146 |           server.notification({
147 |             method: "notifications/resources/list_changed",
148 |           });
149 | 
150 |           responseContent.push({
151 |             type: "image",
152 |             data: base64Screenshot,
153 |             mimeType: "image/png",
154 |           } as ImageContent);
155 |         }
156 | 
157 |         return {
158 |           toolResult: {
159 |             content: responseContent,
160 |             isError: false,
161 |           },
162 |         };
163 |       } catch (error) {
164 |         return {
165 |           toolResult: {
166 |             content: [{
167 |               type: "text",
168 |               text: `Screenshot failed: ${(error as Error).message}`,
169 |             }],
170 |             isError: true,
171 |           },
172 |         };
173 |       }
174 |     }
175 |     case "playwright_click":
176 |       try {
177 |         await page!.click(args.selector);
178 |         return {
179 |           toolResult: {
180 |             content: [{
181 |               type: "text",
182 |               text: `Clicked: ${args.selector}`,
183 |             }],
184 |             isError: false,
185 |           },
186 |         };
187 |       } catch (error) {
188 |         return {
189 |           toolResult: {
190 |             content: [{
191 |               type: "text",
192 |               text: `Failed to click ${args.selector}: ${(error as Error).message}`,
193 |             }],
194 |             isError: true,
195 |           },
196 |         };
197 |       }
198 | 
199 |     case "playwright_fill":
200 |       try {
201 |         await page!.waitForSelector(args.selector);
202 |         await page!.fill(args.selector, args.value);
203 |         return {
204 |           toolResult: {
205 |             content: [{
206 |               type: "text",
207 |               text: `Filled ${args.selector} with: ${args.value}`,
208 |             }],
209 |             isError: false,
210 |           },
211 |         };
212 |       } catch (error) {
213 |         return {
214 |           toolResult: {
215 |             content: [{
216 |               type: "text",
217 |               text: `Failed to type ${args.selector}: ${(error as Error).message}`,
218 |             }],
219 |             isError: true,
220 |           },
221 |         };
222 |       }
223 | 
224 |     case "playwright_select":
225 |       try {
226 |         await page!.waitForSelector(args.selector);
227 |         await page!.selectOption(args.selector, args.value);
228 |         return {
229 |           toolResult: {
230 |             content: [{
231 |               type: "text",
232 |               text: `Selected ${args.selector} with: ${args.value}`,
233 |             }],
234 |             isError: false,
235 |           },
236 |         };
237 |       } catch (error) {
238 |         return {
239 |           toolResult: {
240 |             content: [{
241 |               type: "text",
242 |               text: `Failed to select ${args.selector}: ${(error as Error).message}`,
243 |             }],
244 |             isError: true,
245 |           },
246 |         };
247 |       }
248 | 
249 |     case "playwright_hover":
250 |       try {
251 |         await page!.waitForSelector(args.selector);
252 |         await page!.hover(args.selector);
253 |         return {
254 |           toolResult: {
255 |             content: [{
256 |               type: "text",
257 |               text: `Hovered ${args.selector}`,
258 |             }],
259 |             isError: false,
260 |           },
261 |         };
262 |       } catch (error) {
263 |         return {
264 |           toolResult: {
265 |             content: [{
266 |               type: "text",
267 |               text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
268 |             }],
269 |             isError: true,
270 |           },
271 |         };
272 |       }
273 | 
274 |     case "playwright_evaluate":
275 |       try {
276 |         const result = await page!.evaluate((script) => {
277 |           const logs: string[] = [];
278 |           const originalConsole = { ...console };
279 | 
280 |           ['log', 'info', 'warn', 'error'].forEach(method => {
281 |             (console as any)[method] = (...args: any[]) => {
282 |               logs.push(`[${method}] ${args.join(' ')}`);
283 |               (originalConsole as any)[method](...args);
284 |             };
285 |           });
286 | 
287 |           try {
288 |             const result = eval(script);
289 |             Object.assign(console, originalConsole);
290 |             return { result, logs };
291 |           } catch (error) {
292 |             Object.assign(console, originalConsole);
293 |             throw error;
294 |           }
295 |         }, args.script);
296 | 
297 |         return {
298 |           toolResult: {
299 |             content: [
300 |               {
301 |                 type: "text",
302 |                 text: `Execution result:\n${JSON.stringify(result.result, null, 2)}\n\nConsole output:\n${result.logs.join('\n')}`,
303 |               },
304 |             ],
305 |             isError: false,
306 |           },
307 |         };
308 |       } catch (error) {
309 |         return {
310 |           toolResult: {
311 |             content: [{
312 |               type: "text",
313 |               text: `Script execution failed: ${(error as Error).message}`,
314 |             }],
315 |             isError: true,
316 |           },
317 |         };
318 |       }
319 | 
320 |     case "playwright_get":
321 |       try {
322 |         var response = await apiContext!.get(args.url);
323 | 
324 |         return {
325 |           toolResult: {
326 |             content: [{
327 |               type: "text",
328 |               text: `Performed GET Operation ${args.url}`,
329 |             },
330 |             {
331 |               type: "text",
332 |               text: `Response: ${JSON.stringify(await response.json(), null, 2)}`,
333 |             },
334 |             {
335 |               type: "text",
336 |               text: `Response code ${response.status()}`
337 |             }
338 |             ],
339 |             isError: false,
340 |           },
341 |         };
342 |       } catch (error) {
343 |         return {
344 |           toolResult: {
345 |             content: [{
346 |               type: "text",
347 |               text: `Failed to perform GET operation on ${args.url}: ${(error as Error).message}`,
348 |             }],
349 |             isError: true,
350 |           },
351 |         };
352 |       }
353 | 
354 |     case "playwright_post":
355 |       try {
356 |         var data = {
357 |           data: args.value,
358 |           headers: {
359 |             'Content-Type': 'application/json'
360 |           }
361 |         };
362 | 
363 |         var response = await apiContext!.post(args.url, data);
364 |         return {
365 |           toolResult: {
366 |             content: [{
367 |               type: "text",
368 |               text: `Performed POST Operation ${args.url} with data ${JSON.stringify(args.value, null, 2)}`,
369 |             },
370 |             {
371 |               type: "text",
372 |               text: `Response: ${JSON.stringify(await response.json(), null, 2)}`,
373 |             },
374 |             {
375 |               type: "text",
376 |               text: `Response code ${response.status()}`
377 |             }],
378 |             isError: false,
379 |           },
380 |         };
381 |       } catch (error) {
382 |         return {
383 |           toolResult: {
384 |             content: [{
385 |               type: "text",
386 |               text: `Failed to perform POST operation on ${args.url}: ${(error as Error).message}`,
387 |             }],
388 |             isError: true,
389 |           },
390 |         };
391 |       }
392 | 
393 |     case "playwright_put":
394 |       try {
395 |         var data = {
396 |           data: args.value,
397 |           headers: {
398 |             'Content-Type': 'application/json'
399 |           }
400 |         };
401 |         var response = await apiContext!.put(args.url, data);
402 | 
403 |         return {
404 |           toolResult: {
405 |             content: [{
406 |               type: "text",
407 |               text: `Performed PUT Operation ${args.url} with data ${JSON.stringify(args.value, null, 2)}`,
408 |             }, {
409 |               type: "text",
410 |               text: `Response: ${JSON.stringify(await response.json(), null, 2)}`,
411 |             },
412 |             {
413 |               type: "text",
414 |               text: `Response code ${response.status()}`
415 |             }],
416 |             isError: false,
417 |           },
418 |         };
419 |       } catch (error) {
420 |         return {
421 |           toolResult: {
422 |             content: [{
423 |               type: "text",
424 |               text: `Failed to perform PUT operation on ${args.url}: ${(error as Error).message}`,
425 |             }],
426 |             isError: true,
427 |           },
428 |         };
429 |       }
430 | 
431 |     case "playwright_delete":
432 |       try {
433 |         var response = await apiContext!.delete(args.url);
434 | 
435 |         return {
436 |           toolResult: {
437 |             content: [{
438 |               type: "text",
439 |               text: `Performed delete Operation ${args.url}`,
440 |             },
441 |             {
442 |               type: "text",
443 |               text: `Response code ${response.status()}`
444 |             }],
445 |             isError: false,
446 |           },
447 |         };
448 |       } catch (error) {
449 |         return {
450 |           toolResult: {
451 |             content: [{
452 |               type: "text",
453 |               text: `Failed to perform delete operation on ${args.url}: ${(error as Error).message}`,
454 |             }],
455 |             isError: true,
456 |           },
457 |         };
458 |       }
459 | 
460 |     case "playwright_patch":
461 |       try {
462 |         var data = {
463 |           data: args.value,
464 |           headers: {
465 |             'Content-Type': 'application/json'
466 |           }
467 |         };
468 |         var response = await apiContext!.patch(args.url, data);
469 | 
470 |         return {
471 |           toolResult: {
472 |             content: [{
473 |               type: "text",
474 |               text: `Performed PATCH Operation ${args.url} with data ${JSON.stringify(args.value, null, 2)}`,
475 |             }, {
476 |               type: "text",
477 |               text: `Response: ${JSON.stringify(await response.json(), null, 2)}`,
478 |             }, {
479 |               type: "text",
480 |               text: `Response code ${response.status()}`
481 |             }],
482 |             isError: false,
483 |           },
484 |         };
485 |       } catch (error) {
486 |         return {
487 |           toolResult: {
488 |             content: [{
489 |               type: "text",
490 |               text: `Failed to perform PATCH operation on ${args.url}: ${(error as Error).message}`,
491 |             }],
492 |             isError: true,
493 |           },
494 |         };
495 |       }
496 | 
497 |     default:
498 |       return {
499 |         toolResult: {
500 |           content: [{
501 |             type: "text",
502 |             text: `Unknown tool: ${name}`,
503 |           }],
504 |           isError: true,
505 |         },
506 |       };
507 |   }
508 | }
509 | 
510 | // Expose utility functions for resource management
511 | export function getConsoleLogs(): string[] {
512 |   return consoleLogs;
513 | }
514 | 
515 | export function getScreenshots(): Map<string, string> {
516 |   return screenshots;
517 | }
```