robot_lab 0.0.1 → 0.0.6

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 (187) 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 +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  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/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -1,264 +1,252 @@
1
1
  # MCP Transports
2
2
 
3
- Communication methods for MCP connections.
3
+ Communication methods for MCP client-server connections.
4
4
 
5
5
  ## Overview
6
6
 
7
- Transports handle the low-level communication between MCP clients and servers. RobotLab supports multiple transport types for different deployment scenarios.
7
+ Transports handle the low-level communication between `MCP::Client` and external MCP servers. All transports implement the same interface defined by `Transports::Base`, using JSON-RPC 2.0 for message exchange and the MCP protocol version `2024-11-05` for initialization.
8
8
 
9
- ## Transport Types
9
+ RobotLab provides four built-in transport types:
10
10
 
11
- ### Stdio
11
+ | Transport | Class | Use Case |
12
+ |-----------|-------|----------|
13
+ | Stdio | `Transports::Stdio` | Local subprocess servers |
14
+ | WebSocket | `Transports::WebSocket` | Real-time bidirectional |
15
+ | SSE | `Transports::SSE` | Server-sent events |
16
+ | Streamable HTTP | `Transports::StreamableHTTP` | HTTP with session support |
12
17
 
13
- Standard input/output for local process communication.
18
+ ## Base Interface
14
19
 
15
- ```ruby
16
- {
17
- type: "stdio",
18
- command: "npx",
19
- args: ["@modelcontextprotocol/server-filesystem", "/path"],
20
- env: { "DEBUG" => "true" }
21
- }
22
- ```
20
+ All transports inherit from `RobotLab::MCP::Transports::Base` and implement:
23
21
 
24
- **Options:**
22
+ ```ruby
23
+ class RobotLab::MCP::Transports::Base
24
+ attr_reader :config # => Hash (symbolized keys)
25
25
 
26
- | Option | Type | Description |
27
- |--------|------|-------------|
28
- | `command` | `String` | Executable command |
29
- | `args` | `Array` | Command arguments |
30
- | `env` | `Hash` | Environment variables |
31
- | `cwd` | `String` | Working directory |
26
+ def connect # Establish connection, returns self
27
+ def send_request(message) # Send JSON-RPC message, returns Hash response
28
+ def close # Close connection, returns self
29
+ def connected? # Returns Boolean
30
+ end
31
+ ```
32
32
 
33
- **Use Cases:**
33
+ ## Stdio Transport
34
34
 
35
- - Local development
36
- - CLI tools
37
- - Subprocess servers
35
+ **Class:** `RobotLab::MCP::Transports::Stdio`
38
36
 
39
- ### WebSocket
37
+ Spawns a subprocess and communicates via stdin/stdout using JSON-RPC messages (one per line). Automatically sends MCP `initialize` and `notifications/initialized` on connect.
40
38
 
41
- Bidirectional real-time communication.
39
+ ### Configuration
42
40
 
43
41
  ```ruby
44
42
  {
45
- type: "websocket",
46
- url: "wss://mcp.example.com/ws",
47
- headers: { "Authorization" => "Bearer token" }
43
+ type: "stdio",
44
+ command: "mcp-server-filesystem", # Required: executable command
45
+ args: ["--root", "/data"], # Optional: command arguments
46
+ env: { "DEBUG" => "true" } # Optional: environment variables
48
47
  }
49
48
  ```
50
49
 
51
- **Options:**
50
+ | Key | Type | Required | Description |
51
+ |-----|------|----------|-------------|
52
+ | `command` | `String` | Yes | Executable command to spawn |
53
+ | `args` | `Array<String>` | No | Command arguments |
54
+ | `env` | `Hash` | No | Environment variables (merged with current env) |
52
55
 
53
- | Option | Type | Description |
54
- |--------|------|-------------|
55
- | `url` | `String` | WebSocket endpoint |
56
- | `headers` | `Hash` | HTTP headers |
57
- | `ping_interval` | `Integer` | Keep-alive interval (seconds) |
56
+ ### Behavior
58
57
 
59
- **Use Cases:**
58
+ - Uses `Open3.popen3` to spawn the subprocess
59
+ - Writes JSON-RPC messages to stdin (one per line)
60
+ - Reads responses from stdout, skipping notifications (messages without `id`)
61
+ - `connected?` returns `true` when the subprocess is alive
62
+ - `close` terminates stdin, stdout, stderr, and kills the subprocess
60
63
 
61
- - Remote servers
62
- - Real-time applications
63
- - Long-running connections
64
+ ### Example
64
65
 
65
- ### SSE (Server-Sent Events)
66
+ ```ruby
67
+ transport = RobotLab::MCP::Transports::Stdio.new(
68
+ command: "mcp-server-filesystem",
69
+ args: ["--root", "/data"],
70
+ env: { "DEBUG" => "true" }
71
+ )
66
72
 
67
- Unidirectional server-to-client streaming.
73
+ transport.connect
74
+ response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
75
+ transport.close
76
+ ```
77
+
78
+ ## WebSocket Transport
79
+
80
+ **Class:** `RobotLab::MCP::Transports::WebSocket`
81
+
82
+ Uses `async-websocket` for non-blocking bidirectional communication. Requires the `async-websocket` gem.
83
+
84
+ ### Configuration
68
85
 
69
86
  ```ruby
70
87
  {
71
- type: "sse",
72
- url: "https://mcp.example.com/sse",
73
- headers: { "Authorization" => "Bearer token" }
88
+ type: "ws", # or "websocket"
89
+ url: "wss://mcp.example.com/ws" # Required: WebSocket endpoint
74
90
  }
75
91
  ```
76
92
 
77
- **Options:**
93
+ | Key | Type | Required | Description |
94
+ |-----|------|----------|-------------|
95
+ | `url` | `String` | Yes | WebSocket endpoint URL |
96
+
97
+ ### Behavior
78
98
 
79
- | Option | Type | Description |
80
- |--------|------|-------------|
81
- | `url` | `String` | SSE endpoint |
82
- | `headers` | `Hash` | HTTP headers |
83
- | `post_url` | `String` | Endpoint for client messages |
99
+ - Uses `Async::WebSocket::Client.connect` within an `Async` block
100
+ - Sends JSON-RPC messages as JSON strings
101
+ - Reads responses synchronously within the async context
102
+ - Raises `MCPError` if the `async-websocket` gem is not installed
84
103
 
85
- **Use Cases:**
104
+ ### Example
86
105
 
87
- - Firewall-friendly connections
88
- - Browser-based clients
89
- - Streaming responses
106
+ ```ruby
107
+ transport = RobotLab::MCP::Transports::WebSocket.new(
108
+ url: "ws://localhost:8080"
109
+ )
110
+
111
+ transport.connect
112
+ response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
113
+ transport.close
114
+ ```
90
115
 
91
- ### HTTP
116
+ ## SSE Transport
92
117
 
93
- Request/response communication.
118
+ **Class:** `RobotLab::MCP::Transports::SSE`
119
+
120
+ Uses `async-http` for HTTP-based communication. Sends requests via HTTP POST and reads responses. Requires the `async-http` gem.
121
+
122
+ ### Configuration
94
123
 
95
124
  ```ruby
96
125
  {
97
- type: "http",
98
- url: "https://mcp.example.com/mcp",
99
- headers: { "Authorization" => "Bearer token" },
100
- timeout: 30
126
+ type: "sse",
127
+ url: "http://localhost:8080/sse" # Required: SSE endpoint
101
128
  }
102
129
  ```
103
130
 
104
- **Options:**
131
+ | Key | Type | Required | Description |
132
+ |-----|------|----------|-------------|
133
+ | `url` | `String` | Yes | SSE/HTTP endpoint URL |
105
134
 
106
- | Option | Type | Description |
107
- |--------|------|-------------|
108
- | `url` | `String` | HTTP endpoint |
109
- | `headers` | `Hash` | HTTP headers |
110
- | `timeout` | `Integer` | Request timeout (seconds) |
111
- | `retry` | `Integer` | Retry attempts |
135
+ ### Behavior
112
136
 
113
- **Use Cases:**
137
+ - Creates an `Async::HTTP::Client` on connect
138
+ - Sends JSON-RPC messages via HTTP POST with `Content-Type: application/json`
139
+ - Reads and parses JSON response body
140
+ - Raises `MCPError` if the `async-http` gem is not installed
114
141
 
115
- - Simple integrations
116
- - Stateless operations
117
- - Load-balanced servers
118
-
119
- ## Examples
120
-
121
- ### Local Server with Stdio
142
+ ### Example
122
143
 
123
144
  ```ruby
124
- network = RobotLab.create_network do
125
- mcp [
126
- {
127
- name: "filesystem",
128
- transport: {
129
- type: "stdio",
130
- command: "npx",
131
- args: ["@modelcontextprotocol/server-filesystem", "/home/user/docs"]
132
- }
133
- }
134
- ]
135
- end
145
+ transport = RobotLab::MCP::Transports::SSE.new(
146
+ url: "http://localhost:8080/sse"
147
+ )
148
+
149
+ transport.connect
150
+ response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
151
+ transport.close
136
152
  ```
137
153
 
138
- ### Remote Server with WebSocket
154
+ ## Streamable HTTP Transport
139
155
 
140
- ```ruby
141
- network = RobotLab.create_network do
142
- mcp [
143
- {
144
- name: "remote_tools",
145
- transport: {
146
- type: "websocket",
147
- url: "wss://tools.example.com/mcp",
148
- headers: {
149
- "Authorization" => "Bearer #{ENV['MCP_TOKEN']}"
150
- }
151
- }
152
- }
153
- ]
154
- end
155
- ```
156
+ **Class:** `RobotLab::MCP::Transports::StreamableHTTP`
157
+
158
+ HTTP-based transport with session management and optional authentication. Supports session IDs for maintaining server-side state across requests. Requires the `async-http` gem.
156
159
 
157
- ### Multiple Transports
160
+ ### Configuration
158
161
 
159
162
  ```ruby
160
- network = RobotLab.create_network do
161
- mcp [
162
- # Local filesystem
163
- {
164
- name: "fs",
165
- transport: { type: "stdio", command: "mcp-fs" }
166
- },
167
- # Remote API
168
- {
169
- name: "api",
170
- transport: {
171
- type: "http",
172
- url: "https://api.example.com/mcp"
173
- }
174
- },
175
- # Real-time service
176
- {
177
- name: "realtime",
178
- transport: {
179
- type: "websocket",
180
- url: "wss://realtime.example.com/mcp"
181
- }
182
- }
183
- ]
184
- end
163
+ {
164
+ type: "streamable-http", # or "http"
165
+ url: "https://server.smithery.ai/neon/mcp", # Required: HTTP endpoint
166
+ session_id: "abc123", # Optional: session identifier
167
+ auth_provider: -> { "Bearer #{token}" } # Optional: auth callback
168
+ }
185
169
  ```
186
170
 
187
- ### Custom Transport
171
+ | Key | Type | Required | Description |
172
+ |-----|------|----------|-------------|
173
+ | `url` | `String` | Yes | HTTP endpoint URL |
174
+ | `session_id` | `String` | No | Pre-existing session identifier |
175
+ | `auth_provider` | `Proc` | No | Callback returning Authorization header value |
176
+
177
+ ### Behavior
178
+
179
+ - Creates an `Async::HTTP::Client` on connect
180
+ - Sends MCP `initialize` on connect; if no session ID was provided, extracts it from the server response (`serverInfo.sessionId`)
181
+ - Sends `X-Session-ID` header with each request when a session ID is available
182
+ - Calls `auth_provider` for each request to populate the `Authorization` header
183
+ - Exposes `session_id` reader for accessing the current session ID
184
+ - Raises `MCPError` if the `async-http` gem is not installed
185
+
186
+ ### Example
188
187
 
189
188
  ```ruby
190
- class CustomTransport
191
- def initialize(options)
192
- @options = options
193
- end
194
-
195
- def connect
196
- # Establish connection
197
- end
198
-
199
- def disconnect
200
- # Close connection
201
- end
202
-
203
- def send(message)
204
- # Send message to server
205
- end
206
-
207
- def receive
208
- # Receive message from server
209
- end
210
- end
189
+ transport = RobotLab::MCP::Transports::StreamableHTTP.new(
190
+ url: "https://server.smithery.ai/neon/mcp",
191
+ auth_provider: -> { "Bearer #{ENV['MCP_TOKEN']}" }
192
+ )
211
193
 
212
- RobotLab::MCP.register_transport(:custom, CustomTransport)
194
+ transport.connect
195
+ puts transport.session_id # => assigned by server or pre-configured
213
196
 
214
- # Use custom transport
215
- {
216
- type: "custom",
217
- option1: "value1"
218
- }
197
+ response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
198
+ transport.close
219
199
  ```
220
200
 
221
201
  ## Connection Lifecycle
222
202
 
223
- ```mermaid
224
- sequenceDiagram
225
- participant C as Client
226
- participant T as Transport
227
- participant S as Server
203
+ All transports follow the same lifecycle:
228
204
 
229
- C->>T: connect()
230
- T->>S: Establish connection
231
- S-->>T: Connection established
232
- T-->>C: Connected
205
+ 1. **Create** -- instantiate with configuration hash
206
+ 2. **Connect** -- establish connection and perform MCP protocol initialization
207
+ 3. **Request/Response** -- send JSON-RPC requests, receive responses
208
+ 4. **Close** -- tear down connection and release resources
233
209
 
234
- C->>T: send(request)
235
- T->>S: Forward request
236
- S-->>T: Response
237
- T-->>C: receive() → response
210
+ Each transport sends the MCP `initialize` message during connect:
238
211
 
239
- C->>T: disconnect()
240
- T->>S: Close connection
212
+ ```json
213
+ {
214
+ "jsonrpc": "2.0",
215
+ "id": 0,
216
+ "method": "initialize",
217
+ "params": {
218
+ "protocolVersion": "2024-11-05",
219
+ "capabilities": {},
220
+ "clientInfo": {
221
+ "name": "RobotLab",
222
+ "version": "<current version>"
223
+ }
224
+ }
225
+ }
241
226
  ```
242
227
 
243
228
  ## Error Handling
244
229
 
230
+ All transports raise `RobotLab::MCPError` for connection and communication failures:
231
+
245
232
  ```ruby
246
233
  begin
247
- client.connect
248
- rescue RobotLab::MCP::ConnectionError => e
249
- case e.transport
250
- when "stdio"
251
- puts "Command failed: #{e.message}"
252
- when "websocket"
253
- puts "WebSocket error: #{e.message}"
254
- when "http"
255
- puts "HTTP error: #{e.status} - #{e.message}"
256
- end
234
+ transport.connect
235
+ transport.send_request(message)
236
+ rescue RobotLab::MCPError => e
237
+ puts "Transport error: #{e.message}"
238
+ ensure
239
+ transport.close
257
240
  end
258
241
  ```
259
242
 
243
+ Specific error cases:
244
+ - **Not connected** -- calling `send_request` before `connect` raises `MCPError`
245
+ - **Missing gem** -- WebSocket, SSE, and HTTP transports raise `MCPError` with a `LoadError` message if required gems are not installed
246
+ - **No response** -- Stdio transport raises `MCPError` if the subprocess produces no output
247
+
260
248
  ## See Also
261
249
 
262
250
  - [MCP Overview](index.md)
263
251
  - [MCP Client](client.md)
264
- - [MCP Server](server.md)
252
+ - [Server Configuration](server.md)
@@ -8,60 +8,75 @@ RobotLab uses a structured message system to represent conversations between use
8
8
 
9
9
  ```ruby
10
10
  # User input
11
- user_msg = UserMessage.new("Hello", thread_id: "123")
11
+ user_msg = UserMessage.new("Hello", session_id: "123")
12
12
 
13
13
  # Assistant response
14
- text_msg = TextMessage.new("Hi there!")
14
+ text_msg = TextMessage.new(role: "assistant", content: "Hi there!")
15
15
 
16
16
  # Tool interaction
17
- tool_call = ToolCallMessage.new(id: "call_1", name: "get_weather", input: { city: "NYC" })
18
- tool_result = ToolResultMessage.new(id: "call_1", result: { temp: 72 })
17
+ tool = ToolMessage.new(id: "call_1", name: "get_weather", input: { city: "NYC" })
18
+ tool_call = ToolCallMessage.new(role: "assistant", tools: [tool])
19
+ tool_result = ToolResultMessage.new(tool: tool, content: { data: { temp: 72 } })
19
20
  ```
20
21
 
21
22
  ## Message Hierarchy
22
23
 
23
24
  ```
24
25
  Message (base)
25
- ├── UserMessage - User input with metadata
26
- ├── TextMessage - Assistant text response
27
- ├── ToolMessage - Tool-related messages
28
- │ ├── ToolCallMessage - Tool invocation
29
- │ └── ToolResultMessage - Tool result
30
- └── SystemMessage - System prompts
26
+ ├── TextMessage - role + text content
27
+ ├── ToolCallMessage - role + Array<ToolMessage>
28
+ └── ToolResultMessage - tool + result content
29
+
30
+ UserMessage - Standalone (not a Message subclass)
31
+ ToolMessage - Standalone (not a Message subclass)
31
32
  ```
32
33
 
33
34
  ## Common Interface
34
35
 
35
- All messages implement:
36
+ All Message subclasses implement:
36
37
 
37
38
  ```ruby
38
- message.role # => Symbol (:user, :assistant, :tool)
39
+ message.role # => String ("user", "assistant", "tool_result")
39
40
  message.content # => String or structured data
41
+ message.type # => String ("text", "tool_call", "tool_result")
40
42
  message.to_h # => Hash representation
41
43
  message.to_json # => JSON string
42
44
  ```
43
45
 
46
+ Type and role predicates:
47
+
48
+ ```ruby
49
+ message.text? # => true if type is "text"
50
+ message.tool_call? # => true if type is "tool_call"
51
+ message.tool_result? # => true if type is "tool_result"
52
+ message.system? # => true if role is "system"
53
+ message.user? # => true if role is "user"
54
+ message.assistant? # => true if role is "assistant"
55
+ message.stopped? # => true if stop_reason is "stop"
56
+ message.tool_stop? # => true if stop_reason is "tool"
57
+ ```
58
+
44
59
  ## Classes
45
60
 
46
61
  | Class | Description |
47
62
  |-------|-------------|
48
- | [UserMessage](user-message.md) | User input with thread and metadata |
49
- | [TextMessage](text-message.md) | Assistant text response |
50
- | [ToolCallMessage](tool-call-message.md) | Tool invocation request |
63
+ | [UserMessage](user-message.md) | User input with session and metadata |
64
+ | [TextMessage](text-message.md) | Text message with role (system, user, or assistant) |
65
+ | [ToolCallMessage](tool-call-message.md) | Tool invocation request containing ToolMessage objects |
51
66
  | [ToolResultMessage](tool-result-message.md) | Tool execution result |
52
67
 
53
- ## Usage in State
68
+ ## Usage in Memory
54
69
 
55
- Messages are typically accessed through state:
70
+ Messages are typically accessed through memory:
56
71
 
57
72
  ```ruby
58
- state.messages # => Array<Message>
73
+ memory.messages # => Array<Message>
59
74
 
60
75
  # Format for LLM
61
- state.format_history # => Array<Hash>
76
+ memory.format_history # => Array<Message>
62
77
  ```
63
78
 
64
79
  ## See Also
65
80
 
66
- - [State](../core/state.md)
81
+ - [Memory](../core/memory.md)
67
82
  - [Message Flow Architecture](../../architecture/message-flow.md)
@@ -1,24 +1,26 @@
1
1
  # TextMessage
2
2
 
3
- Assistant text response.
3
+ Text message from system, user, or assistant.
4
4
 
5
5
  ## Class: `RobotLab::TextMessage`
6
6
 
7
7
  ```ruby
8
- message = TextMessage.new("Hello! How can I help you today?")
8
+ message = TextMessage.new(role: "assistant", content: "Hello! How can I help you today?")
9
9
  ```
10
10
 
11
11
  ## Constructor
12
12
 
13
13
  ```ruby
14
- TextMessage.new(content)
14
+ TextMessage.new(role:, content:, stop_reason: nil)
15
15
  ```
16
16
 
17
17
  **Parameters:**
18
18
 
19
19
  | Name | Type | Description |
20
20
  |------|------|-------------|
21
- | `content` | `String` | Response text |
21
+ | `role` | `String` | Message role ("system", "user", or "assistant") |
22
+ | `content` | `String` | The text content |
23
+ | `stop_reason` | `String`, `nil` | Stop reason ("stop" or "tool") |
22
24
 
23
25
  ## Attributes
24
26
 
@@ -28,15 +30,31 @@ TextMessage.new(content)
28
30
  message.content # => String
29
31
  ```
30
32
 
31
- The response text.
33
+ The text content.
32
34
 
33
35
  ### role
34
36
 
35
37
  ```ruby
36
- message.role # => :assistant
38
+ message.role # => "assistant"
37
39
  ```
38
40
 
39
- Always returns `:assistant`.
41
+ Returns a String: `"system"`, `"user"`, or `"assistant"`.
42
+
43
+ ### type
44
+
45
+ ```ruby
46
+ message.type # => "text"
47
+ ```
48
+
49
+ Always returns `"text"`.
50
+
51
+ ### stop_reason
52
+
53
+ ```ruby
54
+ message.stop_reason # => "stop" or nil
55
+ ```
56
+
57
+ The stop reason, if any.
40
58
 
41
59
  ## Methods
42
60
 
@@ -52,8 +70,10 @@ Hash representation.
52
70
 
53
71
  ```ruby
54
72
  {
55
- role: :assistant,
56
- content: "Hello! How can I help you today?"
73
+ type: "text",
74
+ role: "assistant",
75
+ content: "Hello! How can I help you today?",
76
+ stop_reason: "stop"
57
77
  }
58
78
  ```
59
79
 
@@ -65,34 +85,60 @@ message.to_json # => String
65
85
 
66
86
  JSON representation.
67
87
 
88
+ ### Predicates
89
+
90
+ ```ruby
91
+ message.text? # => true
92
+ message.tool_call? # => false
93
+ message.assistant? # => true (if role is "assistant")
94
+ message.user? # => false
95
+ message.stopped? # => true (if stop_reason is "stop")
96
+ ```
97
+
68
98
  ## Examples
69
99
 
70
- ### Basic Response
100
+ ### System Message
101
+
102
+ ```ruby
103
+ message = TextMessage.new(role: "system", content: "You are a helpful assistant")
104
+ message.system? # => true
105
+ ```
106
+
107
+ ### User Message
108
+
109
+ ```ruby
110
+ message = TextMessage.new(role: "user", content: "What's the weather?")
111
+ message.user? # => true
112
+ ```
113
+
114
+ ### Assistant Response
71
115
 
72
116
  ```ruby
73
- message = TextMessage.new("Your order has shipped!")
117
+ message = TextMessage.new(
118
+ role: "assistant",
119
+ content: "Your order has shipped!",
120
+ stop_reason: "stop"
121
+ )
122
+ message.assistant? # => true
123
+ message.stopped? # => true
74
124
  ```
75
125
 
76
126
  ### In Robot Results
77
127
 
78
128
  ```ruby
79
- result = robot.run(state: state)
129
+ result = robot.run("Tell me a joke")
80
130
 
81
- # Extract text messages
82
- result.output.each do |msg|
83
- if msg.is_a?(TextMessage)
84
- puts msg.content
85
- end
131
+ # The result is a TextMessage when the assistant replies with text
132
+ if result.text?
133
+ puts result.content
86
134
  end
87
135
  ```
88
136
 
89
137
  ### Filtering Text Content
90
138
 
91
139
  ```ruby
92
- # Get only text responses from results
93
- text_responses = state.results.flat_map(&:output).select do |msg|
94
- msg.is_a?(TextMessage)
95
- end.map(&:content)
140
+ # Get only text messages from memory
141
+ text_messages = memory.messages.select(&:text?).map(&:content)
96
142
  ```
97
143
 
98
144
  ## See Also