Skip to content

Commit aa22258

Browse files
committed
feat: use pageIdx as token
1 parent 522374e commit aa22258

7 files changed

Lines changed: 83 additions & 81 deletions

File tree

docs/tool-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@
264264

265265
**Parameters:**
266266

267+
- **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page.
267268
- **pageSize** (integer) _(optional)_: Maximum number of requests to return. When omitted, returns all requests.
268-
- **pageToken** (string) _(optional)_: Opaque token representing the next page. Use the token returned by a previous call.
269269

270270
---
271271

src/McpResponse.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class McpResponse implements Response {
3636

3737
setIncludeNetworkRequests(
3838
value: boolean,
39-
options?: {pageSize?: number; pageToken?: string | null},
39+
options?: {pageSize?: number; pageIdx?: number},
4040
): void {
4141
this.#includeNetworkRequests = value;
4242
if (!value || !options) {
@@ -46,7 +46,7 @@ export class McpResponse implements Response {
4646

4747
this.#networkRequestsPaginationOptions = {
4848
pageSize: options.pageSize,
49-
pageToken: options.pageToken ?? undefined,
49+
pageIdx: options.pageIdx,
5050
};
5151
}
5252

@@ -72,9 +72,8 @@ export class McpResponse implements Response {
7272
get attachedNetworkRequestUrl(): string | undefined {
7373
return this.#attachedNetworkRequestUrl;
7474
}
75-
get networkRequestsPageToken(): string | undefined {
76-
const token = this.#networkRequestsPaginationOptions?.pageToken;
77-
return token ?? undefined;
75+
get networkRequestsPageIdx(): number | undefined {
76+
return this.#networkRequestsPaginationOptions?.pageIdx;
7877
}
7978

8079
appendResponseLine(value: string): void {
@@ -184,21 +183,22 @@ Call browser_handle_dialog to handle it before continuing.`);
184183
requests,
185184
this.#networkRequestsPaginationOptions,
186185
);
187-
if (paginationResult.invalidToken) {
188-
response.push('Invalid page token provided. Showing first page.');
186+
if (paginationResult.invalidPage) {
187+
response.push('Invalid page number provided. Showing first page.');
189188
}
190189

191-
const {startIndex, endIndex} = paginationResult;
190+
const {startIndex, endIndex, currentPage, totalPages} =
191+
paginationResult;
192192
response.push(
193-
`Showing ${startIndex + 1}-${endIndex} of ${requests.length}.`,
193+
`Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`,
194194
);
195195

196196
if (this.#networkRequestsPaginationOptions) {
197-
if (paginationResult.nextPageToken) {
198-
response.push(`Next: ${paginationResult.nextPageToken}`);
197+
if (paginationResult.hasNextPage) {
198+
response.push(`Next page: ${currentPage + 1}`);
199199
}
200-
if (paginationResult.previousPageToken) {
201-
response.push(`Prev: ${paginationResult.previousPageToken}`);
200+
if (paginationResult.hasPreviousPage) {
201+
response.push(`Previous page: ${currentPage - 1}`);
202202
}
203203
}
204204

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface Response {
4444
setIncludePages(value: boolean): void;
4545
setIncludeNetworkRequests(
4646
value: boolean,
47-
options?: {pageSize?: number; pageToken?: string | null},
47+
options?: {pageSize?: number; pageIdx?: number},
4848
): void;
4949
setIncludeConsoleData(value: boolean): void;
5050
setIncludeSnapshot(value: boolean): void;

src/tools/network.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
*/
66

77
import z from 'zod';
8-
import {defineTool} from './ToolDefinition.js';
9-
import {ToolCategories} from './categories.js';
8+
import { defineTool } from './ToolDefinition.js';
9+
import { ToolCategories } from './categories.js';
1010

1111
export const listNetworkRequests = defineTool({
1212
name: 'list_network_requests',
@@ -24,17 +24,19 @@ export const listNetworkRequests = defineTool({
2424
.describe(
2525
'Maximum number of requests to return. When omitted, returns all requests.',
2626
),
27-
pageToken: z
28-
.string()
27+
pageIdx: z
28+
.number()
29+
.int()
30+
.min(0)
2931
.optional()
3032
.describe(
31-
'Opaque token representing the next page. Use the token returned by a previous call.',
33+
'Page number to return (0-based). When omitted, returns the first page.',
3234
),
3335
},
3436
handler: async (request, response) => {
3537
response.setIncludeNetworkRequests(true, {
3638
pageSize: request.params.pageSize,
37-
pageToken: request.params.pageToken ?? null,
39+
pageIdx: request.params.pageIdx,
3840
});
3941
},
4042
});

src/utils/pagination.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66

77
export type PaginationOptions = {
88
pageSize?: number;
9-
pageToken?: string;
9+
pageIdx?: number;
1010
};
1111

1212
export type PaginationResult<TItem> = {
1313
items: readonly TItem[];
14-
nextPageToken?: string;
15-
previousPageToken?: string;
14+
currentPage: number;
15+
totalPages: number;
16+
hasNextPage: boolean;
17+
hasPreviousPage: boolean;
1618
startIndex: number;
1719
endIndex: number;
18-
invalidToken: boolean;
20+
invalidPage: boolean;
1921
};
2022

2123
const DEFAULT_PAGE_SIZE = 20;
@@ -29,59 +31,57 @@ export function paginate<TItem>(
2931
if (!options || noPaginationOptions(options)) {
3032
return {
3133
items,
32-
nextPageToken: undefined,
33-
previousPageToken: undefined,
34+
currentPage: 0,
35+
totalPages: 1,
36+
hasNextPage: false,
37+
hasPreviousPage: false,
3438
startIndex: 0,
3539
endIndex: total,
36-
invalidToken: false,
40+
invalidPage: false,
3741
};
3842
}
3943

4044
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
41-
const {startIndex, invalidToken} = resolveStartIndex(
42-
options.pageToken,
43-
total,
45+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
46+
const {currentPage, invalidPage} = resolvePageIndex(
47+
options.pageIdx,
48+
totalPages,
4449
);
4550

51+
const startIndex = currentPage * pageSize;
4652
const pageItems = items.slice(startIndex, startIndex + pageSize);
4753
const endIndex = startIndex + pageItems.length;
4854

49-
const nextPageToken = endIndex < total ? String(endIndex) : undefined;
50-
const previousPageToken =
51-
startIndex > 0 ? String(Math.max(startIndex - pageSize, 0)) : undefined;
52-
5355
return {
5456
items: pageItems,
55-
nextPageToken,
56-
previousPageToken,
57+
currentPage,
58+
totalPages,
59+
hasNextPage: currentPage < totalPages - 1,
60+
hasPreviousPage: currentPage > 0,
5761
startIndex,
5862
endIndex,
59-
invalidToken,
63+
invalidPage,
6064
};
6165
}
6266

6367
function noPaginationOptions(options: PaginationOptions): boolean {
64-
return (
65-
options.pageSize === undefined &&
66-
(options.pageToken === undefined || options.pageToken === null)
67-
);
68+
return options.pageSize === undefined && options.pageIdx === undefined;
6869
}
6970

70-
function resolveStartIndex(
71-
pageToken: string | undefined,
72-
total: number,
71+
function resolvePageIndex(
72+
pageIdx: number | undefined,
73+
totalPages: number,
7374
): {
74-
startIndex: number;
75-
invalidToken: boolean;
75+
currentPage: number;
76+
invalidPage: boolean;
7677
} {
77-
if (pageToken === undefined || pageToken === null) {
78-
return {startIndex: 0, invalidToken: false};
78+
if (pageIdx === undefined) {
79+
return {currentPage: 0, invalidPage: false};
7980
}
8081

81-
const parsed = Number.parseInt(pageToken, 10);
82-
if (Number.isNaN(parsed) || parsed < 0 || parsed >= total) {
83-
return {startIndex: 0, invalidToken: true};
82+
if (pageIdx < 0 || pageIdx >= totalPages) {
83+
return {currentPage: 0, invalidPage: true};
8484
}
8585

86-
return {startIndex: parsed, invalidToken: false};
86+
return {currentPage: pageIdx, invalidPage: false};
8787
}

tests/McpResponse.test.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
* Copyright 2025 Google LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6-
import {describe, it} from 'node:test';
6+
import { describe, it } from 'node:test';
77
import assert from 'assert';
88

9-
import {getMockRequest, html, withBrowser} from './utils.js';
9+
import { getMockRequest, html, withBrowser } from './utils.js';
1010

1111
describe('McpResponse', () => {
1212
it('list pages', async () => {
@@ -120,7 +120,7 @@ Navigation timeout set to 100000 ms`,
120120
});
121121
it('adds image when image is attached', async () => {
122122
await withBrowser(async (response, context) => {
123-
response.attachImage({data: 'imageBase64', mimeType: 'image/png'});
123+
response.attachImage({ data: 'imageBase64', mimeType: 'image/png' });
124124
const result = await response.handle('test', context);
125125
assert.strictEqual(result[0].text, `# test response`);
126126
assert.equal(result[1].type, 'image');
@@ -185,7 +185,7 @@ Call browser_handle_dialog to handle it before continuing.`,
185185
result[0].text,
186186
`# test response
187187
## Network requests
188-
Showing 1-1 of 1.
188+
Showing 1-1 of 1 (Page 1 of 1).
189189
http://example.com GET [pending]`,
190190
);
191191
});
@@ -218,7 +218,7 @@ Status: [pending]
218218
### Request Headers
219219
- content-size:10
220220
## Network requests
221-
Showing 1-1 of 1.
221+
Showing 1-1 of 1 (Page 1 of 1).
222222
http://example.com GET [pending]`,
223223
);
224224
});
@@ -267,66 +267,66 @@ Log>`),
267267
describe('McpResponse network pagination', () => {
268268
it('returns all requests when pagination is not provided', async () => {
269269
await withBrowser(async (response, context) => {
270-
const requests = Array.from({length: 5}, () => getMockRequest());
270+
const requests = Array.from({ length: 5 }, () => getMockRequest());
271271
context.getNetworkRequests = () => requests;
272272
response.setIncludeNetworkRequests(true);
273273
const result = await response.handle('test', context);
274274
const text = (result[0].text as string).toString();
275-
assert.ok(text.includes('Showing 1-5 of 5.'));
276-
assert.ok(!text.includes('Next:'));
277-
assert.ok(!text.includes('Prev:'));
275+
assert.ok(text.includes('Showing 1-5 of 5 (Page 1 of 1).'));
276+
assert.ok(!text.includes('Next page:'));
277+
assert.ok(!text.includes('Previous page:'));
278278
});
279279
});
280280

281281
it('returns first page by default', async () => {
282282
await withBrowser(async (response, context) => {
283-
const requests = Array.from({length: 30}, (_, idx) =>
284-
getMockRequest({method: `GET-${idx}`}),
283+
const requests = Array.from({ length: 30 }, (_, idx) =>
284+
getMockRequest({ method: `GET-${idx}` }),
285285
);
286286
context.getNetworkRequests = () => {
287287
return requests;
288288
};
289-
response.setIncludeNetworkRequests(true, {pageSize: 10});
289+
response.setIncludeNetworkRequests(true, { pageSize: 10 });
290290
const result = await response.handle('test', context);
291291
const text = (result[0].text as string).toString();
292-
assert.ok(text.includes('Showing 1-10 of 30.'));
293-
assert.ok(text.includes('Next: 10'));
294-
assert.ok(!text.includes('Prev:'));
292+
assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).'));
293+
assert.ok(text.includes('Next page: 1'));
294+
assert.ok(!text.includes('Previous page:'));
295295
});
296296
});
297297

298-
it('returns subsequent page when token provided', async () => {
298+
it('returns subsequent page when pageIdx provided', async () => {
299299
await withBrowser(async (response, context) => {
300-
const requests = Array.from({length: 25}, (_, idx) =>
301-
getMockRequest({method: `GET-${idx}`}),
300+
const requests = Array.from({ length: 25 }, (_, idx) =>
301+
getMockRequest({ method: `GET-${idx}` }),
302302
);
303303
context.getNetworkRequests = () => requests;
304304
response.setIncludeNetworkRequests(true, {
305305
pageSize: 10,
306-
pageToken: '10',
306+
pageIdx: 1,
307307
});
308308
const result = await response.handle('test', context);
309309
const text = (result[0].text as string).toString();
310-
assert.ok(text.includes('Showing 11-20 of 25.'));
311-
assert.ok(text.includes('Next: 20'));
312-
assert.ok(text.includes('Prev: 0'));
310+
assert.ok(text.includes('Showing 11-20 of 25 (Page 2 of 3).'));
311+
assert.ok(text.includes('Next page: 2'));
312+
assert.ok(text.includes('Previous page: 0'));
313313
});
314314
});
315315

316-
it('handles invalid token by showing first page', async () => {
316+
it('handles invalid page number by showing first page', async () => {
317317
await withBrowser(async (response, context) => {
318-
const requests = Array.from({length: 5}, () => getMockRequest());
318+
const requests = Array.from({ length: 5 }, () => getMockRequest());
319319
context.getNetworkRequests = () => requests;
320320
response.setIncludeNetworkRequests(true, {
321321
pageSize: 2,
322-
pageToken: 'invalid',
322+
pageIdx: 10, // Invalid page number
323323
});
324324
const result = await response.handle('test', context);
325325
const text = (result[0].text as string).toString();
326326
assert.ok(
327-
text.includes('Invalid page token provided. Showing first page.'),
327+
text.includes('Invalid page number provided. Showing first page.'),
328328
);
329-
assert.ok(text.includes('Showing 1-2 of 5.'));
329+
assert.ok(text.includes('Showing 1-2 of 5 (Page 1 of 3).'));
330330
});
331331
});
332332
});

tests/tools/network.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('network', () => {
1818
await withBrowser(async (response, context) => {
1919
await listNetworkRequests.handler({params: {}}, response, context);
2020
assert.ok(response.includeNetworkRequests);
21-
assert.strictEqual(response.networkRequestsPageToken, undefined);
21+
assert.strictEqual(response.networkRequestsPageIdx, undefined);
2222
});
2323
});
2424
});

0 commit comments

Comments
 (0)