Class: MCPClient::ServerStdio
- Inherits:
-
ServerBase
- Object
- ServerBase
- MCPClient::ServerStdio
- Includes:
- JsonRpcTransport
- Defined in:
- lib/mcp_client/server_stdio.rb,
lib/mcp_client/server_stdio/json_rpc_transport.rb
Overview
JSON-RPC implementation of MCP server over stdio.
Defined Under Namespace
Modules: JsonRpcTransport
Constant Summary collapse
- READ_TIMEOUT =
Timeout in seconds for responses
15
Instance Attribute Summary collapse
-
#command ⇒ String, Array
readonly
The command used to launch the server.
-
#env ⇒ Object
readonly
Returns the value of attribute env.
Attributes inherited from ServerBase
Instance Method Summary collapse
-
#call_tool(tool_name, parameters) ⇒ Object
Call a tool with the given parameters.
-
#cleanup ⇒ void
Clean up the server connection Closes all stdio handles and terminates any running processes and threads.
-
#connect ⇒ Boolean
Connect to the MCP server by launching the command process via stdin/stdout.
-
#handle_line(line) ⇒ void
Handle a line of output from the stdio server Parses JSON-RPC messages and adds them to pending responses.
-
#initialize(command:, retries: 0, retry_backoff: 1, read_timeout: READ_TIMEOUT, name: nil, logger: nil, env: {}) ⇒ ServerStdio
constructor
Initialize a new ServerStdio instance.
-
#list_tools ⇒ Array<MCPClient::Tool>
List all tools available from the MCP server.
-
#start_reader ⇒ Thread
Spawn a reader thread to collect JSON-RPC responses.
Methods included from JsonRpcTransport
#call_tool_streaming, #ensure_initialized, #next_id, #perform_initialize, #rpc_notify, #rpc_request, #send_request, #wait_response
Methods included from JsonRpcCommon
#build_jsonrpc_notification, #build_jsonrpc_request, #initialization_params, #ping, #process_jsonrpc_response, #with_retry
Methods inherited from ServerBase
#call_tool_streaming, #on_notification, #ping, #rpc_notify, #rpc_request
Constructor Details
#initialize(command:, retries: 0, retry_backoff: 1, read_timeout: READ_TIMEOUT, name: nil, logger: nil, env: {}) ⇒ ServerStdio
Initialize a new ServerStdio instance
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/mcp_client/server_stdio.rb', line 33 def initialize(command:, retries: 0, retry_backoff: 1, read_timeout: READ_TIMEOUT, name: nil, logger: nil, env: {}) super(name: name) @command_array = command.is_a?(Array) ? command : nil @command = command.is_a?(Array) ? command.join(' ') : command @mutex = Mutex.new @cond = ConditionVariable.new @next_id = 1 @pending = {} @initialized = false initialize_logger(logger) @max_retries = retries @retry_backoff = retry_backoff @read_timeout = read_timeout @env = env || {} end |
Instance Attribute Details
#command ⇒ String, Array (readonly)
Returns the command used to launch the server.
19 20 21 |
# File 'lib/mcp_client/server_stdio.rb', line 19 def command @command end |
#env ⇒ Object (readonly)
Returns the value of attribute env.
19 |
# File 'lib/mcp_client/server_stdio.rb', line 19 attr_reader :command, :env |
Instance Method Details
#call_tool(tool_name, parameters) ⇒ Object
Call a tool with the given parameters
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/mcp_client/server_stdio.rb', line 131 def call_tool(tool_name, parameters) ensure_initialized req_id = next_id # JSON-RPC method for calling a tool req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'tools/call', 'params' => { 'name' => tool_name, 'arguments' => parameters } } send_request(req) res = wait_response(req_id) if (err = res['error']) raise MCPClient::Errors::ServerError, err['message'] end res['result'] rescue StandardError => e raise MCPClient::Errors::ToolCallError, "Error calling tool '#{tool_name}': #{e.}" end |
#cleanup ⇒ void
This method returns an undefined value.
Clean up the server connection Closes all stdio handles and terminates any running processes and threads
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/mcp_client/server_stdio.rb', line 155 def cleanup return unless @stdin @stdin.close unless @stdin.closed? @stdout.close unless @stdout.closed? @stderr.close unless @stderr.closed? if @wait_thread&.alive? Process.kill('TERM', @wait_thread.pid) @wait_thread.join(1) end @reader_thread&.kill rescue StandardError # Clean up resources during unexpected termination ensure @stdin = @stdout = @stderr = @wait_thread = @reader_thread = nil end |
#connect ⇒ Boolean
Connect to the MCP server by launching the command process via stdin/stdout
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/mcp_client/server_stdio.rb', line 52 def connect if @command_array if @env.any? @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, *@command_array) else @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(*@command_array) end elsif @env.any? @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, @command) else @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@command) end true rescue StandardError => e raise MCPClient::Errors::ConnectionError, "Failed to connect to MCP server: #{e.}" end |
#handle_line(line) ⇒ void
This method returns an undefined value.
Handle a line of output from the stdio server Parses JSON-RPC messages and adds them to pending responses
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/mcp_client/server_stdio.rb', line 85 def handle_line(line) msg = JSON.parse(line) @logger.debug("Received line: #{line.chomp}") # Dispatch JSON-RPC notifications (no id, has method) if msg['method'] && !msg.key?('id') @notification_callback&.call(msg['method'], msg['params']) return end # Handle standard JSON-RPC responses id = msg['id'] return unless id @mutex.synchronize do @pending[id] = msg @cond.broadcast end rescue JSON::ParserError # Skip non-JSONRPC lines in the output stream end |
#list_tools ⇒ Array<MCPClient::Tool>
List all tools available from the MCP server
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/mcp_client/server_stdio.rb', line 109 def list_tools ensure_initialized req_id = next_id # JSON-RPC method for listing tools req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'tools/list', 'params' => {} } send_request(req) res = wait_response(req_id) if (err = res['error']) raise MCPClient::Errors::ServerError, err['message'] end (res.dig('result', 'tools') || []).map { |td| MCPClient::Tool.from_json(td, server: self) } rescue StandardError => e raise MCPClient::Errors::ToolCallError, "Error listing tools: #{e.}" end |
#start_reader ⇒ Thread
Spawn a reader thread to collect JSON-RPC responses
71 72 73 74 75 76 77 78 79 |
# File 'lib/mcp_client/server_stdio.rb', line 71 def start_reader @reader_thread = Thread.new do @stdout.each_line do |line| handle_line(line) end rescue StandardError # Reader thread aborted unexpectedly end end |