From 26df5917a64c3ee8f01beba16e0c90b548d4b60c Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Sat, 3 Oct 2026 19:22:38 -0400 Subject: [PATCH 1/4] chore(deploy): record remote MCP image 6a59fbd and mcp.oilpriceapi.com domain Co-Authored-By: Claude Opus 5.5 --- deployment/openai-mcp.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deployment/openai-mcp.yaml b/deployment/openai-mcp.yaml index d3b92ff..24bbb03 100644 --- a/deployment/openai-mcp.yaml +++ b/deployment/openai-mcp.yaml @@ -1,5 +1,6 @@ # Isolated App Platform app. APP_URL binds its stable HTTPS ingress. -# Reviewed source 55e7da9856c78de0c4298080600534088e8ba92d, immutable DOCR image. +# Reviewed source 6a59fbdffc88bdc4a347f81799fb56b006cbf462 (#150), immutable DOCR image. +# Rollback: previous digest sha256:02b58d701a7f4cd74def1f8045c6529afb9f0cb616429e57ec78b5e3b000b1e2. # Do not apply this spec to either existing API/frontend app. name: oilpriceapi-openai-mcp region: nyc @@ -9,7 +10,7 @@ services: registry_type: DOCR registry: opa-2025-registry repository: oilpriceapi-openai-mcp - digest: sha256:02b58d701a7f4cd74def1f8045c6529afb9f0cb616429e57ec78b5e3b000b1e2 + digest: sha256:a2bc659c691845cead42f14ee7157786a8d69fcc872c8b8d8b4d8a5f1355dc0a http_port: 8080 instance_count: 1 instance_size_slug: apps-s-1vcpu-1gb @@ -32,3 +33,7 @@ services: alerts: - rule: DEPLOYMENT_FAILED - rule: DOMAIN_FAILED +domains: + # Cloudflare CNAME, DNS-only (not proxied) so platform egress is not challenged. + - domain: mcp.oilpriceapi.com + type: PRIMARY From 7988e577d4bbd7794e279d793dfbfa9fbdc18b5b Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Sat, 3 Oct 2026 19:51:42 -0400 Subject: [PATCH 2/4] chore(deploy): allow Claude origins on the remote MCP app Co-Authored-By: Claude Opus 5.5 --- deployment/openai-mcp.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deployment/openai-mcp.yaml b/deployment/openai-mcp.yaml index 24bbb03..5ad4bc2 100644 --- a/deployment/openai-mcp.yaml +++ b/deployment/openai-mcp.yaml @@ -21,6 +21,10 @@ services: - key: NODE_ENV scope: RUN_TIME value: production + # Claude's hosted connector sends Origin: https://claude.ai on /register and /mcp. + - key: MCP_ALLOWED_ORIGINS + scope: RUN_TIME + value: https://claude.ai,https://claude.com health_check: http_path: /health initial_delay_seconds: 10 From 7fbc9f71940ff014f1e13fed577b0fa6c1069a49 Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Sat, 3 Oct 2026 19:52:02 -0400 Subject: [PATCH 3/4] chore(deploy): state the origin rule as measured Co-Authored-By: Claude Opus 5.5 --- deployment/openai-mcp.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/openai-mcp.yaml b/deployment/openai-mcp.yaml index 5ad4bc2..64c5f88 100644 --- a/deployment/openai-mcp.yaml +++ b/deployment/openai-mcp.yaml @@ -21,7 +21,7 @@ services: - key: NODE_ENV scope: RUN_TIME value: production - # Claude's hosted connector sends Origin: https://claude.ai on /register and /mcp. + # Without this, any request with Origin: https://claude.ai gets 403 on /register and /mcp. - key: MCP_ALLOWED_ORIGINS scope: RUN_TIME value: https://claude.ai,https://claude.com From fbee258581903e3e7bdb617f99d8ec56540b5d45 Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Sat, 17 Oct 2026 18:28:39 -0400 Subject: [PATCH 4/4] chore(deploy): prepare durable remote MCP 0.1.2 rollout --- deployment/README.md | 45 +++++++++++++++++ deployment/openai-mcp.yaml | 100 ++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 deployment/README.md 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 64c5f88..b99f97d 100644 --- a/deployment/openai-mcp.yaml +++ b/deployment/openai-mcp.yaml @@ -1,43 +1,63 @@ -# Isolated App Platform app. APP_URL binds its stable HTTPS ingress. -# Reviewed source 6a59fbdffc88bdc4a347f81799fb56b006cbf462 (#150), immutable DOCR image. -# Rollback: previous digest sha256:02b58d701a7f4cd74def1f8045c6529afb9f0cb616429e57ec78b5e3b000b1e2. -# 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:a2bc659c691845cead42f14ee7157786a8d69fcc872c8b8d8b4d8a5f1355dc0a - 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 - # Without this, any request with Origin: https://claude.ai gets 403 on /register and /mcp. - - key: MCP_ALLOWED_ORIGINS - scope: RUN_TIME - value: https://claude.ai,https://claude.com - health_check: - http_path: /health - initial_delay_seconds: 10 - period_seconds: 15 - timeout_seconds: 5 - success_threshold: 1 - failure_threshold: 3 - routes: - - path: / -alerts: - - rule: DEPLOYMENT_FAILED - - rule: DOMAIN_FAILED +- 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: - # Cloudflare CNAME, DNS-only (not proxied) so platform egress is not challenged. - - domain: mcp.oilpriceapi.com - type: PRIMARY +- domain: mcp.oilpriceapi.com + type: PRIMARY +region: nyc +alerts: +- 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