Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions includes/Core/McpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ class McpServer {
*
* @var \WP\MCP\Infrastructure\ErrorHandling\Contracts\McpErrorHandlerInterface
*/
public McpErrorHandlerInterface $error_handler;
private McpErrorHandlerInterface $error_handler;

/**
* Observability handler instance.
*
* @var \WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface
*/
public McpObservabilityHandlerInterface $observability_handler;
private McpObservabilityHandlerInterface $observability_handler;

/**
* Server ID.
Expand Down
2 changes: 2 additions & 0 deletions includes/Domain/Prompts/RegisterAbilityAsMcpPrompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* @package McpAdapter
*/

declare( strict_types=1 );

namespace WP\MCP\Domain\Prompts;

use WP\MCP\Domain\Utils\McpNameSanitizer;
Expand Down
22 changes: 11 additions & 11 deletions includes/Handlers/Prompts/PromptsHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ class PromptsHandler {
*
* @var list<string>
*/
private static $valid_content_types = array( 'text', 'image', 'audio', 'resource_link', 'resource' );
private static array $valid_content_types = array( 'text', 'image', 'audio', 'resource_link', 'resource' );

/**
* Valid role values for PromptMessage.
*
* @var list<string>
*/
private static $valid_roles = array( 'user', 'assistant' );
private static array $valid_roles = array( 'user', 'assistant' );

/**
* Default role for messages when not specified.
*
* @var string
*/
private static $default_role = 'user';
private static string $default_role = 'user';

/**
* The WordPress MCP instance.
Expand Down Expand Up @@ -85,7 +85,7 @@ public function list_prompts(): ListPromptsResult {
apply_filters( 'mcp_adapter_prompts_list', $prompts, $this->mcp ),
$prompts,
'mcp_adapter_prompts_list',
$this->mcp->error_handler
$this->mcp->get_error_handler()
);

return ListPromptsResult::fromArray(
Expand Down Expand Up @@ -180,7 +180,7 @@ public function get_prompt( array $params, $request_id = 0 ) {
$result = apply_filters( 'mcp_adapter_prompt_get_result', $result, $arguments, $prompt_name, $mcp_prompt, $this->mcp );

if ( is_wp_error( $result ) ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Prompt execution returned WP_Error',
array(
'prompt_name' => $prompt_name,
Expand All @@ -194,7 +194,7 @@ public function get_prompt( array $params, $request_id = 0 ) {

return $this->normalize_result_to_dto( $result, $prompt, $prompt_name );
} catch ( \Throwable $e ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Prompt execution failed',
array(
'prompt_name' => $prompt_name,
Expand Down Expand Up @@ -278,7 +278,7 @@ private function normalize_tier1_messages(

foreach ( $result['messages'] as $index => $message ) {
if ( ! is_array( $message ) ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Invalid message structure in prompt result, skipping',
array(
'prompt_name' => $prompt_name,
Expand Down Expand Up @@ -450,7 +450,7 @@ private function normalize_tier5_fallback(
string $prompt_name
): GetPromptResult {
// Log observability event for fallback normalization.
$this->mcp->observability_handler->record_event(
$this->mcp->get_observability_handler()->record_event(
'prompt_result_fallback_normalization',
array(
'prompt_name' => $prompt_name,
Expand Down Expand Up @@ -536,7 +536,7 @@ private function validate_content_type( array $content, string $prompt_name ): a

// Check if type is missing.
if ( null === $type || '' === $type ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Missing content type in prompt result, defaulting to text',
array(
'prompt_name' => $prompt_name,
Expand All @@ -554,7 +554,7 @@ private function validate_content_type( array $content, string $prompt_name ): a

// Check if type is valid.
if ( ! in_array( $type, self::$valid_content_types, true ) ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Invalid content type in prompt result, converting to text',
array(
'prompt_name' => $prompt_name,
Expand Down Expand Up @@ -596,7 +596,7 @@ private function validate_role( string $role, string $prompt_name ): string {
}

if ( '' !== $prompt_name ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Invalid role in prompt message, defaulting to user',
array(
'prompt_name' => $prompt_name,
Expand Down
6 changes: 3 additions & 3 deletions includes/Handlers/Resources/ResourcesHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function list_resources(): ListResourcesResult {
apply_filters( 'mcp_adapter_resources_list', $resources, $this->mcp ),
$resources,
'mcp_adapter_resources_list',
$this->mcp->error_handler
$this->mcp->get_error_handler()
);

return ListResourcesResult::fromArray(
Expand Down Expand Up @@ -162,7 +162,7 @@ public function read_resource( array $params, $request_id = 0 ) {

// Handle WP_Error objects returned by McpResource execution.
if ( is_wp_error( $contents ) ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Resource execution returned WP_Error object',
array(
'uri' => $uri,
Expand All @@ -186,7 +186,7 @@ public function read_resource( array $params, $request_id = 0 ) {
)
);
} catch ( \Throwable $exception ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Error reading resource',
array(
'uri' => $uri,
Expand Down
10 changes: 5 additions & 5 deletions includes/Handlers/Tools/ToolsHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function list_tools(): ListToolsResult {
apply_filters( 'mcp_adapter_tools_list', $tools, $this->mcp ),
$tools,
'mcp_adapter_tools_list',
$this->mcp->error_handler
$this->mcp->get_error_handler()
);

return ListToolsResult::fromArray(
Expand Down Expand Up @@ -134,7 +134,7 @@ public function call_tool( array $params, $request_id = 0 ) {

$mcp_tool = $this->mcp->get_mcp_tool( $tool_name );
if ( ! $mcp_tool ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Tool not found',
array(
'tool_name' => $tool_name,
Expand All @@ -151,7 +151,7 @@ public function call_tool( array $params, $request_id = 0 ) {
if ( is_wp_error( $permission ) ) {
$error_message = $permission->get_error_message();

$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Tool permission check failed',
array(
'tool_name' => $tool_name,
Expand Down Expand Up @@ -205,7 +205,7 @@ public function call_tool( array $params, $request_id = 0 ) {
$result = apply_filters( 'mcp_adapter_tool_call_result', $result, $args, $tool_name, $mcp_tool, $this->mcp );

if ( is_wp_error( $result ) ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Tool execution returned WP_Error',
array(
'tool_name' => $tool_name,
Expand Down Expand Up @@ -309,7 +309,7 @@ public function call_tool( array $params, $request_id = 0 ) {
)
);
} catch ( \Throwable $exception ) {
$this->mcp->error_handler->log(
$this->mcp->get_error_handler()->log(
'Error calling tool',
array(
'tool' => $request_params['name'],
Expand Down
10 changes: 5 additions & 5 deletions includes/Transport/HttpTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function __construct( McpTransportContext $transport_context ) {
*/
public function register_routes(): void {
// Get server info from request handler's transport context
$server = $this->request_handler->transport_context->mcp_server;
$server = $this->request_handler->get_transport_context()->mcp_server;

// Single endpoint for MCP communication (POST, GET reserved for SSE, DELETE for session termination).
// Do not remove GET: it is part of the MCP HTTP transport shape and will be implemented (SSE) in a future iteration.
Expand All @@ -78,7 +78,7 @@ public function check_permission( \WP_REST_Request $request ) {
$context = new HttpRequestContext( $request );

// Check permission using callback or default
$transport_context = $this->request_handler->transport_context;
$transport_context = $this->request_handler->get_transport_context();

if ( null !== $transport_context->transport_permission_callback ) {
try {
Expand All @@ -91,15 +91,15 @@ public function check_permission( \WP_REST_Request $request ) {
}

// Log the error and deny access (fail-closed)
$this->request_handler->transport_context->error_handler->log(
$this->request_handler->get_transport_context()->error_handler->log(
'Permission callback returned WP_Error: ' . $result->get_error_message(),
array( 'HttpTransport::check_permission' )
);

return false;
} catch ( \Throwable $e ) {
// Log the error and deny access (fail-closed)
$this->request_handler->transport_context->error_handler->log( 'Error in transport permission callback: ' . $e->getMessage(), array( 'HttpTransport::check_permission' ) );
$this->request_handler->get_transport_context()->error_handler->log( 'Error in transport permission callback: ' . $e->getMessage(), array( 'HttpTransport::check_permission' ) );

return false;
}
Expand Down Expand Up @@ -127,7 +127,7 @@ public function check_permission( \WP_REST_Request $request ) {

if ( ! $user_has_capability ) {
$user_id = get_current_user_id();
$this->request_handler->transport_context->error_handler->log(
$this->request_handler->get_transport_context()->error_handler->log(
sprintf( 'Permission denied for MCP API access. User ID %d does not have capability "%s"', $user_id, $user_capability ),
array( 'HttpTransport::check_permission' )
);
Expand Down
15 changes: 13 additions & 2 deletions includes/Transport/Infrastructure/HttpRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HttpRequestHandler {
*
* @var \WP\MCP\Transport\Infrastructure\McpTransportContext
*/
public McpTransportContext $transport_context;
private McpTransportContext $transport_context;

/**
* Constructor.
Expand All @@ -38,6 +38,17 @@ public function __construct( McpTransportContext $transport_context ) {
$this->transport_context = $transport_context;
}

/**
* Get the transport context.
*
* @since n.e.x.t
*
* @return \WP\MCP\Transport\Infrastructure\McpTransportContext
*/
public function get_transport_context(): McpTransportContext {
return $this->transport_context;
}

/**
* Route HTTP request to appropriate handler.
*
Expand Down Expand Up @@ -88,7 +99,7 @@ private function handle_mcp_request( HttpRequestContext $context ): \WP_REST_Res

return $this->process_mcp_messages( $context );
} catch ( \Throwable $exception ) {
$this->transport_context->mcp_server->error_handler->log(
$this->transport_context->mcp_server->get_error_handler()->log(
'Unexpected error in handle_mcp_request',
array(
'transport' => static::class,
Expand Down
98 changes: 80 additions & 18 deletions includes/Transport/Infrastructure/McpTransportContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,32 @@
class McpTransportContext {

/**
* Initialize the transport context.
* Required property keys for the constructor array.
*
* @var list<string>
*/
// phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition -- False positive: sniff mistakes array() commas for multi-const commas (only handles short syntax).
private const REQUIRED_KEYS = array(
'mcp_server',
'initialize_handler',
'tools_handler',
'resources_handler',
'prompts_handler',
'system_handler',
'observability_handler',
);

/**
* Optional property keys for the constructor array.
*
* @param \WP\MCP\Core\McpServer $mcp_server The MCP server instance.
* @param \WP\MCP\Handlers\Initialize\InitializeHandler $initialize_handler The initialize handler.
* @param \WP\MCP\Handlers\Tools\ToolsHandler $tools_handler The tools handler.
* @param \WP\MCP\Handlers\Resources\ResourcesHandler $resources_handler The resources handler.
* @param \WP\MCP\Handlers\Prompts\PromptsHandler $prompts_handler The prompts handler.
* @param \WP\MCP\Handlers\System\SystemHandler $system_handler The system handler.
* @param string $observability_handler The observability handler class name.
* @param \WP\MCP\Transport\Infrastructure\RequestRouter|null $request_router The request router service.
* @param callable|null $transport_permission_callback Optional custom permission callback for transport-level authentication.
* @var list<string>
*/
// phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition -- False positive: sniff mistakes array() commas for multi-const commas (only handles short syntax).
private const OPTIONAL_KEYS = array(
'request_router',
'transport_permission_callback',
'error_handler',
);

/**
* The MCP server instance.
Expand Down Expand Up @@ -128,18 +142,66 @@ class McpTransportContext {
* error_handler?: \WP\MCP\Infrastructure\ErrorHandling\Contracts\McpErrorHandlerInterface
* } $properties Properties to set on the context.
* Note: request_router is optional and will be auto-created if not provided.
*
* @throws \InvalidArgumentException If required keys are missing or unknown keys are present.
*
* @since n.e.x.t
*/
public function __construct( array $properties ) {
foreach ( $properties as $name => $value ) {
$this->$name = $value;
}
$this->validate_properties( $properties );

// Assign required properties.
$this->mcp_server = $properties['mcp_server'];
$this->initialize_handler = $properties['initialize_handler'];
$this->tools_handler = $properties['tools_handler'];
$this->resources_handler = $properties['resources_handler'];
$this->prompts_handler = $properties['prompts_handler'];
$this->system_handler = $properties['system_handler'];
$this->observability_handler = $properties['observability_handler'];

// Assign optional properties (error_handler defaults to the server's handler).
$this->error_handler = $properties['error_handler'] ?? $properties['mcp_server']->get_error_handler();

// If request_router is provided, we're done
if ( isset( $properties['request_router'] ) ) {
return;
$this->transport_permission_callback = $properties['transport_permission_callback'] ?? null;

// Create request_router if not provided.
$this->request_router = $properties['request_router'] ?? new RequestRouter( $this );
}

/**
* Validate that the properties array contains all required keys and no unknown keys.
*
* @param array<string, mixed> $properties Properties to validate.
*
* @throws \InvalidArgumentException If required keys are missing or unknown keys are present.
*
* @since n.e.x.t
*/
private function validate_properties( array $properties ): void {
$provided_keys = array_keys( $properties );
$allowed_keys = array_merge( self::REQUIRED_KEYS, self::OPTIONAL_KEYS );

// Check for unknown keys.
$unknown_keys = array_diff( $provided_keys, $allowed_keys );
if ( ! empty( $unknown_keys ) ) {
throw new \InvalidArgumentException(
sprintf(
'Unknown properties provided to McpTransportContext: %1$s. Allowed properties: %2$s.',
esc_html( implode( ', ', $unknown_keys ) ),
esc_html( implode( ', ', $allowed_keys ) )
)
);
}

// Create request_router if not provided
$this->request_router = new RequestRouter( $this );
// Check for missing required keys.
$missing_keys = array_diff( self::REQUIRED_KEYS, $provided_keys );
if ( ! empty( $missing_keys ) ) {
throw new \InvalidArgumentException(
sprintf(
'Missing required properties for McpTransportContext: %s.',
esc_html( implode( ', ', $missing_keys ) )
)
);
}
}
}
Loading
Loading