fix(search): emit pagination cursor on first truncated page

On the first page (offset==0) ripgrep capped results at exactly max_results
and set Truncated=true, but the cursor-footer guard (len>max_results) was
false, so the response said "(truncated...)" with no cursor — the caller
was told more matches exist but given no way to fetch them. Pagination only
worked from page 2. Emit a cursor on the first page using rg's total match
count for the remaining estimate.

Adds a strict regression test (asserts, not skips); the existing pagination
test was silently skipping its round-trip because page 1 never had a cursor.
This commit is contained in:
2026-06-20 00:44:41 +01:00
parent 3195e353e2
commit 2fb9d3e379
2 changed files with 44 additions and 1 deletions
+11 -1
View File
@@ -127,13 +127,23 @@ func (s *Server) handleFileSearch(ctx context.Context, request mcp.CallToolReque
// Apply in-process max_results cap and compute cursor footer.
var cursorLine string
if maxResults > 0 && len(results.Results) > maxResults {
switch {
case maxResults > 0 && len(results.Results) > maxResults:
// Cursor pages (offset>0) fetch all results, so cap here and emit a cursor.
remaining := len(results.Results) - maxResults
results.Results = results.Results[:maxResults]
results.Truncated = true
nextOffset := offset + maxResults
nextCursor := cursor.Encode(nextOffset, queryHash)
cursorLine = fmt.Sprintf("[cursor: %s, remaining: %d]", nextCursor, remaining)
case offset == 0 && maxResults > 0 && results.Truncated:
// First page: ripgrep capped us at exactly maxResults in parseOutput, but
// more matches exist. Emit a cursor so the caller can page instead of being
// told "truncated" with no way to continue. remaining derives from rg's
// total match count (a submatch count — an upper-bound proxy for rows).
nextCursor := cursor.Encode(maxResults, queryHash)
remaining := max(results.Total-maxResults, 1)
cursorLine = fmt.Sprintf("[cursor: %s, remaining: %d]", nextCursor, remaining)
}
s.logger.Info("search completed",