falkordb-mcpserver
Version:
Model Context Protocol server for FalkorDB graph databases - enables AI assistants to query and manage graph data using natural language
146 lines (145 loc) • 5.42 kB
JavaScript
import { appendFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { userLogDir } from 'platformdirs';
/**
* Enhanced logging service for MCP servers with both MCP client notifications and file fallback
* Logs are sent to MCP clients when connected, and always written to files for persistence
*/
export class Logger {
logDir;
logFile;
mcpServer;
constructor() {
// For MCP servers, we primarily use MCP notifications for logging
// File logging is optional and only enabled if environment allows it
this.logDir = userLogDir('falkordb-mcp', 'mulliken-llc', '0.1.0', false, true);
this.logFile = join(this.logDir, 'falkordb-mcp.log');
// Only create log directory if we're in development or explicitly enabled
if (process.env.ENABLE_FILE_LOGGING === 'true' || process.env.NODE_ENV === 'development') {
try {
if (!existsSync(this.logDir)) {
mkdirSync(this.logDir, { recursive: true });
}
}
catch (error) {
// If we can't create logs directory, disable file logging
console.warn('Failed to create log directory:', error instanceof Error ? error.message : String(error));
this.logFile = '';
}
}
else {
// Disable file logging by default for MCP servers
this.logFile = '';
}
}
/**
* Set the MCP server instance to enable client notifications
* This should be called after the server is created but before starting
*/
setMcpServer(server) {
this.mcpServer = server;
}
formatLog(level, message, context) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...(context && { context }),
pid: process.pid
};
return JSON.stringify(logEntry) + '\n';
}
writeLog(level, message, context) {
try {
// Always write to file for persistence
const logLine = this.formatLog(level, message, context);
appendFileSync(this.logFile, logLine);
}
catch {
// If we can't log to file, we can't do much about it without breaking MCP
// In production, consider using a more robust logging solution
}
}
async sendMcpLog(level, message, context) {
if (!this.mcpServer) {
return;
}
try {
// Format log data for MCP client
const logData = context ? `${message} | ${JSON.stringify(context)}` : message;
// Send notification to MCP client
await this.mcpServer.server.notification({
method: 'notifications/message',
params: {
level: level.toLowerCase(),
data: logData,
logger: 'falkordb-mcp'
}
});
}
catch {
// If MCP notification fails, just continue - file logging is our fallback
// Don't log this error to avoid infinite loops
}
}
async log(level, message, context) {
// Always write to file
this.writeLog(level, message, context);
// Try to send to MCP client if server is available
await this.sendMcpLog(level, message, context);
}
async info(message, context) {
await this.log('INFO', message, context);
}
async warn(message, context) {
await this.log('WARN', message, context);
}
async error(message, error, context) {
const errorContext = error ? {
name: error.name,
message: error.message,
stack: error.stack,
...error.isOperational !== undefined && {
isOperational: error.isOperational
},
...context
} : context;
await this.log('ERROR', message, errorContext);
}
async debug(message, context) {
if (process.env.NODE_ENV === 'development') {
await this.log('DEBUG', message, context);
}
}
// Synchronous versions for backward compatibility in cases where async isn't possible
infoSync(message, context) {
this.writeLog('INFO', message, context);
// Fire and forget for MCP notification
this.sendMcpLog('INFO', message, context).catch(() => { });
}
warnSync(message, context) {
this.writeLog('WARN', message, context);
this.sendMcpLog('WARN', message, context).catch(() => { });
}
errorSync(message, error, context) {
const errorContext = error ? {
name: error.name,
message: error.message,
stack: error.stack,
...error.isOperational !== undefined && {
isOperational: error.isOperational
},
...context
} : context;
this.writeLog('ERROR', message, errorContext);
this.sendMcpLog('ERROR', message, errorContext).catch(() => { });
}
debugSync(message, context) {
if (process.env.NODE_ENV === 'development') {
this.writeLog('DEBUG', message, context);
this.sendMcpLog('DEBUG', message, context).catch(() => { });
}
}
}
// Export singleton instance
export const logger = new Logger();