docs/MCP_HTTP_TRANSPORT.md
~> 0.6 in gemspec)MCP::Server::Transports::StdioTransport — stdin/stdout JSON-RPC (current default)MCP::Server::Transports::StreamableHTTPTransport — HTTP POST/GET with optional SSE streamingNative support: YES. The mcp gem v0.6.0 ships StreamableHTTPTransport with full Rack compatibility.
The transport (lib/mcp/server/transports/streamable_http_transport.rb) provides:
transport.handle_request(rack_request) returns [status, headers, body]Mcp-Session-Id header on initialization, tracks sessions server-sideStreamableHTTPTransport.new(server, stateless: true) for multi-node deploymentstransport.send_notification(method, params, session_id:) pushes events to connected SSE streamsThe gem includes example servers (examples/http_server.rb, examples/streamable_http_server.rb) demonstrating the Rack integration:
# Build the MCP server (same as stdio)
server = Woods::MCP::Server.build(index_dir: index_dir)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
server.transport = transport
# Wrap in a Rack app
app = proc do |env|
request = Rack::Request.new(env)
transport.handle_request(request)
end
# Run with any Rack-compatible server (Puma, Falcon, WEBrick)
Rackup::Handler.get("puma").run(app, Port: 9292, Host: "localhost")
For embedding in a Rails app, the Server#handle_json method enables a minimal controller:
class McpController < ApplicationController
skip_before_action :verify_authenticity_token
def handle
response = @mcp_server.handle_json(request.body.read)
render json: response
end
end
This provides non-streaming Streamable HTTP transport (POST-only, no SSE).
Add exe/woods-mcp-http alongside the existing stdio executable:
#!/usr/bin/env ruby
# frozen_string_literal: true
require "rackup"
require_relative "../lib/woods"
require_relative "../lib/woods/mcp/server"
# ... other requires ...
index_dir = ARGV[0] || ENV["WOODS_DIR"] || Dir.pwd
port = (ENV["PORT"] || 9292).to_i
server = Woods::MCP::Server.build(index_dir: index_dir, retriever: retriever)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
server.transport = transport
app = proc { |env| transport.handle_request(Rack::Request.new(env)) }
Rackup::Handler.get("puma").run(app, Port: port, Host: "localhost")
Complexity: Low — ~30 lines, mirrors the existing exe structure.
Dependencies: Requires rackup gem + a Rack server (e.g., puma). The gemspec already has puma as a dev dependency. For production use, rackup would need to be added as an optional dependency or documented as a user-provided requirement.
A Rack middleware that mounts the MCP server at a configurable path:
# lib/woods/mcp/rack_middleware.rb
module Woods
module MCP
class RackMiddleware
def initialize(app, index_dir:, path: "/mcp")
@app = app
@path = path
server = Server.build(index_dir: index_dir)
@transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(server)
server.transport = @transport
end
def call(env)
if env["PATH_INFO"].start_with?(@path)
@transport.handle_request(Rack::Request.new(env))
else
@app.call(env)
end
end
end
end
end
Complexity: Low-medium — adds a mountable middleware class. Useful for host apps that want to expose MCP alongside their existing routes.
A single woods-mcp executable that supports both transports:
woods-mcp # stdio (default, backward compatible)
woods-mcp --http # HTTP on port 9292
woods-mcp --http --port 8080 # HTTP on custom port
Complexity: Low — add CLI flag parsing to existing exe.
Start with Option A (standalone HTTP executable). Rationale:
mcp gem already provides the full transport; we just need a thin wrapperrackup/puma are already dev dependencies; users wanting HTTP would install themexe/woods-mcp-http), 1 modified (woods.gemspec to register the new executable)spec/mcp/server_spec.rb covers all tool behavior; transport-level testing would be integration-only (Rack test with mock requests)mcp gem tracks the spec closely; v0.6.0 already implements the full Streamable HTTP spec including stateless mode