{"record":{"id":"bf2c68a7bee28084","repo":"siyuan-note/siyuan","slug":"tools-list-exceeded-d-pages","errorCode":null,"errorMessage":"tools/list exceeded %d pages","messagePattern":"tools/list exceeded (.+?) pages","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/mcp.go","lineNumber":368,"sourceCode":"\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}\n\nfunc mcpToolName(server conf.MCPServer, toolName string, collision bool) string {\n\tname := \"mcp_\" + sanitize(server.Name) + \"_\" + sanitize(toolName)\n\tif !collision && len(name) <= maxMCPToolNameLen {\n\t\treturn name","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/mcp.go#L350-L386","documentation":"Returned by the MCP `tools/list` paginator when the loop completes `maxMCPToolListPages` (defined as 1000) iterations without the server ever returning an empty `NextCursor`. This is a hard ceiling to prevent unbounded pagination; hitting it means the server keeps advertising more pages beyond a sane limit.","triggerScenarios":"A server that always returns a non-empty `NextCursor` (without repeating it, which would trip the repeated-cursor guard) and never signals the end of the tool list, forcing the client to stop after 1000 pages.","commonSituations":"A misbehaving MCP server generating unbounded cursors; an extremely large tool catalog that legitimately exceeds 1000 pages (unlikely — 1000 pages is far beyond any real catalog); a server bug where the terminal empty cursor is never emitted.","solutions":["Patch the server to terminate pagination with an empty `NextCursor` once all tools are listed.","Confirm the server is not in an error state that prevents it from finishing the listing.","If you genuinely need a higher ceiling, raise `maxMCPToolListPages` in `kernel/mcp/client/mcp.go` after auditing memory and latency implications."],"exampleFix":"// before (server side)\nreturn { tools, nextCursor: cursor + 1 } // never ends\n// after\nif noMoreTools {\n    return { tools, nextCursor: \"\" } // terminate\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"tools, err := listAllTools(ctx, client.ListTools)\nif err != nil && strings.Contains(err.Error(), \"exceeded\") {\n    // server never terminates pagination; cap or disable it\n    return err\n}","preventionTips":["Ensure servers terminate tool listing with an empty NextCursor.","Investigate any server that reports thousands of tool pages.","Adjust maxMCPToolListPages only after a deliberate trade-off analysis."],"tags":["mcp","protocol","limits","loop-detection"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}