Class: Rubino::MCP::MCPToolWrapper

Inherits:
Tools::Base show all
Defined in:
lib/rubino/mcp/mcp_tool_wrapper.rb

Overview

Wraps an MCP tool (from ruby_llm-mcp) into the Rubino::Tools::Base interface. This allows MCP tools to be used seamlessly alongside built-in tools.

Constant Summary collapse

MAX_NAME_LENGTH =

Cap on the (prefixed) tool name length. A misbehaving MCP server can advertise an absurdly long tool name (e.g. 20k chars), which would be registered uncapped, blow up the tools table, and 400 at the provider.

64

Instance Attribute Summary collapse

Attributes inherited from Tools::Base

#cancel_token, #read_tracker, #stream_chunk, #stream_kind

Instance Method Summary collapse

Methods inherited from Tools::Base

#cancellation_requested?, #config_key, #emit_chunk, #risky?, workspace_root, workspace_roots

Constructor Details

#initialize(mcp_tool, server_name:) ⇒ MCPToolWrapper

Returns a new instance of MCPToolWrapper.



15
16
17
18
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 15

def initialize(mcp_tool, server_name:)
  @mcp_tool = mcp_tool
  @server_name = server_name
end

Instance Attribute Details

#mcp_toolObject (readonly)

Returns the value of attribute mcp_tool.



8
9
10
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 8

def mcp_tool
  @mcp_tool
end

#server_nameObject (readonly)

Returns the value of attribute server_name.



8
9
10
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 8

def server_name
  @server_name
end

Instance Method Details

#bare_nameObject

The original, UNPREFIXED tool name the server advertised (#name returns the collision-safe <server>_<tool> registry/model-facing name).



63
64
65
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 63

def bare_name
  @mcp_tool.name
end

#call(arguments) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 74

def call(arguments)
  result = @mcp_tool.execute(**symbolize_keys(arguments))
  # ruby_llm-mcp reports tool failures by RETURNING `{ error: "…" }`
  # instead of raising. Map both failure paths onto the registry's
  # "Error: …" convention (Tools::Result#errorish?) so an errored MCP
  # call renders ✗ like any built-in tool, not "✓ done" (#172).
  error = result[:error] || result["error"] if result.is_a?(Hash)
  return "Error: MCP tool #{@server_name}/#{@mcp_tool.name}: #{error}" if error

  result.to_s
rescue StandardError => e
  "Error: MCP tool #{@server_name}/#{@mcp_tool.name}: #{e.message}"
end

#descriptionObject



27
28
29
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 27

def description
  @mcp_tool.description
end

#display_nameObject

The display label for the live tool card and approval card: "<bare_tool> (mcp:<server>)" — e.g. echo (mcp:chaos). The model-facing #name (chaos_echo) is unchanged; this is display-only.



70
71
72
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 70

def display_name
  "#{bare_name} (mcp:#{@server_name})"
end

#input_schemaObject



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 31

def input_schema
  # The server-advertised JSON schema lives in RubyLLM::MCP::Tool#params_schema.
  # The inherited RubyLLM::Tool#parameters DSL accessor is ALWAYS empty for
  # MCP tools — forwarding it sent every tool to the model with `parameters:
  # {}`, so the model had to guess argument names and every call failed
  # server-side validation with -32602 (#170).
  schema = @mcp_tool.params_schema if @mcp_tool.respond_to?(:params_schema)
  # Coerce anything that isn't a Hash (nil, or a truthy non-Hash like a
  # string) to a valid empty object schema. A server advertising a
  # non-Hash `inputSchema` would otherwise poison the whole wire tool
  # list and 400 every subsequent model call (S1-MCP-1).
  schema.is_a?(Hash) ? schema : { type: "object", properties: {} }
end

#mcp?Boolean

True: this tool's code runs on an external MCP server. The display layer reads this (NOT the name shape) to mark the call/approval card.

Returns:

  • (Boolean)


52
53
54
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 52

def mcp?
  true
end

#mcp_serverObject

The MCP server this tool is provided by, used in the display marker.



57
58
59
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 57

def mcp_server
  @server_name
end

#nameObject



20
21
22
23
24
25
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 20

def name
  # Prefix with server name to avoid collisions. Cap the length so a
  # hostile/buggy server can't register a giant name that breaks the
  # `tools` table or 400s the provider (S1-MCP-2).
  "#{@server_name}_#{@mcp_tool.name}"[0, MAX_NAME_LENGTH]
end

#risk_levelObject



45
46
47
48
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 45

def risk_level
  # MCP tools are external, default to medium risk
  :medium
end

#to_tool_definitionObject

Override to provide the raw MCP tool definition for LLM



89
90
91
92
93
94
95
# File 'lib/rubino/mcp/mcp_tool_wrapper.rb', line 89

def to_tool_definition
  {
    name: name,
    description: description,
    parameters: input_schema
  }
end