siyuan-note/siyuan · error

tools/list repeated cursor

Error message

tools/list repeated cursor %q

What it means

listAllMCPTools paginates a remote MCP server's tools/list results, tracking every NextCursor it has already followed. When the server returns a cursor identical to one already seen, pagination would loop forever, so the client aborts with this error. It indicates a buggy or non-conformant MCP server implementation rather than a client configuration problem.

Solutions

  1. Upgrade or fix the MCP server so each page returns a distinct NextCursor and empty string terminates the list
  2. Verify the server actually advances its cursor when the request carries a Cursor parameter (test with a raw tools/list call)
  3. Check for proxies/caches between client and server that replay responses
  4. If a third-party server cannot be fixed, reduce the tool count so it fits one page, or use a corrected server build

Example fix

// before: server always returns NextCursor "abc"
// after: server returns empty NextCursor on the last page
if hasMorePages {
    result.NextCursor = nextCursorToken // unique per page
} else {
    result.NextCursor = ""
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

conn, err := listAllMCPTools(ctx, listPage)
if err != nil {
    if strings.Contains(err.Error(), "repeated cursor") {
        logging.LogWarnf("mcp: server cursor bug: %v", err)
        return // mark server unhealthy, skip tool registration
    }
    return err
}

Prevention

When it happens

Trigger: connectOneServer calls listAllMCPTools; the server returns a tools/list page whose NextCursor equals a previously returned cursor (including returning the first page's cursor again).

Common situations: An MCP server with a broken cursor implementation (always returns the same cursor), a server that ignores the requested cursor parameter, a cached/stale middleware replaying the first page, or a mocked/test server misbehaving.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/4101a31d49e0222f. Report an issue: GitHub.

Appendix: source

Thrown at kernel/mcp/client/mcp.go:363

	var (
		allTools []*mcp.Tool
		params   *mcp.ListToolsParams
	)
	seenCursors := map[string]struct{}{}
	for page := 0; page < maxMCPToolListPages; page++ {
		result, err := listPage(ctx, params)
		if err != nil {
			return nil, err
		}
		if result == nil {
			return nil, fmt.Errorf("tools/list returned an empty response")
		}
		allTools = append(allTools, result.Tools...)
		if result.NextCursor == "" {
			return allTools, nil
		}
		if _, exists := seenCursors[result.NextCursor]; exists {
			return nil, fmt.Errorf("tools/list repeated cursor %q", result.NextCursor)
		}
		seenCursors[result.NextCursor] = struct{}{}
		params = &mcp.ListToolsParams{Cursor: result.NextCursor}
	}
	return nil, fmt.Errorf("tools/list exceeded %d pages", maxMCPToolListPages)
}

func sanitizedServerNameCollision(server conf.MCPServer) bool {
	mcpMu.Lock()
	defer mcpMu.Unlock()
	sanitizedName := sanitize(server.Name)
	for _, configured := range mcpServers {
		if configured.ID != server.ID && sanitize(configured.Name) == sanitizedName {
			return true
		}
	}
	return false
}

View on GitHub (pinned to 9f775e8a12)