robot_lab 0.0.1 → 0.0.4

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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +90 -0
  5. data/README.md +203 -46
  6. data/Rakefile +70 -1
  7. data/docs/api/core/index.md +12 -0
  8. data/docs/api/core/robot.md +478 -130
  9. data/docs/api/core/tool.md +205 -209
  10. data/docs/api/history/active-record-adapter.md +174 -94
  11. data/docs/api/history/config.md +186 -93
  12. data/docs/api/history/index.md +57 -61
  13. data/docs/api/history/thread-manager.md +123 -73
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/streaming/context.md +157 -74
  19. data/docs/api/streaming/events.md +114 -166
  20. data/docs/api/streaming/index.md +74 -72
  21. data/docs/architecture/core-concepts.md +361 -112
  22. data/docs/architecture/index.md +97 -59
  23. data/docs/architecture/message-flow.md +138 -129
  24. data/docs/architecture/network-orchestration.md +197 -50
  25. data/docs/architecture/robot-execution.md +199 -146
  26. data/docs/architecture/state-management.md +255 -187
  27. data/docs/concepts.md +312 -48
  28. data/docs/examples/basic-chat.md +89 -77
  29. data/docs/examples/index.md +222 -47
  30. data/docs/examples/mcp-server.md +207 -203
  31. data/docs/examples/multi-robot-network.md +129 -35
  32. data/docs/examples/rails-application.md +159 -160
  33. data/docs/examples/tool-usage.md +295 -204
  34. data/docs/getting-started/configuration.md +275 -162
  35. data/docs/getting-started/index.md +1 -1
  36. data/docs/getting-started/installation.md +22 -13
  37. data/docs/getting-started/quick-start.md +166 -121
  38. data/docs/guides/building-robots.md +417 -212
  39. data/docs/guides/creating-networks.md +94 -24
  40. data/docs/guides/mcp-integration.md +152 -113
  41. data/docs/guides/memory.md +220 -164
  42. data/docs/guides/streaming.md +80 -110
  43. data/docs/guides/using-tools.md +259 -212
  44. data/docs/index.md +50 -37
  45. data/examples/01_simple_robot.rb +6 -9
  46. data/examples/02_tools.rb +6 -9
  47. data/examples/03_network.rb +13 -14
  48. data/examples/04_mcp.rb +5 -8
  49. data/examples/05_streaming.rb +5 -8
  50. data/examples/06_prompt_templates.rb +42 -37
  51. data/examples/07_network_memory.rb +13 -14
  52. data/examples/08_llm_config.rb +140 -0
  53. data/examples/09_chaining.rb +223 -0
  54. data/examples/10_memory.rb +331 -0
  55. data/examples/11_network_introspection.rb +230 -0
  56. data/examples/12_message_bus.rb +74 -0
  57. data/examples/13_spawn.rb +90 -0
  58. data/examples/14_rusty_circuit/comic.rb +143 -0
  59. data/examples/14_rusty_circuit/display.rb +203 -0
  60. data/examples/14_rusty_circuit/heckler.rb +57 -0
  61. data/examples/14_rusty_circuit/open_mic.rb +121 -0
  62. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  63. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  64. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  65. data/examples/14_rusty_circuit/scout.rb +173 -0
  66. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  67. data/examples/14_rusty_circuit/show.log +234 -0
  68. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  69. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  70. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  71. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  72. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  73. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  74. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  75. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  76. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  77. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  78. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  79. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  80. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  81. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  82. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  83. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  84. data/examples/README.md +197 -0
  85. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  86. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  87. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  88. data/examples/prompts/comedian.md +6 -0
  89. data/examples/prompts/comedy_critic.md +10 -0
  90. data/examples/prompts/configurable.md +9 -0
  91. data/examples/prompts/dispatcher.md +12 -0
  92. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  93. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  94. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  95. data/examples/prompts/frontmatter_named_test.md +5 -0
  96. data/examples/prompts/frontmatter_tools_test.md +6 -0
  97. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  98. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  99. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  100. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  101. data/examples/prompts/llm_config_demo.md +20 -0
  102. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  103. data/examples/prompts/os_advocate.md +13 -0
  104. data/examples/prompts/os_chief.md +13 -0
  105. data/examples/prompts/os_editor.md +13 -0
  106. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  107. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  108. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  109. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  110. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  111. data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
  112. data/lib/robot_lab/adapters/openai.rb +2 -1
  113. data/lib/robot_lab/ask_user.rb +75 -0
  114. data/lib/robot_lab/config/defaults.yml +121 -0
  115. data/lib/robot_lab/config.rb +183 -0
  116. data/lib/robot_lab/error.rb +6 -0
  117. data/lib/robot_lab/mcp/client.rb +1 -1
  118. data/lib/robot_lab/memory.rb +2 -2
  119. data/lib/robot_lab/robot.rb +523 -249
  120. data/lib/robot_lab/robot_message.rb +44 -0
  121. data/lib/robot_lab/robot_result.rb +1 -0
  122. data/lib/robot_lab/robotic_model.rb +1 -1
  123. data/lib/robot_lab/streaming/context.rb +1 -1
  124. data/lib/robot_lab/tool.rb +108 -172
  125. data/lib/robot_lab/tool_config.rb +1 -1
  126. data/lib/robot_lab/tool_manifest.rb +2 -18
  127. data/lib/robot_lab/version.rb +1 -1
  128. data/lib/robot_lab.rb +66 -55
  129. metadata +107 -116
  130. data/examples/prompts/assistant/user.txt.erb +0 -1
  131. data/examples/prompts/billing/user.txt.erb +0 -1
  132. data/examples/prompts/classifier/user.txt.erb +0 -1
  133. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  134. data/examples/prompts/escalation/user.txt.erb +0 -34
  135. data/examples/prompts/general/user.txt.erb +0 -1
  136. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  137. data/examples/prompts/helper/user.txt.erb +0 -1
  138. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  139. data/examples/prompts/order_support/user.txt.erb +0 -22
  140. data/examples/prompts/product_support/user.txt.erb +0 -32
  141. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  142. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  143. data/examples/prompts/technical/user.txt.erb +0 -1
  144. data/examples/prompts/triage/user.txt.erb +0 -17
  145. data/lib/robot_lab/configuration.rb +0 -143
@@ -38,6 +38,20 @@ network = RobotLab.create_network(name: "parallel", concurrency: :threads) do
38
38
  end
39
39
  ```
40
40
 
41
+ ### Shared Memory
42
+
43
+ Networks provide a shared memory accessible to all robots:
44
+
45
+ ```ruby
46
+ network = RobotLab.create_network(name: "pipeline") do
47
+ task :first, robot1, depends_on: :none
48
+ end
49
+
50
+ # Pre-populate shared memory
51
+ network.memory[:project] = "Q4 Report"
52
+ network.memory[:user_id] = 123
53
+ ```
54
+
41
55
  ## Adding Tasks
42
56
 
43
57
  ### Sequential Tasks
@@ -72,7 +86,7 @@ end
72
86
 
73
87
  ### Optional Tasks
74
88
 
75
- Optional tasks only run when explicitly activated:
89
+ Optional tasks only run when explicitly activated by a preceding robot:
76
90
 
77
91
  ```ruby
78
92
  network = RobotLab.create_network(name: "router") do
@@ -85,7 +99,7 @@ end
85
99
 
86
100
  ## Per-Task Configuration
87
101
 
88
- Tasks can have individual context and configuration that's deep-merged with the network's run parameters:
102
+ Tasks can have individual context and configuration that is deep-merged with the network's run parameters:
89
103
 
90
104
  ```ruby
91
105
  network = RobotLab.create_network(name: "support") do
@@ -105,8 +119,8 @@ end
105
119
  | Option | Description |
106
120
  |--------|-------------|
107
121
  | `context` | Hash merged with run params (task values override) |
108
- | `mcp` | MCP servers for this task |
109
- | `tools` | Tools available to this task |
122
+ | `mcp` | MCP servers for this task (`:none`, `:inherit`, or array) |
123
+ | `tools` | Tools available to this task (`:none`, `:inherit`, or array) |
110
124
  | `memory` | Task-specific memory |
111
125
  | `depends_on` | `:none`, `[:task1]`, or `:optional` |
112
126
 
@@ -117,7 +131,9 @@ Use optional tasks with custom Robot subclasses for intelligent routing:
117
131
  ```ruby
118
132
  class ClassifierRobot < RobotLab::Robot
119
133
  def call(result)
120
- robot_result = run(**extract_run_context(result))
134
+ context = extract_run_context(result)
135
+ message = context.delete(:message)
136
+ robot_result = run(message, **context)
121
137
 
122
138
  new_result = result
123
139
  .with_context(@name.to_sym, robot_result)
@@ -194,6 +210,25 @@ result.halted? # Whether execution was halted early
194
210
  result.continued? # Whether execution continued normally
195
211
  ```
196
212
 
213
+ ## Broadcasting
214
+
215
+ Networks support a broadcast channel for network-wide announcements:
216
+
217
+ ```ruby
218
+ # Register a broadcast handler
219
+ network.on_broadcast do |message|
220
+ case message[:payload][:event]
221
+ when :pause
222
+ puts "Pausing: #{message[:payload][:reason]}"
223
+ when :phase_complete
224
+ puts "Phase complete: #{message[:payload][:phase]}"
225
+ end
226
+ end
227
+
228
+ # Send broadcasts during execution
229
+ network.broadcast(event: :phase_complete, phase: "analysis")
230
+ ```
231
+
197
232
  ## Patterns
198
233
 
199
234
  ### Classifier Pattern
@@ -203,7 +238,10 @@ Route to specialists based on classification:
203
238
  ```ruby
204
239
  class SupportClassifier < RobotLab::Robot
205
240
  def call(result)
206
- robot_result = run(**extract_run_context(result))
241
+ context = extract_run_context(result)
242
+ message = context.delete(:message)
243
+ robot_result = run(message, **context)
244
+
207
245
  new_result = result
208
246
  .with_context(@name.to_sym, robot_result)
209
247
  .continue(robot_result)
@@ -259,7 +297,9 @@ A robot can halt execution early:
259
297
  ```ruby
260
298
  class ValidatorRobot < RobotLab::Robot
261
299
  def call(result)
262
- robot_result = run(**extract_run_context(result))
300
+ context = extract_run_context(result)
301
+ message = context.delete(:message)
302
+ robot_result = run(message, **context)
263
303
 
264
304
  if robot_result.last_text_content.include?("INVALID")
265
305
  # Stop the pipeline
@@ -274,6 +314,30 @@ class ValidatorRobot < RobotLab::Robot
274
314
  end
275
315
  ```
276
316
 
317
+ ### Data Passing Between Tasks
318
+
319
+ Access previous task results via context:
320
+
321
+ ```ruby
322
+ class ResponderRobot < RobotLab::Robot
323
+ def call(result)
324
+ # Get classifier's output
325
+ classification = result.context[:classifier]&.last_text_content
326
+
327
+ context = extract_run_context(result)
328
+ message = context.delete(:message)
329
+
330
+ # Use classification in the message or context
331
+ robot_result = run(
332
+ "Classification: #{classification}\n\nUser message: #{message}",
333
+ **context
334
+ )
335
+
336
+ result.with_context(@name.to_sym, robot_result).continue(robot_result)
337
+ end
338
+ end
339
+ ```
340
+
277
341
  ## Visualization
278
342
 
279
343
  ### ASCII Visualization
@@ -290,6 +354,13 @@ puts network.to_mermaid
290
354
  # => Mermaid graph definition
291
355
  ```
292
356
 
357
+ ### DOT Format (Graphviz)
358
+
359
+ ```ruby
360
+ puts network.to_dot
361
+ # => Graphviz DOT format
362
+ ```
363
+
293
364
  ### Execution Plan
294
365
 
295
366
  ```ruby
@@ -305,6 +376,7 @@ network.robots # => Hash of name => Robot
305
376
  network.robot(:billing) # => Robot instance
306
377
  network["billing"] # => Robot instance (alias)
307
378
  network.available_robots # => Array of Robot instances
379
+ network.memory # => Memory instance (shared)
308
380
  network.to_h # => Hash representation
309
381
  ```
310
382
 
@@ -323,25 +395,14 @@ task :respond, responder, depends_on: [:classify]
323
395
  task :do_everything, mega_robot, depends_on: :none
324
396
  ```
325
397
 
326
- ### 2. Use Context for Data Passing
398
+ ### 2. Use Per-Task Context
327
399
 
328
- Access previous results via context:
400
+ Pass task-specific configuration through context:
329
401
 
330
402
  ```ruby
331
- class ResponderRobot < RobotLab::Robot
332
- def call(result)
333
- # Get classifier's output
334
- classification = result.context[:classifier]&.last_text_content
335
-
336
- # Use it in this robot's run
337
- robot_result = run(
338
- **extract_run_context(result),
339
- classification: classification
340
- )
341
-
342
- result.with_context(@name.to_sym, robot_result).continue(robot_result)
343
- end
344
- end
403
+ task :billing, billing_robot,
404
+ context: { department: "billing", max_refund: 500 },
405
+ depends_on: :optional
345
406
  ```
346
407
 
347
408
  ### 3. Handle Missing Results
@@ -359,8 +420,17 @@ def call(result)
359
420
  end
360
421
  ```
361
422
 
423
+ ### 4. Reset Memory Between Runs
424
+
425
+ If reusing a network, reset shared memory between runs:
426
+
427
+ ```ruby
428
+ network.reset_memory
429
+ result = network.run(message: "New request")
430
+ ```
431
+
362
432
  ## Next Steps
363
433
 
364
434
  - [Using Tools](using-tools.md) - Add capabilities to robots
365
- - [Memory Guide](memory.md) - Persistent memory across runs
435
+ - [Memory Guide](memory.md) - Shared memory patterns
366
436
  - [API Reference: Network](../api/core/network.md) - Complete API
@@ -12,13 +12,15 @@ MCP is a protocol that allows LLM applications to connect to external servers th
12
12
 
13
13
  ## Configuring MCP Servers
14
14
 
15
- ### At Network Level
15
+ ### At Robot Level
16
16
 
17
- ```ruby
18
- network = RobotLab.create_network do
19
- name "dev_assistant"
17
+ Use the `mcp:` parameter on `RobotLab.build` to connect a robot to MCP servers:
20
18
 
21
- mcp [
19
+ ```ruby
20
+ robot = RobotLab.build(
21
+ name: "coder",
22
+ template: :developer,
23
+ mcp: [
22
24
  {
23
25
  name: "filesystem",
24
26
  transport: {
@@ -31,40 +33,72 @@ network = RobotLab.create_network do
31
33
  name: "github",
32
34
  transport: {
33
35
  type: "stdio",
34
- command: "mcp-server-github"
36
+ command: "mcp-server-github",
37
+ env: { "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"] }
35
38
  }
36
39
  }
37
40
  ]
38
- end
41
+ )
39
42
  ```
40
43
 
41
- ### At Robot Level
44
+ ### In Template Front Matter
45
+
46
+ MCP servers can be declared directly in a template's YAML front matter, making the template fully self-contained:
47
+
48
+ ```markdown title="prompts/github_assistant.md"
49
+ ---
50
+ description: GitHub assistant with MCP tool access
51
+ mcp:
52
+ - name: github
53
+ transport: stdio
54
+ command: npx
55
+ args: ["-y", "@modelcontextprotocol/server-github"]
56
+ ---
57
+ You are a helpful GitHub assistant with access to GitHub tools via MCP.
58
+ ```
42
59
 
43
60
  ```ruby
44
- robot = RobotLab.build do
45
- name "coder"
61
+ # MCP config comes from the template — no mcp: parameter needed
62
+ robot = RobotLab.build(template: :github_assistant)
63
+ ```
46
64
 
47
- # Use network's MCP servers
48
- mcp :inherit
65
+ Constructor `mcp:` overrides frontmatter `mcp:` when provided.
49
66
 
50
- # Or specific servers
51
- mcp [
52
- { name: "filesystem", transport: { type: "stdio", command: "mcp-fs" } }
53
- ]
67
+ ### Hierarchical Configuration
54
68
 
55
- # Or disable MCP
56
- mcp :none
57
- end
58
- ```
69
+ The `mcp:` parameter supports three modes:
59
70
 
60
- ### Global Configuration
71
+ | Value | Behavior |
72
+ |-------|----------|
73
+ | `:none` | No MCP servers (default) |
74
+ | `:inherit` | Inherit from network or global config |
75
+ | `[...]` | Explicit array of server configurations |
61
76
 
62
77
  ```ruby
63
- RobotLab.configure do |config|
64
- config.mcp = [
65
- { name: "common_tools", transport: { type: "stdio", command: "common-mcp" } }
66
- ]
67
- end
78
+ # Inherit from network/config
79
+ robot = RobotLab.build(
80
+ name: "reader",
81
+ system_prompt: "You help read files.",
82
+ mcp: :inherit
83
+ )
84
+
85
+ # Disable MCP explicitly
86
+ robot = RobotLab.build(
87
+ name: "calculator",
88
+ system_prompt: "You do math.",
89
+ mcp: :none
90
+ )
91
+ ```
92
+
93
+ ### Resolution Order
94
+
95
+ MCP configuration resolves through a hierarchy: **runtime > robot build > network > global config**. Each level can override the previous:
96
+
97
+ ```
98
+ Global (RobotLab.config.mcp)
99
+ -> Network (task mcp: [...])
100
+ -> Robot (mcp: :inherit | :none | [...])
101
+ -> Runtime (robot.run("msg", mcp: [...]))
68
102
  ```
69
103
 
70
104
  ## Transport Types
@@ -134,68 +168,54 @@ Streamable HTTP transport with session support:
134
168
 
135
169
  ## Using MCP Tools
136
170
 
137
- Once configured, MCP tools are automatically available to robots:
171
+ Once configured, MCP tools are automatically discovered and made available to the robot. The robot connects to MCP servers on its first `run` call and discovers tools dynamically:
138
172
 
139
173
  ```ruby
140
- network = RobotLab.create_network do
141
- mcp [
174
+ robot = RobotLab.build(
175
+ name: "helper",
176
+ system_prompt: <<~PROMPT
177
+ You can help users with GitHub tasks.
178
+ Use available tools to search repositories, create issues, etc.
179
+ PROMPT,
180
+ mcp: [
142
181
  { name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
143
182
  ]
183
+ )
144
184
 
145
- add_robot RobotLab.build {
146
- name "helper"
147
- template <<~PROMPT
148
- You can help users with GitHub tasks.
149
- Use available tools to search repositories, create issues, etc.
150
- PROMPT
151
- }
152
- end
153
-
154
- # The robot can now use GitHub MCP tools
155
- state = RobotLab.create_state(message: "Find repositories about machine learning")
156
- network.run(state: state)
185
+ # MCP tools are automatically available
186
+ result = robot.run("Find repositories about machine learning")
187
+ puts result.last_text_content
157
188
  ```
158
189
 
159
190
  ## Filtering MCP Tools
160
191
 
161
- Restrict which MCP tools are available:
192
+ Use the `tools:` parameter to restrict which tools (including MCP-discovered tools) are available to a robot:
162
193
 
163
194
  ```ruby
164
- robot = RobotLab.build do
165
- name "reader"
166
- mcp :inherit
167
-
168
- # Only allow specific MCP tools
169
- tools %w[read_file search_code list_directory]
170
- end
171
- ```
172
-
173
- ## MCP Server Configuration
174
-
175
- ### Server Object
176
-
177
- ```ruby
178
- server = RobotLab::MCP::Server.new(
179
- name: "my_server",
180
- transport: {
181
- type: "stdio",
182
- command: "my-mcp-server"
183
- }
195
+ robot = RobotLab.build(
196
+ name: "reader",
197
+ system_prompt: "You help read and search files.",
198
+ mcp: [
199
+ { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
200
+ ],
201
+ tools: %w[read_file search_files list_directory] # Only allow specific tools
184
202
  )
185
-
186
- server.name # => "my_server"
187
- server.transport_type # => "stdio"
188
- server.to_h # Hash representation
189
203
  ```
190
204
 
191
- ### Client Object
205
+ ## MCP in Networks
192
206
 
193
- ```ruby
194
- client = RobotLab::MCP::Client.new(server: server)
195
- client.connect
207
+ When running robots in a network, use per-task MCP configuration:
196
208
 
197
- client.connected? # => true
198
- client.to_h # Client info
209
+ ```ruby
210
+ network = RobotLab.create_network(name: "dev_pipeline") do
211
+ task :planner, planner_robot, depends_on: :none
212
+ task :coder, coder_robot,
213
+ mcp: [
214
+ { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
215
+ ],
216
+ depends_on: [:planner]
217
+ task :reviewer, reviewer_robot, depends_on: [:coder]
218
+ end
199
219
  ```
200
220
 
201
221
  ## Common MCP Servers
@@ -245,54 +265,70 @@ Tools: `search_repositories`, `create_issue`, `get_file_contents`, etc.
245
265
 
246
266
  Tools: `query`, `list_tables`, `describe_table`
247
267
 
268
+ ## MCP Server and Client Objects
269
+
270
+ For programmatic access, you can work with MCP objects directly:
271
+
272
+ ```ruby
273
+ # Server configuration
274
+ server = RobotLab::MCP::Server.new(
275
+ name: "my_server",
276
+ transport: {
277
+ type: "stdio",
278
+ command: "my-mcp-server"
279
+ }
280
+ )
281
+
282
+ # Client connection
283
+ client = RobotLab::MCP::Client.new(server)
284
+ client.connect
285
+
286
+ client.connected? # => true
287
+ client.list_tools # => Array of tool definitions
288
+ client.call_tool("search", { query: "ruby" })
289
+ client.list_resources # => Array of resource definitions
290
+ client.disconnect
291
+ ```
292
+
248
293
  ## Error Handling
249
294
 
250
295
  ### Connection Errors
251
296
 
252
297
  ```ruby
253
298
  begin
254
- network.run(state: state)
299
+ result = robot.run("Search for repos")
255
300
  rescue RobotLab::MCPError => e
256
301
  puts "MCP Error: #{e.message}"
257
- # Handle gracefully
258
302
  end
259
303
  ```
260
304
 
261
- ### Missing Dependencies
262
-
263
- ```ruby
264
- # If async-websocket not installed
265
- rescue RobotLab::MCPError => e
266
- if e.message.include?("async-websocket")
267
- puts "Install async-websocket gem for WebSocket support"
268
- end
269
- end
270
- ```
305
+ !!! tip
306
+ MCP connection failures are logged as warnings but do not raise errors by default. The robot will continue without MCP tools if a server is unreachable.
271
307
 
272
308
  ## Disconnecting
273
309
 
274
- Robots automatically disconnect from MCP servers when done:
310
+ Robots can be manually disconnected from MCP servers:
275
311
 
276
312
  ```ruby
277
- robot.disconnect # Manually disconnect
313
+ robot.disconnect # Disconnect all MCP clients
278
314
  ```
279
315
 
280
- Networks handle this automatically at the end of a run.
281
-
282
316
  ## Patterns
283
317
 
284
318
  ### Development vs Production
285
319
 
286
320
  ```ruby
287
- network = RobotLab.create_network do
288
- mcp_config = if Rails.env.development?
289
- [{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
290
- else
291
- [{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
292
- end
293
-
294
- mcp mcp_config
321
+ mcp_config = if Rails.env.development?
322
+ [{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
323
+ else
324
+ [{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
295
325
  end
326
+
327
+ robot = RobotLab.build(
328
+ name: "file_handler",
329
+ system_prompt: "You manage files.",
330
+ mcp: mcp_config
331
+ )
296
332
  ```
297
333
 
298
334
  ### Dynamic Server Selection
@@ -305,9 +341,11 @@ def mcp_servers_for_user(user)
305
341
  servers
306
342
  end
307
343
 
308
- network = RobotLab.create_network do
309
- mcp mcp_servers_for_user(current_user)
310
- end
344
+ robot = RobotLab.build(
345
+ name: "assistant",
346
+ system_prompt: "You help the user with connected services.",
347
+ mcp: mcp_servers_for_user(current_user)
348
+ )
311
349
  ```
312
350
 
313
351
  ## Best Practices
@@ -330,24 +368,25 @@ end
330
368
 
331
369
  ### 2. Limit Tool Access
332
370
 
371
+ Restrict which MCP tools are available to a robot using the `tools:` parameter:
372
+
333
373
  ```ruby
334
- # Don't expose all tools
335
- robot = RobotLab.build do
336
- mcp :inherit
337
- tools %w[read_file search_files] # No write access
338
- end
374
+ robot = RobotLab.build(
375
+ name: "reader",
376
+ system_prompt: "You read and search files.",
377
+ mcp: [{ name: "fs", transport: { type: "stdio", command: "mcp-fs" } }],
378
+ tools: %w[read_file search_files] # No write access
379
+ )
339
380
  ```
340
381
 
341
- ### 3. Handle Disconnections
382
+ ### 3. Use Appropriate Transports
342
383
 
343
- ```ruby
344
- begin
345
- result = network.run(state: state)
346
- rescue RobotLab::MCPError
347
- # Retry without MCP
348
- result = network.run(state: state, mcp: :none)
349
- end
350
- ```
384
+ | Transport | Best For |
385
+ |-----------|----------|
386
+ | `stdio` | Local servers, CLI tools |
387
+ | `websocket` | Persistent connections, bidirectional |
388
+ | `sse` | Server push, event streams |
389
+ | `streamable_http` | Remote APIs, session-based |
351
390
 
352
391
  ## Next Steps
353
392