A proof-of-concept demonstrating:
- MCP on Kubernetes - Running Model Context Protocol servers on GKE
- Workload Identity Federation - Keyless authentication from GitHub Actions to GCP (no service account keys)
- MCP-UI - Rich interactive interfaces instead of raw JSON responses
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.
Python 3.12 • Starlette • FastMCP • MCP-UI • GKE • GitHub Actions OIDC
├── .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.
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.
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 "---------------------------------------------------"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
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 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 name with interactive UI display (uses geocoding).
| Parameter | Type | Description |
|---|---|---|
city |
string | City name (e.g., "London", "New York") |
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
git clone https://github.com/ettysekhon/mcp-server-gke.git
cd mcp-server-gke
uv syncuv run uvicorn src.main:app --reload --port 8000| Endpoint | Description |
|---|---|
/ |
Service status and version |
/health |
Health check for k8s probes |
/mcp |
MCP server (Streamable HTTP transport) |
I strictly avoided long-lived JSON Service Account keys.
- GitHub OIDC: GitHub signs a temporary token during the Action run.
- GCP Exchange: Google validates the token against the Workload Identity Pool.
- 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
- Commit changes to main.
- The pipeline authenticates via WIF.
- Docker image is built and pushed to Artifact Registry (europe-west2).
- Kubernetes manifests in k8s/ are hydrated with the new image tag.
- Changes are applied to the GKE cluster.
- Cluster Name: mcp-cluster
- Region: europe-west2 (London)
- Service Type: LoadBalancer (Exposes a Public IP)
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 --watchOnce you have the EXTERNAL-IP:
# Health check
curl http://<EXTERNAL-IP>/health
# Service status
curl http://<EXTERNAL-IP>/Use MCP Inspector for raw protocol testing:
npx @modelcontextprotocol/inspector- Set Transport Type to
Streamable HTTP - Enter URL:
http://<EXTERNAL-IP>/mcp - Click Connect
- Go to Tools tab to test
get_weatherandget_weather_by_city
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 devThen connect to http://localhost:8000/mcp (local) or http://<EXTERNAL-IP>/mcp (deployed).
- 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
