Class: Legate::Mcp::ToolWrapper
- Defined in:
- lib/legate/mcp/tool_wrapper.rb
Overview
Base class for dynamically created Legate::Tool wrappers around external MCP tools. Instances of anonymous subclasses generated by ‘from_mcp_schema` are used.
Class Attribute Summary collapse
-
.mcp_client ⇒ Object
References stored on the dynamically created subclass.
-
.mcp_input_schema ⇒ Object
References stored on the dynamically created subclass.
-
.mcp_tool_name ⇒ Object
References stored on the dynamically created subclass.
Attributes inherited from Tool
#description, #name, #parameters
Class Method Summary collapse
-
.from_mcp_schema(mcp_schema, mcp_client, tool_registry) ⇒ Class
Creates a new anonymous Legate::Tool subclass that wraps an external MCP tool.
Instance Method Summary collapse
-
#perform_execution(params, _context) ⇒ Hash
Executes the wrapped MCP tool via the stored MCP client.
Methods inherited from Tool
define_metadata, #execute, inherited, #initialize, #validate_and_coerce_params, #validate_params
Methods included from Tool::MetadataDsl
Constructor Details
This class inherits a constructor from Legate::Tool
Class Attribute Details
.mcp_client ⇒ Object
References stored on the dynamically created subclass
20 21 22 |
# File 'lib/legate/mcp/tool_wrapper.rb', line 20 def mcp_client @mcp_client end |
.mcp_input_schema ⇒ Object
References stored on the dynamically created subclass
20 21 22 |
# File 'lib/legate/mcp/tool_wrapper.rb', line 20 def mcp_input_schema @mcp_input_schema end |
.mcp_tool_name ⇒ Object
References stored on the dynamically created subclass
20 21 22 |
# File 'lib/legate/mcp/tool_wrapper.rb', line 20 def mcp_tool_name @mcp_tool_name end |
Class Method Details
.from_mcp_schema(mcp_schema, mcp_client, tool_registry) ⇒ Class
Creates a new anonymous Legate::Tool subclass that wraps an external MCP tool. Registers the new tool class with the provided Legate::ToolRegistry instance.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/legate/mcp/tool_wrapper.rb', line 30 def self.from_mcp_schema(mcp_schema, mcp_client, tool_registry) # Validate inputs including the registry unless mcp_schema.is_a?(Hash) && mcp_schema[:name] && mcp_client.is_a?(Legate::Mcp::Client) && tool_registry.is_a?(Legate::ToolRegistry) Mcp.logger.error('Invalid input for ToolWrapper.from_mcp_schema: Schema, Client, or Registry invalid.') return nil end mcp_name = mcp_schema[:name] mcp_description = mcp_schema[:description] || "MCP Tool: #{mcp_name}" mcp_input_schema = mcp_schema[:inputSchema] || {} mcp_properties = mcp_input_schema[:properties] || {} mcp_required = mcp_input_schema[:required] || [] legate_params = Util::SchemaConverter.json_to_legate(mcp_properties, mcp_required) # Create anonymous class wrapper_class = Class.new(ToolWrapper) do # --- CAPTURE local variables for use in method definitions --- captured_mcp_name_sym = mcp_name.to_sym captured_mcp_description = mcp_description captured_legate_params = legate_params # Store references needed for execution on the class itself self.mcp_client = mcp_client self.mcp_tool_name = mcp_name # Keep original string name for execution self.mcp_input_schema = mcp_input_schema # --- Define the tool_metadata method explicitly on this anonymous class --- define_singleton_method(:tool_metadata) do { name: captured_mcp_name_sym, description: captured_mcp_description, parameters: captured_legate_params } end # --- ALSO explicitly define the individual readers for robustness --- # (These might be called by other parts of Legate or ToolRegistry indirectly) define_singleton_method(:tool_name) { captured_mcp_name_sym } define_singleton_method(:description) { captured_mcp_description } define_singleton_method(:parameters_definition) { captured_legate_params } # --- Keep the original define_metadata call as well for global registration --- # Although redundant for metadata retrieval via tool_metadata, it handles global registration. ( name: captured_mcp_name_sym, description: captured_mcp_description, parameters: captured_legate_params ) end Mcp.logger.info("Created Legate Tool wrapper for MCP tool: '#{mcp_name}'") # Register with the PROVIDED registry instance begin tool_registry.register(mcp_name.to_sym, wrapper_class) Mcp.logger.debug("Registered wrapper for MCP tool '#{mcp_name}' with provided ToolRegistry.") rescue Legate::ToolRegistry::ToolExistsError => e Mcp.logger.warn("MCP Tool '#{mcp_name}' conflicts with an existing tool in provided registry: #{e.}") rescue StandardError => e Mcp.logger.error("Failed to register wrapper for MCP tool '#{mcp_name}' with provided registry: #{e.}") return nil end wrapper_class end |
Instance Method Details
#perform_execution(params, _context) ⇒ Hash
Executes the wrapped MCP tool via the stored MCP client.
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 130 131 132 133 134 135 |
# File 'lib/legate/mcp/tool_wrapper.rb', line 104 def perform_execution(params, _context) self.class.mcp_client || begin Mcp.logger.error("MCP Client not configured for tool wrapper: #{self.class.mcp_tool_name}") return { status: :error, error_message: 'Internal configuration error: MCP Client missing.' } end mcp_tool_name_str = self.class.mcp_tool_name mcp_client_instance = self.class.mcp_client Mcp.logger.info("Executing wrapped MCP tool '#{mcp_tool_name_str}' with params: #{params.inspect}") # TODO: V1.1 - Translate Legate params back to JSON structure based on mcp_input_schema? # For V1, assume flat hash structure matches between Legate and MCP tool for basic types. mcp_args = params.transform_keys(&:to_s) # Convert symbol keys back to strings for JSON begin result = mcp_client_instance.call_tool(mcp_tool_name_str, mcp_args) Mcp.logger.info("MCP tool '#{mcp_tool_name_str}' executed successfully.") { status: :success, result: result } rescue Legate::Mcp::RemoteToolError => e Mcp.logger.error("MCP tool '#{mcp_tool_name_str}' returned an error: #{e}") { status: :error, error_message: "MCP Tool Error: #{e.}", error_details: { code: e.code, data: e.data } } rescue Legate::Mcp::ConnectionError, Legate::Mcp::ProtocolError => e Mcp.logger.error("MCP communication error during '#{mcp_tool_name_str}' execution: #{e.}") { status: :error, error_message: "MCP Communication Error: #{e.}" } rescue StandardError => e Mcp.logger.error("Unexpected error during MCP tool '#{mcp_tool_name_str}' execution: #{e.class} - #{e.}") Mcp.logger.error(e.backtrace.join("\n")) { status: :error, error_message: "Internal Error: #{e.}" } end end |