#
tokens: 48330/50000 20/25 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 3. Use http://codebase.md/weotzi/browser-tools-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .DS_Store
├── .gitignore
├── browser-tools-mcp
│   ├── mcp-server.ts
│   ├── package-lock.json
│   ├── package.json
│   ├── README.md
│   └── tsconfig.json
├── browser-tools-server
│   ├── browser-connector.ts
│   ├── lighthouse
│   │   ├── accessibility.ts
│   │   ├── best-practices.ts
│   │   ├── index.ts
│   │   ├── performance.ts
│   │   ├── seo.ts
│   │   └── types.ts
│   ├── package-lock.json
│   ├── package.json
│   ├── puppeteer-service.ts
│   ├── README.md
│   └── tsconfig.json
├── chrome-extension
│   ├── background.js
│   ├── devtools.html
│   ├── devtools.js
│   ├── manifest.json
│   ├── panel.html
│   └── panel.js
├── docs
│   ├── mcp-docs.md
│   └── mcp.md
├── LICENSE
└── README.md
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | dist
3 | .port
4 | .DS_Store
5 | 
```

--------------------------------------------------------------------------------
/browser-tools-mcp/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Browser Tools MCP Server
 2 | 
 3 | A Model Context Protocol (MCP) server that provides AI-powered browser tools integration. This server works in conjunction with the Browser Tools Server to provide AI capabilities for browser debugging and analysis.
 4 | 
 5 | ## Features
 6 | 
 7 | - MCP protocol implementation
 8 | - Browser console log access
 9 | - Network request analysis
10 | - Screenshot capture capabilities
11 | - Element selection and inspection
12 | - Real-time browser state monitoring
13 | - Accessibility, performance, SEO, and best practices audits
14 | 
15 | ## Prerequisites
16 | 
17 | - Node.js 14 or higher
18 | - Browser Tools Server running
19 | - Chrome or Chromium browser installed (required for audit functionality)
20 | 
21 | ## Installation
22 | 
23 | ```bash
24 | npx @agentdeskai/browser-tools-mcp
25 | ```
26 | 
27 | Or install globally:
28 | 
29 | ```bash
30 | npm install -g @agentdeskai/browser-tools-mcp
31 | ```
32 | 
33 | ## Usage
34 | 
35 | 1. First, make sure the Browser Tools Server is running:
36 | 
37 | ```bash
38 | npx @agentdeskai/browser-tools-server
39 | ```
40 | 
41 | 2. Then start the MCP server:
42 | 
43 | ```bash
44 | npx @agentdeskai/browser-tools-mcp
45 | ```
46 | 
47 | 3. The MCP server will connect to the Browser Tools Server and provide the following capabilities:
48 | 
49 | - Console log retrieval
50 | - Network request monitoring
51 | - Screenshot capture
52 | - Element selection
53 | - Browser state analysis
54 | - Accessibility and performance audits
55 | 
56 | ## MCP Functions
57 | 
58 | The server provides the following MCP functions:
59 | 
60 | - `mcp_getConsoleLogs` - Retrieve browser console logs
61 | - `mcp_getConsoleErrors` - Get browser console errors
62 | - `mcp_getNetworkErrors` - Get network error logs
63 | - `mcp_getNetworkSuccess` - Get successful network requests
64 | - `mcp_getNetworkLogs` - Get all network logs
65 | - `mcp_getSelectedElement` - Get the currently selected DOM element
66 | - `mcp_runAccessibilityAudit` - Run a WCAG-compliant accessibility audit
67 | - `mcp_runPerformanceAudit` - Run a performance audit
68 | - `mcp_runSEOAudit` - Run an SEO audit
69 | - `mcp_runBestPracticesAudit` - Run a best practices audit
70 | 
71 | ## Integration
72 | 
73 | This server is designed to work with AI tools and platforms that support the Model Context Protocol (MCP). It provides a standardized interface for AI models to interact with browser state and debugging information.
74 | 
75 | ## License
76 | 
77 | MIT
78 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # BrowserTools MCP
  2 | 
  3 | > Make your AI tools 10x more aware and capable of interacting with your browser
  4 | 
  5 | This application is a powerful browser monitoring and interaction tool that enables AI-powered applications via Anthropic's Model Context Protocol (MCP) to capture and analyze browser data through a Chrome extension.
  6 | 
  7 | Read our [docs](https://browsertools.agentdesk.ai/) for the full installation, quickstart and contribution guides.
  8 | 
  9 | ## Roadmap
 10 | 
 11 | Check out our project roadmap here: [Github Roadmap / Project Board](https://github.com/orgs/AgentDeskAI/projects/1/views/1)
 12 | 
 13 | ## Updates
 14 | 
 15 | v1.2.0 is out! Here's a quick breakdown of the update:
 16 | - You can now enable "Allow Auto-Paste into Cursor" within the DevTools panel. Screenshots will be automatically pasted into Cursor (just make sure to focus/click into the Agent input field in Cursor, otherwise it won't work!)
 17 | - Integrated a suite of SEO, performance, accessibility, and best practice analysis tools via Lighthouse
 18 | - Implemented a NextJS specific prompt used to improve SEO for a NextJS application
 19 | - Added Debugger Mode as a tool which executes all debugging tools in a particular sequence, along with a prompt to improve reasoning
 20 | - Added Audit Mode as a tool to execute all auditing tools in a particular sequence
 21 | - Resolved Windows connectivity issues
 22 | - Improved networking between BrowserTools server, extension and MCP server with host/port auto-discovery, auto-reconnect, and graceful shutdown mechanisms
 23 | - Added ability to more easily exit out of the Browser Tools server with Ctrl+C
 24 | 
 25 |   
 26 | 
 27 | Please make sure to update the version in your IDE / MCP client as so:
 28 | `npx @agentdeskai/[email protected]`
 29 | 
 30 | Also make sure to download the latest version of the chrome extension here:
 31 | [v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.2.0/BrowserTools-1.2.0-extension.zip)
 32 | 
 33 | From there you can run the local node server like so:
 34 | `npx @agentdeskai/[email protected]`
 35 | 
 36 | Make sure to specify version 1.2.0 since NPX caching may prevent you from getting the latest version! You should only have to do this once for every update. After you do it once, you should be on the latest version.
 37 | 
 38 | And once you've opened your chrome dev tools, logs should be getting sent to your server 🦾
 39 | 
 40 | If you have any questions or issues, feel free to open an issue ticket! And if you have any ideas to make this better, feel free to reach out or open an issue ticket with an enhancement tag or reach out to me at [@tedx_ai on x](https://x.com/tedx_ai)
 41 | 
 42 | ## Full Update Notes:
 43 | 
 44 | Coding agents like Cursor can run these audits against the current page seamlessly. By leveraging Puppeteer and the Lighthouse npm library, BrowserTools MCP can now:
 45 | 
 46 | - Evaluate pages for WCAG compliance
 47 | - Identify performance bottlenecks
 48 | - Flag on-page SEO issues
 49 | - Check adherence to web development best practices
 50 | - Review NextJS specific issues with SEO
 51 | 
 52 | ...all without leaving your IDE 🎉
 53 | 
 54 | ---
 55 | 
 56 | ## 🔑 Key Additions
 57 | 
 58 | | Audit Type         | Description                                                                                                                              |
 59 | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
 60 | | **Accessibility**  | WCAG-compliant checks for color contrast, missing alt text, keyboard navigation traps, ARIA attributes, and more.                        |
 61 | | **Performance**    | Lighthouse-driven analysis of render-blocking resources, excessive DOM size, unoptimized images, and other factors affecting page speed. |
 62 | | **SEO**            | Evaluates on-page SEO factors (like metadata, headings, and link structure) and suggests improvements for better search visibility.      |
 63 | | **Best Practices** | Checks for general best practices in web development.                                                                                    |
 64 | | **NextJS Audit**   | Injects a prompt used to perform a NextJS audit.                                                                                         |
 65 | | **Audit Mode**     | Runs all auditing tools in a sequence.                                                                                                   |
 66 | | **Debugger Mode**  | Runs all debugging tools in a sequence.                                                                                                  |
 67 | 
 68 | ---
 69 | 
 70 | ## 🛠️ Using Audit Tools
 71 | 
 72 | ### ✅ **Before You Start**
 73 | 
 74 | Ensure you have:
 75 | 
 76 | - An **active tab** in your browser
 77 | - The **BrowserTools extension enabled**
 78 | 
 79 | ### ▶️ **Running Audits**
 80 | 
 81 | **Headless Browser Automation**:  
 82 |  Puppeteer automates a headless Chrome instance to load the page and collect audit data, ensuring accurate results even for SPAs or content loaded via JavaScript.
 83 | 
 84 | The headless browser instance remains active for **60 seconds** after the last audit call to efficiently handle consecutive audit requests.
 85 | 
 86 | **Structured Results**:  
 87 |  Each audit returns results in a structured JSON format, including overall scores and detailed issue lists. This makes it easy for MCP-compatible clients to interpret the findings and present actionable insights.
 88 | 
 89 | The MCP server provides tools to run audits on the current page. Here are example queries you can use to trigger them:
 90 | 
 91 | #### Accessibility Audit (`runAccessibilityAudit`)
 92 | 
 93 | Ensures the page meets accessibility standards like WCAG.
 94 | 
 95 | > **Example Queries:**
 96 | >
 97 | > - "Are there any accessibility issues on this page?"
 98 | > - "Run an accessibility audit."
 99 | > - "Check if this page meets WCAG standards."
100 | 
101 | #### Performance Audit (`runPerformanceAudit`)
102 | 
103 | Identifies performance bottlenecks and loading issues.
104 | 
105 | > **Example Queries:**
106 | >
107 | > - "Why is this page loading so slowly?"
108 | > - "Check the performance of this page."
109 | > - "Run a performance audit."
110 | 
111 | #### SEO Audit (`runSEOAudit`)
112 | 
113 | Evaluates how well the page is optimized for search engines.
114 | 
115 | > **Example Queries:**
116 | >
117 | > - "How can I improve SEO for this page?"
118 | > - "Run an SEO audit."
119 | > - "Check SEO on this page."
120 | 
121 | #### Best Practices Audit (`runBestPracticesAudit`)
122 | 
123 | Checks for general best practices in web development.
124 | 
125 | > **Example Queries:**
126 | >
127 | > - "Run a best practices audit."
128 | > - "Check best practices on this page."
129 | > - "Are there any best practices issues on this page?"
130 | 
131 | #### Audit Mode (`runAuditMode`)
132 | 
133 | Runs all audits in a particular sequence. Will run a NextJS audit if the framework is detected.
134 | 
135 | > **Example Queries:**
136 | >
137 | > - "Run audit mode."
138 | > - "Enter audit mode."
139 | 
140 | #### NextJS Audits (`runNextJSAudit`)
141 | 
142 | Checks for best practices and SEO improvements for NextJS applications
143 | 
144 | > **Example Queries:**
145 | >
146 | > - "Run a NextJS audit."
147 | > - "Run a NextJS audit, I'm using app router."
148 | > - "Run a NextJS audit, I'm using page router."
149 | 
150 | #### Debugger Mode (`runDebuggerMode`)
151 | 
152 | Runs all debugging tools in a particular sequence
153 | 
154 | > **Example Queries:**
155 | >
156 | > - "Enter debugger mode."
157 | 
158 | ## Architecture
159 | 
160 | There are three core components all used to capture and analyze browser data:
161 | 
162 | 1. **Chrome Extension**: A browser extension that captures screenshots, console logs, network activity and DOM elements.
163 | 2. **Node Server**: An intermediary server that facilitates communication between the Chrome extension and any instance of an MCP server.
164 | 3. **MCP Server**: A Model Context Protocol server that provides standardized tools for AI clients to interact with the browser.
165 | 
166 | ```
167 | ┌─────────────┐     ┌──────────────┐     ┌───────────────┐     ┌─────────────┐
168 | │  MCP Client │ ──► │  MCP Server  │ ──► │  Node Server  │ ──► │   Chrome    │
169 | │  (e.g.      │ ◄── │  (Protocol   │ ◄── │ (Middleware)  │ ◄── │  Extension  │
170 | │   Cursor)   │     │   Handler)   │     │               │     │             │
171 | └─────────────┘     └──────────────┘     └───────────────┘     └─────────────┘
172 | ```
173 | 
174 | Model Context Protocol (MCP) is a capability supported by Anthropic AI models that
175 | allow you to create custom tools for any compatible client. MCP clients like Claude
176 | Desktop, Cursor, Cline or Zed can run an MCP server which "teaches" these clients
177 | about a new tool that they can use.
178 | 
179 | These tools can call out to external APIs but in our case, **all logs are stored locally** on your machine and NEVER sent out to any third-party service or API. BrowserTools MCP runs a local instance of a NodeJS API server which communicates with the BrowserTools Chrome Extension.
180 | 
181 | All consumers of the BrowserTools MCP Server interface with the same NodeJS API and Chrome extension.
182 | 
183 | #### Chrome Extension
184 | 
185 | - Monitors XHR requests/responses and console logs
186 | - Tracks selected DOM elements
187 | - Sends all logs and current element to the BrowserTools Connector
188 | - Connects to Websocket server to capture/send screenshots
189 | - Allows user to configure token/truncation limits + screenshot folder path
190 | 
191 | #### Node Server
192 | 
193 | - Acts as middleware between the Chrome extension and MCP server
194 | - Receives logs and currently selected element from Chrome extension
195 | - Processes requests from MCP server to capture logs, screenshot or current element
196 | - Sends Websocket command to the Chrome extension for capturing a screenshot
197 | - Intelligently truncates strings and # of duplicate objects in logs to avoid token limits
198 | - Removes cookies and sensitive headers to avoid sending to LLMs in MCP clients
199 | 
200 | #### MCP Server
201 | 
202 | - Implements the Model Context Protocol
203 | - Provides standardized tools for AI clients
204 | - Compatible with various MCP clients (Cursor, Cline, Zed, Claude Desktop, etc.)
205 | 
206 | ## Installation
207 | 
208 | Installation steps can be found in our documentation:
209 | 
210 | - [BrowserTools MCP Docs](https://browsertools.agentdesk.ai/)
211 | 
212 | ## Usage
213 | 
214 | Once installed and configured, the system allows any compatible MCP client to:
215 | 
216 | - Monitor browser console output
217 | - Capture network traffic
218 | - Take screenshots
219 | - Analyze selected elements
220 | - Wipe logs stored in our MCP server
221 | - Run accessibility, performance, SEO, and best practices audits
222 | 
223 | ## Compatibility
224 | 
225 | - Works with any MCP-compatible client
226 | - Primarily designed for Cursor IDE integration
227 | - Supports other AI editors and MCP clients
228 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Browser Tools Server
  2 | 
  3 | A powerful browser tools server for capturing and managing browser events, logs, and screenshots. This server works in conjunction with the Browser Tools Chrome Extension to provide comprehensive browser debugging capabilities.
  4 | 
  5 | ## Features
  6 | 
  7 | - Console log capture
  8 | - Network request monitoring
  9 | - Screenshot capture
 10 | - Element selection tracking
 11 | - WebSocket real-time communication
 12 | - Configurable log limits and settings
 13 | - Lighthouse-powered accessibility, performance, SEO, and best practices audits
 14 | 
 15 | ## Installation
 16 | 
 17 | ```bash
 18 | npx @agentdeskai/browser-tools-server
 19 | ```
 20 | 
 21 | Or install globally:
 22 | 
 23 | ```bash
 24 | npm install -g @agentdeskai/browser-tools-server
 25 | ```
 26 | 
 27 | ## Usage
 28 | 
 29 | 1. Start the server:
 30 | 
 31 | ```bash
 32 | npx @agentdeskai/browser-tools-server
 33 | ```
 34 | 
 35 | 2. The server will start on port 3025 by default
 36 | 
 37 | 3. Install and enable the Browser Tools Chrome Extension
 38 | 
 39 | 4. The server exposes the following endpoints:
 40 | 
 41 | - `/console-logs` - Get console logs
 42 | - `/console-errors` - Get console errors
 43 | - `/network-errors` - Get network error logs
 44 | - `/network-success` - Get successful network requests
 45 | - `/all-xhr` - Get all network requests
 46 | - `/screenshot` - Capture screenshots
 47 | - `/selected-element` - Get currently selected DOM element
 48 | - `/accessibility-audit` - Run accessibility audit on current page
 49 | - `/performance-audit` - Run performance audit on current page
 50 | - `/seo-audit` - Run SEO audit on current page
 51 | 
 52 | ## API Documentation
 53 | 
 54 | ### GET Endpoints
 55 | 
 56 | - `GET /console-logs` - Returns recent console logs
 57 | - `GET /console-errors` - Returns recent console errors
 58 | - `GET /network-errors` - Returns recent network errors
 59 | - `GET /network-success` - Returns recent successful network requests
 60 | - `GET /all-xhr` - Returns all recent network requests
 61 | - `GET /selected-element` - Returns the currently selected DOM element
 62 | 
 63 | ### POST Endpoints
 64 | 
 65 | - `POST /extension-log` - Receive logs from the extension
 66 | - `POST /screenshot` - Capture and save screenshots
 67 | - `POST /selected-element` - Update the selected element
 68 | - `POST /wipelogs` - Clear all stored logs
 69 | - `POST /accessibility-audit` - Run a WCAG-compliant accessibility audit on the current page
 70 | - `POST /performance-audit` - Run a performance audit on the current page
 71 | - `POST /seo-audit` - Run a SEO audit on the current page
 72 | 
 73 | # Audit Functionality
 74 | 
 75 | The server provides Lighthouse-powered audit capabilities through four AI-optimized endpoints. These audits have been specifically tailored for AI consumption, with structured data, clear categorization, and smart prioritization.
 76 | 
 77 | ## Smart Limit Implementation
 78 | 
 79 | All audit tools implement a "smart limit" approach to provide the most relevant information based on impact severity:
 80 | 
 81 | - **Critical issues**: No limit (all issues are shown)
 82 | - **Serious issues**: Up to 15 items per issue
 83 | - **Moderate issues**: Up to 10 items per issue
 84 | - **Minor issues**: Up to 3 items per issue
 85 | 
 86 | This ensures that the most important issues are always included in the response, while less important ones are limited to maintain a manageable response size for AI processing.
 87 | 
 88 | ## Common Audit Response Structure
 89 | 
 90 | All audit responses follow a similar structure:
 91 | 
 92 | ```json
 93 | {
 94 |   "metadata": {
 95 |     "url": "https://example.com",
 96 |     "timestamp": "2025-03-06T16:28:30.930Z",
 97 |     "device": "desktop",
 98 |     "lighthouseVersion": "11.7.1"
 99 |   },
100 |   "report": {
101 |     "score": 88,
102 |     "audit_counts": {
103 |       "failed": 2,
104 |       "passed": 17,
105 |       "manual": 10,
106 |       "informative": 0,
107 |       "not_applicable": 42
108 |     }
109 |     // Audit-specific content
110 |     // ...
111 |   }
112 | }
113 | ```
114 | 
115 | ## Accessibility Audit (`/accessibility-audit`)
116 | 
117 | The accessibility audit evaluates web pages against WCAG standards, identifying issues that affect users with disabilities.
118 | 
119 | ### Response Format
120 | 
121 | ```json
122 | {
123 |   "metadata": {
124 |     "url": "https://example.com",
125 |     "timestamp": "2025-03-06T16:28:30.930Z",
126 |     "device": "desktop",
127 |     "lighthouseVersion": "11.7.1"
128 |   },
129 |   "report": {
130 |     "score": 88,
131 |     "audit_counts": {
132 |       "failed": 2,
133 |       "passed": 17,
134 |       "manual": 10,
135 |       "informative": 0,
136 |       "not_applicable": 42
137 |     },
138 |     "issues": [
139 |       {
140 |         "id": "meta-viewport",
141 |         "title": "`[user-scalable=\"no\"]` is used in the `<meta name=\"viewport\">` element or the `[maximum-scale]` attribute is less than 5.",
142 |         "impact": "critical",
143 |         "category": "a11y-best-practices",
144 |         "elements": [
145 |           {
146 |             "selector": "head > meta",
147 |             "snippet": "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0\">",
148 |             "label": "head > meta",
149 |             "issue_description": "Fix any of the following: user-scalable on <meta> tag disables zooming on mobile devices"
150 |           }
151 |         ],
152 |         "score": 0
153 |       }
154 |     ],
155 |     "categories": {
156 |       "a11y-navigation": { "score": 0, "issues_count": 0 },
157 |       "a11y-aria": { "score": 0, "issues_count": 1 },
158 |       "a11y-best-practices": { "score": 0, "issues_count": 1 }
159 |     },
160 |     "critical_elements": [
161 |       {
162 |         "selector": "head > meta",
163 |         "snippet": "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0\">",
164 |         "label": "head > meta",
165 |         "issue_description": "Fix any of the following: user-scalable on <meta> tag disables zooming on mobile devices"
166 |       }
167 |     ],
168 |     "prioritized_recommendations": [
169 |       "Fix ARIA attributes and roles",
170 |       "Fix 1 issues in a11y-best-practices"
171 |     ]
172 |   }
173 | }
174 | ```
175 | 
176 | ### Key Features
177 | 
178 | - **Issues Categorized by Impact**: Critical, serious, moderate, and minor
179 | - **Element-Specific Information**: Selectors, snippets, and labels for affected elements
180 | - **Issue Categories**: ARIA, navigation, color contrast, forms, keyboard access, etc.
181 | - **Critical Elements List**: Quick access to the most serious issues
182 | - **Prioritized Recommendations**: Actionable advice in order of importance
183 | 
184 | ## Performance Audit (`/performance-audit`)
185 | 
186 | The performance audit analyzes page load speed, Core Web Vitals, and optimization opportunities.
187 | 
188 | ### Response Format
189 | 
190 | ```json
191 | {
192 |   "metadata": {
193 |     "url": "https://example.com",
194 |     "timestamp": "2025-03-06T16:27:44.900Z",
195 |     "device": "desktop",
196 |     "lighthouseVersion": "11.7.1"
197 |   },
198 |   "report": {
199 |     "score": 60,
200 |     "audit_counts": {
201 |       "failed": 11,
202 |       "passed": 21,
203 |       "manual": 0,
204 |       "informative": 20,
205 |       "not_applicable": 8
206 |     },
207 |     "metrics": [
208 |       {
209 |         "id": "lcp",
210 |         "score": 0,
211 |         "value_ms": 14149,
212 |         "passes_core_web_vital": false,
213 |         "element_selector": "div.heading > span",
214 |         "element_type": "text",
215 |         "element_content": "Welcome to Example"
216 |       },
217 |       {
218 |         "id": "fcp",
219 |         "score": 0.53,
220 |         "value_ms": 1542,
221 |         "passes_core_web_vital": false
222 |       },
223 |       {
224 |         "id": "si",
225 |         "score": 0,
226 |         "value_ms": 6883
227 |       },
228 |       {
229 |         "id": "tti",
230 |         "score": 0,
231 |         "value_ms": 14746
232 |       },
233 |       {
234 |         "id": "cls",
235 |         "score": 1,
236 |         "value_ms": 0.001,
237 |         "passes_core_web_vital": true
238 |       },
239 |       {
240 |         "id": "tbt",
241 |         "score": 1,
242 |         "value_ms": 43,
243 |         "passes_core_web_vital": true
244 |       }
245 |     ],
246 |     "opportunities": [
247 |       {
248 |         "id": "render_blocking_resources",
249 |         "savings_ms": 1270,
250 |         "severity": "serious",
251 |         "resources": [
252 |           {
253 |             "url": "styles.css",
254 |             "savings_ms": 781
255 |           }
256 |         ]
257 |       }
258 |     ],
259 |     "page_stats": {
260 |       "total_size_kb": 2190,
261 |       "total_requests": 108,
262 |       "resource_counts": {
263 |         "js": 86,
264 |         "css": 1,
265 |         "img": 3,
266 |         "font": 3,
267 |         "other": 15
268 |       },
269 |       "third_party_size_kb": 2110,
270 |       "main_thread_blocking_time_ms": 693
271 |     },
272 |     "prioritized_recommendations": ["Improve Largest Contentful Paint (LCP)"]
273 |   }
274 | }
275 | ```
276 | 
277 | ### Key Features
278 | 
279 | - **Core Web Vitals Analysis**: LCP, FCP, CLS, TBT with pass/fail status
280 | - **Element Information for LCP**: Identifies what's causing the largest contentful paint
281 | - **Optimization Opportunities**: Specific actions to improve performance with estimated time savings
282 | - **Resource Breakdown**: By type, size, and origin (first vs. third party)
283 | - **Main Thread Analysis**: Blocking time metrics to identify JavaScript performance issues
284 | - **Resource-Specific Recommendations**: For each optimization opportunity
285 | 
286 | ## SEO Audit (`/seo-audit`)
287 | 
288 | The SEO audit checks search engine optimization best practices and identifies issues that could affect search ranking.
289 | 
290 | ### Response Format
291 | 
292 | ```json
293 | {
294 |   "metadata": {
295 |     "url": "https://example.com",
296 |     "timestamp": "2025-03-06T16:29:12.455Z",
297 |     "device": "desktop",
298 |     "lighthouseVersion": "11.7.1"
299 |   },
300 |   "report": {
301 |     "score": 91,
302 |     "audit_counts": {
303 |       "failed": 1,
304 |       "passed": 10,
305 |       "manual": 1,
306 |       "informative": 0,
307 |       "not_applicable": 3
308 |     },
309 |     "issues": [
310 |       {
311 |         "id": "is-crawlable",
312 |         "title": "Page is blocked from indexing",
313 |         "impact": "critical",
314 |         "category": "crawlability",
315 |         "score": 0
316 |       }
317 |     ],
318 |     "categories": {
319 |       "content": { "score": 0, "issues_count": 0 },
320 |       "mobile": { "score": 0, "issues_count": 0 },
321 |       "crawlability": { "score": 0, "issues_count": 1 },
322 |       "other": { "score": 0, "issues_count": 0 }
323 |     },
324 |     "prioritized_recommendations": [
325 |       "Fix crawlability issues (1 issues): robots.txt, sitemaps, and redirects"
326 |     ]
327 |   }
328 | }
329 | ```
330 | 
331 | ### Key Features
332 | 
333 | - **Issues Categorized by Impact**: Critical, serious, moderate, and minor
334 | - **SEO Categories**: Content, mobile friendliness, crawlability
335 | - **Issue Details**: Information about what's causing each SEO problem
336 | - **Prioritized Recommendations**: Actionable advice in order of importance
337 | 
338 | ## Best Practices Audit (`/best-practices-audit`)
339 | 
340 | The best practices audit evaluates adherence to web development best practices related to security, trust, user experience, and browser compatibility.
341 | 
342 | ### Response Format
343 | 
344 | ```json
345 | {
346 |   "metadata": {
347 |     "url": "https://example.com",
348 |     "timestamp": "2025-03-06T17:01:38.029Z",
349 |     "device": "desktop",
350 |     "lighthouseVersion": "11.7.1"
351 |   },
352 |   "report": {
353 |     "score": 74,
354 |     "audit_counts": {
355 |       "failed": 4,
356 |       "passed": 10,
357 |       "manual": 0,
358 |       "informative": 2,
359 |       "not_applicable": 1
360 |     },
361 |     "issues": [
362 |       {
363 |         "id": "deprecations",
364 |         "title": "Uses deprecated APIs",
365 |         "impact": "critical",
366 |         "category": "security",
367 |         "score": 0,
368 |         "details": [
369 |           {
370 |             "value": "UnloadHandler"
371 |           }
372 |         ]
373 |       },
374 |       {
375 |         "id": "errors-in-console",
376 |         "title": "Browser errors were logged to the console",
377 |         "impact": "serious",
378 |         "category": "user-experience",
379 |         "score": 0,
380 |         "details": [
381 |           {
382 |             "source": "console.error",
383 |             "description": "ReferenceError: variable is not defined"
384 |           }
385 |         ]
386 |       }
387 |     ],
388 |     "categories": {
389 |       "security": { "score": 75, "issues_count": 1 },
390 |       "trust": { "score": 100, "issues_count": 0 },
391 |       "user-experience": { "score": 50, "issues_count": 1 },
392 |       "browser-compat": { "score": 100, "issues_count": 0 },
393 |       "other": { "score": 75, "issues_count": 2 }
394 |     },
395 |     "prioritized_recommendations": [
396 |       "Address 1 security issues: vulnerabilities, CSP, deprecations",
397 |       "Improve 1 user experience issues: console errors, user interactions"
398 |     ]
399 |   }
400 | }
401 | ```
402 | 
403 | ### Key Features
404 | 
405 | - **Issues Categorized by Impact**: Critical, serious, moderate, and minor
406 | - **Best Practice Categories**: Security, trust, user experience, browser compatibility
407 | - **Detailed Issue Information**: Specific problems affecting best practices compliance
408 | - **Security Focus**: Special attention to security vulnerabilities and deprecated APIs
409 | - **Prioritized Recommendations**: Actionable advice in order of importance
410 | 
411 | ## License
412 | 
413 | MIT
414 | 
415 | # Puppeteer Service
416 | 
417 | A comprehensive browser automation service built on Puppeteer to provide reliable cross-platform browser control capabilities.
418 | 
419 | ## Features
420 | 
421 | - **Cross-Platform Browser Support**:
422 | 
423 |   - Windows, macOS, and Linux support
424 |   - Chrome, Edge, Brave, and Firefox detection
425 |   - Fallback strategy for finding browser executables
426 | 
427 | - **Smart Browser Management**:
428 | 
429 |   - Singleton browser instance with automatic cleanup
430 |   - Connection retry mechanisms
431 |   - Temporary user data directories with cleanup
432 | 
433 | - **Rich Configuration Options**:
434 |   - Custom browser paths
435 |   - Network condition emulation
436 |   - Device emulation (mobile, tablet, desktop)
437 |   - Resource blocking
438 |   - Cookies and headers customization
439 |   - Locale and timezone emulation
440 | 
```

--------------------------------------------------------------------------------
/chrome-extension/devtools.html:
--------------------------------------------------------------------------------

```html
 1 | <!DOCTYPE html>
 2 | <html>
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <title>BrowserTools MCP</title>
 6 |   </head>
 7 |   <body>
 8 |     <!-- DevTools extension script -->
 9 |     <script src="devtools.js"></script>
10 |   </body>
11 | </html>
12 | 
```

--------------------------------------------------------------------------------
/browser-tools-mcp/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "outDir": "./dist",
 8 |     "rootDir": ".",
 9 |     "strict": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["*.ts"],
14 |   "exclude": ["node_modules", "dist"]
15 | } 
```

--------------------------------------------------------------------------------
/browser-tools-server/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "outDir": "./dist",
 8 |     "rootDir": ".",
 9 |     "strict": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true
13 |   },
14 |   "include": ["**/*.ts"],
15 |   "exclude": ["node_modules", "dist"]
16 | }
17 | 
```

--------------------------------------------------------------------------------
/chrome-extension/manifest.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "name": "BrowserTools MCP",
 3 |     "version": "1.2.0",
 4 |     "description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more",
 5 |     "manifest_version": 3,
 6 |     "devtools_page": "devtools.html",
 7 |     "permissions": [
 8 |       "activeTab",
 9 |       "debugger",
10 |       "storage",
11 |       "tabs",
12 |       "tabCapture",
13 |       "windows"
14 |     ],
15 |     "host_permissions": [
16 |       "<all_urls>"
17 |     ],
18 |     "background": {
19 |       "service_worker": "background.js"
20 |     }
21 | }
22 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@agentdeskai/browser-tools-server",
 3 |   "version": "1.2.0",
 4 |   "description": "A browser tools server for capturing and managing browser events, logs, and screenshots",
 5 |   "type": "module",
 6 |   "main": "dist/browser-connector.js",
 7 |   "bin": {
 8 |     "browser-tools-server": "./dist/browser-connector.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc",
12 |     "start": "tsc && node dist/browser-connector.js",
13 |     "prepublishOnly": "npm run build"
14 |   },
15 |   "keywords": [
16 |     "browser",
17 |     "tools",
18 |     "debugging",
19 |     "logging",
20 |     "screenshots",
21 |     "chrome",
22 |     "extension"
23 |   ],
24 |   "author": "AgentDesk AI",
25 |   "license": "MIT",
26 |   "dependencies": {
27 |     "@modelcontextprotocol/sdk": "^1.4.1",
28 |     "body-parser": "^1.20.3",
29 |     "cors": "^2.8.5",
30 |     "express": "^4.21.2",
31 |     "lighthouse": "^11.6.0",
32 |     "llm-cost": "^1.0.5",
33 |     "node-fetch": "^2.7.0",
34 |     "puppeteer-core": "^22.4.1",
35 |     "ws": "^8.18.0"
36 |   },
37 |   "optionalDependencies": {
38 |     "chrome-launcher": "^1.1.2"
39 |   },
40 |   "devDependencies": {
41 |     "@types/ws": "^8.5.14",
42 |     "@types/body-parser": "^1.19.5",
43 |     "@types/cors": "^2.8.17",
44 |     "@types/express": "^5.0.0",
45 |     "@types/node": "^22.13.1",
46 |     "@types/node-fetch": "^2.6.11",
47 |     "@types/puppeteer-core": "^7.0.4",
48 |     "typescript": "^5.7.3"
49 |   }
50 | }
51 | 
```

--------------------------------------------------------------------------------
/browser-tools-mcp/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@agentdeskai/browser-tools-mcp",
 3 |   "version": "1.2.0",
 4 |   "description": "MCP (Model Context Protocol) server for browser tools integration",
 5 |   "main": "dist/mcp-server.js",
 6 |   "bin": {
 7 |     "browser-tools-mcp": "dist/mcp-server.js"
 8 |   },
 9 |   "scripts": {
10 |     "inspect": "tsc && npx @modelcontextprotocol/inspector node -- dist/mcp-server.js",
11 |     "inspect-live": "npx @modelcontextprotocol/inspector npx -- @agentdeskai/browser-tools-mcp",
12 |     "build": "tsc",
13 |     "start": "tsc && node dist/mcp-server.js",
14 |     "prepublishOnly": "npm run build",
15 |     "update": "npm run build && npm version patch && npm publish"
16 |   },
17 |   "keywords": [
18 |     "mcp",
19 |     "model-context-protocol",
20 |     "browser",
21 |     "tools",
22 |     "debugging",
23 |     "ai",
24 |     "chrome",
25 |     "extension"
26 |   ],
27 |   "author": "AgentDesk AI",
28 |   "license": "MIT",
29 |   "dependencies": {
30 |     "@modelcontextprotocol/sdk": "^1.4.1",
31 |     "body-parser": "^1.20.3",
32 |     "cors": "^2.8.5",
33 |     "express": "^4.21.2",
34 |     "llm-cost": "^1.0.5",
35 |     "node-fetch": "^2.7.0",
36 |     "ws": "^8.18.0"
37 |   },
38 |   "devDependencies": {
39 |     "@types/ws": "^8.5.14",
40 |     "@types/body-parser": "^1.19.5",
41 |     "@types/cors": "^2.8.17",
42 |     "@types/express": "^5.0.0",
43 |     "@types/node": "^22.13.1",
44 |     "@types/node-fetch": "^2.6.11",
45 |     "typescript": "^5.7.3"
46 |   }
47 | }
48 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Audit categories available in Lighthouse
 3 |  */
 4 | export enum AuditCategory {
 5 |   ACCESSIBILITY = "accessibility",
 6 |   PERFORMANCE = "performance",
 7 |   SEO = "seo",
 8 |   BEST_PRACTICES = "best-practices", // Not yet implemented
 9 |   PWA = "pwa", // Not yet implemented
10 | }
11 | 
12 | /**
13 |  * Base interface for Lighthouse report metadata
14 |  */
15 | export interface LighthouseReport<T = any> {
16 |   metadata: {
17 |     url: string;
18 |     timestamp: string; // ISO 8601, e.g., "2025-02-27T14:30:00Z"
19 |     device: string; // e.g., "mobile", "desktop"
20 |     lighthouseVersion: string; // e.g., "10.4.0"
21 |   };
22 | 
23 |   // For backward compatibility with existing report formats
24 |   overallScore?: number;
25 |   failedAuditsCount?: number;
26 |   passedAuditsCount?: number;
27 |   manualAuditsCount?: number;
28 |   informativeAuditsCount?: number;
29 |   notApplicableAuditsCount?: number;
30 |   failedAudits?: any[];
31 | 
32 |   // New format for specialized reports
33 |   report?: T; // Generic report data that will be specialized by each audit type
34 | }
35 | 
36 | /**
37 |  * Configuration options for Lighthouse audits
38 |  */
39 | export interface LighthouseConfig {
40 |   flags: {
41 |     output: string[];
42 |     onlyCategories: string[];
43 |     formFactor: string;
44 |     port: number | undefined;
45 |     screenEmulation: {
46 |       mobile: boolean;
47 |       width: number;
48 |       height: number;
49 |       deviceScaleFactor: number;
50 |       disabled: boolean;
51 |     };
52 |   };
53 |   config: {
54 |     extends: string;
55 |     settings: {
56 |       onlyCategories: string[];
57 |       emulatedFormFactor: string;
58 |       throttling: {
59 |         cpuSlowdownMultiplier: number;
60 |       };
61 |     };
62 |   };
63 | }
64 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import lighthouse from "lighthouse";
  2 | import type { Result as LighthouseResult, Flags } from "lighthouse";
  3 | import {
  4 |   connectToHeadlessBrowser,
  5 |   scheduleBrowserCleanup,
  6 | } from "../puppeteer-service.js";
  7 | import { LighthouseConfig, AuditCategory } from "./types.js";
  8 | 
  9 | /**
 10 |  * Creates a Lighthouse configuration object
 11 |  * @param categories Array of categories to audit
 12 |  * @returns Lighthouse configuration and flags
 13 |  */
 14 | export function createLighthouseConfig(
 15 |   categories: string[] = [AuditCategory.ACCESSIBILITY]
 16 | ): LighthouseConfig {
 17 |   return {
 18 |     flags: {
 19 |       output: ["json"],
 20 |       onlyCategories: categories,
 21 |       formFactor: "desktop",
 22 |       port: undefined as number | undefined,
 23 |       screenEmulation: {
 24 |         mobile: false,
 25 |         width: 1350,
 26 |         height: 940,
 27 |         deviceScaleFactor: 1,
 28 |         disabled: false,
 29 |       },
 30 |     },
 31 |     config: {
 32 |       extends: "lighthouse:default",
 33 |       settings: {
 34 |         onlyCategories: categories,
 35 |         emulatedFormFactor: "desktop",
 36 |         throttling: { cpuSlowdownMultiplier: 1 },
 37 |       },
 38 |     },
 39 |   };
 40 | }
 41 | 
 42 | /**
 43 |  * Runs a Lighthouse audit on the specified URL via CDP
 44 |  * @param url The URL to audit
 45 |  * @param categories Array of categories to audit, defaults to ["accessibility"]
 46 |  * @returns Promise resolving to the Lighthouse result
 47 |  * @throws Error if the URL is invalid or if the audit fails
 48 |  */
 49 | export async function runLighthouseAudit(
 50 |   url: string,
 51 |   categories: string[]
 52 | ): Promise<LighthouseResult> {
 53 |   console.log(`Starting Lighthouse ${categories.join(", ")} audit for: ${url}`);
 54 | 
 55 |   if (!url || url === "about:blank") {
 56 |     console.error("Invalid URL for Lighthouse audit");
 57 |     throw new Error(
 58 |       "Cannot run audit on an empty page or about:blank. Please navigate to a valid URL first."
 59 |     );
 60 |   }
 61 | 
 62 |   try {
 63 |     // Always use a dedicated headless browser for audits
 64 |     console.log("Using dedicated headless browser for audit");
 65 | 
 66 |     // Determine if this is a performance audit - we need to load all resources for performance audits
 67 |     const isPerformanceAudit = categories.includes(AuditCategory.PERFORMANCE);
 68 | 
 69 |     // For performance audits, we want to load all resources
 70 |     // For accessibility or other audits, we can block non-essential resources
 71 |     try {
 72 |       const { port } = await connectToHeadlessBrowser(url, {
 73 |         blockResources: !isPerformanceAudit,
 74 |       });
 75 | 
 76 |       console.log(`Connected to browser on port: ${port}`);
 77 | 
 78 |       // Create Lighthouse config
 79 |       const { flags, config } = createLighthouseConfig(categories);
 80 |       flags.port = port;
 81 | 
 82 |       console.log(
 83 |         `Running Lighthouse with categories: ${categories.join(", ")}`
 84 |       );
 85 |       const runnerResult = await lighthouse(url, flags as Flags, config);
 86 |       console.log("Lighthouse scan completed");
 87 | 
 88 |       if (!runnerResult?.lhr) {
 89 |         console.error("Lighthouse audit failed to produce results");
 90 |         throw new Error("Lighthouse audit failed to produce results");
 91 |       }
 92 | 
 93 |       // Schedule browser cleanup after a delay to allow for subsequent audits
 94 |       scheduleBrowserCleanup();
 95 | 
 96 |       // Return the result
 97 |       const result = runnerResult.lhr;
 98 | 
 99 |       return result;
100 |     } catch (browserError) {
101 |       // Check if the error is related to Chrome/Edge not being available
102 |       const errorMessage =
103 |         browserError instanceof Error
104 |           ? browserError.message
105 |           : String(browserError);
106 |       if (
107 |         errorMessage.includes("Chrome could not be found") ||
108 |         errorMessage.includes("Failed to launch browser") ||
109 |         errorMessage.includes("spawn ENOENT")
110 |       ) {
111 |         throw new Error(
112 |           "Chrome or Edge browser could not be found. Please ensure that Chrome or Edge is installed on your system to run audits."
113 |         );
114 |       }
115 |       // Re-throw other errors
116 |       throw browserError;
117 |     }
118 |   } catch (error) {
119 |     console.error("Lighthouse audit failed:", error);
120 |     // Schedule browser cleanup even if the audit fails
121 |     scheduleBrowserCleanup();
122 |     throw new Error(
123 |       `Lighthouse audit failed: ${
124 |         error instanceof Error ? error.message : String(error)
125 |       }`
126 |     );
127 |   }
128 | }
129 | 
130 | // Export from specific audit modules
131 | export * from "./accessibility.js";
132 | export * from "./performance.js";
133 | export * from "./seo.js";
134 | export * from "./types.js";
135 | 
```

--------------------------------------------------------------------------------
/chrome-extension/panel.html:
--------------------------------------------------------------------------------

```html
  1 | <!DOCTYPE html>
  2 | <html>
  3 | <head>
  4 |     <style>
  5 |         body {
  6 |             padding: 16px;
  7 |             font-family: system-ui, -apple-system, sans-serif;
  8 |             background-color: #282828;
  9 |             color: #fff;
 10 |         }
 11 |         .endpoint-list {
 12 |             margin: 16px 0;
 13 |         }
 14 |         .endpoint-item {
 15 |             display: flex;
 16 |             gap: 8px;
 17 |             margin-bottom: 8px;
 18 |             align-items: center;
 19 |         }
 20 |         .endpoint-form {
 21 |             display: flex;
 22 |             gap: 8px;
 23 |             margin-bottom: 16px;
 24 |             align-items: center;
 25 |         }
 26 |         button {
 27 |             padding: 4px 8px;
 28 |         }
 29 |         input {
 30 |             padding: 4px;
 31 |         }
 32 |         .status-indicator {
 33 |             width: 8px;
 34 |             height: 8px;
 35 |             border-radius: 50%;
 36 |             display: inline-block;
 37 |         }
 38 |         .status-connected {
 39 |             background: #4caf50;
 40 |         }
 41 |         .status-disconnected {
 42 |             background: #f44336;
 43 |         }
 44 |         .form-group {
 45 |             margin-bottom: 16px;
 46 |         }
 47 |         .form-group label {
 48 |             display: block;
 49 |             margin-bottom: 4px;
 50 |         }
 51 |         .checkbox-group {
 52 |             margin-bottom: 8px;
 53 |         }
 54 |         .checkbox-group-2 {
 55 |             margin-bottom: 6px;
 56 |         }
 57 |         input[type="number"],
 58 |         input[type="text"] {
 59 |             padding: 4px;
 60 |             width: 200px;
 61 |         }
 62 |         .settings-section {
 63 |             border: 1px solid #ccc;
 64 |             padding: 16px;
 65 |             margin-bottom: 16px;
 66 |             border-radius: 4px;
 67 |         }
 68 |         .settings-header {
 69 |             display: flex;
 70 |             align-items: center;
 71 |             justify-content: space-between;
 72 |             cursor: pointer;
 73 |             user-select: none;
 74 |         }
 75 |         .settings-header h3 {
 76 |             margin: 0;
 77 |         }
 78 |         .settings-content {
 79 |             display: none;
 80 |             margin-top: 16px;
 81 |         }
 82 |         .settings-content.visible {
 83 |             display: block;
 84 |         }
 85 |         .chevron {
 86 |             width: 20px;
 87 |             height: 20px;
 88 |             transition: transform 0.3s ease;
 89 |         }
 90 |         .chevron.open {
 91 |             transform: rotate(180deg);
 92 |         }
 93 |         .quick-actions {
 94 |             display: flex;
 95 |             gap: 8px;
 96 |             margin-bottom: 16px;
 97 |         }
 98 |         .action-button {
 99 |             background-color: #4a4a4a;
100 |             color: white;
101 |             border: none;
102 |             padding: 8px 16px;
103 |             border-radius: 4px;
104 |             cursor: pointer;
105 |             transition: background-color 0.2s;
106 |         }
107 |         .action-button:hover {
108 |             background-color: #5a5a5a;
109 |         }
110 |         .action-button.danger {
111 |             background-color: #f44336;
112 |         }
113 |         .action-button.danger:hover {
114 |             background-color: #d32f2f;
115 |         }
116 |     </style>
117 | </head>
118 | <body>
119 |     <div class="settings-section">
120 |         <h3>Quick Actions</h3>
121 |         <div class="quick-actions">
122 |             <button id="capture-screenshot" class="action-button">
123 |                 Capture Screenshot
124 |             </button>
125 |             <button id="wipe-logs" class="action-button danger">
126 |                 Wipe All Logs
127 |             </button>
128 |         </div>
129 |         <div class="checkbox-group-2" style="margin-top: 10px; display: flex; align-items: center;">
130 |             <label>
131 |                 <input type="checkbox" id="allow-auto-paste">
132 |                 Allow Auto-paste to Cursor
133 |             </label>
134 |         </div>
135 |     </div>
136 | 
137 |     <div class="settings-section">
138 |         <h3>Screenshot Settings</h3>
139 |         <div class="form-group">
140 |             <label for="screenshot-path">Provide a directory to save screenshots to (by default screenshots will be saved to your downloads folder if no path is provided)</label>
141 |             <input type="text" id="screenshot-path" placeholder="/path/to/screenshots">
142 |         </div>
143 |     </div>
144 | 
145 |     <div class="settings-section">
146 |         <h3>Server Connection Settings</h3>
147 |         <div class="form-group">
148 |             <label for="server-host">Server Host</label>
149 |             <input type="text" id="server-host" placeholder="localhost or IP address">
150 |         </div>
151 |         <div class="form-group">
152 |             <label for="server-port">Server Port</label>
153 |             <input type="number" id="server-port" min="1" max="65535" value="3025">
154 |         </div>
155 |         <div class="quick-actions">
156 |             <button id="discover-server" class="action-button">
157 |                 Auto-Discover Server
158 |             </button>
159 |             <button id="test-connection" class="action-button">
160 |                 Test Connection
161 |             </button>
162 |         </div>
163 |         <div id="connection-status" style="margin-top: 8px; display: none;">
164 |             <span id="status-icon" class="status-indicator"></span>
165 |             <span id="status-text"></span>
166 |         </div>
167 |     </div>
168 | 
169 |     <div class="settings-section">
170 |         <div class="settings-header" id="advanced-settings-header">
171 |             <h3>Advanced Settings</h3>
172 |             <svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
173 |                 <polyline points="6 9 12 15 18 9"></polyline>
174 |             </svg>
175 |         </div>
176 |         
177 |         <div class="settings-content" id="advanced-settings-content">
178 |             <div class="form-group">
179 |                 <label for="log-limit">Log Limit (number of logs)</label>
180 |                 <input type="number" id="log-limit" min="1" value="50">
181 |             </div>
182 | 
183 |             <div class="form-group">
184 |                 <label for="query-limit">Query Limit (characters)</label>
185 |                 <input type="number" id="query-limit" min="1" value="30000">
186 |             </div>
187 | 
188 |             <div class="form-group">
189 |                 <label for="string-size-limit">String Size Limit (characters)</label>
190 |                 <input type="number" id="string-size-limit" min="1" value="500">
191 |             </div>
192 | 
193 |             <div class="form-group">
194 |                 <label for="max-log-size">Max Log Size (characters)</label>
195 |                 <input type="number" id="max-log-size" min="1000" value="20000">
196 |             </div>
197 | 
198 |             <div class="checkbox-group">
199 |                 <label>
200 |                     <input type="checkbox" id="show-request-headers">
201 |                     Include Request Headers
202 |                 </label>
203 |             </div>
204 | 
205 |             <div class="checkbox-group">
206 |                 <label>
207 |                     <input type="checkbox" id="show-response-headers">
208 |                     Include Response Headers
209 |                 </label>
210 |             </div>
211 |         </div>
212 |     </div>
213 | 
214 |     <script src="panel.js"></script>
215 | </body>
216 | </html> 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/best-practices.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Result as LighthouseResult } from "lighthouse";
  2 | import { AuditCategory, LighthouseReport } from "./types.js";
  3 | import { runLighthouseAudit } from "./index.js";
  4 | 
  5 | // === Best Practices Report Types ===
  6 | 
  7 | /**
  8 |  * Best Practices-specific report content structure
  9 |  */
 10 | export interface BestPracticesReportContent {
 11 |   score: number; // Overall score (0-100)
 12 |   audit_counts: {
 13 |     // Counts of different audit types
 14 |     failed: number;
 15 |     passed: number;
 16 |     manual: number;
 17 |     informative: number;
 18 |     not_applicable: number;
 19 |   };
 20 |   issues: AIBestPracticesIssue[];
 21 |   categories: {
 22 |     [category: string]: {
 23 |       score: number;
 24 |       issues_count: number;
 25 |     };
 26 |   };
 27 |   prioritized_recommendations?: string[]; // Ordered list of recommendations
 28 | }
 29 | 
 30 | /**
 31 |  * Full Best Practices report implementing the base LighthouseReport interface
 32 |  */
 33 | export type AIOptimizedBestPracticesReport =
 34 |   LighthouseReport<BestPracticesReportContent>;
 35 | 
 36 | /**
 37 |  * AI-optimized Best Practices issue
 38 |  */
 39 | interface AIBestPracticesIssue {
 40 |   id: string; // e.g., "js-libraries"
 41 |   title: string; // e.g., "Detected JavaScript libraries"
 42 |   impact: "critical" | "serious" | "moderate" | "minor";
 43 |   category: string; // e.g., "security", "trust", "user-experience", "browser-compat"
 44 |   details?: {
 45 |     name?: string; // Name of the item (e.g., library name, vulnerability)
 46 |     version?: string; // Version information if applicable
 47 |     value?: string; // Current value or status
 48 |     issue?: string; // Description of the issue
 49 |   }[];
 50 |   score: number | null; // 0-1 or null
 51 | }
 52 | 
 53 | // Original interfaces for backward compatibility
 54 | interface BestPracticesAudit {
 55 |   id: string;
 56 |   title: string;
 57 |   description: string;
 58 |   score: number | null;
 59 |   scoreDisplayMode: string;
 60 |   details?: BestPracticesAuditDetails;
 61 | }
 62 | 
 63 | interface BestPracticesAuditDetails {
 64 |   items?: Array<Record<string, unknown>>;
 65 |   type?: string; // e.g., "table"
 66 | }
 67 | 
 68 | // This ensures we always include critical issues while limiting less important ones
 69 | const DETAIL_LIMITS: Record<string, number> = {
 70 |   critical: Number.MAX_SAFE_INTEGER, // No limit for critical issues
 71 |   serious: 15, // Up to 15 items for serious issues
 72 |   moderate: 10, // Up to 10 items for moderate issues
 73 |   minor: 3, // Up to 3 items for minor issues
 74 | };
 75 | 
 76 | /**
 77 |  * Runs a Best Practices audit on the specified URL
 78 |  * @param url The URL to audit
 79 |  * @returns Promise resolving to AI-optimized Best Practices audit results
 80 |  */
 81 | export async function runBestPracticesAudit(
 82 |   url: string
 83 | ): Promise<AIOptimizedBestPracticesReport> {
 84 |   try {
 85 |     const lhr = await runLighthouseAudit(url, [AuditCategory.BEST_PRACTICES]);
 86 |     return extractAIOptimizedData(lhr, url);
 87 |   } catch (error) {
 88 |     throw new Error(
 89 |       `Best Practices audit failed: ${
 90 |         error instanceof Error ? error.message : String(error)
 91 |       }`
 92 |     );
 93 |   }
 94 | }
 95 | 
 96 | /**
 97 |  * Extract AI-optimized Best Practices data from Lighthouse results
 98 |  */
 99 | const extractAIOptimizedData = (
100 |   lhr: LighthouseResult,
101 |   url: string
102 | ): AIOptimizedBestPracticesReport => {
103 |   const categoryData = lhr.categories[AuditCategory.BEST_PRACTICES];
104 |   const audits = lhr.audits || {};
105 | 
106 |   // Add metadata
107 |   const metadata = {
108 |     url,
109 |     timestamp: lhr.fetchTime || new Date().toISOString(),
110 |     device: lhr.configSettings?.formFactor || "desktop",
111 |     lighthouseVersion: lhr.lighthouseVersion || "unknown",
112 |   };
113 | 
114 |   // Process audit results
115 |   const issues: AIBestPracticesIssue[] = [];
116 |   const categories: { [key: string]: { score: number; issues_count: number } } =
117 |     {
118 |       security: { score: 0, issues_count: 0 },
119 |       trust: { score: 0, issues_count: 0 },
120 |       "user-experience": { score: 0, issues_count: 0 },
121 |       "browser-compat": { score: 0, issues_count: 0 },
122 |       other: { score: 0, issues_count: 0 },
123 |     };
124 | 
125 |   // Counters for audit types
126 |   let failedCount = 0;
127 |   let passedCount = 0;
128 |   let manualCount = 0;
129 |   let informativeCount = 0;
130 |   let notApplicableCount = 0;
131 | 
132 |   // Process failed audits (score < 1)
133 |   const failedAudits = Object.entries(audits)
134 |     .filter(([, audit]) => {
135 |       const score = audit.score;
136 |       return (
137 |         score !== null &&
138 |         score < 1 &&
139 |         audit.scoreDisplayMode !== "manual" &&
140 |         audit.scoreDisplayMode !== "notApplicable"
141 |       );
142 |     })
143 |     .map(([auditId, audit]) => ({ auditId, ...audit }));
144 | 
145 |   // Update counters
146 |   Object.values(audits).forEach((audit) => {
147 |     const { score, scoreDisplayMode } = audit;
148 | 
149 |     if (scoreDisplayMode === "manual") {
150 |       manualCount++;
151 |     } else if (scoreDisplayMode === "informative") {
152 |       informativeCount++;
153 |     } else if (scoreDisplayMode === "notApplicable") {
154 |       notApplicableCount++;
155 |     } else if (score === 1) {
156 |       passedCount++;
157 |     } else if (score !== null && score < 1) {
158 |       failedCount++;
159 |     }
160 |   });
161 | 
162 |   // Process failed audits into AI-friendly format
163 |   failedAudits.forEach((ref: any) => {
164 |     // Determine impact level based on audit score and weight
165 |     let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
166 |     const score = ref.score || 0;
167 | 
168 |     // Use a more reliable approach to determine impact
169 |     if (score === 0) {
170 |       impact = "critical";
171 |     } else if (score < 0.5) {
172 |       impact = "serious";
173 |     } else if (score < 0.9) {
174 |       impact = "moderate";
175 |     } else {
176 |       impact = "minor";
177 |     }
178 | 
179 |     // Categorize the issue
180 |     let category = "other";
181 | 
182 |     // Security-related issues
183 |     if (
184 |       ref.auditId.includes("csp") ||
185 |       ref.auditId.includes("security") ||
186 |       ref.auditId.includes("vulnerab") ||
187 |       ref.auditId.includes("password") ||
188 |       ref.auditId.includes("cert") ||
189 |       ref.auditId.includes("deprecat")
190 |     ) {
191 |       category = "security";
192 |     }
193 |     // Trust and legitimacy issues
194 |     else if (
195 |       ref.auditId.includes("doctype") ||
196 |       ref.auditId.includes("charset") ||
197 |       ref.auditId.includes("legit") ||
198 |       ref.auditId.includes("trust")
199 |     ) {
200 |       category = "trust";
201 |     }
202 |     // User experience issues
203 |     else if (
204 |       ref.auditId.includes("user") ||
205 |       ref.auditId.includes("experience") ||
206 |       ref.auditId.includes("console") ||
207 |       ref.auditId.includes("errors") ||
208 |       ref.auditId.includes("paste")
209 |     ) {
210 |       category = "user-experience";
211 |     }
212 |     // Browser compatibility issues
213 |     else if (
214 |       ref.auditId.includes("compat") ||
215 |       ref.auditId.includes("browser") ||
216 |       ref.auditId.includes("vendor") ||
217 |       ref.auditId.includes("js-lib")
218 |     ) {
219 |       category = "browser-compat";
220 |     }
221 | 
222 |     // Count issues by category
223 |     categories[category].issues_count++;
224 | 
225 |     // Create issue object
226 |     const issue: AIBestPracticesIssue = {
227 |       id: ref.auditId,
228 |       title: ref.title,
229 |       impact,
230 |       category,
231 |       score: ref.score,
232 |       details: [],
233 |     };
234 | 
235 |     // Extract details if available
236 |     const refDetails = ref.details as BestPracticesAuditDetails | undefined;
237 |     if (refDetails?.items && Array.isArray(refDetails.items)) {
238 |       const itemLimit = DETAIL_LIMITS[impact];
239 |       const detailItems = refDetails.items.slice(0, itemLimit);
240 | 
241 |       detailItems.forEach((item: Record<string, unknown>) => {
242 |         issue.details = issue.details || [];
243 | 
244 |         // Different audits have different detail structures
245 |         const detail: Record<string, string> = {};
246 | 
247 |         if (typeof item.name === "string") detail.name = item.name;
248 |         if (typeof item.version === "string") detail.version = item.version;
249 |         if (typeof item.issue === "string") detail.issue = item.issue;
250 |         if (item.value !== undefined) detail.value = String(item.value);
251 | 
252 |         // For JS libraries, extract name and version
253 |         if (
254 |           ref.auditId === "js-libraries" &&
255 |           typeof item.name === "string" &&
256 |           typeof item.version === "string"
257 |         ) {
258 |           detail.name = item.name;
259 |           detail.version = item.version;
260 |         }
261 | 
262 |         // Add other generic properties that might exist
263 |         for (const [key, value] of Object.entries(item)) {
264 |           if (!detail[key] && typeof value === "string") {
265 |             detail[key] = value;
266 |           }
267 |         }
268 | 
269 |         issue.details.push(detail as any);
270 |       });
271 |     }
272 | 
273 |     issues.push(issue);
274 |   });
275 | 
276 |   // Calculate category scores (0-100)
277 |   Object.keys(categories).forEach((category) => {
278 |     // Simplified scoring: if there are issues in this category, score is reduced proportionally
279 |     const issueCount = categories[category].issues_count;
280 |     if (issueCount > 0) {
281 |       // More issues = lower score, max penalty of 25 points per issue
282 |       const penalty = Math.min(100, issueCount * 25);
283 |       categories[category].score = Math.max(0, 100 - penalty);
284 |     } else {
285 |       categories[category].score = 100;
286 |     }
287 |   });
288 | 
289 |   // Generate prioritized recommendations
290 |   const prioritized_recommendations: string[] = [];
291 | 
292 |   // Prioritize recommendations by category with most issues
293 |   Object.entries(categories)
294 |     .filter(([_, data]) => data.issues_count > 0)
295 |     .sort(([_, a], [__, b]) => b.issues_count - a.issues_count)
296 |     .forEach(([category, data]) => {
297 |       let recommendation = "";
298 | 
299 |       switch (category) {
300 |         case "security":
301 |           recommendation = `Address ${data.issues_count} security issues: vulnerabilities, CSP, deprecations`;
302 |           break;
303 |         case "trust":
304 |           recommendation = `Fix ${data.issues_count} trust & legitimacy issues: doctype, charset`;
305 |           break;
306 |         case "user-experience":
307 |           recommendation = `Improve ${data.issues_count} user experience issues: console errors, user interactions`;
308 |           break;
309 |         case "browser-compat":
310 |           recommendation = `Resolve ${data.issues_count} browser compatibility issues: outdated libraries, vendor prefixes`;
311 |           break;
312 |         default:
313 |           recommendation = `Fix ${data.issues_count} other best practice issues`;
314 |       }
315 | 
316 |       prioritized_recommendations.push(recommendation);
317 |     });
318 | 
319 |   // Return the optimized report
320 |   return {
321 |     metadata,
322 |     report: {
323 |       score: categoryData?.score ? Math.round(categoryData.score * 100) : 0,
324 |       audit_counts: {
325 |         failed: failedCount,
326 |         passed: passedCount,
327 |         manual: manualCount,
328 |         informative: informativeCount,
329 |         not_applicable: notApplicableCount,
330 |       },
331 |       issues,
332 |       categories,
333 |       prioritized_recommendations,
334 |     },
335 |   };
336 | };
337 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/accessibility.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Result as LighthouseResult } from "lighthouse";
  2 | import { AuditCategory, LighthouseReport } from "./types.js";
  3 | import { runLighthouseAudit } from "./index.js";
  4 | 
  5 | // === Accessibility Report Types ===
  6 | 
  7 | /**
  8 |  * Accessibility-specific report content structure
  9 |  */
 10 | export interface AccessibilityReportContent {
 11 |   score: number; // Overall score (0-100)
 12 |   audit_counts: {
 13 |     // Counts of different audit types
 14 |     failed: number;
 15 |     passed: number;
 16 |     manual: number;
 17 |     informative: number;
 18 |     not_applicable: number;
 19 |   };
 20 |   issues: AIAccessibilityIssue[];
 21 |   categories: {
 22 |     [category: string]: {
 23 |       score: number;
 24 |       issues_count: number;
 25 |     };
 26 |   };
 27 |   critical_elements: AIAccessibilityElement[];
 28 |   prioritized_recommendations?: string[]; // Ordered list of recommendations
 29 | }
 30 | 
 31 | /**
 32 |  * Full accessibility report implementing the base LighthouseReport interface
 33 |  */
 34 | export type AIOptimizedAccessibilityReport =
 35 |   LighthouseReport<AccessibilityReportContent>;
 36 | 
 37 | /**
 38 |  * AI-optimized accessibility issue
 39 |  */
 40 | interface AIAccessibilityIssue {
 41 |   id: string; // e.g., "color-contrast"
 42 |   title: string; // e.g., "Color contrast is sufficient"
 43 |   impact: "critical" | "serious" | "moderate" | "minor";
 44 |   category: string; // e.g., "contrast", "aria", "forms", "keyboard"
 45 |   elements?: AIAccessibilityElement[]; // Elements with issues
 46 |   score: number | null; // 0-1 or null
 47 | }
 48 | 
 49 | /**
 50 |  * Accessibility element with issues
 51 |  */
 52 | interface AIAccessibilityElement {
 53 |   selector: string; // CSS selector
 54 |   snippet?: string; // HTML snippet
 55 |   label?: string; // Element label
 56 |   issue_description?: string; // Description of the issue
 57 |   value?: string | number; // Current value (e.g., contrast ratio)
 58 | }
 59 | 
 60 | // Original interfaces for backward compatibility
 61 | interface AccessibilityAudit {
 62 |   id: string; // e.g., "color-contrast"
 63 |   title: string; // e.g., "Color contrast is sufficient"
 64 |   description: string; // e.g., "Ensures text is readable..."
 65 |   score: number | null; // 0-1 (normalized), null for manual/informative
 66 |   scoreDisplayMode: string; // e.g., "binary", "numeric", "manual"
 67 |   details?: AuditDetails; // Optional, structured details
 68 |   weight?: number; // Optional, audit weight for impact calculation
 69 | }
 70 | 
 71 | type AuditDetails = {
 72 |   items?: Array<{
 73 |     node?: {
 74 |       selector: string; // e.g., ".my-class"
 75 |       snippet?: string; // HTML snippet
 76 |       nodeLabel?: string; // e.g., "Modify logging size limits / truncation"
 77 |       explanation?: string; // Explanation of why the node fails the audit
 78 |     };
 79 |     value?: string | number; // Specific value (e.g., contrast ratio)
 80 |     explanation?: string; // Explanation at the item level
 81 |   }>;
 82 |   debugData?: string; // Optional, debug information
 83 |   [key: string]: any; // Flexible for other detail types (tables, etc.)
 84 | };
 85 | 
 86 | // Original limits were optimized for human consumption
 87 | // This ensures we always include critical issues while limiting less important ones
 88 | const DETAIL_LIMITS = {
 89 |   critical: Number.MAX_SAFE_INTEGER, // No limit for critical issues
 90 |   serious: 15, // Up to 15 items for serious issues
 91 |   moderate: 10, // Up to 10 items for moderate issues
 92 |   minor: 3, // Up to 3 items for minor issues
 93 | };
 94 | 
 95 | /**
 96 |  * Runs an accessibility audit on the specified URL
 97 |  * @param url The URL to audit
 98 |  * @returns Promise resolving to AI-optimized accessibility audit results
 99 |  */
100 | export async function runAccessibilityAudit(
101 |   url: string
102 | ): Promise<AIOptimizedAccessibilityReport> {
103 |   try {
104 |     const lhr = await runLighthouseAudit(url, [AuditCategory.ACCESSIBILITY]);
105 |     return extractAIOptimizedData(lhr, url);
106 |   } catch (error) {
107 |     throw new Error(
108 |       `Accessibility audit failed: ${
109 |         error instanceof Error ? error.message : String(error)
110 |       }`
111 |     );
112 |   }
113 | }
114 | 
115 | /**
116 |  * Extract AI-optimized accessibility data from Lighthouse results
117 |  */
118 | const extractAIOptimizedData = (
119 |   lhr: LighthouseResult,
120 |   url: string
121 | ): AIOptimizedAccessibilityReport => {
122 |   const categoryData = lhr.categories[AuditCategory.ACCESSIBILITY];
123 |   const audits = lhr.audits || {};
124 | 
125 |   // Add metadata
126 |   const metadata = {
127 |     url,
128 |     timestamp: lhr.fetchTime || new Date().toISOString(),
129 |     device: "desktop", // This could be made configurable
130 |     lighthouseVersion: lhr.lighthouseVersion,
131 |   };
132 | 
133 |   // Initialize variables
134 |   const issues: AIAccessibilityIssue[] = [];
135 |   const criticalElements: AIAccessibilityElement[] = [];
136 |   const categories: {
137 |     [category: string]: { score: number; issues_count: number };
138 |   } = {};
139 | 
140 |   // Count audits by type
141 |   let failedCount = 0;
142 |   let passedCount = 0;
143 |   let manualCount = 0;
144 |   let informativeCount = 0;
145 |   let notApplicableCount = 0;
146 | 
147 |   // Process audit refs
148 |   const auditRefs = categoryData?.auditRefs || [];
149 | 
150 |   // First pass: count audits by type and initialize categories
151 |   auditRefs.forEach((ref) => {
152 |     const audit = audits[ref.id];
153 |     if (!audit) return;
154 | 
155 |     // Count by scoreDisplayMode
156 |     if (audit.scoreDisplayMode === "manual") {
157 |       manualCount++;
158 |     } else if (audit.scoreDisplayMode === "informative") {
159 |       informativeCount++;
160 |     } else if (audit.scoreDisplayMode === "notApplicable") {
161 |       notApplicableCount++;
162 |     } else if (audit.score !== null) {
163 |       // Binary pass/fail
164 |       if (audit.score >= 0.9) {
165 |         passedCount++;
166 |       } else {
167 |         failedCount++;
168 |       }
169 |     }
170 | 
171 |     // Process categories
172 |     if (ref.group) {
173 |       // Initialize category if not exists
174 |       if (!categories[ref.group]) {
175 |         categories[ref.group] = { score: 0, issues_count: 0 };
176 |       }
177 | 
178 |       // Update category score and issues count
179 |       if (audit.score !== null && audit.score < 0.9) {
180 |         categories[ref.group].issues_count++;
181 |       }
182 |     }
183 |   });
184 | 
185 |   // Second pass: process failed audits into AI-friendly format
186 |   auditRefs
187 |     .filter((ref) => {
188 |       const audit = audits[ref.id];
189 |       return audit && audit.score !== null && audit.score < 0.9;
190 |     })
191 |     .sort((a, b) => (b.weight || 0) - (a.weight || 0))
192 |     // No limit on number of failed audits - we'll show them all
193 |     .forEach((ref) => {
194 |       const audit = audits[ref.id];
195 | 
196 |       // Determine impact level based on score and weight
197 |       let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
198 |       if (audit.score === 0) {
199 |         impact = "critical";
200 |       } else if (audit.score !== null && audit.score <= 0.5) {
201 |         impact = "serious";
202 |       } else if (audit.score !== null && audit.score > 0.7) {
203 |         impact = "minor";
204 |       }
205 | 
206 |       // Create elements array
207 |       const elements: AIAccessibilityElement[] = [];
208 | 
209 |       if (audit.details) {
210 |         const details = audit.details as any;
211 |         if (details.items && Array.isArray(details.items)) {
212 |           const items = details.items;
213 |           // Apply limits based on impact level
214 |           const itemLimit = DETAIL_LIMITS[impact];
215 |           items.slice(0, itemLimit).forEach((item: any) => {
216 |             if (item.node) {
217 |               const element: AIAccessibilityElement = {
218 |                 selector: item.node.selector,
219 |                 snippet: item.node.snippet,
220 |                 label: item.node.nodeLabel,
221 |                 issue_description: item.node.explanation || item.explanation,
222 |               };
223 | 
224 |               if (item.value !== undefined) {
225 |                 element.value = item.value;
226 |               }
227 | 
228 |               elements.push(element);
229 | 
230 |               // Add to critical elements if impact is critical or serious
231 |               if (impact === "critical" || impact === "serious") {
232 |                 criticalElements.push(element);
233 |               }
234 |             }
235 |           });
236 |         }
237 |       }
238 | 
239 |       // Create the issue
240 |       const issue: AIAccessibilityIssue = {
241 |         id: ref.id,
242 |         title: audit.title,
243 |         impact,
244 |         category: ref.group || "other",
245 |         elements: elements.length > 0 ? elements : undefined,
246 |         score: audit.score,
247 |       };
248 | 
249 |       issues.push(issue);
250 |     });
251 | 
252 |   // Calculate overall score
253 |   const score = Math.round((categoryData?.score || 0) * 100);
254 | 
255 |   // Generate prioritized recommendations
256 |   const prioritized_recommendations: string[] = [];
257 | 
258 |   // Add category-specific recommendations
259 |   Object.entries(categories)
260 |     .filter(([_, data]) => data.issues_count > 0)
261 |     .sort(([_, a], [__, b]) => b.issues_count - a.issues_count)
262 |     .forEach(([category, data]) => {
263 |       let recommendation = "";
264 | 
265 |       switch (category) {
266 |         case "a11y-color-contrast":
267 |           recommendation = "Improve color contrast for better readability";
268 |           break;
269 |         case "a11y-names-labels":
270 |           recommendation = "Add proper labels to all interactive elements";
271 |           break;
272 |         case "a11y-aria":
273 |           recommendation = "Fix ARIA attributes and roles";
274 |           break;
275 |         case "a11y-navigation":
276 |           recommendation = "Improve keyboard navigation and focus management";
277 |           break;
278 |         case "a11y-language":
279 |           recommendation = "Add proper language attributes to HTML";
280 |           break;
281 |         case "a11y-tables-lists":
282 |           recommendation = "Fix table and list structures for screen readers";
283 |           break;
284 |         default:
285 |           recommendation = `Fix ${data.issues_count} issues in ${category}`;
286 |       }
287 | 
288 |       prioritized_recommendations.push(recommendation);
289 |     });
290 | 
291 |   // Add specific high-impact recommendations
292 |   if (issues.some((issue) => issue.id === "color-contrast")) {
293 |     prioritized_recommendations.push(
294 |       "Fix low contrast text for better readability"
295 |     );
296 |   }
297 | 
298 |   if (issues.some((issue) => issue.id === "document-title")) {
299 |     prioritized_recommendations.push("Add a descriptive page title");
300 |   }
301 | 
302 |   if (issues.some((issue) => issue.id === "image-alt")) {
303 |     prioritized_recommendations.push("Add alt text to all images");
304 |   }
305 | 
306 |   // Create the report content
307 |   const reportContent: AccessibilityReportContent = {
308 |     score,
309 |     audit_counts: {
310 |       failed: failedCount,
311 |       passed: passedCount,
312 |       manual: manualCount,
313 |       informative: informativeCount,
314 |       not_applicable: notApplicableCount,
315 |     },
316 |     issues,
317 |     categories,
318 |     critical_elements: criticalElements,
319 |     prioritized_recommendations:
320 |       prioritized_recommendations.length > 0
321 |         ? prioritized_recommendations
322 |         : undefined,
323 |   };
324 | 
325 |   // Return the full report following the LighthouseReport interface
326 |   return {
327 |     metadata,
328 |     report: reportContent,
329 |   };
330 | };
331 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/seo.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Result as LighthouseResult } from "lighthouse";
  2 | import { AuditCategory, LighthouseReport } from "./types.js";
  3 | import { runLighthouseAudit } from "./index.js";
  4 | 
  5 | // === SEO Report Types ===
  6 | 
  7 | /**
  8 |  * SEO-specific report content structure
  9 |  */
 10 | export interface SEOReportContent {
 11 |   score: number; // Overall score (0-100)
 12 |   audit_counts: {
 13 |     // Counts of different audit types
 14 |     failed: number;
 15 |     passed: number;
 16 |     manual: number;
 17 |     informative: number;
 18 |     not_applicable: number;
 19 |   };
 20 |   issues: AISEOIssue[];
 21 |   categories: {
 22 |     [category: string]: {
 23 |       score: number;
 24 |       issues_count: number;
 25 |     };
 26 |   };
 27 |   prioritized_recommendations?: string[]; // Ordered list of recommendations
 28 | }
 29 | 
 30 | /**
 31 |  * Full SEO report implementing the base LighthouseReport interface
 32 |  */
 33 | export type AIOptimizedSEOReport = LighthouseReport<SEOReportContent>;
 34 | 
 35 | /**
 36 |  * AI-optimized SEO issue
 37 |  */
 38 | interface AISEOIssue {
 39 |   id: string; // e.g., "meta-description"
 40 |   title: string; // e.g., "Document has a meta description"
 41 |   impact: "critical" | "serious" | "moderate" | "minor";
 42 |   category: string; // e.g., "content", "mobile", "crawlability"
 43 |   details?: {
 44 |     selector?: string; // CSS selector if applicable
 45 |     value?: string; // Current value
 46 |     issue?: string; // Description of the issue
 47 |   }[];
 48 |   score: number | null; // 0-1 or null
 49 | }
 50 | 
 51 | // Original interfaces for backward compatibility
 52 | interface SEOAudit {
 53 |   id: string; // e.g., "meta-description"
 54 |   title: string; // e.g., "Document has a meta description"
 55 |   description: string; // e.g., "Meta descriptions improve SEO..."
 56 |   score: number | null; // 0-1 or null
 57 |   scoreDisplayMode: string; // e.g., "binary"
 58 |   details?: SEOAuditDetails; // Optional, structured details
 59 |   weight?: number; // For prioritization
 60 | }
 61 | 
 62 | interface SEOAuditDetails {
 63 |   items?: Array<{
 64 |     selector?: string; // e.g., "meta[name='description']"
 65 |     issue?: string; // e.g., "Meta description is missing"
 66 |     value?: string; // e.g., Current meta description text
 67 |   }>;
 68 |   type?: string; // e.g., "table"
 69 | }
 70 | 
 71 | // This ensures we always include critical issues while limiting less important ones
 72 | const DETAIL_LIMITS = {
 73 |   critical: Number.MAX_SAFE_INTEGER, // No limit for critical issues
 74 |   serious: 15, // Up to 15 items for serious issues
 75 |   moderate: 10, // Up to 10 items for moderate issues
 76 |   minor: 3, // Up to 3 items for minor issues
 77 | };
 78 | 
 79 | /**
 80 |  * Runs an SEO audit on the specified URL
 81 |  * @param url The URL to audit
 82 |  * @returns Promise resolving to AI-optimized SEO audit results
 83 |  */
 84 | export async function runSEOAudit(url: string): Promise<AIOptimizedSEOReport> {
 85 |   try {
 86 |     const lhr = await runLighthouseAudit(url, [AuditCategory.SEO]);
 87 |     return extractAIOptimizedData(lhr, url);
 88 |   } catch (error) {
 89 |     throw new Error(
 90 |       `SEO audit failed: ${
 91 |         error instanceof Error ? error.message : String(error)
 92 |       }`
 93 |     );
 94 |   }
 95 | }
 96 | 
 97 | /**
 98 |  * Extract AI-optimized SEO data from Lighthouse results
 99 |  */
100 | const extractAIOptimizedData = (
101 |   lhr: LighthouseResult,
102 |   url: string
103 | ): AIOptimizedSEOReport => {
104 |   const categoryData = lhr.categories[AuditCategory.SEO];
105 |   const audits = lhr.audits || {};
106 | 
107 |   // Add metadata
108 |   const metadata = {
109 |     url,
110 |     timestamp: lhr.fetchTime || new Date().toISOString(),
111 |     device: "desktop", // This could be made configurable
112 |     lighthouseVersion: lhr.lighthouseVersion,
113 |   };
114 | 
115 |   // Initialize variables
116 |   const issues: AISEOIssue[] = [];
117 |   const categories: {
118 |     [category: string]: { score: number; issues_count: number };
119 |   } = {
120 |     content: { score: 0, issues_count: 0 },
121 |     mobile: { score: 0, issues_count: 0 },
122 |     crawlability: { score: 0, issues_count: 0 },
123 |     other: { score: 0, issues_count: 0 },
124 |   };
125 | 
126 |   // Count audits by type
127 |   let failedCount = 0;
128 |   let passedCount = 0;
129 |   let manualCount = 0;
130 |   let informativeCount = 0;
131 |   let notApplicableCount = 0;
132 | 
133 |   // Process audit refs
134 |   const auditRefs = categoryData?.auditRefs || [];
135 | 
136 |   // First pass: count audits by type and initialize categories
137 |   auditRefs.forEach((ref) => {
138 |     const audit = audits[ref.id];
139 |     if (!audit) return;
140 | 
141 |     // Count by scoreDisplayMode
142 |     if (audit.scoreDisplayMode === "manual") {
143 |       manualCount++;
144 |     } else if (audit.scoreDisplayMode === "informative") {
145 |       informativeCount++;
146 |     } else if (audit.scoreDisplayMode === "notApplicable") {
147 |       notApplicableCount++;
148 |     } else if (audit.score !== null) {
149 |       // Binary pass/fail
150 |       if (audit.score >= 0.9) {
151 |         passedCount++;
152 |       } else {
153 |         failedCount++;
154 |       }
155 |     }
156 | 
157 |     // Categorize the issue
158 |     let category = "other";
159 |     if (
160 |       ref.id.includes("crawl") ||
161 |       ref.id.includes("http") ||
162 |       ref.id.includes("redirect") ||
163 |       ref.id.includes("robots")
164 |     ) {
165 |       category = "crawlability";
166 |     } else if (
167 |       ref.id.includes("viewport") ||
168 |       ref.id.includes("font-size") ||
169 |       ref.id.includes("tap-targets")
170 |     ) {
171 |       category = "mobile";
172 |     } else if (
173 |       ref.id.includes("document") ||
174 |       ref.id.includes("meta") ||
175 |       ref.id.includes("description") ||
176 |       ref.id.includes("canonical") ||
177 |       ref.id.includes("title") ||
178 |       ref.id.includes("link")
179 |     ) {
180 |       category = "content";
181 |     }
182 | 
183 |     // Update category score and issues count
184 |     if (audit.score !== null && audit.score < 0.9) {
185 |       categories[category].issues_count++;
186 |     }
187 |   });
188 | 
189 |   // Second pass: process failed audits into AI-friendly format
190 |   auditRefs
191 |     .filter((ref) => {
192 |       const audit = audits[ref.id];
193 |       return audit && audit.score !== null && audit.score < 0.9;
194 |     })
195 |     .sort((a, b) => (b.weight || 0) - (a.weight || 0))
196 |     // No limit on failed audits - we'll filter dynamically based on impact
197 |     .forEach((ref) => {
198 |       const audit = audits[ref.id];
199 | 
200 |       // Determine impact level based on score and weight
201 |       let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
202 |       if (audit.score === 0) {
203 |         impact = "critical";
204 |       } else if (audit.score !== null && audit.score <= 0.5) {
205 |         impact = "serious";
206 |       } else if (audit.score !== null && audit.score > 0.7) {
207 |         impact = "minor";
208 |       }
209 | 
210 |       // Categorize the issue
211 |       let category = "other";
212 |       if (
213 |         ref.id.includes("crawl") ||
214 |         ref.id.includes("http") ||
215 |         ref.id.includes("redirect") ||
216 |         ref.id.includes("robots")
217 |       ) {
218 |         category = "crawlability";
219 |       } else if (
220 |         ref.id.includes("viewport") ||
221 |         ref.id.includes("font-size") ||
222 |         ref.id.includes("tap-targets")
223 |       ) {
224 |         category = "mobile";
225 |       } else if (
226 |         ref.id.includes("document") ||
227 |         ref.id.includes("meta") ||
228 |         ref.id.includes("description") ||
229 |         ref.id.includes("canonical") ||
230 |         ref.id.includes("title") ||
231 |         ref.id.includes("link")
232 |       ) {
233 |         category = "content";
234 |       }
235 | 
236 |       // Extract details
237 |       const details: { selector?: string; value?: string; issue?: string }[] =
238 |         [];
239 | 
240 |       if (audit.details) {
241 |         const auditDetails = audit.details as any;
242 |         if (auditDetails.items && Array.isArray(auditDetails.items)) {
243 |           // Determine item limit based on impact
244 |           const itemLimit = DETAIL_LIMITS[impact];
245 | 
246 |           auditDetails.items.slice(0, itemLimit).forEach((item: any) => {
247 |             const detail: {
248 |               selector?: string;
249 |               value?: string;
250 |               issue?: string;
251 |             } = {};
252 | 
253 |             if (item.selector) {
254 |               detail.selector = item.selector;
255 |             }
256 | 
257 |             if (item.value !== undefined) {
258 |               detail.value = item.value;
259 |             }
260 | 
261 |             if (item.issue) {
262 |               detail.issue = item.issue;
263 |             }
264 | 
265 |             if (Object.keys(detail).length > 0) {
266 |               details.push(detail);
267 |             }
268 |           });
269 |         }
270 |       }
271 | 
272 |       // Create the issue
273 |       const issue: AISEOIssue = {
274 |         id: ref.id,
275 |         title: audit.title,
276 |         impact,
277 |         category,
278 |         details: details.length > 0 ? details : undefined,
279 |         score: audit.score,
280 |       };
281 | 
282 |       issues.push(issue);
283 |     });
284 | 
285 |   // Calculate overall score
286 |   const score = Math.round((categoryData?.score || 0) * 100);
287 | 
288 |   // Generate prioritized recommendations
289 |   const prioritized_recommendations: string[] = [];
290 | 
291 |   // Add category-specific recommendations
292 |   Object.entries(categories)
293 |     .filter(([_, data]) => data.issues_count > 0)
294 |     .sort(([_, a], [__, b]) => b.issues_count - a.issues_count)
295 |     .forEach(([category, data]) => {
296 |       if (data.issues_count === 0) return;
297 | 
298 |       let recommendation = "";
299 | 
300 |       switch (category) {
301 |         case "content":
302 |           recommendation = `Improve SEO content (${data.issues_count} issues): titles, descriptions, and headers`;
303 |           break;
304 |         case "mobile":
305 |           recommendation = `Optimize for mobile devices (${data.issues_count} issues)`;
306 |           break;
307 |         case "crawlability":
308 |           recommendation = `Fix crawlability issues (${data.issues_count} issues): robots.txt, sitemaps, and redirects`;
309 |           break;
310 |         default:
311 |           recommendation = `Fix ${data.issues_count} SEO issues in category: ${category}`;
312 |       }
313 | 
314 |       prioritized_recommendations.push(recommendation);
315 |     });
316 | 
317 |   // Add specific high-impact recommendations
318 |   if (issues.some((issue) => issue.id === "meta-description")) {
319 |     prioritized_recommendations.push(
320 |       "Add a meta description to improve click-through rate"
321 |     );
322 |   }
323 | 
324 |   if (issues.some((issue) => issue.id === "document-title")) {
325 |     prioritized_recommendations.push(
326 |       "Add a descriptive page title with keywords"
327 |     );
328 |   }
329 | 
330 |   if (issues.some((issue) => issue.id === "hreflang")) {
331 |     prioritized_recommendations.push(
332 |       "Fix hreflang implementation for international SEO"
333 |     );
334 |   }
335 | 
336 |   if (issues.some((issue) => issue.id === "canonical")) {
337 |     prioritized_recommendations.push("Implement proper canonical tags");
338 |   }
339 | 
340 |   // Create the report content
341 |   const reportContent: SEOReportContent = {
342 |     score,
343 |     audit_counts: {
344 |       failed: failedCount,
345 |       passed: passedCount,
346 |       manual: manualCount,
347 |       informative: informativeCount,
348 |       not_applicable: notApplicableCount,
349 |     },
350 |     issues,
351 |     categories,
352 |     prioritized_recommendations:
353 |       prioritized_recommendations.length > 0
354 |         ? prioritized_recommendations
355 |         : undefined,
356 |   };
357 | 
358 |   // Return the full report following the LighthouseReport interface
359 |   return {
360 |     metadata,
361 |     report: reportContent,
362 |   };
363 | };
364 | 
```

--------------------------------------------------------------------------------
/docs/mcp.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP TypeScript SDK ![NPM Version](mdc:https:/img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](mdc:https:/img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)
  2 | 
  3 | ## Table of Contents
  4 | 
  5 | - [Overview](mdc:#overview)
  6 | - [Installation](mdc:#installation)
  7 | - [Quickstart](mdc:#quickstart)
  8 | - [What is MCP?](mdc:#what-is-mcp)
  9 | - [Core Concepts](mdc:#core-concepts)
 10 |   - [Server](mdc:#server)
 11 |   - [Resources](mdc:#resources)
 12 |   - [Tools](mdc:#tools)
 13 |   - [Prompts](mdc:#prompts)
 14 | - [Running Your Server](mdc:#running-your-server)
 15 |   - [stdio](mdc:#stdio)
 16 |   - [HTTP with SSE](mdc:#http-with-sse)
 17 |   - [Testing and Debugging](mdc:#testing-and-debugging)
 18 | - [Examples](mdc:#examples)
 19 |   - [Echo Server](mdc:#echo-server)
 20 |   - [SQLite Explorer](mdc:#sqlite-explorer)
 21 | - [Advanced Usage](mdc:#advanced-usage)
 22 |   - [Low-Level Server](mdc:#low-level-server)
 23 |   - [Writing MCP Clients](mdc:#writing-mcp-clients)
 24 |   - [Server Capabilities](mdc:#server-capabilities)
 25 | 
 26 | ## Overview
 27 | 
 28 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:
 29 | 
 30 | - Build MCP clients that can connect to any MCP server
 31 | - Create MCP servers that expose resources, prompts and tools
 32 | - Use standard transports like stdio and SSE
 33 | - Handle all MCP protocol messages and lifecycle events
 34 | 
 35 | ## Installation
 36 | 
 37 | ```bash
 38 | npm install @modelcontextprotocol/sdk
 39 | ```
 40 | 
 41 | ## Quick Start
 42 | 
 43 | Let's create a simple MCP server that exposes a calculator tool and some data:
 44 | 
 45 | ```typescript
 46 | import {
 47 |   McpServer,
 48 |   ResourceTemplate,
 49 | } from "@modelcontextprotocol/sdk/server/mcp.js";
 50 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 51 | import { z } from "zod";
 52 | 
 53 | // Create an MCP server
 54 | const server = new McpServer({
 55 |   name: "Demo",
 56 |   version: "1.0.0",
 57 | });
 58 | 
 59 | // Add an addition tool
 60 | server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({
 61 |   content: [{ type: "text", text: String(a + b) }],
 62 | }));
 63 | 
 64 | // Add a dynamic greeting resource
 65 | server.resource(
 66 |   "greeting",
 67 |   new ResourceTemplate("greeting://{name}", { list: undefined }),
 68 |   async (uri, { name }) => ({
 69 |     contents: [
 70 |       {
 71 |         uri: uri.href,
 72 |         text: `Hello, ${name}!`,
 73 |       },
 74 |     ],
 75 |   })
 76 | );
 77 | 
 78 | // Start receiving messages on stdin and sending messages on stdout
 79 | const transport = new StdioServerTransport();
 80 | await server.connect(transport);
 81 | ```
 82 | 
 83 | ## What is MCP?
 84 | 
 85 | The [Model Context Protocol (MCP)](mdc:https:/modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
 86 | 
 87 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
 88 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
 89 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
 90 | - And more!
 91 | 
 92 | ## Core Concepts
 93 | 
 94 | ### Server
 95 | 
 96 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
 97 | 
 98 | ```typescript
 99 | const server = new McpServer({
100 |   name: "My App",
101 |   version: "1.0.0",
102 | });
103 | ```
104 | 
105 | ### Resources
106 | 
107 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
108 | 
109 | ```typescript
110 | // Static resource
111 | server.resource("config", "config://app", async (uri) => ({
112 |   contents: [
113 |     {
114 |       uri: uri.href,
115 |       text: "App configuration here",
116 |     },
117 |   ],
118 | }));
119 | 
120 | // Dynamic resource with parameters
121 | server.resource(
122 |   "user-profile",
123 |   new ResourceTemplate("users://{userId}/profile", { list: undefined }),
124 |   async (uri, { userId }) => ({
125 |     contents: [
126 |       {
127 |         uri: uri.href,
128 |         text: `Profile data for user ${userId}`,
129 |       },
130 |     ],
131 |   })
132 | );
133 | ```
134 | 
135 | ### Tools
136 | 
137 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
138 | 
139 | ```typescript
140 | // Simple tool with parameters
141 | server.tool(
142 |   "calculate-bmi",
143 |   {
144 |     weightKg: z.number(),
145 |     heightM: z.number(),
146 |   },
147 |   async ({ weightKg, heightM }) => ({
148 |     content: [
149 |       {
150 |         type: "text",
151 |         text: String(weightKg / (heightM * heightM)),
152 |       },
153 |     ],
154 |   })
155 | );
156 | 
157 | // Async tool with external API call
158 | server.tool("fetch-weather", { city: z.string() }, async ({ city }) => {
159 |   const response = await fetch(`https://api.weather.com/${city}`);
160 |   const data = await response.text();
161 |   return {
162 |     content: [{ type: "text", text: data }],
163 |   };
164 | });
165 | ```
166 | 
167 | ### Prompts
168 | 
169 | Prompts are reusable templates that help LLMs interact with your server effectively:
170 | 
171 | ```typescript
172 | server.prompt("review-code", { code: z.string() }, ({ code }) => ({
173 |   messages: [
174 |     {
175 |       role: "user",
176 |       content: {
177 |         type: "text",
178 |         text: `Please review this code:\n\n${code}`,
179 |       },
180 |     },
181 |   ],
182 | }));
183 | ```
184 | 
185 | ## Running Your Server
186 | 
187 | MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
188 | 
189 | ### stdio
190 | 
191 | For command-line tools and direct integrations:
192 | 
193 | ```typescript
194 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
195 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
196 | 
197 | const server = new McpServer({
198 |   name: "example-server",
199 |   version: "1.0.0",
200 | });
201 | 
202 | // ... set up server resources, tools, and prompts ...
203 | 
204 | const transport = new StdioServerTransport();
205 | await server.connect(transport);
206 | ```
207 | 
208 | ### HTTP with SSE
209 | 
210 | For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:
211 | 
212 | ```typescript
213 | import express from "express";
214 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
215 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
216 | 
217 | const server = new McpServer({
218 |   name: "example-server",
219 |   version: "1.0.0",
220 | });
221 | 
222 | // ... set up server resources, tools, and prompts ...
223 | 
224 | const app = express();
225 | 
226 | app.get("/sse", async (req, res) => {
227 |   const transport = new SSEServerTransport("/messages", res);
228 |   await server.connect(transport);
229 | });
230 | 
231 | app.post("/messages", async (req, res) => {
232 |   // Note: to support multiple simultaneous connections, these messages will
233 |   // need to be routed to a specific matching transport. (This logic isn't
234 |   // implemented here, for simplicity.)
235 |   await transport.handlePostMessage(req, res);
236 | });
237 | 
238 | app.listen(3001);
239 | ```
240 | 
241 | ### Testing and Debugging
242 | 
243 | To test your server, you can use the [MCP Inspector](mdc:https:/github.com/modelcontextprotocol/inspector). See its README for more information.
244 | 
245 | ## Examples
246 | 
247 | ### Echo Server
248 | 
249 | A simple server demonstrating resources, tools, and prompts:
250 | 
251 | ```typescript
252 | import {
253 |   McpServer,
254 |   ResourceTemplate,
255 | } from "@modelcontextprotocol/sdk/server/mcp.js";
256 | import { z } from "zod";
257 | 
258 | const server = new McpServer({
259 |   name: "Echo",
260 |   version: "1.0.0",
261 | });
262 | 
263 | server.resource(
264 |   "echo",
265 |   new ResourceTemplate("echo://{message}", { list: undefined }),
266 |   async (uri, { message }) => ({
267 |     contents: [
268 |       {
269 |         uri: uri.href,
270 |         text: `Resource echo: ${message}`,
271 |       },
272 |     ],
273 |   })
274 | );
275 | 
276 | server.tool("echo", { message: z.string() }, async ({ message }) => ({
277 |   content: [{ type: "text", text: `Tool echo: ${message}` }],
278 | }));
279 | 
280 | server.prompt("echo", { message: z.string() }, ({ message }) => ({
281 |   messages: [
282 |     {
283 |       role: "user",
284 |       content: {
285 |         type: "text",
286 |         text: `Please process this message: ${message}`,
287 |       },
288 |     },
289 |   ],
290 | }));
291 | ```
292 | 
293 | ### SQLite Explorer
294 | 
295 | A more complex example showing database integration:
296 | 
297 | ```typescript
298 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
299 | import sqlite3 from "sqlite3";
300 | import { promisify } from "util";
301 | import { z } from "zod";
302 | 
303 | const server = new McpServer({
304 |   name: "SQLite Explorer",
305 |   version: "1.0.0",
306 | });
307 | 
308 | // Helper to create DB connection
309 | const getDb = () => {
310 |   const db = new sqlite3.Database("database.db");
311 |   return {
312 |     all: promisify<string, any[]>(db.all.bind(db)),
313 |     close: promisify(db.close.bind(db)),
314 |   };
315 | };
316 | 
317 | server.resource("schema", "schema://main", async (uri) => {
318 |   const db = getDb();
319 |   try {
320 |     const tables = await db.all(
321 |       "SELECT sql FROM sqlite_master WHERE type='table'"
322 |     );
323 |     return {
324 |       contents: [
325 |         {
326 |           uri: uri.href,
327 |           text: tables.map((t: { sql: string }) => t.sql).join("\n"),
328 |         },
329 |       ],
330 |     };
331 |   } finally {
332 |     await db.close();
333 |   }
334 | });
335 | 
336 | server.tool("query", { sql: z.string() }, async ({ sql }) => {
337 |   const db = getDb();
338 |   try {
339 |     const results = await db.all(sql);
340 |     return {
341 |       content: [
342 |         {
343 |           type: "text",
344 |           text: JSON.stringify(results, null, 2),
345 |         },
346 |       ],
347 |     };
348 |   } catch (err: unknown) {
349 |     const error = err as Error;
350 |     return {
351 |       content: [
352 |         {
353 |           type: "text",
354 |           text: `Error: ${error.message}`,
355 |         },
356 |       ],
357 |       isError: true,
358 |     };
359 |   } finally {
360 |     await db.close();
361 |   }
362 | });
363 | ```
364 | 
365 | ## Advanced Usage
366 | 
367 | ### Low-Level Server
368 | 
369 | For more control, you can use the low-level Server class directly:
370 | 
371 | ```typescript
372 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
373 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
374 | import {
375 |   ListPromptsRequestSchema,
376 |   GetPromptRequestSchema,
377 | } from "@modelcontextprotocol/sdk/types.js";
378 | 
379 | const server = new Server(
380 |   {
381 |     name: "example-server",
382 |     version: "1.0.0",
383 |   },
384 |   {
385 |     capabilities: {
386 |       prompts: {},
387 |     },
388 |   }
389 | );
390 | 
391 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
392 |   return {
393 |     prompts: [
394 |       {
395 |         name: "example-prompt",
396 |         description: "An example prompt template",
397 |         arguments: [
398 |           {
399 |             name: "arg1",
400 |             description: "Example argument",
401 |             required: true,
402 |           },
403 |         ],
404 |       },
405 |     ],
406 |   };
407 | });
408 | 
409 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
410 |   if (request.params.name !== "example-prompt") {
411 |     throw new Error("Unknown prompt");
412 |   }
413 |   return {
414 |     description: "Example prompt",
415 |     messages: [
416 |       {
417 |         role: "user",
418 |         content: {
419 |           type: "text",
420 |           text: "Example prompt text",
421 |         },
422 |       },
423 |     ],
424 |   };
425 | });
426 | 
427 | const transport = new StdioServerTransport();
428 | await server.connect(transport);
429 | ```
430 | 
431 | ### Writing MCP Clients
432 | 
433 | The SDK provides a high-level client interface:
434 | 
435 | ```typescript
436 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
437 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
438 | 
439 | const transport = new StdioClientTransport({
440 |   command: "node",
441 |   args: ["server.js"],
442 | });
443 | 
444 | const client = new Client(
445 |   {
446 |     name: "example-client",
447 |     version: "1.0.0",
448 |   },
449 |   {
450 |     capabilities: {
451 |       prompts: {},
452 |       resources: {},
453 |       tools: {},
454 |     },
455 |   }
456 | );
457 | 
458 | await client.connect(transport);
459 | 
460 | // List prompts
461 | const prompts = await client.listPrompts();
462 | 
463 | // Get a prompt
464 | const prompt = await client.getPrompt("example-prompt", {
465 |   arg1: "value",
466 | });
467 | 
468 | // List resources
469 | const resources = await client.listResources();
470 | 
471 | // Read a resource
472 | const resource = await client.readResource("file:///example.txt");
473 | 
474 | // Call a tool
475 | const result = await client.callTool({
476 |   name: "example-tool",
477 |   arguments: {
478 |     arg1: "value",
479 |   },
480 | });
481 | ```
482 | 
483 | ## Documentation
484 | 
485 | - [Model Context Protocol documentation](mdc:https:/modelcontextprotocol.io)
486 | - [MCP Specification](mdc:https:/spec.modelcontextprotocol.io)
487 | - [Example Servers](mdc:https:/github.com/modelcontextprotocol/servers)
488 | 
489 | ## Contributing
490 | 
491 | Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.
492 | 
493 | ## License
494 | 
495 | This project is licensed under the MIT License—see the [LICENSE](mdc:LICENSE) file for details.
496 | 
```

--------------------------------------------------------------------------------
/chrome-extension/background.js:
--------------------------------------------------------------------------------

```javascript
  1 | // Listen for messages from the devtools panel
  2 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  3 |   if (message.type === "GET_CURRENT_URL" && message.tabId) {
  4 |     getCurrentTabUrl(message.tabId)
  5 |       .then((url) => {
  6 |         sendResponse({ success: true, url: url });
  7 |       })
  8 |       .catch((error) => {
  9 |         sendResponse({ success: false, error: error.message });
 10 |       });
 11 |     return true; // Required to use sendResponse asynchronously
 12 |   }
 13 | 
 14 |   // Handle explicit request to update the server with the URL
 15 |   if (message.type === "UPDATE_SERVER_URL" && message.tabId && message.url) {
 16 |     console.log(
 17 |       `Background: Received request to update server with URL for tab ${message.tabId}: ${message.url}`
 18 |     );
 19 |     updateServerWithUrl(
 20 |       message.tabId,
 21 |       message.url,
 22 |       message.source || "explicit_update"
 23 |     )
 24 |       .then(() => {
 25 |         if (sendResponse) sendResponse({ success: true });
 26 |       })
 27 |       .catch((error) => {
 28 |         console.error("Background: Error updating server with URL:", error);
 29 |         if (sendResponse)
 30 |           sendResponse({ success: false, error: error.message });
 31 |       });
 32 |     return true; // Required to use sendResponse asynchronously
 33 |   }
 34 | 
 35 |   if (message.type === "CAPTURE_SCREENSHOT" && message.tabId) {
 36 |     // First get the server settings
 37 |     chrome.storage.local.get(["browserConnectorSettings"], (result) => {
 38 |       const settings = result.browserConnectorSettings || {
 39 |         serverHost: "localhost",
 40 |         serverPort: 3025,
 41 |       };
 42 | 
 43 |       // Validate server identity first
 44 |       validateServerIdentity(settings.serverHost, settings.serverPort)
 45 |         .then((isValid) => {
 46 |           if (!isValid) {
 47 |             console.error(
 48 |               "Cannot capture screenshot: Not connected to a valid browser tools server"
 49 |             );
 50 |             sendResponse({
 51 |               success: false,
 52 |               error:
 53 |                 "Not connected to a valid browser tools server. Please check your connection settings.",
 54 |             });
 55 |             return;
 56 |           }
 57 | 
 58 |           // Continue with screenshot capture
 59 |           captureAndSendScreenshot(message, settings, sendResponse);
 60 |         })
 61 |         .catch((error) => {
 62 |           console.error("Error validating server:", error);
 63 |           sendResponse({
 64 |             success: false,
 65 |             error: "Failed to validate server identity: " + error.message,
 66 |           });
 67 |         });
 68 |     });
 69 |     return true; // Required to use sendResponse asynchronously
 70 |   }
 71 | });
 72 | 
 73 | // Validate server identity
 74 | async function validateServerIdentity(host, port) {
 75 |   try {
 76 |     const response = await fetch(`http://${host}:${port}/.identity`, {
 77 |       signal: AbortSignal.timeout(3000), // 3 second timeout
 78 |     });
 79 | 
 80 |     if (!response.ok) {
 81 |       console.error(`Invalid server response: ${response.status}`);
 82 |       return false;
 83 |     }
 84 | 
 85 |     const identity = await response.json();
 86 | 
 87 |     // Validate the server signature
 88 |     if (identity.signature !== "mcp-browser-connector-24x7") {
 89 |       console.error("Invalid server signature - not the browser tools server");
 90 |       return false;
 91 |     }
 92 | 
 93 |     return true;
 94 |   } catch (error) {
 95 |     console.error("Error validating server identity:", error);
 96 |     return false;
 97 |   }
 98 | }
 99 | 
100 | // Helper function to process the tab and run the audit
101 | function processTabForAudit(tab, tabId) {
102 |   const url = tab.url;
103 | 
104 |   if (!url) {
105 |     console.error(`No URL available for tab ${tabId}`);
106 |     return;
107 |   }
108 | 
109 |   // Update our cache and the server with this URL
110 |   tabUrls.set(tabId, url);
111 |   updateServerWithUrl(tabId, url);
112 | }
113 | 
114 | // Track URLs for each tab
115 | const tabUrls = new Map();
116 | 
117 | // Function to get the current URL for a tab
118 | async function getCurrentTabUrl(tabId) {
119 |   try {
120 |     console.log("Background: Getting URL for tab", tabId);
121 | 
122 |     // First check if we have it cached
123 |     if (tabUrls.has(tabId)) {
124 |       const cachedUrl = tabUrls.get(tabId);
125 |       console.log("Background: Found cached URL:", cachedUrl);
126 |       return cachedUrl;
127 |     }
128 | 
129 |     // Otherwise get it from the tab
130 |     try {
131 |       const tab = await chrome.tabs.get(tabId);
132 |       if (tab && tab.url) {
133 |         // Cache the URL
134 |         tabUrls.set(tabId, tab.url);
135 |         console.log("Background: Got URL from tab:", tab.url);
136 |         return tab.url;
137 |       } else {
138 |         console.log("Background: Tab exists but no URL found");
139 |       }
140 |     } catch (tabError) {
141 |       console.error("Background: Error getting tab:", tabError);
142 |     }
143 | 
144 |     // If we can't get the tab directly, try querying for active tabs
145 |     try {
146 |       const tabs = await chrome.tabs.query({
147 |         active: true,
148 |         currentWindow: true,
149 |       });
150 |       if (tabs && tabs.length > 0 && tabs[0].url) {
151 |         const activeUrl = tabs[0].url;
152 |         console.log("Background: Got URL from active tab:", activeUrl);
153 |         // Cache this URL as well
154 |         tabUrls.set(tabId, activeUrl);
155 |         return activeUrl;
156 |       }
157 |     } catch (queryError) {
158 |       console.error("Background: Error querying tabs:", queryError);
159 |     }
160 | 
161 |     console.log("Background: Could not find URL for tab", tabId);
162 |     return null;
163 |   } catch (error) {
164 |     console.error("Background: Error getting tab URL:", error);
165 |     return null;
166 |   }
167 | }
168 | 
169 | // Listen for tab updates to detect page refreshes and URL changes
170 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
171 |   // Track URL changes
172 |   if (changeInfo.url) {
173 |     console.log(`URL changed in tab ${tabId} to ${changeInfo.url}`);
174 |     tabUrls.set(tabId, changeInfo.url);
175 | 
176 |     // Send URL update to server if possible
177 |     updateServerWithUrl(tabId, changeInfo.url, "tab_url_change");
178 |   }
179 | 
180 |   // Check if this is a page refresh (status becoming "complete")
181 |   if (changeInfo.status === "complete") {
182 |     // Update URL in our cache
183 |     if (tab.url) {
184 |       tabUrls.set(tabId, tab.url);
185 |       // Send URL update to server if possible
186 |       updateServerWithUrl(tabId, tab.url, "page_complete");
187 |     }
188 | 
189 |     retestConnectionOnRefresh(tabId);
190 |   }
191 | });
192 | 
193 | // Listen for tab activation (switching between tabs)
194 | chrome.tabs.onActivated.addListener((activeInfo) => {
195 |   const tabId = activeInfo.tabId;
196 |   console.log(`Tab activated: ${tabId}`);
197 | 
198 |   // Get the URL of the newly activated tab
199 |   chrome.tabs.get(tabId, (tab) => {
200 |     if (chrome.runtime.lastError) {
201 |       console.error("Error getting tab info:", chrome.runtime.lastError);
202 |       return;
203 |     }
204 | 
205 |     if (tab && tab.url) {
206 |       console.log(`Active tab changed to ${tab.url}`);
207 | 
208 |       // Update our cache
209 |       tabUrls.set(tabId, tab.url);
210 | 
211 |       // Send URL update to server
212 |       updateServerWithUrl(tabId, tab.url, "tab_activated");
213 |     }
214 |   });
215 | });
216 | 
217 | // Function to update the server with the current URL
218 | async function updateServerWithUrl(tabId, url, source = "background_update") {
219 |   if (!url) {
220 |     console.error("Cannot update server with empty URL");
221 |     return;
222 |   }
223 | 
224 |   console.log(`Updating server with URL for tab ${tabId}: ${url}`);
225 | 
226 |   // Get the saved settings
227 |   chrome.storage.local.get(["browserConnectorSettings"], async (result) => {
228 |     const settings = result.browserConnectorSettings || {
229 |       serverHost: "localhost",
230 |       serverPort: 3025,
231 |     };
232 | 
233 |     // Maximum number of retry attempts
234 |     const maxRetries = 3;
235 |     let retryCount = 0;
236 |     let success = false;
237 | 
238 |     while (retryCount < maxRetries && !success) {
239 |       try {
240 |         // Send the URL to the server
241 |         const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/current-url`;
242 |         console.log(
243 |           `Attempt ${
244 |             retryCount + 1
245 |           }/${maxRetries} to update server with URL: ${url}`
246 |         );
247 | 
248 |         const response = await fetch(serverUrl, {
249 |           method: "POST",
250 |           headers: {
251 |             "Content-Type": "application/json",
252 |           },
253 |           body: JSON.stringify({
254 |             url: url,
255 |             tabId: tabId,
256 |             timestamp: Date.now(),
257 |             source: source,
258 |           }),
259 |           // Add a timeout to prevent hanging requests
260 |           signal: AbortSignal.timeout(5000),
261 |         });
262 | 
263 |         if (response.ok) {
264 |           const responseData = await response.json();
265 |           console.log(
266 |             `Successfully updated server with URL: ${url}`,
267 |             responseData
268 |           );
269 |           success = true;
270 |         } else {
271 |           console.error(
272 |             `Server returned error: ${response.status} ${response.statusText}`
273 |           );
274 |           retryCount++;
275 |           // Wait before retrying
276 |           await new Promise((resolve) => setTimeout(resolve, 500));
277 |         }
278 |       } catch (error) {
279 |         console.error(`Error updating server with URL: ${error.message}`);
280 |         retryCount++;
281 |         // Wait before retrying
282 |         await new Promise((resolve) => setTimeout(resolve, 500));
283 |       }
284 |     }
285 | 
286 |     if (!success) {
287 |       console.error(
288 |         `Failed to update server with URL after ${maxRetries} attempts`
289 |       );
290 |     }
291 |   });
292 | }
293 | 
294 | // Clean up when tabs are closed
295 | chrome.tabs.onRemoved.addListener((tabId) => {
296 |   tabUrls.delete(tabId);
297 | });
298 | 
299 | // Function to retest connection when a page is refreshed
300 | async function retestConnectionOnRefresh(tabId) {
301 |   console.log(`Page refreshed in tab ${tabId}, retesting connection...`);
302 | 
303 |   // Get the saved settings
304 |   chrome.storage.local.get(["browserConnectorSettings"], async (result) => {
305 |     const settings = result.browserConnectorSettings || {
306 |       serverHost: "localhost",
307 |       serverPort: 3025,
308 |     };
309 | 
310 |     // Test the connection with the last known host and port
311 |     const isConnected = await validateServerIdentity(
312 |       settings.serverHost,
313 |       settings.serverPort
314 |     );
315 | 
316 |     // Notify all devtools instances about the connection status
317 |     chrome.runtime.sendMessage({
318 |       type: "CONNECTION_STATUS_UPDATE",
319 |       isConnected: isConnected,
320 |       tabId: tabId,
321 |     });
322 | 
323 |     // Always notify for page refresh, whether connected or not
324 |     // This ensures any ongoing discovery is cancelled and restarted
325 |     chrome.runtime.sendMessage({
326 |       type: "INITIATE_AUTO_DISCOVERY",
327 |       reason: "page_refresh",
328 |       tabId: tabId,
329 |       forceRestart: true, // Add a flag to indicate this should force restart any ongoing processes
330 |     });
331 | 
332 |     if (!isConnected) {
333 |       console.log(
334 |         "Connection test failed after page refresh, initiating auto-discovery..."
335 |       );
336 |     } else {
337 |       console.log("Connection test successful after page refresh");
338 |     }
339 |   });
340 | }
341 | 
342 | // Function to capture and send screenshot
343 | function captureAndSendScreenshot(message, settings, sendResponse) {
344 |   // Get the inspected window's tab
345 |   chrome.tabs.get(message.tabId, (tab) => {
346 |     if (chrome.runtime.lastError) {
347 |       console.error("Error getting tab:", chrome.runtime.lastError);
348 |       sendResponse({
349 |         success: false,
350 |         error: chrome.runtime.lastError.message,
351 |       });
352 |       return;
353 |     }
354 | 
355 |     // Get all windows to find the one containing our tab
356 |     chrome.windows.getAll({ populate: true }, (windows) => {
357 |       const targetWindow = windows.find((w) =>
358 |         w.tabs.some((t) => t.id === message.tabId)
359 |       );
360 | 
361 |       if (!targetWindow) {
362 |         console.error("Could not find window containing the inspected tab");
363 |         sendResponse({
364 |           success: false,
365 |           error: "Could not find window containing the inspected tab",
366 |         });
367 |         return;
368 |       }
369 | 
370 |       // Capture screenshot of the window containing our tab
371 |       chrome.tabs.captureVisibleTab(
372 |         targetWindow.id,
373 |         { format: "png" },
374 |         (dataUrl) => {
375 |           // Ignore DevTools panel capture error if it occurs
376 |           if (
377 |             chrome.runtime.lastError &&
378 |             !chrome.runtime.lastError.message.includes("devtools://")
379 |           ) {
380 |             console.error(
381 |               "Error capturing screenshot:",
382 |               chrome.runtime.lastError
383 |             );
384 |             sendResponse({
385 |               success: false,
386 |               error: chrome.runtime.lastError.message,
387 |             });
388 |             return;
389 |           }
390 | 
391 |           // Send screenshot data to browser connector using configured settings
392 |           const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/screenshot`;
393 |           console.log(`Sending screenshot to ${serverUrl}`);
394 | 
395 |           fetch(serverUrl, {
396 |             method: "POST",
397 |             headers: {
398 |               "Content-Type": "application/json",
399 |             },
400 |             body: JSON.stringify({
401 |               data: dataUrl,
402 |               path: message.screenshotPath,
403 |             }),
404 |           })
405 |             .then((response) => response.json())
406 |             .then((result) => {
407 |               if (result.error) {
408 |                 console.error("Error from server:", result.error);
409 |                 sendResponse({ success: false, error: result.error });
410 |               } else {
411 |                 console.log("Screenshot saved successfully:", result.path);
412 |                 // Send success response even if DevTools capture failed
413 |                 sendResponse({
414 |                   success: true,
415 |                   path: result.path,
416 |                   title: tab.title || "Current Tab",
417 |                 });
418 |               }
419 |             })
420 |             .catch((error) => {
421 |               console.error("Error sending screenshot data:", error);
422 |               sendResponse({
423 |                 success: false,
424 |                 error: error.message || "Failed to save screenshot",
425 |               });
426 |             });
427 |         }
428 |       );
429 |     });
430 |   });
431 | }
432 | 
```

--------------------------------------------------------------------------------
/browser-tools-server/lighthouse/performance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Result as LighthouseResult } from "lighthouse";
  2 | import { AuditCategory, LighthouseReport } from "./types.js";
  3 | import { runLighthouseAudit } from "./index.js";
  4 | 
  5 | // === Performance Report Types ===
  6 | 
  7 | /**
  8 |  * Performance-specific report content structure
  9 |  */
 10 | export interface PerformanceReportContent {
 11 |   score: number; // Overall score (0-100)
 12 |   audit_counts: {
 13 |     // Counts of different audit types
 14 |     failed: number;
 15 |     passed: number;
 16 |     manual: number;
 17 |     informative: number;
 18 |     not_applicable: number;
 19 |   };
 20 |   metrics: AIOptimizedMetric[];
 21 |   opportunities: AIOptimizedOpportunity[];
 22 |   page_stats?: AIPageStats; // Optional page statistics
 23 |   prioritized_recommendations?: string[]; // Ordered list of recommendations
 24 | }
 25 | 
 26 | /**
 27 |  * Full performance report implementing the base LighthouseReport interface
 28 |  */
 29 | export type AIOptimizedPerformanceReport =
 30 |   LighthouseReport<PerformanceReportContent>;
 31 | 
 32 | // AI-optimized performance metric format
 33 | interface AIOptimizedMetric {
 34 |   id: string; // Short ID like "lcp", "fcp"
 35 |   score: number | null; // 0-1 score
 36 |   value_ms: number; // Value in milliseconds
 37 |   element_type?: string; // For LCP: "image", "text", etc.
 38 |   element_selector?: string; // DOM selector for the element
 39 |   element_url?: string; // For images/videos
 40 |   element_content?: string; // For text content (truncated)
 41 |   passes_core_web_vital?: boolean; // Whether this metric passes as a Core Web Vital
 42 | }
 43 | 
 44 | // AI-optimized opportunity format
 45 | interface AIOptimizedOpportunity {
 46 |   id: string; // Like "render_blocking", "http2"
 47 |   savings_ms: number; // Time savings in ms
 48 |   severity?: "critical" | "serious" | "moderate" | "minor"; // Severity classification
 49 |   resources: Array<{
 50 |     url: string; // Resource URL
 51 |     savings_ms?: number; // Individual resource savings
 52 |     size_kb?: number; // Size in KB
 53 |     type?: string; // Resource type (js, css, img, etc.)
 54 |     is_third_party?: boolean; // Whether this is a third-party resource
 55 |   }>;
 56 | }
 57 | 
 58 | // Page stats for AI analysis
 59 | interface AIPageStats {
 60 |   total_size_kb: number; // Total page weight in KB
 61 |   total_requests: number; // Total number of requests
 62 |   resource_counts: {
 63 |     // Count by resource type
 64 |     js: number;
 65 |     css: number;
 66 |     img: number;
 67 |     font: number;
 68 |     other: number;
 69 |   };
 70 |   third_party_size_kb: number; // Size of third-party resources
 71 |   main_thread_blocking_time_ms: number; // Time spent blocking the main thread
 72 | }
 73 | 
 74 | // This ensures we always include critical issues while limiting less important ones
 75 | const DETAIL_LIMITS = {
 76 |   critical: Number.MAX_SAFE_INTEGER, // No limit for critical issues
 77 |   serious: 15, // Up to 15 items for serious issues
 78 |   moderate: 10, // Up to 10 items for moderate issues
 79 |   minor: 3, // Up to 3 items for minor issues
 80 | };
 81 | 
 82 | /**
 83 |  * Performance audit adapted for AI consumption
 84 |  * This format is optimized for AI agents with:
 85 |  * - Concise, relevant information without redundant descriptions
 86 |  * - Key metrics and opportunities clearly structured
 87 |  * - Only actionable data that an AI can use for recommendations
 88 |  */
 89 | export async function runPerformanceAudit(
 90 |   url: string
 91 | ): Promise<AIOptimizedPerformanceReport> {
 92 |   try {
 93 |     const lhr = await runLighthouseAudit(url, [AuditCategory.PERFORMANCE]);
 94 |     return extractAIOptimizedData(lhr, url);
 95 |   } catch (error) {
 96 |     throw new Error(
 97 |       `Performance audit failed: ${
 98 |         error instanceof Error ? error.message : String(error)
 99 |       }`
100 |     );
101 |   }
102 | }
103 | 
104 | /**
105 |  * Extract AI-optimized performance data from Lighthouse results
106 |  */
107 | const extractAIOptimizedData = (
108 |   lhr: LighthouseResult,
109 |   url: string
110 | ): AIOptimizedPerformanceReport => {
111 |   const audits = lhr.audits || {};
112 |   const categoryData = lhr.categories[AuditCategory.PERFORMANCE];
113 |   const score = Math.round((categoryData?.score || 0) * 100);
114 | 
115 |   // Add metadata
116 |   const metadata = {
117 |     url,
118 |     timestamp: lhr.fetchTime || new Date().toISOString(),
119 |     device: "desktop", // This could be made configurable
120 |     lighthouseVersion: lhr.lighthouseVersion,
121 |   };
122 | 
123 |   // Count audits by type
124 |   const auditRefs = categoryData?.auditRefs || [];
125 |   let failedCount = 0;
126 |   let passedCount = 0;
127 |   let manualCount = 0;
128 |   let informativeCount = 0;
129 |   let notApplicableCount = 0;
130 | 
131 |   auditRefs.forEach((ref) => {
132 |     const audit = audits[ref.id];
133 |     if (!audit) return;
134 | 
135 |     if (audit.scoreDisplayMode === "manual") {
136 |       manualCount++;
137 |     } else if (audit.scoreDisplayMode === "informative") {
138 |       informativeCount++;
139 |     } else if (audit.scoreDisplayMode === "notApplicable") {
140 |       notApplicableCount++;
141 |     } else if (audit.score !== null) {
142 |       if (audit.score >= 0.9) {
143 |         passedCount++;
144 |       } else {
145 |         failedCount++;
146 |       }
147 |     }
148 |   });
149 | 
150 |   const audit_counts = {
151 |     failed: failedCount,
152 |     passed: passedCount,
153 |     manual: manualCount,
154 |     informative: informativeCount,
155 |     not_applicable: notApplicableCount,
156 |   };
157 | 
158 |   const metrics: AIOptimizedMetric[] = [];
159 |   const opportunities: AIOptimizedOpportunity[] = [];
160 | 
161 |   // Extract core metrics
162 |   if (audits["largest-contentful-paint"]) {
163 |     const lcp = audits["largest-contentful-paint"];
164 |     const lcpElement = audits["largest-contentful-paint-element"];
165 | 
166 |     const metric: AIOptimizedMetric = {
167 |       id: "lcp",
168 |       score: lcp.score,
169 |       value_ms: Math.round(lcp.numericValue || 0),
170 |       passes_core_web_vital: lcp.score !== null && lcp.score >= 0.9,
171 |     };
172 | 
173 |     // Enhanced LCP element detection
174 | 
175 |     // 1. Try from largest-contentful-paint-element audit
176 |     if (lcpElement && lcpElement.details) {
177 |       const lcpDetails = lcpElement.details as any;
178 | 
179 |       // First attempt - try to get directly from items
180 |       if (
181 |         lcpDetails.items &&
182 |         Array.isArray(lcpDetails.items) &&
183 |         lcpDetails.items.length > 0
184 |       ) {
185 |         const item = lcpDetails.items[0];
186 | 
187 |         // For text elements in tables format
188 |         if (item.type === "table" && item.items && item.items.length > 0) {
189 |           const firstTableItem = item.items[0];
190 | 
191 |           if (firstTableItem.node) {
192 |             if (firstTableItem.node.selector) {
193 |               metric.element_selector = firstTableItem.node.selector;
194 |             }
195 | 
196 |             // Determine element type based on path or selector
197 |             const path = firstTableItem.node.path;
198 |             const selector = firstTableItem.node.selector || "";
199 | 
200 |             if (path) {
201 |               if (
202 |                 selector.includes(" > img") ||
203 |                 selector.includes(" img") ||
204 |                 selector.endsWith("img") ||
205 |                 path.includes(",IMG")
206 |               ) {
207 |                 metric.element_type = "image";
208 | 
209 |                 // Try to extract image name from selector
210 |                 const imgMatch = selector.match(/img[.][^> ]+/);
211 |                 if (imgMatch && !metric.element_url) {
212 |                   metric.element_url = imgMatch[0];
213 |                 }
214 |               } else if (
215 |                 path.includes(",SPAN") ||
216 |                 path.includes(",P") ||
217 |                 path.includes(",H")
218 |               ) {
219 |                 metric.element_type = "text";
220 |               }
221 |             }
222 | 
223 |             // Try to extract text content if available
224 |             if (firstTableItem.node.nodeLabel) {
225 |               metric.element_content = firstTableItem.node.nodeLabel.substring(
226 |                 0,
227 |                 100
228 |               );
229 |             }
230 |           }
231 |         }
232 |         // Original handling for direct items
233 |         else if (item.node?.nodeLabel) {
234 |           // Determine element type from node label
235 |           if (item.node.nodeLabel.startsWith("<img")) {
236 |             metric.element_type = "image";
237 |             // Try to extract image URL from the node snippet
238 |             const match = item.node.snippet?.match(/src="([^"]+)"/);
239 |             if (match && match[1]) {
240 |               metric.element_url = match[1];
241 |             }
242 |           } else if (item.node.nodeLabel.startsWith("<video")) {
243 |             metric.element_type = "video";
244 |           } else if (item.node.nodeLabel.startsWith("<h")) {
245 |             metric.element_type = "heading";
246 |           } else {
247 |             metric.element_type = "text";
248 |           }
249 | 
250 |           if (item.node?.selector) {
251 |             metric.element_selector = item.node.selector;
252 |           }
253 |         }
254 |       }
255 |     }
256 | 
257 |     // 2. Try from lcp-lazy-loaded audit
258 |     const lcpImageAudit = audits["lcp-lazy-loaded"];
259 |     if (lcpImageAudit && lcpImageAudit.details) {
260 |       const lcpImageDetails = lcpImageAudit.details as any;
261 | 
262 |       if (
263 |         lcpImageDetails.items &&
264 |         Array.isArray(lcpImageDetails.items) &&
265 |         lcpImageDetails.items.length > 0
266 |       ) {
267 |         const item = lcpImageDetails.items[0];
268 | 
269 |         if (item.url) {
270 |           metric.element_type = "image";
271 |           metric.element_url = item.url;
272 |         }
273 |       }
274 |     }
275 | 
276 |     // 3. Try directly from the LCP audit details
277 |     if (!metric.element_url && lcp.details) {
278 |       const lcpDirectDetails = lcp.details as any;
279 | 
280 |       if (lcpDirectDetails.items && Array.isArray(lcpDirectDetails.items)) {
281 |         for (const item of lcpDirectDetails.items) {
282 |           if (item.url || (item.node && item.node.path)) {
283 |             if (item.url) {
284 |               metric.element_url = item.url;
285 |               metric.element_type = item.url.match(
286 |                 /\.(jpg|jpeg|png|gif|webp|svg)$/i
287 |               )
288 |                 ? "image"
289 |                 : "resource";
290 |             }
291 |             if (item.node && item.node.selector) {
292 |               metric.element_selector = item.node.selector;
293 |             }
294 |             break;
295 |           }
296 |         }
297 |       }
298 |     }
299 | 
300 |     // 4. Check for specific audit that might contain image info
301 |     const largestImageAudit = audits["largest-image-paint"];
302 |     if (largestImageAudit && largestImageAudit.details) {
303 |       const imageDetails = largestImageAudit.details as any;
304 | 
305 |       if (
306 |         imageDetails.items &&
307 |         Array.isArray(imageDetails.items) &&
308 |         imageDetails.items.length > 0
309 |       ) {
310 |         const item = imageDetails.items[0];
311 | 
312 |         if (item.url) {
313 |           // If we have a large image that's close in time to LCP, it's likely the LCP element
314 |           metric.element_type = "image";
315 |           metric.element_url = item.url;
316 |         }
317 |       }
318 |     }
319 | 
320 |     // 5. Check for network requests audit to find image resources
321 |     if (!metric.element_url) {
322 |       const networkRequests = audits["network-requests"];
323 | 
324 |       if (networkRequests && networkRequests.details) {
325 |         const networkDetails = networkRequests.details as any;
326 | 
327 |         if (networkDetails.items && Array.isArray(networkDetails.items)) {
328 |           // Get all image resources loaded close to the LCP time
329 |           const lcpTime = lcp.numericValue || 0;
330 |           const imageResources = networkDetails.items
331 |             .filter(
332 |               (item: any) =>
333 |                 item.url &&
334 |                 item.mimeType &&
335 |                 item.mimeType.startsWith("image/") &&
336 |                 item.endTime &&
337 |                 Math.abs(item.endTime - lcpTime) < 500 // Within 500ms of LCP
338 |             )
339 |             .sort(
340 |               (a: any, b: any) =>
341 |                 Math.abs(a.endTime - lcpTime) - Math.abs(b.endTime - lcpTime)
342 |             );
343 | 
344 |           if (imageResources.length > 0) {
345 |             const closestImage = imageResources[0];
346 | 
347 |             if (!metric.element_type) {
348 |               metric.element_type = "image";
349 |               metric.element_url = closestImage.url;
350 |             }
351 |           }
352 |         }
353 |       }
354 |     }
355 | 
356 |     metrics.push(metric);
357 |   }
358 | 
359 |   if (audits["first-contentful-paint"]) {
360 |     const fcp = audits["first-contentful-paint"];
361 |     metrics.push({
362 |       id: "fcp",
363 |       score: fcp.score,
364 |       value_ms: Math.round(fcp.numericValue || 0),
365 |       passes_core_web_vital: fcp.score !== null && fcp.score >= 0.9,
366 |     });
367 |   }
368 | 
369 |   if (audits["speed-index"]) {
370 |     const si = audits["speed-index"];
371 |     metrics.push({
372 |       id: "si",
373 |       score: si.score,
374 |       value_ms: Math.round(si.numericValue || 0),
375 |     });
376 |   }
377 | 
378 |   if (audits["interactive"]) {
379 |     const tti = audits["interactive"];
380 |     metrics.push({
381 |       id: "tti",
382 |       score: tti.score,
383 |       value_ms: Math.round(tti.numericValue || 0),
384 |     });
385 |   }
386 | 
387 |   // Add CLS (Cumulative Layout Shift)
388 |   if (audits["cumulative-layout-shift"]) {
389 |     const cls = audits["cumulative-layout-shift"];
390 |     metrics.push({
391 |       id: "cls",
392 |       score: cls.score,
393 |       // CLS is not in ms, but a unitless value
394 |       value_ms: Math.round((cls.numericValue || 0) * 1000) / 1000, // Convert to 3 decimal places
395 |       passes_core_web_vital: cls.score !== null && cls.score >= 0.9,
396 |     });
397 |   }
398 | 
399 |   // Add TBT (Total Blocking Time)
400 |   if (audits["total-blocking-time"]) {
401 |     const tbt = audits["total-blocking-time"];
402 |     metrics.push({
403 |       id: "tbt",
404 |       score: tbt.score,
405 |       value_ms: Math.round(tbt.numericValue || 0),
406 |       passes_core_web_vital: tbt.score !== null && tbt.score >= 0.9,
407 |     });
408 |   }
409 | 
410 |   // Extract opportunities
411 |   if (audits["render-blocking-resources"]) {
412 |     const rbrAudit = audits["render-blocking-resources"];
413 | 
414 |     // Determine impact level based on potential savings
415 |     let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
416 |     const savings = Math.round(rbrAudit.numericValue || 0);
417 | 
418 |     if (savings > 2000) {
419 |       impact = "critical";
420 |     } else if (savings > 1000) {
421 |       impact = "serious";
422 |     } else if (savings < 300) {
423 |       impact = "minor";
424 |     }
425 | 
426 |     const opportunity: AIOptimizedOpportunity = {
427 |       id: "render_blocking_resources",
428 |       savings_ms: savings,
429 |       severity: impact,
430 |       resources: [],
431 |     };
432 | 
433 |     const rbrDetails = rbrAudit.details as any;
434 |     if (rbrDetails && rbrDetails.items && Array.isArray(rbrDetails.items)) {
435 |       // Determine how many items to include based on impact
436 |       const itemLimit = DETAIL_LIMITS[impact];
437 | 
438 |       rbrDetails.items
439 |         .slice(0, itemLimit)
440 |         .forEach((item: { url?: string; wastedMs?: number }) => {
441 |           if (item.url) {
442 |             // Extract file name from full URL
443 |             const fileName = item.url.split("/").pop() || item.url;
444 |             opportunity.resources.push({
445 |               url: fileName,
446 |               savings_ms: Math.round(item.wastedMs || 0),
447 |             });
448 |           }
449 |         });
450 |     }
451 | 
452 |     if (opportunity.resources.length > 0) {
453 |       opportunities.push(opportunity);
454 |     }
455 |   }
456 | 
457 |   if (audits["uses-http2"]) {
458 |     const http2Audit = audits["uses-http2"];
459 | 
460 |     // Determine impact level based on potential savings
461 |     let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
462 |     const savings = Math.round(http2Audit.numericValue || 0);
463 | 
464 |     if (savings > 2000) {
465 |       impact = "critical";
466 |     } else if (savings > 1000) {
467 |       impact = "serious";
468 |     } else if (savings < 300) {
469 |       impact = "minor";
470 |     }
471 | 
472 |     const opportunity: AIOptimizedOpportunity = {
473 |       id: "http2",
474 |       savings_ms: savings,
475 |       severity: impact,
476 |       resources: [],
477 |     };
478 | 
479 |     const http2Details = http2Audit.details as any;
480 |     if (
481 |       http2Details &&
482 |       http2Details.items &&
483 |       Array.isArray(http2Details.items)
484 |     ) {
485 |       // Determine how many items to include based on impact
486 |       const itemLimit = DETAIL_LIMITS[impact];
487 | 
488 |       http2Details.items
489 |         .slice(0, itemLimit)
490 |         .forEach((item: { url?: string }) => {
491 |           if (item.url) {
492 |             // Extract file name from full URL
493 |             const fileName = item.url.split("/").pop() || item.url;
494 |             opportunity.resources.push({ url: fileName });
495 |           }
496 |         });
497 |     }
498 | 
499 |     if (opportunity.resources.length > 0) {
500 |       opportunities.push(opportunity);
501 |     }
502 |   }
503 | 
504 |   // After extracting all metrics and opportunities, collect page stats
505 |   // Extract page stats
506 |   let page_stats: AIPageStats | undefined;
507 | 
508 |   // Total page stats
509 |   const totalByteWeight = audits["total-byte-weight"];
510 |   const networkRequests = audits["network-requests"];
511 |   const thirdPartyAudit = audits["third-party-summary"];
512 |   const mainThreadWork = audits["mainthread-work-breakdown"];
513 | 
514 |   if (networkRequests && networkRequests.details) {
515 |     const resourceDetails = networkRequests.details as any;
516 | 
517 |     if (resourceDetails.items && Array.isArray(resourceDetails.items)) {
518 |       const resources = resourceDetails.items;
519 |       const totalRequests = resources.length;
520 | 
521 |       // Calculate total size and counts by type
522 |       let totalSizeKb = 0;
523 |       let jsCount = 0,
524 |         cssCount = 0,
525 |         imgCount = 0,
526 |         fontCount = 0,
527 |         otherCount = 0;
528 | 
529 |       resources.forEach((resource: any) => {
530 |         const sizeKb = resource.transferSize
531 |           ? Math.round(resource.transferSize / 1024)
532 |           : 0;
533 |         totalSizeKb += sizeKb;
534 | 
535 |         // Count by mime type
536 |         const mimeType = resource.mimeType || "";
537 |         if (mimeType.includes("javascript") || resource.url.endsWith(".js")) {
538 |           jsCount++;
539 |         } else if (mimeType.includes("css") || resource.url.endsWith(".css")) {
540 |           cssCount++;
541 |         } else if (
542 |           mimeType.includes("image") ||
543 |           /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(resource.url)
544 |         ) {
545 |           imgCount++;
546 |         } else if (
547 |           mimeType.includes("font") ||
548 |           /\.(woff|woff2|ttf|otf|eot)$/i.test(resource.url)
549 |         ) {
550 |           fontCount++;
551 |         } else {
552 |           otherCount++;
553 |         }
554 |       });
555 | 
556 |       // Calculate third-party size
557 |       let thirdPartySizeKb = 0;
558 |       if (thirdPartyAudit && thirdPartyAudit.details) {
559 |         const thirdPartyDetails = thirdPartyAudit.details as any;
560 |         if (thirdPartyDetails.items && Array.isArray(thirdPartyDetails.items)) {
561 |           thirdPartyDetails.items.forEach((item: any) => {
562 |             if (item.transferSize) {
563 |               thirdPartySizeKb += Math.round(item.transferSize / 1024);
564 |             }
565 |           });
566 |         }
567 |       }
568 | 
569 |       // Get main thread blocking time
570 |       let mainThreadBlockingTimeMs = 0;
571 |       if (mainThreadWork && mainThreadWork.numericValue) {
572 |         mainThreadBlockingTimeMs = Math.round(mainThreadWork.numericValue);
573 |       }
574 | 
575 |       // Create page stats object
576 |       page_stats = {
577 |         total_size_kb: totalSizeKb,
578 |         total_requests: totalRequests,
579 |         resource_counts: {
580 |           js: jsCount,
581 |           css: cssCount,
582 |           img: imgCount,
583 |           font: fontCount,
584 |           other: otherCount,
585 |         },
586 |         third_party_size_kb: thirdPartySizeKb,
587 |         main_thread_blocking_time_ms: mainThreadBlockingTimeMs,
588 |       };
589 |     }
590 |   }
591 | 
592 |   // Generate prioritized recommendations
593 |   const prioritized_recommendations: string[] = [];
594 | 
595 |   // Add key recommendations based on failed audits with high impact
596 |   if (
597 |     audits["render-blocking-resources"] &&
598 |     audits["render-blocking-resources"].score !== null &&
599 |     audits["render-blocking-resources"].score === 0
600 |   ) {
601 |     prioritized_recommendations.push("Eliminate render-blocking resources");
602 |   }
603 | 
604 |   if (
605 |     audits["uses-responsive-images"] &&
606 |     audits["uses-responsive-images"].score !== null &&
607 |     audits["uses-responsive-images"].score === 0
608 |   ) {
609 |     prioritized_recommendations.push("Properly size images");
610 |   }
611 | 
612 |   if (
613 |     audits["uses-optimized-images"] &&
614 |     audits["uses-optimized-images"].score !== null &&
615 |     audits["uses-optimized-images"].score === 0
616 |   ) {
617 |     prioritized_recommendations.push("Efficiently encode images");
618 |   }
619 | 
620 |   if (
621 |     audits["uses-text-compression"] &&
622 |     audits["uses-text-compression"].score !== null &&
623 |     audits["uses-text-compression"].score === 0
624 |   ) {
625 |     prioritized_recommendations.push("Enable text compression");
626 |   }
627 | 
628 |   if (
629 |     audits["uses-http2"] &&
630 |     audits["uses-http2"].score !== null &&
631 |     audits["uses-http2"].score === 0
632 |   ) {
633 |     prioritized_recommendations.push("Use HTTP/2");
634 |   }
635 | 
636 |   // Add more specific recommendations based on Core Web Vitals
637 |   if (
638 |     audits["largest-contentful-paint"] &&
639 |     audits["largest-contentful-paint"].score !== null &&
640 |     audits["largest-contentful-paint"].score < 0.5
641 |   ) {
642 |     prioritized_recommendations.push("Improve Largest Contentful Paint (LCP)");
643 |   }
644 | 
645 |   if (
646 |     audits["cumulative-layout-shift"] &&
647 |     audits["cumulative-layout-shift"].score !== null &&
648 |     audits["cumulative-layout-shift"].score < 0.5
649 |   ) {
650 |     prioritized_recommendations.push("Reduce layout shifts (CLS)");
651 |   }
652 | 
653 |   if (
654 |     audits["total-blocking-time"] &&
655 |     audits["total-blocking-time"].score !== null &&
656 |     audits["total-blocking-time"].score < 0.5
657 |   ) {
658 |     prioritized_recommendations.push("Reduce JavaScript execution time");
659 |   }
660 | 
661 |   // Create the performance report content
662 |   const reportContent: PerformanceReportContent = {
663 |     score,
664 |     audit_counts,
665 |     metrics,
666 |     opportunities,
667 |     page_stats,
668 |     prioritized_recommendations:
669 |       prioritized_recommendations.length > 0
670 |         ? prioritized_recommendations
671 |         : undefined,
672 |   };
673 | 
674 |   // Return the full report following the LighthouseReport interface
675 |   return {
676 |     metadata,
677 |     report: reportContent,
678 |   };
679 | };
680 | 
```

--------------------------------------------------------------------------------
/docs/mcp-docs.md:
--------------------------------------------------------------------------------

```markdown
   1 | ## Resources
   2 | 
   3 | Expose data and content from your servers to LLMs
   4 | 
   5 | Resources are a core primitive in the Model Context Protocol (MCP) that allow servers to expose data and content that can be read by clients and used as context for LLM interactions.
   6 | 
   7 | Resources are designed to be application-controlled, meaning that the client application can decide how and when they should be used. Different MCP clients may handle resources differently. For example:
   8 | 
   9 | Claude Desktop currently requires users to explicitly select resources before they can be used
  10 | Other clients might automatically select resources based on heuristics
  11 | Some implementations may even allow the AI model itself to determine which resources to use
  12 | Server authors should be prepared to handle any of these interaction patterns when implementing resource support. In order to expose data to models automatically, server authors should use a model-controlled primitive such as Tools.
  13 | 
  14 | ​
  15 | Overview
  16 | Resources represent any kind of data that an MCP server wants to make available to clients. This can include:
  17 | 
  18 | File contents
  19 | Database records
  20 | API responses
  21 | Live system data
  22 | Screenshots and images
  23 | Log files
  24 | And more
  25 | Each resource is identified by a unique URI and can contain either text or binary data.
  26 | 
  27 | ​
  28 | Resource URIs
  29 | Resources are identified using URIs that follow this format:
  30 | 
  31 | [protocol]://[host]/[path]
  32 | For example:
  33 | 
  34 | file:///home/user/documents/report.pdf
  35 | postgres://database/customers/schema
  36 | screen://localhost/display1
  37 | The protocol and path structure is defined by the MCP server implementation. Servers can define their own custom URI schemes.
  38 | 
  39 | ​
  40 | Resource types
  41 | Resources can contain two types of content:
  42 | 
  43 | ​
  44 | Text resources
  45 | Text resources contain UTF-8 encoded text data. These are suitable for:
  46 | 
  47 | Source code
  48 | Configuration files
  49 | Log files
  50 | JSON/XML data
  51 | Plain text
  52 | ​
  53 | Binary resources
  54 | Binary resources contain raw binary data encoded in base64. These are suitable for:
  55 | 
  56 | Images
  57 | PDFs
  58 | Audio files
  59 | Video files
  60 | Other non-text formats
  61 | ​
  62 | Resource discovery
  63 | Clients can discover available resources through two main methods:
  64 | 
  65 | ​
  66 | Direct resources
  67 | Servers expose a list of concrete resources via the resources/list endpoint. Each resource includes:
  68 | 
  69 | {
  70 | uri: string; // Unique identifier for the resource
  71 | name: string; // Human-readable name
  72 | description?: string; // Optional description
  73 | mimeType?: string; // Optional MIME type
  74 | }
  75 | ​
  76 | Resource templates
  77 | For dynamic resources, servers can expose URI templates that clients can use to construct valid resource URIs:
  78 | 
  79 | {
  80 | uriTemplate: string; // URI template following RFC 6570
  81 | name: string; // Human-readable name for this type
  82 | description?: string; // Optional description
  83 | mimeType?: string; // Optional MIME type for all matching resources
  84 | }
  85 | ​
  86 | Reading resources
  87 | To read a resource, clients make a resources/read request with the resource URI.
  88 | 
  89 | The server responds with a list of resource contents:
  90 | 
  91 | {
  92 | contents: [
  93 | {
  94 | uri: string; // The URI of the resource
  95 | mimeType?: string; // Optional MIME type
  96 | 
  97 |       // One of:
  98 |       text?: string;      // For text resources
  99 |       blob?: string;      // For binary resources (base64 encoded)
 100 |     }
 101 | 
 102 | ]
 103 | }
 104 | Servers may return multiple resources in response to one resources/read request. This could be used, for example, to return a list of files inside a directory when the directory is read.
 105 | 
 106 | ​
 107 | Resource updates
 108 | MCP supports real-time updates for resources through two mechanisms:
 109 | 
 110 | ​
 111 | List changes
 112 | Servers can notify clients when their list of available resources changes via the notifications/resources/list_changed notification.
 113 | 
 114 | ​
 115 | Content changes
 116 | Clients can subscribe to updates for specific resources:
 117 | 
 118 | Client sends resources/subscribe with resource URI
 119 | Server sends notifications/resources/updated when the resource changes
 120 | Client can fetch latest content with resources/read
 121 | Client can unsubscribe with resources/unsubscribe
 122 | ​
 123 | Example implementation
 124 | Here’s a simple example of implementing resource support in an MCP server:
 125 | 
 126 | ## Prompts
 127 | 
 128 | Create reusable prompt templates and workflows
 129 | 
 130 | Prompts enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
 131 | 
 132 | Prompts are designed to be user-controlled, meaning they are exposed from servers to clients with the intention of the user being able to explicitly select them for use.
 133 | 
 134 | ​
 135 | Overview
 136 | Prompts in MCP are predefined templates that can:
 137 | 
 138 | Accept dynamic arguments
 139 | Include context from resources
 140 | Chain multiple interactions
 141 | Guide specific workflows
 142 | Surface as UI elements (like slash commands)
 143 | ​
 144 | Prompt structure
 145 | Each prompt is defined with:
 146 | 
 147 | {
 148 | name: string; // Unique identifier for the prompt
 149 | description?: string; // Human-readable description
 150 | arguments?: [ // Optional list of arguments
 151 | {
 152 | name: string; // Argument identifier
 153 | description?: string; // Argument description
 154 | required?: boolean; // Whether argument is required
 155 | }
 156 | ]
 157 | }
 158 | ​
 159 | Discovering prompts
 160 | Clients can discover available prompts through the prompts/list endpoint:
 161 | 
 162 | // Request
 163 | {
 164 | method: "prompts/list"
 165 | }
 166 | 
 167 | // Response
 168 | {
 169 | prompts: [
 170 | {
 171 | name: "analyze-code",
 172 | description: "Analyze code for potential improvements",
 173 | arguments: [
 174 | {
 175 | name: "language",
 176 | description: "Programming language",
 177 | required: true
 178 | }
 179 | ]
 180 | }
 181 | ]
 182 | }
 183 | ​
 184 | Using prompts
 185 | To use a prompt, clients make a prompts/get request:
 186 | 
 187 | // Request
 188 | {
 189 | method: "prompts/get",
 190 | params: {
 191 | name: "analyze-code",
 192 | arguments: {
 193 | language: "python"
 194 | }
 195 | }
 196 | }
 197 | 
 198 | // Response
 199 | {
 200 | description: "Analyze Python code for potential improvements",
 201 | messages: [
 202 | {
 203 | role: "user",
 204 | content: {
 205 | type: "text",
 206 | text: "Please analyze the following Python code for potential improvements:\n\n`python\ndef calculate_sum(numbers):\n    total = 0\n    for num in numbers:\n        total = total + num\n    return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n`"
 207 | }
 208 | }
 209 | ]
 210 | }
 211 | ​
 212 | Dynamic prompts
 213 | Prompts can be dynamic and include:
 214 | 
 215 | ​
 216 | Embedded resource context
 217 | 
 218 | {
 219 | "name": "analyze-project",
 220 | "description": "Analyze project logs and code",
 221 | "arguments": [
 222 | {
 223 | "name": "timeframe",
 224 | "description": "Time period to analyze logs",
 225 | "required": true
 226 | },
 227 | {
 228 | "name": "fileUri",
 229 | "description": "URI of code file to review",
 230 | "required": true
 231 | }
 232 | ]
 233 | }
 234 | When handling the prompts/get request:
 235 | 
 236 | {
 237 | "messages": [
 238 | {
 239 | "role": "user",
 240 | "content": {
 241 | "type": "text",
 242 | "text": "Analyze these system logs and the code file for any issues:"
 243 | }
 244 | },
 245 | {
 246 | "role": "user",
 247 | "content": {
 248 | "type": "resource",
 249 | "resource": {
 250 | "uri": "logs://recent?timeframe=1h",
 251 | "text": "[2024-03-14 15:32:11] ERROR: Connection timeout in network.py:127\n[2024-03-14 15:32:15] WARN: Retrying connection (attempt 2/3)\n[2024-03-14 15:32:20] ERROR: Max retries exceeded",
 252 | "mimeType": "text/plain"
 253 | }
 254 | }
 255 | },
 256 | {
 257 | "role": "user",
 258 | "content": {
 259 | "type": "resource",
 260 | "resource": {
 261 | "uri": "file:///path/to/code.py",
 262 | "text": "def connect_to_service(timeout=30):\n retries = 3\n for attempt in range(retries):\n try:\n return establish_connection(timeout)\n except TimeoutError:\n if attempt == retries - 1:\n raise\n time.sleep(5)\n\ndef establish_connection(timeout):\n # Connection implementation\n pass",
 263 | "mimeType": "text/x-python"
 264 | }
 265 | }
 266 | }
 267 | ]
 268 | }
 269 | ​
 270 | Multi-step workflows
 271 | 
 272 | const debugWorkflow = {
 273 | name: "debug-error",
 274 | async getMessages(error: string) {
 275 | return [
 276 | {
 277 | role: "user",
 278 | content: {
 279 | type: "text",
 280 | text: `Here's an error I'm seeing: ${error}`
 281 | }
 282 | },
 283 | {
 284 | role: "assistant",
 285 | content: {
 286 | type: "text",
 287 | text: "I'll help analyze this error. What have you tried so far?"
 288 | }
 289 | },
 290 | {
 291 | role: "user",
 292 | content: {
 293 | type: "text",
 294 | text: "I've tried restarting the service, but the error persists."
 295 | }
 296 | }
 297 | ];
 298 | }
 299 | };
 300 | ​
 301 | Example implementation
 302 | Here’s a complete example of implementing prompts in an MCP server:
 303 | 
 304 | TypeScript
 305 | Python
 306 | 
 307 | import { Server } from "@modelcontextprotocol/sdk/server";
 308 | import {
 309 | ListPromptsRequestSchema,
 310 | GetPromptRequestSchema
 311 | } from "@modelcontextprotocol/sdk/types";
 312 | 
 313 | const PROMPTS = {
 314 | "git-commit": {
 315 | name: "git-commit",
 316 | description: "Generate a Git commit message",
 317 | arguments: [
 318 | {
 319 | name: "changes",
 320 | description: "Git diff or description of changes",
 321 | required: true
 322 | }
 323 | ]
 324 | },
 325 | "explain-code": {
 326 | name: "explain-code",
 327 | description: "Explain how code works",
 328 | arguments: [
 329 | {
 330 | name: "code",
 331 | description: "Code to explain",
 332 | required: true
 333 | },
 334 | {
 335 | name: "language",
 336 | description: "Programming language",
 337 | required: false
 338 | }
 339 | ]
 340 | }
 341 | };
 342 | 
 343 | const server = new Server({
 344 | name: "example-prompts-server",
 345 | version: "1.0.0"
 346 | }, {
 347 | capabilities: {
 348 | prompts: {}
 349 | }
 350 | });
 351 | 
 352 | // List available prompts
 353 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
 354 | return {
 355 | prompts: Object.values(PROMPTS)
 356 | };
 357 | });
 358 | 
 359 | // Get specific prompt
 360 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
 361 | const prompt = PROMPTS[request.params.name];
 362 | if (!prompt) {
 363 | throw new Error(`Prompt not found: ${request.params.name}`);
 364 | }
 365 | 
 366 | if (request.params.name === "git-commit") {
 367 | return {
 368 | messages: [
 369 | {
 370 | role: "user",
 371 | content: {
 372 | type: "text",
 373 | text: `Generate a concise but descriptive commit message for these changes:\n\n${request.params.arguments?.changes}`
 374 | }
 375 | }
 376 | ]
 377 | };
 378 | }
 379 | 
 380 | if (request.params.name === "explain-code") {
 381 | const language = request.params.arguments?.language || "Unknown";
 382 | return {
 383 | messages: [
 384 | {
 385 | role: "user",
 386 | content: {
 387 | type: "text",
 388 | text: `Explain how this ${language} code works:\n\n${request.params.arguments?.code}`
 389 | }
 390 | }
 391 | ]
 392 | };
 393 | }
 394 | 
 395 | throw new Error("Prompt implementation not found");
 396 | });
 397 | ​
 398 | Best practices
 399 | When implementing prompts:
 400 | 
 401 | Use clear, descriptive prompt names
 402 | Provide detailed descriptions for prompts and arguments
 403 | Validate all required arguments
 404 | Handle missing arguments gracefully
 405 | Consider versioning for prompt templates
 406 | Cache dynamic content when appropriate
 407 | Implement error handling
 408 | Document expected argument formats
 409 | Consider prompt composability
 410 | Test prompts with various inputs
 411 | ​
 412 | UI integration
 413 | Prompts can be surfaced in client UIs as:
 414 | 
 415 | Slash commands
 416 | Quick actions
 417 | Context menu items
 418 | Command palette entries
 419 | Guided workflows
 420 | Interactive forms
 421 | ​
 422 | Updates and changes
 423 | Servers can notify clients about prompt changes:
 424 | 
 425 | Server capability: prompts.listChanged
 426 | Notification: notifications/prompts/list_changed
 427 | Client re-fetches prompt list
 428 | ​
 429 | Security considerations
 430 | When implementing prompts:
 431 | 
 432 | Validate all arguments
 433 | Sanitize user input
 434 | Consider rate limiting
 435 | Implement access controls
 436 | Audit prompt usage
 437 | Handle sensitive data appropriately
 438 | Validate generated content
 439 | Implement timeouts
 440 | Consider prompt injection risks
 441 | Document security requirements
 442 | 
 443 | ## Tools
 444 | 
 445 | Tools
 446 | Enable LLMs to perform actions through your server
 447 | 
 448 | Tools are a powerful primitive in the Model Context Protocol (MCP) that enable servers to expose executable functionality to clients. Through tools, LLMs can interact with external systems, perform computations, and take actions in the real world.
 449 | 
 450 | Tools are designed to be model-controlled, meaning that tools are exposed from servers to clients with the intention of the AI model being able to automatically invoke them (with a human in the loop to grant approval).
 451 | 
 452 | ​
 453 | Overview
 454 | Tools in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions. Key aspects of tools include:
 455 | 
 456 | Discovery: Clients can list available tools through the tools/list endpoint
 457 | Invocation: Tools are called using the tools/call endpoint, where servers perform the requested operation and return results
 458 | Flexibility: Tools can range from simple calculations to complex API interactions
 459 | Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems.
 460 | 
 461 | ​
 462 | Tool definition structure
 463 | Each tool is defined with the following structure:
 464 | 
 465 | {
 466 | name: string; // Unique identifier for the tool
 467 | description?: string; // Human-readable description
 468 | inputSchema: { // JSON Schema for the tool's parameters
 469 | type: "object",
 470 | properties: { ... } // Tool-specific parameters
 471 | }
 472 | }
 473 | ​
 474 | Implementing tools
 475 | Here’s an example of implementing a basic tool in an MCP server:
 476 | 
 477 | TypeScript
 478 | Python
 479 | 
 480 | const server = new Server({
 481 | name: "example-server",
 482 | version: "1.0.0"
 483 | }, {
 484 | capabilities: {
 485 | tools: {}
 486 | }
 487 | });
 488 | 
 489 | // Define available tools
 490 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 491 | return {
 492 | tools: [{
 493 | name: "calculate_sum",
 494 | description: "Add two numbers together",
 495 | inputSchema: {
 496 | type: "object",
 497 | properties: {
 498 | a: { type: "number" },
 499 | b: { type: "number" }
 500 | },
 501 | required: ["a", "b"]
 502 | }
 503 | }]
 504 | };
 505 | });
 506 | 
 507 | // Handle tool execution
 508 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
 509 | if (request.params.name === "calculate_sum") {
 510 | const { a, b } = request.params.arguments;
 511 | return {
 512 | content: [
 513 | {
 514 | type: "text",
 515 | text: String(a + b)
 516 | }
 517 | ]
 518 | };
 519 | }
 520 | throw new Error("Tool not found");
 521 | });
 522 | ​
 523 | Example tool patterns
 524 | Here are some examples of types of tools that a server could provide:
 525 | 
 526 | ​
 527 | System operations
 528 | Tools that interact with the local system:
 529 | 
 530 | {
 531 | name: "execute_command",
 532 | description: "Run a shell command",
 533 | inputSchema: {
 534 | type: "object",
 535 | properties: {
 536 | command: { type: "string" },
 537 | args: { type: "array", items: { type: "string" } }
 538 | }
 539 | }
 540 | }
 541 | ​
 542 | API integrations
 543 | Tools that wrap external APIs:
 544 | 
 545 | {
 546 | name: "github_create_issue",
 547 | description: "Create a GitHub issue",
 548 | inputSchema: {
 549 | type: "object",
 550 | properties: {
 551 | title: { type: "string" },
 552 | body: { type: "string" },
 553 | labels: { type: "array", items: { type: "string" } }
 554 | }
 555 | }
 556 | }
 557 | ​
 558 | Data processing
 559 | Tools that transform or analyze data:
 560 | 
 561 | {
 562 | name: "analyze_csv",
 563 | description: "Analyze a CSV file",
 564 | inputSchema: {
 565 | type: "object",
 566 | properties: {
 567 | filepath: { type: "string" },
 568 | operations: {
 569 | type: "array",
 570 | items: {
 571 | enum: ["sum", "average", "count"]
 572 | }
 573 | }
 574 | }
 575 | }
 576 | }
 577 | ​
 578 | Best practices
 579 | When implementing tools:
 580 | 
 581 | Provide clear, descriptive names and descriptions
 582 | Use detailed JSON Schema definitions for parameters
 583 | Include examples in tool descriptions to demonstrate how the model should use them
 584 | Implement proper error handling and validation
 585 | Use progress reporting for long operations
 586 | Keep tool operations focused and atomic
 587 | Document expected return value structures
 588 | Implement proper timeouts
 589 | Consider rate limiting for resource-intensive operations
 590 | Log tool usage for debugging and monitoring
 591 | ​
 592 | Security considerations
 593 | When exposing tools:
 594 | 
 595 | ​
 596 | Input validation
 597 | Validate all parameters against the schema
 598 | Sanitize file paths and system commands
 599 | Validate URLs and external identifiers
 600 | Check parameter sizes and ranges
 601 | Prevent command injection
 602 | ​
 603 | Access control
 604 | Implement authentication where needed
 605 | Use appropriate authorization checks
 606 | Audit tool usage
 607 | Rate limit requests
 608 | Monitor for abuse
 609 | ​
 610 | Error handling
 611 | Don’t expose internal errors to clients
 612 | Log security-relevant errors
 613 | Handle timeouts appropriately
 614 | Clean up resources after errors
 615 | Validate return values
 616 | ​
 617 | Tool discovery and updates
 618 | MCP supports dynamic tool discovery:
 619 | 
 620 | Clients can list available tools at any time
 621 | Servers can notify clients when tools change using notifications/tools/list_changed
 622 | Tools can be added or removed during runtime
 623 | Tool definitions can be updated (though this should be done carefully)
 624 | ​
 625 | Error handling
 626 | Tool errors should be reported within the result object, not as MCP protocol-level errors. This allows the LLM to see and potentially handle the error. When a tool encounters an error:
 627 | 
 628 | Set isError to true in the result
 629 | Include error details in the content array
 630 | Here’s an example of proper error handling for tools:
 631 | 
 632 | TypeScript
 633 | Python
 634 | 
 635 | try {
 636 | // Tool operation
 637 | const result = performOperation();
 638 | return {
 639 | content: [
 640 | {
 641 | type: "text",
 642 | text: `Operation successful: ${result}`
 643 | }
 644 | ]
 645 | };
 646 | } catch (error) {
 647 | return {
 648 | isError: true,
 649 | content: [
 650 | {
 651 | type: "text",
 652 | text: `Error: ${error.message}`
 653 | }
 654 | ]
 655 | };
 656 | }
 657 | This approach allows the LLM to see that an error occurred and potentially take corrective action or request human intervention.
 658 | 
 659 | ​
 660 | Testing tools
 661 | A comprehensive testing strategy for MCP tools should cover:
 662 | 
 663 | Functional testing: Verify tools execute correctly with valid inputs and handle invalid inputs appropriately
 664 | Integration testing: Test tool interaction with external systems using both real and mocked dependencies
 665 | Security testing: Validate authentication, authorization, input sanitization, and rate limiting
 666 | Performance testing: Check behavior under load, timeout handling, and resource cleanup
 667 | Error handling: Ensure tools properly report errors through the MCP protocol and clean up resources
 668 | 
 669 | ## Sampling
 670 | 
 671 | Sampling
 672 | Let your servers request completions from LLMs
 673 | 
 674 | Sampling is a powerful MCP feature that allows servers to request LLM completions through the client, enabling sophisticated agentic behaviors while maintaining security and privacy.
 675 | 
 676 | This feature of MCP is not yet supported in the Claude Desktop client.
 677 | 
 678 | ​
 679 | How sampling works
 680 | The sampling flow follows these steps:
 681 | 
 682 | Server sends a sampling/createMessage request to the client
 683 | Client reviews the request and can modify it
 684 | Client samples from an LLM
 685 | Client reviews the completion
 686 | Client returns the result to the server
 687 | This human-in-the-loop design ensures users maintain control over what the LLM sees and generates.
 688 | 
 689 | ​
 690 | Message format
 691 | Sampling requests use a standardized message format:
 692 | 
 693 | {
 694 | messages: [
 695 | {
 696 | role: "user" | "assistant",
 697 | content: {
 698 | type: "text" | "image",
 699 | 
 700 |         // For text:
 701 |         text?: string,
 702 | 
 703 |         // For images:
 704 |         data?: string,             // base64 encoded
 705 |         mimeType?: string
 706 |       }
 707 |     }
 708 | 
 709 | ],
 710 | modelPreferences?: {
 711 | hints?: [{
 712 | name?: string // Suggested model name/family
 713 | }],
 714 | costPriority?: number, // 0-1, importance of minimizing cost
 715 | speedPriority?: number, // 0-1, importance of low latency
 716 | intelligencePriority?: number // 0-1, importance of capabilities
 717 | },
 718 | systemPrompt?: string,
 719 | includeContext?: "none" | "thisServer" | "allServers",
 720 | temperature?: number,
 721 | maxTokens: number,
 722 | stopSequences?: string[],
 723 | metadata?: Record<string, unknown>
 724 | }
 725 | ​
 726 | Request parameters
 727 | ​
 728 | Messages
 729 | The messages array contains the conversation history to send to the LLM. Each message has:
 730 | 
 731 | role: Either “user” or “assistant”
 732 | content: The message content, which can be:
 733 | Text content with a text field
 734 | Image content with data (base64) and mimeType fields
 735 | ​
 736 | Model preferences
 737 | The modelPreferences object allows servers to specify their model selection preferences:
 738 | 
 739 | hints: Array of model name suggestions that clients can use to select an appropriate model:
 740 | 
 741 | name: String that can match full or partial model names (e.g. “claude-3”, “sonnet”)
 742 | Clients may map hints to equivalent models from different providers
 743 | Multiple hints are evaluated in preference order
 744 | Priority values (0-1 normalized):
 745 | 
 746 | costPriority: Importance of minimizing costs
 747 | speedPriority: Importance of low latency response
 748 | intelligencePriority: Importance of advanced model capabilities
 749 | Clients make the final model selection based on these preferences and their available models.
 750 | 
 751 | ​
 752 | System prompt
 753 | An optional systemPrompt field allows servers to request a specific system prompt. The client may modify or ignore this.
 754 | 
 755 | ​
 756 | Context inclusion
 757 | The includeContext parameter specifies what MCP context to include:
 758 | 
 759 | "none": No additional context
 760 | "thisServer": Include context from the requesting server
 761 | "allServers": Include context from all connected MCP servers
 762 | The client controls what context is actually included.
 763 | 
 764 | ​
 765 | Sampling parameters
 766 | Fine-tune the LLM sampling with:
 767 | 
 768 | temperature: Controls randomness (0.0 to 1.0)
 769 | maxTokens: Maximum tokens to generate
 770 | stopSequences: Array of sequences that stop generation
 771 | metadata: Additional provider-specific parameters
 772 | ​
 773 | Response format
 774 | The client returns a completion result:
 775 | 
 776 | {
 777 | model: string, // Name of the model used
 778 | stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string,
 779 | role: "user" | "assistant",
 780 | content: {
 781 | type: "text" | "image",
 782 | text?: string,
 783 | data?: string,
 784 | mimeType?: string
 785 | }
 786 | }
 787 | ​
 788 | Example request
 789 | Here’s an example of requesting sampling from a client:
 790 | 
 791 | {
 792 | "method": "sampling/createMessage",
 793 | "params": {
 794 | "messages": [
 795 | {
 796 | "role": "user",
 797 | "content": {
 798 | "type": "text",
 799 | "text": "What files are in the current directory?"
 800 | }
 801 | }
 802 | ],
 803 | "systemPrompt": "You are a helpful file system assistant.",
 804 | "includeContext": "thisServer",
 805 | "maxTokens": 100
 806 | }
 807 | }
 808 | ​
 809 | Best practices
 810 | When implementing sampling:
 811 | 
 812 | Always provide clear, well-structured prompts
 813 | Handle both text and image content appropriately
 814 | Set reasonable token limits
 815 | Include relevant context through includeContext
 816 | Validate responses before using them
 817 | Handle errors gracefully
 818 | Consider rate limiting sampling requests
 819 | Document expected sampling behavior
 820 | Test with various model parameters
 821 | Monitor sampling costs
 822 | ​
 823 | Human in the loop controls
 824 | Sampling is designed with human oversight in mind:
 825 | 
 826 | ​
 827 | For prompts
 828 | Clients should show users the proposed prompt
 829 | Users should be able to modify or reject prompts
 830 | System prompts can be filtered or modified
 831 | Context inclusion is controlled by the client
 832 | ​
 833 | For completions
 834 | Clients should show users the completion
 835 | Users should be able to modify or reject completions
 836 | Clients can filter or modify completions
 837 | Users control which model is used
 838 | ​
 839 | Security considerations
 840 | When implementing sampling:
 841 | 
 842 | Validate all message content
 843 | Sanitize sensitive information
 844 | Implement appropriate rate limits
 845 | Monitor sampling usage
 846 | Encrypt data in transit
 847 | Handle user data privacy
 848 | Audit sampling requests
 849 | Control cost exposure
 850 | Implement timeouts
 851 | Handle model errors gracefully
 852 | ​
 853 | Common patterns
 854 | ​
 855 | Agentic workflows
 856 | Sampling enables agentic patterns like:
 857 | 
 858 | Reading and analyzing resources
 859 | Making decisions based on context
 860 | Generating structured data
 861 | Handling multi-step tasks
 862 | Providing interactive assistance
 863 | ​
 864 | Context management
 865 | Best practices for context:
 866 | 
 867 | Request minimal necessary context
 868 | Structure context clearly
 869 | Handle context size limits
 870 | Update context as needed
 871 | Clean up stale context
 872 | ​
 873 | Error handling
 874 | Robust error handling should:
 875 | 
 876 | Catch sampling failures
 877 | Handle timeout errors
 878 | Manage rate limits
 879 | Validate responses
 880 | Provide fallback behaviors
 881 | Log errors appropriately
 882 | ​
 883 | Limitations
 884 | Be aware of these limitations:
 885 | 
 886 | Sampling depends on client capabilities
 887 | Users control sampling behavior
 888 | Context size has limits
 889 | Rate limits may apply
 890 | Costs should be considered
 891 | Model availability varies
 892 | Response times vary
 893 | Not all content types supported
 894 | 
 895 | ## Roots
 896 | 
 897 | Roots
 898 | Understanding roots in MCP
 899 | 
 900 | Roots are a concept in MCP that define the boundaries where servers can operate. They provide a way for clients to inform servers about relevant resources and their locations.
 901 | 
 902 | ​
 903 | What are Roots?
 904 | A root is a URI that a client suggests a server should focus on. When a client connects to a server, it declares which roots the server should work with. While primarily used for filesystem paths, roots can be any valid URI including HTTP URLs.
 905 | 
 906 | For example, roots could be:
 907 | 
 908 | file:///home/user/projects/myapp
 909 | https://api.example.com/v1
 910 | ​
 911 | Why Use Roots?
 912 | Roots serve several important purposes:
 913 | 
 914 | Guidance: They inform servers about relevant resources and locations
 915 | Clarity: Roots make it clear which resources are part of your workspace
 916 | Organization: Multiple roots let you work with different resources simultaneously
 917 | ​
 918 | How Roots Work
 919 | When a client supports roots, it:
 920 | 
 921 | Declares the roots capability during connection
 922 | Provides a list of suggested roots to the server
 923 | Notifies the server when roots change (if supported)
 924 | While roots are informational and not strictly enforcing, servers should:
 925 | 
 926 | Respect the provided roots
 927 | Use root URIs to locate and access resources
 928 | Prioritize operations within root boundaries
 929 | ​
 930 | Common Use Cases
 931 | Roots are commonly used to define:
 932 | 
 933 | Project directories
 934 | Repository locations
 935 | API endpoints
 936 | Configuration locations
 937 | Resource boundaries
 938 | ​
 939 | Best Practices
 940 | When working with roots:
 941 | 
 942 | Only suggest necessary resources
 943 | Use clear, descriptive names for roots
 944 | Monitor root accessibility
 945 | Handle root changes gracefully
 946 | ​
 947 | Example
 948 | Here’s how a typical MCP client might expose roots:
 949 | 
 950 | {
 951 | "roots": [
 952 | {
 953 | "uri": "file:///home/user/projects/frontend",
 954 | "name": "Frontend Repository"
 955 | },
 956 | {
 957 | "uri": "https://api.example.com/v1",
 958 | "name": "API Endpoint"
 959 | }
 960 | ]
 961 | }
 962 | This configuration suggests the server focus on both a local repository and an API endpoint while keeping them logically separated.
 963 | 
 964 | ## Transports
 965 | 
 966 | Transports
 967 | Learn about MCP’s communication mechanisms
 968 | 
 969 | Transports in the Model Context Protocol (MCP) provide the foundation for communication between clients and servers. A transport handles the underlying mechanics of how messages are sent and received.
 970 | 
 971 | ​
 972 | Message Format
 973 | MCP uses JSON-RPC 2.0 as its wire format. The transport layer is responsible for converting MCP protocol messages into JSON-RPC format for transmission and converting received JSON-RPC messages back into MCP protocol messages.
 974 | 
 975 | There are three types of JSON-RPC messages used:
 976 | 
 977 | ​
 978 | Requests
 979 | 
 980 | {
 981 | jsonrpc: "2.0",
 982 | id: number | string,
 983 | method: string,
 984 | params?: object
 985 | }
 986 | ​
 987 | Responses
 988 | 
 989 | {
 990 | jsonrpc: "2.0",
 991 | id: number | string,
 992 | result?: object,
 993 | error?: {
 994 | code: number,
 995 | message: string,
 996 | data?: unknown
 997 | }
 998 | }
 999 | ​
1000 | Notifications
1001 | 
1002 | {
1003 | jsonrpc: "2.0",
1004 | method: string,
1005 | params?: object
1006 | }
1007 | ​
1008 | Built-in Transport Types
1009 | MCP includes two standard transport implementations:
1010 | 
1011 | ​
1012 | Standard Input/Output (stdio)
1013 | The stdio transport enables communication through standard input and output streams. This is particularly useful for local integrations and command-line tools.
1014 | 
1015 | Use stdio when:
1016 | 
1017 | Building command-line tools
1018 | Implementing local integrations
1019 | Needing simple process communication
1020 | Working with shell scripts
1021 | TypeScript (Server)
1022 | TypeScript (Client)
1023 | Python (Server)
1024 | Python (Client)
1025 | 
1026 | const server = new Server({
1027 | name: "example-server",
1028 | version: "1.0.0"
1029 | }, {
1030 | capabilities: {}
1031 | });
1032 | 
1033 | const transport = new StdioServerTransport();
1034 | await server.connect(transport);
1035 | ​
1036 | Server-Sent Events (SSE)
1037 | SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication.
1038 | 
1039 | Use SSE when:
1040 | 
1041 | Only server-to-client streaming is needed
1042 | Working with restricted networks
1043 | Implementing simple updates
1044 | TypeScript (Server)
1045 | TypeScript (Client)
1046 | Python (Server)
1047 | Python (Client)
1048 | 
1049 | import express from "express";
1050 | 
1051 | const app = express();
1052 | 
1053 | const server = new Server({
1054 | name: "example-server",
1055 | version: "1.0.0"
1056 | }, {
1057 | capabilities: {}
1058 | });
1059 | 
1060 | let transport: SSEServerTransport | null = null;
1061 | 
1062 | app.get("/sse", (req, res) => {
1063 | transport = new SSEServerTransport("/messages", res);
1064 | server.connect(transport);
1065 | });
1066 | 
1067 | app.post("/messages", (req, res) => {
1068 | if (transport) {
1069 | transport.handlePostMessage(req, res);
1070 | }
1071 | });
1072 | 
1073 | app.listen(3000);
1074 | ​
1075 | Custom Transports
1076 | MCP makes it easy to implement custom transports for specific needs. Any transport implementation just needs to conform to the Transport interface:
1077 | 
1078 | You can implement custom transports for:
1079 | 
1080 | Custom network protocols
1081 | Specialized communication channels
1082 | Integration with existing systems
1083 | Performance optimization
1084 | TypeScript
1085 | Python
1086 | 
1087 | interface Transport {
1088 | // Start processing messages
1089 | start(): Promise<void>;
1090 | 
1091 | // Send a JSON-RPC message
1092 | send(message: JSONRPCMessage): Promise<void>;
1093 | 
1094 | // Close the connection
1095 | close(): Promise<void>;
1096 | 
1097 | // Callbacks
1098 | onclose?: () => void;
1099 | onerror?: (error: Error) => void;
1100 | onmessage?: (message: JSONRPCMessage) => void;
1101 | }
1102 | ​
1103 | Error Handling
1104 | Transport implementations should handle various error scenarios:
1105 | 
1106 | Connection errors
1107 | Message parsing errors
1108 | Protocol errors
1109 | Network timeouts
1110 | Resource cleanup
1111 | Example error handling:
1112 | 
1113 | TypeScript
1114 | Python
1115 | 
1116 | class ExampleTransport implements Transport {
1117 | async start() {
1118 | try {
1119 | // Connection logic
1120 | } catch (error) {
1121 | this.onerror?.(new Error(`Failed to connect: ${error}`));
1122 | throw error;
1123 | }
1124 | }
1125 | 
1126 | async send(message: JSONRPCMessage) {
1127 | try {
1128 | // Sending logic
1129 | } catch (error) {
1130 | this.onerror?.(new Error(`Failed to send message: ${error}`));
1131 | throw error;
1132 | }
1133 | }
1134 | }
1135 | ​
1136 | Best Practices
1137 | When implementing or using MCP transport:
1138 | 
1139 | Handle connection lifecycle properly
1140 | Implement proper error handling
1141 | Clean up resources on connection close
1142 | Use appropriate timeouts
1143 | Validate messages before sending
1144 | Log transport events for debugging
1145 | Implement reconnection logic when appropriate
1146 | Handle backpressure in message queues
1147 | Monitor connection health
1148 | Implement proper security measures
1149 | ​
1150 | Security Considerations
1151 | When implementing transport:
1152 | 
1153 | ​
1154 | Authentication and Authorization
1155 | Implement proper authentication mechanisms
1156 | Validate client credentials
1157 | Use secure token handling
1158 | Implement authorization checks
1159 | ​
1160 | Data Security
1161 | Use TLS for network transport
1162 | Encrypt sensitive data
1163 | Validate message integrity
1164 | Implement message size limits
1165 | Sanitize input data
1166 | ​
1167 | Network Security
1168 | Implement rate limiting
1169 | Use appropriate timeouts
1170 | Handle denial of service scenarios
1171 | Monitor for unusual patterns
1172 | Implement proper firewall rules
1173 | ​
1174 | Debugging Transport
1175 | Tips for debugging transport issues:
1176 | 
1177 | Enable debug logging
1178 | Monitor message flow
1179 | Check connection states
1180 | Validate message formats
1181 | Test error scenarios
1182 | Use network analysis tools
1183 | Implement health checks
1184 | Monitor resource usage
1185 | Test edge cases
1186 | Use proper error tracking
1187 | 
```
Page 1/3FirstPrevNextLast