Module: RailsAiContext::Tools::SafeCall

Defined in:
lib/rails_ai_context/tools/safe_call.rb

Overview

Last-resort rescue for tool execution, prepended to every registered tool's singleton class so it wraps each tool's self.call regardless of when the subclass defines the method.

Without this, an exception inside a tool escapes to the MCP SDK, which converts it into a JSON-RPC -32603 protocol error. Most AI clients swallow protocol errors, so the model never sees what went wrong and cannot recover. An error tool-result (isError: true) lands in the model's context instead, with enough detail to retry or route around the failure.

Instance Method Summary collapse

Instance Method Details

#call(**kwargs) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rails_ai_context/tools/safe_call.rb', line 16

def call(**kwargs)
  # Held as a local, not a thread-local: the wrapper sees both the value
  # it discarded and the response that came back, so the note needs no
  # state outliving the call. It also lands on every way out of a tool,
  # not just text_response, and a tool that calls another tool cannot
  # have its note consumed by the inner one.
  discarded = discarded_detail(kwargs)
  kwargs = normalize_detail(kwargs)
  Thread.current[:rails_ai_context_call_params] = session_params(kwargs)

  # Held across the tool body: a concurrent live reload must not unload
  # constants while this call is reading them.
  #
  # The rescue sits INSIDE the block on purpose. `executor.wrap` reports
  # anything that crosses it to the host app's error_reporter as
  # unhandled, which would page a team over a failure this method turns
  # into an ordinary isError result. Nothing escapes the block, so
  # nothing is reported.
  RailsAiContext::CodeReloader.with_app_code do
    begin
      append_note(super(**kwargs), invalid_detail_note(discarded))
    rescue StandardError => e
      # A failed call must not leak its recorded params into the next
      # call's session entry.
      Thread.current[:rails_ai_context_call_params] = nil
      failure_response(e)
    end
  end
end