# Directory Structure ``` ├── .gitignore ├── index.ts ├── LICENSE ├── package copy.json ├── package-lock.json ├── package.json ├── README.md └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules/ 2 | build/ 3 | .env 4 | *.log 5 | .DS_Store 6 | .idea/ 7 | .vscode/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # @enemyrr/mcp-server-pagespeed 2 | 3 | A Model Context Protocol server that provides Google PageSpeed Insights analysis. This server enables AI models to analyze webpage performance through a standardized interface. 4 | 5 | <a href="https://glama.ai/mcp/servers/wes81w8il2"><img width="380" height="200" src="https://glama.ai/mcp/servers/wes81w8il2/badge" alt="Server Pagespeed MCP server" /></a> 6 | 7 | ## Installation & Setup for Cursor IDE 8 | 9 | 1. Clone and build the project: 10 | ```bash 11 | git clone https://github.com/enemyrr/mcp-server-pagespeed.git 12 | cd mcp-server-pagespeed 13 | npm install 14 | npm run build 15 | ``` 16 | 17 | 2. Add the server in Cursor IDE settings: 18 | - Open Command Palette (Cmd/Ctrl + Shift + P) 19 | - Search for "MCP: Add Server" 20 | - Fill in the fields: 21 | - Name: `pagespeed` 22 | - Type: `command` 23 | - Command: `node /absolute/path/to/mcp-server-pagespeed/build/index.js` 24 | 25 | > **Note**: Replace `/absolute/path/to/` with the actual path where you cloned and built the project. 26 | 27 | ## Command-line Usage 28 | 29 | Just run: 30 | 31 | ```bash 32 | npx mcp-server-pagespeed 33 | ``` 34 | 35 | ## Available Tools 36 | 37 | ### analyze_pagespeed 38 | Analyze a webpage using Google PageSpeed Insights API. 39 | 40 | ```typescript 41 | use_mcp_tool({ 42 | server_name: "pagespeed", 43 | tool_name: "analyze_pagespeed", 44 | arguments: { 45 | url: "https://example.com" 46 | } 47 | }); 48 | ``` 49 | 50 | The tool returns: 51 | - Overall performance score (0-100) 52 | - Loading experience metrics 53 | - First Contentful Paint 54 | - First Input Delay 55 | - Top 5 improvement suggestions with: 56 | - Title 57 | - Description 58 | - Potential impact 59 | - Current value 60 | 61 | ## Features 62 | 63 | - Real-time webpage performance analysis 64 | - Detailed loading experience metrics 65 | - Prioritized improvement suggestions 66 | - Comprehensive error handling 67 | - TypeScript support 68 | 69 | ## Error Handling 70 | 71 | The server provides detailed error messages for: 72 | - Invalid URLs 73 | - API request failures 74 | - Connection issues 75 | - Invalid tool calls 76 | 77 | ## Contributing 78 | 79 | Contributions are welcome! Please feel free to submit a Pull Request to https://github.com/enemyrr/mcp-server-pagespeed 80 | 81 | ## License 82 | 83 | MIT 84 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "moduleResolution": "node", 6 | "outDir": "./build", 7 | "rootDir": "./", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": [ 14 | "*.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ] 19 | } ``` -------------------------------------------------------------------------------- /package copy.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@enemyrr/mcp-mysql-server", 3 | "version": "0.1.0", 4 | "description": "A Model Context Protocol server for MySQL database operations", 5 | "type": "module", 6 | "bin": { 7 | "mcp-mysql": "./build/index.js" 8 | }, 9 | "files": [ 10 | "build" 11 | ], 12 | "scripts": { 13 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 14 | "prepare": "npm run build", 15 | "watch": "tsc --watch", 16 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 17 | }, 18 | "keywords": [ 19 | "mcp", 20 | "model-context-protocol", 21 | "mysql", 22 | "database", 23 | "claude", 24 | "anthropic" 25 | ], 26 | "author": "enemyrr", 27 | "license": "MIT", 28 | "dependencies": { 29 | "@modelcontextprotocol/sdk": "0.6.0", 30 | "dotenv": "^16.4.7", 31 | "mysql2": "^3.11.5" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^20.11.24", 35 | "typescript": "^5.3.3" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/enemyrr/mcp-mysql-server" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | } 44 | } 45 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-server-pagespeed", 3 | "version": "1.0.0", 4 | "description": "A Model Context Protocol server for Google PageSpeed Insights", 5 | "type": "module", 6 | "bin": { 7 | "mcp-pagespeed": "./build/index.js" 8 | }, 9 | "files": [ 10 | "build" 11 | ], 12 | "main": "build/index.js", 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "start": "node build/index.js", 18 | "dev": "ts-node --esm index.ts", 19 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 20 | }, 21 | "keywords": [ 22 | "mcp", 23 | "model-context-protocol", 24 | "pagespeed", 25 | "google", 26 | "performance" 27 | ], 28 | "author": "@enemyrr", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@modelcontextprotocol/sdk": "0.6.0", 32 | "axios": "^1.6.7" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^20.11.19", 36 | "ts-node": "^10.9.2", 37 | "typescript": "^5.3.3" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/enemyrr/mcp-server-pagespeed" 42 | }, 43 | "publishConfig": { 44 | "access": "public" 45 | } 46 | } 47 | ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { ErrorCode, ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; 5 | import axios from 'axios'; 6 | 7 | interface PageSpeedInsight { 8 | score: number; 9 | title: string; 10 | description: string; 11 | displayValue?: string; 12 | } 13 | 14 | interface ProcessedPageSpeedResult { 15 | performanceScore: number; 16 | insights: PageSpeedInsight[]; 17 | loadingExperience: { 18 | firstContentfulPaint: { 19 | category: string; 20 | percentile: number; 21 | }; 22 | firstInputDelay: { 23 | category: string; 24 | percentile: number; 25 | }; 26 | }; 27 | } 28 | 29 | class PageSpeedServer { 30 | private server: Server; 31 | 32 | constructor() { 33 | this.server = new Server( 34 | { 35 | name: 'pagespeed-server', 36 | version: '1.0.0', 37 | }, 38 | { 39 | capabilities: { 40 | tools: {}, 41 | }, 42 | } 43 | ); 44 | 45 | this.setupToolHandlers(); 46 | this.server.onerror = (error) => console.error('[MCP Error]', error); 47 | } 48 | 49 | private setupToolHandlers() { 50 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 51 | tools: [ 52 | { 53 | name: 'analyze_pagespeed', 54 | description: 'Analyzes a webpage using Google PageSpeed Insights API', 55 | inputSchema: { 56 | type: 'object', 57 | properties: { 58 | url: { 59 | type: 'string', 60 | description: 'The URL to analyze' 61 | } 62 | }, 63 | required: ['url'] 64 | } 65 | } 66 | ] 67 | })); 68 | 69 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 70 | if (request.params.name === 'analyze_pagespeed') { 71 | const { url } = request.params.arguments as { url: string }; 72 | 73 | try { 74 | const response = await axios.get<any>( 75 | `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}` 76 | ); 77 | 78 | const result = response.data; 79 | const processedResult: ProcessedPageSpeedResult = { 80 | performanceScore: Math.round(result.lighthouseResult.categories.performance.score * 100), 81 | insights: [], 82 | loadingExperience: { 83 | firstContentfulPaint: { 84 | category: result.loadingExperience?.metrics?.FIRST_CONTENTFUL_PAINT_MS?.category || 'N/A', 85 | percentile: result.loadingExperience?.metrics?.FIRST_CONTENTFUL_PAINT_MS?.percentile || 0 86 | }, 87 | firstInputDelay: { 88 | category: result.loadingExperience?.metrics?.FIRST_INPUT_DELAY_MS?.category || 'N/A', 89 | percentile: result.loadingExperience?.metrics?.FIRST_INPUT_DELAY_MS?.percentile || 0 90 | } 91 | } 92 | }; 93 | 94 | // Process audits and extract insights 95 | const audits = result.lighthouseResult.audits; 96 | for (const [key, audit] of Object.entries(audits)) { 97 | const typedAudit = audit as any; 98 | if (typedAudit.score !== null && typedAudit.score < 1) { 99 | processedResult.insights.push({ 100 | score: typedAudit.score, 101 | title: typedAudit.title, 102 | description: typedAudit.description, 103 | displayValue: typedAudit.displayValue 104 | }); 105 | } 106 | } 107 | 108 | // Sort insights by score (lowest first) 109 | processedResult.insights.sort((a, b) => a.score - b.score); 110 | 111 | return { 112 | content: [ 113 | { 114 | type: 'text', 115 | text: JSON.stringify({ 116 | summary: `Your page performance score is ${processedResult.performanceScore}/100`, 117 | loadingExperience: { 118 | firstContentfulPaint: `${processedResult.loadingExperience.firstContentfulPaint.category} (${processedResult.loadingExperience.firstContentfulPaint.percentile}ms)`, 119 | firstInputDelay: `${processedResult.loadingExperience.firstInputDelay.category} (${processedResult.loadingExperience.firstInputDelay.percentile}ms)` 120 | }, 121 | topImprovements: processedResult.insights.slice(0, 5).map(insight => ({ 122 | title: insight.title, 123 | description: insight.description, 124 | impact: Math.round((1 - insight.score) * 100) + '% improvement possible', 125 | currentValue: insight.displayValue 126 | })) 127 | }, null, 2) 128 | } 129 | ] 130 | }; 131 | } catch (error) { 132 | console.error('Error analyzing URL:', error); 133 | throw { 134 | code: ErrorCode.InternalError, 135 | message: 'Failed to analyze URL', 136 | details: error instanceof Error ? error.message : 'Unknown error' 137 | }; 138 | } 139 | } 140 | 141 | throw { 142 | code: ErrorCode.MethodNotFound, 143 | message: `Unknown tool: ${request.params.name}` 144 | }; 145 | }); 146 | } 147 | 148 | async run() { 149 | const transport = new StdioServerTransport(); 150 | await this.server.connect(transport); 151 | console.error('PageSpeed MCP server running on stdio'); 152 | } 153 | } 154 | 155 | const server = new PageSpeedServer(); 156 | server.run().catch(console.error); ```