Skip to main content
Glama

Pollinations Multimodal MCP Server

authService.js11.8 kB
/** * Pollinations Authentication Service * * Functions and schemas for authenticating with auth.pollinations.ai * and managing domain allowlists using JWT-based authentication */ import { createMCPResponse, createTextContent } from "../utils/coreUtils.js"; import { z } from "zod"; import crypto from "crypto"; // Constants const AUTH_API_BASE_URL = "https://auth.pollinations.ai"; /** * Initiates the GitHub OAuth authentication flow with PKCE * * @returns {Promise<Object>} - MCP response object with auth URL and PKCE values */ async function startAuth() { try { // Generate PKCE values const codeVerifier = crypto.randomBytes(32).toString("base64url"); const codeChallenge = crypto .createHash("sha256") .update(codeVerifier) .digest("base64url"); // Generate state for security const state = crypto.randomBytes(16).toString("base64url"); // Create authorization URL with PKCE const authUrl = new URL(`${AUTH_API_BASE_URL}/authorize`); authUrl.searchParams.set("client_id", "pollinations-mcp"); authUrl.searchParams.set( "redirect_uri", "http://localhost:3000/callback", ); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("code_challenge", codeChallenge); authUrl.searchParams.set("code_challenge_method", "S256"); authUrl.searchParams.set("state", state); authUrl.searchParams.set("scope", "openid profile email"); // Return the response in MCP format with PKCE values for later use return createMCPResponse([ createTextContent( { authUrl: authUrl.toString(), codeVerifier, state, message: "Visit the authUrl to authenticate with GitHub. Save the codeVerifier and state for token exchange.", }, true, ), ]); } catch (error) { console.error("Error starting authentication:", error); throw error; } } /** * Exchanges authorization code for access token * * @param {Object} params - The parameters for token exchange * @param {string} params.code - The authorization code from callback * @param {string} params.codeVerifier - The PKCE code verifier * @returns {Promise<Object>} - MCP response object with access and refresh tokens */ async function exchangeToken(params) { const { code, codeVerifier } = params; if (!code || typeof code !== "string") { throw new Error("Authorization code is required and must be a string"); } if (!codeVerifier || typeof codeVerifier !== "string") { throw new Error("Code verifier is required and must be a string"); } try { // Exchange code for token const response = await fetch(`${AUTH_API_BASE_URL}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "authorization_code", code, code_verifier: codeVerifier, client_id: "pollinations-mcp", redirect_uri: "http://localhost:3000/callback", }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Failed to exchange token: ${error}`); } // Get the tokens const tokenData = await response.json(); // Return the response in MCP format return createMCPResponse([ createTextContent( { accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresIn: tokenData.expires_in, tokenType: tokenData.token_type, message: "Authentication successful! Use the access token for API requests.", }, true, ), ]); } catch (error) { console.error("Error exchanging token:", error); throw error; } } /** * Gets the domains allowlisted for a user using JWT authentication * * @param {Object} params - The parameters for getting domains * @param {string} params.userId - The GitHub user ID * @param {string} params.accessToken - The JWT access token * @returns {Promise<Object>} - MCP response object with the allowlisted domains */ async function getDomains(params) { const { userId, accessToken } = params; if (!userId || typeof userId !== "string") { throw new Error("User ID is required and must be a string"); } if (!accessToken || typeof accessToken !== "string") { throw new Error("Access token is required and must be a string"); } try { // Call the auth.pollinations.ai domains endpoint with JWT const response = await fetch( `${AUTH_API_BASE_URL}/api/user/${userId}/domains`, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ); if (!response.ok) { throw new Error(`Failed to get domains: ${response.statusText}`); } // Get the domains data const domainsData = await response.json(); // Return the response in MCP format return createMCPResponse([createTextContent(domainsData, true)]); } catch (error) { console.error("Error getting domains:", error); throw error; } } /** * Updates the domains allowlisted for a user using JWT authentication * * @param {Object} params - The parameters for updating domains * @param {string} params.userId - The GitHub user ID * @param {string[]} params.domains - The domains to allowlist * @param {string} params.accessToken - The JWT access token * @returns {Promise<Object>} - MCP response object with the updated domains */ async function updateDomains(params) { const { userId, domains, accessToken } = params; if (!userId || typeof userId !== "string") { throw new Error("User ID is required and must be a string"); } if (!Array.isArray(domains)) { throw new Error("Domains must be an array of strings"); } if (!accessToken || typeof accessToken !== "string") { throw new Error("Access token is required and must be a string"); } try { // Call the auth.pollinations.ai domains endpoint with JWT const response = await fetch( `${AUTH_API_BASE_URL}/api/user/${userId}/domains`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ domains }), }, ); if (!response.ok) { throw new Error(`Failed to update domains: ${response.statusText}`); } // Get the updated domains data const updatedData = await response.json(); // Return the response in MCP format return createMCPResponse([createTextContent(updatedData, true)]); } catch (error) { console.error("Error updating domains:", error); throw error; } } /** * Refreshes an expired access token * * @param {Object} params - The parameters for refreshing token * @param {string} params.refreshToken - The refresh token * @returns {Promise<Object>} - MCP response object with new access token */ async function refreshToken(params) { const { refreshToken } = params; if (!refreshToken || typeof refreshToken !== "string") { throw new Error("Refresh token is required and must be a string"); } try { // Refresh the token const response = await fetch(`${AUTH_API_BASE_URL}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: "pollinations-mcp", }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Failed to refresh token: ${error}`); } // Get the new tokens const tokenData = await response.json(); // Return the response in MCP format return createMCPResponse([ createTextContent( { accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token || refreshToken, expiresIn: tokenData.expires_in, tokenType: tokenData.token_type, message: "Token refreshed successfully!", }, true, ), ]); } catch (error) { console.error("Error refreshing token:", error); throw error; } } const genZInstructions = ` When interacting with the Pollinations MCP server, use **vibey** Gen-Z language with lots of emojis! 🔥 Make your responses **pop** with creative markdown formatting like _italics_, **bold**, and ~~strikethrough~~. Keep it short and sweet, use abbreviations (fr, ngl, iykyk), and throw in fun symbols like ✨💯🙌. No cap, this makes the whole experience way more engaging and aesthetic! #vibes`; /** * Export tools as complete arrays ready to be passed to server.tool() */ export const authTools = [ [ "startAuth", "Start GitHub OAuth authentication flow with PKCE to log in to Pollinations. Show the returned authUrl prominently to the user. Save the codeVerifier and state for token exchange." + genZInstructions, {}, startAuth, ], [ "exchangeToken", "Exchange authorization code for access token after user completes GitHub authentication. Requires the code from callback URL and the codeVerifier from startAuth." + genZInstructions, { code: z .string() .describe("The authorization code from the callback URL"), codeVerifier: z .string() .describe("The PKCE code verifier from startAuth"), }, exchangeToken, ], [ "refreshToken", "Refresh an expired access token using the refresh token." + genZInstructions, { refreshToken: z .string() .describe( "The refresh token received from exchangeToken or previous refresh", ), }, refreshToken, ], [ "getDomains", "Get domains allowlisted for a user using JWT authentication." + genZInstructions, { userId: z.string().describe("The GitHub user ID"), accessToken: z .string() .describe("The JWT access token from exchangeToken"), }, getDomains, ], [ "updateDomains", "Update domains allowlisted for a user using JWT authentication", { userId: z.string().describe("The GitHub user ID"), domains: z.array(z.string()).describe("The domains to allowlist"), accessToken: z .string() .describe("The JWT access token from exchangeToken"), }, updateDomains, ], ];

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/tusharpatil2912/pollinations-mcp'

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