Skip to content
Closed
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ A Model Context Protocol (MCP) server that provides browser automation capabilit
- **Fast and lightweight**: Uses Playwright's accessibility tree, not pixel-based input.
- **LLM-friendly**: No vision models needed, operates purely on structured data.
- **Deterministic tool application**: Avoids ambiguity common with screenshot-based approaches.
- **API Mocking**: Intercept and mock API responses for testing and simulation.

### Use Cases

- Web navigation and form-filling
- Data extraction from structured content
- Automated testing driven by LLMs
- General-purpose browser interaction for agents
- API mocking and response simulation

### Example config

Expand Down Expand Up @@ -247,6 +249,21 @@ The Playwright MCP provides a set of tools for browser automation. Here are all
- Parameters:
- `time` (number): The time to wait in seconds (capped at 10 seconds)

- **browser_mock_api**
- Description: Mock API responses by intercepting network requests
- Parameters:
- `url` (string): URL pattern to match for interception (supports glob and regex patterns)
- `method` (string, optional): HTTP method to match (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, or ALL)
- `status` (number, default 200): HTTP status code to return
- `contentType` (string, default "application/json"): Content-Type header for the response
- `body` (string): Response body content (typically JSON formatted as a string)
- `headers` (object, optional): Additional response headers to include

- **browser_clear_mock**
- Description: Clear previously configured API mocks
- Parameters:
- `url` (string, optional): URL pattern to remove mocking for. If not provided, all mocks will be cleared

- **browser_close**
- Description: Close the page
- Parameters: None
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createServerWithTools } from './server';
import * as snapshot from './tools/snapshot';
import * as common from './tools/common';
import * as screenshot from './tools/screenshot';
import * as mock from './tools/mock';
import { console } from './resources/console';

import type { Tool } from './tools/tool';
Expand All @@ -44,6 +45,8 @@ const snapshotTools: Tool[] = [
snapshot.type,
snapshot.selectOption,
snapshot.screenshot,
mock.mockApi(true),
mock.clearMock(true),
...commonTools,
];

Expand All @@ -57,6 +60,8 @@ const screenshotTools: Tool[] = [
screenshot.click,
screenshot.drag,
screenshot.type,
mock.mockApi(false),
mock.clearMock(false),
...commonTools,
];

Expand Down
124 changes: 124 additions & 0 deletions src/tools/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

import { captureAriaSnapshot } from './utils';

import type { ToolFactory, Tool } from './tool';
import type * as playwright from 'playwright';

const mockApiSchema = z.object({
url: z.string().describe('URL pattern to match for interception (supports glob and regex patterns)'),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'ALL']).optional()
.describe('HTTP method to match (ALL matches any method)'),
status: z.number().min(100).max(599).default(200)
.describe('HTTP status code to return'),
contentType: z.string().default('application/json')
.describe('Content-Type header for the response'),
body: z.string().describe('Response body content (typically JSON formatted as a string)'),
headers: z.record(z.string()).optional()
.describe('Additional response headers to include'),
});

export const mockApi: ToolFactory = snapshot => ({
schema: {
name: 'browser_mock_api',
description: 'Mock API responses by intercepting network requests',
inputSchema: zodToJsonSchema(mockApiSchema),
},
handle: async (context, params) => {
const validatedParams = mockApiSchema.parse(params);
const page = context.existingPage();

// Generate a unique ID for this route handler
const routeId = `route_${Date.now()}_${Math.floor(Math.random() * 10000)}`;

// Create the mock response
const mockResponse = {
status: validatedParams.status,
headers: {
'Content-Type': validatedParams.contentType,
...(validatedParams.headers || {})
},
body: validatedParams.body
};

// Set up the route handler with the appropriate method filter if specified
if (validatedParams.method && validatedParams.method !== 'ALL') {
await page.route(validatedParams.url, (route: playwright.Route) => {
if (route.request().method() === validatedParams.method) {
route.fulfill(mockResponse);
} else {
route.continue();
}
}, { times: 0 });
} else {
await page.route(validatedParams.url, (route: playwright.Route) => {
route.fulfill(mockResponse);
}, { times: 0 });
}

if (snapshot) {
return captureAriaSnapshot(context);
}

return {
content: [{
type: 'text',
text: `API mock created for ${validatedParams.url}${validatedParams.method ? ` (${validatedParams.method})` : ''} with status ${validatedParams.status}`,
}],
};
},
});

const clearMockSchema = z.object({
url: z.string().optional().describe('URL pattern to remove mocking for. If not provided, all mocks will be cleared'),
});

export const clearMock: ToolFactory = snapshot => ({
schema: {
name: 'browser_clear_mock',
description: 'Clear previously configured API mocks',
inputSchema: zodToJsonSchema(clearMockSchema),
},
handle: async (context, params) => {
const validatedParams = clearMockSchema.parse(params);
const page = context.existingPage();

if (validatedParams.url) {
await page.unroute(validatedParams.url);
} else {
// Clear all routes - this is a bit of a hack as there's no direct "unroute all" method
// We use a pattern that matches all URLs and then unroute it
await page.unroute('**');
}

if (snapshot) {
return captureAriaSnapshot(context);
}

return {
content: [{
type: 'text',
text: validatedParams.url
? `Cleared API mock for ${validatedParams.url}`
: 'Cleared all API mocks',
}],
};
},
});