Skip to main content
Glama

Webby MCP Server

index.ts•16.9 kB
#!/usr/bin/env node /** * Webby MCP Server - Comprehensive Website Validator * Provides performance, accessibility, SEO, and security testing tools */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; // Import all tools import { analyzePageSpeed } from './src/performance/pagespeed.js'; import { analyzeGTmetrix } from './src/performance/gtmetrix.js'; import { analyzeWebPageTest } from './src/performance/webpagetest.js'; import { analyzeWAVE } from './src/accessibility/wave.js'; import { analyzeAxe } from './src/accessibility/axe.js'; import { analyzeMozillaObservatory } from './src/security/mozilla-observatory.js'; import { analyzeSSLLabs, analyzeSSLLabsComplete } from './src/security/ssl-labs.js'; import { runAllPerformance, runAllAccessibility, runAllSEO, runAllSecurity, runComprehensive, } from './src/orchestrator/run-all.js'; // Zod schemas for validation const PageSpeedArgsSchema = z.object({ url: z.string().url(), strategy: z.enum(['mobile', 'desktop']).optional(), apiKey: z.string().optional(), }); const GTmetrixArgsSchema = z.object({ url: z.string().url(), apiKey: z.string(), location: z.string().optional(), browser: z.string().optional(), }); const WebPageTestArgsSchema = z.object({ url: z.string().url(), location: z.string().optional(), runs: z.number().optional(), waitForResults: z.boolean().optional(), timeout: z.number().optional(), }); const WAVEArgsSchema = z.object({ url: z.string().url(), apiKey: z.string(), reporttype: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional(), }); const AxeArgsSchema = z.object({ url: z.string().url(), wcagLevel: z.string().optional(), }); const MozillaObservatoryArgsSchema = z.object({ url: z.string().url(), forceRescan: z.boolean().optional(), }); const SSLLabsArgsSchema = z.object({ url: z.string().url(), email: z.string().email(), maxAge: z.number().optional(), startNew: z.boolean().optional(), waitForComplete: z.boolean().optional(), maxWaitMinutes: z.number().optional(), }); const AllPerformanceArgsSchema = z.object({ url: z.string().url(), pagespeedApiKey: z.string().optional(), gtmetrixApiKey: z.string().optional(), webpagetestEnabled: z.boolean().optional(), webpagetestWaitForResults: z.boolean().optional(), }); const AllAccessibilityArgsSchema = z.object({ url: z.string().url(), waveApiKey: z.string().optional(), wcagLevel: z.string().optional(), }); const AllSEOArgsSchema = z.object({ url: z.string().url(), }); const AllSecurityArgsSchema = z.object({ url: z.string().url(), email: z.string().email(), waitForSSL: z.boolean().optional(), }); const ComprehensiveArgsSchema = z.object({ url: z.string().url(), email: z.string().email(), categories: z.array(z.enum(['performance', 'accessibility', 'seo', 'security'])).optional(), pagespeedApiKey: z.string().optional(), gtmetrixApiKey: z.string().optional(), waveApiKey: z.string().optional(), wcagLevel: z.string().optional(), waitForSSL: z.boolean().optional(), }); // Create server instance const server = new Server( { name: 'webby', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Define available tools const tools: Tool[] = [ // Performance Tools { name: 'validate_performance_pagespeed', description: 'Analyze website performance using Google PageSpeed Insights. Returns Core Web Vitals and performance scores. Free API with 25K requests/day.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to analyze' }, strategy: { type: 'string', enum: ['mobile', 'desktop'], description: 'Device type (default: mobile)' }, apiKey: { type: 'string', description: 'Optional API key for higher quota' }, }, required: ['url'], }, }, { name: 'validate_performance_gtmetrix', description: 'Analyze website performance using GTmetrix. Requires API key (free tier available).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, apiKey: { type: 'string', description: 'GTmetrix API key (required)' }, location: { type: 'string', description: 'Test location (e.g., vancouver-canada)' }, browser: { type: 'string', description: 'Browser type (e.g., chrome)' }, }, required: ['url', 'apiKey'], }, }, { name: 'validate_performance_webpagetest', description: 'Analyze website performance using WebPageTest via browser automation. Free 300 tests/month. Returns test ID immediately or waits for full results.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to analyze' }, location: { type: 'string', description: 'Test location (e.g., Dulles:Chrome)' }, runs: { type: 'number', description: 'Number of test runs (default: 1)' }, waitForResults: { type: 'boolean', description: 'Wait for test to complete (default: false, returns test ID immediately)' }, timeout: { type: 'number', description: 'Timeout in milliseconds (default: 300000 = 5 minutes)' }, }, required: ['url'], }, }, // Accessibility Tools { name: 'validate_accessibility_wave', description: 'Analyze website accessibility using WAVE. Tests WCAG compliance, errors, and contrast issues. Requires API key.', inputSchema: { type: 'object', properties: { url: { type: 'string' }, apiKey: { type: 'string', description: 'WAVE API key (required)' }, reporttype: { type: 'number', enum: [1, 2, 3, 4], description: 'Detail level (1-4)' }, }, required: ['url', 'apiKey'], }, }, { name: 'validate_accessibility_axe', description: 'Analyze website accessibility using Axe. Free, open-source, finds ~57% of WCAG issues with zero false positives.', inputSchema: { type: 'object', properties: { url: { type: 'string' }, wcagLevel: { type: 'string', description: 'WCAG level (wcag2a, wcag2aa, wcag2aaa, wcag21aa, wcag22aa)' }, }, required: ['url'], }, }, // Security Tools { name: 'validate_security_mozilla_observatory', description: 'Analyze HTTP security headers using Mozilla Observatory. Tests CSP, HSTS, etc. Free API, 1 scan per minute per domain.', inputSchema: { type: 'object', properties: { url: { type: 'string' }, forceRescan: { type: 'boolean', description: 'Force new scan (default: false)' }, }, required: ['url'], }, }, { name: 'validate_security_ssl_labs', description: 'Analyze SSL/TLS configuration using SSL Labs. Comprehensive certificate and protocol analysis. Long-running (may take minutes).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, email: { type: 'string', description: 'Your email (required by API)' }, maxAge: { type: 'number', description: 'Max cached report age in hours' }, startNew: { type: 'boolean', description: 'Force new assessment' }, waitForComplete: { type: 'boolean', description: 'Wait for completion (default: false)' }, maxWaitMinutes: { type: 'number', description: 'Max wait time in minutes (default: 5)' }, }, required: ['url', 'email'], }, }, // Category Runners { name: 'validate_all_performance', description: 'Run all available performance tests (PageSpeed Insights + optionally WebPageTest + optionally GTmetrix).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, pagespeedApiKey: { type: 'string', description: 'Optional PageSpeed API key' }, gtmetrixApiKey: { type: 'string', description: 'Optional GTmetrix API key' }, webpagetestEnabled: { type: 'boolean', description: 'Run WebPageTest via browser automation (default: false)' }, webpagetestWaitForResults: { type: 'boolean', description: 'Wait for WebPageTest to complete (default: false)' }, }, required: ['url'], }, }, { name: 'validate_all_accessibility', description: 'Run all accessibility tests (Axe + optionally WAVE if API key provided).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, waveApiKey: { type: 'string', description: 'Optional WAVE API key' }, wcagLevel: { type: 'string', description: 'WCAG level for Axe' }, }, required: ['url'], }, }, { name: 'validate_all_seo', description: 'Run SEO analysis using PageSpeed Insights (includes Lighthouse SEO).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, }, required: ['url'], }, }, { name: 'validate_all_security', description: 'Run all security tests (Mozilla Observatory + SSL Labs).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, email: { type: 'string', description: 'Email for SSL Labs' }, waitForSSL: { type: 'boolean', description: 'Wait for SSL Labs to complete' }, }, required: ['url', 'email'], }, }, // Comprehensive Runner { name: 'validate_comprehensive', description: 'Run comprehensive validation across all categories (performance, accessibility, SEO, security).', inputSchema: { type: 'object', properties: { url: { type: 'string' }, email: { type: 'string', description: 'Email for SSL Labs (required)' }, categories: { type: 'array', items: { type: 'string', enum: ['performance', 'accessibility', 'seo', 'security'] }, description: 'Categories to test (default: all)' }, pagespeedApiKey: { type: 'string' }, gtmetrixApiKey: { type: 'string' }, waveApiKey: { type: 'string' }, wcagLevel: { type: 'string' }, waitForSSL: { type: 'boolean' }, }, required: ['url', 'email'], }, }, ]; // List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { // Performance Tools case 'validate_performance_pagespeed': { const validatedArgs = PageSpeedArgsSchema.parse(args); const result = await analyzePageSpeed(validatedArgs.url, { strategy: validatedArgs.strategy, apiKey: validatedArgs.apiKey, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_performance_gtmetrix': { const validatedArgs = GTmetrixArgsSchema.parse(args); const result = await analyzeGTmetrix(validatedArgs.url, { apiKey: validatedArgs.apiKey, location: validatedArgs.location, browser: validatedArgs.browser, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_performance_webpagetest': { const validatedArgs = WebPageTestArgsSchema.parse(args); const result = await analyzeWebPageTest(validatedArgs.url, { location: validatedArgs.location, runs: validatedArgs.runs, waitForResults: validatedArgs.waitForResults, timeout: validatedArgs.timeout, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } // Accessibility Tools case 'validate_accessibility_wave': { const validatedArgs = WAVEArgsSchema.parse(args); const result = await analyzeWAVE(validatedArgs.url, { apiKey: validatedArgs.apiKey, reporttype: validatedArgs.reporttype, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_accessibility_axe': { const validatedArgs = AxeArgsSchema.parse(args); const result = await analyzeAxe(validatedArgs.url, { wcagLevel: validatedArgs.wcagLevel, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } // Security Tools case 'validate_security_mozilla_observatory': { const validatedArgs = MozillaObservatoryArgsSchema.parse(args); const result = await analyzeMozillaObservatory(validatedArgs.url, { forceRescan: validatedArgs.forceRescan, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_security_ssl_labs': { const validatedArgs = SSLLabsArgsSchema.parse(args); const result = validatedArgs.waitForComplete ? await analyzeSSLLabsComplete( validatedArgs.url, { email: validatedArgs.email, maxAge: validatedArgs.maxAge, startNew: validatedArgs.startNew }, validatedArgs.maxWaitMinutes ) : await analyzeSSLLabs(validatedArgs.url, { email: validatedArgs.email, maxAge: validatedArgs.maxAge, startNew: validatedArgs.startNew, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } // Category Runners case 'validate_all_performance': { const validatedArgs = AllPerformanceArgsSchema.parse(args); const result = await runAllPerformance(validatedArgs.url, { pagespeed: { apiKey: validatedArgs.pagespeedApiKey }, webpagetest: validatedArgs.webpagetestEnabled ? { waitForResults: validatedArgs.webpagetestWaitForResults } : undefined, gtmetrix: validatedArgs.gtmetrixApiKey ? { apiKey: validatedArgs.gtmetrixApiKey } : undefined, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_all_accessibility': { const validatedArgs = AllAccessibilityArgsSchema.parse(args); const result = await runAllAccessibility(validatedArgs.url, { wave: validatedArgs.waveApiKey ? { apiKey: validatedArgs.waveApiKey } : undefined, axe: { wcagLevel: validatedArgs.wcagLevel }, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_all_seo': { const validatedArgs = AllSEOArgsSchema.parse(args); const result = await runAllSEO(validatedArgs.url); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'validate_all_security': { const validatedArgs = AllSecurityArgsSchema.parse(args); const result = await runAllSecurity(validatedArgs.url, { sslLabs: { email: validatedArgs.email, waitForComplete: validatedArgs.waitForSSL, }, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } // Comprehensive Runner case 'validate_comprehensive': { const validatedArgs = ComprehensiveArgsSchema.parse(args); const result = await runComprehensive(validatedArgs.url, { performance: { pagespeed: { apiKey: validatedArgs.pagespeedApiKey }, gtmetrix: validatedArgs.gtmetrixApiKey ? { apiKey: validatedArgs.gtmetrixApiKey } : undefined, }, accessibility: { wave: validatedArgs.waveApiKey ? { apiKey: validatedArgs.waveApiKey } : undefined, axe: { wcagLevel: validatedArgs.wcagLevel }, }, security: { sslLabs: { email: validatedArgs.email, waitForComplete: validatedArgs.waitForSSL, }, }, categories: validatedArgs.categories, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { if (error instanceof z.ZodError) { throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); } throw error; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Webby MCP Server running on stdio'); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });

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/cordlesssteve/webby-mcp'

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