From 3a53ade46769821ce53b8a7c936c45d2eb5897d4 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Wed, 19 Feb 2025 11:43:29 -0800 Subject: [PATCH] splits mcp code to assistant/mcp extensions and runs open deep research async with early pass at messaging (#329) WIP check-in, using log messages to send status updates while tool is running - will follow up with desired use of custom notifications --- .devcontainer/devcontainer.json | 1 - .../codespace-assistant/.vscode/settings.json | 16 +- .../codespace-assistant/assistant/chat.py | 4 +- .../codespace-assistant/assistant/config.py | 6 +- .../assistant/extensions/tools/__init__.py | 12 - .../extensions/tools/__mcp_tool_utils.py | 149 ------ .../assistant/response/completion_handler.py | 53 +- .../assistant/response/request_builder.py | 7 +- .../assistant/response/response.py | 9 +- .../assistant/response/step_handler.py | 61 ++- .../assistant/response/utils/__init__.py | 15 +- .../assistant/response/utils/message_utils.py | 7 + .../assistant/response/utils/openai_utils.py | 97 ++-- assistants/codespace-assistant/pyproject.toml | 11 +- assistants/codespace-assistant/uv.lock | 213 +++----- assistants/explorer-assistant/uv.lock | 10 +- .../guided-conversation-assistant/uv.lock | 10 +- assistants/prospector-assistant/uv.lock | 10 +- assistants/skill-assistant/uv.lock | 4 +- .../python/python-02-simple-chatbot/uv.lock | 10 +- .../python-03-multimodel-chatbot/uv.lock | 10 +- libraries/python/anthropic-client/uv.lock | 10 +- .../.vscode/settings.json | 9 +- .../assistant_extensions/mcp/__init__.py | 19 + .../assistant_extensions/mcp/_model.py | 37 +- .../assistant_extensions/mcp/_server_utils.py | 77 ++- .../assistant_extensions/mcp/_tool_utils.py | 206 ++++++++ .../assistant-extensions/pyproject.toml | 20 +- libraries/python/assistant-extensions/uv.lock | 185 +++++-- .../mcp-extensions/.vscode/settings.json | 55 ++ libraries/python/mcp-extensions/Makefile | 2 + libraries/python/mcp-extensions/README.md | 114 +++++ .../mcp-extensions/mcp_extensions/__init__.py | 14 + .../mcp-extensions/mcp_extensions/_model.py | 22 + .../mcp_extensions/_tool_utils.py | 131 +++++ .../python/mcp-extensions/pyproject.toml | 27 + .../mcp-extensions/tests/test_tool_utils.py | 83 +++ libraries/python/mcp-extensions/uv.lock | 481 ++++++++++++++++++ libraries/python/openai-client/uv.lock | 10 +- libraries/python/skills/skill-library/uv.lock | 4 +- .../mcp-server-giphy/mcp_server/server.py | 13 +- .../mcp-server-giphy/mcp_server/start.py | 11 +- .../mcp_server/open_deep_research.py | 39 +- .../mcp_server/server.py | 16 +- .../mcp_server/start.py | 11 +- .../pyproject.toml | 4 + .../mcp-server-open-deep-research/uv.lock | 44 +- .../{{ project_slug }}/mcp_server/start.py | 4 +- .../mcp-server-vscode/.vscode/settings.json | 18 - semantic-workbench.code-workspace | 88 +++- .../Conversations/InteractHistory.tsx | 8 +- .../Conversations/Message/InteractMessage.tsx | 8 +- 52 files changed, 1836 insertions(+), 649 deletions(-) delete mode 100644 assistants/codespace-assistant/assistant/extensions/tools/__init__.py delete mode 100644 assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py create mode 100644 libraries/python/assistant-extensions/assistant_extensions/mcp/__init__.py rename assistants/codespace-assistant/assistant/extensions/tools/__model.py => libraries/python/assistant-extensions/assistant_extensions/mcp/_model.py (92%) rename assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py => libraries/python/assistant-extensions/assistant_extensions/mcp/_server_utils.py (58%) create mode 100644 libraries/python/assistant-extensions/assistant_extensions/mcp/_tool_utils.py create mode 100644 libraries/python/mcp-extensions/.vscode/settings.json create mode 100644 libraries/python/mcp-extensions/Makefile create mode 100644 libraries/python/mcp-extensions/README.md create mode 100644 libraries/python/mcp-extensions/mcp_extensions/__init__.py create mode 100644 libraries/python/mcp-extensions/mcp_extensions/_model.py create mode 100644 libraries/python/mcp-extensions/mcp_extensions/_tool_utils.py create mode 100644 libraries/python/mcp-extensions/pyproject.toml create mode 100644 libraries/python/mcp-extensions/tests/test_tool_utils.py create mode 100644 libraries/python/mcp-extensions/uv.lock diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9c33cde0..2a16c50b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -65,7 +65,6 @@ "ms-python.debugpy", "ms-vscode.extension-test-runner", "ms-python.python", - "ms-toolsai.datawrangler", "ms-vscode.makefile-tools", "ms-vscode.vscode-node-azure-pack", "tamasfe.even-better-toml", diff --git a/assistants/codespace-assistant/.vscode/settings.json b/assistants/codespace-assistant/.vscode/settings.json index 6fdd0c2b..c990f36c 100644 --- a/assistants/codespace-assistant/.vscode/settings.json +++ b/assistants/codespace-assistant/.vscode/settings.json @@ -9,6 +9,14 @@ "editor.formatOnType": true, "editor.formatOnSave": true, "files.eol": "\n", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, "files.trimTrailingWhitespace": true, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode", @@ -25,6 +33,9 @@ "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.typeCheckingMode": "standard", "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.testing.pytestEnabled": true, + "python.testing.cwd": "${workspaceFolder}", + "python.testing.pytestArgs": [], "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, @@ -38,8 +49,10 @@ "ruff.nativeServer": "on", "search.exclude": { "**/.venv": true, - "**/.data": true + "**/.data": true, + "**/__pycache__": true }, + // For use with optional extension: "streetsidesoftware.code-spell-checker" "cSpell.ignorePaths": [ ".venv", @@ -69,6 +82,7 @@ "pydantic", "pyproject", "pyright", + "pytest", "semanticworkbench", "tiktoken", "updown", diff --git a/assistants/codespace-assistant/assistant/chat.py b/assistants/codespace-assistant/assistant/chat.py index f0ae5b6c..77454fb8 100644 --- a/assistants/codespace-assistant/assistant/chat.py +++ b/assistants/codespace-assistant/assistant/chat.py @@ -10,6 +10,7 @@ import deepmerge from assistant_extensions.attachments import AttachmentsExtension +from assistant_extensions.mcp import MCPToolsConfigModel from content_safety.evaluators import CombinedContentSafetyEvaluator from semantic_workbench_api_model.workbench_model import ( ConversationEvent, @@ -27,7 +28,6 @@ ) from .config import AssistantConfigModel -from .extensions.tools import ToolsConfigModel from .response import respond_to_conversation logger = logging.getLogger(__name__) @@ -67,7 +67,7 @@ async def content_evaluator_factory(context: ConversationContext) -> ContentSafe ) -async def tools_config_provider(context: AssistantContext) -> ToolsConfigModel: +async def tools_config_provider(context: AssistantContext) -> MCPToolsConfigModel: return (await assistant_config.get(context)).extensions_config.tools diff --git a/assistants/codespace-assistant/assistant/config.py b/assistants/codespace-assistant/assistant/config.py index 1fb1b3bb..71bcd42f 100644 --- a/assistants/codespace-assistant/assistant/config.py +++ b/assistants/codespace-assistant/assistant/config.py @@ -6,13 +6,13 @@ OpenAIClientConfigModel, ) from assistant_extensions.attachments import AttachmentsConfigModel +from assistant_extensions.mcp import MCPToolsConfigModel from content_safety.evaluators import CombinedContentSafetyEvaluatorConfig from openai_client import AzureOpenAIServiceConfig, OpenAIRequestConfig from pydantic import BaseModel, Field from semantic_workbench_assistant.config import UISchema from . import helpers -from .extensions.tools import ToolsConfigModel # The semantic workbench app uses react-jsonschema-form for rendering # dynamic configuration forms based on the configuration model and UI schema @@ -31,12 +31,12 @@ class ExtensionsConfigModel(BaseModel): tools: Annotated[ - ToolsConfigModel, + MCPToolsConfigModel, Field( title="Tools Configuration", description="Configuration for the tools.", ), - ] = ToolsConfigModel() + ] = MCPToolsConfigModel() attachments: Annotated[ AttachmentsConfigModel, diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__init__.py b/assistants/codespace-assistant/assistant/extensions/tools/__init__.py deleted file mode 100644 index 068944d2..00000000 --- a/assistants/codespace-assistant/assistant/extensions/tools/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .__mcp_server_utils import establish_mcp_sessions, get_mcp_server_prompts -from .__mcp_tool_utils import handle_tool_call, retrieve_tools_from_sessions -from .__model import ToolCall, ToolsConfigModel - -__all__ = [ - "ToolsConfigModel", - "ToolCall", - "establish_mcp_sessions", - "get_mcp_server_prompts", - "retrieve_tools_from_sessions", - "handle_tool_call", -] diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py deleted file mode 100644 index 0b0454af..00000000 --- a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py +++ /dev/null @@ -1,149 +0,0 @@ -# utils/tool_utils.py -import logging -from textwrap import dedent -from typing import AsyncGenerator, List - -import deepmerge -from mcp import Tool -from mcp.types import EmbeddedResource, ImageContent, TextContent - -from .__model import MCPSession, ToolCall, ToolCallResult, ToolMessageType, ToolsConfigModel - -logger = logging.getLogger(__name__) - - -def retrieve_tools_from_sessions(mcp_sessions: List[MCPSession], tools_config: ToolsConfigModel) -> List[Tool]: - """ - Retrieve tools from all MCP sessions, excluding any tools that are disabled in the tools config. - """ - return [ - tool - for mcp_session in mcp_sessions - for tool in mcp_session.tools - if tool.name not in tools_config.tools_disabled - ] - - -def get_mcp_session_and_tool_by_tool_name( - mcp_sessions: List[MCPSession], - tool_name: str, -) -> tuple[MCPSession | None, Tool | None]: - """ - Retrieve the MCP session and tool by tool name. - """ - return next( - ((mcp_session, tool) for mcp_session in mcp_sessions for tool in mcp_session.tools if tool.name == tool_name), - (None, None), - ) - - -async def execute_tool_call( - mcp_session: MCPSession, - tool_call: ToolCall, - method_metadata_key: str, -) -> ToolCallResult: - - # Initialize metadata - metadata = {} - - # Initialize tool_result - tool_result = None - tool_output: list[TextContent | ImageContent | EmbeddedResource] = [] - content_items: List[str] = [] - - # Invoke the tool - try: - logger.debug(f"Invoking '{mcp_session.config.key}.{tool_call.name}' with arguments: {tool_call.arguments}") - tool_result = await mcp_session.client_session.call_tool(tool_call.name, tool_call.arguments) - tool_output = tool_result.content - except Exception as e: - logger.exception(f"Error executing tool '{tool_call.name}': {e}") - content_items.append(f"An error occurred while executing the tool '{tool_call.to_json()}': {e}") - - # Update metadata with tool result - deepmerge.always_merger.merge( - metadata, - { - "debug": { - method_metadata_key: { - "tool_result": tool_output, - }, - }, - }, - ) - - for tool_output_item in tool_output: - if isinstance(tool_output_item, TextContent): - content_items.append(tool_output_item.text) - if isinstance(tool_output_item, ImageContent): - content_items.append(tool_output_item.model_dump_json()) - if isinstance(tool_output_item, EmbeddedResource): - content_items.append(tool_output_item.model_dump_json()) - - # Return the tool call result - return ToolCallResult( - id=tool_call.id, - content="\n\n".join(content_items), - message_type=ToolMessageType.tool_result, - metadata=metadata, - ) - -async def handle_tool_call( - mcp_sessions: List[MCPSession], - tool_call: ToolCall, - method_metadata_key: str, -) -> ToolCallResult: - """ - Handle the tool call by invoking the appropriate tool and returning a ToolCallResult. - """ - - # Find the tool and session from the full collection of sessions - mcp_session, tool = get_mcp_session_and_tool_by_tool_name(mcp_sessions, tool_call.name) - - if not mcp_session or not tool: - return ToolCallResult( - id=tool_call.id, - content=f"Tool '{tool_call.name}' not found in any of the sessions.", - message_type=ToolMessageType.notice, - metadata={}, - ) - - return await execute_tool_call(mcp_session, tool_call, method_metadata_key) - - -async def handle_long_running_tool_call( - mcp_sessions: List[MCPSession], - tool_call: ToolCall, - method_metadata_key: str, -) -> AsyncGenerator[ToolCallResult, None]: - """ - Handle the streaming tool call by invoking the appropriate tool and returning a ToolCallResult. - """ - - # Find the tool and session from the full collection of sessions - mcp_session, tool = get_mcp_session_and_tool_by_tool_name(mcp_sessions, tool_call.name) - - if not mcp_session or not tool: - yield ToolCallResult( - id=tool_call.id, - content=f"Tool '{tool_call.name}' not found in any of the sessions.", - message_type=ToolMessageType.notice, - metadata={}, - ) - return - - # For now, let's just hack to return an immediate response to indicate that the tool call was received - # and is being processed and that the results will be sent in a separate message. - yield ToolCallResult( - id=tool_call.id, - content=dedent(f""" - Processing tool call '{tool_call.name}'. - Estimated time to completion: {mcp_session.config.task_completion_estimate} - """).strip(), - message_type=ToolMessageType.tool_result, - metadata={}, - ) - - # Perform the tool call - tool_call_result = await execute_tool_call(mcp_session, tool_call, method_metadata_key) - yield tool_call_result diff --git a/assistants/codespace-assistant/assistant/response/completion_handler.py b/assistants/codespace-assistant/assistant/response/completion_handler.py index 8e0fe576..ab1bc283 100644 --- a/assistants/codespace-assistant/assistant/response/completion_handler.py +++ b/assistants/codespace-assistant/assistant/response/completion_handler.py @@ -5,6 +5,7 @@ from typing import List import deepmerge +from assistant_extensions.mcp import ExtendedCallToolRequestParams, MCPSession, handle_mcp_tool_call from openai.types.chat import ( ChatCompletion, ChatCompletionToolMessageParam, @@ -14,18 +15,13 @@ from semantic_workbench_api_model.workbench_model import ( MessageType, NewConversationMessage, + UpdateParticipant, ) from semantic_workbench_assistant.assistant_app import ConversationContext -from assistant.extensions.tools.__model import MCPSession - -from ..extensions.tools import ( - ToolCall, - handle_tool_call, -) from .models import StepResult from .utils import ( - extract_content_from_tool_calls, + extract_content_from_mcp_tool_calls, get_response_duration_message, get_token_usage_message, ) @@ -55,7 +51,7 @@ async def handle_error(error_message: str) -> StepResult: step_result.status = "error" return step_result - # Get service and request configuration for generative model + # get service and request configuration for generative model generative_request_config = request_config # get the total tokens used for the completion @@ -67,10 +63,10 @@ async def handle_error(error_message: str) -> StepResult: content = completion.choices[0].message.content # check if the completion has tool calls - tool_calls: list[ToolCall] = [] + tool_calls: list[ExtendedCallToolRequestParams] = [] if completion.choices[0].message.tool_calls: - ai_context, tool_calls = extract_content_from_tool_calls([ - ToolCall( + ai_context, tool_calls = extract_content_from_mcp_tool_calls([ + ExtendedCallToolRequestParams( id=tool_call.id, name=tool_call.function.name, arguments=json.loads( @@ -104,7 +100,7 @@ async def handle_error(error_message: str) -> StepResult: deepmerge.always_merger.merge( step_result.metadata, { - "tool_calls": [tool_call.to_dict() for tool_call in tool_calls], + "tool_calls": [tool_call.model_dump(mode="json") for tool_call in tool_calls], }, ) @@ -170,21 +166,32 @@ async def handle_error(error_message: str) -> StepResult: tool_call_count = 0 for tool_call in tool_calls: tool_call_count += 1 - try: - tool_call_result = await handle_tool_call( - mcp_sessions, - tool_call, - f"{metadata_key}:request:tool_call_{tool_call_count}", - ) - except Exception as e: - logger.exception(f"Error handling tool call: {e}") - return await handle_error("An error occurred while handling the tool call.") + + tool_call_status = f"using tool `{tool_call.name}`" + async with context.set_status(f"{tool_call_status}..."): + + async def on_logging_message(msg: str) -> None: + await context.update_participant_me(UpdateParticipant(status=f"{tool_call_status}: {msg}")) + + try: + tool_call_result = await handle_mcp_tool_call( + mcp_sessions, + tool_call, + f"{metadata_key}:request:tool_call_{tool_call_count}", + on_logging_message, + ) + except Exception as e: + logger.exception(f"Error handling tool call: {e}") + return await handle_error("An error occurred while handling the tool call.") # Update content and metadata with tool call result metadata deepmerge.always_merger.merge(step_result.metadata, tool_call_result.metadata) - content = ( - tool_call_result.content if len(tool_call_result.content) > 0 else "[tool call returned no content]" + # FIXME only supporting 1 content item and it's text for now, should support other content types/quantity + # Get the content from the tool call result + content = next( + (content_item.text for content_item in tool_call_result.content if content_item.type == "text"), + "[tool call returned no content]", ) # Add the token count for the tool call result to the total token count diff --git a/assistants/codespace-assistant/assistant/response/request_builder.py b/assistants/codespace-assistant/assistant/response/request_builder.py index 3dcb66e8..5830ca76 100644 --- a/assistants/codespace-assistant/assistant/response/request_builder.py +++ b/assistants/codespace-assistant/assistant/response/request_builder.py @@ -3,6 +3,7 @@ from typing import List from assistant_extensions.attachments import AttachmentsConfigModel, AttachmentsExtension +from assistant_extensions.mcp import MCPToolsConfigModel from openai.types.chat import ( ChatCompletionDeveloperMessageParam, ChatCompletionMessageParam, @@ -18,9 +19,7 @@ ) from semantic_workbench_assistant.assistant_app import ConversationContext -from assistant.config import PromptsConfigModel -from assistant.extensions.tools.__model import ToolsConfigModel - +from ..config import PromptsConfigModel from .utils import ( build_system_message_content, get_history_messages, @@ -43,7 +42,7 @@ async def build_request( prompts_config: PromptsConfigModel, request_config: OpenAIRequestConfig, tools: List[ChatCompletionToolParam] | None, - tools_config: ToolsConfigModel, + tools_config: MCPToolsConfigModel, attachments_config: AttachmentsConfigModel, silence_token: str, ) -> BuildRequestResult: diff --git a/assistants/codespace-assistant/assistant/response/response.py b/assistants/codespace-assistant/assistant/response/response.py index 738ce7ac..4123591f 100644 --- a/assistants/codespace-assistant/assistant/response/response.py +++ b/assistants/codespace-assistant/assistant/response/response.py @@ -3,6 +3,7 @@ from typing import Any, List from assistant_extensions.attachments import AttachmentsExtension +from assistant_extensions.mcp import MCPSession, establish_mcp_sessions, get_mcp_server_prompts from semantic_workbench_api_model.workbench_model import ( ConversationMessage, MessageType, @@ -10,15 +11,9 @@ ) from semantic_workbench_assistant.assistant_app import ConversationContext -from assistant.extensions.tools.__model import MCPSession -from assistant.response.utils.openai_utils import get_ai_client_configs - from ..config import AssistantConfigModel -from ..extensions.tools import ( - establish_mcp_sessions, - get_mcp_server_prompts, -) from .step_handler import next_step +from .utils import get_ai_client_configs logger = logging.getLogger(__name__) diff --git a/assistants/codespace-assistant/assistant/response/step_handler.py b/assistants/codespace-assistant/assistant/response/step_handler.py index 36e7c325..a8efbecf 100644 --- a/assistants/codespace-assistant/assistant/response/step_handler.py +++ b/assistants/codespace-assistant/assistant/response/step_handler.py @@ -5,6 +5,7 @@ import deepmerge from assistant_extensions.attachments import AttachmentsConfigModel, AttachmentsExtension +from assistant_extensions.mcp import MCPSession, MCPToolsConfigModel from openai.types.chat import ( ChatCompletion, ParsedChatCompletion, @@ -16,17 +17,14 @@ ) from semantic_workbench_assistant.assistant_app import ConversationContext -from assistant.config import PromptsConfigModel -from assistant.extensions.tools.__mcp_tool_utils import retrieve_tools_from_sessions -from assistant.extensions.tools.__model import MCPSession, ToolsConfigModel -from assistant.response.utils.formatting_utils import get_formatted_token_count - +from ..config import PromptsConfigModel from .completion_handler import handle_completion from .models import StepResult from .request_builder import build_request from .utils import ( - convert_mcp_tools_to_openai_tools, get_completion, + get_formatted_token_count, + get_openai_tools_from_mcp_sessions, ) logger = logging.getLogger(__name__) @@ -40,7 +38,7 @@ async def next_step( request_config: OpenAIRequestConfig, service_config: AzureOpenAIServiceConfig | OpenAIServiceConfig, prompts_config: PromptsConfigModel, - tools_config: ToolsConfigModel, + tools_config: MCPToolsConfigModel, attachments_config: AttachmentsConfigModel, metadata: dict[str, Any], metadata_key: str, @@ -77,8 +75,7 @@ async def handle_error(error_message: str, error_debug: dict[str, Any] | None = silence_token = "{{SILENCE}}" # convert the tools to make them compatible with the OpenAI API - mcp_tools = retrieve_tools_from_sessions(mcp_sessions, tools_config) - tools = convert_mcp_tools_to_openai_tools(mcp_tools) + tools = get_openai_tools_from_mcp_sessions(mcp_sessions, tools_config) build_request_result = await build_request( mcp_prompts=mcp_prompts, @@ -117,31 +114,33 @@ async def handle_error(error_message: str, error_debug: dict[str, Any] | None = # generate a response from the AI model async with create_client(service_config) as client: - try: - completion = await get_completion(client, request_config, chat_message_params, tools) - - except Exception as e: - logger.exception(f"exception occurred calling openai chat completion: {e}") - deepmerge.always_merger.merge( - step_result.metadata, - { - "debug": { - metadata_key: { - "error": str(e), + completion_status = "reasoning..." if request_config.is_reasoning_model else "thinking..." + async with context.set_status(completion_status): + try: + completion = await get_completion(client, request_config, chat_message_params, tools) + + except Exception as e: + logger.exception(f"exception occurred calling openai chat completion: {e}") + deepmerge.always_merger.merge( + step_result.metadata, + { + "debug": { + metadata_key: { + "error": str(e), + }, }, }, - }, - ) - await context.send_messages( - NewConversationMessage( - content="An error occurred while calling the OpenAI API. Is it configured correctly?" - " View the debug inspector for more information.", - message_type=MessageType.notice, - metadata=step_result.metadata, ) - ) - step_result.status = "error" - return step_result + await context.send_messages( + NewConversationMessage( + content="An error occurred while calling the OpenAI API. Is it configured correctly?" + " View the debug inspector for more information.", + message_type=MessageType.notice, + metadata=step_result.metadata, + ) + ) + step_result.status = "error" + return step_result if completion is None: return await handle_error("No response from OpenAI.") diff --git a/assistants/codespace-assistant/assistant/response/utils/__init__.py b/assistants/codespace-assistant/assistant/response/utils/__init__.py index 84ddfbd4..47393e8f 100644 --- a/assistants/codespace-assistant/assistant/response/utils/__init__.py +++ b/assistants/codespace-assistant/assistant/response/utils/__init__.py @@ -1,18 +1,25 @@ -from .formatting_utils import get_response_duration_message, get_token_usage_message +from .formatting_utils import get_formatted_token_count, get_response_duration_message, get_token_usage_message from .message_utils import ( build_system_message_content, conversation_message_to_chat_message_params, get_history_messages, ) -from .openai_utils import convert_mcp_tools_to_openai_tools, extract_content_from_tool_calls, get_completion +from .openai_utils import ( + extract_content_from_mcp_tool_calls, + get_ai_client_configs, + get_completion, + get_openai_tools_from_mcp_sessions, +) __all__ = [ "build_system_message_content", - "convert_mcp_tools_to_openai_tools", "conversation_message_to_chat_message_params", - "extract_content_from_tool_calls", + "extract_content_from_mcp_tool_calls", + "get_ai_client_configs", "get_completion", + "get_formatted_token_count", "get_history_messages", + "get_openai_tools_from_mcp_sessions", "get_response_duration_message", "get_token_usage_message", ] diff --git a/assistants/codespace-assistant/assistant/response/utils/message_utils.py b/assistants/codespace-assistant/assistant/response/utils/message_utils.py index ea02638a..bfd55a7e 100644 --- a/assistants/codespace-assistant/assistant/response/utils/message_utils.py +++ b/assistants/codespace-assistant/assistant/response/utils/message_utils.py @@ -105,6 +105,13 @@ def tool_calls_from_metadata(metadata: dict[str, Any]) -> list[ChatCompletionMes tool_call_params: list[ChatCompletionMessageToolCallParam] = [] for tool_call in tool_calls: + if not isinstance(tool_call, dict): + try: + tool_call = json.loads(tool_call) + except json.JSONDecodeError: + logger.warning(f"Failed to parse tool call from metadata: {tool_call}") + continue + id = tool_call["id"] name = tool_call["name"] arguments = json.dumps(tool_call["arguments"]) diff --git a/assistants/codespace-assistant/assistant/response/utils/openai_utils.py b/assistants/codespace-assistant/assistant/response/utils/openai_utils.py index f1ac1202..d99ba500 100644 --- a/assistants/codespace-assistant/assistant/response/utils/openai_utils.py +++ b/assistants/codespace-assistant/assistant/response/utils/openai_utils.py @@ -4,8 +4,13 @@ from textwrap import dedent from typing import List, Literal, Tuple -import deepmerge -from mcp import Tool +from assistant_extensions.mcp import ( + ExtendedCallToolRequestParams, + MCPSession, + MCPToolsConfigModel, + retrieve_mcp_tools_from_sessions, +) +from mcp_extensions import convert_tools_to_openai_tools from openai import AsyncOpenAI, NotGiven from openai.types.chat import ( ChatCompletion, @@ -13,13 +18,10 @@ ChatCompletionToolParam, ParsedChatCompletion, ) -from openai.types.shared_params import FunctionDefinition, FunctionParameters from openai_client import AzureOpenAIServiceConfig, OpenAIRequestConfig, OpenAIServiceConfig from pydantic import BaseModel -from assistant.config import AssistantConfigModel - -from ...extensions.tools import ToolCall +from ...config import AssistantConfigModel logger = logging.getLogger(__name__) @@ -77,20 +79,20 @@ async def get_completion( return completion -def extract_content_from_tool_calls( - tool_calls: List[ToolCall], -) -> Tuple[str | None, List[ToolCall]]: +def extract_content_from_mcp_tool_calls( + tool_calls: List[ExtendedCallToolRequestParams], +) -> Tuple[str | None, List[ExtendedCallToolRequestParams]]: """ Extracts the AI content from the tool calls. - This function takes a list of ToolCall objects and extracts the AI content from them. It returns a tuple - containing the AI content and the updated list of ToolCall objects. + This function takes a list of MCPToolCall objects and extracts the AI content from them. It returns a tuple + containing the AI content and the updated list of MCPToolCall objects. Args: - tool_calls(List[ToolCall]): The list of ToolCall objects. + tool_calls(List[MCPToolCall]): The list of MCPToolCall objects. Returns: - Tuple[str | None, List[ToolCall]]: A tuple containing the AI content and the updated list of ToolCall + Tuple[str | None, List[MCPToolCall]]: A tuple containing the AI content and the updated list of MCPToolCall objects. """ ai_content: list[str] = [] @@ -98,7 +100,7 @@ def extract_content_from_tool_calls( for tool_call in tool_calls: # Split the AI content from the tool call - content, updated_tool_call = split_ai_content_from_tool_call(tool_call) + content, updated_tool_call = split_ai_content_from_mcp_tool_call(tool_call) if content is not None: ai_content.append(content) @@ -108,13 +110,16 @@ def extract_content_from_tool_calls( return "\n\n".join(ai_content).strip(), updated_tool_calls -def split_ai_content_from_tool_call( - tool_call: ToolCall, -) -> Tuple[str | None, ToolCall]: +def split_ai_content_from_mcp_tool_call( + tool_call: ExtendedCallToolRequestParams, +) -> Tuple[str | None, ExtendedCallToolRequestParams]: """ Splits the AI content from the tool call. """ + if not tool_call.arguments: + return None, tool_call + # Check if the tool call has an "aiContext" argument if "aiContext" in tool_call.arguments: # Extract the AI content @@ -126,44 +131,24 @@ def split_ai_content_from_tool_call( return None, tool_call -def convert_mcp_tools_to_openai_tools(mcp_tools: List[Tool] | None) -> List[ChatCompletionToolParam] | None: - if not mcp_tools: - return None - - openai_tools: List[ChatCompletionToolParam] = [] - for mcp_tool in mcp_tools: - # add parameter for explaining the step for the user observing the assistant - parameters: FunctionParameters = deepmerge.always_merger.merge( - mcp_tool.inputSchema.copy(), - { - "properties": { - # Add the "aiContext" parameter to the input schema - "aiContext": { - "type": "string", - "description": dedent(""" - Explanation of why the AI is using this tool and what it expects to accomplish. - This message is displayed to the user, coming from the point of view of the - assistant and should fit within the flow of the ongoing conversation, responding - to the preceding user message. - """).strip(), - }, - }, - # Ensure that the "aiContext" parameter is required - "required": ["aiContext"], - }, - ) - - function = FunctionDefinition( - name=mcp_tool.name, - description=mcp_tool.description if mcp_tool.description else "[no description provided]", - parameters=parameters, - ) - - openai_tools.append( - ChatCompletionToolParam( - function=function, - type="function", - ) - ) +def get_openai_tools_from_mcp_sessions( + mcp_sessions: List[MCPSession], tools_config: MCPToolsConfigModel +) -> List[ChatCompletionToolParam] | None: + """ + Retrieve the tools from the MCP sessions. + """ + mcp_tools = retrieve_mcp_tools_from_sessions(mcp_sessions, tools_config) + extra_parameters = { + "aiContext": { + "type": "string", + "description": dedent(""" + Explanation of why the AI is using this tool and what it expects to accomplish. + This message is displayed to the user, coming from the point of view of the + assistant and should fit within the flow of the ongoing conversation, responding + to the preceding user message. + """).strip(), + }, + } + openai_tools = convert_tools_to_openai_tools(mcp_tools, extra_parameters) return openai_tools diff --git a/assistants/codespace-assistant/pyproject.toml b/assistants/codespace-assistant/pyproject.toml index 4768ca61..6e0f8253 100644 --- a/assistants/codespace-assistant/pyproject.toml +++ b/assistants/codespace-assistant/pyproject.toml @@ -6,15 +6,11 @@ authors = [{ name = "Semantic Workbench Team" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "anthropic>=0.40.0", - "anthropic-client>=0.1.0", "assistant-drive>=0.1.0", - "assistant-extensions[attachments]>=0.1.0", + "assistant-extensions[attachments, mcp]>=0.1.0", + "mcp-extensions[openai]>=0.1.0", "content-safety>=0.1.0", "deepmerge>=2.0", - "html2docx>=1.6.0", - "markdown>=3.6", - "mcp>=1.2.0", "openai>=1.61.0", "openai-client>=0.1.0", "tiktoken>=0.8.0", @@ -30,6 +26,7 @@ package = true anthropic-client = { path = "../../libraries/python/anthropic-client", editable = true } assistant-drive = { path = "../../libraries/python/assistant-drive", editable = true } assistant-extensions = { path = "../../libraries/python/assistant-extensions", editable = true } +mcp-extensions = { path = "../../libraries/python/mcp-extensions", editable = true } content-safety = { path = "../../libraries/python/content-safety/", editable = true } openai-client = { path = "../../libraries/python/openai-client", editable = true } @@ -38,4 +35,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [dependency-groups] -dev = ["pyright>=1.1.389"] +dev = ["pyright>=1.1.389", "pytest>=7.4.3"] diff --git a/assistants/codespace-assistant/uv.lock b/assistants/codespace-assistant/uv.lock index 9af21d89..36dbf1f6 100644 --- a/assistants/codespace-assistant/uv.lock +++ b/assistants/codespace-assistant/uv.lock @@ -207,18 +207,22 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] +mcp = [ + { name = "mcp" }, + { name = "mcp-extensions", extra = ["openai"] }, +] [package.metadata] requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -406,7 +410,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -418,15 +422,11 @@ name = "codespace-assistant" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "anthropic" }, - { name = "anthropic-client" }, { name = "assistant-drive" }, - { name = "assistant-extensions", extra = ["attachments"] }, + { name = "assistant-extensions", extra = ["attachments", "mcp"] }, { name = "content-safety" }, { name = "deepmerge" }, - { name = "html2docx" }, - { name = "markdown" }, - { name = "mcp" }, + { name = "mcp-extensions", extra = ["openai"] }, { name = "openai" }, { name = "openai-client" }, { name = "tiktoken" }, @@ -435,26 +435,26 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pyright" }, + { name = "pytest" }, ] [package.metadata] requires-dist = [ - { name = "anthropic", specifier = ">=0.40.0" }, - { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-extensions", extras = ["attachments"], editable = "../../libraries/python/assistant-extensions" }, + { name = "assistant-extensions", extras = ["attachments", "mcp"], editable = "../../libraries/python/assistant-extensions" }, { name = "content-safety", editable = "../../libraries/python/content-safety" }, { name = "deepmerge", specifier = ">=2.0" }, - { name = "html2docx", specifier = ">=1.6.0" }, - { name = "markdown", specifier = ">=3.6" }, - { name = "mcp", specifier = ">=1.2.0" }, + { name = "mcp-extensions", extras = ["openai"], editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "tiktoken", specifier = ">=0.8.0" }, ] [package.metadata.requires-dev] -dev = [{ name = "pyright", specifier = ">=1.1.389" }] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=7.4.3" }, +] [[package]] name = "colorama" @@ -686,19 +686,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] -[[package]] -name = "html2docx" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-docx" }, - { name = "tinycss2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/6c/a1122f2fe5a772b0c63e0a4966c7ae093f81ff23aa8ee04bba6b69c54e40/html2docx-1.6.0.tar.gz", hash = "sha256:13ef5653d7a3ffe625251705f15ae203938ce2f98be93e62f6fdf80630c33d2d", size = 47045 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/f2/291d81ff6699c3874cd4143cd2815bd82f8d4129c6ef7bfdfcd410db6132/html2docx-1.6.0-py3-none-any.whl", hash = "sha256:ec7ceacb53eaf4ad5217d96f61dbd5cd62bc6f28d3830e4721e5428300255bdf", size = 35686 }, -] - [[package]] name = "httpcore" version = "1.0.7" @@ -783,6 +770,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + [[package]] name = "isodate" version = "0.7.2" @@ -851,74 +847,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 }, ] -[[package]] -name = "lxml" -version = "5.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, - { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, - { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, - { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, - { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, - { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, - { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, - { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, - { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, - { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, - { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, - { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, - { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, - { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, - { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, - { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, - { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, - { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, - { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, - { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, - { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 }, - { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 }, - { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 }, - { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 }, - { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 }, - { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 }, - { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 }, - { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 }, - { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 }, - { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 }, - { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 }, - { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, - { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, - { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, - { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, - { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, - { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, - { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, - { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, - { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, - { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, - { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, - { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, - { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, - { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, - { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, - { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, - { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, - { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, - { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, - { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, -] - -[[package]] -name = "markdown" -version = "3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -998,6 +926,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, ] +[[package]] +name = "mcp-extensions" +version = "0.1.0" +source = { editable = "../../libraries/python/mcp-extensions" } +dependencies = [ + { name = "deepmerge" }, + { name = "mcp" }, +] + +[package.optional-dependencies] +openai = [ + { name = "openai" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepmerge", specifier = ">=2.0" }, + { name = "mcp", specifier = ">=1.2.1" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.63.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=8.3.1" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1099,7 +1054,7 @@ wheels = [ [[package]] name = "openai" -version = "1.61.0" +version = "1.63.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1111,9 +1066,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/2a/b3fa8790be17d632f59d4f50257b909a3f669036e5195c1ae55737274620/openai-1.61.0.tar.gz", hash = "sha256:216f325a24ed8578e929b0f1b3fb2052165f3b04b0461818adaa51aa29c71f8a", size = 350174 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/76/70c5ad6612b3e4c89fa520266bbf2430a89cae8bd87c1e2284698af5927e/openai-1.61.0-py3-none-any.whl", hash = "sha256:e8c512c0743accbdbe77f3429a1490d862f8352045de8dc81969301eb4a4f666", size = 460623 }, + { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, ] [[package]] @@ -1238,12 +1193,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1453,28 +1417,30 @@ wheels = [ ] [[package]] -name = "python-dateutil" -version = "2.9.0.post0" +name = "pytest" +version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] [[package]] -name = "python-docx" -version = "1.1.2" +name = "python-dateutil" +version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lxml" }, - { name = "typing-extensions" }, + { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/e4/386c514c53684772885009c12b67a7edd526c15157778ac1b138bc75063e/python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd", size = 5656581 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/3d/330d9efbdb816d3f60bf2ad92f05e1708e4a1b9abe80461ac3444c83f749/python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe", size = 244315 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] [[package]] @@ -1812,24 +1778,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, ] -[[package]] -name = "tinycss2" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, -] - [[package]] name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ @@ -1968,15 +1922,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, ] -[[package]] -name = "webencodings" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, -] - [[package]] name = "websockets" version = "14.2" diff --git a/assistants/explorer-assistant/uv.lock b/assistants/explorer-assistant/uv.lock index 98aab84f..5b14852f 100644 --- a/assistants/explorer-assistant/uv.lock +++ b/assistants/explorer-assistant/uv.lock @@ -207,7 +207,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -217,8 +216,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -406,7 +406,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -1211,7 +1211,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1784,7 +1784,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ diff --git a/assistants/guided-conversation-assistant/uv.lock b/assistants/guided-conversation-assistant/uv.lock index 539fc545..e30a260e 100644 --- a/assistants/guided-conversation-assistant/uv.lock +++ b/assistants/guided-conversation-assistant/uv.lock @@ -252,7 +252,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -262,8 +261,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -451,7 +451,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -1443,7 +1443,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -2100,7 +2100,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ diff --git a/assistants/prospector-assistant/uv.lock b/assistants/prospector-assistant/uv.lock index fa2776d1..350fc7a7 100644 --- a/assistants/prospector-assistant/uv.lock +++ b/assistants/prospector-assistant/uv.lock @@ -245,7 +245,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -255,8 +254,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -444,7 +444,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -1436,7 +1436,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -2150,7 +2150,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ diff --git a/assistants/skill-assistant/uv.lock b/assistants/skill-assistant/uv.lock index f633b762..56182f89 100644 --- a/assistants/skill-assistant/uv.lock +++ b/assistants/skill-assistant/uv.lock @@ -268,7 +268,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -278,8 +277,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, diff --git a/examples/python/python-02-simple-chatbot/uv.lock b/examples/python/python-02-simple-chatbot/uv.lock index acf1ead3..7f90755c 100644 --- a/examples/python/python-02-simple-chatbot/uv.lock +++ b/examples/python/python-02-simple-chatbot/uv.lock @@ -237,7 +237,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -247,8 +246,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -427,7 +427,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -1047,7 +1047,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1515,7 +1515,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ diff --git a/examples/python/python-03-multimodel-chatbot/uv.lock b/examples/python/python-03-multimodel-chatbot/uv.lock index bbd97943..660e90ee 100644 --- a/examples/python/python-03-multimodel-chatbot/uv.lock +++ b/examples/python/python-03-multimodel-chatbot/uv.lock @@ -241,7 +241,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -251,8 +250,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../../libraries/python/anthropic-client" }, { name = "assistant-drive", editable = "../../../libraries/python/assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../../libraries/python/assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../../libraries/python/mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../../libraries/python/openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -440,7 +440,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -1231,7 +1231,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1767,7 +1767,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ diff --git a/libraries/python/anthropic-client/uv.lock b/libraries/python/anthropic-client/uv.lock index 04d3e27c..dd566206 100644 --- a/libraries/python/anthropic-client/uv.lock +++ b/libraries/python/anthropic-client/uv.lock @@ -212,7 +212,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -222,8 +221,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "." }, { name = "assistant-drive", editable = "../assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -417,7 +417,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -1094,7 +1094,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1643,7 +1643,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ diff --git a/libraries/python/assistant-extensions/.vscode/settings.json b/libraries/python/assistant-extensions/.vscode/settings.json index e2803512..104aae8d 100644 --- a/libraries/python/assistant-extensions/.vscode/settings.json +++ b/libraries/python/assistant-extensions/.vscode/settings.json @@ -13,9 +13,7 @@ "python.analysis.autoFormatStrings": true, "python.analysis.autoImportCompletions": true, "python.analysis.diagnosticMode": "workspace", - "python.analysis.fixAll": [ - "source.unusedImports" - ], + "python.analysis.fixAll": ["source.unusedImports"], "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.typeCheckingMode": "standard", "python.defaultInterpreterPath": "${workspaceFolder}/.venv", @@ -50,10 +48,13 @@ "DMAIC", "endregion", "Excalidraw", + "giphy", + "modelcontextprotocol", "openai", "pdfplumber", "pydantic", "pyright", - "pytest" + "pytest", + "semanticworkbench" ] } diff --git a/libraries/python/assistant-extensions/assistant_extensions/mcp/__init__.py b/libraries/python/assistant-extensions/assistant_extensions/mcp/__init__.py new file mode 100644 index 00000000..7ea594cd --- /dev/null +++ b/libraries/python/assistant-extensions/assistant_extensions/mcp/__init__.py @@ -0,0 +1,19 @@ +from ._model import ( + ExtendedCallToolRequestParams, + ExtendedCallToolResult, + MCPSession, + MCPToolsConfigModel, +) +from ._server_utils import establish_mcp_sessions, get_mcp_server_prompts +from ._tool_utils import handle_mcp_tool_call, retrieve_mcp_tools_from_sessions + +__all__ = [ + "MCPSession", + "MCPToolsConfigModel", + "ExtendedCallToolRequestParams", + "ExtendedCallToolResult", + "establish_mcp_sessions", + "get_mcp_server_prompts", + "handle_mcp_tool_call", + "retrieve_mcp_tools_from_sessions", +] diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__model.py b/libraries/python/assistant-extensions/assistant_extensions/mcp/_model.py similarity index 92% rename from assistants/codespace-assistant/assistant/extensions/tools/__model.py rename to libraries/python/assistant-extensions/assistant_extensions/mcp/_model.py index f47a0ba0..e7e59a54 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__model.py +++ b/libraries/python/assistant-extensions/assistant_extensions/mcp/_model.py @@ -1,17 +1,19 @@ -import json import logging -from enum import StrEnum from textwrap import dedent -from typing import Annotated, Any, List +from typing import Annotated, Any, Awaitable, Callable, List -from attr import dataclass from mcp import ClientSession, Tool +from mcp.types import CallToolRequestParams, CallToolResult from pydantic import BaseModel, Field from semantic_workbench_assistant.config import UISchema logger = logging.getLogger(__name__) +# define type for on_logging_message callback +OnMCPLoggingMessageHandler = Callable[[str], Awaitable[None]] + + class MCPServerEnvConfig(BaseModel): key: Annotated[str, Field(title="Key", description="Environment variable key.")] value: Annotated[str, Field(title="Value", description="Environment variable value.")] @@ -53,7 +55,7 @@ class MCPServerConfig(BaseModel): ] = 30 -class ToolsConfigModel(BaseModel): +class MCPToolsConfigModel(BaseModel): enabled: Annotated[ bool, Field( @@ -201,31 +203,10 @@ async def initialize(self) -> None: logger.debug(f"Loaded {len(tools_result.tools)} tools from session '{self.config.key}'") -@dataclass -class ToolCall: +class ExtendedCallToolRequestParams(CallToolRequestParams): id: str - name: str - arguments: dict[str, Any] - - def to_dict(self) -> dict[str, Any]: - return { - "id": self.id, - "name": self.name, - "arguments": self.arguments, - } - - def to_json(self, **kwargs) -> str: - return json.dumps(self, default=lambda o: o.__dict__, **kwargs) - - -class ToolMessageType(StrEnum): - notice = "notice" - tool_result = "tool_result" -@dataclass -class ToolCallResult: +class ExtendedCallToolResult(CallToolResult): id: str - content: str - message_type: ToolMessageType metadata: dict[str, Any] diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py b/libraries/python/assistant-extensions/assistant_extensions/mcp/_server_utils.py similarity index 58% rename from assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py rename to libraries/python/assistant-extensions/assistant_extensions/mcp/_server_utils.py index 31cc19f1..8a5abb84 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py +++ b/libraries/python/assistant-extensions/assistant_extensions/mcp/_server_utils.py @@ -1,4 +1,5 @@ import logging +from asyncio import CancelledError from contextlib import AsyncExitStack, asynccontextmanager from typing import AsyncIterator, List, Optional @@ -6,7 +7,7 @@ from mcp.client.sse import sse_client from mcp.client.stdio import StdioServerParameters, stdio_client -from .__model import MCPServerConfig, MCPSession, ToolsConfigModel +from ._model import MCPServerConfig, MCPSession, MCPToolsConfigModel logger = logging.getLogger(__name__) @@ -20,7 +21,9 @@ def get_env_dict(server_config: MCPServerConfig) -> dict[str, str] | None: @asynccontextmanager -async def connect_to_mcp_server(server_config: MCPServerConfig) -> AsyncIterator[Optional[ClientSession]]: +async def connect_to_mcp_server( + server_config: MCPServerConfig, +) -> AsyncIterator[Optional[ClientSession]]: """Connect to a single MCP server defined in the config.""" if server_config.command.startswith("http"): async with connect_to_mcp_server_sse(server_config) as client_session: @@ -31,42 +34,74 @@ async def connect_to_mcp_server(server_config: MCPServerConfig) -> AsyncIterator @asynccontextmanager -async def connect_to_mcp_server_stdio(server_config: MCPServerConfig) -> AsyncIterator[Optional[ClientSession]]: +async def connect_to_mcp_server_stdio( + server_config: MCPServerConfig, +) -> AsyncIterator[Optional[ClientSession]]: """Connect to a single MCP server defined in the config.""" server_params = StdioServerParameters( - command=server_config.command, args=server_config.args, env=get_env_dict(server_config) + command=server_config.command, + args=server_config.args, + env=get_env_dict(server_config), + ) + logger.debug( + f"Attempting to connect to {server_config.key} with command: {server_config.command} {' '.join(server_config.args)}" ) try: - logger.debug( - f"Attempting to connect to {server_config.key} with command: {server_config.command} {' '.join(server_config.args)}" - ) async with stdio_client(server_params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as client_session: await client_session.initialize() yield client_session # Yield the session for use + except Exception as e: logger.exception(f"Error connecting to {server_config.key}: {e}") - yield None # Yield None if connection fails + raise @asynccontextmanager -async def connect_to_mcp_server_sse(server_config: MCPServerConfig) -> AsyncIterator[Optional[ClientSession]]: +async def connect_to_mcp_server_sse( + server_config: MCPServerConfig, +) -> AsyncIterator[Optional[ClientSession]]: """Connect to a single MCP server defined in the config using SSE transport.""" try: - logger.debug(f"Attempting to connect to {server_config.key} with SSE transport: {server_config.command}") + logger.debug( + f"Attempting to connect to {server_config.key} with SSE transport: {server_config.command}" + ) headers = get_env_dict(server_config) - async with sse_client(url=server_config.command, headers=headers) as (read_stream, write_stream): + + # FIXME: Bumping timeout to 15 minutes, but this should be configurable + async with sse_client( + url=server_config.command, headers=headers, sse_read_timeout=60 * 15 + ) as ( + read_stream, + write_stream, + ): async with ClientSession(read_stream, write_stream) as client_session: await client_session.initialize() yield client_session # Yield the session for use + + except ExceptionGroup as e: + logger.exception(f"TaskGroup failed in SSE client for {server_config.key}: {e}") + for sub_extension in e.exceptions: + logger.error(f"Sub-exception: {server_config.key}: {sub_extension}") + raise + except CancelledError as e: + logger.exception( + f"Task was cancelled in SSE client for {server_config.key}: {e}" + ) + raise + except RuntimeError as e: + logger.exception(f"Runtime error in SSE client for {server_config.key}: {e}") + raise except Exception as e: logger.exception(f"Error connecting to {server_config.key}: {e}") - yield None + raise -async def establish_mcp_sessions(tools_config: ToolsConfigModel, stack: AsyncExitStack) -> List[MCPSession]: +async def establish_mcp_sessions( + tools_config: MCPToolsConfigModel, stack: AsyncExitStack +) -> List[MCPSession]: """ Establish connections to MCP servers using the provided AsyncExitStack. """ @@ -78,10 +113,14 @@ async def establish_mcp_sessions(tools_config: ToolsConfigModel, stack: AsyncExi logger.debug(f"Skipping disabled server: {server_config.key}") continue - client_session: ClientSession | None = await stack.enter_async_context(connect_to_mcp_server(server_config)) + client_session: ClientSession | None = await stack.enter_async_context( + connect_to_mcp_server(server_config) + ) if client_session: # Create an MCP session with the client session - mcp_session = MCPSession(config=server_config, client_session=client_session) + mcp_session = MCPSession( + config=server_config, client_session=client_session + ) # Initialize the session to load tools, resources, etc. await mcp_session.initialize() # Add the session to the list of established sessions @@ -91,6 +130,10 @@ async def establish_mcp_sessions(tools_config: ToolsConfigModel, stack: AsyncExi return mcp_sessions -def get_mcp_server_prompts(tools_config: ToolsConfigModel) -> List[str]: +def get_mcp_server_prompts(tools_config: MCPToolsConfigModel) -> List[str]: """Get the prompts for all MCP servers.""" - return [mcp_server.prompt for mcp_server in tools_config.mcp_servers if mcp_server.prompt] + return [ + mcp_server.prompt + for mcp_server in tools_config.mcp_servers + if mcp_server.prompt + ] diff --git a/libraries/python/assistant-extensions/assistant_extensions/mcp/_tool_utils.py b/libraries/python/assistant-extensions/assistant_extensions/mcp/_tool_utils.py new file mode 100644 index 00000000..d930f16f --- /dev/null +++ b/libraries/python/assistant-extensions/assistant_extensions/mcp/_tool_utils.py @@ -0,0 +1,206 @@ +# utils/tool_utils.py +import logging +from textwrap import dedent +from typing import AsyncGenerator, List + +import deepmerge +from mcp import ServerNotification, Tool +from mcp.types import CallToolResult, EmbeddedResource, ImageContent, TextContent +from mcp_extensions import execute_tool_with_notifications + +from ._model import ( + ExtendedCallToolRequestParams, + ExtendedCallToolResult, + MCPSession, + MCPToolsConfigModel, + OnMCPLoggingMessageHandler, +) + +logger = logging.getLogger(__name__) + + +def retrieve_mcp_tools_from_sessions( + mcp_sessions: List[MCPSession], tools_config: MCPToolsConfigModel +) -> List[Tool]: + """ + Retrieve tools from all MCP sessions, excluding any tools that are disabled in the tools config. + """ + return [ + tool + for mcp_session in mcp_sessions + for tool in mcp_session.tools + if tool.name not in tools_config.tools_disabled + ] + + +def get_mcp_session_and_tool_by_tool_name( + mcp_sessions: List[MCPSession], + tool_name: str, +) -> tuple[MCPSession | None, Tool | None]: + """ + Retrieve the MCP session and tool by tool name. + """ + return next( + ( + (mcp_session, tool) + for mcp_session in mcp_sessions + for tool in mcp_session.tools + if tool.name == tool_name + ), + (None, None), + ) + + +async def handle_mcp_tool_call( + mcp_sessions: List[MCPSession], + tool_call: ExtendedCallToolRequestParams, + method_metadata_key: str, + on_logging_message: OnMCPLoggingMessageHandler, +) -> ExtendedCallToolResult: + """ + Handle the tool call by invoking the appropriate tool and returning a ToolCallResult. + """ + + # Find the tool and session from the full collection of sessions + mcp_session, tool = get_mcp_session_and_tool_by_tool_name( + mcp_sessions, tool_call.name + ) + + if not mcp_session or not tool: + return ExtendedCallToolResult( + id=tool_call.id, + content=[ + TextContent( + type="text", + text=f"Tool '{tool_call.name}' not found in any of the sessions.", + ) + ], + isError=True, + metadata={}, + ) + + return await execute_tool( + mcp_session, tool_call, method_metadata_key, on_logging_message + ) + + +async def handle_long_running_tool_call( + mcp_sessions: List[MCPSession], + tool_call: ExtendedCallToolRequestParams, + method_metadata_key: str, + on_logging_message: OnMCPLoggingMessageHandler, +) -> AsyncGenerator[ExtendedCallToolResult, None]: + """ + Handle the streaming tool call by invoking the appropriate tool and returning a ToolCallResult. + """ + + # Find the tool and session from the full collection of sessions + mcp_session, tool = get_mcp_session_and_tool_by_tool_name( + mcp_sessions, tool_call.name + ) + + if not mcp_session or not tool: + yield ExtendedCallToolResult( + id=tool_call.id, + content=[ + TextContent( + type="text", + text=f"Tool '{tool_call.name}' not found in any of the sessions.", + ) + ], + isError=True, + metadata={}, + ) + return + + # For now, let's just hack to return an immediate response to indicate that the tool call was received + # and is being processed and that the results will be sent in a separate message. + yield ExtendedCallToolResult( + id=tool_call.id, + content=[ + TextContent( + type="text", + text=dedent(f""" + Processing tool call '{tool_call.name}'. + Estimated time to completion: {mcp_session.config.task_completion_estimate} + """).strip(), + ), + ], + metadata={}, + ) + + # Perform the tool call + tool_call_result = await execute_tool( + mcp_session, tool_call, method_metadata_key, on_logging_message + ) + yield tool_call_result + + +async def execute_tool( + mcp_session: MCPSession, + tool_call: ExtendedCallToolRequestParams, + method_metadata_key: str, + on_logging_message: OnMCPLoggingMessageHandler, +) -> ExtendedCallToolResult: + # Initialize metadata + metadata = {} + + # Initialize tool_result + tool_result = None + tool_output: list[TextContent | ImageContent | EmbeddedResource] = [] + content_items: List[str] = [] + + async def tool_call_function() -> CallToolResult: + return await mcp_session.client_session.call_tool( + tool_call.name, tool_call.arguments + ) + + async def notification_handler(message: ServerNotification) -> None: + if message.root.method == "notifications/message": + await on_logging_message(message.root.params.data) + else: + logger.warning(f"Received unknown notification: {message}") + + logger.debug( + f"Invoking '{mcp_session.config.key}.{tool_call.name}' with arguments: {tool_call.arguments}" + ) + tool_result = await execute_tool_with_notifications( + mcp_session.client_session, tool_call_function, notification_handler + ) + tool_output = tool_result.content + + # Update metadata with tool result + deepmerge.always_merger.merge( + metadata, + { + "debug": { + method_metadata_key: { + "tool_result": tool_output, + }, + }, + }, + ) + + # FIXME: for now, we'll just dump the tool output as text but we should support other content types + for tool_output_item in tool_output: + if isinstance(tool_output_item, TextContent): + if tool_output_item.text.strip() != "": + content_items.append(tool_output_item.text) + if isinstance(tool_output_item, ImageContent): + content_items.append(tool_output_item.model_dump_json()) + if isinstance(tool_output_item, EmbeddedResource): + content_items.append(tool_output_item.model_dump_json()) + + # Return the tool call result + return ExtendedCallToolResult( + id=tool_call.id, + content=[ + TextContent( + type="text", + text="\n\n".join(content_items) + if len(content_items) > 0 + else "[tool call successful, but no output, this may indicate empty file, directory, etc.]", + ), + ], + metadata=metadata, + ) diff --git a/libraries/python/assistant-extensions/pyproject.toml b/libraries/python/assistant-extensions/pyproject.toml index 978938f1..187a0c71 100644 --- a/libraries/python/assistant-extensions/pyproject.toml +++ b/libraries/python/assistant-extensions/pyproject.toml @@ -1,11 +1,8 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" [project] name = "assistant-extensions" version = "0.1.0" -description = "Add your description here" +description = "Extensions for the Semantic Workbench OpenAI assistant." authors = [{ name = "Semantic Workbench Team" }] readme = "README.md" requires-python = ">=3.11" @@ -20,14 +17,27 @@ dependencies = [ ] [project.optional-dependencies] -attachments = ["docx2txt>=0.8", "pdfplumber>=0.11.2", "assistant-drive>=0.1.0"] +# For any of the above dependencies that are specific to a single extension, it'd be good +# to consider moving them to the optional-dependencies section. This way, the dependencies +# are only installed when the specific extension is installed, to reduce the overall size +# of the package installation, especially when bundling larger dependencies. +attachments = ["docx2txt>=0.8", "pdfplumber>=0.11.2"] +mcp = ["mcp>=1.2.1", "mcp-extensions[openai]>=0.1.0"] [dependency-groups] dev = ["pyright>=1.1.389", "pytest>=8.3.1", "pytest-asyncio>=0.23.8"] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.ruff] +target-version = "py311" + [tool.uv.sources] anthropic-client = { path = "../anthropic-client", editable = true } assistant-drive = { path = "../assistant-drive", editable = true } +mcp-extensions = { path = "../mcp-extensions", editable = true } openai-client = { path = "../openai-client", editable = true } semantic-workbench-assistant = { path = "../semantic-workbench-assistant", editable = true } diff --git a/libraries/python/assistant-extensions/uv.lock b/libraries/python/assistant-extensions/uv.lock index 3a3dae24..b675434a 100644 --- a/libraries/python/assistant-extensions/uv.lock +++ b/libraries/python/assistant-extensions/uv.lock @@ -209,10 +209,13 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] +mcp = [ + { name = "mcp" }, + { name = "mcp-extensions", extra = ["openai"] }, +] [package.dev-dependencies] dev = [ @@ -226,8 +229,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../anthropic-client" }, { name = "assistant-drive", editable = "../assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -421,7 +425,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -682,6 +686,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, ] +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + [[package]] name = "idna" version = "3.10" @@ -834,6 +847,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/87/4c364e0f109eea2402079abecbe33fef4f347b551a11423d1f4e187ea497/MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", size = 15741 }, ] +[[package]] +name = "mcp" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/30/51e4555826126e3954fa2ab1e934bf74163c5fe05e98f38ca4d0f8abbf63/mcp-1.2.1.tar.gz", hash = "sha256:c9d43dbfe943aa1530e2be8f54b73af3ebfb071243827b4483d421684806cb45", size = 103968 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, +] + +[[package]] +name = "mcp-extensions" +version = "0.1.0" +source = { editable = "../mcp-extensions" } +dependencies = [ + { name = "deepmerge" }, + { name = "mcp" }, +] + +[package.optional-dependencies] +openai = [ + { name = "openai" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepmerge", specifier = ">=2.0" }, + { name = "mcp", specifier = ">=1.2.1" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.63.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=8.3.1" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -935,7 +994,7 @@ wheels = [ [[package]] name = "openai" -version = "1.61.0" +version = "1.63.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -947,9 +1006,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/2a/b3fa8790be17d632f59d4f50257b909a3f669036e5195c1ae55737274620/openai-1.61.0.tar.gz", hash = "sha256:216f325a24ed8578e929b0f1b3fb2052165f3b04b0461818adaa51aa29c71f8a", size = 350174 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/76/70c5ad6612b3e4c89fa520266bbf2430a89cae8bd87c1e2284698af5927e/openai-1.61.0-py3-none-any.whl", hash = "sha256:e8c512c0743accbdbe77f3429a1490d862f8352045de8dc81969301eb4a4f666", size = 460623 }, + { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, ] [[package]] @@ -1088,7 +1147,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1163,76 +1222,82 @@ wheels = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, ] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, - { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, - { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, - { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, - { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, - { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, - { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, - { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, - { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, - { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, - { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, - { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, - { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, - { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, - { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, - { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, - { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, - { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, - { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, - { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, - { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, - { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, - { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, - { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, - { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, - { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, - { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, - { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, - { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, - { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, - { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, - { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, - { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, - { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, - { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, ] [[package]] name = "pydantic-settings" -version = "2.5.2" +version = "2.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 } +sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 }, + { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, ] [[package]] @@ -1596,6 +1661,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "sse-starlette" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, +] + [[package]] name = "starlette" version = "0.38.6" @@ -1643,7 +1722,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ diff --git a/libraries/python/mcp-extensions/.vscode/settings.json b/libraries/python/mcp-extensions/.vscode/settings.json new file mode 100644 index 00000000..d650f87b --- /dev/null +++ b/libraries/python/mcp-extensions/.vscode/settings.json @@ -0,0 +1,55 @@ +{ + "editor.bracketPairColorization.enabled": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + }, + "editor.guides.bracketPairs": "active", + "editor.formatOnPaste": true, + "editor.formatOnType": true, + "editor.formatOnSave": true, + "files.eol": "\n", + "files.trimTrailingWhitespace": true, + "python.analysis.autoFormatStrings": true, + "python.analysis.autoImportCompletions": true, + "python.analysis.diagnosticMode": "workspace", + "python.analysis.fixAll": ["source.unusedImports"], + "python.analysis.inlayHints.functionReturnTypes": true, + "python.analysis.typeCheckingMode": "standard", + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.unusedImports": "explicit", + "source.organizeImports": "explicit", + "source.formatDocument": "explicit" + } + }, + "ruff.nativeServer": "on", + "search.exclude": { + "**/.venv": true, + "**/.data": true, + "**/__pycache__": true + }, + // For use with optional extension: "streetsidesoftware.code-spell-checker" + "cSpell.ignorePaths": [ + ".venv", + "node_modules", + "package-lock.json", + "settings.json", + "uv.lock" + ], + "cSpell.words": [ + "asyncio", + "deepmerge", + "fastmcp", + "interoperating", + "Lifecycles", + "pydantic", + "pyright", + "pytest" + ] +} diff --git a/libraries/python/mcp-extensions/Makefile b/libraries/python/mcp-extensions/Makefile new file mode 100644 index 00000000..1ad4520a --- /dev/null +++ b/libraries/python/mcp-extensions/Makefile @@ -0,0 +1,2 @@ +repo_root = $(shell git rev-parse --show-toplevel) +include $(repo_root)/tools/makefiles/python.mk diff --git a/libraries/python/mcp-extensions/README.md b/libraries/python/mcp-extensions/README.md new file mode 100644 index 00000000..3cb1fbde --- /dev/null +++ b/libraries/python/mcp-extensions/README.md @@ -0,0 +1,114 @@ +# MCP Extensions Library + +The `mcp-extensions` library is a supplemental toolkit designed to enhance the functionality and usability of the Model Context Protocol (MCP) framework. MCP provides a standardized interface for bridging AI models with external resources, tools, and prompts. This library builds on that foundation by offering utility methods, helper functions, and extended models to improve workflow efficiency and enable advanced integration features. + +## What is MCP? + +MCP (Model Context Protocol) allows applications to provide structured data sources, executable tools, and reusable prompts to Large Language Models (LLMs) in a standardized way. Using MCP, you can: + +- Build MCP clients to communicate with MCP servers. +- Create MCP servers that expose resources, tools, and prompts for model interaction. +- Leverage seamless integrations between your services and LLM applications. + +The `mcp-extensions` library supplements this ecosystem to bridge specific gaps in workflows or extend capabilities. + +## Features + +### 1. Tool Execution Enhancements + +- **Notification-based Lifecycles:** Implement `execute_tool_with_notifications` to handle tool calls with real-time notifications to the server. This is particularly valuable for: + - Progress tracking. + - Handling asynchronous tool execution. + +```python +await execute_tool_with_notifications( + session=session, + tool_call_function=my_tool_call, + notification_handler=handle_server_notifications +) +``` + +### 2. Conversion Utilities + +- **Convert MCP Tools:** Leverages `convert_tools_to_openai_tools` to transform MCP tools into OpenAI-compatible function definitions. Useful for interoperating with ecosystems that leverage OpenAI's function call syntax. + +```python +converted_tools = convert_tools_to_openai_tools(mcp_tools, extra_properties={'user_context': 'optional'}) +``` + +### 3. Progress Notifications + +- **Report Progress to MCP Clients:** + Provide fine-grained updates about ongoing tasks using `send_tool_call_progress`. Ideal for applications where the client requires detailed visibility into long-running tasks. + +```python +await send_tool_call_progress(context, "50% task completed", data={"step": 3}) +``` + +### 4. Extended Data Models + +- **Custom Server Notification Handlers:** Includes extended models like `ToolCallFunction`, `ServerNotificationHandler`, and `ToolCallProgressMessage` for greater flexibility when handling server events and workflow lifecycles. + +```python +from mcp_extensions._model import ServerNotificationHandler, ToolCallProgressMessage +``` + +## Use Cases + +### A. Enhanced Tool Lifecycle Management + +The library helps in managing tool lifecycles—for asynchronous executions, task progress reporting, and server-side notification handling. + +### B. Cross-Ecosystem Interoperability + +By transforming MCP tool definitions to OpenAI's tool schema, the library facilitates hybrid use cases where functionality needs to work seamlessly across frameworks. + +### C. Real-Time Execution Feedback + +Applications requiring frequent updates about task statuses benefit from the notification-based features built into the library. + +## Installation + +To install the `mcp-extensions` library, run: + +```bash +pip install mcp-extensions +``` + +Ensure that you are using Python 3.11 or later to leverage the library's features. + +## Supported Dependencies + +The library depends on: + +- `deepmerge`: For combining tool properties with additional metadata. +- `mcp`: Required for core protocol interactions. + +Optional: + +- `openai`: If integrating with OpenAI's APIs. + +## Getting Started + +Here is a minimal example to use the library’s tool execution utilities: + +```python +from mcp_extensions import execute_tool_with_notifications + +async def my_tool_call(): + # Perform tool-specific task + return {"result": "completed"} + +async def handle_server_notifications(notification): + print(f"Received server notification: {notification}") + +await execute_tool_with_notifications( + session=session, + tool_call_function=my_tool_call, + notification_handler=handle_server_notifications +) +``` + +--- + +For more information on the Model Context Protocol, visit the [official documentation](https://modelcontextprotocol.io). diff --git a/libraries/python/mcp-extensions/mcp_extensions/__init__.py b/libraries/python/mcp-extensions/mcp_extensions/__init__.py new file mode 100644 index 00000000..a67d0dd5 --- /dev/null +++ b/libraries/python/mcp-extensions/mcp_extensions/__init__.py @@ -0,0 +1,14 @@ +from ._model import ServerNotificationHandler, ToolCallFunction, ToolCallProgressMessage +from ._tool_utils import convert_tools_to_openai_tools, execute_tool_with_notifications, send_tool_call_progress + +# Exported utilities and models for external use. +# These components enhance interactions with MCP workflows by providing utilities for notifications, +# progress updates, and tool conversion specific to the MCP ecosystem. +__all__ = [ + "convert_tools_to_openai_tools", + "execute_tool_with_notifications", + "send_tool_call_progress", + "ServerNotificationHandler", + "ToolCallFunction", + "ToolCallProgressMessage", +] diff --git a/libraries/python/mcp-extensions/mcp_extensions/_model.py b/libraries/python/mcp-extensions/mcp_extensions/_model.py new file mode 100644 index 00000000..3373bcc7 --- /dev/null +++ b/libraries/python/mcp-extensions/mcp_extensions/_model.py @@ -0,0 +1,22 @@ +from typing import Any, Awaitable, Callable + +from mcp import ServerNotification +from mcp.types import CallToolResult +from pydantic import BaseModel + +ToolCallFunction = Callable[[], Awaitable[CallToolResult]] +ServerNotificationHandler = Callable[[ServerNotification], Awaitable[None]] + + +class ToolCallProgressMessage(BaseModel): + """ + Represents a progress message for an active tool call. + + Attributes: + message (str): A brief textual update describing the current tool execution state. + data (dict[str, Any] | None): Optional dictionary containing structured data relevant + to the progress update. + """ + + message: str + data: dict[str, Any] | None diff --git a/libraries/python/mcp-extensions/mcp_extensions/_tool_utils.py b/libraries/python/mcp-extensions/mcp_extensions/_tool_utils.py new file mode 100644 index 00000000..08db3892 --- /dev/null +++ b/libraries/python/mcp-extensions/mcp_extensions/_tool_utils.py @@ -0,0 +1,131 @@ +# utils/tool_utils.py +import asyncio +import logging +from typing import Any, List + +import deepmerge +from mcp import ClientSession, ServerNotification, Tool +from mcp.server.fastmcp import Context +from mcp.types import CallToolResult +from openai.types.chat import ( + ChatCompletionToolParam, +) +from openai.types.shared_params import FunctionDefinition + +from ._model import ServerNotificationHandler, ToolCallFunction + +logger = logging.getLogger(__name__) + + +async def send_tool_call_progress( + fastmcp_server_context: Context, message: str, data: dict[str, Any] | None = None +) -> None: + """ + Sends a progress update message for a tool call to the FastMCP server. This is useful for providing + real-time feedback to clients regarding task status. + """ + + await fastmcp_server_context.session.send_log_message( + level="info", + data=message, + ) + + # FIXME: Would prefer to use this to send data via a custom notification, but it's not working + # session: BaseSession = fastmcp_server_context.session + # jsonrpc_notification = JSONRPCNotification( + # method="tool_call_progress", + # jsonrpc="2.0", + # params=ToolCallProgressMessage( + # message=message, + # data=data, + # ).model_dump(mode="json"), + # ) + # await session._write_stream.send(JSONRPCMessage(jsonrpc_notification)) + + +async def execute_tool_with_notifications( + session: ClientSession, + tool_call_function: ToolCallFunction, + notification_handler: ServerNotificationHandler, +) -> CallToolResult: + """ + Executes a tool call while concurrently processing incoming notifications from the MCP server. This ensures + smooth handling of asynchronous updates during execution. + + Args: + session: The MCP client session facilitating communication with the server. + tool_call_function: The asynchronous tool call function to execute. + notification_handler: Asynchronous callback to process server notifications during execution. + + Returns: + The result of the tool call, typically wrapped as a protocol-compliant response. + """ + + # Create a task for listening to incoming messages + async def message_listener() -> None: + async for message in session.incoming_messages: + if isinstance(message, Exception): + raise message + if isinstance(message, ServerNotification): + await notification_handler(message) + else: + logger.warning(f"Received unknown message: {message}") + + # Create a task group to run both the tool call and message listener + async with asyncio.TaskGroup() as task_group: + # Start the message listener + listener_task = task_group.create_task(message_listener()) + + try: + # Execute the tool call + result = await tool_call_function() + return result + finally: + # Cancel the listener task when we're done + listener_task.cancel() + + +def convert_tools_to_openai_tools( + mcp_tools: List[Tool] | None, extra_properties: dict[str, Any] | None = None +) -> List[ChatCompletionToolParam] | None: + """ + Converts MCP tools into OpenAI-compatible tool schemas to facilitate interoperability. + Extra properties can be appended to the generated schema, enabling richer descriptions + or added functionality (e.g., custom fields for user context or explanations). + """ + + if not mcp_tools: + return None + + openai_tools: List[ChatCompletionToolParam] = [] + for mcp_tool in mcp_tools: + parameters = mcp_tool.inputSchema.copy() + + if isinstance(extra_properties, dict): + # Add the extra properties to the input schema + parameters = deepmerge.always_merger.merge( + parameters, + { + "properties": { + **extra_properties, + }, + "required": [ + *extra_properties.keys(), + ], + }, + ) + + function = FunctionDefinition( + name=mcp_tool.name, + description=mcp_tool.description if mcp_tool.description else "[no description provided]", + parameters=parameters, + ) + + openai_tools.append( + ChatCompletionToolParam( + function=function, + type="function", + ) + ) + + return openai_tools diff --git a/libraries/python/mcp-extensions/pyproject.toml b/libraries/python/mcp-extensions/pyproject.toml new file mode 100644 index 00000000..905e4e75 --- /dev/null +++ b/libraries/python/mcp-extensions/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "mcp-extensions" +version = "0.1.0" +description = "Extensions for Model Context Protocol (MCP) clients / servers." +authors = [{ name = "Semantic Workbench Team" }] +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "deepmerge>=2.0", + "mcp>=1.2.1", + "pytest-asyncio>=0.25.3", +] + +[project.optional-dependencies] +# For any of the above dependencies that are specific to a single extension, it'd be good +# to consider moving them to the optional-dependencies section. This way, the dependencies +# are only installed when the specific extension is installed, to reduce the overall size +# of the package installation, especially when bundling larger dependencies. +openai = ["openai>=1.63.2"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = ["pyright>=1.1.389", "pytest>=8.3.1"] + diff --git a/libraries/python/mcp-extensions/tests/test_tool_utils.py b/libraries/python/mcp-extensions/tests/test_tool_utils.py new file mode 100644 index 00000000..7f9e115e --- /dev/null +++ b/libraries/python/mcp-extensions/tests/test_tool_utils.py @@ -0,0 +1,83 @@ +from unittest.mock import AsyncMock, MagicMock ## This suites and ensure!Cl=Success + +import pytest +from mcp_extensions._tool_utils import ( + convert_tools_to_openai_tools, + execute_tool_with_notifications, + send_tool_call_progress, +) + + +def test_convert_tools_to_openai_tools_empty(): + result = convert_tools_to_openai_tools([]) + assert result is None + + +# Test: Notification handling in execute_tool_with_notifications +@pytest.mark.asyncio +async def test_execute_tool_with_notification_handling(): + mock_session = AsyncMock() + mock_session.incoming_messages = AsyncMock(return_value=[]) + mock_tool_call_function = AsyncMock(return_value="result") + mock_handler = AsyncMock() + + await execute_tool_with_notifications( + session=mock_session, + tool_call_function=mock_tool_call_function, + notification_handler=mock_handler, + ) + + mock_handler.assert_not_called() + mock_tool_call_function.assert_awaited_once() + + +# Test: send_tool_call_progress +@pytest.mark.asyncio +async def test_send_tool_call_progress(): + mock_context = AsyncMock() + message = "Progress update" + data = {"step": 1} + + await send_tool_call_progress(mock_context, message, data) + + # Ensure the log message was sent properly + mock_context.session.send_log_message.assert_called_once_with( + level="info", + data=message, + ) + + +# Test: execute_tool_with_notifications +@pytest.mark.asyncio +async def test_execute_tool_with_notifications(): + mock_session = AsyncMock() + mock_tool_call_function = AsyncMock(return_value="result") + mock_notification_handler = AsyncMock() + + result = await execute_tool_with_notifications( + session=mock_session, + tool_call_function=mock_tool_call_function, + notification_handler=mock_notification_handler, + ) + + assert result == "result" + mock_tool_call_function.assert_awaited_once() + mock_notification_handler.assert_not_called() + + +# Test: convert_tools_to_openai_tools +def test_convert_tools_to_openai_tools(): + mock_tool = MagicMock() + mock_tool.name = "test_tool" + mock_tool.inputSchema = {"type": "object", "properties": {}} + mock_tool.description = "A test tool." + + result = convert_tools_to_openai_tools([mock_tool]) + + assert result is not None and len(result) == 1 + assert result[0]["function"]["name"] == "test_tool" + assert "description" in result[0]["function"] and result[0]["function"]["description"] == "A test tool." + assert "parameters" in result[0]["function"] and result[0]["function"]["parameters"] == { + "type": "object", + "properties": {}, + } diff --git a/libraries/python/mcp-extensions/uv.lock b/libraries/python/mcp-extensions/uv.lock new file mode 100644 index 00000000..37c6c9d3 --- /dev/null +++ b/libraries/python/mcp-extensions/uv.lock @@ -0,0 +1,481 @@ +version = 1 +requires-python = ">=3.11" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475 }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jiter" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b0/c1a7caa7f9dc5f1f6cfa08722867790fe2d3645d6e7170ca280e6e52d163/jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b", size = 303666 }, + { url = "https://files.pythonhosted.org/packages/f5/97/0468bc9eeae43079aaa5feb9267964e496bf13133d469cfdc135498f8dd0/jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15", size = 311934 }, + { url = "https://files.pythonhosted.org/packages/e5/69/64058e18263d9a5f1e10f90c436853616d5f047d997c37c7b2df11b085ec/jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0", size = 335506 }, + { url = "https://files.pythonhosted.org/packages/9d/14/b747f9a77b8c0542141d77ca1e2a7523e854754af2c339ac89a8b66527d6/jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f", size = 355849 }, + { url = "https://files.pythonhosted.org/packages/53/e2/98a08161db7cc9d0e39bc385415890928ff09709034982f48eccfca40733/jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099", size = 381700 }, + { url = "https://files.pythonhosted.org/packages/7a/38/1674672954d35bce3b1c9af99d5849f9256ac8f5b672e020ac7821581206/jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74", size = 389710 }, + { url = "https://files.pythonhosted.org/packages/f8/9b/92f9da9a9e107d019bcf883cd9125fa1690079f323f5a9d5c6986eeec3c0/jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586", size = 345553 }, + { url = "https://files.pythonhosted.org/packages/44/a6/6d030003394e9659cd0d7136bbeabd82e869849ceccddc34d40abbbbb269/jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc", size = 376388 }, + { url = "https://files.pythonhosted.org/packages/ad/8d/87b09e648e4aca5f9af89e3ab3cfb93db2d1e633b2f2931ede8dabd9b19a/jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88", size = 511226 }, + { url = "https://files.pythonhosted.org/packages/77/95/8008ebe4cdc82eac1c97864a8042ca7e383ed67e0ec17bfd03797045c727/jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6", size = 504134 }, + { url = "https://files.pythonhosted.org/packages/26/0d/3056a74de13e8b2562e4d526de6dac2f65d91ace63a8234deb9284a1d24d/jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44", size = 203103 }, + { url = "https://files.pythonhosted.org/packages/4e/1e/7f96b798f356e531ffc0f53dd2f37185fac60fae4d6c612bbbd4639b90aa/jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855", size = 206717 }, + { url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 }, + { url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 }, + { url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 }, + { url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 }, + { url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 }, + { url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 }, + { url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 }, + { url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 }, + { url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 }, + { url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 }, + { url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 }, + { url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 }, + { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 }, + { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 }, + { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 }, + { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 }, + { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 }, + { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 }, + { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 }, + { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 }, + { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 }, + { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 }, + { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 }, + { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 }, + { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 }, + { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 }, +] + +[[package]] +name = "mcp" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/30/51e4555826126e3954fa2ab1e934bf74163c5fe05e98f38ca4d0f8abbf63/mcp-1.2.1.tar.gz", hash = "sha256:c9d43dbfe943aa1530e2be8f54b73af3ebfb071243827b4483d421684806cb45", size = 103968 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, +] + +[[package]] +name = "mcp-extensions" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "deepmerge" }, + { name = "mcp" }, + { name = "pytest-asyncio" }, +] + +[package.optional-dependencies] +openai = [ + { name = "openai" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepmerge", specifier = ">=2.0" }, + { name = "mcp", specifier = ">=1.2.1" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.63.2" }, + { name = "pytest-asyncio", specifier = ">=0.25.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=8.3.1" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "openai" +version = "1.63.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, +] + +[[package]] +name = "pyright" +version = "1.1.394" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/e4/79f4d8a342eed6790fdebdb500e95062f319ee3d7d75ae27304ff995ae8c/pyright-1.1.394.tar.gz", hash = "sha256:56f2a3ab88c5214a451eb71d8f2792b7700434f841ea219119ade7f42ca93608", size = 3809348 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/4c/50c74e3d589517a9712a61a26143b587dba6285434a17aebf2ce6b82d2c3/pyright-1.1.394-py3-none-any.whl", hash = "sha256:5f74cce0a795a295fb768759bbeeec62561215dea657edcaab48a932b031ddbb", size = 5679540 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/a8/ecbc8ede70921dd2f544ab1cadd3ff3bf842af27f87bbdea774c7baa1d38/pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a", size = 54239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sse-starlette" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, +] + +[[package]] +name = "starlette" +version = "0.45.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "uvicorn" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, +] diff --git a/libraries/python/openai-client/uv.lock b/libraries/python/openai-client/uv.lock index 40555c9f..8b57d19c 100644 --- a/libraries/python/openai-client/uv.lock +++ b/libraries/python/openai-client/uv.lock @@ -207,7 +207,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -217,8 +216,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../anthropic-client" }, { name = "assistant-drive", editable = "../assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "." }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, @@ -406,7 +406,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -1091,7 +1091,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1654,7 +1654,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ diff --git a/libraries/python/skills/skill-library/uv.lock b/libraries/python/skills/skill-library/uv.lock index 402425d4..6331d2c1 100644 --- a/libraries/python/skills/skill-library/uv.lock +++ b/libraries/python/skills/skill-library/uv.lock @@ -209,7 +209,6 @@ dependencies = [ [package.optional-dependencies] attachments = [ - { name = "assistant-drive" }, { name = "docx2txt" }, { name = "pdfplumber" }, ] @@ -219,8 +218,9 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "anthropic-client", editable = "../../anthropic-client" }, { name = "assistant-drive", editable = "../../assistant-drive" }, - { name = "assistant-drive", marker = "extra == 'attachments'", editable = "../../assistant-drive" }, { name = "docx2txt", marker = "extra == 'attachments'", specifier = ">=0.8" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], marker = "extra == 'mcp'", editable = "../../mcp-extensions" }, { name = "openai", specifier = ">=1.61.0" }, { name = "openai-client", editable = "../../openai-client" }, { name = "pdfplumber", marker = "extra == 'attachments'", specifier = ">=0.11.2" }, diff --git a/mcp-servers/mcp-server-giphy/mcp_server/server.py b/mcp-servers/mcp-server-giphy/mcp_server/server.py index 13cdfc0c..a404cbd5 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/server.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/server.py @@ -1,6 +1,6 @@ from typing import Optional -from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp import Context, FastMCP from . import settings from .giphy_search import perform_search @@ -8,15 +8,18 @@ # Set the name of the MCP server server_name = "GIPHY MCP Server" -def create_mcp_server() -> FastMCP: +def create_mcp_server() -> FastMCP: # Initialize FastMCP with debug logging. mcp = FastMCP(name=server_name, log_level=settings.log_level) - # Define each tool and its setup. - @mcp.tool() - async def giphy_search_tool(context: str, search_term: str) -> Optional[list]: + async def giphy_search_tool(context: str, search_term: str, ctx: Context) -> Optional[list]: + await ctx.session.send_log_message( + level="info", + data="searching...", + ) + # Perform search using context and search term search_results = perform_search(context, search_term) diff --git a/mcp-servers/mcp-server-giphy/mcp_server/start.py b/mcp-servers/mcp-server-giphy/mcp_server/start.py index cb10911a..66fd2605 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/start.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/start.py @@ -4,21 +4,17 @@ from .server import create_mcp_server + def main() -> None: # Command-line arguments for transport and port - parse_args = argparse.ArgumentParser(description=f"Start the MCP server.") + parse_args = argparse.ArgumentParser(description="Start the MCP server.") parse_args.add_argument( "--transport", default="stdio", choices=["stdio", "sse"], help="Transport protocol to use ('stdio' or 'sse'). Default is 'stdio'.", ) - parse_args.add_argument( - "--port", - type=int, - default=8000, - help="Port to use for SSE (default is 8000)." - ) + parse_args.add_argument("--port", type=int, default=8000, help="Port to use for SSE (default is 8000).") args = parse_args.parse_args() mcp = create_mcp_server() @@ -27,5 +23,6 @@ def main() -> None: mcp.run(transport=args.transport) + if __name__ == "__main__": main() diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/open_deep_research.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/open_deep_research.py index 8e9ac9a5..eeef3c8f 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/open_deep_research.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/open_deep_research.py @@ -1,8 +1,18 @@ +import asyncio import os import threading +from typing import Awaitable, Callable from dotenv import load_dotenv from huggingface_hub import login +from smolagents import ( + CodeAgent, + # HfApiModel, + LiteLLMModel, + ToolCallingAgent, +) + +from . import settings from .libs.open_deep_research.text_inspector_tool import TextInspectorTool from .libs.open_deep_research.text_web_browser import ( ArchiveSearchTool, @@ -16,16 +26,6 @@ ) from .libs.open_deep_research.visual_qa import visualizer -from smolagents import ( - CodeAgent, - # HfApiModel, - LiteLLMModel, - ToolCallingAgent, -) - -from . import settings - - AUTHORIZED_IMPORTS = [ "requests", "zipfile", @@ -74,13 +74,28 @@ os.makedirs(f"./{BROWSER_CONFIG['downloads_folder']}", exist_ok=True) -def perform_deep_research(model_id, question) -> str: +async def perform_deep_research( + model_id: str, question: str, on_status_update: Callable[[str], Awaitable[None]] +) -> str: + await on_status_update("starting research...") + + return await asyncio.to_thread( + deep_research, + model_id, + question, + ) + + +def deep_research( + model_id: str, + question: str, +) -> str: text_limit = 100000 model = LiteLLMModel( model_id, custom_role_conversions=custom_role_conversions, - max_completion_tokens=50000, + max_completion_tokens=10000, reasoning_effort="high", ) document_inspection_tool = TextInspectorTool(model, text_limit) diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/server.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/server.py index 5826a692..fe174f53 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/server.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/server.py @@ -1,4 +1,5 @@ -from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp import Context, FastMCP +from mcp_extensions import send_tool_call_progress from . import settings from .open_deep_research import perform_deep_research @@ -14,7 +15,7 @@ def create_mcp_server() -> FastMCP: # Define each tool and its setup. @mcp.tool() - async def deep_research(context: str, request: str) -> str: + async def deep_research(context: str, request: str, ctx: Context) -> str: """ A specialized team member that thoroughly researches the internet to answer your questions. Use them for anything requiring web browsing—provide as much context as possible, especially @@ -28,6 +29,15 @@ async def deep_research(context: str, request: str) -> str: possible about what you need and the desired output. """ - return perform_deep_research("o1", f"Context:\n{context}\n\nRequest:\n{request}") + await send_tool_call_progress(ctx, "Researching...", data={"context": context, "request": request}) + + async def on_status_update(status: str) -> None: + await send_tool_call_progress(ctx, status) + + # Make sure to run the async version of the function to avoid blocking the event loop. + deep_research_result = await perform_deep_research( + model_id="o1", question=f"Context:\n{context}\n\nRequest:\n{request}", on_status_update=on_status_update + ) + return deep_research_result return mcp diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/start.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/start.py index cb10911a..66fd2605 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/start.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/start.py @@ -4,21 +4,17 @@ from .server import create_mcp_server + def main() -> None: # Command-line arguments for transport and port - parse_args = argparse.ArgumentParser(description=f"Start the MCP server.") + parse_args = argparse.ArgumentParser(description="Start the MCP server.") parse_args.add_argument( "--transport", default="stdio", choices=["stdio", "sse"], help="Transport protocol to use ('stdio' or 'sse'). Default is 'stdio'.", ) - parse_args.add_argument( - "--port", - type=int, - default=8000, - help="Port to use for SSE (default is 8000)." - ) + parse_args.add_argument("--port", type=int, default=8000, help="Port to use for SSE (default is 8000).") args = parse_args.parse_args() mcp = create_mcp_server() @@ -27,5 +23,6 @@ def main() -> None: mcp.run(transport=args.transport) + if __name__ == "__main__": main() diff --git a/mcp-servers/mcp-server-open-deep-research/pyproject.toml b/mcp-servers/mcp-server-open-deep-research/pyproject.toml index b60a00cc..9476fa74 100644 --- a/mcp-servers/mcp-server-open-deep-research/pyproject.toml +++ b/mcp-servers/mcp-server-open-deep-research/pyproject.toml @@ -6,6 +6,7 @@ authors = [{ name = "Semantic Workbench Team" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ + "mcp-extensions[openai]>=0.1.0", "mcp>=1.2.1", "anthropic>=0.37.1", "beautifulsoup4>=4.12.3", @@ -57,6 +58,9 @@ packages = ["mcp_server"] [tool.uv] package = true +[tool.uv.sources] +mcp-extensions = { path = "../../libraries/python/mcp-extensions", editable = true } + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/mcp-servers/mcp-server-open-deep-research/uv.lock b/mcp-servers/mcp-server-open-deep-research/uv.lock index 4bdceea2..b988159e 100644 --- a/mcp-servers/mcp-server-open-deep-research/uv.lock +++ b/mcp-servers/mcp-server-open-deep-research/uv.lock @@ -453,6 +453,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/84/0df6c5981f5fc722381662ff8cfbdf8aad64bec875f75d80b55bfef394ce/datasets-3.2.0-py3-none-any.whl", hash = "sha256:f3d2ba2698b7284a4518019658596a6a8bc79f31e51516524249d6c59cf0fe2a", size = 480647 }, ] +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475 }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -966,6 +975,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/0d/6770742a84c8aa1d36c0d628896a380584c5759612e66af7446af07d8775/mcp-1.2.1-py3-none-any.whl", hash = "sha256:579bf9c9157850ebb1344f3ca6f7a3021b0123c44c9f089ef577a7062522f0fd", size = 66453 }, ] +[[package]] +name = "mcp-extensions" +version = "0.1.0" +source = { editable = "../../libraries/python/mcp-extensions" } +dependencies = [ + { name = "deepmerge" }, + { name = "mcp" }, +] + +[package.optional-dependencies] +openai = [ + { name = "openai" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepmerge", specifier = ">=2.0" }, + { name = "mcp", specifier = ">=1.2.1" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.63.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=8.3.1" }, +] + [[package]] name = "mcp-server-open-deep-research" version = "0.1.0" @@ -982,6 +1018,7 @@ dependencies = [ { name = "mammoth" }, { name = "markdownify" }, { name = "mcp" }, + { name = "mcp-extensions", extra = ["openai"] }, { name = "numexpr" }, { name = "numpy" }, { name = "openai" }, @@ -1031,6 +1068,7 @@ requires-dist = [ { name = "mammoth", specifier = ">=1.8.0" }, { name = "markdownify", specifier = ">=0.13.1" }, { name = "mcp", specifier = ">=1.2.1" }, + { name = "mcp-extensions", extras = ["openai"], editable = "../../libraries/python/mcp-extensions" }, { name = "numexpr", specifier = ">=2.10.1" }, { name = "numpy", specifier = ">=2.1.2" }, { name = "openai", specifier = ">=1.52.2" }, @@ -1372,7 +1410,7 @@ wheels = [ [[package]] name = "openai" -version = "1.62.0" +version = "1.63.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1384,9 +1422,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/82/163797f1ebfc7effd23e942fd4e52e023e8f9e0d6895e838c62c7737311d/openai-1.62.0.tar.gz", hash = "sha256:ef3f6864ae2f75fa6296bc9811acf684b95557fcb611fe95734215a8b9150b43", size = 352798 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/0f/c8f8d3b0c313d9ee0539941eb38fd549ba3b776f0daf19a3fb6e4aeac247/openai-1.62.0-py3-none-any.whl", hash = "sha256:dcb7f9fb4fbc3f27e3ffd2d7bf045be9211510d7fafefcef7ad2302cb27484e0", size = 464814 }, + { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, ] [[package]] diff --git a/mcp-servers/mcp-server-template/template/{{ project_slug }}/mcp_server/start.py b/mcp-servers/mcp-server-template/template/{{ project_slug }}/mcp_server/start.py index cb10911a..55ee00dc 100644 --- a/mcp-servers/mcp-server-template/template/{{ project_slug }}/mcp_server/start.py +++ b/mcp-servers/mcp-server-template/template/{{ project_slug }}/mcp_server/start.py @@ -4,9 +4,10 @@ from .server import create_mcp_server + def main() -> None: # Command-line arguments for transport and port - parse_args = argparse.ArgumentParser(description=f"Start the MCP server.") + parse_args = argparse.ArgumentParser(description="Start the MCP server.") parse_args.add_argument( "--transport", default="stdio", @@ -27,5 +28,6 @@ def main() -> None: mcp.run(transport=args.transport) + if __name__ == "__main__": main() diff --git a/mcp-servers/mcp-server-vscode/.vscode/settings.json b/mcp-servers/mcp-server-vscode/.vscode/settings.json index ef81f53c..2c4e60ce 100644 --- a/mcp-servers/mcp-server-vscode/.vscode/settings.json +++ b/mcp-servers/mcp-server-vscode/.vscode/settings.json @@ -25,24 +25,6 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, - "python.analysis.autoFormatStrings": true, - "python.analysis.autoImportCompletions": true, - "python.analysis.diagnosticMode": "workspace", - "python.analysis.fixAll": ["source.unusedImports"], - "python.analysis.inlayHints.functionReturnTypes": true, - "python.analysis.typeCheckingMode": "standard", - "python.defaultInterpreterPath": "${workspaceFolder}/.venv", - "[python]": { - "editor.defaultFormatter": "charliermarsh.ruff", - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.unusedImports": "explicit", - "source.organizeImports": "explicit", - "source.formatDocument": "explicit" - } - }, - "ruff.nativeServer": "on", "search.exclude": { "out": true, // set this to false to include "out" folder in search results "dist": true // set this to false to include "dist" folder in search results diff --git a/semantic-workbench.code-workspace b/semantic-workbench.code-workspace index 8a0fcf32..215a27c9 100644 --- a/semantic-workbench.code-workspace +++ b/semantic-workbench.code-workspace @@ -75,10 +75,6 @@ "name": "assistants:skill-assistant", "path": "assistants/skill-assistant" }, - // { - // "name": "libraries:anthropic-client", - // "path": "libraries/python/anthropic-client" - // }, { "name": "libraries:assistant-drive", "path": "libraries/python/assistant-drive" @@ -99,6 +95,10 @@ "name": "libraries:guided-conversation", "path": "libraries/python/guided-conversation" }, + { + "name": "libraries:mcp-extensions", + "path": "libraries/python/mcp-extensions" + }, { "name": "libraries:openai-client", "path": "libraries/python/openai-client" @@ -153,6 +153,82 @@ } ], "settings": { - "markdown.validate.enabled": true + "markdown.validate.enabled": true, + "ruff.configuration": "${workspaceFolder}/ruff.toml", + "ruff.importStrategy": "useBundled", + "ruff.nativeServer": "on", + "better-comments.highlightPlainText": true, + "better-comments.multilineComments": true, + "better-comments.tags": [ + { + "tag": "!", + "color": "#FF2D00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "?", + "color": "#3498DB", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "//", + "color": "#474747", + "strikethrough": true, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "todo", + "color": "#FF8C00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "fixme", + "color": "#FF2D00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "*", + "color": "#98C379", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + } + ] + }, + "extensions": { + "recommendations": [ + "aaron-bond.better-comments", + "charliermarsh.ruff", + "dbaeumer.vscode-eslint", + "epivision.vscode-file-header", + "esbenp.prettier-vscode", + "ms-python.debugpy", + "ms-python.python", + "ms-vscode.makefile-tools", + "ms-vscode.vscode-node-azure-pack", + "tamasfe.even-better-toml", + "streetsidesoftware.code-spell-checker" + ] } -} \ No newline at end of file +} diff --git a/workbench-app/src/components/Conversations/InteractHistory.tsx b/workbench-app/src/components/Conversations/InteractHistory.tsx index b2b1ee41..4f2eacc4 100644 --- a/workbench-app/src/components/Conversations/InteractHistory.tsx +++ b/workbench-app/src/components/Conversations/InteractHistory.tsx @@ -126,11 +126,9 @@ export const InteractHistory: React.FC = (props) => { // this can happen temporarily if the provided conversation was just // re-retrieved, but the participants have not been re-retrieved yet return ( - <> -
- Participant not found: {message.sender.participantId} in conversation {conversation.id} -
- +
+ Participant not found: {message.sender.participantId} in conversation {conversation.id} +
); } diff --git a/workbench-app/src/components/Conversations/Message/InteractMessage.tsx b/workbench-app/src/components/Conversations/Message/InteractMessage.tsx index afcc4463..b34466db 100644 --- a/workbench-app/src/components/Conversations/Message/InteractMessage.tsx +++ b/workbench-app/src/components/Conversations/Message/InteractMessage.tsx @@ -98,7 +98,7 @@ interface InteractMessageProps { export const InteractMessage: React.FC = (props) => { const { conversation, message, participant, hideParticipant, displayDate, readOnly, onRead, onRewind } = props; const classes = useClasses(); - const { isMessageVisible, isUnread } = useConversationUtility(); + const { isMessageVisible, isMessageVisibleRef, isUnread } = useConversationUtility(); const isUser = participant.role === 'user'; const date = Utility.toFormattedDateString(message.timestamp, 'dddd, MMMM D'); @@ -130,7 +130,11 @@ export const InteractMessage: React.FC = (props) => { const body = ; - const footer = ; + const footer = ( +
+ +
+ ); const composedMessage = participant.role === 'assistant' ? (