PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.0.15
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.0.15
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / app / Modules / MCP / AbilitiesRegistrar.php

AbilitiesRegistrar.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.0.15, at app/Modules/MCP/AbilitiesRegistrar.php

560 lines 32.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Modules\MCP;
4
5 use FluentBoards\App\Modules\MCP\Tools\BoardTools;
6 use FluentBoards\App\Modules\MCP\Tools\CommentTools;
7 use FluentBoards\App\Modules\MCP\Tools\ContextTools;
8 use FluentBoards\App\Modules\MCP\Tools\LabelTools;
9 use FluentBoards\App\Modules\MCP\Tools\StageTools;
10 use FluentBoards\App\Modules\MCP\Tools\TaskQueryTools;
11 use FluentBoards\App\Modules\MCP\Tools\TaskTools;
12 use FluentBoards\App\Services\PermissionManager;
13
14 /**
15 * Single source of truth for Fluent Boards MCP abilities.
16 */
17 class AbilitiesRegistrar
18 {
19 public static function getDefinitions()
20 {
21 return [
22 'fluent-boards/get-fluentboards-context' => [
23 'label' => __('Get FluentBoards Context', 'fluent-boards'),
24 'description' => __('Discovery. Returns current user, site metadata, permissions, enums, safety levels, rate hints, and usage guidelines. Call once per session.', 'fluent-boards'),
25 'input_schema' => [
26 'type' => 'object',
27 'properties' => new \stdClass(),
28 ],
29 'execute_callback' => [ContextTools::class, 'getContext'],
30 'permission_callback' => function () {
31 return PermissionManager::hasAppAccess();
32 },
33 'annotations' => ['readonly' => true],
34 ],
35
36 'fluent-boards/list-boards' => [
37 'label' => __('List Boards', 'fluent-boards'),
38 'description' => __('List boards visible to the current user. Supports search, type, archived filter, pagination, and compact counts.', 'fluent-boards'),
39 'input_schema' => [
40 'type' => 'object',
41 'properties' => [
42 'search' => ['type' => 'string'],
43 'type' => ['type' => 'string', 'enum' => ['to-do', 'roadmap']],
44 'include_archived' => ['type' => 'boolean', 'default' => false],
45 'sort_by' => ['type' => 'string', 'enum' => ['id', 'title', 'created_at', 'updated_at'], 'default' => 'created_at'],
46 'sort_type' => ['type' => 'string', 'enum' => ['ASC', 'DESC'], 'default' => 'DESC'],
47 'page' => ['type' => 'integer', 'default' => 1],
48 'per_page' => ['type' => 'integer', 'default' => 20, 'description' => 'Max 100.'],
49 ],
50 ],
51 'execute_callback' => [BoardTools::class, 'listBoards'],
52 'permission_callback' => function () {
53 return PermissionManager::userHasAnyBoardAccess();
54 },
55 'annotations' => ['readonly' => true],
56 ],
57
58 'fluent-boards/get-board' => [
59 'label' => __('Get Board', 'fluent-boards'),
60 'description' => __('Board details. Includes stages, labels, members, and optional task summary for one board.', 'fluent-boards'),
61 'input_schema' => [
62 'type' => 'object',
63 'properties' => [
64 'board_id' => ['type' => 'integer'],
65 'include_tasks' => ['type' => 'boolean', 'default' => false],
66 ],
67 'required' => ['board_id'],
68 ],
69 'execute_callback' => [BoardTools::class, 'getBoard'],
70 'permission_callback' => function ($params = []) {
71 return BoardTools::canReadBoard($params);
72 },
73 'annotations' => ['readonly' => true],
74 ],
75
76 'fluent-boards/create-board' => [
77 'label' => __('Create Board', 'fluent-boards'),
78 'description' => __('Create a board using Fluent Boards board-creation permission. Accepts custom stages, labels and members; falls back to the default stages and labels when they are omitted. Fires native board-created hooks.', 'fluent-boards'),
79 'input_schema' => [
80 'type' => 'object',
81 'properties' => [
82 'title' => ['type' => 'string'],
83 'description' => ['type' => 'string'],
84 'type' => ['type' => 'string', 'enum' => ['to-do', 'roadmap'], 'default' => 'to-do'],
85 'currency' => ['type' => 'string', 'default' => 'USD'],
86 'crm_contact_id' => ['type' => 'integer'],
87 'folder_id' => ['type' => 'integer', 'description' => 'Optional folder id.'],
88 'stages' => [
89 'type' => 'array',
90 'description' => 'Optional stages, in order, for both board types. Omit to get the default stages.',
91 'items' => [
92 'type' => 'object',
93 'properties' => [
94 'title' => ['type' => 'string'],
95 'slug' => ['type' => 'string'],
96 'position' => ['type' => 'integer'],
97 'default_task_status' => [
98 'type' => 'string',
99 'enum' => ['open', 'closed'],
100 'description' => 'Status given to tasks landing in this stage. Defaults to open, or closed for stages titled Completed/Done. Ignored for roadmap boards.',
101 ],
102 ],
103 'required' => ['title'],
104 ],
105 ],
106 'labels' => [
107 'type' => 'array',
108 'description' => 'Optional named labels. Omit to get the five default colour labels.',
109 'items' => [
110 'type' => 'object',
111 'properties' => [
112 'title' => ['type' => 'string'],
113 'bg_color' => ['type' => 'string', 'description' => 'Hex background colour. Defaults to #f3f4f6.'],
114 'color' => ['type' => 'string', 'description' => 'Hex text colour. Defaults to #1B2533.'],
115 ],
116 'required' => ['title'],
117 ],
118 ],
119 'member_ids' => [
120 'type' => 'array',
121 'description' => 'Optional WordPress user ids to add as board members. Max 50. The creator is always a member.',
122 'items' => ['type' => 'integer'],
123 'maxItems' => 50,
124 ],
125 ],
126 'required' => ['title'],
127 ],
128 'execute_callback' => [BoardTools::class, 'createBoard'],
129 'permission_callback' => function () {
130 return PermissionManager::userHasBoardCreationPermission();
131 },
132 ],
133
134 'fluent-boards/list-tasks' => [
135 'label' => __('List Tasks', 'fluent-boards'),
136 'description' => __('List/filter tasks in a board. Supports search, stage, status, priority, assignee, labels, due date, archived filter, and pagination.', 'fluent-boards'),
137 'input_schema' => [
138 'type' => 'object',
139 'properties' => [
140 'board_id' => ['type' => 'integer'],
141 'search' => ['type' => 'string'],
142 'stage' => ['type' => 'array', 'items' => ['type' => ['string', 'integer']]],
143 'task_status' => ['type' => 'array', 'items' => ['type' => 'string']],
144 'priority' => ['type' => 'array', 'items' => ['type' => 'string']],
145 'assignee' => ['type' => 'array', 'items' => ['type' => ['string', 'integer']]],
146 'labels' => ['type' => 'array', 'items' => ['type' => ['string', 'integer']]],
147 'due_date' => ['type' => 'array', 'items' => ['type' => 'string']],
148 'include_archived' => ['type' => 'boolean', 'default' => false],
149 'sort_by' => ['type' => 'string', 'enum' => ['title', 'status', 'created_at', 'position'], 'default' => 'position'],
150 'sort_direction' => ['type' => 'string', 'enum' => ['asc', 'desc'], 'default' => 'asc'],
151 'page' => ['type' => 'integer', 'default' => 1],
152 'per_page' => ['type' => 'integer', 'default' => 20, 'description' => 'Max 100.'],
153 ],
154 'required' => ['board_id'],
155 ],
156 'execute_callback' => [TaskTools::class, 'listTasks'],
157 'permission_callback' => function ($params = []) {
158 return BoardTools::canReadBoard($params);
159 },
160 'annotations' => ['readonly' => true],
161 ],
162
163 'fluent-boards/get-task' => [
164 'label' => __('Get Task', 'fluent-boards'),
165 'description' => __('Full task details. Includes board, stage, labels, assignees, watchers, comments, and recent activities.', 'fluent-boards'),
166 'input_schema' => [
167 'type' => 'object',
168 'properties' => [
169 'board_id' => ['type' => 'integer'],
170 'task_id' => ['type' => 'integer'],
171 ],
172 'required' => ['board_id', 'task_id'],
173 ],
174 'execute_callback' => [TaskTools::class, 'getTask'],
175 'permission_callback' => function ($params = []) {
176 return BoardTools::canReadBoard($params);
177 },
178 'annotations' => ['readonly' => true],
179 ],
180
181 'fluent-boards/create-task' => [
182 'label' => __('Create Task', 'fluent-boards'),
183 'description' => __('Create a task in a board stage. Fires native Fluent Boards task-created hooks and default assignee/watchers logic.', 'fluent-boards'),
184 'input_schema' => [
185 'type' => 'object',
186 'properties' => [
187 'board_id' => ['type' => 'integer'],
188 'stage_id' => ['type' => 'integer'],
189 'title' => ['type' => 'string'],
190 'description' => ['type' => 'string'],
191 'priority' => ['type' => 'string', 'description' => 'Priority key. Built-ins: urgent, high, medium, low. Custom priority keys registered via fluent_boards/task_priorities are allowed. Pass an empty string for no priority.'],
192 'due_at' => ['type' => 'string', 'description' => 'Date/time parseable by WordPress. Site timezone.'],
193 'started_at' => ['type' => 'string', 'description' => 'Date/time parseable by WordPress. Site timezone.'],
194 'crm_contact_id' => ['type' => 'integer'],
195 ],
196 'required' => ['board_id', 'stage_id', 'title'],
197 ],
198 'execute_callback' => [TaskTools::class, 'createTask'],
199 'permission_callback' => function ($params = []) {
200 return BoardTools::canWriteBoard($params);
201 },
202 ],
203
204 'fluent-boards/update-task' => [
205 'label' => __('Update Task', 'fluent-boards'),
206 'description' => __('Update task fields: title, description, status, priority, due_at, started_at, assignees, crm_contact_id, or settings.', 'fluent-boards'),
207 'input_schema' => [
208 'type' => 'object',
209 'properties' => [
210 'board_id' => ['type' => 'integer'],
211 'task_id' => ['type' => 'integer'],
212 'title' => ['type' => 'string'],
213 'description' => ['type' => 'string'],
214 'status' => ['type' => 'string', 'enum' => ['open', 'closed']],
215 'priority' => ['type' => 'string', 'description' => 'Omit to leave unchanged. Pass an empty string to clear. Custom priority keys registered via fluent_boards/task_priorities are allowed.'],
216 'due_at' => ['type' => 'string', 'description' => 'Omit to leave unchanged. Pass an empty string to clear.'],
217 'started_at' => ['type' => 'string', 'description' => 'Omit to leave unchanged. Pass an empty string to clear.'],
218 'assignees' => [
219 'type' => 'array',
220 'items' => ['type' => 'integer'],
221 'description' => __('Replaces current assignees. Newly added user ids must identify board members or global administrators. Pass an empty array to clear all assignees.', 'fluent-boards'),
222 ],
223 'crm_contact_id' => ['type' => 'integer', 'description' => 'Omit to leave unchanged. Pass 0 to clear.'],
224 'settings' => ['type' => 'object'],
225 ],
226 'required' => ['board_id', 'task_id'],
227 ],
228 'execute_callback' => [TaskTools::class, 'updateTask'],
229 'permission_callback' => function ($params = []) {
230 return BoardTools::canWriteBoard($params);
231 },
232 ],
233
234 'fluent-boards/move-task' => [
235 'label' => __('Move Task', 'fluent-boards'),
236 'description' => __('Move a task to another stage or board. Provide position or neighboring task ids for ordering.', 'fluent-boards'),
237 'input_schema' => [
238 'type' => 'object',
239 'properties' => [
240 'board_id' => ['type' => 'integer', 'description' => 'Current board id.'],
241 'task_id' => ['type' => 'integer'],
242 'target_board_id' => ['type' => 'integer', 'description' => 'Optional. Defaults to current board.'],
243 'target_stage_id' => ['type' => 'integer'],
244 'position' => ['type' => 'integer', 'description' => '1-based fallback position.'],
245 'previous_task_id' => ['type' => 'integer'],
246 'next_task_id' => ['type' => 'integer'],
247 ],
248 'required' => ['board_id', 'task_id', 'target_stage_id'],
249 ],
250 'execute_callback' => [TaskTools::class, 'moveTask'],
251 'permission_callback' => function ($params = []) {
252 return TaskTools::canMoveTask($params);
253 },
254 ],
255
256 'fluent-boards/archive-task' => [
257 'label' => __('Archive or Restore Task', 'fluent-boards'),
258 'description' => __('Archive or restore one task. Set archived=true to archive, false to restore.', 'fluent-boards'),
259 'input_schema' => [
260 'type' => 'object',
261 'properties' => [
262 'board_id' => ['type' => 'integer'],
263 'task_id' => ['type' => 'integer'],
264 'archived' => ['type' => 'boolean', 'default' => true],
265 ],
266 'required' => ['board_id', 'task_id'],
267 ],
268 'execute_callback' => [TaskTools::class, 'archiveTask'],
269 'permission_callback' => function ($params = []) {
270 return BoardTools::canWriteBoard($params);
271 },
272 ],
273
274 'fluent-boards/add-comment' => [
275 'label' => __('Add Comment', 'fluent-boards'),
276 'description' => __('Add a private or public comment to a task. Fires native comment-created hooks and notifications.', 'fluent-boards'),
277 'input_schema' => [
278 'type' => 'object',
279 'properties' => [
280 'board_id' => ['type' => 'integer'],
281 'task_id' => ['type' => 'integer'],
282 'description' => ['type' => 'string'],
283 'privacy' => ['type' => 'string', 'enum' => ['private', 'public'], 'default' => 'private'],
284 'mentioned_ids'=> ['type' => 'array', 'items' => ['type' => 'integer']],
285 ],
286 'required' => ['board_id', 'task_id', 'description'],
287 ],
288 'execute_callback' => [CommentTools::class, 'addComment'],
289 'permission_callback' => function ($params = []) {
290 return BoardTools::canWriteBoard($params);
291 },
292 ],
293
294 'fluent-boards/list-labels' => [
295 'label' => __('List Labels', 'fluent-boards'),
296 'description' => __('List the labels of a board, optionally only those already used by tasks.', 'fluent-boards'),
297 'input_schema' => [
298 'type' => 'object',
299 'properties' => [
300 'board_id' => ['type' => 'integer'],
301 'only_used' => ['type' => 'boolean', 'default' => false, 'description' => 'Return only labels currently attached to at least one task.'],
302 ],
303 'required' => ['board_id'],
304 ],
305 'execute_callback' => [LabelTools::class, 'listLabels'],
306 'permission_callback' => function ($params = []) {
307 return BoardTools::canReadBoard($params);
308 },
309 'annotations' => ['readonly' => true],
310 ],
311
312 'fluent-boards/save-label' => [
313 'label' => __('Create or Update Label', 'fluent-boards'),
314 'description' => __('Create a board label, or update an existing one when label_id is given. Only supplied fields change. Fires native board-label hooks.', 'fluent-boards'),
315 'input_schema' => [
316 'type' => 'object',
317 'properties' => [
318 'board_id' => ['type' => 'integer'],
319 'label_id' => ['type' => 'integer', 'description' => 'Omit to create a new label.'],
320 'title' => ['type' => 'string', 'description' => 'Required when creating unless bg_color is given.'],
321 'bg_color' => ['type' => 'string', 'description' => 'Hex background colour. Defaults to #f3f4f6 on create.'],
322 'color' => ['type' => 'string', 'description' => 'Hex text colour. Defaults to #1B2533 on create.'],
323 ],
324 'required' => ['board_id'],
325 ],
326 'execute_callback' => [LabelTools::class, 'saveLabel'],
327 'permission_callback' => function ($params = []) {
328 return BoardTools::canWriteBoard($params);
329 },
330 ],
331
332 'fluent-boards/delete-label' => [
333 'label' => __('Delete Label', 'fluent-boards'),
334 'description' => __('Delete a board label and detach it from every task that carries it.', 'fluent-boards'),
335 'input_schema' => [
336 'type' => 'object',
337 'properties' => [
338 'board_id' => ['type' => 'integer'],
339 'label_id' => ['type' => 'integer'],
340 ],
341 'required' => ['board_id', 'label_id'],
342 ],
343 'execute_callback' => [LabelTools::class, 'deleteLabel'],
344 'permission_callback' => function ($params = []) {
345 return BoardTools::canWriteBoard($params);
346 },
347 ],
348
349 'fluent-boards/add-task-label' => [
350 'label' => __('Add Task Label', 'fluent-boards'),
351 'description' => __('Attach one or more existing board labels to a task, by id or by title.', 'fluent-boards'),
352 'input_schema' => [
353 'type' => 'object',
354 'properties' => [
355 'board_id' => ['type' => 'integer'],
356 'task_id' => ['type' => 'integer'],
357 'label_id' => ['type' => 'integer', 'description' => 'Single label id. Combine with label_ids/label_titles if needed.'],
358 'label_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'maxItems' => 50],
359 'label_titles' => [
360 'type' => 'array',
361 'items' => ['type' => 'string'],
362 'maxItems' => 50,
363 'description' => 'Label titles on the same board, matched case-insensitively. Unknown titles are an error; create them with create-label first.',
364 ],
365 ],
366 'required' => ['board_id', 'task_id'],
367 ],
368 'execute_callback' => [LabelTools::class, 'addTaskLabel'],
369 'permission_callback' => function ($params = []) {
370 return BoardTools::canWriteBoard($params);
371 },
372 ],
373
374 'fluent-boards/remove-task-label' => [
375 'label' => __('Remove Task Label', 'fluent-boards'),
376 'description' => __('Detach one or more labels from a task, by id or by title. The board label itself is kept.', 'fluent-boards'),
377 'input_schema' => [
378 'type' => 'object',
379 'properties' => [
380 'board_id' => ['type' => 'integer'],
381 'task_id' => ['type' => 'integer'],
382 'label_id' => ['type' => 'integer'],
383 'label_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'maxItems' => 50],
384 'label_titles' => ['type' => 'array', 'items' => ['type' => 'string'], 'maxItems' => 50],
385 ],
386 'required' => ['board_id', 'task_id'],
387 ],
388 'execute_callback' => [LabelTools::class, 'removeTaskLabel'],
389 'permission_callback' => function ($params = []) {
390 return BoardTools::canWriteBoard($params);
391 },
392 ],
393
394 'fluent-boards/list-my-tasks' => [
395 'label' => __('List My Tasks', 'fluent-boards'),
396 'description' => __('List tasks across every board the caller can see, grouped by the dashboard categories: assigned, mentioned, upcoming, due today, overdue, completed, or others (no due date).', 'fluent-boards'),
397 'input_schema' => [
398 'type' => 'object',
399 'properties' => [
400 'user_id' => ['type' => 'integer', 'description' => 'Defaults to the current user. Results stay limited to boards the caller can access.'],
401 'task_type' => [
402 'type' => 'string',
403 'enum' => TaskQueryTools::TASK_TYPES,
404 'default' => 'assigned',
405 ],
406 'board_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'Optional board filter.'],
407 'order_by' => ['type' => 'string', 'enum' => TaskQueryTools::ORDER_BY, 'default' => 'due_at'],
408 'order' => ['type' => 'string', 'enum' => ['ASC', 'DESC'], 'default' => 'ASC'],
409 'page' => ['type' => 'integer', 'default' => 1],
410 'per_page' => ['type' => 'integer', 'default' => 20, 'description' => 'Max 50.'],
411 ],
412 ],
413 'execute_callback' => [TaskQueryTools::class, 'listMyTasks'],
414 'permission_callback' => function () {
415 return PermissionManager::hasAppAccess();
416 },
417 'annotations' => ['readonly' => true],
418 ],
419
420 'fluent-boards/search-tasks' => [
421 'label' => __('Search Tasks', 'fluent-boards'),
422 'description' => __('Search tasks by title across every board the caller can see. Terms need at least three characters. Supports the "id:123" exact lookup and "archived:term" prefixes used by the product search box.', 'fluent-boards'),
423 'input_schema' => [
424 'type' => 'object',
425 'properties' => [
426 'query' => ['type' => 'string', 'minLength' => 3, 'description' => 'At least three characters, or an "id:123" lookup.'],
427 'board_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'Optional board filter.'],
428 'include_archived' => ['type' => 'boolean', 'default' => false],
429 'page' => ['type' => 'integer', 'default' => 1],
430 'per_page' => ['type' => 'integer', 'default' => 20, 'description' => 'Max 50.'],
431 ],
432 'required' => ['query'],
433 ],
434 'execute_callback' => [TaskQueryTools::class, 'searchTasks'],
435 'permission_callback' => function () {
436 return PermissionManager::hasAppAccess();
437 },
438 'annotations' => ['readonly' => true],
439 ],
440
441 'fluent-boards/save-stage' => [
442 'label' => __('Create or Update Stage', 'fluent-boards'),
443 'description' => __('Create a stage, or update an existing one when stage_id is given. Only supplied fields change.', 'fluent-boards'),
444 'input_schema' => [
445 'type' => 'object',
446 'properties' => [
447 'board_id' => ['type' => 'integer'],
448 'stage_id' => ['type' => 'integer', 'description' => 'Omit to create a new stage.'],
449 'title' => ['type' => 'string', 'description' => 'Required when creating.'],
450 'position' => ['type' => 'integer', 'description' => '1-based position on the board.'],
451 'default_task_status' => ['type' => 'string', 'enum' => ['open', 'closed'], 'description' => 'Status given to tasks landing in this stage.'],
452 'bg_color' => ['type' => 'string'],
453 'color' => ['type' => 'string'],
454 ],
455 'required' => ['board_id'],
456 ],
457 'execute_callback' => [StageTools::class, 'saveStage'],
458 'permission_callback' => function ($params = []) {
459 return BoardTools::canWriteBoard($params);
460 },
461 ],
462
463 'fluent-boards/archive-stage' => [
464 'label' => __('Archive Stage', 'fluent-boards'),
465 'description' => __('Archive or restore a stage. Archived stages keep their tasks; set archived=false to restore at the end of the board.', 'fluent-boards'),
466 'input_schema' => [
467 'type' => 'object',
468 'properties' => [
469 'board_id' => ['type' => 'integer'],
470 'stage_id' => ['type' => 'integer'],
471 'archived' => ['type' => 'boolean', 'default' => true],
472 ],
473 'required' => ['board_id', 'stage_id'],
474 ],
475 'execute_callback' => [StageTools::class, 'archiveStage'],
476 'permission_callback' => function ($params = []) {
477 return BoardTools::canWriteBoard($params);
478 },
479 ],
480
481 'fluent-boards/assign-task' => [
482 'label' => __('Assign Task', 'fluent-boards'),
483 'description' => __('Add, remove, or sync task assignees. Newly added user ids must identify existing WordPress users who are board members or global administrators. Adding someone already assigned is a no-op. Assignees also become task watchers.', 'fluent-boards'),
484 'input_schema' => [
485 'type' => 'object',
486 'properties' => [
487 'board_id' => ['type' => 'integer'],
488 'task_id' => ['type' => 'integer'],
489 'user_id' => ['type' => 'integer', 'description' => 'Single user id. Ignored when user_ids is provided.'],
490 'user_ids' => ['type' => 'array', 'items' => ['type' => 'integer'], 'maxItems' => 50],
491 'mode' => [
492 'type' => 'string',
493 'enum' => ['add', 'remove', 'sync'],
494 'default' => 'add',
495 'description' => 'sync makes the assignee list exactly match the ids given. Pass user_ids: [] with sync to clear all assignees.',
496 ],
497 ],
498 'required' => ['board_id', 'task_id'],
499 ],
500 'execute_callback' => [TaskTools::class, 'assignTask'],
501 'permission_callback' => function ($params = []) {
502 return BoardTools::canWriteBoard($params);
503 },
504 ],
505 ];
506 }
507
508 public static function register()
509 {
510 foreach (self::getDefinitions() as $name => $definition) {
511 $args = [
512 'label' => $definition['label'],
513 'description' => $definition['description'],
514 'category' => 'fluent-boards',
515 'execute_callback' => self::wrapExecuteCallback($name, $definition['execute_callback']),
516 'permission_callback' => $definition['permission_callback'],
517 'meta' => [
518 'show_in_rest' => true,
519 'mcp' => [
520 'public' => true,
521 ],
522 ],
523 ];
524
525 if (!empty($definition['input_schema'])) {
526 $args['input_schema'] = $definition['input_schema'];
527 }
528
529 if (!empty($definition['annotations'])) {
530 $args['meta']['annotations'] = $definition['annotations'];
531 }
532
533 wp_register_ability($name, $args);
534 }
535 }
536
537 private static function wrapExecuteCallback($toolName, $callback)
538 {
539 return function ($params) use ($toolName, $callback) {
540 try {
541 return call_user_func($callback, is_array($params) ? $params : []);
542 } catch (\Throwable $e) {
543 do_action('fluent_boards/mcp_tool_exception', $e, $toolName, $params);
544
545 $details = [
546 'tool' => $toolName,
547 'exception' => get_class($e),
548 ];
549
550 if (defined('WP_DEBUG') && WP_DEBUG) {
551 $details['file'] = $e->getFile() . ':' . $e->getLine();
552 $details['trace'] = array_slice(explode("\n", $e->getTraceAsString()), 0, 5);
553 }
554
555 return new \WP_Error('failed', $e->getMessage(), $details);
556 }
557 };
558 }
559 }
560