Skip to content

feat: MCP server endpoint (read-only, v0.12.0) - #216

Closed
pluginslab wants to merge 9 commits into
devfrom
feat/001-mcp-server-endpoint
Closed

pluginslab wants to merge 9 commits into
devfrom
feat/001-mcp-server-endpoint

Conversation

@pluginslab

Copy link
Copy Markdown
Owner

Summary

Built-in MCP server at POST /wp-json/wp-agentic-admin/v1/mcp so external AI clients (Claude, ChatGPT, custom agents) can call this plugin's registered abilities — plus a curated allowlist of third-party Abilities API entries — over JSON-RPC 2.0, authenticated with WordPress application passwords. No dependency on Automattic's wordpress-mcp.

Read-only in this release: abilities are only exposed if they declare meta.annotations.readonly = true. Master toggle defaults OFF — privacy-first guarantee preserved out of the box.

Full spec + plan: .claude/plans/features/001-mcp-server-endpoint/{spec,plan,progress}.md. Walkthrough + curl examples in docs/MCP-ENDPOINT.md.

7 commits:

  • 39b7f98 docs(plan): spec + plan
  • 150871c feat(mcp): settings keys + sanitizer
  • 3e4476c feat(mcp): Ability_Registry (discovery + filtering + tool mapping)
  • 134cb8e feat(mcp): JsonRpc_Server (initialize / ping / tools/list / tools/call)
  • 117e3e0 feat(mcp): REST route /wp-agentic-admin/v1/mcp gated by toggle
  • 2bb8baa feat(mcp): React Settings UI + admin-only /mcp-settings REST
  • 2e385f5 chore: bump to 0.12.0 + docs

Verification done locally

Full MCP wire-protocol roundtrip against the docker stack — initialize → notifications/initialized (204) → tools/list (25 tools) → tools/call wp-agentic-admin__site-health (returned real WP 6.9.4 / PHP 8.3.29 / theme data) → unknown-tool error envelope (-32601) → ping. claude mcp get wp-agentic-admin returns ✓ Connected.

PHPCS clean on every new file. Jest unit tests 96/96 pass. JSX lint 0 errors. No new warnings.

Test plan

  • Endpoint disabled by default after plugin update — POST /wp-agentic-admin/v1/mcp returns HTTP 404
  • WP-Admin → Agentic Admin → Settings tab → MCP Endpoint card renders
  • Toggle on, no creds → HTTP 401
  • Toggle on, valid app password → initialize returns serverInfo v0.12.0
  • tools/list returns only abilities with meta.annotations.readonly = true
  • tools/call on an admin-gated ability with a subscriber app password → -32001 Permission denied
  • Enable expose-third + tick core/get-site-info → tools/list grows by 1
  • Copy-endpoint-URL button works in the UI
  • Disable master toggle → endpoint returns 404 again
  • No regression in the existing browser-side ReAct flow, LLM proxy, or admin pages

🤖 Generated with Claude Code

pluginslab and others added 8 commits May 20, 2026 17:21
Read-only MCP endpoint exposing registered abilities via JSON-RPC 2.0.
No dependency on Automattic's wordpress-mcp; uses WP app passwords for auth.
Master toggle defaults OFF to preserve the privacy-first headline.

See .claude/plans/features/001-mcp-server-endpoint/ for full spec + plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an 'mcp' section to Settings::get_settings_config() with four fields:
- agentic_admin_mcp_enabled        (checkbox, default 0)
- agentic_admin_mcp_expose_own     (checkbox, default 1)
- agentic_admin_mcp_expose_third   (checkbox, default 0)
- agentic_admin_mcp_allowlist      (ability_list, default [])

New 'ability_list' sanitization case in update_field() that validates each
entry against the ability ID regex used by agentic_admin_register_ability().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Discovers WP_Ability entries via wp_get_abilities() and filters them by:
- Read-only annotation (v1 hard requirement)
- Settings: expose own + expose third-party + allowlist

Maps WP_Ability → MCP tool descriptor, converting "ns/name" IDs to
"ns__name" tool names (reversible via resolve_tool_name()).

Smoke-tested against the local WP: 25 own readonly abilities exposed,
3 third-party (core/*) discovered for the allowlist UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure JSON-RPC 2.0 dispatch — no HTTP coupling. Implements the four
methods the v1 endpoint supports:
- initialize (protocolVersion 2025-03-26, serverInfo, tools capability)
- ping
- tools/list (enumerates exposed abilities via Ability_Registry)
- tools/call (resolve name → check_permissions → execute → wrap result)

tools/call wraps WP_Ability::execute() output as MCP content blocks,
returns isError:true on WP_Error / Throwable instead of leaking traces.

Smoke-tested end-to-end against the live WP: 25 tools listed, site-health
ability executed via JSON-RPC, error envelopes correct for unknown
method + unknown tool.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires Rest_Endpoint into the plugin bootstrap. The route is registered
inside rest_api_init only when agentic_admin_mcp_enabled is on — so a
disabled endpoint returns HTTP 404 (no route exists), not 401/403.

Permission callback requires is_user_logged_in() only; per-ability
permission_callback runs inside JsonRpc_Server::handle_tools_call.

End-to-end verified against local docker stack:
- disabled → 404
- no auth → 401
- app-password auth → initialize, tools/list (25), tools/call site-health
- disable → 404 again

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an MCP Endpoint section to the React Settings tab:
- Master toggle (off by default) with collapsible details
- Endpoint URL display + Copy button
- Expose-own / expose-third-party toggles
- Allowlist for third-party abilities, grouped by source plugin
- Read-only abilities are checkable; others rendered disabled (v1)

Backend: new admin-only REST routes at /wp-agentic-admin/v1/mcp-settings
(GET/POST) — capability gate is current_user_can('manage_options').

End-to-end verified: enabling expose-third + allowlisting core/get-site-info
grows /wp-agentic-admin/v1/mcp tools/list from 25 to 26.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Version bump (wp-agentic-admin.php × 3, package.json, readme.txt stable
tag, package-lock.json) and:

- .release-notes/0.12.0.md  — draft release notes for the MCP feature
- docs/MCP-ENDPOINT.md      — how to enable, app-password setup, curl
                              walkthrough, tool name mapping, Claude
                              Desktop config, troubleshooting,
                              coexistence note with Automattic's
                              wordpress-mcp
- README.md                 — short Key Features bullet pointing to the
                              endpoint doc
- .claude/CLAUDE.md         — Further Reading entry + a one-line note
                              about how readonly abilities flow into MCP

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address findings from the security review on PR #216:

- JSON-RPC: notifications (no `id` field) now correctly return no
  response across ALL methods, including `tools/call`. Previously
  tools/call short-circuited the notification check and emitted an
  envelope, violating JSON-RPC 2.0 §4.1 ("Notifications [...] MUST NOT
  be replied to").
- Sanitize error envelopes returned to clients:
  - Throwable from execute() → log full message via error_log,
    surface generic "Tool execution failed." (no longer reflects the
    ability name or exception message)
  - WP_Error path → sanitize_text_field + truncate to 240 chars, so a
    chatty error message can't smuggle a stack trace or SQL fragment
    back to a low-privilege client.
- Cache hardening: nocache_headers() on every POST /mcp response so
  misconfigured intermediaries can't cache user-scoped tool lists or
  ability outputs.
- Documentation: class docblock on Settings_Rest spelling out the
  CSRF model (rely on WP REST's cookie-nonce check for browser calls;
  app-password Basic auth correctly bypasses nonce).

Verified live: notification tools/call now returns HTTP 204 with empty
body; regular tools/call unchanged; Cache-Control reflects no-store +
must-revalidate; Jest 96/96 still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pluginslab

Copy link
Copy Markdown
Owner Author

Security review follow-up (commit 793ef7f) — fixes pushed.

The general-purpose security reviewer flagged 2 mediums + several lows/infos. Verdict was "ship with fixes."

Fixed in this PR:

  • [Medium] JSON-RPC notification on tools/call now correctly returns no response (was: emitted an envelope, violating §4.1) — verified: notification call now returns HTTP 204 + empty body, regular call unchanged.
  • [Medium] Settings_Rest CSRF model documented in class docblock (relies on WP REST cookie-nonce for browser, app-password Basic for CLI — both correct).
  • [Low] Throwable in execute(): log via error_log, surface generic "Tool execution failed." (no longer reflects ability name or exception message).
  • [Low] WP_Error path: sanitize_text_field + 240-char truncate before returning to client.
  • [Info] nocache_headers() on every POST /mcp response — verified Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private.

Deferred to follow-up issues:

All checks still green locally: PHPCS clean on every new file, Jest 96/96, JS lint 0 errors. Endpoint smoke-tested end-to-end again after the fixes.

Walk wp_get_abilities() at most once per Ability_Registry instance.
A single MCP request constructs one registry and calls tools/list +
tools/call against it, so the registry walk + settings reads happen
once even if the client invokes multiple tools.

Adds flush_cache() for tests / CLI workers that need to force
re-discovery. Production REST flow doesn't need to call it.

docs(mcp): document tools/list disclosure model (#219, closing as decided)

Added a "Who can see tools/list?" subsection to docs/MCP-ENDPOINT.md
spelling out that subscriber-level app passwords CAN see the tool
catalog (names, descriptions, schemas) but CANNOT invoke abilities
gated above their cap. Matches MCP convention; documented so it's a
deliberate choice, not an accident.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pluginslab

Copy link
Copy Markdown
Owner Author

Follow-ups landed (commit bf47c59).

All three security-review follow-ups resolved. PR is ready for human review + CI.

@pluginslab
pluginslab requested a review from ivdimova May 20, 2026 18:06

@ivdimova ivdimova left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code reviewed. The MCP server implementation is well-structured with clean separation of concerns across the four new classes. Security has been thoroughly addressed — read-only enforcement, auth gating, notification handling, error sanitization, and cache headers all look correct. All three security-review follow-ups (#217, #218, #219) are closed. No blocking issues. Ready to merge.

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