Class: Clacky::Mcp::HttpTransport

Inherits:
Transport
  • Object
show all
Defined in:
lib/clacky/mcp/http_transport.rb

Overview

MCP streamable-http transport (spec 2025-03-26).

One endpoint URL handles both client→server (POST) and server→client (SSE). We POST every JSON-RPC message; the server may respond with either:

- application/json   → single response, deliver immediately
- text/event-stream  → one or more "data:" SSE events, each a JSON-RPC msg

Session tracking: the server returns Mcp-Session-Id on the initialize response; we echo it on every subsequent request.

Constant Summary collapse

DEFAULT_OPEN_TIMEOUT =
10
DEFAULT_READ_TIMEOUT =
120

Instance Method Summary collapse

Constructor Details

#initialize(name:, url:, headers: {}, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT) ⇒ HttpTransport

Returns a new instance of HttpTransport.

Raises:



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/clacky/mcp/http_transport.rb', line 26

def initialize(name:, url:, headers: {}, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT)
  @name = name
  @uri  = URI.parse(url)
  raise TransportError, "MCP server '#{name}' url is not http(s): #{url}" unless %w[http https].include?(@uri.scheme)

  @extra_headers = (headers || {}).transform_keys(&:to_s).transform_values(&:to_s)
  @open_timeout  = open_timeout
  @read_timeout  = read_timeout

  @session_id = nil
  @on_message = nil
  @lock = Monitor.new
  @alive = false
  @last_error = nil
end

Instance Method Details

#alive?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/clacky/mcp/http_transport.rb', line 51

def alive?
  @alive
end

#on_message(&blk) ⇒ Object



74
75
76
# File 'lib/clacky/mcp/http_transport.rb', line 74

def on_message(&blk)
  @on_message = blk
end

#send_message(payload) ⇒ Object

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/clacky/mcp/http_transport.rb', line 55

def send_message(payload)
  raise TransportError, "transport stopped" unless @alive

  body = JSON.generate(payload)
  is_request = payload.is_a?(Hash) && payload.key?(:id) || (payload.is_a?(Hash) && payload.key?("id"))

  Thread.new do
    begin
      dispatch_post(body, is_request: is_request)
    rescue StandardError => e
      @last_error = e
      @on_message&.call({
        "id"    => payload[:id] || payload["id"],
        "error" => { "code" => -32000, "message" => "HTTP transport error: #{e.message}" }
      })
    end
  end
end

#startObject



42
43
44
45
# File 'lib/clacky/mcp/http_transport.rb', line 42

def start
  @alive = true
  self
end

#stderr_tail(bytes: 4096) ⇒ Object



78
79
80
# File 'lib/clacky/mcp/http_transport.rb', line 78

def stderr_tail(bytes: 4096)
  @last_error ? "last error: #{@last_error.class}: #{@last_error.message}" : ""
end

#stopObject



47
48
49
# File 'lib/clacky/mcp/http_transport.rb', line 47

def stop
  @alive = false
end