26require_once DOL_DOCUMENT_ROOT .
"/ai/class/mcptool.class.php";
40 const CTX_ASSISTANT =
'assistant';
42 const CTX_MCP_SERVER =
'mcp_server';
56 private $loadedTools = [];
62 private $toolsByName = [];
80 public function __construct($db, $user, $conf =
null, $toolcontext =
'')
90 $this->toolcontext = (!empty($toolcontext)) ? $toolcontext : self::CTX_ASSISTANT;
107 return (method_exists($toolInstance,
'isSystem') && $toolInstance->isSystem());
126 if ($this->toolcontext === self::CTX_MCP_SERVER) {
127 $constName =
'AI_MCP_SERVER_ALLOWED_TOOLS';
129 $constName =
'AI_ASSISTANT_ALLOWED_TOOLS';
139 if ($raw ===
'NONE') {
141 return array(
'__blocked__');
144 return array_values(array_filter(array_map(
'trim', explode(
',', $raw))));
158 return $allDiscoveredTools;
160 if ($raw ===
'NONE') {
165 return array_values(array_filter(array_map(
'trim', explode(
',', $raw))));
195 $toolsDir = DOL_DOCUMENT_ROOT .
'/ai/tools/';
196 if (!is_dir($toolsDir)) {
197 dol_syslog(
'[McpHandler] MCP tools directory not found: ' . $toolsDir, LOG_INFO);
201 $files = glob($toolsDir .
'*.php');
202 foreach ($files as $file) {
205 $realFilePath = realpath($file);
206 if ($realFilePath ===
false || strpos($realFilePath, realpath($toolsDir)) !== 0) {
207 dol_syslog(
'[McpHandler] Attempted to load tool outside of allowed directory: ' . $file, LOG_WARNING);
211 require_once $realFilePath;
213 $basename = basename($file,
'.class.php');
214 $className =
'Tool' . str_replace(
' ',
'', ucwords(str_replace(
'_',
' ', $basename)));
216 if (!class_exists($className)) {
217 dol_syslog(
"[McpHandler] Tool class '{$className}' not found in file '{$file}'.", LOG_WARNING);
221 $toolInstance =
new $className($this->db, $this->
user, $this->
conf);
223 if ($toolInstance instanceof
McpTool) {
226 dol_syslog(
"[McpHandler] Tool class '{$className}' does not extend McpTool.", LOG_ERR);
228 }
catch (\Throwable $e) {
229 dol_syslog(
"[McpHandler] Failed to load tool from file '{$file}': " . $e->getMessage(), LOG_ERR);
245 if (!is_object($hookmanager)) {
246 require_once DOL_DOCUMENT_ROOT .
'/core/class/hookmanager.class.php';
250 $hookmanager->initHooks([
'aimcp']);
252 $parameters = [
'db' => $this->db,
'user' => $this->user,
'conf' => $this->conf];
256 $hookmanager->executeHooks(
'addMcpTools', $parameters, $this, $action);
258 if (!is_array($hookmanager->resArray)) {
262 foreach ($hookmanager->resArray as $moduleTools) {
263 if (!is_array($moduleTools)) {
266 foreach ($moduleTools as $toolInstance) {
267 if ($toolInstance instanceof
McpTool) {
268 $this->
registerTool(get_class($toolInstance), $toolInstance);
270 dol_syslog(
'[McpHandler] A module provided a tool that is not an instance of McpTool.', LOG_WARNING);
274 }
catch (\Throwable $e) {
275 dol_syslog(
'[McpHandler] Error during \'addMcpTools\' hook execution: ' . $e->getMessage(), LOG_ERR);
289 $this->loadedTools[$key] = $toolInstance;
293 if (isset($def[
'name'])) {
294 if (isset($this->toolsByName[$def[
'name']])) {
296 "[McpHandler] Tool name conflict: '{$def['name']}' is already registered by '" . get_class($this->toolsByName[$def[
'name']]) .
"'. Skipping registration from '" . get_class($toolInstance) .
"'.",
300 $this->toolsByName[$def[
'name']] = $toolInstance;
304 dol_syslog(
'[McpHandler] Successfully registered MCP tool: ' . get_class($toolInstance), LOG_INFO);
319 foreach ($this->loadedTools as $tool) {
321 $className = get_class($tool);
323 foreach ($tool->getDefinitions() as $def) {
324 $def[
'is_system'] = $isSystem;
325 $def[
'class_name'] = $className;
326 $def[
'categories'] = $tool->getCategories();
351 foreach ($this->loadedTools as $tool) {
354 foreach ($tool->getDefinitions() as $def) {
355 $name = isset($def[
'name']) ? $def[
'name'] :
'';
362 $def[
'is_system'] =
true;
363 $def[
'categories'] = $tool->getCategories();
368 $def[
'is_system'] =
false;
370 if (empty($allowed)) {
372 $def[
'categories'] = $tool->getCategories();
377 if (in_array($name, $allowed,
true)) {
378 $def[
'categories'] = $tool->getCategories();
408 foreach ($this->loadedTools as $tool) {
415 foreach ($tool->getDefinitions() as $def) {
416 $name = isset($def[
'name']) ? $def[
'name'] :
'';
421 if (!empty($def[
'is_system'])) {
425 if (empty($allowed)) {
427 $def[
'categories'] = $tool->getCategories();
432 if (in_array($name, $allowed,
true)) {
433 $def[
'categories'] = $tool->getCategories();
455 if (!isset($this->toolsByName[$toolName])) {
456 return [
"error" =>
"Tool '{$toolName}' not found."];
459 $toolInstance = $this->toolsByName[$toolName];
465 if (!empty($allowed) && !in_array($toolName, $allowed,
true)) {
467 "[McpHandler] Blocked execution of tool '$toolName' in tool context '{$this->toolcontext}' (not in allow-list).",
470 return array(
'error' =>
"Tool '" . $toolName .
"' is not available in this tool context.");
476 dol_syslog(
'[McpHandler] Executing tool \'' . $toolName .
'\' with args:
' . json_encode($args), LOG_INFO);
477 $result = $toolInstance->execute($toolName, $args);
478 dol_syslog('[
McpHandler] Tool \
'' . $toolName .
'\' executed successfully.
', LOG_INFO);
480 } catch (\Throwable $e) {
481 dol_syslog('[
McpHandler] Error executing tool \
'' . $toolName .
'\':
' . $e->getMessage(), LOG_ERR);
482 return ["error" => "An internal error occurred while executing the tool '{$toolName}
'. Details have been logged."];
Class to handle MCP (Model Context Protocol).
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.
__construct($db, $user, $conf=null, $toolcontext='')
Constructor.
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.