Class: MCPClient::ServerStdio
- Inherits:
-
ServerBase
- Object
- ServerBase
- MCPClient::ServerStdio
- Defined in:
- lib/mcp_client/server_stdio.rb
Overview
JSON-RPC implementation of MCP server over stdio.
Constant Summary collapse
- READ_TIMEOUT =
Timeout in seconds for responses
15
Instance Attribute Summary collapse
-
#command ⇒ Object
readonly
Returns the value of attribute command.
Instance Method Summary collapse
-
#call_tool(tool_name, parameters) ⇒ Object
Call a tool with the given parameters.
-
#cleanup ⇒ Object
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 stdout/stdin.
-
#handle_line(line) ⇒ Object
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, logger: nil) ⇒ ServerStdio
constructor
A new instance of ServerStdio.
-
#list_tools ⇒ Array<MCPClient::Tool>
List all tools available from the MCP server.
-
#start_reader ⇒ Object
Spawn a reader thread to collect JSON-RPC responses.
Constructor Details
#initialize(command:, retries: 0, retry_backoff: 1, logger: nil) ⇒ ServerStdio
Returns a new instance of ServerStdio.
20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/mcp_client/server_stdio.rb', line 20 def initialize(command:, retries: 0, retry_backoff: 1, logger: nil) super() @command = command.is_a?(Array) ? command.join(' ') : command @mutex = Mutex.new @cond = ConditionVariable.new @next_id = 1 @pending = {} @initialized = false @logger = logger || Logger.new($stdout, level: Logger::WARN) @max_retries = retries @retry_backoff = retry_backoff end |
Instance Attribute Details
#command ⇒ Object (readonly)
Returns the value of attribute command.
11 12 13 |
# File 'lib/mcp_client/server_stdio.rb', line 11 def command @command end |
Instance Method Details
#call_tool(tool_name, parameters) ⇒ Object
Call a tool with the given parameters
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/mcp_client/server_stdio.rb', line 97 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.message}" end |
#cleanup ⇒ Object
Clean up the server connection Closes all stdio handles and terminates any running processes and threads
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/mcp_client/server_stdio.rb', line 120 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 stdout/stdin
36 37 38 39 40 41 |
# File 'lib/mcp_client/server_stdio.rb', line 36 def connect @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@command) true rescue StandardError => e raise MCPClient::Errors::ConnectionError, "Failed to connect to MCP server: #{e.message}" end |
#handle_line(line) ⇒ Object
Handle a line of output from the stdio server Parses JSON-RPC messages and adds them to pending responses
57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/mcp_client/server_stdio.rb', line 57 def handle_line(line) msg = JSON.parse(line) @logger.debug("Received line: #{line.chomp}") 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
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/mcp_client/server_stdio.rb', line 75 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) } rescue StandardError => e raise MCPClient::Errors::ToolCallError, "Error listing tools: #{e.message}" end |
#start_reader ⇒ Object
Spawn a reader thread to collect JSON-RPC responses
44 45 46 47 48 49 50 51 52 |
# File 'lib/mcp_client/server_stdio.rb', line 44 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 |