Skip to content

feat(mcp-helper): add MCP connect helper PoC for AI tool onboarding#111

Open
MathieuLamiot wants to merge 8 commits into
developfrom
feat/mcp-connect-helper
Open

feat(mcp-helper): add MCP connect helper PoC for AI tool onboarding#111
MathieuLamiot wants to merge 8 commits into
developfrom
feat/mcp-connect-helper

Conversation

@MathieuLamiot

Copy link
Copy Markdown
Owner

Description

Adds a self-contained mcp-helper-lib/ package and wires it into the sybgo plugin as MCP_Module. Non-technical WordPress admins can now generate ready-to-paste JSON config blocks for Claude Desktop and GitHub Copilot directly from the Application Passwords section of their profile page, without ever needing to edit JSON files by hand.

Type of change

  • New feature (non-breaking change which adds functionality).

Detailed scenario

What was tested

Manual scenario — full user journey (local wp-env)

  1. Navigated to /wp-admin/profile.php as admin.
  2. Entered a name in the Application Password field and clicked Add Application Password.
  3. The new password notice appeared with a "Connect to AI" button inline next to the Copy button — verified the button is injected by the MutationObserver without page reload.
  4. Clicked Connect to AI → modal opened showing the Claude Desktop tab pre-selected with a fully-formed JSON block containing the correct WP_API_URL, WP_API_USERNAME, and WP_API_PASSWORD.
  5. Clicked the GitHub Copilot tab → JSON switched to the Copilot format (github.copilot.chat.mcp.servers) without a page reload.
  6. Clicked Copy JSON → button label changed to "Copied!" for 2 s; clipboard contained the full JSON.
  7. Closed modal via × button and via Escape key — both worked.
  8. Clicked the backdrop outside the modal content — modal closed correctly.

Screenshots from all 8 steps were captured by the Playwright spec (tests/e2e/specs/screenshots/mcp-connect-journey.spec.ts) and saved to tests/e2e/screenshots/mcp-journey/.

Step 2 — Connect to AI button
Step 3 — Modal, Claude Desktop tab
Step 4 — Modal, GitHub Copilot tab
Step 5 — Copied! feedback

How to test

  1. Start the local environment: bin/dev-up.sh (runs wp-env start and seeds the DB).
  2. Navigate to http://localhost:8888/wp-admin/profile.php and log in as admin.
  3. Scroll to the Application Passwords section.
  4. Type any name in the field and click Add Application Password.
  5. Verify a green notice appears with the new password and a "Connect to AI" button next to the built-in Copy button.
  6. Click Connect to AI.
  7. Verify the modal opens with two tabs: Claude Desktop (active by default) and GitHub Copilot.
  8. Verify the JSON block in the Claude Desktop tab contains:
    • WP_API_URL: http://localhost:8888/wp-json/mcp/mcp-adapter-default-server
    • WP_API_USERNAME: admin
    • WP_API_PASSWORD: the password shown in the notice
  9. Click the GitHub Copilot tab — verify the JSON changes to the Copilot format (github.copilot.chat.mcp.servers).
  10. Click Copy JSON — verify the button label briefly shows "Copied!" then reverts.
  11. Close the modal with ×, Escape, or clicking the dark backdrop — all three should work.

To run the Playwright screenshot suite:

cd tests/e2e
nvm use 22
npx playwright test specs/screenshots/mcp-connect-journey.spec.ts --project=chromium

All 6 tests should pass and screenshots should appear in tests/e2e/screenshots/mcp-journey/.

Affected Features & Quality Assurance Scope

  • WordPress admin profile page (profile.php, user-edit.php) — new UI injected inline.
  • Application Passwords section — MutationObserver watches for new password rows.
  • No existing sybgo features (reports, email, events, AI summary) are touched.

Technical description

Architecture overview

mcp-helper-lib/ — standalone package

The lib is completely decoupled from sybgo internals. Any plugin activates it with one call:

\WPMedia\MCPHelper\MCP_Helper::init();

A private static bool $initialized guard makes repeated calls from multiple plugins a no-op — hooks and assets are registered exactly once per request.

MCP_Helper wires four WordPress hooks on first call:

  • show_user_profile / edit_user_profilerender_modal() outputs the hidden #mcp-helper-modal div.
  • admin_enqueue_scriptsenqueue_assets() registers JS/CSS on profile.php and user-edit.php only; guarded by wp_script_is() so the second plugin calling init() skips asset registration.
  • wp_ajax_mcp_helper_get_tools → returns tool metadata (nonce + edit_users cap required).

MCP_Config_Provider is a pure data class holding tool metadata (labels, config file paths, instructions). JSON generation happens entirely client-side — the Application Password is never sent back to the server after creation.

mcp-config.js uses a MutationObserver on #application-passwords-section to detect when WordPress 5.6+'s REST-powered password form adds a .new-application-password-notice element, then injects the Connect to AI button. The JSON is built from mcpHelper.siteUrl + mcpHelper.username (localised by PHP) and the password value read from input.code in the notice.

Asset web-accessibility in Docker / wp-env

The vendor/wp-media/mcp-helper-lib symlink does not resolve inside Docker containers (the symlink target path is in the host filesystem). MCP_Module::boot() sets the mcp_helper_assets_url filter to SYBGO_PLUGIN_URL . 'assets', pointing to the plugin's own assets/ directory. wp-plugin/assets/js/mcp-config.js and wp-plugin/assets/css/mcp-config.css are copied from the lib and served from the plugin URL.

Button type="button" fix

All buttons inside the modal (mcp-helper-tab-btn, mcp-helper-copy-btn) explicitly declare type="button". Without this, browsers default to type="submit" inside a <form>, which caused tab and copy clicks to submit the WordPress profile form and navigate away.

See updated docs:

New dependencies

  • wp-media/mcp-helper-lib: * (path repository — local package in this monorepo, no external download).

Risks

  • Security: The mcp_helper_get_tools AJAX action verifies a nonce and the edit_users capability before responding. The Application Password is read from the DOM and used only client-side; it is never sent to the server after creation (WordPress already displayed it once).
  • Idempotency: If another plugin in the same install also calls MCP_Helper::init(), hooks and assets are registered exactly once. No double-rendering, no duplicate enqueues.
  • Asset path: The mcp_helper_assets_url filter lets any plugin redirect asset loading to a web-accessible URL. The fallback in get_lib_url() resolves relative to WP_CONTENT_DIR / ABSPATH and should work for standard installs.

Mandatory Checklist

Code validation

  • I validated all the Acceptance Criteria. If possible, provide screenshots or videos.
  • I triggered all changed lines of code at least once without new errors/warnings/notices.
  • I implemented built-in tests to cover the new/changed code.

Code style

  • I wrote a self-explanatory code about what it does.
  • I protected entry points against unexpected inputs.
  • I did not introduce unnecessary complexity.
  • Output messages (errors, notices, logs) are explicit enough for users to understand the issue and are actionable.

Unticked items justification

Unit tests: This PoC is intentionally thin on PHP logic (MCP_Helper delegates to hooks; MCP_Config_Provider is a pure data class). The primary value is the browser-side interaction (MutationObserver, modal, JSON generation, clipboard), which is covered by the Playwright screenshot spec. PHP unit tests can be added when the PoC graduates to a production feature.

Additional Checks

  • In the case of complex code, I wrote comments to explain it.
  • When possible, I prepared ways to observe the implemented system (logs, data, etc.).
  • I added error handling logic when using functions that could throw errors (HTTP/API request, filesystem, etc.)

MathieuLamiot and others added 8 commits May 19, 2026 14:10
Introduces `mcp-helper-lib/`, a standalone, idempotent WordPress admin
helper that injects a "Connect to AI" button next to newly-created
Application Passwords and opens a modal where the user picks Claude
Desktop or GitHub Copilot and gets a ready-to-paste JSON config block.

- `mcp-helper-lib/class-mcp-config-provider.php` — pure data layer:
  tool metadata and JSON config array generation for both tools.
- `mcp-helper-lib/class-mcp-helper.php` — self-contained singleton;
  registers show_user_profile, admin_enqueue_scripts, and AJAX hooks
  exactly once even when called by multiple plugins.
- `mcp-helper-lib/assets/js/mcp-config.js` — MutationObserver watches
  for new application passwords, injects the button, drives the modal
  (tab switching, client-side JSON generation, clipboard copy).
- `mcp-helper-lib/assets/css/mcp-config.css` — modal and code block styles.
- `wp-plugin/modules/class-mcp-module.php` — thin Module_Interface wrapper
  that calls MCP_Helper::init() during Sybgo's boot sequence.
- `wp-plugin/class-sybgo.php` — adds MCP_Module to build_modules().
- `wp-plugin/composer.json` — adds mcp-helper-lib path repository + require.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…JAX action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olution in Docker

- Add mcp_helper_assets_url filter so calling plugins can serve assets from
  their own web-accessible directory instead of vendor/
- MCP_Module sets the filter to SYBGO_PLUGIN_URL/assets, where copies of
  mcp-config.js and mcp-config.css now live
- Fix MutationObserver target: WordPress 5.6+ creates .new-application-password-notice
  (not .new-application-password); capture password from input.code inside it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ubmission

Tab buttons and the copy button inside the modal had no explicit type, so the
browser defaulted to type="submit". Clicking them on the WordPress profile page
(which is a <form>) submitted the profile form and navigated away before
screenshots could capture the open modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures all 6 steps of the demo flow (profile page → password creation →
Connect to AI button → modal tabs → copy feedback) for product team demo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…er, and E2E tests for modal close behaviors

Adds 18 PHPUnit unit tests covering AC3, AC4, AC8 (tool config structure, idempotency, page guard),
and 3 Playwright E2E tests covering AC6 (modal close via × button, Escape key, and backdrop click).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MCP_Module uses SYBGO_PLUGIN_URL at runtime but it was absent from the
PHPStan bootstrap, causing a constant.notFound error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Symlinks created by Composer path repositories do not resolve inside
Docker containers because the target path is relative to the host
filesystem. Setting "symlink": false makes Composer mirror the files,
which works in both local dev and CI (E2E + Plugin Check workflows).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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