Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,12 @@

### `get_network_request`

**Description:** Gets a network request by URL. You can get all requests by calling [`list_network_requests`](#list_network_requests).
**Description:** Gets a network request by reqid. You can get all requests by calling [`list_network_requests`](#list_network_requests).
Get the request currently selected in the DevTools UI by ommitting reqid

**Parameters:**

- **reqid** (number) **(required)**: The reqid of a request on the page from the listed network requests
- **reqid** (number) _(optional)_: The reqid of the network request. If omitted, looks up the current request selected in DevTools UI.

---

Expand Down
44 changes: 42 additions & 2 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,13 @@ export class McpContext implements Context {
);
});

await this.#detectOpenDevToolsWindows(allPages);
await this.detectOpenDevToolsWindows();

return this.#pages;
}

async #detectOpenDevToolsWindows(pages: Page[]) {
async detectOpenDevToolsWindows() {
const pages = await this.browser.pages();
this.#pageToDevToolsPage = new Map<Page, Page>();
for (const devToolsPage of pages) {
if (devToolsPage.url().startsWith('devtools://')) {
Expand Down Expand Up @@ -377,6 +378,45 @@ export class McpContext implements Context {
return this.#pageToDevToolsPage.get(page);
}

async getDevToolsData(): Promise<undefined | {requestId?: number}> {
try {
const selectedPage = this.getSelectedPage();
const devtoolsPage = this.getDevToolsPage(selectedPage);
if (devtoolsPage) {
const cdpRequestId = await devtoolsPage.evaluate(async () => {
// @ts-expect-error no types
const UI = await import('/bundled/ui/legacy/legacy.js');
// @ts-expect-error no types
const SDK = await import('/bundled/core/sdk/sdk.js');
const request = UI.Context.Context.instance().flavor(
SDK.NetworkRequest.NetworkRequest,
);
return request?.requestId();
});
if (!cdpRequestId) {
this.logger('no context request');
return;
}
const request = this.#networkCollector.find(selectedPage, request => {
// @ts-expect-error id is internal.
return request.id === cdpRequestId;
});
if (!request) {
this.logger('no collected request for ' + cdpRequestId);
return;
}
return {
requestId: this.#networkCollector.getIdForResource(request),
};
} else {
this.logger('no devtools page deteched');
}
} catch (err) {
this.logger('error getting devtools data', err);
}
return;
}

/**
* Creates a text snapshot of a page.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class McpResponse implements Response {
pagination?: PaginationOptions;
resourceTypes?: ResourceType[];
includePreservedRequests?: boolean;
networkRequestIdInDevToolsUI?: number;
};
#consoleDataOptions?: {
include: boolean;
Expand All @@ -67,6 +68,7 @@ export class McpResponse implements Response {
options?: PaginationOptions & {
resourceTypes?: ResourceType[];
includePreservedRequests?: boolean;
networkRequestIdInDevToolsUI?: number;
},
): void {
if (!value) {
Expand All @@ -85,6 +87,7 @@ export class McpResponse implements Response {
: undefined,
resourceTypes: options?.resourceTypes,
includePreservedRequests: options?.includePreservedRequests,
networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
};
}

Expand Down Expand Up @@ -391,6 +394,8 @@ Call ${handleDialog.name} to handle it before continuing.`);
getShortDescriptionForRequest(
request,
context.getNetworkRequestStableId(request),
context.getNetworkRequestStableId(request) ===
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
),
);
}
Expand Down
28 changes: 22 additions & 6 deletions src/PageCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,32 @@ export class PageCollector<T> {
throw new Error('No requests found for selected page');
}

for (const navigation of navigations) {
for (const collected of navigation) {
if (collected[stableIdSymbol] === stableId) {
return collected;
}
}
const item = this.find(page, item => item[stableIdSymbol] === stableId);

if (item) {
return item;
}

throw new Error('Request not found for selected page');
}

find(
page: Page,
filter: (item: WithSymbolId<T>) => boolean,
): WithSymbolId<T> | undefined {
const navigations = this.storage.get(page);
if (!navigations) {
return;
}

for (const navigation of navigations) {
const item = navigation.find(filter);
if (item) {
return item;
}
}
return;
}
}

export class NetworkCollector extends PageCollector<HTTPRequest> {
Expand Down
3 changes: 2 additions & 1 deletion src/formatters/networkFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ const BODY_CONTEXT_SIZE_LIMIT = 10000;
export function getShortDescriptionForRequest(
request: HTTPRequest,
id: number,
selectedInDevToolsUI = false,
): string {
// TODO truncate the URL
return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}`;
return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}${selectedInDevToolsUI ? ` [selected in DevTools UI]` : ''}`;
}

export function getStatusFromRequest(request: HTTPRequest): string {
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ function registerTool(tool: ToolDefinition): void {
try {
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
const context = await getContext();
await context.detectOpenDevToolsWindows();
const response = new McpResponse();
await tool.handler(
{
Expand Down
2 changes: 2 additions & 0 deletions src/tools/ToolDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface Response {
options?: PaginationOptions & {
resourceTypes?: string[];
includePreservedRequests?: boolean;
networkRequestIdInDevToolsUI?: number;
},
): void;
setIncludeConsoleData(
Expand Down Expand Up @@ -102,6 +103,7 @@ export type Context = Readonly<{
text: string;
timeout?: number | undefined;
}): Promise<Element>;
getDevToolsData(): Promise<undefined | {requestId?: number}>;
}>;

export function defineTool<Schema extends zod.ZodRawShape>(
Expand Down
25 changes: 20 additions & 5 deletions src/tools/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,46 @@ export const listNetworkRequests = defineTool({
'Set to true to return the preserved requests over the last 3 navigations.',
),
},
handler: async (request, response) => {
handler: async (request, response, context) => {
const data = await context.getDevToolsData();
response.setIncludeNetworkRequests(true, {
pageSize: request.params.pageSize,
pageIdx: request.params.pageIdx,
resourceTypes: request.params.resourceTypes,
includePreservedRequests: request.params.includePreservedRequests,
networkRequestIdInDevToolsUI: data?.requestId,
});
},
});

export const getNetworkRequest = defineTool({
name: 'get_network_request',
description: `Gets a network request by URL. You can get all requests by calling ${listNetworkRequests.name}.`,
description: `Gets a network request by reqid. You can get all requests by calling ${listNetworkRequests.name}.
Get the request currently selected in the DevTools UI by ommitting reqid`,
annotations: {
category: ToolCategory.NETWORK,
readOnlyHint: true,
},
schema: {
reqid: zod
.number()
.optional()
.describe(
'The reqid of a request on the page from the listed network requests',
'The reqid of the network request. If omitted, looks up the current request selected in DevTools UI.',
),
},
handler: async (request, response, _context) => {
response.attachNetworkRequest(request.params.reqid);
handler: async (request, response, context) => {
if (request.params.reqid) {
response.attachNetworkRequest(request.params.reqid);
} else {
const data = await context.getDevToolsData();
if (data?.requestId) {
response.attachNetworkRequest(data?.requestId);
} else {
response.appendResponseLine(
`Nothing is currently selected in DevTools UI.`,
);
}
}
},
});
10 changes: 10 additions & 0 deletions tests/formatters/networkFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ describe('networkFormatter', () => {
'reqid=1 GET http://example.com [failed - Error in Network]',
);
});

it('marks requests selected in DevTools UI', async () => {
const request = getMockRequest();
const result = getShortDescriptionForRequest(request, 1, true);

assert.equal(
result,
'reqid=1 GET http://example.com [pending] [selected in DevTools UI]',
);
});
});

describe('getFormattedHeaderValue', () => {
Expand Down