class LittleGhost::MCP::Toolset
Loads remote Tool classes through an application-created official MCP::Client. The client factory runs during Tool discovery with the current Tool::Binding. It must return a fresh, unconnected client without starting remote work. After the factory returns, LittleGhost connects the client, shares it among the generated Tool instances, and calls close on its transport, when supported, as the owning run or ToolRegistry closes.
Discovery and Tool calls carry the run’s cancellation and deadline into the SDK. The official SDK executes cancellable requests on worker threads. Calls through the official HTTP transport may overlap. When the official stdio transport is supplied directly, calls are serialized and cancelling one invalidates that run’s session. Custom or decorated transports own their serialization, cancellation-safe invalidation, and cleanup. SDK handlers may run on worker or listener threads, so application callbacks must support concurrent use and must not depend on the calling fiber’s local state. Treat handler requests as untrusted and authorize any local work or data they can reach.
class HelpCenterTools < LittleGhost::MCP::Toolset client do |_binding| transport = MCP::Client::HTTP.new(url: "https://mcp.example/rpc") MCP::Client.new(transport:) end end
Public Class Methods
# File lib/little_ghost/mcp/toolset.rb, line 62 def client(**connect_options, &factory) return client_factory_value if !factory && connect_options.empty? raise ArgumentError, "client requires a factory block" unless factory self.connect_options_value = connect_options.dup.freeze self.client_factory_value = factory end
Declares the factory for a fresh, unconnected official MCP::Client. The factory receives the current Tool::Binding. LittleGhost forwards connect_options to MCP::Client#connect, then calls close on the returned transport when it exposes that method. Calling client without a block or options returns the inherited factory, if one is configured.
Source
# File lib/little_ghost/mcp/toolset.rb, line 95 def map_result(&mapping) return result_mapping_value unless mapping self.result_mapping_value = mapping end
Maps the value produced by LittleGhost’s default result conversion. The block also receives the raw tools/call result: Hash, the official mcp_tool:, the submitted arguments:, and the current binding:. Return any Ruby value or Tool::Result. With no block, returns the inherited mapping, if one is configured.
# File lib/little_ghost/mcp/toolset.rb, line 80 def map_tool(&mapping) return tool_mapping_value unless mapping self.tool_mapping_value = mapping end
Maps each generated Tool class before it is bound to the Agent. The block receives the generated class, the official MCP::Client::Tool as mcp_tool:, and the current binding:. Return the class, a subclass, or nil to omit it. Renaming the class does not change the operation name sent to the server. With no block, returns the inherited mapping, if one is configured.
# File lib/little_ghost/mcp/toolset.rb, line 123 def on_error(&callback) return error_callback_value unless callback self.error_callback_value = callback end
Observes an expected discovery failure caught by optional true. The block receives the translated LittleGhost error and the current binding:. Exceptions raised by the block propagate. With no block, returns the inherited callback, if one is configured.
# File lib/little_ghost/mcp/toolset.rb, line 109 def optional(value = UNSET) return optional_value if value.equal?(UNSET) self.optional_value = !!value end
Makes expected provider and protocol discovery failures produce no tools. Configuration, cancellation, deadline, and callback failures still propagate. With no argument, reports whether discovery is optional.
Source
# File lib/little_ghost/mcp/toolset.rb, line 139 def tools(binding) context = binding.run&.context context&.check! official_client = build_client(binding) session = Session.new(official_client) connect_client(official_client, context:) binding.run&.register(session) Instrumentation.instrument(:mcp_discovery, toolset: toolset_name) do |telemetry| adapter = Adapter.new( client: official_client, session:, name: toolset_name, tool_mapper: wrapped_tool_mapper, result_mapper: wrapped_result_mapper ) discovered = adapter.tools(context:, binding:) session.close if discovered.empty? && !binding.run telemetry[:outcome] = :success telemetry[:tool_count] = discovered.length discovered end rescue CallbackFailure => error session&.close raise error.original rescue ProviderError, ProtocolError, ToolError => error session&.close raise unless optional_value error_callback_value&.call(error, binding:) [] rescue session&.close raise end
Connects the configured client and returns Tool classes for binding. When the binding has a run, the run owns the shared client session. Otherwise, the ToolRegistry that resolves the returned classes closes the session through its generated Tool instances. A caller that bypasses ToolRegistry must instantiate and close a returned class.
Expected discovery failures return an empty Array when optional true. After the factory returns a client, later failures call close when its transport exposes that method; failures then propagate unless they are optional.