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

Commit 3539a80

Browse files
committed
chore: list extension pages
1 parent 802b5f7 commit 3539a80

File tree

5 files changed

+155
-38
lines changed

5 files changed

+155
-38
lines changed

src/McpContext.ts

Lines changed: 27 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+
#extensionPopupPages = new WeakMap<Target, Page>();
112113

113114
#extensionServiceWorkerMap = new WeakMap<Target, string>();
114115
#nextExtensionServiceWorkerId = 1;
@@ -558,6 +559,32 @@ export class McpContext implements Context {
558559
async createPagesSnapshot(): Promise<Page[]> {
559560
const {pages: allPages, isolatedContextNames} = await this.#getAllPages();
560561

562+
const allTargets = await this.browser.targets();
563+
const extensionTargets = allTargets.filter(target => {
564+
return (
565+
target.url().startsWith('chrome-extension://') &&
566+
(target.type() === 'page' || target.type() === 'other')
567+
);
568+
});
569+
570+
for (const target of extensionTargets) {
571+
let page = await target.page();
572+
if (!page) {
573+
page = this.#extensionPopupPages.get(target) ?? null;
574+
if (!page) {
575+
try {
576+
page = await target.asPage();
577+
this.#extensionPopupPages.set(target, page);
578+
} catch (e) {
579+
this.logger('Failed to get page for extension target', e);
580+
}
581+
}
582+
}
583+
if (page && !allPages.includes(page)) {
584+
allPages.push(page);
585+
}
586+
}
587+
561588
for (const page of allPages) {
562589
let mcpPage = this.#mcpPages.get(page);
563590
if (!mcpPage) {

src/McpResponse.ts

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface TraceInsightData {
4242
export class McpResponse implements Response {
4343
#includePages = false;
4444
#includeExtensionServiceWorkers = false;
45+
#includeExtensionPages = false;
4546
#snapshotParams?: SnapshotParams;
4647
#attachedNetworkRequestId?: number;
4748
#attachedNetworkRequestOptions?: {
@@ -94,6 +95,7 @@ export class McpResponse implements Response {
9495

9596
if (this.#args.categoryExtensions) {
9697
this.#includeExtensionServiceWorkers = value;
98+
this.#includeExtensionPages = value;
9799
}
98100
}
99101

@@ -501,6 +503,7 @@ export class McpResponse implements Response {
501503
pages?: object[];
502504
pagination?: object;
503505
extensionServiceWorkers?: object[];
506+
extensionPages?: object[];
504507
} = {};
505508

506509
const response = [`# ${toolName} response`];
@@ -564,38 +567,66 @@ Call ${handleDialog.name} to handle it before continuing.`);
564567
}
565568

566569
if (this.#includePages) {
567-
const parts = [`## Pages`];
568-
for (const page of context.getPages()) {
569-
const isolatedContextName = context.getIsolatedContextName(page);
570-
const contextLabel = isolatedContextName
571-
? ` isolatedContext=${isolatedContextName}`
572-
: '';
573-
parts.push(
574-
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`,
575-
);
570+
const allPages = context.getPages();
571+
const regularPages = this.#includeExtensionPages
572+
? allPages.filter(p => !p.url().startsWith('chrome-extension://'))
573+
: allPages;
574+
575+
if (regularPages.length) {
576+
const parts = [`## Pages`];
577+
for (const page of regularPages) {
578+
const isolatedContextName = context.getIsolatedContextName(page);
579+
const contextLabel = isolatedContextName
580+
? ` isolatedContext=${isolatedContextName}`
581+
: '';
582+
parts.push(
583+
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`,
584+
);
585+
}
586+
response.push(...parts);
587+
structuredContent.pages = regularPages.map(page => {
588+
const isolatedContextName = context.getIsolatedContextName(page);
589+
const entry: {
590+
id: number | undefined;
591+
url: string;
592+
selected: boolean;
593+
isolatedContext?: string;
594+
} = {
595+
id: context.getPageId(page),
596+
url: page.url(),
597+
selected: context.isPageSelected(page),
598+
};
599+
if (isolatedContextName) {
600+
entry.isolatedContext = isolatedContextName;
601+
}
602+
return entry;
603+
});
576604
}
577-
response.push(...parts);
578-
structuredContent.pages = context.getPages().map(page => {
579-
const isolatedContextName = context.getIsolatedContextName(page);
580-
const entry: {
581-
id: number | undefined;
582-
url: string;
583-
selected: boolean;
584-
isolatedContext?: string;
585-
} = {
586-
id: context.getPageId(page),
587-
url: page.url(),
588-
selected: context.isPageSelected(page),
589-
};
590-
if (isolatedContextName) {
591-
entry.isolatedContext = isolatedContextName;
605+
606+
if (this.#includeExtensionPages) {
607+
const extensionPages = allPages.filter(p =>
608+
p.url().startsWith('chrome-extension://'),
609+
);
610+
if (extensionPages.length) {
611+
response.push(`## Extension Pages`);
612+
for (const page of extensionPages) {
613+
response.push(
614+
`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}`,
615+
);
616+
}
617+
structuredContent.extensionPages = extensionPages.map(page => {
618+
return {
619+
id: context.getPageId(page),
620+
url: page.url(),
621+
selected: context.isPageSelected(page),
622+
};
623+
});
592624
}
593-
return entry;
594-
});
625+
}
595626
}
596627

597628
if (this.#includeExtensionServiceWorkers) {
598-
if (!context.getExtensionServiceWorkers().length) {
629+
if (context.getExtensionServiceWorkers().length) {
599630
response.push(`## Extension Service Workers`);
600631
}
601632

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
};
@@ -215,7 +215,7 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
215215
try {
216216
const browser = await puppeteer.launch({
217217
channel: puppeteerChannel,
218-
targetFilter: makeTargetFilter(),
218+
targetFilter: makeTargetFilter(options.enableExtensions),
219219
executablePath,
220220
defaultViewport: null,
221221
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,

tests/tools/pages.test.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import type {Dialog} from 'puppeteer-core';
1212
import sinon from 'sinon';
1313

1414
import type {ParsedArguments} from '../../src/cli.js';
15-
import {installExtension} from '../../src/tools/extensions.js';
15+
import {
16+
installExtension,
17+
triggerExtensionAction,
18+
} from '../../src/tools/extensions.js';
1619
import {
1720
listPages,
1821
newPage,
@@ -25,6 +28,8 @@ import {
2528
} from '../../src/tools/pages.js';
2629
import {html, withMcpContext} from '../utils.js';
2730

31+
import {extractId} from './extensions.test.js';
32+
2833
const EXTENSION_PATH = path.join(
2934
import.meta.dirname,
3035
'../../../tests/tools/fixtures/extension-sw',
@@ -46,8 +51,61 @@ describe('pages', () => {
4651
assert.ok(response.includePages);
4752
});
4853
});
54+
it(`list pages for extension pages with --category-extensions`, async () => {
55+
await withMcpContext(
56+
async (response, context) => {
57+
await installExtension.handler(
58+
{params: {path: EXTENSION_PATH}},
59+
response,
60+
context,
61+
);
62+
63+
const extensionId = extractId(response);
64+
assert.ok(extensionId);
65+
66+
await triggerExtensionAction.handler(
67+
{params: {id: extensionId}},
68+
response,
69+
context,
70+
);
71+
const popupTarget = await context.browser.waitForTarget(
72+
t => t.type() === 'page' && t.url().includes('chrome-extension://'),
73+
);
74+
75+
response.resetResponseLineForTesting();
76+
77+
const listPageDef = listPages({
78+
categoryExtensions: true,
79+
} as ParsedArguments);
80+
await listPageDef.handler(
81+
{params: {}, page: context.getSelectedMcpPage()},
82+
response,
83+
context,
84+
);
85+
86+
const result = await response.handle(listPageDef.name, context);
87+
const textContent = result.content.find(c => c.type === 'text') as {
88+
type: 'text';
89+
text: string;
90+
};
91+
assert.ok(textContent);
92+
93+
assert.ok(textContent.text.includes(popupTarget.url()));
94+
},
95+
{
96+
channel: process.env.CANARY_EXECUTABLE_PATH
97+
? undefined
98+
: 'chrome-canary',
99+
executablePath: process.env.CANARY_EXECUTABLE_PATH,
100+
},
101+
{
102+
categoryExtensions: true,
103+
} as ParsedArguments,
104+
);
105+
});
106+
49107
for (const categoryExtensions of [true, false]) {
50-
it(`list pages ${categoryExtensions ? 'with' : 'without'} --category-extensions`, async () => {
108+
it(`list pages for extension service workers ${categoryExtensions ? 'with' : 'without'} --category-extensions`, async () => {
51109
await withMcpContext(
52110
async (response, context) => {
53111
await installExtension.handler(

0 commit comments

Comments
 (0)