devcontext
Version:
DevContext is a cutting-edge Model Context Protocol (MCP) server designed to provide developers with continuous, project-centric context awareness.
384 lines (340 loc) • 10.7 kB
JavaScript
;
/**
* main.js
*
* Main entry point for the MCP server.
* Initializes the database connection and starts the MCP server.
*/
// IMPORTANT: Set MCP_MODE before any imports or other code runs
process.env.MCP_MODE = "true";
// ========== INTERCEPT STDOUT/STDERR BEFORE IMPORTS ==========
// Replace these functions before any imports so they can't be used
// by imported modules to log messages that would break JSON parsing
// Store original methods
const originalStdoutWrite = process.stdout.write;
const originalStderrWrite = process.stderr.write;
const originalConsoleLog = console.log;
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
const originalConsoleInfo = console.info;
const originalConsoleDebug = console.debug;
// Function to test if string is valid JSON
function isValidJson(str) {
try {
JSON.parse(str);
return true;
} catch (e) {
return false;
}
}
// More comprehensive check for valid JSON output
function isValidMcpOutput(str) {
if (typeof str !== "string") return false;
// Trim whitespace
const trimmed = str.trim();
// Must start with { or [ for valid JSON
if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
return false;
}
// Try parsing as JSON
try {
JSON.parse(trimmed);
return true;
} catch (e) {
return false;
}
}
// Completely replace stdout.write - only allow valid JSON through
process.stdout.write = function (chunk, encoding, callback) {
// In MCP mode, strictly validate all output
if (process.env.MCP_MODE === "true") {
if (typeof chunk === "string" && isValidMcpOutput(chunk)) {
return originalStdoutWrite.apply(process.stdout, arguments);
}
// Silently drop invalid output
if (callback) callback();
return true;
} else {
// In normal mode, allow all output
return originalStdoutWrite.apply(process.stdout, arguments);
}
};
// Redirect all stderr to our own handler in MCP mode
process.stderr.write = function (chunk, encoding, callback) {
// In MCP mode, don't output anything to stderr
if (process.env.MCP_MODE === "true") {
// Silently succeed, don't write anything
if (callback) callback();
return true;
} else {
// In normal mode, use original handler
return originalStderrWrite.apply(process.stderr, arguments);
}
};
// Replace all console methods
console.log = function (...args) {
if (process.env.MCP_MODE === "true") {
// In MCP mode, only let valid JSON strings through to stdout
if (
args.length === 1 &&
typeof args[0] === "string" &&
isValidMcpOutput(args[0])
) {
originalStdoutWrite.call(process.stdout, args[0] + "\n");
}
// Silently drop all other output
return;
} else {
// In normal mode, use original method
return originalConsoleLog.apply(console, args);
}
};
console.error = function (...args) {
if (process.env.MCP_MODE === "true") {
// Completely suppress in MCP mode
return;
} else {
return originalConsoleError.apply(console, args);
}
};
console.warn = function (...args) {
if (process.env.MCP_MODE === "true") {
// Completely suppress in MCP mode
return;
} else {
return originalConsoleWarn.apply(console, args);
}
};
console.info = function (...args) {
if (process.env.MCP_MODE === "true") {
// Completely suppress in MCP mode
return;
} else {
return originalConsoleInfo.apply(console, args);
}
};
console.debug = function (...args) {
if (process.env.MCP_MODE === "true") {
// Completely suppress in MCP mode
return;
} else {
return originalConsoleDebug.apply(console, args);
}
};
// ========== NOW SAFE TO IMPORT MODULES ==========
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from "./config.js";
import {
testDbConnection,
initializeDatabaseSchema,
getDbClient,
} from "./db.js";
import { logMessage } from "./utils/logger.js";
import allTools from "./tools/index.js";
import {
createToolHandler,
createInitializeContextHandler,
createFinalizeContextHandler,
} from "./tools/mcpDevContextTools.js";
import { applyDecayToAll } from "./logic/ContextPrioritizerLogic.js";
import { scheduleConsolidation } from "./logic/GlobalPatternRepository.js";
// Store timers for cleanup during shutdown
let decayTimer = null;
/**
* Start the MCP server
* Initializes database and listens for MCP requests
*/
async function startServer() {
// Determine if we're in MCP mode
const isMcpMode = process.env.MCP_MODE === "true";
if (!isMcpMode) {
// === NORMAL MODE STARTUP ===
// Check if database credentials are set
if (!TURSO_DATABASE_URL || !TURSO_AUTH_TOKEN) {
logMessage(
"error",
"Database credentials not set. TURSO_DATABASE_URL and TURSO_AUTH_TOKEN are required."
);
process.exit(1);
}
// Get database client
try {
logMessage("info", "Getting database client...");
const dbClient = getDbClient();
logMessage("info", "Database client created successfully.");
} catch (error) {
logMessage("error", `Failed to create database client: ${error.message}`);
process.exit(1);
}
// Test database connection
try {
logMessage("info", "Testing database connection...");
await testDbConnection();
logMessage("info", "Database connection successful.");
} catch (error) {
logMessage("error", `Database connection failed: ${error.message}`);
process.exit(1);
}
// Initialize database schema
try {
logMessage("info", "Initializing database schema...");
await initializeDatabaseSchema();
logMessage("info", "Database schema initialized successfully.");
} catch (error) {
logMessage(
"error",
`Failed to initialize database schema: ${error.message}`
);
process.exit(1);
}
// Schedule periodic background tasks
try {
// Schedule pattern consolidation (e.g., every hour)
scheduleConsolidation(60);
logMessage("info", "Scheduled periodic pattern consolidation.");
// Schedule context decay (e.g., every 24 hours)
const decayInterval = 24 * 60 * 60 * 1000; // 24 hours
decayTimer = setInterval(async () => {
try {
logMessage("info", "Applying context decay...");
await applyDecayToAll();
logMessage("info", "Context decay applied successfully.");
} catch (err) {
logMessage("error", "Error applying context decay:", {
error: err.message,
});
}
}, decayInterval);
logMessage(
"info",
`Scheduled periodic context decay every ${
decayInterval / (60 * 60 * 1000)
} hours.`
);
} catch (error) {
logMessage(
"warn",
`Failed to schedule background tasks: ${error.message}`
);
// Continue server startup despite scheduling failure
}
} else {
// === MCP MODE STARTUP ===
// In MCP mode, we still need database, but do it silently
// And most importantly, never allow any logging to reach stdout
try {
// Wrap all DB operations in a single try/catch to prevent any errors from escaping
try {
// Create DB client silently
getDbClient();
// Test connection silently
await testDbConnection();
// Init schema silently
await initializeDatabaseSchema();
} catch (innerError) {
// Completely swallow any database errors in MCP mode
// Never log or throw - this prevents stdout corruption
}
// Skip scheduling background tasks in MCP mode
} catch (outerError) {
// Double protection - ensure nothing escapes
// Never log or throw in MCP mode
}
}
// Create and initialize the MCP server
const server = new McpServer({
name: "cursor10x",
version: "2.0.0",
});
// Register all tools with appropriate wrappers
for (const tool of allTools) {
let wrappedHandler;
// Use specialized handlers for initialize and finalize context tools
if (tool.name === "initialize_conversation_context") {
wrappedHandler = createInitializeContextHandler(tool.handler);
} else if (tool.name === "finalize_conversation_context") {
wrappedHandler = createFinalizeContextHandler(tool.handler);
} else {
// Use general handler for other tools
wrappedHandler = createToolHandler(tool.handler, tool.name);
}
// Register the tool with the wrapped handler
server.tool(tool.name, tool.inputSchema, wrappedHandler);
if (!isMcpMode) {
logMessage("info", `Registered tool: ${tool.name}`);
}
}
const transport = new StdioServerTransport();
if (!isMcpMode) {
logMessage("info", `Starting MCP server with PID ${process.pid}...`);
}
// Set up graceful shutdown handler
setupGracefulShutdown();
try {
await server.connect(transport);
if (!isMcpMode) {
logMessage("info", "MCP server stopped.");
}
cleanupTimers();
} catch (error) {
if (!isMcpMode) {
logMessage("error", `MCP server error: ${error.message}`);
}
cleanupTimers();
process.exit(1);
}
}
/**
* Set up handlers for graceful shutdown
*/
function setupGracefulShutdown() {
// Skip logging in MCP mode
const isMcpMode = process.env.MCP_MODE === "true";
// Handle terminal signals
process.on("SIGINT", () => {
if (!isMcpMode) {
logMessage("info", "Received SIGINT signal. Shutting down gracefully...");
}
cleanupTimers();
process.exit(0);
});
process.on("SIGTERM", () => {
if (!isMcpMode) {
logMessage(
"info",
"Received SIGTERM signal. Shutting down gracefully..."
);
}
cleanupTimers();
process.exit(0);
});
}
/**
* Clean up all interval timers
*/
function cleanupTimers() {
const isMcpMode = process.env.MCP_MODE === "true";
if (decayTimer) {
clearInterval(decayTimer);
decayTimer = null;
if (!isMcpMode) {
logMessage("info", "Cleared context decay timer.");
}
}
}
// Run the server unless this file is being required as a module
if (
import.meta.url === import.meta.mainUrl ||
process.env.NODE_ENV !== "test"
) {
startServer().catch((error) => {
// Skip logging in MCP mode
if (process.env.MCP_MODE !== "true") {
logMessage("error", `Unhandled error in startServer: ${error.message}`);
}
// Don't use console.error here since we've replaced it
process.exit(1);
});
}
export { startServer };