Class: MCP::Server::Transports::StdioTransport

Inherits:
Transport
  • Object
show all
Defined in:
lib/mcp/server/transports/stdio_transport.rb

Constant Summary collapse

STATUS_INTERRUPTED =
Signal.list["INT"] + 128

Instance Method Summary collapse

Methods inherited from Transport

#handle_json_request, #handle_request

Constructor Details

#initialize(server) ⇒ StdioTransport

Returns a new instance of StdioTransport.



12
13
14
15
16
17
18
# File 'lib/mcp/server/transports/stdio_transport.rb', line 12

def initialize(server)
  super(server)
  @open = false
  @session = nil
  $stdin.set_encoding("UTF-8")
  $stdout.set_encoding("UTF-8")
end

Instance Method Details

#closeObject



33
34
35
# File 'lib/mcp/server/transports/stdio_transport.rb', line 33

def close
  @open = false
end

#openObject



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/mcp/server/transports/stdio_transport.rb', line 20

def open
  @open = true
  @session = ServerSession.new(server: @server, transport: self)
  while @open && (line = $stdin.gets)
    response = @session.handle_json(line.strip)
    send_response(response) if response
  end
rescue Interrupt
  warn("\nExiting...")

  exit(STATUS_INTERRUPTED)
end

#send_notification(method, params = nil) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/mcp/server/transports/stdio_transport.rb', line 43

def send_notification(method, params = nil)
  notification = {
    jsonrpc: "2.0",
    method: method,
  }
  notification[:params] = params if params

  send_response(notification)
  true
rescue => e
  MCP.configuration.exception_reporter.call(e, { error: "Failed to send notification" })
  false
end

#send_request(method, params = nil) ⇒ Object

NOTE: This signature deliberately matches the abstract ‘Transport#send_request` contract (`method, params = nil`) without the cancellation kwargs that `StreamableHTTPTransport#send_request` accepts. On Ruby 2.7 the project’s supported minimum a method that mixes a positional ‘params` Hash with explicit keyword arguments cannot be called as `send_request(method, { … })` - the trailing Hash would be auto-promoted to keyword arguments. Stdio is single-threaded and blocks on `$stdin.gets`, so nested-request cancellation has very limited value here regardless; servers that need cancellation propagation for nested server-to-client requests should use `StreamableHTTPTransport`.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/mcp/server/transports/stdio_transport.rb', line 64

def send_request(method, params = nil)
  request_id = generate_request_id
  request = { jsonrpc: "2.0", id: request_id, method: method }
  request[:params] = params if params

  begin
    send_response(request)
  rescue => e
    MCP.configuration.exception_reporter.call(e, { error: "Failed to send request" })
    raise
  end

  while @open && (line = $stdin.gets)
    begin
      parsed = JSON.parse(line.strip, symbolize_names: true)
    rescue JSON::ParserError => e
      MCP.configuration.exception_reporter.call(e, { error: "Failed to parse response" })
      raise
    end

    if parsed[:id] == request_id && !parsed.key?(:method)
      if parsed[:error]
        raise StandardError, "Client returned an error for #{method} request (code: #{parsed[:error][:code]}): #{parsed[:error][:message]}"
      end

      return parsed[:result]
    else
      response = @session ? @session.handle(parsed) : @server.handle(parsed)
      send_response(response) if response
    end
  end

  raise "Transport closed while waiting for response to #{method} request."
end

#send_response(message) ⇒ Object



37
38
39
40
41
# File 'lib/mcp/server/transports/stdio_transport.rb', line 37

def send_response(message)
  json_message = message.is_a?(String) ? message : JSON.generate(message)
  $stdout.puts(json_message)
  $stdout.flush
end