{"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":"http","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/mcp.go#L338-L374","documentation":"Returned by the MCP client's `tools/list` pagination helper when a page returns a nil `*mcp.ListToolsResult`. The protocol expects every successful `tools/list` response to carry a result object (even one with an empty `Tools` slice and no `NextCursor`); a nil result violates that contract and is treated as a server bug.","triggerScenarios":"An MCP server (stdio or HTTP/streamable) replies to `tools/list` with no result object — e.g. an empty JSON-RPC success payload, a server that returns `null`, or a transport that decoded the response into a nil pointer.","commonSituations":"Connecting to a third-party or in-development MCP server that does not conform to the result envelope; a proxy stripping the result field; a transport deserialization edge case.","solutions":["Update or patch the MCP server so it always returns a non-nil `ListToolsResult` (use `{\"tools\":[]}` if it has none).","Verify the server speaks the MCP `tools/list` method and the correct JSON-RPC result envelope.","If you control the client integration, log the raw response to identify whether the server or the transport is dropping the result."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before listing, do a lightweight handshake/initialize to confirm the server conforms:\nif _, err := client.Initialize(ctx, ...); err != nil {\n    return fmt.Errorf(\"mcp server initialize failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"tools, err := listAllTools(ctx, client.ListTools)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty response\") {\n        // server returned nil result; flag the server as non-conformant\n    }\n    return err\n}","preventionTips":["Use a conformant MCP server implementation that always returns a result object.","Smoke-test new servers with a quick tools/list before registering them.","Surface this error to the user with the offending server name for remediation."],"tags":["mcp","protocol","network","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}