privateGPT MCP Server

  • src
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import https from 'https'; import dotenv from 'dotenv'; import net from 'net'; import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs'; import os from 'os'; import crypto from 'crypto'; import moment from 'moment'; // Optional: Entfernen, wenn nicht benötigt import { prefixMessages, messages } from './pgpt-messages.js'; import { logEvent, setAllowWrittenLogfile, LOG_FILE_PATH } from './logger.js'; import figlet from 'figlet'; import chalk from 'chalk'; import { promisify } from 'util'; import express from 'express'; import { createServer as createHttpServer } from 'http'; import { Server as SocketIoServer } from 'socket.io'; import chokidar from 'chokidar'; import stripAnsi from 'strip-ansi'; import tls from 'tls'; // Promisifizieren von figlet.text für die Verwendung mit async/await const figletAsync = promisify(figlet.text); dotenv.config({ path: './pgpt.env' }); // Geben Sie explizit den Pfad zur Datei an // JSON-Datei laden // `__dirname`-Ersatz für ES-Module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // JSON-Dateipfad relativ zum Skript const envFilePath = path.resolve(__dirname, '../pgpt.env.json'); let envConfig; try { envConfig = JSON.parse(fs.readFileSync(envFilePath, 'utf-8')); } catch (error) { logEvent('system', 'conf', 'Env Load Err', error.message, 'error'); process.exit(1); } // Helper-Funktionen function getEnvVar(key, nestedPath = null, fallback = null) { // Prüfen, ob ein verschachtelter Pfad angegeben ist if (nestedPath) { const value = nestedPath.reduce((acc, part) => acc && acc[part], envConfig); if (value === undefined || value === null) { if (fallback !== null) return fallback; logEvent( 'system', 'conf', 'Missing Config', `Missing .json configuration variable: ${key}`, 'error' ); process.exit(1); } return value; } // Direkter Zugriff if (envConfig[key] === undefined || envConfig[key] === null) { if (fallback !== null) return fallback; logEvent( 'system', 'conf', 'Missing Config', `Missing .json configuration variable: ${key}`, 'error' ); process.exit(1); } return envConfig[key]; } // Nachrichten basierend auf Sprache let lang = getEnvVar('LANGUAGE', ['Server_Config', 'LANGUAGE'], 'en').toLowerCase(); if (!(lang in messages)) { logEvent('system', 'conf', 'Lang Warning', `Language "${lang}" is not supported. Fallback in English.`, 'warn'); lang = 'en'; } const t = messages[lang]; const l = prefixMessages[lang]; /** * Funktion zum Anzeigen des Startheaders */ function displayStartHeader() { // Generiere ASCII-Art figlet.text('Fujitsu PGPT MCP-Server', { font: 'Slant', // Schriftart, kann angepasst werden horizontalLayout: 'default', verticalLayout: 'default', width: 80, whitespaceBreak: true }, function(err, data) { if (err) { logEvent('system', 'CLI', l.prefix_Env_Load_Err, t.errorCreatingAsciiArt, 'error'); console.dir(err); return; } // Farbige Ausgabe mit Chalk console.log( chalk.green.bold(data) + '\n' + chalk.blue(`${t.mcpVersion} 2.1.0\n`) + chalk.yellow(`${t.mcpPort} ${Port}\n`) + chalk.cyan(`${t.mcpStartTime} ${new Date().toLocaleString()}\n`) + chalk.magenta(`${t.mcpLicense} MIT`) ); }); } const privateApiUrl = getEnvVar('PRIVATE_GPT_API_URL', ['PGPT_Url', 'PRIVATE_GPT_API_URL']); const requestedLang = getEnvVar('LANGUAGE', ['Server_Config', 'LANGUAGE'], 'en').toLowerCase(); const apiUrl = getEnvVar('API_URL', ['PGPT_Url', 'API_URL']); const Port = getEnvVar('PORT', ['Server_Config', 'PORT'], '5000'); const restrictedGroups = getEnvVar('RESTRICTED_GROUPS', ['Restrictions', 'RESTRICTED_GROUPS'], 'false').toString(); const OpenAICompAPI = getEnvVar('ENABLE_OPEN_AI_COMP_API', ['Restrictions', 'ENABLE_OPEN_AI_COMP_API'], 'false').toString(); const sslValidate = getEnvVar('SSL_VALIDATE', ['Server_Config', 'SSL_VALIDATE'], 'false').toString(); const enableTLS = getEnvVar('ENABLE_TLS', ['Server_Config', 'ENABLE_TLS'], 'false').toLowerCase() === 'true'; const PwEncryption = getEnvVar('PW_ENCRYPTION', ['Server_Config', 'PW_ENCRYPTION'], 'false') === 'true'; const AllowKeygen = getEnvVar('ALLOW_KEYGEN', ['Server_Config', 'ALLOW_KEYGEN'], 'false') === 'true'; const allowWrittenLogfile = getEnvVar('WRITTEN_LOGFILE', ['Logging', 'WRITTEN_LOGFILE'], 'false').toString(); const LogIps = getEnvVar('LOG_IPs', ['Logging', 'LOG_IPs'], 'false').toString(); const anonymousMode = getEnvVar('ANONYMOUS_MODE', ['Logging', 'ANONYMOUS_MODE'], 'false').toString(); // Funktion zur Pfad-Expansion function expandPath(filePath) { if (filePath.startsWith('~')) { return path.join(os.homedir(), filePath.slice(1)); } return filePath; } // Load the public key const publicKeyPath = expandPath(getEnvVar('PUBLIC_KEY', ['Server_Config', 'PUBLIC_KEY'])); const privateKeyPath = expandPath(getEnvVar('PRIVATE_KEY', ['Server_Config', 'PRIVATE_KEY'])); const sslKeyPath = expandPath(getEnvVar('SSL_KEY_PATH', ['Server_Config', 'SSL_KEY_PATH'])); const sslCertPath = expandPath(getEnvVar('SSL_CERT_PATH', ['Server_Config', 'SSL_CERT_PATH'])); let publicKey; let privateKey; try { publicKey = fs.readFileSync(publicKeyPath, 'utf8'); privateKey = fs.readFileSync(privateKeyPath, 'utf8'); logEvent('system', 'conf', l.prefix_Public_API_URL, publicKey, 'info'); // Möglicherweise hier ein anderer Prefix benötigt logEvent('system', 'conf', l.prefix_Private_API_URL, privateKey, 'info'); // Möglicherweise hier ein anderer Prefix benötigt } catch (error) { logEvent('system', 'conf', l.prefix_File_Path, error.path, 'error'); logEvent('system', 'conf', l.prefix_Env_Load_Err, error.message, 'error'); process.exit(1); // Abbrechen, falls Schlüssel nicht geladen werden können } if (PwEncryption) { logEvent('system', 'conf', l.prefix_PW_Encryption, t.passwordEncEnabled, 'info'); } else { logEvent('system', 'conf', l.prefix_PW_Encryption, t.passwordEncDisabled, 'info'); } function validateUrl(url, t) { if (!url.startsWith('https://')) { logEvent('system', 'conf', l.prefix_URL_Warning, t.apiUrlWarning, 'warn'); url = url.replace(/^http:\/\//, 'https://'); } url = url.replace(/([^:]\/)\/+/g, '$1'); // Doppelte Schrägstriche nach "://" entfernen if (!url.endsWith('/api/v1')) { logEvent('system', 'conf', l.prefix_URL_Warning_V1, t.apiUrlWarningV1, 'warn'); url = `${url.replace(/\/$/, '')}/api/v1`; } try { new URL(url); } catch { logEvent('system', 'conf', l.prefix_URL_Invalid, `${t.apiUrlInvalid} ${url}`, 'error'); process.exit(1); } return url; } function validatePort(port, t) { const portNumber = parseInt(port, 10); if (isNaN(portNumber) || portNumber < 1 || portNumber > 65535) { logEvent('system', 'conf', l.prefix_Port_Invalid, t.portInvalid, 'error'); process.exit(1); } return portNumber; } function validateBoolean(varName, value, t, useProxy = false) { if (useProxy && (varName === 'HEADER_ENCRYPTED')) { if (value !== 'true' && value !== 'false') { logEvent('system', 'conf', l.prefix_Validation_Err, t.validationError.replace('${var}', varName).replace('${value}', value), 'error'); process.exit(1); } return value === 'true'; } // Allgemeine Validierung if (value !== 'true' && value !== 'false') { logEvent('system', 'conf', l.prefix_Validation_Err, t.validationError.replace('${var}', varName).replace('${value}', value), 'error'); process.exit(1); } return value === 'true'; } /** * Decrypt a cryptographic string using the private key. * @param {string} encryptedData - The encrypted string in Base64 format. * @returns {string} - The decrypted password. */ function decryptPassword(encryptedData) { try { const decryptedPassword = crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING, // Ensure consistent padding }, Buffer.from(encryptedData, 'base64') ).toString('utf8'); return decryptedPassword; } catch (error) { logEvent('system', 'conf', l.prefix_PW_Encryption, error.message, 'error'); // Möglicherweise hier ein anderer Prefix benötigt throw new Error(t.decryptPwdError); } } // Funktion für Verschlüsselung function encryptWithPublicKey(data) { try { return crypto.publicEncrypt( { key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING, // Explicitly set padding }, Buffer.from(data) ).toString('base64'); } catch (err) { logEvent('system', 'conf', l.prefix_PW_Encryption, err.message, 'error'); // Möglicherweise hier ein anderer Prefix benötigt throw new Error(t.encryptPwdError); } } /** * Encrypt a given password and return the encrypted key. * @param {string} password - The password to be encrypted. * @returns {string} - The encrypted password as a Base64 string. */ function getEncryptedKey(password) { try { return encryptWithPublicKey(password); } catch (err) { // Die Fehlerbehandlung wurde bereits in encryptWithPublicKey durchgeführt throw err; } } const enableLogin = getEnvVar('ENABLE_LOGIN', ['Functions', 'ENABLE_LOGIN'], false); const enableLogout = getEnvVar('ENABLE_LOGOUT', ['Functions', 'ENABLE_LOGOUT'], false); const enableChat = getEnvVar('ENABLE_CHAT', ['Functions', 'ENABLE_CHAT'], false); const enableContinueChat = getEnvVar('ENABLE_CONTINUE_CHAT', ['Functions', 'ENABLE_CONTINUE_CHAT'], false); const enableGetChatInfo = getEnvVar('ENABLE_GET_CHAT_INFO', ['Functions', 'ENABLE_GET_CHAT_INFO'], false); const enableDeleteAllChats = getEnvVar('ENABLE_DELETE_ALL_CHATS', ['Functions', 'ENABLE_DELETE_ALL_CHATS'], false); const enableDeleteChat = getEnvVar('ENABLE_DELETE_CHAT', ['Functions', 'ENABLE_DELETE_CHAT'], false); const enableListGroups = getEnvVar('ENABLE_LIST_GROUPS', ['Functions', 'ENABLE_LIST_GROUPS'], false); const enableStoreGroup = getEnvVar('ENABLE_STORE_GROUP', ['Functions', 'ENABLE_STORE_GROUP'], false); const enableDeleteGroup = getEnvVar('ENABLE_DELETE_GROUP', ['Functions', 'ENABLE_DELETE_GROUP'], false); const enableCreateSource = getEnvVar('ENABLE_CREATE_SOURCE', ['Functions', 'ENABLE_CREATE_SOURCE'], false); const enableEditSource = getEnvVar('ENABLE_EDIT_SOURCE', ['Functions', 'ENABLE_EDIT_SOURCE'], false); const enableDeleteSource = getEnvVar('ENABLE_DELETE_SOURCE', ['Functions', 'ENABLE_DELETE_SOURCE'], false); const enableGetSource = getEnvVar('ENABLE_GET_SOURCE', ['Functions', 'ENABLE_GET_SOURCE'], false); const enableListSources = getEnvVar('ENABLE_LIST_SOURCES', ['Functions', 'ENABLE_LIST_SOURCES'], false); const enableStoreUser = getEnvVar('ENABLE_STORE_USER', ['Functions', 'ENABLE_STORE_USER'], false); const enableEditUser = getEnvVar('ENABLE_EDIT_USER', ['Functions', 'ENABLE_EDIT_USER'], false); const enableDeleteUser = getEnvVar('ENABLE_DELETE_USER', ['Functions', 'ENABLE_DELETE_USER'], false); const enableReactivateUser = getEnvVar('ENABLE_REACTIVATE_USER', ['Functions', 'ENABLE_REACTIVATE_USER'], false); // Loggen der Server-Konfiguration logEvent('system', 'conf', l.prefix_Server_Config, JSON.stringify(envConfig, null, 2), 'info'); logEvent('system', 'conf', l.prefix_Private_API_URL, privateApiUrl, 'info'); logEvent('system', 'conf', l.prefix_Public_API_URL, apiUrl, 'info'); logEvent('system', 'conf', l.prefix_Port, Port, 'info'); logEvent('system', 'conf', l.prefix_Language, requestedLang, 'info'); logEvent('system', 'conf', l.prefix_SSL_Validation, sslValidate, 'info'); logEvent('system', 'conf', l.prefix_EnableTLS, enableTLS, 'info'); logEvent('system', 'conf', l.prefix_sslKeyPath, sslKeyPath, 'info'); logEvent('system', 'conf', l.prefix_sslCertPath, sslCertPath, 'info'); logEvent('system', 'conf', l.prefix_PW_Encryption, PwEncryption ? t.encryptionEnabled : t.encryptionDisabled, 'info'); logEvent('system', 'conf', l.prefix_Allow_Keygen, AllowKeygen ? t.keygenEnabled : t.keygenDisabled, 'info'); logEvent('system', 'conf', l.prefix_Private_API_URL, privateKeyPath, 'info'); // Möglicherweise hier ein anderer Prefix benötigt logEvent('system', 'conf', l.prefix_Public_API_URL, publicKeyPath, 'info'); // Möglicherweise hier ein anderer Prefix benötigt logEvent('system', 'conf', l.prefix_Restricted_Groups, restrictedGroups, 'info'); logEvent('system', 'conf', l.prefix_openaicompAPI, OpenAICompAPI, 'info'); logEvent('system', 'conf', l.prefix_WRITTEN_LOGFILE, allowWrittenLogfile, 'info'); logEvent('system', 'conf', l.prefix_LOG_IPs, LogIps, 'info'); logEvent('system', 'conf', l.prefix_ANONYMOUS_MODE, anonymousMode, 'info'); // Loggen der deaktivierten Funktionen const allFunctions = [ { name: 'Login', enabled: enableLogin }, { name: 'Logout', enabled: enableLogout }, { name: 'Chat', enabled: enableChat }, { name: 'Continue Chat', enabled: enableContinueChat }, { name: 'Get Chat Info', enabled: enableGetChatInfo }, { name: 'Delete All Chats', enabled: enableDeleteAllChats }, { name: 'Delete Chat', enabled: enableDeleteChat }, { name: 'List Groups', enabled: enableListGroups }, { name: 'Store Group', enabled: enableStoreGroup }, { name: 'Delete Group', enabled: enableDeleteGroup }, { name: 'Create Source', enabled: enableCreateSource }, { name: 'Edit Source', enabled: enableEditSource }, { name: 'Delete Source', enabled: enableDeleteSource }, { name: 'Get Source', enabled: enableGetSource }, { name: 'List Sources', enabled: enableListSources }, { name: 'Store User', enabled: enableStoreUser }, { name: 'Edit User', enabled: enableEditUser }, { name: 'Delete User', enabled: enableDeleteUser }, { name: 'Reactivate User', enabled: enableReactivateUser }, // { name: 'Open AI compatible API Chat', enabled: enableChat }, // { name: 'Continue Chat', enabled: enableContinueChat }, ]; // Filtern, um nur deaktivierte (false) zu bekommen const disabledFunctions = allFunctions.filter(f => !f.enabled); // Loggen der deaktivierten Funktionen if (disabledFunctions.length === 0) { logEvent( 'system', 'conf', l.prefix_All_Funcs, t.allFunctionsEnabled, 'info' ); } else { logEvent('system', 'conf', l.prefix_Deact_Funcs, t.deactivatedFunctions, 'warn'); disabledFunctions.forEach(func => { logEvent('system', 'conf', func.name, t.functionDisabled, 'warn'); }); } logEvent('system', 'conf', l.prefix_Validation_Err, t.apiUrlValidated.replace('${url}', apiUrl), 'info'); // Debugging für RESTRICTED_GROUPS logEvent( 'system', 'conf', l.prefix_Restricted_Groups, t.accessRestrictedGroups.replace('${val}', envConfig.Restrictions?.RESTRICTED_GROUPS.toString()), 'info' ); // Zugriff und Validierung von RESTRICTED_GROUPS const isRestrictedGroupsEnabled = validateBoolean( 'RESTRICTED_GROUPS', restrictedGroups, t ); logEvent('system', 'conf', l.prefix_Restricted_Groups, t.restrictedGroupsSuccess.replace('${status}', isRestrictedGroupsEnabled.toString()), 'info'); // Zugriff und Validierung von WRITTEN_LOGFILE const isWrittenLogfileEnabled = validateBoolean( 'WRITTEN_LOGFILE', allowWrittenLogfile, t ); logEvent('system', 'conf', l.prefix_WRITTEN_LOGFILE, t.AllowLoggingSuccess.replace('${status}', isWrittenLogfileEnabled.toString()), 'info'); setAllowWrittenLogfile(isWrittenLogfileEnabled); // Zugriff und Validierung von LOG_IPs const isLogIpsEnabled = validateBoolean( 'LOG_IPs', LogIps, t ); logEvent('system', 'conf', l.prefix_LOG_IPs, t.LogIpsSuccess.replace('${status}', isLogIpsEnabled.toString()), 'info'); // Zugriff und Validierung von ANONYMOUS_MODE const isanonymousModeEnabled = validateBoolean( 'ANONYMOUS_MODE', anonymousMode, t ); logEvent('system', 'conf', l.prefix_ANONYMOUS_MODE, t.anonymousModeSuccess.replace('${status}', isanonymousModeEnabled.toString()), 'info'); function baseLogOptions() { return { AllowLoggingEnabled: isWrittenLogfileEnabled, LogIps: isLogIpsEnabled, anonymousMode: isanonymousModeEnabled, }; } // SSL-Validierung const isSSLValidationEnabled = validateBoolean( 'SSL_VALIDATE', getEnvVar('SSL_VALIDATE', ['Server_Config', 'SSL_VALIDATE'], 'false').toString(), t ); const sslSymbol = isSSLValidationEnabled ? '✔️' : '⚠️'; logEvent('system', 'conf', l.prefix_SSL_Validation, t.sslValidationSet .replace('${symbol}', sslSymbol) .replace('${value}', String(isSSLValidationEnabled)), 'info' ); // Port validieren const validatedPort = validatePort(Port, t); logEvent('system', 'conf', l.prefix_Port_Validated, t.portValidated.replace('${port}', validatedPort), 'info'); // Einlesen der Proxy_Config-Parameter const useProxy = validateBoolean( 'USE_PROXY', getEnvVar('USE_PROXY', ['Proxy_Config', 'USE_PROXY'], 'false').toString(), t ); const authHeaderEncrypted = useProxy ? validateBoolean( 'HEADER_ENCRYPTED', getEnvVar('HEADER_ENCRYPTED', ['Proxy_Config', 'HEADER_ENCRYPTED'], 'false').toString(), t, useProxy ) : false; const AccessHeader = useProxy ? getEnvVar('ACCESS_HEADER', ['Proxy_Config', 'ACCESS_HEADER'], null) : null; // Verarbeiten des Proxy-Passworts let ProxyAccessHeader; if (authHeaderEncrypted) { try { ProxyAccessHeader = decryptPassword(AccessHeader); } catch (error) { logEvent('system', 'conf', l.prefix_API_Request_Error, error.message, 'error'); // Möglicherweise hier ein anderer Prefix benötigt process.exit(1); } } else { ProxyAccessHeader = AccessHeader; } // Falls in der HAProxy-Konfiguration ein X-Custom-Header verlangt wird: const customHeaderValue = ProxyAccessHeader; // Optional: Überprüfen, ob alle erforderlichen Proxy-Daten vorhanden sind if (useProxy && authHeaderEncrypted) { if (!ProxyAccessHeader) { logEvent('system', 'conf', l.prefix_API_Request_Error, t.proxyAuthMissing, 'error'); // Möglicherweise hier ein anderer Prefix benötigt process.exit(1); } } // Ausgabe der Proxy-Konfiguration (mit maskiertem Passwort) logEvent('system', 'conf', l.prefix_Proxy_Config, t.proxyUseProxy.replace('${val}', useProxy), 'info'); // Beispiel: Tool-Überprüfung function isToolEnabled(toolName) { const envKey = `ENABLE_${toolName.toUpperCase()}`; if (!(envKey in envConfig.Functions)) { logEvent( 'system', 'N/A', l.prefix_Tool_Warn, t.toolNotDefinedInConfig.replace('${toolName}', toolName), 'warn' ); return false; } return envConfig.Functions[envKey] === true; } /* ################ Helper Functions ############################*/ // Helper-Funktion, um zu prüfen, ob ein Tool aktiviert ist, und eine Fehlermeldung zu generieren function checkToolEnabled(toolName) { if (toolName === "keygen" && AllowKeygen) { return null; } if (toolName === "oai_comp_api") { if (OpenAICompAPI) { return null; } else { logEvent('system', 'N/A', l.prefix_Tool_Disabled, t.toolDisabledLog.replace('${toolName}', toolName), 'error'); } } if (!OpenAICompAPI || !isToolEnabled(toolName)) { logEvent( 'system', 'N/A', l.prefix_Tool_Disabled, t.toolDisabledLog.replace('${toolName}', toolName), 'error' ); return { status: 'error', message: messages[lang].toolDisabledError.replace('${toolName}', toolName), }; } return null; // Tool ist aktiviert } function validateToken(token) { if (!token) { if (!isanonymousModeEnabled) logEvent('system', 'N/A', l.prefix_API_Request_Error, t.missingTokenError, 'error'); // Möglicherweise hier ein anderer Prefix benötigt return { status: 'error', message: t.missingTokenError, statusCode: 401 // Optional für konsistenten HTTP-Status }; } return null; } function getArguments(input) { if (input.arguments) { return input.arguments; } else if (input.params?.arguments) { return input.params.arguments; } else { if (!isanonymousModeEnabled) logEvent( 'system', 'N/A', l.prefix_API_Request_Error, t.invalidArgumentsError.replace('${args}', JSON.stringify(input)), 'error' // Möglicherweise hier ein anderer Prefix benötigt ); return {}; // Leeres Objekt als Fallback } } // Parameter zuordnen const API_URL = apiUrl; const PORT = Port; // Server-Startkonfiguration ausgeben const serverConfig = JSON.stringify({ API_URL, PORT }, null, 2); logEvent('system', 'N/A', l.prefix_Server_Config, t.serverStartedLog, 'info'); // mit Konfiguration: ${serverConfig} // Funktion zum Erstellen der Authorization-Header für Basic Auth function createBasicAuthHeader(username, password) { const authString = Buffer.from(`${username}:${password}`).toString('base64'); return `Basic ${authString}`; } /* const t = { // Beispielhafte Sprachvariablen ConnectionEstablished: 'Verbindung hergestellt', dataReceivedMsg: 'Daten empfangen: ${data}', ResponseSuccessfullySent: 'Antwort erfolgreich gesendet', ConnectionClosed: 'Verbindung geschlossen', ServerStopped: 'Server gestoppt' }; const l = { // Beispielhafte Log-Prefixes prefix_tcpServerError: 'TCP-Server Fehler', prefix_Shutdown: 'Herunterfahren' };*/ export class TcpServerTransport { /** * Konstruktor * @param {number} port - Port, auf dem der Server lauschen soll. * @param {boolean} enableTLS - true, wenn SSL/TLS verwendet werden soll, ansonsten false. * @param {string} sslKeyPath - Pfad zur SSL-Schlüsseldatei (nur benötigt, wenn enableTLS true ist). * @param {string} sslCertPath - Pfad zur SSL-Zertifikatsdatei (nur benötigt, wenn enableTLS true ist). */ constructor(port, enableTLS, sslKeyPath, sslCertPath) { this.port = port; this.server = null; this.clients = new Map(); // Map zur Speicherung der Clients this.enableTLS = enableTLS; // Flag, ob SSL/TLS verwendet werden soll if (this.enableTLS && (!fs.existsSync(sslKeyPath) || !fs.existsSync(sslCertPath))) { logEvent('system', 'TLS', l.prefix_sslError, t.NoTLSCertFound, 'error'); process.exit(1); } // // Falls SSL aktiviert ist, laden wir die TLS-Optionen (Schlüssel und Zertifikat) if (this.enableTLS) { this.tlsOptions = { key: fs.readFileSync(sslKeyPath), cert: fs.readFileSync(sslCertPath) // Falls CA benötigt wird, z. B.: // ca: fs.readFileSync(caPath), // requestCert: false, // oder true, wenn Clients sich per Zertifikat ausweisen sollen }; } } /** * Startet den Server. * @param {Function} onMessage - Callback-Funktion, die auf eingehende Nachrichten reagiert. * @returns {Promise} - Wird erfüllt, wenn der Server erfolgreich gestartet wurde. */ async start(onMessage) { return new Promise((resolve, reject) => { // Entscheide anhand des enableTLS-Flags, ob ein TLS/SSL-Server oder ein normaler TCP-Server gestartet wird. if (this.enableTLS) { this.server = tls.createServer(this.tlsOptions, (socket) => { this.handleConnection(socket, onMessage); }); } else { this.server = net.createServer((socket) => { this.handleConnection(socket, onMessage); }); } // Hinzufügen des 'connection'-Listeners (einmalig außerhalb des createServer-Callbacks) this.server.on('connection', (socket) => { if (!isanonymousModeEnabled) { logEvent('server', socket.remotePort || 'unknown', 'Connection Established', t.ConnectionEstablished, 'info'); } socket.setKeepAlive(true, 30000); // Keep-Alive für jede Verbindung }); // Server starten this.server.listen(this.port, () => { const modus = this.enableTLS ? 'TLS/SSL' : 'Plain TCP'; if (!isanonymousModeEnabled) { logEvent('server', this.port, 'Server Start', `Server lauscht auf Port ${this.port} [${modus}]`, 'info'); } resolve(); }); // Fehlerbehandlung auf Server-Ebene this.server.on('error', (err) => { if (!isanonymousModeEnabled) { logEvent('server', this.port, l.prefix_tcpServerError, `Serverfehler: ${err.message || err}`, 'error'); } reject(err); }); }); } /** * Behandelt eingehende Verbindungen. * @param {object} socket - Das Socket-Objekt der Verbindung. * @param {Function} onMessage - Callback, das auf empfangene Nachrichten reagiert. */ handleConnection(socket, onMessage) { const clientIP = socket.remoteAddress || 'unknown'; const clientPort = socket.remotePort || 'unknown'; this.clients.set(socket, { ip: clientIP, port: clientPort }); // Logging: Neue Verbindung if (isLogIpsEnabled) { if (!isanonymousModeEnabled) { logEvent(clientIP, clientPort, 'Connection New', t.ConnectionEstablished, 'info'); } } else { if (!isanonymousModeEnabled) { logEvent('*****', '****', 'Connection New', t.ConnectionEstablished, 'info'); } } socket.on('error', (err) => { const client = this.clients.get(socket); // ECONNRESET speziell behandeln if (err.code === 'ECONNRESET') { // Wenn gewünscht, nur auf „info“-Level loggen oder gar nicht if (!isanonymousModeEnabled) { if (isLogIpsEnabled) { logEvent( client?.ip || 'unknown', client?.port || 'unknown', 'Socket Warn', t.ErrorConnReset, 'info' ); } else { logEvent( '*****', '*****', 'Socket Warn', t.ErrorConnReset, 'info' ); } } return; } // Andere Fehler normal als 'error' loggen if (!isanonymousModeEnabled) { if (isLogIpsEnabled) { logEvent( client?.ip || 'unknown', client?.port || 'unknown', 'Socket Error', `Socket-Error: ${err.message || err}`, 'error' ); } else { logEvent( '*****', '*****', 'Socket Error', `Socket-Error: ${err.message || err}`, 'error' ); }; } }); // Ereignis: Daten empfangen socket.on('data', async (data) => { const client = this.clients.get(socket); if (isLogIpsEnabled) { if (!isanonymousModeEnabled) { logEvent(client.ip, client.port, 'Data Received', t.dataReceivedMsg.replace('${data}', data.toString()), 'info'); } } else { if (!isanonymousModeEnabled) { logEvent('*****', '****', 'Data Received', t.dataReceivedMsg.replace('${data}', data.toString()), 'info'); } } try { const message = JSON.parse(data.toString()); const response = await onMessage(message); const responseString = JSON.stringify(response) + '\n'; // Delimiter hinzufügen socket.write(responseString, () => { socket.end(); // Verbindung schließen, wenn das Schreiben fertig ist }); // Logging: Erfolgreiche Antwort if (isLogIpsEnabled) { if (!isanonymousModeEnabled) { logEvent(client.ip, client.port, 'Response Sent', t.ResponseSuccessfullySent, 'info'); } } else { if (!isanonymousModeEnabled) { logEvent('*****', '****', 'Response Sent', t.ResponseSuccessfullySent, 'info'); } } } catch (err) { if (isLogIpsEnabled) { if (!isanonymousModeEnabled) { logEvent(client.ip, client.port, 'Error Processing Message', `Fehler: ${err.message || err}`, 'error'); } } else { if (!isanonymousModeEnabled) { logEvent('*****', '****', 'Error Processing Message', `Fehler: ${err.message || err}`, 'error'); } } // Sende eine Fehlerantwort mit Delimiter const errorResponse = JSON.stringify({ error: 'Ungültiges Nachrichtenformat' }) + '\n'; socket.write(errorResponse, () => { socket.end(); // Verbindung schließen }); } }); // Ereignis: Verbindung geschlossen socket.on('close', () => { const client = this.clients.get(socket); if (isLogIpsEnabled) { if (!isanonymousModeEnabled) { logEvent(client.ip, client.port, 'Connection Closed', t.ConnectionClosed, 'info'); } } else { if (!isanonymousModeEnabled) { logEvent('*****', '****', 'Connection Closed', t.ConnectionClosed, 'info'); } } this.clients.delete(socket); // Entferne den Client aus der Map }); // Fehlerbehandlung für einzelne Sockets socket.on('error', (err) => { const client = this.clients.get(socket); if (!isanonymousModeEnabled) { logEvent( client?.ip || 'unknown', client?.port || 'unknown', 'Socket Error', `Socket-Fehler: ${err.message || err}`, 'error' ); } }); } /** * Stoppt den Server und leert die Clientliste. */ async stop() { if (this.server) { this.server.close(() => { if (!isanonymousModeEnabled) { logEvent('server', this.port, l.prefix_Shutdown, t.ServerStopped, 'info'); } this.clients.clear(); // Alle Clients entfernen }); } } } class PrivateGPTServer { constructor() { this.server = new Server({ name: 'pgpt-mcp-server', version: '2.8.0', }, { capabilities: { resources: {}, tools: {}, }, }); const axiosConfig = { baseURL: apiUrl, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, httpsAgent: new https.Agent({ rejectUnauthorized: isSSLValidationEnabled }) }; // Conditionally Header setzen, wenn USE_PROXY = true if (useProxy) { // Nur wenn der Proxy in der envConfig aktiviert ist, // wird der spezielle Header gesetzt axiosConfig.headers['X-Custom-Header'] = customHeaderValue; } else { // Falls du sicherstellen willst, dass der Header *nicht* gesetzt wird: delete axiosConfig.headers['X-Custom-Header']; } // Axios-Instanz anlegen this.axiosInstance = axios.create(axiosConfig); // Interceptors für Logging von Requests und Responses this.axiosInstance.interceptors.request.use((config) => { const headers = { ...config.headers }; if (headers.Authorization) { // Prüfen, ob es sich um Bearer oder Basic handelt if (headers.Authorization.startsWith('Bearer ')) { // Bearer maskieren headers.Authorization = 'Bearer ********'; } else if (headers.Authorization.startsWith('Basic ')) { // Basic maskieren headers.Authorization = 'Basic ********'; } } if (!isanonymousModeEnabled) logEvent('axios', 'ReqHd', l.prefix_requestHeaders, `Headers: ${JSON.stringify(headers)}`, 'info'); return config; }, (error) => { if (!isanonymousModeEnabled) logEvent('axios', 'ReqEr', l.prefix_apiRequestError, t.requestError.replace('${error}', error.message || error), 'error'); return Promise.reject(error); }); this.axiosInstance.interceptors.response.use((response) => { if (!isanonymousModeEnabled) { logEvent( 'axios', 'ResOK', l.prefix_responseReceived, t.responseReceived.replace('${status}', response?.status?.toString() || 'No Status'), 'info' ); } return response; }, (error) => { const errorMsg = error.response ? JSON.stringify(error.response.data) : error.message; if (!isanonymousModeEnabled) logEvent('axios', 'ResEr', l.prefix_apiRequestError, t.responseError.replace('${error}', errorMsg), 'error'); return Promise.reject(error); }); this.setupResourceHandlers(); this.setupToolHandlers(); // Fehlerbehandlung this.server.onerror = (error) => { if (!isanonymousModeEnabled) logEvent( 'server', 'N/A', l.prefix_MCP_Error, t.mcpErrorPrefix.replace('${error}', error.message || JSON.stringify(error, null, 2)), 'error' ); }; process.on('SIGINT', async () => { await this.server.close(); if (!isanonymousModeEnabled) logEvent('process', 'N/A', l.prefix_Shutdown, t.serverShutdownLog, 'info'); process.exit(0); }); } async ensureAuthenticated(token) { if (this.axiosInstance.defaults.headers.common['Authorization']) { delete this.axiosInstance.defaults.headers.common['Authorization']; } this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`; } /** * Login-Funktion mit Logging * @param {string} email - Benutzer-E-Mail * @param {string} password - Benutzer-Passwort * @returns {string} - Authentifizierungs-Token */ async login(email, password) { if (!isanonymousModeEnabled) logEvent(email, 'N/A', l.prefix_login, t.authStarted, 'info'); try { const loginResponse = await this.axiosInstance.post('/login', { email, password }); if (!isanonymousModeEnabled) logEvent( email, 'N/A', l.prefix_loginSuccess, t.loginTokenReceived.replace('${token}', loginResponse.data.data.token), 'info' ); return loginResponse.data.data.token; } catch (error) { if (!isanonymousModeEnabled) logEvent( email, 'N/A', l.prefix_loginError, t.loginErrorPrefix.replace('${error}', error.message || error), 'error' ); throw new Error(t.authFailed); } } /** * Authentifizierung sicherstellen mit Logging * @param {string} token - Authentifizierungs-Token */ async ensureAuthenticated(token) { if (!token) { if (!isanonymousModeEnabled) logEvent('auth', 'check', l.prefix_apiRequestError, t.missingTokenError, 'error'); throw new Error(t.missingTokenError); } if (!isanonymousModeEnabled) logEvent('auth', 'token', l.prefix_apiRequestError, t.settingToken, 'info'); // Entferne den Basic Auth Header, falls vorhanden if (this.axiosInstance.defaults.headers.common['Authorization']) { delete this.axiosInstance.defaults.headers.common['Authorization']; } // Setze das Token als Authorization-Header this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`; if (!isanonymousModeEnabled) logEvent('axios', 'Auth', l.prefix_loginSuccess, t.tokenSetSuccess, 'info'); } /** * Gruppen validieren mit Logging * @param {Array} groups - Gruppenliste * @param {string} token - Authentifizierungs-Token * @returns {Object} - Validierungsergebnis */ async validateGroups(groups, token, IP, Port) { try { if (!isanonymousModeEnabled) logEvent( IP, Port, l.prefix_Incoming_Message, t.checkingGroups.replace('${groups}', JSON.stringify(groups)), 'info' ); if (!token) { if (!isanonymousModeEnabled) logEvent(IP, Port, l.prefix_apiRequestError, t.missingTokenError, 'error'); throw new Error(t.missingTokenGroups); } const response = await this.axiosInstance.get('/groups', { headers: { Authorization: `Bearer ${token}` } }); const availableGroups = response.data?.data?.assignableGroups || []; if (!isanonymousModeEnabled) logEvent(IP, Port, 'Available Groups', t.availableGroups.replace('${availableGroups}', JSON.stringify(availableGroups)), 'info'); const invalidGroups = groups.filter(group => !availableGroups.includes(group)); if (invalidGroups.length > 0) { if (!isanonymousModeEnabled) logEvent( 'validateGroups', 'N/A', l.prefix_validationErr, t.invalidGroupsLog.replace('${groups}', JSON.stringify(invalidGroups)), 'error' ); return { isValid: false, invalidGroups }; } if (!isanonymousModeEnabled) logEvent(IP, Port, 'Validation OK', t.allGroupsValid, 'info'); return { isValid: true }; } catch (error) { const errorMessage = error.response?.data || error.message; if (!isanonymousModeEnabled) logEvent( 'validateGroups', 'N/A', l.prefix_Validation_Err, t.groupValidationErrorPrefix.replace('${error}', errorMessage), 'error' ); throw new Error( error.response?.data?.message || t.fetchGroupsErrorBackup ); } } setupResourceHandlers() { // List available resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] })); // List resource templates this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] })); // Read resource this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (!request.params?.uri) { throw new McpError( ErrorCode.InvalidRequest, t.missingUriParameter ); } throw new McpError(ErrorCode.InvalidRequest, `Invalid URI: ${request.params.uri}`); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { /* 1.0 Login #####################################################################################*/ name: 'login', description: 'User login to retrieve an API token', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'User email address for login' }, password: { type: 'string', description: 'User password for login' } }, required: ['email', 'password'] } }, { /* 1.1 Logout ####################################################################################*/ name: 'logout', description: 'Invalidate the API token', inputSchema: { type: 'object', properties: {} }, }, { /* 2.0 Chat ######################################################################################*/ name: 'chat', description: 'Start or continue a chat with PrivateGPT with optional RAG capabilities', inputSchema: { type: 'object', properties: { question: { type: 'string', description: 'The question or prompt to send' }, usePublic: { type: 'boolean', description: 'Use public knowledge base', default: false }, groups: { type: 'array', items: { type: 'string' }, description: 'Group names for RAG (exclusive with usePublic)', }, language: { type: 'string', description: 'Language code (e.g., "en")', default: 'en' }, }, required: ['question'], }, }, { /* 2.1 Continue Chat #############################################################################*/ name: 'continue_chat', description: 'Continue an existing chat', inputSchema: { type: 'object', properties: { chatId: { type: 'string', description: 'ID of the existing chat to continue' }, question: { type: 'string', description: 'The next question or message in the chat' }, }, required: ['chatId', 'question'], }, }, { /* 2.2 Get Chat Info #############################################################################*/ name: 'get_chat_info', description: 'Retrieve details about an existing chat using its ID', inputSchema: { type: 'object', properties: { chatId: { type: 'string', description: 'ID of the chat to retrieve details for' }, token: { type: 'string', description: 'Authorization token for API access' }, }, required: ['chatId', 'token'], }, }, { /* 3.0 Create Source #############################################################################*/ name: 'create_source', description: 'Create a new source with automatic markdown formatting', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the source' }, content: { type: 'string', description: 'Markdown-formatted content' }, groups: { type: 'array', items: { type: 'string' }, description: 'Optional groups to assign the source to', }, }, required: ['name', 'content'], }, }, { /* 3.1 Get Source ################################################################################*/ name: 'get_source', description: 'Retrieve information about a specific source', inputSchema: { type: 'object', properties: { sourceId: { type: 'string', description: 'ID of the source to retrieve' }, }, required: ['sourceId'], }, }, { /* 3.2 List Sources ##############################################################################*/ name: 'list_sources', description: 'List all sources in a specific group', inputSchema: { type: 'object', properties: { groupName: { type: 'string', description: 'Group name to list sources from' }, }, required: ['groupName'], }, }, { /* 3.3 Edit Source ###############################################################################*/ name: 'edit_source', description: 'Edit an existing source', inputSchema: { type: 'object', properties: { sourceId: { type: 'string', description: 'ID of the source to edit' }, token: { type: 'string', description: 'Authorization token for API access' }, title: { type: 'string', description: 'New title for the source (optional)' }, content: { type: 'string', description: 'New markdown-formatted content for the source (optional)' }, groups: { type: 'array', items: { type: 'string' }, description: 'Updated group(s) to assign to the source (optional)' } }, required: ['sourceId', 'token'] } }, { /* 3.4 Delete Source #############################################################################*/ name: 'delete_source', description: 'Delete a specific source', inputSchema: { type: 'object', properties: { sourceId: { type: 'string', description: 'ID of the source to delete' }, }, required: ['sourceId'], }, }, { /* 4.0 List Groups ###############################################################################*/ name: 'list_groups', description: 'Retrieve personal and assignable groups', inputSchema: { type: 'object', properties: {} }, }, { /* 4.1 Store Group ###############################################################################*/ name: 'store_group', description: 'Create a new group', inputSchema: { type: 'object', properties: { groupName: { type: 'string', description: 'Name of the new group' }, description: { type: 'string', description: 'Description of the new group' }, }, required: ['groupName'], }, }, { /* 4.2 Delete Group ##############################################################################*/ name: 'delete_group', description: 'Delete an existing group', inputSchema: { type: 'object', properties: { groupName: { type: 'string', description: 'Name of the group to delete' }, }, required: ['groupName'], }, }, { /* 5.0 Store User ################################################################################*/ name: 'store_user', description: 'Create a new user', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the user' }, email: { type: 'string', description: 'Email of the user' }, password: { type: 'string', description: 'Password for the user' }, language: { type: 'string', description: 'Preferred language (optional)', default: 'en' }, timezone: { type: 'string', description: 'Timezone (optional)', default: 'Europe/Berlin' }, roles: { type: 'array', items: { type: 'string' }, description: 'Roles to assign (optional)' }, groups: { type: 'array', items: { type: 'string' }, description: 'Groups to assign (optional)' }, usePublic: { type: 'boolean', description: 'Enable public knowledge (optional)', default: false } }, required: ['name', 'email', 'password'] }, }, { /* 5.1 Edit User #################################################################################*/ name: 'edit_user', description: 'Edit an existing user', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'Email of the user to edit' }, name: { type: 'string', description: 'New name for the user (optional)' }, password: { type: 'string', description: 'New password for the user (optional)' }, language: { type: 'string', description: 'Preferred language (optional)' }, timezone: { type: 'string', description: 'Timezone (optional)' }, roles: { type: 'array', items: { type: 'string' }, description: 'Updated roles (optional)' }, groups: { type: 'array', items: { type: 'string' }, description: 'Updated groups (optional)' }, usePublic: { type: 'boolean', description: 'Enable public knowledge (optional)' } }, required: ['email'] } }, { /* 5.2 Delete User ###############################################################################*/ name: 'delete_user', description: 'Delete an existing user', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'Email of the user to delete' } }, required: ['email'] } }, { /* 6.0 pen AI compatible API Chat ######################################################################################*/ name: 'oai_comp_api_chat', description: 'Start or continue a chat with PrivateGPT with optional RAG capabilities', inputSchema: { type: 'object', properties: { question: { type: 'string', description: 'The question or prompt to send' }, usePublic: { type: 'boolean', description: 'Use public knowledge base', default: false }, groups: { type: 'array', items: { type: 'string' }, description: 'Group names for RAG (exclusive with usePublic)', }, language: { type: 'string', description: 'Language code (e.g., "en")', default: 'en' }, }, required: ['question'], }, }, { /* 6.1 Open AI compatible API Continue Chat #############################################################################*/ name: 'oai_comp_api_continue_chat', description: 'Continue an existing chat', inputSchema: { type: 'object', properties: { chatId: { type: 'string', description: 'ID of the existing chat to continue' }, question: { type: 'string', description: 'The next question or message in the chat' }, }, required: ['chatId', 'question'], }, } ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!request.params?.name) { throw new McpError( ErrorCode.InvalidRequest, t.missingToolName ); } try { //await this.ensureAuthenticated(); if (!isanonymousModeEnabled) logEvent( 'system', 'N/A', l.prefix_Handling_Tool_Request, t.handlingToolRequest.replace('${tool}', request.params.name), 'info' ); switch (request.params.name) { /* 1.0 Login ######################################################################################*/ case 'login': { const disabledResponse = checkToolEnabled('login'); if (disabledResponse) return disabledResponse; const { email, password } = request.params.arguments; // Extrahiere email und password aus der Nachricht if (!email || !password) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_loginError, t.loginMissingCredentialsAlternative, 'error'); return { status: 'E10-R-1000', message: t.loginEmailPasswordRequired }; } try { // Aufruf des Login-Endpunkts der API const loginResponse = await this.axiosInstance.post('/login', { email, password }); // Loggen des erfolgreichen Logins if (!isanonymousModeEnabled) logEvent( 'server', 'swreg', l.prefix_loginSuccess, t.loginTokenReceived.replace('${token}', JSON.stringify(loginResponse.data)), 'info' ); return { // Token zurückgeben status: loginResponse.data?.status || 'I10-R-1001', // Dynamisch, falls der API-Status einheitlich ist message: loginResponse.data?.message || '1.0 Login', // API-Nachricht verwenden oder Standardnachricht token: loginResponse.data?.data?.token // Token aus API-Antwort }; } catch (error) { const errorMessage = error.response?.data?.message || error.message || t.unknownError; // Loggen des Fehlers beim Login if (!isanonymousModeEnabled) logEvent( 'server', 'swreg', l.prefix_loginError, t.loginError.replace('${error}', errorMessage), 'error' ); return { status: error.response?.status || 'E10-R-1001', // API-Fehlerstatus oder Standardfehlerstatus message: errorMessage, }; } } /* 1.1 Logout #####################################################################################*/ case 'logout': { const disabledResponse = checkToolEnabled('logout'); if (disabledResponse) return disabledResponse; const { token } = request.params; // Korrigiert von 'message' zu 'request.params' if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_logout, t.extractedToken.replace('${token}', token), 'info'); const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; try { // Setze den Bearer Token als Standard-Authorization-Header await this.ensureAuthenticated(token); // Console-Log für Header zu Debug-Zwecken console.log('Logout Request Headers:', this.axiosInstance.defaults.headers.common); const logoutResponse = await this.axiosInstance.delete('/logout', { headers: { Authorization: `Bearer ${token}` }}) // Loggen des erfolgreichen Logouts if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_logoutSuccess, t.logoutSuccess.replace('${data}', JSON.stringify(logoutResponse.data)), 'info'); return { data: {}, // Optional: Zusätzliche Daten könnten hier eingefügt werden message: 'success', status: 200, // OK }; } catch (error) { const logoutErrorMessage = error.response?.data || error.message; // Loggen des Fehlers beim Logout if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_logoutError, t.logoutError.replace('${error}', logoutErrorMessage), 'error'); return { data: {}, message: error.response?.data?.message || t.logoutFailedTryAgain, status: error.response?.status || 'E11-R-1100', // Internal Server Error oder spezifischer Statuscode }; } } /* 2.0 Chat #######################################################################################*/ case 'chat': { const disabledResponse = checkToolEnabled('chat'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chat, t.extractedToken.replace('${token}', token), 'info'); // Token prüfen und validieren if (!token) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatError, t.noTokenError, 'error'); return { status: 'E20-R-2000', message: t.missingTokenError }; } const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Argument-Validierung if (!args || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatError, t.missingArgumentsError.replace('${args}', JSON.stringify(args)), 'error'); return { status: 'error', message: t.missingArgumentsError.replace('${args}', JSON.stringify(args)), }; } const { question, usePublic, groups, language } = args; // Konflikt zwischen `usePublic` und `groups` lösen if (usePublic && groups && groups.length > 0) { if (!isanonymousModeEnabled) logEvent('system', 'swreg', l.prefix_chatWarning, t.publicGroupsConflictWarning, 'warn'); args.usePublic = false; } try { // Loggen der Chat-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatRequest, t.sendingChatRequest .replace('${question}', question) .replace('${usePublic}', usePublic) .replace('${groups}', JSON.stringify(groups)) .replace('${language}', language), 'info'); const response = await this.axiosInstance.post( '/chats', { question, usePublic: usePublic || false, groups: Array.isArray(groups) ? groups : [groups], language: language || 'de', }, { headers: { Authorization: `Bearer ${token}` } } ); const data = response.data?.data || {}; // Loggen der erfolgreichen Chat-Antwort if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatSuccess, t.chatResponseSuccess.replace('${data}', JSON.stringify(data)), 'info'); // Erfolgsantwort mit Status und Daten return { status: response.data?.status || 'ok', message: response.data?.message || 'Chat erfolgreich.', content: { chatId: data.chatId, answer: data.answer, sources: data.sources || [], }, }; } catch (error) { const chatApiErrorMessage = error.message || error.response?.data; // Loggen des Fehlers bei der Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatApiError, t.chatApiError.replace('${error}', chatApiErrorMessage), 'error'); // Fehlerantwort mit Status und Nachricht return { status: error.response?.status || 'E20-R-2002', message: error.response?.data?.message || t.chatApiErrorDefault, }; } } /* 2.1 Continue Chat ##############################################################################*/ case 'continue_chat': { const disabledResponse = checkToolEnabled('continue_chat'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; if (!args || !args.chatId || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chatError, t.missingChatParams, 'error'); return { status: 'E21-R-2100', message: t.missingChatParams }; } const { chatId, question } = args; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chat, t.conversationContinuation.replace('${chatId}', chatId), 'info'); try { const continueChatResponse = await this.axiosInstance.patch(`/chats/${chatId}`, { question: question, }); // Loggen der erfolgreichen Fortsetzung der Konversation if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chatSuccess, t.conversationSuccess.replace('${data}', JSON.stringify(continueChatResponse.data, null, 2)), 'info'); return { content: { chatId: continueChatResponse.data.data.chatId, answer: continueChatResponse.data.data.answer, sources: continueChatResponse.data.sources || [], message: continueChatResponse.data.message, status: continueChatResponse.data.status, }, }; } catch (error) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chatError, t.apiRequestError.replace('${error}', error.message), 'error'); return { status: error.response?.status || 'E21-R-2101', message: error.response?.data?.message || t.continueConversationError, }; } } /* 2.2 Get Chat Info ##############################################################################*/ case 'get_chat_info': { const disabledResponse = checkToolEnabled('get_chat_info'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; const { chatId } = args; if (!chatId) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_chat_infoError, t.missingChatId, 'error'); return { status: 'E22-R-2200', message: t.missingChatId }; } try { const response = await this.axiosInstance.get(`/chats/${chatId}`, { headers: { Authorization: `Bearer ${token}` } }); const chatData = response.data?.data; if (!chatData) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_chat_infoError, t.noChatData, 'error'); return { status: 'E22-R-2201', message: t.noChatData, }; } // Loggen der abgerufenen Chat-Informationen if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_chat_infoSuccess, t.chatInfoRetrieved.replace('${chatData}', JSON.stringify(chatData)), 'info'); return { data: { chatId: chatData.chatId, title: chatData.title || 'Unbenannter Chat', language: chatData.language || 'Unbekannt', groups: chatData.groups || [], messages: chatData.messages || [] }, message: response.data?.message || 'Erfolgreich abgerufen.' }; } catch (error) { const fetchChatErrorMessage = error.message || error.response?.data; // Loggen des Fehlers beim Abrufen der Chat-Informationen if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_chat_infoError, t.fetchChatInfoError.replace('${error}', fetchChatErrorMessage), 'error'); return { status: 'E22-R-2202', message: error.response?.data?.message || 'Fehler beim Abrufen der Chat-Informationen.' }; } } /* 2.3 Delete All Chats ##############################################################################*/ case 'delete_all_chats': { const disabledResponse = checkToolEnabled('delete_all_chats'); if (disabledResponse) return disabledResponse; const { token } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; try { const response = await this.axiosInstance.delete('/chats/flush', { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); // Erfolg loggen if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_all_chatsSuccess, t.chatsDeleted, 'info'); return { data: {}, message: response.data?.message || 'success', status: response.status || 200 }; } catch (error) { const deleteChatsErrorMessage = error.message || error.response?.data; // Fehler loggen if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_all_chatsError, t.deleteChatsError.replace('${error}', deleteChatsErrorMessage), 'error'); return { status: error.response?.status || 'E23-R-2300', message: error.response?.data?.message || 'Error deleting all chat history.' }; } } /* 2.4 Delete Specific Chat ##############################################################################*/ case 'delete_chat': { const disabledResponse = checkToolEnabled('delete_chat'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; const { chatId } = args; if (!chatId) { return { status: 'E24-R-2400', message: t.missingChatId }; } try { const response = await this.axiosInstance.delete(`/chats/${chatId}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); // Erfolg loggen if (!isanonymousModeEnabled) { logEvent('server', 'swreg', l.prefix_delete_chatSuccess, t.chatDeleted.replace('${chatId}', chatId), 'info'); } return { data: {}, message: response.data?.message || 'success', status: response.status || 200 }; } catch (error) { const deleteChatErrorMessage = error.message || error.response?.data; // Fehler loggen if (!isanonymousModeEnabled) { logEvent('server', 'swreg', l.prefix_delete_chatError, t.deleteChatError.replace('${chatId}', chatId).replace('${error}', deleteChatErrorMessage), 'error'); } return { status: error.response?.status || 'E24-R-2401', message: error.response?.data?.message || `Error deleting chat with ID ${chatId}.` }; } } /* 3.0 Create Source ##############################################################################*/ case 'create_source': { const disabledResponse = checkToolEnabled('create_source'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; const token = request.params.token; // Validierung: Erforderliche Parameter prüfen if (!token) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceError, t.tokenMissing, 'error'); return { status: 'E30-R-3000', message: t.missingTokenError }; } const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; if (!args || !args.name || !args.content) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceError, t.missingNameAndContent, 'error'); return { status: 'E30-R-3001', message: t.missingNameAndContent }; } const { name, content, groups } = args; try { // Token im Header setzen this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`; // Gruppenvalidierung vorab durchführen if (groups && groups.length > 0) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceGroupCheck, t.checkingGroups.replace('${groups}', JSON.stringify(groups)), 'info'); const response = await this.axiosInstance.get('/groups'); const availableGroups = response.data?.data?.assignableGroups || []; // Ungültige Gruppen ermitteln const invalidGroups = groups.filter(group => !availableGroups.includes(group)); if (invalidGroups.length > 0) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceInvalidGroups, t.invalidGroups.replace('${groups}', invalidGroups.join(', ')), 'error'); return { status: 'E30-R-3002', message: t.invalidGroups.replace('${groups}', invalidGroups.join(', ')), }; } } // API-Aufruf zur Erstellung der Quelle const createSourceResponse = await this.axiosInstance.post( '/sources', { name, content, groups }, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Erstellung der Quelle if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceSuccess, t.createSourceSuccess.replace('${data}', JSON.stringify(createSourceResponse.data)), 'info'); // Erfolgsantwort return { status: createSourceResponse.data?.status || 'ok', message: createSourceResponse.data?.message || 'Quelle erfolgreich erstellt.', data: createSourceResponse.data?.data, }; } catch (error) { const createSourceError = error.response?.data || error.message; // Loggen des Fehlers bei der Erstellung der Quelle if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceError, t.createSourceError.replace('${error}', createSourceError), 'error'); // Fehlerhafte Antwort if (error.response) { if (!isanonymousModeEnabled) { logEvent( 'server', 'swreg', l.prefix_create_sourceResponseError, t.returnStatus.replace('${Status}', error.response.status) + `, Data: ${JSON.stringify(error.response.data)}`, 'error' ); } return { status: 'E30-R-3003', message: 'Ein Fehler ist aufgetreten.', details: { status: error.response.status, headers: error.response.headers, data: error.response.data, }, }; } else if (error.request) { if (!isanonymousModeEnabled) logEvent( 'server', 'swreg', l.prefix_create_sourceNoResponse, t.noServerResponse + ` ${JSON.stringify(error.request)}`, 'error' ); return { status: 'E30-R-3004', message: t.noServerResponse, details: { request: error.request }, }; } else { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_create_sourceUnknownError, t.create_sourceUnknownError.replace('${error}', error.message), 'error'); return { status: 'E30-R-3005', message: error.message || t.unknownError, }; } } } /* 3.1 Get Source #################################################################################*/ case 'get_source': { const disabledResponse = checkToolEnabled('get_source'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_sourceRequest, t.makingGetSourceRequest.replace('${args}', JSON.stringify(args, null, 2)), 'info'); try { const getSourceResponse = await this.axiosInstance.get(`/sources/${args.sourceId}`); // Loggen der erhaltenen Quellenantwort if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_sourceSuccess, t.gotGetSourceResponse.replace('${data}', JSON.stringify(getSourceResponse.data, null, 2)), 'info'); return { content: [ { type: 'text', text: JSON.stringify(getSourceResponse.data, null, 2) } ] }; } catch (error) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_get_sourceError, t.errorHandlingRequest.replace('${error}', error.message || JSON.stringify(error, null, 2)), 'error'); return { status: 'E31-R-3101', message: t.apiErrorDetails .replace('${status}', error.response?.status || 'E31-R-3151') .replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), }; } } /* 3.2 List Sources ##############################################################################*/ case 'list_sources': { const disabledResponse = checkToolEnabled('list_sources'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_sourcesRequest, t.makingListSourcesRequest.replace('${args}', JSON.stringify(args, null, 2)), 'info'); try { const listSourcesResponse = await this.axiosInstance.post('/sources/groups', { groupName: args.groupName }); // Loggen der erhaltenen Quellenliste if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_sourcesSuccess, t.gotListSourcesResponse.replace('${data}', JSON.stringify(listSourcesResponse.data, null, 2)), 'info'); return { content: [ { type: 'text', text: JSON.stringify(listSourcesResponse.data, null, 2) } ] }; } catch (error) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_sourcesError, t.apiRequestError.replace('${error}', error.message || JSON.stringify(error, null, 2)), 'error'); return { status: 'E32-R-3210', message: error.response?.data?.message || t.fetchSourcesError, }; } } /* 3.3 Edit Source ################################################################################*/ case 'edit_source': { const disabledResponse = checkToolEnabled('edit_source'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const { sourceId, title, content, groups } = args; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Validierung: Erforderliche Parameter if (!sourceId) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_sourceError, t.sourceIdRequiredEditSource, 'error'); return { data: {}, message: t.missingParameterError.replace('${parameter}', 'sourceId'), status: 'E33-R-3300', // Bad Request }; } // Loggen des Beginns der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_source, t.editSourceLog.replace('${sourceId}', sourceId).replace('${title}', title || 'unverändert'), 'info'); try { // Nur Felder senden, die tatsächlich aktualisiert werden sollen const payload = {}; if (title) payload.title = title; if (content) payload.content = content; if (groups) payload.groups = groups; const editSourceResponse = await this.axiosInstance.patch( `/sources/${sourceId}`, payload, { headers: { Authorization: `Bearer ${token}` // Nutze den bereitgestellten Token }, } ); // Loggen der erfolgreichen Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_sourceSuccess, t.editSourceSuccess.replace('${data}', JSON.stringify(editSourceResponse.data, null, 2)), 'info'); // Erfolgreiche Antwort return { data: editSourceResponse.data?.data || {}, // Optionale Daten aus der API message: editSourceResponse.data?.message || 'Quelle erfolgreich bearbeitet.', status: editSourceResponse.status || 200, // OK }; } catch (error) { const editSourceError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_sourceError, t.editSourceError.replace('${error}', editSourceError), 'error'); // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || 'Bearbeiten der Quelle fehlgeschlagen. Bitte versuchen Sie es später erneut.', status: error.response?.status || 'E33-R-3301', // Internal Server Error }; } } /* 3.4 Delete Source ##############################################################################*/ case 'delete_source': { const disabledResponse = checkToolEnabled('delete_source'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; // Validierung: Token erforderlich const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; const { sourceId } = args; // Validierung: sourceId erforderlich if (!sourceId) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_sourceError, t.sourceIdRequiredDeleteSource, 'error'); return { data: {}, message: t.missingParameterError.replace('${parameter}', 'sourceId'), status: 'E34-R-3400', // Bad Request }; } try { // API-Aufruf: Quelle löschen const deleteResponse = await this.axiosInstance.delete(`/sources/${sourceId}`, { headers: { Authorization: `Bearer ${token}` }, }); // Loggen der erfolgreichen Quellenlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_sourceSuccess, t.deleteSourceSuccess.replace('${data}', JSON.stringify(deleteResponse.data, null, 2)), 'info'); // Erfolgreiche Antwort return { data: deleteResponse.data?.data || {}, // Optionale Daten aus der API message: deleteResponse.data?.message || 'Quelle erfolgreich gelöscht.', status: deleteResponse.status || 200, // OK }; } catch (error) { // Loggen des Fehlers bei der Quellenlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_sourceError, t.deleteSourceError.replace('${error}', error.message || JSON.stringify(error.response?.data)), 'error'); if (axios.isAxiosError(error)) { const message = error.response?.data?.message || 'Fehler beim Löschen der Quelle.'; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_apiRequestError, t.apiErrorDetails.replace('${status}', error.response?.status || 'E41-R-4100').replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), 'error'); return { content: [ { type: 'text', text: `API-Fehler: ${message}`, }, ], isError: true, }; } throw new McpError( ErrorCode.InternalError, t.deleteSourceInternalError ); } } /* 4.0 List Groups ################################################################################*/ case 'list_groups': { const disabledResponse = checkToolEnabled('list_groups'); if (disabledResponse) return disabledResponse; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_groupsRequest, t.makingListGroupsRequest, 'info'); try { const listGroupsResponse = await this.axiosInstance.get('/groups'); // Loggen der erfolgreichen Gruppenliste if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_groupsSuccess, t.gotListGroupsResponse.replace('${data}', JSON.stringify(listGroupsResponse.data)), 'info'); return { content: [ { type: 'text', text: JSON.stringify(listGroupsResponse.data, null, 2) } ] }; } catch (error) { const fetchGroupsError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers beim Abrufen der Gruppen if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_list_groupsError, t.fetchGroupsError.replace('${error}', fetchGroupsError), 'error'); return { status: 'E40-R-4051', message: `${t.fetchGroupsErrorPrefix} ${error.response?.data?.message || t.unknownError}`, }; } } /* 4.1 Store Group ################################################################################*/ case 'store_group': { const disabledResponse = checkToolEnabled('store_group'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; if (!args || !args.groupName) { throw new McpError( ErrorCode.InvalidRequest, t.missingGroupParameterStore ); } // Loggen des Beginns der Gruppenspeicherung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_groupRequest, t.storeGroupLog .replace('${groupName}', args.groupName) .replace('${description}', args.description || t.noDescriptionProvided), 'info'); try { const storeGroupResponse = await this.axiosInstance.post('/groups', { groupName: args.groupName, description: args.description || '' }); // Loggen der erfolgreichen Gruppenspeicherung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_groupSuccess, t.storeGroupSuccess.replace('${data}', JSON.stringify(storeGroupResponse.data, null, 2)), 'info'); return { content: [ { type: 'text', text: `Gruppe "${args.groupName}" erfolgreich erstellt mit ID: ${storeGroupResponse.data.id}` } ] }; } catch (error) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_groupError, t.errorHandlingRequest.replace('${error}', error.message || JSON.stringify(error, null, 2)), 'error'); if (axios.isAxiosError(error)) { const message = error.response?.data?.message ?? error.message; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_apiRequestError, t.apiErrorDetails.replace('${status}', error.response?.status || 'E41-R-4100').replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), 'error'); return { content: [ { type: 'text', text: `API-Fehler: ${message}` } ], isError: true }; } throw error; } }; /* 4.2 Delete Group ###############################################################################*/ case 'delete_group': { const disabledResponse = checkToolEnabled('delete_group'); if (disabledResponse) return disabledResponse; const { groupName } = request.params.arguments; // Extrahiere die Gruppe if (!groupName) { throw new McpError( ErrorCode.InvalidRequest, t.missingGroupParameterDelete ); } // Loggen des Beginns der Gruppenlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_groupRequest, t.deleteGroupLog.replace('${groupName}', groupName), 'info'); try { // API-Aufruf mit dem notwendigen JSON-Body const deleteGroupResponse = await this.axiosInstance.delete('/groups', { data: { groupName }, // JSON-Body für den DELETE-Request }); // Loggen der erfolgreichen Gruppenlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_groupSuccess, t.deleteGroupSuccessLog.replace('${data}', JSON.stringify(deleteGroupResponse.data)), 'info'); return { content: [ { type: 'text', text: `Gruppe "${groupName}" wurde erfolgreich gelöscht.`, }, ], }; } catch (error) { // Loggen des Fehlers bei der Gruppenlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_groupError, t.apiRequestError.replace('${error}', error.message || JSON.stringify(error.response?.data)), 'error'); if (axios.isAxiosError(error)) { const message = error.response?.data?.message || 'Fehler beim Löschen der Gruppe.'; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_apiRequestError, t.apiErrorDetails.replace('${status}', error.response?.status || 'E41-R-4100').replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), 'error'); return { content: [ { type: 'text', text: `API-Fehler: ${message}`, }, ], isError: true, }; } throw new McpError( ErrorCode.InternalError, t.deleteGroupInternalError ); } } /* 5.0 Store User ################################################################################*/ case 'store_user': { const disabledResponse = checkToolEnabled('store_user'); if (disabledResponse) return disabledResponse; // Token und Argumente aus request.params entnehmen const { token, arguments: args } = request.params; // Token validieren const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Erforderliche Felder prüfen if (!args || !args.name || !args.email || !args.password) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_userError, t.missingNameEmailPwd, 'error'); return { status: 'E50-R-5000', message: t.missingNameEmailPwd }; } try { // Benutzer anlegen const response = await this.axiosInstance.post( '/users', { name: args.name, email: args.email, password: args.password, language: args.language || 'en', timezone: args.timezone || 'Europe/Berlin', roles: args.roles || [], groups: args.groups || [], usePublic: args.usePublic || false }, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Benutzererstellung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_userSuccess, t.createUserSuccess.replace('${data}', JSON.stringify(createUserResponse.data)), 'info'); // Erfolgreiche Antwort return { status: response.data?.status || 'ok', message: response.data?.message || 'Benutzer erfolgreich erstellt.', data: response.data?.data }; } catch (error) { // Loggen des Fehlers bei der Benutzererstellung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_userError, t.createUserError.replace('${error}', error.response?.data || error.message), 'error'); return { status: error.response?.status || 'E50-R-5001', message: error.response?.data?.message || t.createUserError.replace('${error}', error.response?.data || error.message), }; } } /* 5.1 Edit User #################################################################################*/ case 'edit_user': { const disabledResponse = checkToolEnabled('edit_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Mindestens die E-Mail muss angegeben sein, um den User zu identifizieren if (!args || !args.email) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_userError, t.emailRequiredForEdit, 'error'); return { status: 'E51-R-5100', message: t.emailRequiredForEdit }; } try { // Nur Felder senden, die tatsächlich aktualisiert werden sollen const payload = {}; if (args.name) payload.name = args.name; if (args.password) payload.password = args.password; if (args.language) payload.language = args.language; if (args.timezone) payload.timezone = args.timezone; if (Array.isArray(args.roles)) payload.roles = args.roles; if (Array.isArray(args.groups)) payload.groups = args.groups; if (typeof args.usePublic === 'boolean') payload.usePublic = args.usePublic; // E-Mail ist Pflicht, um den Benutzer auf dem Server zu finden payload.email = args.email; const response = await this.axiosInstance.patch( '/users', payload, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Benutzerbearbeitung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_store_userSuccess, t.editUserSuccess.replace('${data}', JSON.stringify(response.data)), 'info'); return { status: response.data?.status || 'ok', message: response.data?.message || t.editUserSuccess.replace('${data}', JSON.stringify(response.data)), data: response.data?.data }; } catch (error) { const editUserError = error.response?.data || error.message; // Loggen des Fehlers bei der Benutzerbearbeitung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_edit_userError, t.editUserError.replace('${error}', editUserError), 'error'); return { status: error.response?.status || 'E51-R-5101', message: error.response?.data?.message || 'Fehler beim Bearbeiten des Benutzers.' }; } } /* 5.2 Delete User ################################################################################*/ case 'delete_user': { const disabledResponse = checkToolEnabled('delete_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // E-Mail ist nötig, um den Benutzer zu löschen if (!args || !args.email) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_userError, t.emailRequiredForDelete, 'error'); return { status: 'E52-R-5200', message: t.emailRequiredForDelete }; } try { // DELETE-Anfrage mit JSON-Body const response = await this.axiosInstance.delete( '/users', { data: { email: args.email }, headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Benutzerlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_userSuccess, t.deleteUserSuccess.replace('${data}', JSON.stringify(response.data, null, 2)), 'info'); return { status: response.data?.status || 'ok', message: response.data?.message || 'Benutzer erfolgreich gelöscht.', data: response.data?.data }; } catch (error) { // Loggen des Fehlers bei der Benutzerlöschung if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_delete_userError, t.deleteUserError.replace('${error}', error.response?.data || error.message), 'error'); return { status: error.response?.status || 'E52-R-5201', message: error.response?.data?.message || 'Fehler beim Löschen des Benutzers.' }; } } /* 5.3 Reactivate User ################################################################################*/ case 'reactivate_user': { const disabledResponse = checkToolEnabled('reactivate_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // E-Mail ist erforderlich, um einen Benutzer zu reaktivieren if (!args || !args.email) { if (!isanonymousModeEnabled) { logEvent('server', 'swreg', l.prefix_reactivate_userError, t.emailRequiredForReactivate, 'error'); } return { status: 'E53-R-5300', message: t.emailRequiredForReactivate }; } try { // POST-Anfrage mit JSON-Body für die Reaktivierung des Benutzers const response = await this.axiosInstance.post( '/api/v1/users/reactivate', { email: args.email }, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } } ); // Loggen der erfolgreichen Benutzerreaktivierung if (!isanonymousModeEnabled) { logEvent('server', 'swreg', l.prefix_reactivate_userSuccess, `Benutzer erfolgreich reaktiviert: ${JSON.stringify(response.data)}`, 'info'); } return { status: response.data?.status || 'ok', message: response.data?.message || 'Benutzer erfolgreich reaktiviert.', data: response.data?.data }; } catch (error) { // Loggen des Fehlers bei der Benutzerreaktivierung if (!isanonymousModeEnabled) { logEvent('server', 'swreg', l.prefix_reactivate_userError, t.reactivateUserError.replace('${error}', error.response?.data || error.message), 'error'); } return { status: error.response?.status || 'E53-R-5301', message: error.response?.data?.message || 'Fehler bei der Reaktivierung des Benutzers.' }; } } /* 6.0 OpenAPI Compatible API Chat #######################################################################################*/ case 'oai_comp_api_chat': { const disabledResponse = checkToolEnabled('oai_comp_api'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = request.params; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chat, t.extractedToken.replace('${token}', token), 'info'); // Token prüfen und validieren if (!token) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatError, t.noTokenError, 'error'); return { status: 'E60-R-6000', message: t.missingTokenError }; } const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Argument-Validierung if (!args || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatError, t.missingArgumentsError.replace('${args}', JSON.stringify(args)), 'error'); return { status: 'error', message: t.missingArgumentsError.replace('${args}', JSON.stringify(args)), }; } const { question, usePublic, groups, language } = args; // Konflikt zwischen `usePublic` und `groups` lösen if (usePublic && groups && groups.length > 0) { if (!isanonymousModeEnabled) logEvent('system', 'swreg', l.prefix_chatWarning, t.publicGroupsConflictWarning, 'warn'); args.usePublic = false; } try { // Loggen der Chat-Anfrage if (!isanonymousModeEnabled) logEvent('oaichat', 'swreg', l.prefix_chatRequest, t.sendingChatRequest .replace('${question}', question) .replace('${usePublic}', usePublic) .replace('${groups}', JSON.stringify(groups)) .replace('${language}', language), 'info'); const response = await this.axiosInstance.post( '/chats', { question, usePublic: usePublic || false, groups: Array.isArray(groups) ? groups : [groups], language: language || 'de', }, { headers: { Authorization: `Bearer ${token}` } } ); const data = response.data?.data || {}; // Loggen der erfolgreichen Chat-Antwort if (!isanonymousModeEnabled) logEvent('server', 'oareg', l.prefix_chatSuccess, t.chatResponseSuccess.replace('${data}', JSON.stringify(data)), 'info'); // Erfolgsantwort mit Status und Daten return { status: response.data?.status || 'ok', message: response.data?.message || 'Chat erfolgreich.', content: { chatId: data.chatId, answer: data.answer, sources: data.sources || [], }, }; } catch (error) { const chatApiErrorMessage = error.message || error.response?.data; // Loggen des Fehlers bei der Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_chatApiError, t.chatApiError.replace('${error}', chatApiErrorMessage), 'error'); // Fehlerantwort mit Status und Nachricht return { status: error.response?.status || 'E60-R-6002', message: error.response?.data?.message || t.chatApiErrorDefault, }; } } /* 6.1 Continue Chat ##############################################################################*/ case 'oai_comp_api_continue_chat': { const disabledResponse = checkToolEnabled('oai_comp_api'); if (disabledResponse) return disabledResponse; const args = request.params.arguments; if (!args || !args.chatId || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chatError, t.missingChatParams, 'error'); return { status: 'E21-R-6100', message: t.missingChatParams }; } const { chatId, question } = args; if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chat, t.conversationContinuation.replace('${chatId}', chatId), 'info'); try { const continueChatResponse = await this.axiosInstance.patch(`/chats/${chatId}`, { question: question, }); // Loggen der erfolgreichen Fortsetzung der Konversation if (!isanonymousModeEnabled) logEvent('server', 'oareg', l.prefix_continue_chatSuccess, t.conversationSuccess.replace('${data}', JSON.stringify(continueChatResponse.data, null, 2)), 'info'); return { content: { chatId: continueChatResponse.data.data.chatId, answer: continueChatResponse.data.data.answer, sources: continueChatResponse.data.sources || [], message: continueChatResponse.data.message, status: continueChatResponse.data.status, }, }; } catch (error) { if (!isanonymousModeEnabled) logEvent('server', 'swreg', l.prefix_continue_chatError, t.apiRequestError.replace('${error}', error.message), 'error'); return { status: error.response?.status || 'E61-R-6101', message: error.response?.data?.message || t.continueConversationError, }; } } default: // Loggen unbekannter Befehle if (!isanonymousModeEnabled) logEvent( 'system', 'swreg', l.prefix_unknownCommand, t.unknownCommandError.replace('${cmd}', request.params.name), 'warn' ); throw new McpError( ErrorCode.MethodNotFound, t.unknownTool.replace('${toolName}', request.params.name) ); } } catch (error) { if (!isanonymousModeEnabled) logEvent('system', 'swreg', l.prefix_Unhandled_Error, t.errorHandlingRequest.replace('${error}', error.message || JSON.stringify(error, null, 2)), 'error'); if (axios.isAxiosError(error)) { const message = error.response?.data?.message ?? error.message; if (!isanonymousModeEnabled) logEvent('system', 'swreg', l.prefix_apiRequestError, t.apiErrorDetails.replace('${status}', error.response?.status || 'Unknown').replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), 'error'); return { content: [ { type: 'text', text: `API error: ${message}` } ], isError: true }; } throw error; } }); } /* ################################################################################################## # MESSAGE HANDLER ##################################################################################################*/ async run() { const isPortInUse = (port) => new Promise((resolve, reject) => { const tester = net.createServer() .once('error', (err) => (err.code === 'EADDRINUSE' ? resolve(true) : reject(err))) .once('listening', () => tester.once('close', () => resolve(false)).close()) .listen(port); }); const PORT = Port; if (await isPortInUse(PORT)) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_Port_Check, t.portInUse.replace('${PORT}', PORT), 'error'); throw new Error(t.portInUse.replace('${PORT}', PORT)); } const transport = new TcpServerTransport(PORT, enableTLS, sslKeyPath, sslCertPath); await transport.start(async (message) => { try { // if (!isanonymousModeEnabled) logEvent('client', 'swmsg', 'Incoming Message', `Nachricht empfangen: ${JSON.stringify(message)}`, 'info'); if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_Incoming_Message, t.incomingMessage.replace('${MESSAGE}', JSON.stringify(message)), 'info'); // Token-Validierung nur durchführen, wenn es nicht der "login"-Befehl ist // if (message.command !== 'login') { // const tokenValidation = validateToken(message.token); // if (tokenValidation) return tokenValidation; // } if (!message || typeof message !== 'object') { throw new McpError( ErrorCode.InvalidRequest, t.invalidOrEmptyRequest ); } // Verarbeite verschiedene Anfragen dynamisch if (!message.command) { throw new McpError( ErrorCode.InvalidRequest, t.missingCommandParameter ); } switch (message.command) { /* 1.0 Login ######################################################################################*/ // clientIP, clientPort, functionName, status, level = 'info') case 'login': { const disabledResponse = checkToolEnabled('login'); if (disabledResponse) return disabledResponse; // Extrahiere die Argumente aus der Nachricht const args = getArguments(message); const { email, password: Pwd } = args; // Überprüfe, ob die E-Mail und das Passwort vorhanden sind if (!email || !Pwd) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_loginError, t.loginEmailPasswordRequired, 'error'); return { status: 'E10-M-1050', message: t.loginEmailPasswordRequired, }; } let password; // Passwort entschlüsseln, falls erforderlich if (typeof PwEncryption !== 'undefined' && PwEncryption) { password = decryptPassword(Pwd); } else { password = Pwd; } try { // Login-API aufrufen const loginResponse = await this.axiosInstance.post('/login', { email, password }); // Loggen des erfolgreichen Logins if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_loginSuccess, t.loginSuccess.replace('${data}', JSON.stringify(loginResponse.data)), 'info'); // Entfernen des Basic Auth Headers und Setzen des Bearer Tokens delete this.axiosInstance.defaults.headers.common['Authorization']; this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${loginResponse.data.data.token}`; // Token zurückgeben return { status: loginResponse.data?.status || 'ok', // Dynamisch, falls der API-Status einheitlich ist message: loginResponse.data?.message || 'Login erfolgreich.', // API-Nachricht verwenden oder Standardnachricht token: loginResponse.data?.data?.token, // Token aus API-Antwort }; } catch (error) { const errorMessage = error.response?.data || error.message; // Loggen des Fehlers beim Login if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_loginError, t.loginError.replace('${error}', errorMessage), 'error'); return { status: error.response?.status || 'E10-M-1051', // API-Fehlerstatus oder Standardfehlerstatus, message: error.response?.data || error.message || 'no error message' }; } } /* 1.1 Logout #####################################################################################*/ case 'logout': { const disabledResponse = checkToolEnabled('logout'); if (disabledResponse) return disabledResponse; const { token } = message; try { const logoutResponse = await this.axiosInstance.delete('/logout', { headers: { Authorization: `Bearer ${token}` } }); // Loggen des erfolgreichen Logouts if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_logoutSuccess, t.logoutSuccess.replace('${data}', JSON.stringify(logoutResponse.data)), 'info'); return { data: {}, // Optional: Zusätzliche Daten könnten hier eingefügt werden status: logoutResponse.data?.status || 'no status', // Dynamisch, falls der API-Status einheitlich ist message: logoutResponse.data?.message || 'no message', // API-Nachricht verwenden oder Standardnachricht }; } catch (error) { const logoutErrorMessage = error.response?.data || error.message; // Loggen des Fehlers beim Logout if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_logoutError, t.logoutError.replace('${error}', logoutErrorMessage), 'error'); return { data: {}, message: error.response?.data || error.message || t.noErrorMessage, status: error.response?.status || 'E11-R-1150', // Internal Server Error oder spezifischer Statuscode }; } } /* 2.0 Chat #######################################################################################*/ case 'chat': { const disabledResponse = checkToolEnabled('chat'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chat, t.extractedToken.replace('${token}', token), 'info'); // Token prüfen und validieren if (!token) { if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatError, t.noTokenError, 'error'); return { status: 'E20-M-2000', message: t.missingTokenError }; } // Argument-Validierung if (!args || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatError, t.missingArgumentsError.replace('${args}', JSON.stringify(args)), 'error'); return { status: 'error', message: t.missingArgumentsError.replace('${args}', JSON.stringify(args)), }; } const { question, usePublic, groups, language } = args; // Konflikt zwischen `usePublic` und `groups` lösen if (usePublic && groups && groups.length > 0) { if (!isanonymousModeEnabled) logEvent('system', 'swmsg', l.prefix_chatWarning, t.publicGroupsConflictWarning, 'warn'); args.usePublic = false; } try { // Loggen der Chat-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatRequest, t.sendingChatRequest .replace('${question}', question) .replace('${usePublic}', usePublic) .replace('${groups}', JSON.stringify(groups)) .replace('${language}', language), 'info'); const response = await this.axiosInstance.post( '/chats', { question, usePublic: usePublic || false, groups: Array.isArray(groups) ? groups : [groups], language: language || 'de', }, { headers: { Authorization: `Bearer ${token}` } } ); const data = response.data?.data || {}; // Loggen der erfolgreichen Chat-Antwort if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatSuccess, t.chatResponseSuccess.replace('${data}', JSON.stringify(data)), 'info'); // Erfolgsantwort mit Status und Daten return { status: response.data?.status || 'ok', message: response.data?.message || 'Chat erfolgreich.', content: { chatId: data.chatId, answer: data.answer, sources: data.sources || [], }, }; } catch (error) { const chatApiErrorMessage = error.message || error.response?.data; // Loggen des Fehlers bei der Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatApiError, t.chatApiError.replace('${error}', chatApiErrorMessage), 'error'); // Fehlerantwort mit Status und Nachricht return { status: error.response?.status || 'E20-R-2002', message: error.response?.data?.message || t.chatApiErrorDefault, }; } } /* 2.1 Continue Chat ##############################################################################*/ case 'continue_chat': { const disabledResponse = checkToolEnabled('continue_chat'); if (disabledResponse) return disabledResponse; const token = message.token; // Token direkt extrahieren const args = message.arguments || {}; // Sichere Extraktion der Argumente const { chatId, question } = args; if (!args || !args.chatId || !args.question) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_continue_chat, t.missingChatParams, 'error'); return { status: 'E21-M-2150', message: t.missingChatParams }; } try { const continueChatResponse = await this.axiosInstance.patch( `/chats/${chatId}`, { question }, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Continue-Chat-Antwort if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_continue_chatSuccess, t.conversationSuccess.replace('${data}', JSON.stringify(continueChatResponse.data, null, 2)), 'info'); return { content: { chatId: continueChatResponse.data.data.chatId, answer: continueChatResponse.data.data.answer, sources: continueChatResponse.data.sources || [], message: continueChatResponse.data.message, status: continueChatResponse.data.status, }, }; } catch (error) { // Loggen des Fehlers bei der Continue-Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_apiRequestError, t.apiRequestError.replace('${error}', error.message), 'error'); return { status: 'E21-M-2151', message: error.response?.data?.message || error.message || t.noErrorMessage, }; } } /* 2.2 Get Chat Info ##############################################################################*/ case 'get_chat_info': { const disabledResponse = checkToolEnabled('get_chat_info'); if (disabledResponse) return disabledResponse; const { token } = message; // Token direkt aus `message` extrahieren const args = message.arguments || {}; // Argumente aus `message` extrahieren const { chatId } = args; // chatId aus den Argumenten extrahieren if (!chatId) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_chat_info, t.missingChatId, 'error'); return { status: 'E22-M-2250', message: t.missingChatId }; } try { const response = await this.axiosInstance.get(`/chats/${chatId}`, { headers: { Authorization: `Bearer ${token}` } }); const chatData = response.data?.data; if (!chatData) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_chat_infoError, t.noChatData, 'error'); return { status: 'E22-M-2251', message: t.noChatData, }; } // Formatiertes Ergebnis zurückgeben if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_chat_infoSuccess, t.ChatInfoRetrieved.replace('${chatData}', JSON.stringify(chatData)), 'info'); return { data: { chatId: chatData.chatId, title: chatData.title || 'Unbenannter Chat', language: chatData.language || 'Unbekannt', groups: chatData.groups || [], messages: chatData.messages || [] }, message: response.data?.message || 'Erfolgreich abgerufen.' }; } catch (error) { const fetchChatErrorMessage = error.message || error.response?.data; // Loggen des Fehlers beim Abrufen der Chat-Informationen if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_chat_infoError, t.fetchChatInfoError.replace('${error}', fetchChatErrorMessage), 'error'); return { status: 'E22-M-2252', message: error.response?.data?.message || t.unknownError, }; } } /* 2.3 Delete All Chats ##############################################################################*/ case 'delete_all_chats': { const disabledResponse = checkToolEnabled('delete_all_chats'); if (disabledResponse) return disabledResponse; const token = message?.token; if (!token) { return { status: 'E23-M-2301', message: t.missingAuthToken }; } const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; try { const response = await this.axiosInstance.delete('/chats/flush', { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); // Erfolg loggen if (!isanonymousModeEnabled) { logEvent('server', 'swmsg', l.prefix_delete_all_chatsSuccess, t.deleteChatsSuccess, 'info'); } return { data: {}, message: response.data?.message || 'success', status: response.status || 200 }; } catch (error) { const deleteChatsErrorMessage = error.message || error.response?.data; // Fehler loggen if (!isanonymousModeEnabled) { logEvent('server', 'swmsg', l.prefix_delete_all_chatsError, t.deleteChatsError.replace('${error}', deleteChatsErrorMessage), 'error'); } return { status: error.response?.status || 'E23-M-2300', message: error.response?.data?.message || 'Error deleting all chat history.' }; } } /* 2.4 Delete Specific Chat ##############################################################################*/ case 'delete_chat': { const disabledResponse = checkToolEnabled('delete_chat'); if (disabledResponse) return disabledResponse; const { token } = message; // Token direkt aus `message` extrahieren const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; const args = message.arguments || {}; // Argumente aus `message` extrahieren const { chatId } = args; // chatId aus den Argumenten extrahieren if (!chatId) { return { status: 'E24-M-2400', message: t.missingChatId }; } try { const response = await this.axiosInstance.delete(`/chats/${chatId}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); // Erfolg loggen if (!isanonymousModeEnabled) { logEvent('server', 'swmsg', l.prefix_delete_chatSuccess, t.chatDeleted.replace('${chatId}', chatId), 'info'); } return { data: {}, message: response.data?.message || 'success', status: response.status || 200 }; } catch (error) { const deleteChatErrorMessage = error.message || error.response?.data; // Fehler loggen if (!isanonymousModeEnabled) { logEvent('server', 'swmsg', l.prefix_delete_chatError, t.deleteChatError.replace('${chatId}', chatId).replace('${error}', deleteChatErrorMessage), 'error'); } return { status: error.response?.status || 'E24-M-2401', message: error.response?.data?.message || `Error deleting chat with ID ${chatId}.` }; } } /* 3.0 Create Source ##############################################################################*/ case 'create_source': { const disabledResponse = checkToolEnabled('create_source'); if (disabledResponse) return disabledResponse; const args = getArguments(message); const token = message.token; // Validierung: Erforderliche Parameter prüfen if (!token) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_create_source, t.missingTokenError, 'error'); return { status: 'E30-M-3050', message: t.missingTokenError }; } if (!args || !args.name || !args.content) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_create_source, t.missingNameAndContent, 'error'); return { status: 'E30-M-3051', message: t.missingParametersError.replace('${parameters}', 'name und content') }; } const { name, content, groups } = args; try { // Gruppenvalidierung vorab durchführen if (groups && groups.length > 0) { const groupValidation = await this.validateGroups(groups, token, 'client', 'swmsg'); if (!groupValidation.isValid) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_create_sourceInvalidGroups, t.InvalidGroups.replace('${GROUPS}', groupValidation.invalidGroups.join(', ')), 'error'); return { status: 'E30-M-3052', message: t.invalidGroupsError.replace('${invalidGroups}', groupValidation.invalidGroups.join(', ')) }; } } // API-Aufruf zur Erstellung der Quelle const createSourceResponse = await this.axiosInstance.post( '/sources', { name, content, groups }, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Erstellung der Quelle if (!isanonymousModeEnabled) { logEvent( 'client', 'message', l.prefix_create_sourceSuccess, t.createSourceSuccess.replace('${data}', JSON.stringify(createSourceResponse.data)), 'info' ); } // Erfolgsantwort return { status: createSourceResponse.data?.status || 'ok', message: createSourceResponse.data?.message || 'Quelle erfolgreich erstellt.', data: createSourceResponse.data?.data, }; } catch (error) { const createSourceError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Erstellung der Quelle if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_create_sourceError, t.createSourceError.replace('${error}', createSourceError), 'error'); // Fehlerhafte Antwort if (error.response) { return { status: 'E30-M-3053', message: error.response?.data?.message || error.message || t.noErrorMessage, details: { status: error.response.status, headers: error.response.headers, data: error.response.data, }, }; } else if (error.request) { return { status: 'E30-M-3054', message: t.noServerResponse, details: { request: error.request }, }; } else { return { status: 'E30-M-3055', message: error.message || t.unknownError, }; } } } /* 3.1 Get Source #################################################################################*/ case 'get_source': { const disabledResponse = checkToolEnabled('get_source'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; // Extrahiere den Token und die Argumente const { sourceId } = args; // Extrahiere sourceId aus den Argumenten if (!sourceId) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_source, t.sourceIdRequiredRetrieveSource, 'error'); return { status: 'E31-M-3150', message: t.sourceIdRequiredRetrieveSource }; } // Loggen des Beginns der Quellenanfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_source, t.makingGetSourceRequest.replace('${sourceId}', sourceId), 'info'); try { const sourceResponse = await this.axiosInstance.get(`/sources/${sourceId}`, { headers: { Authorization: `Bearer ${token}`, // Nutze den vom Client bereitgestellten Token }, }); // Loggen der erhaltenen Quellenantwort if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_sourceSuccess, t.gotGetSourceResponse.replace('${data}', JSON.stringify(sourceResponse.data, null, 2)), 'info'); return { content: sourceResponse.data, }; } catch (error) { // Loggen des Fehlers bei der Quellenanfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_get_sourceError, t.errorHandlingRequest.replace('${error}', error.message || JSON.stringify(error.response?.data, null, 2)), 'error'); return { status: 'E31-M-3151', message: t.apiErrorDetails .replace('${status}', error.response?.status || 'E31-M-3151') .replace('${data}', JSON.stringify(error.response?.data || {}, null, 2)), }; } } /* 3.2 List Sources ###############################################################################*/ case 'list_sources': { const disabledResponse = checkToolEnabled('list_sources'); if (disabledResponse) return disabledResponse; const { token, attributes } = message; // Extrahiere den Token und die Attribute if (!attributes || !attributes.groupName) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_sources, t.groupNameRequired, 'error'); return { status: 'E32-M-3250', message: t.groupNameRequired }; } const groupName = attributes.groupName; // Extrahiere den groupName aus attributes // Loggen des Beginns der Quellenliste-Anfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_sources, t.fetchingSources.replace('${groupName}', groupName), 'info'); try { // Führe die API-Anfrage aus, um die Quellen für die Gruppe zu erhalten const sourceResponse = await this.axiosInstance.post( '/sources/groups', { groupName }, { headers: { Authorization: `Bearer ${token}`, // Nutze den vom Client bereitgestellten Token }, } ); // Loggen der erhaltenen Quellenliste if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_sourcesSuccess, t.sourcesRetrieved.replace('${data}', JSON.stringify(sourceResponse.data, null, 2)), 'info'); return { content: sourceResponse.data, // Sende die Antwort zurück }; } catch (error) { // Loggen des Fehlers bei der Quellenliste-Anfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_sourcesError, t.groupValidationError.replace('${error}', error.message || JSON.stringify(error.response?.data)), 'error'); return { status: 'E32-M-3251', message: error.response?.data?.message || error.message || t.noErrorMessage, }; } } /* 3.3 Edit Source ################################################################################*/ case 'edit_source': { const disabledResponse = checkToolEnabled('edit_source'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; const { sourceId, title, content, groups } = args; // Validierung: Erforderliche Parameter if (!sourceId) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_source, t.sourceIdRequiredEditSource, 'error'); return { status: 'E33-M-3350', message: t.missingParameterError.replace('${parameter}', 'sourceId'), }; } // Loggen des Beginns der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_source, t.editSourceLog.replace('${sourceId}', sourceId).replace('${title}', title || 'unverändert'), 'info'); try { // Nur Felder senden, die tatsächlich aktualisiert werden sollen const payload = {}; if (title) payload.title = title; if (content) payload.content = content; if (groups) payload.groups = groups; const editSourceResponse = await this.axiosInstance.patch( `/sources/${sourceId}`, payload, { headers: { Authorization: `Bearer ${token}` // Nutze den bereitgestellten Token }, } ); // Loggen der erfolgreichen Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_sourceSuccess, t.editSourceSuccess.replace('${data}', JSON.stringify(editSourceResponse.data, null, 2)), 'info'); // Erfolgreiche Antwort return { status: editSourceResponse.data?.status || 'ok', message: editSourceResponse.data?.message || 'Quelle erfolgreich bearbeitet.', data: editSourceResponse.data?.data }; } catch (error) { const editSourceError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_sourceError, t.editSourceError.replace('${error}', editSourceError), 'error'); // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || 'Bearbeiten der Quelle fehlgeschlagen. Bitte versuchen Sie es später erneut.', status: error.response?.status || 'E33-M-3351', // Internal Server Error }; } } /* 3.4 Delete Source ##############################################################################*/ case 'delete_source': { const disabledResponse = checkToolEnabled('delete_source'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; const { sourceId } = args; if (!sourceId) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_source, t.sourceIdRequiredDeleteSource, 'error'); return { status: 'E34-M-3450', message: t.groupNameRequired.replace('${param}', 'sourceId') }; } // Loggen des Beginns der Quellenlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_source, t.deletesourceLog.replace('${SourceName}', sourceId), 'info'); try { const deleteResponse = await this.axiosInstance.delete(`/sources/${sourceId}`, { headers: { Authorization: `Bearer ${token}` }, }); // Loggen der erfolgreichen Quellenlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_sourceSuccess, t.deleteUserSuccess.replace('${data}', JSON.stringify(deleteResponse.data, null, 2)), 'info'); return { content: deleteResponse.data, }; } catch (error) { const deleteSourceError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_sourceError, t.deleteSourceError.replace('${error}', deleteSourceError), 'error'); return { data: {}, message: error.response?.data?.message || 'Löschen der Quelle fehlgeschlagen. Bitte versuchen Sie es später erneut.', status: error.response?.status || 'E34-M-3451', // Internal Server Error }; } } /* 4.0 List Groups ################################################################################*/ case 'list_groups': { const disabledResponse = checkToolEnabled('list_groups'); if (disabledResponse) return disabledResponse; const { token } = message; // Token direkt extrahieren try { await this.ensureAuthenticated(token); const response = await this.axiosInstance.get('/groups'); let assignableGroups = response.data?.data?.assignableGroups || []; const personalGroups = response.data?.data?.personalGroups || []; const messageText = response.data?.message || 'no_message'; // Fallback für Nachricht const status = response.data?.status || 'E40-M-4050'; // Fallback für Status if (isRestrictedGroupsEnabled) { // Loggen der Einschränkung bei RESTRICTED_GROUPS if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_groupsWarning, t.restrictedGroupsWarning, 'warn'); assignableGroups = ["NO ACCESS ALLOWED BY THE MCP-SERVER CONFIG"]; // Alle assignableGroups entfernen } // Loggen der erfolgreichen Gruppenliste if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_groupsSuccess, t.listGroupsSuccess.replace('${GROUPS}', JSON.stringify(response.data)), 'info'); // )`Gruppenliste abgerufen: ${JSON.stringify(response.data)}`, 'info'); return { data: { personalGroups, assignableGroups, message: messageText, status, }, }; } catch (error) { const fetchGroupsError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers beim Abrufen der Gruppen if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_list_groupsError, t.fetchGroupsError.replace('${error}', fetchGroupsError), 'error'); // Detaillierte Fehlerbehandlung if (axios.isAxiosError(error)) { const status = error.response?.status; const serverMessage = error.response?.data?.message || error.message || 'no error message'; return { status: 'E40-M-4051', message: `${t.fetchGroupsErrorPrefix}: ${serverMessage} (Status: ${status})`, // message: t.fetchGroupsErrorPrefix: ${serverMessage} (Status: ${status}), }; } return { status: 'E40-M-4052', message: t.unknownErrorOccured, }; } } /* 4.1 Store Group ################################################################################*/ case 'store_group': { const disabledResponse = checkToolEnabled('store_group'); if (disabledResponse) return disabledResponse; const { groupName, description } = message.arguments; // Extrahiere die Argumente const clientToken = message.token; // Token aus der Anfrage if (!groupName || !description) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_group, t.missingGroupNameAndDesc, 'error'); return { status: 'E41-M-4150', message: t.missingGroupNameAndDesc }; } if (!clientToken) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_group, t.missingTokenError, 'error'); return { status: 'E41-M-4151', message: t.missingTokenError }; } try { // API-Aufruf mit dem vom Client bereitgestellten Token const createGroupResponse = await this.axiosInstance.post( '/groups', { groupName, description }, { headers: { Authorization: `Bearer ${clientToken}` // Nutze nur den Client-Token } } ); // Loggen der erfolgreichen Gruppenerstellung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_groupSuccess, t.createGroupSuccess.replace('${data}', JSON.stringify(createGroupResponse.data)), 'info'); return { content: createGroupResponse.data, }; } catch (error) { const apiError = error.response?.data?.message || error.message || t.noErrorMessage; if (!isanonymousModeEnabled) { logEvent('client', 'swmsg', l.prefix_store_groupError, t.apiRequestError.replace('${error}', apiError), 'error'); } return { status: 'E41-M-4152', message: error.response?.data?.message || error.message || t.noErrorMessage, }; } } /* 4.2 Delete Group ###################################################################################*/ case 'delete_group': { const disabledResponse = checkToolEnabled('delete_group'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; // Extrahiere Token und Argumente const { groupName } = args; if (!groupName) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_group, t.missingGroupNameParam, 'error'); return { status: 'E42-M-4250', message: t.missingGroupNameParam }; } // Loggen des Beginns der Gruppenlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_group, t.deleteGroupLog.replace('${groupName}', groupName), 'info'); try { // API-Aufruf mit dem Token des Clients const deleteGroupResponse = await this.axiosInstance.delete('/groups', { data: { groupName }, // JSON-Body für DELETE-Request headers: { Authorization: `Bearer ${token}` // Nutze den vom Client bereitgestellten Token }, }); // Loggen der erfolgreichen Gruppenlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_groupSuccess, t.deleteGroupSuccessLog.replace('${data}', JSON.stringify(deleteGroupResponse.data)), 'info'); return { data: deleteGroupResponse.data?.data || {}, message: deleteGroupResponse.data?.message || 'success', status: deleteGroupResponse.status || 200, }; } catch (error) { const deleteSourceError = error.response?.data || error.message; // Loggen des Fehlers bei der Gruppenlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_groupError, t.deleteSourceError.replace('${error}', deleteSourceError), 'error'); return { status: 'E42-M-4251', message: error.response?.data?.message || error.message || t.noErrorMessage, }; } } /* 5.0 Store User #################################################################################*/ case 'store_user': { const disabledResponse = checkToolEnabled('store_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; // Validierung der erforderlichen Parameter if (!args || !args.name || !args.email || !args.password) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_user, t.missingNameEmailPwd, 'error'); return { status: 'E50-M-5050', message: t.missingNameEmailPwd }; } const Pwd = args.password; // Überprüfe, ob das Passwort vorhanden ist if (!Pwd) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_user, t.passwordIsRequired, 'error'); return { status: 'E10-M-1050', message: t.passwordIsRequired }; } let password; // Passwort entschlüsseln, falls erforderlich if (typeof PwEncryption !== 'undefined' && PwEncryption) { password = decryptPassword(Pwd); } else { password = Pwd; } try { // Payload für die API-Anfrage const payload = { name: args.name, email: args.email, language: args.language || 'en', timezone: args.timezone || 'Europe/Berlin', password: password, usePublic: args.usePublic || false, groups: args.groups || [], roles: args.roles || [], activateFtp: args.activateFtp || false, ftpPassword: args.ftpPassword || '', }; // Loggen des Payloads vor der API-Anfrage if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_user, t.createUserLog.replace('${payload}', JSON.stringify(payload)), 'info'); // API-Aufruf const createUserResponse = await this.axiosInstance.post('/users', payload, { headers: { Authorization: `Bearer ${token}` } }); // Loggen der erfolgreichen Benutzererstellung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_userSuccess, t.createUserSuccess.replace('${data}', JSON.stringify(createUserResponse.data)), 'info'); return { content: createUserResponse.data, }; } catch (error) { const createUserError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_store_userError, t.createUserError.replace('${error}', createUserError), 'error'); // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || 'Fehler beim Anlegen des Benutzers.', status: error.response?.status || 'E50-M-5051', // Internal Server Error }; } } /* 5.1 Edit User ##################################################################################*/ case 'edit_user': { const disabledResponse = checkToolEnabled('edit_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; const tokenValidation = validateToken(token); if (tokenValidation) return tokenValidation; // Mindestens die E-Mail muss angegeben sein, um den User zu identifizieren if (!args || !args.email) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_user, t.emailRequiredForEdit, 'error'); return { status: 'E51-M-5100', message: t.emailRequiredForEdit }; } let password = null; if (args.password) { const Pwd = args.password; if (!Pwd) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_user, t.passwordIsRequired, 'error'); return { status: 'E51-M-1050', message: t.passwordIsRequired, }; } if (typeof PwEncryption !== 'undefined' && PwEncryption) { password = decryptPassword(Pwd); } else { password = Pwd; } } try { // Nur Felder senden, die tatsächlich aktualisiert werden sollen const payload = {}; if (args.name) payload.name = args.name; if (args.password) payload.password = password; if (args.language) payload.language = args.language; if (args.timezone) payload.timezone = args.timezone; if (Array.isArray(args.roles)) payload.roles = args.roles; if (Array.isArray(args.groups)) payload.groups = args.groups; if (typeof args.usePublic === 'boolean') payload.usePublic = args.usePublic; // E-Mail ist Pflicht, um den Benutzer auf dem Server zu finden payload.email = args.email; const response = await this.axiosInstance.patch( '/users', payload, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Benutzerbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_userSuccess, t.editUserSuccess.replace('${data}', JSON.stringify(response.data)), 'info'); return { status: response.data?.status || 'ok', message: response.data?.message || 'Benutzer erfolgreich bearbeitet.', data: response.data?.data }; } catch (error) { const editUserError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_edit_userError, t.editUserError.replace('${error}', editUserError), 'error'); // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || t.editUserError.replace('${error}', editUserError), status: error.response?.status || 'E51-M-5151', // Internal Server Error }; } } /* 5.2 Delete User ################################################################################*/ case 'delete_user': { const disabledResponse = checkToolEnabled('delete_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; const { email } = args; if (!email) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_user, t.emailRequiredForDelete, 'error'); return { status: 'E52-M-5250', message: t.emailRequiredForDelete }; } // Loggen des Beginns der Benutzerlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_user, t.deleteUserLog.replace('${UserName}', email), 'info'); try { const response = await this.axiosInstance.delete( '/users', { data: { email }, headers: { Authorization: `Bearer ${token}` }, } ); // Loggen der erfolgreichen Benutzerlöschung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_userSuccess, t.deleteUserSuccess.replace('${data}', JSON.stringify(response.data, null, 2)), 'info'); return { content: response.data, }; } catch (error) { const deleteUserError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Quellenbearbeitung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_delete_userError, t.deleteUserError.replace('${error}', deleteUserError), 'error'); // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || 'Löschen des Users fehlgeschlagen. Bitte versuchen Sie es später erneut.', status: error.response?.status || 'E52-M-5251', // Internal Server Error }; } } /* 5.3 Reactivate User ################################################################################*/ case 'reactivate_user': { const disabledResponse = checkToolEnabled('reactivate_user'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; const { email } = args; if (!email) { if (!isanonymousModeEnabled) { logEvent('client', 'swmsg', l.prefix_reactivate_user, t.emailRequiredForReactivate, 'error'); } return { status: 'E53-M-5300', message: t.emailRequiredForReactivate }; } // Loggen des Beginns der Benutzerreaktivierung if (!isanonymousModeEnabled) { logEvent('client', 'swmsg', l.prefix_reactivate_user, t.reactivateUserLog.replace('${UserName}', email), 'info'); } try { const response = await this.axiosInstance.post( '/api/v1/users/reactivate', { email }, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } } ); // Loggen der erfolgreichen Benutzerreaktivierung if (!isanonymousModeEnabled) { logEvent('client', 'swmsg', l.prefix_reactivate_userSuccess, t.reactivateUserSuccess.replace('${data}', JSON.stringify(response.data, null, 2)), 'info'); } return { content: response.data, }; } catch (error) { const reactivateUserError = error.message || JSON.stringify(error.response?.data); // Loggen des Fehlers bei der Benutzerreaktivierung if (!isanonymousModeEnabled) { logEvent('client', 'swmsg', l.prefix_reactivate_userError, t.reactivateUserError.replace('${error}', reactivateUserError), 'error'); } // Fehlerhafte Antwort return { data: {}, message: error.response?.data?.message || 'Reaktivierung des Benutzers fehlgeschlagen. Bitte versuchen Sie es später erneut.', status: error.response?.status || 'E53-M-5301', // Internal Server Error }; } } /* 6.0 Open AI compatible API Chat #######################################################################################*/ case 'oai_comp_api_chat': { const disabledResponse = checkToolEnabled('oai_comp_api'); if (disabledResponse) return disabledResponse; const { token, arguments: args } = message; if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chat, t.extractedToken.replace('${token}', token), 'info'); // Token prüfen und validieren if (!token) { if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatError, t.noTokenError, 'error'); return { status: 'E60-M-6000', message: t.missingTokenError }; } // Argument-Validierung if (!args || !args.question) { if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatError, t.missingArgumentsError.replace('${args}', JSON.stringify(args)), 'error'); return { status: 'error', message: t.missingArgumentsError.replace('${args}', JSON.stringify(args)), }; } const { question, usePublic, groups, language } = args; // Konflikt zwischen `usePublic` und `groups` lösen if (usePublic && groups && groups.length > 0) { if (!isanonymousModeEnabled) logEvent('system', 'swmsg', l.prefix_chatWarning, t.publicGroupsConflictWarning, 'warn'); args.usePublic = false; } try { // Loggen der Chat-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'oamsg', l.prefix_chatRequest, t.sendingChatRequest .replace('${question}', question) .replace('${usePublic}', usePublic) .replace('${groups}', JSON.stringify(groups)) .replace('${language}', language), 'info'); const response = await this.axiosInstance.post( '/chats', { question, usePublic: usePublic || false, groups: Array.isArray(groups) ? groups : [groups], language: language || 'de', }, { headers: { Authorization: `Bearer ${token}` } } ); const data = response.data?.data || {}; // Loggen der erfolgreichen Chat-Antwort if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatSuccess, t.chatResponseSuccess.replace('${data}', JSON.stringify(data)), 'info'); // Erfolgsantwort mit Status und Daten return { status: response.data?.status || 'ok', message: response.data?.message || 'Chat erfolgreich.', content: { chatId: data.chatId, answer: data.answer, sources: data.sources || [], }, }; } catch (error) { const chatApiErrorMessage = error.message || error.response?.data; // Loggen des Fehlers bei der Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('server', 'swmsg', l.prefix_chatApiError, t.chatApiError.replace('${error}', chatApiErrorMessage), 'error'); // Fehlerantwort mit Status und Nachricht return { status: error.response?.status || 'E60-M-6002', message: error.response?.data?.message || t.chatApiErrorDefault, }; } } /* 6.1 Open AI compatible API Continue Chat ##############################################################################*/ case 'oai_comp_api_continue_chat': { const disabledResponse = checkToolEnabled('oai_comp_api'); if (disabledResponse) return disabledResponse; const token = message.token; // Token direkt extrahieren const args = message.arguments || {}; // Sichere Extraktion der Argumente const { chatId, question } = args; if (!args || !args.chatId || !args.question) { if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_continue_chat, t.missingChatParams, 'error'); return { status: 'E61-M-6150', message: t.missingChatParams }; } try { const continueChatResponse = await this.axiosInstance.patch( `/chats/${chatId}`, { question }, { headers: { Authorization: `Bearer ${token}` } } ); // Loggen der erfolgreichen Continue-Chat-Antwort if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_continue_chatSuccess, t.conversationSuccess.replace('${data}', JSON.stringify(continueChatResponse.data, null, 2)), 'info'); return { content: { chatId: continueChatResponse.data.data.chatId, answer: continueChatResponse.data.data.answer, sources: continueChatResponse.data.sources || [], message: continueChatResponse.data.message, status: continueChatResponse.data.status, }, }; } catch (error) { // Loggen des Fehlers bei der Continue-Chat-API-Anfrage if (!isanonymousModeEnabled) logEvent('client', 'oamsg', l.prefix_apiRequestError, t.apiRequestError.replace('${error}', error.message), 'error'); return { status: 'E61-M-6151', message: error.response?.data?.message || error.message || t.noErrorMessage, }; } } /* 9.0 Generate Key ###############################################################################*/ case 'keygen': { const disabledResponse = checkToolEnabled('keygen'); if (disabledResponse) return disabledResponse; const { password } = message.arguments; try { // Passwort verschlüsseln const encryptedPassword = getEncryptedKey(password); // Loggen der erfolgreichen Key-Generierung if (!isanonymousModeEnabled) logEvent('client', 'swmsg', l.prefix_keygen, t.keygenSuccess, 'info'); // Schlüssel zurückgeben return { data: { key: encryptedPassword }, status: 'ok', message: t.KeygenRequired, }; } catch (error) { // Loggen des Fehlers bei der Key-Generierung if (!isanonymousModeEnabled) logEvent( 'client', 'message', l.prefix_keygenError, t.keygenErrorPrefix + ' ' + error.message, 'error' ); return { data: {}, message: error.message || 'Keygen fehlgeschlagen.', status: 'E90-M-1150', }; } } default: { // Loggen unbekannter Befehle if (!isanonymousModeEnabled) logEvent( 'system', 'message', l.prefix_unknownCommand, t.unknownCommandError.replace('${cmd}', message.command), 'warn' ) return { status: 'E99-M-9950', message: t.unknownCommandError.replace('${cmd}', message.command) }; } } } catch (err) { // Loggen des Fehlers im Haupt-Handler console.log('Error object:', err); console.log('Error message:', err?.message); if (!isanonymousModeEnabled) logEvent('system', 'message', l.prefix_tcpServerError, `${t.tcpServerError} ${err.message || JSON.stringify(err, null, 2)}`, 'error'); return { status: 'E52-M-5252', message: err.message || t.internalServerError }; } }); } } const server = new PrivateGPTServer(); // Log-Viewer Webserver konfigurieren const app = express(); const httpServer = createHttpServer(app); const io = new SocketIoServer(httpServer); // const LOG_FILE_PATH = path.join(__dirname, '../logs/server.log'); // Pfad zu Ihrer Logdatei // Statische Dateien bereitstellen (optional, falls eine HTML-Oberfläche benötigt wird) app.use(express.static(path.join(__dirname, 'public'))); io.on('connection', (socket) => { if (isWrittenLogfileEnabled) { logEvent( 'server', 'websocket', l.prefix_clientConnected, messages[lang].clientConnected, 'info' ); sendLogContent(socket); } else { // Optional: Informieren Sie den Client, dass das Logfile nicht aktiviert ist socket.emit('logUpdate', messages[lang].socketEmitLogNotActivated); } socket.on('disconnect', () => { if (isWrittenLogfileEnabled) { logEvent( 'server', 'websocket', l.prefix_clientDisconnected, messages[lang].clientDisconnected, 'info' ); } }); }); // Funktion, um den Loginhalt an einen Client zu senden const sendLogContent = async (socket) => { try { // Überprüfen, ob die Log-Datei existiert await fs.promises.access(LOG_FILE_PATH, fs.constants.F_OK); let data = await fs.promises.readFile(LOG_FILE_PATH, 'utf8'); data = stripAnsi(data); // nutzt jetzt den Import socket.emit('logUpdate', data); } catch (err) { if (!isanonymousModeEnabled) logEvent( 'server', 'filesystem', l.prefix_logReadError, messages[lang].logReadError.replace('${error}', err.message), 'error' ); socket.emit('logUpdate', messages[lang].socketEmitLogReadError); } }; // Überwachung der Logdatei auf Änderungen, nur wenn Schreiben aktiviert ist if (isWrittenLogfileEnabled) { chokidar.watch(LOG_FILE_PATH).on('change', async () => { try { const data = await fs.promises.readFile(LOG_FILE_PATH, 'utf8'); io.sockets.emit('logUpdate', data); } catch (err) { if (!isanonymousModeEnabled) logEvent( 'server', 'filesystem', l.prefix_logChangeError, messages[lang].logChangeError.replace('${error}', err.message), 'error' ); } }); } // Überwachung der Logdatei auf Änderungen /*chokidar.watch(LOG_FILE_PATH).on('change', async () => { try { const data = await fs.promises.readFile(LOG_FILE_PATH, 'utf8'); io.sockets.emit('logUpdate', data); } catch (err) { if (!isanonymousModeEnabled) logEvent( 'server', 'filesystem', l.prefix_logChangeError, messages[lang].logChangeError.replace('${error}', err.message), 'error' ); } });*/ // Log-Viewer HTTP-Server starten const WEB_PORT = 3000; httpServer.listen(WEB_PORT, () => { if (!isanonymousModeEnabled) logEvent( 'server', 'web', l.prefix_logViewerRunning, messages[lang].logViewerRunning.replace('${port}', WEB_PORT), 'info' ); }); // Server läuft server.run().catch(error => { if (!isanonymousModeEnabled) logEvent( 'server', 'N/A', l.prefix_serverError, messages[lang].serverError.replace('${error}', error.message), 'error' ); }); // Anzeige des Start-Headers displayStartHeader();