UNPKG

camoufox-mcp-server

Version:

MCP server for browser automation using Camoufox - a privacy-focused Firefox fork with advanced anti-detection features

170 lines (169 loc) 9.04 kB
import { DEFAULT_MAX_ELEMENTS, MAX_SCREENSHOT_HEIGHT, MAX_SCREENSHOT_WIDTH } from "./config.js"; import { runBrowserOperation, runGuardedPageRead } from "./browser-runtime.js"; import { maybeDetectCaptcha } from "./captcha.js"; import { buildFindPayload, buildFormsPayload, buildLinksPayload, buildNetworkSummary, buildOutlinePayload } from "./extractors.js"; import { buildSuccessContent, buildToolError, buildToolFailure } from "./responses.js"; import { captureScreenshot, isScreenshotDimensionAllowed } from "./screenshots.js"; import { applyStealthProfile, redactUrl } from "./utils.js"; import { appendDiagnostics } from "./diagnostics.js"; export async function handleLinks(input) { const effectiveInput = applyStealthProfile(input); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse links", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { const payload = await runGuardedPageRead(page, requestGuard, () => buildLinksPayload(page, response, effectiveInput.maxLinks ?? DEFAULT_MAX_ELEMENTS, effectiveInput.selector)); requestGuard.assertAllowed(); appendDiagnostics(payload, diagnostics.payload()); if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse links", safeUrl, error, effectiveInput); } } export async function handleForms(input) { const effectiveInput = applyStealthProfile(input); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse forms", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { const payload = await runGuardedPageRead(page, requestGuard, () => buildFormsPayload(page, response, effectiveInput.maxForms ?? 20, effectiveInput.maxFields ?? DEFAULT_MAX_ELEMENTS, effectiveInput.selector)); requestGuard.assertAllowed(); appendDiagnostics(payload, diagnostics.payload()); if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse forms", safeUrl, error, effectiveInput); } } export async function handleOutline(input) { const effectiveInput = applyStealthProfile(input); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse outline", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { const payload = await runGuardedPageRead(page, requestGuard, () => buildOutlinePayload(page, response, effectiveInput.maxItems ?? DEFAULT_MAX_ELEMENTS, effectiveInput.selector)); requestGuard.assertAllowed(); appendDiagnostics(payload, diagnostics.payload()); if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse outline", safeUrl, error, effectiveInput); } } export async function handleFind(input) { const effectiveInput = applyStealthProfile(input); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse find", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { const payload = await runGuardedPageRead(page, requestGuard, () => buildFindPayload(page, response, effectiveInput.query, effectiveInput.maxMatches ?? 5, effectiveInput.contextChars ?? 300, effectiveInput.selector)); requestGuard.assertAllowed(); appendDiagnostics(payload, diagnostics.payload()); if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse find", safeUrl, error, effectiveInput); } } export async function handleScreenshot(input) { const effectiveInput = applyStealthProfile(input); const safeUrl = redactUrl(effectiveInput.url); if (!isScreenshotDimensionAllowed(effectiveInput.viewport, effectiveInput.window)) { return buildToolError(`Screenshot dimensions exceed server policy (${MAX_SCREENSHOT_WIDTH}x${MAX_SCREENSHOT_HEIGHT}).`); } try { return await runBrowserOperation("browse screenshot", effectiveInput, async ({ page, response, requestGuard, }) => { const screenshotResult = await captureScreenshot(page, safeUrl, { fullPage: effectiveInput.fullPage, selector: effectiveInput.selector, type: effectiveInput.type, quality: effectiveInput.quality, }); requestGuard.assertAllowed(); const payload = { url: redactUrl(page.url()), title: await page.title(), status: response?.status(), contentType: response?.headers()["content-type"], screenshot: screenshotResult.screenshotMetadata, }; if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, screenshotResult ?? captchaScreenshot); } return buildSuccessContent(payload, screenshotResult); }); } catch (error) { return buildToolFailure("browse screenshot", safeUrl, error, effectiveInput); } } export async function handleConsole(input) { const effectiveInput = applyStealthProfile({ ...input, includeConsole: true, includeNetwork: false, }); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse console", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { await runGuardedPageRead(page, requestGuard, () => page.title()); requestGuard.assertAllowed(); const payload = { url: redactUrl(page.url()), title: await page.title(), status: response?.status(), contentType: response?.headers()["content-type"], console: diagnostics.payload()?.console ?? [], consoleTruncated: diagnostics.payload()?.consoleTruncated ?? false, }; if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse console", safeUrl, error, effectiveInput); } } export async function handleNetworkSummary(input) { const effectiveInput = applyStealthProfile({ ...input, includeConsole: false, includeNetwork: true, }); const safeUrl = redactUrl(effectiveInput.url); try { return await runBrowserOperation("browse network summary", effectiveInput, async ({ page, response, requestGuard, diagnostics, }) => { const payload = await runGuardedPageRead(page, requestGuard, () => buildNetworkSummary(page, response, diagnostics.payload(), effectiveInput.maxFailures ?? 10)); requestGuard.assertAllowed(); if (effectiveInput.captchaPolicy) { const { mergedPayload, captchaScreenshot } = await maybeDetectCaptcha(page, response, payload, effectiveInput.captchaPolicy, safeUrl); return buildSuccessContent(mergedPayload, captchaScreenshot); } return buildSuccessContent(payload); }); } catch (error) { return buildToolFailure("browse network summary", safeUrl, error, effectiveInput); } }