/**
* Main MCP Color Server implementation
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { ServerConfig } from './types/index';
import { toolRegistry } from './tools/index';
import { logger } from './utils/logger';
import { performanceMonitor, resourceManager } from './utils/index';
import { securityMiddleware, securityAuditor } from './security/index';
import { getVersionInfo } from './version';
// Import tools/index to ensure all tools are registered
import './tools/index';
export class ColorServer {
private server: Server;
private config: ServerConfig;
private performanceStatsInterval: ReturnType<typeof setInterval> | null =
null;
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
constructor(config?: Partial<ServerConfig>) {
const versionInfo = getVersionInfo();
this.config = {
name: versionInfo.name,
description: versionInfo.description,
...config,
// Ensure centralized version always takes precedence
version: versionInfo.version,
};
// Initialize MCP server
this.server = new Server(
{
name: this.config.name,
version: this.config.version,
},
{
capabilities: {
tools: {},
},
}
);
// Only setup request handlers in non-test environments
const isTestEnvironment =
process.env['NODE_ENV'] === 'test' ||
process.env['JEST_WORKER_ID'] !== undefined ||
process.env['CI'] === 'true' ||
typeof jest !== 'undefined' ||
(typeof global !== 'undefined' && 'jest' in global);
if (!isTestEnvironment) {
this.setupRequestHandlers();
}
this.registerTools();
if (!isTestEnvironment) {
this.setupPerformanceMonitoring();
}
logger.info(`${this.config.name} v${this.config.version} initialized`);
}
private setupRequestHandlers(): void {
// Handle tool listing requests
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = toolRegistry.getAllTools();
logger.debug(`Listing ${tools.length} available tools`);
return {
tools: tools.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.parameters,
})),
};
});
// Handle tool execution requests with security middleware
this.server.setRequestHandler(CallToolRequestSchema, async request => {
const { name, arguments: args } = request.params;
const startTime = Date.now();
const operationId = performanceMonitor.startOperation(name);
logger.info(`Executing tool: ${name}`, { tool: name });
try {
// Security check before tool execution
const securityCheck = await securityMiddleware.checkSecurity({
operation: name,
clientId: 'mcp-client', // Could be extracted from request context
parameters: args || {},
});
if (!securityCheck.allowed) {
const executionTime = Date.now() - startTime;
performanceMonitor.endOperation(operationId, name, false);
logger.warn(`Tool execution blocked by security: ${name}`, {
tool: name,
executionTime,
errors: securityCheck.errors,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: false,
error: {
code: 'SECURITY_VIOLATION',
message: 'Request blocked by security policy',
details: securityCheck.errors,
suggestions: [
'Check your input parameters for invalid characters',
'Ensure URLs use HTTPS protocol',
'Reduce request rate if hitting limits',
],
},
metadata: {
execution_time: executionTime,
tool: name,
timestamp: new Date().toISOString(),
rate_limit: securityCheck.rateLimitInfo,
},
},
null,
2
),
},
],
};
}
const tool = toolRegistry.getTool(name);
if (!tool) {
const executionTime = Date.now() - startTime;
performanceMonitor.endOperation(operationId, name, false);
logger.warn(`Tool not found: ${name}`, { tool: name, executionTime });
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: false,
error: {
code: 'TOOL_NOT_FOUND',
message: `Tool '${name}' not found`,
suggestions: [
`Available tools: ${toolRegistry.getToolNames().join(', ')}`,
'Check the tool name spelling and try again',
],
},
metadata: {
execution_time: executionTime,
tool: name,
timestamp: new Date().toISOString(),
},
},
null,
2
),
},
],
};
}
// Execute the tool with sanitized parameters
const result = await tool.handler(
securityCheck.sanitizedParameters || {}
);
const executionTime = Date.now() - startTime;
performanceMonitor.endOperation(operationId, name, true);
logger.logToolExecution(
name,
'Tool executed successfully',
executionTime
);
// Add security warnings to response if any
if (securityCheck.warnings.length > 0) {
if (result.metadata) {
(result.metadata as Record<string, unknown>)['security_warnings'] =
securityCheck.warnings;
}
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const executionTime = Date.now() - startTime;
performanceMonitor.endOperation(operationId, name, false);
logger.error(`Tool execution failed: ${name}`, {
tool: name,
executionTime,
error: error as Error,
});
// Log security event for execution failures
securityAuditor.logSecurityEvent({
type: 'suspicious_activity',
severity: 'medium',
operation: name,
clientId: 'mcp-client',
details: {
error: (error as Error).message,
executionTime,
},
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: false,
error: {
code: 'EXECUTION_ERROR',
message: 'Tool execution failed',
details:
process.env['NODE_ENV'] === 'development'
? (error as Error).message
: 'Internal server error',
},
metadata: {
execution_time: executionTime,
tool: name,
timestamp: new Date().toISOString(),
},
},
null,
2
),
},
],
};
}
});
}
private registerTools(): void {
// All tools are automatically registered when tools/index.ts is imported
// No need to manually register individual tools here
const toolCount = toolRegistry.getAllTools().length;
logger.info(
`Registered ${toolCount} tools: ${toolRegistry.getToolNames().join(', ')}`
);
}
public async start(): Promise<void> {
// Skip actual server startup in test environments
const isTestEnvironment =
process.env['NODE_ENV'] === 'test' ||
process.env['JEST_WORKER_ID'] !== undefined ||
typeof jest !== 'undefined';
if (isTestEnvironment) {
// In test mode, just simulate the startup
logger.info(`${this.config.name} started in test mode`);
return;
}
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info(
`${this.config.name} started successfully on stdio transport`
);
} catch (error) {
logger.error('Failed to start server', { error: error as Error });
throw error;
}
}
public getConfig(): ServerConfig {
return { ...this.config };
}
public getToolCount(): number {
return toolRegistry.getAllTools().length;
}
private setupPerformanceMonitoring(): void {
// Log performance stats periodically
this.performanceStatsInterval = setInterval(() => {
const stats = performanceMonitor.getStats();
const resourceStatus = resourceManager.getResourceStatus();
logger.info('Performance stats', {
totalRequests: stats.totalRequests,
averageResponseTime: Math.round(stats.averageResponseTime),
memoryUsage: Math.round(stats.memoryUsage.current / 1024 / 1024),
cacheHitRate: Math.round(stats.cacheStats.hitRate * 100),
concurrentRequests: stats.concurrentRequests,
resourceStatus: resourceStatus.status,
});
}, 60000); // Log every minute
// Periodic cleanup
this.cleanupInterval = setInterval(async () => {
await resourceManager.cleanup();
}, 300000); // Cleanup every 5 minutes
}
public getPerformanceStats() {
return {
performance: performanceMonitor.getStats(),
resources: resourceManager.getResourceStatus(),
};
}
public getSecurityStats() {
return {
audit: securityAuditor.getMetrics(),
rateLimits: {
// Add rate limiter stats if needed
},
};
}
public generateSecurityReport() {
return securityAuditor.generateSecurityReport();
}
public destroy(): void {
// Clear intervals
if (this.performanceStatsInterval) {
clearInterval(this.performanceStatsInterval);
this.performanceStatsInterval = null;
}
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
}
}
// Export for testing
export { toolRegistry };