Skip to main content
Glama
port-utils.ts3.91 kB
import net from 'net'; import crypto from 'crypto'; import { readJsonFile } from './persistent-auth-config.js'; import { z } from 'zod'; /** * OAuth client information schema for reading existing client data */ const OAuthClientInfoSchema = z.object({ redirect_uris: z.array(z.string()), client_id: z.string().optional(), client_secret: z.string().optional(), }); type OAuthClientInfo = z.infer<typeof OAuthClientInfoSchema>; /** * Finds an available port on the local machine * @param preferredPort Optional preferred port to try first * @returns A promise that resolves to an available port number */ export async function findAvailablePort(preferredPort?: number): Promise<number> { return new Promise((resolve, reject) => { const server = net.createServer(); server.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EADDRINUSE') { // If preferred port is in use, get a random port server.listen(0); } else { reject(err); } }); server.on('listening', () => { const { port } = server.address() as net.AddressInfo; server.close(() => { resolve(port); }); }); // Try preferred port first, or get a random port server.listen(preferredPort || 0); }); } /** * Calculate a consistent default port based on server URL hash * @param serverUrlHash The hashed server URL * @returns A port number in the range 3335-49151 */ export function calculateDefaultPort(serverUrlHash: string): number { // Convert the first 4 bytes of the serverUrlHash into a port offset const offset = parseInt(serverUrlHash.substring(0, 4), 16); // Pick a consistent but random-seeming port from 3335 to 49151 return 3335 + (offset % 45816); } /** * Find the port from existing OAuth client information * @param serverUrlHash The hashed server URL * @returns The existing callback port or undefined if not found */ export async function findExistingClientPort(serverUrlHash: string): Promise<number | undefined> { const clientInfo = await readJsonFile<OAuthClientInfo>(serverUrlHash, 'client_info.json'); if (!clientInfo) { return undefined; } const localhostRedirectUri = clientInfo.redirect_uris .map(uri => { try { return new URL(uri); } catch { return null; } }) .filter((url): url is URL => url !== null) .find(({ hostname }) => hostname === 'localhost' || hostname === '127.0.0.1'); if (!localhostRedirectUri) { return undefined; } const port = parseInt(localhostRedirectUri.port); return isNaN(port) ? undefined : port; } /** * Generate a hash for the server URL to use in filenames * @param serverUrl The server URL to hash * @returns The hashed server URL */ export function getServerUrlHash(serverUrl: string): string { return crypto.createHash('md5').update(serverUrl).digest('hex'); } /** * Smart callback port selection logic for self-hosted WordPress sites * @param serverUrl The WordPress site URL * @param specifiedPort Optional port specified by user * @param _unused Legacy parameter for backward compatibility (ignored) * @returns The selected callback port */ export async function selectCallbackPort( serverUrl: string, specifiedPort?: number, _unused?: boolean ): Promise<number> { const serverUrlHash = getServerUrlHash(serverUrl); // If port is specified, use it if (specifiedPort) { return specifiedPort; } // Try to find existing client port or calculate a default const defaultPort = calculateDefaultPort(serverUrlHash); const [existingClientPort, availablePort] = await Promise.all([ findExistingClientPort(serverUrlHash), findAvailablePort(defaultPort), ]); // Prefer existing client port for consistency if (existingClientPort) { return existingClientPort; } // Use automatically selected port return availablePort; }

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/Automattic/mcp-wordpress-remote'

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