From 2561ea3d8b2d5002f20b6dcb55a57f9f3fac58f1 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Wed, 29 Jan 2025 17:34:19 -0800 Subject: [PATCH] adds sample sse-based mcp-server and client support in Codespace Assistant (#312) --- .../codespace-assistant/.vscode/settings.json | 2 + .../codespace-assistant/assistant/config.py | 13 + .../extensions/tools/__mcp_server_configs.py | 5 + .../extensions/tools/__mcp_server_utils.py | 52 ++- .../extensions/tools/__mcp_tool_utils.py | 71 ++-- .../assistant/extensions/tools/__model.py | 20 + .../assistant/response/completion_handler.py | 7 +- .../assistant/response/response.py | 10 +- .../assistant/response/step_handler.py | 9 +- mcp-servers/mcp-server-giphy/.env.example | 1 + mcp-servers/mcp-server-giphy/.gitignore | 38 ++ .../mcp-server-giphy/.vscode/launch.json | 15 + .../mcp-server-giphy/.vscode/settings.json | 52 +++ mcp-servers/mcp-server-giphy/Makefile | 3 + mcp-servers/mcp-server-giphy/README.md | 63 +++ mcp-servers/mcp-server-giphy/pyproject.toml | 21 + .../mcp-server-giphy/server/__init__.py | 1 + .../mcp-server-giphy/server/giphy_search.py | 25 ++ mcp-servers/mcp-server-giphy/server/main.py | 61 +++ .../mcp-server-giphy/server/sampling.py | 24 ++ mcp-servers/mcp-server-giphy/uv.lock | 391 ++++++++++++++++++ semantic-workbench.code-workspace | 4 + 22 files changed, 825 insertions(+), 63 deletions(-) create mode 100644 mcp-servers/mcp-server-giphy/.env.example create mode 100644 mcp-servers/mcp-server-giphy/.gitignore create mode 100644 mcp-servers/mcp-server-giphy/.vscode/launch.json create mode 100644 mcp-servers/mcp-server-giphy/.vscode/settings.json create mode 100644 mcp-servers/mcp-server-giphy/Makefile create mode 100644 mcp-servers/mcp-server-giphy/README.md create mode 100644 mcp-servers/mcp-server-giphy/pyproject.toml create mode 100644 mcp-servers/mcp-server-giphy/server/__init__.py create mode 100644 mcp-servers/mcp-server-giphy/server/giphy_search.py create mode 100644 mcp-servers/mcp-server-giphy/server/main.py create mode 100644 mcp-servers/mcp-server-giphy/server/sampling.py create mode 100644 mcp-servers/mcp-server-giphy/uv.lock diff --git a/assistants/codespace-assistant/.vscode/settings.json b/assistants/codespace-assistant/.vscode/settings.json index 33df7830..6fdd0c2b 100644 --- a/assistants/codespace-assistant/.vscode/settings.json +++ b/assistants/codespace-assistant/.vscode/settings.json @@ -51,12 +51,14 @@ "cSpell.words": [ "Codespaces", "contentsafety", + "debugpy", "deepmerge", "devcontainer", "dotenv", "endregion", "Excalidraw", "fastapi", + "GIPHY", "jsonschema", "Langchain", "modelcontextprotocol", diff --git a/assistants/codespace-assistant/assistant/config.py b/assistants/codespace-assistant/assistant/config.py index 94cfa8e7..32aa278f 100644 --- a/assistants/codespace-assistant/assistant/config.py +++ b/assistants/codespace-assistant/assistant/config.py @@ -76,6 +76,7 @@ class AssistantConfigModel(BaseModel): - Create core files and folders for the project as needed, such as README.md, .gitignore, etc. - Create language specific files and folders as needed, such as package.json, pyproject.toml, etc. + - Files should include a newline at the end of the file. - Provide instruction for the user on installing dependencies via cli instead of writing these directly to the project files, this will ensure the user has the most up-to-date versions. - Offer to keep the README and other documentation up-to-date with the latest project information, if @@ -86,6 +87,18 @@ class AssistantConfigModel(BaseModel): - Use 'pnpm' for managing dependencies (do not use 'npm' or 'yarn') - It is ok to update '.vscode' folder contents and 'package.json' scripts as needed for adding run and debug configurations, but do not add or remove any other files or folders. + - Consider the following strategy to improve approachability for both + developers and any AI assistants: + - Modularity and Conciseness: Each code file should not exceed one page in length, ensuring concise + and focused code. When a file exceeds one page, consider breaking it into smaller, more focused + files. Individual functions should be easily readable, wrapping larger blocks of code in functions + with clear names and purposes. + - Semantic Names: Use meaningful names for functions and modules to enhance understanding and + maintainability. These names will also be used for semantic searches by the AI assistant. + - Organized Structure: Maintain a well-organized structure, breaking down functionality into clear + and manageable components. + - Update Documentation: Keep documentation, including code comments, up-to-date with the latest + project information. Ultimately, however, the user is in control of the project and can override the above guidance as needed. """).strip() diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_configs.py b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_configs.py index b01bb2e0..09c7d701 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_configs.py +++ b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_configs.py @@ -44,6 +44,11 @@ def get_mcp_server_configs(tools_config: ToolsConfigModel) -> List[MCPServerConf b) Store facts about them as observations """), ), + MCPServerConfig( + name="GIPHY MCP Server", + command="http://127.0.0.1:6000/sse", + args=[], + ), # MCPServerConfig( # name="Sequential Thinking MCP Server", # command="npx", diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py index aeecee02..44e6542d 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py +++ b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_server_utils.py @@ -3,10 +3,11 @@ from typing import AsyncIterator, List, Optional from mcp import ClientSession +from mcp.client.sse import sse_client from mcp.client.stdio import StdioServerParameters, stdio_client from .__mcp_server_configs import get_mcp_server_configs -from .__model import MCPServerConfig, ToolsConfigModel +from .__model import MCPServerConfig, MCPSession, ToolsConfigModel logger = logging.getLogger(__name__) @@ -14,6 +15,17 @@ @asynccontextmanager 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: + yield client_session + else: + async with connect_to_mcp_server_stdio(server_config) as client_session: + yield client_session + + +@asynccontextmanager +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=server_config.env) try: @@ -21,27 +33,47 @@ async def connect_to_mcp_server(server_config: MCPServerConfig) -> AsyncIterator f"Attempting to connect to {server_config.name} 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 session: - await session.initialize() - yield session # Yield the session for use + 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.name}: {e}") yield None # Yield None if connection fails -async def establish_mcp_sessions(tools_config: ToolsConfigModel, stack: AsyncExitStack) -> List[ClientSession]: +@asynccontextmanager +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.name} with SSE transport: {server_config.command}") + async with sse_client(url=server_config.command, headers=server_config.env) 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.name}: {e}") + yield None + + +async def establish_mcp_sessions(tools_config: ToolsConfigModel, stack: AsyncExitStack) -> List[MCPSession]: """ Establish connections to MCP servers using the provided AsyncExitStack. """ - sessions: List[ClientSession] = [] + mcp_sessions: List[MCPSession] = [] for server_config in get_mcp_server_configs(tools_config): - session: ClientSession | None = await stack.enter_async_context(connect_to_mcp_server(server_config)) - if session: - sessions.append(session) + 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(name=server_config.name, 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 + mcp_sessions.append(mcp_session) else: logger.warning(f"Could not establish session with {server_config.name}") - return sessions + return mcp_sessions def get_mcp_server_prompts(tools_config: ToolsConfigModel) -> List[str]: diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py index bd20826e..32da8560 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py +++ b/assistants/codespace-assistant/assistant/extensions/tools/__mcp_tool_utils.py @@ -3,58 +3,51 @@ from typing import List import deepmerge -from mcp import ClientSession, Tool -from mcp.types import TextContent +from mcp import Tool +from mcp.types import EmbeddedResource, ImageContent, TextContent -from .__model import ToolCall, ToolCallResult, ToolMessageType +from .__model import MCPSession, ToolCall, ToolCallResult, ToolMessageType logger = logging.getLogger(__name__) -async def retrieve_tools_from_sessions(sessions: List[ClientSession]) -> List[Tool]: +def retrieve_tools_from_sessions(mcp_sessions: List[MCPSession]) -> List[Tool]: """ Retrieve tools from all MCP sessions. """ - all_tools: List[Tool] = [] - for session in sessions: - try: - tools_response = await session.list_tools() - tools = tools_response.tools - all_tools.extend(tools) - logger.debug(f"Retrieved tools from session: {[tool.name for tool in tools]}") - except Exception as e: - logger.exception(f"Error retrieving tools from session: {e}") - return all_tools + return [tool for mcp_session in mcp_sessions for tool in mcp_session.tools] async def handle_tool_call( - sessions: List[ClientSession], + mcp_sessions: List[MCPSession], tool_call: ToolCall, - all_mcp_tools: List[Tool], method_metadata_key: str, ) -> ToolCallResult: """ Handle the tool call by invoking the appropriate tool and returning a ToolCallResult. """ + # Initialize metadata metadata = {} - tool = next((t for t in all_mcp_tools if t.name == tool_call.name), None) - if not tool: + # Find the tool and session from the full collection of sessions + mcp_session, tool = next( + ( + (mcp_session, tool) + for mcp_session in mcp_sessions + for tool in mcp_session.tools + if tool.name == tool_call.name + ), + (None, None), + ) + if not mcp_session or not tool: return ToolCallResult( id=tool_call.id, - content=f"Tool '{tool_call.name}' not found.", + content=f"Tool '{tool_call.name}' not found in any of the sessions.", message_type=ToolMessageType.notice, metadata={}, ) - target_session = next( - (session for session in sessions if tool_call.name in [tool.name for tool in all_mcp_tools]), None - ) - - if not target_session: - raise ValueError(f"Tool '{tool_call.name}' not found in any of the sessions.") - # Update metadata with tool call details deepmerge.always_merger.merge( metadata, @@ -69,15 +62,17 @@ async def handle_tool_call( # Initialize tool_result tool_result = None + tool_output: list[TextContent | ImageContent | EmbeddedResource] = [] + content_items: List[str] = [] # Invoke the tool try: - logger.debug(f"Invoking tool '{tool_call.name}' with arguments: {tool_call.arguments}") - tool_result = await target_session.call_tool(tool_call.name, tool_call.arguments) - tool_output = tool_result.content[0] if tool_result.content else "" + logger.debug(f"Invoking '{mcp_session.name}.{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}") - tool_output = f"An error occurred while executing the tool '{tool_call.to_json()}': {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( @@ -91,18 +86,18 @@ async def handle_tool_call( }, ) - # Return the tool call result - content: str | None = None - if isinstance(tool_output, str): - content = tool_output - - if isinstance(tool_output, TextContent): - content = tool_output.text + 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=content or "Error executing tool, unsupported output type.", + content="\n\n".join(content_items), message_type=ToolMessageType.tool_result, metadata=metadata, ) diff --git a/assistants/codespace-assistant/assistant/extensions/tools/__model.py b/assistants/codespace-assistant/assistant/extensions/tools/__model.py index 36fae1fc..80e26547 100644 --- a/assistants/codespace-assistant/assistant/extensions/tools/__model.py +++ b/assistants/codespace-assistant/assistant/extensions/tools/__model.py @@ -1,12 +1,16 @@ import json +import logging from enum import StrEnum from textwrap import dedent from typing import Annotated, Any, List, Optional from attr import dataclass +from mcp import ClientSession, Tool from pydantic import BaseModel, Field from semantic_workbench_assistant.config import UISchema +logger = logging.getLogger(__name__) + @dataclass class MCPServerConfig: @@ -17,6 +21,22 @@ class MCPServerConfig: prompt: Optional[str] = None +class MCPSession: + name: str + client_session: ClientSession + tools: List[Tool] = [] + + def __init__(self, name: str, client_session: ClientSession) -> None: + self.name = name + self.client_session = client_session + + async def initialize(self) -> None: + # Load all tools from the session, later we can do the same for resources, prompts, etc. + tools_result = await self.client_session.list_tools() + self.tools = tools_result.tools + logger.debug(f"Loaded {len(tools_result.tools)} tools from session '{self.name}'") + + @dataclass class ToolCall: id: str diff --git a/assistants/codespace-assistant/assistant/response/completion_handler.py b/assistants/codespace-assistant/assistant/response/completion_handler.py index 0626b3c2..1a5ead55 100644 --- a/assistants/codespace-assistant/assistant/response/completion_handler.py +++ b/assistants/codespace-assistant/assistant/response/completion_handler.py @@ -6,7 +6,6 @@ import deepmerge import openai_client -from mcp import ClientSession, Tool from openai.types.chat import ( ChatCompletion, ChatCompletionToolMessageParam, @@ -18,6 +17,8 @@ ) from semantic_workbench_assistant.assistant_app import ConversationContext +from assistant.extensions.tools.__model import MCPSession + from ..config import AssistantConfigModel from ..extensions.tools import ( ToolCall, @@ -36,8 +37,7 @@ async def handle_completion( step_result: StepResult, completion: ParsedChatCompletion | ChatCompletion, - mcp_sessions: List[ClientSession], - mcp_tools: List[Tool], + mcp_sessions: List[MCPSession], context: ConversationContext, config: AssistantConfigModel, silence_token: str, @@ -166,7 +166,6 @@ async def handle_error(error_message: str) -> StepResult: tool_call_result = await handle_tool_call( mcp_sessions, tool_call, - mcp_tools, f"{metadata_key}:request:tool_call_{tool_call_count}", ) except Exception as e: diff --git a/assistants/codespace-assistant/assistant/response/response.py b/assistants/codespace-assistant/assistant/response/response.py index 27c04add..23dd9109 100644 --- a/assistants/codespace-assistant/assistant/response/response.py +++ b/assistants/codespace-assistant/assistant/response/response.py @@ -3,18 +3,18 @@ from typing import Any, List from assistant_extensions.attachments import AttachmentsExtension -from mcp import ClientSession from semantic_workbench_api_model.workbench_model import ( MessageType, NewConversationMessage, ) from semantic_workbench_assistant.assistant_app import ConversationContext +from assistant.extensions.tools.__model import MCPSession + from ..config import AssistantConfigModel from ..extensions.tools import ( establish_mcp_sessions, get_mcp_server_prompts, - retrieve_tools_from_sessions, ) from .step_handler import next_step @@ -34,7 +34,7 @@ async def respond_to_conversation( async with AsyncExitStack() as stack: # If tools are enabled, establish connections to the MCP servers - mcp_sessions: List[ClientSession] = [] + mcp_sessions: List[MCPSession] = [] if config.extensions_config.tools.enabled: mcp_sessions = await establish_mcp_sessions(config.extensions_config.tools, stack) if not mcp_sessions: @@ -50,9 +50,6 @@ async def respond_to_conversation( # Retrieve prompts from the MCP servers mcp_prompts = get_mcp_server_prompts(config.extensions_config.tools) - # Retrieve tools from the MCP sessions - mcp_tools = await retrieve_tools_from_sessions(mcp_sessions) - # Initialize a loop control variable max_steps = config.extensions_config.tools.max_steps interrupted = False @@ -77,7 +74,6 @@ async def respond_to_conversation( step_result = await next_step( mcp_sessions=mcp_sessions, - mcp_tools=mcp_tools, mcp_prompts=mcp_prompts, attachments_extension=attachments_extension, context=context, diff --git a/assistants/codespace-assistant/assistant/response/step_handler.py b/assistants/codespace-assistant/assistant/response/step_handler.py index 20e7bfa0..8f8bf967 100644 --- a/assistants/codespace-assistant/assistant/response/step_handler.py +++ b/assistants/codespace-assistant/assistant/response/step_handler.py @@ -5,7 +5,6 @@ import deepmerge import openai_client from assistant_extensions.attachments import AttachmentsExtension -from mcp import ClientSession, Tool from openai.types.chat import ( ChatCompletion, ParsedChatCompletion, @@ -16,6 +15,9 @@ ) from semantic_workbench_assistant.assistant_app import ConversationContext +from assistant.extensions.tools.__mcp_tool_utils import retrieve_tools_from_sessions +from assistant.extensions.tools.__model import MCPSession + from ..config import AssistantConfigModel from .completion_handler import handle_completion from .models import StepResult @@ -29,8 +31,7 @@ async def next_step( - mcp_sessions: List[ClientSession], - mcp_tools: List[Tool], + mcp_sessions: List[MCPSession], mcp_prompts: List[str], attachments_extension: AttachmentsExtension, context: ConversationContext, @@ -94,6 +95,7 @@ async def handle_error(error_message: str, error_debug: dict[str, Any] | None = completion: ParsedChatCompletion | ChatCompletion | None = None # convert the tools to make them compatible with the OpenAI API + mcp_tools = retrieve_tools_from_sessions(mcp_sessions) tools = convert_mcp_tools_to_openai_tools(mcp_tools) # update the metadata with debug information @@ -148,7 +150,6 @@ async def handle_error(error_message: str, error_debug: dict[str, Any] | None = step_result, completion, mcp_sessions, - mcp_tools, context, config, silence_token, diff --git a/mcp-servers/mcp-server-giphy/.env.example b/mcp-servers/mcp-server-giphy/.env.example new file mode 100644 index 00000000..1590e5da --- /dev/null +++ b/mcp-servers/mcp-server-giphy/.env.example @@ -0,0 +1 @@ +GIPHY_API_KEY=REPLACE_WITH_YOUR_GIPHY_API_KEY \ No newline at end of file diff --git a/mcp-servers/mcp-server-giphy/.gitignore b/mcp-servers/mcp-server-giphy/.gitignore new file mode 100644 index 00000000..59bccd24 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/.gitignore @@ -0,0 +1,38 @@ +# Python files +__pycache__/ +*.py[cod] + +# Virtual environment +.venv + +# Poetry +poetry.lock + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache + +# Environment variables +.env diff --git a/mcp-servers/mcp-server-giphy/.vscode/launch.json b/mcp-servers/mcp-server-giphy/.vscode/launch.json new file mode 100644 index 00000000..ddcb7bfd --- /dev/null +++ b/mcp-servers/mcp-server-giphy/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "mcp-servers: mcp-server-giphy", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/server/main.py", + "args": ["--transport", "sse", "--port", "6000"], + "console": "integratedTerminal", + "consoleName": "mcp-server-giphy" + // "justMyCode": false // Set to false to debug external libraries + } + ] +} diff --git a/mcp-servers/mcp-server-giphy/.vscode/settings.json b/mcp-servers/mcp-server-giphy/.vscode/settings.json new file mode 100644 index 00000000..d3bf8555 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/.vscode/settings.json @@ -0,0 +1,52 @@ +{ + "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, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "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": { + "**/.venv": true, + "**/.data": true + }, + // For use with optional extension: "streetsidesoftware.code-spell-checker" + "cSpell.ignorePaths": [ + ".venv", + "node_modules", + "package-lock.json", + "settings.json", + "uv.lock" + ], + "cSpell.words": ["debugpy", "dotenv", "fastmcp", "GIPHY", "pyright"] +} diff --git a/mcp-servers/mcp-server-giphy/Makefile b/mcp-servers/mcp-server-giphy/Makefile new file mode 100644 index 00000000..b6fe52b3 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/Makefile @@ -0,0 +1,3 @@ +repo_root = $(shell git rev-parse --show-toplevel) +include $(repo_root)/tools/makefiles/python.mk +include $(repo_root)/tools/makefiles/docker-assistant.mk diff --git a/mcp-servers/mcp-server-giphy/README.md b/mcp-servers/mcp-server-giphy/README.md new file mode 100644 index 00000000..3fc5faa4 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/README.md @@ -0,0 +1,63 @@ +# MCP GIPHY Server + +## Overview + +This project implements a Model Context Protocol (MCP) server integration with the GIPHY API. The server allows an assistant to submit recent chat history ('context') and a search term to retrieve a list of candidate results from GIPHY and load their image data. + +## Features + +- **Search and Retrieve:** Uses the GIPHY API to search for images based on a provided search term. +- **Sampling:** Employs the MCP "sampling" feature to request the assistant to choose the most appropriate image for the context. +- **Integration:** The selected GIPHY image and its metadata are returned to the original assistant tool call, enabling it to be included in assistant responses to users. + +## Development Principles + +- **Modularity and Conciseness:** Each code file should not exceed one page in length, ensuring concise and focused code. +- **Semantic Names:** Use meaningful names for functions and modules to enhance understanding and maintainability. +- **Organized Structure:** Maintain a well-organized structure, breaking down functionality into clear and manageable components. + +## Project Structure + +``` +/mcp-server-giphy +│ +├── README.md +├── server +│ ├── __init__.py +│ ├── main.py +│ ├── giphy_search.py +│ ├── sampling.py +│ └── response_handling.py +``` + +## Plan + +1. **Initialize Project Structure:** Create modules and stub functions. +2. **Giphy Search Implementation:** Implement the search functionality using the GIPHY API. +3. **Sampling Feature:** Develop the sampling mechanism to choose an image. +4. **Response Handling:** Ensure the proper integration and response to the assistant tool call. + +## Client Configuration + +To use this MCP server in your setup, consider the following configuration: + +```json +{ + "mcpServers": { + "giphy-server": { + "command": "uv", + "args": [ + "--directory", + "/path/to/project/mcp-server-giphy", + "run", + "server/main.py" + ], + "env": { + "GIPHY_API_KEY": "YOUR_GIPHY_API_KEY" + } + } + } +} +``` + +Ensure that `GIPHY_API_KEY` is set in your environment so that the server can authenticate with the GIPHY API. diff --git a/mcp-servers/mcp-server-giphy/pyproject.toml b/mcp-servers/mcp-server-giphy/pyproject.toml new file mode 100644 index 00000000..5a4a4e00 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "mcp-server-giphy" +version = "0.1.0" +description = "An MCP server integration with the GIPHY API" +authors = [{ name = "Semantic Workbench Team" }] +readme = "README.md" +requires-python = ">=3.11" +dependencies = ["mcp>=1.2.1", "requests>=2.25"] + +[tool.hatch.build.targets.wheel] +packages = ["server"] + +[tool.uv] +package = true + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = ["pyright>=1.1.389"] diff --git a/mcp-servers/mcp-server-giphy/server/__init__.py b/mcp-servers/mcp-server-giphy/server/__init__.py new file mode 100644 index 00000000..ee1d2f98 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/server/__init__.py @@ -0,0 +1 @@ +# Init file for MCP GIPHY Server diff --git a/mcp-servers/mcp-server-giphy/server/giphy_search.py b/mcp-servers/mcp-server-giphy/server/giphy_search.py new file mode 100644 index 00000000..310e48e1 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/server/giphy_search.py @@ -0,0 +1,25 @@ +# Giphy Search Functionality + +import os +from typing import List + +import requests +from pydantic import BaseModel + + +class GiphyResponse(BaseModel): + data: List[dict] + + +def perform_search(context, search_term): + """Perform a search on GIPHY using the context and search term.""" + api_key = os.getenv("GIPHY_API_KEY") # Retrieve the GIPHY API Key from the environment + if not api_key: + raise ValueError("GIPHY_API_KEY not set in the environment") + + giphy_url = f"https://api.giphy.com/v1/gifs/search?api_key={api_key}&q={search_term}&limit=5" + response = requests.get(giphy_url) + if response.status_code == 200: + return GiphyResponse(**response.json()).data + else: + raise Exception("Failed to retrieve Giphy results.") diff --git a/mcp-servers/mcp-server-giphy/server/main.py b/mcp-servers/mcp-server-giphy/server/main.py new file mode 100644 index 00000000..8b15e152 --- /dev/null +++ b/mcp-servers/mcp-server-giphy/server/main.py @@ -0,0 +1,61 @@ +# Main entry point for the MCP GIPHY Server + +import argparse +from typing import Optional + +from dotenv import load_dotenv +from mcp.server.fastmcp import FastMCP + +from server.giphy_search import perform_search + +# Load environment variables from .env +load_dotenv() + +# Command-line arguments for transport and port +parser = argparse.ArgumentParser(description="Run the MCP GIPHY Server.") +parser.add_argument( + "--transport", + default="stdio", + choices=["stdio", "sse"], + help="Transport protocol to use ('stdio' or 'sse'). Default is 'stdio'.", +) +parser.add_argument("--port", type=int, default=8000, help="Port to use for SSE (default is 8000).") + +# Initialize FastMCP with debug logging. +mcp = FastMCP(name="Giphy MCP Server", log_level="DEBUG") + +# Define each tool and its setup. + + +@mcp.tool() +async def giphy_search_tool(context: str, search_term: str) -> Optional[list]: + # Perform search using context and search term + search_results = perform_search(context, search_term) + + # Sampling isn't implemented in FastMCP yet, so we'll need to extend it. + # For now, just return a simplified list. + + return [ + { + "title": result["title"], + "alt_text": result["alt_text"], + "image": result["images"]["original"], + } + for result in search_results + ] + + # # Create sampling request message, integrating search results and context + # sampling_result = await perform_sampling(search_results, context) + + # # Extract and return image selected by sampling + # final_image = next( + # (content for content in sampling_result if content['type'] == "image"), None) + # return final_image["data"] if final_image else None + + +if __name__ == "__main__": + args = parser.parse_args() + if args.transport == "sse": + mcp.settings.port = args.port + + mcp.run(transport=args.transport) diff --git a/mcp-servers/mcp-server-giphy/server/sampling.py b/mcp-servers/mcp-server-giphy/server/sampling.py new file mode 100644 index 00000000..8b01fdfd --- /dev/null +++ b/mcp-servers/mcp-server-giphy/server/sampling.py @@ -0,0 +1,24 @@ +# Sampling Functionality + +from typing import Dict, List + + +async def perform_sampling(search_results: List[Dict], context: str) -> List[Dict]: + """Perform sampling using search results and context.""" + + # sampling_request = { + # "messages": [ + # {"role": "user", "content": {"type": "text", "text": f"context: {context}"}}, + # *[ + # {"role": "assistant", "content": {"type": "image", "data": result["data"], "mimeType": "image/gif"}} + # for result in search_results + # ], + # ], + # "systemPrompt": "Choose the most fitting image based on user context and search results.", + # "includeContext": "none", + # "maxTokens": 100, + # } + + # Placeholder for client interaction; actual implementation needed here + sampling_result = [] # Replace with actual sample call + return sampling_result diff --git a/mcp-servers/mcp-server-giphy/uv.lock b/mcp-servers/mcp-server-giphy/uv.lock new file mode 100644 index 00000000..c8a7b0db --- /dev/null +++ b/mcp-servers/mcp-server-giphy/uv.lock @@ -0,0 +1,391 @@ +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 = "2024.12.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[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 = "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 = "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-server-giphy" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "mcp" }, + { name = "requests" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, +] + +[package.metadata] +requires-dist = [ + { name = "mcp", specifier = ">=1.2.1" }, + { name = "requests", specifier = ">=2.25" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pyright", specifier = ">=1.1.389" }] + +[[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 = "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.393" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/c1/aede6c74e664ab103673e4f1b7fd3d058fef32276be5c43572f4067d4a8e/pyright-1.1.393.tar.gz", hash = "sha256:aeeb7ff4e0364775ef416a80111613f91a05c8e01e58ecfefc370ca0db7aed9c", size = 3790430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/47/f0dd0f8afce13d92e406421ecac6df0990daee84335fc36717678577d3e0/pyright-1.1.393-py3-none-any.whl", hash = "sha256:8320629bb7a44ca90944ba599390162bf59307f3d9fb6e27da3b7011b8c17ae5", size = 5646057 }, +] + +[[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 = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[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 = "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 = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[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/semantic-workbench.code-workspace b/semantic-workbench.code-workspace index 459175be..342addcd 100644 --- a/semantic-workbench.code-workspace +++ b/semantic-workbench.code-workspace @@ -147,6 +147,10 @@ "name": "libraries:skills:skill-library", "path": "libraries/python/skills/skill-library" }, + { + "name": "mcp-servers:mcp-server-giphy", + "path": "mcp-servers/mcp-server-giphy" + }, { "name": "tools", "path": "tools"