Class: RobotLab::MCP::Transports::Stdio
- Defined in:
- lib/robot_lab/mcp/transports/stdio.rb
Overview
StdIO transport for local MCP servers
Spawns a subprocess and communicates via stdin/stdout. All blocking I/O is wrapped with a configurable timeout so a missing or hung server cannot block the caller forever.
Constant Summary
Constants inherited from Base
Instance Attribute Summary collapse
-
#stdin ⇒ Object
readonly
private
Expose the underlying IO objects so ConnectionPoller can register them in its IO.select loop.
-
#stdout ⇒ Object
readonly
private
Expose the underlying IO objects so ConnectionPoller can register them in its IO.select loop.
Attributes inherited from Base
Instance Method Summary collapse
-
#close ⇒ self
Close the connection to the MCP server.
-
#connect ⇒ self
Connect to the MCP server via stdio.
-
#connected? ⇒ Boolean
Check if the transport is connected.
-
#initialize(config) ⇒ Stdio
constructor
Creates a new Stdio transport.
-
#send_request(message) ⇒ Hash
Send a JSON-RPC request to the MCP server.
Constructor Details
#initialize(config) ⇒ Stdio
Creates a new Stdio transport.
30 31 32 33 34 35 36 37 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 30 def initialize(config) super @stdin = nil @stdout = nil @stderr = nil @wait_thread = nil @connected = false end |
Instance Attribute Details
#stdin ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Expose the underlying IO objects so ConnectionPoller can register them in its IO.select loop.
127 128 129 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 127 def stdin @stdin end |
#stdout ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Expose the underlying IO objects so ConnectionPoller can register them in its IO.select loop.
127 128 129 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 127 def stdout @stdout end |
Instance Method Details
#close ⇒ self
Close the connection to the MCP server.
109 110 111 112 113 114 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 109 def close return self unless @connected cleanup_process self end |
#connect ⇒ self
Connect to the MCP server via stdio.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 44 def connect return self if @connected command = @config[:command] args = @config[:args] || [] env = @config[:env] || {} full_env = ENV.to_h.merge(env.transform_keys(&:to_s)) @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(full_env, command, *args) # Verify the process actually started unless @wait_thread.alive? raise MCPError, "MCP server process exited immediately (command: #{command})" end @connected = true # Initialize MCP protocol — this must complete within the timeout send_initialize self rescue Errno::ENOENT => e cleanup_process raise MCPError, "MCP server command not found: #{command} (#{e.})" rescue Timeout::Error cleanup_process raise MCPError, "MCP server '#{command}' did not respond within #{@timeout}s" end |
#connected? ⇒ Boolean
Check if the transport is connected.
119 120 121 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 119 def connected? @connected && @wait_thread&.alive? end |
#send_request(message) ⇒ Hash
Send a JSON-RPC request to the MCP server.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/robot_lab/mcp/transports/stdio.rb', line 79 def send_request() raise MCPError, "Not connected" unless @connected Timeout.timeout(@timeout, Timeout::Error) do json = .to_json @stdin.puts(json) @stdin.flush loop do response_line = @stdout.gets raise MCPError, "No response from MCP server (EOF on stdout)" unless response_line parsed = JSON.parse(response_line, symbolize_names: true) # Skip notifications (messages without an id) next if parsed[:method] && !parsed.key?(:id) return parsed end end rescue Timeout::Error raise MCPError, "MCP server did not respond within #{@timeout}s" rescue Errno::EPIPE, IOError => e @connected = false raise MCPError, "MCP server connection lost: #{e.}" end |