{"record":{"id":"722b768b0315803a","repo":"PrefectHQ/fastmcp","slug":"invalid-cursor-cursor","errorCode":null,"errorMessage":"Invalid cursor: {cursor}","messagePattern":"Invalid cursor: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/utilities/pagination.py","lineNumber":47,"sourceCode":"\n    @classmethod\n    def decode(cls, cursor: str) -> CursorState:\n        \"\"\"Decode cursor from an opaque string.\n\n        Raises:\n            ValueError: If the cursor is invalid or malformed.\n        \"\"\"\n        try:\n            data = json.loads(base64.urlsafe_b64decode(cursor.encode()).decode())\n            return cls(offset=data[\"o\"])\n        except (\n            json.JSONDecodeError,\n            KeyError,\n            ValueError,\n            TypeError,\n            binascii.Error,\n        ) as e:\n            raise ValueError(f\"Invalid cursor: {cursor}\") from e\n\n\ndef paginate_sequence(\n    items: Sequence[T],\n    cursor: str | None,\n    page_size: int,\n) -> tuple[list[T], str | None]:\n    \"\"\"Paginate a sequence of items.\n\n    Args:\n        items: The full sequence to paginate.\n        cursor: Optional cursor from a previous request. None for first page.\n        page_size: Maximum number of items per page.\n\n    Returns:\n        Tuple of (page_items, next_cursor). next_cursor is None if no more pages.\n\n    Raises:","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/utilities/pagination.py#L29-L65","documentation":"FastMCP's pagination cursor is an opaque base64-encoded, signed JSON token; `decode` raised this ValueError because the supplied cursor could not be decoded or did not have the expected structure (the underlying JSONDecodeError/KeyError/TypeError/binascii.Error/ValueError is chained). It means the cursor is corrupt, truncated, or was not produced by `encode`/`paginate_sequence`.","triggerScenarios":"Passing a cursor string that was hand-crafted, URL-decoded/re-encoded incorrectly, truncated by a client, generated by an older/different FastMCP version with an incompatible format, or any non-cursor garbage string in the `cursor` argument of `paginate_sequence` or a paginated MCP `list_*` call.","commonSituations":"Clients mangling the opaque token (e.g. treating it as a page number), storing cursors in systems that strip padding or change encoding, upgrading FastMCP so old persisted cursors no longer decode, or users typing cursor values by hand.","solutions":["Discard the bad cursor and restart pagination from the beginning (`cursor=None`) to obtain a fresh valid token.","Ensure the cursor is passed opaquely — no URL decoding/encoding, trimming, or re-serialization between server response and next request.","If cursors were persisted across a FastMCP upgrade, invalidate stored cursors and re-paginate with the current library version."],"exampleFix":"// before\nconst page = await client.listTools(decodeURIComponent(cursor));\n// after (pass opaquely, or restart)\nconst page = cursor ? await client.listTools(cursor) : await client.listTools();","handlingStrategy":"try-catch","validationCode":"def cursor_looks_valid(cursor: str | None) -> bool:\n    if cursor is None:\n        return True\n    import base64, binascii\n    try:\n        base64.urlsafe_b64decode(cursor)\n        return True\n    except (binascii.Error, ValueError):\n        return False","typeGuard":"def is_opaque_cursor(v: object) -> bool:\n    return isinstance(v, str) and len(v) > 0 and not v.isdigit()","tryCatchPattern":"try:\n    result = paginate_sequence(items, cursor, page_size)\nexcept ValueError as e:\n    if \"Invalid cursor\" in str(e):\n        cursor = None  # restart pagination from the beginning\n        result = paginate_sequence(items, cursor, page_size)\n    else:\n        raise","preventionTips":["Treat cursors as fully opaque — never decode, trim, or URL-transform them.","Don't persist cursors across library upgrades; store the original query instead.","Restart pagination from cursor=None on any invalid-cursor error instead of retrying the bad token."],"tags":["pagination","cursor","encoding"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}