PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / vendor / wordpress / mcp-adapter / includes / Handlers / Resources / ResourcesHandler.php

ResourcesHandler.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at vendor/wordpress/mcp-adapter/includes/Handlers/Resources/ResourcesHandler.php

282 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Resources method handlers for MCP requests.
4 *
5 * @package McpAdapter
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Handlers\Resources;
11
12 use WP\MCP\Core\McpServer;
13 use WP\MCP\Handlers\HandlerHelperTrait;
14 use WP\MCP\Infrastructure\ErrorHandling\McpErrorFactory;
15 use WP\McpSchema\Common\Protocol\DTO\BlobResourceContents;
16 use WP\McpSchema\Common\Protocol\DTO\TextResourceContents;
17 use WP\McpSchema\Server\Resources\DTO\ListResourcesResult;
18 use WP\McpSchema\Server\Resources\DTO\ReadResourceResult;
19
20 /**
21 * Handles resources-related MCP methods.
22 */
23 class ResourcesHandler {
24 use HandlerHelperTrait;
25
26 /**
27 * The WordPress MCP instance.
28 *
29 * @var \WP\MCP\Core\McpServer
30 */
31 private McpServer $mcp;
32
33 /**
34 * Constructor.
35 *
36 * @param \WP\MCP\Core\McpServer $mcp The WordPress MCP instance.
37 */
38 public function __construct( McpServer $mcp ) {
39 $this->mcp = $mcp;
40 }
41
42
43 /**
44 * Handles the resources/list request.
45 *
46 * Returns a ListResourcesResult DTO containing all registered resources.
47 * Returns protocol DTOs as-is; any `_meta` fields are passed through unchanged.
48 *
49 * @return \WP\McpSchema\Server\Resources\DTO\ListResourcesResult Response with resources list.
50 */
51 public function list_resources(): ListResourcesResult {
52 $resources = array_values( $this->mcp->get_resources() );
53
54 /**
55 * Filters the list of resources before returning to the client.
56 *
57 * Use this filter to filter resources by context, add dynamic resources,
58 * or reorder the resources list.
59 *
60 * @since 0.5.0
61 *
62 * @param array<\WP\McpSchema\Server\Resources\DTO\Resource> $resources Array of Resource DTOs.
63 * @param \WP\MCP\Core\McpServer $server The MCP server instance.
64 */
65 $resources = $this->validate_filtered_list(
66 apply_filters( 'mcp_adapter_resources_list', $resources, $this->mcp ),
67 $resources,
68 'mcp_adapter_resources_list',
69 $this->mcp->get_error_handler()
70 );
71
72 return ListResourcesResult::fromArray(
73 array(
74 'resources' => $resources,
75 )
76 );
77 }
78
79 /**
80 * Handles the resources/read request.
81 *
82 * Returns either a ReadResourceResult DTO (for success) or a JSONRPCErrorResponse DTO
83 * (for protocol errors like missing parameter or resource not found).
84 *
85 * Unlike tools, resources don't have a concept of "execution errors" that should be
86 * reported with isError=true. Resource reads either succeed or fail at the protocol level.
87 *
88 * @param array $params Request parameters.
89 * @param string|int|null $request_id Optional. The request ID for JSON-RPC. Default 0.
90 *
91 * @return \WP\McpSchema\Server\Resources\DTO\ReadResourceResult|\WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
92 */
93 public function read_resource( array $params, $request_id = 0 ) {
94 // Extract parameters using helper method.
95 $request_params = $this->extract_params( $params );
96
97 if ( ! isset( $request_params['uri'] ) ) {
98 return McpErrorFactory::missing_parameter( $request_id, 'uri' );
99 }
100
101 $uri = $request_params['uri'];
102 $uri = is_string( $uri ) ? trim( $uri ) : '';
103
104 $mcp_resource = $this->mcp->get_mcp_resource( $uri );
105 if ( ! $mcp_resource ) {
106 return McpErrorFactory::resource_not_found( $request_id, $uri );
107 }
108
109 /** @var \WP\McpSchema\Server\Resources\DTO\Resource $resource */
110 $resource = $mcp_resource->get_protocol_dto();
111
112 try {
113 $has_permission = $mcp_resource->check_permission( $request_params );
114 if ( true !== $has_permission ) {
115 // Extract detailed error message if WP_Error was returned.
116 $error_message = 'Access denied for resource: ' . $resource->getName();
117
118 if ( is_wp_error( $has_permission ) ) {
119 $error_message = $has_permission->get_error_message();
120 }
121
122 return McpErrorFactory::permission_denied( $request_id, $error_message );
123 }
124
125 /**
126 * Filters resource parameters before execution, or short-circuits execution entirely.
127 *
128 * Return the (optionally modified) parameters array to proceed with execution,
129 * or return a WP_Error to block execution and return an error to the client.
130 *
131 * @since 0.5.0
132 *
133 * @param array $params The request parameters.
134 * @param string $uri The resource URI.
135 * @param \WP\MCP\Domain\Resources\McpResource $mcp_resource The MCP resource instance.
136 * @param \WP\MCP\Core\McpServer $server The MCP server instance.
137 */
138 $request_params = apply_filters( 'mcp_adapter_pre_resource_read', $request_params, $uri, $mcp_resource, $this->mcp );
139
140 // Allow pre-filter to short-circuit execution by returning WP_Error.
141 if ( is_wp_error( $request_params ) ) {
142 return McpErrorFactory::internal_error( $request_id, $request_params->get_error_message() );
143 }
144
145 $contents = $mcp_resource->execute( $request_params );
146
147 /**
148 * Filters the resource contents after execution.
149 *
150 * Use this filter for content transformation, caching storage,
151 * PII redaction, or audit logging.
152 *
153 * @since 0.5.0
154 *
155 * @param mixed|\WP_Error $contents The raw resource contents (may be WP_Error).
156 * @param array $params The request parameters used.
157 * @param string $uri The resource URI.
158 * @param \WP\MCP\Domain\Resources\McpResource $mcp_resource The MCP resource instance.
159 * @param \WP\MCP\Core\McpServer $server The MCP server instance.
160 */
161 $contents = apply_filters( 'mcp_adapter_resource_read_result', $contents, $request_params, $uri, $mcp_resource, $this->mcp );
162
163 // Handle WP_Error objects returned by McpResource execution.
164 if ( is_wp_error( $contents ) ) {
165 $this->mcp->get_error_handler()->log(
166 'Resource execution returned WP_Error object',
167 array(
168 'uri' => $uri,
169 'error_code' => $contents->get_error_code(),
170 'error_message' => $contents->get_error_message(),
171 )
172 );
173
174 return McpErrorFactory::internal_error( $request_id, $contents->get_error_message() );
175 }
176
177 // Successful execution - convert contents to DTOs.
178 // Contents should be an array of resource content items.
179 // If it's already an array of properly formatted items, convert each to a DTO.
180 // Otherwise, wrap the result as text content.
181 $content_dtos = $this->convert_contents_to_dtos( $contents, $uri );
182
183 return ReadResourceResult::fromArray(
184 array(
185 'contents' => $content_dtos,
186 )
187 );
188 } catch ( \Throwable $exception ) {
189 $this->mcp->get_error_handler()->log(
190 'Error reading resource',
191 array(
192 'uri' => $uri,
193 'exception' => $exception->getMessage(),
194 )
195 );
196
197 return McpErrorFactory::internal_error( $request_id, 'Failed to read resource' );
198 }
199 }
200
201 /**
202 * Convert ability execution results to resource content DTOs.
203 *
204 * The MCP spec expects contents to be an array of TextResourceContents or BlobResourceContents.
205 * This method handles various return formats from abilities and normalizes them.
206 *
207 * @param mixed $contents The contents returned by the ability.
208 * @param string $uri The resource URI.
209 *
210 * @return array<\WP\McpSchema\Common\Protocol\DTO\TextResourceContents|\WP\McpSchema\Common\Protocol\DTO\BlobResourceContents>
211 */
212 private function convert_contents_to_dtos( $contents, string $uri ): array {
213 // If contents is already an array of properly structured items, convert each.
214 if ( is_array( $contents ) && ! empty( $contents ) ) {
215 // Check if this is an array of content items (has 'uri' or 'text' keys in first item).
216 $first_item = reset( $contents );
217 if ( is_array( $first_item ) && ( isset( $first_item['uri'] ) || isset( $first_item['text'] ) ) ) {
218 return array_map(
219 function ( $item ) use ( $uri ) {
220 return $this->create_content_dto( $item, $uri );
221 },
222 $contents
223 );
224 }
225 }
226
227 // Fallback: wrap as a single text content item.
228 if ( is_string( $contents ) ) {
229 $text = $contents;
230 } else {
231 $text = wp_json_encode( $contents );
232 if ( false === $text ) {
233 $text = '{}';
234 }
235 }
236
237 return array(
238 TextResourceContents::fromArray(
239 array(
240 'uri' => $uri,
241 'text' => $text,
242 )
243 ),
244 );
245 }
246
247 /**
248 * Create a content DTO from an array item.
249 *
250 * @param array $item The content item array.
251 * @param string $default_uri The default URI to use if not specified.
252 *
253 * @return \WP\McpSchema\Common\Protocol\DTO\TextResourceContents|\WP\McpSchema\Common\Protocol\DTO\BlobResourceContents
254 */
255 private function create_content_dto( array $item, string $default_uri ) {
256 $item_uri = $item['uri'] ?? $default_uri;
257 $mime_type = $item['mimeType'] ?? null;
258
259 // If there's blob data, create BlobResourceContents.
260 if ( isset( $item['blob'] ) ) {
261 return BlobResourceContents::fromArray(
262 array(
263 'uri' => $item_uri,
264 'blob' => (string) $item['blob'],
265 'mimeType' => is_string( $mime_type ) ? $mime_type : null,
266 )
267 );
268 }
269
270 // Default to TextResourceContents.
271 $text = $item['text'] ?? '';
272
273 return TextResourceContents::fromArray(
274 array(
275 'uri' => $item_uri,
276 'text' => (string) $text,
277 'mimeType' => is_string( $mime_type ) ? $mime_type : null,
278 )
279 );
280 }
281 }
282