swarm_sdk 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Lazy-loading wrapper for MCP tools
6
+ #
7
+ # Creates minimal tool stub without calling tools/list.
8
+ # Schema is fetched on-demand when LLM needs it.
9
+ #
10
+ # ## Boot Optimization
11
+ #
12
+ # When MCP server tools are pre-specified in configuration:
13
+ # - Boot time: Create stubs instantly (no RPC)
14
+ # - First LLM request: Fetch schema lazily (~100ms one-time cost)
15
+ # - Subsequent requests: Use cached schema (instant)
16
+ #
17
+ # ## Thread Safety
18
+ #
19
+ # Schema loading is protected by Async::Semaphore with double-check pattern
20
+ # to ensure only one fiber fetches the schema even under concurrent access.
21
+ #
22
+ # @example Creating a stub
23
+ # coordinator = RubyLLM::MCP::Coordinator.new(client)
24
+ # stub = McpToolStub.new(
25
+ # coordinator: coordinator,
26
+ # name: "search_code",
27
+ # description: "Search code in repository"
28
+ # )
29
+ #
30
+ # @example Schema is fetched lazily
31
+ # stub.params_schema # First access triggers tools/list RPC
32
+ # stub.params_schema # Cached, instant
33
+ class McpToolStub < Base
34
+ removable true # MCP tools can be controlled by skills
35
+
36
+ attr_reader :name, :client
37
+
38
+ # Create a new MCP tool stub
39
+ #
40
+ # @param client [RubyLLM::MCP::Client] MCP client instance
41
+ # @param name [String] Tool name
42
+ # @param description [String, nil] Tool description (optional, fetched if nil)
43
+ # @param schema [Hash, nil] Tool input schema (optional, fetched if nil)
44
+ #
45
+ # @example Minimal stub (lazy description + schema)
46
+ # McpToolStub.new(client: client, name: "search")
47
+ #
48
+ # @example With description (lazy schema only)
49
+ # McpToolStub.new(
50
+ # client: client,
51
+ # name: "search",
52
+ # description: "Search the codebase"
53
+ # )
54
+ #
55
+ # @example Fully specified (no lazy loading)
56
+ # McpToolStub.new(
57
+ # client: client,
58
+ # name: "search",
59
+ # description: "Search the codebase",
60
+ # schema: { type: "object", properties: {...} }
61
+ # )
62
+ def initialize(client:, name:, description: nil, schema: nil)
63
+ super()
64
+ @client = client
65
+ @name = name
66
+ @mcp_name = name
67
+ @description = description || "MCP tool: #{name}"
68
+ @input_schema = schema
69
+ @schema_loaded = !schema.nil?
70
+ @schema_mutex = Async::Semaphore.new(1) # Thread-safe schema loading
71
+ end
72
+
73
+ # Get tool description
74
+ #
75
+ # @return [String]
76
+ attr_reader :description
77
+
78
+ # Get parameter schema (lazy-loaded on first access)
79
+ #
80
+ # This method is called by RubyLLM when building tool schemas for LLM requests.
81
+ # On first access, it triggers a tools/list RPC to fetch the schema.
82
+ #
83
+ # @return [Hash, nil] JSON Schema for tool parameters
84
+ def params_schema
85
+ ensure_schema_loaded!
86
+ @input_schema
87
+ end
88
+
89
+ # Execute the MCP tool
90
+ #
91
+ # Calls the MCP server's tools/call endpoint with the provided parameters.
92
+ # Schema is NOT required for execution - the server validates parameters.
93
+ #
94
+ # @param params [Hash] Tool parameters
95
+ # @return [String, Hash] Tool result content or error hash
96
+ def execute(**params)
97
+ # Use client.call_tool (client has internal coordinator)
98
+ result = @client.call_tool(
99
+ name: @mcp_name,
100
+ arguments: params,
101
+ )
102
+
103
+ # client.call_tool returns the result content directly
104
+ result
105
+ end
106
+
107
+ private
108
+
109
+ # Lazy-load schema on first access (when LLM needs it)
110
+ #
111
+ # Thread-safe via semaphore with double-check pattern.
112
+ # Multiple concurrent fibers will only trigger one fetch.
113
+ #
114
+ # @return [void]
115
+ def ensure_schema_loaded!
116
+ return if @schema_loaded
117
+
118
+ @schema_mutex.acquire do
119
+ return if @schema_loaded # Double-check after acquiring lock
120
+
121
+ # Fetch tool info from client (calls tools/list if not cached)
122
+ tool_info = @client.tool_info(@mcp_name)
123
+
124
+ if tool_info
125
+ @description = tool_info["description"] || @description
126
+ @input_schema = tool_info["inputSchema"]
127
+ else
128
+ # Tool doesn't exist on server - schema remains nil
129
+ RubyLLM.logger.warn("SwarmSDK: MCP tool '#{@mcp_name}' not found on server during schema fetch")
130
+ end
131
+
132
+ @schema_loaded = true
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -8,7 +8,7 @@ module SwarmSDK
8
8
  # Each edit sees the result of all previous edits, allowing for
9
9
  # coordinated multi-step transformations.
10
10
  # Enforces read-before-edit rule.
11
- class MultiEdit < RubyLLM::Tool
11
+ class MultiEdit < Base
12
12
  include PathResolver
13
13
 
14
14
  # Factory pattern: declare what parameters this tool needs for instantiation
@@ -7,7 +7,7 @@ module SwarmSDK
7
7
  # Supports reading entire files or specific line ranges with line numbers.
8
8
  # Provides system reminders to guide proper usage.
9
9
  # Tracks reads per agent for enforcing read-before-write/edit rules.
10
- class Read < RubyLLM::Tool
10
+ class Read < Base
11
11
  include PathResolver
12
12
 
13
13
  # NOTE: Line length and limit now accessed via SwarmSDK.config
@@ -7,7 +7,7 @@ module SwarmSDK
7
7
  #
8
8
  # Shows all entries in the shared scratchpad with their metadata.
9
9
  # All agents in the swarm share the same scratchpad.
10
- class ScratchpadList < RubyLLM::Tool
10
+ class ScratchpadList < Base
11
11
  define_method(:name) { "ScratchpadList" }
12
12
 
13
13
  description <<~DESC
@@ -7,7 +7,7 @@ module SwarmSDK
7
7
  #
8
8
  # Retrieves content stored by any agent using scratchpad_write.
9
9
  # All agents in the swarm share the same scratchpad.
10
- class ScratchpadRead < RubyLLM::Tool
10
+ class ScratchpadRead < Base
11
11
  define_method(:name) { "ScratchpadRead" }
12
12
 
13
13
  description <<~DESC
@@ -8,7 +8,7 @@ module SwarmSDK
8
8
  # Stores content in volatile, shared storage for temporary communication.
9
9
  # All agents in the swarm share the same scratchpad.
10
10
  # Data is lost when the process ends (not persisted).
11
- class ScratchpadWrite < RubyLLM::Tool
11
+ class ScratchpadWrite < Base
12
12
  define_method(:name) { "ScratchpadWrite" }
13
13
 
14
14
  description <<~DESC
@@ -11,7 +11,9 @@ module SwarmSDK
11
11
  # This is inspired by research showing that explicitly articulating reasoning steps
12
12
  # (chain-of-thought prompting) leads to significantly better outcomes, especially
13
13
  # for complex tasks requiring multi-step reasoning or arithmetic.
14
- class Think < RubyLLM::Tool
14
+ class Think < Base
15
+ removable false # Think is always available
16
+
15
17
  def name
16
18
  "Think"
17
19
  end
@@ -6,7 +6,9 @@ module SwarmSDK
6
6
  #
7
7
  # This tool helps agents track progress on complex multi-step tasks.
8
8
  # Each agent maintains its own independent todo list.
9
- class TodoWrite < RubyLLM::Tool
9
+ class TodoWrite < Base
10
+ removable false # TodoWrite is always available
11
+
10
12
  # Factory pattern: declare what parameters this tool needs for instantiation
11
13
  class << self
12
14
  def creation_requirements
@@ -6,7 +6,7 @@ module SwarmSDK
6
6
  #
7
7
  # Fetches content from URLs, converts HTML to markdown, and processes it
8
8
  # using an AI model to extract information based on a provided prompt.
9
- class WebFetch < RubyLLM::Tool
9
+ class WebFetch < Base
10
10
  def initialize
11
11
  super()
12
12
  @cache = {}
@@ -7,7 +7,7 @@ module SwarmSDK
7
7
  # Creates new files or overwrites existing files.
8
8
  # Enforces read-before-write rule for existing files.
9
9
  # Includes validation and usage guidelines via system reminders.
10
- class Write < RubyLLM::Tool
10
+ class Write < Base
11
11
  include PathResolver
12
12
 
13
13
  # Factory pattern: declare what parameters this tool needs for instantiation
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.6.2"
4
+ VERSION = "2.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarm_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.2
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
@@ -139,6 +139,7 @@ files:
139
139
  - lib/swarm_sdk/agent/definition.rb
140
140
  - lib/swarm_sdk/agent/llm_instrumentation_middleware.rb
141
141
  - lib/swarm_sdk/agent/system_prompt_builder.rb
142
+ - lib/swarm_sdk/agent/tool_registry.rb
142
143
  - lib/swarm_sdk/agent_registry.rb
143
144
  - lib/swarm_sdk/builders/base_builder.rb
144
145
  - lib/swarm_sdk/claude_code_agent_adapter.rb
@@ -205,6 +206,7 @@ files:
205
206
  - lib/swarm_sdk/swarm/tool_configurator.rb
206
207
  - lib/swarm_sdk/swarm_loader.rb
207
208
  - lib/swarm_sdk/swarm_registry.rb
209
+ - lib/swarm_sdk/tools/base.rb
208
210
  - lib/swarm_sdk/tools/bash.rb
209
211
  - lib/swarm_sdk/tools/clock.rb
210
212
  - lib/swarm_sdk/tools/delegate.rb
@@ -219,6 +221,7 @@ files:
219
221
  - lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb
220
222
  - lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb
221
223
  - lib/swarm_sdk/tools/image_formats/tiff_builder.rb
224
+ - lib/swarm_sdk/tools/mcp_tool_stub.rb
222
225
  - lib/swarm_sdk/tools/multi_edit.rb
223
226
  - lib/swarm_sdk/tools/path_resolver.rb
224
227
  - lib/swarm_sdk/tools/read.rb