Class: LittleGhost::MCP::HTTPTransport

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/mcp/client.rb

Overview

HTTPTransport sends MCP JSON-RPC messages over Streamable HTTP. It applies time and response-size limits and keeps the negotiated MCP session ID.

Security and trust

HTTPS is required by default. allow_insecure_http is only for an explicitly trusted local development endpoint. Scope caller-supplied credential headers to the target server. Response bodies and negotiated session IDs are validated before use.

One transport instance retains one negotiated MCP session ID and sends it with later requests. Scope the transport and its Client to one trusted server and one authenticated principal; never share that pair across tenants. LittleGhost does not send MCP session-termination DELETE requests, so configure server-side expiry or manage that lifecycle outside this transport when the server requires explicit cleanup.

Constant Summary collapse

DEFAULT_MAX_RESPONSE_BYTES =

Default upper bound for one MCP response body (10 MiB).

10 * 1024 * 1024
SESSION_ID_PATTERN =

:nodoc:

/\A[\x21-\x7e]{1,256}\z/

Instance Method Summary collapse

Constructor Details

#initialize(url:, headers: {}, timeout: 60, signer: nil, allow_insecure_http: false, max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES) ⇒ HTTPTransport

Configures time and response-size limits. signer, when supplied, is called with each Net::HTTP request before it is sent.

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/little_ghost/mcp/client.rb', line 38

def initialize(url:, headers: {}, timeout: 60, signer: nil, allow_insecure_http: false,
  max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES)
  @uri = URI(url)
  unless %w[http https].include?(@uri.scheme) && @uri.host
    raise ConfigurationError, "MCP URL must be an HTTP(S) URL"
  end
  if @uri.scheme == "http" && !allow_insecure_http
    raise ConfigurationError, "MCP URL must use HTTPS unless allow_insecure_http is enabled"
  end

  @headers = headers.transform_keys(&:to_s).freeze
  @timeout = Float(timeout)
  raise ArgumentError, "timeout must be positive" unless @timeout.positive?

  @max_response_bytes = Integer(max_response_bytes)
  raise ArgumentError, "max_response_bytes must be positive" unless @max_response_bytes.positive?

  @signer = signer
  @session_id = nil
end

Instance Method Details

#send(payload, context: nil) ⇒ Object

Sends one JSON-RPC payload. A RunContext supplies cancellation and a deadline; without it the configured timeout applies.



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/little_ghost/mcp/client.rb', line 61

def send(payload, context: nil)
  return perform_send(payload, timeout: @timeout) unless context

  response = nil
  stream = Support::InterruptibleStream.new(
    cancellation_token: context.cancellation_token,
    deadline: context.deadline
  ) do |emit|
    emit.call(perform_send(payload, timeout: context.remaining_time(@timeout)))
  end
  stream.each { |value| response = value }
  response
end