siyuan-note/siyuan · error
capability handler unavailable
Error message
capability handler unavailable: %s
What it means
After argument validation, validateCapabilityCall requires the registration to have an executable handler: for non-browser capabilities, registration.Tool must exist and expose ContextHandler or Handler. Otherwise the capability is declared but nothing can run it, so execution is refused.
Solutions
- Reconnect the MCP server or reload the plugin so the tool regains its handler
- Ensure tool registration populates Handler or ContextHandler before exposure
- Skip exposing tools with nil handlers when building the round's capability list
- Fix the registration path that admitted a handler-less tool
Example fix
// before: exposing every discovered tool
for _, t := range discovered { register(t) }
// after: only expose tools with an executable handler
for _, t := range discovered {
if t.Handler != nil || t.ContextHandler != nil {
register(t)
}
} Defensive patterns
Strategy: validation
Validate before calling
if !reg.isBrowser() && (reg.Tool == nil || (reg.Tool.ContextHandler == nil && reg.Tool.Handler == nil)) {
return executedToolResult{Text: "capability handler unavailable: " + reg.ID, IsError: true}
} Type guard
func executable(r *capabilityRegistration) bool {
return r.isBrowser() || (r.Tool != nil && (r.Tool.ContextHandler != nil || r.Tool.Handler != nil))
} Prevention
- Never expose tools without Handler or ContextHandler
- Recheck handler presence after MCP reconnects
- Fail fast at registration time, not execution time
When it happens
Trigger: A capabilityRegistration carries a nil Tool, or a Tool whose Handler and ContextHandler are both nil (e.g. an MCP tool whose backend disconnected, or a tool registered as metadata-only) while isBrowser() is false.
Common situations: MCP server connection dropped leaving registered-but-unexecutable tool stubs, plugin tools failing to register their handler function, refactoring that splits registration from handler wiring.
Related errors
- capability is disabled or no longer available
- capability validator unavailable
- AI editor model returned nil stream
- capability was not exposed in this model round
- invalid capability arguments
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ff5f057428a4ae12.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/agent/tools.go:74
func validateCapabilityCall(ctx context.Context, registration *capabilityRegistration, args map[string]any) error {
if registration == nil {
return fmt.Errorf("capability was not exposed in this model round")
}
if !capabilityStillExecutable(registration, args) {
return fmt.Errorf("capability is disabled or no longer available: %s", registration.ID)
}
if ctx.Err() != nil {
return fmt.Errorf("capability execution was cancelled before it started")
}
if registration.Validator == nil {
return fmt.Errorf("capability validator unavailable: %s", registration.ID)
}
if err := registration.Validator.ValidateInputContext(ctx, args); err != nil {
return fmt.Errorf("invalid capability arguments: %w", err)
}
if !registration.isBrowser() &&
(registration.Tool == nil || registration.Tool.ContextHandler == nil && registration.Tool.Handler == nil) {
return fmt.Errorf("capability handler unavailable: %s", registration.ID)
}
return nil
}
// executeTool 执行单次工具调用。
func executeTool(ctx context.Context, tc openai.ToolCall, sessionID string) executedToolResult {
tool, validator := tools.LookupToolWithValidator(tc.Function.Name)
if tool == nil {
return executedToolResult{Text: "unknown tool: " + tc.Function.Name, IsError: true}
}
return executeCapability(ctx, tc, sessionID, &capabilityRegistration{
ID: tools.CapabilityIDForTool(tool),
ModelName: tool.Name,
Source: tool.Source,
Runtime: tool.Runtime,
Tool: tool,
Validator: validator,
})View on GitHub (pinned to 9f775e8a12)