Skip to main content
Glama
samwang0723

MCP AppleMusic Server

applemusic.ts9.05 kB
import { spawn } from 'child_process'; import logger from '../utils/logger.js'; import { platform } from 'os'; interface AppleMusicResponse { success: boolean; output: string; } export class AppleMusicService { constructor() { if (platform() !== 'darwin') { logger.warn('AppleMusicService is only supported on macOS'); } } private sanitizeInput(input: string): string { return input.replace(/"/g, '""'); } private async exec(script: string): Promise<string> { return new Promise((resolve, reject) => { logger.debug('Executing AppleScript:', { script }); const process = spawn('osascript', ['-e', script]); let stdout = ''; let stderr = ''; process.stdout.on('data', data => { stdout += data.toString(); }); process.stderr.on('data', data => { stderr += data.toString(); }); process.on('close', code => { logger.debug('AppleScript execution completed:', { code, stdout, stderr, }); if (code === 0) { resolve(stdout.trim()); } else { const error = stderr.trim() || `Process exited with code ${code}`; logger.error('AppleScript execution failed:', { error, code }); reject(new Error(error)); } }); process.on('error', error => { logger.error('Failed to spawn osascript process:', { error: error.message, }); reject(new Error(`Failed to execute AppleScript: ${error.message}`)); }); }); } async setVolume(level: number): Promise<AppleMusicResponse> { try { if (level < 0 || level > 100) { throw new Error('Volume level must be between 0 and 100'); } const script = `set volume ${level}`; // eslint-disable-next-line @typescript-eslint/no-unused-vars const output = await this.exec(script); logger.info('Volume set successfully:', { level }); return { success: true, output: `Volume set to ${level}` }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to set volume:', { error: errorMessage, level }); return { success: false, output: `Failed to set volume: ${errorMessage}`, }; } } async nextTrack(): Promise<AppleMusicResponse> { try { const script = 'tell application "Music" to next track'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const output = await this.exec(script); logger.info('Next track command executed successfully'); return { success: true, output: 'Skipped to next track' }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to skip to next track:', { error: errorMessage }); return { success: false, output: `Failed to skip to next track: ${errorMessage}`, }; } } async pause(): Promise<AppleMusicResponse> { try { const script = 'tell application "Music" to pause'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const output = await this.exec(script); logger.info('Music paused successfully'); return { success: true, output: 'Music paused' }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to pause music:', { error: errorMessage }); return { success: false, output: `Failed to pause music: ${errorMessage}`, }; } } async play(): Promise<AppleMusicResponse> { try { const script = 'tell application "Music" to play'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const output = await this.exec(script); logger.info('Music playback started successfully'); return { success: true, output: 'Music playback started' }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to start music playback:', { error: errorMessage }); return { success: false, output: `Failed to start music playback: ${errorMessage}`, }; } } async getCurrentTrack(): Promise<AppleMusicResponse> { try { const script = 'tell application "Music" to get name of current track'; const output = await this.exec(script); logger.info('Current track retrieved successfully:', { track: output }); return { success: true, output: output || 'No track currently playing' }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to get current track:', { error: errorMessage }); return { success: false, output: `Failed to get current track: ${errorMessage}`, }; } } async searchAlbum(album: string): Promise<AppleMusicResponse> { try { const sanitizedAlbum = this.sanitizeInput(album); const script = `tell application "Music" set trackList to every track of playlist "Library" whose album contains "${sanitizedAlbum}" repeat with t in trackList log (name of t) & " - " & (artist of t) & " (" & (album of t) & ")" end repeat end tell`; const output = await this.exec(script); logger.info('Album search completed successfully:', { album, resultCount: output.split('\n').length, }); return { success: true, output: output || `No tracks found for album: ${album}`, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to search album:', { error: errorMessage, album }); return { success: false, output: `Failed to search album: ${errorMessage}`, }; } } async searchAndPlay(song: string): Promise<AppleMusicResponse> { try { const sanitizedSong = this.sanitizeInput(song); const script = `tell application "Music" set searchResults to (search playlist "Library" for "${sanitizedSong}") if (count of searchResults) > 0 then play (item 1 of searchResults) return "Playing: " & (name of item 1 of searchResults) else return "No results found" end if end tell`; const output = await this.exec(script); logger.info('Search and play completed successfully:', { song, result: output, }); return { success: true, output }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to search and play song:', { error: errorMessage, song, }); return { success: false, output: `Failed to search and play song: ${errorMessage}`, }; } } async searchSong(song: string): Promise<AppleMusicResponse> { try { const sanitizedSong = this.sanitizeInput(song); const script = `tell application "Music" set searchResults to (search playlist "Library" for "${sanitizedSong}") repeat with t in searchResults log (name of t) & " - " & (artist of t) end repeat end tell`; const output = await this.exec(script); logger.info('Song search completed successfully:', { song, resultCount: output.split('\n').length, }); return { success: true, output: output || `No songs found for: ${song}` }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to search song:', { error: errorMessage, song }); return { success: false, output: `Failed to search song: ${errorMessage}`, }; } } async searchArtist(artist: string): Promise<AppleMusicResponse> { try { const sanitizedArtist = this.sanitizeInput(artist); const script = `tell application "Music" set trackList to every track of playlist "Library" whose artist contains "${sanitizedArtist}" repeat with t in trackList log (name of t) & " - " & (artist of t) end repeat end tell`; const output = await this.exec(script); logger.info('Artist search completed successfully:', { artist, resultCount: output.split('\n').length, }); return { success: true, output: output || `No tracks found for artist: ${artist}`, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Failed to search artist:', { error: errorMessage, artist }); return { success: false, output: `Failed to search artist: ${errorMessage}`, }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samwang0723/mcp-applemusic'

If you have feedback or need assistance with the MCP directory API, please join our Discord server