Skip to content

Cursor Pagination

Large binaries can contain tens of thousands of functions, hundreds of thousands of cross-references, and thousands of strings. Returning all of that in a single tool response would overflow the MCP client’s context window and produce unusable output. MCGhidra uses cursor-based pagination to deliver results in controlled pages.

When a paginated tool returns more items than the page size, the response includes a cursor_id. Pass that cursor ID to cursor_next to get the next page. Continue until has_more is false.

# First page: get 100 functions matching a pattern
result = functions_list(page_size=100, grep="crypt|hash")
# Returns:
# {
# result: [...],
# pagination: {
# cursor_id: "a1b2c3d4e5f67890",
# total_count: 12847,
# filtered_count: 847,
# page_size: 100,
# current_page: 1,
# total_pages: 9,
# has_more: true
# }
# }
# Next page
result = cursor_next(cursor_id="a1b2c3d4e5f67890")
# Returns page 2 of 9
# Continue until has_more is false
result = cursor_next(cursor_id="a1b2c3d4e5f67890")
# ...

Each response also includes a _message field with a human-readable summary like “Showing 100 of 847 items (page 2/9). To get the next 100 items, call: cursor_next(cursor_id=‘a1b2c3d4e5f67890’)”. MCP clients use this to decide whether to continue fetching.

The grep parameter filters results on the server before pagination. This is much more efficient than fetching everything and filtering client-side, because only matching items are stored in the cursor and counted toward page totals.

# Only functions with "auth" or "login" in their name/address
functions_list(grep="auth|login", page_size=50)
# Case-sensitive search (grep_ignorecase defaults to true)
data_list_strings(grep="BEGIN CERTIFICATE", grep_ignorecase=false, page_size=50)

The grep pattern is a regular expression. It matches against all string values in each result item — for a function, that means the name, address, and signature fields are all searched.

Patterns are validated before execution to prevent runaway matches:

  • Maximum 500 characters
  • Maximum 15 repetition operators (*, +, ?, {n,m})
  • Nested quantifiers like (a+)+ are rejected

If a pattern fails validation, the tool returns an error with code INVALID_GREP_PATTERN explaining what to fix.

When you need all matching results without paging through cursors, pass return_all=True:

functions_list(grep="crypt", return_all=True)

This bypasses pagination and returns every matching item in a single response. There is a token budget guard (default: 8,000 estimated tokens) that kicks in if the response would be too large. When the guard triggers, the response includes:

  • A sample of the first 3 items
  • The available field names
  • Suggested narrower queries (grep patterns, field projections, or pagination)

Combine return_all with grep and fields to keep the response size down:

# Get all crypto-related function names and addresses (nothing else)
functions_list(grep="crypt|aes|sha", fields=["name", "address"], return_all=True)

The page_size parameter controls how many items each page contains.

ParameterDefaultMaximum
page_size50500

For most MCP client contexts, 50-100 items per page is a good balance between making progress and keeping individual responses readable. Going above 200 is rarely useful unless you are scripting.

Cursors expire after 5 minutes of inactivity (no cursor_next calls). The timer resets each time a cursor is accessed.

When more than 100 cursors exist for a session, the least-recently-used cursor is evicted to make room. In practice, you will rarely hit this limit unless you start many queries without finishing them.

Each MCP client session gets its own set of cursors. You cannot access or interfere with another session’s cursors. Session IDs are derived from the MCP client context — they are not user-controllable.

ToolWhat it does
cursor_list()Show all active cursors for the current session: IDs, page progress, TTL remaining, grep pattern
cursor_delete(cursor_id="...")Delete a specific cursor to free memory
cursor_delete_all()Delete all cursors for the current session

These are useful for cleanup during long analysis sessions or when you want to re-run a query from scratch.

Example: scanning all strings for credentials

Section titled “Example: scanning all strings for credentials”
# Start with a broad credential search
result = data_list_strings(grep="password|secret|key|token|api_key|credential", page_size=100)
# Process first page of results
# ... examine the strings ...
# Get more if there are additional pages
if result has cursor_id:
result = cursor_next(cursor_id="...")

Example: iterating through all functions matching a pattern

Section titled “Example: iterating through all functions matching a pattern”
# First page
result = functions_list(grep="handle_|process_|parse_", page_size=50)
# Loop through pages
while result has cursor_id:
# Decompile interesting functions from this page
for func in result:
if func looks relevant:
functions_decompile(name=func["name"])
# Advance
result = cursor_next(cursor_id="...")
  • Prefer server-side grep over fetching everything. A query like functions_list(grep="ssl") is far cheaper than functions_list(return_all=True) followed by manual filtering.

  • Use fields to reduce response size. If you only need names and addresses, functions_list(fields=["name", "address"], page_size=100) cuts the per-item size significantly.

  • Small page sizes (50-100) keep individual responses from consuming too much context. You can always fetch more pages.

  • If a cursor expires (5-minute TTL), just re-run the original query. The cursor IDs are not reusable — you get a new one each time.

  • For very large binaries (100K+ functions), start with grep-filtered queries rather than listing everything. Even paginated, iterating through 2,000 pages of 50 items each is slow and rarely what you actually need.