Skip to main content
Loom Core docs

Architecture

Streamable HTTP Transport

MCP v1.0 introduced Streamable HTTP as the standard remote transport. This document covers setup, configuration, and usage of the Streamable HTTP listener in loomd.

For current shipped/in-progress status, see docs/IMPLEMENTATION_STATUS.md.

Architecture

Current (local):
  IDE -> stdio -> [loom proxy] -> unix socket -> [loomd] -> stdio -> [mcp-server]

With HTTP (remote):
  IDE -> stdio -> [loom proxy --remote] -> HTTPS POST -> [loomd /mcp]
                                                  ^
  Web client --------------------------------- HTTPS POST -> [loomd /mcp]

The Unix socket path remains untouched. Streamable HTTP is an additive parallel listener on a separate port.

Quick Start

Local Development (No Auth)

Start the daemon with HTTP on localhost:

loomd --http-addr localhost:8088 --registry /path/to/registry.yaml

Test with curl:

curl -X POST http://localhost:8088/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Expected output includes a JSON-RPC success response and an Mcp-Session-Id header:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "<server-supported-version>",
    "capabilities": {},
    "serverInfo": {"name": "loom", "version": "<daemon-version>"},
    "instructions": "Loom daemon - unified MCP hub management"
  }
}

Use that Mcp-Session-Id value on subsequent requests.

Team/Shared Daemon (With Auth)

  1. Generate a bearer token:
loom auth token-generate
# Token: loom_a1b2c3...
# Stored as: LOOM_HTTP_TOKEN (in macOS Keychain)
  1. Configure the daemon (~/.config/loom/config.yaml):
http:
  auth:
    type: token
    token_secret_key: LOOM_HTTP_TOKEN
  tls_cert_file: /path/to/cert.pem
  tls_key_file: /path/to/key.pem
  1. Start the daemon:
loomd --http-addr :8088 --registry /path/to/registry.yaml
  1. Connect from a developer machine:
loom proxy --remote https://shared-server:8088/mcp --remote-token loom_a1b2c3...

Or set the token via environment variable:

export LOOM_REMOTE_TOKEN=loom_a1b2c3...
loom proxy --remote https://shared-server:8088/mcp

Configuration Reference

Daemon Flags

FlagDescriptionDefault
--http-addrAddress for Streamable HTTP listener(disabled)

File Config (~/.config/loom/config.yaml)

http:
  # Session management
  session_timeout_minutes: 30    # Idle session expiry
  max_sessions: 1000             # Max concurrent sessions

  # Origin restriction (DNS rebinding protection)
  allowed_origins:
    - "https://app.example.com"

  # TLS (required for non-localhost)
  tls_cert_file: /path/to/cert.pem
  tls_key_file: /path/to/key.pem

  # Authentication
  auth:
    type: token                  # token | oidc | mtls | oauth2
    token_secret_key: LOOM_HTTP_TOKEN

    # For type: oidc
    oidc_issuer: https://auth.example.com
    oidc_client_id: loom-daemon

    # For type: mtls
    tls_client_ca: /path/to/ca.pem
    allowed_common_names:
      - "developer.example.com"

Authentication Types

TypeDescriptionUse Case
tokenStatic bearer tokenSmall teams, dev environments
oidcJWT via OIDC providerEnterprise SSO integration
mtlsMutual TLS with client certsZero-trust infrastructure
oauth2OAuth 2.1 with PKCEStandards-based access control

When binding to localhost, auth is optional. When binding to a non-localhost address, auth is required and the daemon refuses to start without it.

OAuth 2.1 (PKCE)

The daemon includes a built-in OAuth 2.1 authorization server. Enable it in the daemon config:

http:
  auth:
    type: oauth2
  oauth:
    enabled: true
    token_ttl_minutes: 60
    allow_dynamic_registration: true

This exposes standard endpoints:

PathDescription
/.well-known/oauth-authorization-serverServer metadata (RFC 8414)
/.well-known/oauth-protected-resourceProtected resource metadata (RFC 9728)
/oauth2/registerDynamic client registration (RFC 7591)
/oauth2/authorizeAuthorization endpoint
/oauth2/tokenToken exchange (PKCE S256 required)
/oauth2/revokeToken revocation (RFC 7009)

See docs/ENTERPRISE_SECURITY.md for the full PKCE flow with curl examples.

Security

  • Localhost binding: Auth is optional (http.auth.type may be omitted).
  • Non-localhost binding: Auth required. The daemon refuses to start without http.auth.type configured.
  • Non-localhost without TLS: The daemon logs a warning. Strongly recommend TLS for any non-localhost deployment.
  • Session management: Sessions expire after configurable idle timeout. A background reaper cleans up expired sessions every minute.

Protocol Details

The Streamable HTTP transport follows the MCP Streamable HTTP specification:

  • POST /mcp: Send JSON-RPC messages. Returns application/json for requests, 202 Accepted for notifications.
  • GET /mcp: Returns 405 Method Not Allowed (server-initiated messages not yet implemented).
  • DELETE /mcp: Terminates a session. Requires Mcp-Session-Id header.
  • Mcp-Session-Id: Set by the server on initialize response. Required for all subsequent requests.

Request Headers

HeaderRequiredDescription
Content-TypeYesMust be application/json
AcceptRecommendedShould include application/json
Mcp-Session-IdAfter initSession ID from initialize response
AuthorizationIf auth enabledBearer <token>

CLI Commands

Token Management

loom auth token-generate              # Generate and store a new token
loom auth token-generate --key MY_KEY # Use custom secret key
loom auth token-show                  # Display current token
loom auth token-revoke                # Delete token

Remote Proxy

loom proxy --remote https://host:8088/mcp --remote-token TOKEN
loom proxy --remote https://host:8088/mcp  # Uses LOOM_REMOTE_TOKEN env

Endpoints

PathAuthDescription
/mcpRequired (if configured)MCP Streamable HTTP endpoint
/healthNoHealth check for load balancer probes
/.well-known/oauth-authorization-serverNoOAuth server metadata (when http.oauth.enabled=true)
/.well-known/oauth-protected-resourceNoOAuth protected resource metadata (when http.oauth.enabled=true)
/oauth2/registerNoDynamic client registration (when http.oauth.enabled=true)
/oauth2/authorizeNoOAuth authorization endpoint (when http.oauth.enabled=true)
/oauth2/tokenNoOAuth token endpoint (when http.oauth.enabled=true)
/oauth2/revokeNoOAuth token revocation endpoint (when http.oauth.enabled=true)