/**
* Herramienta consolidada de autenticación
*/
import { z } from 'zod';
import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { cargarConfiguracion, guardarConfiguracion, } from '../core/configuracion.js';
import { resetearApiSpotify } from '../core/spotify.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROYECTO_ROOT = path.join(__dirname, '../..');
const PERMISOS = [
'user-read-private', 'user-read-email', 'user-read-playback-state',
'user-modify-playback-state', 'user-read-currently-playing', 'playlist-read-private',
'playlist-modify-private', 'playlist-modify-public', 'user-library-read',
'user-library-modify', 'user-read-recently-played', 'user-top-read',
];
const spotifyAuth = {
nombre: 'spotifyAuth',
descripcion: `Gestiona autenticación de Spotify. FLUJO RECOMENDADO:
1. Si error "Credenciales no configuradas": Pide al usuario clientId y clientSecret (de https://developer.spotify.com/dashboard) o que los agregue en env del mcp.json, luego usa accion="configurar"
2. Después de configurar: Usa accion="ejecutar" para completar OAuth (abre navegador automáticamente)
3. Si el usuario no puede autorizar: Usa accion="urlAuth" para darle el link manual
IMPORTANTE: Las credenciales se guardan en ~/.spotify-mcp-tokens.json junto con los tokens de sesión`,
esquema: {
accion: z.enum(['configurar', 'verificar', 'iniciar', 'ejecutar', 'urlAuth', 'cerrar'])
.describe('verificar=comprobar estado (USAR PRIMERO), configurar=guardar clientId+clientSecret, ejecutar=completar OAuth automático, urlAuth=obtener URL manual, cerrar=logout'),
clientId: z.string().optional().describe('Client ID de Spotify Developer Dashboard (solo para configurar)'),
clientSecret: z.string().optional().describe('Client Secret de Spotify Developer Dashboard (solo para configurar)'),
redirectUri: z.string().optional().describe('Redirect URI (default: http://127.0.0.1:8000/callback) - debe coincidir con Spotify Dashboard'),
},
ejecutar: async (args, _extra) => {
const { accion } = args;
switch (accion) {
case 'configurar': {
if (!args.clientId || !args.clientSecret) {
return { content: [{ type: 'text', text: '❌ Requiere clientId y clientSecret' }] };
}
let config;
try {
config = cargarConfiguracion();
}
catch {
config = { clientId: '', clientSecret: '', redirectUri: 'http://127.0.0.1:8000/callback' };
}
config.clientId = args.clientId;
config.clientSecret = args.clientSecret;
config.redirectUri = args.redirectUri || 'http://127.0.0.1:8000/callback';
config.accessToken = undefined;
config.refreshToken = undefined;
guardarConfiguracion(config);
return { content: [{ type: 'text', text: `✓ Credenciales guardadas!\n\nClient ID: ${args.clientId.substring(0, 8)}...${args.clientId.slice(-4)}\n\nUsa accion="ejecutar" para conectar.` }] };
}
case 'verificar': {
try {
const config = cargarConfiguracion();
const tieneCredenciales = !!(config.clientId && config.clientSecret);
const tieneTokens = !!(config.accessToken && config.refreshToken && config.accessToken !== 'run-npm auth to get this');
let estado = '# Estado de Autenticación\n\n';
if (!tieneCredenciales) {
estado += '❌ **Credenciales**: No configuradas\n\nSIGUIENTE: Pide al usuario su clientId y clientSecret de https://developer.spotify.com/dashboard (o que los agregue en env del mcp.json), luego usa spotifyAuth(accion="configurar", clientId="...", clientSecret="...")';
}
else {
estado += `✓ **Credenciales**: Configuradas\n - Client ID: ${config.clientId.substring(0, 8)}...${config.clientId.slice(-4)}\n\n`;
estado += tieneTokens ? '✓ **Sesión**: Conectado y listo para usar' : '❌ **Sesión**: No conectado\n\nSIGUIENTE: Usa spotifyAuth(accion="ejecutar") para completar OAuth automáticamente.';
}
return { content: [{ type: 'text', text: estado }] };
}
catch {
return { content: [{ type: 'text', text: '❌ Sin configurar. SIGUIENTE: Pide al usuario clientId y clientSecret de https://developer.spotify.com/dashboard, luego usa spotifyAuth(accion="configurar", clientId="...", clientSecret="...")' }] };
}
}
case 'iniciar': {
try {
const config = cargarConfiguracion();
if (!config.clientId || !config.clientSecret) {
return { content: [{ type: 'text', text: '❌ Credenciales no configuradas!' }] };
}
const params = new URLSearchParams({
client_id: config.clientId, response_type: 'code', redirect_uri: config.redirectUri,
scope: PERMISOS.join(' '), show_dialog: 'true',
});
const urlAuth = `https://accounts.spotify.com/authorize?${params.toString()}`;
const open = await import('open');
await open.default(urlAuth);
return { content: [{ type: 'text', text: `🌐 Abriendo navegador...\n\nSi no se abre, visita:\n${urlAuth}\n\nLuego usa accion="ejecutar"` }] };
}
catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
case 'ejecutar': {
try {
const config = cargarConfiguracion();
if (!config.clientId || !config.clientSecret) {
return { content: [{ type: 'text', text: '❌ Credenciales no configuradas!' }] };
}
return new Promise((resolve) => {
const proceso = spawn('npm', ['run', 'auth'], { cwd: PROYECTO_ROOT, shell: true, env: { ...process.env } });
let errorMsg = '';
proceso.stderr.on('data', (data) => { errorMsg += data.toString(); });
const timeout = setTimeout(() => { proceso.kill(); resolve({ content: [{ type: 'text', text: '⏱️ Timeout (2 min). Intenta de nuevo.' }] }); }, 120000);
proceso.on('close', (code) => {
clearTimeout(timeout);
if (code === 0) {
resetearApiSpotify(); // Forzar recarga de tokens
}
resolve({ content: [{ type: 'text', text: code === 0 ? '✅ ¡Autenticación completada!' : `❌ Error (código ${code})\n\n${errorMsg}` }] });
});
proceso.on('error', (err) => { clearTimeout(timeout); resolve({ content: [{ type: 'text', text: `❌ Error: ${err.message}` }] }); });
});
}
catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
case 'urlAuth': {
try {
const config = cargarConfiguracion();
if (!config.clientId)
return { content: [{ type: 'text', text: '❌ Credenciales no configuradas!' }] };
const params = new URLSearchParams({ client_id: config.clientId, response_type: 'code', redirect_uri: config.redirectUri, scope: PERMISOS.join(' '), show_dialog: 'true' });
return { content: [{ type: 'text', text: `# URL de Autorización\n\n${`https://accounts.spotify.com/authorize?${params.toString()}`}` }] };
}
catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
case 'cerrar': {
try {
const config = cargarConfiguracion();
config.accessToken = undefined;
config.refreshToken = undefined;
guardarConfiguracion(config);
return { content: [{ type: 'text', text: '✓ Sesión cerrada. Usa accion="ejecutar" para reconectar.' }] };
}
catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
default:
return { content: [{ type: 'text', text: '❌ Acción no válida' }] };
}
},
};
export const herramientasAuth = [spotifyAuth];