@mcp-b/chrome-devtools-mcp 1.8.1 → 1.8.2

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.
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * MCP Client Transport that connects to an extension's MCP server via CDP.
8
+ *
9
+ * Discovers the extension's service worker, attaches to it, and establishes
10
+ * a bidirectional message channel using Runtime.evaluate (client → server)
11
+ * and Runtime.addBinding (server → client).
12
+ *
13
+ * CDP attaches ONLY to the service worker. Pages remain clean with no
14
+ * navigator.webdriver flag, bypassing bot detection entirely.
15
+ */
16
+ export class CDPClientTransport {
17
+ #browser;
18
+ #extensionId;
19
+ #connectTimeout;
20
+ #session = null;
21
+ #started = false;
22
+ #closed = false;
23
+ #bindingHandler = null;
24
+ #disconnectHandler = null;
25
+ onclose;
26
+ onerror;
27
+ onmessage;
28
+ constructor(options) {
29
+ this.#browser = options.browser;
30
+ this.#extensionId = options.extensionId;
31
+ this.#connectTimeout = options.connectTimeout ?? 10_000;
32
+ }
33
+ async start() {
34
+ if (this.#started) {
35
+ throw new Error('CDPClientTransport already started. If using Client class, note that connect() calls start() automatically.');
36
+ }
37
+ if (this.#closed) {
38
+ throw new Error('CDPClientTransport has been closed');
39
+ }
40
+ this.#started = true;
41
+ try {
42
+ // Handle browser disconnect
43
+ this.#disconnectHandler = () => {
44
+ if (this.#closed)
45
+ return;
46
+ this.#browser = null;
47
+ this.onclose?.();
48
+ };
49
+ this.#browser.on('disconnected', this.#disconnectHandler);
50
+ // Find extension service worker target with retry
51
+ const swTarget = await this.#findExtensionServiceWorker();
52
+ if (!swTarget) {
53
+ throw new Error(this.#extensionId
54
+ ? `Extension ${this.#extensionId} service worker not found`
55
+ : 'No extension service worker found. Is an MCP-enabled extension installed?');
56
+ }
57
+ // Create a CDP session directly on the service worker target
58
+ this.#session = await swTarget.createCDPSession();
59
+ // Enable Runtime domain
60
+ await this.#session.send('Runtime.enable');
61
+ // Add binding for receiving messages from the extension
62
+ await this.#session.send('Runtime.addBinding', {
63
+ name: '__mcpCDPToClient',
64
+ });
65
+ // Set up handler for binding calls
66
+ this.#bindingHandler = (event) => {
67
+ if (event.name !== '__mcpCDPToClient')
68
+ return;
69
+ if (this.#closed)
70
+ return;
71
+ try {
72
+ const message = JSON.parse(event.payload);
73
+ this.onmessage?.(message);
74
+ }
75
+ catch (err) {
76
+ this.onerror?.(new Error(`Failed to parse message from extension: ${err}`));
77
+ }
78
+ };
79
+ this.#session.on('Runtime.bindingCalled', this.#bindingHandler);
80
+ // Verify the extension has the CDP transport ready
81
+ const { result } = await this.#session.send('Runtime.evaluate', {
82
+ expression: 'globalThis.__mcpCDPTransport?.isReady === true',
83
+ returnByValue: true,
84
+ });
85
+ if (!result.value) {
86
+ throw new Error('Extension CDP transport not ready. Ensure the extension has CDP bridge support enabled.');
87
+ }
88
+ // Connect our binding to the extension's transport
89
+ await this.#session.send('Runtime.evaluate', {
90
+ expression: `
91
+ globalThis.__mcpCDPTransport._sendBinding = (jsonStr) => {
92
+ __mcpCDPToClient(jsonStr);
93
+ };
94
+ `,
95
+ });
96
+ }
97
+ catch (err) {
98
+ this.#started = false;
99
+ await this.#cleanup();
100
+ throw err;
101
+ }
102
+ }
103
+ async send(message) {
104
+ if (!this.#started) {
105
+ throw new Error('CDPClientTransport not started');
106
+ }
107
+ if (this.#closed) {
108
+ throw new Error('CDPClientTransport has been closed');
109
+ }
110
+ if (!this.#session) {
111
+ throw new Error('CDP session not available');
112
+ }
113
+ const jsonStr = JSON.stringify(message);
114
+ try {
115
+ await this.#session.send('Runtime.evaluate', {
116
+ expression: `globalThis.__mcpCDPTransport.receiveMessage(${JSON.stringify(jsonStr)})`,
117
+ });
118
+ }
119
+ catch (err) {
120
+ const error = new Error(`Failed to send message to extension: ${err}`);
121
+ this.onerror?.(error);
122
+ throw error;
123
+ }
124
+ }
125
+ async close() {
126
+ if (this.#closed)
127
+ return;
128
+ this.#closed = true;
129
+ this.#started = false;
130
+ await this.#cleanup();
131
+ if (this.#browser && this.#disconnectHandler) {
132
+ this.#browser.off('disconnected', this.#disconnectHandler);
133
+ this.#disconnectHandler = null;
134
+ }
135
+ this.onclose?.();
136
+ }
137
+ /**
138
+ * Find the extension's service worker target with retry.
139
+ */
140
+ async #findExtensionServiceWorker() {
141
+ const deadline = Date.now() + this.#connectTimeout;
142
+ const retryInterval = 1_000;
143
+ while (Date.now() < deadline) {
144
+ const targets = this.#browser.targets();
145
+ const sw = targets.find(t => {
146
+ if (t.type() !== 'service_worker')
147
+ return false;
148
+ if (!t.url().startsWith('chrome-extension://'))
149
+ return false;
150
+ if (this.#extensionId && !t.url().includes(this.#extensionId))
151
+ return false;
152
+ return true;
153
+ });
154
+ if (sw)
155
+ return sw;
156
+ const remaining = deadline - Date.now();
157
+ if (remaining > retryInterval) {
158
+ await new Promise(resolve => setTimeout(resolve, retryInterval));
159
+ }
160
+ else {
161
+ break;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ /**
167
+ * Clean up CDP resources.
168
+ */
169
+ async #cleanup() {
170
+ if (this.#session && this.#bindingHandler) {
171
+ this.#session.off('Runtime.bindingCalled', this.#bindingHandler);
172
+ this.#bindingHandler = null;
173
+ }
174
+ if (this.#session) {
175
+ try {
176
+ await this.#session.detach();
177
+ }
178
+ catch {
179
+ // Ignore detach errors
180
+ }
181
+ this.#session = null;
182
+ }
183
+ }
184
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/chrome-devtools-mcp",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "MCP server for Chrome DevTools with WebMCP integration for connecting to website MCP tools",
5
5
  "keywords": [
6
6
  "mcp",