Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
provider-registry.ts6.78 kB
/** * Provider Registry * Fetches provider info from mcps.portel.dev with local fallback */ import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const REGISTRY_API = 'https://api.mcps.portel.dev/api/providers'; export interface ProviderStdioSetup { description: string; command: string; needsSetup: boolean; } export interface ProviderStdioConfig { setup?: ProviderStdioSetup; command: string; args: string[]; } export interface ProviderHttpConfig { url: string; auth: 'bearer' | 'oauth' | 'basic'; docs: string; notes?: string; } export interface Provider { id: string; name: string; description: string; website: string; recommended: 'stdio' | 'http'; stdio?: ProviderStdioConfig; http?: ProviderHttpConfig; _meta?: any; // Preserve MCP metadata (for Photon detection, etc.) } export interface ProviderRegistry { [key: string]: Provider; } let cachedRegistry: ProviderRegistry | null = null; /** * Load local provider registry from JSON file (fallback) */ function loadLocalRegistry(): ProviderRegistry { try { const registryPath = join(__dirname, 'providers.json'); const registryData = readFileSync(registryPath, 'utf-8'); return JSON.parse(registryData); } catch (error: any) { console.error('Failed to load local provider registry:', error.message); return {}; } } /** * Transform MCPPackage to Provider format */ function transformMCPToProvider(mcp: any): Provider | null { try { const meta = mcp._meta?.['dev.portel/setup']; const provider: Provider = { id: mcp.id || mcp.name, name: mcp.displayName || mcp.name, description: mcp.description, website: mcp.homepage || mcp.repository?.url || '', recommended: meta?.recommendedTransport || mcp.transport?.type || 'stdio' }; // Check if this is an HTTP/SSE transport const hasHttpTransport = mcp.transport?.type === 'http' || mcp.transport?.type === 'sse' || mcp.transport?.endpoint; const hasHttpCommand = mcp.installCommand?.includes('HTTP endpoint') || mcp.installCommand?.startsWith('http'); const isHttpBased = hasHttpTransport || hasHttpCommand || meta?.recommendedTransport === 'http' || meta?.recommendedTransport === 'sse'; // Add HTTP config if available if (isHttpBased) { provider.http = { url: mcp.transport?.endpoint || (hasHttpCommand ? mcp.installCommand.replace(/^Use HTTP endpoint:\s*/, '') : ''), auth: meta?.authType || 'bearer', docs: mcp.homepage || '', notes: meta?.notes }; // Update recommended transport if HTTP command detected if (hasHttpCommand && provider.recommended === 'stdio') { provider.recommended = 'http'; } } // Add stdio config if available (but NOT if it's clearly HTTP-based) if (!isHttpBased && (mcp.transport?.type === 'stdio' || mcp.installCommand)) { provider.stdio = { command: mcp.runtimeHint || 'npx', args: mcp.installCommand?.split(' ').slice(1) || [] }; // Add setup info from augmentation if (meta?.setupCommand) { provider.stdio.setup = { description: meta.setupDescription || 'Setup required', command: meta.setupCommand, needsSetup: true }; } } // Preserve _meta for Photon detection and other features if (mcp._meta) { provider._meta = mcp._meta; } return provider; } catch (error) { console.error('Failed to transform MCP:', error); return null; } } /** * Fetch provider registry from mcps.portel.dev */ async function fetchRemoteRegistry(): Promise<ProviderRegistry | null> { try { const response = await fetch(REGISTRY_API, { headers: { 'User-Agent': 'ncp-cli' }, signal: AbortSignal.timeout(5000) // 5 second timeout }); if (!response.ok) { return null; } const result = await response.json(); // Handle API response format: { success: true, data: { id: MCPPackage } } if (result.success && result.data) { const registry: ProviderRegistry = {}; for (const [id, mcp] of Object.entries(result.data)) { const provider = transformMCPToProvider(mcp); if (provider) { registry[id] = provider; } } return registry; } // Fallback: try old format return result.providers || result; } catch (error: any) { // Network error or timeout - will use local fallback return null; } } /** * Load provider registry (remote first, local fallback) */ export async function loadProviderRegistry(): Promise<ProviderRegistry> { // Return cached if available if (cachedRegistry) { return cachedRegistry; } // Try remote first const remoteRegistry = await fetchRemoteRegistry(); if (remoteRegistry) { cachedRegistry = remoteRegistry; return remoteRegistry; } // Fallback to local const localRegistry = loadLocalRegistry(); cachedRegistry = localRegistry; return localRegistry; } /** * Get provider by ID (from remote or local cache) */ export async function getProvider(providerId: string): Promise<Provider | null> { const registry = await loadProviderRegistry(); return registry[providerId.toLowerCase()] || null; } /** * Fetch single provider directly from API (faster for single lookups) */ export async function fetchProvider(providerId: string): Promise<Provider | null> { try { const response = await fetch(`${REGISTRY_API}/${providerId}`, { headers: { 'User-Agent': 'ncp-cli' }, signal: AbortSignal.timeout(5000) }); if (!response.ok) { return null; } const result = await response.json(); // Handle API response format: { success: true, data: MCPPackage } if (result.success && result.data) { return transformMCPToProvider(result.data); } // Fallback: old format return result; } catch { // Fallback to registry lookup return await getProvider(providerId); } } /** * List all available providers */ export async function listProviders(): Promise<Provider[]> { const registry = await loadProviderRegistry(); return Object.values(registry); } /** * Search providers by name or description */ export async function searchProviders(query: string): Promise<Provider[]> { const registry = await loadProviderRegistry(); const lowerQuery = query.toLowerCase(); return Object.values(registry).filter(provider => provider.name.toLowerCase().includes(lowerQuery) || provider.description.toLowerCase().includes(lowerQuery) || provider.id.toLowerCase().includes(lowerQuery) ); }

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/portel-dev/ncp'

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