Deployment
Prerequisites
Section titled “Prerequisites”- 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
Docker Build
Section titled “Docker Build”docker build \ --build-arg GITHUB_PAT=$GITHUB_PAT \ --build-arg VERSION=$(git describe --tags --always) \ -t nemt-mcp-service:latest .Multi-platform (Cloud Run ARM64)
Section titled “Multi-platform (Cloud Run ARM64)”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 .GCP Cloud Run
Section titled “GCP Cloud Run”All configuration is loaded through GCP Secret Manager.
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"Environment Variables
Section titled “Environment Variables”| 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) |
Authentication
Section titled “Authentication”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, OPTIONSAccess-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, Mcp-Session-Id, Mcp-Protocol-VersionAccess-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.
Health Check
Section titled “Health Check”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: 10livenessProbe: httpGet: path: /health port: 8080 periodSeconds: 30Graceful Shutdown
Section titled “Graceful Shutdown”Cloud Run sends SIGTERM before killing the container. The service:
- Stops accepting new HTTP connections
- Drains in-flight requests (30s timeout)
- Closes Valkey connection
- Closes Spanner connection
- Exits cleanly
Multi-Pod Deployment
Section titled “Multi-Pod Deployment”The service is designed for horizontal scaling with no session affinity required.
Stateless MCP Transport
Section titled “Stateless MCP Transport”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 |
Scaling Considerations
Section titled “Scaling Considerations”- Min instances: Set
--min-instances 1to avoid cold starts - No session affinity: Unnecessary with Valkey-backed state
- Connection pooling: The
valkey-goclient handles pooling automatically
Monitoring
Section titled “Monitoring”Structured Logs
Section titled “Structured Logs”All logs are JSON with fields: request_id, session_id, org_id, tool_name, method, path, status, duration.
HIPAA Audit Trail
Section titled “HIPAA Audit Trail”Every MCP tool invocation logged with: action (mcp.{tool_name}), org_id, member_id, timestamp, outcome (allowed/denied).
# Lintgolangci-lint run ./...
# Vetgo vet ./...
# Tests with race detectiongo test -race -count=1 ./...
# Build with trimpathgo build -trimpath -ldflags "-s -w -X .../internal/app.version=$TAG" ./cmd/server/