Skip to content

Commit b0959c8

Browse files
feat: expose previous navigations to the MCP server
1 parent 2ffe56a commit b0959c8

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
@@ -122,9 +122,9 @@ export class McpContext implements Context {
122122
return context;
123123
}
124124

125-
getNetworkRequests(): HTTPRequest[] {
125+
getNetworkRequests(includePreviousNavigations?: number): HTTPRequest[] {
126126
const page = this.getSelectedPage();
127-
return this.#networkCollector.getData(page);
127+
return this.#networkCollector.getData(page, includePreviousNavigations);
128128
}
129129

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

src/McpResponse.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class McpResponse implements Response {
4848
include: boolean;
4949
pagination?: PaginationOptions;
5050
resourceTypes?: ResourceType[];
51+
includePreviousNavigations?: number;
5152
};
5253
#consoleDataOptions?: {
5354
include: boolean;
@@ -68,6 +69,7 @@ export class McpResponse implements Response {
6869
value: boolean,
6970
options?: PaginationOptions & {
7071
resourceTypes?: ResourceType[];
72+
includePreviousNavigations?: number;
7173
},
7274
): void {
7375
if (!value) {
@@ -85,6 +87,7 @@ export class McpResponse implements Response {
8587
}
8688
: undefined,
8789
resourceTypes: options?.resourceTypes,
90+
includePreviousNavigations: options?.includePreviousNavigations,
8891
};
8992
}
9093

@@ -285,7 +288,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
285288
response.push(...this.#getIncludeNetworkRequestsData(context));
286289

287290
if (this.#networkRequestsOptions?.include) {
288-
let requests = context.getNetworkRequests();
291+
let requests = context.getNetworkRequests(
292+
this.#networkRequestsOptions?.includePreviousNavigations,
293+
);
289294

290295
// Apply resource type filtering if specified
291296
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
@@ -48,6 +48,7 @@ export interface Response {
4848
value: boolean,
4949
options?: PaginationOptions & {
5050
resourceTypes?: string[];
51+
includePreviousNavigations?: number;
5152
},
5253
): void;
5354
setIncludeConsoleData(

src/tools/network.ts

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

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)