Skip to main content
Glama
vuvuvu

StreamerSongList MCP Server

by vuvuvu
server.js17.1 kB
#!/usr/bin/env node // Use Node's built-in fetch (Node >= 18). Fail fast if missing. if (typeof globalThis.fetch !== 'function') { console.error('Error: global fetch not found. Node.js 18+ is required.'); process.exit(1); } // Import required modules const path = require('path'); const fs = require('fs'); const pkg = require('../package.json'); // Derive runtime metadata and configuration from package.json / env const PACKAGE_VERSION = pkg.version || '0.0.0'; const SDK_DEP_RANGE = (pkg.dependencies && pkg.dependencies['@modelcontextprotocol/sdk']) || ''; const API_BASE = process.env.SSL_API_BASE || 'https://api.streamersonglist.com/v1'; // Function to find the MCP SDK function findMcpSdk() { try { // First try direct import return require('@modelcontextprotocol/sdk'); } catch (e) { // Look for the SDK in various locations const possiblePaths = [ // Local node_modules path.join(process.cwd(), 'node_modules/@modelcontextprotocol/sdk'), // Parent node_modules (when installed as dependency) path.join(process.cwd(), '../node_modules/@modelcontextprotocol/sdk'), // Global node_modules path.join(process.execPath, '../lib/node_modules/@modelcontextprotocol/sdk') ]; for (const basePath of possiblePaths) { try { if (fs.existsSync(path.join(basePath, 'package.json'))) { // Found the SDK, now try to load the components const serverPath = path.join(basePath, 'dist/cjs/server/index.js'); const stdioPath = path.join(basePath, 'dist/cjs/server/stdio.js'); const typesPath = path.join(basePath, 'dist/cjs/types.js'); if (fs.existsSync(serverPath) && fs.existsSync(stdioPath) && fs.existsSync(typesPath)) { return { Server: require(serverPath).Server, StdioServerTransport: require(stdioPath).StdioServerTransport, CallToolRequestSchema: require(typesPath).CallToolRequestSchema, ListToolsRequestSchema: require(typesPath).ListToolsRequestSchema }; } } } catch (err) { // Continue to next path } } // If we get here, we couldn't find the SDK throw new Error('Could not locate @modelcontextprotocol/sdk in any node_modules directory'); } } // Try to load the MCP SDK try { const sdk = findMcpSdk(); // Make components available globally global.Server = sdk.Server; global.StdioServerTransport = sdk.StdioServerTransport; global.CallToolRequestSchema = sdk.CallToolRequestSchema; global.ListToolsRequestSchema = sdk.ListToolsRequestSchema; } catch (error) { console.error("Error loading MCP SDK:", error.message); const sdkHint = SDK_DEP_RANGE || 'latest'; console.error(`Please install the MCP SDK with: npm install @modelcontextprotocol/sdk@${sdkHint}`); console.error(`If the error persists, try installing the package globally: npm install -g @modelcontextprotocol/sdk@${sdkHint}`); process.exit(1); } // Parse CLI arguments for default streamer preference function resolveDefaultStreamer(argv) { let streamer = null; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === '--streamer' || arg === '-s') { const next = argv[i + 1]; if (next && !next.startsWith('-')) { streamer = next; break; } } else if (arg.startsWith('--streamer=')) { streamer = arg.split('=')[1]; break; } else if (arg.startsWith('-') && !arg.startsWith('--') && arg.length > 2) { streamer = arg.slice(1); break; } } return streamer; } const cliArgs = process.argv.slice(2); const cliDefaultStreamer = resolveDefaultStreamer(cliArgs); if (cliDefaultStreamer) { process.env.DEFAULT_STREAMER = cliDefaultStreamer; } // Create the server let defaultStreamer = process.env.DEFAULT_STREAMER || null; const server = new global.Server( { name: "streamersonglist-mcp", version: PACKAGE_VERSION, }, { capabilities: { tools: {}, }, } ); // Tool definitions const tools = [ { name: "getStreamerByName", description: "Fetch detailed information about a specific streamer", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer", }, }, required: [], }, }, { name: "getQueue", description: "View current song queues with pagination support", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer whose queue to fetch", }, limit: { type: "number", description: "Maximum number of songs to return (default: 50)", default: 50, }, offset: { type: "number", description: "Number of songs to skip for pagination (default: 0)", default: 0, }, }, required: [], }, }, { name: "monitorQueue", description: "Monitor queue changes with configurable polling intervals", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer whose queue to monitor", }, interval: { type: "number", description: "Polling interval in seconds (default: 30)", default: 30, }, duration: { type: "number", description: "How long to monitor in seconds (default: 300)", default: 300, }, }, required: [], }, }, { name: "getSongs", description: "Fetch the complete song list for a streamer with pagination support", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer whose song list to fetch", }, limit: { type: "number", description: "Maximum number of songs to return (default: 100)", default: 100, }, offset: { type: "number", description: "Number of songs to skip for pagination (default: 0)", default: 0, }, }, required: [], }, }, { name: "searchSongs", description: "Search within a streamer's song list by title or artist", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer whose songs to search", }, query: { type: "string", description: "Search query to match against song titles and artists", }, limit: { type: "number", description: "Maximum number of results to return (default: 20)", default: 20, }, }, required: ["query"], }, }, { name: "getSongDetails", description: "Get detailed information about a specific song by ID", inputSchema: { type: "object", properties: { streamerName: { type: "string", description: "The name of the streamer who owns the song", }, songId: { type: "number", description: "The ID of the song to fetch details for", }, }, required: ["songId"], }, }, ]; // List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools, }; }); // Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: rawArgs } = request.params; const args = rawArgs ?? {}; try { switch (name) { case "getStreamerByName": { const { streamerName = defaultStreamer } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } try { const response = await fetch(`${API_BASE}/streamers/${encodeURIComponent(streamerName)}`); if (!response.ok) { return { content: [{ type: "text", text: `Error fetching streamer data: ${response.status} ${response.statusText}` }] }; } const streamerData = await response.json(); return { content: [{ type: "text", text: JSON.stringify(streamerData, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } case "getQueue": { const { streamerName = defaultStreamer, limit = 50, offset = 0 } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } try { const response = await fetch(`${API_BASE}/streamers/${encodeURIComponent(streamerName)}/queue?limit=${limit}&offset=${offset}`); if (!response.ok) { return { content: [{ type: "text", text: `Error fetching queue: ${response.status} ${response.statusText}` }] }; } const queueData = await response.json(); return { content: [{ type: "text", text: JSON.stringify(queueData, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } case "monitorQueue": { const { streamerName = defaultStreamer, interval = 30, duration = 300 } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } try { const updates = []; // Initial queue fetch const initialResponse = await fetch(`https://api.streamersonglist.com/v1/streamers/${encodeURIComponent(streamerName)}/queue`); if (initialResponse.ok) { const initialQueue = await initialResponse.json(); updates.push({ timestamp: new Date().toISOString(), type: 'initial', data: initialQueue }); } const monitoringId = `monitor_${streamerName}_${Date.now()}`; return { content: [{ type: "text", text: `Started monitoring queue for ${streamerName}\n` + `Monitoring ID: ${monitoringId}\n` + `Interval: ${interval} seconds\n` + `Duration: ${duration} seconds\n` + `\nNote: This is a simulation. In a real implementation, this would:\n` + `- Establish WebSocket or SSE connection\n` + `- Subscribe to queue updates for the streamer\n` + `- Send real-time notifications of queue changes\n` + `\nInitial queue data:\n${JSON.stringify(updates, null, 2)}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error setting up monitoring: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } case "getSongs": { const { streamerName = defaultStreamer, limit = 100, offset = 0 } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } try { const response = await fetch(`${API_BASE}/streamers/${encodeURIComponent(streamerName)}/songs?limit=${limit}&offset=${offset}`); if (!response.ok) { return { content: [{ type: "text", text: `Error fetching song list: ${response.status} ${response.statusText}` }] }; } const songsData = await response.json(); return { content: [{ type: "text", text: JSON.stringify(songsData, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } case "searchSongs": { const { streamerName = defaultStreamer, query, limit = 20 } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } if (!query) { throw new Error("query is required for song search"); } try { // First get all songs, then filter locally const response = await fetch(`${API_BASE}/streamers/${encodeURIComponent(streamerName)}/songs?limit=1000`); if (!response.ok) { return { content: [{ type: "text", text: `Error fetching songs for search: ${response.status} ${response.statusText}` }] }; } const songsData = await response.json(); const allSongs = songsData.items || songsData; // Handle different response formats const searchQuery = query.toLowerCase(); // Filter songs by title or artist const filteredSongs = allSongs.filter(song => { const title = (song.title || '').toLowerCase(); const artist = (song.artist || '').toLowerCase(); return title.includes(searchQuery) || artist.includes(searchQuery); }).slice(0, limit); return { content: [{ type: "text", text: `Found ${filteredSongs.length} songs matching "${query}":\n${JSON.stringify(filteredSongs, null, 2)}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } case "getSongDetails": { const { streamerName = defaultStreamer, songId } = args; if (!streamerName) { throw new Error( "streamerName is required. Provide a streamerName or set the DEFAULT_STREAMER environment variable." ); } if (!songId) { throw new Error("songId is required"); } try { // Get all songs and find the specific one const response = await fetch(`${API_BASE}/streamers/${encodeURIComponent(streamerName)}/songs`); if (!response.ok) { return { content: [{ type: "text", text: `Error fetching songs: ${response.status} ${response.statusText}` }] }; } const songsData = await response.json(); const allSongs = songsData.items || songsData; // Handle different response formats const song = allSongs.find(s => s.id === songId); if (!song) { return { content: [{ type: "text", text: `Song with ID ${songId} not found for streamer ${streamerName}` }] }; } return { content: [{ type: "text", text: JSON.stringify(song, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` }], isError: true, }; } }); // Start the server async function main() { const transport = new global.StdioServerTransport(); await server.connect(transport); console.error("StreamerSongList MCP Server running on stdio"); } main().catch((error) => { console.error("Server error:", error); process.exit(1); });

Implementation Reference

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/vuvuvu/streamersonglist-mcp'

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