Skip to content

Commit e313702

Browse files
author
Natallia Harshunova
committed
Implement install_extension command
1 parent 6e52e66 commit e313702

7 files changed

Lines changed: 80 additions & 20 deletions

File tree

src/McpContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,4 +690,8 @@ export class McpContext implements Context {
690690
});
691691
await this.#networkCollector.init(await this.browser.pages());
692692
}
693+
694+
async installExtension(path: string): Promise<string> {
695+
return this.browser.installExtension(path);
696+
}
693697
}

src/cli.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ export const cliOptions = {
198198
default: true,
199199
describe: 'Set to false to exclude tools related to network.',
200200
},
201+
categoryExtension: {
202+
type: 'boolean',
203+
default: true,
204+
hidden: true,
205+
describe: 'Set to false to exclude tools related to extensions.',
206+
},
201207
usageStatistics: {
202208
type: 'boolean',
203209
// Marked as `false` until the feature is ready to be enabled by default.
@@ -261,6 +267,7 @@ export function parseArguments(version: string, argv = process.argv) {
261267
'Disable tools in the performance category',
262268
],
263269
['$0 --no-category-network', 'Disable tools in the network category'],
270+
['$0 --no-category-extension', 'Disable tools in the extension category'],
264271
[
265272
'$0 --user-data-dir=/tmp/user-data-dir',
266273
'Use a custom user data directory',

src/main.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,34 +64,37 @@ async function getContext(): Promise<McpContext> {
6464
const ignoreDefaultChromeArgs: string[] = (
6565
args.ignoreDefaultChromeArg ?? []
6666
).map(String);
67+
if (args.categoryExtension) {
68+
chromeArgs.push('--enable-unsafe-extension-debugging');
69+
}
6770
if (args.proxyServer) {
6871
chromeArgs.push(`--proxy-server=${args.proxyServer}`);
6972
}
7073
const devtools = args.experimentalDevtools ?? false;
7174
const browser =
7275
args.browserUrl || args.wsEndpoint || args.autoConnect
7376
? await ensureBrowserConnected({
74-
browserURL: args.browserUrl,
75-
wsEndpoint: args.wsEndpoint,
76-
wsHeaders: args.wsHeaders,
77-
// Important: only pass channel, if autoConnect is true.
78-
channel: args.autoConnect ? (args.channel as Channel) : undefined,
79-
userDataDir: args.userDataDir,
80-
devtools,
81-
})
77+
browserURL: args.browserUrl,
78+
wsEndpoint: args.wsEndpoint,
79+
wsHeaders: args.wsHeaders,
80+
// Important: only pass channel, if autoConnect is true.
81+
channel: args.autoConnect ? (args.channel as Channel) : undefined,
82+
userDataDir: args.userDataDir,
83+
devtools,
84+
})
8285
: await ensureBrowserLaunched({
83-
headless: args.headless,
84-
executablePath: args.executablePath,
85-
channel: args.channel as Channel,
86-
isolated: args.isolated ?? false,
87-
userDataDir: args.userDataDir,
88-
logFile,
89-
viewport: args.viewport,
90-
chromeArgs,
91-
ignoreDefaultChromeArgs,
92-
acceptInsecureCerts: args.acceptInsecureCerts,
93-
devtools,
94-
});
86+
headless: args.headless,
87+
executablePath: args.executablePath,
88+
channel: args.channel as Channel,
89+
isolated: args.isolated ?? false,
90+
userDataDir: args.userDataDir,
91+
logFile,
92+
viewport: args.viewport,
93+
chromeArgs,
94+
ignoreDefaultChromeArgs,
95+
acceptInsecureCerts: args.acceptInsecureCerts,
96+
devtools,
97+
});
9598

9699
if (context?.browser !== browser) {
97100
context = await McpContext.from(browser, logger, {
@@ -139,6 +142,12 @@ function registerTool(tool: ToolDefinition): void {
139142
) {
140143
return;
141144
}
145+
if (
146+
tool.annotations.category === ToolCategory.EXTENSION &&
147+
args.categoryExtension === false
148+
) {
149+
return;
150+
}
142151
if (
143152
tool.annotations.conditions?.includes('computerVision') &&
144153
!args.experimentalVision

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export type Context = Readonly<{
121121
* Returns a reqid for a cdpRequestId.
122122
*/
123123
resolveCdpElementId(cdpBackendNodeId: number): string | undefined;
124+
installExtension(path: string): Promise<string>;
124125
}>;
125126

126127
export function defineTool<Schema extends zod.ZodRawShape>(

src/tools/categories.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum ToolCategory {
1111
PERFORMANCE = 'performance',
1212
NETWORK = 'network',
1313
DEBUGGING = 'debugging',
14+
EXTENSION = 'extension',
1415
}
1516

1617
export const labels = {
@@ -20,4 +21,5 @@ export const labels = {
2021
[ToolCategory.PERFORMANCE]: 'Performance',
2122
[ToolCategory.NETWORK]: 'Network',
2223
[ToolCategory.DEBUGGING]: 'Debugging',
24+
[ToolCategory.EXTENSION]: 'Extensions',
2325
};

src/tools/extension.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { logger } from '../logger.js';
8+
import { zod } from '../third_party/index.js';
9+
10+
import { ToolCategory } from './categories.js';
11+
import { defineTool } from './ToolDefinition.js';
12+
13+
export const installExtension = defineTool({
14+
name: 'install_extension',
15+
description: 'Installs a Chrome extension from the given path.',
16+
annotations: {
17+
category: ToolCategory.EXTENSION,
18+
readOnlyHint: false,
19+
},
20+
schema: {
21+
path: zod
22+
.string()
23+
.describe('Absolute path to the unpacked extension folder.'),
24+
},
25+
handler: async (request, response, context) => {
26+
const { path } = request.params;
27+
try {
28+
const id = await context.installExtension(path);
29+
response.appendResponseLine(`Extension installed: ${id}`);
30+
} catch (error) {
31+
logger('Extension installation error: ', error);
32+
throw error;
33+
}
34+
},
35+
});

src/tools/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import * as consoleTools from './console.js';
77
import * as emulationTools from './emulation.js';
8+
import * as extensionTools from './extension.js';
89
import * as inputTools from './input.js';
910
import * as networkTools from './network.js';
1011
import * as pagesTools from './pages.js';
@@ -17,6 +18,7 @@ import type {ToolDefinition} from './ToolDefinition.js';
1718
const tools = [
1819
...Object.values(consoleTools),
1920
...Object.values(emulationTools),
21+
...Object.values(extensionTools),
2022
...Object.values(inputTools),
2123
...Object.values(networkTools),
2224
...Object.values(pagesTools),

0 commit comments

Comments
 (0)