mcp-think-tank
Version:
Structured thinking and knowledge management tool for Model Context Protocol
172 lines (171 loc) • 6.55 kB
JavaScript
// src/server.ts
// Import console redirections first to ensure all logging is properly handled
import './utils/console.js';
import { FastMCP } from 'fastmcp';
import { registerMemoryTools } from './memory/tools.js';
import { registerThinkTool } from './think/tools.js';
import { registerTaskTools } from './tasks/tools.js';
import { registerUtilityTools } from './utils/tools.js';
import { registerResearchTools } from './research/index.js';
import { createDirectory } from './utils/fs.js';
import path from 'path';
import * as os from 'os';
import { config } from './config.js';
import { taskStorage } from './tasks/storage.js';
import { wrapFastMCP, ensureDependencies } from './tools/FastMCPAdapter.js';
// Safely log errors to stderr without interfering with stdout JSON
const safeErrorLog = (message) => {
process.stderr.write(`${message}\n`);
};
// Ensure all dependencies are installed
await ensureDependencies();
// Get configuration from environment
const _REQUEST_TIMEOUT = parseInt(process.env.REQUEST_TIMEOUT || '300', 10);
// Create necessary directories
const memoryPath = process.env.MEMORY_PATH || path.join(os.homedir(), '.mcp-think-tank/memory.jsonl');
createDirectory(path.dirname(memoryPath));
// Create FastMCP server
const server = new FastMCP({
name: "MCP Think Tank",
version: config.version // Cast to the expected format
});
// Wrap FastMCP server with ToolManager before registering any tools
wrapFastMCP(server);
// Register memory tools
registerMemoryTools(server);
// Add the 'think' tool for structured reasoning
registerThinkTool(server);
// Register task management tools
registerTaskTools(server);
// Register utility tools
registerUtilityTools(server);
// Register research tools
registerResearchTools(server);
// --- Add FastMCP handshake resources/templates for Cursor compatibility ---
server.addResource({
uri: 'status://health',
name: 'Health',
mimeType: 'text/plain',
load: async () => ({ text: 'ok' })
});
server.addResourceTemplate({
uriTemplate: 'task://{id}',
name: 'Task JSON',
mimeType: 'application/json',
arguments: [{ name: 'id', description: 'Task ID' }],
load: async ({ id }) => ({ text: JSON.stringify({ id }) })
});
// Track active connections
let connectionCount = 0;
let inactivityTimer = null;
let connectionCheckTimer = null;
// Reset inactivity timer
function resetInactivityTimer() {
if (inactivityTimer) {
clearTimeout(inactivityTimer);
inactivityTimer = null;
}
if (config.autoShutdownMs > 0) {
inactivityTimer = setTimeout(() => {
safeErrorLog(`Server inactive for ${config.autoShutdownMs}ms, shutting down...`);
gracefulShutdown();
}, config.autoShutdownMs);
}
}
// Periodically check for active connections
function startConnectionCheck() {
// Clear existing timer if any
if (connectionCheckTimer) {
clearInterval(connectionCheckTimer);
connectionCheckTimer = null;
}
// Check every 60 seconds for active connections
connectionCheckTimer = setInterval(() => {
// For now, we don't have a reliable way to check active connections
// FastMCP doesn't expose a method for this
// This is a placeholder for future implementation
// If AUTO_SHUTDOWN is forced via environment and no connections for a while
if (process.env.FORCE_CHECK_CONNECTIONS === 'true' &&
process.env.AUTO_SHUTDOWN === 'true' &&
connectionCount <= 0) {
safeErrorLog('No active connections detected, auto-shutdown initiated');
gracefulShutdown();
}
// Reset inactivity timer regardless to prevent timeout
resetInactivityTimer();
}, 60000); // 60 second interval
}
// Graceful shutdown function
function gracefulShutdown() {
safeErrorLog('Shutting down MCP Think Tank server...');
// Clear any pending timeouts in task storage
if (taskStorage && typeof taskStorage.clearAllTimeouts === 'function') {
taskStorage.clearAllTimeouts();
}
// Clear inactivity timer if exists
if (inactivityTimer) {
clearTimeout(inactivityTimer);
inactivityTimer = null;
}
// Clear connection check timer if exists
if (connectionCheckTimer) {
clearInterval(connectionCheckTimer);
connectionCheckTimer = null;
}
// Gracefully stop the server (if needed)
try {
// Ensure all pending operations are completed
// FastMCP doesn't currently have a stop method, but we can add safe cleanup here
// Save any pending tasks
taskStorage.saveImmediately();
safeErrorLog('Server shut down successfully');
process.exit(0);
}
catch (err) {
safeErrorLog(`Error during shutdown: ${err}`);
process.exit(1);
}
}
// Handle termination signals
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
process.on('SIGHUP', gracefulShutdown);
// Start the server with error handling
try {
// Determine transport type from environment variable
const transportType = process.env.MCP_TRANSPORT || "stdio";
if (transportType === "http") {
// Ensure the endpoint path starts with a slash
let endpointPath = process.env.MCP_PATH || "/mcp";
if (!endpointPath.startsWith('/')) {
endpointPath = `/${endpointPath}`;
}
// Use SSE transport (Server-Sent Events over HTTP)
server.start({
transportType: "sse",
sse: {
port: parseInt(process.env.MCP_PORT || "8000", 10),
endpoint: endpointPath
}
});
safeErrorLog(`MCP Think Tank server v${config.version} started successfully with SSE/HTTP transport at port ${process.env.MCP_PORT || "8000"}${endpointPath}`);
}
else {
// Default to STDIO for backward compatibility
server.start();
safeErrorLog(`MCP Think Tank server v${config.version} started successfully with STDIO transport`);
}
resetInactivityTimer(); // Start inactivity timer
startConnectionCheck(); // Start connection monitoring
}
catch (e) {
safeErrorLog(`Startup failed: ${e}`);
process.exit(1);
}
// Error handling
process.on('uncaughtException', (error) => {
safeErrorLog(`Uncaught exception: ${error.stack || error.message}`);
});
process.on('unhandledRejection', (reason) => {
safeErrorLog(`Unhandled rejection: ${reason instanceof Error ? reason.stack || reason.message : reason}`);
});