Skip to content

Add Server-Sent Events (SSE) streaming support to MCP transport#90

Merged
rg4444 merged 1 commit into
mainfrom
claude/add-sse-support-G9SVS
Feb 6, 2026
Merged

Add Server-Sent Events (SSE) streaming support to MCP transport#90
rg4444 merged 1 commit into
mainfrom
claude/add-sse-support-G9SVS

Conversation

@rg4444
Copy link
Copy Markdown
Contributor

@rg4444 rg4444 commented Feb 6, 2026

Summary

This PR adds Server-Sent Events (SSE) streaming support to the MCP (Model Context Protocol) HTTP transport layer, enabling bidirectional communication between clients and the Gitea MCP server.

Changes

  1. New SSE module (modules/mcp/sse.go):

    • Implements SSESession for managing individual SSE connections
    • Implements SSESessionManager for tracking active sessions with a configurable limit (100 concurrent sessions)
    • Provides serveSSE() handler for GET requests to establish streaming connections
    • Includes SSE event/comment writing utilities with proper spec compliance
    • Generates cryptographically secure session IDs for session tracking
  2. Enhanced transport layer (modules/mcp/transport.go):

    • Modified ServeHTTP() to support both GET (SSE) and POST (single request) methods
    • Added handlePost() to process single JSON-RPC requests
    • Added handleSessionMessage() to route POST requests with Mcp-Session-Id header to the correct SSE session
    • Maintains backward compatibility with existing POST-only clients
  3. Comprehensive test coverage (modules/mcp/transport_test.go):

    • Tests for POST request handling (initialize, ping, notifications, error cases)
    • Tests for SSE connection establishment and header validation
    • Tests for session management (register, unregister, capacity limits)
    • Tests for SSE event/comment formatting
    • Integration test for POST-to-SSE-session communication flow

How it works

  1. Client initiates SSE connection via GET request with Accept: text/event-stream
  2. Server creates a session, returns Mcp-Session-Id header and endpoint URI
  3. Client sends JSON-RPC requests via POST with Mcp-Session-Id header
  4. Server routes requests to the session and streams responses back via SSE
  5. Server sends keepalive comments every 30 seconds to maintain connection

Features

  • Proper SSE header validation and CORS support
  • Keepalive mechanism to prevent connection timeouts
  • Session capacity management to prevent resource exhaustion
  • Graceful error handling and logging
  • Full JSON-RPC 2.0 compliance for both streaming and single-request modes

Validation

  • No BPMN render/diff impact
  • No image regeneration needed
  • No schema/validator updates required

Testing

  • Unit tests for all SSE components (session management, event writing, ID generation)
  • Integration tests for POST and GET request handling
  • Tests for error cases (bad headers, invalid JSON, session not found)
  • Tests for session capacity limits and concurrent connection handling
  • All tests pass with proper context cancellation and cleanup

https://claude.ai/code/session_0158JDGAyxGQo7eofDYbFXSz

Add Server-Sent Events (SSE) streaming alongside existing HTTP POST
handling so Claude Desktop can connect directly without a bridge script.

- Refactor ServeHTTP to dispatch GET (SSE) and POST requests
- Add SSE session management with keepalive and message routing
- Support bidirectional communication: GET opens SSE stream, POST with
  Mcp-Session-Id header sends JSON-RPC messages to the session
- Send endpoint event on connection for client discovery
- Use cryptographic session IDs and cap concurrent sessions at 100
- Add CORS headers and nginx buffering bypass for SSE streams
- Add comprehensive tests for SSE connection, session management,
  helper functions, and POST-to-SSE integration

https://claude.ai/code/session_0158JDGAyxGQo7eofDYbFXSz
@rg4444 rg4444 merged commit a7d6d68 into main Feb 6, 2026
13 of 23 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dda093ca2a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modules/mcp/sse.go
Comment on lines +143 to +146
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id")
w.Header().Set("Mcp-Session-Id", sessionID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose session ID header for cross-origin clients

The SSE response sets Access-Control-Allow-Origin: * but never exposes Mcp-Session-Id via Access-Control-Expose-Headers. For browser clients using fetch() to open the stream, the session ID header is not readable unless it is explicitly exposed, which prevents the client from attaching the required Mcp-Session-Id on subsequent POSTs. This breaks the advertised GET-then-POST flow for cross-origin usage even though CORS headers are being sent.

Useful? React with 👍 / 👎.

Comment thread modules/mcp/transport.go
Comment on lines +118 to +121
// Send to session for processing
if !session.SendRequest(&req) {
http.Error(w, "Session closed", http.StatusGone)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid treating a full request buffer as a closed session

SendRequest returns false both when the session is closed and when the request channel is full. Here, any false is converted into a 410 Session closed response, so a burst of >16 queued requests (or slow processing) will drop a request and tell the client the session is closed even though the SSE stream is still alive. This can cause lost messages and unnecessary reconnects under load; the error should distinguish backpressure from a closed session (e.g., 429/503 or blocking).

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants