Skip to content

Commit 33d2f55

Browse files
authored
Merge pull request #110 from IBM/streamable-http-support
Streamable http support
2 parents 5897031 + d47599c commit 33d2f55

File tree

5 files changed

+386
-6
lines changed

5 files changed

+386
-6
lines changed

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,18 @@ WEBSOCKET_PING_INTERVAL=30
140140
# SSE client retry timeout (milliseconds)
141141
SSE_RETRY_TIMEOUT=5000
142142

143+
144+
#####################################
145+
# Streamabe HTTP Transport Configuration
146+
#####################################
147+
148+
# Set False to use stateless sessions without event store and True for stateful sessions
149+
USE_STATEFUL_SESSIONS=false
150+
151+
# Set true for JSON responses, false for SSE streams
152+
JSON_RESPONSE_ENABLED=true
153+
154+
143155
#####################################
144156
# Federation
145157
#####################################

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ It supports:
3030

3131
* Federation across multiple MCP and REST services
3232
* Virtualization of legacy APIs as MCP-compliant tools and servers
33-
* Transport over HTTP, JSON-RPC, WebSocket, SSE, and stdio
33+
* Transport over HTTP, JSON-RPC, WebSocket, SSE, stdio and streamable-HTTP
3434
* An Admin UI for real-time management and configuration
3535
* Built-in auth, observability, retries, and rate-limiting
3636
* Scalable deployments via Docker or PyPI, Redis-backed caching, and multi-cluster federation
@@ -833,11 +833,13 @@ You can get started by copying the provided [.env.example](.env.example) to `.en
833833

834834
### Transport
835835

836-
| Setting | Description | Default | Options |
837-
| ------------------------- | ---------------------- | ------- | ------------------------------- |
838-
| `TRANSPORT_TYPE` | Enabled transports | `all` | `http`,`ws`,`sse`,`stdio`,`all` |
839-
| `WEBSOCKET_PING_INTERVAL` | WebSocket ping (secs) | `30` | int > 0 |
840-
| `SSE_RETRY_TIMEOUT` | SSE retry timeout (ms) | `5000` | int > 0 |
836+
| Setting | Description | Default | Options |
837+
| ------------------------- | ---------------------------------- | ------- | ------------------------------- |
838+
| `TRANSPORT_TYPE` | Enabled transports | `all` | `http`,`ws`,`sse`,`stdio`,`all` |
839+
| `WEBSOCKET_PING_INTERVAL` | WebSocket ping (secs) | `30` | int > 0 |
840+
| `SSE_RETRY_TIMEOUT` | SSE retry timeout (ms) | `5000` | int > 0 |
841+
| `USE_STATEFUL_SESSIONS` | streamable http config | `false` | bool |
842+
| `JSON_RESPONSE_ENABLED` | json/sse streams (streamable http) | `true` | bool |
841843

842844
### Federation
843845

mcpgateway/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ def _parse_federation_peers(cls, v):
191191
session_ttl: int = 3600
192192
message_ttl: int = 600
193193

194+
# streamable http transport
195+
use_stateful_sessions: bool = False # Set to False to use stateless sessions without event store
196+
json_response_enabled: bool = True # Enable JSON responses instead of SSE streams
197+
194198
# Development
195199
dev_mode: bool = False
196200
reload: bool = False

mcpgateway/main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
ToolService,
105105
)
106106
from mcpgateway.transports.sse_transport import SSETransport
107+
from mcpgateway.transports.streamablehttp_transport import SessionManagerWrapper, JWTAuthMiddlewareStreamableHttp
107108
from mcpgateway.types import (
108109
InitializeRequest,
109110
InitializeResult,
@@ -144,6 +145,9 @@
144145
sampling_handler = SamplingHandler()
145146
server_service = ServerService()
146147

148+
# Initialize session manager for Streamable HTTP transport
149+
streamable_http_session = SessionManagerWrapper()
150+
147151

148152
# Initialize session registry
149153
session_registry = SessionRegistry(
@@ -190,13 +194,16 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
190194
await logging_service.initialize()
191195
await sampling_handler.initialize()
192196
await resource_cache.initialize()
197+
await streamable_http_session.start()
198+
193199
logger.info("All services initialized successfully")
194200
yield
195201
except Exception as e:
196202
logger.error(f"Error during startup: {str(e)}")
197203
raise
198204
finally:
199205
logger.info("Shutting down MCP Gateway services")
206+
# await stop_streamablehttp()
200207
for service in [
201208
resource_cache,
202209
sampling_handler,
@@ -207,6 +214,7 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
207214
prompt_service,
208215
resource_service,
209216
tool_service,
217+
streamable_http_session
210218
]:
211219
try:
212220
await service.shutdown()
@@ -276,6 +284,9 @@ async def dispatch(self, request: Request, call_next):
276284
# Add custom DocsAuthMiddleware
277285
app.add_middleware(DocsAuthMiddleware)
278286

287+
# Add streamable HTTP middleware for JWT auth
288+
app.add_middleware(JWTAuthMiddlewareStreamableHttp)
289+
279290
# Set up Jinja2 templates and store in app state for later use
280291
templates = Jinja2Templates(directory=str(settings.templates_dir))
281292
app.state.templates = templates
@@ -2028,6 +2039,9 @@ async def readiness_check(db: Session = Depends(get_db)):
20282039
else:
20292040
logger.warning("Admin API routes not mounted - Admin API disabled via MCPGATEWAY_ADMIN_API_ENABLED=False")
20302041

2042+
# Streamable http Mount
2043+
app.mount("/mcp", app=streamable_http_session.handle_streamable_http)
2044+
20312045
# Conditional static files mounting and root redirect
20322046
if UI_ENABLED:
20332047
# Mount static files for UI

0 commit comments

Comments
 (0)