Skip to main content
Glama

MCP Package Docs Server

by sammcj
registry-utils.ts7.4 kB
import { readFileSync, existsSync } from 'fs'; import { homedir } from 'os'; import { join as pathJoin, dirname } from 'path'; import { McpLogger } from './logger.js'; export interface NpmConfig { registry: string; token?: string; } export class RegistryUtils { private logger: McpLogger; private registryMap: Map<string, NpmConfig>; constructor(logger: McpLogger) { this.logger = logger.child('RegistryUtils'); this.registryMap = this.loadNpmConfig(); } /** * Get registry configuration for a package */ public getRegistryConfigForPackage(packageName: string, projectPath?: string): NpmConfig { // Load fresh config if project path is provided if (projectPath) { this.registryMap = this.loadNpmConfig(projectPath); } if (packageName.startsWith("@")) { const scope = packageName.split("/")[0]; return this.registryMap.get(scope) || this.registryMap.get("default") || { registry: "https://registry.npmjs.org" }; } return this.registryMap.get("default") || { registry: "https://registry.npmjs.org" }; } /** * Load npm configuration from .npmrc files */ private loadNpmConfig(projectPath?: string): Map<string, NpmConfig> { const registryMap = new Map<string, NpmConfig>(); registryMap.set("default", { registry: "https://registry.npmjs.org" }); const scopeToRegistry = new Map<string, string>(); const registryToToken = new Map<string, string>(); this.logger.debug("Loading npm configuration...") this.logger.debug("Project directory:", projectPath || "not specified"); // First read global .npmrc as base configuration const globalNpmrcPath = pathJoin(homedir(), ".npmrc"); this.logger.debug("Checking global .npmrc at:", globalNpmrcPath); if (existsSync(globalNpmrcPath)) { this.logger.debug("Found global .npmrc"); try { const npmrcContent = readFileSync(globalNpmrcPath, "utf-8"); this.parseNpmrcContent(npmrcContent, scopeToRegistry, registryToToken, registryMap); } catch (error) { this.logger.error("Error reading global .npmrc:", error); } } // Then read from root to project directory, so local configs take precedence if (projectPath) { const paths: string[] = []; let currentDir = projectPath; const root = dirname(currentDir); // Collect all paths first while (currentDir !== root) { paths.push(currentDir); currentDir = dirname(currentDir); } paths.push(root); // Process paths in reverse order (root to local) for (const dir of paths.reverse()) { const localNpmrcPath = pathJoin(dir, ".npmrc"); this.logger.debug("Checking for .npmrc at:", localNpmrcPath); if (existsSync(localNpmrcPath)) { this.logger.debug("Found .npmrc at:", localNpmrcPath); try { const npmrcContent = readFileSync(localNpmrcPath, "utf-8"); this.parseNpmrcContent(npmrcContent, scopeToRegistry, registryToToken, registryMap); } catch (error) { this.logger.error(`Error reading local .npmrc at ${localNpmrcPath}:`, error); } } } } try { // Associate tokens with registries for (const [scope, registry] of scopeToRegistry.entries()) { const hostname = new URL(registry).host; const token = registryToToken.get(hostname); this.logger.debug(`Setting config for scope ${scope}:`, { registry, token: token ? "[REDACTED]" : undefined }); registryMap.set(scope, { registry, token }); } // Ensure default registry has its token if available const defaultConfig = registryMap.get("default"); if (defaultConfig) { const hostname = new URL(defaultConfig.registry).host; const token = registryToToken.get(hostname); if (token) { this.logger.debug("Setting token for default registry"); registryMap.set("default", { ...defaultConfig, token }); } } this.logger.debug("Final registry configurations:", Object.fromEntries(Array.from(registryMap.entries()).map(([k, v]) => [ k, { registry: v.registry, token: v.token ? "[REDACTED]" : undefined } ])) ); } catch (error) { this.logger.error("Error processing .npmrc configurations:", error); } return registryMap; } /** * Parse .npmrc content */ private parseNpmrcContent( content: string, scopeToRegistry: Map<string, string>, registryToToken: Map<string, string>, registryMap: Map<string, NpmConfig> ): void { const lines = content.split("\n"); for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith("#")) continue; // Handle registry configurations // Match patterns like: // @scope:registry=https://registry.example.com // registry=https://registry.example.com const registryMatch = trimmedLine.match(/^(?:@([^:]+):)?registry=(.+)$/); if (registryMatch) { const [, scope, registry] = registryMatch; const cleanRegistry = registry.replace(/\/$/, ""); if (scope) { scopeToRegistry.set(`@${scope}`, cleanRegistry); } else { registryMap.set("default", { registry: cleanRegistry }); } continue; } // Handle authentication tokens // Match patterns like: // //registry.example.com/:_authToken=token // @scope:_authToken=token // _authToken=token const tokenMatch = trimmedLine.match(/^(?:\/\/([^/]+)\/:|@([^:]+):)?_authToken=(.+)$/); if (tokenMatch) { const [, registry, scope, token] = tokenMatch; if (registry) { // Store token for specific registry // Handle both protocol and non-protocol URLs registryToToken.set(registry, token); if (!registry.includes("://")) { registryToToken.set(`https://${registry}`, token); registryToToken.set(`http://${registry}`, token); } } else if (scope) { // Store token for scope, we'll resolve the registry later const scopeRegistry = scopeToRegistry.get(`@${scope}`); if (scopeRegistry) { try { // Try parsing as URL first const url = new URL(scopeRegistry); registryToToken.set(url.host, token); } catch { // If not a URL, treat as hostname registryToToken.set(scopeRegistry, token); registryToToken.set(`https://${scopeRegistry}`, token); registryToToken.set(`http://${scopeRegistry}`, token); } } } else { // Default token const defaultRegistry = registryMap.get("default")?.registry; if (defaultRegistry) { try { // Try parsing as URL first const url = new URL(defaultRegistry); registryToToken.set(url.host, token); } catch { // If not a URL, treat as hostname registryToToken.set(defaultRegistry, token); registryToToken.set(`https://${defaultRegistry}`, token); registryToToken.set(`http://${defaultRegistry}`, token); } } } } } } }

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/sammcj/mcp-package-docs'

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