robot_lab 0.3.0 → 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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +1 -1
  4. data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
  5. data/lib/robot_lab/version.rb +1 -1
  6. metadata +3 -253
  7. data/.architecture/AGENTS.md +0 -32
  8. data/.architecture/config.yml +0 -8
  9. data/.architecture/members.yml +0 -60
  10. data/.architecture/reviews/feature-free-will.md +0 -490
  11. data/.architecture/reviews/overall-codebase.md +0 -427
  12. data/.claude/settings.local.json +0 -57
  13. data/.codex/config.toml +0 -2
  14. data/.envrc +0 -6
  15. data/.github/workflows/deploy-github-pages.yml +0 -52
  16. data/.irbrc +0 -6
  17. data/.loki +0 -65
  18. data/.quality/reek_baseline.txt +0 -43
  19. data/.rubocop.yml +0 -10
  20. data/Archspec.rb +0 -44
  21. data/CLAUDE.md +0 -141
  22. data/COMMITS.md +0 -196
  23. data/Rakefile +0 -193
  24. data/_typos.toml +0 -21
  25. data/agent2agent_review.md +0 -192
  26. data/agentf_improvements.md +0 -253
  27. data/agents.md +0 -14
  28. data/docs/api/core/index.md +0 -147
  29. data/docs/api/core/memory.md +0 -578
  30. data/docs/api/core/network.md +0 -553
  31. data/docs/api/core/result.md +0 -211
  32. data/docs/api/core/robot.md +0 -1667
  33. data/docs/api/core/state.md +0 -145
  34. data/docs/api/core/tool.md +0 -520
  35. data/docs/api/errors.md +0 -323
  36. data/docs/api/hooks.md +0 -469
  37. data/docs/api/index.md +0 -137
  38. data/docs/api/mcp/client.md +0 -387
  39. data/docs/api/mcp/index.md +0 -267
  40. data/docs/api/mcp/server.md +0 -252
  41. data/docs/api/mcp/transports.md +0 -339
  42. data/docs/api/messages/index.md +0 -126
  43. data/docs/api/messages/text-message.md +0 -170
  44. data/docs/api/messages/tool-call-message.md +0 -206
  45. data/docs/api/messages/tool-result-message.md +0 -242
  46. data/docs/api/messages/user-message.md +0 -232
  47. data/docs/api/skills.md +0 -423
  48. data/docs/api/streaming/context.md +0 -280
  49. data/docs/api/streaming/events.md +0 -185
  50. data/docs/api/streaming/index.md +0 -156
  51. data/docs/api/support.md +0 -846
  52. data/docs/architecture/core-concepts.md +0 -575
  53. data/docs/architecture/index.md +0 -252
  54. data/docs/architecture/message-flow.md +0 -366
  55. data/docs/architecture/network-orchestration.md +0 -478
  56. data/docs/architecture/robot-execution.md +0 -396
  57. data/docs/architecture/state-management.md +0 -410
  58. data/docs/assets/css/custom.css +0 -56
  59. data/docs/assets/images/robot_lab.jpg +0 -0
  60. data/docs/concepts.md +0 -641
  61. data/docs/examples/basic-chat.md +0 -258
  62. data/docs/examples/index.md +0 -425
  63. data/docs/examples/mcp-server.md +0 -403
  64. data/docs/examples/multi-robot-network.md +0 -476
  65. data/docs/examples/tool-usage.md +0 -468
  66. data/docs/getting-started/configuration.md +0 -645
  67. data/docs/getting-started/index.md +0 -56
  68. data/docs/getting-started/installation.md +0 -197
  69. data/docs/getting-started/quick-start.md +0 -293
  70. data/docs/guides/building-robots.md +0 -1187
  71. data/docs/guides/creating-networks.md +0 -736
  72. data/docs/guides/hooks.md +0 -1206
  73. data/docs/guides/index.md +0 -78
  74. data/docs/guides/knowledge.md +0 -219
  75. data/docs/guides/mcp-integration.md +0 -740
  76. data/docs/guides/memory.md +0 -458
  77. data/docs/guides/observability.md +0 -733
  78. data/docs/guides/streaming.md +0 -323
  79. data/docs/guides/using-tools.md +0 -780
  80. data/docs/index.md +0 -280
  81. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  82. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  83. data/examples/.envrc +0 -3
  84. data/examples/01_simple_robot.rb +0 -31
  85. data/examples/02_tools.rb +0 -109
  86. data/examples/03_network.rb +0 -118
  87. data/examples/04_mcp.rb +0 -217
  88. data/examples/05_streaming.rb +0 -137
  89. data/examples/06_prompt_templates.rb +0 -325
  90. data/examples/07_network_memory.rb +0 -313
  91. data/examples/08_llm_config.rb +0 -162
  92. data/examples/09_chaining.rb +0 -251
  93. data/examples/10_memory.rb +0 -315
  94. data/examples/11_network_introspection.rb +0 -239
  95. data/examples/12_message_bus.rb +0 -67
  96. data/examples/13_spawn.rb +0 -85
  97. data/examples/14_rusty_circuit/.envrc +0 -1
  98. data/examples/14_rusty_circuit/.gitignore +0 -1
  99. data/examples/14_rusty_circuit/README.md +0 -1
  100. data/examples/14_rusty_circuit/comic.rb +0 -149
  101. data/examples/14_rusty_circuit/display.rb +0 -217
  102. data/examples/14_rusty_circuit/heckler.rb +0 -65
  103. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  104. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  105. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  106. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  107. data/examples/14_rusty_circuit/scout.rb +0 -165
  108. data/examples/14_rusty_circuit/show.log +0 -234
  109. data/examples/15_memory_network_and_bus/.envrc +0 -1
  110. data/examples/15_memory_network_and_bus/README.md +0 -66
  111. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  112. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  113. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  114. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  115. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  116. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  117. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  118. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  119. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  120. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  121. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  122. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  123. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  124. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  125. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  126. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  127. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  128. data/examples/16_writers_room/.envrc +0 -1
  129. data/examples/16_writers_room/display.rb +0 -158
  130. data/examples/16_writers_room/output/.gitignore +0 -4
  131. data/examples/16_writers_room/output/README.md +0 -69
  132. data/examples/16_writers_room/output/opus_001.md +0 -263
  133. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  134. data/examples/16_writers_room/output/opus_002.md +0 -245
  135. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  136. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  137. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  138. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  139. data/examples/16_writers_room/prompts/writer.md +0 -37
  140. data/examples/16_writers_room/room.rb +0 -186
  141. data/examples/16_writers_room/tools.rb +0 -173
  142. data/examples/16_writers_room/writer.rb +0 -121
  143. data/examples/16_writers_room/writers_room.rb +0 -256
  144. data/examples/17_skills.rb +0 -234
  145. data/examples/18_rails/.envrc +0 -3
  146. data/examples/18_rails/.gitignore +0 -5
  147. data/examples/18_rails/Gemfile +0 -11
  148. data/examples/18_rails/README.md +0 -67
  149. data/examples/18_rails/Rakefile +0 -4
  150. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  151. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  152. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  153. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  154. data/examples/18_rails/app/models/application_record.rb +0 -5
  155. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  156. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  157. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  158. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  159. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  160. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  161. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  162. data/examples/18_rails/bin/dev +0 -7
  163. data/examples/18_rails/bin/rails +0 -6
  164. data/examples/18_rails/bin/setup +0 -15
  165. data/examples/18_rails/config/application.rb +0 -33
  166. data/examples/18_rails/config/cable.yml +0 -2
  167. data/examples/18_rails/config/database.yml +0 -5
  168. data/examples/18_rails/config/environment.rb +0 -4
  169. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  170. data/examples/18_rails/config/routes.rb +0 -6
  171. data/examples/18_rails/config.ru +0 -4
  172. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  173. data/examples/19_token_tracking.rb +0 -138
  174. data/examples/20_circuit_breaker.rb +0 -147
  175. data/examples/21_learning_loop.rb +0 -181
  176. data/examples/22_context_compression.rb +0 -172
  177. data/examples/23_convergence.rb +0 -133
  178. data/examples/24_structured_delegation.rb +0 -151
  179. data/examples/25_history_search/conversation.jsonl +0 -30
  180. data/examples/25_history_search.rb +0 -133
  181. data/examples/26_document_store/api_versioning_adr.md +0 -52
  182. data/examples/26_document_store/incident_postmortem.md +0 -46
  183. data/examples/26_document_store/postgres_runbook.md +0 -49
  184. data/examples/26_document_store/redis_caching_guide.md +0 -48
  185. data/examples/26_document_store/sidekiq_guide.md +0 -51
  186. data/examples/26_document_store.rb +0 -140
  187. data/examples/27_incident_response/README.md +0 -65
  188. data/examples/27_incident_response/incident_response.rb +0 -261
  189. data/examples/28_mcp_discovery.rb +0 -113
  190. data/examples/29_ractor_tools.rb +0 -240
  191. data/examples/30_ractor_network.rb +0 -252
  192. data/examples/31_launch_assessment.rb +0 -246
  193. data/examples/32_newsletter_reader.rb +0 -193
  194. data/examples/33_stock_generator.rb +0 -80
  195. data/examples/33_stock_predictor.rb +0 -327
  196. data/examples/34_agentskills.rb +0 -76
  197. data/examples/35_hooks.rb +0 -266
  198. data/examples/README.md +0 -550
  199. data/examples/common.rb +0 -180
  200. data/examples/prompts/assistant.md +0 -5
  201. data/examples/prompts/audit_trail.md +0 -8
  202. data/examples/prompts/billing.md +0 -10
  203. data/examples/prompts/classifier.md +0 -7
  204. data/examples/prompts/comedian.md +0 -6
  205. data/examples/prompts/comedy_critic.md +0 -10
  206. data/examples/prompts/configurable.md +0 -9
  207. data/examples/prompts/dispatcher.md +0 -12
  208. data/examples/prompts/entity_extractor.md +0 -14
  209. data/examples/prompts/escalation.md +0 -42
  210. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  211. data/examples/prompts/frontmatter_named_test.md +0 -5
  212. data/examples/prompts/frontmatter_tools_test.md +0 -6
  213. data/examples/prompts/general.md +0 -7
  214. data/examples/prompts/github_assistant.md +0 -14
  215. data/examples/prompts/helper.md +0 -4
  216. data/examples/prompts/incident_responder.md +0 -18
  217. data/examples/prompts/keyword_extractor.md +0 -11
  218. data/examples/prompts/llm_config_demo.md +0 -20
  219. data/examples/prompts/order_support.md +0 -35
  220. data/examples/prompts/os_advocate.md +0 -13
  221. data/examples/prompts/os_chief.md +0 -13
  222. data/examples/prompts/os_editor.md +0 -13
  223. data/examples/prompts/parameterized_main_test.md +0 -6
  224. data/examples/prompts/pii_redactor.md +0 -7
  225. data/examples/prompts/product_support.md +0 -37
  226. data/examples/prompts/runbook_protocol.md +0 -12
  227. data/examples/prompts/sentiment_analyzer.md +0 -12
  228. data/examples/prompts/skill_a_test.md +0 -4
  229. data/examples/prompts/skill_b_test.md +0 -4
  230. data/examples/prompts/skill_config_test.md +0 -5
  231. data/examples/prompts/skill_cycle_a_test.md +0 -6
  232. data/examples/prompts/skill_cycle_b_test.md +0 -6
  233. data/examples/prompts/skill_description_test.md +0 -4
  234. data/examples/prompts/skill_leaf_test.md +0 -4
  235. data/examples/prompts/skill_nested_test.md +0 -6
  236. data/examples/prompts/skill_refs_main_test.md +0 -6
  237. data/examples/prompts/skill_self_ref_test.md +0 -6
  238. data/examples/prompts/skill_with_mcp_test.md +0 -9
  239. data/examples/prompts/skill_with_params_test.md +0 -6
  240. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  241. data/examples/prompts/skill_with_tools_test.md +0 -6
  242. data/examples/prompts/sre_compliance.md +0 -10
  243. data/examples/prompts/structured_output.md +0 -7
  244. data/examples/prompts/synthesizer.md +0 -17
  245. data/examples/prompts/technical.md +0 -10
  246. data/examples/prompts/template_with_skills_test.md +0 -6
  247. data/examples/prompts/triage.md +0 -22
  248. data/examples/ruboruby.md +0 -423
  249. data/examples/run_all.rb +0 -60
  250. data/examples/xyzzy.rb +0 -97
  251. data/logfile +0 -8
  252. data/mkdocs.yml +0 -209
  253. data/robot_concurrency.md +0 -38
  254. data/simple_acp_review.md +0 -298
  255. data/temp.md +0 -6
  256. data/tool_manifest_plan.md +0 -155
@@ -1,252 +0,0 @@
1
- # MCP Server Configuration
2
-
3
- Data structure for MCP server connection configuration.
4
-
5
- ## Class: `RobotLab::MCP::Server`
6
-
7
- `Server` is a configuration object that defines how to connect to an MCP server. It holds the server name and transport settings, and validates the configuration on construction.
8
-
9
- This is **not** an MCP server implementation -- it is the configuration used by `MCP::Client` to establish a connection to an external MCP server.
10
-
11
- ```ruby
12
- server = RobotLab::MCP::Server.new(
13
- name: "filesystem",
14
- description: "Read, write, and search local files and directories",
15
- transport: { type: "stdio", command: "mcp-server-filesystem", args: ["--root", "/data"] }
16
- )
17
-
18
- # With custom timeout
19
- server = RobotLab::MCP::Server.new(
20
- name: "slow_server",
21
- transport: { type: "stdio", command: "heavy-mcp-server" },
22
- timeout: 30
23
- )
24
- ```
25
-
26
- ## Constructor
27
-
28
- ```ruby
29
- Server.new(name:, transport:, timeout: nil, description: nil, **_extra)
30
- ```
31
-
32
- **Parameters:**
33
-
34
- | Name | Type | Default | Description |
35
- |------|------|---------|-------------|
36
- | `name` | `String` | **required** | Unique server identifier |
37
- | `transport` | `Hash` | **required** | Transport configuration (must include `type`) |
38
- | `timeout` | `Numeric`, `nil` | `15` | Request timeout in seconds. Values >= 1000 are auto-converted from milliseconds. Minimum 1 second |
39
- | `description` | `String`, `nil` | `""` | Human-readable summary of what the server does; scored by `MCP::ServerDiscovery` |
40
-
41
- Any other keyword is absorbed by `**_extra` and silently discarded, so a
42
- misspelled key raises nothing.
43
-
44
- **Raises:** `ArgumentError` if:
45
- - The transport type is not one of the valid types
46
- - A stdio transport is missing the `:command` key
47
- - A network transport (ws, websocket, sse, streamable-http, http) is missing the `:url` key
48
-
49
- ## Constants
50
-
51
- ```ruby
52
- RobotLab::MCP::Server::VALID_TRANSPORT_TYPES
53
- # => ["stdio", "sse", "ws", "websocket", "streamable-http", "http"]
54
-
55
- RobotLab::MCP::Server::DEFAULT_TIMEOUT
56
- # => 15 (seconds)
57
- ```
58
-
59
- ## Attributes
60
-
61
- ### name
62
-
63
- ```ruby
64
- server.name # => String
65
- ```
66
-
67
- The server identifier string.
68
-
69
- ### description
70
-
71
- ```ruby
72
- server.description # => String
73
- ```
74
-
75
- Human-readable summary of the server's capabilities. Always a String — a `nil`
76
- or omitted `description:` becomes `""`. Used by `MCP::ServerDiscovery`, which
77
- scores `"#{name} #{description}"` against the user's message when a robot is
78
- built with `mcp_discovery: true`. Discovery falls back to the full server list
79
- when no server in the list has a non-empty description.
80
-
81
- ### transport
82
-
83
- ```ruby
84
- server.transport # => Hash
85
- ```
86
-
87
- The normalized transport configuration hash (keys are symbols, type is downcased).
88
-
89
- ### timeout
90
-
91
- ```ruby
92
- server.timeout # => Numeric
93
- ```
94
-
95
- Request timeout in seconds. Defaults to `DEFAULT_TIMEOUT` (15). Values >= 1000 passed to the constructor are auto-converted from milliseconds to seconds. The minimum is 1 second.
96
-
97
- ## Methods
98
-
99
- ### transport_type
100
-
101
- ```ruby
102
- server.transport_type # => String
103
- ```
104
-
105
- Returns the transport type string (e.g., `"stdio"`, `"ws"`, `"sse"`).
106
-
107
- ### to_h
108
-
109
- ```ruby
110
- server.to_h
111
- # => { name: "y", description: "", transport: { type: "sse", url: "http://x" }, timeout: 30.0 }
112
- ```
113
-
114
- Converts the server configuration to a hash representation. The hash is **not**
115
- compacted — all four keys are always present, and `description` is `""` when it
116
- was never set. Note that `timeout` is normalized to a Float unless it defaulted
117
- (`DEFAULT_TIMEOUT` is the Integer `15`).
118
-
119
- ## Transport Configuration Options
120
-
121
- ### Stdio Transport
122
-
123
- For local MCP servers running as subprocesses:
124
-
125
- ```ruby
126
- Server.new(
127
- name: "filesystem",
128
- transport: {
129
- type: "stdio",
130
- command: "mcp-server-filesystem", # Required
131
- args: ["--root", "/data"], # Optional
132
- env: { "DEBUG" => "true" } # Optional
133
- }
134
- )
135
- ```
136
-
137
- | Key | Type | Required | Description |
138
- |-----|------|----------|-------------|
139
- | `type` | `String` | Yes | Must be `"stdio"` |
140
- | `command` | `String` | Yes | Executable command |
141
- | `args` | `Array<String>` | No | Command arguments |
142
- | `env` | `Hash` | No | Environment variables |
143
-
144
- ### WebSocket Transport
145
-
146
- For bidirectional real-time communication:
147
-
148
- ```ruby
149
- Server.new(
150
- name: "neon",
151
- transport: {
152
- type: "ws",
153
- url: "ws://localhost:8080" # Required
154
- }
155
- )
156
- ```
157
-
158
- | Key | Type | Required | Description |
159
- |-----|------|----------|-------------|
160
- | `type` | `String` | Yes | `"ws"` or `"websocket"` |
161
- | `url` | `String` | Yes | WebSocket endpoint URL |
162
-
163
- ### SSE Transport
164
-
165
- For server-sent events streaming:
166
-
167
- ```ruby
168
- Server.new(
169
- name: "streaming",
170
- transport: {
171
- type: "sse",
172
- url: "http://localhost:8080/sse" # Required
173
- }
174
- )
175
- ```
176
-
177
- | Key | Type | Required | Description |
178
- |-----|------|----------|-------------|
179
- | `type` | `String` | Yes | Must be `"sse"` |
180
- | `url` | `String` | Yes | SSE endpoint URL |
181
-
182
- ### Streamable HTTP Transport
183
-
184
- For HTTP-based communication with session management:
185
-
186
- ```ruby
187
- Server.new(
188
- name: "api",
189
- transport: {
190
- type: "streamable-http",
191
- url: "https://server.smithery.ai/neon/mcp", # Required
192
- session_id: "abc123", # Optional
193
- auth_provider: -> { "Bearer #{token}" } # Optional
194
- }
195
- )
196
- ```
197
-
198
- | Key | Type | Required | Description |
199
- |-----|------|----------|-------------|
200
- | `type` | `String` | Yes | `"streamable-http"` or `"http"` |
201
- | `url` | `String` | Yes | HTTP endpoint URL |
202
- | `session_id` | `String` | No | Session identifier |
203
- | `auth_provider` | `Proc` | No | Authentication callback returning auth header value |
204
-
205
- ## Examples
206
-
207
- ### Multiple Server Configurations
208
-
209
- ```ruby
210
- servers = [
211
- {
212
- name: "filesystem",
213
- transport: { type: "stdio", command: "mcp-server-filesystem", args: ["/data"] }
214
- },
215
- {
216
- name: "github",
217
- transport: { type: "stdio", command: "mcp-server-github" }
218
- },
219
- {
220
- name: "database",
221
- transport: { type: "ws", url: "ws://localhost:9090" }
222
- }
223
- ]
224
-
225
- # Pass directly to a robot
226
- robot = Robot.new(
227
- name: "dev_assistant",
228
- system_prompt: "You help with development tasks.",
229
- mcp: servers
230
- )
231
-
232
- # `run` defaults to `mcp: :none, tools: :none` — opt in per run
233
- robot.run("What changed in the repo?", mcp: :inherit, tools: :inherit)
234
- ```
235
-
236
- ### Creating a Client from a Server
237
-
238
- ```ruby
239
- server = RobotLab::MCP::Server.new(
240
- name: "tools",
241
- transport: { type: "ws", url: "ws://localhost:8080" }
242
- )
243
-
244
- client = RobotLab::MCP::Client.new(server)
245
- client.connect
246
- ```
247
-
248
- ## See Also
249
-
250
- - [MCP Overview](index.md)
251
- - [MCP Client](client.md)
252
- - [Transports](transports.md)
@@ -1,339 +0,0 @@
1
- # MCP Transports
2
-
3
- Communication methods for MCP client-server connections.
4
-
5
- ## Overview
6
-
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
-
9
- RobotLab provides four built-in transport types:
10
-
11
- | Transport | Class | Use Case | Status |
12
- |-----------|-------|----------|--------|
13
- | Stdio | `Transports::Stdio` | Local subprocess servers | Fully working |
14
- | WebSocket | `Transports::WebSocket` | Real-time bidirectional | **Broken** — see below |
15
- | SSE | `Transports::SSE` | Server-sent events | Working, but `connect` reports success unconditionally |
16
- | Streamable HTTP | `Transports::StreamableHTTP` | HTTP with session support | Working, but `connect` reports success unconditionally |
17
-
18
- > **Read this before using a non-stdio transport.** Only `Stdio` performs its
19
- > connection and MCP handshake synchronously. The other three wrap that work in
20
- > an `Async do ... end` block whose result is never awaited, so any error raised
21
- > inside — including a refused TCP connection — is discarded. `connect` returns
22
- > `self` regardless. The specific consequences are documented per transport
23
- > below.
24
-
25
- ## Base Interface
26
-
27
- All transports inherit from `RobotLab::MCP::Transports::Base` and implement:
28
-
29
- ```ruby
30
- class RobotLab::MCP::Transports::Base
31
- DEFAULT_TIMEOUT = 15 # seconds
32
-
33
- attr_reader :config # => Hash (symbolized keys, :timeout removed)
34
- attr_reader :timeout # => Numeric (seconds, extracted from config)
35
-
36
- def connect # Establish connection, returns self
37
- def send_request(message) # Send JSON-RPC message, returns Hash response
38
- def close # Close connection, returns self
39
- def connected? # Returns Boolean
40
- end
41
- ```
42
-
43
- The `timeout` is extracted from the config hash during initialization (and removed from `config`). If not provided, it defaults to `DEFAULT_TIMEOUT` (15 seconds). The timeout is propagated from `MCP::Server` through `MCP::Client` to the transport.
44
-
45
- **Only `Stdio` enforces the timeout.** `SSE`, `WebSocket`, and `StreamableHTTP`
46
- store `@timeout` and expose it through the `timeout` reader, but never reference
47
- it — their requests are not time-bounded by this value. If you need a bound on a
48
- non-stdio transport, wrap the call yourself, or route the client through an
49
- `MCP::ConnectionPoller` (stdio only).
50
-
51
- ## Stdio Transport
52
-
53
- **Class:** `RobotLab::MCP::Transports::Stdio`
54
-
55
- Spawns a subprocess and communicates via stdin/stdout using JSON-RPC messages (one per line). Automatically sends MCP `initialize` and `notifications/initialized` on connect. All blocking I/O is wrapped with `Timeout.timeout` so a missing or hung server cannot block the caller forever.
56
-
57
- ### Configuration
58
-
59
- ```ruby
60
- {
61
- type: "stdio",
62
- command: "mcp-server-filesystem", # Required: executable command
63
- args: ["--root", "/data"], # Optional: command arguments
64
- env: { "DEBUG" => "true" }, # Optional: environment variables
65
- timeout: 10 # Optional: request timeout in seconds (default: 15)
66
- }
67
- ```
68
-
69
- | Key | Type | Required | Description |
70
- |-----|------|----------|-------------|
71
- | `command` | `String` | Yes | Executable command to spawn |
72
- | `args` | `Array<String>` | No | Command arguments |
73
- | `env` | `Hash` | No | Environment variables (merged with current env) |
74
- | `timeout` | `Numeric` | No | Request timeout in seconds (default: 15) |
75
-
76
- ### Behavior
77
-
78
- - Uses `Open3.popen3` to spawn the subprocess
79
- - Verifies the process actually started (raises `MCPError` if it exits immediately)
80
- - Writes JSON-RPC messages to stdin (one per line)
81
- - Reads responses from stdout, skipping notifications (messages without `id`)
82
- - All blocking reads are wrapped with `Timeout.timeout` — raises `MCPError` if the server does not respond within the timeout period
83
- - `connected?` returns `true` when the subprocess is alive
84
- - `close` calls `cleanup_process` to reliably close stdin, stdout, stderr and kill the subprocess
85
- - Handles `Errno::ENOENT` (command not found), `Errno::EPIPE` / `IOError` (broken pipe / connection lost), and `Timeout::Error` (hung server) with clear error messages
86
-
87
- ### Example
88
-
89
- ```ruby
90
- transport = RobotLab::MCP::Transports::Stdio.new(
91
- command: "mcp-server-filesystem",
92
- args: ["--root", "/data"],
93
- env: { "DEBUG" => "true" },
94
- timeout: 10
95
- )
96
-
97
- transport.connect
98
- response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
99
- transport.close
100
- ```
101
-
102
- ## WebSocket Transport
103
-
104
- **Class:** `RobotLab::MCP::Transports::WebSocket`
105
-
106
- Intended to use `async-websocket` for non-blocking bidirectional communication.
107
-
108
- > **This transport does not currently work.** `connect` calls
109
- > `Async::HTTP::Endpoint.parse`, but only requires `async` and
110
- > `async/websocket/client` — the `Async::HTTP` namespace is never loaded, so the
111
- > call raises `NameError`. That happens inside an un-awaited `Async` block, so
112
- > the error is swallowed: `connect` returns `self`, no `MCPError` is raised, and
113
- > `connected?` stays `false`. Every subsequent `send_request` then raises
114
- > `MCPError, "Not connected"`.
115
- >
116
- > Verified: `WebSocket.new(url: "ws://127.0.0.1:9/ws").connect.connected?` is
117
- > `false` with no exception surfacing.
118
- >
119
- > The rescue on `connect` only catches `LoadError`, which is raised if
120
- > `async-websocket` is missing. Because of the `Async::HTTP::Endpoint` call, this
121
- > transport also needs `async-http` even once `async-websocket` is installed —
122
- > both are declared as runtime dependencies of the gem.
123
-
124
- ### Configuration
125
-
126
- ```ruby
127
- {
128
- type: "ws", # or "websocket"
129
- url: "wss://mcp.example.com/ws" # Required: WebSocket endpoint
130
- }
131
- ```
132
-
133
- | Key | Type | Required | Description |
134
- |-----|------|----------|-------------|
135
- | `url` | `String` | Yes | WebSocket endpoint URL |
136
-
137
- ### Behavior
138
-
139
- - Intends to use `Async::WebSocket::Client.connect` within an `Async` block, then
140
- send the MCP `initialize` handshake
141
- - In practice the block raises `NameError` on `Async::HTTP::Endpoint` before the
142
- connection is created, and the un-awaited block discards it
143
- - `send_request` sends JSON-RPC messages as JSON strings and reads the response
144
- inside an awaited `Async` block — but it raises `MCPError, "Not connected"`
145
- because `@connected` was never set
146
- - `connect` raises `MCPError` only for `LoadError` (missing `async-websocket`);
147
- it does not raise for a connection failure
148
- - `close` is a no-op while `@connected` is `false`
149
-
150
- ### Example
151
-
152
- ```ruby
153
- transport = RobotLab::MCP::Transports::WebSocket.new(
154
- url: "ws://localhost:8080"
155
- )
156
-
157
- transport.connect
158
- transport.connected? # => false, even against a live server
159
-
160
- # Raises MCPError: "Not connected"
161
- transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
162
- ```
163
-
164
- ## SSE Transport
165
-
166
- **Class:** `RobotLab::MCP::Transports::SSE`
167
-
168
- Uses `async-http` for HTTP-based communication. Sends requests via HTTP POST and reads responses. Requires the `async-http` gem.
169
-
170
- ### Configuration
171
-
172
- ```ruby
173
- {
174
- type: "sse",
175
- url: "http://localhost:8080/sse" # Required: SSE endpoint
176
- }
177
- ```
178
-
179
- | Key | Type | Required | Description |
180
- |-----|------|----------|-------------|
181
- | `url` | `String` | Yes | SSE/HTTP endpoint URL |
182
-
183
- ### Behavior
184
-
185
- - Creates an `Async::HTTP::Client` on connect, then sends the MCP `initialize`
186
- handshake
187
- - Sends JSON-RPC messages via HTTP POST with `Content-Type: application/json`
188
- - Reads and parses JSON response body
189
- - Raises `MCPError` if the `async-http` gem is not installed (`LoadError` only)
190
- - The `timeout` from the server config is stored but never applied
191
-
192
- > **`connect` always reports success.** `@connected = true` is assigned *before*
193
- > `send_initialize` runs, and the whole sequence is inside an un-awaited `Async`
194
- > block. Against an unreachable host, `connect` returns `self`, `connected?`
195
- > returns `true`, and the handshake failure is never surfaced. The first real
196
- > `send_request` is where the failure appears.
197
- >
198
- > Verified: `SSE.new(url: "http://127.0.0.1:9/sse").connect.connected?` is `true`.
199
-
200
- ### Example
201
-
202
- ```ruby
203
- transport = RobotLab::MCP::Transports::SSE.new(
204
- url: "http://localhost:8080/sse"
205
- )
206
-
207
- transport.connect
208
- # connected? is true here whether or not the server exists
209
-
210
- response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
211
- transport.close
212
- ```
213
-
214
- ## Streamable HTTP Transport
215
-
216
- **Class:** `RobotLab::MCP::Transports::StreamableHTTP`
217
-
218
- HTTP-based transport with session management and optional authentication. Supports session IDs for maintaining server-side state across requests. Requires the `async-http` gem.
219
-
220
- ### Configuration
221
-
222
- ```ruby
223
- {
224
- type: "streamable-http", # or "http"
225
- url: "https://server.smithery.ai/neon/mcp", # Required: HTTP endpoint
226
- session_id: "abc123", # Optional: session identifier
227
- auth_provider: -> { "Bearer #{token}" } # Optional: auth callback
228
- }
229
- ```
230
-
231
- | Key | Type | Required | Description |
232
- |-----|------|----------|-------------|
233
- | `url` | `String` | Yes | HTTP endpoint URL |
234
- | `session_id` | `String` | No | Pre-existing session identifier |
235
- | `auth_provider` | `Proc` | No | Callback returning Authorization header value |
236
-
237
- ### Behavior
238
-
239
- - Creates an `Async::HTTP::Client` on connect
240
- - Sends MCP `initialize` on connect; if no session ID was provided, extracts it from the server response (`serverInfo.sessionId`)
241
- - Sends `X-Session-ID` header with each request when a session ID is available
242
- - Calls `auth_provider` for each request to populate the `Authorization` header
243
- - Exposes `session_id` reader for accessing the current session ID
244
- - Raises `MCPError` if the `async-http` gem is not installed (`LoadError` only)
245
- - The `timeout` from the server config is stored but never applied
246
-
247
- > **`connect` always reports success**, for the same reason as SSE:
248
- > `@connected = true` precedes `send_initialize`, and the enclosing `Async` block
249
- > is never awaited. `connected?` returns `true` against an unreachable host, and
250
- > `session_id` stays at whatever you configured (`nil` if you configured nothing)
251
- > because the handshake result was discarded.
252
- >
253
- > Verified: `StreamableHTTP.new(url: "http://127.0.0.1:9/mcp").connect` yields
254
- > `connected? == true`, `session_id == nil`.
255
-
256
- ### Example
257
-
258
- ```ruby
259
- transport = RobotLab::MCP::Transports::StreamableHTTP.new(
260
- url: "https://server.smithery.ai/neon/mcp",
261
- auth_provider: -> { "Bearer #{ENV['MCP_TOKEN']}" }
262
- )
263
-
264
- transport.connect
265
- puts transport.session_id # => pre-configured value, or nil until a request lands
266
-
267
- response = transport.send_request({ jsonrpc: "2.0", id: 1, method: "tools/list" })
268
- transport.close
269
- ```
270
-
271
- ## Connection Lifecycle
272
-
273
- All transports expose the same four-step lifecycle:
274
-
275
- 1. **Create** -- instantiate with configuration hash
276
- 2. **Connect** -- establish connection and perform MCP protocol initialization
277
- 3. **Request/Response** -- send JSON-RPC requests, receive responses
278
- 4. **Close** -- tear down connection and release resources
279
-
280
- Step 2 behaves differently per transport:
281
-
282
- | Transport | `connect` is synchronous | Errors surface from `connect` | `connected?` reflects reality |
283
- |-----------|--------------------------|-------------------------------|-------------------------------|
284
- | `Stdio` | Yes | Yes (`MCPError`) | Yes — also checks the process is alive |
285
- | `SSE` | No (un-awaited `Async`) | No | No — always `true` after `connect` |
286
- | `StreamableHTTP` | No (un-awaited `Async`) | No | No — always `true` after `connect` |
287
- | `WebSocket` | No (un-awaited `Async`) | No | No — always `false` after `connect` |
288
-
289
- Only `Stdio` gives you a trustworthy answer at connect time. For the other three,
290
- treat the first `send_request` as the real connection test.
291
-
292
- Each transport builds the same MCP `initialize` message during connect:
293
-
294
- ```json
295
- {
296
- "jsonrpc": "2.0",
297
- "id": 0,
298
- "method": "initialize",
299
- "params": {
300
- "protocolVersion": "2024-11-05",
301
- "capabilities": {},
302
- "clientInfo": {
303
- "name": "RobotLab",
304
- "version": "<current version>"
305
- }
306
- }
307
- }
308
- ```
309
-
310
- ## Error Handling
311
-
312
- All transports raise `RobotLab::MCPError` for connection and communication failures:
313
-
314
- ```ruby
315
- begin
316
- transport.connect
317
- transport.send_request(message)
318
- rescue RobotLab::MCPError => e
319
- puts "Transport error: #{e.message}"
320
- ensure
321
- transport.close
322
- end
323
- ```
324
-
325
- Specific error cases:
326
- - **Not connected** -- calling `send_request` before `connect` raises `MCPError` (all transports)
327
- - **Missing gem** -- WebSocket, SSE, and HTTP transports raise `MCPError` with a `LoadError` message if the required gem (`async-websocket` / `async-http`) is not installed. This is the *only* error `connect` re-raises on those three
328
- - **Connection refused / unreachable host** -- **not** reported by SSE, WebSocket, or StreamableHTTP `connect`; the error is discarded with the un-awaited `Async` block
329
- - **No response** -- Stdio transport raises `MCPError` if the subprocess produces no output (EOF on stdout)
330
- - **Command not found** -- Stdio transport raises `MCPError` with the original `Errno::ENOENT` message
331
- - **Timeout** -- Stdio transport raises `MCPError` if the server does not respond within the configured timeout. No other transport enforces a timeout
332
- - **Broken pipe** -- Stdio transport raises `MCPError` and marks itself disconnected on `Errno::EPIPE` or `IOError`
333
- - **Immediate exit** -- Stdio transport raises `MCPError` if the server process exits immediately after spawn
334
-
335
- ## See Also
336
-
337
- - [MCP Overview](index.md)
338
- - [MCP Client](client.md)
339
- - [Server Configuration](server.md)