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.
Files changed (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. 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