Skip to main content
Glama
utils.ts5.48 kB
import { URL, fileURLToPath } from "url"; import fs from "fs/promises"; import path from "path"; import { readFileSync } from "fs"; import { logger } from "./logger.js"; // --- Type Definitions --- export type TargetInfo = | { type: "remote"; url: URL; hostname: string } | { type: "localFileUrl"; filePath: string; url: URL } | { type: "localPath"; resolvedPath: string; originalInput: string } | { type: "unsupported"; reason: string; originalInput: string }; // --- Domain Handling --- export function extractDomain(urlString: string): string | null { try { const url = new URL(urlString); if (url.protocol === "http:" || url.protocol === "https:") { return url.hostname.toLowerCase(); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; logger.error( `Invalid URL format: ${errorMessage}. Only http, https, and file protocols are supported.` ); } return null; } export function checkDomainAccess( targetInfo: TargetInfo, allowedDomains: Set<string> ): void { if (targetInfo.type === "remote") { if (!allowedDomains.has("*") && !allowedDomains.has(targetInfo.hostname)) { logger.error( `Domain '${targetInfo.hostname}' is not in the allowed domains list.` ); throw new Error( `Access denied: Fetching from domain '${targetInfo.hostname}' is not allowed by server configuration. Ask user to add domain to allow list.` ); } logger.info(`Domain '${targetInfo.hostname}' is allowed.`); } else if ( targetInfo.type === "localFileUrl" || targetInfo.type === "localPath" ) { logger.warn("Local file access permitted."); } else { throw new Error( `Internal error: Unsupported target type '${targetInfo.type}' during access check.` ); } } export async function parseFetchTarget( targetUrlString: string ): Promise<TargetInfo> { try { const targetUrl = new URL(targetUrlString); if (targetUrl.protocol === "http:" || targetUrl.protocol === "https:") { return { type: "remote", url: targetUrl, hostname: targetUrl.hostname.toLowerCase(), }; } else if (targetUrl.protocol === "file:") { try { const filePath = path.resolve(fileURLToPath(targetUrl)); return { type: "localFileUrl", filePath, url: targetUrl, }; } catch (err: any) { return { type: "unsupported", originalInput: targetUrlString, reason: `Failed to convert file: URL to path: ${err.message}`, }; } } else { return { type: "unsupported", originalInput: targetUrlString, reason: `Unsupported URL protocol: ${targetUrl.protocol}`, }; } } catch (urlError) { try { const resolvedPath = path.resolve(targetUrlString); try { await fs.stat(resolvedPath); } catch (statError) { throw new Error( `Path does not exist or is inaccessible: ${resolvedPath}` ); } return { type: "localPath", resolvedPath, originalInput: targetUrlString, }; } catch (pathError: any) { const urlErrorMessage = urlError instanceof Error ? urlError.message : String(urlError); return { type: "unsupported", originalInput: targetUrlString, reason: `Invalid URL ('${urlErrorMessage}') and failed to resolve as path: ${pathError.message}`, }; } } } export async function fetchContent(targetInfo: TargetInfo): Promise<string> { switch (targetInfo.type) { case "remote": { logger.info(`Fetching remote URL: ${targetInfo.url.toString()}`); const response = await fetch(targetInfo.url.toString()); if (!response.ok) throw new Error(`HTTP error ${response.status}`); return await response.text(); } case "localFileUrl": logger.info( `Reading local file path from file: URL: ${targetInfo.filePath}` ); return await fs.readFile(targetInfo.filePath, "utf-8"); case "localPath": logger.info( `Reading local file path directly: ${targetInfo.resolvedPath}` ); return await fs.readFile(targetInfo.resolvedPath, "utf-8"); case "unsupported": throw new Error( `Cannot fetch content for unsupported target type: ${targetInfo.reason}` ); default: { const exhaustiveCheck: never = targetInfo; throw new Error( `Internal error: Unhandled target info type: ${JSON.stringify( exhaustiveCheck )}` ); } } } export type UrlInput = { url: string } | string; export const normalizeUrlInput = ( input: UrlInput | UrlInput[] ): { url: string }[] => { if (Array.isArray(input)) { return input.map((item) => typeof item === "string" ? { url: item } : item ); } return [typeof input === "string" ? { url: input } : input]; }; export function getVersion(): string { const __filename = fileURLToPath(import.meta.url); let dir = path.dirname(__filename); for (let i = 0; i < 4; i++) { const candidate = path.join(dir, "package.json"); try { const pkg = JSON.parse(readFileSync(candidate, "utf-8")); if (pkg.version) return pkg.version; } catch {} dir = path.dirname(dir); } return "unknown"; } // Copyright (C) 2025 Christopher White // SPDX-License-Identifier: AGPL-3.0-or-later

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/maverickg59/sushimcp'

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