Skip to main content
Glama
iceener

Spotify Streamable MCP Server

by iceener
strategy.ts5.75 kB
// Auth Strategy Pattern // Supports: OAuth, API Key, Bearer Token, Custom Headers /** * Auth strategy types supported by the template. * * - 'oauth': Full OAuth 2.1 PKCE flow with RS token mapping * - 'bearer': Simple static Bearer token (from env) * - 'api_key': API key in custom header (e.g., x-api-key) * - 'custom': Arbitrary headers from config * - 'none': No authentication required */ export type AuthStrategyType = 'oauth' | 'bearer' | 'api_key' | 'custom' | 'none'; /** * Resolved auth headers to inject into tool context. */ export interface ResolvedAuth { /** Auth strategy used */ strategy: AuthStrategyType; /** Headers to pass to API calls */ headers: Record<string, string>; /** Raw access token (if bearer/oauth) */ accessToken?: string; /** Provider tokens (oauth only) */ provider?: { accessToken: string; refreshToken?: string; expiresAt?: number; }; } /** * Strategy configuration parsed from env. */ export interface AuthStrategyConfig { type: AuthStrategyType; /** For api_key: header name (default: x-api-key) */ headerName?: string; /** For api_key/bearer: the token/key value */ value?: string; /** For custom: map of header name → value */ customHeaders?: Record<string, string>; } /** * Parse custom headers from env string. * Format: "X-Header-1:value1,X-Header-2:value2" */ function parseCustomHeaders(value: string | undefined): Record<string, string> { if (!value) return {}; const headers: Record<string, string> = {}; const pairs = value.split(','); for (const pair of pairs) { const colonIndex = pair.indexOf(':'); if (colonIndex === -1) continue; const key = pair.slice(0, colonIndex).trim(); const val = pair.slice(colonIndex + 1).trim(); if (key && val) { headers[key] = val; } } return headers; } /** * Parse auth strategy from config. * * Reads from: * - AUTH_STRATEGY: 'oauth' | 'bearer' | 'api_key' | 'custom' | 'none' * - API_KEY: The API key value (for api_key strategy) * - API_KEY_HEADER: Header name (default: x-api-key) * - BEARER_TOKEN: Static bearer token (for bearer strategy) * - CUSTOM_HEADERS: "Header1:value1,Header2:value2" format */ export function parseAuthStrategy(env: Record<string, unknown>): AuthStrategyConfig { const strategy = (env.AUTH_STRATEGY as string)?.toLowerCase() as AuthStrategyType; switch (strategy) { case 'api_key': return { type: 'api_key', headerName: (env.API_KEY_HEADER as string) || 'x-api-key', value: env.API_KEY as string, }; case 'bearer': return { type: 'bearer', value: env.BEARER_TOKEN as string, }; case 'custom': return { type: 'custom', customHeaders: parseCustomHeaders(env.CUSTOM_HEADERS as string), }; case 'none': return { type: 'none' }; default: // Default to OAuth if AUTH_ENABLED or no strategy specified (including 'oauth') return { type: 'oauth' }; } } /** * Build auth headers from strategy config. * Used for non-OAuth strategies where headers are static. */ export function buildAuthHeaders( strategyConfig: AuthStrategyConfig, ): Record<string, string> { const headers: Record<string, string> = {}; switch (strategyConfig.type) { case 'api_key': if (strategyConfig.value && strategyConfig.headerName) { headers[strategyConfig.headerName] = strategyConfig.value; } break; case 'bearer': if (strategyConfig.value) { headers.Authorization = `Bearer ${strategyConfig.value}`; } break; case 'custom': if (strategyConfig.customHeaders) { Object.assign(headers, strategyConfig.customHeaders); } break; case 'oauth': case 'none': // OAuth headers are resolved dynamically via RS token mapping // 'none' has no headers break; } return headers; } /** * Resolve auth for a request. * * For OAuth: requires incoming RS token to be mapped * For other strategies: uses static config values */ export function resolveStaticAuth(strategyConfig: AuthStrategyConfig): ResolvedAuth { const headers = buildAuthHeaders(strategyConfig); return { strategy: strategyConfig.type, headers, accessToken: strategyConfig.type === 'bearer' ? strategyConfig.value : undefined, }; } /** * Merge incoming request headers with strategy headers. * Strategy headers take precedence (they're the "real" auth). */ export function mergeAuthHeaders( incoming: Record<string, string>, strategy: Record<string, string>, ): Record<string, string> { return { ...incoming, ...strategy, }; } /** * Check if auth strategy requires OAuth flow. */ export function isOAuthStrategy(config: AuthStrategyConfig): boolean { return config.type === 'oauth'; } /** * Check if auth strategy requires any authentication. */ export function requiresAuth(config: AuthStrategyConfig): boolean { return config.type !== 'none'; } /** * Validate that required config values are present for the strategy. */ export function validateAuthConfig(config: AuthStrategyConfig): string[] { const errors: string[] = []; switch (config.type) { case 'api_key': if (!config.value) { errors.push('API_KEY is required when AUTH_STRATEGY=api_key'); } break; case 'bearer': if (!config.value) { errors.push('BEARER_TOKEN is required when AUTH_STRATEGY=bearer'); } break; case 'custom': if (!config.customHeaders || Object.keys(config.customHeaders).length === 0) { errors.push('CUSTOM_HEADERS is required when AUTH_STRATEGY=custom'); } break; } return errors; }

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

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