Skip to content

Commit b5eca0f

Browse files
author
Natallia Harshunova
committed
Implement url allowlist and blocklist
1 parent 6acfdc0 commit b5eca0f

4 files changed

Lines changed: 116 additions & 0 deletions

File tree

src/bin/chrome-devtools-mcp-cli-options.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,17 @@ export const cliOptions = {
210210
describe:
211211
'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
212212
},
213+
blocklist: {
214+
type: 'array',
215+
describe:
216+
'URL patterns to block access to. Uses standard URLPattern API. Cannot be used with --blocklist',
217+
},
218+
allowlist: {
219+
type: 'array',
220+
describe:
221+
'URL patterns to allow access to (blocks everything else). Uses standard URLPattern API. Cannot be used with --blocklist.',
222+
conflicts: ['blocklist'],
223+
},
213224
ignoreDefaultChromeArg: {
214225
type: 'array',
215226
describe:

src/browser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export async function ensureBrowserConnected(options: {
5151
channel?: Channel;
5252
userDataDir?: string;
5353
enableExtensions?: boolean;
54+
blocklist?: string[];
55+
allowlist?: string[];
5456
}) {
5557
const {channel, enableExtensions} = options;
5658
if (browser?.connected) {
@@ -61,6 +63,8 @@ export async function ensureBrowserConnected(options: {
6163
targetFilter: makeTargetFilter(enableExtensions),
6264
defaultViewport: null,
6365
handleDevToolsAsPage: true,
66+
blocklist: options.blocklist,
67+
allowlist: options.allowlist,
6468
};
6569

6670
let autoConnect = false;
@@ -150,6 +154,8 @@ interface McpLaunchOptions {
150154
devtools: boolean;
151155
enableExtensions?: boolean;
152156
viaCli?: boolean;
157+
blocklist?: string[];
158+
allowlist?: string[];
153159
}
154160

155161
export function detectDisplay(): void {
@@ -229,6 +235,8 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
229235
acceptInsecureCerts: options.acceptInsecureCerts,
230236
handleDevToolsAsPage: true,
231237
enableExtensions: options.enableExtensions,
238+
blocklist: options.blocklist,
239+
allowlist: options.allowlist,
232240
});
233241
if (options.logFile) {
234242
// FIXME: we are probably subscribing too late to catch startup logs. We

src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ export async function createMcpServer(
197197
chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
198198
}
199199
const devtools = serverArgs.experimentalDevtools ?? false;
200+
const blocklist = serverArgs.blocklist
201+
? serverArgs.blocklist.map(String)
202+
: undefined;
203+
const allowlist = serverArgs.allowlist
204+
? serverArgs.allowlist.map(String)
205+
: undefined;
206+
200207
const browser =
201208
serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
202209
? await ensureBrowserConnected({
@@ -209,6 +216,8 @@ export async function createMcpServer(
209216
: undefined,
210217
userDataDir: serverArgs.userDataDir,
211218
devtools,
219+
blocklist,
220+
allowlist,
212221
})
213222
: await ensureBrowserLaunched({
214223
headless: serverArgs.headless,
@@ -224,6 +233,8 @@ export async function createMcpServer(
224233
devtools,
225234
enableExtensions: serverArgs.categoryExtensions,
226235
viaCli: serverArgs.viaCli,
236+
blocklist,
237+
allowlist,
227238
});
228239

229240
if (context?.browser !== browser) {

tests/browser.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {executablePath} from 'puppeteer';
1313

1414
import {detectDisplay, ensureBrowserConnected, launch} from '../src/browser.js';
1515

16+
import {serverHooks} from './server.js';
17+
1618
describe('browser', () => {
1719
it('detects display does not crash', () => {
1820
detectDisplay();
@@ -100,4 +102,88 @@ describe('browser', () => {
100102
await browser.close();
101103
}
102104
});
105+
106+
describe('Blocking', () => {
107+
const server = serverHooks();
108+
109+
it('blocks URLs in blocklist', async () => {
110+
server.addHtmlRoute('/allowed.html', '<html><body>Allowed</body></html>');
111+
server.addHtmlRoute('/blocked.html', '<html><body>Blocked</body></html>');
112+
113+
const browser = await launch({
114+
headless: true,
115+
isolated: true,
116+
executablePath: executablePath(),
117+
devtools: false,
118+
blocklist: ['*://*:*/blocked.html'],
119+
});
120+
try {
121+
const page = await browser.newPage();
122+
123+
// Access allowed URL
124+
await page.goto(server.getRoute('/allowed.html'));
125+
const content = await page.evaluate(() => document.body.textContent);
126+
assert.strictEqual(content, 'Allowed');
127+
128+
// Fetch of blocked URL from the page
129+
const fetchResult = await page.evaluate(async url => {
130+
try {
131+
await fetch(url);
132+
return 'SUCCESS';
133+
} catch (err) {
134+
return err instanceof Error ? err.message : String(err);
135+
}
136+
}, server.getRoute('/blocked.html'));
137+
138+
assert.strictEqual(fetchResult, 'Failed to fetch');
139+
} finally {
140+
await browser.close();
141+
}
142+
});
143+
144+
it(
145+
'blocks URLs not in allowlist',
146+
{skip: 'Requires Chrome 149 or greater'},
147+
async () => {
148+
server.addHtmlRoute(
149+
'/allowed.html',
150+
'<html><body>Allowed</body></html>',
151+
);
152+
server.addHtmlRoute(
153+
'/blocked.html',
154+
'<html><body>Blocked</body></html>',
155+
);
156+
157+
const browser = await launch({
158+
headless: true,
159+
isolated: true,
160+
executablePath: executablePath(),
161+
devtools: false,
162+
allowlist: ['*://*/allowed.html'],
163+
});
164+
try {
165+
const page = await browser.newPage();
166+
167+
// Access allowed URL
168+
await page.goto(server.getRoute('/allowed.html'));
169+
const content = await page.evaluate(() => document.body.textContent);
170+
assert.strictEqual(content, 'Allowed');
171+
172+
// Fetch of blocked URL from the page
173+
const fetchResult = await page.evaluate(async url => {
174+
try {
175+
await fetch(url);
176+
return 'SUCCESS';
177+
} catch (err) {
178+
return err instanceof Error ? err.message : String(err);
179+
}
180+
}, server.getRoute('/blocked.html'));
181+
182+
assert.strictEqual(fetchResult, 'Failed to fetch');
183+
} finally {
184+
await browser.close();
185+
}
186+
},
187+
);
188+
});
103189
});

0 commit comments

Comments
 (0)