UNPKG

socraticode

Version:

SocratiCode — MCP server for local codebase indexing, semantic search, and code dependency graphs. All private, all local via Docker.

123 lines • 4.16 kB
// SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2026 Giancarlo Erra - Altaire Limited /** * Structured logger for SocratiCode MCP server. * Outputs JSON-structured log lines to stderr (stdout is reserved for MCP JSON-RPC). * Also forwards log entries as MCP notifications/message so hosts like Cline * can display them in their UI. * * When SOCRATICODE_LOG_FILE is set to an absolute path, all log entries are * also appended to that file — useful for debugging when the MCP host doesn't * surface log notifications. */ import { appendFileSync } from "node:fs"; const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, }; const envLevel = process.env.SOCRATICODE_LOG_LEVEL?.toLowerCase(); const initialLevel = envLevel && Object.hasOwn(LOG_LEVELS, envLevel) ? envLevel : "info"; let currentLevel = initialLevel; const logFilePath = process.env.SOCRATICODE_LOG_FILE || undefined; // Write a separator so you can tell where each server session begins if (logFilePath) { try { appendFileSync(logFilePath, `\n--- SocratiCode session started at ${new Date().toISOString()} ---\n`); } catch { // Can't write — will be caught again when we try to append. } } function shouldLog(level) { return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]; } /** * Override the active log level. Intended for tests so they don't depend on * `SOCRATICODE_LOG_LEVEL` from the host shell. Production callers normally * leave this alone and rely on the env-derived initial level. */ export function setLogLevel(level) { currentLevel = level; } /** Get the active log level. Useful for tests asserting their setup. */ export function getLogLevel() { return currentLevel; } function formatLog(level, message, context) { const entry = { timestamp: new Date().toISOString(), level, data: message, ...context, }; return JSON.stringify(entry); } /** Maps our log levels to the MCP spec level names. */ function toMcpLevel(level) { return level === "warn" ? "warning" : level; } let _mcpLogSender = null; /** * Register a function that forwards log entries as MCP notifications/message. * Called once from index.ts immediately after `new McpServer(...)`. */ export function setMcpLogSender(fn) { _mcpLogSender = fn; } function sendMcpLog(level, message) { if (_mcpLogSender) { try { _mcpLogSender({ level: toMcpLevel(level), logger: "socraticode", data: message }); } catch { // Transport not yet connected or already closed — silently ignore. } } } function emit(level, message, context) { if (!shouldLog(level)) return; // Always write to log file when configured — this is the reliable path if (logFilePath) { try { appendFileSync(logFilePath, `${formatLog(level, message, context)}\n`); } catch { // Silently ignore write errors } } if (_mcpLogSender) { // MCP transport is active: route via notifications/message only. // Writing to stderr while hosted inside Cline causes Cline to emit // "LOG Server "name" info:" with the content silently dropped (Cline's // Logger only appends extra args in IS_DEV mode). // Include context in the message string so error details aren't silently dropped. const fullMessage = context && Object.keys(context).length > 0 ? `${message} ${JSON.stringify(context)}` : message; sendMcpLog(level, fullMessage); } else { // No MCP transport yet (startup, tests, direct invocation): write JSON to stderr. process.stderr.write(`${formatLog(level, message, context)}\n`); } } export const logger = { debug(message, context) { emit("debug", message, context); }, info(message, context) { emit("info", message, context); }, warn(message, context) { emit("warn", message, context); }, error(message, context) { emit("error", message, context); }, }; //# sourceMappingURL=logger.js.map