Skip to content

Add preview environments: CoW provisioner and preview tools - #79

Open
christianc1 wants to merge 1 commit into
mainfrom
feature/preview-environments
Open

christianc1 wants to merge 1 commit into
mainfrom
feature/preview-environments

Conversation

@christianc1

@christianc1 christianc1 commented Aug 21, 2026

Copy link
Copy Markdown
Member

What

Three new MCP tools that give agents disposable, isolated clones of a Local site:

  • preview_start — clones a site (optional siteId, defaults to the endpoint's site) into a preview with its own database, PHP/MySQL processes, domain, and logs, and returns the preview's own MCP endpoint URL. Callers pass a purpose label ("Polylang Fix", "player-v3") and the site is named <Parent Name> - <label> so humans can identify previews in Local's sidebar.
  • preview_list — lists previews with parent site id and MCP URL, so orphans stay visible.
  • preview_destroy — deletes a preview (files to trash); hard-refuses any site not created by preview_start.

Why

Two agent sessions sharing one Local site trample each other: concurrent WP-CLI writes, mixed error logs, one checked-out branch. Previews give each session (e.g. each git worktree) its own environment.

Why not Local's own CloneSite service

Live testing on a real 1.5G multisite showed cloneSite: copies the entire site dir including node_modules/.git, resolves undefined on failure while work continues in background, pops a blocking native error dialog headless callers can't dismiss, and its hardcoded --skip-plugins search-replace dies on sites whose mu-plugins guard on plugin presence. The new provisioner mirrors CloneSite's sequence using the same cradle services, but owns each step.

Key implementation points

  • Copy-on-write copies (cp -Rc clonefile) on APFS for both the file tree and the MySQL data dir — a 1.5G multisite clones in ~76s wall clock with megabytes of physical disk cost. Non-APFS/Windows falls back to a filtered copy that skips node_modules/.git. (src/helpers/fast-copy.ts)
  • Domain rewrite that survives real sites: one bare-domain search-replace pass (subsumes protocol variants; single pass because the tool scans every table per pass) executed with mu-plugins neutralized via --exec=define("WPMU_PLUGIN_DIR", …) — required because some mu-plugins call WP_CLI::error() at registration when a dependency plugin isn't loaded. Plus a literal domain rewrite in wp-config.php (covers multisite DOMAIN_CURRENT_SITE), with changeSiteDomainToHost as a non-fatal finisher.
  • Isolation fixes found by live testing: per-preview WP_REDIS_PREFIX/WP_CACHE_KEY_SALT (shared persistent object caches otherwise serve the parent's cached data to the preview) and retargeting of absolute symlinks that still point into the parent tree (e.g. Query Monitor's db.php, which otherwise double-loads its autoloader and fatals).
  • Tagged from birth: the clone's record gets agentToolsPreview/agentToolsPreviewOf before any copying, so even a failed provision is listable and destroyable; inherited agentToolsEnabled* options are stripped. Previews re-register with the MCP server after a Local restart.
  • UI + observability: sidebar IPC events (without selectSite, so a background preview doesn't steal the user's selection), halted status on step failure, and per-step timing logged through localLogger so every run benchmarks itself in local-lightning.log.
  • Cross-site tool targeting: WP-CLI, log, config, and site-info tools accept an optional siteId to operate on any other registered site. This is the working agent flow: a session connected to the primary site's endpoint spins up a preview, then runs wp_cli/read_error_log against it by id — no client reconnect. Environment tools keep their own siteId routing (they may target unregistered sites).
  • execWpCli extracted from the wp_cli tool as a reusable runner (skip flags, mu-plugin neutralization, timeout); wp_cli tool behavior unchanged from Don't auto-skip plugins/themes in wp_cli #63 (no default skips).

Testing

  • 102/102 unit tests pass (tool-surface snapshots updated for the new tools).
  • Live-verified on macOS/APFS: bare-install site clones in ~22s; a 1.5G subdomain multisite with Redis object cache, Query Monitor db.php drop-in, and mu-plugin WP_CLI guards clones in ~76–104s, serves HTTP correctly, DB fully rewritten (verified via direct SQL including wp_blogs), parent untouched.
  • Destroy verified on healthy, failed, and half-provisioned previews; refusal verified for non-preview sites.

Known limitations / follow-ups

  • preview_start on large sites can exceed MCP client tool timeouts (the clone still completes server-side; a retry then reports the name collision). Follow-up: fast-return + status polling.
  • The preview answers HTTP before the domain rewrite lands mid-provision; callers should not probe until the tool returns.
  • Multisite subsite hosts entries are not created (main + www only).
  • Windows fallback path is untested.
  • Follow-up: per-call audit logging (session clientInfo, tool, args digest) through localLogger.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 46.33% (🎯 40%) 272 / 587
🟢 Statements 46% (🎯 40%) 282 / 613
🟢 Functions 57.14% (🎯 40%) 52 / 91
🟢 Branches 37.12% (🎯 25%) 137 / 369
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/mcp-server.ts 54.65% 40% 66.66% 54.71% 57-73, 81-104, 133, 137-169, 187-189, 254-264, 279-282, 333-340, 343-355, 361-364, 377-382
src/helpers/fast-copy.ts 0% 0% 0% 0% 6-74
src/tools/index.ts 75.75% 66.66% 40% 96% 37, 69, 72, 75, 78, 81, 84, 107
src/tools/preview.ts 7.69% 0% 0% 7.69% 65-90
src/tools/wpcli.ts 37.97% 36.06% 42.85% 36.98% 45-94, 133-179, 194-196, 201-202
Generated in workflow #27 for commit a9fe448 by the Vitest Coverage Report Action

@christianc1
christianc1 force-pushed the feature/preview-environments branch from a5d1d9b to 59848da Compare August 21, 2026 13:53
Adds preview_start / preview_list / preview_destroy MCP tools that clone
a site into a disposable, isolated preview: copy-on-write file and DB
copies on APFS (filtered copy fallback elsewhere), our own provision
sequence built from Local's cradle services, domain rewrite that survives
mu-plugin WP_CLI guards, per-preview object-cache prefixing, and
parent-pointing symlink retargeting. Previews are tagged from birth so
preview_destroy can always clean up, and refuse to delete regular sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@christianc1
christianc1 force-pushed the feature/preview-environments branch from 59848da to a9fe448 Compare August 21, 2026 14:00
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.

1 participant