|
| 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 |
0 commit comments