Skip to content

feat: mcp enable + site create --mcp — make a site MCP-ready in one command#11

Open
vikasiwp wants to merge 1 commit into
mainfrom
feat/mcp-enable
Open

feat: mcp enable + site create --mcp — make a site MCP-ready in one command#11
vikasiwp wants to merge 1 commit into
mainfrom
feat/mcp-enable

Conversation

@vikasiwp

Copy link
Copy Markdown
Contributor

What

Adds two commands that make a site MCP-ready in one shot, so an agent can spin up an InstaWP sandbox and immediately drive it over MCP (InstaMCP) with no wp-admin clicks:

  • instawp mcp enable <site> — for an existing site:
    1. install + activate the InstaMCP plugin if it isn't already active (from the canonical distribution zip; override with --plugin-zip <url> / INSTAMCP_PLUGIN_ZIP),
    2. enable Execute PHP (insta_mcp_execute_php_enabled=1) and Site Files read-write (insta_mcp_site_files_mode=read_write),
    3. mint a Bearer token tied to an administrator (which maps to every scope, incl. mcp:admin, via the plugin's role→scope map),
    4. emit a machine-readable connection blob (--json) plus ready-to-paste .mcp.json and claude mcp add … snippets.
  • instawp site create --mcp [--plugins elementor,bricks] — create → wait for ready → install any --plugins → run the same enable path → same blob.

Idempotent: re-running re-prints the same token instead of minting a new one. The plugin stores only the token's SHA256 hash, so the plaintext is cached locally (~/.config/instawp, keyed by site id, alongside the existing SSH/site caches). --rotate forces a fresh token (upserts a single labelled row — never piles up).

All plumbing reuses the existing SSH transport (ensureSshAccess + execViaSsh, MOTD-stripped) and config store — no new deps.

Connection shape

Uses InstaMCP's supported Bearer-token transport, exactly as documented in the plugin's docs/CLIENTS.md and matching the live instawp-marketing reference (InstaMCP 1.9.2):

https://<site>/insta-mcp?t=<64-char-token>
# or:  URL https://<site>/insta-mcp  +  Header: Authorization: Bearer <token>

⚠️ Deliberate scope decision — no OAuth 2.1 client minting

The task asked to "mint an OAuth client + token." That is not currently possible from the CLI: the plugin ships with INSTA_MCP_OAUTH_FEATURE_ENABLED = false (insta-mcp.php), so the OAuth tables (*_oauth_clients / *_authorization_codes / *_access_tokens / *_refresh_tokens) and the /oauth/authorize|token + .well-known/* routes are never created. There is nothing to register a client against.

This PR therefore provisions the working, supported Bearer path. Enabling true OAuth 2.1 / DCR is a plugin-side change (flip the feature flag, ship the OAuth tables + a client-registration path, generate signing keys) — see the linked issue/report. Called out here per the task's "stop and surface if it needs a plugin change" guidance.

Testing

  • npm run build ✅ · npm test ✅ (486/486)
  • Command wiring verified (instawp mcp enable --help, instawp create --help show the new options).
  • End-to-end against a live site is NOT yet run — it needs SSH to a customer host, which this change's author can't reach. Please validate on test site 2568862 (inspired-bear-cc5731.instawp.site) before merge (QA is being asked to do this).
  • Cross-model review: ran z.ai GLM-4.6 over the diff; applied its actionable findings (fail-fast --plugins slug validation, --plugin-zip URL guard). Its token/SQL "injection" flags were verified not-real (token is generated [0-9a-f] hex; SQL uses $wpdb->prepare).

Next step (human): /code-review ultra <PR#> before merge.

… in one command

Provisions InstaMCP on a site with no wp-admin clicks, so an agent can spin up a
sandbox and immediately drive it over MCP.

- `instawp mcp enable <site>`: install+activate the InstaMCP plugin (if absent),
  enable Execute PHP + Site Files (read-write), mint a Bearer token tied to an
  administrator (→ mcp:admin scope), and emit a machine-readable connection blob
  plus ready-to-paste .mcp.json / `claude mcp add` snippets. Idempotent — the
  token is cached locally (the plugin stores only its hash) so re-running
  re-prints the same token; --rotate forces a fresh one.
- `instawp site create --mcp [--plugins a,b]`: create → wait → install plugins →
  same enable path → same blob.

Uses InstaMCP's supported Bearer-token transport (/insta-mcp?t=<token>). OAuth 2.1
client provisioning is intentionally NOT done: the plugin ships with
INSTA_MCP_OAUTH_FEATURE_ENABLED=false (no OAuth tables/routes to mint against).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude

claude Bot commented Jul 15, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@vikasiwp

Copy link
Copy Markdown
Contributor Author

Next step (human): run /code-review ultra 11 before merge.

Also note the deliberate scope decision in the description: OAuth 2.1 client minting is intentionally not implemented — the plugin ships INSTA_MCP_OAUTH_FEATURE_ENABLED = false, so there are no OAuth tables/routes to mint a client against. This PR provisions the supported Bearer-token path. Enabling OAuth is a plugin-side change.

E2E on live site 2568862 is being validated by the QA agent (author has no SSH to customer hosts).

@vikasiwp

Copy link
Copy Markdown
Contributor Author

QA: PASS ✅

Env: live throwaway sandbox inspired-bear-cc5731.instawp.site (id 2568862, PPU, PHP 8.2, InstaMCP 1.9.2 pre-installed) · InstaWP/cli PR #11 @38d18eb
Criteria: all 4 acceptance criteria met, verified end-to-end over SSH + live MCP calls.

Built the branch clean (npm install with NODE_ENV unset, npm run build ✅). Ran the real command against the real site; before running enable I reset the two capability flags to a disabled state (insta_mcp_execute_php_enabled=0, insta_mcp_site_files_mode=read_only) so the enable path had something to actually flip.

1. mcp enable 2568862 --json → exit 0, correct blob ✅

{"success":true,"data":{"site_id":2568862,"site_url":"https://inspired-bear-cc5731.instawp.site",
"mcp_endpoint":"https://inspired-bear-cc5731.instawp.site/insta-mcp",
"mcp_endpoint_with_token":".../insta-mcp?t=c968a1c3…f1784",
"token":"c968a1c3a2bfa864d414e9bf7482dda49114a7b8eeb597038a1314f2031f1784",
"scopes":["mcp:read","mcp:write","mcp:delete","mcp:admin"],
"capabilities":{"execute_php":true,"site_files":"read_write"},"rotated":false}}
  • exit 0; mcp_endpoint, mcp_endpoint_with_token, token all present.
  • token = 64 hex chars (^[0-9a-f]{64}$ ✓).
  • scopes includes mcp:admin ✓.

2. Endpoint is a working MCP server ✅

Raw JSON-RPC initialize POST to …/insta-mcp?t=<token> (UA InstaWP/1.0) → HTTP 200, valid result:

serverInfo: InstaMCP 1.9.2 · protocolVersion 2025-11-25 · Mcp-Session-Id issued
  • tools/list returned 43 tools incl. the admin-scoped execute_php, db_query, site_files — confirms the mcp:admin mapping is real, not just claimed.
  • Auth is enforced: a bogus token → HTTP 401 (not an open endpoint).

3. On-site state after enable ✅

Via instawp wp 2568862 -- …:

check result
insta-mcp plugin active yes (exit 0)
insta_mcp_execute_php_enabled 01
insta_mcp_site_files_mode read_onlyread_write
token row inserted for user_id 1 (administrator), label InstaWP CLI
sha256 of printed token matches the stored token_hash on the site ✓

4. Idempotency ✅

Re-running mcp enable 2568862 printed the same token (rotated:false) and added no new row — COUNT(*) WHERE label='InstaWP CLI' stayed 1.

Bonus — --rotate

--rotate minted a fresh token (rotated:true), still a single labelled row (no pile-up). New token → 200; the previous token → 401 (hash genuinely replaced).


Repro-before-fix: n/a (new feature) — but I disabled the two flags first and showed enable flip them 0→1 / read_only→read_write, so the run exercised the real code path, not a pre-configured no-op.

Notes for the merger:

  • The mcp:admin scope in the JSON is CLI-asserted (from the admin-token → scope map), and I independently confirmed the token can list & would drive admin tools (execute_php, db_query) over MCP — so it's substantiated, not just a label.
  • One transport caveat, not a PR bug: the QA host's ssh_config forces ControlMaster with a socket under a read-only ~/.ssh; the CLI's sshArgs doesn't set ControlMaster=no/ControlPath=none, so on such a host every SSH call fails to bind the mux socket. It works fine on a normal box. Might be worth having the CLI pin -o ControlPath=none defensively — flagging for engineer, out of scope for this verdict.
  • Plugin install path was NOT exercised (InstaMCP was already active on 2568862, so it hit the "already active" branch). The enable/token/endpoint logic is fully covered; a from-scratch install on a bare site is the one untested sub-path.
  • site create --mcp (the other command in this PR) was not tested here — this task scoped only mcp enable.

Site restored to its original state (token table back to just the pre-existing "Default Token", flags at their original 1/read_write).

— QA

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