legate 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# File: lib/legate/mcp/server/legate_agent_adapter.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fast_mcp'
|
|
5
|
+
require 'json' # Needed to parse tools
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
require_relative '../../agent'
|
|
8
|
+
require_relative '../../tool_registry'
|
|
9
|
+
require_relative '../../session_service/base' # Need base for type check
|
|
10
|
+
require_relative '../../event' # Needed for result processing
|
|
11
|
+
require_relative '../../errors'
|
|
12
|
+
require_relative '../../global_tool_manager' # Added require
|
|
13
|
+
require_relative '../../global_definition_registry' # For definition lookups
|
|
14
|
+
|
|
15
|
+
module Legate
|
|
16
|
+
module Mcp
|
|
17
|
+
module Server
|
|
18
|
+
# (Experimental) Adapter to expose an entire Legate::Agent (defined in GlobalDefinitionRegistry)
|
|
19
|
+
# as a single, simple tool via fast-mcp.
|
|
20
|
+
# The agent runs ephemerally for each call
|
|
21
|
+
class LegateAgentAdapter < FastMcp::Tool
|
|
22
|
+
# --- Class Configuration ---
|
|
23
|
+
# Using class instance variables set by `wrap`
|
|
24
|
+
class << self
|
|
25
|
+
attr_reader :agent_definition_name, :session_service
|
|
26
|
+
end
|
|
27
|
+
# -------------------------
|
|
28
|
+
|
|
29
|
+
# Dynamically creates a new FastMcp::Tool subclass that wraps an Legate Agent definition.
|
|
30
|
+
#
|
|
31
|
+
# @param agent_definition_name [String] The name of the agent definition in GlobalDefinitionRegistry.
|
|
32
|
+
# @param session_service_instance [Legate::SessionService::Base] The session service to use for temporary sessions.
|
|
33
|
+
# @return [Class<LegateAgentAdapter>] A new anonymous class inheriting from LegateAgentAdapter.
|
|
34
|
+
def self.wrap(agent_definition_name, session_service_instance)
|
|
35
|
+
raise ArgumentError, 'Agent definition name must be a non-empty String.' unless agent_definition_name.is_a?(String) && !agent_definition_name.empty?
|
|
36
|
+
raise ArgumentError, 'Session service instance must inherit from Legate::SessionService::Base.' unless session_service_instance.is_a?(Legate::SessionService::Base)
|
|
37
|
+
|
|
38
|
+
# Create the anonymous adapter class
|
|
39
|
+
Class.new(LegateAgentAdapter) do
|
|
40
|
+
# Store config on the generated class
|
|
41
|
+
@agent_definition_name = agent_definition_name
|
|
42
|
+
@session_service = session_service_instance
|
|
43
|
+
|
|
44
|
+
# Set fast-mcp tool metadata
|
|
45
|
+
# Use a modified tool name to avoid clashes if agent name = tool name
|
|
46
|
+
tool_name "run_agent_#{agent_definition_name}"
|
|
47
|
+
description "Runs the Legate Agent '#{agent_definition_name}' with the given prompt."
|
|
48
|
+
|
|
49
|
+
# Define the single prompt argument
|
|
50
|
+
arguments do
|
|
51
|
+
required(:prompt).filled(:string).description('The user input/prompt for the agent')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Mcp.logger.info("Created fast-mcp adapter for Legate agent definition: '#{agent_definition_name}'")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Executes the wrapped Legate Agent for a single turn.
|
|
59
|
+
# Loads definition, creates temp session, runs task, cleans up.
|
|
60
|
+
#
|
|
61
|
+
# @param prompt [String] The user prompt.
|
|
62
|
+
# @return [Any] The final result payload from the agent's execution.
|
|
63
|
+
# @raise [StandardError] If agent execution fails or returns an error status.
|
|
64
|
+
def call(prompt:)
|
|
65
|
+
# Retrieve config from the *class* instance variables
|
|
66
|
+
agent_name = self.class.agent_definition_name
|
|
67
|
+
session_service = self.class.session_service
|
|
68
|
+
unless agent_name && session_service
|
|
69
|
+
raise NotImplementedError,
|
|
70
|
+
'LegateAgentAdapter must be configured using .wrap first.'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Mcp.logger.info("Executing Legate Agent '#{agent_name}' via MCP adapter with prompt: '#{prompt}'")
|
|
74
|
+
|
|
75
|
+
agent = nil
|
|
76
|
+
temp_session = nil
|
|
77
|
+
begin
|
|
78
|
+
# 1. Load Agent Definition from GlobalDefinitionRegistry
|
|
79
|
+
Mcp.logger.debug("Loading agent definition '#{agent_name}' from GlobalDefinitionRegistry...")
|
|
80
|
+
agent_definition_object = Legate::GlobalDefinitionRegistry.find(agent_name.to_sym)
|
|
81
|
+
|
|
82
|
+
# Try get_definition if available (expanded API from Phase 2)
|
|
83
|
+
if !agent_definition_object && Legate::GlobalDefinitionRegistry.respond_to?(:get_definition)
|
|
84
|
+
definition_hash = Legate::GlobalDefinitionRegistry.get_definition(agent_name)
|
|
85
|
+
agent_definition_object = Legate::AgentDefinition.from_hash(definition_hash) if definition_hash
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
raise Legate::Mcp::Error, "Agent definition '#{agent_name}' not found in GlobalDefinitionRegistry." unless agent_definition_object
|
|
89
|
+
|
|
90
|
+
Mcp.logger.debug("Agent definition loaded for '#{agent_name}'.")
|
|
91
|
+
|
|
92
|
+
Mcp.logger.debug("AgentDefinition object ready: #{agent_definition_object.name}, Model=#{agent_definition_object.model_name}")
|
|
93
|
+
|
|
94
|
+
# 2. Create Temporary Session
|
|
95
|
+
Mcp.logger.debug('Creating temporary session...')
|
|
96
|
+
temp_session = session_service.create_session(app_name: agent_name,
|
|
97
|
+
user_id: "mcp_temp_#{SecureRandom.hex(4)}")
|
|
98
|
+
Mcp.logger.debug("Temporary session created: #{temp_session.id}")
|
|
99
|
+
|
|
100
|
+
# 3. Instantiate Agent
|
|
101
|
+
Mcp.logger.debug("Instantiating agent '#{agent_definition_object.name}' with its definition object and session service...")
|
|
102
|
+
|
|
103
|
+
agent = Legate::Agent.new(
|
|
104
|
+
definition: agent_definition_object,
|
|
105
|
+
session_service: session_service # Pass the session_service from adapter config
|
|
106
|
+
)
|
|
107
|
+
# Tool loading is handled by Legate::Agent#initialize based on the definition object.
|
|
108
|
+
|
|
109
|
+
# 4. Start Agent & Run Task
|
|
110
|
+
Mcp.logger.debug('Starting ephemeral agent runtime...')
|
|
111
|
+
agent.start
|
|
112
|
+
Mcp.logger.debug("Running task in temp session #{temp_session.id}...")
|
|
113
|
+
final_event = agent.run_task(
|
|
114
|
+
session_id: temp_session.id,
|
|
115
|
+
user_input: prompt,
|
|
116
|
+
session_service: session_service
|
|
117
|
+
)
|
|
118
|
+
Mcp.logger.debug("Agent run_task finished. Final event: #{final_event.inspect}")
|
|
119
|
+
|
|
120
|
+
# 5. Process Result
|
|
121
|
+
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)
|
|
122
|
+
|
|
123
|
+
result_content = final_event.content
|
|
124
|
+
|
|
125
|
+
case result_content[:status]
|
|
126
|
+
when :success
|
|
127
|
+
result_content[:result] # Return result payload
|
|
128
|
+
when :error
|
|
129
|
+
err_msg = result_content[:error_message] || 'Agent execution failed.'
|
|
130
|
+
Mcp.logger.error("Agent '#{agent_name}' execution failed: #{err_msg}")
|
|
131
|
+
raise StandardError, "Agent Error: #{err_msg}"
|
|
132
|
+
when :pending
|
|
133
|
+
job_id = result_content[:job_id] # Assuming key is :job_id
|
|
134
|
+
msg = result_content[:message] || 'Agent task resulted in a pending job.'
|
|
135
|
+
Mcp.logger.warn("Agent '#{agent_name}' execution ended with pending status (Job: #{job_id}). Returning as structured data.")
|
|
136
|
+
# Return pending structure similar to LegateToolAdapter for consistency
|
|
137
|
+
{ status: 'pending', job_id: job_id, message: msg }
|
|
138
|
+
else
|
|
139
|
+
raise StandardError, "Agent task finished with unknown status: #{result_content[:status]}"
|
|
140
|
+
end
|
|
141
|
+
rescue Legate::Mcp::Error, StandardError => e
|
|
142
|
+
# Catch errors during setup or execution within the adapter
|
|
143
|
+
Mcp.logger.error("Error during LegateAgentAdapter call for '#{agent_name}': #{e.class} - #{e.message}")
|
|
144
|
+
Mcp.logger.error(e.backtrace.join("\n"))
|
|
145
|
+
# Let fast-mcp handle the error
|
|
146
|
+
raise StandardError, "Failed to run agent '#{agent_name}': #{e.message}"
|
|
147
|
+
ensure
|
|
148
|
+
# 6. Cleanup: Stop Agent & Delete Session
|
|
149
|
+
if agent&.running?
|
|
150
|
+
begin
|
|
151
|
+
Mcp.logger.debug('Stopping ephemeral agent runtime...')
|
|
152
|
+
agent.stop
|
|
153
|
+
rescue StandardError => e
|
|
154
|
+
Mcp.logger.error("Error stopping agent runtime during cleanup: #{e.message}")
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
if temp_session && session_service
|
|
158
|
+
begin
|
|
159
|
+
Mcp.logger.debug("Deleting temporary session: #{temp_session.id}")
|
|
160
|
+
session_service.delete_session(session_id: temp_session.id)
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
Mcp.logger.error("Error deleting temporary session #{temp_session.id}: #{e.message}")
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# File: lib/legate/mcp/server/legate_direct_agent_adapter.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fast_mcp'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require_relative '../../agent'
|
|
7
|
+
require_relative '../../session_service/base'
|
|
8
|
+
require_relative '../../event'
|
|
9
|
+
require_relative '../../errors'
|
|
10
|
+
|
|
11
|
+
module Legate
|
|
12
|
+
module Mcp
|
|
13
|
+
module Server
|
|
14
|
+
# Adapter to expose an Legate::Agent instance directly as a single tool via fast-mcp.
|
|
15
|
+
# The agent is used ephemerally for each call.
|
|
16
|
+
class LegateDirectAgentAdapter < FastMcp::Tool
|
|
17
|
+
class << self
|
|
18
|
+
attr_reader :legate_agent_instance, :session_service
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Dynamically creates a new FastMcp::Tool subclass that wraps the given Legate::Agent instance.
|
|
22
|
+
#
|
|
23
|
+
# @param agent_instance [Legate::Agent] The initialized Legate::Agent instance to wrap.
|
|
24
|
+
# @param session_service_instance [Legate::SessionService::Base] The session service for temporary sessions.
|
|
25
|
+
# @return [Class<LegateDirectAgentAdapter>] A new anonymous class inheriting from LegateDirectAgentAdapter.
|
|
26
|
+
def self.wrap(agent_instance, session_service_instance)
|
|
27
|
+
raise ArgumentError, 'Provided object is not a valid Legate::Agent instance.' unless agent_instance.is_a?(Legate::Agent)
|
|
28
|
+
raise ArgumentError, 'Session service instance must inherit from Legate::SessionService::Base.' unless session_service_instance.is_a?(Legate::SessionService::Base)
|
|
29
|
+
|
|
30
|
+
agent_name = agent_instance.name
|
|
31
|
+
agent_description = agent_instance.description
|
|
32
|
+
|
|
33
|
+
# Create the anonymous adapter class
|
|
34
|
+
Class.new(LegateDirectAgentAdapter) do
|
|
35
|
+
# Store instances on the generated class
|
|
36
|
+
@legate_agent_instance = agent_instance
|
|
37
|
+
@session_service = session_service_instance
|
|
38
|
+
|
|
39
|
+
# Set fast-mcp tool metadata
|
|
40
|
+
tool_name "run_agent_#{agent_name}" # Or just agent_name if desired
|
|
41
|
+
description "Runs the Legate Agent '#{agent_name}': #{agent_description}"
|
|
42
|
+
|
|
43
|
+
# Define the single prompt argument
|
|
44
|
+
arguments do
|
|
45
|
+
required(:prompt).filled(:string).description('The user input/prompt for the agent')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Mcp.logger.info("Created direct fast-mcp adapter for Legate agent instance: '#{agent_name}'")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Executes the wrapped Legate Agent instance for a single turn.
|
|
53
|
+
#
|
|
54
|
+
# @param prompt [String] The user prompt.
|
|
55
|
+
# @return [Any] The final result payload from the agent's execution.
|
|
56
|
+
# @raise [StandardError] If agent execution fails or returns an error status.
|
|
57
|
+
def call(prompt:)
|
|
58
|
+
# Retrieve instances from the *class* instance variables
|
|
59
|
+
agent = self.class.legate_agent_instance
|
|
60
|
+
session_service = self.class.session_service
|
|
61
|
+
unless agent && session_service
|
|
62
|
+
raise NotImplementedError,
|
|
63
|
+
'LegateDirectAgentAdapter must be configured using .wrap first.'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
agent_name = agent.name
|
|
67
|
+
Mcp.logger.info("Executing Legate Agent '#{agent_name}' via direct MCP adapter with prompt: '#{prompt}'")
|
|
68
|
+
|
|
69
|
+
temp_session = nil
|
|
70
|
+
was_agent_already_running = agent.running?
|
|
71
|
+
begin
|
|
72
|
+
# 1. Create Temporary Session
|
|
73
|
+
Mcp.logger.debug('Creating temporary session...')
|
|
74
|
+
temp_session = session_service.create_session(app_name: agent_name,
|
|
75
|
+
user_id: "mcp_direct_#{SecureRandom.hex(4)}")
|
|
76
|
+
Mcp.logger.debug("Temporary session created: #{temp_session.id}")
|
|
77
|
+
|
|
78
|
+
# 2. Ensure Agent is Running
|
|
79
|
+
unless was_agent_already_running
|
|
80
|
+
Mcp.logger.debug('Starting ephemeral agent runtime...')
|
|
81
|
+
agent.start
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 3. Run Task
|
|
85
|
+
Mcp.logger.debug("Running task in temp session #{temp_session.id}...")
|
|
86
|
+
final_event = agent.run_task(
|
|
87
|
+
session_id: temp_session.id,
|
|
88
|
+
user_input: prompt,
|
|
89
|
+
session_service: session_service
|
|
90
|
+
)
|
|
91
|
+
Mcp.logger.debug("Agent run_task finished. Final event: #{final_event.inspect}")
|
|
92
|
+
|
|
93
|
+
# 4. Process Result
|
|
94
|
+
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)
|
|
95
|
+
|
|
96
|
+
result_content = final_event.content
|
|
97
|
+
|
|
98
|
+
case result_content[:status]
|
|
99
|
+
when :success
|
|
100
|
+
result_content[:result] # Return result payload
|
|
101
|
+
when :error
|
|
102
|
+
err_msg = result_content[:error_message] || 'Agent execution failed.'
|
|
103
|
+
Mcp.logger.error("Agent '#{agent_name}' execution failed: #{err_msg}")
|
|
104
|
+
raise StandardError, "Agent Error: #{err_msg}"
|
|
105
|
+
when :pending
|
|
106
|
+
job_id = result_content[:job_id]
|
|
107
|
+
msg = result_content[:message] || 'Agent task resulted in a pending job.'
|
|
108
|
+
Mcp.logger.warn("Agent '#{agent_name}' execution ended with pending status (Job: #{job_id}). Returning as structured data.")
|
|
109
|
+
{ status: 'pending', job_id: job_id, message: msg }
|
|
110
|
+
else
|
|
111
|
+
raise StandardError, "Agent task finished with unknown status: #{result_content[:status]}"
|
|
112
|
+
end
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
Mcp.logger.error("Error during LegateDirectAgentAdapter call for '#{agent_name}': #{e.class} - #{e.message}")
|
|
115
|
+
Mcp.logger.error(e.backtrace.join("\n"))
|
|
116
|
+
raise StandardError, "Failed to run agent '#{agent_name}': #{e.message}"
|
|
117
|
+
ensure
|
|
118
|
+
# 5. Cleanup
|
|
119
|
+
if !was_agent_already_running && agent&.running?
|
|
120
|
+
begin
|
|
121
|
+
Mcp.logger.debug('Stopping ephemeral agent runtime...')
|
|
122
|
+
agent.stop
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
Mcp.logger.error("Error stopping agent runtime during cleanup: #{e.message}")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
if temp_session && session_service
|
|
128
|
+
begin
|
|
129
|
+
Mcp.logger.debug("Deleting temporary session: #{temp_session.id}")
|
|
130
|
+
session_service.delete_session(session_id: temp_session.id)
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
Mcp.logger.error("Error deleting temporary session #{temp_session.id}: #{e.message}")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# File: lib/legate/mcp/server/legate_tool_adapter.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fast_mcp'
|
|
5
|
+
require_relative '../../tool'
|
|
6
|
+
require_relative '../../tool_context'
|
|
7
|
+
require_relative '../util/schema_converter'
|
|
8
|
+
require_relative '../../errors'
|
|
9
|
+
|
|
10
|
+
module Legate
|
|
11
|
+
module Mcp
|
|
12
|
+
module Server
|
|
13
|
+
# Base adapter class to expose an Legate::Tool as an MCP tool via fast-mcp.
|
|
14
|
+
# Use the `wrap` class method to dynamically create subclasses for specific Legate tools.
|
|
15
|
+
class LegateToolAdapter < FastMcp::Tool
|
|
16
|
+
# Use standard class instance variables for inheritable attributes
|
|
17
|
+
class << self
|
|
18
|
+
attr_reader :legate_tool_class # Provide a reader
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Dynamically creates a new FastMcp::Tool subclass that wraps the given Legate::Tool class.
|
|
22
|
+
#
|
|
23
|
+
# @param legate_tool_class [Class<Legate::Tool>] The Legate::Tool class to wrap.
|
|
24
|
+
# @return [Class<LegateToolAdapter>] A new anonymous class inheriting from LegateToolAdapter.
|
|
25
|
+
# @raise [ArgumentError] if the provided class is not an Legate::Tool.
|
|
26
|
+
def self.wrap(legate_tool_class)
|
|
27
|
+
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
|
|
28
|
+
|
|
29
|
+
metadata = legate_tool_class.tool_metadata
|
|
30
|
+
# Check metadata hash and required keys
|
|
31
|
+
raise ArgumentError, "Legate::Tool #{legate_tool_class} has incomplete metadata (missing name or description)." unless metadata.is_a?(Hash) && metadata[:name] && metadata[:description]
|
|
32
|
+
|
|
33
|
+
mcp_tool_name = metadata[:name].to_s
|
|
34
|
+
mcp_description = metadata[:description]
|
|
35
|
+
legate_params = metadata[:parameters] || {}
|
|
36
|
+
|
|
37
|
+
# Convert Legate params to a Dry::Schema proc
|
|
38
|
+
schema_proc = Legate::Mcp::Util::SchemaConverter.legate_to_dry_schema(legate_params)
|
|
39
|
+
|
|
40
|
+
# Create the anonymous adapter class
|
|
41
|
+
Class.new(LegateToolAdapter) do
|
|
42
|
+
@legate_tool_class = legate_tool_class
|
|
43
|
+
|
|
44
|
+
# Use fast-mcp DSL methods inside the class definition block
|
|
45
|
+
tool_name mcp_tool_name # Use DSL method
|
|
46
|
+
description mcp_description
|
|
47
|
+
arguments(&schema_proc) if schema_proc
|
|
48
|
+
|
|
49
|
+
Legate.logger.info("Created fast-mcp adapter for Legate tool: #{legate_tool_class} as '#{mcp_tool_name}'")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The `call` method executed by fast-mcp when the tool is invoked.
|
|
54
|
+
# Instantiates the wrapped Legate tool, executes it with a dummy context,
|
|
55
|
+
# and translates the result/error.
|
|
56
|
+
#
|
|
57
|
+
# @param args [Hash] Keyword arguments matching the defined schema.
|
|
58
|
+
# @return [Any] The successful result payload for the MCP response.
|
|
59
|
+
# @raise [StandardError] If the Legate tool returns an error status.
|
|
60
|
+
# @raise [NotImplementedError] If the Legate tool returns a pending status (needs CheckJobStatusTool).
|
|
61
|
+
def call(**args)
|
|
62
|
+
# Access the class instance variable via the reader
|
|
63
|
+
tool_class = self.class.legate_tool_class
|
|
64
|
+
raise NotImplementedError, 'LegateToolAdapter cannot be used directly, use .wrap first.' unless tool_class
|
|
65
|
+
|
|
66
|
+
legate_instance = tool_class.new
|
|
67
|
+
|
|
68
|
+
# Convert string keys from MCP/fast-mcp back to symbols for Legate tool
|
|
69
|
+
legate_params = args.transform_keys(&:to_sym)
|
|
70
|
+
|
|
71
|
+
# Create a dummy/minimal context for the Legate tool execution
|
|
72
|
+
# TODO: Can we provide more meaningful context if running within a larger MCP session?
|
|
73
|
+
dummy_context = Legate::ToolContext.new(
|
|
74
|
+
session_id: SecureRandom.uuid, # Generic ID
|
|
75
|
+
user_id: 'mcp_user',
|
|
76
|
+
app_name: 'mcp_server',
|
|
77
|
+
tool_registry: Legate::ToolRegistry.new # Create a new, empty registry for this dummy context
|
|
78
|
+
# No session_service available here easily
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
Legate.logger.info("Executing Legate tool '#{self.class.tool_name}' via MCP adapter with params: #{legate_params.inspect}")
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
result_hash = legate_instance.execute(legate_params, dummy_context)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
# Catch errors during the tool's execute method itself
|
|
87
|
+
Mcp.logger.error("Error during underlying Legate tool execution for '#{self.class.tool_name}': #{e.class} - #{e.message}")
|
|
88
|
+
# Let fast-mcp handle this standard error, it should map to an MCP error response
|
|
89
|
+
raise StandardError, "Execution Error in Legate tool '#{self.class.tool_name}': #{e.message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Legate.logger.debug("Legate tool '#{self.class.tool_name}' returned hash: #{result_hash.inspect}")
|
|
93
|
+
|
|
94
|
+
# Translate Legate result hash to MCP return/error
|
|
95
|
+
case result_hash[:status]
|
|
96
|
+
when :success
|
|
97
|
+
result_hash[:result] # Return the raw result for MCP
|
|
98
|
+
when :error
|
|
99
|
+
error_message = result_hash[:error_message] || "Unknown error from Legate tool '#{self.class.tool_name}'"
|
|
100
|
+
Legate.logger.error("Legate tool '#{self.class.tool_name}' reported error: #{error_message}")
|
|
101
|
+
# Raise a standard error, fast-mcp should convert this to an MCP error response
|
|
102
|
+
raise StandardError, error_message
|
|
103
|
+
when :pending
|
|
104
|
+
job_id = result_hash[:job_id] # Assuming the key is :job_id now
|
|
105
|
+
message = result_hash[:message] || "Legate tool '#{self.class.tool_name}' started an async job."
|
|
106
|
+
Legate.logger.info("Legate tool '#{self.class.tool_name}' returned pending status (Job ID: #{job_id})")
|
|
107
|
+
# Return a structured hash indicating pending status (as per FR2.2 recommendation)
|
|
108
|
+
# Requires CheckJobStatusTool to be exposed separately via MCP.
|
|
109
|
+
{ status: 'pending', job_id: job_id, message: message }
|
|
110
|
+
else
|
|
111
|
+
unknown_status_msg = "Legate tool '#{self.class.tool_name}' returned unknown status: #{result_hash[:status]}"
|
|
112
|
+
Mcp.logger.error(unknown_status_msg)
|
|
113
|
+
raise StandardError, unknown_status_msg
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# File: lib/legate/mcp/tool_wrapper.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
require_relative '../tool_registry'
|
|
6
|
+
require_relative 'util/schema_converter'
|
|
7
|
+
require_relative 'client' # Need client for execution
|
|
8
|
+
require_relative '../errors'
|
|
9
|
+
|
|
10
|
+
module Legate
|
|
11
|
+
module Mcp
|
|
12
|
+
# Base class for dynamically created Legate::Tool wrappers around external MCP tools.
|
|
13
|
+
# Instances of anonymous subclasses generated by `from_mcp_schema` are used.
|
|
14
|
+
class ToolWrapper < Legate::Tool
|
|
15
|
+
# General description for the base wrapper concept
|
|
16
|
+
tool_description 'Internal base class for dynamically wrapping external MCP tools. Instances are generated automatically.'
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# References stored on the dynamically created subclass
|
|
20
|
+
attr_accessor :mcp_client, :mcp_tool_name, :mcp_input_schema
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Creates a new anonymous Legate::Tool subclass that wraps an external MCP tool.
|
|
24
|
+
# Registers the new tool class with the provided Legate::ToolRegistry instance.
|
|
25
|
+
#
|
|
26
|
+
# @param mcp_schema [Hash] The tool schema hash from MCP server.
|
|
27
|
+
# @param mcp_client [Legate::Mcp::Client] The client instance.
|
|
28
|
+
# @param tool_registry [Legate::ToolRegistry] The specific registry instance to register with.
|
|
29
|
+
# @return [Class] The newly created anonymous ToolWrapper subclass, or nil.
|
|
30
|
+
def self.from_mcp_schema(mcp_schema, mcp_client, tool_registry)
|
|
31
|
+
# Validate inputs including the registry
|
|
32
|
+
unless mcp_schema.is_a?(Hash) && mcp_schema[:name] &&
|
|
33
|
+
mcp_client.is_a?(Legate::Mcp::Client) &&
|
|
34
|
+
tool_registry.is_a?(Legate::ToolRegistry)
|
|
35
|
+
Mcp.logger.error('Invalid input for ToolWrapper.from_mcp_schema: Schema, Client, or Registry invalid.')
|
|
36
|
+
return nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
mcp_name = mcp_schema[:name]
|
|
40
|
+
mcp_description = mcp_schema[:description] || "MCP Tool: #{mcp_name}"
|
|
41
|
+
mcp_input_schema = mcp_schema[:inputSchema] || {}
|
|
42
|
+
mcp_properties = mcp_input_schema[:properties] || {}
|
|
43
|
+
mcp_required = mcp_input_schema[:required] || []
|
|
44
|
+
|
|
45
|
+
legate_params = Util::SchemaConverter.json_to_legate(mcp_properties, mcp_required)
|
|
46
|
+
|
|
47
|
+
# Create anonymous class
|
|
48
|
+
wrapper_class = Class.new(ToolWrapper) do
|
|
49
|
+
# --- CAPTURE local variables for use in method definitions ---
|
|
50
|
+
captured_mcp_name_sym = mcp_name.to_sym
|
|
51
|
+
captured_mcp_description = mcp_description
|
|
52
|
+
captured_legate_params = legate_params
|
|
53
|
+
|
|
54
|
+
# Store references needed for execution on the class itself
|
|
55
|
+
self.mcp_client = mcp_client
|
|
56
|
+
self.mcp_tool_name = mcp_name # Keep original string name for execution
|
|
57
|
+
self.mcp_input_schema = mcp_input_schema
|
|
58
|
+
|
|
59
|
+
# --- Define the tool_metadata method explicitly on this anonymous class ---
|
|
60
|
+
define_singleton_method(:tool_metadata) do
|
|
61
|
+
{
|
|
62
|
+
name: captured_mcp_name_sym,
|
|
63
|
+
description: captured_mcp_description,
|
|
64
|
+
parameters: captured_legate_params
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# --- ALSO explicitly define the individual readers for robustness ---
|
|
69
|
+
# (These might be called by other parts of Legate or ToolRegistry indirectly)
|
|
70
|
+
define_singleton_method(:tool_name) { captured_mcp_name_sym }
|
|
71
|
+
define_singleton_method(:description) { captured_mcp_description }
|
|
72
|
+
define_singleton_method(:parameters_definition) { captured_legate_params }
|
|
73
|
+
|
|
74
|
+
# --- Keep the original define_metadata call as well for global registration ---
|
|
75
|
+
# Although redundant for metadata retrieval via tool_metadata, it handles global registration.
|
|
76
|
+
define_metadata(
|
|
77
|
+
name: captured_mcp_name_sym,
|
|
78
|
+
description: captured_mcp_description,
|
|
79
|
+
parameters: captured_legate_params
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
Mcp.logger.info("Created Legate Tool wrapper for MCP tool: '#{mcp_name}'")
|
|
84
|
+
|
|
85
|
+
# Register with the PROVIDED registry instance
|
|
86
|
+
begin
|
|
87
|
+
tool_registry.register(mcp_name.to_sym, wrapper_class)
|
|
88
|
+
Mcp.logger.debug("Registered wrapper for MCP tool '#{mcp_name}' with provided ToolRegistry.")
|
|
89
|
+
rescue Legate::ToolRegistry::ToolExistsError => e
|
|
90
|
+
Mcp.logger.warn("MCP Tool '#{mcp_name}' conflicts with an existing tool in provided registry: #{e.message}")
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
Mcp.logger.error("Failed to register wrapper for MCP tool '#{mcp_name}' with provided registry: #{e.message}")
|
|
93
|
+
return nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
wrapper_class
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Executes the wrapped MCP tool via the stored MCP client.
|
|
100
|
+
#
|
|
101
|
+
# @param params [Hash] Parameters provided by the Legate agent/planner.
|
|
102
|
+
# @param context [Legate::ToolContext] The execution context (less relevant for external tools).
|
|
103
|
+
# @return [Hash] Legate status hash ({status: :success/:error, result:/error_message:}).
|
|
104
|
+
def perform_execution(params, _context)
|
|
105
|
+
self.class.mcp_client || begin
|
|
106
|
+
Mcp.logger.error("MCP Client not configured for tool wrapper: #{self.class.mcp_tool_name}")
|
|
107
|
+
return { status: :error, error_message: 'Internal configuration error: MCP Client missing.' }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
mcp_tool_name_str = self.class.mcp_tool_name
|
|
111
|
+
mcp_client_instance = self.class.mcp_client
|
|
112
|
+
|
|
113
|
+
Mcp.logger.info("Executing wrapped MCP tool '#{mcp_tool_name_str}' with params: #{params.inspect}")
|
|
114
|
+
|
|
115
|
+
# TODO: V1.1 - Translate Legate params back to JSON structure based on mcp_input_schema?
|
|
116
|
+
# For V1, assume flat hash structure matches between Legate and MCP tool for basic types.
|
|
117
|
+
mcp_args = params.transform_keys(&:to_s) # Convert symbol keys back to strings for JSON
|
|
118
|
+
|
|
119
|
+
begin
|
|
120
|
+
result = mcp_client_instance.call_tool(mcp_tool_name_str, mcp_args)
|
|
121
|
+
Mcp.logger.info("MCP tool '#{mcp_tool_name_str}' executed successfully.")
|
|
122
|
+
{ status: :success, result: result }
|
|
123
|
+
rescue Legate::Mcp::RemoteToolError => e
|
|
124
|
+
Mcp.logger.error("MCP tool '#{mcp_tool_name_str}' returned an error: #{e}")
|
|
125
|
+
{ status: :error, error_message: "MCP Tool Error: #{e.message}",
|
|
126
|
+
error_details: { code: e.code, data: e.data } }
|
|
127
|
+
rescue Legate::Mcp::ConnectionError, Legate::Mcp::ProtocolError => e
|
|
128
|
+
Mcp.logger.error("MCP communication error during '#{mcp_tool_name_str}' execution: #{e.message}")
|
|
129
|
+
{ status: :error, error_message: "MCP Communication Error: #{e.message}" }
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
Mcp.logger.error("Unexpected error during MCP tool '#{mcp_tool_name_str}' execution: #{e.class} - #{e.message}")
|
|
132
|
+
Mcp.logger.error(e.backtrace.join("\n"))
|
|
133
|
+
{ status: :error, error_message: "Internal Error: #{e.message}" }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|