Skip to main content
Glama
index.js7.51 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import puppeteer from 'puppeteer'; import { AxePuppeteer } from '@axe-core/puppeteer'; class A11yServer { constructor() { this.server = new Server( { name: 'a11y-accessibility', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'audit_webpage', description: 'Perform an accessibility audit on a webpage', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of the webpage to audit', }, includeHtml: { type: 'boolean', description: 'Whether to include HTML snippets in the results', default: false, }, tags: { type: 'array', items: { type: 'string', }, description: 'Specific accessibility tags to check (e.g., wcag2a, wcag2aa, wcag21a, best-practice)', }, }, required: ['url'], }, }, { name: 'get_summary', description: 'Get a summary of accessibility issues for a webpage', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of the webpage to audit', }, }, required: ['url'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'audit_webpage': return this.auditWebpage(request.params.arguments); case 'get_summary': return this.getSummary(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } async auditWebpage(args) { if (!args.url) { throw new McpError( ErrorCode.InvalidParams, 'URL is required' ); } try { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); // Set a reasonable viewport await page.setViewport({ width: 1280, height: 800 }); // Navigate to the page await page.goto(args.url, { waitUntil: 'networkidle2', timeout: 30000 }); // Run axe on the page const axeOptions = {}; if (args.tags && args.tags.length > 0) { axeOptions.runOnly = { type: 'tag', values: args.tags, }; } const results = await new AxePuppeteer(page).options(axeOptions).analyze(); // Close the browser await browser.close(); // Format the results const formattedResults = { url: args.url, timestamp: new Date().toISOString(), violations: results.violations.map(violation => { const formattedViolation = { id: violation.id, impact: violation.impact, description: violation.description, helpUrl: violation.helpUrl, nodes: violation.nodes.map(node => { const formattedNode = { impact: node.impact, target: node.target, failureSummary: node.failureSummary, }; if (args.includeHtml) { formattedNode.html = node.html; } return formattedNode; }), }; return formattedViolation; }), passes: results.passes.length, incomplete: results.incomplete.length, inapplicable: results.inapplicable.length, }; return { content: [ { type: 'text', text: JSON.stringify(formattedResults, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error auditing webpage: ${error.message}`, }, ], isError: true, }; } } async getSummary(args) { if (!args.url) { throw new McpError( ErrorCode.InvalidParams, 'URL is required' ); } try { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); // Set a reasonable viewport await page.setViewport({ width: 1280, height: 800 }); // Navigate to the page await page.goto(args.url, { waitUntil: 'networkidle2', timeout: 30000 }); // Run axe on the page const results = await new AxePuppeteer(page).analyze(); // Close the browser await browser.close(); // Create a summary const summary = { url: args.url, timestamp: new Date().toISOString(), totalIssues: results.violations.length, issuesBySeverity: { critical: results.violations.filter(v => v.impact === 'critical').length, serious: results.violations.filter(v => v.impact === 'serious').length, moderate: results.violations.filter(v => v.impact === 'moderate').length, minor: results.violations.filter(v => v.impact === 'minor').length, }, topIssues: results.violations .sort((a, b) => { const impactOrder = { critical: 0, serious: 1, moderate: 2, minor: 3 }; return impactOrder[a.impact] - impactOrder[b.impact]; }) .slice(0, 5) .map(violation => ({ id: violation.id, impact: violation.impact, description: violation.description, helpUrl: violation.helpUrl, })), passedTests: results.passes.length, incompleteTests: results.incomplete.length, }; return { content: [ { type: 'text', text: JSON.stringify(summary, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error getting summary: ${error.message}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('A11y Accessibility MCP server running on stdio'); } } const server = new A11yServer(); server.run().catch(console.error);

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/priyankark/a11y-mcp'

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