@caelo-cms/provisioning 0.11.9 → 0.11.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/dist/adapter.d.ts +6 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/admin-domain.d.ts +10 -1
  4. package/dist/admin-domain.d.ts.map +1 -1
  5. package/dist/admin-domain.js +13 -3
  6. package/dist/admin-domain.js.map +1 -1
  7. package/dist/adopt-existing.d.ts +12 -3
  8. package/dist/adopt-existing.d.ts.map +1 -1
  9. package/dist/adopt-existing.js +143 -0
  10. package/dist/adopt-existing.js.map +1 -1
  11. package/dist/agent/command.d.ts +23 -0
  12. package/dist/agent/command.d.ts.map +1 -1
  13. package/dist/agent/command.js +32 -9
  14. package/dist/agent/command.js.map +1 -1
  15. package/dist/agent/run.d.ts +2 -0
  16. package/dist/agent/run.d.ts.map +1 -1
  17. package/dist/agent/run.js +5 -1
  18. package/dist/agent/run.js.map +1 -1
  19. package/dist/agent-db.js +69 -10
  20. package/dist/aws-task-roles.d.ts +4 -2
  21. package/dist/aws-task-roles.d.ts.map +1 -1
  22. package/dist/aws-task-roles.js +4 -1
  23. package/dist/aws-task-roles.js.map +1 -1
  24. package/dist/caddy.d.ts +21 -4
  25. package/dist/caddy.d.ts.map +1 -1
  26. package/dist/caddy.js +27 -3
  27. package/dist/caddy.js.map +1 -1
  28. package/dist/cli-routing.js +1 -1
  29. package/dist/cli-routing.js.map +1 -1
  30. package/dist/cli.js +68 -7
  31. package/dist/cli.js.map +1 -1
  32. package/dist/cloudsql-tls.d.ts +93 -0
  33. package/dist/cloudsql-tls.d.ts.map +1 -0
  34. package/dist/cloudsql-tls.js +295 -0
  35. package/dist/cloudsql-tls.js.map +1 -0
  36. package/dist/compose.d.ts +43 -6
  37. package/dist/compose.d.ts.map +1 -1
  38. package/dist/compose.js +148 -23
  39. package/dist/compose.js.map +1 -1
  40. package/dist/converge-checkpoint.d.ts +1 -0
  41. package/dist/converge-checkpoint.d.ts.map +1 -1
  42. package/dist/converge-checkpoint.js +8 -1
  43. package/dist/converge-checkpoint.js.map +1 -1
  44. package/dist/converge-db-trust.d.ts +44 -0
  45. package/dist/converge-db-trust.d.ts.map +1 -0
  46. package/dist/converge-db-trust.js +158 -0
  47. package/dist/converge-db-trust.js.map +1 -0
  48. package/dist/converge-job-gcp.d.ts +5 -2
  49. package/dist/converge-job-gcp.d.ts.map +1 -1
  50. package/dist/converge-job-gcp.js +11 -13
  51. package/dist/converge-job-gcp.js.map +1 -1
  52. package/dist/converge-job-preflight.d.ts.map +1 -1
  53. package/dist/converge-job-preflight.js +8 -0
  54. package/dist/converge-job-preflight.js.map +1 -1
  55. package/dist/converge-job.d.ts +17 -0
  56. package/dist/converge-job.d.ts.map +1 -1
  57. package/dist/converge-job.js +12 -0
  58. package/dist/converge-job.js.map +1 -1
  59. package/dist/deployed-release.d.ts +4 -1
  60. package/dist/deployed-release.d.ts.map +1 -1
  61. package/dist/deployed-release.js +18 -8
  62. package/dist/deployed-release.js.map +1 -1
  63. package/dist/gcp-names.d.ts +34 -0
  64. package/dist/gcp-names.d.ts.map +1 -1
  65. package/dist/gcp-names.js +45 -0
  66. package/dist/gcp-names.js.map +1 -1
  67. package/dist/gcp-url-map.d.ts.map +1 -1
  68. package/dist/gcp-url-map.js +6 -2
  69. package/dist/gcp-url-map.js.map +1 -1
  70. package/dist/index.d.ts +3 -2
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/infra-contract.d.ts +1 -1
  75. package/dist/infra-contract.d.ts.map +1 -1
  76. package/dist/infra-contract.js +45 -1
  77. package/dist/infra-contract.js.map +1 -1
  78. package/dist/install-job.d.ts +4 -2
  79. package/dist/install-job.d.ts.map +1 -1
  80. package/dist/install-job.js +4 -1
  81. package/dist/install-job.js.map +1 -1
  82. package/dist/install-state.d.ts +13 -0
  83. package/dist/install-state.d.ts.map +1 -1
  84. package/dist/install-state.js +17 -4
  85. package/dist/install-state.js.map +1 -1
  86. package/dist/lifecycle.d.ts.map +1 -1
  87. package/dist/lifecycle.js +20 -8
  88. package/dist/lifecycle.js.map +1 -1
  89. package/dist/mcp-auth-proxy-setup.d.ts +48 -0
  90. package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
  91. package/dist/mcp-auth-proxy-setup.js +280 -0
  92. package/dist/mcp-auth-proxy-setup.js.map +1 -0
  93. package/dist/migration-runner.d.ts.map +1 -1
  94. package/dist/migration-runner.js +8 -3
  95. package/dist/migration-runner.js.map +1 -1
  96. package/dist/migration-targets.d.ts +11 -0
  97. package/dist/migration-targets.d.ts.map +1 -0
  98. package/dist/migration-targets.js +12 -0
  99. package/dist/migration-targets.js.map +1 -0
  100. package/dist/operator-access.d.ts +10 -0
  101. package/dist/operator-access.d.ts.map +1 -1
  102. package/dist/operator-access.js +5 -0
  103. package/dist/operator-access.js.map +1 -1
  104. package/dist/pool-budget-stack.d.ts +13 -1
  105. package/dist/pool-budget-stack.d.ts.map +1 -1
  106. package/dist/pool-budget-stack.js +11 -1
  107. package/dist/pool-budget-stack.js.map +1 -1
  108. package/dist/public-role-isolation.d.ts +7 -3
  109. package/dist/public-role-isolation.d.ts.map +1 -1
  110. package/dist/public-role-isolation.js +25 -13
  111. package/dist/public-role-isolation.js.map +1 -1
  112. package/dist/release-contract.d.ts.map +1 -1
  113. package/dist/release-contract.js +3 -0
  114. package/dist/release-contract.js.map +1 -1
  115. package/dist/release-manifest.d.ts +25 -1
  116. package/dist/release-manifest.d.ts.map +1 -1
  117. package/dist/release-manifest.js +36 -3
  118. package/dist/release-manifest.js.map +1 -1
  119. package/dist/remote-mcp.d.ts +55 -18
  120. package/dist/remote-mcp.d.ts.map +1 -1
  121. package/dist/remote-mcp.js +307 -77
  122. package/dist/remote-mcp.js.map +1 -1
  123. package/dist/runtime-secrets.d.ts +11 -0
  124. package/dist/runtime-secrets.d.ts.map +1 -1
  125. package/dist/runtime-secrets.js +40 -25
  126. package/dist/runtime-secrets.js.map +1 -1
  127. package/dist/scheduler-setup.d.ts +75 -0
  128. package/dist/scheduler-setup.d.ts.map +1 -0
  129. package/dist/scheduler-setup.js +331 -0
  130. package/dist/scheduler-setup.js.map +1 -0
  131. package/dist/seal-key-rotation.d.ts +7 -7
  132. package/dist/seal-key-rotation.d.ts.map +1 -1
  133. package/dist/seal-key-rotation.js +22 -22
  134. package/dist/seal-key-rotation.js.map +1 -1
  135. package/dist/self-hosted-db-tls.d.ts +37 -0
  136. package/dist/self-hosted-db-tls.d.ts.map +1 -0
  137. package/dist/self-hosted-db-tls.js +220 -0
  138. package/dist/self-hosted-db-tls.js.map +1 -0
  139. package/dist/self-hosted-release.d.ts +3 -1
  140. package/dist/self-hosted-release.d.ts.map +1 -1
  141. package/dist/self-hosted-release.js +33 -9
  142. package/dist/self-hosted-release.js.map +1 -1
  143. package/dist/self-hosted-roles.d.ts +43 -2
  144. package/dist/self-hosted-roles.d.ts.map +1 -1
  145. package/dist/self-hosted-roles.js +87 -1
  146. package/dist/self-hosted-roles.js.map +1 -1
  147. package/dist/self-hosted-scheduler-interval.d.ts +21 -0
  148. package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
  149. package/dist/self-hosted-scheduler-interval.js +68 -0
  150. package/dist/self-hosted-scheduler-interval.js.map +1 -0
  151. package/dist/self-hosted-start.d.ts +0 -21
  152. package/dist/self-hosted-start.d.ts.map +1 -1
  153. package/dist/self-hosted-start.js +39 -1
  154. package/dist/self-hosted-start.js.map +1 -1
  155. package/dist/self-hosted-upgrade.d.ts +19 -7
  156. package/dist/self-hosted-upgrade.d.ts.map +1 -1
  157. package/dist/self-hosted-upgrade.js +78 -25
  158. package/dist/self-hosted-upgrade.js.map +1 -1
  159. package/dist/stack-contract.d.ts +234 -28
  160. package/dist/stack-contract.d.ts.map +1 -1
  161. package/dist/stack-contract.js +397 -39
  162. package/dist/stack-contract.js.map +1 -1
  163. package/dist/stack-converge.d.ts +31 -0
  164. package/dist/stack-converge.d.ts.map +1 -1
  165. package/dist/stack-converge.js +71 -30
  166. package/dist/stack-converge.js.map +1 -1
  167. package/dist/staging-database.d.ts +22 -0
  168. package/dist/staging-database.d.ts.map +1 -0
  169. package/dist/staging-database.js +117 -0
  170. package/dist/staging-database.js.map +1 -0
  171. package/dist/upgrade/app-roll.d.ts +25 -4
  172. package/dist/upgrade/app-roll.d.ts.map +1 -1
  173. package/dist/upgrade/app-roll.js +63 -17
  174. package/dist/upgrade/app-roll.js.map +1 -1
  175. package/dist/upgrade/converge.d.ts +5 -0
  176. package/dist/upgrade/converge.d.ts.map +1 -1
  177. package/dist/upgrade/converge.js +207 -20
  178. package/dist/upgrade/converge.js.map +1 -1
  179. package/dist/upgrade/health.d.ts +31 -7
  180. package/dist/upgrade/health.d.ts.map +1 -1
  181. package/dist/upgrade/health.js +41 -17
  182. package/dist/upgrade/health.js.map +1 -1
  183. package/dist/upgrade/image-forms.d.ts +1 -1
  184. package/dist/upgrade/image-forms.d.ts.map +1 -1
  185. package/dist/upgrade/image-forms.js +24 -7
  186. package/dist/upgrade/image-forms.js.map +1 -1
  187. package/dist/upgrade/release-images.d.ts +12 -3
  188. package/dist/upgrade/release-images.d.ts.map +1 -1
  189. package/dist/upgrade/release-images.js +13 -4
  190. package/dist/upgrade/release-images.js.map +1 -1
  191. package/dist/upgrade/upgrade.d.ts +19 -3
  192. package/dist/upgrade/upgrade.d.ts.map +1 -1
  193. package/dist/upgrade/upgrade.js +37 -2
  194. package/dist/upgrade/upgrade.js.map +1 -1
  195. package/dist/upgrader-gcp.d.ts +13 -0
  196. package/dist/upgrader-gcp.d.ts.map +1 -1
  197. package/dist/upgrader-gcp.js +50 -1
  198. package/dist/upgrader-gcp.js.map +1 -1
  199. package/dist/upgrader-iam-preflight.d.ts.map +1 -1
  200. package/dist/upgrader-iam-preflight.js +6 -1
  201. package/dist/upgrader-iam-preflight.js.map +1 -1
  202. package/dist/upgrader-job.d.ts +1 -1
  203. package/dist/upgrader-job.d.ts.map +1 -1
  204. package/dist/upgrader-job.js +20 -14
  205. package/dist/upgrader-job.js.map +1 -1
  206. package/dist/upgrader-records.d.ts +1 -1
  207. package/dist/upgrader-records.d.ts.map +1 -1
  208. package/dist/upgrader-records.js +26 -7
  209. package/dist/upgrader-records.js.map +1 -1
  210. package/dist/upgrader-setup.d.ts.map +1 -1
  211. package/dist/upgrader-setup.js +10 -5
  212. package/dist/upgrader-setup.js.map +1 -1
  213. package/dist/wizard.d.ts +7 -0
  214. package/dist/wizard.d.ts.map +1 -1
  215. package/dist/wizard.js +11 -0
  216. package/dist/wizard.js.map +1 -1
  217. package/dist/wizards/gcp-cost.d.ts +31 -0
  218. package/dist/wizards/gcp-cost.d.ts.map +1 -1
  219. package/dist/wizards/gcp-cost.js +103 -18
  220. package/dist/wizards/gcp-cost.js.map +1 -1
  221. package/dist/wizards/gcp.d.ts.map +1 -1
  222. package/dist/wizards/gcp.js +129 -27
  223. package/dist/wizards/gcp.js.map +1 -1
  224. package/package.json +2 -2
  225. package/src/adapter.ts +6 -0
  226. package/src/admin-domain.test.ts +9 -8
  227. package/src/admin-domain.ts +13 -3
  228. package/src/admin-memory.test.ts +2 -1
  229. package/src/adopt-existing.test.ts +85 -0
  230. package/src/adopt-existing.ts +141 -3
  231. package/src/agent/command.test.ts +49 -1
  232. package/src/agent/command.ts +39 -9
  233. package/src/agent/run.test.ts +19 -0
  234. package/src/agent/run.ts +11 -2
  235. package/src/aws-task-roles.ts +5 -2
  236. package/src/caddy-redirects.docker.test.ts +2 -0
  237. package/src/caddy-redirects.test.ts +4 -1
  238. package/src/caddy-validate.test.ts +7 -1
  239. package/src/caddy.test.ts +39 -1
  240. package/src/caddy.ts +36 -4
  241. package/src/checkpoint-secrets.test.ts +14 -6
  242. package/src/cli-routing.test.ts +1 -1
  243. package/src/cli-routing.ts +1 -1
  244. package/src/cli.ts +71 -5
  245. package/src/cloudsql-tls.test.ts +255 -0
  246. package/src/cloudsql-tls.ts +359 -0
  247. package/src/compose.ts +193 -24
  248. package/src/converge-checkpoint.ts +8 -1
  249. package/src/converge-db-trust.test.ts +237 -0
  250. package/src/converge-db-trust.ts +212 -0
  251. package/src/converge-job-gcp.ts +11 -12
  252. package/src/converge-job-preflight.test.ts +5 -0
  253. package/src/converge-job-preflight.ts +8 -0
  254. package/src/converge-job.test.ts +70 -0
  255. package/src/converge-job.ts +32 -0
  256. package/src/database-tls-builders.test.ts +56 -0
  257. package/src/deployed-release.test.ts +107 -40
  258. package/src/deployed-release.ts +30 -9
  259. package/src/gateway-env-isolation.test.ts +50 -12
  260. package/src/gcp-names.ts +56 -0
  261. package/src/gcp-url-map.test.ts +6 -8
  262. package/src/gcp-url-map.ts +6 -2
  263. package/src/index.test.ts +10 -4
  264. package/src/index.ts +7 -0
  265. package/src/infra-contract.test.ts +7 -3
  266. package/src/infra-contract.ts +45 -1
  267. package/src/install-job.ts +4 -1
  268. package/src/install-state.ts +33 -4
  269. package/src/lifecycle.ts +23 -11
  270. package/src/mcp-auth-proxy-setup.test.ts +305 -0
  271. package/src/mcp-auth-proxy-setup.ts +374 -0
  272. package/src/mcp-iap-stacks.test.ts +1 -1
  273. package/src/media-volume.test.ts +1 -1
  274. package/src/migration-runner.ts +14 -9
  275. package/src/migration-targets.ts +12 -0
  276. package/src/operator-access-stacks.test.ts +1 -1
  277. package/src/operator-access.test.ts +20 -4
  278. package/src/operator-access.ts +14 -0
  279. package/src/pool-budget-stack.ts +15 -0
  280. package/src/pool-budget-stacks.test.ts +39 -14
  281. package/src/public-role-isolation.test.ts +18 -8
  282. package/src/public-role-isolation.ts +31 -17
  283. package/src/release-contract.ts +3 -0
  284. package/src/release-manifest.test.ts +25 -0
  285. package/src/release-manifest.ts +59 -4
  286. package/src/remote-mcp-stack-config.test.ts +19 -9
  287. package/src/remote-mcp.test.ts +252 -57
  288. package/src/remote-mcp.ts +336 -77
  289. package/src/runtime-secrets.test.ts +37 -14
  290. package/src/runtime-secrets.ts +87 -27
  291. package/src/scheduler-setup.test.ts +201 -0
  292. package/src/scheduler-setup.ts +375 -0
  293. package/src/seal-key-rotation.test.ts +2 -2
  294. package/src/seal-key-rotation.ts +25 -25
  295. package/src/self-hosted-backup.test.ts +1 -0
  296. package/src/self-hosted-compose.test.ts +180 -6
  297. package/src/self-hosted-db-tls.test.ts +111 -0
  298. package/src/self-hosted-db-tls.ts +287 -0
  299. package/src/self-hosted-release-resolve.test.ts +71 -0
  300. package/src/self-hosted-release.ts +41 -11
  301. package/src/self-hosted-roles.integration.test.ts +2 -0
  302. package/src/self-hosted-roles.test.ts +87 -3
  303. package/src/self-hosted-roles.ts +101 -3
  304. package/src/self-hosted-scheduler-interval.test.ts +66 -0
  305. package/src/self-hosted-scheduler-interval.ts +79 -0
  306. package/src/self-hosted-start.test.ts +35 -2
  307. package/src/self-hosted-start.ts +20 -2
  308. package/src/self-hosted-upgrade.test.ts +104 -14
  309. package/src/self-hosted-upgrade.ts +123 -31
  310. package/src/stack-contract-parity.test.ts +79 -12
  311. package/src/stack-contract.test.ts +199 -31
  312. package/src/stack-contract.ts +472 -45
  313. package/src/stack-converge-secrets.test.ts +28 -13
  314. package/src/stack-converge.test.ts +203 -28
  315. package/src/stack-converge.ts +92 -30
  316. package/src/staging-database.test.ts +114 -0
  317. package/src/staging-database.ts +148 -0
  318. package/src/state-store.test.ts +2 -1
  319. package/src/upgrade/app-roll.ts +97 -19
  320. package/src/upgrade/converge.ts +249 -19
  321. package/src/upgrade/health.test.ts +63 -25
  322. package/src/upgrade/health.ts +54 -22
  323. package/src/upgrade/image-forms.test.ts +34 -0
  324. package/src/upgrade/image-forms.ts +23 -8
  325. package/src/upgrade/release-images.test.ts +5 -1
  326. package/src/upgrade/release-images.ts +22 -6
  327. package/src/upgrade/upgrade.test.ts +438 -46
  328. package/src/upgrade/upgrade.ts +64 -6
  329. package/src/upgrader-gcp.ts +66 -3
  330. package/src/upgrader-iam-preflight.test.ts +19 -0
  331. package/src/upgrader-iam-preflight.ts +6 -0
  332. package/src/upgrader-job.test.ts +101 -27
  333. package/src/upgrader-job.ts +27 -14
  334. package/src/upgrader-records.test.ts +97 -12
  335. package/src/upgrader-records.ts +28 -11
  336. package/src/upgrader-setup.test.ts +2 -0
  337. package/src/upgrader-setup.ts +10 -4
  338. package/src/wizard.ts +19 -0
  339. package/src/wizards/gcp-cost.test.ts +103 -0
  340. package/src/wizards/gcp-cost.ts +125 -18
  341. package/src/wizards/gcp.ts +164 -22
  342. package/stacks/aws/Pulumi.yaml +1 -1
  343. package/stacks/aws/index.ts +72 -14
  344. package/stacks/azure/index.ts +108 -21
  345. package/stacks/gcp/Pulumi.yaml +1 -1
  346. package/stacks/gcp/README.md +1 -0
  347. package/stacks/gcp/index.ts +133 -24
  348. package/stacks/gcp-firebase/Pulumi.yaml +1 -1
  349. package/stacks/gcp-firebase/index.ts +212 -34
  350. package/stacks/self-hosted/Pulumi.yaml +3 -0
  351. package/stacks/self-hosted/README.md +6 -1
  352. package/stacks/self-hosted/index.ts +40 -5
package/src/remote-mcp.ts CHANGED
@@ -12,13 +12,13 @@
12
12
  * (#1082: the stack is their one owner — stack-contract.ts says why
13
13
  * they cannot be plain resource inputs), and
14
14
  * {@link checkRemoteMcpIdentityEnv}, the read-only check after it.
15
- * - {@link ensureRemoteMcpEdge} (`gcp` only): the load balancer's part for
16
- * installs whose URL map / Cloud Armor policy predate it — the
17
- * `/.well-known/oauth-*` path rules (the stack ignores `pathRules` after
18
- * creation, gcp-url-map.ts) and the `/api/mcp/*` Cloud Armor rule
19
- * (remote-mcp-edge.ts).
15
+ * - {@link ensureRemoteMcpEdge} (`gcp` only): the MCP-Auth-Proxy's
16
+ * load-balancer part (#1328) — its service, serverless NEG, backend and
17
+ * own Cloud Armor policy with the `/api/mcp/*` rule (remote-mcp-edge.ts),
18
+ * and the URL map's rule sending the remote-MCP paths to it (the stack
19
+ * ignores `pathRules` after creation, gcp-url-map.ts).
20
20
  *
21
- * The secrets, the IAM bindings, the gateway's secret volume and timeout
21
+ * The secrets, the IAM bindings, the proxy's secret volume and timeout
22
22
  * come through the shared contract like everything else
23
23
  * (runtime-secrets.ts, stack-contract.ts, upgrade/app-roll.ts).
24
24
  *
@@ -28,13 +28,14 @@
28
28
  *
29
29
  * Nothing here enables remote MCP: that happens when the Owner sets up the
30
30
  * install's Google sign-in client (Settings → Google sign-in). Until then
31
- * the gateway answers 503 `remote_mcp_not_configured`.
31
+ * the MCP-Auth-Proxy answers its fixed 404.
32
32
  */
33
33
 
34
- import { GCP_PUBLIC_BASE_PATHS } from "@caelo-cms/shared";
34
+ import { GCP_MCP_AUTH_PROXY_PATHS } from "@caelo-cms/shared";
35
35
  import { gcloud as defaultGcloud } from "./gcloud.js";
36
36
  import type { GcloudRunner } from "./gcloud-retry.js";
37
- import { gcpNamePrefix } from "./gcp-names.js";
37
+ import { gcpNamePrefix, mcpAuthProxyEdgeNames } from "./gcp-names.js";
38
+ import { ensureMcpAuthProxyServiceGcp } from "./mcp-auth-proxy-setup.js";
38
39
  import {
39
40
  isMcpArmorRule,
40
41
  type LiveArmorRule,
@@ -43,7 +44,10 @@ import {
43
44
  } from "./remote-mcp-edge.js";
44
45
  import type { HttpFetch } from "./runtime-secrets.js";
45
46
  import {
47
+ CLOUD_RUN_SLUGS,
46
48
  type CloudRunSlug,
49
+ cloudRunServicePrefix,
50
+ fixedCloudRunServiceName,
47
51
  type GcpProvider,
48
52
  REMOTE_MCP_ENV,
49
53
  type RemoteMcpIdentity,
@@ -209,12 +213,15 @@ async function resolveServiceIdentity(
209
213
  const value = r.ok ? r.stdout.trim() : "";
210
214
  return value.startsWith("https://") ? value : null;
211
215
  };
216
+ // #1328 — the broker answers on the MCP-Auth-Proxy's URL: the service
217
+ // that was the gateway before (E13 a), so the URL connectors hold stays.
218
+ const proxyService = target.services["mcp-auth-proxy"];
212
219
  const adminUrl = await url(target.services.admin);
213
- const gatewayUrl = await url(target.services.gateway);
214
- if (!adminUrl || !gatewayUrl) {
220
+ const proxyUrl = await url(proxyService);
221
+ if (!adminUrl || !proxyUrl) {
215
222
  return {
216
223
  ok: false,
217
- error: `read the Cloud Run URL of the ${adminUrl ? "gateway" : "admin"} service (${adminUrl ? target.services.gateway : target.services.admin})`,
224
+ error: `read the Cloud Run URL of the ${adminUrl ? "MCP-Auth-Proxy" : "admin"} service (${adminUrl ? proxyService : target.services.admin})`,
218
225
  };
219
226
  }
220
227
  return {
@@ -225,7 +232,7 @@ async function resolveServiceIdentity(
225
232
  region: target.region,
226
233
  adminServiceName: target.services.admin,
227
234
  adminServiceUrl: adminUrl,
228
- gatewayServiceUrl: gatewayUrl,
235
+ mcpAuthProxyServiceUrl: proxyUrl,
229
236
  }),
230
237
  };
231
238
  }
@@ -262,8 +269,15 @@ export async function findRemoteMcpTarget(
262
269
  ): Promise<RemoteMcpResult<{ readonly target: RemoteMcpTarget }>> {
263
270
  const run = deps.run ?? defaultGcloud;
264
271
  const services: Partial<Record<CloudRunSlug, string>> = {};
265
- for (const slug of ["admin", "gateway"] as const) {
266
- const prefix = `${gcpNamePrefix(base.env)}-${slug}`;
272
+ for (const slug of CLOUD_RUN_SLUGS) {
273
+ // A service created under a fixed name needs no lookup — and on `gcp`
274
+ // the MCP-Auth-Proxy is created after the stack's first `up`.
275
+ const fixed = fixedCloudRunServiceName(base.provider, base.env, slug);
276
+ if (fixed) {
277
+ services[slug] = fixed;
278
+ continue;
279
+ }
280
+ const prefix = cloudRunServicePrefix(base.provider, base.env, slug);
267
281
  const lookup = await resolveGcpResourceName(
268
282
  run,
269
283
  "run-service",
@@ -331,8 +345,8 @@ export async function checkRemoteMcpIdentityEnv(
331
345
  ): Promise<RemoteMcpResult<{ readonly drift: Record<CloudRunSlug, EnvChange[]> }>> {
332
346
  const run = deps.run ?? defaultGcloud;
333
347
  const desired = remoteMcpIdentityEnv(identity);
334
- const drift: Record<CloudRunSlug, EnvChange[]> = { admin: [], gateway: [] };
335
- for (const slug of ["admin", "gateway"] as const) {
348
+ const drift: Record<CloudRunSlug, EnvChange[]> = { admin: [], gateway: [], "mcp-auth-proxy": [] };
349
+ for (const slug of CLOUD_RUN_SLUGS) {
336
350
  const described = await describeService(target, target.services[slug], run);
337
351
  if (!described.ok) return described;
338
352
  const plan = planEnvUpdate(liveContainerEnv(described.json), desired[slug]);
@@ -364,16 +378,32 @@ export interface LiveUrlMap {
364
378
  }
365
379
 
366
380
  export type UrlMapPathsPlan =
367
- | { readonly ok: true; readonly map: LiveUrlMap; readonly added: string[] }
381
+ | {
382
+ readonly ok: true;
383
+ readonly map: LiveUrlMap;
384
+ /** What changed, for the log; empty when the map was already right. */
385
+ readonly changes: string[];
386
+ }
368
387
  | { readonly ok: false; readonly error: string };
369
388
 
389
+ /** Whether a rule's `service` (a full self-link) is the backend service named `name`. */
390
+ function targetsBackend(rule: UrlMapPathRule, name: string): boolean {
391
+ return rule.service?.endsWith(`/backendServices/${name}`) ?? false;
392
+ }
393
+
370
394
  /**
371
- * The URL map with every {@link GCP_PUBLIC_BASE_PATHS} entry routed like
372
- * `/api/*` (to the gateway). Only the rule that carries `/api/*` changes;
373
- * redirect rules the admin wrote stay as they are. `added` is empty when the
374
- * map is already complete.
395
+ * #1328 — the URL map with the remote-MCP paths ({@link GCP_MCP_AUTH_PROXY_PATHS})
396
+ * on their own rule to the MCP-Auth-Proxy's backend, and none of them left
397
+ * on the gateway's `/api/*` rule (#806 put the discovery paths there). Only
398
+ * those two rules change; redirect rules the admin wrote stay as they are.
399
+ *
400
+ * @param proxyBackend the proxy backend's self-link
375
401
  */
376
- export function planUrlMapPaths(maps: readonly LiveUrlMap[], siteHost: string): UrlMapPathsPlan {
402
+ export function planUrlMapPaths(
403
+ maps: readonly LiveUrlMap[],
404
+ siteHost: string,
405
+ proxyBackend: string,
406
+ ): UrlMapPathsPlan {
377
407
  const routing = maps.flatMap((m) =>
378
408
  (m.hostRules ?? [])
379
409
  .filter((h) => h.hosts.includes(siteHost))
@@ -387,47 +417,69 @@ export function planUrlMapPaths(maps: readonly LiveUrlMap[], siteHost: string):
387
417
  }
388
418
  const { map, pathMatcher } = routing[0] as (typeof routing)[number];
389
419
  const matcher = (map.pathMatchers ?? []).find((m) => m.name === pathMatcher);
390
- const gatewayRule = matcher?.pathRules?.find(
391
- (r) => r.service !== undefined && r.urlRedirect === undefined && r.paths.includes("/api/*"),
392
- );
393
- if (!matcher || !gatewayRule) {
420
+ const isGatewayRule = (r: UrlMapPathRule) =>
421
+ r.service !== undefined && r.urlRedirect === undefined && r.paths.includes("/api/*");
422
+ if (!matcher || !(matcher.pathRules ?? []).some(isGatewayRule)) {
394
423
  return {
395
424
  ok: false,
396
425
  error: `URL map ${map.name} has no /api/* rule to the gateway in path matcher ${pathMatcher}; re-run the installer to restore the load balancer`,
397
426
  };
398
427
  }
399
- const added = GCP_PUBLIC_BASE_PATHS.filter((p) => !gatewayRule.paths.includes(p));
400
- if (added.length === 0) return { ok: true, map, added };
428
+ const proxyName = proxyBackend.slice(proxyBackend.lastIndexOf("/") + 1);
429
+ const changes: string[] = [];
430
+ const rules: UrlMapPathRule[] = [];
431
+ let proxyRule = false;
432
+ for (const r of matcher.pathRules ?? []) {
433
+ if (isGatewayRule(r)) {
434
+ const moved = r.paths.filter((p) => GCP_MCP_AUTH_PROXY_PATHS.includes(p));
435
+ if (moved.length > 0) changes.push(`moved ${moved.join(", ")} off the gateway's rule`);
436
+ rules.push({ ...r, paths: r.paths.filter((p) => !GCP_MCP_AUTH_PROXY_PATHS.includes(p)) });
437
+ continue;
438
+ }
439
+ if (targetsBackend(r, proxyName)) {
440
+ proxyRule = true;
441
+ const same =
442
+ r.paths.length === GCP_MCP_AUTH_PROXY_PATHS.length &&
443
+ GCP_MCP_AUTH_PROXY_PATHS.every((p) => r.paths.includes(p));
444
+ if (!same)
445
+ changes.push(`set the MCP-Auth-Proxy rule to ${GCP_MCP_AUTH_PROXY_PATHS.join(", ")}`);
446
+ rules.push({ ...r, paths: [...GCP_MCP_AUTH_PROXY_PATHS] });
447
+ continue;
448
+ }
449
+ rules.push(r);
450
+ }
451
+ if (!proxyRule) {
452
+ changes.push(`routed ${GCP_MCP_AUTH_PROXY_PATHS.join(", ")} to the MCP-Auth-Proxy`);
453
+ // Before the admin's redirect rules: GCP orders nothing by position for
454
+ // path rules (the longest match wins), but a stable place keeps diffs small.
455
+ const at = rules.findIndex(isGatewayRule) + 1;
456
+ rules.splice(at, 0, { paths: [...GCP_MCP_AUTH_PROXY_PATHS], service: proxyBackend });
457
+ }
458
+ if (changes.length === 0) return { ok: true, map, changes };
401
459
  const next: LiveUrlMap = {
402
460
  ...map,
403
461
  pathMatchers: (map.pathMatchers ?? []).map((m) =>
404
- m !== matcher
405
- ? m
406
- : {
407
- ...m,
408
- pathRules: (m.pathRules ?? []).map((r) =>
409
- r === gatewayRule ? { ...r, paths: [...r.paths, ...added] } : r,
410
- ),
411
- },
462
+ m !== matcher ? m : { ...m, pathRules: rules },
412
463
  ),
413
464
  };
414
- return { ok: true, map: next, added };
465
+ return { ok: true, map: next, changes };
415
466
  }
416
467
 
417
468
  const COMPUTE_API = "https://compute.googleapis.com/compute/v1";
418
469
  const OPERATION_POLLS = 60;
419
470
 
420
471
  /**
421
- * Route the remote-MCP discovery paths to the gateway on an existing `gcp`
422
- * install: read the URL map, add the missing paths to its `/api/*` rule and
423
- * write it back through the Compute API (`urlMaps.update` carries the map's
424
- * fingerprint, so a concurrent write — the admin publishing redirects —
425
- * fails instead of being lost; a re-run then succeeds).
472
+ * Route the remote-MCP paths to the MCP-Auth-Proxy on a `gcp` install: read
473
+ * the URL map, apply {@link planUrlMapPaths} and write it back through the
474
+ * Compute API (`urlMaps.update` carries the map's fingerprint, so a
475
+ * concurrent write — the admin publishing redirects — fails instead of
476
+ * being lost; a re-run then succeeds).
426
477
  */
427
478
  export async function ensureUrlMapRemoteMcpPaths(
428
479
  target: Pick<RemoteMcpTarget, "projectId" | "domain">,
480
+ proxyBackend: string,
429
481
  deps: { run?: GcloudRunner; http?: HttpFetch; sleep?: (ms: number) => Promise<void> } = {},
430
- ): Promise<RemoteMcpResult<{ readonly added: string[] }>> {
482
+ ): Promise<RemoteMcpResult<{ readonly changes: string[] }>> {
431
483
  const run = deps.run ?? defaultGcloud;
432
484
  const http = deps.http ?? (fetch as unknown as HttpFetch);
433
485
  const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
@@ -442,9 +494,9 @@ export async function ensureUrlMapRemoteMcpPaths(
442
494
  if (!list.ok) return { ok: false, error: `list URL maps: ${list.stderr.trim()}` };
443
495
  const maps = parseJson(list.stdout || "[]");
444
496
  if (!Array.isArray(maps)) return { ok: false, error: "list URL maps: output is not a JSON list" };
445
- const plan = planUrlMapPaths(maps as LiveUrlMap[], target.domain);
497
+ const plan = planUrlMapPaths(maps as LiveUrlMap[], target.domain, proxyBackend);
446
498
  if (!plan.ok) return plan;
447
- if (plan.added.length === 0) return { ok: true, added: [] };
499
+ if (plan.changes.length === 0) return { ok: true, changes: [] };
448
500
  const token = await run(["auth", "print-access-token"]);
449
501
  const bearer = token.ok ? token.stdout.trim() : "";
450
502
  if (!bearer)
@@ -481,13 +533,43 @@ export async function ensureUrlMapRemoteMcpPaths(
481
533
  error: `URL-map update failed: ${errors.map((e) => e.message).join("; ")}`,
482
534
  };
483
535
  }
484
- return { ok: true, added: plan.added };
536
+ return { ok: true, changes: plan.changes };
537
+ }
538
+
539
+ /** A Cloud Armor policy's rules, or why they could not be read; `missing` when it does not exist. */
540
+ async function describePolicyRules(
541
+ run: GcloudRunner,
542
+ policy: string,
543
+ project: string,
544
+ ): Promise<
545
+ | { readonly ok: true; readonly rules: LiveArmorRule[] }
546
+ | { readonly ok: false; readonly missing: boolean; readonly error: string }
547
+ > {
548
+ const describe = await run([
549
+ "compute",
550
+ "security-policies",
551
+ "describe",
552
+ policy,
553
+ project,
554
+ "--format=json",
555
+ ]);
556
+ if (!describe.ok) {
557
+ return { ok: false, missing: NOT_FOUND.test(describe.stderr), error: describe.stderr.trim() };
558
+ }
559
+ const described = parseJson(describe.stdout) as { rules?: LiveArmorRule[] } | null;
560
+ if (!described)
561
+ return { ok: false, missing: false, error: `describe ${policy}: output is not JSON` };
562
+ return { ok: true, rules: described.rules ?? [] };
485
563
  }
486
564
 
565
+ const NOT_FOUND = /NOT_FOUND|not found|was not found|does not exist/i;
566
+
487
567
  /**
488
- * Give the gateway's Cloud Armor policy the `/api/mcp/*` rule
489
- * ({@link MCP_ARMOR_RULE}): create it, or bring a rule at its priority back
490
- * to spec.
568
+ * #1328 — the MCP-Auth-Proxy's own Cloud Armor policy with the `/api/mcp/*`
569
+ * rule ({@link MCP_ARMOR_RULE}; remote-mcp-edge.ts says why): created when
570
+ * missing, a rule at its priority brought back to spec. Everything else the
571
+ * policy's default rule allows: the proxy rate-limits every route itself
572
+ * (apps/mcp-auth-proxy rate-limit.ts).
491
573
  */
492
574
  export async function ensureMcpArmorRule(
493
575
  target: Pick<RemoteMcpTarget, "projectId" | "env">,
@@ -495,6 +577,56 @@ export async function ensureMcpArmorRule(
495
577
  ): Promise<
496
578
  RemoteMcpResult<{ readonly policy: string; readonly action: "present" | "created" | "updated" }>
497
579
  > {
580
+ const run = deps.run ?? defaultGcloud;
581
+ const project = `--project=${target.projectId}`;
582
+ const { policy } = mcpAuthProxyEdgeNames(target.env);
583
+ let described = await describePolicyRules(run, policy, project);
584
+ if (!described.ok && described.missing) {
585
+ const created = await run([
586
+ "compute",
587
+ "security-policies",
588
+ "create",
589
+ policy,
590
+ project,
591
+ `--description=Caelo ${target.env} MCP-Auth-Proxy — remote-MCP throttle`,
592
+ "--quiet",
593
+ ]);
594
+ if (!created.ok) return { ok: false, error: `create ${policy}: ${created.stderr.trim()}` };
595
+ described = await describePolicyRules(run, policy, project);
596
+ }
597
+ if (!described.ok) return { ok: false, error: `describe ${policy}: ${described.error}` };
598
+ const existing = described.rules.find((r) => r.priority === MCP_ARMOR_RULE.priority);
599
+ if (existing && isMcpArmorRule(existing)) return { ok: true, policy, action: "present" };
600
+ const verb = existing ? "update" : "create";
601
+ const r = await run([
602
+ "compute",
603
+ "security-policies",
604
+ "rules",
605
+ verb,
606
+ String(MCP_ARMOR_RULE.priority),
607
+ `--security-policy=${policy}`,
608
+ project,
609
+ ...mcpArmorRuleFlags(),
610
+ "--quiet",
611
+ ]);
612
+ if (!r.ok)
613
+ return {
614
+ ok: false,
615
+ error: `${verb} rule ${MCP_ARMOR_RULE.priority} on ${policy}: ${r.stderr.trim()}`,
616
+ };
617
+ return { ok: true, policy, action: existing ? "updated" : "created" };
618
+ }
619
+
620
+ /**
621
+ * #1328 — drop the `/api/mcp/*` rule #806 put on the gateway's policy, once
622
+ * the URL map no longer sends those paths to the gateway: there it would
623
+ * only exempt matching paths from the OWASP packs and the ban. A rule at the
624
+ * priority that is not exactly the MCP rule is someone's own and stays.
625
+ */
626
+ export async function retireGatewayMcpArmorRule(
627
+ target: Pick<RemoteMcpTarget, "projectId" | "env">,
628
+ deps: { run?: GcloudRunner } = {},
629
+ ): Promise<RemoteMcpResult<{ readonly removed: string | null }>> {
498
630
  const run = deps.run ?? defaultGcloud;
499
631
  const project = `--project=${target.projectId}`;
500
632
  const prefix = `${gcpNamePrefix(target.env)}-waf`;
@@ -515,56 +647,183 @@ export async function ensureMcpArmorRule(
515
647
  };
516
648
  }
517
649
  const policy = names[0] as string;
518
- const describe = await run([
519
- "compute",
520
- "security-policies",
521
- "describe",
522
- policy,
523
- project,
524
- "--format=json",
525
- ]);
526
- if (!describe.ok) return { ok: false, error: `describe ${policy}: ${describe.stderr.trim()}` };
527
- const described = parseJson(describe.stdout) as { rules?: LiveArmorRule[] } | null;
528
- if (!described) return { ok: false, error: `describe ${policy}: output is not JSON` };
529
- const rules = described.rules ?? [];
530
- const existing = rules.find((r) => r.priority === MCP_ARMOR_RULE.priority);
531
- if (existing && isMcpArmorRule(existing)) return { ok: true, policy, action: "present" };
532
- const verb = existing ? "update" : "create";
650
+ const described = await describePolicyRules(run, policy, project);
651
+ if (!described.ok) return { ok: false, error: `describe ${policy}: ${described.error}` };
652
+ const rule = described.rules.find((r) => r.priority === MCP_ARMOR_RULE.priority);
653
+ if (!rule || !isMcpArmorRule(rule)) return { ok: true, removed: null };
533
654
  const r = await run([
534
655
  "compute",
535
656
  "security-policies",
536
657
  "rules",
537
- verb,
658
+ "delete",
538
659
  String(MCP_ARMOR_RULE.priority),
539
660
  `--security-policy=${policy}`,
540
661
  project,
541
- ...mcpArmorRuleFlags(),
542
662
  "--quiet",
543
663
  ]);
544
- if (!r.ok)
664
+ if (!r.ok) {
545
665
  return {
546
666
  ok: false,
547
- error: `${verb} rule ${MCP_ARMOR_RULE.priority} on ${policy}: ${r.stderr.trim()}`,
667
+ error: `delete rule ${MCP_ARMOR_RULE.priority} on ${policy}: ${r.stderr.trim()}`,
548
668
  };
549
- return { ok: true, policy, action: existing ? "updated" : "created" };
669
+ }
670
+ return { ok: true, removed: policy };
671
+ }
672
+
673
+ /**
674
+ * #1328 — the proxy's serverless NEG and backend service (EXTERNAL_MANAGED,
675
+ * its own Cloud Armor policy), created when missing. Returns the backend's
676
+ * self-link for the URL map.
677
+ */
678
+ export async function ensureMcpAuthProxyBackend(
679
+ target: Pick<RemoteMcpTarget, "projectId" | "region" | "env">,
680
+ deps: { run?: GcloudRunner } = {},
681
+ ): Promise<RemoteMcpResult<{ readonly backend: string; readonly done: string[] }>> {
682
+ const run = deps.run ?? defaultGcloud;
683
+ const project = `--project=${target.projectId}`;
684
+ const names = mcpAuthProxyEdgeNames(target.env);
685
+ const service = fixedCloudRunServiceName("gcp", target.env, "mcp-auth-proxy") as string;
686
+ const done: string[] = [];
687
+ const neg = await run([
688
+ "compute",
689
+ "network-endpoint-groups",
690
+ "describe",
691
+ names.neg,
692
+ `--region=${target.region}`,
693
+ project,
694
+ "--format=value(name)",
695
+ ]);
696
+ if (!neg.ok) {
697
+ if (!NOT_FOUND.test(neg.stderr))
698
+ return { ok: false, error: `describe ${names.neg}: ${neg.stderr.trim()}` };
699
+ const created = await run([
700
+ "compute",
701
+ "network-endpoint-groups",
702
+ "create",
703
+ names.neg,
704
+ `--region=${target.region}`,
705
+ project,
706
+ "--network-endpoint-type=serverless",
707
+ `--cloud-run-service=${service}`,
708
+ "--quiet",
709
+ ]);
710
+ if (!created.ok) return { ok: false, error: `create ${names.neg}: ${created.stderr.trim()}` };
711
+ done.push(`created serverless NEG ${names.neg} → ${service}`);
712
+ }
713
+ const backend = await run([
714
+ "compute",
715
+ "backend-services",
716
+ "describe",
717
+ names.backend,
718
+ "--global",
719
+ project,
720
+ "--format=json",
721
+ ]);
722
+ let backendJson: { selfLink?: string; securityPolicy?: string; backends?: { group?: string }[] };
723
+ if (!backend.ok) {
724
+ if (!NOT_FOUND.test(backend.stderr)) {
725
+ return { ok: false, error: `describe ${names.backend}: ${backend.stderr.trim()}` };
726
+ }
727
+ const created = await run([
728
+ "compute",
729
+ "backend-services",
730
+ "create",
731
+ names.backend,
732
+ "--global",
733
+ project,
734
+ "--load-balancing-scheme=EXTERNAL_MANAGED",
735
+ "--protocol=HTTPS",
736
+ `--security-policy=${names.policy}`,
737
+ "--format=json",
738
+ "--quiet",
739
+ ]);
740
+ if (!created.ok)
741
+ return { ok: false, error: `create ${names.backend}: ${created.stderr.trim()}` };
742
+ done.push(`created backend service ${names.backend} (Cloud Armor ${names.policy})`);
743
+ const parsed = parseJson(created.stdout);
744
+ backendJson = (Array.isArray(parsed) ? parsed[0] : parsed) ?? {};
745
+ } else {
746
+ backendJson = (parseJson(backend.stdout) ?? {}) as typeof backendJson;
747
+ if (!backendJson.securityPolicy?.endsWith(`/securityPolicies/${names.policy}`)) {
748
+ const set = await run([
749
+ "compute",
750
+ "backend-services",
751
+ "update",
752
+ names.backend,
753
+ "--global",
754
+ project,
755
+ `--security-policy=${names.policy}`,
756
+ "--quiet",
757
+ ]);
758
+ if (!set.ok) return { ok: false, error: `attach ${names.policy}: ${set.stderr.trim()}` };
759
+ done.push(`attached Cloud Armor ${names.policy} to ${names.backend}`);
760
+ }
761
+ }
762
+ if (
763
+ !(backendJson.backends ?? []).some((b) =>
764
+ b.group?.endsWith(`/networkEndpointGroups/${names.neg}`),
765
+ )
766
+ ) {
767
+ const added = await run([
768
+ "compute",
769
+ "backend-services",
770
+ "add-backend",
771
+ names.backend,
772
+ "--global",
773
+ project,
774
+ `--network-endpoint-group=${names.neg}`,
775
+ `--network-endpoint-group-region=${target.region}`,
776
+ "--quiet",
777
+ ]);
778
+ if (!added.ok)
779
+ return { ok: false, error: `add ${names.neg} to ${names.backend}: ${added.stderr.trim()}` };
780
+ done.push(`added ${names.neg} to ${names.backend}`);
781
+ }
782
+ const selfLink =
783
+ backendJson.selfLink ??
784
+ `https://www.googleapis.com/compute/v1/projects/${target.projectId}/global/backendServices/${names.backend}`;
785
+ return { ok: true, backend: selfLink, done };
550
786
  }
551
787
 
552
- /** Both load-balancer parts on `gcp`; a no-op success on `gcp-firebase`. */
788
+ /**
789
+ * The MCP-Auth-Proxy's load-balancer part on `gcp`, in the order that never
790
+ * routes a request to something that is not there yet: its service (created
791
+ * once with the release's image; mcp-auth-proxy-setup.ts), its Cloud Armor
792
+ * policy, NEG and backend, the URL map's MCP rule, and last the old MCP rule
793
+ * off the gateway's policy. A no-op success on `gcp-firebase`, where the
794
+ * proxy is reached on its own run.app URL.
795
+ */
553
796
  export async function ensureRemoteMcpEdge(
554
- target: Pick<RemoteMcpTarget, "provider" | "projectId" | "env" | "domain">,
797
+ target: Pick<RemoteMcpTarget, "provider" | "projectId" | "region" | "env" | "domain"> & {
798
+ /** The release's digest-pinned proxy image, for a proxy service that does not exist yet. */
799
+ readonly mcpAuthProxyImageRef: string;
800
+ },
555
801
  deps: { run?: GcloudRunner; http?: HttpFetch; sleep?: (ms: number) => Promise<void> } = {},
556
802
  ): Promise<RemoteMcpResult<{ readonly done: string[] }>> {
557
803
  if (target.provider !== "gcp") return { ok: true, done: [] };
804
+ const run = deps.run ?? defaultGcloud;
558
805
  const done: string[] = [];
559
- const armor = await ensureMcpArmorRule(target, deps);
806
+ const service = await ensureMcpAuthProxyServiceGcp({ ...target, provider: "gcp" }, run);
807
+ if (service.status === "failed")
808
+ return { ok: false, error: `${service.id}: ${service.error ?? ""}` };
809
+ done.push(`${service.id}: ${service.status}`);
810
+ const armor = await ensureMcpArmorRule(target, { run });
560
811
  if (!armor.ok) return { ok: false, error: `Cloud Armor: ${armor.error}` };
561
812
  done.push(`Cloud Armor rule ${MCP_ARMOR_RULE.priority} on ${armor.policy}: ${armor.action}`);
562
- const paths = await ensureUrlMapRemoteMcpPaths(target, deps);
813
+ const backend = await ensureMcpAuthProxyBackend(target, { run });
814
+ if (!backend.ok) return { ok: false, error: `backend: ${backend.error}` };
815
+ done.push(...backend.done);
816
+ const paths = await ensureUrlMapRemoteMcpPaths(target, backend.backend, deps);
563
817
  if (!paths.ok) return { ok: false, error: `URL map: ${paths.error}` };
564
818
  done.push(
565
- paths.added.length > 0
566
- ? `URL map: routed ${paths.added.join(", ")} to the gateway`
567
- : "URL map: discovery paths already routed to the gateway",
819
+ paths.changes.length > 0
820
+ ? `URL map: ${paths.changes.join("; ")}`
821
+ : "URL map: remote-MCP paths already routed to the MCP-Auth-Proxy",
568
822
  );
823
+ const retired = await retireGatewayMcpArmorRule(target, { run });
824
+ if (!retired.ok) return { ok: false, error: `gateway Cloud Armor: ${retired.error}` };
825
+ if (retired.removed) {
826
+ done.push(`removed the remote-MCP rule ${MCP_ARMOR_RULE.priority} from ${retired.removed}`);
827
+ }
569
828
  return { ok: true, done };
570
829
  }