mcp-accessibility-scanner 1.0.11 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/NOTICE.md +21 -0
  2. package/Readme.md +155 -125
  3. package/cli.js +18 -0
  4. package/lib/browserContextFactory.js +228 -0
  5. package/lib/browserContextFactory.js.map +1 -0
  6. package/lib/browserServer.js +152 -0
  7. package/lib/browserServer.js.map +1 -0
  8. package/lib/config.js +190 -0
  9. package/lib/config.js.map +1 -0
  10. package/lib/connection.js +83 -0
  11. package/lib/connection.js.map +1 -0
  12. package/lib/context.js +292 -0
  13. package/lib/context.js.map +1 -0
  14. package/lib/fileUtils.js +33 -0
  15. package/lib/fileUtils.js.map +1 -0
  16. package/lib/httpServer.js +202 -0
  17. package/lib/httpServer.js.map +1 -0
  18. package/lib/index.js +37 -0
  19. package/lib/index.js.map +1 -0
  20. package/lib/javascript.js +50 -0
  21. package/lib/javascript.js.map +1 -0
  22. package/lib/manualPromise.js +112 -0
  23. package/lib/manualPromise.js.map +1 -0
  24. package/lib/package.js +21 -0
  25. package/lib/package.js.map +1 -0
  26. package/lib/pageSnapshot.js +44 -0
  27. package/lib/pageSnapshot.js.map +1 -0
  28. package/lib/program.js +72 -0
  29. package/lib/program.js.map +1 -0
  30. package/lib/server.js +49 -0
  31. package/lib/server.js.map +1 -0
  32. package/lib/tab.js +102 -0
  33. package/lib/tab.js.map +1 -0
  34. package/lib/tools/common.js +69 -0
  35. package/lib/tools/common.js.map +1 -0
  36. package/lib/tools/console.js +45 -0
  37. package/lib/tools/console.js.map +1 -0
  38. package/lib/tools/dialogs.js +53 -0
  39. package/lib/tools/dialogs.js.map +1 -0
  40. package/lib/tools/files.js +52 -0
  41. package/lib/tools/files.js.map +1 -0
  42. package/lib/tools/install.js +57 -0
  43. package/lib/tools/install.js.map +1 -0
  44. package/lib/tools/keyboard.js +47 -0
  45. package/lib/tools/keyboard.js.map +1 -0
  46. package/lib/tools/navigate.js +94 -0
  47. package/lib/tools/navigate.js.map +1 -0
  48. package/lib/tools/network.js +52 -0
  49. package/lib/tools/network.js.map +1 -0
  50. package/lib/tools/pdf.js +50 -0
  51. package/lib/tools/pdf.js.map +1 -0
  52. package/lib/tools/screenshot.js +78 -0
  53. package/lib/tools/screenshot.js.map +1 -0
  54. package/lib/tools/snapshot.js +245 -0
  55. package/lib/tools/snapshot.js.map +1 -0
  56. package/lib/tools/tabs.js +119 -0
  57. package/lib/tools/tabs.js.map +1 -0
  58. package/lib/tools/tool.js +19 -0
  59. package/lib/tools/tool.js.map +1 -0
  60. package/lib/tools/utils.js +81 -0
  61. package/lib/tools/utils.js.map +1 -0
  62. package/lib/tools/vision.js +190 -0
  63. package/lib/tools/vision.js.map +1 -0
  64. package/lib/tools/wait.js +60 -0
  65. package/lib/tools/wait.js.map +1 -0
  66. package/lib/tools.js +59 -0
  67. package/lib/tools.js.map +1 -0
  68. package/lib/transport.js +134 -0
  69. package/lib/transport.js.map +1 -0
  70. package/package.json +41 -13
  71. package/build/accessibilityChecker.js +0 -379
  72. package/build/index.js +0 -958
  73. package/build/server.js +0 -50
package/NOTICE.md ADDED
@@ -0,0 +1,21 @@
1
+ # NOTICE
2
+
3
+ ## mcp-accessibility-scanner
4
+
5
+ Copyright (c) 2024 Justas Monkev
6
+
7
+ This project is licensed under the MIT License.
8
+
9
+ ## Third-Party Code Attribution
10
+
11
+ This project includes code adapted from:
12
+
13
+ ### Microsoft Playwright MCP
14
+ - Copyright (c) Microsoft Corporation
15
+ - Licensed under the Apache License, Version 2.0
16
+ - Original source: https://github.com/microsoft/playwright-mcp
17
+
18
+ Adapted code has been modified to:
19
+ - Convert from ESM to CommonJS module format
20
+ - Integrate with axe-core accessibility scanning
21
+ - Work with existing Playwright instances rather than spawning new ones
package/Readme.md CHANGED
@@ -1,7 +1,9 @@
1
- [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/justasmonkev-mcp-accessibility-scanner-badge.png)](https://mseep.ai/app/justasmonkev-mcp-accessibility-scanner)
2
1
 
3
2
  # MCP Accessibility Scanner 🔍
4
3
 
4
+ [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/justasmonkev-mcp-accessibility-scanner-badge.png)](https://mseep.ai/app/justasmonkev-mcp-accessibility-scanner)
5
+
6
+ A Model Context Protocol (MCP) server that provides automated web accessibility scanning using Playwright and Axe-core. This server enables LLMs to perform WCAG compliance checks, capture annotated screenshots, and generate detailed accessibility reports.
5
7
  A powerful Model Context Protocol (MCP) server that provides automated web accessibility scanning and browser automation using Playwright and Axe-core. This server enables LLMs to perform WCAG compliance checks, interact with web pages, manage persistent browser sessions, and generate detailed accessibility reports with visual annotations.
6
8
 
7
9
  ## Features
@@ -13,16 +15,18 @@ A powerful Model Context Protocol (MCP) server that provides automated web acces
13
15
  🎯 Support for specific violation categories (color contrast, ARIA, forms, keyboard navigation, etc.)
14
16
 
15
17
  ### Browser Automation
16
- 🖱️ Click elements by CSS selector or visible text
17
- ⌨️ Type text into inputs by selector or label
18
- 🔍 Analyze pages to discover all interactive elements
19
- 📸 Capture screenshots after each interaction
20
-
21
- ### Session Management
22
- 🔄 Create persistent browser sessions for multi-step workflows
23
- ⏱️ Automatic session cleanup after 3 minutes of inactivity
24
- 🌐 Navigate between pages while maintaining session state
25
- 📊 Run accessibility scans within active sessions
18
+ 🖱️ Click, hover, and drag elements using accessibility snapshots
19
+ ⌨️ Type text and handle keyboard inputs
20
+ 🔍 Capture page snapshots to discover all interactive elements
21
+ 📸 Take screenshots and save PDFs
22
+ 🎯 Support for both element-based and coordinate-based interactions
23
+
24
+ ### Advanced Features
25
+ 📑 Tab management for multi-page workflows
26
+ 🌐 Monitor console messages and network requests
27
+ ⏱️ Wait for dynamic content to load
28
+ 📁 Handle file uploads and browser dialogs
29
+ 🔄 Navigate through browser history
26
30
 
27
31
  ## Installation
28
32
 
@@ -33,25 +37,6 @@ Using npm:
33
37
  npm install -g mcp-accessibility-scanner
34
38
  ```
35
39
 
36
- ### Docker Installation
37
-
38
- The project includes a Dockerfile that sets up all necessary dependencies including Node.js v22 and Python 3.13.
39
-
40
- 1. Build the Docker image:
41
- ```bash
42
- docker build -t mcp-server .
43
- ```
44
-
45
- 2. Run the container:
46
- ```bash
47
- docker run -it -e MCP_PROXY_DEBUG=true mcp-server
48
- ```
49
-
50
- You can also run it in the background:
51
- ```bash
52
- docker run -d -p 3000:3000 mcp-server
53
- ```
54
-
55
40
  ### Installation in VS Code
56
41
 
57
42
  Install the Accessibility Scanner in VS Code using the VS Code CLI:
@@ -83,108 +68,189 @@ Here's the Claude Desktop configuration:
83
68
 
84
69
  ## Available Tools
85
70
 
86
- The MCP server exposes 18 tools for accessibility scanning and browser automation:
71
+ The MCP server provides comprehensive browser automation and accessibility scanning tools:
87
72
 
88
- ### Accessibility Scanning
73
+ ### Core Accessibility Tool
89
74
 
90
- #### `accessibility-scan`
91
- Performs a comprehensive accessibility scan on a webpage.
75
+ #### `scan_page`
76
+ Performs a comprehensive accessibility scan on the current page using Axe-core.
92
77
 
93
78
  **Parameters:**
94
- - `url`: The webpage URL to scan (required)
95
- - `violationsTag`: Array of WCAG/violation tags to check (required)
96
- - `viewport`: Optional viewport size (default: 1920x1080)
97
- - `shouldRunInHeadless`: Optional headless mode control (default: true)
79
+ - `violationsTag`: Array of WCAG/violation tags to check
98
80
 
99
81
  **Supported Violation Tags:**
100
- - WCAG levels: `wcag2a`, `wcag2aa`, `wcag2aaa`, `wcag21a`, `wcag21aa`, `wcag21aaa`, `wcag22a`, `wcag22aa`, `wcag22aaa`
82
+ - WCAG standards: `wcag2a`, `wcag2aa`, `wcag2aaa`, `wcag21a`, `wcag21aa`, `wcag21aaa`, `wcag22a`, `wcag22aa`, `wcag22aaa`
101
83
  - Section 508: `section508`
102
- - Categories: `cat.color` (contrast), `cat.aria`, `cat.forms`, `cat.keyboard`, `cat.language`, `cat.structure`, etc.
84
+ - Categories: `cat.aria`, `cat.color`, `cat.forms`, `cat.keyboard`, `cat.language`, `cat.name-role-value`, `cat.parsing`, `cat.semantics`, `cat.sensory-and-visual-cues`, `cat.structure`, `cat.tables`, `cat.text-alternatives`, `cat.time-and-media`
103
85
 
104
- ### Browser Automation
86
+ ### Navigation Tools
87
+
88
+ #### `browser_navigate`
89
+ Navigate to a URL.
90
+ - Parameters: `url` (string)
91
+
92
+ #### `browser_navigate_back`
93
+ Go back to the previous page.
94
+
95
+ #### `browser_navigate_forward`
96
+ Go forward to the next page.
97
+
98
+ ### Page Interaction Tools
99
+
100
+ #### `browser_snapshot`
101
+ Capture accessibility snapshot of the current page (better than screenshot for analysis).
102
+
103
+ #### `browser_click`
104
+ Perform click on a web page element.
105
+ - Parameters: `element` (description), `ref` (element reference), `doubleClick` (optional)
106
+
107
+ #### `browser_type`
108
+ Type text into editable element.
109
+ - Parameters: `element`, `ref`, `text`, `submit` (optional), `slowly` (optional)
110
+
111
+ #### `browser_hover`
112
+ Hover over element on page.
113
+ - Parameters: `element`, `ref`
114
+
115
+ #### `browser_drag`
116
+ Perform drag and drop between two elements.
117
+ - Parameters: `startElement`, `startRef`, `endElement`, `endRef`
118
+
119
+ #### `browser_select_option`
120
+ Select an option in a dropdown.
121
+ - Parameters: `element`, `ref`, `values` (array)
122
+
123
+ #### `browser_press_key`
124
+ Press a key on the keyboard.
125
+ - Parameters: `key` (e.g., 'ArrowLeft' or 'a')
126
+
127
+ ### Screenshot & Visual Tools
128
+
129
+ #### `browser_take_screenshot`
130
+ Take a screenshot of the current page.
131
+ - Parameters: `raw` (optional), `filename` (optional), `element` (optional), `ref` (optional)
105
132
 
106
- #### `click-element`
107
- Clicks an element by CSS selector.
108
- - Parameters: `url`, `selector`, `viewport`, `shouldRunInHeadless`
133
+ #### `browser_pdf_save`
134
+ Save page as PDF.
135
+ - Parameters: `filename` (optional, defaults to `page-{timestamp}.pdf`)
109
136
 
110
- #### `click-element-by-text`
111
- Clicks elements by their visible text content.
112
- - Parameters: `url`, `text`, `elementType` (optional), `viewport`, `shouldRunInHeadless`
137
+ ### Browser Management
113
138
 
114
- #### `type-text`
115
- Types text into an input field by CSS selector.
116
- - Parameters: `url`, `selector`, `text`, `viewport`, `shouldRunInHeadless`
139
+ #### `browser_close`
140
+ Close the page.
117
141
 
118
- #### `type-text-by-label`
119
- Types text into input fields by their label text.
120
- - Parameters: `url`, `labelText`, `text`, `viewport`, `shouldRunInHeadless`
142
+ #### `browser_resize`
143
+ Resize the browser window.
144
+ - Parameters: `width`, `height`
121
145
 
122
- #### `analyze-page`
123
- Analyzes page to identify all interactive elements.
124
- - Parameters: `url`, `viewport`, `shouldRunInHeadless`
125
- - Returns: Lists of all buttons, links, and inputs on the page
146
+ ### Tab Management
126
147
 
127
- ### Session Management
148
+ #### `browser_tab_list`
149
+ List all open browser tabs.
128
150
 
129
- #### `create-session`
130
- Creates a persistent browser session for multiple operations.
131
- - Parameters: `sessionId`, `viewport`, `shouldRunInHeadless`
132
- - Sessions auto-expire after 3 minutes of inactivity
151
+ #### `browser_tab_new`
152
+ Open a new tab.
153
+ - Parameters: `url` (optional)
133
154
 
134
- #### `navigate-session`
135
- Navigates to a URL in an existing session.
136
- - Parameters: `sessionId`, `url`
155
+ #### `browser_tab_select`
156
+ Select a tab by index.
157
+ - Parameters: `index`
137
158
 
138
- #### `click-session` / `click-session-by-text`
139
- Click elements within a session.
159
+ #### `browser_tab_close`
160
+ Close a tab.
161
+ - Parameters: `index` (optional, closes current tab if not provided)
140
162
 
141
- #### `type-session` / `type-session-by-label`
142
- Type text within a session.
163
+ ### Information & Monitoring Tools
143
164
 
144
- #### `scan-session`
145
- Run accessibility scan on current page in session.
165
+ #### `browser_console_messages`
166
+ Returns all console messages from the page.
146
167
 
147
- #### `analyze-session`
148
- Analyze current page in session.
168
+ #### `browser_network_requests`
169
+ Returns all network requests since loading the page.
149
170
 
150
- #### `close-session`
151
- Close a browser session.
171
+ ### Utility Tools
152
172
 
153
- #### `list-sessions`
154
- List all active browser sessions.
173
+ #### `browser_wait_for`
174
+ Wait for text to appear/disappear or time to pass.
175
+ - Parameters: `time` (optional), `text` (optional), `textGone` (optional)
176
+
177
+ #### `browser_handle_dialog`
178
+ Handle browser dialogs (alerts, confirms, prompts).
179
+ - Parameters: `accept` (boolean), `promptText` (optional)
180
+
181
+ #### `browser_file_upload`
182
+ Upload files to the page.
183
+ - Parameters: `paths` (array of absolute file paths)
184
+
185
+ ### Vision Mode Tools (Coordinate-based Interaction)
186
+
187
+ #### `browser_screen_capture`
188
+ Take a screenshot for coordinate-based interaction.
189
+
190
+ #### `browser_screen_move_mouse`
191
+ Move mouse to specific coordinates.
192
+ - Parameters: `element`, `x`, `y`
193
+
194
+ #### `browser_screen_click`
195
+ Click at specific coordinates.
196
+ - Parameters: `element`, `x`, `y`
197
+
198
+ #### `browser_screen_drag`
199
+ Drag from one coordinate to another.
200
+ - Parameters: `element`, `startX`, `startY`, `endX`, `endY`
201
+
202
+ #### `browser_screen_type`
203
+ Type text (coordinate-independent).
204
+ - Parameters: `text`, `submit` (optional)
155
205
 
156
206
  ## Usage Examples
157
207
 
158
208
  ### Basic Accessibility Scan
159
209
  ```
160
- Could you scan example.com for WCAG 2.1 AA compliance issues?
210
+ 1. Navigate to example.com using browser_navigate
211
+ 2. Run scan_page with violationsTag: ["wcag21aa"]
161
212
  ```
162
213
 
163
214
  ### Color Contrast Check
164
215
  ```
165
- Please check example.com for color contrast accessibility issues (cat.color).
216
+ 1. Use browser_navigate to go to example.com
217
+ 2. Run scan_page with violationsTag: ["cat.color"]
166
218
  ```
167
219
 
168
- ### Multi-step Workflow with Sessions
220
+ ### Multi-step Workflow
169
221
  ```
170
- 1. Create a session and navigate to example.com
171
- 2. Click the "Sign In" button
172
- 3. Type "user@example.com" into the email field
173
- 4. Run an accessibility scan on the login page
174
- 5. Close the session
222
+ 1. Navigate to example.com with browser_navigate
223
+ 2. Take a browser_snapshot to see available elements
224
+ 3. Click the "Sign In" button using browser_click
225
+ 4. Type "user@example.com" using browser_type
226
+ 5. Run scan_page on the login page
227
+ 6. Take a browser_take_screenshot to capture the final state
175
228
  ```
176
229
 
177
230
  ### Page Analysis
178
231
  ```
179
- Can you analyze example.com and tell me what interactive elements are available?
232
+ 1. Navigate to example.com
233
+ 2. Use browser_snapshot to capture all interactive elements
234
+ 3. Review console messages with browser_console_messages
235
+ 4. Check network activity with browser_network_requests
180
236
  ```
181
237
 
182
- ### Smart Element Interaction
238
+ ### Tab Management
183
239
  ```
184
- Navigate to example.com and click the button that says "Get Started"
240
+ 1. Open a new tab with browser_tab_new
241
+ 2. Navigate to different pages in each tab
242
+ 3. Switch between tabs using browser_tab_select
243
+ 4. List all tabs with browser_tab_list
185
244
  ```
186
245
 
187
- **Note:** All tools automatically save annotated screenshots to your downloads folder, with accessibility violations highlighted in red and numbered badges.
246
+ ### Waiting for Dynamic Content
247
+ ```
248
+ 1. Navigate to a page
249
+ 2. Use browser_wait_for to wait for specific text to appear
250
+ 3. Interact with the dynamically loaded content
251
+ ```
252
+
253
+ **Note:** Most interaction tools require element references from browser_snapshot. Always capture a snapshot before attempting to interact with page elements.
188
254
 
189
255
  ## Development
190
256
 
@@ -195,42 +261,6 @@ cd mcp-accessibility-scanner
195
261
  npm install
196
262
  ```
197
263
 
198
- Start the TypeScript compiler in watch mode:
199
- ```bash
200
- npm run watch
201
- ```
202
-
203
- Test the MCP server locally:
204
- ```bash
205
- npm run inspector
206
- ```
207
-
208
- ### Docker Development
209
-
210
- For development using Docker:
211
-
212
- 1. Build the development image:
213
- ```bash
214
- docker build -t mcp-server-dev .
215
- ```
216
-
217
- 2. Run with volume mounting for live code changes:
218
- ```bash
219
- docker run -it -v $(pwd):/app -p 3000:3000 -e MCP_PROXY_DEBUG=true mcp-server-dev
220
- ```
221
-
222
- ## Project Structure
223
-
224
- ```
225
- ├── src/ # Source code
226
- │ ├── index.ts # MCP server setup and tool definitions
227
- │ └── scanner.ts # Core scanning functionality
228
- ├── build/ # Compiled JavaScript output
229
- ├── Dockerfile # Docker configuration for containerized setup
230
- ├── package.json # Project configuration and dependencies
231
- └── tsconfig.json # TypeScript configuration
232
- ```
233
-
234
264
  ## License
235
265
 
236
266
  MIT
package/cli.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import './lib/program.js';
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import fs from 'node:fs';
17
+ import net from 'node:net';
18
+ import path from 'node:path';
19
+ import os from 'node:os';
20
+ import debug from 'debug';
21
+ import * as playwright from 'playwright';
22
+ import { userDataDir } from './fileUtils.js';
23
+ const testDebug = debug('pw:mcp:test');
24
+ export function contextFactory(browserConfig) {
25
+ if (browserConfig.remoteEndpoint)
26
+ return new RemoteContextFactory(browserConfig);
27
+ if (browserConfig.cdpEndpoint)
28
+ return new CdpContextFactory(browserConfig);
29
+ if (browserConfig.isolated)
30
+ return new IsolatedContextFactory(browserConfig);
31
+ if (browserConfig.browserAgent)
32
+ return new BrowserServerContextFactory(browserConfig);
33
+ return new PersistentContextFactory(browserConfig);
34
+ }
35
+ class BaseContextFactory {
36
+ browserConfig;
37
+ _browserPromise;
38
+ name;
39
+ constructor(name, browserConfig) {
40
+ this.name = name;
41
+ this.browserConfig = browserConfig;
42
+ }
43
+ async _obtainBrowser() {
44
+ if (this._browserPromise)
45
+ return this._browserPromise;
46
+ testDebug(`obtain browser (${this.name})`);
47
+ this._browserPromise = this._doObtainBrowser();
48
+ void this._browserPromise.then(browser => {
49
+ browser.on('disconnected', () => {
50
+ this._browserPromise = undefined;
51
+ });
52
+ }).catch(() => {
53
+ this._browserPromise = undefined;
54
+ });
55
+ return this._browserPromise;
56
+ }
57
+ async _doObtainBrowser() {
58
+ throw new Error('Not implemented');
59
+ }
60
+ async createContext() {
61
+ testDebug(`create browser context (${this.name})`);
62
+ const browser = await this._obtainBrowser();
63
+ const browserContext = await this._doCreateContext(browser);
64
+ return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) };
65
+ }
66
+ async _doCreateContext(browser) {
67
+ throw new Error('Not implemented');
68
+ }
69
+ async _closeBrowserContext(browserContext, browser) {
70
+ testDebug(`close browser context (${this.name})`);
71
+ if (browser.contexts().length === 1)
72
+ this._browserPromise = undefined;
73
+ await browserContext.close().catch(() => { });
74
+ if (browser.contexts().length === 0) {
75
+ testDebug(`close browser (${this.name})`);
76
+ await browser.close().catch(() => { });
77
+ }
78
+ }
79
+ }
80
+ class IsolatedContextFactory extends BaseContextFactory {
81
+ constructor(browserConfig) {
82
+ super('isolated', browserConfig);
83
+ }
84
+ async _doObtainBrowser() {
85
+ await injectCdpPort(this.browserConfig);
86
+ const browserType = playwright[this.browserConfig.browserName];
87
+ return browserType.launch({
88
+ ...this.browserConfig.launchOptions,
89
+ handleSIGINT: false,
90
+ handleSIGTERM: false,
91
+ }).catch(error => {
92
+ if (error.message.includes('Executable doesn\'t exist'))
93
+ throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
94
+ throw error;
95
+ });
96
+ }
97
+ async _doCreateContext(browser) {
98
+ return browser.newContext(this.browserConfig.contextOptions);
99
+ }
100
+ }
101
+ class CdpContextFactory extends BaseContextFactory {
102
+ constructor(browserConfig) {
103
+ super('cdp', browserConfig);
104
+ }
105
+ async _doObtainBrowser() {
106
+ return playwright.chromium.connectOverCDP(this.browserConfig.cdpEndpoint);
107
+ }
108
+ async _doCreateContext(browser) {
109
+ return this.browserConfig.isolated ? await browser.newContext() : browser.contexts()[0];
110
+ }
111
+ }
112
+ class RemoteContextFactory extends BaseContextFactory {
113
+ constructor(browserConfig) {
114
+ super('remote', browserConfig);
115
+ }
116
+ async _doObtainBrowser() {
117
+ const url = new URL(this.browserConfig.remoteEndpoint);
118
+ url.searchParams.set('browser', this.browserConfig.browserName);
119
+ if (this.browserConfig.launchOptions)
120
+ url.searchParams.set('launch-options', JSON.stringify(this.browserConfig.launchOptions));
121
+ return playwright[this.browserConfig.browserName].connect(String(url));
122
+ }
123
+ async _doCreateContext(browser) {
124
+ return browser.newContext();
125
+ }
126
+ }
127
+ class PersistentContextFactory {
128
+ browserConfig;
129
+ _userDataDirs = new Set();
130
+ constructor(browserConfig) {
131
+ this.browserConfig = browserConfig;
132
+ }
133
+ async createContext() {
134
+ await injectCdpPort(this.browserConfig);
135
+ testDebug('create browser context (persistent)');
136
+ const userDataDir = this.browserConfig.userDataDir ?? await this._createUserDataDir();
137
+ this._userDataDirs.add(userDataDir);
138
+ testDebug('lock user data dir', userDataDir);
139
+ const browserType = playwright[this.browserConfig.browserName];
140
+ for (let i = 0; i < 5; i++) {
141
+ try {
142
+ const browserContext = await browserType.launchPersistentContext(userDataDir, {
143
+ ...this.browserConfig.launchOptions,
144
+ ...this.browserConfig.contextOptions,
145
+ handleSIGINT: false,
146
+ handleSIGTERM: false,
147
+ });
148
+ const close = () => this._closeBrowserContext(browserContext, userDataDir);
149
+ return { browserContext, close };
150
+ }
151
+ catch (error) {
152
+ if (error.message.includes('Executable doesn\'t exist'))
153
+ throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
154
+ if (error.message.includes('ProcessSingleton') || error.message.includes('Invalid URL')) {
155
+ // User data directory is already in use, try again.
156
+ await new Promise(resolve => setTimeout(resolve, 1000));
157
+ continue;
158
+ }
159
+ throw error;
160
+ }
161
+ }
162
+ throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
163
+ }
164
+ async _closeBrowserContext(browserContext, userDataDir) {
165
+ testDebug('close browser context (persistent)');
166
+ testDebug('release user data dir', userDataDir);
167
+ await browserContext.close().catch(() => { });
168
+ this._userDataDirs.delete(userDataDir);
169
+ testDebug('close browser context complete (persistent)');
170
+ }
171
+ async _createUserDataDir() {
172
+ let cacheDirectory;
173
+ if (process.platform === 'linux')
174
+ cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
175
+ else if (process.platform === 'darwin')
176
+ cacheDirectory = path.join(os.homedir(), 'Library', 'Caches');
177
+ else if (process.platform === 'win32')
178
+ cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
179
+ else
180
+ throw new Error('Unsupported platform: ' + process.platform);
181
+ const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${this.browserConfig.launchOptions?.channel ?? this.browserConfig?.browserName}-profile`);
182
+ await fs.promises.mkdir(result, { recursive: true });
183
+ return result;
184
+ }
185
+ }
186
+ export class BrowserServerContextFactory extends BaseContextFactory {
187
+ constructor(browserConfig) {
188
+ super('persistent', browserConfig);
189
+ }
190
+ async _doObtainBrowser() {
191
+ const response = await fetch(new URL(`/json/launch`, this.browserConfig.browserAgent), {
192
+ method: 'POST',
193
+ body: JSON.stringify({
194
+ browserType: this.browserConfig.browserName,
195
+ userDataDir: this.browserConfig.userDataDir ?? await this._createUserDataDir(),
196
+ launchOptions: this.browserConfig.launchOptions,
197
+ contextOptions: this.browserConfig.contextOptions,
198
+ }),
199
+ });
200
+ const info = await response.json();
201
+ if (info.error)
202
+ throw new Error(info.error);
203
+ return await playwright.chromium.connectOverCDP(`http://localhost:${info.cdpPort}/`);
204
+ }
205
+ async _doCreateContext(browser) {
206
+ return this.browserConfig.isolated ? await browser.newContext() : browser.contexts()[0];
207
+ }
208
+ async _createUserDataDir() {
209
+ const dir = await userDataDir(this.browserConfig);
210
+ await fs.promises.mkdir(dir, { recursive: true });
211
+ return dir;
212
+ }
213
+ }
214
+ async function injectCdpPort(browserConfig) {
215
+ if (browserConfig.browserName === 'chromium')
216
+ browserConfig.launchOptions.cdpPort = await findFreePort();
217
+ }
218
+ async function findFreePort() {
219
+ return new Promise((resolve, reject) => {
220
+ const server = net.createServer();
221
+ server.listen(0, () => {
222
+ const { port } = server.address();
223
+ server.close(() => resolve(port));
224
+ });
225
+ server.on('error', reject);
226
+ });
227
+ }
228
+ //# sourceMappingURL=browserContextFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserContextFactory.js","sourceRoot":"","sources":["../src/browserContextFactory.ts"],"names":[],"mappings":"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"}