Class: Boxcars::MCP::StdioClient

Inherits:
Client
  • Object
show all
Defined in:
lib/boxcars/mcp/stdio_client.rb

Overview

Minimal MCP stdio client using JSON-RPC framing (‘Content-Length` headers). It supports the core handshake plus tool discovery and invocation.

Constant Summary collapse

DEFAULT_PROTOCOL_VERSION =
"2024-11-05"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command:, args: [], cwd: nil, env: {}, auto_initialize: true, client_name: "boxcars", client_version: Boxcars::VERSION, protocol_version: DEFAULT_PROTOCOL_VERSION, initialization_timeout: 10) ⇒ StdioClient

Returns a new instance of StdioClient.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/boxcars/mcp/stdio_client.rb', line 16

def initialize(command:, args: [], cwd: nil, env: {}, auto_initialize: true,
               client_name: "boxcars", client_version: Boxcars::VERSION,
               protocol_version: DEFAULT_PROTOCOL_VERSION, initialization_timeout: 10)
  @command = command
  @args = Array(args)
  @cwd = cwd
  @env = env || {}
  @auto_initialize = auto_initialize
  @client_name = client_name
  @client_version = client_version
  @protocol_version = protocol_version
  @initialization_timeout = initialization_timeout

  @stdin = nil
  @stdout = nil
  @stderr = nil
  @process_wait_thread = nil
  @stderr_thread = nil
  @stderr_buffer = +""
  @request_id = 0
  @pending_notifications = []
  @initialized = false
  @server_info = nil
  @server_capabilities = nil
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def args
  @args
end

#commandObject (readonly)

Returns the value of attribute command.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def command
  @command
end

#cwdObject (readonly)

Returns the value of attribute cwd.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def cwd
  @cwd
end

#envObject (readonly)

Returns the value of attribute env.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def env
  @env
end

#process_wait_threadObject (readonly)

Returns the value of attribute process_wait_thread.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def process_wait_thread
  @process_wait_thread
end

#server_capabilitiesObject (readonly)

Returns the value of attribute server_capabilities.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def server_capabilities
  @server_capabilities
end

#server_infoObject (readonly)

Returns the value of attribute server_info.



14
15
16
# File 'lib/boxcars/mcp/stdio_client.rb', line 14

def server_info
  @server_info
end

Instance Method Details

#call_tool(name:, arguments:) ⇒ Object



84
85
86
87
88
# File 'lib/boxcars/mcp/stdio_client.rb', line 84

def call_tool(name:, arguments:)
  ensure_initialized!
  response = request("tools/call", { name:, arguments: arguments || {} })
  response["result"] || response[:result] || {}
end

#closeObject



90
91
92
93
94
95
96
97
98
# File 'lib/boxcars/mcp/stdio_client.rb', line 90

def close
  @stdin&.close unless @stdin&.closed?
  @stdout&.close unless @stdout&.closed?
  @stderr&.close unless @stderr&.closed?
  @stderr_thread&.kill
  @process_wait_thread&.kill if @process_wait_thread&.alive?
ensure
  @stdin = @stdout = @stderr = @process_wait_thread = @stderr_thread = nil
end

#connect!Object



42
43
44
45
46
47
48
# File 'lib/boxcars/mcp/stdio_client.rb', line 42

def connect!
  return self if connected?

  popen_process!
  initialize_session! if @auto_initialize
  self
end

#connected?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/boxcars/mcp/stdio_client.rb', line 50

def connected?
  !@stdin.nil? && !@stdout.nil? && @process_wait_thread&.alive?
end

#initialize_session!Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/boxcars/mcp/stdio_client.rb', line 54

def initialize_session!
  connect_without_initialize! unless connected?
  return self if @initialized

  response = with_timeout(@initialization_timeout) do
    request("initialize", {
              protocolVersion: @protocol_version,
              capabilities: {},
              clientInfo: {
                name: @client_name,
                version: @client_version
              }
            })
  end

  result = response["result"] || {}
  @server_info = result["serverInfo"] || result[:serverInfo]
  @server_capabilities = result["capabilities"] || result[:capabilities] || {}
  notify("notifications/initialized", {})
  @initialized = true
  self
end

#list_toolsObject



77
78
79
80
81
82
# File 'lib/boxcars/mcp/stdio_client.rb', line 77

def list_tools
  ensure_initialized!
  response = request("tools/list", {})
  result = response["result"] || {}
  result["tools"] || result[:tools] || []
end