Skip to main content
Glama
samwang0723

MCP AppleMusic Server

index.ts7.31 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import express from 'express'; import { randomUUID } from 'node:crypto'; import { z } from 'zod'; import { TextContent, isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import config from './config/index.js'; import { AppleMusicService } from './services/applemusic.js'; // Load environment variables dotenv.config(); class McpServerApp { private createServer(): McpServer { const server = new McpServer({ name: 'mcp-applemusic-server', version: '1.0.0', }); // Instantiate AppleMusicService const appleMusicService = new AppleMusicService(); // Method mapping object to map tool names to service method names const methodMapping: { [key: string]: string } = { 'apple-music-set-volume': 'setVolume', 'apple-music-next-track': 'nextTrack', 'apple-music-pause': 'pause', 'apple-music-play': 'play', 'apple-music-search-album': 'searchAlbum', 'apple-music-search-and-play': 'searchAndPlay', 'apple-music-search-song': 'searchSong', 'apple-music-search-artist': 'searchArtist', 'apple-music-get-current-track': 'getCurrentTrack', }; // Register all Apple Music tools dynamically for (const tool of config.mcp.tools) { const parameters = (tool.parameters as z.ZodObject<z.ZodRawShape>).shape ?? {}; server.tool(tool.name, tool.description, parameters, async args => { try { const methodName = methodMapping[tool.name]; if (!methodName) { throw new Error(`Unknown tool: ${tool.name}`); } // Get the service method const serviceMethod = (appleMusicService as any)[methodName]; if (typeof serviceMethod !== 'function') { throw new Error(`Service method not found: ${methodName}`); } // Call the appropriate service method with validated arguments let result; if (tool.name === 'apple-music-set-volume') { result = await serviceMethod.call(appleMusicService, args.level); } else if (tool.name === 'apple-music-search-album') { result = await serviceMethod.call(appleMusicService, args.album); } else if (tool.name === 'apple-music-search-and-play') { result = await serviceMethod.call(appleMusicService, args.song); } else if (tool.name === 'apple-music-search-song') { result = await serviceMethod.call(appleMusicService, args.song); } else if (tool.name === 'apple-music-search-artist') { result = await serviceMethod.call(appleMusicService, args.artist); } else { // For tools with no parameters (apple-music-next-track, apple-music-pause, apple-music-play, apple-music-get-current-track) result = await serviceMethod.call(appleMusicService); } // Return result in expected MCP format return { content: [ { type: 'text', text: result.output, } as TextContent, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Error executing ${tool.name}: ${errorMessage}`); } }); } return server; } async run() { const app = express(); app.use(express.json()); // Map to store transports by session ID for stateful connections const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'mcp-applemusic-server' }); }); // Handle POST requests for client-to-server communication app.post('/mcp', async (req, res) => { try { // Check for existing session ID const sessionId = req.headers['mcp-session-id'] as string | undefined; let transport: StreamableHTTPServerTransport; let server: McpServer; if (sessionId && transports[sessionId]) { // Reuse existing transport transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: sessionId => { // Store the transport by session ID transports[sessionId] = transport; }, }); // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { delete transports[transport.sessionId]; } }; // Create new server instance server = this.createServer(); // Connect to the MCP server await server.connect(transport); } else { // Invalid request res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: null, }); return; } // Handle the request await transport.handleRequest(req, res, req.body); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); // Reusable handler for GET and DELETE requests const handleSessionRequest = async ( req: express.Request, res: express.Response ) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).send('Invalid or missing session ID'); return; } const transport = transports[sessionId]; await transport.handleRequest(req, res); }; // Handle GET requests for server-to-client notifications via SSE app.get('/mcp', handleSessionRequest); // Handle DELETE requests for session termination app.delete('/mcp', handleSessionRequest); // Start the server app.listen(config.server.port, '0.0.0.0', () => { console.log( `MCP Applemusic Server running on http://0.0.0.0:${config.server.port}` ); console.log( `Health check available at http://0.0.0.0:${config.server.port}/health` ); console.log( `MCP endpoint available at http://0.0.0.0:${config.server.port}/mcp` ); }); } } // Start the server const server = new McpServerApp(); server.run().catch(error => { console.error('Failed to start server:', error); process.exit(1); }); // Handle server shutdown process.on('SIGINT', async () => { console.log('Shutting down server...'); process.exit(0); });

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