{"record":{"id":"0286137736b86f45","repo":"tinyhumansai/openhuman","slug":"mcp-official-deep-page-walk-refused-page-target","errorCode":null,"errorMessage":"MCP official deep-page walk refused: page={target_page} > MAX_CURSOR_WALK_PAGES={MAX_CURSOR_WALK_PAGES}","messagePattern":"MCP official deep-page walk refused: page=(.+?) > MAX_CURSOR_WALK_PAGES=(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/openhuman/mcp/registry/registries/mcp_official.rs","lineNumber":302,"sourceCode":"/// `None` if the cursor chain ran out before reaching `target_page`.\n///\n/// Bails after [`MAX_CURSOR_WALK_PAGES`] iterations to keep a single user\n/// request from fanning into hundreds of upstream calls.\nasync fn walk_cursor_for_page(\n    config: &Config,\n    q: &str,\n    limit: u32,\n    target_page: u32,\n) -> Result<Option<String>> {\n    if target_page <= 1 {\n        return Ok(None);\n    }\n    if target_page > MAX_CURSOR_WALK_PAGES {\n        tracing::warn!(\n            \"[mcp-official] walk refused has_query={} target_page={target_page} max={MAX_CURSOR_WALK_PAGES}\",\n            !q.is_empty()\n        );\n        anyhow::bail!(\n            \"MCP official deep-page walk refused: page={target_page} > MAX_CURSOR_WALK_PAGES={MAX_CURSOR_WALK_PAGES}\"\n        );\n    }\n\n    tracing::debug!(\n        \"[mcp-official] walk start has_query={} q_len={} target_page={target_page} limit={limit}\",\n        !q.is_empty(),\n        q.len()\n    );\n\n    let mut cursor: Option<String> = None;\n    let mut net_fetches = 0u32;\n    let mut cache_fetches = 0u32;\n    // We need the cursor that produces `target_page`, which is the cursor\n    // returned by the response for `target_page - 1`.\n    for page in 1..target_page {\n        let cache_key = format!(\"mcp_official:search:{q}:{page}:{limit}\");\n","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/mcp/registry/registries/mcp_official.rs#L284-L320","documentation":"The registry's cursor walk (fetching page after page to reach a deep `target_page` for offset-style pagination emulation) refuses targets beyond `MAX_CURSOR_WALK_PAGES` (50). To show page N of cursor-paginated results, the client must walk cursors 2..N sequentially; beyond 50 that costs 50+ network fetches for one view, so it bails with this guard (a `[mcp-official] walk refused` warn is logged).","triggerScenarios":"A user or API consumer requesting `page > 50` of official-registry search results; UIs that compute page numbers from a total-count/limit estimate and land beyond the cap; saved deep-link pagination state.","commonSituations":"Long result sets with a jump-to-page control; UIs exposing numeric page input without a bound; scripts paginating the whole catalog by incrementing page numbers.","solutions":["Cap user-facing page navigation at 50, or narrow the search query so results fit in fewer pages.","Replace jump-to-page with cursor-based next/prev navigation (reuse the returned cursor instead of computing page numbers).","If you truly need the whole catalog, walk cursors forward yourself at a sane rate rather than jumping to a deep page.","Persist the current cursor, not the page number, so restoring state never requests a deep page."],"exampleFix":"// before\nlet page = user_requested_page; // can be 500\nlet page = page.clamp(1, 50); // runtime still refuses 500\n\n// after — clamp at the UI boundary AND drive by cursor\nconst MAX_PAGES = 50;\nconst page = Math.min(Math.max(1, user_requested_page), MAX_PAGES);\n// or: navigate with nextCursor returned by the previous response","handlingStrategy":"validation","validationCode":"const MAX_CURSOR_WALK_PAGES: u32 = 50;\nlet target = requested_page.min(MAX_CURSOR_WALK_PAGES).max(1);\n// refuse earlier with a clear message instead of letting the walk bail deep in the client","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Bound page inputs in the UI at 50 and steer users to refine the query instead.","Persist cursors, not page numbers, so restored views never request deep pages.","For full-catalog needs, iterate cursors forward incrementally rather than jumping."],"tags":["mcp","registry","pagination","cursor","limit"],"backgroundTag":"pagination-limit-exceeded","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}