From b87a10b4b246c98f9a1f91ce30d23b94c94f0266 Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Tue, 11 Mar 2025 15:22:52 -0700 Subject: [PATCH] Deploys docker image for mcp-server-giphy Introduces mcp-server docker tooling and Dockerfile. Additionally updates the giphy server to remove some redundant GIPHY_API_KEY retrieval from environment --- .github/workflows/mcp-server-giphy.yml | 83 +++++++++++++++++++ mcp-servers/mcp-server-giphy/Makefile | 3 + .../mcp-server-giphy/mcp_server/__init__.py | 3 - .../mcp-server-giphy/mcp_server/config.py | 16 +--- .../mcp_server/giphy_search.py | 9 +- .../mcp-server-giphy/mcp_server/server.py | 2 +- tools/docker/Dockerfile.assistant | 1 + tools/docker/Dockerfile.mcp-server | 60 ++++++++++++++ tools/docker/docker-entrypoint.sh | 3 +- tools/makefiles/docker-mcp-server.mk | 22 +++++ 10 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/mcp-server-giphy.yml create mode 100644 tools/docker/Dockerfile.mcp-server create mode 100644 tools/makefiles/docker-mcp-server.mk diff --git a/.github/workflows/mcp-server-giphy.yml b/.github/workflows/mcp-server-giphy.yml new file mode 100644 index 000000000..d41dd29c4 --- /dev/null +++ b/.github/workflows/mcp-server-giphy.yml @@ -0,0 +1,83 @@ +name: mcp-server-giphy continuous integration + +on: + pull_request: + branches: ["main"] + paths: + - "mcp-servers/mcp-server-giphy/**" + - "libraries/python/**" + - "tools/docker/**" + - ".github/workflows/mcp-server-giphy.yml" + + push: + branches: ["main"] + paths: + - "mcp-servers/mcp-server-giphy/**" + - "libraries/python/**" + - "tools/docker/**" + - ".github/workflows/mcp-server-giphy.yml" + + workflow_dispatch: + +defaults: + run: + working-directory: mcp-servers/mcp-server-giphy + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + + - name: Set up Python 3.11 + run: uv python install 3.11 + + - name: test + run: | + make test + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: docker-build + run: | + make docker-build + + deploy: + runs-on: ubuntu-latest + environment: production + permissions: + id-token: write # for OIDC login + contents: read + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + needs: build + if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && vars.DEPLOYMENT_ENABLED == 'true' }} + env: + DOCKER_IMAGE_TAG: ${{ github.sha }} + DOCKER_REGISTRY_NAME: ${{ secrets.AZURE_CONTAINER_REGISTRY_NAME }} + AZURE_WEBSITE_RESOURCE_GROUP: ${{ secrets.AZURE_WEBSITE_RESOURCE_GROUP }} + AZURE_WEBSITE_SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + steps: + - uses: actions/checkout@v4 + + - uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: docker-push + run: | + make docker-push + + - name: docker-deploy + run: | + make docker-deploy diff --git a/mcp-servers/mcp-server-giphy/Makefile b/mcp-servers/mcp-server-giphy/Makefile index 1ad4520a2..546be8af5 100644 --- a/mcp-servers/mcp-server-giphy/Makefile +++ b/mcp-servers/mcp-server-giphy/Makefile @@ -1,2 +1,5 @@ repo_root = $(shell git rev-parse --show-toplevel) include $(repo_root)/tools/makefiles/python.mk +include $(repo_root)/tools/makefiles/docker-mcp-server.mk + +MCP_SERVER_MAIN_MODULE=mcp_server.start diff --git a/mcp-servers/mcp-server-giphy/mcp_server/__init__.py b/mcp-servers/mcp-server-giphy/mcp_server/__init__.py index b2ece5650..bf6bd6c59 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/__init__.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/__init__.py @@ -1,6 +1,3 @@ from dotenv import load_dotenv -from . import config -# Load environment variables from .env into the settings object. load_dotenv() -settings = config.Settings() diff --git a/mcp-servers/mcp-server-giphy/mcp_server/config.py b/mcp-servers/mcp-server-giphy/mcp_server/config.py index cfa05446d..4c35e5549 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/config.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/config.py @@ -1,16 +1,8 @@ -import os from pydantic_settings import BaseSettings -log_level = os.environ.get("LOG_LEVEL", "INFO") - -def load_required_env_var(env_var_name: str) -> str: - value = os.environ.get(env_var_name, "") - if not value: - raise ValueError(f"Missing required environment variable: {env_var_name}") - return value +class Settings(BaseSettings): + log_level: str = "INFO" + giphy_api_key: str = "" -giphy_api_key = load_required_env_var("GIPHY_API_KEY") -class Settings(BaseSettings): - log_level: str = log_level - giphy_api_key: str = giphy_api_key +settings = Settings() diff --git a/mcp-servers/mcp-server-giphy/mcp_server/giphy_search.py b/mcp-servers/mcp-server-giphy/mcp_server/giphy_search.py index 194e6d613..e08c2f876 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/giphy_search.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/giphy_search.py @@ -1,7 +1,7 @@ # Giphy Search Functionality -import os from typing import List, Optional +from .config import settings import requests from mcp.server.fastmcp import Context @@ -43,11 +43,10 @@ async def perform_search(context: str, search_term: str, ctx: Context) -> Option async def search(search_term: str) -> Optional[List[dict]]: - 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") + if not settings.giphy_api_key: + raise ValueError("Giphy API key is not set in the configuration.") - giphy_url = f"https://api.giphy.com/v1/gifs/search?api_key={api_key}&q={search_term}&limit=5" + giphy_url = f"https://api.giphy.com/v1/gifs/search?api_key={settings.giphy_api_key}&q={search_term}&limit=5" response = requests.get(giphy_url) if response.status_code == 200: return GiphyResponse(**response.json()).data diff --git a/mcp-servers/mcp-server-giphy/mcp_server/server.py b/mcp-servers/mcp-server-giphy/mcp_server/server.py index 7c12419f8..cc808dbe2 100644 --- a/mcp-servers/mcp-server-giphy/mcp_server/server.py +++ b/mcp-servers/mcp-server-giphy/mcp_server/server.py @@ -2,7 +2,7 @@ from mcp.server.fastmcp import Context, FastMCP -from . import settings +from .config import settings from .giphy_search import perform_search # Set the name of the MCP server diff --git a/tools/docker/Dockerfile.assistant b/tools/docker/Dockerfile.assistant index bb982529c..7c5a77a11 100644 --- a/tools/docker/Dockerfile.assistant +++ b/tools/docker/Dockerfile.assistant @@ -60,6 +60,7 @@ ENV ASSISTANT_APP=${app} ENV assistant__host=0.0.0.0 ENV assistant__port=3001 +ENV PYTHONUNBUFFERED=1 SHELL ["/bin/bash", "-c"] ENTRYPOINT ["/scripts/docker-entrypoint.sh"] diff --git a/tools/docker/Dockerfile.mcp-server b/tools/docker/Dockerfile.mcp-server new file mode 100644 index 000000000..b8939fb43 --- /dev/null +++ b/tools/docker/Dockerfile.mcp-server @@ -0,0 +1,60 @@ +# syntax=docker/dockerfile:1 + +# Dockerfile for mcp-servers +# Context root is expected to be the root of the repo +ARG python_image=python:3.11-slim + +# These build arguments will differ per mcp-server: +# package is the directory name of the mcp-server package under /mcp-servers +ARG package +ARG main_module + +FROM ${python_image} AS build + +ARG package + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +# copy all library packages +COPY ./libraries/python /packages/libraries/python +# copy the assistant package +COPY ./mcp-servers/${package} /packages/mcp-servers/mcp-server + +# install the mcp-server and dependencies to /.venv +RUN uv sync --directory /packages/mcp-servers/mcp-server --no-editable --no-dev --locked + +FROM ${python_image} + +ARG main_module + +# BEGIN: enable ssh in azure web app - comment out if not needed +######## +# install sshd and set password for root +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssh-server \ + && rm -rf /var/lib/apt/lists/* \ + && echo "root:Docker!" | chpasswd + +# azure sshd config +COPY ./tools/docker/azure_website_sshd.conf /etc/ssh/sshd_config +ENV SSHD_PORT=2222 +######## +# END: enable ssh in azure web app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gettext \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /packages/mcp-servers/mcp-server/.venv /packages/mcp-servers/mcp-server/.venv +ENV PATH=/packages/mcp-servers/mcp-server/.venv/bin:$PATH + +COPY ./tools/docker/docker-entrypoint.sh /scripts/docker-entrypoint.sh +RUN chmod +x /scripts/docker-entrypoint.sh + +ENV MAIN_MODULE=${main_module} +ENV PORT=3001 +ENV PYTHONUNBUFFERED=1 + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/scripts/docker-entrypoint.sh"] +CMD ["python", "-m", "${MAIN_MODULE}", "--transport", "sse", "--port", "${PORT}"] diff --git a/tools/docker/docker-entrypoint.sh b/tools/docker/docker-entrypoint.sh index 0dffb205c..af88314f1 100755 --- a/tools/docker/docker-entrypoint.sh +++ b/tools/docker/docker-entrypoint.sh @@ -7,4 +7,5 @@ if [ -n "${SSHD_PORT}" ]; then service ssh start fi -exec "$@" +cmd=$(echo "$@" | envsubst) +exec ${cmd} diff --git a/tools/makefiles/docker-mcp-server.mk b/tools/makefiles/docker-mcp-server.mk new file mode 100644 index 000000000..5c238023f --- /dev/null +++ b/tools/makefiles/docker-mcp-server.mk @@ -0,0 +1,22 @@ +repo_root = $(shell git rev-parse --show-toplevel) +mkfile_dir = $(patsubst %/,%,$(dir $(realpath $(lastword $(MAKEFILE_LIST))))) + +# the directory containing the mcp-servers's Makefile is expected to be +# the directory of the mcp-server +invoking_makefile_directory = $(notdir $(patsubst %/,%,$(dir $(realpath $(firstword $(MAKEFILE_LIST)))))) + +MCP_SERVER_PACKAGE ?= $(invoking_makefile_directory) +MCP_SERVER_IMAGE_NAME ?= $(subst -,_,$(invoking_makefile_directory)) +MCP_SERVER_MAIN_MODULE ?= $(MCP_SERVER_PACKAGE).start + +DOCKER_PATH = $(repo_root) +DOCKER_FILE = $(repo_root)/tools/docker/Dockerfile.mcp-server +DOCKER_BUILD_ARGS = main_module=$(MCP_SERVER_MAIN_MODULE) package=$(MCP_SERVER_PACKAGE) +DOCKER_IMAGE_NAME = $(MCP_SERVER_IMAGE_NAME) + +AZURE_WEBSITE_NAME ?= $(MCP_SERVER_PACKAGE)-service + +include $(mkfile_dir)/docker.mk + +docker-run-local: docker-build + docker run --rm -it --add-host=host.docker.internal:host-gateway $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)