diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml new file mode 100644 index 0000000..202c89e --- /dev/null +++ b/.github/workflows/check-format.yml @@ -0,0 +1,29 @@ +name: ruff + +on: + push: + branches: [main] + pull_request: + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: "Set up Python" + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install the project + run: uv sync --frozen --all-extras --dev + + - name: Run ruff format check + run: uv run --frozen ruff check . diff --git a/.github/workflows/check-types.yml b/.github/workflows/check-types.yml new file mode 100644 index 0000000..3463fcf --- /dev/null +++ b/.github/workflows/check-types.yml @@ -0,0 +1,29 @@ +name: typecheck + +on: + push: + branches: [main] + pull_request: + +jobs: + typecheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: "Set up Python" + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install the project + run: uv sync --frozen --all-extras --dev + + - name: Run pyright + run: uv run --frozen pyright diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e47666..24a31cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,10 +11,19 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: "Set up Python" + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version-file: ".python-version" + + - name: Install the project + run: uv sync --frozen --all-extras --dev - - run: pip install . - - run: pip install -U pytest trio - - run: pytest + - name: Run pytest + run: uv run --frozen pytest diff --git a/mcp_python/__init__.py b/mcp_python/__init__.py index 2285847..78a62be 100644 --- a/mcp_python/__init__.py +++ b/mcp_python/__init__.py @@ -37,7 +37,6 @@ ReadResourceResult, Resource, ResourceUpdatedNotification, - Role as SamplingRole, SamplingMessage, ServerCapabilities, ServerNotification, @@ -49,6 +48,9 @@ Tool, UnsubscribeRequest, ) +from .types import ( + Role as SamplingRole, +) __all__ = [ "CallToolRequest", diff --git a/mcp_python/client/session.py b/mcp_python/client/session.py index 5eab70e..769e945 100644 --- a/mcp_python/client/session.py +++ b/mcp_python/client/session.py @@ -62,7 +62,8 @@ async def initialize(self) -> InitializeResult: if result.protocolVersion != SUPPORTED_PROTOCOL_VERSION: raise RuntimeError( - f"Unsupported protocol version from the server: {result.protocolVersion}" + "Unsupported protocol version from the server: " + f"{result.protocolVersion}" ) await self.send_notification( diff --git a/mcp_python/client/sse.py b/mcp_python/client/sse.py index ebb4842..09826a6 100644 --- a/mcp_python/client/sse.py +++ b/mcp_python/client/sse.py @@ -19,11 +19,17 @@ def remove_request_params(url: str) -> str: @asynccontextmanager -async def sse_client(url: str, headers: dict[str, Any] | None = None, timeout: float = 5, sse_read_timeout: float = 60 * 5): +async def sse_client( + url: str, + headers: dict[str, Any] | None = None, + timeout: float = 5, + sse_read_timeout: float = 60 * 5, +): """ Client transport for SSE. - `sse_read_timeout` determines how long (in seconds) the client will wait for a new event before disconnecting. All other HTTP operations are controlled by `timeout`. + `sse_read_timeout` determines how long (in seconds) the client will wait for a new + event before disconnecting. All other HTTP operations are controlled by `timeout`. """ read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception] read_stream_writer: MemoryObjectSendStream[JSONRPCMessage | Exception] @@ -67,7 +73,10 @@ async def sse_reader( or url_parsed.scheme != endpoint_parsed.scheme ): - error_msg = f"Endpoint origin does not match connection origin: {endpoint_url}" + error_msg = ( + "Endpoint origin does not match " + f"connection origin: {endpoint_url}" + ) logger.error(error_msg) raise ValueError(error_msg) @@ -104,11 +113,16 @@ async def post_writer(endpoint_url: str): logger.debug(f"Sending client message: {message}") response = await client.post( endpoint_url, - json=message.model_dump(by_alias=True, mode="json", exclude_none=True), + json=message.model_dump( + by_alias=True, + mode="json", + exclude_none=True, + ), ) response.raise_for_status() logger.debug( - f"Client message sent successfully: {response.status_code}" + "Client message sent successfully: " + f"{response.status_code}" ) except Exception as exc: logger.error(f"Error in post_writer: {exc}") diff --git a/mcp_python/client/stdio.py b/mcp_python/client/stdio.py index 30c0bf6..f9404d3 100644 --- a/mcp_python/client/stdio.py +++ b/mcp_python/client/stdio.py @@ -28,7 +28,8 @@ class StdioServerParameters(BaseModel): @asynccontextmanager async def stdio_client(server: StdioServerParameters): """ - Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. + Client transport for stdio: this will connect to a server by spawning a + process and communicating with it over stdin/stdout. """ read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception] read_stream_writer: MemoryObjectSendStream[JSONRPCMessage | Exception] diff --git a/mcp_python/server/__init__.py b/mcp_python/server/__init__.py index 7801f64..3339db2 100644 --- a/mcp_python/server/__init__.py +++ b/mcp_python/server/__init__.py @@ -55,9 +55,11 @@ def __init__(self, name: str): def create_initialization_options(self) -> types.InitializationOptions: """Create initialization options from this server instance.""" + def pkg_version(package: str) -> str: try: from importlib.metadata import version + return version(package) except Exception: return "unknown" @@ -69,16 +71,17 @@ def pkg_version(package: str) -> str: ) def get_capabilities(self) -> ServerCapabilities: - """Convert existing handlers to a ServerCapabilities object.""" - def get_capability(req_type: type) -> dict[str, Any] | None: - return {} if req_type in self.request_handlers else None + """Convert existing handlers to a ServerCapabilities object.""" - return ServerCapabilities( - prompts=get_capability(ListPromptsRequest), - resources=get_capability(ListResourcesRequest), - tools=get_capability(ListPromptsRequest), - logging=get_capability(SetLevelRequest) - ) + def get_capability(req_type: type) -> dict[str, Any] | None: + return {} if req_type in self.request_handlers else None + + return ServerCapabilities( + prompts=get_capability(ListPromptsRequest), + resources=get_capability(ListResourcesRequest), + tools=get_capability(ListPromptsRequest), + logging=get_capability(SetLevelRequest), + ) @property def request_context(self) -> RequestContext: @@ -87,7 +90,7 @@ def request_context(self) -> RequestContext: def list_prompts(self): def decorator(func: Callable[[], Awaitable[list[Prompt]]]): - logger.debug(f"Registering handler for PromptListRequest") + logger.debug("Registering handler for PromptListRequest") async def handler(_: Any): prompts = await func() @@ -103,17 +106,19 @@ def get_prompt(self): GetPromptRequest, GetPromptResult, ImageContent, - Role as Role, SamplingMessage, TextContent, ) + from mcp_python.types import ( + Role as Role, + ) def decorator( func: Callable[ [str, dict[str, str] | None], Awaitable[types.PromptResponse] ], ): - logger.debug(f"Registering handler for GetPromptRequest") + logger.debug("Registering handler for GetPromptRequest") async def handler(req: GetPromptRequest): prompt_get = await func(req.params.name, req.params.arguments) @@ -149,7 +154,7 @@ async def handler(req: GetPromptRequest): def list_resources(self): def decorator(func: Callable[[], Awaitable[list[Resource]]]): - logger.debug(f"Registering handler for ListResourcesRequest") + logger.debug("Registering handler for ListResourcesRequest") async def handler(_: Any): resources = await func() @@ -169,7 +174,7 @@ def read_resource(self): ) def decorator(func: Callable[[AnyUrl], Awaitable[str | bytes]]): - logger.debug(f"Registering handler for ReadResourceRequest") + logger.debug("Registering handler for ReadResourceRequest") async def handler(req: ReadResourceRequest): result = await func(req.params.uri) @@ -204,7 +209,7 @@ def set_logging_level(self): from mcp_python.types import EmptyResult def decorator(func: Callable[[LoggingLevel], Awaitable[None]]): - logger.debug(f"Registering handler for SetLevelRequest") + logger.debug("Registering handler for SetLevelRequest") async def handler(req: SetLevelRequest): await func(req.params.level) @@ -219,7 +224,7 @@ def subscribe_resource(self): from mcp_python.types import EmptyResult def decorator(func: Callable[[AnyUrl], Awaitable[None]]): - logger.debug(f"Registering handler for SubscribeRequest") + logger.debug("Registering handler for SubscribeRequest") async def handler(req: SubscribeRequest): await func(req.params.uri) @@ -234,7 +239,7 @@ def unsubscribe_resource(self): from mcp_python.types import EmptyResult def decorator(func: Callable[[AnyUrl], Awaitable[None]]): - logger.debug(f"Registering handler for UnsubscribeRequest") + logger.debug("Registering handler for UnsubscribeRequest") async def handler(req: UnsubscribeRequest): await func(req.params.uri) @@ -249,7 +254,7 @@ def call_tool(self): from mcp_python.types import CallToolResult def decorator(func: Callable[..., Awaitable[Any]]): - logger.debug(f"Registering handler for CallToolRequest") + logger.debug("Registering handler for CallToolRequest") async def handler(req: CallToolRequest): result = await func(req.params.name, **(req.params.arguments or {})) @@ -264,7 +269,7 @@ def progress_notification(self): def decorator( func: Callable[[str | int, float, float | None], Awaitable[None]], ): - logger.debug(f"Registering handler for ProgressNotification") + logger.debug("Registering handler for ProgressNotification") async def handler(req: ProgressNotification): await func( @@ -286,7 +291,7 @@ def decorator( Awaitable[Completion | None], ], ): - logger.debug(f"Registering handler for CompleteRequest") + logger.debug("Registering handler for CompleteRequest") async def handler(req: CompleteRequest): completion = await func(req.params.ref, req.params.argument) @@ -307,10 +312,12 @@ async def run( self, read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception], write_stream: MemoryObjectSendStream[JSONRPCMessage], - initialization_options: types.InitializationOptions + initialization_options: types.InitializationOptions, ): with warnings.catch_warnings(record=True) as w: - async with ServerSession(read_stream, write_stream, initialization_options) as session: + async with ServerSession( + read_stream, write_stream, initialization_options + ) as session: async for message in session.incoming_messages: logger.debug(f"Received message: {message}") @@ -359,14 +366,16 @@ async def run( handler = self.notification_handlers[type(notify)] logger.debug( - f"Dispatching notification of type {type(notify).__name__}" + f"Dispatching notification of type " + f"{type(notify).__name__}" ) try: await handler(notify) except Exception as err: logger.error( - f"Uncaught exception in notification handler: {err}" + f"Uncaught exception in notification handler: " + f"{err}" ) for warning in w: diff --git a/mcp_python/server/__main__.py b/mcp_python/server/__main__.py index efb7dd8..6cb8822 100644 --- a/mcp_python/server/__main__.py +++ b/mcp_python/server/__main__.py @@ -1,11 +1,12 @@ +import importlib.metadata import logging import sys -import importlib.metadata + import anyio from mcp_python.server.session import ServerSession -from mcp_python.server.types import InitializationOptions from mcp_python.server.stdio import stdio_server +from mcp_python.server.types import InitializationOptions from mcp_python.types import ServerCapabilities if not sys.warnoptions: @@ -30,7 +31,18 @@ async def receive_loop(session: ServerSession): async def main(): version = importlib.metadata.version("mcp_python") async with stdio_server() as (read_stream, write_stream): - async with ServerSession(read_stream, write_stream, InitializationOptions(server_name="mcp_python", server_version=version, capabilities=ServerCapabilities())) as session, write_stream: + async with ( + ServerSession( + read_stream, + write_stream, + InitializationOptions( + server_name="mcp_python", + server_version=version, + capabilities=ServerCapabilities(), + ), + ) as session, + write_stream, + ): await receive_loop(session) diff --git a/mcp_python/server/session.py b/mcp_python/server/session.py index c64f799..375e557 100644 --- a/mcp_python/server/session.py +++ b/mcp_python/server/session.py @@ -6,11 +6,11 @@ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from pydantic import AnyUrl +from mcp_python.server.types import InitializationOptions from mcp_python.shared.session import ( BaseSession, RequestResponder, ) -from mcp_python.server.types import InitializationOptions from mcp_python.shared.version import SUPPORTED_PROTOCOL_VERSION from mcp_python.types import ( ClientNotification, @@ -25,7 +25,6 @@ JSONRPCMessage, LoggingLevel, SamplingMessage, - ServerCapabilities, ServerNotification, ServerRequest, ServerResult, @@ -53,7 +52,7 @@ def __init__( self, read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception], write_stream: MemoryObjectSendStream[JSONRPCMessage], - init_options: InitializationOptions + init_options: InitializationOptions, ) -> None: super().__init__(read_stream, write_stream, ClientRequest, ClientNotification) self._initialization_state = InitializationState.NotInitialized @@ -72,7 +71,7 @@ async def _received_request( capabilities=self._init_options.capabilities, serverInfo=Implementation( name=self._init_options.server_name, - version=self._init_options.server_version + version=self._init_options.server_version, ), ) ) diff --git a/mcp_python/server/sse.py b/mcp_python/server/sse.py index c6e9fa6..d261c71 100644 --- a/mcp_python/server/sse.py +++ b/mcp_python/server/sse.py @@ -19,10 +19,14 @@ class SseServerTransport: """ - SSE server transport for MCP. This class provides _two_ ASGI applications, suitable to be used with a framework like Starlette and a server like Hypercorn: - - 1. connect_sse() is an ASGI application which receives incoming GET requests, and sets up a new SSE stream to send server messages to the client. - 2. handle_post_message() is an ASGI application which receives incoming POST requests, which should contain client messages that link to a previously-established SSE session. + SSE server transport for MCP. This class provides _two_ ASGI applications, + suitable to be used with a framework like Starlette and a server like Hypercorn: + + 1. connect_sse() is an ASGI application which receives incoming GET requests, + and sets up a new SSE stream to send server messages to the client. + 2. handle_post_message() is an ASGI application which receives incoming POST + requests, which should contain client messages that link to a + previously-established SSE session. """ _endpoint: str @@ -30,7 +34,8 @@ class SseServerTransport: def __init__(self, endpoint: str) -> None: """ - Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL given. + Creates a new SSE server transport, which will direct the client to POST + messages to the relative or absolute URL given. """ super().__init__() @@ -74,7 +79,9 @@ async def sse_writer(): await sse_stream_writer.send( { "event": "message", - "data": message.model_dump_json(by_alias=True, exclude_none=True), + "data": message.model_dump_json( + by_alias=True, exclude_none=True + ), } ) diff --git a/mcp_python/server/stdio.py b/mcp_python/server/stdio.py index b55df0e..31ae415 100644 --- a/mcp_python/server/stdio.py +++ b/mcp_python/server/stdio.py @@ -7,14 +7,18 @@ from mcp_python.types import JSONRPCMessage + @asynccontextmanager async def stdio_server( - stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.AsyncFile[str] | None = None + stdin: anyio.AsyncFile[str] | None = None, + stdout: anyio.AsyncFile[str] | None = None, ): """ - Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout. + Server transport for stdio: this communicates with an MCP client by reading + from the current process' stdin and writing to stdout. """ - # Purposely not using context managers for these, as we don't want to close standard process handles. + # Purposely not using context managers for these, as we don't want to close + # standard process handles. if not stdin: stdin = anyio.wrap_file(sys.stdin) if not stdout: diff --git a/mcp_python/server/types.py b/mcp_python/server/types.py index 1b56f24..7632406 100644 --- a/mcp_python/server/types.py +++ b/mcp_python/server/types.py @@ -6,6 +6,7 @@ from typing import Literal from pydantic import BaseModel + from mcp_python.types import Role, ServerCapabilities diff --git a/mcp_python/server/websocket.py b/mcp_python/server/websocket.py index 09f8a5a..5ba309b 100644 --- a/mcp_python/server/websocket.py +++ b/mcp_python/server/websocket.py @@ -14,7 +14,8 @@ @asynccontextmanager async def websocket_server(scope: Scope, receive: Receive, send: Send): """ - WebSocket server transport for MCP. This is an ASGI application, suitable to be used with a framework like Starlette and a server like Hypercorn. + WebSocket server transport for MCP. This is an ASGI application, suitable to be + used with a framework like Starlette and a server like Hypercorn. """ websocket = WebSocket(scope, receive, send) @@ -47,7 +48,9 @@ async def ws_writer(): try: async with write_stream_reader: async for message in write_stream_reader: - obj = message.model_dump(by_alias=True, mode="json", exclude_none=True) + obj = message.model_dump( + by_alias=True, mode="json", exclude_none=True + ) await websocket.send_json(obj) except anyio.ClosedResourceError: await websocket.close() diff --git a/mcp_python/shared/session.py b/mcp_python/shared/session.py index 1705e0d..3bc66fc 100644 --- a/mcp_python/shared/session.py +++ b/mcp_python/shared/session.py @@ -69,9 +69,11 @@ class BaseSession( ], ): """ - Implements an MCP "session" on top of read/write streams, including features like request/response linking, notifications, and progress. + Implements an MCP "session" on top of read/write streams, including features + like request/response linking, notifications, and progress. - This class is an async context manager that automatically starts processing messages when entered. + This class is an async context manager that automatically starts processing + messages when entered. """ _response_streams: dict[ @@ -108,7 +110,9 @@ async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): - # Using BaseSession as a context manager should not block on exit (this would be very surprising behavior), so make sure to cancel the tasks in the task group. + # Using BaseSession as a context manager should not block on exit (this + # would be very surprising behavior), so make sure to cancel the tasks + # in the task group. self._task_group.cancel_scope.cancel() return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) @@ -118,9 +122,11 @@ async def send_request( result_type: type[ReceiveResultT], ) -> ReceiveResultT: """ - Sends a request and wait for a response. Raises an McpError if the response contains an error. + Sends a request and wait for a response. Raises an McpError if the + response contains an error. - Do not use this method to emit notifications! Use send_notification() instead. + Do not use this method to emit notifications! Use send_notification() + instead. """ request_id = self._request_id @@ -132,7 +138,9 @@ async def send_request( self._response_streams[request_id] = response_stream jsonrpc_request = JSONRPCRequest( - jsonrpc="2.0", id=request_id, **request.model_dump(by_alias=True, mode="json", exclude_none=True) + jsonrpc="2.0", + id=request_id, + **request.model_dump(by_alias=True, mode="json", exclude_none=True), ) # TODO: Support progress callbacks @@ -147,10 +155,12 @@ async def send_request( async def send_notification(self, notification: SendNotificationT) -> None: """ - Emits a notification, which is a one-way message that does not expect a response. + Emits a notification, which is a one-way message that does not expect + a response. """ jsonrpc_notification = JSONRPCNotification( - jsonrpc="2.0", **notification.model_dump(by_alias=True, mode="json", exclude_none=True) + jsonrpc="2.0", + **notification.model_dump(by_alias=True, mode="json", exclude_none=True), ) await self._write_stream.send(JSONRPCMessage(jsonrpc_notification)) @@ -165,7 +175,9 @@ async def _send_response( jsonrpc_response = JSONRPCResponse( jsonrpc="2.0", id=request_id, - result=response.model_dump(by_alias=True, mode="json", exclude_none=True), + result=response.model_dump( + by_alias=True, mode="json", exclude_none=True + ), ) await self._write_stream.send(JSONRPCMessage(jsonrpc_response)) @@ -180,7 +192,9 @@ async def _receive_loop(self) -> None: await self._incoming_message_stream_writer.send(message) elif isinstance(message.root, JSONRPCRequest): validated_request = self._receive_request_type.model_validate( - message.root.model_dump(by_alias=True, mode="json", exclude_none=True) + message.root.model_dump( + by_alias=True, mode="json", exclude_none=True + ) ) responder = RequestResponder( request_id=message.root.id, @@ -196,7 +210,9 @@ async def _receive_loop(self) -> None: await self._incoming_message_stream_writer.send(responder) elif isinstance(message.root, JSONRPCNotification): notification = self._receive_notification_type.model_validate( - message.root.model_dump(by_alias=True, mode="json", exclude_none=True) + message.root.model_dump( + by_alias=True, mode="json", exclude_none=True + ) ) await self._received_notification(notification) @@ -208,7 +224,8 @@ async def _receive_loop(self) -> None: else: await self._incoming_message_stream_writer.send( RuntimeError( - f"Received response with an unknown request ID: {message}" + "Received response with an unknown " + f"request ID: {message}" ) ) @@ -216,21 +233,25 @@ async def _received_request( self, responder: RequestResponder[ReceiveRequestT, SendResultT] ) -> None: """ - Can be overridden by subclasses to handle a request without needing to listen on the message stream. + Can be overridden by subclasses to handle a request without needing to + listen on the message stream. - If the request is responded to within this method, it will not be forwarded on to the message stream. + If the request is responded to within this method, it will not be + forwarded on to the message stream. """ async def _received_notification(self, notification: ReceiveNotificationT) -> None: """ - Can be overridden by subclasses to handle a notification without needing to listen on the message stream. + Can be overridden by subclasses to handle a notification without needing + to listen on the message stream. """ async def send_progress_notification( self, progress_token: str | int, progress: float, total: float | None = None ) -> None: """ - Sends a progress notification for a request that is currently being processed. + Sends a progress notification for a request that is currently being + processed. """ @property diff --git a/mcp_python/types.py b/mcp_python/types.py index 1087526..983964a 100644 --- a/mcp_python/types.py +++ b/mcp_python/types.py @@ -6,14 +6,19 @@ """ Model Context Protocol bindings for Python -These bindings were generated from https://github.com/anthropic-experimental/mcp-spec, using Claude, with a prompt something like the following: +These bindings were generated from https://github.com/anthropic-experimental/mcp-spec, +using Claude, with a prompt something like the following: -Generate idiomatic Python bindings for this schema for MCP, or the "Model Context Protocol." The schema is defined in TypeScript, but there's also a JSON Schema version for reference. +Generate idiomatic Python bindings for this schema for MCP, or the "Model Context +Protocol." The schema is defined in TypeScript, but there's also a JSON Schema version +for reference. * For the bindings, let's use Pydantic V2 models. -* Each model should allow extra fields everywhere, by specifying `model_config = ConfigDict(extra='allow')`. Do this in every case, instead of a custom base class. +* Each model should allow extra fields everywhere, by specifying `model_config = + ConfigDict(extra='allow')`. Do this in every case, instead of a custom base class. * Union types should be represented with a Pydantic `RootModel`. -* Define additional model classes instead of using dictionaries. Do this even if they're not separate types in the schema. +* Define additional model classes instead of using dictionaries. Do this even if they're + not separate types in the schema. """ @@ -24,7 +29,10 @@ class RequestParams(BaseModel): class Meta(BaseModel): progressToken: ProgressToken | None = None """ - If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + If specified, the caller is requesting out-of-band progress notifications for + this request (as represented by notifications/progress). The value of this + parameter is an opaque token that will be attached to any subsequent + notifications. The receiver is not obligated to provide these notifications. """ model_config = ConfigDict(extra="allow") @@ -38,9 +46,11 @@ class Meta(BaseModel): _meta: Meta | None = None """ - This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + This parameter name is reserved by MCP to allow clients and servers to attach + additional metadata to their notifications. """ + RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams) NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams) MethodT = TypeVar("MethodT", bound=str) @@ -68,7 +78,8 @@ class Result(BaseModel): _meta: dict[str, Any] | None = None """ - This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + This result property is reserved by the protocol to allow clients and servers to + attach additional metadata to their responses. """ @@ -112,9 +123,15 @@ class ErrorData(BaseModel): code: int """The error type that occurred.""" message: str - """A short description of the error. The message SHOULD be limited to a concise single sentence.""" + """ + A short description of the error. The message SHOULD be limited to a concise single + sentence. + """ data: Any | None = None - """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).""" + """ + Additional information about the error. The value of this member is defined by the + sender (e.g. detailed error information, nested errors etc.). + """ model_config = ConfigDict(extra="allow") @@ -182,14 +199,17 @@ class InitializeRequestParams(RequestParams): class InitializeRequest(Request): - """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + """ + This request is sent from the client to the server when it first connects, asking it + to begin initialization. + """ method: Literal["initialize"] params: InitializeRequestParams class InitializeResult(Result): - """After receiving an initialize request from the client, the server sends this response.""" + """After receiving an initialize request from the client, the server sends this.""" protocolVersion: Literal[1] """The version of the Model Context Protocol that the server wants to use.""" @@ -198,14 +218,20 @@ class InitializeResult(Result): class InitializedNotification(Notification): - """This notification is sent from the client to the server after initialization has finished.""" + """ + This notification is sent from the client to the server after initialization has + finished. + """ method: Literal["notifications/initialized"] params: NotificationParams | None = None class PingRequest(Request): - """A ping, issued by either the server or the client, to check that the other party is still alive.""" + """ + A ping, issued by either the server or the client, to check that the other party is + still alive. + """ method: Literal["ping"] params: RequestParams | None = None @@ -215,16 +241,25 @@ class ProgressNotificationParams(NotificationParams): """Parameters for progress notifications.""" progressToken: ProgressToken - """The progress token which was given in the initial request, used to associate this notification with the request that is proceeding.""" + """ + The progress token which was given in the initial request, used to associate this + notification with the request that is proceeding. + """ progress: float - """The progress thus far. This should increase every time progress is made, even if the total is unknown.""" + """ + The progress thus far. This should increase every time progress is made, even if the + total is unknown. + """ total: float | None = None """Total number of items to process (or total progress required), if known.""" model_config = ConfigDict(extra="allow") class ProgressNotification(Notification): - """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + """ + An out-of-band notification used to inform the receiver of a progress update for a + long-running request. + """ method: Literal["notifications/progress"] params: ProgressNotificationParams @@ -251,13 +286,19 @@ class ResourceTemplate(BaseModel): """A template description for resources available on the server.""" uriTemplate: str - """A URI template (according to RFC 6570) that can be used to construct resource URIs.""" + """ + A URI template (according to RFC 6570) that can be used to construct resource + URIs. + """ name: str | None = None """A human-readable name for the type of resource this template refers to.""" description: str | None = None """A human-readable description of what this template is for.""" mimeType: str | None = None - """The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.""" + """ + The MIME type for all resources that match this template. This should only be + included if all resources matching this template have the same type. + """ model_config = ConfigDict(extra="allow") @@ -272,7 +313,10 @@ class ReadResourceRequestParams(RequestParams): """Parameters for reading a resource.""" uri: AnyUrl - """The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it.""" + """ + The URI of the resource to read. The URI can use any protocol; it is up to the + server how to interpret it. + """ model_config = ConfigDict(extra="allow") @@ -297,7 +341,10 @@ class TextResourceContents(ResourceContents): """Text contents of a resource.""" text: str - """The text of the item. This must only be set if the item can actually be represented as text (not binary data).""" + """ + The text of the item. This must only be set if the item can actually be represented + as text (not binary data). + """ class BlobResourceContents(ResourceContents): @@ -314,7 +361,10 @@ class ReadResourceResult(Result): class ResourceListChangedNotification(Notification): - """An optional notification from the server to the client, informing it that the list of resources it can read from has changed.""" + """ + An optional notification from the server to the client, informing it that the list + of resources it can read from has changed. + """ method: Literal["notifications/resources/list_changed"] params: NotificationParams | None = None @@ -324,12 +374,18 @@ class SubscribeRequestParams(RequestParams): """Parameters for subscribing to a resource.""" uri: AnyUrl - """The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it.""" + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to + the server how to interpret it. + """ model_config = ConfigDict(extra="allow") class SubscribeRequest(Request): - """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.""" + """ + Sent from the client to request resources/updated notifications from the server + whenever a particular resource changes. + """ method: Literal["resources/subscribe"] params: SubscribeRequestParams @@ -344,7 +400,10 @@ class UnsubscribeRequestParams(RequestParams): class UnsubscribeRequest(Request): - """Sent from the client to request cancellation of resources/updated notifications from the server.""" + """ + Sent from the client to request cancellation of resources/updated notifications from + the server. + """ method: Literal["resources/unsubscribe"] params: UnsubscribeRequestParams @@ -354,19 +413,25 @@ class ResourceUpdatedNotificationParams(NotificationParams): """Parameters for resource update notifications.""" uri: AnyUrl - """The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.""" + """ + The URI of the resource that has been updated. This might be a sub-resource of the + one that the client actually subscribed to. + """ model_config = ConfigDict(extra="allow") class ResourceUpdatedNotification(Notification): - """A notification from the server to the client, informing it that a resource has changed and may need to be read again.""" + """ + A notification from the server to the client, informing it that a resource has + changed and may need to be read again. + """ method: Literal["notifications/resources/updated"] params: ResourceUpdatedNotificationParams class ListPromptsRequest(Request): - """Sent from the client to request a list of prompts and prompt templates the server has.""" + """Sent from the client to request a list of prompts and prompt templates.""" method: Literal["prompts/list"] params: RequestParams | None = None @@ -435,7 +500,10 @@ class ImageContent(BaseModel): data: str """The base64-encoded image data.""" mimeType: str - """The MIME type of the image. Different providers may support different image types.""" + """ + The MIME type of the image. Different providers may support different + image types. + """ model_config = ConfigDict(extra="allow") @@ -505,7 +573,10 @@ class CallToolResult(Result): class ToolListChangedNotification(Notification): - """An optional notification from the server to the client, informing it that the list of tools it offers has changed.""" + """ + An optional notification from the server to the client, informing it that the list + of tools it offers has changed. + """ method: Literal["notifications/tools/list_changed"] params: NotificationParams | None = None @@ -537,7 +608,10 @@ class LoggingMessageNotificationParams(NotificationParams): logger: str | None = None """An optional name of the logger issuing this message.""" data: Any - """The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.""" + """ + The data to be logged, such as a string message or an object. Any JSON serializable + type is allowed here. + """ model_config = ConfigDict(extra="allow") @@ -558,7 +632,10 @@ class CreateMessageRequestParams(RequestParams): systemPrompt: str | None = None """An optional system prompt the server wants to use for sampling.""" includeContext: IncludeContext | None = None - """A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.""" + """ + A request to include context from one or more MCP servers (including the caller), to + be attached to the prompt. + """ temperature: float | None = None maxTokens: int """The maximum number of tokens to sample, as requested by the server.""" @@ -638,9 +715,15 @@ class Completion(BaseModel): values: list[str] """An array of completion values. Must not exceed 100 items.""" total: int | None = None - """The total number of completion options available. This can exceed the number of values actually sent in the response.""" + """ + The total number of completion options available. This can exceed the number of + values actually sent in the response. + """ hasMore: bool | None = None - """Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.""" + """ + Indicates whether there are additional completion options beyond those provided in + the current response, even if the exact total is unknown. + """ model_config = ConfigDict(extra="allow") diff --git a/pyproject.toml b/pyproject.toml index 208aece..1337320 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,5 +38,8 @@ target-version = "py38" [tool.uv] dev-dependencies = [ + "pyright>=1.1.384", + "pytest>=8.3.3", + "ruff>=0.6.9", "trio>=0.26.2", ] diff --git a/tests/client/test_session.py b/tests/client/test_session.py index 17634fe..cb7f038 100644 --- a/tests/client/test_session.py +++ b/tests/client/test_session.py @@ -59,14 +59,18 @@ async def mock_server(): JSONRPCResponse( jsonrpc="2.0", id=jsonrpc_request.root.id, - result=result.model_dump(by_alias=True, mode="json", exclude_none=True), + result=result.model_dump( + by_alias=True, mode="json", exclude_none=True + ), ) ) ) jsonrpc_notification = await client_to_server_receive.receive() assert isinstance(jsonrpc_notification.root, JSONRPCNotification) initialized_notification = ClientNotification.model_validate( - jsonrpc_notification.model_dump(by_alias=True, mode="json", exclude_none=True) + jsonrpc_notification.model_dump( + by_alias=True, mode="json", exclude_none=True + ) ) async def listen_session(): diff --git a/tests/server/test_session.py b/tests/server/test_session.py index 01813a7..addf0f5 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -33,7 +33,13 @@ async def run_server(): nonlocal received_initialized async with ServerSession( - client_to_server_receive, server_to_client_send, InitializationOptions(server_name='mcp_python', server_version='0.1.0', capabilities=ServerCapabilities()) + client_to_server_receive, + server_to_client_send, + InitializationOptions( + server_name="mcp_python", + server_version="0.1.0", + capabilities=ServerCapabilities(), + ), ) as server_session: async for message in server_session.incoming_messages: if isinstance(message, Exception): diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..37bb8ec --- /dev/null +++ b/uv.lock @@ -0,0 +1,479 @@ +version = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version < '3.13'", + "python_full_version >= '3.13'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "mcp-python" +version = "0.1.5.dev0" +source = { editable = "." } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "sse-starlette" }, + { name = "starlette" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "trio" }, +] + +[package.metadata] +requires-dist = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "sse-starlette" }, + { name = "starlette" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.384" }, + { name = "pytest", specifier = ">=8.3.3" }, + { name = "ruff", specifier = ">=0.6.9" }, + { name = "trio", specifier = ">=0.26.2" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, + { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, + { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, + { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, + { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, + { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, + { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, + { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, + { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, + { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, + { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, + { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, + { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, + { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, + { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, + { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, + { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, + { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, + { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, + { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, +] + +[[package]] +name = "pyright" +version = "1.1.384" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/00/a23114619f9d005f4b0f35e037c76cee029174d090a6f73a355749c74f4a/pyright-1.1.384.tar.gz", hash = "sha256:25e54d61f55cbb45f1195ff89c488832d7a45d59f3e132f178fdf9ef6cafc706", size = 21956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/4a/e7f4d71d194ba675f3577d11eebe4e17a592c4d1c3f9986d4b321ba3c809/pyright-1.1.384-py3-none-any.whl", hash = "sha256:f0b6f4db2da38f27aeb7035c26192f034587875f751b847e9ad42ed0c704ac9e", size = 18578 }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "ruff" +version = "0.6.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 }, + { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 }, + { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 }, + { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 }, + { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 }, + { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 }, + { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 }, + { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 }, + { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 }, + { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 }, + { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 }, + { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 }, + { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 }, + { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 }, + { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 }, + { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "sse-starlette" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, +] + +[[package]] +name = "starlette" +version = "0.39.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/0a/62fbd5697f6174041f9b4e2e377b6f383f9189b77dbb7d73d24624caca1d/starlette-0.39.2.tar.gz", hash = "sha256:caaa3b87ef8518ef913dac4f073dea44e85f73343ad2bdc17941931835b2a26a", size = 2573080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/f0/04547f776c8845be46df4bdd1f11159c088bd39e916f35d7da1b9f6eb3ef/starlette-0.39.2-py3-none-any.whl", hash = "sha256:134dd6deb655a9775991d352312d53f1879775e5cc8a481f966e83416a2c3f71", size = 73219 }, +] + +[[package]] +name = "tomli" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/b9/de2a5c0144d7d75a57ff355c0c24054f965b2dc3036456ae03a51ea6264b/tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed", size = 16096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 }, +] + +[[package]] +name = "trio" +version = "0.26.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/03/ab0e9509be0c6465e2773768ec25ee0cb8053c0b91471ab3854bbf2294b2/trio-0.26.2.tar.gz", hash = "sha256:0346c3852c15e5c7d40ea15972c4805689ef2cb8b5206f794c9c19450119f3a4", size = 561156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/70/efa56ce2271c44a7f4f43533a0477e6854a0948e9f7b76491de1fd3be7c9/trio-0.26.2-py3-none-any.whl", hash = "sha256:c5237e8133eb0a1d72f09a971a55c28ebe69e351c783fc64bc37db8db8bbe1d0", size = 475996 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "uvicorn" +version = "0.31.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/87/a886eda9ed495a3a4506d5a125cd07c54524280718c4969bde88f075fe98/uvicorn-0.31.1.tar.gz", hash = "sha256:f5167919867b161b7bcaf32646c6a94cdbd4c3aa2eb5c17d36bb9aa5cfd8c493", size = 77368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/55/37407280931038a3f21fa0245d60edeaa76f18419581aa3f4397761c78df/uvicorn-0.31.1-py3-none-any.whl", hash = "sha256:adc42d9cac80cf3e51af97c1851648066841e7cfb6993a4ca8de29ac1548ed41", size = 63666 }, +]