@pedrocid/music-mcp
Version:
MCP server for controlling Apple Music on macOS (v1.0.5)
170 lines (169 loc) • 7.26 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.managePlaylistTool = void 0;
exports.handleManagePlaylist = handleManagePlaylist;
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.managePlaylistTool = {
name: 'manage_playlist',
description: 'Create and manage playlists',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['create', 'add_track', 'remove_track', 'rename', 'delete', 'list', 'get_tracks'],
description: 'Action to perform on playlists'
},
playlistName: {
type: 'string',
description: 'Name of the playlist (required for most actions)'
},
trackId: {
type: 'string',
description: 'Track identifier or search term (required for add/remove track)'
},
newName: {
type: 'string',
description: 'New name for the playlist (required for rename)'
}
},
required: ['action']
}
};
async function handleManagePlaylist(input) {
logger_js_1.logger.info({ action: input.action, playlistName: input.playlistName }, 'Managing playlist');
const config = (0, config_js_1.getConfig)();
try {
let result;
switch (input.action) {
case 'list':
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/library/get-playlists.applescript'), [], config.timeoutSeconds * 1000);
break;
case 'create':
if (!input.playlistName) {
return {
success: false,
message: 'Playlist name is required for create action',
error: 'Missing playlist name'
};
}
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/create-playlist.applescript'), [input.playlistName], config.timeoutSeconds * 1000);
break;
case 'add_track':
if (!input.playlistName || !input.trackId) {
return {
success: false,
message: 'Playlist name and track ID/search term are required for add_track action',
error: 'Missing required parameters'
};
}
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/add-to-playlist.applescript'), [input.playlistName, input.trackId], config.timeoutSeconds * 1000);
break;
case 'get_tracks':
if (!input.playlistName) {
return {
success: false,
message: 'Playlist name is required for get_tracks action',
error: 'Missing playlist name'
};
}
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/get-playlist-tracks.applescript'), [input.playlistName], config.timeoutSeconds * 1000);
break;
case 'remove_track':
if (!input.playlistName || !input.trackId) {
return {
success: false,
message: 'Playlist name and track search term are required for remove_track action',
error: 'Missing required parameters'
};
}
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/remove-from-playlist.applescript'), [input.playlistName, input.trackId], config.timeoutSeconds * 1000);
break;
case 'rename':
if (!input.playlistName || !input.newName) {
return {
success: false,
message: 'Current playlist name and new name are required for rename action',
error: 'Missing required parameters'
};
}
// For rename, we'll use AppleScript to change the playlist name
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/rename-playlist.applescript'), [input.playlistName, input.newName], config.timeoutSeconds * 1000);
break;
case 'delete':
if (!input.playlistName) {
return {
success: false,
message: 'Playlist name is required for delete action',
error: 'Missing playlist name'
};
}
// For delete, we'll use AppleScript to remove the playlist
result = await executeAppleScript((0, path_1.join)(__dirname, '../scripts/playlist/delete-playlist.applescript'), [input.playlistName], config.timeoutSeconds * 1000);
break;
default:
return {
success: false,
message: `Unknown action: ${input.action}`,
error: 'Invalid action'
};
}
if (result.startsWith('Error')) {
return {
success: false,
message: result,
error: result
};
}
// Try to parse JSON result for list operations
let data;
if (input.action === 'list' || input.action === 'get_tracks') {
try {
data = JSON.parse(result);
}
catch {
// If not JSON, return as string
data = result;
}
}
else {
data = result;
}
return {
success: true,
data,
message: result
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
logger_js_1.logger.error({ error, action: input.action }, 'Playlist management failed');
return {
success: false,
message: `Playlist ${input.action} failed: ${errorMessage}`,
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}`);
}
}