import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { UserSessionManager, UserCredentials } from './user-session-manager.js';
import { AuthCredentials, QueryParams, QueryParamsSchema, UMBRELLA_ENDPOINTS } from './types.js';
import * as fs from 'fs';
import * as path from 'path';
// Enhanced logging utilities
class MCPLogger {
private logFile: string;
constructor() {
this.logFile = path.join(process.cwd(), 'mcp-server.log');
this.log('INFO', '=== MCP Server Starting ===');
}
private log(level: string, message: string, data?: any) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
data: data ? JSON.stringify(data, null, 2) : undefined,
pid: process.pid
};
const logLine = `[${timestamp}] ${level}: ${message}${data ? '\nData: ' + JSON.stringify(data, null, 2) : ''}\n`;
// Write to file
try {
fs.appendFileSync(this.logFile, logLine);
} catch (error) {
console.error('Failed to write to log file:', error);
}
// Also write to stderr for Claude Desktop logs
console.error(logLine);
}
info(message: string, data?: any) {
this.log('INFO', message, data);
}
warn(message: string, data?: any) {
this.log('WARN', message, data);
}
error(message: string, data?: any) {
this.log('ERROR', message, data);
}
debug(message: string, data?: any) {
this.log('DEBUG', message, data);
}
request(method: string, params: any) {
this.log('REQUEST', `Method: ${method}`, { method, params });
}
response(method: string, result: any) {
this.log('RESPONSE', `Method: ${method}`, { method, result });
}
auth(message: string, username?: string, success?: boolean) {
this.log('AUTH', message, { username, success, timestamp: new Date().toISOString() });
}
}
export class EnhancedUmbrellaMcpServer {
private server: Server;
private sessionManager: UserSessionManager;
private baseURL: string;
private logger: MCPLogger;
constructor(baseURL: string) {
this.baseURL = baseURL;
this.logger = new MCPLogger();
this.sessionManager = new UserSessionManager(baseURL);
this.logger.info('Initializing Enhanced Umbrella MCP Server', {
baseURL: this.baseURL,
nodeVersion: process.version,
platform: process.platform,
cwd: process.cwd()
});
this.server = new Server(
{
name: process.env.MCP_SERVER_NAME || 'Umbrella MCP Enhanced',
version: process.env.MCP_SERVER_VERSION || '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
process.on('uncaughtException', (error) => {
this.logger.error('Uncaught Exception', {
error: error.message,
stack: error.stack,
name: error.name
});
});
process.on('unhandledRejection', (reason, promise) => {
this.logger.error('Unhandled Rejection', {
reason: reason instanceof Error ? reason.message : String(reason),
stack: reason instanceof Error ? reason.stack : undefined,
promise: String(promise)
});
});
}
private setupToolHandlers(): void {
this.logger.info('Setting up tool handlers');
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
this.logger.request('listTools', request.params);
try {
const authTool: Tool = {
name: 'authenticate_user',
description: 'Authenticate with Umbrella Cost API using username and password (multi-tenant)',
inputSchema: {
type: 'object',
properties: {
username: {
type: 'string',
description: 'Your Umbrella Cost username (email address)',
},
password: {
type: 'string',
description: 'Your Umbrella Cost password',
},
sessionId: {
type: 'string',
description: 'Optional: Custom session identifier (defaults to username-based ID)',
},
},
required: ['username', 'password'],
},
};
const logoutTool: Tool = {
name: 'logout',
description: 'Log out and remove user session',
inputSchema: {
type: 'object',
properties: {
username: {
type: 'string',
description: 'Username to logout (optional - will logout current session if not provided)',
},
},
required: [],
},
};
const sessionStatusTool: Tool = {
name: 'session_status',
description: 'Check authentication status and session information',
inputSchema: {
type: 'object',
properties: {
username: {
type: 'string',
description: 'Username to check status for (optional)',
},
},
required: [],
},
};
const apiTools: Tool[] = UMBRELLA_ENDPOINTS.map((endpoint) => ({
name: `api_${endpoint.path.replace(/\//g, '_').replace(/[-]/g, '_')}`,
description: `${endpoint.description} (${endpoint.category})`,
inputSchema: {
type: 'object',
properties: {
...(endpoint.parameters && Object.keys(endpoint.parameters).length > 0
? Object.entries(endpoint.parameters).reduce((acc, [key, desc]) => {
acc[key] = {
type: 'string',
description: desc,
};
return acc;
}, {} as Record<string, any>)
: {}),
},
required: [],
},
}));
const listEndpointsTool: Tool = {
name: 'list_endpoints',
description: 'List all available Umbrella Cost API endpoints with their descriptions',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
};
const helpTool: Tool = {
name: 'help',
description: 'Get help and usage information for the Umbrella MCP server',
inputSchema: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'Specific help topic (authentication, endpoints, parameters)',
},
},
required: [],
},
};
const result = {
tools: [authTool, logoutTool, sessionStatusTool, listEndpointsTool, helpTool, ...apiTools],
};
this.logger.response('listTools', { toolCount: result.tools.length });
return result;
} catch (error: any) {
this.logger.error('Error in listTools', {
error: error.message,
stack: error.stack
});
throw error;
}
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
this.logger.request('callTool', { toolName: name, arguments: args });
try {
// Authentication tools
if (name === 'authenticate_user') {
const result = await this.handleAuthenticateUser(args as any);
this.logger.response('callTool:authenticate_user', result);
return result;
}
if (name === 'logout') {
const result = this.handleLogout(args as any);
this.logger.response('callTool:logout', result);
return result;
}
if (name === 'session_status') {
const result = this.handleSessionStatus(args as any);
this.logger.response('callTool:session_status', result);
return result;
}
// List endpoints tool
if (name === 'list_endpoints') {
const result = this.handleListEndpoints();
this.logger.response('callTool:list_endpoints', result);
return result;
}
// Help tool
if (name === 'help') {
const result = this.handleHelp(args?.topic as string);
this.logger.response('callTool:help', result);
return result;
}
// API endpoint tools
if (name.startsWith('api_')) {
const endpointPath = name.replace('api_', '').replace(/_/g, '/').replace(/^/, '/');
const endpoint = UMBRELLA_ENDPOINTS.find(ep => ep.path === endpointPath);
if (!endpoint) {
const errorResult = {
content: [
{
type: 'text',
text: `Error: Unknown endpoint "${endpointPath}". Use "list_endpoints" to see available endpoints.`,
},
],
};
this.logger.error('Unknown endpoint', { toolName: name, endpointPath });
return errorResult;
}
const result = await this.handleApiCall(endpoint.path, args);
this.logger.response('callTool:api_call', { endpoint: endpoint.path, success: !result.content[0].text.includes('Error') });
return result;
}
const errorResult = {
content: [
{
type: 'text',
text: `Error: Unknown tool "${name}". Use "help" to see available tools.`,
},
],
};
this.logger.error('Unknown tool', { toolName: name });
return errorResult;
} catch (error: any) {
this.logger.error('Error in callTool', {
toolName: name,
error: error.message,
stack: error.stack,
arguments: args
});
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
};
}
});
}
private async handleAuthenticateUser(args: { username: string; password: string; sessionId?: string }) {
this.logger.auth('Authentication attempt started', args.username);
try {
// Validate that credentials are provided
if (!args.username || !args.password) {
this.logger.auth('Authentication failed: Missing credentials', args.username, false);
return {
content: [
{
type: 'text',
text: `❌ Authentication failed: Both username and password are required.\n\nPlease provide your Umbrella Cost credentials:\n- Username: Your email address\n- Password: Your account password`,
},
],
};
}
this.logger.auth('Validating credentials with Umbrella API', args.username);
const credentials: UserCredentials = {
username: args.username,
password: args.password
};
const result = await this.sessionManager.authenticateUser(credentials, args.sessionId);
if (result.success) {
const stats = this.sessionManager.getStats();
this.logger.auth('Authentication successful', args.username, true);
this.logger.info('Session created', {
username: args.username,
sessionId: result.sessionId,
activeSessions: stats.activeSessions
});
return {
content: [
{
type: 'text',
text: `✅ Successfully authenticated as ${args.username}\n\n**Session ID:** ${result.sessionId}\n\nYour credentials have been securely processed and a temporary access token has been obtained.\n\nYou can now use the API tools to query your Umbrella Cost data. Use "list_endpoints" to see available endpoints.\n\n📊 **Server Status:** ${stats.activeSessions} active session(s)`,
},
],
};
} else {
this.logger.auth('Authentication failed', args.username, false);
this.logger.error('Authentication error details', {
username: args.username,
error: result.error,
sessionId: result.sessionId
});
return {
content: [
{
type: 'text',
text: `❌ Authentication failed: ${result.error}\n\nPlease verify your credentials and try again:\n- Ensure your username is your full email address\n- Check your password is correct\n- Confirm your account has API access permissions\n\n**Note:** Your credentials are only used to obtain a secure token and are not stored.`,
},
],
};
}
} catch (error: any) {
this.logger.auth('Authentication exception', args.username, false);
this.logger.error('Authentication exception details', {
username: args.username,
error: error.message,
stack: error.stack
});
return {
content: [
{
type: 'text',
text: `❌ Authentication error: ${error.message}`,
},
],
};
}
}
private handleLogout(args: { username?: string }) {
this.logger.info('Logout request', { username: args.username });
try {
const stats = this.sessionManager.getStats();
if (args.username) {
// Logout specific user
const removed = this.sessionManager.removeUserSession(args.username);
if (removed) {
this.logger.info('User logged out successfully', { username: args.username });
return {
content: [
{
type: 'text',
text: `✅ Successfully logged out user: ${args.username}\n\n📊 **Server Status:** ${stats.activeSessions - 1} active session(s) remaining`,
},
],
};
} else {
this.logger.warn('No active session found for logout', { username: args.username });
return {
content: [
{
type: 'text',
text: `⚠️ No active session found for user: ${args.username}`,
},
],
};
}
} else {
// Show active sessions for admin purposes
const activeSessions = this.sessionManager.getActiveSessions();
this.logger.info('Listing active sessions', { count: activeSessions.length });
if (activeSessions.length === 0) {
return {
content: [
{
type: 'text',
text: `ℹ️ No active sessions to logout.`,
},
],
};
} else {
let output = `📊 **Active Sessions (${activeSessions.length}):**\n\n`;
activeSessions.forEach(session => {
output += `- **${session.username}** (${session.id})\n`;
output += ` Last activity: ${session.lastActivity.toISOString()}\n\n`;
});
output += `Use \`logout(username="user@email.com")\` to logout a specific user.`;
return {
content: [
{
type: 'text',
text: output,
},
],
};
}
}
} catch (error: any) {
this.logger.error('Logout error', {
username: args.username,
error: error.message,
stack: error.stack
});
return {
content: [
{
type: 'text',
text: `❌ Logout error: ${error.message}`,
},
],
};
}
}
private handleSessionStatus(args: { username?: string }) {
this.logger.info('Session status request', { username: args.username });
try {
const stats = this.sessionManager.getStats();
if (args.username) {
// Check specific user session
const session = this.sessionManager.getUserSessionByUsername(args.username);
if (session) {
this.logger.info('Session status found', { username: args.username, sessionId: session.id });
return {
content: [
{
type: 'text',
text: `✅ **Session Status for ${args.username}:**\n\n- **Status:** Authenticated\n- **Session ID:** ${session.id}\n- **Created:** ${session.createdAt.toISOString()}\n- **Last Activity:** ${session.lastActivity.toISOString()}\n- **Active:** Yes`,
},
],
};
} else {
this.logger.warn('Session not found for user', { username: args.username });
return {
content: [
{
type: 'text',
text: `❌ **No active session found for:** ${args.username}\n\nUse \`authenticate_user\` to create a session.`,
},
],
};
}
} else {
// Show overall server status
const activeSessions = this.sessionManager.getActiveSessions();
this.logger.info('Server status requested', {
totalSessions: stats.totalSessions,
activeSessions: stats.activeSessions,
expiredSessions: stats.expiredSessions
});
let output = `📊 **Multi-Tenant Server Status:**\n\n`;
output += `- **Total Sessions:** ${stats.totalSessions}\n`;
output += `- **Active Sessions:** ${stats.activeSessions}\n`;
output += `- **Expired Sessions:** ${stats.expiredSessions}\n\n`;
if (activeSessions.length > 0) {
output += `**Active Users:**\n`;
activeSessions.forEach(session => {
output += `- ${session.username} (active ${Math.round((new Date().getTime() - session.lastActivity.getTime()) / 1000 / 60)} min ago)\n`;
});
} else {
output += `**No active user sessions.**\n\nUse \`authenticate_user\` to create a session.`;
}
return {
content: [
{
type: 'text',
text: output,
},
],
};
}
} catch (error: any) {
this.logger.error('Session status error', {
username: args.username,
error: error.message,
stack: error.stack
});
return {
content: [
{
type: 'text',
text: `❌ Session status error: ${error.message}`,
},
],
};
}
}
private handleListEndpoints() {
this.logger.info('List endpoints request');
const endpointsByCategory = UMBRELLA_ENDPOINTS.reduce((acc, endpoint) => {
if (!acc[endpoint.category]) {
acc[endpoint.category] = [];
}
acc[endpoint.category].push(endpoint);
return acc;
}, {} as Record<string, typeof UMBRELLA_ENDPOINTS>);
let output = '# Available Umbrella Cost API Endpoints\n\n';
Object.entries(endpointsByCategory).forEach(([category, endpoints]) => {
output += `## ${category}\n\n`;
endpoints.forEach((endpoint) => {
const toolName = `api_${endpoint.path.replace(/\//g, '_').replace(/[-]/g, '_')}`;
output += `### ${toolName}\n`;
output += `**Path:** \`${endpoint.method} ${endpoint.path}\`\n`;
output += `**Description:** ${endpoint.description}\n`;
if (endpoint.parameters && Object.keys(endpoint.parameters).length > 0) {
output += `**Parameters:**\n`;
Object.entries(endpoint.parameters).forEach(([param, desc]) => {
output += `- \`${param}\`: ${desc}\n`;
});
}
output += '\n';
});
});
return {
content: [
{
type: 'text',
text: output,
},
],
};
}
private handleHelp(topic?: string) {
this.logger.info('Help request', { topic });
let helpText = '';
if (!topic || topic === 'general') {
helpText = `# Umbrella MCP Server Help\n\n`;
helpText += `This MCP server provides read-only access to the Umbrella Cost finops SaaS platform.\n\n`;
helpText += `## Getting Started\n`;
helpText += `1. First, authenticate using your Umbrella Cost credentials:\n`;
helpText += ' ```\n authenticate_user(username="your-email@domain.com", password="your-password")\n ```\n\n';
helpText += `2. List available endpoints:\n`;
helpText += ' ```\n list_endpoints()\n ```\n\n';
helpText += `3. Make API calls to retrieve your cloud cost data.\n\n`;
helpText += `## Available Help Topics\n`;
helpText += `- \`authentication\`: How to authenticate and manage credentials\n`;
helpText += `- \`endpoints\`: Information about available API endpoints\n`;
helpText += `- \`parameters\`: How to use query parameters\n\n`;
} else if (topic === 'authentication') {
helpText = `# Authentication Help\n\n`;
helpText += `To use the Umbrella MCP server, you need to authenticate with your Umbrella Cost credentials.\n\n`;
helpText += `## Credentials Setup\n`;
helpText += `You need to provide your own Umbrella Cost credentials:\n\n`;
helpText += `**Authentication Process:**\n`;
helpText += `- Use your registered Umbrella Cost email address as username\n`;
helpText += `- Use your account password\n`;
helpText += `- Both MSP and Direct customer accounts are supported\n\n`;
helpText += `## Authentication Process\n`;
helpText += `1. Call the \`authenticate_user\` tool with your username and password\n`;
helpText += `2. The server will obtain an authentication token from Umbrella Cost\n`;
helpText += `3. The token will be used automatically for all subsequent API calls\n\n`;
helpText += `## Security Notes\n`;
helpText += `- Your credentials are only used to obtain a token and are not stored\n`;
helpText += `- All API access is read-only for security\n`;
helpText += `- Tokens expire after a certain time and you may need to re-authenticate\n\n`;
} else if (topic === 'endpoints') {
helpText = `# API Endpoints Help\n\n`;
helpText += `The Umbrella MCP server provides access to various Umbrella Cost API endpoints organized by category:\n\n`;
helpText += `## Categories\n`;
helpText += `- **Cost Analysis**: Core cost and usage data\n`;
helpText += `- **Usage Analysis**: Detailed resource usage information\n`;
helpText += `- **Resource Explorer**: Resource-level cost breakdown\n`;
helpText += `- **Dashboards**: Dashboard configurations\n`;
helpText += `- **Budget Management**: Budget tracking and alerts\n`;
helpText += `- **Recommendations**: Cost optimization suggestions\n`;
helpText += `- **Anomaly Detection**: Cost anomaly identification\n`;
helpText += `- **Commitment Analysis**: Reserved instances and savings plans\n`;
helpText += `- **Kubernetes**: Container cost analysis\n`;
helpText += `- **User Management**: User and customer information\n\n`;
helpText += `Use \`list_endpoints()\` to see all available endpoints with descriptions.\n\n`;
} else if (topic === 'parameters') {
helpText = `# Query Parameters Help\n\n`;
helpText += `Many API endpoints accept query parameters to filter and customize the results:\n\n`;
helpText += `## Common Parameters\n`;
helpText += `- \`startDate\`: Start date for queries (YYYY-MM-DD format)\n`;
helpText += `- \`endDate\`: End date for queries (YYYY-MM-DD format)\n`;
helpText += `- \`accountId\`: Filter by specific AWS account ID\n`;
helpText += `- \`region\`: Filter by AWS region\n`;
helpText += `- \`serviceNames\`: Filter by service names (array)\n`;
helpText += `- \`limit\`: Limit the number of results returned\n`;
helpText += `- \`offset\`: Skip a number of results (for pagination)\n\n`;
helpText += `## Example Usage\n`;
helpText += '```\n';
helpText += 'api__invoices_caui(\n';
helpText += ' startDate="2024-01-01",\n';
helpText += ' endDate="2024-01-31",\n';
helpText += ' accountId="123456789012"\n';
helpText += ')\n';
helpText += '```\n\n';
helpText += `Each endpoint may have specific parameters. Check the endpoint description for details.\n\n`;
} else {
helpText = `Unknown help topic: "${topic}". Available topics: general, authentication, endpoints, parameters`;
}
return {
content: [
{
type: 'text',
text: helpText,
},
],
};
}
private async handleApiCall(path: string, args: any) {
this.logger.info('API call started', {
endpoint: path,
argumentsKeys: args ? Object.keys(args) : [],
hasUsername: !!args?.username
});
try {
// Determine which user to use - check if username is provided in args
let username = args?.username;
let session = null;
if (username) {
// User explicitly provided username
this.logger.debug('Looking for session by username', { username });
session = this.sessionManager.getUserSessionByUsername(username);
if (!session) {
this.logger.warn('No session found for specified username', { username });
return {
content: [
{
type: 'text',
text: `❌ No active session found for user: ${username}\n\nPlease authenticate first using \`authenticate_user\`.`,
},
],
};
}
} else {
// No username provided - check if there's exactly one active session
const activeSessions = this.sessionManager.getActiveSessions();
this.logger.debug('No username provided, checking active sessions', { count: activeSessions.length });
if (activeSessions.length === 0) {
this.logger.warn('No authenticated users found');
return {
content: [
{
type: 'text',
text: `❌ No authenticated users found.\n\nPlease authenticate first using \`authenticate_user(username="your-email@domain.com", password="your-password")\`.`,
},
],
};
} else if (activeSessions.length === 1) {
// Use the single active session
session = this.sessionManager.getUserSessionByUsername(activeSessions[0].username);
username = activeSessions[0].username;
this.logger.debug('Using single active session', { username });
} else {
// Multiple active sessions - user must specify which one
this.logger.warn('Multiple active sessions found', { count: activeSessions.length });
let output = `❌ Multiple users authenticated. Please specify which user:\n\n`;
output += `**Active Users:**\n`;
activeSessions.forEach(s => {
output += `- ${s.username}\n`;
});
output += `\n**Usage:** Add \`username="user@email.com"\` to your API call.`;
return {
content: [
{
type: 'text',
text: output,
},
],
};
}
}
if (!session) {
this.logger.error('Unable to find authenticated session');
return {
content: [
{
type: 'text',
text: `❌ Unable to find authenticated session. Please authenticate first.`,
},
],
};
}
// Remove username from args to avoid sending it to API
const { username: _, ...apiArgs } = args || {};
// Validate and parse query parameters
const queryParams: QueryParams = {};
if (apiArgs) {
// Convert args to query params, validating common parameters
Object.entries(apiArgs).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
(queryParams as any)[key] = value;
}
});
}
this.logger.info('Making API request', {
endpoint: path,
username,
sessionId: session.id,
queryParams: Object.keys(queryParams)
});
const validatedParams = QueryParamsSchema.partial().parse(queryParams);
const response = await session.apiClient.makeRequest(path, validatedParams);
this.logger.info('API request completed', {
endpoint: path,
username,
success: response.success,
hasData: !!response.data,
dataType: response.data ? (Array.isArray(response.data) ? 'array' : typeof response.data) : 'none',
dataLength: Array.isArray(response.data) ? response.data.length : undefined,
error: response.error || 'none'
});
if (response.success) {
let output = `# API Response: ${path}\n\n`;
output += `**Authenticated as:** ${username}\n`;
output += `**Session ID:** ${session.id}\n\n`;
output += `**Status:** ✅ Success\n\n`;
if (response.data) {
// Format the response data nicely
if (Array.isArray(response.data)) {
output += `**Results:** ${response.data.length} items\n\n`;
if (response.data.length > 0) {
output += '```json\n';
output += JSON.stringify(response.data.slice(0, 5), null, 2); // Show first 5 items
if (response.data.length > 5) {
output += `\n... and ${response.data.length - 5} more items\n`;
}
output += '\n```\n';
}
} else if (typeof response.data === 'object') {
output += '**Data:**\n```json\n';
output += JSON.stringify(response.data, null, 2);
output += '\n```\n';
} else {
output += `**Data:** ${response.data}\n`;
}
}
return {
content: [
{
type: 'text',
text: output,
},
],
};
} else {
this.logger.warn('API request failed', {
endpoint: path,
username,
error: response.error,
message: response.message
});
return {
content: [
{
type: 'text',
text: `❌ API Error for ${username}: ${response.error}\n\n${response.message || ''}`,
},
],
};
}
} catch (error: any) {
this.logger.error('API call exception', {
endpoint: path,
error: error.message,
stack: error.stack,
args
});
return {
content: [
{
type: 'text',
text: `❌ Request Error: ${error.message}`,
},
],
};
}
}
async run(): Promise<void> {
this.logger.info('Starting MCP server transport');
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info('MCP Server started successfully', {
serverName: 'Umbrella MCP Enhanced',
baseURL: this.baseURL,
multiTenant: true,
logFile: path.join(process.cwd(), 'mcp-server.log')
});
console.error(`🚀 Umbrella MCP Server started successfully (Enhanced Logging)`);
console.error(`📡 Base URL: ${this.baseURL}`);
console.error(`🔒 Security: Read-only access enabled`);
console.error(`👥 Multi-tenant: Concurrent user sessions supported`);
console.error(`📝 Logging: Enhanced logging enabled`);
console.error(`📄 Log File: ${path.join(process.cwd(), 'mcp-server.log')}`);
console.error(`💡 Use "authenticate_user" to get started`);
// Set up graceful shutdown
process.on('SIGINT', () => {
this.logger.info('Received SIGINT, shutting down gracefully');
console.error('🛑 Received SIGINT, shutting down gracefully...');
this.shutdown();
process.exit(0);
});
process.on('SIGTERM', () => {
this.logger.info('Received SIGTERM, shutting down gracefully');
console.error('🛑 Received SIGTERM, shutting down gracefully...');
this.shutdown();
process.exit(0);
});
} catch (error: any) {
this.logger.error('Failed to start MCP server', {
error: error.message,
stack: error.stack
});
throw error;
}
}
shutdown(): void {
this.logger.info('Shutting down MCP server', {
activeSessions: this.sessionManager.getStats().activeSessions
});
console.error('🔒 Shutting down Enhanced Umbrella MCP Server...');
this.sessionManager.shutdown();
console.error('✅ Shutdown complete');
this.logger.info('Shutdown complete');
}
}