Class: Legate::Mcp::Server::LegateToolAdapter
- Inherits:
-
FastMcp::Tool
- Object
- FastMcp::Tool
- Legate::Mcp::Server::LegateToolAdapter
- Defined in:
- lib/legate/mcp/server/legate_tool_adapter.rb
Overview
Base adapter class to expose an Legate::Tool as an MCP tool via fast-mcp. Use the ‘wrap` class method to dynamically create subclasses for specific Legate tools.
Class Attribute Summary collapse
-
.legate_tool_class ⇒ Object
readonly
Provide a reader.
Class Method Summary collapse
-
.wrap(legate_tool_class) ⇒ Class<LegateToolAdapter>
Dynamically creates a new FastMcp::Tool subclass that wraps the given Legate::Tool class.
Instance Method Summary collapse
-
#call(**args) ⇒ Any
The ‘call` method executed by fast-mcp when the tool is invoked.
Class Attribute Details
.legate_tool_class ⇒ Object (readonly)
Provide a reader
18 19 20 |
# File 'lib/legate/mcp/server/legate_tool_adapter.rb', line 18 def legate_tool_class @legate_tool_class end |
Class Method Details
.wrap(legate_tool_class) ⇒ Class<LegateToolAdapter>
Dynamically creates a new FastMcp::Tool subclass that wraps the given Legate::Tool class.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/legate/mcp/server/legate_tool_adapter.rb', line 26 def self.wrap(legate_tool_class) raise ArgumentError, "Provided class #{legate_tool_class} is not a valid Legate::Tool class." unless legate_tool_class.is_a?(Class) && legate_tool_class < Legate::Tool = legate_tool_class. # Check metadata hash and required keys raise ArgumentError, "Legate::Tool #{legate_tool_class} has incomplete metadata (missing name or description)." unless .is_a?(Hash) && [:name] && [:description] mcp_tool_name = [:name].to_s mcp_description = [:description] legate_params = [:parameters] || {} # Convert Legate params to a Dry::Schema proc schema_proc = Legate::Mcp::Util::SchemaConverter.legate_to_dry_schema(legate_params) # Create the anonymous adapter class Class.new(LegateToolAdapter) do @legate_tool_class = legate_tool_class # Use fast-mcp DSL methods inside the class definition block tool_name mcp_tool_name # Use DSL method description mcp_description arguments(&schema_proc) if schema_proc Legate.logger.info("Created fast-mcp adapter for Legate tool: #{legate_tool_class} as '#{mcp_tool_name}'") end end |
Instance Method Details
#call(**args) ⇒ Any
The ‘call` method executed by fast-mcp when the tool is invoked. Instantiates the wrapped Legate tool, executes it with a dummy context, and translates the result/error.
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/legate/mcp/server/legate_tool_adapter.rb', line 61 def call(**args) # Access the class instance variable via the reader tool_class = self.class.legate_tool_class raise NotImplementedError, 'LegateToolAdapter cannot be used directly, use .wrap first.' unless tool_class legate_instance = tool_class.new # Convert string keys from MCP/fast-mcp back to symbols for Legate tool legate_params = args.transform_keys(&:to_sym) # Create a dummy/minimal context for the Legate tool execution # TODO: Can we provide more meaningful context if running within a larger MCP session? dummy_context = Legate::ToolContext.new( session_id: SecureRandom.uuid, # Generic ID user_id: 'mcp_user', app_name: 'mcp_server', tool_registry: Legate::ToolRegistry.new # Create a new, empty registry for this dummy context # No session_service available here easily ) Legate.logger.info("Executing Legate tool '#{self.class.tool_name}' via MCP adapter with params: #{legate_params.inspect}") begin result_hash = legate_instance.execute(legate_params, dummy_context) rescue StandardError => e # Catch errors during the tool's execute method itself Mcp.logger.error("Error during underlying Legate tool execution for '#{self.class.tool_name}': #{e.class} - #{e.}") # Let fast-mcp handle this standard error, it should map to an MCP error response raise StandardError, "Execution Error in Legate tool '#{self.class.tool_name}': #{e.}" end Legate.logger.debug("Legate tool '#{self.class.tool_name}' returned hash: #{result_hash.inspect}") # Translate Legate result hash to MCP return/error case result_hash[:status] when :success result_hash[:result] # Return the raw result for MCP when :error = result_hash[:error_message] || "Unknown error from Legate tool '#{self.class.tool_name}'" Legate.logger.error("Legate tool '#{self.class.tool_name}' reported error: #{}") # Raise a standard error, fast-mcp should convert this to an MCP error response raise StandardError, when :pending job_id = result_hash[:job_id] # Assuming the key is :job_id now = result_hash[:message] || "Legate tool '#{self.class.tool_name}' started an async job." Legate.logger.info("Legate tool '#{self.class.tool_name}' returned pending status (Job ID: #{job_id})") # Return a structured hash indicating pending status (as per FR2.2 recommendation) # Requires CheckJobStatusTool to be exposed separately via MCP. { status: 'pending', job_id: job_id, message: } else unknown_status_msg = "Legate tool '#{self.class.tool_name}' returned unknown status: #{result_hash[:status]}" Mcp.logger.error(unknown_status_msg) raise StandardError, unknown_status_msg end end |