Use 120 characters instead of 88 (#856)
This commit is contained in:
parent
f7265f7b91
commit
543961968c
1 changed files with 687 additions and 2142 deletions
|
|
@ -82,9 +82,7 @@ class EventStore(ABC):
|
|||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def store_event(
|
||||
self, stream_id: StreamId, message: JSONRPCMessage
|
||||
) -> EventId:
|
||||
async def store_event(self, stream_id: StreamId, message: JSONRPCMessage) -> EventId:
|
||||
"""
|
||||
Stores an event for later retrieval.
|
||||
|
||||
|
|
@ -125,9 +123,7 @@ class StreamableHTTPServerTransport:
|
|||
"""
|
||||
|
||||
# Server notification streams for POST requests as well as standalone SSE stream
|
||||
_read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] | None = (
|
||||
None
|
||||
)
|
||||
_read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] | None = None
|
||||
_read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] | None = None
|
||||
_write_stream: MemoryObjectSendStream[SessionMessage] | None = None
|
||||
_write_stream_reader: MemoryObjectReceiveStream[SessionMessage] | None = None
|
||||
|
|
@ -153,12 +149,8 @@ class StreamableHTTPServerTransport:
|
|||
Raises:
|
||||
ValueError: If the session ID contains invalid characters.
|
||||
"""
|
||||
if mcp_session_id is not None and not SESSION_ID_PATTERN.fullmatch(
|
||||
mcp_session_id
|
||||
):
|
||||
raise ValueError(
|
||||
"Session ID must only contain visible ASCII characters (0x21-0x7E)"
|
||||
)
|
||||
if mcp_session_id is not None and not SESSION_ID_PATTERN.fullmatch(mcp_session_id):
|
||||
raise ValueError("Session ID must only contain visible ASCII characters (0x21-0x7E)")
|
||||
|
||||
self.mcp_session_id = mcp_session_id
|
||||
self.is_json_response_enabled = is_json_response_enabled
|
||||
|
|
@ -218,9 +210,7 @@ class StreamableHTTPServerTransport:
|
|||
response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id
|
||||
|
||||
return Response(
|
||||
response_message.model_dump_json(by_alias=True, exclude_none=True)
|
||||
if response_message
|
||||
else None,
|
||||
response_message.model_dump_json(by_alias=True, exclude_none=True) if response_message else None,
|
||||
status_code=status_code,
|
||||
headers=response_headers,
|
||||
)
|
||||
|
|
@ -233,9 +223,7 @@ class StreamableHTTPServerTransport:
|
|||
"""Create event data dictionary from an EventMessage."""
|
||||
event_data = {
|
||||
"event": "message",
|
||||
"data": event_message.message.model_dump_json(
|
||||
by_alias=True, exclude_none=True
|
||||
),
|
||||
"data": event_message.message.model_dump_json(by_alias=True, exclude_none=True),
|
||||
}
|
||||
|
||||
# If an event ID was provided, include it
|
||||
|
|
@ -283,42 +271,29 @@ class StreamableHTTPServerTransport:
|
|||
accept_header = request.headers.get("accept", "")
|
||||
accept_types = [media_type.strip() for media_type in accept_header.split(",")]
|
||||
|
||||
has_json = any(
|
||||
media_type.startswith(CONTENT_TYPE_JSON) for media_type in accept_types
|
||||
)
|
||||
has_sse = any(
|
||||
media_type.startswith(CONTENT_TYPE_SSE) for media_type in accept_types
|
||||
)
|
||||
has_json = any(media_type.startswith(CONTENT_TYPE_JSON) for media_type in accept_types)
|
||||
has_sse = any(media_type.startswith(CONTENT_TYPE_SSE) for media_type in accept_types)
|
||||
|
||||
return has_json, has_sse
|
||||
|
||||
def _check_content_type(self, request: Request) -> bool:
|
||||
"""Check if the request has the correct Content-Type."""
|
||||
content_type = request.headers.get("content-type", "")
|
||||
content_type_parts = [
|
||||
part.strip() for part in content_type.split(";")[0].split(",")
|
||||
]
|
||||
content_type_parts = [part.strip() for part in content_type.split(";")[0].split(",")]
|
||||
|
||||
return any(part == CONTENT_TYPE_JSON for part in content_type_parts)
|
||||
|
||||
async def _handle_post_request(
|
||||
self, scope: Scope, request: Request, receive: Receive, send: Send
|
||||
) -> None:
|
||||
async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None:
|
||||
"""Handle POST requests containing JSON-RPC messages."""
|
||||
writer = self._read_stream_writer
|
||||
if writer is None:
|
||||
raise ValueError(
|
||||
"No read stream writer available. Ensure connect() is called first."
|
||||
)
|
||||
raise ValueError("No read stream writer available. Ensure connect() is called first.")
|
||||
try:
|
||||
# Check Accept headers
|
||||
has_json, has_sse = self._check_accept_headers(request)
|
||||
if not (has_json and has_sse):
|
||||
response = self._create_error_response(
|
||||
(
|
||||
"Not Acceptable: Client must accept both application/json and "
|
||||
"text/event-stream"
|
||||
),
|
||||
("Not Acceptable: Client must accept both application/json and " "text/event-stream"),
|
||||
HTTPStatus.NOT_ACCEPTABLE,
|
||||
)
|
||||
await response(scope, receive, send)
|
||||
|
|
@ -346,9 +321,7 @@ class StreamableHTTPServerTransport:
|
|||
try:
|
||||
raw_message = json.loads(body)
|
||||
except json.JSONDecodeError as e:
|
||||
response = self._create_error_response(
|
||||
f"Parse error: {str(e)}", HTTPStatus.BAD_REQUEST, PARSE_ERROR
|
||||
)
|
||||
response = self._create_error_response(f"Parse error: {str(e)}", HTTPStatus.BAD_REQUEST, PARSE_ERROR)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
|
||||
|
|
@ -364,10 +337,7 @@ class StreamableHTTPServerTransport:
|
|||
return
|
||||
|
||||
# Check if this is an initialization request
|
||||
is_initialization_request = (
|
||||
isinstance(message.root, JSONRPCRequest)
|
||||
and message.root.method == "initialize"
|
||||
)
|
||||
is_initialization_request = isinstance(message.root, JSONRPCRequest) and message.root.method == "initialize"
|
||||
|
||||
if is_initialization_request:
|
||||
# Check if the server already has an established session
|
||||
|
|
@ -406,9 +376,7 @@ class StreamableHTTPServerTransport:
|
|||
# Extract the request ID outside the try block for proper scope
|
||||
request_id = str(message.root.id)
|
||||
# Register this stream for the request ID
|
||||
self._request_streams[request_id] = anyio.create_memory_object_stream[
|
||||
EventMessage
|
||||
](0)
|
||||
self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](0)
|
||||
request_stream_reader = self._request_streams[request_id][1]
|
||||
|
||||
if self.is_json_response_enabled:
|
||||
|
|
@ -424,16 +392,12 @@ class StreamableHTTPServerTransport:
|
|||
# Use similar approach to SSE writer for consistency
|
||||
async for event_message in request_stream_reader:
|
||||
# If it's a response, this is what we're waiting for
|
||||
if isinstance(
|
||||
event_message.message.root, JSONRPCResponse | JSONRPCError
|
||||
):
|
||||
if isinstance(event_message.message.root, JSONRPCResponse | JSONRPCError):
|
||||
response_message = event_message.message
|
||||
break
|
||||
# For notifications and request, keep waiting
|
||||
else:
|
||||
logger.debug(
|
||||
f"received: {event_message.message.root.method}"
|
||||
)
|
||||
logger.debug(f"received: {event_message.message.root.method}")
|
||||
|
||||
# At this point we should have a response
|
||||
if response_message:
|
||||
|
|
@ -442,9 +406,7 @@ class StreamableHTTPServerTransport:
|
|||
await response(scope, receive, send)
|
||||
else:
|
||||
# This shouldn't happen in normal operation
|
||||
logger.error(
|
||||
"No response message received before stream closed"
|
||||
)
|
||||
logger.error("No response message received before stream closed")
|
||||
response = self._create_error_response(
|
||||
"Error processing request: No response received",
|
||||
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
|
|
@ -462,9 +424,7 @@ class StreamableHTTPServerTransport:
|
|||
await self._clean_up_memory_streams(request_id)
|
||||
else:
|
||||
# Create SSE stream
|
||||
sse_stream_writer, sse_stream_reader = (
|
||||
anyio.create_memory_object_stream[dict[str, str]](0)
|
||||
)
|
||||
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0)
|
||||
|
||||
async def sse_writer():
|
||||
# Get the request ID from the incoming request message
|
||||
|
|
@ -495,11 +455,7 @@ class StreamableHTTPServerTransport:
|
|||
"Cache-Control": "no-cache, no-transform",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": CONTENT_TYPE_SSE,
|
||||
**(
|
||||
{MCP_SESSION_ID_HEADER: self.mcp_session_id}
|
||||
if self.mcp_session_id
|
||||
else {}
|
||||
),
|
||||
**({MCP_SESSION_ID_HEADER: self.mcp_session_id} if self.mcp_session_id else {}),
|
||||
}
|
||||
response = EventSourceResponse(
|
||||
content=sse_stream_reader,
|
||||
|
|
@ -544,9 +500,7 @@ class StreamableHTTPServerTransport:
|
|||
"""
|
||||
writer = self._read_stream_writer
|
||||
if writer is None:
|
||||
raise ValueError(
|
||||
"No read stream writer available. Ensure connect() is called first."
|
||||
)
|
||||
raise ValueError("No read stream writer available. Ensure connect() is called first.")
|
||||
|
||||
# Validate Accept header - must include text/event-stream
|
||||
_, has_sse = self._check_accept_headers(request)
|
||||
|
|
@ -585,17 +539,13 @@ class StreamableHTTPServerTransport:
|
|||
return
|
||||
|
||||
# Create SSE stream
|
||||
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[
|
||||
dict[str, str]
|
||||
](0)
|
||||
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0)
|
||||
|
||||
async def standalone_sse_writer():
|
||||
try:
|
||||
# Create a standalone message stream for server-initiated messages
|
||||
|
||||
self._request_streams[GET_STREAM_KEY] = (
|
||||
anyio.create_memory_object_stream[EventMessage](0)
|
||||
)
|
||||
self._request_streams[GET_STREAM_KEY] = anyio.create_memory_object_stream[EventMessage](0)
|
||||
standalone_stream_reader = self._request_streams[GET_STREAM_KEY][1]
|
||||
|
||||
async with sse_stream_writer, standalone_stream_reader:
|
||||
|
|
@ -732,9 +682,7 @@ class StreamableHTTPServerTransport:
|
|||
|
||||
return True
|
||||
|
||||
async def _replay_events(
|
||||
self, last_event_id: str, request: Request, send: Send
|
||||
) -> None:
|
||||
async def _replay_events(self, last_event_id: str, request: Request, send: Send) -> None:
|
||||
"""
|
||||
Replays events that would have been sent after the specified event ID.
|
||||
Only used when resumability is enabled.
|
||||
|
|
@ -754,9 +702,7 @@ class StreamableHTTPServerTransport:
|
|||
headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id
|
||||
|
||||
# Create SSE stream for replay
|
||||
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[
|
||||
dict[str, str]
|
||||
](0)
|
||||
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[dict[str, str]](0)
|
||||
|
||||
async def replay_sender():
|
||||
try:
|
||||
|
|
@ -767,15 +713,11 @@ class StreamableHTTPServerTransport:
|
|||
await sse_stream_writer.send(event_data)
|
||||
|
||||
# Replay past events and get the stream ID
|
||||
stream_id = await event_store.replay_events_after(
|
||||
last_event_id, send_event
|
||||
)
|
||||
stream_id = await event_store.replay_events_after(last_event_id, send_event)
|
||||
|
||||
# If stream ID not in mapping, create it
|
||||
if stream_id and stream_id not in self._request_streams:
|
||||
self._request_streams[stream_id] = (
|
||||
anyio.create_memory_object_stream[EventMessage](0)
|
||||
)
|
||||
self._request_streams[stream_id] = anyio.create_memory_object_stream[EventMessage](0)
|
||||
msg_reader = self._request_streams[stream_id][1]
|
||||
|
||||
# Forward messages to SSE
|
||||
|
|
@ -829,12 +771,8 @@ class StreamableHTTPServerTransport:
|
|||
|
||||
# Create the memory streams for this connection
|
||||
|
||||
read_stream_writer, read_stream = anyio.create_memory_object_stream[
|
||||
SessionMessage | Exception
|
||||
](0)
|
||||
write_stream, write_stream_reader = anyio.create_memory_object_stream[
|
||||
SessionMessage
|
||||
](0)
|
||||
read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0)
|
||||
write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0)
|
||||
|
||||
# Store the streams
|
||||
self._read_stream_writer = read_stream_writer
|
||||
|
|
@ -867,35 +805,24 @@ class StreamableHTTPServerTransport:
|
|||
session_message.metadata,
|
||||
ServerMessageMetadata,
|
||||
)
|
||||
and session_message.metadata.related_request_id
|
||||
is not None
|
||||
and session_message.metadata.related_request_id is not None
|
||||
):
|
||||
target_request_id = str(
|
||||
session_message.metadata.related_request_id
|
||||
)
|
||||
target_request_id = str(session_message.metadata.related_request_id)
|
||||
|
||||
request_stream_id = (
|
||||
target_request_id
|
||||
if target_request_id is not None
|
||||
else GET_STREAM_KEY
|
||||
)
|
||||
request_stream_id = target_request_id if target_request_id is not None else GET_STREAM_KEY
|
||||
|
||||
# Store the event if we have an event store,
|
||||
# regardless of whether a client is connected
|
||||
# messages will be replayed on the re-connect
|
||||
event_id = None
|
||||
if self._event_store:
|
||||
event_id = await self._event_store.store_event(
|
||||
request_stream_id, message
|
||||
)
|
||||
event_id = await self._event_store.store_event(request_stream_id, message)
|
||||
logger.debug(f"Stored {event_id} from {request_stream_id}")
|
||||
|
||||
if request_stream_id in self._request_streams:
|
||||
try:
|
||||
# Send both the message and the event ID
|
||||
await self._request_streams[request_stream_id][0].send(
|
||||
EventMessage(message, event_id)
|
||||
)
|
||||
await self._request_streams[request_stream_id][0].send(EventMessage(message, event_id))
|
||||
except (
|
||||
anyio.BrokenResourceError,
|
||||
anyio.ClosedResourceError,
|
||||
|
|
|
|||
Loading…
Reference in a new issue