robot_lab 0.2.8 → 0.3.1

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +16 -18
  4. data/lib/robot_lab/ask_user.rb +3 -3
  5. data/lib/robot_lab/config/defaults.yml +5 -5
  6. data/lib/robot_lab/config.rb +2 -2
  7. data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
  8. data/lib/robot_lab/memory.rb +16 -7
  9. data/lib/robot_lab/robot/hooking.rb +26 -0
  10. data/lib/robot_lab/robot/result_building.rb +119 -0
  11. data/lib/robot_lab/robot.rb +37 -120
  12. data/lib/robot_lab/run_config.rb +52 -20
  13. data/lib/robot_lab/tool.rb +7 -12
  14. data/lib/robot_lab/version.rb +1 -1
  15. data/lib/robot_lab.rb +3 -3
  16. metadata +15 -274
  17. data/.architecture/AGENTS.md +0 -32
  18. data/.architecture/config.yml +0 -8
  19. data/.architecture/members.yml +0 -60
  20. data/.architecture/reviews/feature-free-will.md +0 -490
  21. data/.architecture/reviews/overall-codebase.md +0 -427
  22. data/.claude/settings.local.json +0 -57
  23. data/.codex/config.toml +0 -2
  24. data/.envrc +0 -6
  25. data/.github/workflows/deploy-github-pages.yml +0 -52
  26. data/.irbrc +0 -6
  27. data/.loki +0 -65
  28. data/.quality/reek_baseline.txt +0 -43
  29. data/.rubocop.yml +0 -10
  30. data/Archspec.rb +0 -44
  31. data/CLAUDE.md +0 -139
  32. data/COMMITS.md +0 -196
  33. data/Rakefile +0 -193
  34. data/_typos.toml +0 -21
  35. data/agent2agent_review.md +0 -192
  36. data/agentf_improvements.md +0 -253
  37. data/agents.md +0 -14
  38. data/docs/api/core/index.md +0 -147
  39. data/docs/api/core/memory.md +0 -578
  40. data/docs/api/core/network.md +0 -553
  41. data/docs/api/core/result.md +0 -211
  42. data/docs/api/core/robot.md +0 -1667
  43. data/docs/api/core/state.md +0 -145
  44. data/docs/api/core/tool.md +0 -520
  45. data/docs/api/errors.md +0 -323
  46. data/docs/api/hooks.md +0 -469
  47. data/docs/api/index.md +0 -137
  48. data/docs/api/mcp/client.md +0 -387
  49. data/docs/api/mcp/index.md +0 -267
  50. data/docs/api/mcp/server.md +0 -252
  51. data/docs/api/mcp/transports.md +0 -339
  52. data/docs/api/messages/index.md +0 -126
  53. data/docs/api/messages/text-message.md +0 -170
  54. data/docs/api/messages/tool-call-message.md +0 -206
  55. data/docs/api/messages/tool-result-message.md +0 -242
  56. data/docs/api/messages/user-message.md +0 -232
  57. data/docs/api/skills.md +0 -423
  58. data/docs/api/streaming/context.md +0 -280
  59. data/docs/api/streaming/events.md +0 -185
  60. data/docs/api/streaming/index.md +0 -156
  61. data/docs/api/support.md +0 -846
  62. data/docs/architecture/core-concepts.md +0 -575
  63. data/docs/architecture/index.md +0 -252
  64. data/docs/architecture/message-flow.md +0 -366
  65. data/docs/architecture/network-orchestration.md +0 -478
  66. data/docs/architecture/robot-execution.md +0 -396
  67. data/docs/architecture/state-management.md +0 -410
  68. data/docs/assets/css/custom.css +0 -56
  69. data/docs/assets/images/robot_lab.jpg +0 -0
  70. data/docs/concepts.md +0 -641
  71. data/docs/examples/basic-chat.md +0 -258
  72. data/docs/examples/index.md +0 -425
  73. data/docs/examples/mcp-server.md +0 -403
  74. data/docs/examples/multi-robot-network.md +0 -476
  75. data/docs/examples/tool-usage.md +0 -468
  76. data/docs/getting-started/configuration.md +0 -645
  77. data/docs/getting-started/index.md +0 -56
  78. data/docs/getting-started/installation.md +0 -200
  79. data/docs/getting-started/quick-start.md +0 -293
  80. data/docs/guides/building-robots.md +0 -1187
  81. data/docs/guides/creating-networks.md +0 -736
  82. data/docs/guides/hooks.md +0 -1206
  83. data/docs/guides/index.md +0 -78
  84. data/docs/guides/knowledge.md +0 -219
  85. data/docs/guides/mcp-integration.md +0 -740
  86. data/docs/guides/memory.md +0 -458
  87. data/docs/guides/observability.md +0 -733
  88. data/docs/guides/streaming.md +0 -323
  89. data/docs/guides/using-tools.md +0 -780
  90. data/docs/index.md +0 -280
  91. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  92. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  93. data/examples/.envrc +0 -1
  94. data/examples/01_simple_robot.rb +0 -31
  95. data/examples/02_tools.rb +0 -109
  96. data/examples/03_network.rb +0 -118
  97. data/examples/04_mcp.rb +0 -215
  98. data/examples/05_streaming.rb +0 -137
  99. data/examples/06_prompt_templates.rb +0 -325
  100. data/examples/07_network_memory.rb +0 -313
  101. data/examples/08_llm_config.rb +0 -162
  102. data/examples/09_chaining.rb +0 -251
  103. data/examples/10_memory.rb +0 -315
  104. data/examples/11_network_introspection.rb +0 -239
  105. data/examples/12_message_bus.rb +0 -67
  106. data/examples/13_spawn.rb +0 -85
  107. data/examples/14_rusty_circuit/.envrc +0 -1
  108. data/examples/14_rusty_circuit/.gitignore +0 -1
  109. data/examples/14_rusty_circuit/README.md +0 -1
  110. data/examples/14_rusty_circuit/comic.rb +0 -149
  111. data/examples/14_rusty_circuit/display.rb +0 -217
  112. data/examples/14_rusty_circuit/heckler.rb +0 -65
  113. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  114. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  115. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  116. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  117. data/examples/14_rusty_circuit/scout.rb +0 -165
  118. data/examples/14_rusty_circuit/show.log +0 -234
  119. data/examples/15_memory_network_and_bus/.envrc +0 -1
  120. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  121. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  122. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  123. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  124. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  125. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
  126. data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
  127. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  128. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  129. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  130. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
  131. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
  132. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  133. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  134. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  135. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  136. data/examples/16_writers_room/.envrc +0 -1
  137. data/examples/16_writers_room/display.rb +0 -158
  138. data/examples/16_writers_room/output/.gitignore +0 -4
  139. data/examples/16_writers_room/output/README.md +0 -69
  140. data/examples/16_writers_room/output/opus_001.md +0 -263
  141. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  142. data/examples/16_writers_room/output/opus_002.md +0 -245
  143. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  144. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  145. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  146. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  147. data/examples/16_writers_room/prompts/writer.md +0 -37
  148. data/examples/16_writers_room/room.rb +0 -186
  149. data/examples/16_writers_room/tools.rb +0 -173
  150. data/examples/16_writers_room/writer.rb +0 -121
  151. data/examples/16_writers_room/writers_room.rb +0 -256
  152. data/examples/17_skills.rb +0 -234
  153. data/examples/18_rails/.envrc +0 -3
  154. data/examples/18_rails/.gitignore +0 -5
  155. data/examples/18_rails/Gemfile +0 -11
  156. data/examples/18_rails/README.md +0 -67
  157. data/examples/18_rails/Rakefile +0 -4
  158. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  159. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  160. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  161. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  162. data/examples/18_rails/app/models/application_record.rb +0 -5
  163. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  164. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  165. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  166. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  167. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  168. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  169. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  170. data/examples/18_rails/bin/dev +0 -7
  171. data/examples/18_rails/bin/rails +0 -6
  172. data/examples/18_rails/bin/setup +0 -15
  173. data/examples/18_rails/config/application.rb +0 -33
  174. data/examples/18_rails/config/cable.yml +0 -2
  175. data/examples/18_rails/config/database.yml +0 -5
  176. data/examples/18_rails/config/environment.rb +0 -4
  177. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  178. data/examples/18_rails/config/routes.rb +0 -6
  179. data/examples/18_rails/config.ru +0 -4
  180. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  181. data/examples/19_token_tracking.rb +0 -138
  182. data/examples/20_circuit_breaker.rb +0 -147
  183. data/examples/21_learning_loop.rb +0 -181
  184. data/examples/22_context_compression.rb +0 -172
  185. data/examples/23_convergence.rb +0 -133
  186. data/examples/24_structured_delegation.rb +0 -151
  187. data/examples/25_history_search/conversation.jsonl +0 -30
  188. data/examples/25_history_search.rb +0 -133
  189. data/examples/26_document_store/api_versioning_adr.md +0 -52
  190. data/examples/26_document_store/incident_postmortem.md +0 -46
  191. data/examples/26_document_store/postgres_runbook.md +0 -49
  192. data/examples/26_document_store/redis_caching_guide.md +0 -48
  193. data/examples/26_document_store/sidekiq_guide.md +0 -51
  194. data/examples/26_document_store.rb +0 -140
  195. data/examples/27_incident_response/incident_response.rb +0 -261
  196. data/examples/28_mcp_discovery.rb +0 -113
  197. data/examples/29_ractor_tools.rb +0 -240
  198. data/examples/30_ractor_network.rb +0 -252
  199. data/examples/31_launch_assessment.rb +0 -246
  200. data/examples/32_newsletter_reader.rb +0 -193
  201. data/examples/33_stock_generator.rb +0 -80
  202. data/examples/33_stock_predictor.rb +0 -327
  203. data/examples/34_agentskills.rb +0 -76
  204. data/examples/35_hooks.rb +0 -266
  205. data/examples/README.md +0 -533
  206. data/examples/common.rb +0 -148
  207. data/examples/prompts/assistant.md +0 -5
  208. data/examples/prompts/audit_trail.md +0 -8
  209. data/examples/prompts/billing.md +0 -10
  210. data/examples/prompts/classifier.md +0 -7
  211. data/examples/prompts/comedian.md +0 -6
  212. data/examples/prompts/comedy_critic.md +0 -10
  213. data/examples/prompts/configurable.md +0 -9
  214. data/examples/prompts/dispatcher.md +0 -12
  215. data/examples/prompts/entity_extractor.md +0 -14
  216. data/examples/prompts/escalation.md +0 -42
  217. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  218. data/examples/prompts/frontmatter_named_test.md +0 -5
  219. data/examples/prompts/frontmatter_tools_test.md +0 -6
  220. data/examples/prompts/general.md +0 -7
  221. data/examples/prompts/github_assistant.md +0 -14
  222. data/examples/prompts/helper.md +0 -4
  223. data/examples/prompts/incident_responder.md +0 -18
  224. data/examples/prompts/keyword_extractor.md +0 -11
  225. data/examples/prompts/llm_config_demo.md +0 -20
  226. data/examples/prompts/order_support.md +0 -35
  227. data/examples/prompts/os_advocate.md +0 -13
  228. data/examples/prompts/os_chief.md +0 -13
  229. data/examples/prompts/os_editor.md +0 -13
  230. data/examples/prompts/parameterized_main_test.md +0 -6
  231. data/examples/prompts/pii_redactor.md +0 -7
  232. data/examples/prompts/product_support.md +0 -37
  233. data/examples/prompts/runbook_protocol.md +0 -12
  234. data/examples/prompts/sentiment_analyzer.md +0 -12
  235. data/examples/prompts/skill_a_test.md +0 -4
  236. data/examples/prompts/skill_b_test.md +0 -4
  237. data/examples/prompts/skill_config_test.md +0 -5
  238. data/examples/prompts/skill_cycle_a_test.md +0 -6
  239. data/examples/prompts/skill_cycle_b_test.md +0 -6
  240. data/examples/prompts/skill_description_test.md +0 -4
  241. data/examples/prompts/skill_leaf_test.md +0 -4
  242. data/examples/prompts/skill_nested_test.md +0 -6
  243. data/examples/prompts/skill_refs_main_test.md +0 -6
  244. data/examples/prompts/skill_self_ref_test.md +0 -6
  245. data/examples/prompts/skill_with_mcp_test.md +0 -9
  246. data/examples/prompts/skill_with_params_test.md +0 -6
  247. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  248. data/examples/prompts/skill_with_tools_test.md +0 -6
  249. data/examples/prompts/sre_compliance.md +0 -10
  250. data/examples/prompts/structured_output.md +0 -7
  251. data/examples/prompts/synthesizer.md +0 -17
  252. data/examples/prompts/technical.md +0 -10
  253. data/examples/prompts/template_with_skills_test.md +0 -6
  254. data/examples/prompts/triage.md +0 -22
  255. data/examples/ruboruby.md +0 -423
  256. data/examples/xyzzy.rb +0 -97
  257. data/logfile +0 -8
  258. data/mkdocs.yml +0 -209
  259. data/robot_concurrency.md +0 -38
  260. data/simple_acp_review.md +0 -298
  261. data/temp.md +0 -6
  262. 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)