Class: Legate::Mcp::Server::LegateDirectAgentAdapter

Inherits:
FastMcp::Tool
  • Object
show all
Defined in:
lib/legate/mcp/server/legate_direct_agent_adapter.rb

Overview

Adapter to expose an Legate::Agent instance directly as a single tool via fast-mcp. The agent is used ephemerally for each call.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.legate_agent_instanceObject (readonly)

Returns the value of attribute legate_agent_instance.



18
19
20
# File 'lib/legate/mcp/server/legate_direct_agent_adapter.rb', line 18

def legate_agent_instance
  @legate_agent_instance
end

.session_serviceObject (readonly)

Returns the value of attribute session_service.



18
19
20
# File 'lib/legate/mcp/server/legate_direct_agent_adapter.rb', line 18

def session_service
  @session_service
end

Class Method Details

.wrap(agent_instance, session_service_instance) ⇒ Class<LegateDirectAgentAdapter>

Dynamically creates a new FastMcp::Tool subclass that wraps the given Legate::Agent instance.

Parameters:

Returns:

Raises:

  • (ArgumentError)


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
# File 'lib/legate/mcp/server/legate_direct_agent_adapter.rb', line 26

def self.wrap(agent_instance, session_service_instance)
  raise ArgumentError, 'Provided object is not a valid Legate::Agent instance.' unless agent_instance.is_a?(Legate::Agent)
  raise ArgumentError, 'Session service instance must inherit from Legate::SessionService::Base.' unless session_service_instance.is_a?(Legate::SessionService::Base)

  agent_name = agent_instance.name
  agent_description = agent_instance.description

  # Create the anonymous adapter class
  Class.new(LegateDirectAgentAdapter) do
    # Store instances on the generated class
    @legate_agent_instance = agent_instance
    @session_service = session_service_instance

    # Set fast-mcp tool metadata
    tool_name "run_agent_#{agent_name}" # Or just agent_name if desired
    description "Runs the Legate Agent '#{agent_name}': #{agent_description}"

    # Define the single prompt argument
    arguments do
      required(:prompt).filled(:string).description('The user input/prompt for the agent')
    end

    Mcp.logger.info("Created direct fast-mcp adapter for Legate agent instance: '#{agent_name}'")
  end
end

Instance Method Details

#call(prompt:) ⇒ Any

Executes the wrapped Legate Agent instance for a single turn.

Parameters:

  • prompt (String)

    The user prompt.

Returns:

  • (Any)

    The final result payload from the agent’s execution.

Raises:

  • (StandardError)

    If agent execution fails or returns an error status.



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
98
99
100
101
102
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
130
131
132
133
134
135
136
# File 'lib/legate/mcp/server/legate_direct_agent_adapter.rb', line 57

def call(prompt:)
  # Retrieve instances from the *class* instance variables
  agent = self.class.legate_agent_instance
  session_service = self.class.session_service
  unless agent && session_service
    raise NotImplementedError,
          'LegateDirectAgentAdapter must be configured using .wrap first.'
  end

  agent_name = agent.name
  Mcp.logger.info("Executing Legate Agent '#{agent_name}' via direct MCP adapter with prompt: '#{prompt}'")

  temp_session = nil
  was_agent_already_running = agent.running?
  begin
    # 1. Create Temporary Session
    Mcp.logger.debug('Creating temporary session...')
    temp_session = session_service.create_session(app_name: agent_name,
                                                  user_id: "mcp_direct_#{SecureRandom.hex(4)}")
    Mcp.logger.debug("Temporary session created: #{temp_session.id}")

    # 2. Ensure Agent is Running
    unless was_agent_already_running
      Mcp.logger.debug('Starting ephemeral agent runtime...')
      agent.start
    end

    # 3. Run Task
    Mcp.logger.debug("Running task in temp session #{temp_session.id}...")
    final_event = agent.run_task(
      session_id: temp_session.id,
      user_input: prompt,
      session_service: session_service
    )
    Mcp.logger.debug("Agent run_task finished. Final event: #{final_event.inspect}")

    # 4. Process Result
    raise StandardError, "Agent task finished with unexpected event format: #{final_event.inspect}" unless final_event.is_a?(Legate::Event) && final_event.role == :agent && final_event.content.is_a?(Hash)

    result_content = final_event.content

    case result_content[:status]
    when :success
      result_content[:result] # Return result payload
    when :error
      err_msg = result_content[:error_message] || 'Agent execution failed.'
      Mcp.logger.error("Agent '#{agent_name}' execution failed: #{err_msg}")
      raise StandardError, "Agent Error: #{err_msg}"
    when :pending
      job_id = result_content[:job_id]
      msg = result_content[:message] || 'Agent task resulted in a pending job.'
      Mcp.logger.warn("Agent '#{agent_name}' execution ended with pending status (Job: #{job_id}). Returning as structured data.")
      { status: 'pending', job_id: job_id, message: msg }
    else
      raise StandardError, "Agent task finished with unknown status: #{result_content[:status]}"
    end
  rescue StandardError => e
    Mcp.logger.error("Error during LegateDirectAgentAdapter call for '#{agent_name}': #{e.class} - #{e.message}")
    Mcp.logger.error(e.backtrace.join("\n"))
    raise StandardError, "Failed to run agent '#{agent_name}': #{e.message}"
  ensure
    # 5. Cleanup
    if !was_agent_already_running && agent&.running?
      begin
        Mcp.logger.debug('Stopping ephemeral agent runtime...')
        agent.stop
      rescue StandardError => e
        Mcp.logger.error("Error stopping agent runtime during cleanup: #{e.message}")
      end
    end
    if temp_session && session_service
      begin
        Mcp.logger.debug("Deleting temporary session: #{temp_session.id}")
        session_service.delete_session(session_id: temp_session.id)
      rescue StandardError => e
        Mcp.logger.error("Error deleting temporary session #{temp_session.id}: #{e.message}")
      end
    end
  end
end