create-ailk 0.24.0 → 0.26.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/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP HTTP transport for @working-theory/mcp.
|
|
3
|
-
*
|
|
4
|
-
* Wraps the MCP SDK's StreamableHTTPServerTransport in a Fastify server with:
|
|
5
|
-
* - @fastify/cors — open for reads, allowlist-by-config for writes
|
|
6
|
-
* - @fastify/rate-limit — 60/min reads, 10/min writes (env-overridable)
|
|
7
|
-
* - abuse heuristics — UA deny list, honeypot, email validation, daily cap
|
|
8
|
-
*
|
|
9
|
-
* Decision references:
|
|
10
|
-
* Decision 1 — same binary, env-var transport selection (apps/mcp/src/index.ts;
|
|
11
|
-
* the Vercel entry, apps/mcp/src/app.ts, serves HTTP only — #8070)
|
|
12
|
-
* Decision 2 — configurable path, default /mcp, standalone root-mounted
|
|
13
|
-
* Decision 3 — @fastify/rate-limit per-IP token bucket
|
|
14
|
-
* Decision 4 — in-process abuse heuristics for write tools
|
|
15
|
-
* Decision 5 — @fastify/cors open reads, allowlist writes
|
|
16
|
-
*
|
|
17
|
-
* Two modes, chosen by `config.stateless` (#8070 D1; the entries read it from
|
|
18
|
-
* `MCP_HTTP_STATELESS` through `readStatelessEnv`, src/boot.ts):
|
|
19
|
-
* - stateful (the long-running entry's default): an `initialize` creates a
|
|
20
|
-
* StreamableHTTPServerTransport + McpServer pair kept in an in-memory map
|
|
21
|
-
* keyed by its Mcp-Session-Id. Sessions live in ONE process's memory and
|
|
22
|
-
* are not persisted across restarts, so this mode needs a single instance.
|
|
23
|
-
* - stateless (the Vercel entry's default): every POST gets a fresh
|
|
24
|
-
* transport + server and no session ID; GET and DELETE answer 400. Any
|
|
25
|
-
* instance can answer any request.
|
|
26
|
-
*
|
|
27
|
-
* The anonymous `config.path` never reads `Authorization`. Beside it,
|
|
28
|
-
* `${config.path}/workspace` serves the signed-in workspace tools (#3591 D15):
|
|
29
|
-
* POST only and always stateless, each request carrying an agent key that is
|
|
30
|
-
* handed to that request's tools and nothing else — see
|
|
31
|
-
* {@link registerWorkspaceRoute}.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { randomUUID } from "node:crypto";
|
|
35
|
-
import type { IncomingMessage } from "node:http";
|
|
36
|
-
|
|
37
|
-
import cors from "@fastify/cors";
|
|
38
|
-
import rateLimit from "@fastify/rate-limit";
|
|
39
|
-
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
40
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
41
|
-
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
42
|
-
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
43
|
-
import Fastify, {
|
|
44
|
-
type FastifyInstance,
|
|
45
|
-
type FastifyReply,
|
|
46
|
-
type FastifyRequest,
|
|
47
|
-
} from "fastify";
|
|
48
|
-
|
|
49
|
-
import {
|
|
50
|
-
AbuseGuard,
|
|
51
|
-
AbuseReasonCode,
|
|
52
|
-
type AbuseConfig,
|
|
53
|
-
} from "./abuse/index.js";
|
|
54
|
-
import { trustedCallerAddress } from "./request-context.js";
|
|
55
|
-
import { createMcpServer } from "./server.js";
|
|
56
|
-
import { createWorkspaceMcpServer } from "./workspace-server.js";
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* HTTP status for each {@link AbuseReasonCode} an AbuseGuard refusal can
|
|
60
|
-
* carry (#5994).
|
|
61
|
-
*
|
|
62
|
-
* A refusal of the CALLER — a denied User-Agent, an IP that has spent its
|
|
63
|
-
* daily budget — is **403**: the input was fine, the caller is not allowed to
|
|
64
|
-
* make the call right now. A refusal of the INPUT — a filled honeypot, a
|
|
65
|
-
* malformed email — is **422**.
|
|
66
|
-
*
|
|
67
|
-
* Keying on the code rather than on the refusal prose is what this table is
|
|
68
|
-
* for: the split used to be `reason.includes("daily cap")`, so a reworded
|
|
69
|
-
* reason silently demoted a rate-limit refusal to 422 — and `capture_lead`'s
|
|
70
|
-
* wording only matched by accident, because the tool's name begins with
|
|
71
|
-
* "cap". Typing the record over the full union also means a new reason code
|
|
72
|
-
* fails to compile here until it declares its status.
|
|
73
|
-
*/
|
|
74
|
-
export const ABUSE_REFUSAL_STATUS: Record<AbuseReasonCode, 403 | 422> = {
|
|
75
|
-
[AbuseReasonCode.BLOCKED_USER_AGENT]: 403,
|
|
76
|
-
[AbuseReasonCode.DAILY_CAP_REACHED]: 403,
|
|
77
|
-
[AbuseReasonCode.HONEYPOT_FILLED]: 422,
|
|
78
|
-
[AbuseReasonCode.INVALID_EMAIL]: 422,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* MCP tool names that are write operations (subject to stricter CORS + rate
|
|
83
|
-
* limits, and to the AbuseGuard).
|
|
84
|
-
*
|
|
85
|
-
* `start_purchase` joined in #5971: it has a side effect at Stripe and it is
|
|
86
|
-
* worth money to abuse, so it takes the write branch — the origin allow-list,
|
|
87
|
-
* the write CORS posture, and `abuseGuard.validate()` — rather than the read
|
|
88
|
-
* branch's UA check alone.
|
|
89
|
-
*
|
|
90
|
-
* `create_booking` and `submit_flow_step` joined in #8071: each forwards
|
|
91
|
-
* caller-chosen data to the hub — a real booking on the seller's calendar
|
|
92
|
-
* that emails the attendee an invite, and a flow step whose completing call
|
|
93
|
-
* can create a Lead — so they take the same branch, each on its own daily
|
|
94
|
-
* cap. `__tests__/write-tool-guard.test.ts` fails when a catalog tool that
|
|
95
|
-
* sends a non-GET request to the hub is missing from this set.
|
|
96
|
-
*/
|
|
97
|
-
// Any name added here MUST be enforced through the batch-aware path in
|
|
98
|
-
// writeToolCalls() below — a request body can be a JSON-RPC ARRAY, and a
|
|
99
|
-
// per-request (rather than per-message) check lets N batched calls through on
|
|
100
|
-
// one cap unit. See the #5971 security review and the batch regression tests
|
|
101
|
-
// in __tests__/http-transport.test.ts.
|
|
102
|
-
export const WRITE_TOOLS = new Set([
|
|
103
|
-
"capture_lead",
|
|
104
|
-
"subscribe_newsletter",
|
|
105
|
-
"request_callback",
|
|
106
|
-
"start_purchase",
|
|
107
|
-
"create_booking",
|
|
108
|
-
"submit_flow_step",
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
export interface HttpTransportCorsConfig {
|
|
112
|
-
/**
|
|
113
|
-
* Origins allowed to call write tools.
|
|
114
|
-
* Empty array = no cross-origin writes (most restrictive; default).
|
|
115
|
-
* ["*"] = allow all origins for writes (use only in dev/testing).
|
|
116
|
-
*/
|
|
117
|
-
writeAllowedOrigins: string[];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface HttpTransportRateLimits {
|
|
121
|
-
/** Max requests per minute per IP for read tools. Default 60. */
|
|
122
|
-
readPerMin: number;
|
|
123
|
-
/** Max requests per minute per IP for write tools. Default 10. */
|
|
124
|
-
writePerMin: number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface HttpTransportConfig {
|
|
128
|
-
/** URL path to mount the MCP endpoint on. Default "/mcp". */
|
|
129
|
-
path?: string;
|
|
130
|
-
/** Rate limit configuration. */
|
|
131
|
-
rateLimits: HttpTransportRateLimits;
|
|
132
|
-
/** CORS configuration. */
|
|
133
|
-
cors: HttpTransportCorsConfig;
|
|
134
|
-
/** Abuse protection configuration. */
|
|
135
|
-
abuse: AbuseConfig;
|
|
136
|
-
/**
|
|
137
|
-
* When true, use stateless transport mode: no session IDs, every request
|
|
138
|
-
* creates a fresh McpServer + transport. Needed on a host that runs several
|
|
139
|
-
* instances (the Vercel entry), and used by tests.
|
|
140
|
-
* Default: false (stateful). The entries set it from `MCP_HTTP_STATELESS`
|
|
141
|
-
* (`readStatelessEnv`, src/boot.ts — #8070 D1).
|
|
142
|
-
*/
|
|
143
|
-
stateless?: boolean;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** One write-tool invocation found in a request body. */
|
|
147
|
-
interface WriteToolCall {
|
|
148
|
-
toolName: string;
|
|
149
|
-
args: unknown;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Every write-tool invocation carried by a request body, in order.
|
|
154
|
-
*
|
|
155
|
-
* JSON-RPC 2.0 allows a BATCH: an array of messages in one POST. The MCP SDK
|
|
156
|
-
* honours it — `webStandardStreamableHttp.js` maps an array body to N parsed
|
|
157
|
-
* messages and dispatches every one. So a body must be normalised to a LIST of
|
|
158
|
-
* messages before any of them is classified; inspecting `body.method` on what
|
|
159
|
-
* may be an array silently answers "not a write" for the whole batch, and the
|
|
160
|
-
* request then takes the read branch: no write-origin check, and no
|
|
161
|
-
* `AbuseGuard` cap unit consumed by ANY of the N calls it goes on to execute.
|
|
162
|
-
*
|
|
163
|
-
* That is a real bypass of the per-IP daily cap, and for `start_purchase` each
|
|
164
|
-
* bypassed call creates a genuine Stripe Checkout Session on the seller's
|
|
165
|
-
* account. It predates `start_purchase` — `capture_lead`,
|
|
166
|
-
* `subscribe_newsletter` and `request_callback` have carried it since this
|
|
167
|
-
* transport was written — and normalising here closes it for all four at once
|
|
168
|
-
* (#5971 security review, High).
|
|
169
|
-
*
|
|
170
|
-
* Returns one entry PER matching message, so the caller can charge the guard
|
|
171
|
-
* once per call rather than once per request.
|
|
172
|
-
*/
|
|
173
|
-
function writeToolCalls(body: unknown): WriteToolCall[] {
|
|
174
|
-
const messages = Array.isArray(body) ? body : [body];
|
|
175
|
-
const calls: WriteToolCall[] = [];
|
|
176
|
-
|
|
177
|
-
for (const message of messages) {
|
|
178
|
-
if (!message || typeof message !== "object") continue;
|
|
179
|
-
const req = message as Record<string, unknown>;
|
|
180
|
-
if (req["method"] !== "tools/call") continue;
|
|
181
|
-
const params = req["params"] as Record<string, unknown> | undefined;
|
|
182
|
-
if (!params) continue;
|
|
183
|
-
const toolName = params["name"];
|
|
184
|
-
if (typeof toolName !== "string" || !WRITE_TOOLS.has(toolName)) continue;
|
|
185
|
-
calls.push({ toolName, args: params["arguments"] ?? {} });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return calls;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Determine whether an Origin header is in the write allowlist.
|
|
193
|
-
* ["*"] matches any origin. Empty list blocks all cross-origin writes.
|
|
194
|
-
*/
|
|
195
|
-
function isOriginAllowedForWrite(
|
|
196
|
-
origin: string | undefined,
|
|
197
|
-
allowedOrigins: string[],
|
|
198
|
-
): boolean {
|
|
199
|
-
if (!origin) return true; // Same-origin or non-browser clients have no Origin header
|
|
200
|
-
if (allowedOrigins.includes("*")) return true;
|
|
201
|
-
return allowedOrigins.includes(origin);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* The "retry in …" wording of the write bucket's refusal, matching what
|
|
206
|
-
* @fastify/rate-limit's default error says for the same window — `format()`
|
|
207
|
-
* from `@lukeed/ms` over whole seconds, which for a one-minute window is
|
|
208
|
-
* "1 minute" or "N second(s)".
|
|
209
|
-
*/
|
|
210
|
-
function retryIn(seconds: number): string {
|
|
211
|
-
if (seconds >= 60) return "1 minute";
|
|
212
|
-
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* An agent key's shape (#3591 D2): `ailk_agt_` plus 32 random bytes in
|
|
217
|
-
* base64url. apps/mcp checks only this; apps/api is where the key is checked.
|
|
218
|
-
*/
|
|
219
|
-
const AGENT_BEARER = /^Bearer (ailk_agt_[A-Za-z0-9_-]{43})$/;
|
|
220
|
-
|
|
221
|
-
/** The agent key an `Authorization` header carries, or null. */
|
|
222
|
-
function agentKeyOf(header: string | string[] | undefined): string | null {
|
|
223
|
-
if (typeof header !== "string") return null;
|
|
224
|
-
return AGENT_BEARER.exec(header)?.[1] ?? null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* The signed-in workspace surface (#3591 D15) at `path`: POST only, always
|
|
229
|
-
* stateless — a fresh transport and `createWorkspaceMcpServer()` per request,
|
|
230
|
-
* closed when the response ends, and no session map, so GET and DELETE are
|
|
231
|
-
* 405. In order, a POST must:
|
|
232
|
-
*
|
|
233
|
-
* 1. carry `Authorization: Bearer ailk_agt_<43 base64url>` — else 401 in
|
|
234
|
-
* `onRequest`, before the body is read and before any tool runs. A
|
|
235
|
-
* personal token (`tok_…`) is refused here like any other shape;
|
|
236
|
-
* 2. be one JSON-RPC message — a batch (array) body is 400;
|
|
237
|
-
* 3. pass the abuse guard's User-Agent check.
|
|
238
|
-
*
|
|
239
|
-
* Rate limiting is apps/api's per-agent limiter, which every forwarded call
|
|
240
|
-
* meets after the key is verified (D4); the app-wide per-IP limit still
|
|
241
|
-
* applies here. The key is attached as the SDK's `req.auth` for this request
|
|
242
|
-
* only, which the SDK hands each tool as `extra.authInfo`.
|
|
243
|
-
*/
|
|
244
|
-
function registerWorkspaceRoute(
|
|
245
|
-
app: FastifyInstance,
|
|
246
|
-
path: string,
|
|
247
|
-
abuseGuard: AbuseGuard,
|
|
248
|
-
): void {
|
|
249
|
-
const notAllowed = async (_request: FastifyRequest, reply: FastifyReply) =>
|
|
250
|
-
reply
|
|
251
|
-
.code(405)
|
|
252
|
-
.header("allow", "POST")
|
|
253
|
-
.send({ error: "method_not_allowed" });
|
|
254
|
-
app.get(path, notAllowed);
|
|
255
|
-
app.delete(path, notAllowed);
|
|
256
|
-
|
|
257
|
-
app.post(path, {
|
|
258
|
-
onRequest: async (request, reply) => {
|
|
259
|
-
if (agentKeyOf(request.headers["authorization"]) === null) {
|
|
260
|
-
return reply.code(401).send({ error: "unauthorized" });
|
|
261
|
-
}
|
|
262
|
-
},
|
|
263
|
-
preHandler: async (request, reply) => {
|
|
264
|
-
if (Array.isArray(request.body)) {
|
|
265
|
-
return reply.code(400).send({ error: "batch_not_supported" });
|
|
266
|
-
}
|
|
267
|
-
const ua = abuseGuard.validate("__read__", {}, request);
|
|
268
|
-
if (!ua.allowed) {
|
|
269
|
-
return reply.code(403).send({ error: ua.reason });
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
handler: async (request, reply) => {
|
|
273
|
-
const raw = request.raw as IncomingMessage & { auth?: AuthInfo };
|
|
274
|
-
raw.auth = {
|
|
275
|
-
token: agentKeyOf(request.headers["authorization"])!,
|
|
276
|
-
clientId: "ailk-agent",
|
|
277
|
-
scopes: [],
|
|
278
|
-
};
|
|
279
|
-
const transport = new StreamableHTTPServerTransport({
|
|
280
|
-
enableJsonResponse: true,
|
|
281
|
-
});
|
|
282
|
-
const server = createWorkspaceMcpServer();
|
|
283
|
-
try {
|
|
284
|
-
// The cast is the same exactOptionalPropertyTypes one the anonymous
|
|
285
|
-
// route carries.
|
|
286
|
-
await server.connect(transport as unknown as Transport);
|
|
287
|
-
await transport.handleRequest(raw, reply.raw, request.body);
|
|
288
|
-
} catch (err) {
|
|
289
|
-
if (!reply.sent) {
|
|
290
|
-
reply.code(500).send({
|
|
291
|
-
jsonrpc: "2.0",
|
|
292
|
-
error: { code: -32603, message: "Internal server error" },
|
|
293
|
-
id: null,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
throw err;
|
|
297
|
-
} finally {
|
|
298
|
-
await transport.close().catch(() => undefined);
|
|
299
|
-
await server.close().catch(() => undefined);
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Register the MCP HTTP transport at `config.path` on an existing Fastify
|
|
307
|
-
* instance: CORS, the per-IP rate limit, the abuse guard, the anonymous MCP
|
|
308
|
-
* route and the signed-in workspace route (#8070 D2). The caller owns the
|
|
309
|
-
* instance — and so its options, such as the Vercel entry's `trustProxy`
|
|
310
|
-
* (src/serverless-app.ts).
|
|
311
|
-
*
|
|
312
|
-
* @param app - The Fastify instance to register on (not yet listening)
|
|
313
|
-
* @param config - HTTP transport configuration
|
|
314
|
-
*/
|
|
315
|
-
export async function registerHttpTransport(
|
|
316
|
-
app: FastifyInstance,
|
|
317
|
-
config: HttpTransportConfig,
|
|
318
|
-
): Promise<void> {
|
|
319
|
-
const mcpPath = config.path ?? "/mcp";
|
|
320
|
-
const abuseGuard = new AbuseGuard(config.abuse);
|
|
321
|
-
|
|
322
|
-
// Active transports keyed by session ID (stateful mode only).
|
|
323
|
-
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
324
|
-
|
|
325
|
-
// ── CORS ──────────────────────────────────────────────────────────────────
|
|
326
|
-
// Read tools: open (origin: true — reflect the caller's Origin).
|
|
327
|
-
// Write tools: enforced per-request in the preHandler below.
|
|
328
|
-
// We register a permissive global CORS so browser preflight succeeds for
|
|
329
|
-
// reads; write-tool CORS is enforced by the preHandler, not this plugin.
|
|
330
|
-
await app.register(cors, {
|
|
331
|
-
origin: true,
|
|
332
|
-
methods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// ── Rate limiting ─────────────────────────────────────────────────────────
|
|
336
|
-
// Global ceiling — readPerMin per IP, counted on every request (writes
|
|
337
|
-
// included) by the plugin's own onRequest hook.
|
|
338
|
-
await app.register(rateLimit, {
|
|
339
|
-
max: config.rateLimits.readPerMin,
|
|
340
|
-
timeWindow: "1 minute",
|
|
341
|
-
keyGenerator: (request: FastifyRequest) => request.ip,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// The write bucket — writePerMin per IP, charged in the POST preHandler
|
|
345
|
-
// below once per write-tool call, after the body is parsed and classified
|
|
346
|
-
// (#9251). `createRateLimit()` gives it its own in-memory store with the
|
|
347
|
-
// ceiling's key generator and window; it is the plugin's seam for a second
|
|
348
|
-
// limiter on a request the ceiling has already counted (a hook from
|
|
349
|
-
// `app.rateLimit()` would skip such a request as already limited).
|
|
350
|
-
const writeLimit = app.createRateLimit({
|
|
351
|
-
max: config.rateLimits.writePerMin,
|
|
352
|
-
timeWindow: "1 minute",
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// ── MCP route ─────────────────────────────────────────────────────────────
|
|
356
|
-
|
|
357
|
-
// GET — SSE stream for an existing session
|
|
358
|
-
app.get(mcpPath, async (request, reply) => {
|
|
359
|
-
const sessionId = request.headers["mcp-session-id"] as string | undefined;
|
|
360
|
-
if (!sessionId || !transports.has(sessionId)) {
|
|
361
|
-
reply.code(400).send("Invalid or missing session ID");
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const transport = transports.get(sessionId)!;
|
|
365
|
-
await transport.handleRequest(request.raw, reply.raw);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// POST — new session initialize or existing session message
|
|
369
|
-
app.post(mcpPath, {
|
|
370
|
-
preHandler: async (request, reply) => {
|
|
371
|
-
const body = request.body as unknown;
|
|
372
|
-
const origin = request.headers["origin"] as string | undefined;
|
|
373
|
-
|
|
374
|
-
// A batch body counts as a write request when ANY of its messages names
|
|
375
|
-
// a write tool — see writeToolCalls().
|
|
376
|
-
const writeCalls = writeToolCalls(body);
|
|
377
|
-
|
|
378
|
-
// Write-tool CORS enforcement
|
|
379
|
-
if (writeCalls.length > 0) {
|
|
380
|
-
if (!isOriginAllowedForWrite(origin, config.cors.writeAllowedOrigins)) {
|
|
381
|
-
reply
|
|
382
|
-
.code(403)
|
|
383
|
-
.send({ error: "CORS: cross-origin write not allowed" });
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Write-tool rate limit (#9251) — writePerMin per IP, tighter than
|
|
388
|
-
// the ceiling and counting write-tool calls only. Charged once per
|
|
389
|
-
// call, like the abuse guard below, so a batch of N spends N units.
|
|
390
|
-
// The refusal is the one the ceiling emits — the plugin's four
|
|
391
|
-
// headers and Fastify's 429 error body — so a caller sees one
|
|
392
|
-
// rate-limit shape whichever bucket ran out, never a tool-level
|
|
393
|
-
// error (see ABUSE_REFUSAL_STATUS on why that distinction matters).
|
|
394
|
-
for (const _call of writeCalls) {
|
|
395
|
-
const limit = await writeLimit(request);
|
|
396
|
-
if (limit.isAllowed || !limit.isExceeded) continue;
|
|
397
|
-
reply
|
|
398
|
-
.code(429)
|
|
399
|
-
.header("x-ratelimit-limit", limit.max)
|
|
400
|
-
.header("x-ratelimit-remaining", 0)
|
|
401
|
-
.header("x-ratelimit-reset", limit.ttlInSeconds)
|
|
402
|
-
.header("retry-after", limit.ttlInSeconds)
|
|
403
|
-
.send({
|
|
404
|
-
statusCode: 429,
|
|
405
|
-
error: "Too Many Requests",
|
|
406
|
-
message: `Rate limit exceeded, retry in ${retryIn(limit.ttlInSeconds)}`,
|
|
407
|
-
});
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Abuse heuristics — charged ONCE PER CALL, not once per request, so a
|
|
412
|
-
// batch of N cannot spend one cap unit and execute N times. The whole
|
|
413
|
-
// request is refused on the first message that fails, before the SDK
|
|
414
|
-
// dispatches any of them.
|
|
415
|
-
for (const { toolName, args } of writeCalls) {
|
|
416
|
-
const abuseResult = abuseGuard.validate(toolName, args, request);
|
|
417
|
-
if (!abuseResult.allowed) {
|
|
418
|
-
reply
|
|
419
|
-
.code(ABUSE_REFUSAL_STATUS[abuseResult.code])
|
|
420
|
-
.send({ error: abuseResult.reason });
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
// Read-tool or protocol message: check UA deny list only
|
|
426
|
-
const abuseGuardResult = abuseGuard.validate("__read__", {}, request);
|
|
427
|
-
if (!abuseGuardResult.allowed) {
|
|
428
|
-
reply.code(403).send({ error: abuseGuardResult.reason });
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
},
|
|
433
|
-
handler: async (request, reply) =>
|
|
434
|
-
// The lead-write tools read this address (#8073) through
|
|
435
|
-
// `visitorForwardHeadersForThisCall()` while the SDK dispatches their
|
|
436
|
-
// call, somewhere inside `transport.handleRequest()` below — the whole
|
|
437
|
-
// handler body runs inside the store so every path reaches it.
|
|
438
|
-
trustedCallerAddress.run(request.ip, async () => {
|
|
439
|
-
const body = request.body as unknown;
|
|
440
|
-
const sessionId = request.headers["mcp-session-id"] as
|
|
441
|
-
| string
|
|
442
|
-
| undefined;
|
|
443
|
-
|
|
444
|
-
try {
|
|
445
|
-
let transport: StreamableHTTPServerTransport;
|
|
446
|
-
|
|
447
|
-
if (config.stateless) {
|
|
448
|
-
// Stateless mode: each request gets a fresh transport + server.
|
|
449
|
-
// enableJsonResponse: true ensures plain JSON responses (no SSE).
|
|
450
|
-
// sessionIdGenerator is intentionally omitted (stateless = no sessions).
|
|
451
|
-
transport = new StreamableHTTPServerTransport({
|
|
452
|
-
enableJsonResponse: true,
|
|
453
|
-
});
|
|
454
|
-
const server = createMcpServer();
|
|
455
|
-
// Cast needed: SDK's StreamableHTTPServerTransport.onclose is typed
|
|
456
|
-
// as `(() => void) | undefined`, which conflicts with Transport.onclose
|
|
457
|
-
// under exactOptionalPropertyTypes: true. The runtime behaviour is
|
|
458
|
-
// identical; the cast is safe.
|
|
459
|
-
await server.connect(transport as unknown as Transport);
|
|
460
|
-
try {
|
|
461
|
-
await transport.handleRequest(request.raw, reply.raw, body);
|
|
462
|
-
} finally {
|
|
463
|
-
// Close the per-request transport + server to release internal
|
|
464
|
-
// handles. Without this, the MCP SDK's transport leaves open
|
|
465
|
-
// sockets that surface as `socket.destroySoon is not a function`
|
|
466
|
-
// during Jest teardown on Node 20.x (@hono/node-server compat).
|
|
467
|
-
await transport.close().catch(() => undefined);
|
|
468
|
-
await server.close().catch(() => undefined);
|
|
469
|
-
}
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (sessionId && transports.has(sessionId)) {
|
|
474
|
-
// Existing session — reuse transport
|
|
475
|
-
transport = transports.get(sessionId)!;
|
|
476
|
-
} else if (!sessionId && isInitializeRequest(body)) {
|
|
477
|
-
// New initialization — create a stateful transport + fresh server
|
|
478
|
-
transport = new StreamableHTTPServerTransport({
|
|
479
|
-
sessionIdGenerator: () => randomUUID(),
|
|
480
|
-
onsessioninitialized: (sid) => {
|
|
481
|
-
transports.set(sid, transport);
|
|
482
|
-
},
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
transport.onclose = () => {
|
|
486
|
-
const sid = transport.sessionId;
|
|
487
|
-
if (sid) transports.delete(sid);
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const server = createMcpServer();
|
|
491
|
-
// Cast needed: SDK's StreamableHTTPServerTransport.onclose is typed
|
|
492
|
-
// as `(() => void) | undefined`, which conflicts with Transport.onclose
|
|
493
|
-
// under exactOptionalPropertyTypes: true. The runtime behaviour is
|
|
494
|
-
// identical; the cast is safe.
|
|
495
|
-
await server.connect(transport as unknown as Transport);
|
|
496
|
-
} else {
|
|
497
|
-
reply.code(400).send({
|
|
498
|
-
jsonrpc: "2.0",
|
|
499
|
-
error: {
|
|
500
|
-
code: -32000,
|
|
501
|
-
message: "Bad Request: no valid session",
|
|
502
|
-
},
|
|
503
|
-
id: null,
|
|
504
|
-
});
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
await transport.handleRequest(request.raw, reply.raw, body);
|
|
509
|
-
} catch (err) {
|
|
510
|
-
if (!reply.sent) {
|
|
511
|
-
reply.code(500).send({
|
|
512
|
-
jsonrpc: "2.0",
|
|
513
|
-
error: { code: -32603, message: "Internal server error" },
|
|
514
|
-
id: null,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
throw err;
|
|
518
|
-
}
|
|
519
|
-
}),
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
// DELETE — session termination
|
|
523
|
-
app.delete(mcpPath, async (request, reply) => {
|
|
524
|
-
const sessionId = request.headers["mcp-session-id"] as string | undefined;
|
|
525
|
-
if (!sessionId || !transports.has(sessionId)) {
|
|
526
|
-
reply.code(400).send("Invalid or missing session ID");
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
const transport = transports.get(sessionId)!;
|
|
530
|
-
await transport.handleRequest(request.raw, reply.raw);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
// ── Signed-in workspace route (#3591 D15) ─────────────────────────────────
|
|
534
|
-
registerWorkspaceRoute(app, `${mcpPath}/workspace`, abuseGuard);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Build and return a configured Fastify instance that exposes the MCP HTTP
|
|
539
|
-
* transport at `config.path`. The instance is not yet listening — callers
|
|
540
|
-
* call `.listen()` or `.inject()` (tests). Sets no `trustProxy`, so
|
|
541
|
-
* `request.ip` is the socket's address.
|
|
542
|
-
*
|
|
543
|
-
* @param config - HTTP transport configuration
|
|
544
|
-
* @returns Configured Fastify instance (ready to listen or inject)
|
|
545
|
-
*/
|
|
546
|
-
export async function buildHttpTransportApp(
|
|
547
|
-
config: HttpTransportConfig,
|
|
548
|
-
): Promise<FastifyInstance> {
|
|
549
|
-
const app = Fastify({ logger: false });
|
|
550
|
-
await registerHttpTransport(app, config);
|
|
551
|
-
return app;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Start the HTTP transport bound to the configured port.
|
|
556
|
-
* Returns the listening Fastify instance.
|
|
557
|
-
*/
|
|
558
|
-
export async function startHttpTransport(
|
|
559
|
-
config: HttpTransportConfig & { port: number },
|
|
560
|
-
): Promise<FastifyInstance> {
|
|
561
|
-
const app = await buildHttpTransportApp(config);
|
|
562
|
-
await app.listen({ port: config.port, host: "0.0.0.0" });
|
|
563
|
-
return app;
|
|
564
|
-
}
|