5656
5757server_id_var : contextvars .ContextVar [str ] = contextvars .ContextVar ("server_id" , default = None )
5858
59- ## ------------------------------ Event store ------------------------------
59+ # ------------------------------ Event store ------------------------------
6060
6161
6262@dataclass
@@ -92,7 +92,16 @@ def __init__(self, max_events_per_stream: int = 100):
9292 self .event_index : dict [EventId , EventEntry ] = {}
9393
9494 async def store_event (self , stream_id : StreamId , message : JSONRPCMessage ) -> EventId :
95- """Stores an event with a generated event ID."""
95+ """
96+ Stores an event with a generated event ID.
97+
98+ Args:
99+ stream_id (StreamId): The ID of the stream.
100+ message (JSONRPCMessage): The message to store.
101+
102+ Returns:
103+ EventId: The ID of the stored event.
104+ """
96105 event_id = str (uuid4 ())
97106 event_entry = EventEntry (event_id = event_id , stream_id = stream_id , message = message )
98107
@@ -117,7 +126,16 @@ async def replay_events_after(
117126 last_event_id : EventId ,
118127 send_callback : EventCallback ,
119128 ) -> StreamId | None :
120- """Replays events that occurred after the specified event ID."""
129+ """
130+ Replays events that occurred after the specified event ID.
131+
132+ Args:
133+ last_event_id (EventId): The ID of the last received event. Replay starts after this event.
134+ send_callback (EventCallback): Async callback to send each replayed event.
135+
136+ Returns:
137+ StreamId | None: The stream ID if the event is found and replayed, otherwise None.
138+ """
121139 if last_event_id not in self .event_index :
122140 logger .warning (f"Event ID { last_event_id } not found in store" )
123141 return None
@@ -138,7 +156,7 @@ async def replay_events_after(
138156 return stream_id
139157
140158
141- ## ------------------------------ Streamable HTTP Transport ------------------------------
159+ # ------------------------------ Streamable HTTP Transport ------------------------------
142160
143161
144162@asynccontextmanager
@@ -148,7 +166,7 @@ async def get_db():
148166
149167 Yields:
150168 A database session instance from SessionLocal.
151- Ensures the session is closed after use.
169+ Ensures the session is closed after use.
152170 """
153171 db = SessionLocal ()
154172 try :
@@ -168,7 +186,7 @@ async def call_tool(name: str, arguments: dict) -> List[Union[types.TextContent,
168186
169187 Returns:
170188 List of content (TextContent, ImageContent, or EmbeddedResource) from the tool response.
171- Logs and returns an empty list on failure.
189+ Logs and returns an empty list on failure.
172190 """
173191 try :
174192 async with get_db () as db :
@@ -190,7 +208,7 @@ async def list_tools() -> List[types.Tool]:
190208
191209 Returns:
192210 A list of Tool objects containing metadata such as name, description, and input schema.
193- Logs and returns an empty list on failure.
211+ Logs and returns an empty list on failure.
194212 """
195213 server_id = server_id_var .get ()
196214
@@ -260,6 +278,10 @@ async def handle_streamable_http(self, scope: Scope, receive: Receive, send: Sen
260278 scope (Scope): ASGI scope object containing connection information.
261279 receive (Receive): ASGI receive callable.
262280 send (Send): ASGI send callable.
281+
282+ Raises:
283+ Exception: Any exception raised during request handling is logged.
284+
263285 Logs any exceptions that occur during request handling.
264286 """
265287
@@ -277,20 +299,30 @@ async def handle_streamable_http(self, scope: Scope, receive: Receive, send: Sen
277299 raise
278300
279301
280- ## ------------------------- Authentication for /mcp routes ------------------------------
302+ # ------------------------- Authentication for /mcp routes ------------------------------
281303
282304
283305async def streamable_http_auth (scope , receive , send ):
284306 """
285307 Perform authentication check in middleware context (ASGI scope).
286308
287- If path does not end with "/mcp", just continue (return True).
309+ This function is intended to be used in middleware wrapping ASGI apps.
310+ It authenticates only requests targeting paths ending in "/mcp" or "/mcp/".
288311
289- Only check Authorization header for Bearer token.
290- If no Bearer token provided, allow (return True).
312+ Behavior:
313+ - If the path does not end with "/mcp", authentication is skipped.
314+ - If there is no Authorization header, the request is allowed.
315+ - If a Bearer token is present, it is verified using `verify_credentials`.
316+ - If verification fails, a 401 Unauthorized JSON response is sent.
291317
292- If auth_required is True and Bearer token provided, verify it.
293- If verification fails, send 401 JSONResponse and return False.
318+ Args:
319+ scope: The ASGI scope dictionary, which includes request metadata.
320+ receive: ASGI receive callable used to receive events.
321+ send: ASGI send callable used to send events (e.g. a 401 response).
322+
323+ Returns:
324+ bool: True if authentication passes or is skipped.
325+ False if authentication fails and a 401 response is sent.
294326 """
295327
296328 path = scope .get ("path" , "" )
0 commit comments