Skip to main content
Glama

Spotify Streamable MCP Server

by iceener
catalog.ts3.8 kB
import { z } from 'zod'; import { MinimalEntityCodec, SearchResponseCodec, TrackCodec, } from '../../types/spotify.codecs.ts'; import { mapStatusToCode } from '../../utils/http-result.ts'; import { toSlimAlbum, toSlimArtist, toSlimPlaylist, toSlimTrack, } from '../../utils/mappers.ts'; import type { HttpClient } from '../http-client.ts'; export type SearchParams = { q: string; types: string[]; market?: string; limit?: number; offset?: number; include_external?: 'audio'; }; export async function searchCatalog( http: HttpClient, apiBaseUrl: string, getAppToken: (signal?: AbortSignal) => Promise<string>, params: SearchParams, signal?: AbortSignal, ) { const token = await getAppToken(signal); const base = apiBaseUrl.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`; const url = new URL('search', base); url.searchParams.set('q', params.q); url.searchParams.set('type', params.types.join(',')); if (params.limit) { url.searchParams.set('limit', String(params.limit)); } if (params.offset) { url.searchParams.set('offset', String(params.offset)); } if (params.market) { url.searchParams.set('market', params.market); } if (params.include_external) { url.searchParams.set('include_external', params.include_external); } const response = await http(url.toString(), { headers: { Authorization: `Bearer ${token}` }, signal, }); if (!response.ok) { const text = await response.text().catch(() => ''); const code = mapStatusToCode(response.status); throw new Error( `Search failed: ${response.status} ${response.statusText}${ text ? ` - ${text}` : '' } [${code}]`, ); } const json = SearchResponseCodec.parse(await response.json()); const totals: Record<string, number> = {}; const items: Array< | ReturnType<typeof toSlimTrack> | ReturnType<typeof toSlimAlbum> | ReturnType<typeof toSlimArtist> | ReturnType<typeof toSlimPlaylist> > = []; if (json.tracks) { totals.track = json.tracks.total ?? 0; const trackItems = Array.isArray(json.tracks.items) ? json.tracks.items : []; for (const raw of trackItems) { const parsed = TrackCodec.safeParse(raw); if (parsed.success) { const slim = toSlimTrack(parsed.data); if (slim.id && slim.name) { items.push(slim); } } } } if (json.albums) { totals.album = json.albums.total ?? 0; const albumItems = Array.isArray(json.albums.items) ? json.albums.items : []; for (const raw of albumItems) { const parsed = MinimalEntityCodec.safeParse(raw); if (parsed.success) { const slim = toSlimAlbum(parsed.data); if (slim.id && slim.name) { items.push(slim); } } } } if (json.artists) { totals.artist = json.artists.total ?? 0; const artistItems = Array.isArray(json.artists.items) ? json.artists.items : []; for (const raw of artistItems) { const parsed = MinimalEntityCodec.safeParse(raw); if (parsed.success) { const slim = toSlimArtist(parsed.data); if (slim.id && slim.name) { items.push(slim); } } } } if (json.playlists) { totals.playlist = json.playlists.total ?? 0; const playlistItems = Array.isArray(json.playlists.items) ? json.playlists.items : []; for (const raw of playlistItems) { const parsed = MinimalEntityCodec.extend({ owner: z.object({ display_name: z.string().nullable().optional() }).optional(), }).safeParse(raw); if (parsed.success) { const slim = toSlimPlaylist(parsed.data); if (slim.id && slim.name) { items.push(slim); } } } } return { totals, items } as const; }

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/iceener/spotify-streamable-mcp-server'

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