🤖 Sistema SaaS que permite a clientes subir archivos Excel, procesarlos con OpenAI y exponer los datos mediante una API REST para chatbots (SendPulse, etc.).
- ✅ Autenticación multiusuario con Laravel Sanctum
- ✅ Procesamiento asíncrono de archivos Excel/CSV con Jobs/Queue
- ✅ Integración con OpenAI para limpiar y estructurar datos automáticamente
- ✅ API REST pública con tokens únicos por cliente
- ✅ Compatible con hosting compartido (cPanel)
- ✅ Tests automatizados con PHPUnit
- ✅ Manejo robusto de errores con logs detallados
- PHP 8.1+
- Composer
- MySQL 5.7+ / MariaDB 10.3+
- Laravel 11
- Cuenta de OpenAI con API Key
git clone https://github.com/jonasvelasquez01/laravel-mcp-server-multiusuario.git
cd laravel-mcp-server-multiusuariocomposer installSi no tienes las dependencias instaladas, ejecuta:
composer require laravel/sanctum
composer require phpoffice/phpspreadsheet
composer require openai-php/clientCopia .env.example a .env:
cp .env.example .envEdita .env y configura:
APP_NAME="MCP Server"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mcp_server
DB_USERNAME=root
DB_PASSWORD=
# Clave de OpenAI (OBLIGATORIA)
OPENAI_API_KEY=sk-tu-api-key-aqui
# Queue (database por defecto, cambiar a redis en producción)
QUEUE_CONNECTION=database
# CORS para SendPulse
CORS_ALLOWED_ORIGINS=*php artisan key:generateCrea la base de datos mcp_server en MySQL:
CREATE DATABASE mcp_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateEsto creará las tablas:
clientes→ usuarios del sistemaproducts→ productos procesados por clienteconsultas→ log de consultas desde SendPulsepersonal_access_tokens→ tokens de Sanctumjobs→ cola de trabajos (queue)
Para procesamiento asíncrono de archivos Excel, ejecuta el worker:
php artisan queue:work --tries=3 --timeout=120En producción (cPanel): configura un cron job cada minuto:
* * * * * cd /ruta/a/tu/proyecto && php artisan schedule:run >> /dev/null 2>&1Y añade en app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('queue:work --stop-when-empty')->everyMinute();
}php artisan serveLa aplicación estará en http://localhost:8000
php artisan testLos tests cubren:
- ✅ Registro y login de clientes
- ✅ Validación de datos de entrada
- ✅ Subida de archivos Excel
- ✅ Endpoint MCP público con validación de tokens
- ✅ Búsqueda de productos
POST /api/register
Body (JSON):
{
"nombre": "Juan Pérez",
"email": "juan@example.com",
"password": "password123",
"password_confirmation": "password123"
}Respuesta (201):
{
"cliente": {
"id": 1,
"nombre": "Juan Pérez",
"email": "juan@example.com"
},
"sanctum_token": "1|abc123...",
"api_token": "xyz789..."
}sanctum_token→ usar en headerAuthorization: Bearer {token}para endpoints protegidosapi_token→ usar en la URL del endpoint MCP público
POST /api/login
Body (JSON):
{
"email": "juan@example.com",
"password": "password123"
}Respuesta (200):
{
"cliente": {...},
"sanctum_token": "1|abc123...",
"api_token": "xyz789..."
}POST /api/upload
Headers:
Authorization: Bearer {sanctum_token}
Content-Type: multipart/form-data
Body (Form Data):
file→ archivo Excel (.xlsx, .xls, .csv) - máx. 10MB
Respuesta (202):
{
"message": "Archivo recibido. Procesando en segundo plano...",
"status": "queued",
"file": "uploads/abc123.xlsx"
}Proceso:
- El archivo se sube y almacena
- Se despacha un Job a la cola
- El Job lee el Excel con PhpSpreadsheet
- Envía los datos a OpenAI para estructurarlos
- Guarda los productos en la base de datos
Nota: El procesamiento puede tardar 10-60 segundos dependiendo del tamaño del archivo.
POST /api/mcp/{api_token}/consulta
Headers:
Content-Type: application/json
Body (JSON):
{
"pregunta": "precio del aceite 20w50"
}Respuesta (200):
{
"respuesta": "El Aceite Motor 20W50 Premium cuesta $12.50 según la lista actualizada del cliente."
}Si no se encuentra:
{
"respuesta": "No se encontró información relevante."
}Notas:
- No requiere
Authorizationheader, usaapi_tokenen la URL - Guarda cada consulta en la tabla
consultas - Busca productos por coincidencia parcial en nombre, categoría y descripción
En tu chatbot de SendPulse, configura una acción HTTP:
URL: https://tu-dominio.com/api/mcp/TU_API_TOKEN_AQUI/consulta
Método: POST
Headers:
Content-Type: application/json
Body:
{
"pregunta": "{{user_message}}"
}Respuesta: Extrae respuesta del JSON y envíala al usuario.
| Campo | Tipo | Descripción |
|---|---|---|
| id | bigint | ID único |
| nombre | varchar(255) | Nombre del cliente |
| varchar(255) | Email único | |
| password | varchar(255) | Contraseña hasheada |
| api_token | varchar(80) | Token único para endpoint MCP |
| Campo | Tipo | Descripción |
|---|---|---|
| id | bigint | ID único |
| cliente_id | bigint | FK a clientes |
| producto | varchar(255) | Nombre del producto |
| precio | decimal(12,2) | Precio |
| descripcion | text | Descripción |
| categoria | varchar(255) | Categoría |
| ultima_actualizacion | timestamp | Fecha de última actualización |
| Campo | Tipo | Descripción |
|---|---|---|
| id | bigint | ID único |
| cliente_id | bigint | FK a clientes |
| texto_consulta | text | Pregunta recibida |
| respuesta | text | Respuesta generada |
| fecha | timestamp | Fecha de la consulta |
curl -X POST http://localhost:8000/api/register \
-H "Content-Type: application/json" \
-d '{
"nombre": "Tienda AutoParts",
"email": "autoparts@example.com",
"password": "seguro123",
"password_confirmation": "seguro123"
}'Respuesta:
{
"cliente": {...},
"sanctum_token": "2|abcdefg12345",
"api_token": "xyz789token"
}curl -X POST http://localhost:8000/api/upload \
-H "Authorization: Bearer 2|abcdefg12345" \
-F "file=@productos.xlsx"Respuesta:
{
"message": "Archivo recibido. Procesando en segundo plano...",
"status": "queued"
}curl -X POST http://localhost:8000/api/mcp/xyz789token/consulta \
-H "Content-Type: application/json" \
-d '{"pregunta": "precio del filtro de aceite"}'Respuesta:
{
"respuesta": "El Filtro de Aceite Premium cuesta $5.99 según la lista actualizada del cliente."
}- ✅ Contraseñas hasheadas con bcrypt
- ✅ Tokens únicos por cliente (60 caracteres aleatorios)
- ✅ Validación de MIME types en uploads
- ✅ Límite de 10MB por archivo
- ✅ Middleware de validación de tokens
- ✅ Rate limiting (configurable en
RouteServiceProvider)
Recomendaciones:
- En producción, usa HTTPS
- Configura
QUEUE_CONNECTION=redispara mejor rendimiento - Añade rate limiting al endpoint MCP
- Implementa soft deletes para auditoría
Los logs se guardan en storage/logs/laravel.log. Eventos clave:
- Subida de archivos
- Procesamiento de Excel (inicio y fin)
- Consultas MCP
- Errores de OpenAI
- Fallos de Jobs
Ver logs en tiempo real:
tail -f storage/logs/laravel.logVer estado de la cola:
php artisan queue:failedReintentar jobs fallidos:
php artisan queue:retry allSube todos los archivos excepto vendor y node_modules.
cd public_html/mcp
composer install --optimize-autoloader --no-devEdita .env con los datos de tu hosting:
APP_ENV=production
APP_DEBUG=false
DB_HOST=localhost
DB_DATABASE=usuario_mcp_server
DB_USERNAME=usuario_mysql
DB_PASSWORD=tu_password
OPENAI_API_KEY=sk-...En cPanel, apunta el dominio a /public_html/mcp/public
php artisan migrate --forceEn cPanel → Cron Jobs:
* * * * * cd /home/usuario/public_html/mcp && php artisan queue:work --stop-when-empty >/dev/null 2>&1
php artisan config:cache
php artisan route:cache
php artisan view:cacheEn ConsultaController@handle, puedes:
- Usar búsqueda full-text de MySQL
- Implementar embeddings con OpenAI para búsqueda semántica
- Añadir sinónimos y stemming
En OpenAIProcessor.php, línea 32:
'model' => 'gpt-4', // cambiar a gpt-4 para mejor calidadCrear un endpoint para notificar al cliente cuando termine el procesamiento:
// app/Jobs/ProcessExcelJob.php
public function handle() {
// ... procesamiento
Http::post($cliente->webhook_url, [
'status' => 'completed',
'products' => $updated
]);
}- Fork el proyecto
- Crea una rama (
git checkout -b feature/mejora) - Commit tus cambios (
git commit -am 'Añade nueva funcionalidad') - Push a la rama (
git push origin feature/mejora) - Abre un Pull Request
Este proyecto es de código abierto bajo licencia MIT.
Para reportar bugs o solicitar features, abre un issue en GitHub.
- Configurar
.envcon datos de producción - Ejecutar
composer install --no-dev --optimize-autoloader - Ejecutar
php artisan migrate --force - Configurar cron para queue worker
- Apuntar DocumentRoot a
/public - Ejecutar
php artisan config:cache - Probar registro de cliente
- Probar subida de Excel
- Probar endpoint MCP desde SendPulse
- Configurar HTTPS con certificado SSL
- Configurar backups automáticos de BD
¡Listo! 🎉 Tu servidor MCP está funcionando.