Skip to main content
Glama

Spotify Streamable MCP Server

by iceener
mcp.ts8.05 kB
import { randomUUID } from "node:crypto"; import type { HttpBindings } from "@hono/node-server"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { toFetchResponse, toReqRes } from "fetch-to-node"; import { Hono } from "hono"; import { runWithRequestContext } from "../../core/context.ts"; import { ensureSession } from "../../core/session.ts"; import { getSpotifyTokensByRsToken, validateSpotifyToken, } from "../../core/auth.ts"; import { logger } from "../../utils/logger.ts"; export function buildMcpRoutes(params: { server: McpServer; transports: Map<string, StreamableHTTPServerTransport>; }) { const { server, transports } = params; const app = new Hono<{ Bindings: HttpBindings }>(); const MCP_SESSION_HEADER = "Mcp-Session-Id"; app.post("/", async (c) => { const { req, res } = toReqRes(c.req.raw); try { const sessionIdHeader = c.req.header(MCP_SESSION_HEADER) ?? undefined; let body: unknown; try { body = await c.req.json(); } catch { body = undefined; } const isInitialize = Boolean( body && (body as { method?: string }).method === "initialize" ); let transport = sessionIdHeader ? transports.get(sessionIdHeader) : undefined; let didCreate = false; if (!transport) { const created = new StreamableHTTPServerTransport({ sessionIdGenerator: isInitialize ? () => sessionIdHeader || randomUUID() : undefined, onsessioninitialized: isInitialize ? (sid: string) => { transports.set(sid, created); res.setHeader(MCP_SESSION_HEADER, sid); try { ensureSession(sid); } catch {} try { const authHeader = (req.headers["authorization"] as string | undefined) ?? undefined; const rsToken = authHeader ?.toLowerCase() .startsWith("bearer ") ? authHeader.slice("bearer ".length).trim() : undefined; if (rsToken) { const spotify = getSpotifyTokensByRsToken(rsToken); if (spotify) { // Validate the access token before storing it const isValid = await validateSpotifyToken( spotify.access_token ); if (isValid) { const s = ensureSession(sid); s.spotify = { access_token: spotify.access_token, refresh_token: spotify.refresh_token, expires_at: spotify.expires_at, scopes: spotify.scopes, }; void logger.info("auth", { message: "Spotify token validated and stored in session", sessionId: sid, }); } else { void logger.warning("auth", { message: "Invalid Spotify token provided - rejecting session", sessionId: sid, }); // Return an error response for invalid tokens res.statusCode = 401; res.setHeader("Content-Type", "application/json"); res.end( JSON.stringify({ jsonrpc: "2.0", error: { code: -32001, message: "Invalid or expired Spotify credentials provided", }, id: null, }) ); return; } } } } catch (error) { void logger.error("auth", { message: "Error during token validation", sessionId: sid, error: (error as Error).message, }); // Return an error response for validation failures res.statusCode = 500; res.setHeader("Content-Type", "application/json"); res.end( JSON.stringify({ jsonrpc: "2.0", error: { code: -32002, message: "Failed to validate credentials", }, id: null, }) ); return; } void logger.info("mcp", { message: "Session initialized", sessionId: sid, }); } : undefined, }); transport = created; didCreate = true; } transport.onerror = (error) => { void logger.error("transport", { message: "Transport error", error: (error as Error).message, }); }; if (didCreate) { await server.connect(transport); } const sessionId = (req.headers["mcp-session-id"] as string | undefined) ?? undefined; await runWithRequestContext({ sessionId }, async () => { await transport!.handleRequest(req, res, body); }); res.on("close", () => {}); return toFetchResponse(res); } catch (error) { // eslint-disable-next-line no-console console.error("MCP POST /mcp error:", (error as Error).message); return c.json( { jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null, }, 500 ); } }); app.get("/", async (c) => { const { req, res } = toReqRes(c.req.raw); const sessionIdHeader = c.req.header(MCP_SESSION_HEADER); if (!sessionIdHeader) { return c.json( { jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed - no session" }, id: null, }, 405 ); } try { const transport = transports.get(sessionIdHeader); if (!transport) return c.text("Invalid session", 404); await runWithRequestContext({ sessionId: sessionIdHeader }, async () => { await transport!.handleRequest(req, res); }); return toFetchResponse(res); } catch (error) { return c.json( { jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null, }, 500 ); } }); app.delete("/", async (c) => { const { req, res } = toReqRes(c.req.raw); const sessionIdHeader = c.req.header(MCP_SESSION_HEADER); if (!sessionIdHeader) { return c.json( { jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed - no session" }, id: null, }, 405 ); } try { const transport = transports.get(sessionIdHeader); if (!transport) return c.text("Invalid session", 404); await runWithRequestContext({ sessionId: sessionIdHeader }, async () => { await transport!.handleRequest(req, res); }); transports.delete(sessionIdHeader); transport.close(); return toFetchResponse(res); } catch (error) { return c.json( { jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null, }, 500 ); } }); return app; }

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