Skip to content

Commit 3c8ff5a

Browse files
committed
fix + cleanup stdio
1 parent d80b137 commit 3c8ff5a

File tree

3 files changed

+150
-146
lines changed

3 files changed

+150
-146
lines changed

lib/mcp/server/transports/stdio.rb

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,33 @@
44
require "json"
55

66
module MCP
7-
module Transports
8-
class StdioTransport < Transport
9-
def initialize(server)
10-
@server = server
11-
@open = false
12-
$stdin.set_encoding("UTF-8")
13-
$stdout.set_encoding("UTF-8")
14-
super
15-
end
7+
class Server
8+
module Transports
9+
class StdioTransport < Transport
10+
def initialize(server)
11+
@server = server
12+
@open = false
13+
$stdin.set_encoding("UTF-8")
14+
$stdout.set_encoding("UTF-8")
15+
super
16+
end
1617

17-
def open
18-
@open = true
19-
while @open && (line = $stdin.gets)
20-
handle_json_request(line.strip)
18+
def open
19+
@open = true
20+
while @open && (line = $stdin.gets)
21+
handle_json_request(line.strip)
22+
end
2123
end
22-
end
2324

24-
def close
25-
@open = false
26-
end
25+
def close
26+
@open = false
27+
end
2728

28-
def send_response(message)
29-
json_message = message.is_a?(String) ? message : JSON.generate(message)
30-
$stdout.puts(json_message)
31-
$stdout.flush
29+
def send_response(message)
30+
json_message = message.is_a?(String) ? message : JSON.generate(message)
31+
$stdout.puts(json_message)
32+
$stdout.flush
33+
end
3234
end
3335
end
3436
end
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
require "mcp/server/transports/stdio"
5+
require "json"
6+
7+
module MCP
8+
class Server
9+
module Transports
10+
class StdioTest < ActiveSupport::TestCase
11+
include InstrumentationTestHelper
12+
13+
setup do
14+
configuration = MCP::Configuration.new
15+
configuration.instrumentation_callback = instrumentation_helper.callback
16+
@server = Server.new(name: "test_server", configuration: configuration)
17+
@transport = StdioTransport.new(@server)
18+
end
19+
20+
test "initializes with server and closed state" do
21+
server = @transport.instance_variable_get(:@server)
22+
assert_equal @server.object_id, server.object_id
23+
refute @transport.instance_variable_get(:@open)
24+
end
25+
26+
test "processes JSON-RPC requests from stdin and sends responses to stdout" do
27+
request = {
28+
jsonrpc: "2.0",
29+
method: "ping",
30+
id: "123",
31+
}
32+
input = StringIO.new(JSON.generate(request) + "\n")
33+
output = StringIO.new
34+
35+
original_stdin = $stdin
36+
original_stdout = $stdout
37+
38+
begin
39+
$stdin = input
40+
$stdout = output
41+
42+
thread = Thread.new { @transport.open }
43+
sleep(0.1)
44+
@transport.close
45+
thread.join
46+
47+
response = JSON.parse(output.string, symbolize_names: true)
48+
assert_equal("2.0", response[:jsonrpc])
49+
assert_equal("123", response[:id])
50+
assert_empty(response[:result])
51+
refute(@transport.instance_variable_get(:@open))
52+
ensure
53+
$stdin = original_stdin
54+
$stdout = original_stdout
55+
end
56+
end
57+
58+
test "sends string responses to stdout" do
59+
output = StringIO.new
60+
original_stdout = $stdout
61+
62+
begin
63+
$stdout = output
64+
@transport.send_response("test response")
65+
assert_equal("test response\n", output.string)
66+
ensure
67+
$stdout = original_stdout
68+
end
69+
end
70+
71+
test "sends JSON responses to stdout" do
72+
output = StringIO.new
73+
original_stdout = $stdout
74+
75+
begin
76+
$stdout = output
77+
response = { key: "value" }
78+
@transport.send_response(response)
79+
assert_equal(JSON.generate(response) + "\n", output.string)
80+
ensure
81+
$stdout = original_stdout
82+
end
83+
end
84+
85+
test "handles valid JSON-RPC requests" do
86+
request = {
87+
jsonrpc: "2.0",
88+
method: "ping",
89+
id: "123",
90+
}
91+
output = StringIO.new
92+
original_stdout = $stdout
93+
94+
begin
95+
$stdout = output
96+
@transport.send(:handle_request, JSON.generate(request))
97+
response = JSON.parse(output.string, symbolize_names: true)
98+
assert_equal("2.0", response[:jsonrpc])
99+
assert_nil(response[:id])
100+
assert_nil(response[:result])
101+
ensure
102+
$stdout = original_stdout
103+
end
104+
end
105+
106+
test "handles invalid JSON requests" do
107+
invalid_json = "invalid json"
108+
output = StringIO.new
109+
original_stdout = $stdout
110+
111+
begin
112+
$stdout = output
113+
@transport.send(:handle_request, invalid_json)
114+
response = JSON.parse(output.string, symbolize_names: true)
115+
assert_equal("2.0", response[:jsonrpc])
116+
assert_nil(response[:id])
117+
assert_equal(-32600, response[:error][:code])
118+
assert_equal("Invalid Request", response[:error][:message])
119+
assert_equal("Request must be an array or a hash", response[:error][:data])
120+
ensure
121+
$stdout = original_stdout
122+
end
123+
end
124+
end
125+
end
126+
end
127+
end

test/mcp/server/transports/stdio_transport_test.rb

Lines changed: 0 additions & 125 deletions
This file was deleted.

0 commit comments

Comments
 (0)