{"record":{"id":"c64d58078e81b922","repo":"siyuan-note/siyuan","slug":"tools-list-returned-an-empty-response","errorCode":null,"errorMessage":"tools/list returned an empty response","messagePattern":"tools/list returned an empty response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/mcp.go","lineNumber":356,"sourceCode":"\tsetMCPRuntimeStateForContext(ctx, server.ID, \"connected\", registered, \"\", \"\")\n\tlogging.LogInfof(\"mcp: server [%s] connected, %d tools registered\", server.Name, registered)\n\treturn connection\n}\n\nfunc listAllMCPTools(ctx context.Context,\n\tlistPage func(context.Context, *mcp.ListToolsParams) (*mcp.ListToolsResult, error)) ([]*mcp.Tool, error) {\n\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)","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/client/mcp.go#L338-L374","documentation":"listAllMCPTools returns this error when an MCP server's tools/list pagination call yields a nil result despite no transport error. The MCP client treats a nil result as a protocol violation — a conforming server must return a tools/list result object (possibly with zero tools). Fired inside the bounded pagination loop before appending tools.","triggerScenarios":"A connected MCP server responds to tools/list (any page, including the first) with an empty/nil result — e.g. a non-conforming server implementation, a proxy stripping the result, or JSON-RPC success with null result.","commonSituations":"Third-party MCP servers that return null instead of {tools: []}, middleware/API gateways mangling responses, version mismatch where the peer speaks an older MCP dialect.","solutions":["Check the MCP server implementation: ensure tools/list returns a result object (empty tools array is valid) rather than null","Capture the raw JSON-RPC response for the failing server and compare against the MCP spec","Retry/upgrade the MCP server; if it is third-party, report the non-conforming tools/list response"],"exampleFix":"// server side (non-conforming)\nreturn nil, nil\n// after\nreturn &mcp.ListToolsResult{Tools: []mcp.Tool{}}, nil","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"tools, err := listAllMCPTools(ctx, client)\nif err != nil {\n\tif strings.Contains(err.Error(), \"tools/list returned an empty response\") {\n\t\t// skip the server or mark it unhealthy; do not retry blindly (page bound already applies)\n\t}\n\treturn err\n}","preventionTips":["Validate MCP server conformance before registration (tools/list must return a result object)","Test third-party servers with a null-result probe during onboarding","Keep server and client MCP protocol versions aligned"],"tags":["mcp","protocol","pagination"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}