PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
extendify / app / Mcp / Clients.php

Clients.php in Extendify 3.2.2, at app/Mcp/Clients.php

423 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The assistants we can tell someone how to connect, and what to tell them.
5 */
6
7 namespace Extendify\Mcp;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Mcp\OAuth\Metadata;
12
13 class Clients
14 {
15 /**
16 * @return array
17 */
18 public static function all()
19 {
20 return [
21 self::claude(),
22 self::chatgpt(),
23 self::anythingElse(),
24 ];
25 }
26
27 /**
28 * Decoded, or a name like "Tom &amp; Jerry" gets copied with the entity.
29 *
30 * @return string
31 */
32 public static function siteName()
33 {
34 $name = trim(\wp_specialchars_decode(\get_bloginfo('name'), ENT_QUOTES));
35
36 return $name !== '' ? $name : (string) \wp_parse_url(\home_url(), PHP_URL_HOST);
37 }
38
39 /**
40 * Ends by asking for the tool list, so the person learns it worked without us saying so.
41 *
42 * @return string
43 */
44 public static function prompt()
45 {
46 return sprintf(
47 /* translators: %s: this site's MCP address, a URL. The whole line is pasted into an AI assistant. */
48 \__(
49 'Add the MCP server at %s, sign in when prompted, then list the tools you get from it.',
50 'extendify-local'
51 ),
52 Metadata::resource()
53 );
54 }
55
56 /**
57 * The marks WordPress's own Connectors screen draws, so both screens look alike.
58 *
59 * @param array $client - One of the all() entries.
60 * @return string
61 */
62 public static function logo(array $client)
63 {
64 $claude = 'M6.2 21.024L12.416 17.536L12.52 17.232L12.416 17.064H12.112L11.072 17L7.52 16.904L4.44 16.776L1.456 '
65 . '16.616L0.704 16.456L0 15.528L0.072 15.064L0.704 14.64L1.608 14.72L3.608 14.856L6.608 15.064L8.784 15'
66 . '.192L12.008 15.528H12.52L12.592 15.32L12.416 15.192L12.28 15.064L9.176 12.96L5.816 10.736L4.056 9.45'
67 . '6L3.104 8.808L2.624 8.2L2.416 6.872L3.28 5.92L4.44 6L4.736 6.08L5.912 6.984L8.424 8.928L11.704 11.34'
68 . '4L12.184 11.744L12.376 11.608L12.4 11.512L12.184 11.152L10.4 7.928L8.496 4.648L7.648 3.288L7.424 2.4'
69 . '72C7.344 2.136 7.288 1.856 7.288 1.512L8.272 0.176L8.816 0L10.128 0.176L10.68 0.656L11.496 2.52L12.8'
70 . '16 5.456L14.864 9.448L15.464 10.632L15.784 11.728L15.904 12.064H16.112V11.872L16.28 9.624L16.592 6.8'
71 . '64L16.896 3.312L17 2.312L17.496 1.112L18.48 0.464L19.248 0.832L19.88 1.736L19.792 2.32L19.416 4.76L1'
72 . '8.68 8.584L18.2 11.144H18.48L18.8 10.824L20.096 9.104L22.272 6.384L23.232 5.304L24.352 4.112L25.072 '
73 . '3.544H26.432L27.432 5.032L26.984 6.568L25.584 8.344L24.424 9.848L22.76 12.088L21.72 13.88L21.816 14.'
74 . '024L22.064 14L25.824 13.2L27.856 12.832L30.28 12.416L31.376 12.928L31.496 13.448L31.064 14.512L28.47'
75 . '2 15.152L25.432 15.76L20.904 16.832L20.848 16.872L20.912 16.952L22.952 17.144L23.824 17.192H25.96L29'
76 . '.936 17.488L30.976 18.176L31.6 19.016L31.496 19.656L29.896 20.472L27.736 19.96L22.696 18.76L20.968 1'
77 . '8.328H20.728V18.472L22.168 19.88L24.808 22.264L28.112 25.336L28.28 26.096L27.856 26.696L27.408 26.63'
78 . '2L24.504 24.448L23.384 23.464L20.848 21.328H20.68V21.552L21.264 22.408L24.352 27.048L24.512 28.472L2'
79 . '4.288 28.936L23.488 29.216L22.608 29.056L20.8 26.52L18.936 23.664L17.432 21.104L17.248 21.208L16.36 '
80 . '30.768L15.944 31.256L14.984 31.624L14.184 31.016L13.76 30.032L14.184 28.088L14.696 25.552L15.112 23.'
81 . '536L15.488 21.032L15.712 20.2L15.696 20.144L15.512 20.168L13.624 22.76L10.752 26.64L8.48 29.072L7.93'
82 . '6 29.288L6.992 28.8L7.08 27.928L7.608 27.152L10.752 23.152L12.648 20.672L13.872 19.24L13.864 19.032H'
83 . '13.792L5.44 24.456L3.952 24.648L3.312 24.048L3.392 23.064L3.696 22.744L6.208 21.016L6.2 21.024Z';
84 $chatgpt = 'M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.065'
85 . '1 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 '
86 . '0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7'
87 . '718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755'
88 . ' 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.16'
89 . '86a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 '
90 . '1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1'
91 . '-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1'
92 . '.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2'
93 . '.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364l2.0201-1.1685a.0757.0757 0 0 1 .0'
94 . '71 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.4043-.6813zm2.0107-3.0231l'
95 . '-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l'
96 . '4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.056'
97 . '7V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.097'
98 . '6-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z';
99
100 $marks = [
101 'claude' => '<svg viewBox="0 0 32 32"><path fill="#D97757" d="' . $claude . '"/></svg>',
102 'chatgpt' => '<svg viewBox="0 0 24 24"><path fill="currentColor" d="' . $chatgpt . '"/></svg>',
103 ];
104
105 return $marks[$client['id']] ?? '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/>'
106 . '<circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>';
107 }
108
109 /**
110 * @param string $url - Where the link goes.
111 * @return string
112 */
113 private static function newTab($url)
114 {
115 return 'href="' . \esc_url($url) . '" target="_blank" rel="noopener noreferrer"';
116 }
117
118 /**
119 * @param array $substeps - The click path the step's button would have saved.
120 * @return array
121 */
122 private static function buttonFallback(array $substeps)
123 {
124 return [
125 /* translators: opens manual instructions for when the setup button did not work. */
126 'summary' => \__('Button didn\'t work?', 'extendify-local'),
127 'substeps' => $substeps,
128 ];
129 }
130
131 // phpcs:disable Generic.Files.LineLength.TooLong -- one msgid per sentence; the markup can push one past 120.
132 /**
133 * @param string $assistant - The assistant's product name.
134 * @return string
135 */
136 private static function approveOnSite($assistant)
137 {
138 return sprintf(
139 /* translators: %s: an AI assistant's name, such as Claude. Approve is this site's button. */
140 \__(
141 'You\'ll be taken to WordPress to choose what %s can do. Then click <strong>Approve</strong>.',
142 'extendify-local'
143 ),
144 $assistant
145 );
146 }
147
148 /**
149 * @return array
150 */
151 private static function claude()
152 {
153 $dialog = '?' . http_build_query([
154 'modal' => 'add-custom-connector',
155 'connectorName' => self::siteName(),
156 'connectorUrl' => Metadata::resource(),
157 ], '', '&', PHP_QUERY_RFC3986);
158
159 return [
160 'id' => 'claude',
161 'name' => 'Claude',
162 'gate' => [
163 'tone' => 'ok',
164 /* translators: Free is a Claude plan name; keep it in English. */
165 'text' => \__('Works on every Claude plan. The Free plan allows one custom connector.', 'extendify-local'),
166 ],
167 'steps' => [
168 [
169 /* translators: heading of the first setup step; Claude is a product name. */
170 'title' => \__('Add this site to Claude', 'extendify-local'),
171 'links' => [
172 [
173 /* translators: button that opens Claude's add-connector dialog in a new tab. */
174 'label' => \__('Add to Claude', 'extendify-local'),
175 'url' => 'https://claude.ai/customize/connectors' . $dialog,
176 'primary' => true,
177 ],
178 ],
179 'substeps' => [
180 /* translators: Continue is a button label in Claude's add-connector dialog. */
181 \__('Claude opens with the name and address filled in. Click <strong>Continue</strong>.', 'extendify-local'),
182 /* translators: Add is a button label in Claude's add-connector dialog. */
183 \__(
184 'On the next screen, leave the authentication settings as they are and click <strong>Add</strong>.',
185 'extendify-local'
186 ),
187 ],
188 'asides' => [
189 self::buttonFallback([
190 sprintf(
191 /* translators: %s: link attributes, keep them as is. */
192 \__('Open <a %s>Claude\'s connector settings</a>.', 'extendify-local'),
193 self::newTab('https://claude.ai/customize/connectors')
194 ),
195 /* translators: Add custom connector is a label in Claude. */
196 \__('Click <strong>+</strong>, then <strong>Add custom connector</strong>.', 'extendify-local'),
197 [
198 /* translators: Continue is a button label in Claude's add-connector dialog. */
199 'text' => \__('Fill in the name and address, then click <strong>Continue</strong>.', 'extendify-local'),
200 'fields' => [
201 /* translators: the label of the name field an assistant asks for when adding this site. */
202 'name' => \__('Name', 'extendify-local'),
203 /* translators: the label over this site's MCP address. MCP is a protocol name. */
204 'url' => \__('MCP server URL', 'extendify-local'),
205 ],
206 ],
207 /* translators: Add is a button label in Claude's add-connector dialog. */
208 \__('Leave the authentication settings as they are and click <strong>Add</strong>.', 'extendify-local'),
209 ]),
210 [
211 /* translators: opens the setup for Claude's business plans; keep the plan names in English. */
212 'summary' => \__('On a Team or Enterprise plan?', 'extendify-local'),
213 'links' => [
214 [
215 /* translators: button opening Claude's organization settings, which only an owner can reach. */
216 'label' => \__('Add for your organization', 'extendify-local'),
217 'url' => 'https://claude.ai/admin-settings/connectors' . $dialog,
218 'primary' => false,
219 ],
220 ],
221 'substeps' => [
222 sprintf(
223 /* translators: %s: link attributes, keep them as is. Add, Custom and Web are labels in Claude. */
224 \__(
225 'An owner uses the button below to add it once for everyone. If that doesn\'t work, open <a %s>Organization settings</a>, click <strong>Add</strong>, then <strong>Custom</strong> (choose <strong>Web</strong> if asked).',
226 'extendify-local'
227 ),
228 self::newTab('https://claude.ai/admin-settings/connectors')
229 ),
230 sprintf(
231 /* translators: %s: link attributes, keep them as is. Connect is a button label in Claude; translate it. */
232 \__(
233 'Then each member clicks <strong>Connect</strong> next to it in <a %s>Claude\'s connector settings</a> and approves access.',
234 'extendify-local'
235 ),
236 self::newTab('https://claude.ai/customize/connectors')
237 ),
238 ],
239 ],
240 ],
241 ],
242 [
243 /* translators: heading of the setup step, where the person approves the assistant here. */
244 'title' => \__('Approve access', 'extendify-local'),
245 'substeps' => [
246 /* translators: Connect is the button on the screen Claude shows next. */
247 \__('Claude asks you to connect. Click <strong>Connect</strong>.', 'extendify-local'),
248 self::approveOnSite('Claude'),
249 /* translators: describes the Claude screen shown after approving; nothing on it needs changing. */
250 \__(
251 'You\'ll be taken back to Claude, to a screen listing its permissions. You don\'t need to change anything.',
252 'extendify-local'
253 ),
254 ],
255 ],
256 [
257 /* translators: heading of the last setup step, where the connection is used in a chat. */
258 'title' => \__('Use it in a chat', 'extendify-local'),
259 'text' => [
260 /* translators: the quoted request is an example to type into Claude; translate it. */
261 \__('Start a new chat and ask Claude about your site, for example: “List my latest posts.”', 'extendify-local'),
262 /* translators: + and Connectors open Claude's tools menu in a chat. In right-to-left languages, write the arrow as ←. */
263 \__(
264 'If it doesn\'t use your site, click <strong>+</strong> → <strong>Connectors</strong> and check that this site is turned on.',
265 'extendify-local'
266 ),
267 ],
268 ],
269 ],
270 ];
271 }
272
273 /**
274 * @return array
275 */
276 private static function chatgpt()
277 {
278 return [
279 'id' => 'chatgpt',
280 'name' => 'ChatGPT',
281 'gate' => [
282 'tone' => 'warn',
283 /* translators: Business, Enterprise and Edu are OpenAI plan names; keep them in English. */
284 'text' => \__(
285 'On Business, only admins and owners can connect it. On Enterprise and Edu, an admin has to allow it first.',
286 'extendify-local'
287 ),
288 ],
289 'steps' => [
290 [
291 /* translators: heading of a setup step; developer mode is a ChatGPT setting. */
292 'title' => \__('Turn on developer mode', 'extendify-local'),
293 'text' => [
294 /* translators: Settings, Security and login and Developer mode are ChatGPT labels. In right-to-left languages, write the arrow as ←. */
295 \__(
296 'In ChatGPT, open <code>Settings → Security and login</code> and turn on <strong>Developer mode</strong>.',
297 'extendify-local'
298 ),
299 ],
300 ],
301 [
302 /* translators: heading of a setup step where this site is added to ChatGPT as an MCP app. */
303 'title' => \__('Add this site as an MCP app', 'extendify-local'),
304 'substeps' => [
305 /* translators: + is a button in ChatGPT's Plugins page; Browse plugins and Create app are ChatGPT labels. */
306 \__(
307 'Open Plugins with the button below. Click <strong>+</strong> (top right, or next to the search box under Browse plugins) and choose <strong>Create app</strong>.',
308 'extendify-local'
309 ),
310 /* translators: Create MCP app is a ChatGPT button label. */
311 \__('Click <strong>Create MCP app</strong> in the bottom-left corner.', 'extendify-local'),
312 /* translators: Connection and Authentication are ChatGPT field labels; use the same word for Connection as the address field's label below. OAuth is a sign-in standard. */
313 \__(
314 'Give it a name, paste this address into <strong>Connection</strong>, and leave Authentication set to <strong>OAuth</strong>.',
315 'extendify-local'
316 ),
317 ],
318 'links' => [
319 [
320 /* translators: button that opens ChatGPT's Plugins page in a new tab. */
321 'label' => \__('Open ChatGPT Plugins', 'extendify-local'),
322 'url' => 'https://chatgpt.com/plugins',
323 'primary' => true,
324 ],
325 ],
326 'fields' => [
327 /* translators: the label of the name field an assistant asks for when adding this site. */
328 'name' => \__('Name', 'extendify-local'),
329 /* translators: the label over this site's MCP address, as ChatGPT names that field; the step above names it too, so use the same word. */
330 'url' => \__('Connection', 'extendify-local'),
331 ],
332 ],
333 [
334 /* translators: heading of the setup step, where the person approves the assistant here. */
335 'title' => \__('Approve access', 'extendify-local'),
336 'substeps' => [
337 /* translators: the quoted checkbox and Create are ChatGPT labels. */
338 \__(
339 'Check <strong>I understand and want to continue</strong>, then click <strong>Create</strong>.',
340 'extendify-local'
341 ),
342 /* translators: Sign in with is the start of a ChatGPT button label. */
343 \__('Click <strong>Sign in with</strong> followed by your app\'s name.', 'extendify-local')
344 . ' ' . self::approveOnSite('ChatGPT'),
345 /* translators: describes the ChatGPT page shown after approving; nothing on it needs changing. */
346 \__(
347 'You\'ll be taken back to ChatGPT, to the app\'s page listing its permissions. You don\'t need to change anything.',
348 'extendify-local'
349 ),
350 ],
351 ],
352 [
353 /* translators: heading of the last setup step, where the connection is used in a chat. */
354 'title' => \__('Use it in a chat', 'extendify-local'),
355 'text' => [
356 /* translators: @ is typed in ChatGPT's message box; + and More are its chat menu labels. In right-to-left languages, write the arrow as ←. */
357 \__(
358 'Start a new chat, type <strong>@</strong> and pick your site, or open <strong>+</strong> → <strong>More</strong>.',
359 'extendify-local'
360 ),
361 \__('ChatGPT asks before it changes anything on your site.', 'extendify-local'),
362 ],
363 ],
364 ],
365 ];
366 }
367 // phpcs:enable Generic.Files.LineLength.TooLong
368
369 /**
370 * @return array
371 */
372 private static function anythingElse()
373 {
374 return [
375 'id' => 'other',
376 /* translators: the picker tile for any assistant not listed by name, beside Claude and ChatGPT. */
377 'name' => \__('Other', 'extendify-local'),
378 'gate' => [
379 'tone' => 'ok',
380 /* translators: MCP is a protocol name; keep it in English. */
381 'text' => \__(
382 'Works with any AI assistant that can connect to a remote MCP server.',
383 'extendify-local'
384 ),
385 ],
386 'steps' => [
387 [
388 /* translators: heading of a setup step for an assistant not listed by name. */
389 'title' => \__('Add this site\'s address to your assistant', 'extendify-local'),
390 /* translators: MCP is a protocol name; keep it in English. */
391 'text' => \__(
392 'In your assistant\'s MCP or connector settings, add a remote server with this address.',
393 'extendify-local'
394 ),
395 'fields' => [
396 /* translators: the label over this site's MCP address. MCP is a protocol name. */
397 'url' => \__('MCP server URL', 'extendify-local'),
398 ],
399 ],
400 [
401 /* translators: heading of an optional setup step: the assistant configures itself. */
402 'title' => \__('Or let it set itself up', 'extendify-local'),
403 /* translators: coding tools are AI assistants that run in a code editor or a terminal. */
404 'text' => \__(
405 'Assistants that can change their own settings, like coding tools, can use this prompt.',
406 'extendify-local'
407 ),
408 'fields' => ['prompt' => ''],
409 ],
410 [
411 /* translators: heading of the setup step, where the person approves the assistant here. */
412 'title' => \__('Approve access', 'extendify-local'),
413 /* translators: Approve is the button on this site's own sign-in page. */
414 'text' => \__(
415 'You\'ll be taken to WordPress. Sign in if prompted, then click <strong>Approve</strong>.',
416 'extendify-local'
417 ),
418 ],
419 ],
420 ];
421 }
422 }
423