fix: reset request context after the MCP request, resolve client name
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:
@@ -5,7 +5,8 @@ declare(strict_types=1);
|
||||
if (! class_exists('WP_REST_Request')) {
|
||||
class WP_REST_Request
|
||||
{
|
||||
public function __construct(private string $route = '')
|
||||
/** @param array<string, string> $headers Lower-case header name => value. */
|
||||
public function __construct(private string $route = '', private array $headers = [])
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,5 +14,10 @@ if (! class_exists('WP_REST_Request')) {
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function get_header(string $key): ?string
|
||||
{
|
||||
return $this->headers[strtolower($key)] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,14 @@ namespace LIT\MCP\Tests\Unit\Server;
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Server\RequestContext;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use stdClass;
|
||||
use WP_REST_Request;
|
||||
use WPMedia\MCP\OAuth\Auth\JWT;
|
||||
|
||||
final class RequestContextTest extends TestCase
|
||||
{
|
||||
private const ROUTE = '/mcp/mcp-oauth-server';
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
RequestContext::reset();
|
||||
@@ -24,37 +28,90 @@ final class RequestContextTest extends TestCase
|
||||
|
||||
public function testRecordRouteMarksTheOAuthServerRoute(): void
|
||||
{
|
||||
$request = new WP_REST_Request('/mcp/mcp-oauth-server');
|
||||
|
||||
RequestContext::recordRoute(null, [], $request);
|
||||
RequestContext::recordRoute(null, [], new WP_REST_Request(self::ROUTE));
|
||||
|
||||
self::assertTrue(RequestContext::isOAuthServerRequest());
|
||||
}
|
||||
|
||||
public function testRecordRouteIgnoresOtherRoutes(): void
|
||||
{
|
||||
$request = new WP_REST_Request('/wp/v2/users/me');
|
||||
|
||||
RequestContext::recordRoute(null, [], $request);
|
||||
RequestContext::recordRoute(null, [], new WP_REST_Request('/wp/v2/users/me'));
|
||||
|
||||
self::assertFalse(RequestContext::isOAuthServerRequest());
|
||||
}
|
||||
|
||||
public function testRecordRouteReturnsResponseUnchanged(): void
|
||||
{
|
||||
$response = new \stdClass();
|
||||
$request = new WP_REST_Request('/mcp/mcp-oauth-server');
|
||||
$response = new stdClass();
|
||||
|
||||
$result = RequestContext::recordRoute($response, [], $request);
|
||||
|
||||
self::assertSame($response, $result);
|
||||
self::assertSame($response, RequestContext::recordRoute($response, [], new WP_REST_Request(self::ROUTE)));
|
||||
}
|
||||
|
||||
public function testRegisterHooksRecordRouteOnRestRequestBeforeCallbacks(): void
|
||||
public function testClearRouteResetsTheFlagAfterOurRequest(): void
|
||||
{
|
||||
$request = new WP_REST_Request(self::ROUTE);
|
||||
RequestContext::recordRoute(null, [], $request);
|
||||
$response = new stdClass();
|
||||
|
||||
$result = RequestContext::clearRoute($response, [], $request);
|
||||
|
||||
self::assertSame($response, $result);
|
||||
self::assertFalse(RequestContext::isOAuthServerRequest());
|
||||
}
|
||||
|
||||
public function testClearRouteIgnoresNestedRequestsToOtherRoutes(): void
|
||||
{
|
||||
RequestContext::recordRoute(null, [], new WP_REST_Request(self::ROUTE));
|
||||
|
||||
RequestContext::clearRoute(null, [], new WP_REST_Request('/wp/v2/posts'));
|
||||
|
||||
self::assertTrue(RequestContext::isOAuthServerRequest());
|
||||
}
|
||||
|
||||
public function testClientNameFallsBackToTheClientIdClaim(): void
|
||||
{
|
||||
Functions\when('get_option')->justReturn('test-secret');
|
||||
$token = JWT::encode([
|
||||
'sub' => '3',
|
||||
'app_pass_id' => 'uuid-unknown',
|
||||
'client_id' => 'https://claude.ai/oauth/mcp-oauth-client-metadata',
|
||||
'exp' => time() + 60,
|
||||
], 'test-secret');
|
||||
|
||||
RequestContext::recordRoute(
|
||||
null,
|
||||
[],
|
||||
new WP_REST_Request(self::ROUTE, ['authorization' => 'Bearer ' . $token])
|
||||
);
|
||||
|
||||
self::assertSame('https://claude.ai/oauth/mcp-oauth-client-metadata', RequestContext::clientName());
|
||||
}
|
||||
|
||||
public function testClientNameIsEmptyForAnInvalidToken(): void
|
||||
{
|
||||
Functions\when('get_option')->justReturn('test-secret');
|
||||
|
||||
RequestContext::recordRoute(null, [], new WP_REST_Request(self::ROUTE, ['authorization' => 'Bearer garbage']));
|
||||
|
||||
self::assertSame('', RequestContext::clientName());
|
||||
}
|
||||
|
||||
public function testResetCanForceAClientName(): void
|
||||
{
|
||||
RequestContext::reset(true, 'Claude');
|
||||
|
||||
self::assertTrue(RequestContext::isOAuthServerRequest());
|
||||
self::assertSame('Claude', RequestContext::clientName());
|
||||
}
|
||||
|
||||
public function testRegisterHooksBeforeAndAfterCallbacks(): void
|
||||
{
|
||||
Functions\expect('add_filter')
|
||||
->once()
|
||||
->with('rest_request_before_callbacks', [RequestContext::class, 'recordRoute'], 10, 3);
|
||||
Functions\expect('add_filter')
|
||||
->once()
|
||||
->with('rest_request_after_callbacks', [RequestContext::class, 'clearRoute'], 10, 3);
|
||||
|
||||
(new RequestContext())->register();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user