Skip to content

Commit c947532

Browse files
feat: expose previous navigations to the MCP server
1 parent b6b42ec commit c947532

File tree

8 files changed

+119
-6
lines changed

8 files changed

+119
-6
lines changed

src/McpContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ export class McpContext implements Context {
128128
return context;
129129
}
130130

131-
getNetworkRequests(): HTTPRequest[] {
131+
getNetworkRequests(includePreviousNavigations?: number): HTTPRequest[] {
132132
const page = this.getSelectedPage();
133-
return this.#networkCollector.getData(page);
133+
return this.#networkCollector.getData(page, includePreviousNavigations);
134134
}
135135

136136
getConsoleData(): Array<ConsoleMessage | Error> {

src/McpResponse.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export class McpResponse implements Response {
4040
include: boolean;
4141
pagination?: PaginationOptions;
4242
resourceTypes?: ResourceType[];
43+
includePreviousNavigations?: number;
4344
};
4445
#consoleDataOptions?: {
4546
include: boolean;
@@ -60,6 +61,7 @@ export class McpResponse implements Response {
6061
value: boolean,
6162
options?: PaginationOptions & {
6263
resourceTypes?: ResourceType[];
64+
includePreviousNavigations?: number;
6365
},
6466
): void {
6567
if (!value) {
@@ -77,6 +79,7 @@ export class McpResponse implements Response {
7779
}
7880
: undefined,
7981
resourceTypes: options?.resourceTypes,
82+
includePreviousNavigations: options?.includePreviousNavigations,
8083
};
8184
}
8285

@@ -344,7 +347,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
344347
response.push(...this.#formatConsoleData(data.consoleData));
345348

346349
if (this.#networkRequestsOptions?.include) {
347-
let requests = context.getNetworkRequests();
350+
let requests = context.getNetworkRequests(
351+
this.#networkRequestsOptions?.includePreviousNavigations,
352+
);
348353

349354
// Apply resource type filtering if specified
350355
if (this.#networkRequestsOptions.resourceTypes?.length) {

src/PageCollector.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,18 @@ export class PageCollector<T> {
133133
this.storage.delete(page);
134134
}
135135

136-
getData(page: Page): T[] {
136+
getData(page: Page, includePreviousNavigations?: number): T[] {
137137
const navigations = this.storage.get(page);
138138
if (!navigations) {
139139
return [];
140140
}
141-
return navigations[0];
141+
142+
const data: T[] = [];
143+
for (let index = includePreviousNavigations ?? 0; index >= 0; index--) {
144+
console.log('Loop index', index);
145+
data.push(...navigations[index]);
146+
}
147+
return data;
142148
}
143149

144150
getIdForResource(resource: WithSymbolId<T>): number {
@@ -168,6 +174,7 @@ export class NetworkCollector extends PageCollector<HTTPRequest> {
168174
super(browser, collect => {
169175
return {
170176
request: req => {
177+
console.log('Collected');
171178
collect(req);
172179
},
173180
} as ListenerMap;
@@ -190,7 +197,7 @@ export class NetworkCollector extends PageCollector<HTTPRequest> {
190197
// Keep all requests since the last navigation request including that
191198
// navigation request itself.
192199
// Keep the reference
193-
if (lastRequestIdx) {
200+
if (lastRequestIdx !== -1) {
194201
const fromCurrentNavigation = requests.splice(lastRequestIdx);
195202
navigations.unshift(fromCurrentNavigation);
196203
} else {

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface Response {
5050
value: boolean,
5151
options?: PaginationOptions & {
5252
resourceTypes?: string[];
53+
includePreviousNavigations?: number;
5354
},
5455
): void;
5556
setIncludeConsoleData(

src/tools/network.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,22 @@ export const listNetworkRequests = defineTool({
6363
.describe(
6464
'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.',
6565
),
66+
includePreviousNavigations: z
67+
.number()
68+
.int()
69+
.positive()
70+
.max(2)
71+
.optional()
72+
.describe(
73+
'Number of previous navigations to include. Current navigation is always included.',
74+
),
6675
},
6776
handler: async (request, response) => {
6877
response.setIncludeNetworkRequests(true, {
6978
pageSize: request.params.pageSize,
7079
pageIdx: request.params.pageIdx,
7180
resourceTypes: request.params.resourceTypes,
81+
includePreviousNavigations: request.params.includePreviousNavigations,
7282
});
7383
},
7484
});

tests/PageCollector.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,40 @@ describe('NetworkCollector', () => {
250250
assert.equal(collector.getData(page)[0], navRequest);
251251
assert.equal(collector.getData(page)[1], request2);
252252
});
253+
254+
it('correctly picks up after multiple back to back navigations', async () => {
255+
const browser = getMockBrowser();
256+
const page = (await browser.pages())[0];
257+
const mainFrame = page.mainFrame();
258+
const navRequest = getMockRequest({
259+
navigationRequest: true,
260+
frame: page.mainFrame(),
261+
});
262+
const navRequest2 = getMockRequest({
263+
navigationRequest: true,
264+
frame: page.mainFrame(),
265+
});
266+
const request = getMockRequest();
267+
268+
const collector = new NetworkCollector(browser);
269+
await collector.init();
270+
page.emit('request', navRequest);
271+
assert.equal(collector.getData(page)[0], navRequest);
272+
273+
page.emit('framenavigated', mainFrame);
274+
assert.equal(collector.getData(page).length, 1);
275+
assert.equal(collector.getData(page)[0], navRequest);
276+
277+
page.emit('request', navRequest2);
278+
assert.equal(collector.getData(page).length, 2);
279+
assert.equal(collector.getData(page)[0], navRequest);
280+
assert.equal(collector.getData(page)[1], navRequest2);
281+
282+
page.emit('framenavigated', mainFrame);
283+
assert.equal(collector.getData(page).length, 1);
284+
assert.equal(collector.getData(page)[0], navRequest2);
285+
286+
page.emit('request', request);
287+
assert.equal(collector.getData(page).length, 2);
288+
});
253289
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
exports[`network > network_list_requests > list requests form current navigations only 1`] = `
2+
# list_request response
3+
## Network requests
4+
Showing 1-1 of 1 (Page 1 of 1).
5+
reqid=3 GET data:text/html,<div>Hello 3</div> [success - 200]
6+
`;
7+
8+
exports[`network > network_list_requests > list requests from previous navigations 1`] = `
9+
# list_request response
10+
## Network requests
11+
Showing 1-2 of 2 (Page 1 of 1).
12+
reqid=2 GET data:text/html,<div>Hello 2</div> [success - 200]
13+
reqid=3 GET data:text/html,<div>Hello 3</div> [success - 200]
14+
`;

tests/tools/network.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ describe('network', () => {
2121
assert.strictEqual(response.networkRequestsPageIdx, undefined);
2222
});
2323
});
24+
25+
it('list requests form current navigations only', async t => {
26+
await withBrowser(async (response, context) => {
27+
const page = await context.getSelectedPage();
28+
await page.goto('data:text/html,<div>Hello 1</div>');
29+
await page.goto('data:text/html,<div>Hello 2</div>');
30+
await page.goto('data:text/html,<div>Hello 3</div>');
31+
await listNetworkRequests.handler(
32+
{
33+
params: {},
34+
},
35+
response,
36+
context,
37+
);
38+
const responseData = await response.handle('list_request', context);
39+
t.assert.snapshot?.(responseData[0].text);
40+
});
41+
});
42+
43+
it.only('list requests from previous navigations', async t => {
44+
await withBrowser(async (response, context) => {
45+
const page = await context.getSelectedPage();
46+
await page.goto('data:text/html,<div>Hello 1</div>');
47+
await page.goto('data:text/html,<div>Hello 2</div>');
48+
console.log('Last navigtation');
49+
await page.goto('data:text/html,<div>Hello 3</div>');
50+
await listNetworkRequests.handler(
51+
{
52+
params: {
53+
includePreviousNavigations: 1,
54+
},
55+
},
56+
response,
57+
context,
58+
);
59+
const responseData = await response.handle('list_request', context);
60+
t.assert.snapshot?.(responseData[0].text);
61+
});
62+
});
2463
});
2564
describe('network_get_request', () => {
2665
it('attaches request', async () => {
@@ -32,6 +71,7 @@ describe('network', () => {
3271
response,
3372
context,
3473
);
74+
3575
assert.equal(response.attachedNetworkRequestId, 1);
3676
});
3777
});

0 commit comments

Comments
 (0)