Skip to content

A secure, production-ready reference architecture for deploying MCP servers to GKE using Workload Identity Federation (WIF) and GitHub Actions. Now with MCP-UI support for interactive weather displays.

Notifications You must be signed in to change notification settings

ettysekhon/mcp-server-gke

Repository files navigation

MCP Server on GKE with Workload Identity Federation (WIF)

CI/CD

Overview

A proof-of-concept demonstrating:

  1. MCP on Kubernetes - Running Model Context Protocol servers on GKE
  2. Workload Identity Federation - Keyless authentication from GitHub Actions to GCP (no service account keys)
  3. MCP-UI - Rich interactive interfaces instead of raw JSON responses

Why MCP-UI?

Text is great for simple confirmations. It's terrible for weather forecasts, data tables, or anything requiring visual structure. MCP-UI lets the server return HTML interfaces that render directly in the chat window — the right output format at the right moment.

See docs/MCP_UI.md for implementation details.

Stack

Python 3.12 • Starlette • FastMCP • MCP-UI • GKE • GitHub Actions OIDC

Project Structure

├── .github/workflows/
│   └── deploy.yml     # CI/CD Pipeline definition
├── k8s/
│   └── deployment.yml # Kubernetes Deployment & Service manifests
├── src/
│   └── main.py        # Application entrypoint & logic
├── Dockerfile         # Multi-stage build instruction
├── pyproject.toml     # Dependencies managed by uv
└── uv.lock            # Exact versions for reproducibility

## Prerequisites

uv: Our chosen Python package manager. It replaces pip, poetry, and venv.

```bash
# macOS / Linux
curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh

Google Cloud CLI: For interaction with GCP resources.

 Docker: For local container builds and testing

Infrastructure Setup (Bootstrap) To provision the required infrastructure (GKE Cluster, Artifact Registry, and Workload Identity), run the following script in Google Cloud Shell.

Prerequisite: Ensure you have created a GCP Project and enabled Billing.

Provision Resources

Copy and paste this block into your Cloud Shell terminal. It will auto-detect the project ID and provision all necessary resources idempotently.

# --- CONFIGURATION ---
export PROJECT_ID=$(gcloud config get-value project)
export REGION="europe-west2"
export CLUSTER_NAME="mcp-cluster"
export REPO_NAME="mcp-repo"
export SA_NAME="github-actions-sa"
export POOL_NAME="github-pool-v2"
export PROVIDER_NAME="github-provider-v2"
export USER_GITHUB="ettysekhon"  # <--- VERIFY THIS MATCHES YOUR GITHUB USER/ORG

echo "Provisioning Infrastructure for: $PROJECT_ID"

# 1. Enable APIs
gcloud services enable container.googleapis.com artifactregistry.googleapis.com iamcredentials.googleapis.com

# 2. Create Artifact Registry
if ! gcloud artifacts repositories describe $REPO_NAME --location=$REGION > /dev/null 2>&1; then
    gcloud artifacts repositories create $REPO_NAME --repository-format=docker --location=$REGION --description="Docker repository for MCP Server"
fi

# 3. Create GKE Cluster
if ! gcloud container clusters describe $CLUSTER_NAME --location=$REGION > /dev/null 2>&1; then
    echo "Creating GKE Cluster (approx 5-8 mins)..."
    gcloud container clusters create-auto $CLUSTER_NAME --location=$REGION
fi

# 4. Configure Identity & Security
if ! gcloud iam service-accounts describe "$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" > /dev/null 2>&1; then
    gcloud iam service-accounts create $SA_NAME --display-name="GitHub Actions Deployer"
fi

# Grant Roles
gcloud artifacts repositories add-iam-policy-binding $REPO_NAME --location=$REGION --member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" --role="roles/artifactregistry.writer"
gcloud projects add-iam-policy-binding $PROJECT_ID --member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" --role="roles/container.developer"

# Setup Workload Identity Federation
if ! gcloud iam workload-identity-pools describe $POOL_NAME --location="global" > /dev/null 2>&1; then
    gcloud iam workload-identity-pools create $POOL_NAME --location="global" --display-name="GitHub Actions Pool V2"
fi

if ! gcloud iam workload-identity-pools providers describe $PROVIDER_NAME --location="global" --workload-identity-pool=$POOL_NAME > /dev/null 2>&1; then
    gcloud iam workload-identity-pools providers create-oidc $PROVIDER_NAME --location="global" --workload-identity-pool=$POOL_NAME --display-name="GitHub Provider" --issuer-uri="[https://token.actions.githubusercontent.com](https://token.actions.githubusercontent.com)" --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" --attribute-condition="assertion.repository_owner == '${USER_GITHUB}'"
fi

# Link GitHub Repo to Service Account
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
gcloud iam service-accounts add-iam-policy-binding "$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" --role="roles/iam.workloadIdentityUser" --member="principalSet://[iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_NAME/attribute.repository_owner/$](https://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_NAME/attribute.repository_owner/$){USER_GITHUB}"

echo ""
echo "SETUP COMPLETE. Add these to GitHub Secrets:"
echo "---------------------------------------------------"
echo "GCP_WIF_PROVIDER: projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_NAME/providers/$PROVIDER_NAME"
echo "GCP_WIF_SA:       $SA_NAME@$PROJECT_ID.iam.gserviceaccount.com"
echo "---------------------------------------------------"

Configure GitHub Secrets

Navigate to the GitHub Repository > Settings > Secrets and variables > Actions and add the two secrets outputted by the script above:

  • GCP_WIF_PROVIDER
  • GCP_WIF_SA

MCP Tools

The server exposes weather tools via the MCP protocol at /mcp. Tools return rich HTML interfaces via MCP-UI for beautiful weather displays in compatible clients.

get_weather

Get weather by coordinates with interactive UI display.

Parameter Type Description
latitude float Latitude (e.g., 51.5074 for London)
longitude float Longitude (e.g., -0.1278 for London)

get_weather_by_city

Get weather by city name with interactive UI display (uses geocoding).

Parameter Type Description
city string City name (e.g., "London", "New York")

get_weather_json

Get raw JSON weather data (for programmatic access).

Parameter Type Description
latitude float Latitude (e.g., 51.5074 for London)
longitude float Longitude (e.g., -0.1278 for London)

UI Response includes:

  • Current temperature with weather emoji icon
  • Weather description based on WMO codes
  • Humidity and wind speed
  • 7-day forecast cards with daily high/low temps and precipitation

Supported MCP-UI hosts: ChatGPT Apps SDK, Goose, LibreChat, MCP-UI Chat, Postman, ui-inspector

Development Workflow

git clone https://github.com/ettysekhon/mcp-server-gke.git
cd mcp-server-gke
uv sync
uv run uvicorn src.main:app --reload --port 8000

Endpoints

Endpoint Description
/ Service status and version
/health Health check for k8s probes
/mcp MCP server (Streamable HTTP transport)

 Security Model

I strictly avoided long-lived JSON Service Account keys.

  1. GitHub OIDC: GitHub signs a temporary token during the Action run.
  2. GCP Exchange: Google validates the token against the Workload Identity Pool.
  3. Impersonation: Google grants a short-lived access token for the github-actions-sa Service Account.
sequenceDiagram
    participant GH as GitHub Action
    participant GCP as Google Cloud (WIF)
    participant SA as Service Account
    
    GH->>GH: 1. Sign OIDC Token
    GH->>GCP: 2. Exchange Token
    GCP->>GCP: 3. Verify Repo & Owner
    GCP->>SA: 4. Grant Access
    SA->>GH: 5. Return Short-lived Key
    Note over GH, SA: No static keys stored!

Process

  1. Commit changes to main.
  2. The pipeline authenticates via WIF.
  3. Docker image is built and pushed to Artifact Registry (europe-west2).
  4. Kubernetes manifests in k8s/ are hydrated with the new image tag.
  5. Changes are applied to the GKE cluster.
  • Cluster Name: mcp-cluster
  • Region: europe-west2 (London)
  • Service Type: LoadBalancer (Exposes a Public IP)

 Troubleshooting

Pending External IP When a new Service is deployed, the LoadBalancer may take 1-3 minutes to provision a static IP. Monitor the process with:

kubectl get service mcp-server-service --watch

Testing the Deployed Service

Once you have the EXTERNAL-IP:

# Health check
curl http://<EXTERNAL-IP>/health

# Service status
curl http://<EXTERNAL-IP>/

Testing MCP Tools

MCP Inspector (JSON debugging)

Use MCP Inspector for raw protocol testing:

npx @modelcontextprotocol/inspector
  1. Set Transport Type to Streamable HTTP
  2. Enter URL: http://<EXTERNAL-IP>/mcp
  3. Click Connect
  4. Go to Tools tab to test get_weather and get_weather_by_city

MCP Inspector Tool Calls

UI Inspector (MCP-UI preview)

Use ui-inspector to see the rendered HTML weather cards:

git clone https://github.com/idosal/ui-inspector
cd ui-inspector
npm install
npm run dev

Then connect to http://localhost:8000/mcp (local) or http://<EXTERNAL-IP>/mcp (deployed).

Roadmap

  • MCP Protocol: Integrated FastMCP with Open-Meteo weather API
  • MCP-UI: Rich HTML weather displays via mcp-ui-server
  • Authentication: Implement API Key middleware for client security
  • Database: Configure private IP connectivity to Cloud SQL

About

A secure, production-ready reference architecture for deploying MCP servers to GKE using Workload Identity Federation (WIF) and GitHub Actions. Now with MCP-UI support for interactive weather displays.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published