{"record":{"id":"4101a31d49e0222f","repo":"siyuan-note/siyuan","slug":"tools-list-repeated-cursor-q","errorCode":null,"errorMessage":"tools/list repeated cursor %q","messagePattern":"tools/list repeated cursor %q","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/mcp.go","lineNumber":363,"sourceCode":"\tvar (\n\t\tallTools []*mcp.Tool\n\t\tparams   *mcp.ListToolsParams\n\t)\n\tseenCursors := map[string]struct{}{}\n\tfor page := 0; page < maxMCPToolListPages; page++ {\n\t\tresult, err := listPage(ctx, params)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif result == nil {\n\t\t\treturn nil, fmt.Errorf(\"tools/list returned an empty response\")\n\t\t}\n\t\tallTools = append(allTools, result.Tools...)\n\t\tif result.NextCursor == \"\" {\n\t\t\treturn allTools, nil\n\t\t}\n\t\tif _, exists := seenCursors[result.NextCursor]; exists {\n\t\t\treturn nil, fmt.Errorf(\"tools/list repeated cursor %q\", result.NextCursor)\n\t\t}\n\t\tseenCursors[result.NextCursor] = struct{}{}\n\t\tparams = &mcp.ListToolsParams{Cursor: result.NextCursor}\n\t}\n\treturn nil, fmt.Errorf(\"tools/list exceeded %d pages\", maxMCPToolListPages)\n}\n\nfunc sanitizedServerNameCollision(server conf.MCPServer) bool {\n\tmcpMu.Lock()\n\tdefer mcpMu.Unlock()\n\tsanitizedName := sanitize(server.Name)\n\tfor _, configured := range mcpServers {\n\t\tif configured.ID != server.ID && sanitize(configured.Name) == sanitizedName {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/mcp.go#L345-L381","documentation":"Returned by the MCP `tools/list` paginator when the same `NextCursor` value appears twice across pages. The helper keeps a `seenCursors` set precisely to detect servers that return a cursor pointing back to a page already fetched, which would otherwise loop forever; a repeat is treated as a protocol violation.","triggerScenarios":"An MCP server returns a `NextCursor` equal to one it already returned, e.g. always echoing the same cursor, or cycling through a fixed set without termination. The detection triggers on the second occurrence of any cursor.","commonSituations":"A buggy or non-conformant MCP server; a pagination implementation that forgets to clear the cursor on the last page; a mocked/stub server used in testing that returns a static cursor.","solutions":["Fix the server so each `NextCursor` is unique and the final page returns an empty `NextCursor` to terminate pagination.","Update the MCP server library/version if the bug is known upstream.","If testing, ensure your stub advances cursors and eventually signals completion."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"tools, err := listAllTools(ctx, client.ListTools)\nif err != nil && strings.Contains(err.Error(), \"repeated cursor\") {\n    // server has a pagination bug; disable or report it\n    return err\n}","preventionTips":["Prefer well-maintained MCP server libraries with correct pagination.","When building a server, ensure each cursor is unique and the last page sends an empty cursor.","Log the repeated cursor value to pinpoint the server bug."],"tags":["mcp","protocol","loop-detection","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}