Class: SolidLoop::Mcp::Toolset

Inherits:
Object
  • Object
show all
Includes:
Transport
Defined in:
lib/solid_loop/mcp/toolset.rb

Overview

In-process MCP server: a toolset IS both the server and its transport. deliver dispatches initialize / tools/list / tools/call without HTTP, preserving the JSON boundary (string keys) so local and remote behavior cannot drift. See docs/guides/mcp_transports.md.

class SearchTools < SolidLoop::Mcp::Toolset
server_name "search"

tool "fetch_url",
     description: "Fetch a URL and return a text summary",
     input_schema: { type: "object", properties: {} } do |args, ctx|
  Search::Fetch.call(loop: ctx.loop).to_json
end
end

Defined Under Namespace

Modules: SchemaValidator Classes: PromptDefinition, PublicToolError, ToolDefinition

Constant Summary collapse

OPAQUE_TOOL_ERROR =

Opaque text returned for an unexpected internal exception.

"Internal error executing tool".freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.own_promptsObject



170
171
172
# File 'lib/solid_loop/mcp/toolset.rb', line 170

def own_prompts
  @own_prompts ||= {}
end

.own_toolsObject



161
162
163
# File 'lib/solid_loop/mcp/toolset.rb', line 161

def own_tools
  @own_tools ||= {}
end

.prompt(prompt_name, description:, arguments: [], &block) ⇒ Object

A reusable, parameterized message template served via prompts/get. The block returns a String (wrapped into a single user message) or a ready-made messages array.

prompt "audit_report",
     description: "Skeleton for a period audit",
     arguments: [ { name: "period", description: "e.g. 2026-Q1", required: true } ] do |args, ctx|
"Audit the #{args['period']} period and list anomalies."
end

Raises:

  • (ArgumentError)


144
145
146
147
148
149
150
151
152
153
# File 'lib/solid_loop/mcp/toolset.rb', line 144

def prompt(prompt_name, description:, arguments: [], &block)
  raise ArgumentError, "prompt '#{prompt_name}' requires a block" unless block

  own_prompts[prompt_name.to_s] = PromptDefinition.new(
    name:        prompt_name.to_s,
    description: description,
    arguments:   arguments,
    handler:     block
  )
end

.promptsObject



165
166
167
168
# File 'lib/solid_loop/mcp/toolset.rb', line 165

def prompts
  parent = superclass.respond_to?(:prompts) ? superclass.prompts : {}
  parent.merge(own_prompts)
end

.server_name(value = nil) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/solid_loop/mcp/toolset.rb', line 111

def server_name(value = nil)
  @server_name = value.to_s if value

  klass = self
  while klass.respond_to?(:own_tools)
    explicit = klass.instance_variable_get(:@server_name)
    return explicit if explicit
    klass = klass.superclass
  end

  name.to_s.demodulize.underscore
end

.tool(tool_name, description:, input_schema:, &block) ⇒ Object

Raises:

  • (ArgumentError)


124
125
126
127
128
129
130
131
132
133
# File 'lib/solid_loop/mcp/toolset.rb', line 124

def tool(tool_name, description:, input_schema:, &block)
  raise ArgumentError, "tool '#{tool_name}' requires a block" unless block

  own_tools[tool_name.to_s] = ToolDefinition.new(
    name:         tool_name.to_s,
    description:  description,
    input_schema: input_schema,
    handler:      block
  )
end

.toolsObject

Declared tools, including those inherited from parent toolsets.



156
157
158
159
# File 'lib/solid_loop/mcp/toolset.rb', line 156

def tools
  parent = superclass.respond_to?(:tools) ? superclass.tools : {}
  parent.merge(own_tools)
end

Instance Method Details

#deliver(payload, session_id: nil, context: nil) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/solid_loop/mcp/toolset.rb', line 175

def deliver(payload, session_id: nil, context: nil)
  request = json_boundary(payload)

  body =
    case request["method"]
    when "initialize"   then handle_initialize(request, context) { |sid| session_id = sid }
    when "tools/list"   then success_envelope(request, "tools" => tool_specs)
    when "tools/call"   then handle_tools_call(request, context)
    when "prompts/list" then success_envelope(request, "prompts" => prompt_specs)
    when "prompts/get"  then handle_prompts_get(request, context)
    else error_envelope(request, -32601, "Method not found: #{request['method']}")
    end

  Result.new(
    body:         body,
    session_id:   session_id,
    raw_request:  payload,
    raw_response: body,
    status:       200
  )
end

#on_initialize(_params, _context) ⇒ Object

Toolsets are stateless by default (nil => SolidLoop synthesizes a session id). Override to mint a real session id and receive it back on subsequent calls.



200
201
202
# File 'lib/solid_loop/mcp/toolset.rb', line 200

def on_initialize(_params, _context)
  nil
end