28if (!defined(
'NOTOKENRENEWAL')) {
29 define(
'NOTOKENRENEWAL', 1);
31if (!defined(
'NOREQUIREMENU')) {
32 define(
'NOREQUIREMENU', 1);
34if (!defined(
'NOREQUIREHTML')) {
35 define(
'NOREQUIREHTML', 1);
37if (!defined(
'NOREQUIREAJAX')) {
38 define(
'NOREQUIREAJAX', 1);
40if (!defined(
'NOCSRFCHECK')) {
41 define(
'NOCSRFCHECK', 1);
45require
'../../main.inc.php';
51require_once DOL_DOCUMENT_ROOT .
'/ai/class/mcp_protocol.class.php';
53while (ob_get_level()) {
59 http_response_code(503);
62 "error" => [
"code" => -32000,
"message" =>
"MCP Server Disabled"]
73header(
'Content-Type: application/json');
74header(
'X-Content-Type-Options: nosniff');
76$headers = function_exists(
'getallheaders') ? getallheaders() : [];
77$headers = array_change_key_case($headers, CASE_LOWER);
79$authHeader = $headers[
'authorization'] ??
'';
80$apiKeyHeader = $headers[
'x-api-key'] ??
'';
88$apiKeyQuery = $_GET[
'api_key'] ?? $_GET[
'key'] ??
'';
93if (!empty($storedKey)) {
95 if (!empty($apiKeyHeader)) {
96 $valid = hash_equals($storedKey, $apiKeyHeader);
100 if (!$valid && !empty($authHeader)) {
102 if (preg_match(
'/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
103 $token = trim($matches[1]);
104 $valid = hash_equals($storedKey, $token);
109 if (!$valid && !empty($apiKeyQuery)) {
110 $valid = hash_equals($storedKey, $apiKeyQuery);
115 dol_syslog(
'[MCP Server] Unauthorized access attempt. IP=' . ($_SERVER[
'REMOTE_ADDR'] ??
'unknown'), LOG_WARNING);
117 http_response_code(401);
120 "error" => [
"code" => -32000,
"message" =>
"Unauthorized"]
131 $result = $serviceUser->fetch($userId);
134 $serviceUser->loadRights();
142 $user = $serviceUser;
144 http_response_code(500);
147 "error" => [
"code" => -32000,
"message" =>
"MCP Service User not found"]
152 http_response_code(503);
155 "error" => [
"code" => -32000,
"message" =>
"MCP Server Misconfigured: AI_MCP_USER_ID not set"]
163require_once DOL_DOCUMENT_ROOT .
'/ai/lib/ai.lib.php';
179 global
$db, $serviceUser;
181 $method = isset($req[
'method']) ? (
string) $req[
'method'] :
'';
182 if ($method !==
'tools/call' || !function_exists(
'ai_log_request')) {
186 $params = isset($req[
'params']) && is_array($req[
'params']) ? $req[
'params'] : [];
187 $toolName = isset($params[
'name']) ? (
string) $params[
'name'] :
'';
188 $toolArgs = isset($params[
'arguments']) ? $params[
'arguments'] : [];
190 $argsJson = is_string($toolArgs) ? $toolArgs : (
string) json_encode($toolArgs);
191 $query =
'[MCP] ' . $toolName .
' ' .
dol_substr($argsJson, 0, 1000);
193 $responseShape = [
'tool' => $toolName,
'arguments' => $toolArgs];
197 if (is_array($resp) && isset($resp[
'error'])) {
199 $errorMsg = is_array($resp[
'error']) && isset($resp[
'error'][
'message'])
200 ? (
string) $resp[
'error'][
'message']
201 : (
string) json_encode($resp[
'error']);
202 } elseif (is_array($resp) && isset($resp[
'result'][
'isError']) && $resp[
'result'][
'isError']) {
204 $errorMsg = is_array($resp[
'result'][
'content'] ??
null) ? (
string) json_encode($resp[
'result'][
'content']) :
'';
207 $rawResStr = is_string($resp) ? $resp : (
string) json_encode($resp);
215 microtime(
true) - $tStart,
226 $tStart = microtime(
true);
229 $rawInput = file_get_contents(
'php://input');
230 if ($rawInput ===
false || strlen($rawInput) > 1024 * 1024) {
231 throw new Exception(
"Invalid or too large request");
234 $request = json_decode($rawInput,
true);
236 if (json_last_error() !== JSON_ERROR_NONE) {
243 if (is_array($request) && array_keys($request) === range(0, count($request) - 1)) {
245 if (count($request) > 20) {
246 http_response_code(413);
249 "error" => [
"code" => -32000,
"message" =>
"Batch too large"]
257 foreach ($request as $req) {
258 if (!is_array($req)) {
262 $reqStart = microtime(
true);
263 $res = $server->handleRequest($req);
273 echo json_encode($responses);
276 if (!is_array($request)) {
277 throw new Exception(
"Invalid request format");
280 $response = $server->handleRequest($request);
282 if ($response !==
null) {
283 echo json_encode($response);
292 '[MCP Server] Fatal error: ' . $e->getMessage(),
301 "message" =>
"Parse error"
ai_log_request($db, $user, $query, array $response, $provider, float $time, float $confidence, $status, $error='', $rawReq='', $rawRes='')
Log AI Request with Raw Payloads.
Class to manage Dolibarr users.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
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
mcp_log_request(array $req, $resp, float $tStart, string $rawInput)
Persist an MCP tools/call invocation to the llx_ai_request_log table.