Skip to content

Commit 18d166a

Browse files
authored
Merge branch 'main' into fix/make-category-extension-explicit
2 parents 6fb8295 + 6279177 commit 18d166a

9 files changed

Lines changed: 179 additions & 23 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.20.1"
2+
".": "0.20.2"
33
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [0.20.2](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.20.1...chrome-devtools-mcp-v0.20.2) (2026-03-18)
4+
5+
6+
### 📄 Documentation
7+
8+
* add troubleshooting for Claude Code plugin HTTPS clone failures ([#1195](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1195)) ([d082ca4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d082ca4ecd35a023d09f9c1ff949d5fb0c3fb069))
9+
310
## [0.20.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.20.0...chrome-devtools-mcp-v0.20.1) (2026-03-16)
411

512

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chrome-devtools-mcp",
3-
"version": "0.20.1",
3+
"version": "0.20.2",
44
"description": "MCP server for Chrome DevTools",
55
"type": "module",
66
"bin": {

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
"url": "https://github.com/ChromeDevTools/chrome-devtools-mcp",
88
"source": "github"
99
},
10-
"version": "0.20.1",
10+
"version": "0.20.2",
1111
"packages": [
1212
{
1313
"registryType": "npm",
1414
"registryBaseUrl": "https://registry.npmjs.org",
1515
"identifier": "chrome-devtools-mcp",
16-
"version": "0.20.1",
16+
"version": "0.20.2",
1717
"transport": {
1818
"type": "stdio"
1919
},

src/DevtoolsUtils.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,61 @@ import type {
1515
Target as PuppeteerTarget,
1616
} from './third_party/index.js';
1717

18+
export function extractUrlLikeFromDevToolsTitle(
19+
title: string,
20+
): string | undefined {
21+
const match = title.match(new RegExp(`DevTools - (.*)`));
22+
return match?.[1] ?? undefined;
23+
}
24+
25+
export function urlsEqual(url1: string, url2: string): boolean {
26+
const normalizedUrl1 = normalizeUrl(url1);
27+
const normalizedUrl2 = normalizeUrl(url2);
28+
return normalizedUrl1 === normalizedUrl2;
29+
}
30+
31+
/**
32+
* For the sake of the MCP server, when we determine if two URLs are equal we
33+
* remove some parts:
34+
*
35+
* 1. We do not care about the protocol.
36+
* 2. We do not care about trailing slashes.
37+
* 3. We do not care about "www".
38+
* 4. We ignore the hash parts.
39+
*
40+
* For example, if the user types "record a trace on foo.com", we would want to
41+
* match a tab in the connected Chrome instance that is showing "www.foo.com/"
42+
*/
43+
function normalizeUrl(url: string): string {
44+
let result = url.trim();
45+
46+
// Remove protocols
47+
if (result.startsWith('https://')) {
48+
result = result.slice(8);
49+
} else if (result.startsWith('http://')) {
50+
result = result.slice(7);
51+
}
52+
53+
// Remove 'www.'. This ensures that we find the right URL regardless of if the user adds `www` or not.
54+
if (result.startsWith('www.')) {
55+
result = result.slice(4);
56+
}
57+
58+
// We use target URLs to locate DevTools but those often do
59+
// no include hash.
60+
const hashIdx = result.lastIndexOf('#');
61+
if (hashIdx !== -1) {
62+
result = result.slice(0, hashIdx);
63+
}
64+
65+
// Remove trailing slash
66+
if (result.endsWith('/')) {
67+
result = result.slice(0, -1);
68+
}
69+
70+
return result;
71+
}
72+
1873
/**
1974
* A mock implementation of an issues manager that only implements the methods
2075
* that are actually used by the IssuesAggregator

src/McpContext.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import fs from 'node:fs/promises';
88
import path from 'node:path';
99

1010
import type {TargetUniverse} from './DevtoolsUtils.js';
11-
import {UniverseManager} from './DevtoolsUtils.js';
11+
import {
12+
extractUrlLikeFromDevToolsTitle,
13+
UniverseManager,
14+
urlsEqual,
15+
} from './DevtoolsUtils.js';
1216
import {McpPage} from './McpPage.js';
1317
import type {ListenerMap, UncaughtError} from './PageCollector.js';
1418
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
@@ -649,21 +653,37 @@ export class McpContext implements Context {
649653
async detectOpenDevToolsWindows() {
650654
this.logger('Detecting open DevTools windows');
651655
const {pages} = await this.#getAllPages();
652-
653-
await Promise.all(
654-
pages.map(async page => {
655-
const mcpPage = this.#mcpPages.get(page);
656-
if (!mcpPage) {
657-
return;
658-
}
659-
660-
if (await page.hasDevTools()) {
661-
mcpPage.devToolsPage = await page.openDevTools();
662-
} else {
663-
mcpPage.devToolsPage = undefined;
656+
// Clear all devToolsPage references before re-detecting.
657+
for (const mcpPage of this.#mcpPages.values()) {
658+
mcpPage.devToolsPage = undefined;
659+
}
660+
for (const devToolsPage of pages) {
661+
if (devToolsPage.url().startsWith('devtools://')) {
662+
try {
663+
this.logger('Calling getTargetInfo for ' + devToolsPage.url());
664+
const data = await devToolsPage
665+
// @ts-expect-error no types for _client().
666+
._client()
667+
.send('Target.getTargetInfo');
668+
const devtoolsPageTitle = data.targetInfo.title;
669+
const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
670+
if (!urlLike) {
671+
continue;
672+
}
673+
// TODO: lookup without a loop.
674+
for (const page of this.#pages) {
675+
if (urlsEqual(page.url(), urlLike)) {
676+
const mcpPage = this.#mcpPages.get(page);
677+
if (mcpPage) {
678+
mcpPage.devToolsPage = devToolsPage;
679+
}
680+
}
681+
}
682+
} catch (error) {
683+
this.logger('Issue occurred while trying to find DevTools', error);
664684
}
665-
}),
666-
);
685+
}
686+
}
667687
}
668688

669689
getExtensionServiceWorkers(): ExtensionServiceWorker[] {

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
// If moved update release-please config
88
// x-release-please-start-version
9-
export const VERSION = '0.20.1';
9+
export const VERSION = '0.20.2';
1010
// x-release-please-end

tests/DevtoolsUtils.test.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {afterEach, describe, it} from 'node:test';
99

1010
import sinon from 'sinon';
1111

12-
import {UniverseManager} from '../src/DevtoolsUtils.js';
12+
import {
13+
extractUrlLikeFromDevToolsTitle,
14+
urlsEqual,
15+
UniverseManager,
16+
} from '../src/DevtoolsUtils.js';
1317
import {DevTools} from '../src/third_party/index.js';
1418
import type {Browser, Target} from '../src/third_party/index.js';
1519

@@ -20,6 +24,76 @@ import {
2024
withBrowser,
2125
} from './utils.js';
2226

27+
describe('extractUrlFromDevToolsTitle', () => {
28+
it('deals with no trailing /', () => {
29+
assert.strictEqual(
30+
extractUrlLikeFromDevToolsTitle('DevTools - example.com'),
31+
'example.com',
32+
);
33+
});
34+
it('deals with a trailing /', () => {
35+
assert.strictEqual(
36+
extractUrlLikeFromDevToolsTitle('DevTools - example.com/'),
37+
'example.com/',
38+
);
39+
});
40+
it('deals with www', () => {
41+
assert.strictEqual(
42+
extractUrlLikeFromDevToolsTitle('DevTools - www.example.com/'),
43+
'www.example.com/',
44+
);
45+
});
46+
it('deals with complex url', () => {
47+
assert.strictEqual(
48+
extractUrlLikeFromDevToolsTitle(
49+
'DevTools - www.example.com/path.html?a=b#3',
50+
),
51+
'www.example.com/path.html?a=b#3',
52+
);
53+
});
54+
});
55+
56+
describe('urlsEqual', () => {
57+
it('ignores trailing slashes', () => {
58+
assert.strictEqual(
59+
urlsEqual('https://google.com/', 'https://google.com'),
60+
true,
61+
);
62+
});
63+
64+
it('ignores www', () => {
65+
assert.strictEqual(
66+
urlsEqual('https://google.com/', 'https://www.google.com'),
67+
true,
68+
);
69+
});
70+
71+
it('ignores protocols', () => {
72+
assert.strictEqual(
73+
urlsEqual('https://google.com/', 'http://www.google.com'),
74+
true,
75+
);
76+
});
77+
78+
it('does not ignore other subdomains', () => {
79+
assert.strictEqual(
80+
urlsEqual('https://google.com/', 'https://photos.google.com'),
81+
false,
82+
);
83+
});
84+
85+
it('ignores hash', () => {
86+
assert.strictEqual(
87+
urlsEqual('https://google.com/#', 'http://www.google.com'),
88+
true,
89+
);
90+
assert.strictEqual(
91+
urlsEqual('https://google.com/#21', 'http://www.google.com#12'),
92+
true,
93+
);
94+
});
95+
});
96+
2397
describe('UniverseManager', () => {
2498
afterEach(() => {
2599
sinon.restore();

0 commit comments

Comments
 (0)