dolibarr 25.0.0-alpha
configure_tools.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2022 Alice Adminson <aadminson@example.com>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Coryright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
7 * Copyright (C) 2026 Nick Fragoulis
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY, without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
41require '../../main.inc.php';
42
51require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
52require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
53require_once DOL_DOCUMENT_ROOT . '/ai/lib/ai.lib.php';
54require_once DOL_DOCUMENT_ROOT . '/ai/class/mcp.class.php';
55require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
56
57$langs->loadLangs(array('admin', 'other', 'main'));
58
59// Access control
60if (!$user->admin) {
62}
63if (!isModEnabled('ai')) {
64 accessforbidden('Module AI not activated.');
65}
66
67// Parameters
68$action = GETPOST('action', 'aZ09');
69$toolcontext = GETPOST('toolcontext', 'alpha');
70$toolname = GETPOST('toolname', 'alpha');
71$mode = GETPOST('mode', 'alpha');
72$backtopage = GETPOST('backtopage', 'alpha');
73
74// Load unfiltered schema
75$mcpHandler = new McpHandler($db, $user, $conf, McpHandler::CTX_ASSISTANT);
76$mcpHandler->loadTools();
77
78$unfilteredSchema = $mcpHandler->getToolsSchemaUnfiltered();
79
80// Build grouped lists from the schema metadata set by getToolsSchemaUnfiltered()
81$allDiscoveredTools = array(); // names of all non-system tools
82$groupedNormalTools = array(); // non-system tools grouped by class_name
83$groupedSystemTools = array(); // system tools grouped by class_name
84
85if (!empty($unfilteredSchema) && is_array($unfilteredSchema)) {
86 foreach ($unfilteredSchema as $def) {
87 $name = $def['name'];
88 $isSystem = !empty($def['is_system']);
89 $classNameKey = !empty($def['class_name']) ? (string) $def['class_name'] : 'DefaultTools';
90
91 if (!$isSystem) {
92 $allDiscoveredTools[] = $name;
93 $groupedNormalTools[$classNameKey][] = $def;
94 } else {
95 $groupedSystemTools[$classNameKey][] = $def;
96 }
97 }
98}
99
100$rawAst = getDolGlobalString('AI_ASSISTANT_ALLOWED_TOOLS');
101$rawMcp = getDolGlobalString('AI_MCP_SERVER_ALLOWED_TOOLS');
102$astAllowed = McpHandler::resolveAllowList($rawAst, $allDiscoveredTools);
103$mcpAllowed = McpHandler::resolveAllowList($rawMcp, $allDiscoveredTools);
104
109if ($action == 'addrights' || $action == 'delrights') {
110 if (GETPOST('token', 'alpha') !== $_SESSION['token']) {
111 accessforbidden('Bad token');
112 }
113
114 if (!empty($toolcontext) && !empty($toolname)) {
115 if ($toolcontext === 'ast') {
116 if ($action == 'addrights') {
117 if (!in_array($toolname, $astAllowed, true)) {
118 $astAllowed[] = $toolname;
119 }
120 } else {
121 $astAllowed = array_values(array_diff($astAllowed, array($toolname)));
122 }
123 $val = empty($astAllowed) ? 'NONE' : implode(',', $astAllowed);
124 dolibarr_set_const($db, 'AI_ASSISTANT_ALLOWED_TOOLS', $val, 'chaine', 0, '', $conf->entity);
125 }
126
127 if ($toolcontext === 'mcp') {
128 if ($action == 'addrights') {
129 if (!in_array($toolname, $mcpAllowed, true)) {
130 $mcpAllowed[] = $toolname;
131 }
132 } else {
133 $mcpAllowed = array_values(array_diff($mcpAllowed, array($toolname)));
134 }
135 $val = empty($mcpAllowed) ? 'NONE' : implode(',', $mcpAllowed);
136 dolibarr_set_const($db, 'AI_MCP_SERVER_ALLOWED_TOOLS', $val, 'chaine', 0, '', $conf->entity);
137 }
138 }
139
140 header('Location: ' . $_SERVER['PHP_SELF']);
141 exit;
142}
143
144if ($action == 'apply_preset' && !empty($toolcontext) && !empty($mode)) {
145 if (GETPOST('token', 'alpha') !== $_SESSION['token']) {
146 accessforbidden('Bad token');
147 }
148
149 $resultSet = array();
150
151 if ($mode === 'all') {
152 $resultSet = $allDiscoveredTools;
153 } elseif ($mode === 'none') {
154 $resultSet = array();
155 } elseif ($mode === 'readonly') {
156 foreach ($allDiscoveredTools as $tName) {
157 // Tools starting with a mutating verb are write tools.
158 // This heuristic matches the naming convention used throughout ai/tools/*.
159 // External tool authors should follow the same convention.
160 if (!preg_match('/^(create|update|delete|add|remove|change|write|edit|validate|pay|send)/i', $tName)) {
161 $resultSet[] = $tName;
162 }
163 }
164 }
165
166 $val = empty($resultSet) ? 'NONE' : implode(',', $resultSet);
167 $constTarget = ($toolcontext === 'mcp') ? 'AI_MCP_SERVER_ALLOWED_TOOLS' : 'AI_ASSISTANT_ALLOWED_TOOLS';
168 dolibarr_set_const($db, $constTarget, $val, 'chaine', 0, '', $conf->entity);
169
170 header('Location: ' . $_SERVER['PHP_SELF']);
171 exit;
172}
173
174/*
175 * VIEW
176 */
177
179$title = 'AiSetup';
180
181llxHeader('', $langs->trans($title), '', '', 0, 0, array('/ai/js/ai.js'), array('/ai/css/ai.css'), '', 'mod-ai page-admin');
182
183$linkback = '<a href="' . ($backtopage ? $backtopage : DOL_URL_ROOT . '/admin/modules.php?restore_lastsearch_values=1') . '">' . img_picto($langs->trans("BackToModuleList"), 'back', 'class="pictofixedwidth"') . '<span class="hideonsmartphone">' . $langs->trans("BackToModuleList") . '</span></a>';
184
185print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
186
187$head = aiAdminPrepareHead();
188print dol_get_fiche_head($head, 'tools', 'MCP Server', -1, 'ai');
189
190print '<!-- Tools quick setup -->';
191print '<span class="opacitymedium">' . $langs->trans("ToolAccessControlHelp") . '</span><br><br>';
192
193print '<div class="marginleftonly" style="display:flex; flex-wrap:wrap; gap:40px; margin-top:15px; padding-top:15px;">';
194
195// Presets For Chat Assistant
196print '<div>';
197print '<strong>' . $langs->trans('PresetsForChatAssistant') . ':</strong><br>';
198print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'ast', 'mode' => 'all'), true) . '" style="margin:4px 2px;">' . $langs->trans('AllTools') . '</a>';
199print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'ast', 'mode' => 'readonly'), true) . '" style="margin:4px 2px;">' . $langs->trans('ViewOnly') . '</a>';
200print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'ast', 'mode' => 'none'), true) . '" style="margin:4px 2px;">' . $langs->trans('None') . '</a>';
201print '</div>';
202
203// Presets For MCP Server
204print '<div>';
205print '<strong>' . $langs->trans('PresetsForMcpServer') . ':</strong><br>';
206print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'mcp', 'mode' => 'all'), true) . '" style="margin:4px 2px;">' . $langs->trans('AllTools') . '</a>';
207print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'mcp', 'mode' => 'readonly'), true) . '" style="margin:4px 2px;">' . $langs->trans('ViewOnly') . '</a>';
208print '<a class="button" href="' . dolBuildUrl($_SERVER['PHP_SELF'], array('action' => 'apply_preset', 'toolcontext' => 'mcp', 'mode' => 'none'), true) . '" style="margin:4px 2px;">' . $langs->trans('None') . '</a>';
209print '</div>';
210
211print '</div>';
212
213print '<br>';
214
215// Tools table
216print '<div class="div-table-responsive-no-min">';
217print '<table class="noborder centpercent" id="toolsTable">';
218print '<tr class="liste_titre">';
219print '<td class="tdoverflowmax200" style="min-width: 130px;">' . $langs->trans('Tool') . '</td>';
220print '<td class="hideonsmartphone">' . $langs->trans('ToolDescription') . '</td>';
221print '<td class="center" style="min-width: 60px;">' . $langs->trans('ToolActionType') . '</td>';
222print '<td class="center nowraponall">' . $langs->trans('ChatAssistant') . '</td>';
223print '<td class="center nowraponall">' . $langs->trans('McpServer') . '</td>';
224print '</tr>';
225
226if (empty($groupedNormalTools) && empty($groupedSystemTools)) {
227 print '<tr class="oddeven"><td colspan="5" class="opacitymedium">' . $langs->trans('NoMcpToolsDiscovered') . '</td></tr>';
228} else {
229 $groupId = 0;
230 $finalGroupsList = array_merge($groupedNormalTools, $groupedSystemTools);
231
232 foreach ($finalGroupsList as $categoryName => $definitions) {
233 $groupId++;
234
235 print '<tr class="trgroup trforbreaknobg" data-group="group-' . $groupId . '">';
236 print '<td colspan="5" class="mcp-trigger-collapse" style="cursor:pointer;">';
237 print '<span class="toggle-icon">▼</span> ' . dol_escape_htmltag($categoryName);
238 print '</td>';
239 print '</tr>';
240
241 foreach ($definitions as $def) {
242 $name = $def['name'];
243 $desc = !empty($def['description']) ? $def['description'] : '-';
244 $isSystem = !empty($def['is_system']);
245
246 $isAstOn = $isSystem ? true : in_array($name, $astAllowed, true);
247 $isMcpOn = $isSystem ? true : in_array($name, $mcpAllowed, true);
248
249 if (preg_match('/^(create|update|delete|add|remove|change|write|edit|validate|pay|send)/i', $name)) {
250 $type = 'write';
251 $typeBadge = '<span class="badge badge-status2" style="display: inline-block;">' . $langs->trans('Modify') . '</span>';
252 } else {
253 $type = 'read';
254 $typeBadge = '<span class="badge badge-status4" style="display: inline-block;">' . $langs->trans('ViewOnly') . '</span>';
255 }
256
257 print '<tr class="oddeven group-' . $groupId . '" data-tool-name="' . dol_escape_htmltag($name) . '" data-tool-type="' . $type . '">';
258
259 // Tool
260 print '<td style="padding-left:20px; min-width: 130px; word-break: break-all;" class="tdoverflowmax200">';
261 print '<strong>' . dol_escape_htmltag($name) . '</strong>';
262 if ($isSystem) {
263 print '<br><span class="badgeneutral">' . $langs->trans('System') . '</span>';
264 }
265 print '</td>';
266
267 // Tool Description (hidden on mobile)
268 print '<td class="small opacitymedium hideonsmartphone">' . dol_escape_htmltag($desc) . '</td>';
269
270 // Type Badge
271 print '<td class="center">' . $typeBadge . '</td>';
272
273
274 $lockCssClass = $isSystem ? ' opacitymedium disabled' : '';
275
276 // Chat Assistant Switch
277 print '<td class="center nowraponall">';
278 if ($isSystem) {
279 print img_picto($langs->trans('Active'), 'switch_on', '', 0, 0, 0, '', 'opacitymedium');
280 } else {
281 $actAst = $isAstOn ? 'delrights' : 'addrights';
282 $pictoAst = $isAstOn ? 'switch_on'.($type == 'write' ? '_warning' : '') : 'switch_off';
283 $urlAst = dolBuildUrl($_SERVER["PHP_SELF"], [
284 'action' => $actAst,
285 'toolcontext' => 'ast',
286 'toolname' => $name
287 ], true);
288
289 print '<a class="ctx-ast-link reposition' . $lockCssClass . '" href="' . $urlAst . '">';
290 print img_picto($langs->trans($isAstOn ? 'Remove' : 'Add'), $pictoAst, '', 0, 0, 0, '', !$isAstOn ? 'font-status6' : ($type == 'write' ? 'font-status2' : 'font-status4'));
291 print '</a>';
292 }
293 print '</td>';
294
295 // MCP Server Switch
296 print '<td class="center nowraponall">';
297 if ($isSystem) {
298 print img_picto($langs->trans('Active'), 'switch_on', '', 0, 0, 0, '', 'opacitymedium');
299 } else {
300 $actMcp = $isMcpOn ? 'delrights' : 'addrights';
301 $pictoMcp = $isMcpOn ? 'switch_on'.($type == 'write' ? '_warning' : '') : 'switch_off';
302 $urlMcp = dolBuildUrl($_SERVER["PHP_SELF"], [
303 'action' => $actMcp,
304 'toolcontext' => 'mcp',
305 'toolname' => $name
306 ], true);
307
308 print '<a class="ctx-mcp-link reposition' . $lockCssClass . '" href="' . $urlMcp . '">';
309 print img_picto($langs->trans($isMcpOn ? 'Remove' : 'Add'), $pictoMcp, '', 0, 0, 0, '', !$isMcpOn ? 'font-status6' : ($type == 'write' ? 'font-status2' : 'font-status4'));
310 print '</a>';
311 }
312 print '</td>';
313
314 print '</tr>' . "\n";
315 }
316 }
317}
318
319print '</table>';
320print '</div>';
321
322print dol_get_fiche_end();
323
324llxFooter();
325$db->close();
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
aiAdminPrepareHead()
Prepare admin pages header.
Definition ai.lib.php:531
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to handle MCP (Model Context Protocol).
Definition mcp.class.php:40
static resolveAllowList($raw, $allDiscoveredTools)
Resolves a raw allow-list constant value into an explicit PHP array of tool names.
if( $action=='addrights'||$action=='delrights') if($action=='apply_preset' &&!empty($toolcontext) &&!empty($mode) $help_url)
ACTIONS.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:738
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.