Skip to content

Paid-launch readiness: Freemius licensing, packaging, build & CI#332

Open
mumega848 wants to merge 27 commits into
mainfrom
claude/keen-heisenberg-WP5hf
Open

Paid-launch readiness: Freemius licensing, packaging, build & CI#332
mumega848 wants to merge 27 commits into
mainfrom
claude/keen-heisenberg-WP5hf

Conversation

@mumega848
Copy link
Copy Markdown

@mumega848 mumega848 commented May 25, 2026

What this does

This branch started as the Freemius licensing migration and grew into the paid-launch readiness work for MCPWP: licensing single-source-of-truth, free/paid packaging, positioning/copy accuracy, build + release pipeline, naming cleanup, and CI fixes.

1. Licensing — Freemius as single source of truth

  • Removes the legacy local license store (~400 lines: activate()/deactivate(), trial tracking, expiration, Lemon Squeezy calls).
  • New canonical get_license_info() accessor returns one self-validating snapshot (plan, is_pro, is_paying, is_agency) used by REST, MCP, CLI, and admin — guaranteeing is_pro=false ⇒ plan=unlicensed and is_pro=true ⇒ plan never free/unlicensed.
  • Keeps two dev overrides: SPAI_WPORG_BUILD (force free) and MUMCP_PRO (force pro).

2. Tool-category governance

  • Enforces admin-disabled tool categories at execution (tools/call), not just discovery — closing a gap where a known tool name could still be invoked.
  • Fix from review: four block tools (wp_parse_blocks, wp_serialize_blocks, wp_validate_blocks, wp_get_block_design_system) and two auto menu-item tools were registered but missing from the category map, so they defaulted to site. Mapped block tools → gutenberg, menu tools → site so the gate behaves correctly in both directions.

3. Packaging & release

  • build.sh is now the single source of truth for both zips; the WP.org build strips includes/pro/ and the self-hosted updater (WordPress.org handles updates; the updater load is guarded by file_exists). Fixed a stale-zip accumulation bug (zip -r appends).
  • release.yml now calls build.sh instead of duplicating zip logic — this also fixes the shipped zip's top folder being named site-pilot-ai-temp instead of site-pilot-ai.
  • Added secret-gated, no-op-by-default publish steps: Freemius upload (GTM: replace site-level update override with durable Freemius release flow #324) and WordPress.org SVN deploy (GTM website: finish AppSumo/Product Hunt launch readiness pass #325). Neither runs until the corresponding repository secrets are set.

4. Positioning, docs & naming

5. CI fix

  • validate.yml's metadata check hardcoded the old spai-updates/... updater URL, which no longer matched version.json's mcp-updates/mumega-mcp-latest.zip — this was failing the PHP 8.2 job (and fail-fast cancelled the rest) on every PR. Updated to the canonical URL.

Review notes / follow-ups (not blocking, for the author to confirm)

  • Approvals REST gating: the approval MCP tools are now Pro-gated, but /wp-json/spai/v1/approvals/* uses verify_api_key only (no license gate). Confirm whether approvals via REST should also be license-gated (and whether Spai_Approvals even loads on free).
  • Free→Pro tool split (approvals, SEO, webhooks, site-state, etc.) is intentional, but existing free-tier users / agent playbooks that reference these tools will lose them on update — consider a migration notice.
  • Optional perf: is_pro_active() calls get_license_info() ~6×/tools/call; a per-request memo would remove redundant Freemius lookups.
  • GitHub repo "About" description still says "239 MCP tools … 100% free" — needs updating in repo settings (not in-repo).

How to test

  1. Unit tests: cd site-pilot-ai && vendor/bin/phpunit --bootstrap tests/bootstrap.php tests (59 tests green).
  2. Build: ./build.sh → verify dist/site-pilot-ai-wporg.zip excludes includes/pro/ and the updater, dist/site-pilot-ai.zip includes both, both with a site-pilot-ai/ root folder.
  3. Entitlement consistency: GET /site-pilot-ai/v1/site/info reports consistent plan/is_pro for each Freemius state; MUMCP_PRO and SPAI_WPORG_BUILD overrides behave as documented.
  4. Category gate: disable a category in WP Admin → MCPWP → Tools; verify tools/call for that category returns -32003, and that disabling gutenberg now blocks wp_parse_blocks.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8

claude added 24 commits May 25, 2026 22:52
Establish a single source of truth for plan and pro_active so MCP/REST/CLI
can no longer report a free plan while pro is active.

- Add Spai_License::get_license_info() canonical accessor that returns a
  self-validated, consistent {plan, is_pro, is_paying, is_agency} snapshot.
- Derive capabilities plan/pro_active in Spai_Core from that accessor after
  the spai_site_capabilities filter, and use it for site-info + cache match.
- Stop Spai_Pro_Bootstrap from hardcoding plan/pro_active; keep only the
  genuine pro-module-only flags (learnpress, tp_events).
- Align REST MCP is_pro_active() and REST site onboard to the canonical value.
- Make the CLI --test plan display read the unified value without contradictory
  defaulting.
- Add LicenseCapabilitiesTest covering the unlicensed/trial/pro/agency/expired/
  WP.org matrix and extend the test bootstrap with the needed stubs.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
The Tools tab lets admins disable whole MCP tool categories via the
spai_disabled_tool_categories option. This was applied at discovery time
in get_all_tools() (hiding tools from tools/list) but handle_tools_call()
never consulted it, so a tool in an admin-disabled category was hidden
from listings yet still fully executable via a direct tools/call.

Add a global gate in handle_tools_call() (after the unknown-tool check,
before the per-key role check) that rejects any tool whose category is in
the disabled list with a -32003 error. The gate applies regardless of the
API key's role, keeping discovery and execution consistent.

Add DisabledToolCategoriesTest regression coverage for the full cycle.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Remove the vestigial Lemon Squeezy integration, the dead local
license-key store (spai_pro_license), and the dead local trial
mechanism (spai_trial_started) from Spai_License so entitlement is
resolved entirely through Freemius (plus the SPAI_WPORG_BUILD and
MUMCP_PRO build/dev overrides).

- is_pro/is_paying/get_plan/is_trial_active now read only from
  Freemius via spai_get_fs_instance(); get_license_info() keeps its
  #319 shape and consistency guarantees.
- Drop activate()/deactivate() (Lemon Squeezy API calls),
  get_license_data/key/expiration/is_expired/site_limit, start_trial,
  get_trial_days_remaining, get_info() (hardcoded lemon_squeezy),
  and the OPTION_KEY/TRIAL_KEY/TRIAL_DAYS constants and $license_data.
- Rework LicenseCapabilitiesTest to simulate every state by stubbing
  the Freemius instance instead of the removed options; add a
  configurable Freemius double to the test bootstrap.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Relocate 25 MCP tools from the free registry to the Pro registry so they
are only exposed when an entitlement is active. The Pro registry is merged
in get_all_tools/categories/map only when is_pro_active(), so moving the
schema, route map, and category for each tool hides it from non-Pro users.

Moved (free -> pro):
- SEO intelligence (11): readiness, structured data, media SEO, site audit,
  content quality, issues, autofix plan, search-performance import/trends,
  WooCommerce SEO report, content coherence report.
- Event store / outbound webhooks (9): full 'webhooks' category, including
  the webhook CRUD/test/logs tools and event listing/schema tools.
- Approval / rollback (4): wp_list_approvals, wp_get_approval,
  wp_apply_approval, wp_rollback_approval. wp_approve_request and
  wp_reject_request intentionally stay free (not named, no *approval* match).
- Site-state snapshot (1): wp_get_site_state only.

Underlying REST routes are unchanged and still live on free-tier
controllers, so they resolve for Pro clients via the merged map.

Add McpProGatingTest covering absence for non-pro and presence for pro,
plus an is_multisite() bootstrap stub needed to exercise Pro::get_tools().

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
(cherry picked from commit 63cf3ee38d6c8ed9809ad4361979adfab78f97cc)
Complete the agent-safety approval-system gating: the approve/reject
actions on pending mutation requests now live in the Pro registry with
the rest of the approval/rollback tools, so a free user can no longer
act on pending requests.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
- Pro::get_tools() appended the two Google Indexing tools to the wrong
  local array ($tools[] vs $pro_tools[]), so they were never returned
  even to Pro users. Corrected so wp_submit_to_google_index and
  wp_google_index_status are now registered.
- handle_tools_call() now returns an actionable -32003 "requires a Pro
  license" error with an upgrade URL when a non-Pro site calls a
  Pro-gated tool, instead of a generic "Unknown tool" response (#327).

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Fix stale assumptions in the V3 plan and product roadmap: billing is
Freemius (not Stripe; Stripe only for a future hosted dashboard), tool
count is ~250+ (not "239"; exact count tracked in #322), and unify the
operator-polish and agency-SaaS theses around the agent-safety +
SEO-intelligence paid moat.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Clean up the $supported_themes array in the active Pro themes handler:
remove duplicate 'flavor' array keys and the junk 'flavor'/'flavflavor'
placeholder entries, and correct the OceanWP option_key from the bogus
'theme_mods_flavor' to 'ocean_options'.

The remaining entries (astra, generatepress, kadence, oceanwp) are now
internally consistent with no duplicate keys; astra/generatepress/kadence
dispatch to their existing get_/update_<slug>_settings handlers, and
oceanwp falls back to the generic theme_mods handlers.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Replace stale free-only and "239 tools" claims across public-facing copy
to match the Freemius free/Operator/Agency model.

- #317/#318: frame WP.org build as the free core; paid tiers unlock the
  agent-safety and SEO-intelligence layers.
- #322: replace "239"/"Up to 239" with verified counts (119 free + 137
  Pro = 250+); exact accounting tracked in #322.
- #331: lead value prop with outcomes (approval-safe edits + rollback,
  SEO intelligence, content coherence, site-state, Control Room).
- #330: add "Works with any agent runtime" sections (MCP-neutral;
  supplier to Bedrock AgentCore, Antigravity, Managed Agents, etc).

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
The shipped product is the single-distribution Freemius build: build.sh
and scripts/release_freemius.sh package only site-pilot-ai/ (paid build
keeps includes/pro/, WP.org build strips it). The standalone
site-pilot-ai-pro addon plugin used the older free-plugin + pro-addon
model, is referenced by no build script, CI workflow, or the active
plugin, and carried a divergent (and corrupted) copy of the Pro code.
Removing it eliminates the duplicate source of truth.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Reflect the single-distribution model: Pro Elementor endpoints load from
includes/pro/ when licensed, not from the retired standalone plugin.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
- Remove the abandoned plugin/wp-ai-operator.php prototype and the empty
  stray ziQ9IgA7 file (both were already excluded from builds).
- build.sh: strip includes/class-spai-updater.php from the WP.org package
  (WP.org handles updates; the updater load is guarded by file_exists, so
  its absence is safe). The paid build keeps it.
- build.sh: remove stale dist zips before packaging, since `zip -r` appends
  to an existing archive and was retaining removed files across rebuilds.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
- Replace the duplicated inline zip logic with a single build.sh call, so
  the workflow and local builds share one source of truth. This also fixes
  the shipped zips' top folder being named site-pilot-ai-temp instead of
  site-pilot-ai, and inherits the WP.org updater strip.
- Add secret-gated, no-op-by-default steps to upload the paid build to
  Freemius (#324) and deploy the WP.org build to plugin SVN (#325). Neither
  runs until the corresponding repository secrets are set.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
The release-metadata consistency check hardcoded the old
spai-updates/mumega-site-pilot-ai-latest.zip URL, which no longer matches
version.json's current mcp-updates/mumega-mcp-latest.zip (rebranded to
MCPWP, canonical path per #323). This was failing the PHP 8.2 validation
job (and fail-fast cancelled the sibling PHP jobs) on every PR.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
… notes (#329)

Replace hardcoded "239 tools" with accurate "~119 free / 250+ with Pro integrations"
framing in docs/blog-launch-post.md (5 occurrences) and docs/demo-script.md (1 occurrence).
Also corrects the demo-script blueprint count from 24 to 14.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
(cherry picked from commit e859bc6f69527a6b69d29d86e69547b61be6510d)
The blueprint catalog (Spai_Page_Builder::get_blueprint_catalog) defines
exactly 24 blueprints, matching the README. A prior edit had set 14.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
…efs #322)

- mcp-server/src/index.ts: use MCPWP brand in --version and --help output;
  replace dead Mumega-com/mcp-for-wp repo URL with canonical mcpwp.net in docs
- mcp-server/server.json, mcp-server/package.json: update repo URL to
  github.com/mumega-com/mcpwp
- mcp-server/README.md: update repo URLs to mumega-com/mcpwp; fix tool
  count from stale 200+ to 250+
- README.md: update repo URLs to mumega-com/mcpwp; fix tool count from
  stale 239 to 250+ in badge, comparison table, tools section, and roadmap

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
(cherry picked from commit e9a3635c405f2a8ea0b6aa1710620f3dd77170d8)
Re-applies the GitHub URL rename from the naming cleanup that was dropped
when resolving the README merge (the tool-count fixes were already in).

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Review found four block tools (wp_parse_blocks, wp_serialize_blocks,
wp_validate_blocks, wp_get_block_design_system) and two auto menu-item
tools were registered but absent from get_tool_categories(), so they fell
back to the gate's default 'site' category. With the new execution-time
category enforcement this meant disabling 'gutenberg' failed to block the
block tools, while disabling 'site' wrongly blocked them. Map the block
tools to 'gutenberg' and the menu tools to 'site' (their correct homes).

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
@mumega848 mumega848 changed the title Migrate licensing to Freemius as single source of truth Paid-launch readiness: Freemius licensing, packaging, build & CI May 26, 2026
claude added 3 commits May 26, 2026 04:39
The oceanwp slug was listed as supported but had no settings handler, so
get_settings() silently fell through to generic theme_mods while
detect_theme() still advertised settings_type=custom_option. Add real
get_/update_oceanwp_settings() that read/write the ocean_options option
(OceanWP's actual storage).

Also correct two misleading metadata claims: kadence's option_key was
theme_mods_kadence but get_kadence_settings() reads theme_mods, and
get_settings_type() reported every supported theme as custom_option even
though kadence is theme_mods-backed. Drive settings_type from a per-theme
storage field so the reported type matches what each handler actually reads.

The duplicate site-pilot-ai-pro copy of this class is already removed on
this branch, so a single canonical handler remains.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Per the finish-or-demote decision on #329: Forms, Events (ThimPress), and
Multilingual are narrow/partial and are not core Pro value, so mark them
experimental and stop presenting them as paid features. LearnPress stays a
full Pro integration (already documented as Full CRUD).

- COMPATIBILITY.md: add a Pro-value-vs-experimental note; mark Forms
  read-only/experimental; add honest experimental entries for Multilingual
  (writes WPML/Polylang only, TranslatePress detection-only) and Events
  (thin tp_event CRUD, no ticketing/registration).
- API.md: correct the false "Full multilingual ... TranslatePress" claim,
  relabel Forms and Multilanguage as experimental, fix the TOC anchor.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
CONTRIBUTING still used the old "mumcp" name and the stale "239 tools"
count. Align with the README's MCPWP brand and 250+ count.

https://claude.ai/code/session_017aUcY2VsN82yQ2AocAuHD8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants