cloudflare-browser-rendering-mcp
by amotivv
Verified
#!/usr/bin/env node
import { spawn } from 'child_process';
import readline from 'readline';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* Test script for the Cloudflare Browser Rendering MCP server
*
* This script tests the MCP server by sending test requests for each tool
* and verifying the responses.
*/
// Configuration
const SERVER_PATH = path.join(__dirname, 'dist', 'index.js');
const TEST_URL = 'https://developers.cloudflare.com/browser-rendering/';
const TEST_QUERY = 'browser rendering api';
const TEST_SELECTORS = {
heading: 'h1',
description: '.DocSearch-content p:first-of-type'
};
// Colors for console output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
};
/**
* Log a message with a prefix and color
*/
function log(prefix, message, color = colors.reset) {
console.log(`${color}[${prefix}]${colors.reset} ${message}`);
}
/**
* Log a success message
*/
function success(message) {
log('SUCCESS', message, colors.green);
}
/**
* Log an error message
*/
function error(message) {
log('ERROR', message, colors.red);
}
/**
* Log an info message
*/
function info(message) {
log('INFO', message, colors.blue);
}
/**
* Log a warning message
*/
function warn(message) {
log('WARNING', message, colors.yellow);
}
/**
* Check if the server path exists
*/
function checkServerPath() {
if (!fs.existsSync(SERVER_PATH)) {
error(`Server not found at ${SERVER_PATH}`);
error('Make sure you have built the project with "npm run build"');
process.exit(1);
}
success(`Server found at ${SERVER_PATH}`);
}
/**
* Start the MCP server process
*/
function startServer() {
info('Starting MCP server...');
// Set the BROWSER_RENDERING_API environment variable if not already set
if (!process.env.BROWSER_RENDERING_API) {
warn('BROWSER_RENDERING_API environment variable not set');
warn('Some tests may fail if the Cloudflare Worker is not configured');
} else {
info(`Using Cloudflare Worker at: ${process.env.BROWSER_RENDERING_API}`);
}
// Start the server process
const serverProcess = spawn('node', [SERVER_PATH], {
stdio: ['pipe', 'pipe', 'pipe']
});
// Handle server process events
serverProcess.on('error', (err) => {
error(`Failed to start server: ${err.message}`);
process.exit(1);
});
// Show all server output for better debugging
serverProcess.stderr.on('data', (data) => {
const logLines = data.toString().trim().split('\n');
for (const logLine of logLines) {
if (logLine.trim()) {
if (logLine.includes('[Error]')) {
console.error(`${colors.red}[Server] ${logLine}${colors.reset}`);
} else if (logLine.includes('[Warning]') || logLine.includes('[Warn]')) {
console.error(`${colors.yellow}[Server] ${logLine}${colors.reset}`);
} else if (logLine.includes('[Setup]')) {
console.error(`${colors.cyan}[Server] ${logLine}${colors.reset}`);
} else if (logLine.includes('[API]')) {
console.error(`${colors.blue}[Server] ${logLine}${colors.reset}`);
} else {
console.error(`${colors.dim}[Server] ${logLine}${colors.reset}`);
}
}
}
});
// Create readline interface for stdin/stdout
const rl = readline.createInterface({
input: serverProcess.stdout,
output: serverProcess.stdin,
terminal: false
});
success('MCP server started');
return { serverProcess, rl };
}
/**
* Send a request to the MCP server
*/
async function sendRequest(rl, request) {
return new Promise((resolve) => {
// Debug the request
info(`Sending request: ${request.method} (ID: ${request.id})`);
// Listen for the response
const responseHandler = (line) => {
try {
// Try to parse the response as JSON
const response = JSON.parse(line);
// Check if this is a response to our request
if (response.id === request.id) {
// Debug the response
if (response.error) {
error(`Received error response: ${JSON.stringify(response.error)}`);
} else {
info(`Received successful response for ID: ${response.id}`);
}
rl.removeListener('line', responseHandler);
resolve(response);
}
} catch (err) {
// Log non-JSON lines for debugging
if (line.trim()) {
warn(`Received non-JSON line: ${line.substring(0, 100)}${line.length > 100 ? '...' : ''}`);
}
}
};
rl.on('line', responseHandler);
// Send the request with proper JSON-RPC 2.0 format
rl.output.write(JSON.stringify(request) + '\n');
});
}
/**
* Test the fetch_page tool
*/
async function testFetchPage(rl) {
info('Testing fetch_page tool...');
const request = {
jsonrpc: "2.0",
id: 'test-fetch-page',
method: 'tools/call',
params: {
name: 'fetch_page',
arguments: {
url: TEST_URL,
maxContentLength: 1000
}
}
};
try {
const response = await sendRequest(rl, request);
// Check for JSON-RPC 2.0 response format
if (response.jsonrpc === "2.0" && response.result && response.result.content && response.result.content[0].text) {
success('fetch_page tool test passed');
return true;
} else {
error('fetch_page tool test failed: Invalid response format');
console.error(JSON.stringify(response, null, 2));
return false;
}
} catch (err) {
error(`fetch_page tool test failed: ${err.message}`);
return false;
}
}
/**
* Test the search_documentation tool
*/
async function testSearchDocumentation(rl) {
info('Testing search_documentation tool...');
const request = {
jsonrpc: "2.0",
id: 'test-search-documentation',
method: 'tools/call',
params: {
name: 'search_documentation',
arguments: {
query: TEST_QUERY,
maxResults: 2
}
}
};
try {
const response = await sendRequest(rl, request);
// Check for JSON-RPC 2.0 response format
if (response.jsonrpc === "2.0" && response.result && response.result.content && response.result.content[0].text) {
success('search_documentation tool test passed');
return true;
} else {
error('search_documentation tool test failed: Invalid response format');
console.error(JSON.stringify(response, null, 2));
return false;
}
} catch (err) {
error(`search_documentation tool test failed: ${err.message}`);
return false;
}
}
/**
* Test the extract_structured_content tool
*/
async function testExtractStructuredContent(rl) {
info('Testing extract_structured_content tool...');
const request = {
jsonrpc: "2.0",
id: 'test-extract-structured-content',
method: 'tools/call',
params: {
name: 'extract_structured_content',
arguments: {
url: TEST_URL,
selectors: TEST_SELECTORS
}
}
};
try {
const response = await sendRequest(rl, request);
// Check for JSON-RPC 2.0 response format
if (response.jsonrpc === "2.0" && response.result && response.result.content && response.result.content[0].text) {
success('extract_structured_content tool test passed');
return true;
} else {
error('extract_structured_content tool test failed: Invalid response format');
console.error(JSON.stringify(response, null, 2));
return false;
}
} catch (err) {
error(`extract_structured_content tool test failed: ${err.message}`);
return false;
}
}
/**
* Test the summarize_content tool
*/
async function testSummarizeContent(rl) {
info('Testing summarize_content tool...');
const request = {
jsonrpc: "2.0",
id: 'test-summarize-content',
method: 'tools/call',
params: {
name: 'summarize_content',
arguments: {
url: TEST_URL,
maxLength: 300
}
}
};
try {
const response = await sendRequest(rl, request);
// Check for JSON-RPC 2.0 response format
if (response.jsonrpc === "2.0" && response.result && response.result.content && response.result.content[0].text) {
success('summarize_content tool test passed');
return true;
} else {
error('summarize_content tool test failed: Invalid response format');
console.error(JSON.stringify(response, null, 2));
return false;
}
} catch (err) {
error(`summarize_content tool test failed: ${err.message}`);
return false;
}
}
/**
* Test the take_screenshot tool
*/
async function testTakeScreenshot(rl) {
info('Testing take_screenshot tool...');
const request = {
jsonrpc: "2.0",
id: 'test-take-screenshot',
method: 'tools/call',
params: {
name: 'take_screenshot',
arguments: {
url: TEST_URL,
width: 1024,
height: 768,
fullPage: false
}
}
};
try {
const response = await sendRequest(rl, request);
// Check for JSON-RPC 2.0 response format
if (response.jsonrpc === "2.0" && response.result && response.result.content && response.result.content[0].text) {
success('take_screenshot tool test passed');
return true;
} else {
error('take_screenshot tool test failed: Invalid response format');
console.error(JSON.stringify(response, null, 2));
return false;
}
} catch (err) {
error(`take_screenshot tool test failed: ${err.message}`);
return false;
}
}
/**
* Run all tests
*/
async function runTests() {
// Check if the server path exists
checkServerPath();
// Start the server
const { serverProcess, rl } = startServer();
// Wait for the server to initialize
await new Promise(resolve => setTimeout(resolve, 1000));
// Run the tests
const results = {
fetchPage: await testFetchPage(rl),
searchDocumentation: await testSearchDocumentation(rl),
extractStructuredContent: await testExtractStructuredContent(rl),
summarizeContent: await testSummarizeContent(rl),
takeScreenshot: await testTakeScreenshot(rl)
};
// Print the test results
console.log('\n');
log('TEST RESULTS', '='.repeat(50), colors.bright);
for (const [test, passed] of Object.entries(results)) {
const status = passed ? `${colors.green}PASSED${colors.reset}` : `${colors.red}FAILED${colors.reset}`;
console.log(`${test}: ${status}`);
}
const totalTests = Object.keys(results).length;
const passedTests = Object.values(results).filter(Boolean).length;
console.log('\n');
log('SUMMARY', `${passedTests}/${totalTests} tests passed`,
passedTests === totalTests ? colors.green : colors.yellow);
// Terminate the server process
serverProcess.kill();
process.exit(passedTests === totalTests ? 0 : 1);
}
// Run the tests
runTests().catch(err => {
error(`Test runner error: ${err.message}`);
process.exit(1);
});