Class: MCP::Client::Stdio
- Inherits:
-
Object
- Object
- MCP::Client::Stdio
- Defined in:
- lib/mcp/client/stdio.rb
Constant Summary collapse
- CLOSE_TIMEOUT =
Seconds to wait for the server process to exit before sending SIGTERM. Matches the Python and TypeScript SDKs’ shutdown timeout: github.com/modelcontextprotocol/python-sdk/blob/v1.26.0/src/mcp/client/stdio/__init__.py#L48 github.com/modelcontextprotocol/typescript-sdk/blob/v1.27.1/src/client/stdio.ts#L221
2- STDERR_READ_SIZE =
4096
Instance Attribute Summary collapse
-
#args ⇒ Object
readonly
Returns the value of attribute args.
-
#command ⇒ Object
readonly
Returns the value of attribute command.
-
#env ⇒ Object
readonly
Returns the value of attribute env.
Instance Method Summary collapse
- #close ⇒ Object
-
#initialize(command:, args: [], env: nil, read_timeout: nil) ⇒ Stdio
constructor
A new instance of Stdio.
- #send_request(request:) ⇒ Object
- #start ⇒ Object
Constructor Details
#initialize(command:, args: [], env: nil, read_timeout: nil) ⇒ Stdio
Returns a new instance of Stdio.
24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/mcp/client/stdio.rb', line 24 def initialize(command:, args: [], env: nil, read_timeout: nil) @command = command @args = args @env = env @read_timeout = read_timeout @stdin = nil @stdout = nil @stderr = nil @wait_thread = nil @stderr_thread = nil @started = false @initialized = false end |
Instance Attribute Details
#args ⇒ Object (readonly)
Returns the value of attribute args.
22 23 24 |
# File 'lib/mcp/client/stdio.rb', line 22 def args @args end |
#command ⇒ Object (readonly)
Returns the value of attribute command.
22 23 24 |
# File 'lib/mcp/client/stdio.rb', line 22 def command @command end |
#env ⇒ Object (readonly)
Returns the value of attribute env.
22 23 24 |
# File 'lib/mcp/client/stdio.rb', line 22 def env @env end |
Instance Method Details
#close ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/mcp/client/stdio.rb', line 74 def close return unless @started @stdin.close @stdout.close @stderr.close begin Timeout.timeout(CLOSE_TIMEOUT) { @wait_thread.value } rescue Timeout::Error begin Process.kill("TERM", @wait_thread.pid) Timeout.timeout(CLOSE_TIMEOUT) { @wait_thread.value } rescue Timeout::Error begin Process.kill("KILL", @wait_thread.pid) rescue Errno::ESRCH nil end rescue Errno::ESRCH nil end end @stderr_thread.join(CLOSE_TIMEOUT) @started = false @initialized = false end |
#send_request(request:) ⇒ Object
38 39 40 41 42 43 44 |
# File 'lib/mcp/client/stdio.rb', line 38 def send_request(request:) start unless @started initialize_session unless @initialized (request) read_response(request) end |
#start ⇒ Object
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/mcp/client/stdio.rb', line 46 def start raise "MCP::Client::Stdio already started" if @started spawn_env = @env || {} @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(spawn_env, @command, *@args) @stdout.set_encoding("UTF-8") @stdin.set_encoding("UTF-8") # Drain stderr in the background to prevent the pipe buffer from filling up, # which would cause the server process to block and deadlock. @stderr_thread = Thread.new do loop do @stderr.readpartial(STDERR_READ_SIZE) end rescue IOError nil end @started = true rescue Errno::ENOENT, Errno::EACCES, Errno::ENOEXEC => e raise RequestHandlerError.new( "Failed to spawn server process: #{e.message}", {}, error_type: :internal_error, original_error: e, ) end |