Skip to content

Commit 78766ae

Browse files
authored
Merge branch 'IBM:main' into main
2 parents a435c3b + 6f9db42 commit 78766ae

File tree

8 files changed

+411
-58
lines changed

8 files changed

+411
-58
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: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[![Docker Image](https://img.shields.io/badge/docker-ghcr.io%2Fibm%2Fmcp--context--forge-blue)](https://github.com/ibm/mcp-context-forge/pkgs/container/mcp-context-forge) 
1818

1919

20-
ContextForge MCP Gateway is a feature-rich gateway & proxy that federates MCP and REST services - unifying discovery, auth, rate-limiting, observability, virtual servers, multi-transport protocols, and an optional Admin UI into one clean endpoint for your AI clients. It runs as a fully compliant MCP server, deployable via PyPI or Docker, and scales to multi-cluster environments on Kubernetes with Redis-backed federation and caching.
20+
ContextForge MCP Gateway is a feature-rich gateway, proxy and MCP Registry that federates MCP and REST services - unifying discovery, auth, rate-limiting, observability, virtual servers, multi-transport protocols, and an optional Admin UI into one clean endpoint for your AI clients. It runs as a fully compliant MCP server, deployable via PyPI or Docker, and scales to multi-cluster environments on Kubernetes with Redis-backed federation and caching.
2121

2222
![MCP Gateway](https://ibm.github.io/mcp-context-forge/images/mcpgateway.gif)
2323
---
@@ -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
@@ -51,7 +51,7 @@ For a list of upcoming features, check out the [ContextForge MCP Gateway Roadmap
5151
</details>
5252

5353
<details>
54-
<summary><strong>🌐 Federation of Peer Gateways</strong></summary>
54+
<summary><strong>🌐 Federation of Peer Gateways (MCP Registry)</strong></summary>
5555

5656
* Auto-discovers or configures peer gateways (via mDNS or manual)
5757
* Performs health checks and merges remote registries transparently
@@ -275,6 +275,8 @@ docker run -d --name mcpgateway \
275275
ghcr.io/ibm/mcp-context-forge:0.1.1
276276
```
277277

278+
Using `--network=host` allows Docker to access the local network, allowing you to add MCP servers running on your host. See [Docker Host network driver documentation](https://docs.docker.com/engine/network/drivers/host/) for more details.
279+
278280
</details>
279281

280282
---
@@ -833,11 +835,13 @@ You can get started by copying the provided [.env.example](.env.example) to `.en
833835

834836
### Transport
835837

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

842846
### Federation
843847

@@ -1717,7 +1721,7 @@ podman-prod - Build production container image (using ubi-micro → scr
17171721
podman-run - Run the container on HTTP (port 4444)
17181722
podman-run-shell - Run the container on HTTP (port 4444) and start a shell
17191723
podman-run-ssl - Run the container on HTTPS (port 4444, self-signed)
1720-
podman-run-ssl-host - Run the container on HTTPS with --network-host (port 4444, self-signed)
1724+
podman-run-ssl-host - Run the container on HTTPS with --network=host (port 4444, self-signed)
17211725
podman-stop - Stop & remove the container
17221726
podman-test - Quick curl smoke-test against the container
17231727
podman-logs - Follow container logs (⌃C to quit)

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: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@
104104
ToolService,
105105
)
106106
from mcpgateway.transports.sse_transport import SSETransport
107+
from mcpgateway.transports.streamablehttp_transport import (
108+
JWTAuthMiddlewareStreamableHttp,
109+
SessionManagerWrapper,
110+
)
107111
from mcpgateway.types import (
108112
InitializeRequest,
109113
InitializeResult,
@@ -144,6 +148,9 @@
144148
sampling_handler = SamplingHandler()
145149
server_service = ServerService()
146150

151+
# Initialize session manager for Streamable HTTP transport
152+
streamable_http_session = SessionManagerWrapper()
153+
147154

148155
# Initialize session registry
149156
session_registry = SessionRegistry(
@@ -190,24 +197,17 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
190197
await logging_service.initialize()
191198
await sampling_handler.initialize()
192199
await resource_cache.initialize()
200+
await streamable_http_session.start()
201+
193202
logger.info("All services initialized successfully")
194203
yield
195204
except Exception as e:
196205
logger.error(f"Error during startup: {str(e)}")
197206
raise
198207
finally:
199208
logger.info("Shutting down MCP Gateway services")
200-
for service in [
201-
resource_cache,
202-
sampling_handler,
203-
logging_service,
204-
completion_service,
205-
root_service,
206-
gateway_service,
207-
prompt_service,
208-
resource_service,
209-
tool_service,
210-
]:
209+
# await stop_streamablehttp()
210+
for service in [resource_cache, sampling_handler, logging_service, completion_service, root_service, gateway_service, prompt_service, resource_service, tool_service, streamable_http_session]:
211211
try:
212212
await service.shutdown()
213213
except Exception as e:
@@ -276,6 +276,9 @@ async def dispatch(self, request: Request, call_next):
276276
# Add custom DocsAuthMiddleware
277277
app.add_middleware(DocsAuthMiddleware)
278278

279+
# Add streamable HTTP middleware for JWT auth
280+
app.add_middleware(JWTAuthMiddlewareStreamableHttp)
281+
279282
# Set up Jinja2 templates and store in app state for later use
280283
templates = Jinja2Templates(directory=str(settings.templates_dir))
281284
app.state.templates = templates
@@ -2028,6 +2031,9 @@ async def readiness_check(db: Session = Depends(get_db)):
20282031
else:
20292032
logger.warning("Admin API routes not mounted - Admin API disabled via MCPGATEWAY_ADMIN_API_ENABLED=False")
20302033

2034+
# Streamable http Mount
2035+
app.mount("/mcp", app=streamable_http_session.handle_streamable_http)
2036+
20312037
# Conditional static files mounting and root redirect
20322038
if UI_ENABLED:
20332039
# Mount static files for UI

mcpgateway/templates/admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ <h3 class="text-lg font-bold mb-4">Add New Prompt</h3>
12141214
<!-- Gateways Panel -->
12151215
<div id="gateways-panel" class="tab-panel hidden">
12161216
<div class="flex justify-between items-center mb-4">
1217-
<h2 class="text-2xl font-bold">Federated Gateways (MCP)</h2>
1217+
<h2 class="text-2xl font-bold">Federated Gateways (MCP Registry)</h2>
12181218
<p class="text-sm text-gray-600 mt-1">Gateways are where you register external MCP servers to federate their tools/resources/prompts into your environment.</p>
12191219
<div class="flex items-center">
12201220
<input

0 commit comments

Comments
 (0)