@geraldmaron/construct 1.4.1 → 1.5.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.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/transport/auth.mjs — remote (HTTP) MCP authorization primitives.
|
|
3
|
+
*
|
|
4
|
+
* Pure, network-free functions the http entrypoint composes at request time.
|
|
5
|
+
* Split out from the transport wiring so every rule is unit-testable without a
|
|
6
|
+
* socket. Four responsibilities, in the order the http entrypoint applies them:
|
|
7
|
+
*
|
|
8
|
+
* 1. resolveHttpAuthConfig — reads the http-remote auth config and FAILS
|
|
9
|
+
* CLOSED. http mode has no default identity; a missing bearer secret,
|
|
10
|
+
* missing audience, or missing allowed-origin list is a startup error,
|
|
11
|
+
* never a warning. There is no skip/allow env var — the absence of config
|
|
12
|
+
* is the refusal.
|
|
13
|
+
* 2. validateOrigin / validateHost — DNS-rebinding defense. A browser that
|
|
14
|
+
* resolves a trusted name to 127.0.0.1 still sends the attacker's Origin
|
|
15
|
+
* and the attacker's Host; both must be on the operator's allowlist or the
|
|
16
|
+
* request is rejected before any tool dispatch. Pairs with N7's SSRF work.
|
|
17
|
+
* 3. verifyBearer — RFC 8707 shaped: the inbound token must carry the exact
|
|
18
|
+
* audience (resource indicator) this server was configured for. A token
|
|
19
|
+
* minted for a different audience is rejected even if otherwise valid, so a
|
|
20
|
+
* token stolen from an upstream service cannot be replayed here.
|
|
21
|
+
* 4. mintScopedContext — the broker's internal actor context is derived from
|
|
22
|
+
* the VERIFIED claims and NEVER contains the inbound token. Token
|
|
23
|
+
* passthrough is structurally impossible: the raw bearer is consumed by
|
|
24
|
+
* verifyBearer and dropped; nothing downstream is handed the credential.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import crypto from 'node:crypto';
|
|
28
|
+
|
|
29
|
+
export class HttpAuthConfigError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'HttpAuthConfigError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class HttpAuthError extends Error {
|
|
37
|
+
constructor(message, { status = 401 } = {}) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = 'HttpAuthError';
|
|
40
|
+
this.status = status;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The scoped context carries the token as a symbol-keyed, non-enumerable
|
|
45
|
+
// nothing: there is no field to hold it. mintScopedContext returns a frozen
|
|
46
|
+
// object with only the derived, non-secret identity. A downstream caller that
|
|
47
|
+
// tries to forward `ctx.token` finds `undefined`, and JSON.stringify(ctx) can
|
|
48
|
+
// never leak a credential because none was ever stored.
|
|
49
|
+
|
|
50
|
+
const CONTEXT_FIELDS = Object.freeze(['actor', 'audience', 'scopes', 'issuedAt', 'source']);
|
|
51
|
+
|
|
52
|
+
function parseList(raw) {
|
|
53
|
+
if (Array.isArray(raw)) return raw.map((s) => String(s).trim()).filter(Boolean);
|
|
54
|
+
if (typeof raw === 'string') return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the http-remote auth config from env, failing closed. Returns a
|
|
60
|
+
* frozen config when every required field is present; throws
|
|
61
|
+
* HttpAuthConfigError otherwise. The http entrypoint calls this BEFORE binding
|
|
62
|
+
* a socket so a misconfigured server never accepts a single request.
|
|
63
|
+
*
|
|
64
|
+
* Required (no defaults, no skip path):
|
|
65
|
+
* CONSTRUCT_MCP_HTTP_BEARER_SECRET — HMAC secret the inbound token is signed with
|
|
66
|
+
* CONSTRUCT_MCP_HTTP_AUDIENCE — this server's resource indicator (RFC 8707)
|
|
67
|
+
* CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS — comma list of allowed Origin header values
|
|
68
|
+
* Optional:
|
|
69
|
+
* CONSTRUCT_MCP_HTTP_ALLOWED_HOSTS — comma list of allowed Host header values
|
|
70
|
+
* (defaults to localhost forms)
|
|
71
|
+
* CONSTRUCT_MCP_HTTP_HOST / _PORT — bind address (defaults to 127.0.0.1)
|
|
72
|
+
*/
|
|
73
|
+
export function resolveHttpAuthConfig(env = process.env) {
|
|
74
|
+
const missing = [];
|
|
75
|
+
const bearerSecret = env.CONSTRUCT_MCP_HTTP_BEARER_SECRET;
|
|
76
|
+
const audience = env.CONSTRUCT_MCP_HTTP_AUDIENCE;
|
|
77
|
+
const allowedOrigins = parseList(env.CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS);
|
|
78
|
+
|
|
79
|
+
if (!bearerSecret) missing.push('CONSTRUCT_MCP_HTTP_BEARER_SECRET');
|
|
80
|
+
if (!audience) missing.push('CONSTRUCT_MCP_HTTP_AUDIENCE');
|
|
81
|
+
if (allowedOrigins.length === 0) missing.push('CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS');
|
|
82
|
+
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
throw new HttpAuthConfigError(
|
|
85
|
+
`http-remote MCP refuses to start: missing required auth config (${missing.join(', ')}). `
|
|
86
|
+
+ 'HTTP transport has no default identity; there is no skip flag — supply the config or run stdio-local.',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const allowedHosts = parseList(env.CONSTRUCT_MCP_HTTP_ALLOWED_HOSTS);
|
|
91
|
+
const bindHost = env.CONSTRUCT_MCP_HTTP_HOST || '127.0.0.1';
|
|
92
|
+
const bindPort = Number(env.CONSTRUCT_MCP_HTTP_PORT) || 7391;
|
|
93
|
+
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
bearerSecret,
|
|
96
|
+
audience,
|
|
97
|
+
allowedOrigins: Object.freeze(allowedOrigins),
|
|
98
|
+
allowedHosts: Object.freeze(allowedHosts.length > 0 ? allowedHosts : defaultAllowedHosts(bindHost, bindPort)),
|
|
99
|
+
bindHost,
|
|
100
|
+
bindPort,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Localhost-bind is the default; the default host allowlist is exactly the
|
|
105
|
+
// loopback forms for the bind address so a request whose Host names a public
|
|
106
|
+
// interface is rejected unless the operator explicitly widens allowedHosts.
|
|
107
|
+
|
|
108
|
+
function defaultAllowedHosts(bindHost, bindPort) {
|
|
109
|
+
const names = ['localhost', '127.0.0.1', '[::1]', bindHost];
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const n of names) {
|
|
112
|
+
out.push(n);
|
|
113
|
+
out.push(`${n}:${bindPort}`);
|
|
114
|
+
}
|
|
115
|
+
return [...new Set(out)];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validate the inbound Origin against the allowlist. A missing Origin is
|
|
120
|
+
* allowed only for non-browser clients (no Origin header at all); a present
|
|
121
|
+
* Origin that is not on the allowlist is a DNS-rebinding attempt and is
|
|
122
|
+
* rejected. Returns true when the request may proceed.
|
|
123
|
+
*/
|
|
124
|
+
export function validateOrigin(origin, config) {
|
|
125
|
+
if (origin == null || origin === '') return true;
|
|
126
|
+
return config.allowedOrigins.includes(origin);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate the inbound Host header against the allowlist. A rebinding attack
|
|
131
|
+
* resolves a trusted name to a loopback IP but still carries the attacker's
|
|
132
|
+
* Host value; requiring an exact allowlist match closes that path. A missing
|
|
133
|
+
* Host is rejected — a well-formed HTTP/1.1 request always carries one.
|
|
134
|
+
*/
|
|
135
|
+
export function validateHost(host, config) {
|
|
136
|
+
if (host == null || host === '') return false;
|
|
137
|
+
return config.allowedHosts.includes(host);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Constant-time compare so a token-signature check cannot be turned into a
|
|
141
|
+
// timing oracle. Both operands are hex/base64url strings of the same length
|
|
142
|
+
// when the request is well-formed; a length mismatch short-circuits to false
|
|
143
|
+
// without leaking where the difference is.
|
|
144
|
+
|
|
145
|
+
function timingSafeEqualStr(a, b) {
|
|
146
|
+
const ab = Buffer.from(String(a));
|
|
147
|
+
const bb = Buffer.from(String(b));
|
|
148
|
+
if (ab.length !== bb.length) return false;
|
|
149
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function b64urlDecode(seg) {
|
|
153
|
+
return Buffer.from(seg.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function b64urlEncode(buf) {
|
|
157
|
+
return Buffer.from(buf).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Mint an internal HMAC bearer for tests and local tooling: a compact
|
|
162
|
+
* `<payloadB64url>.<sigB64url>` token whose payload is a JSON claims object —
|
|
163
|
+
* the token shape verifyBearer accepts. Not a full JWT — deliberately minimal
|
|
164
|
+
* so the audience/expiry/signature semantics are auditable in one file.
|
|
165
|
+
*/
|
|
166
|
+
export function mintInternalBearer(claims, secret) {
|
|
167
|
+
const payload = b64urlEncode(JSON.stringify(claims));
|
|
168
|
+
const sig = b64urlEncode(crypto.createHmac('sha256', secret).update(payload).digest());
|
|
169
|
+
return `${payload}.${sig}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Verify an inbound bearer against the configured secret AND audience. Order:
|
|
174
|
+
* shape → signature → expiry → audience. Returns the verified claims on
|
|
175
|
+
* success; throws HttpAuthError (401) otherwise. The audience check is the
|
|
176
|
+
* resource-indicator binding: a token minted for a different resource is
|
|
177
|
+
* rejected here, which is what makes a stolen upstream token un-replayable.
|
|
178
|
+
*
|
|
179
|
+
* The returned claims are DATA ONLY. The caller passes them to
|
|
180
|
+
* mintScopedContext; the raw token string is never returned and never stored.
|
|
181
|
+
*/
|
|
182
|
+
export function verifyBearer(rawAuthHeader, config, { now = () => Date.now() } = {}) {
|
|
183
|
+
if (typeof rawAuthHeader !== 'string' || !rawAuthHeader.startsWith('Bearer ')) {
|
|
184
|
+
throw new HttpAuthError('missing or malformed Authorization: Bearer header');
|
|
185
|
+
}
|
|
186
|
+
const token = rawAuthHeader.slice('Bearer '.length).trim();
|
|
187
|
+
const parts = token.split('.');
|
|
188
|
+
if (parts.length !== 2) throw new HttpAuthError('malformed bearer token');
|
|
189
|
+
|
|
190
|
+
const [payloadB64, sigB64] = parts;
|
|
191
|
+
const expectedSig = b64urlEncode(crypto.createHmac('sha256', config.bearerSecret).update(payloadB64).digest());
|
|
192
|
+
if (!timingSafeEqualStr(sigB64, expectedSig)) {
|
|
193
|
+
throw new HttpAuthError('bearer signature does not verify');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let claims;
|
|
197
|
+
try {
|
|
198
|
+
claims = JSON.parse(b64urlDecode(payloadB64));
|
|
199
|
+
} catch {
|
|
200
|
+
throw new HttpAuthError('bearer payload is not valid JSON');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (typeof claims.exp === 'number' && now() >= claims.exp * 1000) {
|
|
204
|
+
throw new HttpAuthError('bearer token expired');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const aud = Array.isArray(claims.aud) ? claims.aud : (claims.aud == null ? [] : [claims.aud]);
|
|
208
|
+
if (!aud.includes(config.audience)) {
|
|
209
|
+
throw new HttpAuthError(
|
|
210
|
+
`bearer audience mismatch: token is for ${JSON.stringify(claims.aud)} but this server is ${JSON.stringify(config.audience)}`,
|
|
211
|
+
{ status: 403 },
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return claims;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Mint the broker's internal actor context from VERIFIED claims. Token
|
|
220
|
+
* passthrough is structurally impossible: the inbound token never reaches this
|
|
221
|
+
* function — only the already-verified claims do — and the returned object has
|
|
222
|
+
* no field capable of holding a credential.
|
|
223
|
+
*
|
|
224
|
+
* The result is frozen and carries exactly CONTEXT_FIELDS. A read of a token
|
|
225
|
+
* off it (ctx.token, ctx.bearer, ctx.authorization) yields undefined.
|
|
226
|
+
*/
|
|
227
|
+
export function mintScopedContext(claims, config, { now = () => Date.now() } = {}) {
|
|
228
|
+
const ctx = {
|
|
229
|
+
actor: claims.sub || claims.actor || 'http-remote-unknown',
|
|
230
|
+
audience: config.audience,
|
|
231
|
+
scopes: Array.isArray(claims.scopes) ? [...claims.scopes] : parseList(claims.scope),
|
|
232
|
+
issuedAt: new Date(now()).toISOString(),
|
|
233
|
+
source: 'http-remote',
|
|
234
|
+
};
|
|
235
|
+
return Object.freeze(ctx);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { CONTEXT_FIELDS };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/transport/http.mjs — http-remote MCP entrypoint (opt-in, fail-closed).
|
|
3
|
+
*
|
|
4
|
+
* The network transport. Every guard the stdio path does not need lives here,
|
|
5
|
+
* applied in order per request before the MCP SDK transport ever sees the body:
|
|
6
|
+
*
|
|
7
|
+
* 1. Host allowlist — DNS-rebinding defense (a rebound name still carries
|
|
8
|
+
* the attacker's Host).
|
|
9
|
+
* 2. Origin allowlist — DNS-rebinding defense for browser callers.
|
|
10
|
+
* 3. Bearer + audience — RFC 8707 resource-indicator binding; a token for a
|
|
11
|
+
* different audience is rejected.
|
|
12
|
+
* 4. Scoped-context mint — the broker actor context is derived from verified
|
|
13
|
+
* claims; the inbound token is dropped and never
|
|
14
|
+
* forwarded downstream (token passthrough impossible).
|
|
15
|
+
*
|
|
16
|
+
* Startup itself fails closed: constructHttpServer resolves the auth config
|
|
17
|
+
* first (resolveHttpAuthConfig throws HttpAuthConfigError on any missing field)
|
|
18
|
+
* and binds to localhost by default. TLS is expected to be terminated by a
|
|
19
|
+
* reverse proxy in front of this server; the localhost bind default keeps the
|
|
20
|
+
* cleartext hop on the loopback interface. See docs follow-up in the bead.
|
|
21
|
+
*
|
|
22
|
+
* authorizeHttpRequest is the pure decision function — headers in, a decision
|
|
23
|
+
* or a thrown HttpAuthError out — so the whole authorization pipeline is
|
|
24
|
+
* unit-testable without opening a socket. startHttpTransport wires it to a
|
|
25
|
+
* real node:http server only at the entrypoint.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import http from 'node:http';
|
|
29
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
30
|
+
import {
|
|
31
|
+
resolveHttpAuthConfig,
|
|
32
|
+
validateOrigin,
|
|
33
|
+
validateHost,
|
|
34
|
+
verifyBearer,
|
|
35
|
+
mintScopedContext,
|
|
36
|
+
HttpAuthError,
|
|
37
|
+
} from './auth.mjs';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve config and refuse to continue if auth is unconfigured. Callers that
|
|
41
|
+
* want to know whether http mode CAN start call this and catch
|
|
42
|
+
* HttpAuthConfigError; the http entrypoint calls it before binding a socket.
|
|
43
|
+
*/
|
|
44
|
+
export function constructHttpServer(env = process.env) {
|
|
45
|
+
return resolveHttpAuthConfig(env);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Decide whether a single inbound HTTP request may reach tool dispatch. Runs
|
|
50
|
+
* the four guards in order and throws HttpAuthError (with a 401/403 status) on
|
|
51
|
+
* the first failure. On success returns { context } — the scoped internal actor
|
|
52
|
+
* context minted from verified claims, with NO token field.
|
|
53
|
+
*
|
|
54
|
+
* @param {object} headers - lowercased request headers (host, origin, authorization)
|
|
55
|
+
* @param {object} config - the frozen config from resolveHttpAuthConfig
|
|
56
|
+
*/
|
|
57
|
+
export function authorizeHttpRequest(headers, config, { now = () => Date.now() } = {}) {
|
|
58
|
+
const host = headers.host ?? headers.Host;
|
|
59
|
+
if (!validateHost(host, config)) {
|
|
60
|
+
throw new HttpAuthError(`host not allowed: ${JSON.stringify(host)}`, { status: 421 });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const origin = headers.origin ?? headers.Origin;
|
|
64
|
+
if (!validateOrigin(origin, config)) {
|
|
65
|
+
throw new HttpAuthError(`origin not allowed: ${JSON.stringify(origin)}`, { status: 403 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const authHeader = headers.authorization ?? headers.Authorization;
|
|
69
|
+
const claims = verifyBearer(authHeader, config, { now });
|
|
70
|
+
|
|
71
|
+
const context = mintScopedContext(claims, config, { now });
|
|
72
|
+
return { context };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The WWW-Authenticate challenge names the resource metadata location per the
|
|
76
|
+
// MCP authorization guidance so a compliant client can discover how to obtain
|
|
77
|
+
// a correctly-scoped token instead of guessing.
|
|
78
|
+
|
|
79
|
+
function writeAuthError(res, err, config) {
|
|
80
|
+
const status = err instanceof HttpAuthError ? err.status : 401;
|
|
81
|
+
res.statusCode = status;
|
|
82
|
+
res.setHeader('Content-Type', 'application/json');
|
|
83
|
+
if (status === 401) {
|
|
84
|
+
res.setHeader('WWW-Authenticate', `Bearer realm="construct-mcp", audience="${config.audience}"`);
|
|
85
|
+
}
|
|
86
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Start a real http-remote MCP server bound to localhost by default. Fails
|
|
91
|
+
* closed at the first line: a missing auth config throws before listen().
|
|
92
|
+
* Returns { server, transport, config } once listening.
|
|
93
|
+
*
|
|
94
|
+
* Each request is authorized by authorizeHttpRequest first; only an authorized
|
|
95
|
+
* request is handed to the SDK transport, and the scoped context (never the
|
|
96
|
+
* token) is attached for the dispatch layer to read.
|
|
97
|
+
*/
|
|
98
|
+
export async function startHttpTransport(mcpServer, { env = process.env } = {}) {
|
|
99
|
+
const config = resolveHttpAuthConfig(env);
|
|
100
|
+
|
|
101
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
102
|
+
await mcpServer.connect(transport);
|
|
103
|
+
|
|
104
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
105
|
+
let decision;
|
|
106
|
+
try {
|
|
107
|
+
decision = authorizeHttpRequest(req.headers, config);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
writeAuthError(res, err, config);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The verified, token-free scoped context rides on the request object for
|
|
114
|
+
// the dispatch layer; the raw Authorization header is stripped here, so
|
|
115
|
+
// nothing downstream can forward the inbound credential.
|
|
116
|
+
|
|
117
|
+
req.constructScopedContext = decision.context;
|
|
118
|
+
delete req.headers.authorization;
|
|
119
|
+
delete req.headers.Authorization;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await transport.handleRequest(req, res);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (!res.headersSent) {
|
|
125
|
+
res.statusCode = 500;
|
|
126
|
+
res.end(JSON.stringify({ error: err?.message || 'internal error' }));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await new Promise((resolveListen) => {
|
|
132
|
+
httpServer.listen(config.bindPort, config.bindHost, resolveListen);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { server: httpServer, transport, config };
|
|
136
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/transport/mode.mjs — MCP transport-mode resolution.
|
|
3
|
+
*
|
|
4
|
+
* Two explicit, non-overlapping modes with separate entrypoints:
|
|
5
|
+
* stdio-local — the default. A local subprocess spoken to over stdin/stdout;
|
|
6
|
+
* identity comes from the local OS session, so no network authn.
|
|
7
|
+
* Behavior is unchanged from the pre-separation server.
|
|
8
|
+
* http-remote — opt-in only, via CONSTRUCT_MCP_TRANSPORT=http (or =http-remote).
|
|
9
|
+
* A network surface, so it REQUIRES authn config and refuses to
|
|
10
|
+
* start without it (see resolveHttpAuthConfig).
|
|
11
|
+
*
|
|
12
|
+
* There is no auto-detection and no implicit http: a server started with no
|
|
13
|
+
* transport env is always stdio-local. Selecting http is a deliberate operator
|
|
14
|
+
* act, and that act carries the fail-closed auth requirement with it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const TRANSPORT_STDIO = 'stdio-local';
|
|
18
|
+
export const TRANSPORT_HTTP = 'http-remote';
|
|
19
|
+
|
|
20
|
+
export const TRANSPORT_MODE_ENV_KEY = 'CONSTRUCT_MCP_TRANSPORT';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the transport mode from env. Anything but an explicit http selection
|
|
24
|
+
* resolves to stdio-local, so a typo or empty value degrades to the safe local
|
|
25
|
+
* mode rather than silently exposing a network surface.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveTransportMode(env = process.env) {
|
|
28
|
+
const raw = String(env?.[TRANSPORT_MODE_ENV_KEY] || '').trim().toLowerCase();
|
|
29
|
+
if (raw === 'http' || raw === 'http-remote' || raw === 'streamable-http') return TRANSPORT_HTTP;
|
|
30
|
+
return TRANSPORT_STDIO;
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/transport/stdio.mjs — stdio-local MCP entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* The default transport, factored out of server.mjs so the http entrypoint can
|
|
5
|
+
* live beside it under one mode selector. Behavior is byte-for-byte the same as
|
|
6
|
+
* the original inline `new StdioServerTransport(); await server.connect(...)`:
|
|
7
|
+
* a local subprocess over stdin/stdout, no network surface, no authn — identity
|
|
8
|
+
* is the local OS session. The server object (tool catalog + dispatch) is
|
|
9
|
+
* passed in already built, so this module never re-registers a handler.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Connect the pre-built MCP server to a stdio transport. Resolves once the
|
|
16
|
+
* transport is connected; the process then stays alive serving requests.
|
|
17
|
+
*/
|
|
18
|
+
export async function startStdioTransport(server) {
|
|
19
|
+
const transport = new StdioServerTransport();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
return transport;
|
|
22
|
+
}
|
package/lib/mcp-catalog.json
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
"name": "Context7",
|
|
7
7
|
"category": "core",
|
|
8
8
|
"description": "Library and framework documentation lookup. Agents use this before writing code against any dependency.",
|
|
9
|
-
"package": "@upstash/context7-mcp@
|
|
9
|
+
"package": "@upstash/context7-mcp@3.2.2",
|
|
10
10
|
"command": "npx",
|
|
11
11
|
"args": [
|
|
12
12
|
"-y",
|
|
13
|
-
"@upstash/context7-mcp@
|
|
13
|
+
"@upstash/context7-mcp@3.2.2"
|
|
14
14
|
],
|
|
15
15
|
"env": {},
|
|
16
16
|
"requiredEnv": [],
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"name": "Sequential Thinking",
|
|
32
32
|
"category": "optional",
|
|
33
33
|
"description": "Structured multi-step reasoning chains. Helps with complex planning and analysis.",
|
|
34
|
-
"package": "@modelcontextprotocol/server-sequential-thinking",
|
|
34
|
+
"package": "@modelcontextprotocol/server-sequential-thinking@2026.7.4",
|
|
35
35
|
"command": "npx",
|
|
36
36
|
"args": [
|
|
37
37
|
"-y",
|
|
38
|
-
"@modelcontextprotocol/server-sequential-thinking"
|
|
38
|
+
"@modelcontextprotocol/server-sequential-thinking@2026.7.4"
|
|
39
39
|
],
|
|
40
40
|
"env": {},
|
|
41
41
|
"requiredEnv": [],
|
package/lib/mcp-manager.mjs
CHANGED
|
@@ -29,8 +29,15 @@ import {
|
|
|
29
29
|
} from './mcp-platform-config.mjs';
|
|
30
30
|
import { getMcpById as getMcpByIdFromRegistry, loadPluginRegistry } from './plugin-registry.mjs';
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Claude Code does not read MCP server definitions from ~/.claude/settings.json
|
|
33
|
+
// (confirmed against code.claude.com/docs/en/mcp's installation-scopes table and
|
|
34
|
+
// code.claude.com/docs/en/managed-mcp, plus live `claude mcp list`/`claude mcp get`
|
|
35
|
+
// repro on v2.1.201 — construct-ranh). User-scope servers live in the top-level
|
|
36
|
+
// `mcpServers` object of ~/.claude.json; settings.json carries only policy keys
|
|
37
|
+
// (allowedMcpServers, deniedMcpServers, etc.) and hooks/permissions, untouched here.
|
|
38
|
+
|
|
39
|
+
function getClaudeUserConfigPath() {
|
|
40
|
+
return join(homedir(), '.claude.json');
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
// VS Code's "MCP: Open User Configuration" edits a per-profile mcp.json keyed
|
|
@@ -196,20 +203,24 @@ function getConfiguredEnvValues(mcp) {
|
|
|
196
203
|
return values;
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
if (!existsSync(
|
|
206
|
+
function loadClaudeUserConfig() {
|
|
207
|
+
const claudeUserConfigPath = getClaudeUserConfigPath();
|
|
208
|
+
if (!existsSync(claudeUserConfigPath)) return {};
|
|
202
209
|
try {
|
|
203
|
-
return JSON.parse(readFileSync(
|
|
210
|
+
return JSON.parse(readFileSync(claudeUserConfigPath, 'utf8'));
|
|
204
211
|
} catch {
|
|
205
212
|
return {};
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
215
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
// Rewrites the whole file: ~/.claude.json also holds unrelated Claude Code
|
|
217
|
+
// runtime state (oauthAccount, projects, numStartups, ...). Only the top-level
|
|
218
|
+
// `mcpServers` key is ever touched here; every other key round-trips untouched.
|
|
219
|
+
|
|
220
|
+
function saveClaudeUserConfig(config) {
|
|
221
|
+
const claudeUserConfigPath = getClaudeUserConfigPath();
|
|
222
|
+
mkdirSync(dirname(claudeUserConfigPath), { recursive: true });
|
|
223
|
+
writeFileSync(claudeUserConfigPath, JSON.stringify(config, null, 2) + '\n');
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
function loadOpenCodeConfig() {
|
|
@@ -226,12 +237,12 @@ function getMcpById(id) {
|
|
|
226
237
|
|
|
227
238
|
function getInstalledMcps() {
|
|
228
239
|
const registry = loadPluginRegistry({ cwd: process.cwd(), homeDir: homedir() });
|
|
229
|
-
const
|
|
240
|
+
const claudeUserConfig = loadClaudeUserConfig();
|
|
230
241
|
const oc = loadOpenCodeConfig();
|
|
231
242
|
const codexConfig = readCodexConfig();
|
|
232
243
|
const codexServers = Array.from(codexConfig.matchAll(/^\[mcp_servers\.("?)([^"\]\n]+)\1\]/gm)).map((match) => match[2]);
|
|
233
244
|
const set = new Set([
|
|
234
|
-
...Object.keys(
|
|
245
|
+
...Object.keys(claudeUserConfig.mcpServers ?? {}),
|
|
235
246
|
...Object.keys(oc.mcp ?? {}),
|
|
236
247
|
...codexServers,
|
|
237
248
|
]);
|
|
@@ -300,7 +311,7 @@ export function cmdMcpList() {
|
|
|
300
311
|
|
|
301
312
|
/**
|
|
302
313
|
* construct mcp add <id>
|
|
303
|
-
* Interactive Guided Wizard: collect required env vars and write to
|
|
314
|
+
* Interactive Guided Wizard: collect required env vars and write to ~/.claude.json.
|
|
304
315
|
*/
|
|
305
316
|
export async function cmdMcpAdd(id) {
|
|
306
317
|
const mcp = getMcpById(id);
|
|
@@ -412,15 +423,15 @@ export async function cmdMcpAdd(id) {
|
|
|
412
423
|
resolvedValues.MEMORY_PORT = process.env.MEMORY_PORT || '8765';
|
|
413
424
|
}
|
|
414
425
|
|
|
415
|
-
// Write to Claude settings
|
|
416
|
-
const
|
|
417
|
-
if (!
|
|
426
|
+
// Write to Claude Code's user-scope MCP config (~/.claude.json), not settings.json
|
|
427
|
+
const claudeUserConfig = loadClaudeUserConfig();
|
|
428
|
+
if (!claudeUserConfig.mcpServers) claudeUserConfig.mcpServers = {};
|
|
418
429
|
if (isManagedOnHost(mcp, 'claude')) {
|
|
419
|
-
|
|
430
|
+
claudeUserConfig.mcpServers[id] = buildClaudeMcpEntry(id, mcp, resolvedValues, { auth: authMode });
|
|
420
431
|
} else {
|
|
421
|
-
delete
|
|
432
|
+
delete claudeUserConfig.mcpServers[id];
|
|
422
433
|
}
|
|
423
|
-
|
|
434
|
+
saveClaudeUserConfig(claudeUserConfig);
|
|
424
435
|
|
|
425
436
|
// Write to OpenCode config
|
|
426
437
|
const oc = loadOpenCodeConfig();
|
|
@@ -484,13 +495,13 @@ export async function cmdMcpAdd(id) {
|
|
|
484
495
|
* construct mcp remove <id>
|
|
485
496
|
*/
|
|
486
497
|
export function cmdMcpRemove(id) {
|
|
487
|
-
const
|
|
498
|
+
const claudeUserConfig = loadClaudeUserConfig();
|
|
488
499
|
const openCodeState = readOpenCodeConfig();
|
|
489
500
|
const oc = openCodeState.config ?? {};
|
|
490
501
|
const openCodeId = getOpenCodeMcpId(id);
|
|
491
502
|
const vscodePaths = getVSCodeUserMcpPaths();
|
|
492
503
|
const cursorPath = getCursorMcpPath();
|
|
493
|
-
const hasClaudeEntry = Boolean(
|
|
504
|
+
const hasClaudeEntry = Boolean(claudeUserConfig.mcpServers?.[id]);
|
|
494
505
|
const hasOpenCodeEntry = Boolean(oc.mcp?.[openCodeId] || oc.mcp?.[id] || (id === 'memory' && oc.mcp?.cass));
|
|
495
506
|
const hasCodexEntry = readCodexConfig().includes(`[mcp_servers.${id}]`) || readCodexConfig().includes(`[mcp_servers.${tomlString(id)}]`);
|
|
496
507
|
const hasVscodeEntry = vscodePaths.some((p) => mcpFileHasEntry(p, 'servers', id));
|
|
@@ -503,8 +514,8 @@ export function cmdMcpRemove(id) {
|
|
|
503
514
|
const mcp = getMcpById(id);
|
|
504
515
|
const name = mcp?.name ?? id;
|
|
505
516
|
|
|
506
|
-
if (
|
|
507
|
-
|
|
517
|
+
if (claudeUserConfig.mcpServers) delete claudeUserConfig.mcpServers[id];
|
|
518
|
+
saveClaudeUserConfig(claudeUserConfig);
|
|
508
519
|
|
|
509
520
|
if (oc.mcp) {
|
|
510
521
|
delete oc.mcp[openCodeId];
|