Skip to main content
Glama

Autoconsent MCP

by noisysocks
index.ts15.2 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, CallToolResult, TextContent, ImageContent, Tool, } from "@modelcontextprotocol/sdk/types.js"; import puppeteer, { Browser, Page } from "puppeteer"; import { searchHTML, printElement } from "./tools/dom"; import { testRule } from "./tools/test-rule"; import { resetBrowserData } from "./tools/browser-reset"; import type { AutoConsentCMPRule } from "@duckduckgo/autoconsent"; // Define the tools const TOOLS: Tool[] = [ { name: "navigate", description: "Navigate to any URL in the browser", inputSchema: { type: "object", properties: { url: { type: "string", description: "URL to navigate to" }, }, required: ["url"], }, }, { name: "reload", description: "Reload the current page", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "screenshot", description: "Capture screenshots of the entire page or specific elements", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name for the screenshot" }, width: { type: "number", description: "Width in pixels (default: 1280)", }, height: { type: "number", description: "Height in pixels (default: 720)", }, encoded: { type: "boolean", description: "If true, capture the screenshot as a base64-encoded data URI (as text) instead of binary image content. Default false.", }, }, required: ["name"], }, }, { name: "click", description: "Click elements on the page", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for element to click", }, }, required: ["selector"], }, }, { name: "select", description: "Select an element with SELECT tag", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector for element to select", }, value: { type: "string", description: "Value to select" }, }, required: ["selector", "value"], }, }, { name: "evaluate", description: "Execute JavaScript in the browser console", inputSchema: { type: "object", properties: { script: { type: "string", description: "JavaScript code to execute" }, }, required: ["script"], }, }, { name: "search_html", description: "Outputs the HTML of elements that deeply contain the given search query. Elements that don't contain the given query are omitted using a [...] placeholder.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query" }, }, required: ["query"], }, }, { name: "print_element", description: "Outputs the full HTML of the given element", inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector" }, }, required: ["selector"], }, }, { name: "test_rule", description: "Tests the given Autoconsent rule on the given URL", inputSchema: { type: "object", properties: { url: { type: "string", description: "URL to navigate to" }, rule: { type: "object", description: "Autoconsent rule (AutoConsentCMPRule)", }, }, required: ["url", "rule"], }, }, { name: "reset_browser_data", description: "Reset browser data including cookies, cache, localStorage, and sessionStorage", inputSchema: { type: "object", properties: { clearCookies: { type: "boolean", description: "Clear browser cookies (default: true)", }, clearCache: { type: "boolean", description: "Clear browser cache (default: true)", }, clearLocalStorage: { type: "boolean", description: "Clear localStorage (default: true)", }, clearSessionStorage: { type: "boolean", description: "Clear sessionStorage (default: true)", }, }, required: [], }, }, ]; // Global state let browser: Browser | null = null; let page: Page | null = null; const consoleLogs: string[] = []; const screenshots = new Map<string, string>(); async function ensureBrowser(): Promise<Page> { if (!browser || !browser.connected) { browser = await puppeteer.launch({ headless: false, defaultViewport: { width: 1280, height: 720 }, }); const pages = await browser.pages(); page = pages[0]; page.on("console", (msg) => { const logEntry = `[${msg.type()}] ${msg.text()}`; consoleLogs.push(logEntry); server.notification({ method: "notifications/resources/updated", params: { uri: "console://logs" }, }); }); } return page!; } async function handleToolCall( name: string, args: any, ): Promise<CallToolResult> { const page = await ensureBrowser(); switch (name) { case "navigate": await page.goto(args.url, { waitUntil: "domcontentloaded" }); return { content: [ { type: "text", text: `Navigated to ${args.url}`, }, ], isError: false, }; case "reload": await page.reload({ waitUntil: "domcontentloaded" }); const currentUrl = page.url(); return { content: [ { type: "text", text: `Reloaded page: ${currentUrl}`, }, ], isError: false, }; case "screenshot": { const width = args.width ?? 1280; const height = args.height ?? 720; const encoded = args.encoded ?? false; await page.setViewport({ width, height }); const screenshot = await page.screenshot({ encoding: "base64", fullPage: false, }); if (!screenshot) { return { content: [ { type: "text", text: "Screenshot failed", }, ], isError: true, }; } screenshots.set(args.name, screenshot as string); server.notification({ method: "notifications/resources/list_changed", }); return { content: [ { type: "text", text: `Screenshot '${args.name}' taken at ${width}x${height}`, } as TextContent, encoded ? ({ type: "text", text: `data:image/png;base64,${screenshot}`, } as TextContent) : ({ type: "image", data: screenshot, mimeType: "image/png", } as ImageContent), ], isError: false, }; } case "click": try { await page.click(args.selector); return { content: [ { type: "text", text: `Clicked: ${args.selector}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to click ${args.selector}: ${(error as Error).message}`, }, ], isError: true, }; } case "select": try { await page.waitForSelector(args.selector); await page.select(args.selector, args.value); return { content: [ { type: "text", text: `Selected ${args.selector} with: ${args.value}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to select ${args.selector}: ${(error as Error).message}`, }, ], isError: true, }; } case "evaluate": try { await page.evaluate(() => { (window as any).mcpHelper = { logs: [], originalConsole: { ...console }, }; ["log", "info", "warn", "error"].forEach((method) => { (console as any)[method] = (...args: any[]) => { (window as any).mcpHelper.logs.push( `[${method}] ${args.join(" ")}`, ); ((window as any).mcpHelper.originalConsole as any)[method]( ...args, ); }; }); }); const result = await page.evaluate(args.script); const logs = await page.evaluate(() => { Object.assign(console, (window as any).mcpHelper.originalConsole); const logs = (window as any).mcpHelper.logs; delete (window as any).mcpHelper; return logs; }); return { content: [ { type: "text", text: `Execution result:\n${JSON.stringify(result, null, 2)}\n\nConsole output:\n${logs.join("\n")}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Script execution failed: ${(error as Error).message}`, }, ], isError: true, }; } case "search_html": try { const html = await searchHTML(page, args.query); return { content: [ { type: "text", text: html, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to search HTML: ${(error as Error).message}`, }, ], isError: true, }; } case "print_element": try { const html = await printElement(page, args.selector); return { content: [ { type: "text", text: html, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to print element: ${(error as Error).message}`, }, ], isError: true, }; } case "test_rule": try { const rule = args.rule as AutoConsentCMPRule; const results = await testRule(page, args.url, rule); let output = `Test results for rule "${rule.name}" on ${args.url}:\n\n`; if (results.errors.length > 0) { output += `Errors:\n${results.errors.map((err) => JSON.stringify(err, null, 2)).join("\n")}\n\n`; } if (results.cmpDetectedMessage) { output += `CMP Detected: ${JSON.stringify(results.cmpDetectedMessage, null, 2)}\n\n`; } if (results.popupFoundMessage) { output += `Popup Found: ${JSON.stringify(results.popupFoundMessage, null, 2)}\n\n`; } if (results.optOutResultMessage) { output += `Opt-out Result: ${JSON.stringify(results.optOutResultMessage, null, 2)}\n\n`; } return { content: [ { type: "text", text: output, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to test rule: ${(error as Error).message}`, }, ], isError: true, }; } case "reset_browser_data": try { const options = { clearCookies: args.clearCookies ?? true, clearCache: args.clearCache ?? true, clearLocalStorage: args.clearLocalStorage ?? true, clearSessionStorage: args.clearSessionStorage ?? true, }; await resetBrowserData(page, options); const clearedItems = []; if (options.clearCookies) clearedItems.push("cookies"); if (options.clearCache) clearedItems.push("cache"); if (options.clearLocalStorage) clearedItems.push("localStorage"); if (options.clearSessionStorage) clearedItems.push("sessionStorage"); return { content: [ { type: "text", text: `Successfully cleared: ${clearedItems.join(", ")}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to reset browser data: ${(error as Error).message}`, }, ], isError: true, }; } default: return { content: [ { type: "text", text: `Unknown tool: ${name}`, }, ], isError: true, }; } } const server = new Server( { name: "autoconsent-mcp", version: "1.0.0", }, { capabilities: { resources: {}, tools: {}, }, }, ); // Setup request handlers server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: "console://logs", mimeType: "text/plain", name: "Browser console logs", }, ...Array.from(screenshots.keys()).map((name) => ({ uri: `screenshot://${name}`, mimeType: "image/png", name: `Screenshot: ${name}`, })), ], })); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri.toString(); if (uri === "console://logs") { return { contents: [ { uri, mimeType: "text/plain", text: consoleLogs.join("\n"), }, ], }; } if (uri.startsWith("screenshot://")) { const name = uri.split("://")[1]; const screenshot = screenshots.get(name); if (screenshot) { return { contents: [ { uri, mimeType: "image/png", blob: screenshot, }, ], }; } } throw new Error(`Resource not found: ${uri}`); }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}), ); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); } runServer().catch(console.error); process.stdin.on("close", () => { console.error("Autoconsent MCP Server closed"); if (browser) { browser.close(); } server.close(); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/noisysocks/autoconsent-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server