From 9854d3e92b15eb23b04ce8ee41bcd7f3024c3205 Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 25 Aug 2026 08:43:57 +0200 Subject: [PATCH] feat(search): expose page to match the API The Google provider now takes page instead of start. CLI --page, MCP, and buildUrl follow that contract. Co-authored-by: Cursor --- README.md | 5 +++-- packages/core/bin/help.js | 4 +++- packages/core/test/cli.mjs | 3 ++- packages/mcp/src/schemas.js | 1 + packages/mcp/src/tools/search.js | 2 +- packages/search/docs/README.md | 8 +++++++- packages/search/src/index.d.ts | 1 + packages/search/src/index.js | 5 +++-- packages/search/test/build-url.js | 14 +++++++++++++- 9 files changed, 34 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c6ad31c..4e5fb87 100644 --- a/README.md +++ b/README.md @@ -216,10 +216,11 @@ const autocomplete = await microlink.search('how to fine tune', { type: 'autocom console.log(autocomplete.results.map(r => r.value)) // → ['how to fine tune llm', ...] ``` -`location` (ISO 3166-1 country code) localizes ranking and language; `period` (`hour`/`day`/`week`/`month`/`year`) constrains freshness; `limit` caps results per page: +`location` (ISO 3166-1 country code) localizes ranking and language; `period` (`hour`/`day`/`week`/`month`/`year`) constrains freshness; `limit` caps results per page; `page` selects which page to fetch (1 default): ```mjs await microlink.search('recetas de pasta', { location: 'es', limit: 10 }) +await microlink.search('node.js frameworks', { page: 2 }) ``` Results compose in depth: every result with a `url` exposes lazy `.html()` and `.markdown()` for fetching the full page content only when needed — the source-expansion pattern for RAG: @@ -243,7 +244,7 @@ const markdown = await page.markdown() // the SERP as Markdown const html = await page.html() // the SERP as HTML ``` -Pages chain with `.next()`: +Pages chain with `.next()`, or jump ahead with `{ page: 2 }`: ```mjs let page = await microlink.search('node.js frameworks') diff --git a/packages/core/bin/help.js b/packages/core/bin/help.js index 7c25e94..fc278b5 100644 --- a/packages/core/bin/help.js +++ b/packages/core/bin/help.js @@ -242,6 +242,7 @@ const PRODUCTS = { 'news, images, videos, places, maps, shopping, scholar, patents, autocomplete' ], ['--limit', 'Maximum number of results'], + ['--page', 'Results page (1 default)'], ['--html', 'Fetch HTML for the results page and each result'], ['--markdown', 'Fetch Markdown for the results page and each result'], ['--location', 'Country or locale (e.g. es)'], @@ -261,7 +262,8 @@ const PRODUCTS = { [ 'search "the matrix" --markdown', 'include Markdown for the SERP and each result' - ] + ], + ['search "the matrix" --page 2', 'second page of results'] ] }, function: { diff --git a/packages/core/test/cli.mjs b/packages/core/test/cli.mjs index a415e8e..2d884ae 100644 --- a/packages/core/test/cli.mjs +++ b/packages/core/test/cli.mjs @@ -48,12 +48,13 @@ test('prints command help for product --help', async t => { t.false(stdout.includes('Products')) }) -test('search help documents html, markdown, and limit flags', async t => { +test('search help documents html, markdown, limit, and page flags', async t => { const { stdout } = await $('node', [bin, 'search', '--help']) t.true(stdout.includes('search ')) t.true(stdout.includes('--html')) t.true(stdout.includes('--markdown')) t.true(stdout.includes('--limit')) + t.true(stdout.includes('--page')) }) test('prints command help for --help before the product', async t => { diff --git a/packages/mcp/src/schemas.js b/packages/mcp/src/schemas.js index 06d749f..cc06b11 100644 --- a/packages/mcp/src/schemas.js +++ b/packages/mcp/src/schemas.js @@ -403,6 +403,7 @@ export const searchInputSchema = z ]) .optional(), limit: z.coerce.number().int().positive().optional(), + page: z.coerce.number().int().positive().optional(), location: z.string().min(1).optional(), period: z.enum(['hour', 'day', 'week', 'month', 'year']).optional() }) diff --git a/packages/mcp/src/tools/search.js b/packages/mcp/src/tools/search.js index 00ea79a..bf8f278 100644 --- a/packages/mcp/src/tools/search.js +++ b/packages/mcp/src/tools/search.js @@ -8,7 +8,7 @@ export function search (server) { [ 'Search Google and get structured results via Microlink (requires an API key).', 'Returns `results` (title, url, description) plus `knowledgeGraph`, `peopleAlsoAsk`, and `relatedSearches` when available.', - 'Use `type` for a vertical ("search" default, "news", "images", "videos", "places", "maps", "shopping", "scholar", "patents", "autocomplete"), and `limit` / `location` / `period` to refine.', + 'Use `type` for a vertical ("search" default, "news", "images", "videos", "places", "maps", "shopping", "scholar", "patents", "autocomplete"), and `limit` / `page` / `location` / `period` to refine.', 'Google search operators (`site:`, `filetype:`, quotes, ...) work as-is. Mirrors the `microlink.search(query)` library method.' ].join(' '), searchInputSchema, diff --git a/packages/search/docs/README.md b/packages/search/docs/README.md index 921702b..bf727bc 100644 --- a/packages/search/docs/README.md +++ b/packages/search/docs/README.md @@ -136,7 +136,13 @@ for (const result of results) { ### Pagination -Pages chain naturally: +Jump to a page with `page` (1 default), matching the API query param: + +```js +const page2 = await google('node.js frameworks', { page: 2 }) +``` + +Pages also chain with `.next()`: ```js const page1 = await google('node.js frameworks') diff --git a/packages/search/src/index.d.ts b/packages/search/src/index.d.ts index 721cdc7..6835b16 100644 --- a/packages/search/src/index.d.ts +++ b/packages/search/src/index.d.ts @@ -318,6 +318,7 @@ export declare function buildUrl( location?: string type?: Type period?: Period + page?: number } ): URL diff --git a/packages/search/src/index.js b/packages/search/src/index.js index 5a80bd4..0f93f48 100644 --- a/packages/search/src/index.js +++ b/packages/search/src/index.js @@ -9,10 +9,11 @@ const buildPath = (query, limit, location) => .filter(v => v !== undefined) .join('/') -const buildUrl = (query, { limit, location, type, period } = {}) => { +const buildUrl = (query, { limit, location, type, period, page } = {}) => { const url = new URL(`https://${DOMAIN}/${buildPath(query, limit, location)}`) if (type) url.searchParams.set('type', type) if (period) url.searchParams.set('period', period) + if (page > 1) url.searchParams.set('page', String(page)) return url } @@ -71,7 +72,7 @@ const createGoogleClient = ctxOpts => { query, { limit, location, type, period, html, markdown, page = 1, ...opts } = {} ) => { - const url = buildUrl(query, { limit, location, type, period }) + const url = buildUrl(query, { limit, location, type, period, page }) const result = await fetchPage(url, { ...ctxOpts, ...opts }, page, query) if (html) { await resolve(result, 'html') diff --git a/packages/search/test/build-url.js b/packages/search/test/build-url.js index ba1a231..790a253 100644 --- a/packages/search/test/build-url.js +++ b/packages/search/test/build-url.js @@ -36,14 +36,26 @@ test('omits falsy params', t => { t.is(url.searchParams.get('period'), null) }) +test('sets page param when greater than 1', t => { + const url = buildUrl('q', { page: 2 }) + t.is(url.searchParams.get('page'), '2') +}) + +test('omits page on the first page', t => { + t.is(buildUrl('q').searchParams.get('page'), null) + t.is(buildUrl('q', { page: 1 }).searchParams.get('page'), null) +}) + test('combines all options', t => { const url = buildUrl('q', { limit: 5, location: 'fr', type: 'images', - period: 'last_month' + period: 'last_month', + page: 3 }) t.is(url.pathname, '/q/5/fr') t.is(url.searchParams.get('type'), 'images') t.is(url.searchParams.get('period'), 'last_month') + t.is(url.searchParams.get('page'), '3') })