Skip to content

Add a no-login reactivation page for blocked calls, A/B vs pricing - #1574

Merged
helpcodeai merged 3 commits into
mainfrom
feat/reactivation-link
Nov 7, 2026
Merged

helpcodeai merged 3 commits into
mainfrom
feat/reactivation-link

Conversation

@helpcodeai

@helpcodeai helpcodeai commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When an ended Cloud trial blocks a tool call, the AI client shows a link to reactivate the workspace. Today that link is the public pricing page: an anonymous checkout with an extra plan-choice step. This PR adds a no-login reactivation page and A/B-tests it against the pricing page, 50/50 per workspace.

  • Link: https://cloud.anythingmcp.com/reactivate/<token>.
  • Token: AES-256-GCM, valid 30 days, key derived from ENCRYPTION_KEY (HKDF, info anythingmcp/reactivate-link/v1). It holds only the workspace id, the calling user id and an expiry, encrypted, and no address. Holding it lets you see the workspace name and its plans, and pay for that workspace. Nothing else.
  • Page: shows the workspace, the plan picker (preselected from usage), "what happens next" and a trust panel. It opens Stripe Checkout for that workspace without a sign-in. Back from Stripe (?paid=1) it polls until the workspace is active.
    • Languages: the website's seven (en, de, it, es, zh, ja, ru), in copy.ts. It follows the amcp_locale cookie, then the browser's languages, else English.
    • AI client: the copy names the client whose tool call was blocked last (metadata.client on trial_expired_call_blocked), else the first client the workspace connected, else "your AI". Only that client's directory listing is shown; both are shown when the client is unknown.
    • Trust: the live workspace count and last month's AI tool calls from /api/public/stats, plus the sign-up page's badges (EU hosting, GDPR · DPA, AES-256-GCM, open source) and a "Works with" row.
    • Header: "Sign in" (/login?redirect=/), or "Open your workspace" when a session exists.
  • Binding: the paid licence carries organizationId, and the unclaimed-licence sweep binds it in about 2–3 minutes. The sweep runs every minute and binds licences that are at least 2 minutes old.
  • Tool text: identical in both arms; only the URL differs.

Changes

Backend:

  • license/reactivation-token.ts: create and read the token.
  • license/reactivation.controller.ts: GET /api/license/reactivate/:token returns state, workspace name, trial end, AI client and usage. POST /api/license/reactivate/:token/checkout takes {plan, billingPeriod} and returns {url}.
    • Cloud only; self-hosted gets 404.
    • Throttled per IP: 30/min for GET, 10/min for POST.
    • 409 when the workspace already pays or has a failed payment pending. 404 for an invalid or expired token.
    • Checkout e-mail: the token's user if still an active ADMIN, else the first active ADMIN. Source mcp_reactivate.
  • license-checkout.ts:
    • reactivationLinkVariant (sha256 reactivate-link-v1:<org>, 50/50) and trialReactivationLink.
    • mcp_reactivate added to CHECKOUT_SOURCES.
  • dynamic-mcp-tools.ts: uses the arm's link. trial_expired_call_blocked records metadata.variant.
  • New event reactivate_page_viewed, at most once per workspace per UTC day.
  • EmailVerifiedGuard allowlist: /api/license/reactivate/.
  • license-wall.service.ts: connectedClient() extracted.

Frontend:

  • app/reactivate/[token] page and layout (no-referrer, noindex).
  • Route made public in proxy.ts, auth-context, license-wall and onboarding-redirect.
  • Page copy in seven languages in app/reactivate/[token]/copy.ts. The dashboard has three UI languages; this page is reached from a chat, so it follows the website's seven. It has its own plan chooser, because the dashboard's PlanPicker is English only.
  • reactivation API client; mcp_reactivate added to checkout-source.ts.

How to read the test

For each arm, divide the purchases by the number of blocked workspaces. Count the blocked workspaces from trial_expired_call_blocked grouped by metadata.variant. Count the purchases from Stripe checkouts with checkout_source mcp_reactivate (page arm) or mcp_blocked (pricing arm).

Deployment modes

  • Cloud: as above.
  • Self-hosted: both endpoints answer 404, and the blocked-call path is Cloud-only. No change.

Type

  • New feature

Testing

  • Added/updated unit tests

Commands (worktree from origin/main after npm ci and prisma generate):

  • packages/backend:
    • npx jest --maxWorkers=4 src/license src/mcp-server src/auth/email-verified src/audit/product-event: 63 suites passed, 2 skipped; 1,056 tests passed, 5 skipped.
    • npx tsc --noEmit -p tsconfig.json: clean.
    • npx eslint on the changed files (no --fix): clean.
  • packages/frontend:
    • npx tsc --noEmit -p tsconfig.json: clean.
    • npx eslint on the new page, copy, layout, proxy and spec: clean.
    • npx playwright test tests/e2e/reactivate-link.spec.ts tests/e2e/card-trial.spec.ts tests/e2e/auth.spec.ts tests/e2e/i18n.spec.ts: 112 passed.
      • reactivate-link.spec.ts covers the signed-out view and checkout, the header sign-in, Claude / ChatGPT / unknown-client wording and directories, the six non-English browser languages, Team preselection, an expired link, and polling after payment.

How the paid licence reaches the workspace, checked in the site code (anythingmcp-website main):

  1. checkout/start copies the intent's organizationId into the Stripe session metadata.
  2. The webhook creates the licence with that organizationId.
  3. /api/license/unclaimed lists active Cloud licences without an instanceId once they are 2 minutes old.
  4. The Cloud's sweep binds a licence that carries an organizationId to that workspace.

No site change is needed. The pricing-page and wall checkouts are unchanged. Still to confirm on the first real purchase from the page arm: the binder's log line.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • This PR has a descriptive title

When an ended trial blocks a tool call, half of the workspaces now get a
link to /reactivate/<token> instead of the pricing page. The token (AES-GCM,
30 days, key from ENCRYPTION_KEY) names the workspace; the page shows its
plans and opens Stripe Checkout for it without a sign-in, and the paid
licence binds through the unclaimed-licence sweep. The arm is recorded on
trial_expired_call_blocked and the checkout source is mcp_reactivate.
…sign-in

The page picks its language among the website's seven (amcp_locale cookie,
then the browser), names the AI client whose call was blocked (else the
first one the workspace connected) and shows only that client's directory,
adds the live workspace and monthly tool-call numbers with the sign-up
page's badges, and offers Sign in / Open your workspace in the header.
@helpcodeai
helpcodeai enabled auto-merge (squash) November 7, 2026 20:19
@helpcodeai
helpcodeai merged commit 989b275 into main Nov 7, 2026
16 checks passed
@helpcodeai
helpcodeai deleted the feat/reactivation-link branch November 7, 2026 20:37
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant