From 92fc54818b563645d4d50ef9b679cd7ef8b76288 Mon Sep 17 00:00:00 2001 From: Oliver Everett Date: Mon, 3 Aug 2026 19:07:26 +0800 Subject: [PATCH 1/2] Python: migrate mcp connector to mcp 2.x Bump the mcp dependency to >=2.0.0,<3.0 and update the connector for the 2.0 breaking changes: - Server: the lowlevel @server.list_tools()/call_tool()/get_prompt()/ set_logging_level() decorators were removed. Handlers are now passed to the Server(...) constructor as on_list_tools/on_call_tool/on_list_prompts/ on_get_prompt/on_set_logging_level, take a (ServerRequestContext, params) pair, and return result models (ListToolsResult/CallToolResult/...). The per-request session for sending log messages is taken from the handler context instead of the removed server.request_context. - Client: streamablehttp_client -> streamable_http_client (2-tuple); its headers/timeout/sse_read_timeout kwargs were removed, so the streamable http plugin builds an httpx2 client via create_mcp_http_client instead. ClientSession.read_timeout_seconds now takes float seconds. - camelCase model fields are snake_case: input_schema, mime_type, tool_use_id, model_preferences, max_tokens, system_prompt. - RequestContext -> ClientRequestContext for the sampling callback; message_handler now receives ServerNotification|Exception (no RequestResponder), and list-changed notifications are matched via .method directly. - McpError -> MCPError (raised with code/message). - The websocket transport was removed in mcp 2.0; MCPWebsocketPlugin now raises a clear configuration error at connect time instead of failing at import. - Tests and the test MCP server asset updated; uv.lock regenerated. Tests: 23 passed (unit), 1 passed (integration, real stdio server). ruff and mypy clean. --- python/pyproject.toml | 4 +- python/semantic_kernel/connectors/mcp.py | 381 +++++++++--------- .../test_plugins/TestMCPPlugin/mcp_server.py | 4 +- python/tests/unit/connectors/mcp/test_mcp.py | 122 +++--- python/uv.lock | 273 ++++--------- 5 files changed, 323 insertions(+), 461 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index d1d0b57b59f0..3b85b3a2ac4b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ # Protobuf # explicit typing extensions "typing-extensions>=4.13", - "mcp>=1.26.0,<2.0", + "mcp>=2.0.0,<3.0", ] ### Optional dependencies @@ -96,7 +96,7 @@ hugging_face = [ "torch==2.13.0" ] mcp = [ - "mcp>=1.8,<2.0", + "mcp>=2.0.0,<3.0", ] milvus = [ "pymilvus >= 2.3,< 2.7", diff --git a/python/semantic_kernel/connectors/mcp.py b/python/semantic_kernel/connectors/mcp.py index 789461b2c805..7b0b1f2414e4 100644 --- a/python/semantic_kernel/connectors/mcp.py +++ b/python/semantic_kernel/connectors/mcp.py @@ -8,21 +8,19 @@ from abc import abstractmethod from collections.abc import Awaitable, Callable, Sequence from contextlib import AbstractAsyncContextManager, AsyncExitStack, _AsyncGeneratorContextManager -from datetime import timedelta from functools import partial from itertools import chain from typing import TYPE_CHECKING, Any from mcp import types -from mcp.client.session import ClientSession +from mcp.client.session import ClientRequestContext, ClientSession from mcp.client.sse import sse_client from mcp.client.stdio import StdioServerParameters, stdio_client -from mcp.client.streamable_http import streamablehttp_client -from mcp.client.websocket import websocket_client +from mcp.client.streamable_http import streamable_http_client from mcp.server.lowlevel import Server -from mcp.shared.context import RequestContext -from mcp.shared.exceptions import McpError -from mcp.shared.session import RequestResponder +from mcp.server.lowlevel.server import ServerRequestContext +from mcp.shared._httpx_utils import create_mcp_http_client +from mcp.shared.exceptions import MCPError from semantic_kernel import Kernel from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase @@ -110,14 +108,14 @@ def _mcp_content_types_to_kernel_content( if isinstance(mcp_type, types.TextContent): return [TextContent(text=mcp_type.text, inner_content=mcp_type)] if isinstance(mcp_type, types.ImageContent): - return [ImageContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)] + return [ImageContent(data=mcp_type.data, mime_type=mcp_type.mime_type, inner_content=mcp_type)] if isinstance(mcp_type, types.AudioContent): - return [AudioContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)] + return [AudioContent(data=mcp_type.data, mime_type=mcp_type.mime_type, inner_content=mcp_type)] if isinstance(mcp_type, types.ResourceLink): return [ BinaryContent( uri=mcp_type.uri, # type: ignore - mime_type=mcp_type.mimeType, + mime_type=mcp_type.mime_type, inner_content=mcp_type, ) ] @@ -131,7 +129,7 @@ def _mcp_content_types_to_kernel_content( inner_content=mcp_type, name=mcp_type.type, result=list(chain(*[_mcp_content_types_to_kernel_content(mcp_type.content)])), - call_id=mcp_type.toolUseId, + call_id=mcp_type.tool_use_id, ) ] # subtypes of EmbeddedResource @@ -160,15 +158,15 @@ def _kernel_content_to_mcp_content_types( if isinstance(content, TextContent): return [types.TextContent(type="text", text=content.text)] if isinstance(content, ImageContent): - return [types.ImageContent(type="image", data=content.data_string, mimeType=content.mime_type)] + return [types.ImageContent(type="image", data=content.data_string, mime_type=content.mime_type)] if isinstance(content, AudioContent): - return [types.AudioContent(type="audio", data=content.data_string, mimeType=content.mime_type)] + return [types.AudioContent(type="audio", data=content.data_string, mime_type=content.mime_type)] if isinstance(content, BinaryContent): return [ types.EmbeddedResource( type="resource", resource=types.BlobResourceContents( - blob=content.data_string, mimeType=content.mime_type, uri=content.uri or "sk://binary" + blob=content.data_string, mime_type=content.mime_type, uri=content.uri or "sk://binary" ), ) ] @@ -204,8 +202,8 @@ def _get_parameter_dict_from_mcp_prompt(prompt: types.Prompt) -> list[dict[str, @experimental def _get_parameter_dicts_from_mcp_tool(tool: types.Tool) -> list[dict[str, Any]]: """Creates an MCPFunction instance from a tool.""" - properties = tool.inputSchema.get("properties", None) - required = tool.inputSchema.get("required", []) + properties = tool.input_schema.get("properties", None) + required = tool.input_schema.get("required", []) # Check if 'properties' is missing or not a dictionary if not properties: return [] @@ -334,7 +332,7 @@ async def _inner_connect(self, ready_event: asyncio.Event) -> None: ClientSession( read_stream=transport[0], write_stream=transport[1], - read_timeout_seconds=timedelta(seconds=self.request_timeout) if self.request_timeout else None, + read_timeout_seconds=float(self.request_timeout) if self.request_timeout else None, message_handler=self.message_handler, logging_callback=self.logging_callback, sampling_callback=self.sampling_callback, @@ -353,7 +351,7 @@ async def _inner_connect(self, ready_event: asyncio.Event) -> None: "Failed to initialize session. Please check your configuration." ) from ex self.session = session - elif self.session._request_id == 0: + elif self.session.initialize_result is None: # If the session is not initialized, we need to reinitialize it await self.session.initialize() logger.debug("Connected to MCP server: %s", self.session) @@ -381,7 +379,7 @@ async def _inner_connect(self, ready_event: asyncio.Event) -> None: pass async def sampling_callback( - self, context: RequestContext[ClientSession, Any], params: types.CreateMessageRequestParams + self, context: ClientRequestContext, params: types.CreateMessageRequestParams ) -> types.CreateMessageResult | types.ErrorData: """Callback function for sampling. @@ -423,9 +421,9 @@ async def sampling_callback( message="No services in Kernel. Please set a kernel with one or more services.", ) logger.debug("Sampling callback called with params: %s", params) - if params.modelPreferences is not None and params.modelPreferences.hints: - # TODO (eavanvalkenburg): deal with other parts of the modelPreferences concept - names = [hint.name for hint in params.modelPreferences.hints] + if params.model_preferences is not None and params.model_preferences.hints: + # TODO (eavanvalkenburg): deal with other parts of the model_preferences concept + names = [hint.name for hint in params.model_preferences.hints] else: names = ["default"] @@ -444,12 +442,12 @@ async def sampling_callback( completion_settings.temperature = params.temperature # type: ignore if "max_completion_tokens" in completion_settings.__class__.model_fields: - completion_settings.max_completion_tokens = params.maxTokens # type: ignore + completion_settings.max_completion_tokens = params.max_tokens # type: ignore elif "max_tokens" in completion_settings.__class__.model_fields: - completion_settings.max_tokens = params.maxTokens # type: ignore + completion_settings.max_tokens = params.max_tokens # type: ignore elif "max_output_tokens" in completion_settings.__class__.model_fields: - completion_settings.max_output_tokens = params.maxTokens # type: ignore - chat_history = ChatHistory(system_message=params.systemPrompt) + completion_settings.max_output_tokens = params.max_tokens # type: ignore + chat_history = ChatHistory(system_message=params.system_prompt) for msg in params.messages: chat_history.add_message(_mcp_prompt_message_to_kernel_content(msg)) try: @@ -505,7 +503,7 @@ async def logging_callback(self, params: types.LoggingMessageNotificationParams) async def message_handler( self, - message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, + message: types.ServerNotification | Exception, ) -> None: """Handle messages from the MCP server. @@ -519,8 +517,8 @@ async def message_handler( if isinstance(message, Exception): logger.error("Error from MCP server: %s", message) return - if isinstance(message, types.ServerNotification): - match message.root.method: + if isinstance(message, (types.ToolListChangedNotification, types.PromptListChangedNotification)): + match message.method: case "notifications/tools/list_changed": await self.load_tools() case "notifications/prompts/list_changed": @@ -616,7 +614,7 @@ async def call_tool( ) try: return _mcp_call_tool_result_to_kernel_contents(await self.session.call_tool(tool_name, arguments=kwargs)) - except McpError: + except MCPError: raise except Exception as ex: raise FunctionExecutionException(f"Failed to call tool '{tool_name}'.") from ex @@ -634,7 +632,7 @@ async def get_prompt(self, prompt_name: str, **kwargs: Any) -> list[ChatMessageC try: prompt_result = await self.session.get_prompt(prompt_name, arguments=kwargs) return [_mcp_prompt_message_to_kernel_content(message) for message in prompt_result.messages] - except McpError: + except MCPError: raise except Exception as ex: raise FunctionExecutionException(f"Failed to call prompt '{prompt_name}'.") from ex @@ -835,7 +833,7 @@ def __init__( """Initialize the MCP streamable http plugin. The arguments are used to create a streamable http client. - see mcp.client.streamable_http.streamablehttp_client for more details. + see mcp.client.streamable_http.streamable_http_client for more details. Any extra arguments passed to the constructor will be passed to the streamable http client constructor. @@ -884,17 +882,23 @@ def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]: args: dict[str, Any] = { "url": self.url, } - if self.headers: - args["headers"] = self.headers - if self.timeout: - args["timeout"] = self.timeout - if self.sse_read_timeout: - args["sse_read_timeout"] = self.sse_read_timeout + # In mcp 2.x the streamable http client only accepts a pre-built http_client; + # headers/timeout/sse_read_timeout are configured on that client instead. + if self.headers or self.timeout or self.sse_read_timeout: + timeout = None + if self.timeout or self.sse_read_timeout: + import httpx2 + + timeout = httpx2.Timeout( + self.timeout if self.timeout else 30.0, + read=self.sse_read_timeout if self.sse_read_timeout else 300.0, + ) + args["http_client"] = create_mcp_http_client(headers=self.headers or None, timeout=timeout) if self.terminate_on_close is not None: args["terminate_on_close"] = self.terminate_on_close if self._client_kwargs: args.update(self._client_kwargs) - return streamablehttp_client(**args) + return streamable_http_client(**args) class MCPWebsocketPlugin(MCPPluginBase): @@ -917,8 +921,9 @@ def __init__( ) -> None: """Initialize the MCP websocket plugin. - The arguments are used to create a websocket client. - see mcp.client.websocket.websocket_client for more details. + Note: the websocket transport was removed from the mcp Python SDK in 2.0, + so connecting this plugin raises a KernelPluginInvalidConfigurationError. + Use MCPStdioPlugin, MCPSsePlugin, or MCPStreamableHttpPlugin instead. Any extra arguments passed to the constructor will be passed to the websocket client constructor. @@ -957,12 +962,11 @@ def __init__( def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]: """Get an MCP websocket client.""" - args: dict[str, Any] = { - "url": self.url, - } - if self._client_kwargs: - args.update(self._client_kwargs) - return websocket_client(**args) + # The websocket transport was removed from the mcp Python SDK in 2.0. + raise KernelPluginInvalidConfigurationError( + "The MCP websocket transport is not available: mcp.client.websocket was removed in mcp 2.0. " + "Use MCPStdioPlugin, MCPSsePlugin, or MCPStreamableHttpPlugin instead." + ) # region: Kernel as MCP Server @@ -1066,171 +1070,176 @@ def create_mcp_server_from_kernel( mcp.server.lowlevel.Server """ - server_args: dict[str, Any] = { - "name": server_name, - "version": version, - "instructions": instructions, - } - if lifespan: - server_args["lifespan"] = lifespan - if kwargs: - server_args.update(kwargs) - if excluded_functions is not None and not isinstance(excluded_functions, list): excluded_functions = [excluded_functions] # type: ignore - server: Server["LifespanResultT"] = Server(**server_args) # type: ignore[call-arg] - functions_to_expose = [ func for func in kernel.get_full_list_of_function_metadata() if func.name not in (excluded_functions or []) ] exposed_names = frozenset(func.name for func in functions_to_expose) - if len(functions_to_expose) > 0: - - @server.list_tools() - async def _list_tools() -> list[types.Tool]: - """List all tools in the kernel.""" - tools = [ - types.Tool( - name=func.name, - description=func.description, - inputSchema={ - "type": "object", - "properties": { - param.name: param.schema_data - for param in func.parameters - if param.name and param.schema_data and param.include_in_function_choices - }, - "required": [ - param.name - for param in func.parameters - if param.name and param.is_required and param.include_in_function_choices - ], + # In mcp 2.x there is no server-level request_context; handlers receive a per-request + # ServerRequestContext carrying the connection-scoped session. We track the most recent + # one so helper functions (e.g. _log) can reach the session to emit log messages. + current_ctx: dict[str, ServerRequestContext | None] = {"ctx": None} + + server: Server["LifespanResultT"] | None = None + + async def _list_tools( + ctx: ServerRequestContext, params: types.PaginatedRequestParams | None + ) -> types.ListToolsResult: + """List all tools in the kernel.""" + current_ctx["ctx"] = ctx + tools = [ + types.Tool( + name=func.name, + description=func.description, + input_schema={ + "type": "object", + "properties": { + param.name: param.schema_data + for param in func.parameters + if param.name and param.schema_data and param.include_in_function_choices }, - ) - for func in functions_to_expose - ] - await _log(level="debug", data=f"List of tools: {tools}") - await asyncio.sleep(0.0) - return tools - - @server.call_tool() - async def _call_tool( - *args: Any, - ) -> Sequence[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource]: - """Call a tool in the kernel.""" - function_name, arguments = args[0], args[1] - if function_name not in exposed_names: - raise McpError( - error=types.ErrorData( - code=types.METHOD_NOT_FOUND, - message=f"Unknown tool: {function_name}", - ) - ) - await _log(level="debug", data=f"Calling tool: {function_name}") - result = await _call_kernel_function(function_name, arguments) - if result: - value = result.value - messages: list[ - types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource - ] = [] - if isinstance(value, list): - for item in value: - match item: - case ( - TextContent() | ImageContent() | BinaryContent() | AudioContent() | ChatMessageContent() - ): - messages.extend(_kernel_content_to_mcp_content_types(item)) - case _: - messages.append( - types.TextContent(type="text", text=str(item)), - ) - else: - match value: + "required": [ + param.name + for param in func.parameters + if param.name and param.is_required and param.include_in_function_choices + ], + }, + ) + for func in functions_to_expose + ] + await _log(level="debug", data=f"List of tools: {tools}") + await asyncio.sleep(0.0) + return types.ListToolsResult(tools=tools) + + async def _call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult: + """Call a tool in the kernel.""" + current_ctx["ctx"] = ctx + function_name, arguments = params.name, params.arguments or {} + if function_name not in exposed_names: + raise MCPError( + code=types.METHOD_NOT_FOUND, + message=f"Unknown tool: {function_name}", + ) + await _log(level="debug", data=f"Calling tool: {function_name}") + result = await _call_kernel_function(function_name, arguments) + if result: + value = result.value + messages: list[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource] = [] + if isinstance(value, list): + for item in value: + match item: case TextContent() | ImageContent() | BinaryContent() | AudioContent() | ChatMessageContent(): - messages.extend(_kernel_content_to_mcp_content_types(value)) + messages.extend(_kernel_content_to_mcp_content_types(item)) case _: messages.append( - types.TextContent(type="text", text=str(value)), + types.TextContent(type="text", text=str(item)), ) - return messages - raise McpError( - error=types.ErrorData( - code=types.INTERNAL_ERROR, - message=f"Function {function_name} returned no result", - ), - ) - - if prompts: + else: + match value: + case TextContent() | ImageContent() | BinaryContent() | AudioContent() | ChatMessageContent(): + messages.extend(_kernel_content_to_mcp_content_types(value)) + case _: + messages.append( + types.TextContent(type="text", text=str(value)), + ) + return types.CallToolResult(content=messages) + raise MCPError( + code=types.INTERNAL_ERROR, + message=f"Function {function_name} returned no result", + ) - @server.list_prompts() - async def _list_prompts() -> list[types.Prompt]: - """List all prompts in the kernel.""" - mcp_prompts = [] - for prompt in prompts: - mcp_prompts.append( - types.Prompt( - name=prompt.prompt_template_config.name, - description=prompt.prompt_template_config.description, - arguments=[ - types.PromptArgument( - name=var.name, - description=var.description, - required=var.is_required, - ) - for var in prompt.prompt_template_config.input_variables - ], - ) + async def _list_prompts( + ctx: ServerRequestContext, params: types.PaginatedRequestParams | None + ) -> types.ListPromptsResult: + """List all prompts in the kernel.""" + current_ctx["ctx"] = ctx + mcp_prompts = [] + for prompt in prompts or []: + mcp_prompts.append( + types.Prompt( + name=prompt.prompt_template_config.name, + description=prompt.prompt_template_config.description, + arguments=[ + types.PromptArgument( + name=var.name, + description=var.description, + required=var.is_required, + ) + for var in prompt.prompt_template_config.input_variables + ], ) - await _log(level="debug", data=f"List of prompts: {mcp_prompts}") - return mcp_prompts - - @server.get_prompt() - async def _get_prompt(name: str, arguments: dict[str, Any] | None) -> types.GetPromptResult: - """Get a prompt by name.""" - prompt = next((p for p in prompts if p.prompt_template_config.name == name), None) - if prompt is None: - return types.GetPromptResult(description="Prompt not found", messages=[]) - - # Call the prompt - rendered_prompt = await prompt.render( - kernel, - KernelArguments(**arguments) if arguments is not None else KernelArguments(), ) - # since the return type of a get_prompts is a list of messages, - # we need to convert the rendered prompt to a list of messages - # by using the ChatHistory class - chat_history = ChatHistory.from_rendered_prompt(rendered_prompt) - messages = [] - for message in chat_history.messages: - messages.append( - types.PromptMessage( - role=message.role.value - if message.role in (AuthorRole.ASSISTANT, AuthorRole.USER) - else "assistant", - content=_kernel_content_to_mcp_content_types(message)[0], - ) + await _log(level="debug", data=f"List of prompts: {mcp_prompts}") + return types.ListPromptsResult(prompts=mcp_prompts) + + async def _get_prompt(ctx: ServerRequestContext, params: types.GetPromptRequestParams) -> types.GetPromptResult: + """Get a prompt by name.""" + current_ctx["ctx"] = ctx + name, arguments = params.name, params.arguments + prompt = next((p for p in (prompts or []) if p.prompt_template_config.name == name), None) + if prompt is None: + return types.GetPromptResult(description="Prompt not found", messages=[]) + + # Call the prompt + rendered_prompt = await prompt.render( + kernel, + KernelArguments(**arguments) if arguments is not None else KernelArguments(), # type: ignore[arg-type] + ) + # since the return type of a get_prompts is a list of messages, + # we need to convert the rendered prompt to a list of messages + # by using the ChatHistory class + chat_history = ChatHistory.from_rendered_prompt(rendered_prompt) + messages = [] + for message in chat_history.messages: + messages.append( + types.PromptMessage( + role=message.role.value if message.role in (AuthorRole.ASSISTANT, AuthorRole.USER) else "assistant", + content=_kernel_content_to_mcp_content_types(message)[0], ) - return types.GetPromptResult(messages=messages) + ) + return types.GetPromptResult(messages=messages) + + async def _set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> types.EmptyResult: + """Set the logging level for the server.""" + current_ctx["ctx"] = ctx + logger.setLevel(LOG_LEVEL_MAPPING[params.level]) + # emit this log with the new minimum level + await _log(level=params.level, data=f"Log level set to {params.level}") + return types.EmptyResult() + + server_args: dict[str, Any] = { + "name": server_name, + "version": version, + "instructions": instructions, + } + if lifespan: + server_args["lifespan"] = lifespan + if len(functions_to_expose) > 0: + server_args["on_list_tools"] = _list_tools + server_args["on_call_tool"] = _call_tool + if prompts: + server_args["on_list_prompts"] = _list_prompts + server_args["on_get_prompt"] = _get_prompt + server_args["on_set_logging_level"] = _set_logging_level + if kwargs: + server_args.update(kwargs) + + server = Server(**server_args) # type: ignore[call-arg] async def _log(level: types.LoggingLevel, data: Any) -> None: """Log a message to the server and logger.""" # Log to the local logger logger.log(LOG_LEVEL_MAPPING[level], data) - if server and server.request_context and server.request_context.session: + ctx = current_ctx.get("ctx") + if ctx and ctx.session: try: - await server.request_context.session.send_log_message(level=level, data=data) + await ctx.session.send_log_message(level=level, data=data) except Exception as e: logger.error("Failed to send log message to server: %s", e) - @server.set_logging_level() - async def _set_logging_level(level: types.LoggingLevel) -> None: - """Set the logging level for the server.""" - logger.setLevel(LOG_LEVEL_MAPPING[level]) - # emit this log with the new minimum level - await _log(level=level, data=f"Log level set to {level}") - async def _call_kernel_function(function_name: str, arguments: Any) -> FunctionResult | None: function = kernel.get_function(plugin_name=None, function_name=function_name) arguments["server"] = server diff --git a/python/tests/assets/test_plugins/TestMCPPlugin/mcp_server.py b/python/tests/assets/test_plugins/TestMCPPlugin/mcp_server.py index 63c741ded463..fd1099f49312 100644 --- a/python/tests/assets/test_plugins/TestMCPPlugin/mcp_server.py +++ b/python/tests/assets/test_plugins/TestMCPPlugin/mcp_server.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. -from mcp.server.fastmcp import FastMCP +from mcp.server.mcpserver import MCPServer -mcp = FastMCP("Echo") +mcp = MCPServer("Echo") @mcp.resource("echo://{message}") diff --git a/python/tests/unit/connectors/mcp/test_mcp.py b/python/tests/unit/connectors/mcp/test_mcp.py index b4623489de12..9e01f152b0ff 100644 --- a/python/tests/unit/connectors/mcp/test_mcp.py +++ b/python/tests/unit/connectors/mcp/test_mcp.py @@ -22,12 +22,12 @@ def list_tool_calls_with_slash() -> ListToolsResult: Tool( name="nasa/get-astronomy-picture", description="func with slash", - inputSchema={"properties": {}, "required": []}, + input_schema={"properties": {}, "required": []}, ), Tool( name="weird\\name with spaces", description="func with backslash and spaces", - inputSchema={"properties": {}, "required": []}, + input_schema={"properties": {}, "required": []}, ), ] ) @@ -40,7 +40,7 @@ def list_tool_calls() -> ListToolsResult: Tool( name="func1", description="func1", - inputSchema={ + input_schema={ "properties": { "name": {"type": "string"}, }, @@ -50,7 +50,7 @@ def list_tool_calls() -> ListToolsResult: Tool( name="func2", description="func2", - inputSchema={}, + input_schema={}, ), ] ) @@ -66,7 +66,7 @@ def list_tool_calls() -> ListToolsResult: async def test_mcp_plugin_session_not_initialize(plugin_class, plugin_args): # Test if Client can insert it's own Session mock_session = AsyncMock(spec=ClientSession) - mock_session._request_id = 0 + mock_session.initialize_result = None mock_session.initialize = AsyncMock() async with plugin_class(name="test", session=mock_session, **plugin_args) as plugin: assert plugin.session is mock_session @@ -83,7 +83,7 @@ async def test_mcp_plugin_session_not_initialize(plugin_class, plugin_args): async def test_mcp_plugin_session_initialized(plugin_class, plugin_args): # Test if Client can insert it's own initialized Session mock_session = AsyncMock(spec=ClientSession) - mock_session._request_id = 1 + mock_session.initialize_result = MagicMock() mock_session.initialize = AsyncMock() async with plugin_class(name="test", session=mock_session, **plugin_args) as plugin: assert plugin.session is mock_session @@ -174,8 +174,8 @@ async def test_mcp_tool_and_prompt_names_do_not_shadow_plugin_attributes(): session = AsyncMock(spec=ClientSession) session.list_tools.return_value = ListToolsResult( tools=[ - Tool(name="kernel", description="reserved", inputSchema={}), - Tool(name="safe_tool", description="safe", inputSchema={}), + Tool(name="kernel", description="reserved", input_schema={}), + Tool(name="safe_tool", description="safe", input_schema={}), ] ) session.list_prompts.return_value = types.ListPromptsResult( @@ -201,8 +201,8 @@ async def test_mcp_tool_and_prompt_names_can_reload_existing_mcp_functions(): plugin = MCPSsePlugin(name="TestMCPPlugin", url="http://localhost:8080/sse") session = AsyncMock(spec=ClientSession) session.list_tools.side_effect = [ - ListToolsResult(tools=[Tool(name="safe_tool", description="first tool", inputSchema={})]), - ListToolsResult(tools=[Tool(name="safe_tool", description="second tool", inputSchema={})]), + ListToolsResult(tools=[Tool(name="safe_tool", description="first tool", input_schema={})]), + ListToolsResult(tools=[Tool(name="safe_tool", description="second tool", input_schema={})]), ] session.list_prompts.side_effect = [ types.ListPromptsResult(prompts=[types.Prompt(name="safe_prompt", description="first prompt", arguments=[])]), @@ -283,35 +283,19 @@ async def test_with_kwargs_stdio(mock_session, mock_client, list_tool_calls, ker assert len(loaded_plugin.functions["func2"].parameters) == 0 -@patch("semantic_kernel.connectors.mcp.websocket_client") -@patch("semantic_kernel.connectors.mcp.ClientSession") -async def test_with_kwargs_websocket(mock_session, mock_client, list_tool_calls, kernel: "Kernel"): - mock_read = MagicMock() - mock_write = MagicMock() +async def test_websocket_transport_removed(kernel: "Kernel"): + """The websocket transport was removed from the mcp Python SDK in 2.0. - mock_generator = MagicMock() - # Make the mock_stdio_client return an AsyncMock for the context manager - mock_generator.__aenter__.return_value = (mock_read, mock_write) - mock_generator.__aexit__.return_value = (mock_read, mock_write) - - # Make the mock_stdio_client return an AsyncMock for the context manager - mock_client.return_value = mock_generator - mock_session.return_value.__aenter__.return_value.list_tools.return_value = list_tool_calls - async with MCPWebsocketPlugin( - name="TestMCPPlugin", - description="Test MCP Plugin", - url="http://localhost:8080/websocket", - ) as plugin: - mock_client.assert_called_once_with(url="http://localhost:8080/websocket") - loaded_plugin = kernel.add_plugin(plugin) - assert loaded_plugin is not None - assert loaded_plugin.name == "TestMCPPlugin" - assert loaded_plugin.description == "Test MCP Plugin" - assert loaded_plugin.functions.get("func1") is not None - assert loaded_plugin.functions["func1"].parameters[0].name == "name" - assert loaded_plugin.functions["func1"].parameters[0].is_required - assert loaded_plugin.functions.get("func2") is not None - assert len(loaded_plugin.functions["func2"].parameters) == 0 + Connecting an MCPWebsocketPlugin should fail with a clear configuration error + rather than an import error at module load time. + """ + with pytest.raises(KernelPluginInvalidConfigurationError, match="Failed to connect to the MCP server"): + async with MCPWebsocketPlugin( + name="TestMCPPlugin", + description="Test MCP Plugin", + url="http://localhost:8080/websocket", + ): + pass @patch("semantic_kernel.connectors.mcp.sse_client") @@ -345,19 +329,19 @@ async def test_with_kwargs_sse(mock_session, mock_client, list_tool_calls, kerne assert len(loaded_plugin.functions["func2"].parameters) == 0 -@patch("semantic_kernel.connectors.mcp.streamablehttp_client") +@patch("semantic_kernel.connectors.mcp.streamable_http_client") @patch("semantic_kernel.connectors.mcp.ClientSession") async def test_with_kwargs_streamablehttp(mock_session, mock_client, list_tool_calls, kernel: "Kernel"): mock_read = MagicMock() mock_write = MagicMock() - mock_callback = MagicMock() mock_generator = MagicMock() - # Make the mock_streamablehttp_client return an AsyncMock for the context manager - mock_generator.__aenter__.return_value = (mock_read, mock_write, mock_callback) - mock_generator.__aexit__.return_value = (mock_read, mock_write, mock_callback) + # Make the mock streamable_http_client return an AsyncMock for the context manager + # (mcp 2.x yields a 2-tuple of read/write streams). + mock_generator.__aenter__.return_value = (mock_read, mock_write) + mock_generator.__aexit__.return_value = (mock_read, mock_write) - # Make the mock_streamablehttp_client return an AsyncMock for the context manager + # Make the mock streamable_http_client return an AsyncMock for the context manager mock_client.return_value = mock_generator mock_session.return_value.__aenter__.return_value.list_tools.return_value = list_tool_calls async with MCPStreamableHttpPlugin( @@ -382,9 +366,10 @@ async def test_kernel_as_mcp_server(kernel: "Kernel", decorated_native_function, kernel.add_functions("test", [decorated_native_function]) server = kernel.as_mcp_server() assert server is not None - assert types.PingRequest in server.request_handlers - assert types.ListToolsRequest in server.request_handlers - assert types.CallToolRequest in server.request_handlers + # mcp 2.x registers request handlers by method name on the lowlevel Server. + assert server.get_request_handler("ping") is not None + assert server.get_request_handler("tools/list") is not None + assert server.get_request_handler("tools/call") is not None assert server.name == "Semantic Kernel MCP Server" @@ -435,8 +420,8 @@ async def test_mcp_tool_name_collision_detected(caplog): session = AsyncMock(spec=ClientSession) session.list_tools.return_value = ListToolsResult( tools=[ - Tool(name="read-document", description="first tool", inputSchema={}), - Tool(name="read document", description="second tool", inputSchema={}), + Tool(name="read-document", description="first tool", input_schema={}), + Tool(name="read document", description="second tool", input_schema={}), ] ) plugin.session = session @@ -483,8 +468,8 @@ async def test_mcp_tool_name_collision_detected_across_reload(caplog): plugin = MCPSsePlugin(name="TestMCPPlugin", url="http://localhost:8080/sse") session = AsyncMock(spec=ClientSession) session.list_tools.side_effect = [ - ListToolsResult(tools=[Tool(name="read-document", description="first tool", inputSchema={})]), - ListToolsResult(tools=[Tool(name="read document", description="second tool", inputSchema={})]), + ListToolsResult(tools=[Tool(name="read-document", description="first tool", input_schema={})]), + ListToolsResult(tools=[Tool(name="read document", description="second tool", input_schema={})]), ] plugin.session = session @@ -504,7 +489,7 @@ async def test_mcp_prompt_does_not_replace_registered_tool_name(caplog): plugin = MCPSsePlugin(name="TestMCPPlugin", url="http://localhost:8080/sse") session = AsyncMock(spec=ClientSession) session.list_tools.return_value = ListToolsResult( - tools=[Tool(name="read-document", description="first tool", inputSchema={})] + tools=[Tool(name="read-document", description="first tool", input_schema={})] ) session.list_prompts.return_value = types.ListPromptsResult( prompts=[types.Prompt(name="read document", description="second item", arguments=[])] @@ -544,30 +529,17 @@ def secret_admin(target: str) -> str: server = create_mcp_server_from_kernel(kernel, excluded_functions=["secret_admin"]) - # Verify the server was created with handlers - assert types.ListToolsRequest in server.request_handlers - assert types.CallToolRequest in server.request_handlers + # Verify the server was created with handlers (mcp 2.x registers by method name) + assert server.get_request_handler("tools/list") is not None + assert server.get_request_handler("tools/call") is not None - # Mock _get_cached_tool_definition to bypass SDK request context requirements - # (normally set by a real MCP session transport) - async def _fake_get_cached_tool_definition(tool_name): - return None + # Invoke the registered tools/call handler directly with a call for the excluded function. + handler = server.get_request_handler("tools/call").handler + params = types.CallToolRequestParams(name="secret_admin", arguments={}) - server._get_cached_tool_definition = _fake_get_cached_tool_definition + # The call must raise an MCPError (Unknown tool), and the side effect must not fire. + from mcp.shared.exceptions import MCPError - # Build a proper CallToolRequest as the MCP SDK would send - call_tool_request = types.CallToolRequest( - method="tools/call", - params=types.CallToolRequestParams(name="secret_admin", arguments={}), - ) - - # The internal handler wraps our _call_tool; invoke via the registered handler - handler = server.request_handlers[types.CallToolRequest] - result = await handler(call_tool_request) - - # The call must fail (isError=True) with the correct error message - assert result.root.isError is True, "Calling an excluded function should return an error" - assert any("Unknown tool" in c.text for c in result.root.content if hasattr(c, "text")), ( - f"Expected 'Unknown tool' error, got: {result.root.content}" - ) + with pytest.raises(MCPError, match="Unknown tool"): + await handler(MagicMock(), params) assert not side_effect_called, "Excluded function's side effect should not have fired" diff --git a/python/uv.lock b/python/uv.lock index ef385caff4d0..787ea2a992a6 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -271,8 +271,7 @@ dependencies = [ { name = "docstring-parser", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "jiter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "sniffio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] @@ -354,8 +353,7 @@ dependencies = [ { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "termcolor", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tiktoken", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -976,8 +974,7 @@ dependencies = [ { name = "orjson", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "overrides", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pybase64", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pypika", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1720,8 +1717,7 @@ dependencies = [ { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "proto-plus", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/be/31ce7fd658ddebafbe5583977ddee536b2bacc491ad10b5a067388aec66f/google_cloud_aiplatform-1.133.0.tar.gz", hash = "sha256:3a6540711956dd178daaab3c2c05db476e46d94ac25912b8cf4f59b00b058ae0", size = 9921309, upload-time = "2026-01-08T22:11:25.079Z" } @@ -1837,8 +1833,7 @@ dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "google-auth", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tenacity", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -2182,6 +2177,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "truststore", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" }, +] + [[package]] name = "httptools" version = "0.7.1" @@ -2246,12 +2254,19 @@ http2 = [ ] [[package]] -name = "httpx-sse" -version = "0.4.3" +name = "httpx2" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "httpcore2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "truststore", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" }, ] [[package]] @@ -2305,11 +2320,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -3041,16 +3056,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.28.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "httpx-sse", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "httpx2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "jsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, - { name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "mcp-types", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyjwt", extra = ["crypto"], marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-multipart", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, @@ -3060,9 +3074,22 @@ dependencies = [ { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "uvicorn", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/33/32d4dff2c95bb5d897c3ef4c83649a08996b17b58f0a326d2495d4c81179/mcp-2.0.0.tar.gz", hash = "sha256:0f440e735c13ece8bb19bc62cf0b86f4313448432fbb77d35e14034f4e050728", size = 1662284, upload-time = "2026-07-28T13:45:32.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl", hash = "sha256:1cb4c75d2d2c7b8c1d756355e5d82a39f2822cc7f13e22a2051d7ca3592349d6", size = 349980, upload-time = "2026-07-28T13:45:28.853Z" }, +] + +[[package]] +name = "mcp-types" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/56/9b8e1c152f61f6c6b07c4b5896c88c7d0ae90bac6ee6306f852fcc5c1eb0/mcp_types-2.0.0.tar.gz", hash = "sha256:d7d939b9285c9961ae8866ba75ef85da34d12bafe276efbf4eb6a131786d8379", size = 66632, upload-time = "2026-07-28T13:45:33.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4c/c78d78c3d52b0ac594ad7cc8ef5972adfe070e3597a8a4c6ce0cd39196ea/mcp_types-2.0.0-py3-none-any.whl", hash = "sha256:6b2de797ca2797f568b79529e1b25948e34de511bcc0bd82fef1039a6d1b8eb0", size = 69649, upload-time = "2026-07-28T13:45:30.713Z" }, ] [[package]] @@ -3079,8 +3106,7 @@ name = "microsoft-agents-activity" version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/8a/3dbdf47f3ddabf646987ddf6f5260e77865c6812177b8759f1c7fc395ac8/microsoft_agents_activity-0.8.0.tar.gz", hash = "sha256:f9e7d92db119cf93dd0642a5e698732c40a450c064306ad076b0d83d95eae114", size = 61226, upload-time = "2026-02-24T18:28:49.283Z" } wheels = [ @@ -3136,8 +3162,7 @@ dependencies = [ { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-exporter-otlp-proto-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -3853,8 +3878,7 @@ version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/5a/652dac4b7affc2b37b95386f8ae78f22808af09d720689e3d7a86b6ed98e/ollama-0.6.1.tar.gz", hash = "sha256:478c67546836430034b415ed64fa890fd3d1ff91781a9d548b3325274e69d7c6", size = 51620, upload-time = "2025-11-13T23:02:17.416Z" } wheels = [ @@ -4044,8 +4068,7 @@ dependencies = [ { name = "distro", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "jiter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "sniffio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -5141,173 +5164,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pydantic" -version = "2.11.10" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.13.*' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version == '3.13.*' and sys_platform == 'linux'", - "python_full_version == '3.12.*' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform == 'linux'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'win32'", -] -dependencies = [ - { name = "annotated-types", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic-core", version = "2.33.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "typing-extensions", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "typing-inspection", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/54/ecab642b3bed45f7d5f59b38443dcb36ef50f85af192e6ece103dbfe9587/pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423", size = 788494, upload-time = "2025-10-04T10:40:41.338Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/1f/73c53fcbfb0b5a78f91176df41945ca466e71e9d9d836e5c522abda39ee7/pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a", size = 444823, upload-time = "2025-10-04T10:40:39.055Z" }, -] - [[package]] name = "pydantic" version = "2.13.4" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '4' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'darwin'", - "python_full_version >= '4' and sys_platform == 'linux'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'linux'", - "python_full_version >= '4' and sys_platform == 'win32'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'win32'", -] dependencies = [ - { name = "annotated-types", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, - { name = "pydantic-core", version = "2.46.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, - { name = "typing-extensions", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, - { name = "typing-inspection", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "annotated-types", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pydantic-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] -[[package]] -name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.13.*' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version == '3.13.*' and sys_platform == 'linux'", - "python_full_version == '3.12.*' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform == 'linux'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'win32'", -] -dependencies = [ - { name = "typing-extensions", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, -] - [[package]] name = "pydantic-core" version = "2.46.4" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '4' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'darwin'", - "python_full_version >= '4' and sys_platform == 'linux'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'linux'", - "python_full_version >= '4' and sys_platform == 'win32'", - "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'win32'", -] dependencies = [ - { name = "typing-extensions", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } wheels = [ @@ -5423,8 +5300,7 @@ name = "pydantic-settings" version = "2.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] @@ -5982,8 +5858,7 @@ dependencies = [ { name = "httpx", extra = ["http2"], marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "portalocker", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/15/5e/ec560881e086f893947c8798949c72de5cfae9453fd05c2250f8dfeaa571/qdrant_client-1.12.1.tar.gz", hash = "sha256:35e8e646f75b7b883b3d2d0ee4c69c5301000bba41c82aa546e985db0f1aeb72", size = 237441, upload-time = "2024-10-29T17:31:09.698Z" } @@ -6016,8 +5891,7 @@ dependencies = [ { name = "jsonpath-ng", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "ml-dtypes", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-ulid", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "redis", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -6708,8 +6582,7 @@ dependencies = [ { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "prance", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pybars4", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and sys_platform == 'darwin') or (python_full_version >= '3.11' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, @@ -6852,8 +6725,8 @@ requires-dist = [ { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.51,<1.75" }, { name = "ipykernel", marker = "extra == 'notebooks'", specifier = ">=6.29,<8.0" }, { name = "jinja2", specifier = "~=3.1" }, - { name = "mcp", specifier = ">=1.26.0,<2.0" }, - { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.8,<2.0" }, + { name = "mcp", specifier = ">=2.0.0,<3.0" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=2.0.0,<3.0" }, { name = "microsoft-agents-activity", marker = "extra == 'copilotstudio'", specifier = ">=0.3.1" }, { name = "microsoft-agents-copilotstudio-client", marker = "extra == 'copilotstudio'", specifier = ">=0.3.1" }, { name = "milvus", marker = "sys_platform != 'win32' and extra == 'milvus'", specifier = ">=2.3,<2.3.8" }, @@ -7435,6 +7308,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typer" version = "0.24.1" @@ -7836,8 +7718,7 @@ dependencies = [ { name = "grpcio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pydantic", version = "2.11.10", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pydantic", version = "2.13.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "validators", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/76/14e07761c5fb7e8573e3cff562e2d9073c65f266db0e67511403d10435b1/weaviate_client-4.18.3.tar.gz", hash = "sha256:9d889246d62be36641a7f2b8cedf5fb665b804d46f7a53ae37e02d297a11f119", size = 783634, upload-time = "2025-12-03T09:38:28.261Z" } From b3eb1cbba0404b2b5e8254a8338f6efd69e9f617 Mon Sep 17 00:00:00 2001 From: Oliver Everett Date: Tue, 4 Aug 2026 09:31:28 +0800 Subject: [PATCH 2/2] Python: address MCP 2.x review feedback on connector - Pass the per-request ServerRequestContext into _log explicitly instead of storing the most recent context in a shared dict, which was not safe under concurrent requests. - In the streamable http plugin, fall back to the configured timeout for the read timeout when sse_read_timeout is unset, so a short timeout also bounds reads instead of defaulting reads to 300s. --- python/semantic_kernel/connectors/mcp.py | 35 +++++++++++------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/python/semantic_kernel/connectors/mcp.py b/python/semantic_kernel/connectors/mcp.py index 7b0b1f2414e4..d39085800789 100644 --- a/python/semantic_kernel/connectors/mcp.py +++ b/python/semantic_kernel/connectors/mcp.py @@ -889,9 +889,13 @@ def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]: if self.timeout or self.sse_read_timeout: import httpx2 + # Map the overall default from `timeout`, and let `sse_read_timeout` only + # widen the read window. When `sse_read_timeout` is unset, fall back to + # `timeout` so a configured short timeout also bounds reads. + default = self.timeout if self.timeout else 30.0 timeout = httpx2.Timeout( - self.timeout if self.timeout else 30.0, - read=self.sse_read_timeout if self.sse_read_timeout else 300.0, + default, + read=self.sse_read_timeout if self.sse_read_timeout else default, ) args["http_client"] = create_mcp_http_client(headers=self.headers or None, timeout=timeout) if self.terminate_on_close is not None: @@ -1078,18 +1082,12 @@ def create_mcp_server_from_kernel( ] exposed_names = frozenset(func.name for func in functions_to_expose) - # In mcp 2.x there is no server-level request_context; handlers receive a per-request - # ServerRequestContext carrying the connection-scoped session. We track the most recent - # one so helper functions (e.g. _log) can reach the session to emit log messages. - current_ctx: dict[str, ServerRequestContext | None] = {"ctx": None} - server: Server["LifespanResultT"] | None = None async def _list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None ) -> types.ListToolsResult: """List all tools in the kernel.""" - current_ctx["ctx"] = ctx tools = [ types.Tool( name=func.name, @@ -1110,20 +1108,19 @@ async def _list_tools( ) for func in functions_to_expose ] - await _log(level="debug", data=f"List of tools: {tools}") + await _log(ctx, level="debug", data=f"List of tools: {tools}") await asyncio.sleep(0.0) return types.ListToolsResult(tools=tools) async def _call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult: """Call a tool in the kernel.""" - current_ctx["ctx"] = ctx function_name, arguments = params.name, params.arguments or {} if function_name not in exposed_names: raise MCPError( code=types.METHOD_NOT_FOUND, message=f"Unknown tool: {function_name}", ) - await _log(level="debug", data=f"Calling tool: {function_name}") + await _log(ctx, level="debug", data=f"Calling tool: {function_name}") result = await _call_kernel_function(function_name, arguments) if result: value = result.value @@ -1155,7 +1152,6 @@ async def _list_prompts( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None ) -> types.ListPromptsResult: """List all prompts in the kernel.""" - current_ctx["ctx"] = ctx mcp_prompts = [] for prompt in prompts or []: mcp_prompts.append( @@ -1172,12 +1168,11 @@ async def _list_prompts( ], ) ) - await _log(level="debug", data=f"List of prompts: {mcp_prompts}") + await _log(ctx, level="debug", data=f"List of prompts: {mcp_prompts}") return types.ListPromptsResult(prompts=mcp_prompts) async def _get_prompt(ctx: ServerRequestContext, params: types.GetPromptRequestParams) -> types.GetPromptResult: """Get a prompt by name.""" - current_ctx["ctx"] = ctx name, arguments = params.name, params.arguments prompt = next((p for p in (prompts or []) if p.prompt_template_config.name == name), None) if prompt is None: @@ -1204,10 +1199,9 @@ async def _get_prompt(ctx: ServerRequestContext, params: types.GetPromptRequestP async def _set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> types.EmptyResult: """Set the logging level for the server.""" - current_ctx["ctx"] = ctx logger.setLevel(LOG_LEVEL_MAPPING[params.level]) # emit this log with the new minimum level - await _log(level=params.level, data=f"Log level set to {params.level}") + await _log(ctx, level=params.level, data=f"Log level set to {params.level}") return types.EmptyResult() server_args: dict[str, Any] = { @@ -1229,11 +1223,14 @@ async def _set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRe server = Server(**server_args) # type: ignore[call-arg] - async def _log(level: types.LoggingLevel, data: Any) -> None: - """Log a message to the server and logger.""" + async def _log(ctx: ServerRequestContext | None, level: types.LoggingLevel, data: Any) -> None: + """Log a message to the server and logger. + + The per-request context is passed in explicitly so concurrent requests never + share or overwrite a stored context (each handler logs through its own session). + """ # Log to the local logger logger.log(LOG_LEVEL_MAPPING[level], data) - ctx = current_ctx.get("ctx") if ctx and ctx.session: try: await ctx.session.send_log_message(level=level, data=data)