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

  1. Reconnect the MCP server or reload the plugin so the tool regains its handler
  2. Ensure tool registration populates Handler or ContextHandler before exposure
  3. Skip exposing tools with nil handlers when building the round's capability list
  4. 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

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


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)