27require_once DOL_DOCUMENT_ROOT .
"/ai/class/mcptool.class.php";
41 const CTX_ASSISTANT =
'assistant';
43 const CTX_MCP_SERVER =
'mcp_server';
57 private $loadedTools = [];
63 private $toolsByName = [];
81 public function __construct($db, $user, $conf_obj =
null, $toolcontext =
'')
86 if ($conf_obj ===
null) {
90 $this->
conf = $conf_obj;
92 $this->toolcontext = (!empty($toolcontext)) ? $toolcontext : self::CTX_ASSISTANT;
109 return (method_exists($toolInstance,
'isSystem') && $toolInstance->isSystem());
128 if ($this->toolcontext === self::CTX_MCP_SERVER) {
129 $constName =
'AI_MCP_SERVER_ALLOWED_TOOLS';
131 $constName =
'AI_ASSISTANT_ALLOWED_TOOLS';
141 if ($raw ===
'NONE') {
143 return array(
'__blocked__');
146 return array_values(array_filter(array_map(
'trim', explode(
',', $raw))));
160 return $allDiscoveredTools;
162 if ($raw ===
'NONE') {
167 return array_values(array_filter(array_map(
'trim', explode(
',', $raw))));
197 $toolsDir = DOL_DOCUMENT_ROOT .
'/ai/tools/';
198 if (!is_dir($toolsDir)) {
199 dol_syslog(
'[McpHandler] MCP tools directory not found: ' . $toolsDir, LOG_INFO);
203 $files = glob($toolsDir .
'*.php');
204 foreach ($files as $file) {
207 $realFilePath = realpath($file);
208 if ($realFilePath ===
false || strpos($realFilePath, realpath($toolsDir)) !== 0) {
209 dol_syslog(
'[McpHandler] Attempted to load tool outside of allowed directory: ' . $file, LOG_WARNING);
213 require_once $realFilePath;
215 $basename = basename($file,
'.class.php');
216 $className =
'Tool' . str_replace(
' ',
'', ucwords(str_replace(
'_',
' ', $basename)));
218 if (!class_exists($className)) {
219 dol_syslog(
"[McpHandler] Tool class '{$className}' not found in file '{$file}'.", LOG_WARNING);
223 $toolInstance =
new $className($this->db, $this->
user, $this->
conf);
225 if ($toolInstance instanceof
McpTool) {
228 dol_syslog(
"[McpHandler] Tool class '{$className}' does not extend McpTool.", LOG_ERR);
230 }
catch (\Throwable $e) {
231 dol_syslog(
"[McpHandler] Failed to load tool from file '{$file}': " . $e->getMessage(), LOG_ERR);
247 if (!is_object($hookmanager)) {
248 require_once DOL_DOCUMENT_ROOT .
'/core/class/hookmanager.class.php';
252 $hookmanager->initHooks([
'aimcp']);
254 $parameters = [
'db' => $this->db,
'user' => $this->user,
'conf' => $this->conf];
258 $hookmanager->executeHooks(
'addMcpTools', $parameters, $this, $action);
260 if (!is_array($hookmanager->resArray)) {
264 foreach ($hookmanager->resArray as $moduleTools) {
265 if (!is_array($moduleTools)) {
268 foreach ($moduleTools as $toolInstance) {
269 if ($toolInstance instanceof
McpTool) {
270 $this->
registerTool(get_class($toolInstance), $toolInstance);
272 dol_syslog(
'[McpHandler] A module provided a tool that is not an instance of McpTool.', LOG_WARNING);
276 }
catch (\Throwable $e) {
277 dol_syslog(
'[McpHandler] Error during \'addMcpTools\' hook execution: ' . $e->getMessage(), LOG_ERR);
291 $this->loadedTools[$key] = $toolInstance;
295 if (isset($def[
'name'])) {
296 if (isset($this->toolsByName[$def[
'name']])) {
298 "[McpHandler] Tool name conflict: '{$def['name']}' is already registered by '" . get_class($this->toolsByName[$def[
'name']]) .
"'. Skipping registration from '" . get_class($toolInstance) .
"'.",
302 $this->toolsByName[$def[
'name']] = $toolInstance;
306 dol_syslog(
'[McpHandler] Successfully registered MCP tool: ' . get_class($toolInstance), LOG_INFO);
321 foreach ($this->loadedTools as $tool) {
323 $className = get_class($tool);
325 foreach ($tool->getDefinitions() as $def) {
326 $def[
'is_system'] = $isSystem;
327 $def[
'class_name'] = $className;
328 $def[
'categories'] = $tool->getCategories();
353 foreach ($this->loadedTools as $tool) {
356 foreach ($tool->getDefinitions() as $def) {
357 $name = isset($def[
'name']) ? $def[
'name'] :
'';
364 $def[
'is_system'] =
true;
365 $def[
'categories'] = $tool->getCategories();
370 $def[
'is_system'] =
false;
372 if (empty($allowed)) {
374 $def[
'categories'] = $tool->getCategories();
379 if (in_array($name, $allowed,
true)) {
380 $def[
'categories'] = $tool->getCategories();
410 foreach ($this->loadedTools as $tool) {
417 foreach ($tool->getDefinitions() as $def) {
418 $name = isset($def[
'name']) ? $def[
'name'] :
'';
423 if (!empty($def[
'is_system'])) {
427 if (empty($allowed)) {
429 $def[
'categories'] = $tool->getCategories();
434 if (in_array($name, $allowed,
true)) {
435 $def[
'categories'] = $tool->getCategories();
457 if (!isset($this->toolsByName[$toolName])) {
458 return [
"error" =>
"Tool '{$toolName}' not found."];
461 $toolInstance = $this->toolsByName[$toolName];
467 if (!empty($allowed) && !in_array($toolName, $allowed,
true)) {
469 "[McpHandler] Blocked execution of tool '$toolName' in tool context '{$this->toolcontext}' (not in allow-list).",
472 return array(
'error' =>
"Tool '" . $toolName .
"' is not available in this tool context.");
478 dol_syslog(
'[McpHandler] Executing tool \'' . $toolName .
'\' with args:
' . json_encode($args), LOG_INFO);
479 $result = $toolInstance->execute($toolName, $args);
480 dol_syslog('[
McpHandler] Tool \
'' . $toolName .
'\' executed successfully.
', LOG_INFO);
482 } catch (\Throwable $e) {
483 dol_syslog('[
McpHandler] Error executing tool \
'' . $toolName .
'\':
' . $e->getMessage(), LOG_ERR);
484 return ["error" => "An internal error occurred while executing the tool '{$toolName}
'. Details have been logged."];
Class to handle MCP (Model Context Protocol).
__construct($db, $user, $conf_obj=null, $toolcontext='')
Constructor.
isSystemTool($toolInstance)
Returns true if the given tool instance declares itself as a system tool.
getToolsSchemaForLLM()
Returns the schema of tools permitted in the current context, with system tools completely excluded.
getToolsSchemaUnfiltered()
Returns the full schema of every loaded tool with no allow-list filtering.
loadExternalTools()
Loads external tools registered via the 'addMcpTools' hook.
registerTool(string $key, McpTool $toolInstance)
Helper method to register a tool instance and populate lookup arrays.
getAllowedToolsList()
Returns the configured allow-list for the current context as an array of tool names.
loadTools()
Load all available MCP tools.
loadNativeTools()
Load native tools from the specific tools directory.
getToolsSchema()
Returns the schema of all tools permitted in the current context.
static resolveAllowList($raw, $allDiscoveredTools)
Resolves a raw allow-list constant value into an explicit PHP array of tool names.
executeTool(string $toolName, array $args)
Execute a specific tool by its name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
conf($dolibarr_main_document_root)
Load conf file (file must exists)
$conf db user
Active Directory does not allow anonymous connections.