@tiflux/mcp
Version:
TiFlux MCP Server - Model Context Protocol integration for Claude Code and other AI clients
360 lines (295 loc) • 10.3 kB
JavaScript
/**
* TiFlux MCP Server v2.0 - Nova Arquitetura
*
* Nova implementação com:
* - Container DI completo
* - Infrastructure layer (HTTP + Cache)
* - Logging estruturado
* - Configuração por ambiente
* - Error handling robusto
*/
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const {
CallToolRequestSchema,
ListToolsRequestSchema
} = require('@modelcontextprotocol/sdk/types.js');
// Core components
const Container = require('./src/core/Container');
const Config = require('./src/core/Config');
const Logger = require('./src/core/Logger');
// Infrastructure
const InfrastructureBootstrap = require('./src/infrastructure/InfrastructureBootstrap');
// Handlers (ainda serão refatorados nas próximas fases)
const schemas = require('./src/schemas');
const TicketHandlers = require('./src/handlers/tickets');
const ClientHandlers = require('./src/handlers/clients');
const UserHandlers = require('./src/handlers/users');
const StageHandlers = require('./src/handlers/stages');
const CatalogItemHandlers = require('./src/handlers/catalog_items');
const InternalCommunicationsHandlers = require('./src/handlers/internal_communications');
class TifluxMCPServerV2 {
constructor() {
this.container = null;
this.server = null;
this.logger = null;
this.config = null;
// Handlers (serão substituídos por services na Fase 3)
this.ticketHandlers = null;
this.clientHandlers = null;
this.userHandlers = null;
this.stageHandlers = null;
this.catalogItemHandlers = null;
this.internalCommunicationsHandlers = null;
this.isInitialized = false;
}
/**
* Inicializa o servidor com DI Container
*/
async initialize() {
try {
// 1. Setup Container DI
this.container = new Container();
// 2. Setup Config
this.config = new Config();
await this.config.load();
this.container.registerInstance('config', this.config);
// 3. Setup Logger
this.logger = new Logger(this.config.get('logging', {}));
this.container.registerInstance('logger', this.logger);
// 4. Bootstrap Infrastructure Layer
InfrastructureBootstrap.register(this.container);
InfrastructureBootstrap.registerEnvironmentConfig(this.container);
// 5. Setup MCP Server
this.server = new Server(
{
name: 'tiflux-mcp',
version: '2.0.0',
vendor: 'TiFlux'
},
{
capabilities: {
tools: {}
}
}
);
// 6. Initialize handlers (temporário até Fase 3)
this.ticketHandlers = new TicketHandlers();
this.clientHandlers = new ClientHandlers();
this.userHandlers = new UserHandlers();
this.stageHandlers = new StageHandlers();
this.catalogItemHandlers = new CatalogItemHandlers();
this.internalCommunicationsHandlers = new InternalCommunicationsHandlers();
// 7. Setup handlers
this.setupHandlers();
// 8. Health check inicial
const healthChecker = this.container.resolve('infrastructureHealthChecker');
const health = await healthChecker.checkHealth();
this.logger.info('TiFlux MCP Server v2.0 initialized successfully', {
version: '2.0.0',
environment: this.config.get('environment'),
infrastructure: health,
container: {
registeredServices: this.container.list().length
}
});
this.isInitialized = true;
} catch (error) {
console.error('[FATAL] Failed to initialize TiFlux MCP Server v2.0:', error);
process.exit(1);
}
}
/**
* Setup dos handlers MCP
*/
setupHandlers() {
// Handler para listar tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
this.logger.debug('Listing available tools');
return { tools: schemas.all };
});
// Handler para executar tools com logging e error handling
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const requestId = Math.random().toString(36).substring(7);
const timer = this.logger.startTimer(`tool_${name}_${requestId}`);
this.logger.info('Tool execution started', {
requestId,
toolName: name,
hasArguments: !!args && Object.keys(args).length > 0
});
try {
let result;
// Roteamento por área do sistema (será refatorado na Fase 4)
switch (name) {
// Tools de tickets
case 'get_ticket':
result = await this.ticketHandlers.handleGetTicket(args);
break;
case 'create_ticket':
result = await this.ticketHandlers.handleCreateTicket(args);
break;
case 'update_ticket':
result = await this.ticketHandlers.handleUpdateTicket(args);
break;
case 'list_tickets':
result = await this.ticketHandlers.handleListTickets(args);
break;
case 'cancel_ticket':
result = await this.ticketHandlers.handleCancelTicket(args);
break;
case 'close_ticket':
result = await this.ticketHandlers.handleCloseTicket(args);
break;
case 'create_ticket_answer':
result = await this.ticketHandlers.handleCreateTicketAnswer(args);
break;
case 'get_ticket_files':
result = await this.ticketHandlers.handleGetTicketFiles(args);
break;
case 'update_ticket_entities':
result = await this.ticketHandlers.handleUpdateTicketEntities(args);
break;
// Tools de clientes
case 'search_client':
result = await this.clientHandlers.handleSearchClient(args);
break;
// Tools de usuários
case 'search_user':
result = await this.userHandlers.handleSearchUser(args);
break;
// Tools de estágios
case 'search_stage':
result = await this.stageHandlers.handleSearchStage(args);
break;
// Tools de itens de catálogo
case 'search_catalog_item':
result = await this.catalogItemHandlers.handleSearchCatalogItem(args);
break;
// Tools de comunicações internas
case 'create_internal_communication':
result = await this.internalCommunicationsHandlers.handleCreateInternalCommunication(args);
break;
case 'list_internal_communications':
result = await this.internalCommunicationsHandlers.handleListInternalCommunications(args);
break;
case 'get_internal_communication':
result = await this.internalCommunicationsHandlers.handleGetInternalCommunication(args);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
timer();
this.logger.info('Tool execution completed successfully', {
requestId,
toolName: name,
hasResult: !!result
});
return result;
} catch (error) {
timer();
this.logger.error('Tool execution failed', {
requestId,
toolName: name,
error: error.message,
stack: error.stack
});
// Re-throw para MCP SDK handle
throw error;
}
});
}
/**
* Executa o servidor
*/
async run() {
if (!this.isInitialized) {
await this.initialize();
}
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info('TiFlux MCP Server v2.0 connected and ready', {
transport: 'stdio',
pid: process.pid,
nodeVersion: process.version
});
// Setup graceful shutdown
this.setupGracefulShutdown();
} catch (error) {
this.logger.error('Failed to start server', { error: error.message });
process.exit(1);
}
}
/**
* Setup graceful shutdown
*/
setupGracefulShutdown() {
const cleanup = async (signal) => {
this.logger.info(`Received ${signal}, shutting down gracefully...`);
try {
// Cleanup infrastructure
const healthChecker = this.container.resolve('infrastructureHealthChecker');
await healthChecker.cleanup();
// Cleanup logger
if (this.logger.close) {
this.logger.close();
}
this.logger.info('Server shutdown completed');
process.exit(0);
} catch (error) {
console.error('Error during shutdown:', error);
process.exit(1);
}
};
process.on('SIGTERM', () => cleanup('SIGTERM'));
process.on('SIGINT', () => cleanup('SIGINT'));
process.on('uncaughtException', (error) => {
this.logger.error('Uncaught exception', { error: error.message, stack: error.stack });
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
this.logger.error('Unhandled rejection', {
reason: reason?.toString(),
promise: promise?.toString()
});
});
}
/**
* Retorna estatísticas do servidor
*/
async getStats() {
if (!this.isInitialized) {
return { initialized: false };
}
const healthChecker = this.container.resolve('infrastructureHealthChecker');
const cacheStrategy = this.container.resolve('cacheStrategy');
return {
initialized: true,
version: '2.0.0',
uptime: process.uptime(),
memory: process.memoryUsage(),
infrastructure: await healthChecker.checkHealth(),
cache: cacheStrategy.getStats(),
container: {
services: this.container.list()
}
};
}
/**
* Retorna o container DI (para testes)
*/
getContainer() {
return this.container;
}
}
// Inicializar servidor se executado diretamente
if (require.main === module) {
const server = new TifluxMCPServerV2();
server.run().catch((error) => {
console.error('[FATAL] Server startup failed:', error);
process.exit(1);
});
}
module.exports = TifluxMCPServerV2;