dolibarr 24.0.0-beta
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$unfilteredSchema = $mcpHandler->getToolsSchemaUnfiltered();
77
78// Build grouped lists from the schema metadata set by getToolsSchemaUnfiltered()
79$allDiscoveredTools = array(); // names of all non-system tools
80$groupedNormalTools = array(); // non-system tools grouped by class_name
81$groupedSystemTools = array(); // system tools grouped by class_name
82
83if (!empty($unfilteredSchema) && is_array($unfilteredSchema)) {
84 foreach ($unfilteredSchema as $def) {
85 $name = $def['name'];
86 $isSystem = !empty($def['is_system']);
87 $classNameKey = !empty($def['class_name']) ? (string) $def['class_name'] : 'DefaultTools';
88
89 if (!$isSystem) {
90 $allDiscoveredTools[] = $name;
91 $groupedNormalTools[$classNameKey][] = $def;
92 } else {
93 $groupedSystemTools[$classNameKey][] = $def;
94 }
95 }
96}
97
98$rawAst = getDolGlobalString('AI_ASSISTANT_ALLOWED_TOOLS');
99$rawMcp = getDolGlobalString('AI_MCP_SERVER_ALLOWED_TOOLS');
100$astAllowed = McpHandler::resolveAllowList($rawAst, $allDiscoveredTools);
101$mcpAllowed = McpHandler::resolveAllowList($rawMcp, $allDiscoveredTools);
102
107if ($action == 'addrights' || $action == 'delrights') {
108 if (GETPOST('token', 'alpha') !== $_SESSION['token']) {
109 accessforbidden('Bad token');
110 }
111
112 if (!empty($toolcontext) && !empty($toolname)) {
113 if ($toolcontext === 'ast') {
114 if ($action == 'addrights') {
115 if (!in_array($toolname, $astAllowed, true)) {
116 $astAllowed[] = $toolname;
117 }
118 } else {
119 $astAllowed = array_values(array_diff($astAllowed, array($toolname)));
120 }
121 $val = empty($astAllowed) ? 'NONE' : implode(',', $astAllowed);
122 dolibarr_set_const($db, 'AI_ASSISTANT_ALLOWED_TOOLS', $val, 'chaine', 0, '', $conf->entity);
123 }
124
125 if ($toolcontext === 'mcp') {
126 if ($action == 'addrights') {
127 if (!in_array($toolname, $mcpAllowed, true)) {
128 $mcpAllowed[] = $toolname;
129 }
130 } else {
131 $mcpAllowed = array_values(array_diff($mcpAllowed, array($toolname)));
132 }
133 $val = empty($mcpAllowed) ? 'NONE' : implode(',', $mcpAllowed);
134 dolibarr_set_const($db, 'AI_MCP_SERVER_ALLOWED_TOOLS', $val, 'chaine', 0, '', $conf->entity);
135 }
136 }
137
138 header('Location: ' . $_SERVER['PHP_SELF']);
139 exit;
140}
141
142if ($action == 'apply_preset' && !empty($toolcontext) && !empty($mode)) {
143 if (GETPOST('token', 'alpha') !== $_SESSION['token']) {
144 accessforbidden('Bad token');
145 }
146
147 $resultSet = array();
148
149 if ($mode === 'all') {
150 $resultSet = $allDiscoveredTools;
151 } elseif ($mode === 'none') {
152 $resultSet = array();
153 } elseif ($mode === 'readonly') {
154 foreach ($allDiscoveredTools as $tName) {
155 // Tools starting with a mutating verb are write tools.
156 // This heuristic matches the naming convention used throughout ai/tools/*.
157 // External tool authors should follow the same convention.
158 if (!preg_match('/^(create|update|delete|add|remove|change|write|edit|validate|pay|send)/i', $tName)) {
159 $resultSet[] = $tName;
160 }
161 }
162 }
163
164 $val = empty($resultSet) ? 'NONE' : implode(',', $resultSet);
165 $constTarget = ($toolcontext === 'mcp') ? 'AI_MCP_SERVER_ALLOWED_TOOLS' : 'AI_ASSISTANT_ALLOWED_TOOLS';
166 dolibarr_set_const($db, $constTarget, $val, 'chaine', 0, '', $conf->entity);
167
168 header('Location: ' . $_SERVER['PHP_SELF']);
169 exit;
170}
171
172/*
173 * VIEW
174 */
175
177$title = 'AiSetup';
178llxHeader('', $langs->trans($title), '', '', 0, 0, array(dol_buildpath('/ai/js/ai.js', 1)), array(dol_buildpath('/ai/css/ai.css', 1)), '', 'mod-ai page-admin');
179
180$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>';
181
182print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
183
184$head = aiAdminPrepareHead();
185print dol_get_fiche_head($head, 'tools', 'MCP Server', -1, 'ai');
186
187print '<span class="opacitymedium">' . $langs->trans("ToolAccessControlHelp") . '</span><br><br>';
188
189print '<div class="marginleftonly" style="display:flex; flex-wrap:wrap; gap:40px; margin-top:15px; padding-top:15px; border-top:1px solid #ddd;">';
190
191// Presets For Chat Assistant
192print '<div>';
193print '<strong>' . $langs->trans('PresetsForChatAssistant') . ':</strong><br>';
194print '<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>';
195print '<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>';
196print '<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>';
197print '</div>';
198
199// Presets For MCP Server
200print '<div>';
201print '<strong>' . $langs->trans('PresetsForMcpServer') . ':</strong><br>';
202print '<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>';
203print '<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>';
204print '<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>';
205print '</div>';
206
207print '</div>';
208
209
210// Tools table
211print '<div class="div-table-responsive-no-min">';
212print '<table class="noborder centpercent" id="toolsTable">';
213print '<tr class="liste_titre">';
214print '<td class="tdoverflowmax200" style="min-width: 130px;">' . $langs->trans('Tool') . '</td>';
215print '<td class="center" style="min-width: 60px;">' . $langs->trans('ToolActionType') . '</td>';
216print '<td class="hideonsmartphone">' . $langs->trans('ToolDescription') . '</td>';
217print '<td class="center nowraponall">' . $langs->trans('ChatAssistant') . '</td>';
218print '<td class="center nowraponall">' . $langs->trans('McpServer') . '</td>';
219print '</tr>';
220
221if (empty($groupedNormalTools) && empty($groupedSystemTools)) {
222 print '<tr class="oddeven"><td colspan="5" class="opacitymedium">' . $langs->trans('NoMcpToolsDiscovered') . '</td></tr>';
223} else {
224 $groupId = 0;
225 $finalGroupsList = array_merge($groupedNormalTools, $groupedSystemTools);
226
227 foreach ($finalGroupsList as $categoryName => $definitions) {
228 $groupId++;
229
230 print '<tr class="trgroup" data-group="group-' . $groupId . '">';
231 print '<td colspan="5" class="mcp-trigger-collapse" style="cursor:pointer;">';
232 print '<span class="toggle-icon">▼</span> ' . dol_escape_htmltag($categoryName);
233 print '</td>';
234 print '</tr>';
235
236 foreach ($definitions as $def) {
237 $name = $def['name'];
238 $desc = !empty($def['description']) ? $def['description'] : '-';
239 $isSystem = !empty($def['is_system']);
240
241 $isAstOn = $isSystem ? true : in_array($name, $astAllowed, true);
242 $isMcpOn = $isSystem ? true : in_array($name, $mcpAllowed, true);
243
244 if (preg_match('/^(create|update|delete|add|remove|change|write|edit|validate|pay|send)/i', $name)) {
245 $type = 'write';
246 $typeBadge = '<span class="badge badge-status2" style="display: inline-block;">' . $langs->trans('Modify') . '</span>';
247 } else {
248 $type = 'read';
249 $typeBadge = '<span class="badge badge-status4" style="display: inline-block;">' . $langs->trans('ViewOnly') . '</span>';
250 }
251
252 print '<tr class="oddeven group-' . $groupId . '" data-tool-name="' . dol_escape_htmltag($name) . '" data-tool-type="' . $type . '">';
253
254 // Tool
255 print '<td style="padding-left:20px; min-width: 130px; word-break: break-all;" class="tdoverflowmax200">';
256 print '<strong>' . dol_escape_htmltag($name) . '</strong>';
257 if ($isSystem) {
258 print '<br><span class="badgeneutral">' . $langs->trans('System') . '</span>';
259 }
260 print '</td>';
261
262 // Type Badge
263 print '<td class="center">' . $typeBadge . '</td>';
264
265 // Tool Description (hidden on mobile)
266 print '<td class="small opacitymedium hideonsmartphone">' . dol_escape_htmltag($desc) . '</td>';
267
268 $lockCssClass = $isSystem ? ' opacitymedium disabled' : '';
269
270 // Chat Assistant Switch
271 print '<td class="center nowraponall">';
272 if ($isSystem) {
273 print img_picto($langs->trans('Active'), 'switch_on', '', 0, 0, 0, '', 'opacitymedium');
274 } else {
275 $actAst = $isAstOn ? 'delrights' : 'addrights';
276 $pictoAst = $isAstOn ? 'switch_on'.($type == 'write' ? '_warning' : '') : 'switch_off';
277 $urlAst = dolBuildUrl($_SERVER["PHP_SELF"], [
278 'action' => $actAst,
279 'toolcontext' => 'ast',
280 'toolname' => $name
281 ], true);
282
283 print '<a class="ctx-ast-link reposition' . $lockCssClass . '" href="' . $urlAst . '">';
284 print img_picto($langs->trans($isAstOn ? 'Remove' : 'Add'), $pictoAst, '', 0, 0, 0, '', !$isAstOn ? 'font-status6' : ($type == 'write' ? 'font-status2' : 'font-status4'));
285 print '</a>';
286 }
287 print '</td>';
288
289 // MCP Server Switch
290 print '<td class="center nowraponall">';
291 if ($isSystem) {
292 print img_picto($langs->trans('Active'), 'switch_on', '', 0, 0, 0, '', 'opacitymedium');
293 } else {
294 $actMcp = $isMcpOn ? 'delrights' : 'addrights';
295 $pictoMcp = $isMcpOn ? 'switch_on'.($type == 'write' ? '_warning' : '') : 'switch_off';
296 $urlMcp = dolBuildUrl($_SERVER["PHP_SELF"], [
297 'action' => $actMcp,
298 'toolcontext' => 'mcp',
299 'toolname' => $name
300 ], true);
301
302 print '<a class="ctx-mcp-link reposition' . $lockCssClass . '" href="' . $urlMcp . '">';
303 print img_picto($langs->trans($isMcpOn ? 'Remove' : 'Add'), $pictoMcp, '', 0, 0, 0, '', !$isMcpOn ? 'font-status6' : ($type == 'write' ? 'font-status2' : 'font-status4'));
304 print '</a>';
305 }
306 print '</td>';
307
308 print '</tr>' . "\n";
309 }
310 }
311}
312
313print '</table>';
314print '</div>';
315
316print dol_get_fiche_end();
317
318llxFooter();
319$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:407
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:39
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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
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.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.