@truefrontier/puppeteer-laravel-mcp-server
Version:
A Model Context Protocol server for Laravel Herd browser automation using Puppeteer
211 lines • 8.5 kB
JavaScript
import { serverConfig } from '../core/config.js';
import { writeFileSync } from 'fs';
import { join } from 'path';
export class PuppeteerTools {
instance;
page;
debugInfo = [];
constructor(instance, page) {
this.instance = instance;
this.page = page;
}
addDebugInfo(message, level = 'info', context) {
this.debugInfo.push({
timestamp: new Date(),
message,
level,
context,
});
}
async addVisualDebug(message, type = 'info') {
if (!serverConfig.visualDebug)
return;
const colors = {
info: '#2196F3',
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336',
};
await this.page.evaluate((debugMessage, color) => {
const debugContainer = document.getElementById('claude-debug-container') || (() => {
const container = document.createElement('div');
container.id = 'claude-debug-container';
container.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
max-width: 300px;
z-index: 999999;
font-family: monospace;
font-size: 12px;
`;
document.body.appendChild(container);
return container;
})();
const debugElement = document.createElement('div');
debugElement.style.cssText = `
background: ${color};
color: white;
padding: 8px;
margin: 2px 0;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
opacity: 0.9;
`;
debugElement.textContent = `${new Date().toLocaleTimeString()}: ${debugMessage}`;
debugContainer.insertBefore(debugElement, debugContainer.firstChild);
// Keep only last 5 debug messages
while (debugContainer.children.length > 5) {
debugContainer.removeChild(debugContainer.lastChild);
}
}, message, colors[type]);
}
async navigate(url, options = {}) {
this.addDebugInfo(`Navigating to: ${url}`, 'info', { url, options });
await this.addVisualDebug(`Navigating to: ${url}`, 'info');
try {
// Handle Laravel Herd domain resolution
if (!url.startsWith('http')) {
url = `${serverConfig.sslEnabled ? 'https' : 'http'}://${url}${serverConfig.defaultDomainSuffix}`;
}
if (options.viewport) {
await this.page.setViewport(options.viewport);
}
await this.page.goto(url, {
waitUntil: options.waitUntil || 'networkidle2',
timeout: options.timeout || serverConfig.browserTimeout,
});
this.addDebugInfo(`Successfully navigated to: ${url}`, 'info');
await this.addVisualDebug(`Navigation complete: ${url}`, 'success');
}
catch (error) {
this.addDebugInfo(`Navigation failed: ${error}`, 'error', { url, error });
await this.addVisualDebug(`Navigation failed: ${error}`, 'error');
throw error;
}
}
async screenshot(options = {}) {
this.addDebugInfo('Taking screenshot', 'info', options);
await this.addVisualDebug('Taking screenshot', 'info');
try {
const screenshotBuffer = await this.page.screenshot({
type: options.type || 'png',
quality: options.quality,
fullPage: options.fullPage !== false,
clip: options.clip,
});
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = options.path || `screenshot-${timestamp}.png`;
const filepath = join(process.cwd(), 'screenshots', filename);
// Ensure screenshots directory exists
const { existsSync, mkdirSync } = await import('fs');
const screenshotDir = join(process.cwd(), 'screenshots');
if (!existsSync(screenshotDir)) {
mkdirSync(screenshotDir, { recursive: true });
}
writeFileSync(filepath, screenshotBuffer);
this.addDebugInfo(`Screenshot saved: ${filepath}`, 'info');
await this.addVisualDebug('Screenshot captured', 'success');
return filepath;
}
catch (error) {
this.addDebugInfo(`Screenshot failed: ${error}`, 'error', { error });
await this.addVisualDebug(`Screenshot failed: ${error}`, 'error');
throw error;
}
}
async interact(selector, action, value, options = {}) {
this.addDebugInfo(`Interacting: ${action} on ${selector}`, 'info', { selector, action, value, options });
await this.addVisualDebug(`${action} on ${selector}`, 'info');
try {
const element = await this.page.waitForSelector(selector, {
timeout: options.timeout || 10000,
visible: true,
});
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
switch (action) {
case 'click':
await element.click();
break;
case 'type':
if (value === undefined) {
throw new Error('Value required for type action');
}
await element.type(value);
break;
case 'select':
if (value === undefined) {
throw new Error('Value required for select action');
}
await this.page.select(selector, value);
break;
case 'scroll':
await element.scrollIntoView();
break;
default:
throw new Error(`Unknown action: ${action}`);
}
this.addDebugInfo(`Successfully performed ${action} on ${selector}`, 'info');
await this.addVisualDebug(`${action} completed`, 'success');
}
catch (error) {
this.addDebugInfo(`Interaction failed: ${error}`, 'error', { selector, action, error });
await this.addVisualDebug(`${action} failed: ${error}`, 'error');
throw error;
}
}
async execute(script, ...args) {
this.addDebugInfo('Executing JavaScript', 'info', { script: script.substring(0, 100) + '...' });
await this.addVisualDebug('Executing JavaScript', 'info');
try {
const result = await this.page.evaluate(script, ...args);
this.addDebugInfo('JavaScript executed successfully', 'info');
await this.addVisualDebug('JavaScript executed', 'success');
return result;
}
catch (error) {
this.addDebugInfo(`JavaScript execution failed: ${error}`, 'error', { script, error });
await this.addVisualDebug(`JavaScript failed: ${error}`, 'error');
throw error;
}
}
async waitFor(selector, options = {}) {
this.addDebugInfo(`Waiting for: ${selector}`, 'info', { selector, options });
await this.addVisualDebug(`Waiting for: ${selector}`, 'info');
try {
await this.page.waitForSelector(selector, {
timeout: options.timeout || 10000,
visible: options.visible,
hidden: options.hidden,
});
this.addDebugInfo(`Element found: ${selector}`, 'info');
await this.addVisualDebug(`Element ready: ${selector}`, 'success');
}
catch (error) {
this.addDebugInfo(`Wait failed: ${error}`, 'error', { selector, error });
await this.addVisualDebug(`Wait timeout: ${selector}`, 'error');
throw error;
}
}
async getPageInfo() {
const title = await this.page.title();
const url = this.page.url();
const viewport = this.page.viewport();
return {
title,
url,
viewport,
debugInfo: this.debugInfo,
timestamp: new Date(),
};
}
getDebugInfo() {
return [...this.debugInfo];
}
clearDebugInfo() {
this.debugInfo = [];
}
}
//# sourceMappingURL=puppeteer-tools.js.map