#!/usr/bin/env node
/**
* 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.
*/
// Patch the MCP Server class to intercept tools/list responses for filtering
// This must happen BEFORE any Playwright MCP modules are loaded
const fs = require('fs');
const path = require('path');
// Usage tracking file path
const usageFilePath = path.join(process.cwd(), 'usage.txt');
// Load existing usage data or initialize empty object
function loadUsageData() {
try {
if (fs.existsSync(usageFilePath)) {
const content = fs.readFileSync(usageFilePath, 'utf8');
const usage = {};
for (const line of content.split('\n')) {
const match = line.match(/^(.+):\s*(\d+)$/);
if (match) {
usage[match[1]] = parseInt(match[2], 10);
}
}
return usage;
}
} catch (e) {
// If file doesn't exist or is malformed, start fresh
}
return {};
}
// Save usage data to file
function saveUsageData(usage) {
const lines = Object.entries(usage)
.sort(([a], [b]) => a.localeCompare(b))
.map(([tool, count]) => `${tool}: ${count}`);
fs.writeFileSync(usageFilePath, lines.join('\n') + '\n');
}
// Track a tool call
function trackToolUsage(toolName) {
const usage = loadUsageData();
usage[toolName] = (usage[toolName] || 0) + 1;
saveUsageData(usage);
}
// Parse --config argument to get tools config before the rest of the code runs
function parseToolsConfig() {
const configArgIndex = process.argv.findIndex(arg => arg === '--config' || arg.startsWith('--config='));
if (configArgIndex === -1) return null;
let configPath;
if (process.argv[configArgIndex].startsWith('--config=')) {
configPath = process.argv[configArgIndex].split('=')[1];
} else {
configPath = process.argv[configArgIndex + 1];
}
if (!configPath) return null;
// Resolve relative paths
if (!path.isAbsolute(configPath)) {
configPath = path.resolve(process.cwd(), configPath);
}
try {
const configContent = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(configContent);
return config.tools || null;
} catch (e) {
return null;
}
}
const toolsConfig = parseToolsConfig();
// Patch the Server prototype BEFORE any modules use it
const mcpBundle = require('playwright/lib/mcp/sdk/bundle');
const ServerClass = mcpBundle.Server;
const originalSetRequestHandler = ServerClass.prototype.setRequestHandler;
ServerClass.prototype.setRequestHandler = function(schema, handler) {
// Intercept CallToolRequestSchema to track tool usage
if (schema === mcpBundle.CallToolRequestSchema) {
const originalHandler = handler;
const wrappedHandler = async (request, extra) => {
const toolName = request.params?.name;
if (toolName) {
trackToolUsage(toolName);
}
return originalHandler.call(this, request, extra);
};
return originalSetRequestHandler.call(this, schema, wrappedHandler);
}
// Filter tools list if config is provided
if (toolsConfig && schema === mcpBundle.ListToolsRequestSchema) {
const originalHandler = handler;
const wrappedHandler = async (request, extra) => {
const result = await originalHandler.call(this, request, extra);
if (result?.tools) {
if (toolsConfig.include) {
const includeSet = new Set(toolsConfig.include);
result.tools = result.tools.filter(t => includeSet.has(t.name));
} else if (toolsConfig.exclude) {
const excludeSet = new Set(toolsConfig.exclude);
result.tools = result.tools.filter(t => !excludeSet.has(t.name));
}
}
return result;
};
return originalSetRequestHandler.call(this, schema, wrappedHandler);
}
return originalSetRequestHandler.call(this, schema, handler);
};
// NOW we can load the rest of the modules
const { program } = require('playwright-core/lib/utilsBundle');
const { decorateCommand } = require('playwright/lib/mcp/program');
const packageJSON = require('./package.json');
const p = program.version('Version ' + packageJSON.version).name('Playwright MCP');
decorateCommand(p, packageJSON.version)
void program.parseAsync(process.argv);