@pedrocid/music-mcp
Version:
MCP server for controlling Apple Music on macOS (v1.0.5)
155 lines (154 loc) • 6.01 kB
JavaScript
"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}`);
}
}