Skip to content

Deployment

  • Go 1.25+
  • Docker
  • GCP project with Spanner, Memorystore for Valkey, Cloud Run
  • GOPRIVATE=github.com/MediDrive-Tech/* for private modules
  • GitHub PAT for building Docker images with private deps
Terminal window
docker build \
--build-arg GITHUB_PAT=$GITHUB_PAT \
--build-arg VERSION=$(git describe --tags --always) \
-t nemt-mcp-service:latest .
Terminal window
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg GITHUB_PAT=$GITHUB_PAT \
--build-arg VERSION=$(git describe --tags --always) \
-t gcr.io/$PROJECT_ID/nemt-mcp-service:latest \
--push .

All configuration is loaded through GCP Secret Manager.

Terminal window
gcloud run deploy nemt-mcp-service \
--image gcr.io/$PROJECT_ID/nemt-mcp-service:latest \
--region us-east1 \
--port 8080 \
--memory 512Mi \
--cpu 1 \
--min-instances 1 \
--max-instances 10 \
--set-secrets "\
SPANNER_INSTANCE=spanner-instance:latest,\
SPANNER_DATABASE=spanner-database:latest,\
SPANNER_NEMT_CORE_URL=spanner-nemt-core-url:latest,\
VALKEY_HOST=valkey-host:latest,\
VALKEY_PORT=valkey-port:latest,\
GOOGLE_MAPS_API_KEY=google-maps-api-key:latest,\
MCP_API_KEY_MAPPINGS=mcp-api-key-mappings:latest,\
MCP_HOST=mcp-host:latest,\
MCP_PORT=mcp-port:latest"
Variable Description
SPANNER_INSTANCE Spanner instance ID
SPANNER_DATABASE Spanner database name
SPANNER_NEMT_CORE_URL Full Spanner resource URL
VALKEY_HOST Valkey/Redis host
VALKEY_PORT Valkey/Redis port
GOOGLE_MAPS_API_KEY Google Maps geocoding API key
MCP_API_KEY_MAPPINGS Token-to-tenant mappings: token:org_id:client_name (comma-separated)
MCP_HOST HTTP server bind host (default 0.0.0.0)
MCP_PORT HTTP server bind port (default 8080)

The service accepts API keys via two headers:

Header Format Example
Authorization Bearer <token> Authorization: Bearer dev-test-key
X-API-Key <token> X-API-Key: dev-test-key

Authorization: Bearer is checked first; X-API-Key is the fallback. Both resolve the token against MCP_API_KEY_MAPPINGS.

The service sets permissive CORS headers for MCP Inspector and browser-based clients:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
  • Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, Mcp-Session-Id, Mcp-Protocol-Version
  • Access-Control-Expose-Headers: Mcp-Session-Id

The MCP SDK’s CrossOriginProtection is configured but bypassed by the CORS middleware for preflight (OPTIONS) requests, allowing the MCP Inspector to connect from any origin.

GET /health (no auth) returns component status:

{"status": "ok", "spanner": "ok", "valkey": "ok"}

Configure Cloud Run probes:

startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30

Cloud Run sends SIGTERM before killing the container. The service:

  1. Stops accepting new HTTP connections
  2. Drains in-flight requests (30s timeout)
  3. Closes Valkey connection
  4. Closes Spanner connection
  5. Exits cleanly

The service is designed for horizontal scaling with no session affinity required.

Setting Stateless: true in StreamableHTTPOptions tells the MCP SDK not to store transport-level sessions in memory. All application state is in Valkey:

State Key Pattern TTL
Sessions mcp:session:{session_id} 1h
Booking drafts mcp:draft:{session_id} 24h
Token cache mcp:token:{sha256_hash} 5min
Reference data mcp:ref:{org_id}:{lob_id}:* 1h
  • Min instances: Set --min-instances 1 to avoid cold starts
  • No session affinity: Unnecessary with Valkey-backed state
  • Connection pooling: The valkey-go client handles pooling automatically

All logs are JSON with fields: request_id, session_id, org_id, tool_name, method, path, status, duration.

Every MCP tool invocation logged with: action (mcp.{tool_name}), org_id, member_id, timestamp, outcome (allowed/denied).

Terminal window
# Lint
golangci-lint run ./...
# Vet
go vet ./...
# Tests with race detection
go test -race -count=1 ./...
# Build with trimpath
go build -trimpath -ldflags "-s -w -X .../internal/app.version=$TAG" ./cmd/server/