Skip to content

Commit 12df0b1

Browse files
authored
Merge pull request #111 from IBM/documentation-review
Linting
2 parents 33d2f55 + 1c84a2c commit 12df0b1

File tree

5 files changed

+43
-67
lines changed

5 files changed

+43
-67
lines changed

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ SSE_RETRY_TIMEOUT=5000
146146
#####################################
147147

148148
# Set False to use stateless sessions without event store and True for stateful sessions
149-
USE_STATEFUL_SESSIONS=false
149+
USE_STATEFUL_SESSIONS=false
150150

151151
# Set true for JSON responses, false for SSE streams
152-
JSON_RESPONSE_ENABLED=true
152+
JSON_RESPONSE_ENABLED=true
153153

154154

155155
#####################################

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
---
@@ -1719,7 +1721,7 @@ podman-prod - Build production container image (using ubi-micro → scr
17191721
podman-run - Run the container on HTTP (port 4444)
17201722
podman-run-shell - Run the container on HTTP (port 4444) and start a shell
17211723
podman-run-ssl - Run the container on HTTPS (port 4444, self-signed)
1722-
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)
17231725
podman-stop - Stop & remove the container
17241726
podman-test - Quick curl smoke-test against the container
17251727
podman-logs - Follow container logs (⌃C to quit)

mcpgateway/main.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@
104104
ToolService,
105105
)
106106
from mcpgateway.transports.sse_transport import SSETransport
107-
from mcpgateway.transports.streamablehttp_transport import SessionManagerWrapper, JWTAuthMiddlewareStreamableHttp
107+
from mcpgateway.transports.streamablehttp_transport import (
108+
JWTAuthMiddlewareStreamableHttp,
109+
SessionManagerWrapper,
110+
)
108111
from mcpgateway.types import (
109112
InitializeRequest,
110113
InitializeResult,
@@ -204,18 +207,7 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
204207
finally:
205208
logger.info("Shutting down MCP Gateway services")
206209
# await stop_streamablehttp()
207-
for service in [
208-
resource_cache,
209-
sampling_handler,
210-
logging_service,
211-
completion_service,
212-
root_service,
213-
gateway_service,
214-
prompt_service,
215-
resource_service,
216-
tool_service,
217-
streamable_http_session
218-
]:
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]:
219211
try:
220212
await service.shutdown()
221213
except Exception as e:

mcpgateway/transports/streamablehttp_transport.py

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,42 @@
1111
- SessionManagerWrapper: Manages the lifecycle of streamable HTTP sessions
1212
- JWTAuthMiddlewareStreamableHttp: Middleware for JWT authentication
1313
- Configuration options for:
14-
1. stateful/stateless operation
14+
1. stateful/stateless operation
1515
2. JSON response mode or SSE streams
1616
- InMemoryEventStore: A simple in-memory event storage system for maintaining session state
1717
1818
"""
1919

2020
import logging
21+
from collections import deque
2122
from contextlib import AsyncExitStack, asynccontextmanager
23+
from dataclasses import dataclass
2224
from typing import List, Union
23-
24-
from starlette.types import Receive, Scope, Send
25-
from starlette.middleware.base import BaseHTTPMiddleware
26-
from starlette.requests import Request
27-
from starlette.responses import JSONResponse
28-
from starlette.datastructures import Headers
29-
from fastapi.security.utils import get_authorization_scheme_param
30-
from starlette.status import HTTP_401_UNAUTHORIZED
31-
from starlette.types import ASGIApp
25+
from uuid import uuid4
3226

3327
import mcp.types as types
28+
from fastapi.security.utils import get_authorization_scheme_param
3429
from mcp.server.lowlevel import Server
35-
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
36-
37-
from mcpgateway.services.tool_service import ToolService
38-
from mcpgateway.db import SessionLocal
39-
from mcpgateway.config import settings
40-
from mcpgateway.utils.verify_credentials import verify_credentials
41-
42-
43-
from collections import deque
44-
from dataclasses import dataclass
45-
from uuid import uuid4
46-
4730
from mcp.server.streamable_http import (
4831
EventCallback,
4932
EventId,
5033
EventMessage,
5134
EventStore,
5235
StreamId,
5336
)
37+
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
5438
from mcp.types import JSONRPCMessage
39+
from starlette.datastructures import Headers
40+
from starlette.middleware.base import BaseHTTPMiddleware
41+
from starlette.requests import Request
42+
from starlette.responses import JSONResponse
43+
from starlette.status import HTTP_401_UNAUTHORIZED
44+
from starlette.types import ASGIApp, Receive, Scope, Send
5545

46+
from mcpgateway.config import settings
47+
from mcpgateway.db import SessionLocal
48+
from mcpgateway.services.tool_service import ToolService
49+
from mcpgateway.utils.verify_credentials import verify_credentials
5650

5751
logger = logging.getLogger(__name__)
5852
logging.basicConfig(level=logging.INFO)
@@ -63,6 +57,7 @@
6357

6458
## ------------------------------ Event store ------------------------------
6559

60+
6661
@dataclass
6762
class EventEntry:
6863
"""
@@ -95,14 +90,10 @@ def __init__(self, max_events_per_stream: int = 100):
9590
# event_id -> EventEntry for quick lookup
9691
self.event_index: dict[EventId, EventEntry] = {}
9792

98-
async def store_event(
99-
self, stream_id: StreamId, message: JSONRPCMessage
100-
) -> EventId:
93+
async def store_event(self, stream_id: StreamId, message: JSONRPCMessage) -> EventId:
10194
"""Stores an event with a generated event ID."""
10295
event_id = str(uuid4())
103-
event_entry = EventEntry(
104-
event_id=event_id, stream_id=stream_id, message=message
105-
)
96+
event_entry = EventEntry(event_id=event_id, stream_id=stream_id, message=message)
10697

10798
# Get or create deque for this stream
10899
if stream_id not in self.streams:
@@ -148,6 +139,7 @@ async def replay_events_after(
148139

149140
## ------------------------------ Streamable HTTP Transport ------------------------------
150141

142+
151143
@asynccontextmanager
152144
async def get_db():
153145
"""
@@ -184,12 +176,7 @@ async def call_tool(name: str, arguments: dict) -> List[Union[types.TextContent,
184176
logger.warning(f"No content returned by tool: {name}")
185177
return []
186178

187-
return [
188-
types.TextContent(
189-
type=result.content[0].type,
190-
text=result.content[0].text
191-
)
192-
]
179+
return [types.TextContent(type=result.content[0].type, text=result.content[0].text)]
193180
except Exception as e:
194181
logger.exception(f"Error calling tool '{name}': {e}")
195182
return []
@@ -207,19 +194,12 @@ async def list_tools() -> List[types.Tool]:
207194
try:
208195
async with get_db() as db:
209196
tools = await tool_service.list_tools(db)
210-
return [
211-
types.Tool(
212-
name=tool.name,
213-
description=tool.description,
214-
inputSchema=tool.input_schema
215-
) for tool in tools
216-
]
197+
return [types.Tool(name=tool.name, description=tool.description, inputSchema=tool.input_schema) for tool in tools]
217198
except Exception as e:
218199
logger.exception("Error listing tools")
219200
return []
220201

221202

222-
223203
class SessionManagerWrapper:
224204
"""
225205
Wrapper class for managing the lifecycle of a StreamableHTTPSessionManager instance.
@@ -230,13 +210,13 @@ def __init__(self) -> None:
230210
"""
231211
Initializes the session manager and the exit stack used for managing its lifecycle.
232212
"""
233-
213+
234214
if settings.use_stateful_sessions:
235215
event_store = InMemoryEventStore()
236-
stateless=False
216+
stateless = False
237217
else:
238-
event_store=None
239-
stateless=True
218+
event_store = None
219+
stateless = True
240220

241221
self.session_manager = StreamableHTTPSessionManager(
242222
app=mcp_app,
@@ -252,7 +232,7 @@ async def start(self) -> None:
252232
"""
253233
logger.info("Initializing Streamable HTTP service")
254234
await self.stack.enter_async_context(self.session_manager.run())
255-
235+
256236
async def shutdown(self) -> None:
257237
"""
258238
Gracefully shuts down the Streamable HTTP session manager.
@@ -276,14 +256,17 @@ async def handle_streamable_http(self, scope: Scope, receive: Receive, send: Sen
276256
logger.exception("Error handling streamable HTTP request")
277257
raise
278258

259+
279260
## ------------------------- FastAPI Middleware for Authentication ------------------------------
280261

262+
281263
class JWTAuthMiddlewareStreamableHttp(BaseHTTPMiddleware):
282264
"""
283265
Middleware for handling JWT authentication in an ASGI application.
284266
This middleware checks for JWT tokens in the authorization header or cookies
285267
and verifies the credentials before allowing access to protected routes.
286268
"""
269+
287270
def __init__(self, app: ASGIApp):
288271
"""
289272
Initialize the middleware with the given ASGI application.
@@ -344,5 +327,3 @@ async def dispatch(self, request: Request, call_next):
344327
status_code=HTTP_401_UNAUTHORIZED,
345328
headers={"WWW-Authenticate": "Bearer"},
346329
)
347-
348-

mcpgateway/wrapper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,14 @@ def _extract_base_url(url: str) -> str:
113113

114114
path = parsed.path or ""
115115
if "/servers/" in path:
116-
path = path.split("/servers")[0] # ".../servers/123" -> "..."
116+
path = path.split("/servers")[0] # ".../servers/123" -> "..."
117117
elif path.endswith("/servers"):
118-
path = path[:-len("/servers")] # ".../servers" -> "..."
118+
path = path[: -len("/servers")] # ".../servers" -> "..."
119119
# otherwise keep the existing path (supports APP_ROOT_PATH)
120120

121121
return f"{parsed.scheme}://{parsed.netloc}{path}"
122122

123+
123124
BASE_URL: str = _extract_base_url(SERVER_CATALOG_URLS[0]) if SERVER_CATALOG_URLS else ""
124125

125126
# -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)