Skip to main content
Glama
iceener

Spotify Streamable MCP Server

by iceener
input-parsers.ts3.45 kB
// Shared OAuth input parsing for both Node.js and Cloudflare Workers import type { UnifiedConfig } from '../config/env.js'; import type { AuthorizeInput, OAuthConfig, ProviderConfig, TokenInput, } from './types.js'; /** * Parse authorization request from URL search params. */ export function parseAuthorizeInput(url: URL, sessionId?: string): AuthorizeInput { return { codeChallenge: url.searchParams.get('code_challenge') || '', codeChallengeMethod: url.searchParams.get('code_challenge_method') || '', redirectUri: url.searchParams.get('redirect_uri') || '', requestedScope: url.searchParams.get('scope') ?? undefined, state: url.searchParams.get('state') ?? undefined, sid: url.searchParams.get('sid') || sessionId || undefined, }; } /** * Parse callback request from URL search params. */ export function parseCallbackInput(url: URL): { code: string | null; state: string | null; } { return { code: url.searchParams.get('code'), state: url.searchParams.get('state'), }; } /** * Parse token request from form data or JSON body. */ export async function parseTokenInput(request: Request): Promise<URLSearchParams> { const contentType = request.headers.get('content-type') || ''; if (contentType.includes('application/x-www-form-urlencoded')) { const text = await request.text(); return new URLSearchParams(text); } // Try JSON fallback const json = (await request.json().catch(() => ({}))) as Record<string, string>; return new URLSearchParams(json); } /** * Build TokenInput from parsed form data. */ export function buildTokenInput(form: URLSearchParams): TokenInput | { error: string } { const grant = form.get('grant_type'); if (grant === 'refresh_token') { const refreshToken = form.get('refresh_token'); if (!refreshToken) { return { error: 'missing_refresh_token' }; } return { grant: 'refresh_token', refreshToken }; } if (grant === 'authorization_code') { const code = form.get('code'); const codeVerifier = form.get('code_verifier'); if (!code || !codeVerifier) { return { error: 'missing_code_or_verifier' }; } return { grant: 'authorization_code', code, codeVerifier }; } return { error: 'unsupported_grant_type' }; } /** * Build ProviderConfig from UnifiedConfig. */ export function buildProviderConfig(config: UnifiedConfig): ProviderConfig { return { clientId: config.PROVIDER_CLIENT_ID, clientSecret: config.PROVIDER_CLIENT_SECRET, accountsUrl: config.PROVIDER_ACCOUNTS_URL || 'https://provider.example.com', oauthScopes: config.OAUTH_SCOPES, }; } /** * Build OAuthConfig from UnifiedConfig. */ export function buildOAuthConfig(config: UnifiedConfig): OAuthConfig { return { redirectUri: config.OAUTH_REDIRECT_URI, redirectAllowlist: config.OAUTH_REDIRECT_ALLOWLIST, redirectAllowAll: config.OAUTH_REDIRECT_ALLOW_ALL, }; } /** * Build flow options from request URL. */ export function buildFlowOptions( url: URL, config: UnifiedConfig, overrides: { callbackPath?: string; tokenEndpointPath?: string } = {}, ): { baseUrl: string; isDev: boolean; callbackPath: string; tokenEndpointPath: string; } { return { baseUrl: url.origin, isDev: config.NODE_ENV === 'development', callbackPath: overrides.callbackPath ?? '/oauth/callback', tokenEndpointPath: overrides.tokenEndpointPath ?? '/api/token', }; }

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