siyuan-note/siyuan · error
tools/list returned an empty response
Error message
tools/list returned an empty response
What it means
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.
Source
Thrown at kernel/mcp/client/mcp.go:356
setMCPRuntimeStateForContext(ctx, server.ID, "connected", registered, "", "")
logging.LogInfof("mcp: server [%s] connected, %d tools registered", server.Name, registered)
return connection
}
func listAllMCPTools(ctx context.Context,
listPage func(context.Context, *mcp.ListToolsParams) (*mcp.ListToolsResult, error)) ([]*mcp.Tool, error) {
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)View on GitHub (pinned to 251596fc0d)
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.
Defensive patterns
Strategy: try-catch
Validate before calling
// Before listing, do a lightweight handshake/initialize to confirm the server conforms:
if _, err := client.Initialize(ctx, ...); err != nil {
return fmt.Errorf("mcp server initialize failed: %w", err)
} Try / catch
tools, err := listAllTools(ctx, client.ListTools)
if err != nil {
if strings.Contains(err.Error(), "empty response") {
// server returned nil result; flag the server as non-conformant
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- tools/list repeated cursor %q
- Failed to update agent session permission
- missing query param [u]
- decode [u] failed: %s
- parse [u] failed: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/c64d58078e81b922.
Report an issue: GitHub.