robot_lab 0.3.0 → 0.5.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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. data/tool_manifest_plan.md +0 -155
@@ -1,403 +0,0 @@
1
- # MCP Server
2
-
3
- Connecting robots to Model Context Protocol servers for external tool access.
4
-
5
- ## Overview
6
-
7
- This example demonstrates how to connect robots to external MCP servers. MCP servers expose tools that robots can discover and invoke automatically. RobotLab supports stdio, HTTP, WebSocket, and SSE transports.
8
-
9
- The runnable version of everything below is
10
- [`examples/04_mcp.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/04_mcp.rb)
11
- (direct `MCP::Client` usage in Part 1, robot integration in Part 2).
12
-
13
- > [!WARNING]
14
- > **Two things are required to actually use MCP tools in a run.** `Robot#run`
15
- > defaults to `mcp: :none, tools: :none`, so build-time `mcp:` servers are never
16
- > connected by a plain `robot.run("...")`. Pass both:
17
- >
18
- > ```ruby
19
- > robot.run("...", mcp: :inherit, tools: :inherit)
20
- > ```
21
- >
22
- > `mcp: :inherit` triggers the connection attempt; `tools: :inherit` is what sends
23
- > the discovered tools to the LLM.
24
-
25
- > [!WARNING]
26
- > `transport:` must be a **nested hash**. A flat `transport: stdio` with sibling
27
- > `command:` / `args:` keys raises
28
- > `NoMethodError: undefined method 'transform_keys' for an instance of String`
29
- > internally — and that error is swallowed, so the robot silently builds with zero
30
- > MCP tools. Connection failures are logged and recorded in
31
- > `robot.failed_mcp_server_names`; they are never raised.
32
-
33
- ## Using MCP with a Robot
34
-
35
- The primary pattern is to pass MCP server configurations via `mcp:` or `mcp_servers:` when building a robot:
36
-
37
- ```ruby
38
- #!/usr/bin/env ruby
39
-
40
- require "bundler/setup"
41
- require "robot_lab"
42
-
43
- # MCP server configuration (stdio transport)
44
- github_server = {
45
- name: "github",
46
- transport: {
47
- type: "stdio",
48
- command: "github-mcp-server",
49
- args: ["stdio"],
50
- env: {
51
- "GITHUB_PERSONAL_ACCESS_TOKEN" => ENV.fetch("GITHUB_PERSONAL_ACCESS_TOKEN", "")
52
- }
53
- }
54
- }
55
-
56
- # Create a robot with MCP server integration
57
- # Tools are automatically discovered when the robot connects
58
- robot = RobotLab.build(
59
- name: "github_assistant",
60
- system_prompt: <<~PROMPT,
61
- You are a GitHub assistant with access to repository tools.
62
- Help users search repos, manage issues, and explore code.
63
- PROMPT
64
- mcp: [github_server],
65
- model: "claude-sonnet-4"
66
- )
67
-
68
- # MCP clients are created lazily. connect_mcp! forces the connection now so the
69
- # counts below are meaningful -- without it both lines print empty results.
70
- robot.connect_mcp!
71
-
72
- puts "MCP Servers: #{robot.mcp_clients.keys.join(", ")}"
73
- puts "MCP Tools: #{robot.mcp_tools.size} discovered"
74
- puts "Failed servers: #{robot.failed_mcp_server_names.join(", ")}" if robot.failed_mcp_server_names.any?
75
-
76
- # Run the robot -- both kwargs are required for it to reach the MCP tools
77
- result = robot.run(
78
- "What are the top 3 most starred Ruby web frameworks on GitHub?",
79
- mcp: :inherit,
80
- tools: :inherit
81
- )
82
- puts result.last_text_content
83
-
84
- # Confirm which tools were sent this turn
85
- puts "Tools sent: #{robot.chat.tools.size}"
86
-
87
- # Always disconnect MCP clients when done
88
- robot.disconnect
89
- ```
90
-
91
- > [!NOTE]
92
- > `result.tool_calls` is effectively always empty — it reads the final assistant
93
- > message, which contains only text once ruby_llm's tool loop has finished. Use the
94
- > `on_tool_call:` callback or the Hook system to observe MCP tool invocations.
95
-
96
- ## Direct MCP Client Usage
97
-
98
- You can also use the MCP client directly without a robot:
99
-
100
- ```ruby
101
- require "robot_lab"
102
-
103
- # Create and connect MCP client
104
- github_server = {
105
- name: "github",
106
- transport: {
107
- type: "stdio",
108
- command: "github-mcp-server",
109
- args: ["stdio"],
110
- env: { "GITHUB_PERSONAL_ACCESS_TOKEN" => ENV["GITHUB_PERSONAL_ACCESS_TOKEN"] }
111
- }
112
- }
113
-
114
- # NOTE: the config is a single POSITIONAL argument.
115
- # MCP::Client.new(name: ..., transport: ...) raises ArgumentError.
116
- client = RobotLab::MCP::Client.new(github_server)
117
- client.connect
118
-
119
- if client.connected?
120
- # List available tools
121
- tools = client.list_tools
122
- tools.each do |tool|
123
- puts "#{tool[:name]}: #{tool[:description]}"
124
- end
125
-
126
- # Call a tool directly
127
- result = client.call_tool("search_repositories", {
128
- query: "language:ruby stars:>1000",
129
- per_page: 5
130
- })
131
-
132
- puts JSON.pretty_generate(result)
133
-
134
- client.disconnect
135
- end
136
- ```
137
-
138
- ## Multiple MCP Servers
139
-
140
- A robot can connect to multiple MCP servers simultaneously:
141
-
142
- ```ruby
143
- filesystem_server = {
144
- name: "filesystem",
145
- transport: {
146
- type: "stdio",
147
- command: "npx",
148
- args: ["@modelcontextprotocol/server-filesystem", "/data"]
149
- }
150
- }
151
-
152
- github_server = {
153
- name: "github",
154
- transport: {
155
- type: "stdio",
156
- command: "github-mcp-server",
157
- args: ["stdio"],
158
- env: { "GITHUB_PERSONAL_ACCESS_TOKEN" => ENV["GITHUB_PERSONAL_ACCESS_TOKEN"] }
159
- }
160
- }
161
-
162
- robot = RobotLab.build(
163
- name: "developer",
164
- system_prompt: "You help with coding tasks using GitHub and the filesystem.",
165
- mcp: [filesystem_server, github_server],
166
- model: "claude-sonnet-4"
167
- )
168
-
169
- result = robot.run(
170
- "Search for Ruby repos with CI configs and list their workflow files",
171
- mcp: :inherit,
172
- tools: :inherit
173
- )
174
- puts result.last_text_content
175
-
176
- robot.disconnect
177
- ```
178
-
179
- ## MCP in Networks
180
-
181
- `mcp` and `tools` are the only two fields a network passes down to its member
182
- robots. Declare them **per task** — the `task` DSL is what reliably reaches the
183
- robot's `run` call.
184
-
185
- ```ruby
186
- # Create robots -- leave mcp:/tools: unset in the constructor
187
- data_analyst = RobotLab.build(
188
- name: "data_analyst",
189
- system_prompt: "You analyze data."
190
- )
191
-
192
- file_manager = RobotLab.build(
193
- name: "file_manager",
194
- system_prompt: "You manage files."
195
- )
196
-
197
- # Per-task MCP + tools configuration
198
- network = RobotLab.create_network(name: "support_with_mcp") do
199
- task :data_analyst, data_analyst,
200
- mcp: [github_server],
201
- tools: :inherit, # send everything discovered
202
- depends_on: :none
203
-
204
- task :file_manager, file_manager,
205
- mcp: [filesystem_server],
206
- tools: %w[read_file list_directory], # allowlist only these tool names
207
- depends_on: :optional
208
- end
209
-
210
- result = network.run(message: "Analyze the project structure")
211
- ```
212
-
213
- > [!WARNING]
214
- > Build-time `:inherit` cuts both ways, so scope it deliberately.
215
- >
216
- > For a **standalone** robot it is a trap: `:inherit` resolves against the global
217
- > level (`:none`), yielding an allowlist of `["none"]` that matches nothing, so the
218
- > robot sends no tools no matter what you pass at run time.
219
- >
220
- > Inside a **network**, build-time `:inherit` is exactly how a robot opts into the
221
- > network's `config:` list — the parent is resolved at run time from
222
- > `network_config`. Verified with a network `config:` of `tools: %w[ReadFile]` on a
223
- > robot holding `ReadFile` and `ListDir`:
224
- >
225
- > | Robot constructor | Task line | Tools sent |
226
- > |---|---|---|
227
- > | `tools: :inherit` | `tools: :inherit` | `[:read_file]` — network allowlist applied |
228
- > | (unset) | `tools: :inherit` | `[:read_file, :list_dir]` — allowlist ignored |
229
- >
230
- > A network-level `config:` also propagates *only* `mcp` and `tools`. LLM fields
231
- > (`model`, `temperature`, `max_tokens`, ...) and callbacks (`on_content`) are read
232
- > from each robot's own config at construction time and are never inherited from
233
- > the network.
234
-
235
- ## Transport Types
236
-
237
- The `type:` field must be one of `stdio`, `sse`, `ws`, `websocket`,
238
- `streamable-http`, or `http`. Anything else raises `ArgumentError` — note that
239
- `streamable_http` with an underscore is **not** valid.
240
-
241
- `timeout:` sits alongside `transport:` on the server config and defaults to 15
242
- seconds. It is normalized: `nil` becomes 15, any value of 1000 or more is treated
243
- as milliseconds (5000 → 5.0s), and anything under 1 second is floored to 1.
244
-
245
- > [!NOTE]
246
- > Only the stdio transport actually enforces `timeout`. The SSE, WebSocket, and
247
- > StreamableHTTP transports store the value and never reference it.
248
-
249
- ## HTTP Transport
250
-
251
- Connect to remote MCP servers over HTTP. Requires the `async-http` gem.
252
-
253
- ```ruby
254
- robot = RobotLab.build(
255
- name: "remote_assistant",
256
- system_prompt: "You have access to remote tools.",
257
- mcp: [
258
- {
259
- name: "remote_api",
260
- timeout: 30,
261
- transport: {
262
- type: "http",
263
- url: "https://mcp.example.com/mcp",
264
- # Auth is supplied by a callable, not a headers hash
265
- auth_provider: -> { "Bearer #{ENV['MCP_TOKEN']}" }
266
- }
267
- }
268
- ]
269
- )
270
-
271
- result = robot.run("Use the remote tools to check system status", mcp: :inherit, tools: :inherit)
272
- robot.disconnect
273
- ```
274
-
275
- > [!WARNING]
276
- > A `headers:` key in a streamable-http transport config is **silently discarded**.
277
- > The transport builds its own header hash on every request and only merges in
278
- > `Authorization` when `auth_provider` is set. Use `auth_provider:` (a proc
279
- > returning the full header value) for authentication.
280
-
281
- > [!WARNING]
282
- > The HTTP and SSE transports set `@connected = true` *before* the MCP
283
- > initialization handshake completes, so `connected?` returns `true` even against
284
- > an unreachable host. Treat it as "a connect was attempted", not "the server
285
- > answered", and check `robot.failed_mcp_server_names` as well.
286
-
287
- ## WebSocket Transport
288
-
289
- For real-time bidirectional communication. Requires the `async-websocket` gem.
290
-
291
- ```ruby
292
- robot = RobotLab.build(
293
- name: "realtime_assistant",
294
- system_prompt: "You monitor real-time events.",
295
- mcp: [
296
- {
297
- name: "realtime",
298
- transport: {
299
- type: "websocket",
300
- url: "ws://localhost:8765"
301
- }
302
- }
303
- ]
304
- )
305
-
306
- result = robot.run("Subscribe to the events channel", mcp: :inherit, tools: :inherit)
307
- robot.disconnect
308
- ```
309
-
310
- > [!CAUTION]
311
- > The WebSocket transport currently raises `NameError` on
312
- > `Async::HTTP::Endpoint` inside an un-awaited `Async` block. It is not usable as
313
- > shipped — prefer stdio or HTTP.
314
-
315
- ## SSE Transport
316
-
317
- Server-Sent Events transport. Requires the `async-http` gem.
318
-
319
- ```ruby
320
- robot = RobotLab.build(
321
- name: "sse_assistant",
322
- system_prompt: "You have access to streaming tools.",
323
- mcp: [
324
- {
325
- name: "streaming_api",
326
- transport: {
327
- type: "sse",
328
- url: "https://api.example.com/sse"
329
- }
330
- }
331
- ]
332
- )
333
-
334
- result = robot.run("Stream the latest metrics", mcp: :inherit, tools: :inherit)
335
- robot.disconnect
336
- ```
337
-
338
- ## Runtime MCP Selection
339
-
340
- `mcp:` and `tools:` on `robot.run` decide, per call, what the LLM sees:
341
-
342
- ```ruby
343
- robot = RobotLab.build(
344
- name: "flexible_bot",
345
- system_prompt: "You use available tools.",
346
- mcp: [github_server]
347
- )
348
-
349
- # Connect the configured MCP servers and send their tools
350
- result = robot.run("Search for Ruby repos", mcp: :inherit, tools: :inherit)
351
-
352
- # The DEFAULT: no MCP connection, no tools sent. Writing these out is
353
- # redundant -- omitting both kwargs does exactly the same thing.
354
- result = robot.run("Just answer from your knowledge")
355
-
356
- # Connect MCP but send only two named tools
357
- result = robot.run(
358
- "List the open issues",
359
- mcp: :inherit,
360
- tools: %w[list_issues get_issue]
361
- )
362
-
363
- robot.disconnect
364
- ```
365
-
366
- > [!NOTE]
367
- > `tools:` is a **name allowlist** across every available tool — local and MCP
368
- > alike. `tools: :none` means "zero tools of any kind", not "MCP only"; there is no
369
- > switch that selects local tools versus MCP tools.
370
-
371
- ## Running
372
-
373
- ```bash
374
- # Set API keys
375
- export ANTHROPIC_API_KEY="your-key"
376
- export GITHUB_PERSONAL_ACCESS_TOKEN="your-token"
377
-
378
- # Install MCP server (example: GitHub)
379
- brew install github-mcp-server
380
-
381
- # Direct MCP::Client usage + robot integration
382
- ruby examples/04_mcp.rb
383
-
384
- # Semantic MCP server selection (no LLM calls)
385
- ruby examples/28_mcp_discovery.rb
386
- ```
387
-
388
- ## Key Concepts
389
-
390
- 1. **MCP Configuration**: Pass server configs via `mcp:` / `mcp_servers:` on `RobotLab.build` or `Robot.new`. `transport:` must be a nested hash
391
- 2. **Nothing is sent by default**: `run` defaults to `mcp: :none, tools: :none`. Pass `mcp: :inherit, tools: :inherit` to connect and send
392
- 3. **Lazy connection**: MCP clients are created on the first qualifying `run`. Call `robot.connect_mcp!` to connect eagerly — before that, `mcp_clients` and `mcp_tools` are empty
393
- 4. **Failures are silent**: connection errors are logged and recorded in `robot.failed_mcp_server_names`, never raised
394
- 5. **Transport Types**: `stdio`, `sse`, `ws`, `websocket`, `streamable-http`, `http` (`streamable_http` is invalid)
395
- 6. **Tool Filtering**: `tools:` is an allowlist over local *and* MCP tools combined; entries must match how each tool was attached (class-attached → `"ReadFile"`, instance-attached → `"read_file"`)
396
- 7. **Networks**: an explicit list on the `task` line applies directly; to inherit the network's `config:` list instead, build the robot with `tools: :inherit` *and* pass `tools: :inherit` on the task
397
- 8. **Cleanup**: Always call `robot.disconnect` when done to release MCP connections
398
-
399
- ## See Also
400
-
401
- - [MCP Integration Guide](../guides/mcp-integration.md)
402
- - [MCP API Reference](../api/mcp/index.md)
403
- - [Transports](../api/mcp/transports.md)