Skip to content

HTTP Transport

Chris & Mike edited this page Apr 21, 2026 · 11 revisions

HTTP Transport

HTTP transport configuration and reference for Memory Journal MCP Server.


Protocol Support

Memory Journal supports two MCP transport protocols simultaneously when running in HTTP mode:

Protocol Spec Version Endpoints Session Management
Streamable HTTP 2025-03-26 POST/GET/DELETE /mcp mcp-session-id header
Legacy SSE 2024-11-05 GET /sse, POST /messages sessionId query param

Why both? Streamable HTTP is the current MCP standard. Legacy SSE ensures backward compatibility with older MCP clients (e.g., MCP Inspector, older SDK versions).

Note

Legacy SSE is only available in stateful mode. Stateless mode only supports Streamable HTTP.


Starting the Server

npm:

# Stateful (default) — supports both protocols
memory-journal-mcp --transport http --port 3000

# Bind to all interfaces (required for containers)
memory-journal-mcp --transport http --port 3000 --server-host 0.0.0.0

# Stateless (serverless) — Streamable HTTP only
memory-journal-mcp --transport http --port 3000 --stateless

Docker:

# Stateful
docker run --rm -p 3000:3000 \
  -v ./data:/app/data \
  writenotenow/memory-journal-mcp:latest \
  --transport http --port 3000 --server-host 0.0.0.0

# Stateless
docker run --rm -p 3000:3000 \
  -v ./data:/app/data \
  writenotenow/memory-journal-mcp:latest \
  --transport http --port 3000 --server-host 0.0.0.0 --stateless

Important

Docker containers must use --server-host 0.0.0.0 to accept connections from outside the container.


Endpoint Reference

Endpoint Description Mode
GET / Server info (name, version, protocols, endpoints) Both
POST /mcp JSON-RPC requests (initialize, tools/call, etc.) Both
GET /mcp SSE stream for server-to-client notifications Stateful
DELETE /mcp Session termination Stateful
GET /sse Legacy SSE connection (MCP 2024-11-05) Stateful
POST /messages Legacy SSE message endpoint Stateful
GET /health Health check ({ status: "ok", timestamp }) Both
GET /.well-known/oauth-protected-resource RFC 9728 Protected Resource Metadata Both

Root Info (GET /)

Returns server metadata and available endpoints:

{
  "name": "memory-journal-mcp",
  "version": "4.x.x",
  "status": "running",
  "protocols": [
    "Streamable HTTP (MCP 2025-03-26)",
    "Legacy SSE (MCP 2024-11-05)"
  ],
  "endpoints": {
    "POST /mcp": "Streamable HTTP endpoint",
    "GET /mcp": "SSE stream (stateful)",
    "DELETE /mcp": "Session termination",
    "GET /sse": "Legacy SSE connection (MCP 2024-11-05)",
    "POST /messages": "Legacy SSE message endpoint",
    "GET /health": "Health check"
  }
}

Health Check (GET /health)

Always public (no authentication, no rate limiting):

{
  "status": "healthy",
  "timestamp": "2026-03-05T07:00:00.000Z"
}

Security Features

Every HTTP response includes the following security headers:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
Content-Security-Policy default-src 'none'; frame-ancestors 'none'
Cache-Control no-store, no-cache, must-revalidate
Referrer-Policy no-referrer
Permissions-Policy camera=(), microphone=(), geolocation=()

Optionally, when --enable-hsts is set:

Header Value
Strict-Transport-Security max-age=31536000; includeSubDomains

Rate Limiting

  • 100 requests per minute per IP (sliding window)
  • Returns 429 Too Many Requests when exceeded
  • Health check (GET /health) is exempt

CORS

Configurable via CLI or environment variable. Supports comma-separated multiple origins (exact-match only):

# Single origin
memory-journal-mcp --transport http --cors-origin "http://localhost:3000"

# Multiple origins (comma-separated)
memory-journal-mcp --transport http --cors-origin "http://localhost:3000,https://app.example.com"

# Environment variable
MCP_CORS_ORIGIN=http://localhost:3000 memory-journal-mcp --transport http

Default: blank (strict opt-in). Use specific origins to enable CORS access.

Body Size Limit

Request bodies are limited to 1 MB to prevent memory exhaustion. Requests exceeding this limit receive 413 Payload Too Large.

404 Handler

Unknown paths return a structured JSON response:

{
  "error": "Not found"
}

Cross-Protocol Guard

Session IDs are protocol-scoped:

  • SSE session IDs are rejected on /mcp endpoints
  • Streamable HTTP session IDs are rejected on /messages endpoint

This prevents session confusion when both protocols are active simultaneously.


Session Management

Stateful Mode (Default)

  • UUID-based session IDs via crypto.randomUUID()
  • 30-minute idle timeout with automatic cleanup
  • 24-hour absolute TTL limit for streamable HTTP sessions
  • 5-minute sweep interval for expired sessions

Streamable HTTP sessions:

# 1. Initialize — server returns mcp-session-id header
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

# 2. Subsequent requests — include mcp-session-id
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "mcp-session-id: YOUR_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

# 3. Terminate session
curl -X DELETE http://localhost:3000/mcp \
  -H "mcp-session-id: YOUR_SESSION_ID"

Legacy SSE sessions:

# 1. Open SSE connection — server sends endpoint event with sessionId
curl -N http://localhost:3000/sse

# 2. Send messages to the returned endpoint
curl -X POST "http://localhost:3000/messages?sessionId=YOUR_SSE_SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

Stateless Mode

No session tracking. Each request is independent. Best for serverless deployments.

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test_simple","arguments":{"message":"Hello"}}}'

Stateful vs Stateless

Feature Stateful (default) Stateless (--stateless)
Progress Notifications ✅ Yes ❌ No
SSE Streaming ✅ Yes ❌ No
Legacy SSE Protocol ✅ Yes ❌ No
Session Management ✅ Yes ❌ No
Serverless Compatible ⚠️ Complex ✅ Native
Horizontal Scaling ⚠️ Sticky sessions ✅ Any instance

Choose Stateful when:

  • Running on persistent infrastructure (VMs, containers, dedicated servers)
  • Need progress notifications for long-running tools
  • Need Legacy SSE for backward compatibility

Choose Stateless when:

  • Deploying to serverless (AWS Lambda, Cloudflare Workers, Vercel)
  • Horizontal scaling without sticky sessions
  • Simple request-response patterns

Architecture

The HTTP transport is implemented as a modular directory:

src/transports/http/
  types.ts      — Configuration, constants, rate limiting types
  security.ts   — CORS, security headers, rate limiting, client IP
  handlers.ts   — Health check, root info, auth middleware
  server/       — HTTP server implementations
    index.ts    — HttpTransport class
    stateful.ts — Streamable HTTP (session management)
    stateless.ts — Stateless HTTP (serverless)
    legacy-sse.ts — SSE transport (MCP 2024-11-05)
  index.ts      — Barrel re-export

Key design decisions:

  • Extracted from mcp-server.ts — HTTP logic lives in its own module, keeping mcp-server.ts focused on tool/resource/prompt registration
  • Dual-protocol — Both StreamableHTTPServerTransport and SSEServerTransport share the same Express app
  • Built-in rate limiting — Zero-dependency sliding window with automatic cleanup
  • Server timeouts — Request (120s), keep-alive (65s), and headers (66s) for DoS mitigation
  • Session isolation — Each protocol maintains its own session map; cross-protocol access is blocked

Configuration Reference

CLI Flag Env Variable Default Description
--transport http stdio Enable HTTP transport
--port <number> PORT 3000 HTTP port
--server-host <host> MCP_HOST localhost Bind address (0.0.0.0 for containers)
--stateless false Disable session management
--cors-origin <origins> MCP_CORS_ORIGIN (blank) CORS allowed origins (comma-separated)
--oauth-enabled OAUTH_ENABLED false Enable OAuth 2.1 authentication
--oauth-issuer <url> OAUTH_ISSUER OAuth issuer URL
--oauth-audience <aud> OAUTH_AUDIENCE Expected JWT audience claim
--oauth-jwks-uri <url> OAUTH_JWKS_URI JWKS endpoint for token verification
--oauth-clock-tolerance <seconds> 60 Clock skew tolerance for JWT validation
--backup-interval <minutes> 0 Automated backup interval (0 = off)
--keep-backups <count> 5 Max backups retained during cleanup
--vacuum-interval <minutes> 0 Database optimize interval (0 = off)
--rebuild-index-interval <minutes> 0 Vector index rebuild interval (0 = off)
--digest-interval <minutes> 0 Proactive analytics computation interval (0 = off)
--briefing-entries <n> BRIEFING_ENTRY_COUNT 3 Journal entries in briefing
--briefing-summaries <n> BRIEFING_SUMMARY_COUNT 1 Session summaries in briefing
--briefing-include-team BRIEFING_INCLUDE_TEAM false Include team DB entries in briefing
--briefing-issues <n> BRIEFING_ISSUE_COUNT 0 Issues to list in briefing (0 = count only)
--briefing-prs <n> BRIEFING_PR_COUNT 0 PRs to list in briefing (0 = count only)
--briefing-pr-status BRIEFING_PR_STATUS false Show PR status breakdown (open/merged/closed)
--rules-file <path> RULES_FILE_PATH Path to user rules file for agent awareness
--skills-dir <path> SKILLS_DIR_PATH Path to skills directory for agent awareness
--briefing-workflows <n> BRIEFING_WORKFLOW_COUNT 0 Workflow runs to list in briefing (0 = status only)
--briefing-workflow-status BRIEFING_WORKFLOW_STATUS false Show workflow status breakdown in briefing
--briefing-copilot BRIEFING_COPILOT_REVIEWS false Aggregate Copilot review state in briefing

Next: Learn about Configuration or check Security.

Clone this wiki locally