豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit b52f12a

Browse files
committed
chore: add extension pages to list_pages tool
1 parent d95e9ba commit b52f12a

File tree

9 files changed

+277
-51
lines changed

9 files changed

+277
-51
lines changed

src/McpContext.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class McpContext implements Context {
109109
null;
110110

111111
#nextPageId = 1;
112+
#extensionPages = new WeakMap<Target, Page>();
112113

113114
#extensionServiceWorkerMap = new WeakMap<Target, string>();
114115
#nextExtensionServiceWorkerId = 1;
@@ -589,6 +590,36 @@ export class McpContext implements Context {
589590
this.#options.experimentalIncludeAllPages,
590591
);
591592

593+
const allTargets = this.browser.targets();
594+
const extensionTargets = allTargets.filter(target => {
595+
return (
596+
target.url().startsWith('chrome-extension://') &&
597+
target.type() === 'page'
598+
);
599+
});
600+
601+
for (const target of extensionTargets) {
602+
// Right now target.page() returns null for popup and side panel pages.
603+
let page = await target.page();
604+
if (!page) {
605+
// We need to cache pages instances for targets because target.asPage()
606+
// returns a new page instance every time.
607+
page = this.#extensionPages.get(target) ?? null;
608+
if (!page) {
609+
try {
610+
page = await target.asPage();
611+
this.#extensionPages.set(target, page);
612+
} catch (e) {
613+
this.logger('Failed to get page for extension target', e);
614+
}
615+
}
616+
}
617+
618+
if (page && !allPages.includes(page)) {
619+
allPages.push(page);
620+
}
621+
}
622+
592623
// Build a reverse lookup from BrowserContext instance → name.
593624
const contextToName = new Map<BrowserContext, string>();
594625
for (const [name, ctx] of this.#isolatedContexts) {

src/McpResponse.ts

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {DevTools} from './third_party/index.js';
1616
import type {
1717
ConsoleMessage,
1818
ImageContent,
19+
Page,
1920
ResourceType,
2021
TextContent,
2122
} from './third_party/index.js';
@@ -42,6 +43,7 @@ interface TraceInsightData {
4243
export class McpResponse implements Response {
4344
#includePages = false;
4445
#includeExtensionServiceWorkers = false;
46+
#includeExtensionPages = false;
4547
#snapshotParams?: SnapshotParams;
4648
#attachedNetworkRequestId?: number;
4749
#attachedNetworkRequestOptions?: {
@@ -94,6 +96,7 @@ export class McpResponse implements Response {
9496

9597
if (this.#args.categoryExtensions) {
9698
this.#includeExtensionServiceWorkers = value;
99+
this.#includeExtensionPages = value;
97100
}
98101
}
99102

@@ -501,6 +504,7 @@ export class McpResponse implements Response {
501504
pages?: object[];
502505
pagination?: object;
503506
extensionServiceWorkers?: object[];
507+
extensionPages?: object[];
504508
} = {};
505509

506510
const response = [];
@@ -559,34 +563,54 @@ Call ${handleDialog.name} to handle it before continuing.`);
559563
}
560564

561565
if (this.#includePages) {
562-
const parts = [`## Pages`];
563-
for (const page of context.getPages()) {
564-
const isolatedContextName = context.getIsolatedContextName(page);
565-
const contextLabel = isolatedContextName
566-
? ` isolatedContext=${isolatedContextName}`
567-
: '';
568-
parts.push(
569-
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`,
570-
);
566+
const allPages = context.getPages();
567+
568+
const {regularPages, extensionPages} = allPages.reduce(
569+
(acc: {regularPages: Page[]; extensionPages: Page[]}, page: Page) => {
570+
if (page.url().startsWith('chrome-extension://')) {
571+
acc.extensionPages.push(page);
572+
} else {
573+
acc.regularPages.push(page);
574+
}
575+
return acc;
576+
},
577+
{regularPages: [], extensionPages: []},
578+
);
579+
580+
if (regularPages.length) {
581+
const parts = [`## Pages`];
582+
const structuredPages = [];
583+
for (const page of regularPages) {
584+
const isolatedContextName = context.getIsolatedContextName(page);
585+
const contextLabel = isolatedContextName
586+
? ` isolatedContext=${isolatedContextName}`
587+
: '';
588+
parts.push(
589+
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`,
590+
);
591+
structuredPages.push(createStructuredPage(page, context));
592+
}
593+
response.push(...parts);
594+
structuredContent.pages = structuredPages;
571595
}
572-
response.push(...parts);
573-
structuredContent.pages = context.getPages().map(page => {
574-
const isolatedContextName = context.getIsolatedContextName(page);
575-
const entry: {
576-
id: number | undefined;
577-
url: string;
578-
selected: boolean;
579-
isolatedContext?: string;
580-
} = {
581-
id: context.getPageId(page),
582-
url: page.url(),
583-
selected: context.isPageSelected(page),
584-
};
585-
if (isolatedContextName) {
586-
entry.isolatedContext = isolatedContextName;
596+
597+
if (this.#includeExtensionPages) {
598+
if (extensionPages.length) {
599+
response.push(`## Extension Pages`);
600+
const structuredExtensionPages = [];
601+
for (const page of extensionPages) {
602+
const isolatedContextName = context.getIsolatedContextName(page);
603+
const contextLabel = isolatedContextName
604+
? ` isolatedContext=${isolatedContextName}`
605+
: '';
606+
response.push(
607+
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`,
608+
);
609+
structuredExtensionPages.push(createStructuredPage(page, context));
610+
}
611+
structuredContent.extensionPages = structuredExtensionPages;
587612
}
588-
return entry;
589-
});
613+
}
590614
}
591615

592616
if (this.#includeExtensionServiceWorkers) {
@@ -803,3 +827,21 @@ Call ${handleDialog.name} to handle it before continuing.`);
803827
this.#textResponseLines = [];
804828
}
805829
}
830+
function createStructuredPage(page: Page, context: McpContext) {
831+
const isolatedContextName = context.getIsolatedContextName(page);
832+
const entry: {
833+
id: number | undefined;
834+
url: string;
835+
selected: boolean;
836+
isolatedContext?: string;
837+
} = {
838+
id: context.getPageId(page),
839+
url: page.url(),
840+
selected: context.isPageSelected(page),
841+
};
842+
if (isolatedContextName) {
843+
entry.isolatedContext = isolatedContextName;
844+
}
845+
return entry;
846+
}
847+

src/browser.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ import {puppeteer} from './third_party/index.js';
2020

2121
let browser: Browser | undefined;
2222

23-
function makeTargetFilter() {
24-
const ignoredPrefixes = new Set([
25-
'chrome://',
26-
'chrome-extension://',
27-
'chrome-untrusted://',
28-
]);
23+
function makeTargetFilter(enableExtensions = false) {
24+
const ignoredPrefixes = new Set(['chrome://', 'chrome-untrusted://']);
25+
if (!enableExtensions) {
26+
ignoredPrefixes.add('chrome-extension://');
27+
}
2928

3029
return function targetFilter(target: Target): boolean {
3130
if (target.url() === 'chrome://newtab/') {
@@ -51,14 +50,15 @@ export async function ensureBrowserConnected(options: {
5150
devtools: boolean;
5251
channel?: Channel;
5352
userDataDir?: string;
53+
enableExtensions?: boolean;
5454
}) {
55-
const {channel} = options;
55+
const {channel, enableExtensions} = options;
5656
if (browser?.connected) {
5757
return browser;
5858
}
5959

6060
const connectOptions: Parameters<typeof puppeteer.connect>[0] = {
61-
targetFilter: makeTargetFilter(),
61+
targetFilter: makeTargetFilter(enableExtensions),
6262
defaultViewport: null,
6363
handleDevToolsAsPage: true,
6464
};
@@ -217,7 +217,7 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
217217
try {
218218
const browser = await puppeteer.launch({
219219
channel: puppeteerChannel,
220-
targetFilter: makeTargetFilter(),
220+
targetFilter: makeTargetFilter(options.enableExtensions),
221221
executablePath,
222222
defaultViewport: null,
223223
userDataDir,

src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export async function createMcpServer(
7979
: undefined,
8080
userDataDir: serverArgs.userDataDir,
8181
devtools,
82+
enableExtensions: serverArgs.categoryExtensions,
8283
})
8384
: await ensureBrowserLaunched({
8485
headless: serverArgs.headless,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "Test Extension Side Panel",
3+
"version": "1.0.0",
4+
"manifest_version": 3,
5+
"permissions": ["sidePanel"],
6+
"action": {
7+
"default_title": "Click to open panel"
8+
},
9+
"background": {
10+
"service_worker": "sw.js"
11+
},
12+
"side_panel": {
13+
"default_path": "sidepanel.html"
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Side Panel</title>
5+
</head>
6+
<body>
7+
<h1>Side Panel</h1>
8+
</body>
9+
</html>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
chrome.sidePanel
2+
.setPanelBehavior({openPanelOnActionClick: true})
3+
.catch(console.error);

tests/tools/pages.test.js.snapshot

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
exports[`pages > list_pages > list pages for extension pages with --category-extensions 1`] = `
2+
## Pages
3+
1: about:blank [selected]
4+
## Extension Pages
5+
2: chrome-extension://<extension-id>/popup.html
6+
`;
7+
8+
exports[`pages > list_pages > list pages for extension service workers with --category-extensions 1`] = `
9+
## Pages
10+
1: about:blank [selected]
11+
## Extension Service Workers
12+
sw-1: chrome-extension://<extension-id>/sw.js
13+
`;
14+
15+
exports[`pages > list_pages > list pages for extension service workers without --category-extensions 1`] = `
16+
## Pages
17+
1: about:blank [selected]
18+
`;
19+
20+
exports[`pages > list_pages > list pages for side panels with --category-extensions 1`] = `
21+
## Pages
22+
1: about:blank
23+
## Extension Pages
24+
2: chrome-extension://<extension-id>/sidepanel.html [selected]
25+
## Extension Service Workers
26+
sw-1: chrome-extension://<extension-id>/sw.js
27+
`;

0 commit comments

Comments
 (0)