27if (!defined(
'NOTOKENRENEWAL')) {
28 define(
'NOTOKENRENEWAL', 1);
30if (!defined(
'NOREQUIREMENU')) {
31 define(
'NOREQUIREMENU', 1);
33if (!defined(
'NOREQUIREHTML')) {
34 define(
'NOREQUIREHTML', 1);
36if (!defined(
'NOREQUIREAJAX')) {
37 define(
'NOREQUIREAJAX', 1);
39if (!defined(
'NOCSRFCHECK')) {
40 define(
'NOCSRFCHECK', 1);
44require
'../../main.inc.php';
50require_once DOL_DOCUMENT_ROOT .
'/ai/class/mcp_protocol.class.php';
52while (ob_get_level()) {
58 http_response_code(503);
61 "error" => [
"code" => -32000,
"message" =>
"MCP Server Disabled"]
72header(
'Content-Type: application/json');
73header(
'X-Content-Type-Options: nosniff');
75$headers = function_exists(
'getallheaders') ? getallheaders() : [];
76$headers = array_change_key_case($headers, CASE_LOWER);
78$authHeader = $headers[
'authorization'] ??
'';
79$apiKeyHeader = $headers[
'x-api-key'] ??
'';
87$apiKeyQuery = $_GET[
'api_key'] ?? $_GET[
'key'] ??
'';
92if (!empty($storedKey)) {
94 if (!empty($apiKeyHeader)) {
95 $valid = hash_equals($storedKey, $apiKeyHeader);
99 if (!$valid && !empty($authHeader)) {
101 if (preg_match(
'/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
102 $token = trim($matches[1]);
103 $valid = hash_equals($storedKey, $token);
108 if (!$valid && !empty($apiKeyQuery)) {
109 $valid = hash_equals($storedKey, $apiKeyQuery);
114 dol_syslog(
'[MCP Server] Unauthorized access attempt. IP=' . ($_SERVER[
'REMOTE_ADDR'] ??
'unknown'), LOG_WARNING);
116 http_response_code(401);
119 "error" => [
"code" => -32000,
"message" =>
"Unauthorized"]
130 $result = $serviceUser->fetch($userId);
133 $serviceUser->loadRights();
135 http_response_code(500);
138 "error" => [
"code" => -32000,
"message" =>
"MCP Service User not found"]
143 http_response_code(503);
146 "error" => [
"code" => -32000,
"message" =>
"MCP Server Misconfigured: AI_MCP_USER_ID not set"]
154require_once DOL_DOCUMENT_ROOT .
'/ai/lib/ai.lib.php';
170 global
$db, $serviceUser;
172 $method = isset($req[
'method']) ? (
string) $req[
'method'] :
'';
173 if ($method !==
'tools/call' || !function_exists(
'ai_log_request')) {
177 $params = isset($req[
'params']) && is_array($req[
'params']) ? $req[
'params'] : [];
178 $toolName = isset($params[
'name']) ? (
string) $params[
'name'] :
'';
179 $toolArgs = isset($params[
'arguments']) ? $params[
'arguments'] : [];
181 $argsJson = is_string($toolArgs) ? $toolArgs : (
string) json_encode($toolArgs);
182 $query =
'[MCP] ' . $toolName .
' ' .
dol_substr($argsJson, 0, 1000);
184 $responseShape = [
'tool' => $toolName,
'arguments' => $toolArgs];
188 if (is_array($resp) && isset($resp[
'error'])) {
190 $errorMsg = is_array($resp[
'error']) && isset($resp[
'error'][
'message'])
191 ? (
string) $resp[
'error'][
'message']
192 : (
string) json_encode($resp[
'error']);
193 } elseif (is_array($resp) && isset($resp[
'result'][
'isError']) && $resp[
'result'][
'isError']) {
195 $errorMsg = is_array($resp[
'result'][
'content'] ??
null) ? (
string) json_encode($resp[
'result'][
'content']) :
'';
198 $rawResStr = is_string($resp) ? $resp : (
string) json_encode($resp);
206 microtime(
true) - $tStart,
217 $tStart = microtime(
true);
220 $rawInput = file_get_contents(
'php://input');
221 if ($rawInput ===
false || strlen($rawInput) > 1024 * 1024) {
222 throw new Exception(
"Invalid or too large request");
225 $request = json_decode($rawInput,
true);
227 if (json_last_error() !== JSON_ERROR_NONE) {
234 if (is_array($request) && array_keys($request) === range(0, count($request) - 1)) {
236 if (count($request) > 20) {
237 http_response_code(413);
240 "error" => [
"code" => -32000,
"message" =>
"Batch too large"]
248 foreach ($request as $req) {
249 if (!is_array($req)) {
253 $reqStart = microtime(
true);
254 $res = $server->handleRequest($req);
264 echo json_encode($responses);
267 if (!is_array($request)) {
268 throw new Exception(
"Invalid request format");
271 $response = $server->handleRequest($request);
273 if ($response !==
null) {
274 echo json_encode($response);
283 '[MCP Server] Fatal error: ' . $e->getMessage(),
292 "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.