Class: PostHog::MCP::RackMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/posthog/mcp/rack_middleware.rb

Overview

Note:

Experimental. PostHog::MCP is not officially supported; see the docs at https://posthog.com/docs/mcp-analytics.

Rack middleware for stateless / multi-pod MCP servers built on a custom Rack stack (not the mcp gem's Streamable HTTP transport, which instrument wires automatically).

It publishes the request's headers to RequestScope, so an instrumented MCP::Server dispatched anywhere below it sees the HTTP context the gem's own transport would have given it, and it carries the Mcp-Session-Id token minted at initialize back onto the response. Clients replay that token on every request, so any pod recovers $session_id and the client identity from the header alone.

Neither the request nor the response body is read here. The token comes from whoever handled the request and already knows it succeeded: the instrumented server, or - for a hand-rolled dispatcher built on Client - a call to the mint hook this middleware exposes as env['posthog_mcp.mint'].

The decoded token (replayed or freshly minted) is exposed to the app as env['posthog_mcp.session'] (SessionTokenPayload).

Examples:

An instrumented server behind a custom Rack stack

use PostHog::MCP::RackMiddleware

A hand-rolled dispatcher minting the session itself

session = env['posthog_mcp.mint']&.call(
  client_name: info['name'], client_version: info['version'], protocol_version: params['protocolVersion']
)
analytics.capture_initialize(session_id: session&.session_id, ...)

Constant Summary collapse

ENV_KEY =
'posthog_mcp.session'
MINT_ENV_KEY =
'posthog_mcp.mint'

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ RackMiddleware

Returns a new instance of RackMiddleware.



40
41
42
# File 'lib/posthog/mcp/rack_middleware.rb', line 40

def initialize(app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/posthog/mcp/rack_middleware.rb', line 44

def call(env)
  replayed = SessionToken.decode(SessionToken.read_header(MCP_SESSION_HEADER => env['HTTP_MCP_SESSION_ID']))
  env[ENV_KEY] = replayed if replayed

  RequestScope.with(headers: RequestScope.headers_from_env(env), transport: :http) do |scope|
    env[MINT_ENV_KEY] = mint_hook(scope, env) unless replayed
    status, headers, body = @app.call(env)
    settle_token(scope[:mint], status, headers, env, replayed)
    [status, headers, body]
  ensure
    env.delete(MINT_ENV_KEY)
  end
end