swarm_sdk 2.7.5 → 2.7.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6aaba8cabb5957715e88ee02e40e9492fe3546791c806d1ddaff95e967f77aba
4
- data.tar.gz: 752d15e6e69bb1a9941212d1c33622d678dcc0dc734b88b77a845c7fca4e982b
3
+ metadata.gz: b83fd113813e0667b02d12c5ef6b9f4d47c134afa0665e2b6e482a6c9d3dacd7
4
+ data.tar.gz: 8a758e06817af7690c21fc446597b0e1a91b876643d20a4a111a62bc953d3d5e
5
5
  SHA512:
6
- metadata.gz: 36532a008bbb328cddf60bca55d7d8e210b2c0701ca546190164a16067d3878e26c6c4a6d347e9f262669c34289bad7ac0ddd714062a6d4ab9807e0ef5fd9dc5
7
- data.tar.gz: e7e512794d27d5bd378ea1c96e66f18683cbab8c95e3820672bd74174e0fd5963894958a61aa2e7cf40485c908a7a7adf6a7cbda9d7bfdd480ccd0c3156e4d15
6
+ metadata.gz: 5bfcfaf82ca105e6b1e1f04e009c703b641c25af6797ccd72c945a0c8dbbfbe60a0d783fc9e8d43322bbefac95b16123c4e7232c4ac9e1850a770d1120568344
7
+ data.tar.gz: 0aa16f8d6ebe0fd2acb4cb52baba570000c82c4607d015d3a4569d671f9743cb1199edd80953002b8dc0fb673fb464ebfc2d5d0c645f3afb50258c7451281304
@@ -45,17 +45,23 @@ module SwarmSDK
45
45
  Swarm.apply_mcp_logging_configuration
46
46
 
47
47
  mcp_server_configs.each do |server_config|
48
+ tools_config = server_config[:tools]
49
+ mode = tools_config.nil? ? :discovery : :optimized
50
+
51
+ # Emit event before initialization
52
+ emit_mcp_init_start(agent_name, server_config, mode)
53
+
48
54
  client = initialize_mcp_client(server_config)
49
55
 
50
56
  # Store client for cleanup
51
57
  @mcp_clients[agent_name] << client
52
58
 
53
- tools_config = server_config[:tools]
54
-
55
59
  if tools_config.nil?
56
60
  # Discovery mode: Fetch all tools from server (calls tools/list)
57
61
  # client.tools returns RubyLLM::Tool instances (already wrapped by internal Coordinator)
58
62
  all_tools = client.tools
63
+ tool_names = all_tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s }
64
+
59
65
  all_tools.each do |tool|
60
66
  chat.tool_registry.register(
61
67
  tool,
@@ -63,14 +69,20 @@ module SwarmSDK
63
69
  metadata: { server_name: server_config[:name] },
64
70
  )
65
71
  end
72
+
73
+ # Emit completion event for discovery mode
74
+ emit_mcp_init_complete(agent_name, server_config, mode, all_tools.size, tool_names)
66
75
  RubyLLM.logger.debug("SwarmSDK: Discovered and registered #{all_tools.size} tools from MCP server '#{server_config[:name]}'")
67
76
  else
68
77
  # Optimized mode: Create tool stubs without tools/list RPC (Plan 025)
69
78
  # Use client directly (it has internal coordinator)
79
+ tool_names = tools_config.map(&:to_s)
80
+
70
81
  tools_config.each do |tool_name|
71
82
  stub = Tools::McpToolStub.new(
72
83
  client: client,
73
84
  name: tool_name.to_s,
85
+ server_name: server_config[:name],
74
86
  )
75
87
  chat.tool_registry.register(
76
88
  stub,
@@ -78,6 +90,9 @@ module SwarmSDK
78
90
  metadata: { server_name: server_config[:name] },
79
91
  )
80
92
  end
93
+
94
+ # Emit completion event for optimized mode
95
+ emit_mcp_init_complete(agent_name, server_config, mode, tools_config.size, tool_names)
81
96
  RubyLLM.logger.debug("SwarmSDK: Registered #{tools_config.size} tool stubs from MCP server '#{server_config[:name]}' (lazy schema)")
82
97
  end
83
98
  rescue StandardError => e
@@ -187,6 +202,42 @@ module SwarmSDK
187
202
 
188
203
  streamable_config
189
204
  end
205
+
206
+ # Emit MCP server initialization start event
207
+ #
208
+ # @param agent_name [Symbol] Agent name
209
+ # @param server_config [Hash] MCP server configuration
210
+ # @param mode [Symbol] Initialization mode (:discovery or :optimized)
211
+ # @return [void]
212
+ def emit_mcp_init_start(agent_name, server_config, mode)
213
+ LogStream.emit(
214
+ type: "mcp_server_init_start",
215
+ agent: agent_name,
216
+ server_name: server_config[:name],
217
+ transport_type: server_config[:type],
218
+ mode: mode,
219
+ )
220
+ end
221
+
222
+ # Emit MCP server initialization complete event
223
+ #
224
+ # @param agent_name [Symbol] Agent name
225
+ # @param server_config [Hash] MCP server configuration
226
+ # @param mode [Symbol] Initialization mode (:discovery or :optimized)
227
+ # @param tool_count [Integer] Number of tools registered
228
+ # @param tool_names [Array<String>] Names of registered tools
229
+ # @return [void]
230
+ def emit_mcp_init_complete(agent_name, server_config, mode, tool_count, tool_names)
231
+ LogStream.emit(
232
+ type: "mcp_server_init_complete",
233
+ agent: agent_name,
234
+ server_name: server_config[:name],
235
+ transport_type: server_config[:type],
236
+ mode: mode,
237
+ tool_count: tool_count,
238
+ tools: tool_names,
239
+ )
240
+ end
190
241
  end
191
242
  end
192
243
  end
@@ -4,8 +4,10 @@ module SwarmSDK
4
4
  module Tools
5
5
  module ImageExtractors
6
6
  # Extracts images from PDF documents
7
- # Supports JPEG (DCTDecode), FlateDecode, and LZWDecode formats
8
- # Converts non-JPEG images to TIFF format
7
+ # Only extracts JPEG images (DCTDecode format) which are LLM API compatible
8
+ # Non-JPEG images (FlateDecode, LZWDecode) are skipped because they would
9
+ # require TIFF format which is not supported by LLM APIs
10
+ # Supported LLM image formats: ['png', 'jpeg', 'gif', 'webp']
9
11
  class PdfImageExtractor
10
12
  class << self
11
13
  # Extract all images from a PDF document
@@ -65,11 +67,13 @@ module SwarmSDK
65
67
 
66
68
  case filter
67
69
  when :DCTDecode
68
- # JPEG images can be saved directly
70
+ # JPEG images can be saved directly - LLM API compatible
69
71
  save_jpeg(stream, page_number, name, temp_dir)
70
72
  when :FlateDecode, :LZWDecode, nil
71
- # Raw or compressed formats - save as TIFF
72
- save_as_tiff(stream, page_number, name, temp_dir)
73
+ # Skip non-JPEG images to avoid TIFF format (not supported by LLM APIs)
74
+ # LLM APIs only support: ['png', 'jpeg', 'gif', 'webp']
75
+ # These images would require TIFF conversion which causes API errors
76
+ nil
73
77
  end
74
78
  # Unsupported formats return nil
75
79
  rescue StandardError
@@ -33,22 +33,24 @@ module SwarmSDK
33
33
  class McpToolStub < Base
34
34
  removable true # MCP tools can be controlled by skills
35
35
 
36
- attr_reader :name, :client
36
+ attr_reader :name, :client, :server_name
37
37
 
38
38
  # Create a new MCP tool stub
39
39
  #
40
40
  # @param client [RubyLLM::MCP::Client] MCP client instance
41
41
  # @param name [String] Tool name
42
+ # @param server_name [String, nil] MCP server name for error context
42
43
  # @param description [String, nil] Tool description (optional, fetched if nil)
43
44
  # @param schema [Hash, nil] Tool input schema (optional, fetched if nil)
44
45
  #
45
46
  # @example Minimal stub (lazy description + schema)
46
- # McpToolStub.new(client: client, name: "search")
47
+ # McpToolStub.new(client: client, name: "search", server_name: "codebase")
47
48
  #
48
49
  # @example With description (lazy schema only)
49
50
  # McpToolStub.new(
50
51
  # client: client,
51
52
  # name: "search",
53
+ # server_name: "codebase",
52
54
  # description: "Search the codebase"
53
55
  # )
54
56
  #
@@ -56,14 +58,16 @@ module SwarmSDK
56
58
  # McpToolStub.new(
57
59
  # client: client,
58
60
  # name: "search",
61
+ # server_name: "codebase",
59
62
  # description: "Search the codebase",
60
63
  # schema: { type: "object", properties: {...} }
61
64
  # )
62
- def initialize(client:, name:, description: nil, schema: nil)
65
+ def initialize(client:, name:, server_name: nil, description: nil, schema: nil)
63
66
  super()
64
67
  @client = client
65
68
  @name = name
66
69
  @mcp_name = name
70
+ @server_name = server_name || "unknown"
67
71
  @description = description || "MCP tool: #{name}"
68
72
  @input_schema = schema
69
73
  @schema_loaded = !schema.nil?
@@ -93,6 +97,9 @@ module SwarmSDK
93
97
  #
94
98
  # @param params [Hash] Tool parameters
95
99
  # @return [String, Hash] Tool result content or error hash
100
+ # @raise [MCPTimeoutError] When the MCP server times out
101
+ # @raise [MCPTransportError] When there's a transport-level error
102
+ # @raise [MCPError] When any other MCP error occurs
96
103
  def execute(**params)
97
104
  # Use client.call_tool (client has internal coordinator)
98
105
  result = @client.call_tool(
@@ -102,6 +109,23 @@ module SwarmSDK
102
109
 
103
110
  # client.call_tool returns the result content directly
104
111
  result
112
+ rescue RubyLLM::MCP::Errors::TimeoutError => e
113
+ raise MCPTimeoutError, format_mcp_error(
114
+ "MCP request timed out",
115
+ original_message: e.message,
116
+ request_id: e.request_id,
117
+ )
118
+ rescue RubyLLM::MCP::Errors::TransportError => e
119
+ raise MCPTransportError, format_mcp_error(
120
+ "MCP transport error",
121
+ original_message: e.message,
122
+ code: e.code,
123
+ )
124
+ rescue RubyLLM::MCP::Errors::BaseError => e
125
+ raise MCPError, format_mcp_error(
126
+ "MCP error",
127
+ original_message: e.message,
128
+ )
105
129
  end
106
130
 
107
131
  private
@@ -112,6 +136,9 @@ module SwarmSDK
112
136
  # Multiple concurrent fibers will only trigger one fetch.
113
137
  #
114
138
  # @return [void]
139
+ # @raise [MCPTimeoutError] When the MCP server times out during schema fetch
140
+ # @raise [MCPTransportError] When there's a transport-level error
141
+ # @raise [MCPError] When any other MCP error occurs
115
142
  def ensure_schema_loaded!
116
143
  return if @schema_loaded
117
144
 
@@ -131,6 +158,40 @@ module SwarmSDK
131
158
 
132
159
  @schema_loaded = true
133
160
  end
161
+ rescue RubyLLM::MCP::Errors::TimeoutError => e
162
+ raise MCPTimeoutError, format_mcp_error(
163
+ "MCP schema fetch timed out",
164
+ original_message: e.message,
165
+ request_id: e.request_id,
166
+ )
167
+ rescue RubyLLM::MCP::Errors::TransportError => e
168
+ raise MCPTransportError, format_mcp_error(
169
+ "MCP transport error during schema fetch",
170
+ original_message: e.message,
171
+ code: e.code,
172
+ )
173
+ rescue RubyLLM::MCP::Errors::BaseError => e
174
+ raise MCPError, format_mcp_error(
175
+ "MCP error during schema fetch",
176
+ original_message: e.message,
177
+ )
178
+ end
179
+
180
+ # Format MCP error message with contextual information
181
+ #
182
+ # @param prefix [String] Error message prefix
183
+ # @param original_message [String] Original error message from RubyLLM::MCP
184
+ # @param request_id [String, nil] MCP request ID (for timeout errors)
185
+ # @param code [Integer, nil] HTTP status code (for transport errors)
186
+ # @return [String] Formatted error message with full context
187
+ def format_mcp_error(prefix, original_message:, request_id: nil, code: nil)
188
+ parts = [prefix]
189
+ parts << "[server: #{@server_name}]"
190
+ parts << "[tool: #{@mcp_name}]"
191
+ parts << "[request_id: #{request_id}]" if request_id
192
+ parts << "[code: #{code}]" if code
193
+ parts << "- #{original_message}"
194
+ parts.join(" ")
134
195
  end
135
196
  end
136
197
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.7.5"
4
+ VERSION = "2.7.7"
5
5
  end
data/lib/swarm_sdk.rb CHANGED
@@ -71,6 +71,15 @@ module SwarmSDK
71
71
  # Raised when agent turn exceeds turn_timeout
72
72
  class TurnTimeoutError < TimeoutError; end
73
73
 
74
+ # Base class for MCP-related errors (provides context about server/tool)
75
+ class MCPError < Error; end
76
+
77
+ # Raised when MCP request times out
78
+ class MCPTimeoutError < MCPError; end
79
+
80
+ # Raised when MCP transport fails (connection, HTTP errors)
81
+ class MCPTransportError < MCPError; end
82
+
74
83
  class << self
75
84
  # Get the global configuration instance
76
85
  #
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.7.5
4
+ version: 2.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda