Class: LittleGhost::MCP::Toolset

Inherits:
Object
  • Object
show all
Extended by:
Support::ClassAttributes
Defined in:
lib/little_ghost/mcp/toolset.rb

Overview

Connects one MCP server to an Agent as a reusable Tool provider. Each Agent run gets its own connection. map_tool chooses and configures the generated Tool classes; map_result converts results that need application-specific handling.

class HelpCenterTools < LittleGhost::MCP::Toolset
connection url: "https://mcp.example/rpc", timeout: 20
end

class CustomerSupportAgent < LittleGhost::Agent
tools HelpCenterTools
end

Defined Under Namespace

Classes: CallbackFailure

Constant Summary collapse

CONNECTION_OPTIONS =

:nodoc:

%i[url headers timeout signer allow_insecure_http max_response_bytes].freeze
UNSET =

:nodoc:

Object.new.freeze

Class Method Summary collapse

Methods included from Support::ClassAttributes

class_attribute, included

Class Method Details

.connection(value = UNSET, &resolver) ⇒ Object

Declares a static connection Hash or a block called with the current Tool::Binding. The Hash requires url and may include headers, timeout, signer, allow_insecure_http, and max_response_bytes.

:call-seq:

connection() -> Hash, Proc, nil
connection(values) -> Hash
connection { |binding| ... } -> Proc

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
# File 'lib/little_ghost/mcp/toolset.rb', line 48

def connection(value = UNSET, &resolver)
  return connection_value if value.equal?(UNSET) && !resolver
  raise ArgumentError, "provide a connection or a block, not both" unless value.equal?(UNSET) || !resolver

  configured = resolver || value
  unless configured.is_a?(Hash) || configured.respond_to?(:call)
    raise ArgumentError, "connection must be a hash or callable"
  end

  self.connection_value = configured.is_a?(Hash) ? normalize_connection(configured) : configured
end

.map_result(&mapping) ⇒ Object

Maps each immutable MCP::Result. The block receives call: and binding: keywords and returns any Ruby value or Tool::Result.

:call-seq:

map_result() -> Proc, nil
map_result { |result, call:, binding:| ... } -> Proc


80
81
82
83
84
# File 'lib/little_ghost/mcp/toolset.rb', line 80

def map_result(&mapping)
  return result_mapping_value unless mapping

  self.result_mapping_value = mapping
end

.map_tool(&mapping) ⇒ Object

Maps each generated Tool class. The block receives definition: and binding: keywords. Return the configured Tool class, or nil to omit it. Changing Tool#tool_name does not change the operation name sent to the MCP server.

:call-seq:

map_tool() -> Proc, nil
map_tool { |tool_class, definition:, binding:| ... } -> Proc


68
69
70
71
72
# File 'lib/little_ghost/mcp/toolset.rb', line 68

def map_tool(&mapping)
  return tool_mapping_value unless mapping

  self.tool_mapping_value = mapping
end

.on_error(&callback) ⇒ Object

Observes an expected discovery failure caught by optional true. Exceptions raised by this callback propagate.

:call-seq:

on_error() -> Proc, nil
on_error { |error, binding:| ... } -> Proc


105
106
107
108
109
# File 'lib/little_ghost/mcp/toolset.rb', line 105

def on_error(&callback)
  return error_callback_value unless callback

  self.error_callback_value = callback
end

.optional(value = UNSET) ⇒ Object

Makes expected provider and protocol discovery failures produce no tools. Configuration, cancellation, deadline, and callback failures still propagate.

:call-seq:

optional() -> true or false
optional(value) -> true or false


93
94
95
96
97
# File 'lib/little_ghost/mcp/toolset.rb', line 93

def optional(value = UNSET)
  return optional_value if value.equal?(UNSET)

  self.optional_value = !!value
end

.tools(binding) ⇒ Object

Generates Tool classes for an Agent's current binding.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/little_ghost/mcp/toolset.rb', line 112

def tools(binding)
  options = resolved_connection(binding)
  context = binding.run.context if binding.run&.respond_to?(:context)
  mapper = tool_mapping_value
  result_mapper = result_mapping_value
  options = connection_with_wrapped_signer(options)
  Instrumentation.instrument(:mcp_discovery, toolset: toolset_name) do |telemetry|
    client = Client.new(
      transport: HTTPTransport.new(**options),
      name: toolset_name,
      tool_mapper: mapper && lambda do |tool_class, definition:, binding:|
        invoke_application_callback do
          mapper.call(tool_class, definition:, binding:)
        end
      end,
      result_mapper: result_mapper && lambda do |result, call:, binding:|
        result_mapper.call(result, call:, binding:)
      end
    )
    discovered = client.tools(context:, binding:)
    telemetry[:outcome] = :success
    telemetry[:tool_count] = discovered.length
    discovered
  end
rescue CallbackFailure => error
  raise error.original
rescue ProviderError, ProtocolError, ToolError => error
  raise unless optional_value

  error_callback_value&.call(error, binding:)
  []
end