import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { CalendarTools } from './tools/CalendarTools';
import { CalendarService } from './services/CalendarService';
import { ReminderService } from './services/ReminderService';
import { createCacheManager } from './cache';
import { DatabaseManager } from './database';
import { config, validateConfig } from './config';
class CalendarMCPServer {
private server: Server;
private calendarTools: CalendarTools;
private calendarService: CalendarService;
private reminderService: ReminderService;
private cache: ReturnType<typeof createCacheManager>;
private database: DatabaseManager;
constructor() {
// Validate configuration
const configValidation = validateConfig();
if (!configValidation.valid) {
console.error('Configuration validation failed:');
configValidation.errors?.forEach(error => console.error(` - ${error}`));
process.exit(1);
}
// Initialize database and cache
this.database = new DatabaseManager();
this.cache = createCacheManager();
// Initialize services
this.calendarService = new CalendarService(this.cache, this.database);
this.reminderService = new ReminderService(this.database);
// Initialize tools
this.calendarTools = new CalendarTools(this.calendarService, this.reminderService);
// Initialize MCP server
this.server = new Server({
name: config.mcp.serverName,
version: config.mcp.version,
}, {
capabilities: {
tools: {},
},
});
this.setupToolHandlers();
}
private setupToolHandlers(): void {
// Handle list tools request
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: this.calendarTools.getTools(),
};
});
// Handle tool call request
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Extract user ID from arguments or use a default for now
// In a production system, this would come from authentication
const userId = args?.user_id || 'default-user';
// Call the appropriate tool handler
const result = await this.calendarTools.handleToolCall(name, args, userId);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
],
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error(`Tool call error [${name}]:`, error);
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${errorMessage}`
);
}
});
}
async start(): Promise<void> {
try {
console.log('Initializing Calendar MCP Server...');
// Initialize database
console.log('Connecting to database...');
await this.database.initialize();
// Initialize cache
console.log('Connecting to cache...');
if ('connect' in this.cache) {
await (this.cache as any).connect();
}
// Test connections
const dbHealth = await this.database.ping();
const cacheHealth = await this.cache.ping();
console.log(`Database connection: ${dbHealth ? 'OK' : 'FAILED'}`);
console.log(`Cache connection: ${cacheHealth ? 'OK' : 'FAILED'}`);
if (!dbHealth) {
throw new Error('Database connection failed');
}
// Start MCP server
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log(`🚀 Calendar MCP Server started successfully!`);
console.log(`📅 Available tools: ${this.calendarTools.getTools().length}`);
console.log(`🔧 Features enabled: ${JSON.stringify(config.features)}`);
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
async stop(): Promise<void> {
console.log('Shutting down Calendar MCP Server...');
try {
// Close database connection
if (this.database) {
await this.database.close();
console.log('Database connection closed');
}
// Close cache connection
if (this.cache && 'disconnect' in this.cache) {
await (this.cache as any).disconnect();
console.log('Cache connection closed');
}
console.log('Calendar MCP Server stopped successfully');
} catch (error) {
console.error('Error during shutdown:', error);
}
}
}
// Error handling
process.on('SIGINT', async () => {
console.log('\nReceived SIGINT, shutting down gracefully...');
if (server) {
await server.stop();
}
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('\nReceived SIGTERM, shutting down gracefully...');
if (server) {
await server.stop();
}
process.exit(0);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
// Start the server
const server = new CalendarMCPServer();
async function main() {
try {
await server.start();
// Keep the process running
process.stdin.resume();
} catch (error) {
console.error('Failed to start MCP server:', error);
process.exit(1);
}
}
if (require.main === module) {
main();
}
export { CalendarMCPServer };