Class: SwarmSDK::Tools::McpToolStub

Inherits:
Base
  • Object
show all
Defined in:
lib/swarm_sdk/tools/mcp_tool_stub.rb

Overview

Lazy-loading wrapper for MCP tools

Creates minimal tool stub without calling tools/list. Schema is fetched on-demand when LLM needs it.

Boot Optimization

When MCP server tools are pre-specified in configuration:

  • Boot time: Create stubs instantly (no RPC)
  • First LLM request: Fetch schema lazily (~100ms one-time cost)
  • Subsequent requests: Use cached schema (instant)

Thread Safety

Schema loading is protected by Async::Semaphore with double-check pattern to ensure only one fiber fetches the schema even under concurrent access.

Examples:

Creating a stub

coordinator = RubyLLM::MCP::Coordinator.new(client)
stub = McpToolStub.new(
  coordinator: coordinator,
  name: "search_code",
  description: "Search code in repository"
)

Schema is fetched lazily

stub.params_schema  # First access triggers tools/list RPC
stub.params_schema  # Cached, instant

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

removable, removable?, #removable?

Constructor Details

#initialize(client:, name:, server_name: nil, description: nil, schema: nil) ⇒ McpToolStub

Create a new MCP tool stub

Examples:

Minimal stub (lazy description + schema)

McpToolStub.new(client: client, name: "search", server_name: "codebase")

With description (lazy schema only)

McpToolStub.new(
  client: client,
  name: "search",
  server_name: "codebase",
  description: "Search the codebase"
)

Fully specified (no lazy loading)

McpToolStub.new(
  client: client,
  name: "search",
  server_name: "codebase",
  description: "Search the codebase",
  schema: { type: "object", properties: {...} }
)


65
66
67
68
69
70
71
72
73
74
75
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 65

def initialize(client:, name:, server_name: nil, description: nil, schema: nil)
  super()
  @client = client
  @name = name
  @mcp_name = name
  @server_name = server_name || "unknown"
  @description = description || "MCP tool: #{name}"
  @input_schema = schema
  @schema_loaded = !schema.nil?
  @schema_mutex = Async::Semaphore.new(1) # Thread-safe schema loading
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



36
37
38
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 36

def client
  @client
end

#descriptionString (readonly)

Get tool description



80
81
82
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 80

def description
  @description
end

#nameObject (readonly)

Returns the value of attribute name.



36
37
38
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 36

def name
  @name
end

#server_nameObject (readonly)

Returns the value of attribute server_name.



36
37
38
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 36

def server_name
  @server_name
end

Instance Method Details

#execute(**params) ⇒ String, Hash

Execute the MCP tool

Calls the MCP server's tools/call endpoint with the provided parameters. Schema is NOT required for execution - the server validates parameters.

Raises:



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 103

def execute(**params)
  # Use client.call_tool (client has internal coordinator)
  result = @client.call_tool(
    name: @mcp_name,
    arguments: params,
  )

  # client.call_tool returns the result content directly
  result
rescue RubyLLM::MCP::Errors::TimeoutError => e
  raise MCPTimeoutError, format_mcp_error(
    "MCP request timed out",
    original_message: e.message,
    request_id: e.request_id,
  )
rescue RubyLLM::MCP::Errors::TransportError => e
  raise MCPTransportError, format_mcp_error(
    "MCP transport error",
    original_message: e.message,
    code: e.code,
  )
rescue RubyLLM::MCP::Errors::BaseError => e
  raise MCPError, format_mcp_error(
    "MCP error",
    original_message: e.message,
  )
end

#params_schemaHash?

Get parameter schema (lazy-loaded on first access)

This method is called by RubyLLM when building tool schemas for LLM requests. On first access, it triggers a tools/list RPC to fetch the schema.



88
89
90
91
# File 'lib/swarm_sdk/tools/mcp_tool_stub.rb', line 88

def params_schema
  ensure_schema_loaded!
  @input_schema
end