@moretea-labs/forge 1.8.0 → 1.8.1
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.
- package/AGENTS.md +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context-pack.ts +10 -8
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/stage-runtime-release.ts +0 -44
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomUUID
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
2
|
import { existsSync, watch } from 'fs';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
4
|
import express, { type Request, type Response, type NextFunction } from 'express';
|
|
5
5
|
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest, type NodeMcpRequestHandler } from "@modelcontextprotocol/node";
|
|
6
|
-
import { createMcpHandler, isLegacyRequest, type McpRequestContext
|
|
7
|
-
import {
|
|
6
|
+
import { createMcpHandler, isLegacyRequest, type McpRequestContext } from "@modelcontextprotocol/server";
|
|
7
|
+
import { InvalidTokenError } from "@modelcontextprotocol/server-legacy/auth";
|
|
8
8
|
import {
|
|
9
9
|
buildMultiRepositoryToolDefinitions,
|
|
10
10
|
createCanonicalRuntimeProxy,
|
|
@@ -17,39 +17,29 @@ import {
|
|
|
17
17
|
} from '../server';
|
|
18
18
|
import {
|
|
19
19
|
loadMcpServiceLocalConfig,
|
|
20
|
-
loadMcpServiceRuntimeState,
|
|
21
20
|
mcpServiceOAuthTokenStoreFallbackPaths,
|
|
22
21
|
mcpServiceOAuthTokenStorePath,
|
|
23
22
|
parseMcpHttpAuthMode,
|
|
24
23
|
readMcpServiceBearerToken,
|
|
25
24
|
readMcpServiceOAuthPassphrase,
|
|
26
|
-
type McpLocalConfig,
|
|
27
25
|
type McpHttpAuthMode,
|
|
28
26
|
} from '../auth';
|
|
29
27
|
import { createMcpOAuthProvider, McpOAuthTokenStore } from '../oauth';
|
|
30
28
|
import { resolveMcpRepoRoot } from '../repo';
|
|
31
|
-
import { buildMcpToolDefinitions } from '../tool-mapping/tools';
|
|
32
29
|
import { resolveControllerHome } from '../../../src/cli/repositories/controller-home';
|
|
33
|
-
import {
|
|
34
|
-
controllerExposureSnapshot,
|
|
35
|
-
} from '../toolset';
|
|
36
|
-
import { readForgeRuntimeStatus } from '../../../src/runtime/control-plane/runtime-status-client';
|
|
37
|
-
import { invalidateExecutionSession } from '../../../src/runtime/control-plane/execution/session-store';
|
|
38
|
-
import { runtimeIdentitySnapshot } from '../runtime-gateway/runtime-tools';
|
|
39
|
-
import { projectionBlocksReadiness, readRepositoryProjectionSnapshot } from '../../../src/runtime/projections/materialized-view';
|
|
40
30
|
import { readRuntimeGeneration } from '../../../src/runtime/control-plane/runtime-generation';
|
|
31
|
+
import { recordMcpTransportEvent } from '../../../src/runtime/diagnostics/mcp-timing';
|
|
41
32
|
import { readRuntimeStatusSnapshot, runtimeStatusPath } from '../../../src/runtime/root/status';
|
|
42
|
-
import { getRepository, listRepositories } from '../../../src/cli/repositories/registry';
|
|
43
|
-
import { buildControllerTaskLedgerProjection } from '../../../src/cli/controller/task-ledger';
|
|
44
|
-
import { legacyIssueAuthorityRetired } from '../../../src/cli/controller/legacy-issue-cutover';
|
|
45
|
-
import { reconcileReadinessProjectionSource } from '../readiness-projection';
|
|
46
33
|
import {
|
|
47
34
|
FORGE_MCP_SCHEMA_VERSION,
|
|
48
35
|
FORGE_TOOL_SURFACE,
|
|
49
36
|
FORGE_VERSION,
|
|
50
|
-
repositoryIdentity,
|
|
51
37
|
} from '../../../src/cli/controller/runtime-config';
|
|
52
|
-
import { McpSessionRegistry, type McpSessionRoute } from './session-registry';
|
|
38
|
+
import { McpSessionRegistry, type ClosableMcpTransport, type McpSessionRegistryOptions, type McpSessionRoute } from './session-registry';
|
|
39
|
+
import { getConfiguredPublicOrigin, getPublicOrigin, registerMcpOAuthHttpRoutes } from './oauth-http';
|
|
40
|
+
import { registerMcpHttpObservationRoutes } from './http-observation';
|
|
41
|
+
import { RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN } from '../../../src/runtime/root/mcp-transport';
|
|
42
|
+
export { isAllowedMcpOAuthRedirectUri, isIncompleteOAuthAuthorizeRequest } from './oauth-http';
|
|
53
43
|
import {
|
|
54
44
|
connectionIdentity,
|
|
55
45
|
ensureForgeInstanceIdentity,
|
|
@@ -65,17 +55,6 @@ export interface McpHttpOptions extends McpServerOptions {
|
|
|
65
55
|
auth?: string;
|
|
66
56
|
}
|
|
67
57
|
|
|
68
|
-
function localControllerDiagnosticMatchesRuntime(
|
|
69
|
-
payload: Record<string, unknown> | null,
|
|
70
|
-
generation?: string,
|
|
71
|
-
): boolean {
|
|
72
|
-
return payload?.status === 'ok'
|
|
73
|
-
&& payload.toolSurface === FORGE_TOOL_SURFACE
|
|
74
|
-
&& payload.schemaVersion === FORGE_MCP_SCHEMA_VERSION
|
|
75
|
-
&& payload.version === FORGE_VERSION
|
|
76
|
-
&& (generation === undefined || payload.generation === generation);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
58
|
function bearerFromRequest(req: Request): string | null {
|
|
80
59
|
const header = req.headers.authorization;
|
|
81
60
|
if (typeof header !== 'string') return null;
|
|
@@ -228,8 +207,42 @@ function sendMcpToolSurfaceReset(
|
|
|
228
207
|
});
|
|
229
208
|
}
|
|
230
209
|
|
|
210
|
+
/**
|
|
211
|
+
* A schema-fenced transport cannot be reused after the reset response. Delay
|
|
212
|
+
* retirement until Express has flushed the reconnect instruction, then release
|
|
213
|
+
* its session-local state. Work identity is Controller-owned, not transport-owned.
|
|
214
|
+
*/
|
|
215
|
+
function retireAfterToolSurfaceReset(
|
|
216
|
+
res: Response,
|
|
217
|
+
registry: HttpSessionRegistry,
|
|
218
|
+
sessionId: string,
|
|
219
|
+
): void {
|
|
220
|
+
let retired = false;
|
|
221
|
+
const retire = (): void => {
|
|
222
|
+
if (retired) return;
|
|
223
|
+
retired = true;
|
|
224
|
+
void registry.close(sessionId, 'tool_surface_changed');
|
|
225
|
+
};
|
|
226
|
+
res.once('finish', retire);
|
|
227
|
+
res.once('close', retire);
|
|
228
|
+
}
|
|
229
|
+
|
|
231
230
|
export function mcpRequestError(error: unknown) {
|
|
232
231
|
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
232
|
+
if (rawMessage.includes(RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN)) {
|
|
233
|
+
return {
|
|
234
|
+
status: 503 as const,
|
|
235
|
+
body: {
|
|
236
|
+
error: 'runtime_cutover_outcome_unknown' as const,
|
|
237
|
+
code: RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN,
|
|
238
|
+
message: 'The Canonical Runtime changed after this request was admitted. Reconcile the original request/effect identity before retrying.',
|
|
239
|
+
recoverable: true as const,
|
|
240
|
+
retryable: false as const,
|
|
241
|
+
sessionPreserved: true as const,
|
|
242
|
+
action: 'reconcile' as const,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
233
246
|
const retryable = /(?:\b502\b|\b503\b|\b429\b|ECONNRESET|ETIMEDOUT|EAI_AGAIN|CANONICAL_RUNTIME_TIMEOUT|server_busy|session_capacity|gateway)/i.test(rawMessage);
|
|
234
247
|
return {
|
|
235
248
|
status: (retryable ? 503 : 500) as 500 | 503,
|
|
@@ -254,343 +267,6 @@ export function sendMcpRequestError(res: Response, error: unknown): void {
|
|
|
254
267
|
res.status(response.status).json(response.body);
|
|
255
268
|
}
|
|
256
269
|
|
|
257
|
-
function getConfiguredPublicOrigin(config: McpLocalConfig | null): string | undefined {
|
|
258
|
-
const configured = process.env.FORGE_MCP_PUBLIC_ORIGIN?.trim();
|
|
259
|
-
if (configured) {
|
|
260
|
-
try {
|
|
261
|
-
return new URL(configured).origin;
|
|
262
|
-
} catch (_error) {
|
|
263
|
-
// Fall through to service or legacy config.
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const endpoint = config?.chatgpt?.endpoint?.trim();
|
|
267
|
-
if (!endpoint) return undefined;
|
|
268
|
-
try {
|
|
269
|
-
return new URL(endpoint).origin;
|
|
270
|
-
} catch (_error) {
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function getPublicOrigin(req: Request, configuredOrigin: string | undefined): string {
|
|
276
|
-
if (configuredOrigin) return configuredOrigin;
|
|
277
|
-
const proto = (req.headers['x-forwarded-proto'] as string | undefined) ?? 'https';
|
|
278
|
-
const host = (req.headers['x-forwarded-host'] as string | undefined) ?? req.headers.host ?? '127.0.0.1:8765';
|
|
279
|
-
return `${proto}://${host}`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function localControllerHealthUrl(host: string, port: number): string {
|
|
283
|
-
return `http://${host === '::1' ? '[::1]' : host}:${port}/health`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function jsonHealth(url: string): Promise<Record<string, unknown> | null> {
|
|
287
|
-
const controller = new AbortController();
|
|
288
|
-
const timeout = setTimeout(() => controller.abort(), 2_000);
|
|
289
|
-
try {
|
|
290
|
-
const response = await fetch(url, {
|
|
291
|
-
signal: controller.signal,
|
|
292
|
-
headers: { accept: 'application/json' },
|
|
293
|
-
});
|
|
294
|
-
if (!response.ok) return null;
|
|
295
|
-
return await response.json() as Record<string, unknown>;
|
|
296
|
-
} catch (_error) {
|
|
297
|
-
return null;
|
|
298
|
-
} finally {
|
|
299
|
-
clearTimeout(timeout);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function isAllowedMcpOAuthRedirectUri(redirectUri: string): boolean {
|
|
304
|
-
try {
|
|
305
|
-
const url = new URL(redirectUri);
|
|
306
|
-
if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
if (
|
|
310
|
-
url.protocol === 'https:' &&
|
|
311
|
-
(url.origin === 'https://chatgpt.com' || url.origin === 'https://chat.openai.com') &&
|
|
312
|
-
url.pathname.startsWith('/connector/oauth/')
|
|
313
|
-
) {
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
return false;
|
|
317
|
-
} catch (_error) {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function isRegisteredRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
|
|
323
|
-
return (client.redirect_uris ?? []).some((registered) => redirectUriMatches(redirectUri, registered));
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function isRegisteredExternalHttpsRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
|
|
327
|
-
try {
|
|
328
|
-
const url = new URL(redirectUri);
|
|
329
|
-
return url.protocol === 'https:' && !url.username && !url.password && isRegisteredRedirectUri(redirectUri, client);
|
|
330
|
-
} catch (_error) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function isSafeOAuthFallbackRedirectUri(redirectUri: string): boolean {
|
|
336
|
-
try {
|
|
337
|
-
const url = new URL(redirectUri);
|
|
338
|
-
if (url.username || url.password) return false;
|
|
339
|
-
if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return true;
|
|
340
|
-
return url.protocol === 'https:';
|
|
341
|
-
} catch (_error) {
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async function getOrRegisterPublicOAuthClient(
|
|
347
|
-
provider: ReturnType<typeof createMcpOAuthProvider>,
|
|
348
|
-
clientId: string,
|
|
349
|
-
redirectUri: string | undefined,
|
|
350
|
-
req: Request,
|
|
351
|
-
): Promise<OAuthClientInformationFull | undefined> {
|
|
352
|
-
const existing = await provider.clientsStore.getClient(clientId);
|
|
353
|
-
if (existing) return existing as OAuthClientInformationFull;
|
|
354
|
-
if (!redirectUri || !isSafeOAuthFallbackRedirectUri(redirectUri) || !provider.clientsStore.registerClient) return undefined;
|
|
355
|
-
oauthTrace(req, 'authorize:auto_register_public_client', {
|
|
356
|
-
redirectScheme: new URL(redirectUri).protocol,
|
|
357
|
-
redirectHost: new URL(redirectUri).hostname,
|
|
358
|
-
});
|
|
359
|
-
return provider.clientsStore.registerClient({
|
|
360
|
-
client_id: clientId,
|
|
361
|
-
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
362
|
-
client_name: 'forge OAuth fallback client',
|
|
363
|
-
redirect_uris: [redirectUri],
|
|
364
|
-
token_endpoint_auth_method: 'none',
|
|
365
|
-
grant_types: ['authorization_code', 'refresh_token'],
|
|
366
|
-
response_types: ['code'],
|
|
367
|
-
} as unknown as Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>) as OAuthClientInformationFull;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function escapeHtmlAttribute(value: string): string {
|
|
371
|
-
return value
|
|
372
|
-
.replace(/&/g, '&')
|
|
373
|
-
.replace(/"/g, '"')
|
|
374
|
-
.replace(/'/g, ''')
|
|
375
|
-
.replace(/</g, '<')
|
|
376
|
-
.replace(/>/g, '>');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function renderPassphrasePage(params: URLSearchParams): string {
|
|
380
|
-
const hiddenFields = Array.from(params.entries())
|
|
381
|
-
.filter(([key]) => key !== 'passphrase')
|
|
382
|
-
.map(([key, value]) => `<input type="hidden" name="${escapeHtmlAttribute(key)}" value="${escapeHtmlAttribute(value)}">`)
|
|
383
|
-
.join('\n');
|
|
384
|
-
|
|
385
|
-
return `<!doctype html>
|
|
386
|
-
<html lang="en">
|
|
387
|
-
<head><meta charset="utf-8"><title>Authorize forge</title>
|
|
388
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
389
|
-
<style>
|
|
390
|
-
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}
|
|
391
|
-
.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)}
|
|
392
|
-
h1{font-size:20px;margin:0 0 8px}p{margin:0 0 20px;color:#60666d;line-height:1.45}
|
|
393
|
-
input{width:100%;box-sizing:border-box;border:1px solid #bfc4c9;border-radius:8px;padding:12px;font-size:16px}
|
|
394
|
-
button{width:100%;margin-top:14px;border:0;border-radius:8px;padding:12px;background:#1f2328;color:#fff;font-size:16px;font-weight:600}
|
|
395
|
-
</style></head>
|
|
396
|
-
<body><main class="card">
|
|
397
|
-
<h1>Authorize forge</h1>
|
|
398
|
-
<p>Enter the local MCP passphrase to let this MCP client use this workflow-scoped connector.</p>
|
|
399
|
-
<form method="POST" action="/authorize">
|
|
400
|
-
${hiddenFields}
|
|
401
|
-
<input type="password" name="passphrase" placeholder="Passphrase" autofocus>
|
|
402
|
-
<button type="submit">Authorize</button>
|
|
403
|
-
</form>
|
|
404
|
-
</main></body></html>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/** Collect OAuth authorize params from query (GET) or body (POST form). */
|
|
408
|
-
function oauthAuthorizeParamSource(req: Request): Record<string, unknown> {
|
|
409
|
-
if (req.method === 'POST' && req.body && typeof req.body === 'object' && !Buffer.isBuffer(req.body)) {
|
|
410
|
-
return req.body as Record<string, unknown>;
|
|
411
|
-
}
|
|
412
|
-
return req.query as Record<string, unknown>;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function readOAuthAuthorizeString(source: Record<string, unknown>, key: string): string {
|
|
416
|
-
const value = source[key];
|
|
417
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* True when the request lacks the OAuth parameters needed for a real authorization.
|
|
422
|
-
* Incomplete requests must not render the passphrase form (incompatible clients loop there).
|
|
423
|
-
*/
|
|
424
|
-
export function isIncompleteOAuthAuthorizeRequest(source: Record<string, unknown>): boolean {
|
|
425
|
-
const clientId = readOAuthAuthorizeString(source, 'client_id');
|
|
426
|
-
const responseType = readOAuthAuthorizeString(source, 'response_type');
|
|
427
|
-
const codeChallenge = readOAuthAuthorizeString(source, 'code_challenge');
|
|
428
|
-
const redirectUri = readOAuthAuthorizeString(source, 'redirect_uri');
|
|
429
|
-
// redirect_uri may be omitted when the client has a single registered URI; client_id is the usable redirect context.
|
|
430
|
-
const hasRedirectContext = Boolean(redirectUri) || Boolean(clientId);
|
|
431
|
-
return !clientId || !responseType || !codeChallenge || !hasRedirectContext;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function incompleteOAuthAuthorizeResponseBody(): {
|
|
435
|
-
error: 'invalid_request';
|
|
436
|
-
error_description: string;
|
|
437
|
-
message: string;
|
|
438
|
-
hint: string;
|
|
439
|
-
} {
|
|
440
|
-
return {
|
|
441
|
-
error: 'invalid_request',
|
|
442
|
-
error_description:
|
|
443
|
-
'OAuth authorization request is incomplete. Required: client_id, response_type, code_challenge, and a usable redirect context (redirect_uri or a registered client).',
|
|
444
|
-
message:
|
|
445
|
-
'This endpoint expects a complete OAuth authorization request (PKCE). Non-OAuth MCP clients should use /mcp-bearer with Authorization: Bearer <token> instead of /authorize.',
|
|
446
|
-
hint: 'Use POST/GET /mcp-bearer with a forge bearer token for clients that cannot complete OAuth dynamic registration and PKCE.',
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function isOAuthDebugTraceEnabled(): boolean {
|
|
451
|
-
return process.env.FORGE_MCP_OAUTH_TRACE === '1' || process.env.FORGE_MCP_OAUTH_TRACE === 'true';
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const SENSITIVE_OAUTH_FIELDS = new Set([
|
|
455
|
-
'passphrase',
|
|
456
|
-
'code',
|
|
457
|
-
'code_verifier',
|
|
458
|
-
'client_secret',
|
|
459
|
-
'access_token',
|
|
460
|
-
'refresh_token',
|
|
461
|
-
'token',
|
|
462
|
-
'authorization',
|
|
463
|
-
]);
|
|
464
|
-
|
|
465
|
-
function safeOAuthFieldNames(source: Record<string, unknown>): string[] {
|
|
466
|
-
return Object.keys(source)
|
|
467
|
-
.filter((key) => !SENSITIVE_OAUTH_FIELDS.has(key.toLowerCase()))
|
|
468
|
-
.sort();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function oauthTrace(req: Request, event: string, extra: Record<string, unknown> = {}): void {
|
|
472
|
-
if (!isOAuthDebugTraceEnabled()) return;
|
|
473
|
-
const source = oauthAuthorizeParamSource(req);
|
|
474
|
-
const userAgent = typeof req.headers['user-agent'] === 'string'
|
|
475
|
-
? req.headers['user-agent'].split(/[\s/]/)[0]
|
|
476
|
-
: undefined;
|
|
477
|
-
const safe = {
|
|
478
|
-
event,
|
|
479
|
-
method: req.method,
|
|
480
|
-
path: req.path,
|
|
481
|
-
fieldNames: safeOAuthFieldNames(source),
|
|
482
|
-
hasClientId: Boolean(readOAuthAuthorizeString(source, 'client_id')),
|
|
483
|
-
hasRedirectUri: Boolean(readOAuthAuthorizeString(source, 'redirect_uri')),
|
|
484
|
-
hasCodeChallenge: Boolean(readOAuthAuthorizeString(source, 'code_challenge')),
|
|
485
|
-
responseType: readOAuthAuthorizeString(source, 'response_type') || undefined,
|
|
486
|
-
codeChallengeMethod: readOAuthAuthorizeString(source, 'code_challenge_method') || undefined,
|
|
487
|
-
grantType: readOAuthAuthorizeString(source, 'grant_type') || undefined,
|
|
488
|
-
hasResource: Boolean(readOAuthAuthorizeString(source, 'resource')),
|
|
489
|
-
userAgent,
|
|
490
|
-
...extra,
|
|
491
|
-
};
|
|
492
|
-
console.error(`[forge:mcp-oauth] ${JSON.stringify(safe)}`);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function oauthTraceMiddleware(event: string): (req: Request, res: Response, next: NextFunction) => void {
|
|
496
|
-
return (req, res, next) => {
|
|
497
|
-
oauthTrace(req, `${event}:request`);
|
|
498
|
-
res.once('finish', () => oauthTrace(req, `${event}:response`, { statusCode: res.statusCode }));
|
|
499
|
-
next();
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function rejectIncompleteOAuthAuthorize(req: Request, res: Response, next: NextFunction): void {
|
|
504
|
-
const source = oauthAuthorizeParamSource(req);
|
|
505
|
-
if (isIncompleteOAuthAuthorizeRequest(source)) {
|
|
506
|
-
oauthTrace(req, 'authorize:incomplete');
|
|
507
|
-
res.status(400).json(incompleteOAuthAuthorizeResponseBody());
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
oauthTrace(req, 'authorize:complete');
|
|
511
|
-
next();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function requirePassphrase(passphrase: string): (req: Request, res: Response, next: NextFunction) => void {
|
|
515
|
-
return (req, res, next) => {
|
|
516
|
-
const provided = typeof req.body?.passphrase === 'string' ? req.body.passphrase : undefined;
|
|
517
|
-
if (provided) {
|
|
518
|
-
const a = Buffer.from(provided);
|
|
519
|
-
const b = Buffer.from(passphrase);
|
|
520
|
-
if (a.length === b.length && timingSafeEqual(a, b)) {
|
|
521
|
-
next();
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
// Prefer body hidden fields on POST (failed passphrase re-render), else query string on GET.
|
|
526
|
-
const source = oauthAuthorizeParamSource(req);
|
|
527
|
-
const params = new URLSearchParams();
|
|
528
|
-
for (const [key, value] of Object.entries(source)) {
|
|
529
|
-
if (key === 'passphrase') continue;
|
|
530
|
-
if (typeof value === 'string') params.set(key, value);
|
|
531
|
-
}
|
|
532
|
-
if ([...params.keys()].length === 0 && req.url.includes('?')) {
|
|
533
|
-
const fromUrl = new URLSearchParams(req.url.slice(req.url.indexOf('?')));
|
|
534
|
-
for (const [key, value] of fromUrl.entries()) {
|
|
535
|
-
if (key !== 'passphrase') params.set(key, value);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
res.type('html').send(renderPassphrasePage(params));
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function oauthAuthorizationHandler(provider: ReturnType<typeof createMcpOAuthProvider>) {
|
|
543
|
-
return async (req: Request, res: Response) => {
|
|
544
|
-
const query = req.method === 'POST' ? req.body : req.query;
|
|
545
|
-
const clientId = typeof query.client_id === 'string' ? query.client_id : '';
|
|
546
|
-
const responseType = typeof query.response_type === 'string' ? query.response_type : '';
|
|
547
|
-
const codeChallenge = typeof query.code_challenge === 'string' ? query.code_challenge : '';
|
|
548
|
-
const codeChallengeMethod = typeof query.code_challenge_method === 'string' ? query.code_challenge_method : '';
|
|
549
|
-
const state = typeof query.state === 'string' ? query.state : undefined;
|
|
550
|
-
const scope = typeof query.scope === 'string' ? query.scope : undefined;
|
|
551
|
-
let redirectUri = typeof query.redirect_uri === 'string' ? query.redirect_uri : undefined;
|
|
552
|
-
|
|
553
|
-
if (responseType !== 'code') {
|
|
554
|
-
res.status(400).json({ error: 'unsupported_response_type', error_description: 'Only code response type is supported' });
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (!codeChallenge || codeChallengeMethod !== 'S256') {
|
|
558
|
-
res.status(400).json({ error: 'invalid_request', error_description: 'PKCE S256 is required' });
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const client = await getOrRegisterPublicOAuthClient(provider, clientId, redirectUri, req);
|
|
563
|
-
if (!client) {
|
|
564
|
-
res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
if (!redirectUri && client.redirect_uris.length === 1) {
|
|
568
|
-
redirectUri = client.redirect_uris[0];
|
|
569
|
-
}
|
|
570
|
-
if (!redirectUri || (!isAllowedMcpOAuthRedirectUri(redirectUri) && !isRegisteredExternalHttpsRedirectUri(redirectUri, client))) {
|
|
571
|
-
res.status(400).json({
|
|
572
|
-
error: 'invalid_request',
|
|
573
|
-
error_description: 'redirect_uri must be localhost, a ChatGPT connector callback URL, or a registered HTTPS client redirect_uri',
|
|
574
|
-
});
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
if (!isRegisteredRedirectUri(redirectUri, client)) {
|
|
578
|
-
res.status(400).json({
|
|
579
|
-
error: 'invalid_request',
|
|
580
|
-
error_description: 'redirect_uri must match a registered client redirect_uri',
|
|
581
|
-
});
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
await provider.authorize(client as OAuthClientInformationFull, {
|
|
586
|
-
state,
|
|
587
|
-
scopes: scope ? scope.split(' ') : [],
|
|
588
|
-
redirectUri,
|
|
589
|
-
codeChallenge,
|
|
590
|
-
}, res);
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
270
|
function sendBearerUnauthorized(res: Response, description: string, hasConfiguredToken: boolean): void {
|
|
595
271
|
res.setHeader('www-authenticate', 'Bearer realm="forge-mcp"');
|
|
596
272
|
res.status(hasConfiguredToken ? 401 : 503).json({
|
|
@@ -682,9 +358,60 @@ const MCP_STREAM_LEASE_MS = positiveIntegerEnv('FORGE_MCP_STREAM_LEASE_MS', 30 *
|
|
|
682
358
|
const MCP_SESSION_ABSOLUTE_LIFETIME_MS = positiveIntegerEnv('FORGE_MCP_SESSION_ABSOLUTE_LIFETIME_MS', 2 * 60 * 60_000);
|
|
683
359
|
const MCP_ACTIVE_POST_STALL_MS = positiveIntegerEnv('FORGE_MCP_ACTIVE_POST_STALL_MS', 10 * 60_000);
|
|
684
360
|
|
|
361
|
+
export function admitMcpPost(stats: McpRuntimeStats, res: Response): boolean {
|
|
362
|
+
if (stats.activePosts >= MAX_ACTIVE_POSTS) {
|
|
363
|
+
stats.rejectedOverload += 1;
|
|
364
|
+
res.setHeader('retry-after', '1');
|
|
365
|
+
res.status(503).json({
|
|
366
|
+
error: 'server_busy',
|
|
367
|
+
code: 'MCP_SERVER_BUSY',
|
|
368
|
+
message: 'Too many MCP requests are active; retry shortly',
|
|
369
|
+
recoverable: true,
|
|
370
|
+
retryable: true,
|
|
371
|
+
sessionPreserved: true,
|
|
372
|
+
action: 'retry',
|
|
373
|
+
});
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
stats.activePosts += 1;
|
|
378
|
+
let released = false;
|
|
379
|
+
const release = (): void => {
|
|
380
|
+
if (released) return;
|
|
381
|
+
released = true;
|
|
382
|
+
stats.activePosts = Math.max(0, stats.activePosts - 1);
|
|
383
|
+
res.off('finish', release);
|
|
384
|
+
res.off('close', release);
|
|
385
|
+
};
|
|
386
|
+
res.once('finish', release);
|
|
387
|
+
res.once('close', release);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
685
391
|
type McpToolContext = ReturnType<typeof createMcpToolContext>;
|
|
686
392
|
type HttpSessionRegistry = McpSessionRegistry<NodeStreamableHTTPServerTransport, McpToolContext>;
|
|
687
393
|
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Public HTTP transport registry. It owns transport admission/leases and
|
|
397
|
+
* transport-local observability only; closing a transport session must never
|
|
398
|
+
* invalidate durable execution, Work, or ControllerRound authority.
|
|
399
|
+
*/
|
|
400
|
+
export function createMcpHttpSessionRegistry<
|
|
401
|
+
TTransport extends ClosableMcpTransport = NodeStreamableHTTPServerTransport,
|
|
402
|
+
TContext = McpToolContext,
|
|
403
|
+
>(options: McpSessionRegistryOptions<TTransport, TContext> = {}): McpSessionRegistry<TTransport, TContext> {
|
|
404
|
+
return new McpSessionRegistry<TTransport, TContext>({
|
|
405
|
+
maximumSessions: MAX_MCP_SESSIONS,
|
|
406
|
+
maximumSessionsPerPrincipal: MAX_MCP_SESSIONS_PER_PRINCIPAL,
|
|
407
|
+
idleTtlMs: MCP_SESSION_IDLE_TTL_MS,
|
|
408
|
+
streamLeaseMs: MCP_STREAM_LEASE_MS,
|
|
409
|
+
absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
|
|
410
|
+
activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
|
|
411
|
+
...options,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
688
415
|
function principalIdFromModernRequestContext(context: McpRequestContext): string {
|
|
689
416
|
const clientId = context.authInfo?.clientId?.trim();
|
|
690
417
|
if (clientId) return `oauth-client:${clientId}`;
|
|
@@ -731,6 +458,7 @@ async function handleMcpPost(
|
|
|
731
458
|
res.status(400).json({ error: 'invalid JSON request body' });
|
|
732
459
|
return;
|
|
733
460
|
}
|
|
461
|
+
if (!admitMcpPost(stats, res)) return;
|
|
734
462
|
if (modernHandler) {
|
|
735
463
|
const webRequest = await toWebRequest(req, body);
|
|
736
464
|
if (!(await isLegacyRequest(webRequest, body))) {
|
|
@@ -744,7 +472,7 @@ async function handleMcpPost(
|
|
|
744
472
|
res.setHeader('Mcp-Session-Reset', 'reinitialized');
|
|
745
473
|
res.setHeader('x-forge-session-reset', 'reinitialized');
|
|
746
474
|
}
|
|
747
|
-
if (stats.initializing >= MAX_INITIALIZING_SESSIONS
|
|
475
|
+
if (stats.initializing >= MAX_INITIALIZING_SESSIONS) {
|
|
748
476
|
stats.rejectedOverload += 1;
|
|
749
477
|
res.setHeader('retry-after', '1');
|
|
750
478
|
res.status(503).json({
|
|
@@ -759,7 +487,6 @@ async function handleMcpPost(
|
|
|
759
487
|
return;
|
|
760
488
|
}
|
|
761
489
|
stats.initializing += 1;
|
|
762
|
-
stats.activePosts += 1;
|
|
763
490
|
let transport: NodeStreamableHTTPServerTransport | undefined;
|
|
764
491
|
let reservationId: string | undefined;
|
|
765
492
|
let initializedSessionId: string | undefined;
|
|
@@ -839,11 +566,23 @@ async function handleMcpPost(
|
|
|
839
566
|
server = createForgeMcpServerFromContext(sessionContext, runtimeSchema, sharedRuntimeProxy);
|
|
840
567
|
await server.connect(transport);
|
|
841
568
|
await transport.handleRequest(req, res, body);
|
|
569
|
+
if (
|
|
570
|
+
initializedSessionId
|
|
571
|
+
&& 'controllerHome' in sessionContext
|
|
572
|
+
&& typeof sessionContext.controllerHome === 'string'
|
|
573
|
+
) {
|
|
574
|
+
recordMcpTransportEvent(sessionContext.controllerHome, {
|
|
575
|
+
kind: 'session_initialized',
|
|
576
|
+
sessionId: initializedSessionId,
|
|
577
|
+
connectionId: connection.connectionId,
|
|
578
|
+
route,
|
|
579
|
+
principalId,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
842
582
|
} finally {
|
|
843
583
|
if (initializedSessionId) registry.endPost(initializedSessionId);
|
|
844
584
|
if (reservationId) registry.releaseInitialize(reservationId);
|
|
845
|
-
stats.initializing
|
|
846
|
-
stats.activePosts -= 1;
|
|
585
|
+
stats.initializing = Math.max(0, stats.initializing - 1);
|
|
847
586
|
if (!transport?.sessionId) await transport?.close().catch(() => undefined);
|
|
848
587
|
}
|
|
849
588
|
return;
|
|
@@ -851,14 +590,13 @@ async function handleMcpPost(
|
|
|
851
590
|
if (sessionId) {
|
|
852
591
|
const managed = registry.get(sessionId);
|
|
853
592
|
if (managed && managed.route === route && managed.principalId === principalFromRequest(req)) {
|
|
854
|
-
if (managed.inFlightPosts >= MAX_POSTS_PER_SESSION
|
|
593
|
+
if (managed.inFlightPosts >= MAX_POSTS_PER_SESSION) {
|
|
855
594
|
stats.rejectedOverload += 1;
|
|
856
595
|
res.setHeader('retry-after', '1');
|
|
857
596
|
res.status(429).json({ error: 'session_busy', message: 'Too many MCP requests are active; retry shortly' });
|
|
858
597
|
return;
|
|
859
598
|
}
|
|
860
599
|
registry.beginPost(sessionId);
|
|
861
|
-
stats.activePosts += 1;
|
|
862
600
|
try {
|
|
863
601
|
const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
|
|
864
602
|
currentToolSurfaceFingerprint(),
|
|
@@ -878,23 +616,21 @@ async function handleMcpPost(
|
|
|
878
616
|
return;
|
|
879
617
|
}
|
|
880
618
|
}
|
|
881
|
-
// Keep the transport/session alive long enough for the host to observe
|
|
882
|
-
// the recoverable reset and issue a replacement initialize request.
|
|
883
|
-
// The initialize path explicitly supersedes this session afterward.
|
|
884
619
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
620
|
+
retireAfterToolSurfaceReset(res, registry, sessionId);
|
|
885
621
|
return;
|
|
886
622
|
}
|
|
887
623
|
if (toolCallOutsideSessionSchema(body, managed.toolNames)) {
|
|
888
624
|
// A call against a newer discovery surface is the same recoverable
|
|
889
|
-
// schema-fence condition
|
|
890
|
-
//
|
|
625
|
+
// schema-fence condition: this transport must reinitialize before it
|
|
626
|
+
// can execute anything else.
|
|
891
627
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
628
|
+
retireAfterToolSurfaceReset(res, registry, sessionId);
|
|
892
629
|
return;
|
|
893
630
|
}
|
|
894
631
|
await managed.transport.handleRequest(req, res, body);
|
|
895
632
|
} finally {
|
|
896
633
|
registry.endPost(sessionId);
|
|
897
|
-
stats.activePosts -= 1;
|
|
898
634
|
}
|
|
899
635
|
return;
|
|
900
636
|
}
|
|
@@ -933,9 +669,8 @@ async function handleMcpGet(
|
|
|
933
669
|
: undefined,
|
|
934
670
|
);
|
|
935
671
|
if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
|
|
936
|
-
// Preserve the existing SSE transport while asking the host to
|
|
937
|
-
// reinitialize. The replacement initialize owns supersession/cleanup.
|
|
938
672
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
673
|
+
retireAfterToolSurfaceReset(res, registry, sessionId!);
|
|
939
674
|
return;
|
|
940
675
|
}
|
|
941
676
|
await managed.transport.handleRequest(req, res);
|
|
@@ -992,23 +727,17 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
992
727
|
tokenStore?.load();
|
|
993
728
|
const oauthProvider = tokenStore ? createMcpOAuthProvider(tokenStore) : null;
|
|
994
729
|
const configuredPublicOrigin = getConfiguredPublicOrigin(serviceConfig);
|
|
995
|
-
const sessionRegistry =
|
|
996
|
-
maximumSessions: MAX_MCP_SESSIONS,
|
|
997
|
-
maximumSessionsPerPrincipal: MAX_MCP_SESSIONS_PER_PRINCIPAL,
|
|
998
|
-
idleTtlMs: MCP_SESSION_IDLE_TTL_MS,
|
|
999
|
-
streamLeaseMs: MCP_STREAM_LEASE_MS,
|
|
1000
|
-
absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
|
|
1001
|
-
activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
|
|
730
|
+
const sessionRegistry = createMcpHttpSessionRegistry<NodeStreamableHTTPServerTransport, McpToolContext>({
|
|
1002
731
|
onSessionClosed: (session, reason) => {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
732
|
+
if (reason !== 'transport_close') return;
|
|
733
|
+
recordMcpTransportEvent(controllerHome, {
|
|
734
|
+
kind: 'interruption',
|
|
735
|
+
sessionId: session.sessionId,
|
|
736
|
+
connectionId: session.connectionId,
|
|
737
|
+
route: session.route,
|
|
738
|
+
principalId: session.principalId,
|
|
739
|
+
reason,
|
|
740
|
+
});
|
|
1012
741
|
},
|
|
1013
742
|
});
|
|
1014
743
|
const runtimeStats: McpRuntimeStats = { initializing: 0, activePosts: 0, rejectedOverload: 0 };
|
|
@@ -1055,295 +784,43 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
1055
784
|
return await readCanonicalRuntimeToolSchema(context, sharedRuntimeProxy);
|
|
1056
785
|
}
|
|
1057
786
|
: undefined;
|
|
1058
|
-
const localControllerConfig = {
|
|
1059
|
-
enabled: serviceConfig?.localController?.enabled ?? profile === 'controller',
|
|
1060
|
-
host: serviceConfig?.localController?.host ?? '127.0.0.1',
|
|
1061
|
-
port: serviceConfig?.localController?.port ?? 8766,
|
|
1062
|
-
};
|
|
1063
|
-
const compatibilityToolDefinitions = buildMcpToolDefinitions(toolContext.policy, { enableChatgptBrowser: opts.enableChatgptBrowser === true });
|
|
1064
787
|
const toolSurface = toolContext.policy.profile === 'controller' ? FORGE_TOOL_SURFACE : `${toolContext.policy.profile}-legacy-v1`;
|
|
1065
788
|
const toolSurfaceSchemaVersion = toolContext.policy.profile === 'controller' ? FORGE_MCP_SCHEMA_VERSION : 1;
|
|
1066
789
|
const forgeVersion = FORGE_VERSION;
|
|
1067
|
-
const repoId = toolContext.policy.profile === 'controller' || !repoRoot ? undefined : repositoryIdentity(repoRoot);
|
|
1068
|
-
const startedAt = new Date().toISOString();
|
|
1069
|
-
const localOrigin = `http://${host === '::' || host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`;
|
|
1070
|
-
const advertisedOrigin = configuredPublicOrigin ?? localOrigin;
|
|
1071
790
|
const app = express();
|
|
1072
791
|
app.set('trust proxy', 1);
|
|
1073
792
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
res.setHeader('x-forge-tool-surface', toolSurface);
|
|
1102
|
-
res.setHeader('x-forge-version', String(forgeVersion));
|
|
1103
|
-
res.setHeader('x-forge-schema-version', String(toolSurfaceSchemaVersion));
|
|
1104
|
-
if (health?.toolset) res.setHeader('x-forge-toolset', health.toolset);
|
|
1105
|
-
if (health?.runtimeToolSurfaceFingerprint) res.setHeader('x-forge-runtime-tool-surface-fingerprint', health.runtimeToolSurfaceFingerprint);
|
|
1106
|
-
if (health?.toolSurfaceFingerprint) res.setHeader('x-forge-tool-surface-fingerprint', health.toolSurfaceFingerprint);
|
|
1107
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1108
|
-
res.json({
|
|
1109
|
-
status: 'ok',
|
|
1110
|
-
server: 'forge-mcp',
|
|
1111
|
-
forgeInstanceId: forgeInstance.instanceId,
|
|
1112
|
-
...(process.env.FORGE_MCP_INSTANCE_ID
|
|
1113
|
-
? { controllerInstanceId: process.env.FORGE_MCP_INSTANCE_ID }
|
|
1114
|
-
: {}),
|
|
1115
|
-
version: forgeVersion,
|
|
1116
|
-
profile: toolContext.policy.profile,
|
|
1117
|
-
toolSurface,
|
|
1118
|
-
schemaVersion: toolSurfaceSchemaVersion,
|
|
1119
|
-
toolSurfaceFingerprint: health?.toolSurfaceFingerprint,
|
|
1120
|
-
runtimeToolSurfaceFingerprint: health?.runtimeToolSurfaceFingerprint,
|
|
1121
|
-
generation: health?.generation,
|
|
1122
|
-
source: health?.source,
|
|
1123
|
-
// Deprecated diagnostic alias; not a Runtime schema claim.
|
|
1124
|
-
toolset: health?.toolset ?? 'full',
|
|
1125
|
-
gatewayToolset: health?.toolset ?? 'full',
|
|
1126
|
-
toolCount: health?.toolCount,
|
|
1127
|
-
compatibilityToolCount: compatibilityToolDefinitions.length,
|
|
1128
|
-
runtimeIdentity: health?.runtimeIdentity,
|
|
1129
|
-
configuredAccessMode: health?.configuredAccessMode,
|
|
1130
|
-
effectiveAccessMode: health?.effectiveAccessMode,
|
|
1131
|
-
effectiveToolset: health?.effectiveToolset,
|
|
1132
|
-
accessModeSource: health?.accessModeSource,
|
|
1133
|
-
accessModeLastAppliedAt: health?.accessModeLastAppliedAt,
|
|
1134
|
-
exposureRevision: health?.exposureRevision,
|
|
1135
|
-
...(repoId ? { repoId } : {}),
|
|
1136
|
-
startedAt,
|
|
1137
|
-
runner: {
|
|
1138
|
-
enabled: toolContext.policy.execution.agentRunner,
|
|
1139
|
-
defaultTimeoutMs: toolContext.policy.execution.runnerTimeoutMs,
|
|
1140
|
-
maxTimeoutMs: toolContext.policy.execution.runnerMaxTimeoutMs,
|
|
1141
|
-
},
|
|
1142
|
-
auth: authMode === 'oauth'
|
|
1143
|
-
? (oauthPassphrase ? 'oauth' : 'missing')
|
|
1144
|
-
: authMode === 'bearer'
|
|
1145
|
-
? (authToken ? 'required' : 'missing')
|
|
1146
|
-
: 'none',
|
|
1147
|
-
...(oauthProvider ? { oauthAuthorizationCodes: oauthProvider.authorizationCodeDiagnostics() } : {}),
|
|
1148
|
-
mcpEndpoint: `${advertisedOrigin}/mcp`,
|
|
1149
|
-
// Grok now completes standard OAuth dynamic registration + PKCE on the
|
|
1150
|
-
// canonical MCP resource. Keep /mcp-grok below only as a legacy alias.
|
|
1151
|
-
grokEndpoint: `${advertisedOrigin}/mcp`,
|
|
1152
|
-
bearerEndpoint: `${advertisedOrigin}/mcp-bearer`,
|
|
1153
|
-
sessions: {
|
|
1154
|
-
...sessionSnapshot,
|
|
1155
|
-
initializing: runtimeStats.initializing,
|
|
1156
|
-
activePosts: runtimeStats.activePosts,
|
|
1157
|
-
maximumActivePosts: MAX_ACTIVE_POSTS,
|
|
1158
|
-
rejectedOverload: runtimeStats.rejectedOverload,
|
|
1159
|
-
},
|
|
1160
|
-
});
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
// Recovery probes this endpoint every few seconds. Keep it strictly in-memory
|
|
1164
|
-
// and transport-scoped: whole-control-plane readiness below may traverse every
|
|
1165
|
-
// repository projection and probe the local bridge, which must never become a
|
|
1166
|
-
// periodic event-loop load generator for the public MCP Connector itself.
|
|
1167
|
-
app.get('/transport-ready', (_req, res) => {
|
|
1168
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1169
|
-
const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
|
|
1170
|
-
&& runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
|
|
1171
|
-
&& runtimeStats.activePosts < MAX_ACTIVE_POSTS;
|
|
1172
|
-
res.status(sessionCapacityReady ? 200 : 503).json({
|
|
1173
|
-
ready: sessionCapacityReady,
|
|
1174
|
-
profile: toolContext.policy.profile,
|
|
1175
|
-
gateway: sessionCapacityReady ? 'ready' : 'saturated',
|
|
1176
|
-
sessionCapacity: sessionSnapshot,
|
|
1177
|
-
runtimeCapacity: {
|
|
1178
|
-
initializing: runtimeStats.initializing,
|
|
1179
|
-
maximumInitializing: MAX_INITIALIZING_SESSIONS,
|
|
1180
|
-
activePosts: runtimeStats.activePosts,
|
|
1181
|
-
maximumActivePosts: MAX_ACTIVE_POSTS,
|
|
1182
|
-
},
|
|
1183
|
-
});
|
|
1184
|
-
});
|
|
1185
|
-
|
|
1186
|
-
app.get('/ready', async (_req, res) => {
|
|
1187
|
-
const runtimeGeneration = currentRuntimeGeneration();
|
|
1188
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1189
|
-
const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
|
|
1190
|
-
&& runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
|
|
1191
|
-
&& runtimeStats.activePosts < MAX_ACTIVE_POSTS;
|
|
1192
|
-
if (!runtimeControllerHome) {
|
|
1193
|
-
res.status(sessionCapacityReady ? 200 : 503).json({
|
|
1194
|
-
ready: sessionCapacityReady,
|
|
1195
|
-
profile: toolContext.policy.profile,
|
|
1196
|
-
gateway: sessionCapacityReady ? 'ready' : 'saturated',
|
|
1197
|
-
controllerDaemon: 'not-required',
|
|
1198
|
-
sessionCapacity: sessionSnapshot,
|
|
1199
|
-
});
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
const daemon = readForgeRuntimeStatus(runtimeControllerHome);
|
|
1203
|
-
const runtimeState = loadMcpServiceRuntimeState(runtimeControllerHome, repoRoot);
|
|
1204
|
-
const repositories = listRepositories(runtimeControllerHome).filter((repository) => repository.enabled && !repository.removedAt);
|
|
1205
|
-
const projectionSnapshots = repositories.map((repository) => {
|
|
1206
|
-
const snapshot = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
|
|
1207
|
-
const reconciliation = reconcileReadinessProjectionSource(
|
|
1208
|
-
snapshot,
|
|
1209
|
-
legacyIssueAuthorityRetired(repository.canonicalRoot)
|
|
1210
|
-
? undefined
|
|
1211
|
-
: buildControllerTaskLedgerProjection(repository.canonicalRoot),
|
|
1212
|
-
);
|
|
1213
|
-
return { repoId: repository.repoId, snapshot, reconciliation };
|
|
1214
|
-
});
|
|
1215
|
-
const staleRepositories = projectionSnapshots
|
|
1216
|
-
.filter(({ snapshot }) => snapshot.stale)
|
|
1217
|
-
.map(({ repoId }) => repoId);
|
|
1218
|
-
const blockingStaleRepositories = projectionSnapshots
|
|
1219
|
-
.filter(({ snapshot }) => projectionBlocksReadiness(snapshot))
|
|
1220
|
-
.map(({ repoId }) => repoId);
|
|
1221
|
-
const sourceMismatches = projectionSnapshots
|
|
1222
|
-
.filter(({ reconciliation }) => reconciliation.status === 'mismatch')
|
|
1223
|
-
.map(({ repoId, reconciliation }) => ({ repoId, ...reconciliation }));
|
|
1224
|
-
const localBridgeHealth = localControllerConfig.enabled
|
|
1225
|
-
? await jsonHealth(localControllerHealthUrl(localControllerConfig.host, localControllerConfig.port))
|
|
1226
|
-
: null;
|
|
1227
|
-
const localBridgeReady = !localControllerConfig.enabled
|
|
1228
|
-
|| localControllerDiagnosticMatchesRuntime(localBridgeHealth, runtimeGeneration?.generation);
|
|
1229
|
-
const daemonReady = daemon.status === 'ready' && daemon.degraded !== true;
|
|
1230
|
-
const projectionReady = blockingStaleRepositories.length === 0;
|
|
1231
|
-
const publicConfigured = Boolean(runtimeState?.tunnel?.publicEndpoint);
|
|
1232
|
-
const publicReady = !publicConfigured || runtimeState?.tunnel?.healthy === true;
|
|
1233
|
-
const connectorReady = !publicConfigured || (
|
|
1234
|
-
publicReady
|
|
1235
|
-
&& runtimeState?.tunnel?.connectorNeedsReconnect !== true
|
|
1236
|
-
);
|
|
1237
|
-
const ready = daemonReady && projectionReady && localBridgeReady && sessionCapacityReady;
|
|
1238
|
-
res.status(ready ? 200 : 503).json({
|
|
1239
|
-
ready,
|
|
1240
|
-
generation: runtimeGeneration?.generation,
|
|
1241
|
-
source: runtimeGeneration?.source,
|
|
1242
|
-
gateway: { status: ready ? 'ready' : 'degraded', thin: true, eventLoopIsolatedFromWorkers: true },
|
|
1243
|
-
controllerDaemon: daemon,
|
|
1244
|
-
localBridge: {
|
|
1245
|
-
enabled: localControllerConfig.enabled,
|
|
1246
|
-
ready: localBridgeReady,
|
|
1247
|
-
endpoint: `http://${localControllerConfig.host === '::1' ? '[::1]' : localControllerConfig.host}:${localControllerConfig.port}/`,
|
|
1248
|
-
},
|
|
1249
|
-
projections: {
|
|
1250
|
-
ready: projectionReady,
|
|
1251
|
-
repositoryCount: repositories.length,
|
|
1252
|
-
staleRepositories,
|
|
1253
|
-
blockingStaleRepositories,
|
|
1254
|
-
sourceMismatches,
|
|
1255
|
-
},
|
|
1256
|
-
publicReadiness: {
|
|
1257
|
-
configured: publicConfigured,
|
|
1258
|
-
ready: publicReady,
|
|
1259
|
-
endpoint: runtimeState?.tunnel?.publicEndpoint,
|
|
1260
|
-
},
|
|
1261
|
-
connectorReadiness: {
|
|
1262
|
-
configured: publicConfigured,
|
|
1263
|
-
ready: connectorReady,
|
|
1264
|
-
connectorNeedsReconnect: runtimeState?.tunnel?.connectorNeedsReconnect === true,
|
|
1265
|
-
},
|
|
1266
|
-
sessionCapacity: sessionSnapshot,
|
|
1267
|
-
});
|
|
1268
|
-
});
|
|
1269
|
-
|
|
1270
|
-
app.get('/repos/:repoId/health', (req, res) => {
|
|
1271
|
-
if (!runtimeControllerHome) {
|
|
1272
|
-
res.status(404).json({ error: 'controller profile required' });
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
try {
|
|
1276
|
-
const repository = getRepository(req.params.repoId, runtimeControllerHome, { includeRemoved: true });
|
|
1277
|
-
const projection = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
|
|
1278
|
-
res.json({
|
|
1279
|
-
status: repository.enabled && !repository.removedAt ? 'ok' : 'disabled',
|
|
1280
|
-
repository: {
|
|
1281
|
-
repoId: repository.repoId,
|
|
1282
|
-
checkoutId: repository.activeCheckoutId,
|
|
1283
|
-
enabled: repository.enabled,
|
|
1284
|
-
removedAt: repository.removedAt,
|
|
1285
|
-
},
|
|
1286
|
-
projection,
|
|
1287
|
-
});
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
res.status(404).json({ error: error instanceof Error ? error.message : String(error) });
|
|
1290
|
-
}
|
|
793
|
+
registerMcpHttpObservationRoutes({
|
|
794
|
+
app,
|
|
795
|
+
toolContext,
|
|
796
|
+
sessionRegistry,
|
|
797
|
+
runtimeStats,
|
|
798
|
+
runtimeControllerHome,
|
|
799
|
+
repoRoot,
|
|
800
|
+
forgeInstanceId: forgeInstance.instanceId,
|
|
801
|
+
currentRuntimeToolSurfaceFingerprint,
|
|
802
|
+
toolSurface,
|
|
803
|
+
toolSurfaceSchemaVersion,
|
|
804
|
+
forgeVersion,
|
|
805
|
+
authMode,
|
|
806
|
+
authTokenConfigured: Boolean(authToken),
|
|
807
|
+
oauthPassphraseConfigured: Boolean(oauthPassphrase),
|
|
808
|
+
oauthAuthorizationCodeDiagnostics: oauthProvider ? () => oauthProvider.authorizationCodeDiagnostics() : undefined,
|
|
809
|
+
configuredPublicOrigin,
|
|
810
|
+
host,
|
|
811
|
+
port,
|
|
812
|
+
enableChatgptBrowser: opts.enableChatgptBrowser === true,
|
|
813
|
+
localController: {
|
|
814
|
+
enabled: serviceConfig?.localController?.enabled ?? profile === 'controller',
|
|
815
|
+
host: serviceConfig?.localController?.host ?? '127.0.0.1',
|
|
816
|
+
port: serviceConfig?.localController?.port ?? 8766,
|
|
817
|
+
},
|
|
818
|
+
maxInitializingSessions: MAX_INITIALIZING_SESSIONS,
|
|
819
|
+
maxActivePosts: MAX_ACTIVE_POSTS,
|
|
1291
820
|
});
|
|
1292
821
|
|
|
1293
822
|
if (authMode === 'oauth' && oauthProvider) {
|
|
1294
|
-
app
|
|
1295
|
-
app.use('/authorize', oauthTraceMiddleware('authorize'));
|
|
1296
|
-
// Reject incomplete OAuth requests before rendering the passphrase form.
|
|
1297
|
-
app.use('/authorize', rejectIncompleteOAuthAuthorize);
|
|
1298
|
-
app.use('/authorize', requirePassphrase(oauthPassphrase ?? ''));
|
|
1299
|
-
app.use('/authorize', oauthAuthorizationHandler(oauthProvider));
|
|
1300
|
-
app.use('/token', oauthTraceMiddleware('token'));
|
|
1301
|
-
app.use('/token', tokenHandler({ provider: oauthProvider, rateLimit: false }));
|
|
1302
|
-
app.use('/revoke', oauthTraceMiddleware('revoke'));
|
|
1303
|
-
app.use('/revoke', revocationHandler({ provider: oauthProvider, rateLimit: false }));
|
|
1304
|
-
app.use('/register', oauthTraceMiddleware('register'));
|
|
1305
|
-
app.use('/register', clientRegistrationHandler({ clientsStore: oauthProvider.clientsStore, rateLimit: false }));
|
|
1306
|
-
app.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
1307
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1308
|
-
res.json({
|
|
1309
|
-
issuer: origin,
|
|
1310
|
-
authorization_endpoint: `${origin}/authorize`,
|
|
1311
|
-
token_endpoint: `${origin}/token`,
|
|
1312
|
-
revocation_endpoint: `${origin}/revoke`,
|
|
1313
|
-
registration_endpoint: `${origin}/register`,
|
|
1314
|
-
response_types_supported: ['code'],
|
|
1315
|
-
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
1316
|
-
code_challenge_methods_supported: ['S256'],
|
|
1317
|
-
token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
|
|
1318
|
-
scopes_supported: ['forge'],
|
|
1319
|
-
});
|
|
1320
|
-
});
|
|
1321
|
-
app.get('/.well-known/openid-configuration', (req, res) => {
|
|
1322
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1323
|
-
res.json({
|
|
1324
|
-
issuer: origin,
|
|
1325
|
-
authorization_endpoint: `${origin}/authorize`,
|
|
1326
|
-
token_endpoint: `${origin}/token`,
|
|
1327
|
-
registration_endpoint: `${origin}/register`,
|
|
1328
|
-
response_types_supported: ['code'],
|
|
1329
|
-
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
1330
|
-
code_challenge_methods_supported: ['S256'],
|
|
1331
|
-
token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
|
|
1332
|
-
scopes_supported: ['forge'],
|
|
1333
|
-
});
|
|
1334
|
-
});
|
|
1335
|
-
const protectedResourceMetadata = (resourcePath: '/mcp' | '/mcp-grok' | '/mcp-bearer') => (req: Request, res: Response): void => {
|
|
1336
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1337
|
-
res.json({
|
|
1338
|
-
resource: `${origin}${resourcePath}`,
|
|
1339
|
-
authorization_servers: [origin],
|
|
1340
|
-
scopes_supported: ['forge'],
|
|
1341
|
-
bearer_methods_supported: ['header'],
|
|
1342
|
-
});
|
|
1343
|
-
};
|
|
1344
|
-
app.get('/.well-known/oauth-protected-resource/mcp', protectedResourceMetadata('/mcp'));
|
|
1345
|
-
app.get('/.well-known/oauth-protected-resource/mcp-grok', protectedResourceMetadata('/mcp-grok'));
|
|
1346
|
-
app.get('/.well-known/oauth-protected-resource/mcp-bearer', protectedResourceMetadata('/mcp-bearer'));
|
|
823
|
+
registerMcpOAuthHttpRoutes(app, oauthProvider, oauthPassphrase ?? '', configuredPublicOrigin);
|
|
1347
824
|
}
|
|
1348
825
|
|
|
1349
826
|
const setMcpResponseHeaders = (_req: Request, res: Response, next: NextFunction): void => {
|