fix: close etappe-0 whole-branch review findings

- Autoloader: add automattic/jetpack-autoloader and load
  vendor/autoload_packages.php; require wordpress/mcp-adapter directly (^0.5).
- ServerRegistrar: make the adapter create_server() call and the
  OAuthServerRegistrar class_exists() check injectable so createServer()'s
  unhook-before-create ordering and the "mcp-oauth absent" branch are unit
  tested; add a smoke-test check that booting REST triggers no
  _doing_it_wrong().
- mcp-smoke.sh: assert mcp-adapter's default server route 404s, and that the
  subscriber role-gate denial actually carries our/the adapter's denial text
  (not just any isError/error).
- Guard tests/Integration/mint-token.php and tec-probe.php with
  `defined('WP_CLI') && WP_CLI || exit;`; add .gitattributes export-ignore
  for tests/docs-superpowers/.superpowers/phpunit/phpcs config.
- Security: add Server/RequestContext, tracking via
  rest_request_before_callbacks whether the current REST dispatch is our
  OAuth-protected /mcp/mcp-oauth-server route; AbilityRegistrar's
  permission_callback now denies (after the role gate) when abilities are
  invoked outside that route. Wired in Plugin::boot().
- Minors: single Plugin::VERSION constant used by ServerRegistrar;
  IntegrationRegistry caches its computed abilities list; tec-probe.php
  checks wp_insert_post's result and cleans up its probe post in a
  try/finally.

composer test: 47/47 green. composer lint: clean. Full mcp-smoke.sh run ends
ALL CHECKS PASSED (plus the expected /.well-known WARN).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-25 19:23:09 +02:00
co-authored by Claude Opus 5.5
parent 2c03286ba0
commit 0e3332ad3e
17 changed files with 430 additions and 38 deletions
+62
View File
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Server;
use WP_REST_Request;
/**
* Tracks whether the current REST dispatch is serving our OAuth-protected MCP
* server route (`/mcp/mcp-oauth-server`).
*
* Abilities must never run outside that route – not through mcp-adapter's
* (disabled) default server, not through a differently configured server, and
* not through any unrelated REST request. WordPress fires
* `rest_request_before_callbacks` for every matched REST route before any
* permission_callback runs (see WP_REST_Server::dispatch()), so by the time an
* ability's permission_callback executes – itself only reachable from inside
* the MCP route's own callback – the flag below is already accurate for the
* current request.
*/
final class RequestContext
{
private static bool $isOAuthServerRequest = false;
public function register(): void
{
add_filter('rest_request_before_callbacks', [self::class, 'recordRoute'], 10, 3);
}
/**
* @param mixed $response
* @param array<string, mixed> $handler
* @return mixed
*/
public static function recordRoute(mixed $response, array $handler, WP_REST_Request $request): mixed
{
if ($request->get_route() === self::route()) {
self::$isOAuthServerRequest = true;
}
return $response;
}
public static function isOAuthServerRequest(): bool
{
return self::$isOAuthServerRequest;
}
/**
* Test-only: resets (or forces) the recorded state between unit tests.
*/
public static function reset(bool $isOAuthServerRequest = false): void
{
self::$isOAuthServerRequest = $isOAuthServerRequest;
}
private static function route(): string
{
return '/' . ServerRegistrar::NAMESPACE . '/' . ServerRegistrar::ROUTE;
}
}