PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / mcp-server / Server / McpServer.php

McpServer.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/mcp-server/Server/McpServer.php

183 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * JSON-RPC method dispatch for the built-in MCP server (spec 044, FR-002–FR-006).
4 *
5 * @package Templately\Modules\McpServer\Server
6 */
7
8 namespace Templately\Modules\McpServer\Server;
9
10 use Templately\Modules\McpCore\Registry\ToolRegistry;
11 use WP_Error;
12
13 class McpServer {
14
15 /**
16 * Protocol revisions this server speaks, NEWEST FIRST.
17 *
18 * Negotiated rather than asserted (FR-003): if the client names one of these
19 * it is echoed back, otherwise the newest is offered and the client decides.
20 * The reference implementation returns a hardcoded revision regardless of
21 * what the client asked for, silently misrepresenting itself.
22 *
23 * @var string[]
24 */
25 const SUPPORTED_PROTOCOLS = [
26 '2025-06-18',
27 '2025-03-26',
28 '2024-11-05',
29 ];
30
31 /**
32 * Dispatch one JSON-RPC message.
33 *
34 * @param array $message
35 * @param string $access_level
36 * @param string|null $credential_id
37 * @return array|null Null for notifications (no reply is sent).
38 */
39 public static function dispatch( array $message, string $access_level, ?string $credential_id = null ): ?array {
40 $is_notification = JsonRpc::is_notification( $message );
41 $id = $message['id'] ?? null;
42 $method = isset( $message['method'] ) && is_string( $message['method'] ) ? $message['method'] : '';
43 $params = isset( $message['params'] ) && is_array( $message['params'] ) ? $message['params'] : [];
44
45 switch ( $method ) {
46 case 'initialize':
47 $response = self::initialize( $id, $params );
48 break;
49
50 case 'ping':
51 $response = JsonRpc::result( $id, (object) [] );
52 break;
53
54 case 'tools/list':
55 $response = JsonRpc::result( $id, [ 'tools' => self::tool_listing() ] );
56 break;
57
58 case 'tools/call':
59 $response = self::call_tool( $id, $params, $access_level, $credential_id );
60 break;
61
62 default:
63 // Anything under notifications/* gets no reply, ever.
64 if ( 0 === strpos( $method, 'notifications/' ) ) {
65 return null;
66 }
67
68 $response = JsonRpc::error(
69 $id,
70 JsonRpc::METHOD_NOT_FOUND,
71 sprintf(
72 /* translators: %s: JSON-RPC method name. */
73 __( 'Method not found: %s', 'templately' ),
74 $method
75 )
76 );
77 }
78
79 // A message with no `id` is a NOTIFICATION: it is still processed, but no
80 // reply is ever sent (FR-002, JSON-RPC 2.0 §4.1). This was previously
81 // checked only in the default branch, so a notification naming a method
82 // this server implements — `ping`, `initialize`, `tools/list`,
83 // `tools/call` — was answered anyway, with `id: null`, which the spec
84 // reserves for replies to requests that could not be parsed at all.
85 return $is_notification ? null : $response;
86 }
87
88 /**
89 * @param mixed $id
90 * @param array $params
91 * @return array
92 */
93 private static function initialize( $id, array $params ): array {
94 $requested = isset( $params['protocolVersion'] ) && is_string( $params['protocolVersion'] )
95 ? $params['protocolVersion']
96 : '';
97
98 $negotiated = in_array( $requested, self::SUPPORTED_PROTOCOLS, true )
99 ? $requested
100 : self::SUPPORTED_PROTOCOLS[0];
101
102 return JsonRpc::result(
103 $id,
104 [
105 'protocolVersion' => $negotiated,
106 // Only what is actually served is announced (FR-006) — no
107 // resources or prompts, so a client never probes for them.
108 'capabilities' => [
109 'tools' => [ 'listChanged' => false ],
110 ],
111 'serverInfo' => [
112 'name' => 'templately',
113 'version' => defined( 'TEMPLATELY_VERSION' ) ? TEMPLATELY_VERSION : '1.0.0',
114 ],
115 ]
116 );
117 }
118
119 /**
120 * Agent-facing projection. Never leaks callbacks or the access level.
121 *
122 * @return array
123 */
124 private static function tool_listing(): array {
125 $tools = [];
126
127 foreach ( ToolRegistry::get_instance()->all() as $descriptor ) {
128 $tools[] = $descriptor->to_tool_listing();
129 }
130
131 return $tools;
132 }
133
134 /**
135 * @param mixed $id
136 * @param array $params
137 * @param string $access_level
138 * @param string|null $credential_id
139 * @return array
140 */
141 private static function call_tool( $id, array $params, string $access_level, ?string $credential_id ): array {
142 $name = isset( $params['name'] ) && is_string( $params['name'] ) ? $params['name'] : '';
143
144 if ( '' === $name ) {
145 return JsonRpc::error( $id, JsonRpc::INVALID_PARAMS, __( 'Missing tool name.', 'templately' ) );
146 }
147
148 $registry = ToolRegistry::get_instance();
149
150 if ( null === $registry->get( $name ) ) {
151 return JsonRpc::error(
152 $id,
153 JsonRpc::METHOD_NOT_FOUND,
154 sprintf(
155 /* translators: %s: capability name. */
156 __( 'Unknown tool: %s', 'templately' ),
157 $name
158 )
159 );
160 }
161
162 $arguments = isset( $params['arguments'] ) && is_array( $params['arguments'] ) ? $params['arguments'] : [];
163 $result = $registry->execute( $name, $arguments, $access_level, $credential_id );
164
165 if ( $result instanceof WP_Error ) {
166 $status = (int) ( $result->get_error_data()['status'] ?? 0 );
167
168 // A refusal to run at all is a protocol-level error; a failure while
169 // running is a completed exchange carrying isError (FR-005).
170 if ( in_array( $status, [ 400, 403, 404 ], true ) ) {
171 $code = ( 403 === $status ) ? JsonRpc::UNAUTHORIZED
172 : ( ( 404 === $status ) ? JsonRpc::METHOD_NOT_FOUND : JsonRpc::INVALID_PARAMS );
173
174 return JsonRpc::error( $id, $code, $result->get_error_message() );
175 }
176
177 return JsonRpc::tool_error( $id, $result->get_error_message() );
178 }
179
180 return JsonRpc::tool_result( $id, $result );
181 }
182 }
183