UNPKG

@pedrocid/music-mcp

Version:

MCP server for controlling Apple Music on macOS (v1.0.5)

155 lines (154 loc) 6.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeMusicCommandTool = void 0; exports.handleExecuteMusicCommand = handleExecuteMusicCommand; const logger_js_1 = require("../logger.js"); const config_js_1 = require("../config.js"); const child_process_1 = require("child_process"); const path_1 = require("path"); exports.executeMusicCommandTool = { name: 'execute_music_command', description: 'Execute music playback control commands', inputSchema: { type: 'object', properties: { command: { type: 'string', enum: ['play', 'pause', 'next', 'previous', 'toggle_playback', 'play_track'], description: 'The playback command to execute' }, trackSearchTerm: { type: 'string', description: 'Track name or search term (required for play_track command)' }, volume: { type: 'number', minimum: 0, maximum: 100, description: 'Volume level (0-100)' }, position: { type: 'number', minimum: 0, description: 'Position in track (seconds)' }, shuffleMode: { type: 'boolean', description: 'Enable or disable shuffle mode' }, repeatMode: { type: 'string', enum: ['off', 'one', 'all'], description: 'Repeat mode setting' }, rating: { type: 'number', minimum: 0, maximum: 5, description: 'Track rating (0-5 stars)' }, timeoutSeconds: { type: 'number', minimum: 1, description: 'Timeout for the operation in seconds' } }, required: ['command'] } }; async function handleExecuteMusicCommand(input) { logger_js_1.logger.info({ command: input.command }, 'Executing music command'); const config = (0, config_js_1.getConfig)(); const timeout = (input.timeoutSeconds || config.timeoutSeconds) * 1000; try { // Validate volume if provided if (input.volume !== undefined && (input.volume < 0 || input.volume > 100)) { return { success: false, message: 'Volume must be between 0 and 100', error: 'Invalid volume range' }; } // Validate rating if provided if (input.rating !== undefined && (input.rating < 0 || input.rating > 5)) { return { success: false, message: 'Rating must be between 0 and 5', error: 'Invalid rating range' }; } let result; switch (input.command) { case 'play': case 'pause': case 'toggle_playback': result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playback/play-pause.applescript'), [], timeout); break; case 'play_track': if (!input.trackSearchTerm) { return { success: false, message: 'Track search term is required for play_track command', error: 'Missing track search term' }; } result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playback/play-track.applescript'), [input.trackSearchTerm], timeout); break; case 'next': result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playback/next-track.applescript'), [], timeout); break; case 'previous': result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playback/previous-track.applescript'), [], timeout); break; default: return { success: false, message: `Unknown command: ${input.command}`, error: 'Invalid command' }; } // Handle volume setting separately if provided if (input.volume !== undefined) { await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playback/set-volume.applescript'), [input.volume.toString()], timeout); } if (result.startsWith('Error')) { return { success: false, message: result, error: result }; } return { success: true, message: result }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_js_1.logger.error({ error, command: input.command }, 'Music command execution failed'); return { success: false, message: `Failed to execute command: ${errorMessage}. Ensure Music app is running and you have granted automation permissions.`, error: errorMessage }; } } async function executeAppleScript(scriptPath, args = [], timeout = 30000) { try { // Build the command with properly escaped arguments const quotedArgs = args.map(arg => `"${arg.replace(/"/g, '\\"')}"`).join(' '); const command = `osascript "${scriptPath}" ${quotedArgs}`; const result = (0, child_process_1.execSync)(command, { timeout, encoding: 'utf8', stdio: 'pipe' }); return result.toString().trim(); } catch (error) { if (error.code === 'TIMEOUT') { throw new Error(`AppleScript execution timed out after ${timeout}ms`); } throw new Error(`AppleScript execution failed: ${error.message}`); } }