This guide deploys Research MCP as a lightweight remote protocol/state service. The lab or GPU machine remains separate and continues to execute experiments through git pull/push.
ChatGPT / Claude
│ HTTPS + OAuth bearer token
▼
Google Cloud Run
Research MCP
min instances = 0
max instances = 1
│ repo-scoped GitHub credential
▼
GitHub research repository
▲
│ git pull / push
Coding Agent / lab server
Use three independent protections:
- HTTPS from Cloud Run for transport security.
- OAuth/OIDC at the MCP application layer.
MCP_AUTH_ENABLED=truemakes the server validate JWT access tokens against an external issuer/JWKS endpoint, audience, required scopes, and optional subject allowlist. - A narrowly scoped GitHub credential that can access only the research repository the MCP manages.
Do not expose the service publicly while MCP_AUTH_ENABLED=false.
Install and authenticate gcloud, then choose a project and region:
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
gcloud config set run/region YOUR_REGIONEnable required APIs:
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
secretmanager.googleapis.comCreate a dedicated runtime service account:
gcloud iam service-accounts create research-mcp-runtime \
--display-name='Research MCP runtime'Create a fine-grained GitHub token restricted to your research repository with repository Contents read/write permission. Store it in Secret Manager rather than source code or Docker build args:
printf '%s' 'YOUR_GITHUB_FINE_GRAINED_PAT' | \
gcloud secrets create research-mcp-github-token --data-file=-If the secret already exists:
printf '%s' 'YOUR_GITHUB_FINE_GRAINED_PAT' | \
gcloud secrets versions add research-mcp-github-token --data-file=-Grant the runtime identity access to that secret:
gcloud secrets add-iam-policy-binding research-mcp-github-token \
--member='serviceAccount:research-mcp-runtime@YOUR_PROJECT_ID.iam.gserviceaccount.com' \
--role='roles/secretmanager.secretAccessor'Create an Artifact Registry repository once:
gcloud artifacts repositories create research-mcp \
--repository-format=docker \
--location=YOUR_REGIONBuild using the checked-in cloudbuild.research-mcp.yaml:
gcloud builds submit \
--config cloudbuild.research-mcp.yaml \
--substitutions=_IMAGE=YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/research-mcp/research-mcp:latest \
.The image contains only the protocol/MCP package and templates. It intentionally excludes models, datasets, checkpoints, and experiment outputs.
First deploy with Cloud Run IAM still blocking unauthenticated internet access and MCP OAuth disabled. This gives you the stable service URL without exposing an unauthenticated write-capable endpoint:
gcloud run deploy research-mcp \
--image YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/research-mcp/research-mcp:latest \
--service-account research-mcp-runtime@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--no-allow-unauthenticated \
--min-instances 0 \
--max-instances 1 \
--cpu 1 \
--memory 512Mi \
--concurrency 8 \
--set-env-vars RESEARCH_GITHUB_REPO=YOUR_GITHUB_USER/YOUR_RESEARCH_REPO,RESEARCH_GITHUB_BRANCH=main,RESEARCH_TIMEZONE=UTC,MCP_AUTH_ENABLED=false \
--set-secrets GITHUB_TOKEN=research-mcp-github-token:latestRetrieve the URL:
gcloud run services describe research-mcp \
--format='value(status.url)'The MCP endpoint is <service-url>/mcp; liveness is <service-url>/healthz.
Use an external authorization server that issues JWT access tokens and exposes a JWKS endpoint. Configure the MCP resource/audience, normally the full MCP resource URL, for example:
https://YOUR_CLOUD_RUN_HOST/mcp
Recommended token policy:
- asymmetric signing such as RS256;
- short-lived access tokens;
- audience exactly matching
MCP_AUTH_AUDIENCE; - required scope
research:mcp; - optional subject restriction via
MCP_ALLOWED_SUBJECTS.
Research MCP is a resource server only. It does not store passwords or mint access tokens.
Update the service with OAuth enabled:
gcloud run services update research-mcp \
--set-env-vars RESEARCH_GITHUB_REPO=YOUR_GITHUB_USER/YOUR_RESEARCH_REPO,RESEARCH_GITHUB_BRANCH=main,RESEARCH_TIMEZONE=UTC,MCP_AUTH_ENABLED=true,MCP_PUBLIC_URL=https://YOUR_CLOUD_RUN_HOST/mcp,MCP_AUTH_ISSUER_URL=https://YOUR_OAUTH_ISSUER/,MCP_AUTH_AUDIENCE=https://YOUR_CLOUD_RUN_HOST/mcp,MCP_AUTH_JWKS_URL=https://YOUR_OAUTH_ISSUER/.well-known/jwks.json,MCP_AUTH_ALGORITHMS=RS256,MCP_REQUIRED_SCOPES=research:mcp \
--set-secrets GITHUB_TOKEN=research-mcp-github-token:latestOnly after OAuth is configured and the service update succeeds, make Cloud Run reachable to remote MCP clients:
gcloud run services add-iam-policy-binding research-mcp \
--member='allUsers' \
--role='roles/run.invoker'This makes the network endpoint reachable; the MCP application layer must still reject missing or invalid bearer tokens.
Health check:
curl -i https://YOUR_CLOUD_RUN_HOST/healthzUnauthenticated MCP access should not yield a usable MCP session:
curl -i https://YOUR_CLOUD_RUN_HOST/mcpThen validate with a real OAuth access token and an MCP-capable client using read-only tools first:
get_research_status()get_latest_run("COMPLETED")when applicableload_planning_context()
For write-path testing, temporarily point RESEARCH_GITHUB_BRANCH at a dedicated integration-test branch instead of main.
- Start with
min-instances=0andmax-instances=1. - Use request-based billing and budget alerts.
- Rotate the GitHub credential periodically.
- Keep the credential repository-scoped and never expose it to ChatGPT, Claude, logs, or MCP responses.
- Keep the runtime service account dedicated to this service.
/healthzdeliberately does not access GitHub.- MCP HTTP mode is stateless; GitHub remains the durable source of truth.
See .env.remote.example for the environment-variable reference.