diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 0000000..2acbdb7 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,45 @@ +# Remote MCP production release + +This spec belongs only to `oilpriceapi-openai-mcp`, DigitalOcean app +`4a89cb34-72a3-4daf-a893-0628699ded26`. The API and frontend use other apps. + +Build `Dockerfile.remote` for `linux/amd64` from the reviewed source commit in +the spec header. Label the image with `org.opencontainers.image.revision`, run +the remote container smoke with an isolated PostgreSQL database, push it to +the existing DO registry, and pin its immutable digest. Never deploy a mutable +tag or infer the served source from a successful build alone. + +The dedicated `mcp_oauth` database and role on `opa-2025-nyc-db` are prepared. +The role owns only the new database; public connection/temp privileges on that +database and public schema-creation privileges were revoked. The application +creates its own table and indexes at startup. Its pool is capped at four +connections per instance. Keep `instance_count: 1` unless capacity is reviewed. + +Before applying the spec, inject `OILPRICEAPI_MCP_OAUTH_ENCRYPTION_KEY` as a +`RUN_TIME` environment variable of type `SECRET`. The 32-byte key is backed up +in the Mac login Keychain under service `OilPriceAPI MCP production OAuth`, +account `OILPRICEAPI_MCP_OAUTH_ENCRYPTION_KEY`; retrieve it through Keychain +Access or the macOS keyring API. Verify readback without printing the key. +Keep it out of command arguments, logs, source control and release receipts. +Retain the same key on later deployments: changing or losing it invalidates +stored connections. The versioned YAML deliberately contains no key value +and is not a complete deploy command on its own. + +Reconcile against the current live app spec before every update. Preserve its +domain, allowed origins, ingress, capacity and unrelated environment settings. +The database binding supplies its URL and CA certificate. Confirm the MCP app +is a trusted source on the database cluster. A production boot without the +database/key fails closed; a bound CA is verified with hostname validation. + +After activation, verify the serving deployment's image digest, `/health` +version `0.1.2`, and `oauth_store_ready` with `durable: true`. Run anonymous and +authenticated tool smokes, then obtain a synthetic OAuth grant, deploy the +same image once, and prove refresh and authenticated retrieval still work. +Reject refresh-token replay and verify revocation rejects both old and new +access tokens. Review runtime logs for new errors and failed requests. + +The initial transition from the old in-memory store requires existing users +to reconnect once. Later restarts retain the registered client and unexpired +encrypted tokens. Rollback pins the prior digest recorded in the spec header, +preserving the database and key. Rolling back to the old memory-only image +also requires reconnecting; never drop the database as part of rollback. diff --git a/deployment/openai-mcp.yaml b/deployment/openai-mcp.yaml index d3b92ff..b99f97d 100644 --- a/deployment/openai-mcp.yaml +++ b/deployment/openai-mcp.yaml @@ -1,34 +1,63 @@ -# Isolated App Platform app. APP_URL binds its stable HTTPS ingress. -# Reviewed source 55e7da9856c78de0c4298080600534088e8ba92d, immutable DOCR image. -# Do not apply this spec to either existing API/frontend app. +# Isolated MCP app; do not apply to the API or frontend app. +# Reviewed image source: d11cd0315987e9831381a7fe74f5b67d0cb943f8 +# Rollback image: sha256:2755490dc9798fc318819313c16fa20c429534102e081ddd6eeccc7073d505dd +# Inject OILPRICEAPI_MCP_OAUTH_ENCRYPTION_KEY as a RUN_TIME SECRET +# from the password manager before applying. Never commit its value. +# See deployment/README.md for the complete rollout and rollback. name: oilpriceapi-openai-mcp -region: nyc services: - - name: energy-mcp - image: - registry_type: DOCR - registry: opa-2025-registry - repository: oilpriceapi-openai-mcp - digest: sha256:02b58d701a7f4cd74def1f8045c6529afb9f0cb616429e57ec78b5e3b000b1e2 - http_port: 8080 - instance_count: 1 - instance_size_slug: apps-s-1vcpu-1gb - envs: - - key: OILPRICEAPI_MCP_PUBLIC_URL - scope: RUN_TIME - value: ${APP_URL} - - key: NODE_ENV - scope: RUN_TIME - value: production - health_check: - http_path: /health - initial_delay_seconds: 10 - period_seconds: 15 - timeout_seconds: 5 - success_threshold: 1 - failure_threshold: 3 - routes: - - path: / +- name: energy-mcp + image: + registry_type: DOCR + registry: opa-2025-registry + repository: oilpriceapi-openai-mcp + digest: sha256:003efbb235c11502c037ab0e1034a045e4498c4403bd13f8cc2fb28c46d74137 + envs: + - key: OILPRICEAPI_MCP_PUBLIC_URL + value: ${APP_URL} + scope: RUN_TIME + - key: NODE_ENV + value: production + scope: RUN_TIME + - key: MCP_ALLOWED_ORIGINS + value: https://claude.ai,https://claude.com + scope: RUN_TIME + - key: OILPRICEAPI_MCP_OAUTH_DATABASE_URL + scope: RUN_TIME + value: ${mcp-oauth-db.DATABASE_URL} + - key: OILPRICEAPI_MCP_OAUTH_DATABASE_CA_CERT + scope: RUN_TIME + value: ${mcp-oauth-db.CA_CERT} + instance_size_slug: apps-s-1vcpu-1gb + instance_count: 1 + http_port: 8080 + health_check: + initial_delay_seconds: 10 + period_seconds: 15 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 3 + http_path: /health +domains: +- domain: mcp.oilpriceapi.com + type: PRIMARY +region: nyc alerts: - - rule: DEPLOYMENT_FAILED - - rule: DOMAIN_FAILED +- rule: DEPLOYMENT_FAILED +- rule: DOMAIN_FAILED +ingress: + rules: + - match: + path: + prefix: / + component: + name: energy-mcp +features: +- buildpack-stack=ubuntu-22 +databases: +- name: mcp-oauth-db + engine: PG + production: true + cluster_name: opa-2025-nyc-db + db_name: mcp_oauth + db_user: mcp_oauth