Module: OKF::MCP::App

Defined in:
lib/okf/mcp/app.rb

Overview

The Rack seam: the same server definition and stateless transport the --http verb serves, as the app a config.ru runs — so puma, unicorn or any Rack server can host the bundles without this gem depending on one (the reader's server is the reader's dependency; the no-rackup position holds). This module owns transport construction; the WEBrick bridge (okf/mcp/http.rb) builds through it, so the options exist in one place.

Deliberately no listen cap here, unlike the WEBrick bridge's 32: under a Rack 3 server a subscriptions/listen stream holds no thread — the SDK's callable body returns immediately — so the SDK's own default is the right bound.

Defined Under Namespace

Classes: Scope

Constant Summary collapse

MAX_REQUEST_BYTES =

The largest request body the transport reads, matching the SDK's own StreamableHTTPTransport default.

4 * 1024 * 1024

Class Method Summary collapse

Class Method Details

.build(refs = [], engine: nil, allowed_hosts: nil, allowed_origins: nil) ⇒ Object

refs are argv-shaped bundle names — directories and @slugs, exactly what okf mcp takes; empty serves the kernel registry, exactly like okf mcp with no args. +allowed_hosts+/+allowed_origins+ widen the SDK's DNS-rebinding allowlists for a reverse proxy or a DNS name; the same posture as --allow-host, and the same warning — the allowlist is not access control, and a Rack server bound beyond loopback publishes every served bundle with no authentication.



67
68
69
70
71
# File 'lib/okf/mcp/app.rb', line 67

def build(refs = [], engine: nil, allowed_hosts: nil, allowed_origins: nil)
  registry = Array(refs).empty? ? Registry.from_kernel : Registry.from_argv(Array(refs))
  server = Server.build(registry, engine: engine || Backend.detect)
  Scope.new(transport(server, allowed_hosts: allowed_hosts, allowed_origins: allowed_origins))
end

.transport(server, allowed_hosts: nil, allowed_origins: nil, **options) ⇒ Object

Wires one transport to one server definition. Every construction site — this seam and the WEBrick bridge — goes through here, so the shared posture (stateless, JSON responses, the body cap, the blank-allowlist guard) is stated once and cannot drift between the two; a caller passes only what is its own (the bridge: its listen cap).



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/okf/mcp/app.rb', line 78

def transport(server, allowed_hosts: nil, allowed_origins: nil, **options)
  options = { stateless: true, enable_json_response: true,
              max_request_bytes: MAX_REQUEST_BYTES }.merge(options)
  hosts = Array(allowed_hosts)
  options[:allowed_hosts] = hosts unless hosts.empty?
  origins = Array(allowed_origins)
  options[:allowed_origins] = origins unless origins.empty?
  app = ::MCP::Server::Transports::StreamableHTTPTransport.new(server, **options)
  server.transport = app
  app
end