@moretea-labs/forge 1.7.1 → 1.8.0

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 (685) hide show
  1. package/AGENTS.md +58 -62
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +42 -62
  4. package/README.md +2 -2
  5. package/SKILL.md +0 -1
  6. package/THIRD_PARTY_NOTICES.md +5 -2
  7. package/adapters/browser/index.ts +2 -0
  8. package/adapters/chatgpt/browser-delivery-host.ts +71 -0
  9. package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
  10. package/adapters/chatgpt/controller-binding-store.ts +55 -0
  11. package/adapters/chatgpt/controller-host.ts +119 -0
  12. package/adapters/chatgpt/controller-round-host.ts +78 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
  14. package/adapters/chatgpt/provider-delivery.ts +113 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/desktop-operator-contract.ts +7 -0
  18. package/adapters/computer/desktop-operator-discovery.ts +201 -0
  19. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  20. package/adapters/computer/desktop-operator-provider.ts +312 -0
  21. package/adapters/computer/index.ts +1 -0
  22. package/adapters/computer/interaction-target-authority.ts +944 -0
  23. package/adapters/controller-process/binding-store.ts +34 -0
  24. package/adapters/controller-process/controller-host.ts +26 -0
  25. package/adapters/mcp/access-mode.ts +132 -0
  26. package/adapters/mcp/audit.ts +32 -0
  27. package/adapters/mcp/auth.ts +407 -0
  28. package/adapters/mcp/controller-round-compatibility.ts +184 -0
  29. package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
  30. package/adapters/mcp/instructions.ts +36 -0
  31. package/adapters/mcp/legacy-context.ts +18 -0
  32. package/adapters/mcp/multi-repository.ts +437 -0
  33. package/adapters/mcp/oauth.ts +391 -0
  34. package/adapters/mcp/paths.ts +140 -0
  35. package/adapters/mcp/policy.ts +232 -0
  36. package/adapters/mcp/proxy-env.ts +62 -0
  37. package/adapters/mcp/readiness-projection.ts +24 -0
  38. package/adapters/mcp/redaction.ts +21 -0
  39. package/adapters/mcp/repo.ts +24 -0
  40. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  41. package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
  42. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
  43. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
  44. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  45. package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
  46. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  47. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
  48. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  49. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  50. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  51. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  52. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
  53. package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
  54. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
  55. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
  56. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  57. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  58. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
  59. package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
  60. package/adapters/mcp/runtime-gateway/router.ts +728 -0
  61. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
  62. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
  63. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  64. package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
  65. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
  66. package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
  67. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
  68. package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
  69. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
  70. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  71. package/adapters/mcp/server.ts +1048 -0
  72. package/adapters/mcp/setup.ts +791 -0
  73. package/adapters/mcp/tool-capability-cache.ts +87 -0
  74. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  75. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
  76. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
  77. package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
  78. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  79. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  80. package/adapters/mcp/toolset-names.ts +74 -0
  81. package/adapters/mcp/toolset-selection.ts +41 -0
  82. package/adapters/mcp/toolset.ts +336 -0
  83. package/adapters/mcp/transports/http.ts +1456 -0
  84. package/adapters/mcp/transports/session-registry.ts +513 -0
  85. package/adapters/mcp/transports/stdio.ts +8 -0
  86. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
  87. package/adapters/mcp/types.ts +41 -0
  88. package/assets/AGENTS.md +2 -16
  89. package/assets/CLAUDE.md +2 -16
  90. package/assets/hooks/AGENTS.md +3 -14
  91. package/assets/hooks/CLAUDE.md +3 -14
  92. package/assets/hooks/hook-input.sh +9 -2
  93. package/assets/hooks/lib/workflow-state.sh +28 -10
  94. package/assets/hooks/prompt-guard.sh +63 -37
  95. package/assets/hooks/security-sentinel.sh +2 -1
  96. package/assets/hooks/session-start-context.sh +7 -5
  97. package/assets/hooks/stop-orchestrator.sh +13 -3
  98. package/assets/initializer-question-pack.v4.json +11 -7
  99. package/assets/partials/05-workflow.partial.md +18 -73
  100. package/assets/partials/07-footer.partial.md +1 -1
  101. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  102. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  103. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  104. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  105. package/assets/plan-map.json +11 -11
  106. package/assets/plugin-registry.v1.json +9 -3
  107. package/assets/plugin-trust-policy.v1.json +6 -0
  108. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  109. package/assets/recovery/forge-wsl-rescue +271 -0
  110. package/assets/reference-configs/development-protocol.md +11 -15
  111. package/assets/reference-configs/document-generation.md +1 -1
  112. package/assets/reference-configs/global-working-rules.md +9 -1
  113. package/assets/reference-configs/harness-overview.md +22 -112
  114. package/assets/reference-configs/hook-operations.md +1 -1
  115. package/assets/reference-configs/sprint-contracts.md +2 -2
  116. package/assets/skill-version.json +2 -2
  117. package/assets/templates/helpers/architecture-event.ts +3 -34
  118. package/assets/templates/helpers/architecture-queue.sh +61 -10
  119. package/assets/templates/helpers/archive-workflow.sh +90 -180
  120. package/assets/templates/helpers/capability-config.ts +8 -11
  121. package/assets/templates/helpers/capability-resolver.ts +37 -19
  122. package/assets/templates/helpers/capture-plan.sh +2 -2
  123. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  124. package/assets/templates/helpers/check-task-sync.sh +82 -11
  125. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  126. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  127. package/assets/templates/helpers/contract-worktree.sh +26 -24
  128. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  129. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  130. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  131. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  132. package/assets/templates/helpers/new-plan.sh +2 -2
  133. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  134. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  135. package/assets/templates/helpers/switch-plan.sh +1 -1
  136. package/assets/templates/helpers/workflow-contract.ts +1 -0
  137. package/assets/workflow-contract.v1.json +63 -171
  138. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  139. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  140. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  141. package/docs/architecture/CURRENT.md +148 -13
  142. package/docs/architecture/EVOLUTION.md +13 -0
  143. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  144. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  145. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  146. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  147. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
  148. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
  149. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  150. package/docs/architecture/domains/verification.md +7 -0
  151. package/docs/architecture/domains/workflow-engine.md +1 -1
  152. package/docs/architecture/index.md +3 -3
  153. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  154. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  155. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  156. package/docs/forge-plugin-management.md +20 -4
  157. package/docs/forge-runtime-self-healing-loop.md +2 -0
  158. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  159. package/docs/operations/controller-browser-plugin.md +3 -1
  160. package/docs/operations/homebrew.md +2 -2
  161. package/docs/operations/platform-support.md +16 -8
  162. package/docs/operations/platform-support.zh-CN.md +2 -2
  163. package/docs/operations/standalone-disaster-recovery.md +26 -1
  164. package/docs/operations/uu-remote-rescue.md +41 -0
  165. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  166. package/docs/public-usage-guide.md +1 -1
  167. package/docs/public-usage-guide.zh-CN.md +1 -1
  168. package/docs/tutorials/01-install-and-start.md +1 -1
  169. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  170. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  171. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  172. package/docs/wiki/Implementation.md +1 -1
  173. package/package.json +23 -14
  174. package/packages/kernel/controller/api/index.ts +2 -0
  175. package/packages/kernel/controller/application/controller-round-service.ts +29 -0
  176. package/packages/kernel/controller/application/controller-service.ts +30 -0
  177. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
  178. package/packages/kernel/controller/domain/controller-round.ts +107 -0
  179. package/packages/kernel/controller/domain/execution-quality.ts +133 -0
  180. package/packages/kernel/controller/domain/types.ts +56 -0
  181. package/packages/kernel/controller/index.ts +1 -0
  182. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  183. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
  184. package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
  185. package/packages/kernel/controller/ports/controller-host.ts +16 -0
  186. package/packages/kernel/goal/api/index.ts +1 -0
  187. package/packages/kernel/goal/domain/types.ts +40 -0
  188. package/packages/kernel/goal/index.ts +1 -0
  189. package/packages/kernel/identity/api/index.ts +5 -0
  190. package/packages/kernel/identity/application/identity-service.ts +67 -0
  191. package/packages/kernel/identity/domain/scope.ts +63 -0
  192. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  193. package/packages/kernel/identity/domain/types.ts +44 -0
  194. package/packages/kernel/identity/domain/workspace.ts +56 -0
  195. package/packages/kernel/identity/index.ts +1 -0
  196. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  197. package/packages/kernel/memory/api/index.ts +5 -0
  198. package/packages/kernel/memory/application/experience-service.ts +118 -0
  199. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  200. package/packages/kernel/memory/domain/experience.ts +103 -0
  201. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  202. package/packages/kernel/memory/index.ts +1 -0
  203. package/packages/kernel/progression/api/index.ts +2 -0
  204. package/packages/kernel/progression/application/projector.ts +211 -0
  205. package/packages/kernel/progression/domain/types.ts +116 -0
  206. package/packages/kernel/progression/index.ts +1 -0
  207. package/packages/kernel/scheduler/api/index.ts +6 -0
  208. package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
  209. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  210. package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
  211. package/packages/kernel/scheduler/application/settlement.ts +197 -0
  212. package/packages/kernel/scheduler/domain/continuation.ts +38 -0
  213. package/packages/kernel/scheduler/domain/schedule.ts +149 -0
  214. package/packages/kernel/scheduler/index.ts +1 -0
  215. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
  216. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
  217. package/packages/kernel/work/api/index.ts +15 -0
  218. package/packages/kernel/work/application/work-service.ts +148 -0
  219. package/packages/kernel/work/domain/admission-policy.ts +34 -0
  220. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  221. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  222. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  223. package/packages/kernel/work/domain/engineering-design.ts +281 -0
  224. package/packages/kernel/work/domain/engineering-profile.ts +242 -0
  225. package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
  226. package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
  227. package/packages/kernel/work/domain/implementation-review.ts +510 -0
  228. package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
  229. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  230. package/packages/kernel/work/domain/state-machine.ts +259 -0
  231. package/packages/kernel/work/domain/types.ts +528 -0
  232. package/packages/kernel/work/index.ts +1 -0
  233. package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
  234. package/packages/kernel/work/ports/work-contract-store.ts +18 -0
  235. package/packages/plugin-runtime/browser/index.ts +3 -0
  236. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  237. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  238. package/packages/plugin-runtime/browser/session-authority.ts +61 -0
  239. package/packages/plugin-runtime/computer/index.ts +6 -0
  240. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  241. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  242. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  243. package/packages/plugin-runtime/computer/provider.ts +19 -0
  244. package/packages/plugin-runtime/computer/target-authority.ts +190 -0
  245. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  246. package/packages/plugin-runtime/external/index.ts +1 -0
  247. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
  248. package/packages/plugin-runtime/index.ts +3 -0
  249. package/packages/protocols/browser/index.ts +1 -0
  250. package/packages/protocols/browser/session.ts +98 -0
  251. package/packages/protocols/computer/contract.ts +199 -0
  252. package/packages/protocols/computer/index.ts +1 -0
  253. package/packages/protocols/failure/contract.ts +158 -0
  254. package/packages/protocols/failure/index.ts +1 -0
  255. package/packages/protocols/handoff/index.ts +1 -0
  256. package/packages/protocols/handoff/status.ts +22 -0
  257. package/packages/protocols/mcp/execution-context.ts +11 -0
  258. package/packages/protocols/mcp/index.ts +2 -0
  259. package/packages/protocols/mcp/tool-contract.ts +42 -0
  260. package/packages/workflow-runtime/api/index.ts +2 -0
  261. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  262. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  263. package/packages/workflow-runtime/index.ts +1 -0
  264. package/scripts/assemble-template.ts +1 -1
  265. package/scripts/benchmark-computer-hotpath.ts +255 -0
  266. package/scripts/benchmark-controller-round2.ts +54 -2
  267. package/scripts/benchmark-ios-agent-device.ts +3 -6
  268. package/scripts/benchmark-mcp-transport.ts +3 -4
  269. package/scripts/benchmark-operational-memory-activation.ts +235 -0
  270. package/scripts/benchmark-operational-shadow.ts +28 -0
  271. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  272. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  273. package/scripts/canary-controller-semantic-wait.ts +195 -0
  274. package/scripts/canary-global-hook.sh +2 -2
  275. package/scripts/check-mcp-compatibility.ts +173 -7
  276. package/scripts/check-platform-support.mjs +28 -2
  277. package/scripts/check-public-docs.sh +1 -1
  278. package/scripts/check-release-readiness.sh +1 -1
  279. package/scripts/check-repository-hygiene.mjs +92 -0
  280. package/scripts/check-runtime-architecture.mjs +1206 -95
  281. package/scripts/check-source-duplication.mjs +9 -1
  282. package/scripts/check-tarball-install-smoke.sh +98 -15
  283. package/scripts/controller-ngrok-rotation.sh +5 -4
  284. package/scripts/create-project-dirs.sh +10 -74
  285. package/scripts/forge-local-recovery-helper.mjs +204 -0
  286. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  287. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  288. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  289. package/scripts/hook-shim.sh +2 -2
  290. package/scripts/init-project.sh +12 -70
  291. package/scripts/install-desktop-operator-registration.ts +4 -2
  292. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  293. package/scripts/install-standalone-recovery.ts +122 -10
  294. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  295. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  296. package/scripts/lib/controller-home-env.sh +6 -3
  297. package/scripts/lib/project-init-lib.sh +39 -582
  298. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  299. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  300. package/scripts/open-source-audit-allowlist.txt +12 -1
  301. package/scripts/portability-bindings.mjs +70 -0
  302. package/scripts/run-bounded-command.mjs +55 -0
  303. package/scripts/run-bun-test-file.ts +22 -6
  304. package/scripts/run-governed-gate.ts +4 -1
  305. package/scripts/smoke-mcp-http-runtime.ts +1 -1
  306. package/scripts/smoke-runtime-recovery.ts +34 -1
  307. package/scripts/smoke-schedule-engine.ts +1 -1
  308. package/scripts/stage-runtime-release.ts +6 -1
  309. package/scripts/test-governance.ts +51 -2
  310. package/src/cli/agent-jobs/job-manager.ts +10 -1
  311. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  312. package/src/cli/chatgpt-browser/binding.ts +20 -10
  313. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  314. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  315. package/src/cli/chatgpt-browser/engine.ts +10 -11
  316. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  317. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  318. package/src/cli/chatgpt-browser/types.ts +1 -0
  319. package/src/cli/commands/bootstrap-control.ts +334 -0
  320. package/src/cli/commands/brain-assistant.ts +38 -0
  321. package/src/cli/commands/brain.ts +193 -40
  322. package/src/cli/commands/chatgpt.ts +127 -0
  323. package/src/cli/commands/computer.ts +437 -0
  324. package/src/cli/commands/doctor.ts +18 -1
  325. package/src/cli/commands/init-hook.ts +71 -61
  326. package/src/cli/commands/init.ts +3 -1
  327. package/src/cli/commands/mcp.ts +16 -10
  328. package/src/cli/commands/plugin.ts +38 -12
  329. package/src/cli/commands/recovery.ts +493 -80
  330. package/src/cli/commands/runtime.ts +9 -4
  331. package/src/cli/commands/setup-profile.ts +89 -39
  332. package/src/cli/commands/status.ts +2 -1
  333. package/src/cli/controller/check-runner.ts +487 -98
  334. package/src/cli/controller/context/types.ts +7 -1
  335. package/src/cli/controller/context-pack.ts +23 -3
  336. package/src/cli/controller/issue-store.ts +1 -1
  337. package/src/cli/controller/systemd-user.ts +110 -0
  338. package/src/cli/controller/types.ts +10 -70
  339. package/src/cli/controller/worklog.ts +67 -13
  340. package/src/cli/editing/edit-operation-contract.ts +92 -0
  341. package/src/cli/editing/edit-session.ts +289 -13
  342. package/src/cli/github/plugin.ts +17 -12
  343. package/src/cli/hook/route-registry.ts +3 -5
  344. package/src/cli/hook/runtime.ts +49 -3
  345. package/src/cli/index.ts +3 -0
  346. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  347. package/src/cli/local-bridge/console-automations.ts +2 -2
  348. package/src/cli/local-bridge/facade-api.ts +41 -14
  349. package/src/cli/local-bridge/job-store.ts +1 -1
  350. package/src/cli/local-bridge/server.ts +24 -27
  351. package/src/cli/mcp/access-mode.ts +2 -132
  352. package/src/cli/mcp/access-tools.ts +2 -387
  353. package/src/cli/mcp/audit.ts +2 -32
  354. package/src/cli/mcp/auth.ts +2 -404
  355. package/src/cli/mcp/instructions.ts +2 -34
  356. package/src/cli/mcp/legacy-context.ts +2 -16
  357. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  358. package/src/cli/mcp/multi-repository.ts +2 -436
  359. package/src/cli/mcp/oauth.ts +2 -375
  360. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  361. package/src/cli/mcp/paths.ts +2 -140
  362. package/src/cli/mcp/policy.ts +2 -232
  363. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  364. package/src/cli/mcp/proxy-env.ts +2 -62
  365. package/src/cli/mcp/readiness-projection.ts +2 -24
  366. package/src/cli/mcp/redaction.ts +2 -21
  367. package/src/cli/mcp/repo.ts +2 -24
  368. package/src/cli/mcp/repository-tools.ts +2 -1585
  369. package/src/cli/mcp/server.ts +2 -951
  370. package/src/cli/mcp/setup.ts +2 -737
  371. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  372. package/src/cli/mcp/tool-contract.ts +2 -14
  373. package/src/cli/mcp/tools.ts +2 -9
  374. package/src/cli/mcp/toolset-names.ts +2 -70
  375. package/src/cli/mcp/toolset-selection.ts +2 -41
  376. package/src/cli/mcp/toolset.ts +2 -336
  377. package/src/cli/mcp/transports/AGENTS.md +1 -10
  378. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  379. package/src/cli/mcp/transports/http.ts +2 -1414
  380. package/src/cli/mcp/transports/session-registry.ts +2 -490
  381. package/src/cli/mcp/transports/stdio.ts +2 -8
  382. package/src/cli/mcp/types.ts +2 -41
  383. package/src/cli/mcp/umbrella-tools.ts +2 -117
  384. package/src/cli/repositories/command-executor.ts +64 -18
  385. package/src/cli/repositories/command-scope.ts +117 -1
  386. package/src/cli/repositories/controller-home.ts +119 -1
  387. package/src/cli/repositories/identity.ts +13 -0
  388. package/src/cli/repositories/registry.ts +68 -49
  389. package/src/cli/repositories/repository-snapshot.ts +53 -7
  390. package/src/cli/repositories/runtime-storage.ts +27 -2
  391. package/src/cli/repositories/safe-patch.ts +34 -38
  392. package/src/cli/repositories/selected-path-actions.ts +22 -2
  393. package/src/cli/repositories/structured-git.ts +105 -25
  394. package/src/cli/runtime/helper-runner.ts +7 -0
  395. package/src/cli/runtime-invocation.ts +8 -1
  396. package/src/cli/tools/codegraph.ts +44 -25
  397. package/src/core/adoption/manifest-templates.ts +1 -1
  398. package/src/core/adoption/plan.ts +3 -26
  399. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  400. package/src/effects/process-runner.ts +8 -14
  401. package/src/runtime/context/assistant-context.ts +154 -0
  402. package/src/runtime/context/assistant-work-context.ts +147 -0
  403. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  404. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  405. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  406. package/src/runtime/context/context-closure.ts +268 -0
  407. package/src/runtime/context/context-plane.ts +211 -0
  408. package/src/runtime/context/project-engineering-contract.ts +60 -0
  409. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  410. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  411. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  412. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  413. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  414. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  415. package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
  416. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  417. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
  418. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
  419. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
  420. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
  421. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +417 -100
  422. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
  423. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  424. package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
  425. package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
  426. package/src/runtime/control-plane/execution/validation.ts +28 -3
  427. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
  428. package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
  429. package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
  430. package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
  431. package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
  432. package/src/runtime/control-plane/execution/work-handle-authority.ts +267 -16
  433. package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
  434. package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
  435. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
  436. package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
  437. package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
  438. package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
  439. package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
  440. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
  441. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
  442. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  443. package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
  444. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
  445. package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
  446. package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
  447. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  448. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
  449. package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
  450. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
  451. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
  452. package/src/runtime/control-plane/facade/index.ts +4 -2
  453. package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
  454. package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
  455. package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
  456. package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
  457. package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
  458. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
  459. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  460. package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
  461. package/src/runtime/control-plane/facade/types.ts +189 -532
  462. package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
  463. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  464. package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
  465. package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
  466. package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
  467. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
  468. package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
  469. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  470. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  471. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
  472. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  473. package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
  474. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  475. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
  476. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  477. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
  478. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
  479. package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
  480. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  481. package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
  482. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  483. package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
  484. package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
  485. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  486. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  487. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  488. package/src/runtime/control-plane/release-retention.ts +56 -16
  489. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  490. package/src/runtime/control-plane/routing/route-policy.ts +19 -0
  491. package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
  492. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  493. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
  494. package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
  495. package/src/runtime/diagnostics/incident-repair.ts +406 -0
  496. package/src/runtime/diagnostics/mcp-timing.ts +125 -6
  497. package/src/runtime/diagnostics/performance.ts +6 -14
  498. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  499. package/src/runtime/evidence/artifact-store.ts +65 -2
  500. package/src/runtime/evidence/operational-shadow.ts +38 -0
  501. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  502. package/src/runtime/evidence/sensitive-output.ts +4 -4
  503. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  504. package/src/runtime/execution/jobs/store.ts +121 -2
  505. package/src/runtime/execution/managed-workspace.ts +55 -5
  506. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  507. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  508. package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
  509. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  510. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  511. package/src/runtime/execution/process-runtime/check-result.ts +73 -2
  512. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
  513. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  514. package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
  515. package/src/runtime/execution/process-runtime/index.ts +1 -0
  516. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  517. package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
  518. package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
  519. package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
  520. package/src/runtime/execution/process-runtime/runtime.ts +132 -15
  521. package/src/runtime/execution/process-runtime/types.ts +22 -0
  522. package/src/runtime/execution/workers/executor.ts +16 -13
  523. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  524. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  525. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  526. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  527. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  528. package/src/runtime/gateway/mcp/router.ts +2 -1125
  529. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  530. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6651
  531. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  532. package/src/runtime/maintenance/cleanup.ts +38 -24
  533. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  534. package/src/runtime/platform/computer-platform.ts +14 -0
  535. package/src/runtime/platform/executable-discovery.ts +54 -0
  536. package/src/runtime/platform/fetch-http-server.ts +90 -0
  537. package/src/runtime/platform/service-manager.ts +138 -0
  538. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  539. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  540. package/src/runtime/plugins/action-application.ts +135 -0
  541. package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
  542. package/src/runtime/plugins/browser-adapter.ts +140 -100
  543. package/src/runtime/plugins/browser-automation-service.ts +15 -36
  544. package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
  545. package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
  546. package/src/runtime/plugins/browser-native-session-service.ts +1 -1
  547. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  548. package/src/runtime/plugins/browser-registration.ts +16 -0
  549. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  550. package/src/runtime/plugins/browser-runtime.ts +41 -9
  551. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  552. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  553. package/src/runtime/plugins/browser-session-store.ts +229 -36
  554. package/src/runtime/plugins/browser-session-types.ts +2 -98
  555. package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
  556. package/src/runtime/plugins/catalog-trust.ts +82 -0
  557. package/src/runtime/plugins/computer-registration.ts +839 -0
  558. package/src/runtime/plugins/config-store.ts +119 -0
  559. package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
  560. package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
  561. package/src/runtime/plugins/errors.ts +8 -1
  562. package/src/runtime/plugins/external-adapter.ts +45 -322
  563. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  564. package/src/runtime/plugins/external-registration.ts +9 -1
  565. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  566. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  567. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  568. package/src/runtime/plugins/first-party-registry.ts +3 -1
  569. package/src/runtime/plugins/github-adapter.ts +1 -2
  570. package/src/runtime/plugins/gmail-adapter.ts +6 -6
  571. package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
  572. package/src/runtime/plugins/google-shared.ts +31 -46
  573. package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
  574. package/src/runtime/plugins/ios-adapter.ts +1 -2
  575. package/src/runtime/plugins/local-system-adapter.ts +180 -0
  576. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  577. package/src/runtime/plugins/resend-adapter.ts +15 -24
  578. package/src/runtime/plugins/store.ts +493 -51
  579. package/src/runtime/plugins/types.ts +8 -1
  580. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  581. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  582. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  583. package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
  584. package/src/runtime/projections/controller-context.ts +12 -6
  585. package/src/runtime/projections/git-status-sampler.ts +73 -22
  586. package/src/runtime/projections/materialized-view.ts +14 -8
  587. package/src/runtime/recovery/capability-action-application.ts +228 -0
  588. package/src/runtime/recovery/index.ts +3 -0
  589. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  590. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  591. package/src/runtime/recovery/maintenance-executor.ts +358 -76
  592. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  593. package/src/runtime/release/identity.ts +6 -0
  594. package/src/runtime/resources/leases/store.ts +1 -1
  595. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  596. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  597. package/src/runtime/root/browser-session-composition.ts +59 -0
  598. package/src/runtime/root/computer-composition.ts +191 -0
  599. package/src/runtime/root/computer-target-composition.ts +16 -0
  600. package/src/runtime/root/computer-target-persistence.ts +39 -0
  601. package/src/runtime/root/controller-round-composition.ts +107 -0
  602. package/src/runtime/root/controller-services.ts +2 -0
  603. package/src/runtime/root/entry.ts +4 -3
  604. package/src/runtime/root/gateway-adapter.ts +3 -3
  605. package/src/runtime/root/local-bridge.ts +5 -2
  606. package/src/runtime/root/mcp-transport.ts +5 -5
  607. package/src/runtime/root/package-connector-service.ts +108 -56
  608. package/src/runtime/root/package-runtime-release.ts +86 -18
  609. package/src/runtime/root/package-runtime-service.ts +63 -86
  610. package/src/runtime/root/release-execution-canary.ts +71 -0
  611. package/src/runtime/root/release-loader.ts +68 -0
  612. package/src/runtime/root/release-manifest.ts +106 -2
  613. package/src/runtime/root/release-materialize.ts +103 -4
  614. package/src/runtime/root/release-store.ts +31 -2
  615. package/src/runtime/root/runtime.ts +240 -25
  616. package/src/runtime/root/scheduled-controller-composition.ts +51 -0
  617. package/src/runtime/root/scheduler.ts +12 -4
  618. package/src/runtime/root/service-runner.ts +1 -1
  619. package/src/runtime/root/service.ts +41 -16
  620. package/src/runtime/root/status.ts +1 -0
  621. package/src/runtime/root/types.ts +24 -1
  622. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  623. package/src/runtime/shared/process-environment.ts +19 -4
  624. package/src/runtime/shared/storage-capacity.ts +68 -0
  625. package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
  626. package/src/runtime/standalone-recovery/core.ts +984 -230
  627. package/src/runtime/standalone-recovery/entry.ts +182 -51
  628. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  629. package/src/runtime/standalone-recovery/installer.ts +212 -104
  630. package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
  631. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  632. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  633. package/src/runtime/standalone-recovery/performance.ts +135 -0
  634. package/src/runtime/standalone-recovery/release.ts +5 -0
  635. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  636. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
  637. package/src/runtime/workflow/schedules/engine.ts +191 -343
  638. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  639. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  640. package/src/runtime/workflow/schedules/store.ts +2 -607
  641. package/src/runtime/workflow/schedules/types.ts +2 -127
  642. package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
  643. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  644. package/src/runtime/workflows/runtime.ts +486 -0
  645. package/src/testing/test-governance.ts +90 -29
  646. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  647. package/docs/architecture/history/README.md +0 -7
  648. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  649. package/docs/architecture/history/global-hook-runtime.md +0 -166
  650. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  651. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  652. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  653. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  654. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  655. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  656. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  657. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  658. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  659. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  660. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  661. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  662. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  663. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  664. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  665. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  666. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  667. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  668. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  669. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  670. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  671. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  672. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  673. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  674. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  675. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  676. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  677. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  678. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  679. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  680. package/docs/architecture/requests/archive/2026/root.md +0 -68
  681. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  682. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  683. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  684. package/scripts/archive-architecture-request.sh +0 -4
  685. package/scripts/run-skill-evals.ts +0 -1022
@@ -1,1414 +1,2 @@
1
- import { randomUUID, timingSafeEqual } from 'crypto';
2
- import { existsSync, watch } from 'fs';
3
- import { dirname } from 'path';
4
- import express, { type Request, type Response, type NextFunction } from 'express';
5
- import { tokenHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/token.js';
6
- import { revocationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/revoke.js';
7
- import { clientRegistrationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/register.js';
8
- import { redirectUriMatches } from '@modelcontextprotocol/sdk/server/auth/handlers/authorize.js';
9
- import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
10
- import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
11
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
12
- import {
13
- buildMultiRepositoryToolDefinitions,
14
- createCanonicalRuntimeProxy,
15
- createMcpToolContext,
16
- createForgeMcpServerFromContext,
17
- readCanonicalRuntimeToolSchema,
18
- type CanonicalRuntimeProxy,
19
- type CanonicalRuntimeToolSchema,
20
- type McpServerOptions,
21
- } from '../server';
22
- import {
23
- loadMcpServiceLocalConfig,
24
- loadMcpServiceRuntimeState,
25
- mcpServiceOAuthTokenStoreFallbackPaths,
26
- mcpServiceOAuthTokenStorePath,
27
- parseMcpHttpAuthMode,
28
- readMcpServiceBearerToken,
29
- readMcpServiceOAuthPassphrase,
30
- type McpLocalConfig,
31
- type McpHttpAuthMode,
32
- } from '../auth';
33
- import { createMcpOAuthProvider, McpOAuthTokenStore } from '../oauth';
34
- import { resolveMcpRepoRoot } from '../repo';
35
- import { buildMcpToolDefinitions } from '../tools';
36
- import { resolveControllerHome } from '../../repositories/controller-home';
37
- import {
38
- controllerExposureSnapshot,
39
- } from '../toolset';
40
- import { readForgeRuntimeStatus } from '../../../runtime/control-plane/runtime-status-client';
41
- import { invalidateExecutionSession } from '../../../runtime/control-plane/execution/session-store';
42
- import { runtimeIdentitySnapshot } from '../../../runtime/gateway/mcp/runtime-tools';
43
- import { projectionBlocksReadiness, readRepositoryProjectionSnapshot } from '../../../runtime/projections/materialized-view';
44
- import { readRuntimeGeneration } from '../../../runtime/control-plane/runtime-generation';
45
- import { readRuntimeStatusSnapshot, runtimeStatusPath } from '../../../runtime/root/status';
46
- import { getRepository, listRepositories } from '../../repositories/registry';
47
- import { buildControllerTaskLedgerProjection } from '../../controller/task-ledger';
48
- import { legacyIssueAuthorityRetired } from '../../controller/legacy-issue-cutover';
49
- import { reconcileReadinessProjectionSource } from '../readiness-projection';
50
- import {
51
- FORGE_MCP_SCHEMA_VERSION,
52
- FORGE_TOOL_SURFACE,
53
- FORGE_VERSION,
54
- repositoryIdentity,
55
- } from '../../controller/runtime-config';
56
- import { McpSessionRegistry, type McpSessionRoute } from './session-registry';
57
-
58
- export interface McpHttpOptions extends McpServerOptions {
59
- host?: string;
60
- port?: number;
61
- authToken?: string;
62
- auth?: string;
63
- }
64
-
65
- function localControllerDiagnosticMatchesRuntime(
66
- payload: Record<string, unknown> | null,
67
- generation?: string,
68
- ): boolean {
69
- return payload?.status === 'ok'
70
- && payload.toolSurface === FORGE_TOOL_SURFACE
71
- && payload.schemaVersion === FORGE_MCP_SCHEMA_VERSION
72
- && payload.version === FORGE_VERSION
73
- && (generation === undefined || payload.generation === generation);
74
- }
75
-
76
- function bearerFromRequest(req: Request): string | null {
77
- const header = req.headers.authorization;
78
- if (typeof header !== 'string') return null;
79
- const match = /^Bearer\s+(.+)$/i.exec(header.trim());
80
- return match ? match[1].trim() : null;
81
- }
82
-
83
- function principalFromRequest(req: Request): string {
84
- const auth = (req as unknown as { auth?: { clientId?: string } }).auth;
85
- if (auth?.clientId?.trim()) return `oauth-client:${auth.clientId.trim()}`;
86
- return bearerFromRequest(req) ? 'mcp-bearer-client' : 'controller-http-client';
87
- }
88
-
89
- export function isAuthorizedMcpHttpRequest(req: Request, expectedToken: string | null): boolean {
90
- if (!expectedToken) return false;
91
- return bearerFromRequest(req) === expectedToken;
92
- }
93
-
94
- function rawBodyToJson(body: Buffer): unknown | undefined {
95
- if (body.length === 0) return undefined;
96
- return JSON.parse(body.toString('utf-8'));
97
- }
98
-
99
- function isInitializeRequest(body: unknown): boolean {
100
- return typeof body === 'object' && body !== null && (body as Record<string, unknown>).method === 'initialize';
101
- }
102
-
103
- function isServerDiscoverRequest(body: unknown): boolean {
104
- return typeof body === 'object' && body !== null && (body as Record<string, unknown>).method === 'server/discover';
105
- }
106
-
107
- function sendLegacyServerDiscoverUnsupported(res: Response, body: unknown): void {
108
- const record = typeof body === 'object' && body !== null ? body as Record<string, unknown> : {};
109
- const id = typeof record.id === 'string' || typeof record.id === 'number' || record.id === null ? record.id : null;
110
- res.setHeader('Cache-Control', 'no-store');
111
- res.status(404).json({
112
- jsonrpc: '2.0',
113
- id,
114
- error: {
115
- code: -32601,
116
- message: 'Method not found',
117
- },
118
- });
119
- }
120
-
121
- function initializeClientIdentity(req: Request, body: unknown, route: McpSessionRoute, principalId: string): string {
122
- const params = typeof body === 'object' && body !== null
123
- ? (body as { params?: { clientInfo?: { name?: unknown; version?: unknown } } }).params
124
- : undefined;
125
- const clientInfo = params?.clientInfo;
126
- const clientName = typeof clientInfo?.name === 'string' && clientInfo.name.trim() ? clientInfo.name.trim() : 'unknown-client';
127
- const clientVersion = typeof clientInfo?.version === 'string' && clientInfo.version.trim() ? clientInfo.version.trim() : 'unknown-version';
128
- const userAgent = typeof req.headers['user-agent'] === 'string' && req.headers['user-agent'].trim()
129
- ? req.headers['user-agent'].trim().slice(0, 160)
130
- : 'unknown-agent';
131
- return `${principalId}|${route}|${clientName}/${clientVersion}|${userAgent}`;
132
- }
133
-
134
- export interface McpSessionLookupErrorResponse {
135
- status: 400 | 404;
136
- body: {
137
- error: 'missing_session' | 'session_not_found';
138
- code: 'MCP_SESSION_REQUIRED' | 'MCP_SESSION_EXPIRED';
139
- message: string;
140
- recoverable: true;
141
- action: 'reinitialize';
142
- };
143
- }
144
-
145
- export function mcpSessionLookupError(sessionId: string | undefined): McpSessionLookupErrorResponse {
146
- if (!sessionId?.trim()) {
147
- return {
148
- status: 400,
149
- body: {
150
- error: 'missing_session',
151
- code: 'MCP_SESSION_REQUIRED',
152
- message: 'Mcp-Session-Id header is required for this request.',
153
- recoverable: true,
154
- action: 'reinitialize',
155
- },
156
- };
157
- }
158
- return {
159
- status: 404,
160
- body: {
161
- error: 'session_not_found',
162
- code: 'MCP_SESSION_EXPIRED',
163
- message: 'MCP session not found or expired; initialize a new session.',
164
- recoverable: true,
165
- action: 'reinitialize',
166
- },
167
- };
168
- }
169
-
170
- export function sendMcpSessionLookupError(res: Response, sessionId: string | undefined): void {
171
- const response = mcpSessionLookupError(sessionId);
172
- res.setHeader('Cache-Control', 'no-store');
173
- res.setHeader('Mcp-Session-Reset', 'reinitialize');
174
- res.setHeader('x-forge-session-reset', 'reinitialize');
175
- res.status(response.status).json(response.body);
176
- }
177
-
178
- export function mcpSessionToolSurfaceFingerprintIsCurrent(
179
- sessionFingerprint: string | undefined,
180
- currentFingerprint: string | undefined,
181
- ): boolean {
182
- // Missing metadata is tolerated for compatibility and transient
183
- // controller-home read failures. Once both sides are known, equality is the
184
- // schema fence. Runtime generation remains diagnostic-only.
185
- return !sessionFingerprint || !currentFingerprint || sessionFingerprint === currentFingerprint;
186
- }
187
-
188
- export async function resolveMcpSessionCurrentFingerprint(
189
- publishedFingerprint: string | undefined,
190
- loadRuntimeFingerprint?: () => Promise<string | undefined>,
191
- ): Promise<string | undefined> {
192
- // Runtime status publishes the exact schema fingerprint observed at session
193
- // initialization/cutover. Prefer that O(1) fence on the hot path. If the
194
- // publication is temporarily unavailable, retain the previous fail-safe
195
- // behavior by asking the Canonical Runtime directly.
196
- if (publishedFingerprint) return publishedFingerprint;
197
- return await loadRuntimeFingerprint?.();
198
- }
199
-
200
- export function isMcpToolsListRequest(body: unknown): boolean {
201
- const messages = Array.isArray(body) ? body : [body];
202
- return messages.length > 0 && messages.every((message) => {
203
- if (!message || typeof message !== 'object') return false;
204
- return (message as { method?: unknown }).method === 'tools/list';
205
- });
206
- }
207
-
208
- function toolCallOutsideSessionSchema(body: unknown, toolNames: readonly string[] | undefined): boolean {
209
- if (!toolNames) return false;
210
- const messages = Array.isArray(body) ? body : [body];
211
- return messages.some((message) => {
212
- if (!message || typeof message !== 'object') return false;
213
- const rpc = message as { method?: unknown; params?: { name?: unknown } };
214
- return rpc.method === 'tools/call'
215
- && typeof rpc.params?.name === 'string'
216
- && !toolNames.includes(rpc.params.name);
217
- });
218
- }
219
-
220
- function sendMcpToolSurfaceReset(
221
- res: Response,
222
- sessionFingerprint: string | undefined,
223
- currentFingerprint: string | undefined,
224
- ): void {
225
- res.setHeader('Cache-Control', 'no-store');
226
- res.setHeader('Mcp-Session-Reset', 'reinitialize');
227
- res.setHeader('x-forge-session-reset', 'reinitialize');
228
- res.setHeader('x-forge-session-reset-reason', 'tool_surface_changed');
229
- res.status(404).json({
230
- error: 'session_not_found',
231
- code: 'MCP_TOOL_SURFACE_CHANGED',
232
- message: 'MCP Runtime tool surface changed; initialize a new session so tools/list is refreshed.',
233
- recoverable: true,
234
- action: 'reinitialize',
235
- previousFingerprint: sessionFingerprint,
236
- currentFingerprint,
237
- });
238
- }
239
-
240
- export function mcpRequestError(error: unknown) {
241
- const rawMessage = error instanceof Error ? error.message : String(error);
242
- const retryable = /(?:\b502\b|\b503\b|\b429\b|ECONNRESET|ETIMEDOUT|EAI_AGAIN|CANONICAL_RUNTIME_TIMEOUT|server_busy|session_capacity|gateway)/i.test(rawMessage);
243
- return {
244
- status: (retryable ? 503 : 500) as 500 | 503,
245
- body: {
246
- error: 'request_failed' as const,
247
- code: (retryable ? 'MCP_TRANSIENT_FAILURE' : 'MCP_REQUEST_FAILED') as 'MCP_TRANSIENT_FAILURE' | 'MCP_REQUEST_FAILED',
248
- message: retryable
249
- ? 'The MCP Runtime is temporarily unavailable; retry shortly.'
250
- : 'The MCP request could not be completed.',
251
- recoverable: true as const,
252
- retryable,
253
- sessionPreserved: true as const,
254
- action: 'retry' as const,
255
- },
256
- };
257
- }
258
-
259
- export function sendMcpRequestError(res: Response, error: unknown): void {
260
- const response = mcpRequestError(error);
261
- res.setHeader('Cache-Control', 'no-store');
262
- res.setHeader('x-forge-session-preserved', 'true');
263
- res.status(response.status).json(response.body);
264
- }
265
-
266
- function getConfiguredPublicOrigin(config: McpLocalConfig | null): string | undefined {
267
- const configured = process.env.FORGE_MCP_PUBLIC_ORIGIN?.trim();
268
- if (configured) {
269
- try {
270
- return new URL(configured).origin;
271
- } catch (_error) {
272
- // Fall through to service or legacy config.
273
- }
274
- }
275
- const endpoint = config?.chatgpt?.endpoint?.trim();
276
- if (!endpoint) return undefined;
277
- try {
278
- return new URL(endpoint).origin;
279
- } catch (_error) {
280
- return undefined;
281
- }
282
- }
283
-
284
- function getPublicOrigin(req: Request, configuredOrigin: string | undefined): string {
285
- if (configuredOrigin) return configuredOrigin;
286
- const proto = (req.headers['x-forwarded-proto'] as string | undefined) ?? 'https';
287
- const host = (req.headers['x-forwarded-host'] as string | undefined) ?? req.headers.host ?? '127.0.0.1:8765';
288
- return `${proto}://${host}`;
289
- }
290
-
291
- function localControllerHealthUrl(host: string, port: number): string {
292
- return `http://${host === '::1' ? '[::1]' : host}:${port}/health`;
293
- }
294
-
295
- async function jsonHealth(url: string): Promise<Record<string, unknown> | null> {
296
- const controller = new AbortController();
297
- const timeout = setTimeout(() => controller.abort(), 2_000);
298
- try {
299
- const response = await fetch(url, {
300
- signal: controller.signal,
301
- headers: { accept: 'application/json' },
302
- });
303
- if (!response.ok) return null;
304
- return await response.json() as Record<string, unknown>;
305
- } catch (_error) {
306
- return null;
307
- } finally {
308
- clearTimeout(timeout);
309
- }
310
- }
311
-
312
- export function isAllowedMcpOAuthRedirectUri(redirectUri: string): boolean {
313
- try {
314
- const url = new URL(redirectUri);
315
- if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
316
- return true;
317
- }
318
- if (
319
- url.protocol === 'https:' &&
320
- (url.origin === 'https://chatgpt.com' || url.origin === 'https://chat.openai.com') &&
321
- url.pathname.startsWith('/connector/oauth/')
322
- ) {
323
- return true;
324
- }
325
- return false;
326
- } catch (_error) {
327
- return false;
328
- }
329
- }
330
-
331
- function isRegisteredRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
332
- return (client.redirect_uris ?? []).some((registered) => redirectUriMatches(redirectUri, registered));
333
- }
334
-
335
- function isRegisteredExternalHttpsRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
336
- try {
337
- const url = new URL(redirectUri);
338
- return url.protocol === 'https:' && !url.username && !url.password && isRegisteredRedirectUri(redirectUri, client);
339
- } catch (_error) {
340
- return false;
341
- }
342
- }
343
-
344
- function isSafeOAuthFallbackRedirectUri(redirectUri: string): boolean {
345
- try {
346
- const url = new URL(redirectUri);
347
- if (url.username || url.password) return false;
348
- if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return true;
349
- return url.protocol === 'https:';
350
- } catch (_error) {
351
- return false;
352
- }
353
- }
354
-
355
- async function getOrRegisterPublicOAuthClient(
356
- provider: ReturnType<typeof createMcpOAuthProvider>,
357
- clientId: string,
358
- redirectUri: string | undefined,
359
- req: Request,
360
- ): Promise<OAuthClientInformationFull | undefined> {
361
- const existing = await provider.clientsStore.getClient(clientId);
362
- if (existing) return existing as OAuthClientInformationFull;
363
- if (!redirectUri || !isSafeOAuthFallbackRedirectUri(redirectUri) || !provider.clientsStore.registerClient) return undefined;
364
- oauthTrace(req, 'authorize:auto_register_public_client', {
365
- redirectScheme: new URL(redirectUri).protocol,
366
- redirectHost: new URL(redirectUri).hostname,
367
- });
368
- return provider.clientsStore.registerClient({
369
- client_id: clientId,
370
- client_id_issued_at: Math.floor(Date.now() / 1000),
371
- client_name: 'forge OAuth fallback client',
372
- redirect_uris: [redirectUri],
373
- token_endpoint_auth_method: 'none',
374
- grant_types: ['authorization_code', 'refresh_token'],
375
- response_types: ['code'],
376
- } as unknown as Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>) as OAuthClientInformationFull;
377
- }
378
-
379
- function escapeHtmlAttribute(value: string): string {
380
- return value
381
- .replace(/&/g, '&amp;')
382
- .replace(/"/g, '&quot;')
383
- .replace(/'/g, '&#39;')
384
- .replace(/</g, '&lt;')
385
- .replace(/>/g, '&gt;');
386
- }
387
-
388
- function renderPassphrasePage(params: URLSearchParams): string {
389
- const hiddenFields = Array.from(params.entries())
390
- .filter(([key]) => key !== 'passphrase')
391
- .map(([key, value]) => `<input type="hidden" name="${escapeHtmlAttribute(key)}" value="${escapeHtmlAttribute(value)}">`)
392
- .join('\n');
393
-
394
- return `<!doctype html>
395
- <html lang="en">
396
- <head><meta charset="utf-8"><title>Authorize forge</title>
397
- <meta name="viewport" content="width=device-width,initial-scale=1">
398
- <style>
399
- body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f6f6f3;color:#1f2328}
400
- .card{width:min(420px,92vw);background:#fff;border:1px solid #d8d8d0;border-radius:12px;padding:32px;box-shadow:0 12px 40px rgba(0,0,0,.08)}
401
- h1{font-size:20px;margin:0 0 8px}p{margin:0 0 20px;color:#60666d;line-height:1.45}
402
- input{width:100%;box-sizing:border-box;border:1px solid #bfc4c9;border-radius:8px;padding:12px;font-size:16px}
403
- button{width:100%;margin-top:14px;border:0;border-radius:8px;padding:12px;background:#1f2328;color:#fff;font-size:16px;font-weight:600}
404
- </style></head>
405
- <body><main class="card">
406
- <h1>Authorize forge</h1>
407
- <p>Enter the local MCP passphrase to let this MCP client use this workflow-scoped connector.</p>
408
- <form method="POST" action="/authorize">
409
- ${hiddenFields}
410
- <input type="password" name="passphrase" placeholder="Passphrase" autofocus>
411
- <button type="submit">Authorize</button>
412
- </form>
413
- </main></body></html>`;
414
- }
415
-
416
- /** Collect OAuth authorize params from query (GET) or body (POST form). */
417
- function oauthAuthorizeParamSource(req: Request): Record<string, unknown> {
418
- if (req.method === 'POST' && req.body && typeof req.body === 'object' && !Buffer.isBuffer(req.body)) {
419
- return req.body as Record<string, unknown>;
420
- }
421
- return req.query as Record<string, unknown>;
422
- }
423
-
424
- function readOAuthAuthorizeString(source: Record<string, unknown>, key: string): string {
425
- const value = source[key];
426
- return typeof value === 'string' ? value.trim() : '';
427
- }
428
-
429
- /**
430
- * True when the request lacks the OAuth parameters needed for a real authorization.
431
- * Incomplete requests must not render the passphrase form (incompatible clients loop there).
432
- */
433
- export function isIncompleteOAuthAuthorizeRequest(source: Record<string, unknown>): boolean {
434
- const clientId = readOAuthAuthorizeString(source, 'client_id');
435
- const responseType = readOAuthAuthorizeString(source, 'response_type');
436
- const codeChallenge = readOAuthAuthorizeString(source, 'code_challenge');
437
- const redirectUri = readOAuthAuthorizeString(source, 'redirect_uri');
438
- // redirect_uri may be omitted when the client has a single registered URI; client_id is the usable redirect context.
439
- const hasRedirectContext = Boolean(redirectUri) || Boolean(clientId);
440
- return !clientId || !responseType || !codeChallenge || !hasRedirectContext;
441
- }
442
-
443
- function incompleteOAuthAuthorizeResponseBody(): {
444
- error: 'invalid_request';
445
- error_description: string;
446
- message: string;
447
- hint: string;
448
- } {
449
- return {
450
- error: 'invalid_request',
451
- error_description:
452
- 'OAuth authorization request is incomplete. Required: client_id, response_type, code_challenge, and a usable redirect context (redirect_uri or a registered client).',
453
- message:
454
- 'This endpoint expects a complete OAuth authorization request (PKCE). Non-OAuth MCP clients should use /mcp-bearer with Authorization: Bearer <token> instead of /authorize.',
455
- hint: 'Use POST/GET /mcp-bearer with a forge bearer token for clients that cannot complete OAuth dynamic registration and PKCE.',
456
- };
457
- }
458
-
459
- function isOAuthDebugTraceEnabled(): boolean {
460
- return process.env.FORGE_MCP_OAUTH_TRACE === '1' || process.env.FORGE_MCP_OAUTH_TRACE === 'true';
461
- }
462
-
463
- const SENSITIVE_OAUTH_FIELDS = new Set([
464
- 'passphrase',
465
- 'code',
466
- 'code_verifier',
467
- 'client_secret',
468
- 'access_token',
469
- 'refresh_token',
470
- 'token',
471
- 'authorization',
472
- ]);
473
-
474
- function safeOAuthFieldNames(source: Record<string, unknown>): string[] {
475
- return Object.keys(source)
476
- .filter((key) => !SENSITIVE_OAUTH_FIELDS.has(key.toLowerCase()))
477
- .sort();
478
- }
479
-
480
- function oauthTrace(req: Request, event: string, extra: Record<string, unknown> = {}): void {
481
- if (!isOAuthDebugTraceEnabled()) return;
482
- const source = oauthAuthorizeParamSource(req);
483
- const userAgent = typeof req.headers['user-agent'] === 'string'
484
- ? req.headers['user-agent'].split(/[\s/]/)[0]
485
- : undefined;
486
- const safe = {
487
- event,
488
- method: req.method,
489
- path: req.path,
490
- fieldNames: safeOAuthFieldNames(source),
491
- hasClientId: Boolean(readOAuthAuthorizeString(source, 'client_id')),
492
- hasRedirectUri: Boolean(readOAuthAuthorizeString(source, 'redirect_uri')),
493
- hasCodeChallenge: Boolean(readOAuthAuthorizeString(source, 'code_challenge')),
494
- responseType: readOAuthAuthorizeString(source, 'response_type') || undefined,
495
- codeChallengeMethod: readOAuthAuthorizeString(source, 'code_challenge_method') || undefined,
496
- grantType: readOAuthAuthorizeString(source, 'grant_type') || undefined,
497
- hasResource: Boolean(readOAuthAuthorizeString(source, 'resource')),
498
- userAgent,
499
- ...extra,
500
- };
501
- console.error(`[forge:mcp-oauth] ${JSON.stringify(safe)}`);
502
- }
503
-
504
- function oauthTraceMiddleware(event: string): (req: Request, res: Response, next: NextFunction) => void {
505
- return (req, res, next) => {
506
- oauthTrace(req, `${event}:request`);
507
- res.once('finish', () => oauthTrace(req, `${event}:response`, { statusCode: res.statusCode }));
508
- next();
509
- };
510
- }
511
-
512
- function rejectIncompleteOAuthAuthorize(req: Request, res: Response, next: NextFunction): void {
513
- const source = oauthAuthorizeParamSource(req);
514
- if (isIncompleteOAuthAuthorizeRequest(source)) {
515
- oauthTrace(req, 'authorize:incomplete');
516
- res.status(400).json(incompleteOAuthAuthorizeResponseBody());
517
- return;
518
- }
519
- oauthTrace(req, 'authorize:complete');
520
- next();
521
- }
522
-
523
- function requirePassphrase(passphrase: string): (req: Request, res: Response, next: NextFunction) => void {
524
- return (req, res, next) => {
525
- const provided = typeof req.body?.passphrase === 'string' ? req.body.passphrase : undefined;
526
- if (provided) {
527
- const a = Buffer.from(provided);
528
- const b = Buffer.from(passphrase);
529
- if (a.length === b.length && timingSafeEqual(a, b)) {
530
- next();
531
- return;
532
- }
533
- }
534
- // Prefer body hidden fields on POST (failed passphrase re-render), else query string on GET.
535
- const source = oauthAuthorizeParamSource(req);
536
- const params = new URLSearchParams();
537
- for (const [key, value] of Object.entries(source)) {
538
- if (key === 'passphrase') continue;
539
- if (typeof value === 'string') params.set(key, value);
540
- }
541
- if ([...params.keys()].length === 0 && req.url.includes('?')) {
542
- const fromUrl = new URLSearchParams(req.url.slice(req.url.indexOf('?')));
543
- for (const [key, value] of fromUrl.entries()) {
544
- if (key !== 'passphrase') params.set(key, value);
545
- }
546
- }
547
- res.type('html').send(renderPassphrasePage(params));
548
- };
549
- }
550
-
551
- function oauthAuthorizationHandler(provider: ReturnType<typeof createMcpOAuthProvider>) {
552
- return async (req: Request, res: Response) => {
553
- const query = req.method === 'POST' ? req.body : req.query;
554
- const clientId = typeof query.client_id === 'string' ? query.client_id : '';
555
- const responseType = typeof query.response_type === 'string' ? query.response_type : '';
556
- const codeChallenge = typeof query.code_challenge === 'string' ? query.code_challenge : '';
557
- const codeChallengeMethod = typeof query.code_challenge_method === 'string' ? query.code_challenge_method : '';
558
- const state = typeof query.state === 'string' ? query.state : undefined;
559
- const scope = typeof query.scope === 'string' ? query.scope : undefined;
560
- let redirectUri = typeof query.redirect_uri === 'string' ? query.redirect_uri : undefined;
561
-
562
- if (responseType !== 'code') {
563
- res.status(400).json({ error: 'unsupported_response_type', error_description: 'Only code response type is supported' });
564
- return;
565
- }
566
- if (!codeChallenge || codeChallengeMethod !== 'S256') {
567
- res.status(400).json({ error: 'invalid_request', error_description: 'PKCE S256 is required' });
568
- return;
569
- }
570
-
571
- const client = await getOrRegisterPublicOAuthClient(provider, clientId, redirectUri, req);
572
- if (!client) {
573
- res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
574
- return;
575
- }
576
- if (!redirectUri && client.redirect_uris.length === 1) {
577
- redirectUri = client.redirect_uris[0];
578
- }
579
- if (!redirectUri || (!isAllowedMcpOAuthRedirectUri(redirectUri) && !isRegisteredExternalHttpsRedirectUri(redirectUri, client))) {
580
- res.status(400).json({
581
- error: 'invalid_request',
582
- error_description: 'redirect_uri must be localhost, a ChatGPT connector callback URL, or a registered HTTPS client redirect_uri',
583
- });
584
- return;
585
- }
586
- if (!isRegisteredRedirectUri(redirectUri, client)) {
587
- res.status(400).json({
588
- error: 'invalid_request',
589
- error_description: 'redirect_uri must match a registered client redirect_uri',
590
- });
591
- return;
592
- }
593
-
594
- await provider.authorize(client as OAuthClientInformationFull, {
595
- state,
596
- scopes: scope ? scope.split(' ') : [],
597
- redirectUri,
598
- codeChallenge,
599
- }, res);
600
- };
601
- }
602
-
603
- function sendBearerUnauthorized(res: Response, description: string, hasConfiguredToken: boolean): void {
604
- res.setHeader('www-authenticate', 'Bearer realm="forge-mcp"');
605
- res.status(hasConfiguredToken ? 401 : 503).json({
606
- error: hasConfiguredToken ? 'unauthorized' : 'auth_not_configured',
607
- message: description,
608
- });
609
- }
610
-
611
- function sendOAuthUnauthorized(
612
- req: Request,
613
- res: Response,
614
- description: string,
615
- configuredOrigin: string | undefined,
616
- resourcePath = '/mcp',
617
- ): void {
618
- const resourceMetadataUrl = `${getPublicOrigin(req, configuredOrigin)}/.well-known/oauth-protected-resource${resourcePath}`;
619
- res.setHeader(
620
- 'www-authenticate',
621
- `Bearer error="invalid_token", error_description="${description}", resource_metadata="${resourceMetadataUrl}"`,
622
- );
623
- res.status(401).json({ error: 'invalid_token', message: description });
624
- }
625
-
626
- function requireMcpHttpAuth(
627
- mode: McpHttpAuthMode,
628
- bearerToken: string | null,
629
- provider: ReturnType<typeof createMcpOAuthProvider> | null,
630
- configuredOrigin: string | undefined,
631
- resourcePath = '/mcp',
632
- ) {
633
- return (req: Request, res: Response, next: NextFunction) => {
634
- if (mode === 'none') {
635
- next();
636
- return;
637
- }
638
-
639
- if (mode === 'bearer') {
640
- if (!isAuthorizedMcpHttpRequest(req, bearerToken)) {
641
- sendBearerUnauthorized(res, bearerToken ? 'Missing or invalid Authorization header' : 'Bearer token is not configured', Boolean(bearerToken));
642
- return;
643
- }
644
- next();
645
- return;
646
- }
647
-
648
- if (isAuthorizedMcpHttpRequest(req, bearerToken)) {
649
- next();
650
- return;
651
- }
652
-
653
- const token = bearerFromRequest(req);
654
- if (!token || !provider) {
655
- sendOAuthUnauthorized(req, res, token ? 'OAuth is not configured' : 'Missing Authorization header', configuredOrigin, resourcePath);
656
- return;
657
- }
658
- provider.verifyAccessToken(token)
659
- .then((authInfo) => {
660
- (req as unknown as Record<string, unknown>).auth = authInfo;
661
- next();
662
- })
663
- .catch((error: unknown) => {
664
- if (error instanceof InvalidTokenError) {
665
- sendOAuthUnauthorized(req, res, error.message, configuredOrigin, resourcePath);
666
- } else {
667
- res.status(500).json({ error: 'server_error', message: 'Internal Server Error' });
668
- }
669
- });
670
- };
671
- }
672
-
673
- interface McpRuntimeStats {
674
- initializing: number;
675
- activePosts: number;
676
- rejectedOverload: number;
677
- }
678
-
679
- function positiveIntegerEnv(name: string, fallback: number): number {
680
- const value = Number(process.env[name]);
681
- return Number.isInteger(value) && value > 0 ? value : fallback;
682
- }
683
-
684
- const MAX_MCP_SESSIONS = positiveIntegerEnv('FORGE_MCP_MAX_SESSIONS', 64);
685
- const MAX_MCP_SESSIONS_PER_PRINCIPAL = positiveIntegerEnv('FORGE_MCP_MAX_SESSIONS_PER_PRINCIPAL', MAX_MCP_SESSIONS);
686
- const MAX_INITIALIZING_SESSIONS = positiveIntegerEnv('FORGE_MCP_MAX_INITIALIZING_SESSIONS', 8);
687
- const MAX_POSTS_PER_SESSION = positiveIntegerEnv('FORGE_MCP_MAX_POSTS_PER_SESSION', 4);
688
- const MAX_ACTIVE_POSTS = positiveIntegerEnv('FORGE_MCP_MAX_ACTIVE_POSTS', 32);
689
- const MCP_SESSION_IDLE_TTL_MS = positiveIntegerEnv('FORGE_MCP_SESSION_IDLE_TTL_MS', 15 * 60_000);
690
- const MCP_STREAM_LEASE_MS = positiveIntegerEnv('FORGE_MCP_STREAM_LEASE_MS', 30 * 60_000);
691
- const MCP_SESSION_ABSOLUTE_LIFETIME_MS = positiveIntegerEnv('FORGE_MCP_SESSION_ABSOLUTE_LIFETIME_MS', 2 * 60 * 60_000);
692
- const MCP_ACTIVE_POST_STALL_MS = positiveIntegerEnv('FORGE_MCP_ACTIVE_POST_STALL_MS', 10 * 60_000);
693
-
694
- type McpToolContext = ReturnType<typeof createMcpToolContext>;
695
- type HttpSessionRegistry = McpSessionRegistry<StreamableHTTPServerTransport, McpToolContext>;
696
-
697
- async function handleMcpPost(
698
- req: Request,
699
- res: Response,
700
- baseOptions: McpServerOptions,
701
- registry: HttpSessionRegistry,
702
- stats: McpRuntimeStats,
703
- route: McpSessionRoute,
704
- currentToolSurfaceFingerprint: () => string | undefined,
705
- resolveRuntimeSchema?: (context: McpToolContext) => Promise<CanonicalRuntimeToolSchema | undefined>,
706
- sharedRuntimeProxy?: CanonicalRuntimeProxy,
707
- ): Promise<void> {
708
- let body: unknown;
709
- try {
710
- body = rawBodyToJson(req.body as Buffer);
711
- } catch (_error) {
712
- res.status(400).json({ error: 'invalid JSON request body' });
713
- return;
714
- }
715
- const sessionId = req.headers['mcp-session-id'] as string | undefined;
716
- // MCP 2026-07-28 clients probe legacy servers with server/discover before
717
- // falling back to the initialize-era protocol. Forge still serves the
718
- // legacy stateful transport, so reject the unsupported modern RPC with the
719
- // protocol-prescribed Method not found / HTTP 404 response instead of
720
- // misclassifying it as a missing-session HTTP 400.
721
- if (isServerDiscoverRequest(body)) {
722
- sendLegacyServerDiscoverUnsupported(res, body);
723
- return;
724
- }
725
- if (isInitializeRequest(body)) {
726
- if (sessionId) {
727
- res.setHeader('Mcp-Session-Reset', 'reinitialized');
728
- res.setHeader('x-forge-session-reset', 'reinitialized');
729
- }
730
- if (stats.initializing >= MAX_INITIALIZING_SESSIONS || stats.activePosts >= MAX_ACTIVE_POSTS) {
731
- stats.rejectedOverload += 1;
732
- res.setHeader('retry-after', '1');
733
- res.status(503).json({
734
- error: 'server_busy',
735
- code: 'MCP_SERVER_BUSY',
736
- message: 'Too many MCP sessions are initializing; retry shortly',
737
- recoverable: true,
738
- retryable: true,
739
- sessionPreserved: true,
740
- action: 'retry',
741
- });
742
- return;
743
- }
744
- stats.initializing += 1;
745
- stats.activePosts += 1;
746
- let transport: StreamableHTTPServerTransport | undefined;
747
- let reservationId: string | undefined;
748
- let initializedSessionId: string | undefined;
749
- try {
750
- const principalId = principalFromRequest(req);
751
- const clientIdentity = initializeClientIdentity(req, body, route, principalId);
752
- reservationId = await registry.reserveForInitialize({
753
- principalId,
754
- route,
755
- ...(principalId === 'mcp-bearer-client' ? { enforcePrincipalCapacity: false } : {}),
756
- ...(sessionId ? { supersedeSessionId: sessionId } : {}),
757
- });
758
- if (!reservationId) {
759
- stats.rejectedOverload += 1;
760
- res.setHeader('retry-after', '1');
761
- res.status(503).json({
762
- error: 'session_capacity',
763
- code: 'MCP_SESSION_CAPACITY',
764
- message: 'All MCP sessions are executing active requests; retry shortly',
765
- recoverable: true,
766
- retryable: true,
767
- sessionPreserved: true,
768
- action: 'retry',
769
- });
770
- return;
771
- }
772
- const sessionContext = createMcpToolContext({
773
- ...baseOptions,
774
- sessionId: `mcp_${randomUUID().replace(/-/g, '')}`,
775
- principalId,
776
- });
777
- let runtimeSchema = await resolveRuntimeSchema?.(sessionContext);
778
- let server: ReturnType<typeof createForgeMcpServerFromContext> | undefined;
779
- transport = new StreamableHTTPServerTransport({
780
- sessionIdGenerator: () => randomUUID(),
781
- onsessioninitialized: (newSessionId: string): void => {
782
- registry.commitInitialize(reservationId!, {
783
- sessionId: newSessionId,
784
- transport: transport!,
785
- toolContext: sessionContext,
786
- route,
787
- principalId,
788
- clientIdentity,
789
- toolSurfaceFingerprint: runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint(),
790
- toolNames: runtimeSchema?.toolNames,
791
- notifyToolListChanged: async () => { await server?.sendToolListChanged(); },
792
- ...(runtimeSchema && resolveRuntimeSchema
793
- ? {
794
- refreshToolSurface: async () => {
795
- const refreshed = await resolveRuntimeSchema(sessionContext);
796
- if (!refreshed || !runtimeSchema) return undefined;
797
- runtimeSchema.definitions = refreshed.definitions;
798
- runtimeSchema.toolNames = refreshed.toolNames;
799
- runtimeSchema.fingerprint = refreshed.fingerprint;
800
- return {
801
- toolSurfaceFingerprint: refreshed.fingerprint,
802
- toolNames: refreshed.toolNames,
803
- };
804
- },
805
- }
806
- : {}),
807
- });
808
- initializedSessionId = newSessionId;
809
- },
810
- });
811
- transport.onclose = () => {
812
- if (transport?.sessionId) registry.detach(transport.sessionId);
813
- };
814
- server = createForgeMcpServerFromContext(sessionContext, runtimeSchema, sharedRuntimeProxy);
815
- await server.connect(transport);
816
- await transport.handleRequest(req, res, body);
817
- } finally {
818
- if (initializedSessionId) registry.endPost(initializedSessionId);
819
- if (reservationId) registry.releaseInitialize(reservationId);
820
- stats.initializing -= 1;
821
- stats.activePosts -= 1;
822
- if (!transport?.sessionId) await transport?.close().catch(() => undefined);
823
- }
824
- return;
825
- }
826
- if (sessionId) {
827
- const managed = registry.get(sessionId);
828
- if (managed && managed.route === route && managed.principalId === principalFromRequest(req)) {
829
- if (managed.inFlightPosts >= MAX_POSTS_PER_SESSION || stats.activePosts >= MAX_ACTIVE_POSTS) {
830
- stats.rejectedOverload += 1;
831
- res.setHeader('retry-after', '1');
832
- res.status(429).json({ error: 'session_busy', message: 'Too many MCP requests are active; retry shortly' });
833
- return;
834
- }
835
- registry.beginPost(sessionId);
836
- stats.activePosts += 1;
837
- try {
838
- const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
839
- currentToolSurfaceFingerprint(),
840
- resolveRuntimeSchema
841
- ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
842
- : undefined,
843
- );
844
- if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
845
- // Standard MCP hot refresh: allow an explicitly requested tools/list
846
- // to replace the live session's mutable Runtime schema snapshot. Hosts
847
- // that ignore list_changed and call directly still hit the reset fence.
848
- if (isMcpToolsListRequest(body)) {
849
- const refreshed = await registry.refreshToolSurface(sessionId);
850
- if (refreshed
851
- && (!currentFingerprint || refreshed.toolSurfaceFingerprint === currentFingerprint)) {
852
- await managed.transport.handleRequest(req, res, body);
853
- return;
854
- }
855
- }
856
- // Keep the transport/session alive long enough for the host to observe
857
- // the recoverable reset and issue a replacement initialize request.
858
- // The initialize path explicitly supersedes this session afterward.
859
- sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
860
- return;
861
- }
862
- if (toolCallOutsideSessionSchema(body, managed.toolNames)) {
863
- // A call against a newer discovery surface is the same recoverable
864
- // schema-fence condition. Closing here can make hosts unregister the
865
- // entire MCP namespace before they can reinitialize it.
866
- sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
867
- return;
868
- }
869
- await managed.transport.handleRequest(req, res, body);
870
- } finally {
871
- registry.endPost(sessionId);
872
- stats.activePosts -= 1;
873
- }
874
- return;
875
- }
876
- }
877
- sendMcpSessionLookupError(res, sessionId);
878
- }
879
-
880
- async function handleMcpGet(
881
- req: Request,
882
- res: Response,
883
- registry: HttpSessionRegistry,
884
- route: McpSessionRoute,
885
- currentToolSurfaceFingerprint: () => string | undefined,
886
- resolveRuntimeSchema?: (context: McpToolContext) => Promise<CanonicalRuntimeToolSchema | undefined>,
887
- ): Promise<void> {
888
- const sessionId = req.headers['mcp-session-id'] as string | undefined;
889
- const managed = sessionId ? registry.get(sessionId) : undefined;
890
- if (!managed || managed.route !== route || managed.principalId !== principalFromRequest(req)) {
891
- sendMcpSessionLookupError(res, sessionId);
892
- return;
893
- }
894
- registry.beginStream(sessionId!);
895
- let released = false;
896
- const releaseStream = (): void => {
897
- if (released) return;
898
- released = true;
899
- registry.endStream(sessionId!);
900
- };
901
- req.once('aborted', releaseStream);
902
- res.once('close', releaseStream);
903
- try {
904
- const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
905
- currentToolSurfaceFingerprint(),
906
- resolveRuntimeSchema
907
- ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
908
- : undefined,
909
- );
910
- if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
911
- // Preserve the existing SSE transport while asking the host to
912
- // reinitialize. The replacement initialize owns supersession/cleanup.
913
- sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
914
- return;
915
- }
916
- await managed.transport.handleRequest(req, res);
917
- } finally {
918
- req.off('aborted', releaseStream);
919
- res.off('close', releaseStream);
920
- releaseStream();
921
- }
922
- }
923
-
924
- async function handleMcpDelete(req: Request, res: Response, registry: HttpSessionRegistry, route: McpSessionRoute): Promise<void> {
925
- const sessionId = req.headers['mcp-session-id'] as string | undefined;
926
- const managed = sessionId ? registry.get(sessionId) : undefined;
927
- if (!managed || managed.route !== route || managed.principalId !== principalFromRequest(req)) {
928
- sendMcpSessionLookupError(res, sessionId);
929
- return;
930
- }
931
- registry.setPendingCloseReason(sessionId!, 'client_delete');
932
- await managed.transport.handleRequest(req, res);
933
- if (registry.get(sessionId!)) await registry.close(sessionId!, 'client_delete');
934
- }
935
-
936
- export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
937
- const host = opts.host ?? '127.0.0.1';
938
- const port = opts.port ?? 8765;
939
- const controllerHome = resolveControllerHome(opts.controllerHome);
940
- const serviceConfig = loadMcpServiceLocalConfig(controllerHome);
941
- const profile = opts.profile ?? serviceConfig?.profile ?? 'controller';
942
- // A controller Gateway can serve registered repositories without selecting
943
- // one at startup. Do not turn its launchd working directory into an
944
- // implicit repository, especially for package Runtime snapshots.
945
- const repoRoot = profile === 'controller' && !opts.repo?.trim()
946
- ? undefined
947
- : resolveMcpRepoRoot(opts.repo ?? '.');
948
- const authMode = parseMcpHttpAuthMode(opts.auth ?? serviceConfig?.auth?.mode);
949
- const authToken = opts.authToken ?? readMcpServiceBearerToken(controllerHome, repoRoot);
950
- const oauthPassphrase = authMode === 'oauth'
951
- ? readMcpServiceOAuthPassphrase(controllerHome, repoRoot)
952
- : null;
953
- const tokenStore = authMode === 'oauth'
954
- ? new McpOAuthTokenStore(
955
- mcpServiceOAuthTokenStorePath(controllerHome),
956
- mcpServiceOAuthTokenStoreFallbackPaths(controllerHome, repoRoot),
957
- )
958
- : null;
959
- tokenStore?.load();
960
- const oauthProvider = tokenStore ? createMcpOAuthProvider(tokenStore) : null;
961
- const configuredPublicOrigin = getConfiguredPublicOrigin(serviceConfig);
962
- const sessionRegistry = new McpSessionRegistry<StreamableHTTPServerTransport, McpToolContext>({
963
- maximumSessions: MAX_MCP_SESSIONS,
964
- maximumSessionsPerPrincipal: MAX_MCP_SESSIONS_PER_PRINCIPAL,
965
- idleTtlMs: MCP_SESSION_IDLE_TTL_MS,
966
- streamLeaseMs: MCP_STREAM_LEASE_MS,
967
- absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
968
- activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
969
- onSessionClosed: (session, reason) => {
970
- const context = session.toolContext;
971
- if (!('controllerHome' in context) || typeof context.controllerHome !== 'string') return;
972
- const executionSessionId = typeof context.sessionId === 'string' ? context.sessionId.trim() : '';
973
- if (!executionSessionId) return;
974
- invalidateExecutionSession(
975
- context.controllerHome,
976
- executionSessionId,
977
- `mcp_transport_${reason}`,
978
- );
979
- },
980
- });
981
- const runtimeStats: McpRuntimeStats = { initializing: 0, activePosts: 0, rejectedOverload: 0 };
982
- const toolContext = createMcpToolContext({ ...opts, repo: repoRoot, controllerHome, profile });
983
- const baseOptions: McpServerOptions = {
984
- repo: repoRoot,
985
- controllerHome,
986
- profile,
987
- toolset: opts.toolset,
988
- enableChatgptBrowser: opts.enableChatgptBrowser,
989
- enableDevRunner: opts.enableDevRunner,
990
- devRunnerAgents: opts.devRunnerAgents,
991
- devRunnerTimeoutMs: opts.devRunnerTimeoutMs,
992
- devRunnerMaxTimeoutMs: opts.devRunnerMaxTimeoutMs,
993
- };
994
- const runtimeControllerHome = 'controllerHome' in toolContext ? toolContext.controllerHome : undefined;
995
- const sharedRuntimeProxy = runtimeControllerHome && 'controllerHome' in toolContext && !toolContext.runtimeSourceRoot
996
- ? createCanonicalRuntimeProxy(toolContext)
997
- : undefined;
998
- const currentRuntimeGeneration = () => runtimeControllerHome ? readRuntimeGeneration(runtimeControllerHome) : undefined;
999
- const currentRuntimeToolSurfaceFingerprint = () => runtimeControllerHome
1000
- ? readRuntimeStatusSnapshot(runtimeControllerHome)?.toolSurfaceFingerprint
1001
- : undefined;
1002
- let observedRuntimeToolSurfaceFingerprint = currentRuntimeToolSurfaceFingerprint();
1003
- let toolSurfaceNotificationTimer: ReturnType<typeof setTimeout> | undefined;
1004
- const runtimeStatusDirectory = runtimeControllerHome ? dirname(runtimeStatusPath(runtimeControllerHome)) : undefined;
1005
- const runtimeStatusWatcher = runtimeStatusDirectory && existsSync(runtimeStatusDirectory)
1006
- ? watch(runtimeStatusDirectory, { persistent: false }, (_event, filename) => {
1007
- if (String(filename ?? '') !== 'status.json') return;
1008
- if (toolSurfaceNotificationTimer) clearTimeout(toolSurfaceNotificationTimer);
1009
- toolSurfaceNotificationTimer = setTimeout(() => {
1010
- const currentFingerprint = currentRuntimeToolSurfaceFingerprint();
1011
- if (!currentFingerprint || currentFingerprint === observedRuntimeToolSurfaceFingerprint) return;
1012
- observedRuntimeToolSurfaceFingerprint = currentFingerprint;
1013
- void sessionRegistry.notifyToolListChanged(currentFingerprint);
1014
- }, 25);
1015
- toolSurfaceNotificationTimer.unref();
1016
- })
1017
- : undefined;
1018
- runtimeStatusWatcher?.on('error', () => undefined);
1019
- const resolveRuntimeSchema = runtimeControllerHome
1020
- ? async (context: McpToolContext): Promise<CanonicalRuntimeToolSchema | undefined> => {
1021
- if (!('controllerHome' in context)) return undefined;
1022
- return await readCanonicalRuntimeToolSchema(context, sharedRuntimeProxy);
1023
- }
1024
- : undefined;
1025
- const localControllerConfig = {
1026
- enabled: serviceConfig?.localController?.enabled ?? profile === 'controller',
1027
- host: serviceConfig?.localController?.host ?? '127.0.0.1',
1028
- port: serviceConfig?.localController?.port ?? 8766,
1029
- };
1030
- const compatibilityToolDefinitions = buildMcpToolDefinitions(toolContext.policy, { enableChatgptBrowser: opts.enableChatgptBrowser === true });
1031
- const toolSurface = toolContext.policy.profile === 'controller' ? FORGE_TOOL_SURFACE : `${toolContext.policy.profile}-legacy-v1`;
1032
- const toolSurfaceSchemaVersion = toolContext.policy.profile === 'controller' ? FORGE_MCP_SCHEMA_VERSION : 1;
1033
- const forgeVersion = FORGE_VERSION;
1034
- const repoId = toolContext.policy.profile === 'controller' || !repoRoot ? undefined : repositoryIdentity(repoRoot);
1035
- const startedAt = new Date().toISOString();
1036
- const localOrigin = `http://${host === '::' || host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`;
1037
- const advertisedOrigin = configuredPublicOrigin ?? localOrigin;
1038
- const app = express();
1039
- app.set('trust proxy', 1);
1040
-
1041
- const controllerHealth = () => {
1042
- if (!('controllerHome' in toolContext)) return null;
1043
- const runtimeGeneration = currentRuntimeGeneration();
1044
- const exposure = controllerExposureSnapshot(toolContext);
1045
- // Health is a bounded diagnostic endpoint, not an MCP discovery authority.
1046
- // Session initialization and invocation obtain a live Runtime tools/list;
1047
- // health only reports the Runtime's published identity.
1048
- const runtimeFingerprint = currentRuntimeToolSurfaceFingerprint();
1049
- return {
1050
- configuredAccessMode: exposure.access.configuredAccessMode,
1051
- effectiveAccessMode: exposure.access.effectiveAccessMode,
1052
- effectiveToolset: exposure.access.effectiveToolset,
1053
- exposureRevision: exposure.access.exposureRevision,
1054
- accessModeSource: exposure.access.source,
1055
- accessModeLastAppliedAt: exposure.access.lastAppliedAt,
1056
- toolset: exposure.access.effectiveToolset,
1057
- toolSurfaceFingerprint: runtimeFingerprint,
1058
- runtimeToolSurfaceFingerprint: runtimeFingerprint,
1059
- toolCount: undefined,
1060
- generation: runtimeGeneration?.generation,
1061
- source: runtimeGeneration?.source,
1062
- runtimeIdentity: runtimeIdentitySnapshot(toolContext),
1063
- };
1064
- };
1065
-
1066
- app.get('/health', (_req, res) => {
1067
- const health = controllerHealth();
1068
- res.setHeader('x-forge-tool-surface', toolSurface);
1069
- res.setHeader('x-forge-version', String(forgeVersion));
1070
- res.setHeader('x-forge-schema-version', String(toolSurfaceSchemaVersion));
1071
- if (health?.toolset) res.setHeader('x-forge-toolset', health.toolset);
1072
- if (health?.runtimeToolSurfaceFingerprint) res.setHeader('x-forge-runtime-tool-surface-fingerprint', health.runtimeToolSurfaceFingerprint);
1073
- if (health?.toolSurfaceFingerprint) res.setHeader('x-forge-tool-surface-fingerprint', health.toolSurfaceFingerprint);
1074
- const sessionSnapshot = sessionRegistry.snapshot();
1075
- res.json({
1076
- status: 'ok',
1077
- server: 'forge-mcp',
1078
- ...(process.env.FORGE_MCP_INSTANCE_ID
1079
- ? { instanceId: process.env.FORGE_MCP_INSTANCE_ID }
1080
- : {}),
1081
- version: forgeVersion,
1082
- profile: toolContext.policy.profile,
1083
- toolSurface,
1084
- schemaVersion: toolSurfaceSchemaVersion,
1085
- toolSurfaceFingerprint: health?.toolSurfaceFingerprint,
1086
- runtimeToolSurfaceFingerprint: health?.runtimeToolSurfaceFingerprint,
1087
- generation: health?.generation,
1088
- source: health?.source,
1089
- // Deprecated diagnostic alias; not a Runtime schema claim.
1090
- toolset: health?.toolset ?? 'full',
1091
- gatewayToolset: health?.toolset ?? 'full',
1092
- toolCount: health?.toolCount,
1093
- compatibilityToolCount: compatibilityToolDefinitions.length,
1094
- runtimeIdentity: health?.runtimeIdentity,
1095
- configuredAccessMode: health?.configuredAccessMode,
1096
- effectiveAccessMode: health?.effectiveAccessMode,
1097
- effectiveToolset: health?.effectiveToolset,
1098
- accessModeSource: health?.accessModeSource,
1099
- accessModeLastAppliedAt: health?.accessModeLastAppliedAt,
1100
- exposureRevision: health?.exposureRevision,
1101
- ...(repoId ? { repoId } : {}),
1102
- startedAt,
1103
- runner: {
1104
- enabled: toolContext.policy.execution.agentRunner,
1105
- defaultTimeoutMs: toolContext.policy.execution.runnerTimeoutMs,
1106
- maxTimeoutMs: toolContext.policy.execution.runnerMaxTimeoutMs,
1107
- },
1108
- auth: authMode === 'oauth'
1109
- ? (oauthPassphrase ? 'oauth' : 'missing')
1110
- : authMode === 'bearer'
1111
- ? (authToken ? 'required' : 'missing')
1112
- : 'none',
1113
- mcpEndpoint: `${advertisedOrigin}/mcp`,
1114
- // Grok now completes standard OAuth dynamic registration + PKCE on the
1115
- // canonical MCP resource. Keep /mcp-grok below only as a legacy alias.
1116
- grokEndpoint: `${advertisedOrigin}/mcp`,
1117
- bearerEndpoint: `${advertisedOrigin}/mcp-bearer`,
1118
- sessions: {
1119
- ...sessionSnapshot,
1120
- initializing: runtimeStats.initializing,
1121
- activePosts: runtimeStats.activePosts,
1122
- maximumActivePosts: MAX_ACTIVE_POSTS,
1123
- rejectedOverload: runtimeStats.rejectedOverload,
1124
- },
1125
- });
1126
- });
1127
-
1128
- // Recovery probes this endpoint every few seconds. Keep it strictly in-memory
1129
- // and transport-scoped: whole-control-plane readiness below may traverse every
1130
- // repository projection and probe the local bridge, which must never become a
1131
- // periodic event-loop load generator for the public MCP Connector itself.
1132
- app.get('/transport-ready', (_req, res) => {
1133
- const sessionSnapshot = sessionRegistry.snapshot();
1134
- const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
1135
- && runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
1136
- && runtimeStats.activePosts < MAX_ACTIVE_POSTS;
1137
- res.status(sessionCapacityReady ? 200 : 503).json({
1138
- ready: sessionCapacityReady,
1139
- profile: toolContext.policy.profile,
1140
- gateway: sessionCapacityReady ? 'ready' : 'saturated',
1141
- sessionCapacity: sessionSnapshot,
1142
- runtimeCapacity: {
1143
- initializing: runtimeStats.initializing,
1144
- maximumInitializing: MAX_INITIALIZING_SESSIONS,
1145
- activePosts: runtimeStats.activePosts,
1146
- maximumActivePosts: MAX_ACTIVE_POSTS,
1147
- },
1148
- });
1149
- });
1150
-
1151
- app.get('/ready', async (_req, res) => {
1152
- const runtimeGeneration = currentRuntimeGeneration();
1153
- const sessionSnapshot = sessionRegistry.snapshot();
1154
- const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
1155
- && runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
1156
- && runtimeStats.activePosts < MAX_ACTIVE_POSTS;
1157
- if (!runtimeControllerHome) {
1158
- res.status(sessionCapacityReady ? 200 : 503).json({
1159
- ready: sessionCapacityReady,
1160
- profile: toolContext.policy.profile,
1161
- gateway: sessionCapacityReady ? 'ready' : 'saturated',
1162
- controllerDaemon: 'not-required',
1163
- sessionCapacity: sessionSnapshot,
1164
- });
1165
- return;
1166
- }
1167
- const daemon = readForgeRuntimeStatus(runtimeControllerHome);
1168
- const runtimeState = loadMcpServiceRuntimeState(runtimeControllerHome, repoRoot);
1169
- const repositories = listRepositories(runtimeControllerHome).filter((repository) => repository.enabled && !repository.removedAt);
1170
- const projectionSnapshots = repositories.map((repository) => {
1171
- const snapshot = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
1172
- const reconciliation = reconcileReadinessProjectionSource(
1173
- snapshot,
1174
- legacyIssueAuthorityRetired(repository.canonicalRoot)
1175
- ? undefined
1176
- : buildControllerTaskLedgerProjection(repository.canonicalRoot),
1177
- );
1178
- return { repoId: repository.repoId, snapshot, reconciliation };
1179
- });
1180
- const staleRepositories = projectionSnapshots
1181
- .filter(({ snapshot }) => snapshot.stale)
1182
- .map(({ repoId }) => repoId);
1183
- const blockingStaleRepositories = projectionSnapshots
1184
- .filter(({ snapshot }) => projectionBlocksReadiness(snapshot))
1185
- .map(({ repoId }) => repoId);
1186
- const sourceMismatches = projectionSnapshots
1187
- .filter(({ reconciliation }) => reconciliation.status === 'mismatch')
1188
- .map(({ repoId, reconciliation }) => ({ repoId, ...reconciliation }));
1189
- const localBridgeHealth = localControllerConfig.enabled
1190
- ? await jsonHealth(localControllerHealthUrl(localControllerConfig.host, localControllerConfig.port))
1191
- : null;
1192
- const localBridgeReady = !localControllerConfig.enabled
1193
- || localControllerDiagnosticMatchesRuntime(localBridgeHealth, runtimeGeneration?.generation);
1194
- const daemonReady = daemon.status === 'ready' && daemon.degraded !== true;
1195
- const projectionReady = blockingStaleRepositories.length === 0;
1196
- const publicConfigured = Boolean(runtimeState?.tunnel?.publicEndpoint);
1197
- const publicReady = !publicConfigured || runtimeState?.tunnel?.healthy === true;
1198
- const connectorReady = !publicConfigured || (
1199
- publicReady
1200
- && runtimeState?.tunnel?.connectorNeedsReconnect !== true
1201
- );
1202
- const ready = daemonReady && projectionReady && localBridgeReady && sessionCapacityReady;
1203
- res.status(ready ? 200 : 503).json({
1204
- ready,
1205
- generation: runtimeGeneration?.generation,
1206
- source: runtimeGeneration?.source,
1207
- gateway: { status: ready ? 'ready' : 'degraded', thin: true, eventLoopIsolatedFromWorkers: true },
1208
- controllerDaemon: daemon,
1209
- localBridge: {
1210
- enabled: localControllerConfig.enabled,
1211
- ready: localBridgeReady,
1212
- endpoint: `http://${localControllerConfig.host === '::1' ? '[::1]' : localControllerConfig.host}:${localControllerConfig.port}/`,
1213
- },
1214
- projections: {
1215
- ready: projectionReady,
1216
- repositoryCount: repositories.length,
1217
- staleRepositories,
1218
- blockingStaleRepositories,
1219
- sourceMismatches,
1220
- },
1221
- publicReadiness: {
1222
- configured: publicConfigured,
1223
- ready: publicReady,
1224
- endpoint: runtimeState?.tunnel?.publicEndpoint,
1225
- },
1226
- connectorReadiness: {
1227
- configured: publicConfigured,
1228
- ready: connectorReady,
1229
- connectorNeedsReconnect: runtimeState?.tunnel?.connectorNeedsReconnect === true,
1230
- },
1231
- sessionCapacity: sessionSnapshot,
1232
- });
1233
- });
1234
-
1235
- app.get('/repos/:repoId/health', (req, res) => {
1236
- if (!runtimeControllerHome) {
1237
- res.status(404).json({ error: 'controller profile required' });
1238
- return;
1239
- }
1240
- try {
1241
- const repository = getRepository(req.params.repoId, runtimeControllerHome, { includeRemoved: true });
1242
- const projection = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
1243
- res.json({
1244
- status: repository.enabled && !repository.removedAt ? 'ok' : 'disabled',
1245
- repository: {
1246
- repoId: repository.repoId,
1247
- checkoutId: repository.activeCheckoutId,
1248
- enabled: repository.enabled,
1249
- removedAt: repository.removedAt,
1250
- },
1251
- projection,
1252
- });
1253
- } catch (error) {
1254
- res.status(404).json({ error: error instanceof Error ? error.message : String(error) });
1255
- }
1256
- });
1257
-
1258
- if (authMode === 'oauth' && oauthProvider) {
1259
- app.use('/authorize', express.urlencoded({ extended: false, limit: '10kb' }));
1260
- app.use('/authorize', oauthTraceMiddleware('authorize'));
1261
- // Reject incomplete OAuth requests before rendering the passphrase form.
1262
- app.use('/authorize', rejectIncompleteOAuthAuthorize);
1263
- app.use('/authorize', requirePassphrase(oauthPassphrase ?? ''));
1264
- app.use('/authorize', oauthAuthorizationHandler(oauthProvider));
1265
- app.use('/token', oauthTraceMiddleware('token'));
1266
- app.use('/token', tokenHandler({ provider: oauthProvider, rateLimit: false }));
1267
- app.use('/revoke', oauthTraceMiddleware('revoke'));
1268
- app.use('/revoke', revocationHandler({ provider: oauthProvider, rateLimit: false }));
1269
- app.use('/register', oauthTraceMiddleware('register'));
1270
- app.use('/register', clientRegistrationHandler({ clientsStore: oauthProvider.clientsStore, rateLimit: false }));
1271
- app.get('/.well-known/oauth-authorization-server', (req, res) => {
1272
- const origin = getPublicOrigin(req, configuredPublicOrigin);
1273
- res.json({
1274
- issuer: origin,
1275
- authorization_endpoint: `${origin}/authorize`,
1276
- token_endpoint: `${origin}/token`,
1277
- revocation_endpoint: `${origin}/revoke`,
1278
- registration_endpoint: `${origin}/register`,
1279
- response_types_supported: ['code'],
1280
- grant_types_supported: ['authorization_code', 'refresh_token'],
1281
- code_challenge_methods_supported: ['S256'],
1282
- token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
1283
- scopes_supported: ['forge'],
1284
- });
1285
- });
1286
- app.get('/.well-known/openid-configuration', (req, res) => {
1287
- const origin = getPublicOrigin(req, configuredPublicOrigin);
1288
- res.json({
1289
- issuer: origin,
1290
- authorization_endpoint: `${origin}/authorize`,
1291
- token_endpoint: `${origin}/token`,
1292
- registration_endpoint: `${origin}/register`,
1293
- response_types_supported: ['code'],
1294
- grant_types_supported: ['authorization_code', 'refresh_token'],
1295
- code_challenge_methods_supported: ['S256'],
1296
- token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
1297
- scopes_supported: ['forge'],
1298
- });
1299
- });
1300
- const protectedResourceMetadata = (resourcePath: '/mcp' | '/mcp-grok' | '/mcp-bearer') => (req: Request, res: Response): void => {
1301
- const origin = getPublicOrigin(req, configuredPublicOrigin);
1302
- res.json({
1303
- resource: `${origin}${resourcePath}`,
1304
- authorization_servers: [origin],
1305
- scopes_supported: ['forge'],
1306
- bearer_methods_supported: ['header'],
1307
- });
1308
- };
1309
- app.get('/.well-known/oauth-protected-resource/mcp', protectedResourceMetadata('/mcp'));
1310
- app.get('/.well-known/oauth-protected-resource/mcp-grok', protectedResourceMetadata('/mcp-grok'));
1311
- app.get('/.well-known/oauth-protected-resource/mcp-bearer', protectedResourceMetadata('/mcp-bearer'));
1312
- }
1313
-
1314
- const setMcpResponseHeaders = (_req: Request, res: Response, next: NextFunction): void => {
1315
- res.setHeader('x-forge-tool-surface', toolSurface);
1316
- res.setHeader('x-forge-version', String(forgeVersion));
1317
- res.setHeader('x-forge-schema-version', String(toolSurfaceSchemaVersion));
1318
- next();
1319
- };
1320
-
1321
- // Primary MCP path: OAuth (or bearer when --auth bearer). Unchanged for ChatGPT.
1322
- app.use('/mcp', setMcpResponseHeaders);
1323
- app.post('/mcp', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1324
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1325
- if (!res.headersSent) sendMcpRequestError(res, error);
1326
- });
1327
- });
1328
- app.get('/mcp', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin), (req, res) => {
1329
- handleMcpGet(req, res, sessionRegistry, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1330
- if (!res.headersSent) sendMcpRequestError(res, error);
1331
- });
1332
- });
1333
- app.delete('/mcp', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin), (req, res) => {
1334
- handleMcpDelete(req, res, sessionRegistry, '/mcp').catch((error: unknown) => {
1335
- if (!res.headersSent) sendMcpRequestError(res, error);
1336
- });
1337
- });
1338
-
1339
- // Legacy Grok OAuth resource. New Grok connectors should use canonical /mcp.
1340
- app.use('/mcp-grok', setMcpResponseHeaders);
1341
- app.post('/mcp-grok', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin, '/mcp-grok'), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1342
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1343
- if (!res.headersSent) sendMcpRequestError(res, error);
1344
- });
1345
- });
1346
- app.get('/mcp-grok', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin, '/mcp-grok'), (req, res) => {
1347
- handleMcpGet(req, res, sessionRegistry, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1348
- if (!res.headersSent) sendMcpRequestError(res, error);
1349
- });
1350
- });
1351
- app.delete('/mcp-grok', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin, '/mcp-grok'), (req, res) => {
1352
- handleMcpDelete(req, res, sessionRegistry, '/mcp-grok').catch((error: unknown) => {
1353
- if (!res.headersSent) sendMcpRequestError(res, error);
1354
- });
1355
- });
1356
-
1357
- // Bearer-only MCP path for clients that can send Authorization headers. Never advertises OAuth resource_metadata.
1358
- app.use('/mcp-bearer', setMcpResponseHeaders);
1359
- app.post('/mcp-bearer', requireMcpHttpAuth('bearer', authToken, null, configuredPublicOrigin), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1360
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1361
- if (!res.headersSent) sendMcpRequestError(res, error);
1362
- });
1363
- });
1364
- app.get('/mcp-bearer', requireMcpHttpAuth('bearer', authToken, null, configuredPublicOrigin), (req, res) => {
1365
- handleMcpGet(req, res, sessionRegistry, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1366
- if (!res.headersSent) sendMcpRequestError(res, error);
1367
- });
1368
- });
1369
- app.delete('/mcp-bearer', requireMcpHttpAuth('bearer', authToken, null, configuredPublicOrigin), (req, res) => {
1370
- handleMcpDelete(req, res, sessionRegistry, '/mcp-bearer').catch((error: unknown) => {
1371
- if (!res.headersSent) sendMcpRequestError(res, error);
1372
- });
1373
- });
1374
-
1375
- app.use((_req, res) => res.status(404).json({ error: 'not_found' }));
1376
-
1377
- const cleanupTimer = setInterval(() => {
1378
- void sessionRegistry.prune();
1379
- }, 60_000);
1380
- cleanupTimer.unref();
1381
-
1382
- const httpServer = app.listen(port, host);
1383
- httpServer.keepAliveTimeout = 65_000;
1384
- httpServer.headersTimeout = 70_000;
1385
- httpServer.requestTimeout = 120_000;
1386
-
1387
- httpServer.on('close', () => {
1388
- clearInterval(cleanupTimer);
1389
- if (toolSurfaceNotificationTimer) clearTimeout(toolSurfaceNotificationTimer);
1390
- runtimeStatusWatcher?.close();
1391
- void sessionRegistry.closeAll('shutdown');
1392
- void sharedRuntimeProxy?.close();
1393
- });
1394
-
1395
- await new Promise<void>((resolve) => {
1396
- httpServer.once('listening', resolve);
1397
- });
1398
- const authLabel = authMode === 'oauth'
1399
- ? (oauthPassphrase ? 'oauth' : 'oauth-missing')
1400
- : authMode === 'bearer'
1401
- ? (authToken ? 'bearer' : 'missing')
1402
- : 'none';
1403
- console.error(
1404
- `forge mcp http listening on http://${host}:${port}/mcp (auth: ${authLabel}), http://${host}:${port}/mcp-grok (auth: ${authLabel}), and http://${host}:${port}/mcp-bearer (auth: bearer)`,
1405
- );
1406
-
1407
- const shutdown = () => {
1408
- void sessionRegistry.closeAll('shutdown');
1409
- tokenStore?.flush();
1410
- httpServer.close(() => process.exit(0));
1411
- };
1412
- process.once('SIGINT', shutdown);
1413
- process.once('SIGTERM', shutdown);
1414
- }
1
+ /** @deprecated Kernel V2 compatibility shim. MCP HTTP transport lives in adapters/mcp. */
2
+ export * from '../../../../adapters/mcp/transports/http';