UNPKG

php-universal-mcp-server

Version:

Servidor MCP universal para desenvolvimento PHP e criação de sites em qualquer provedor de hospedagem

403 lines (344 loc) 10.4 kB
/** * PHP Provider - Implementação de funções para PHP * Define as funções específicas para operações com PHP */ const BaseProvider = require('./providers/BaseProvider'); const PHPExecutor = require('./php-executor'); const { EventEmitter } = require('events'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); class PHPProvider extends BaseProvider { /** * Construtor do PHP Provider * @param {Object} config - Configuração do provider */ constructor(config = {}) { super(config); // Inicializa o executor PHP this.phpExecutor = new PHPExecutor(config.php || {}); // Verifica disponibilidade do PHP this.phpAvailable = false; this.phpInfo = null; // Inicializa this.initialize(); } /** * Inicializa o provider */ async initialize() { try { // Verifica disponibilidade do PHP this.phpAvailable = await this.phpExecutor.checkPHPAvailability(); if (this.phpAvailable) { // Obtém informações do PHP this.phpInfo = await this.phpExecutor.getPHPInfo(); } } catch (error) { console.warn('Erro ao inicializar PHP Provider:', error.message); this.phpAvailable = false; } } /** * Retorna o nome do provider */ getName() { return 'php-provider'; } /** * Verifica se o provider está disponível */ isAvailable() { return this.phpAvailable; } /** * Executa um comando no provider * @param {Object} payload - Payload do comando */ async execute(payload) { if (!this.phpAvailable) { return { success: false, error: 'PHP não disponível', timestamp: new Date().toISOString() }; } try { const action = payload.action || ''; switch (action) { case 'executeCode': return await this.executePhpCode(payload); case 'executeFile': return await this.executePhpFile(payload); case 'checkSyntax': return await this.checkPhpSyntax(payload); case 'getInfo': return { success: true, phpInfo: this.phpInfo, timestamp: new Date().toISOString() }; default: return { success: false, error: `Ação desconhecida: ${action}`, timestamp: new Date().toISOString() }; } } catch (error) { return { success: false, error: error.message, timestamp: new Date().toISOString() }; } } /** * Executa código PHP * @param {Object} payload - Payload do comando */ async executePhpCode(payload) { const { code, input, options } = payload; if (!code) { return { success: false, error: 'Código PHP não fornecido', timestamp: new Date().toISOString() }; } try { let result; if (input) { // Se houver entrada, executa com input result = await this.phpExecutor.executeWithInput(code, input, options); } else { // Execução normal result = await this.phpExecutor.executeCode(code, options); } return { success: result.success, output: result.output, error: result.error, exitCode: result.exitCode, timestamp: new Date().toISOString() }; } catch (error) { return { success: false, error: error.message, timestamp: new Date().toISOString() }; } } /** * Executa um arquivo PHP * @param {Object} payload - Payload do comando */ async executePhpFile(payload) { const { filePath, options } = payload; if (!filePath) { return { success: false, error: 'Caminho do arquivo não fornecido', timestamp: new Date().toISOString() }; } try { // Verifica se o arquivo existe if (!fs.existsSync(filePath)) { return { success: false, error: `Arquivo não encontrado: ${filePath}`, timestamp: new Date().toISOString() }; } // Executa o arquivo const result = await this.phpExecutor.executeFile(filePath, options); return { success: result.success, output: result.output, error: result.error, exitCode: result.exitCode, timestamp: new Date().toISOString() }; } catch (error) { return { success: false, error: error.message, timestamp: new Date().toISOString() }; } } /** * Verifica a sintaxe do código PHP * @param {Object} payload - Payload do comando */ async checkPhpSyntax(payload) { const { code } = payload; if (!code) { return { success: false, error: 'Código PHP não fornecido', timestamp: new Date().toISOString() }; } try { // Adiciona <?php se não existir let phpCode = code; if (!phpCode.trim().startsWith('<?php')) { phpCode = `<?php\n${phpCode}`; } // Salva o código em um arquivo temporário const tempFilename = path.join( this.phpExecutor.config.tempDir, `php_syntax_${crypto.randomUUID()}.php` ); await fs.promises.writeFile(tempFilename, phpCode, 'utf8'); // Executa o PHP com a opção de verificação de sintaxe const result = await this.phpExecutor.executeFile(tempFilename, { args: ['-l'] // Opção -l para verificação de sintaxe }); // Remove o arquivo temporário await fs.promises.unlink(tempFilename); // Analisa o resultado da verificação const isSyntaxValid = result.output.includes('No syntax errors detected'); return { success: isSyntaxValid, valid: isSyntaxValid, output: result.output, error: result.error, timestamp: new Date().toISOString() }; } catch (error) { return { success: false, valid: false, error: error.message, timestamp: new Date().toISOString() }; } } /** * Cria um stream para execução contínua * @param {Object} payload - Payload do comando */ createStream(payload) { const streamId = crypto.randomUUID(); const emitter = new EventEmitter(); // Executa a operação em background this._executeStreamOperation(streamId, payload, emitter); return { id: streamId, emitter: emitter }; } /** * Executa uma operação de stream * @param {string} streamId - ID do stream * @param {Object} payload - Payload do comando * @param {EventEmitter} emitter - Emissor de eventos */ async _executeStreamOperation(streamId, payload, emitter) { try { // Emite evento de início emitter.emit('start', { id: streamId, timestamp: new Date().toISOString() }); // Determina o tipo de operação const action = payload.action || ''; let result; switch (action) { case 'executeCode': // Separa o código em partes para simular execução progressiva const parts = this._splitCodeForStream(payload.code); let currentCode = ''; // Executa cada parte e emite atualizações for (let i = 0; i < parts.length; i++) { currentCode += parts[i]; // Executa o código acumulado const execResult = await this.phpExecutor.executeCode(currentCode); // Calcula o progresso const progress = Math.round((i + 1) / parts.length * 100); // Emite evento de dados emitter.emit('data', { id: streamId, progress, result: execResult, timestamp: new Date().toISOString() }); // Aguarda um pouco para simular processamento await new Promise(resolve => setTimeout(resolve, 200)); } // Executa o código completo para o resultado final result = await this.phpExecutor.executeCode(payload.code, payload.options); break; case 'executeFile': // Executa o arquivo result = await this.phpExecutor.executeFile(payload.filePath, payload.options); break; default: throw new Error(`Ação de stream não suportada: ${action}`); } // Emite evento de conclusão emitter.emit('end', { id: streamId, completed: true, result, timestamp: new Date().toISOString() }); } catch (error) { // Emite evento de erro emitter.emit('error', { id: streamId, error: error.message, timestamp: new Date().toISOString() }); } } /** * Divide o código em partes para simulação de execução progressiva * @param {string} code - Código a ser dividido * @returns {Array<string>} Partes do código */ _splitCodeForStream(code) { // Divide o código em linhas const lines = code.split('\n'); // Se houver poucas linhas, retorna diretamente if (lines.length <= 5) { return [code]; } // Divide em partes de aproximadamente 5 linhas const chunkSize = Math.max(1, Math.ceil(lines.length / 5)); const parts = []; for (let i = 0; i < lines.length; i += chunkSize) { parts.push(lines.slice(i, i + chunkSize).join('\n') + '\n'); } return parts; } /** * Fecha um stream ativo * @param {Object} stream - Stream a ser fechado */ closeStream(stream) { if (!stream || !stream.emitter) return; // Emite evento de fechamento stream.emitter.emit('close', { id: stream.id, reason: 'user_closed', timestamp: new Date().toISOString() }); } /** * Cancela a execução de um comando * @param {string} sessionId - ID da sessão */ cancel(sessionId) { return { success: true, message: `Sessão ${sessionId} cancelada`, timestamp: new Date().toISOString() }; } } module.exports = PHPProvider;