Skip to main content
Glama
utils.ts6.6 kB
/** * Common utilities for URL generation across different documentation sources */ export interface FrontmatterData { id?: string; slug?: string; title?: string; sidebar_label?: string; [key: string]: any; } /** * Extract frontmatter from document content * Supports YAML frontmatter format used in Markdown/MDX files */ export function parseFrontmatter(content: string): FrontmatterData { const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); if (!frontmatterMatch) { return {}; } const frontmatter = frontmatterMatch[1]; const result: FrontmatterData = {}; // Parse simple key-value pairs const lines = frontmatter.split('\n'); let currentKey = ''; let isInArray = false; for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith('#')) { continue; // Skip empty lines and comments } // Handle array items (lines starting with -) if (trimmedLine.startsWith('-')) { if (isInArray && currentKey) { const arrayValue = trimmedLine.substring(1).trim(); if (!Array.isArray(result[currentKey])) { result[currentKey] = []; } (result[currentKey] as string[]).push(arrayValue); } continue; } // Handle key-value pairs const colonIndex = trimmedLine.indexOf(':'); if (colonIndex !== -1) { currentKey = trimmedLine.substring(0, colonIndex).trim(); const value = trimmedLine.substring(colonIndex + 1).trim(); if (value === '') { // This might be the start of an array isInArray = true; result[currentKey] = []; } else { isInArray = false; // Clean up quoted values result[currentKey] = value.replace(/^["']|["']$/g, ''); } } } return result; } /** * Detect the main section/topic from content for anchor generation */ export function detectContentSection(content: string, anchorStyle: 'docsify' | 'github' | 'custom'): string | null { // Find the first major heading (## or #) that gives context about the content const headingMatch = content.match(/^#{1,2}\s+(.+)$/m); if (!headingMatch) { return null; } const heading = headingMatch[1].trim(); // Convert heading to anchor format based on style switch (anchorStyle) { case 'docsify': // Docsify format: lowercase, spaces to hyphens, remove special chars return heading .toLowerCase() .replace(/[^\w\s-]/g, '') // Remove special characters except hyphens .replace(/\s+/g, '-') // Spaces to hyphens .replace(/-+/g, '-') // Multiple hyphens to single .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens case 'github': // GitHub format: lowercase, spaces to hyphens, keep some special chars return heading .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-'); case 'custom': default: // Return as-is for custom handling return heading; } } /** * Determine the section path from file relative path */ export function extractSectionFromPath(relFile: string): string { if (relFile.includes('guides/')) { return '/guides/'; } else if (relFile.includes('features/')) { return '/features/'; } else if (relFile.includes('tutorials/')) { return '/tutorials/'; } else if (relFile.includes('environments/')) { return '/environments/'; } else if (relFile.includes('getting-started/')) { return '/getting-started/'; } else if (relFile.includes('examples/')) { return '/examples/'; } else if (relFile.includes('api/')) { return '/api/'; } return ''; } /** * Clean filename for URL usage */ export function cleanFilename(filename: string): string { return filename .replace(/\.mdx?$/, '') // Remove .md/.mdx extensions .replace(/\.html?$/, '') // Remove .html/.htm extensions .replace(/\s+/g, '-') // Spaces to hyphens .toLowerCase(); } /** * Build URL with proper path joining */ export function buildUrl(baseUrl: string, ...pathSegments: string[]): string { const cleanBase = baseUrl.replace(/\/$/, ''); // Remove trailing slash const cleanSegments = pathSegments .filter(segment => segment && segment.trim() !== '') // Remove empty segments .map(segment => segment.replace(/^\/|\/$/g, '')); // Remove leading/trailing slashes if (cleanSegments.length === 0) { return cleanBase; } return `${cleanBase}/${cleanSegments.join('/')}`; } /** * Extract library ID from document ID path * Used for search result URL generation */ export function extractLibraryIdFromPath(docId: string): string { if (docId.startsWith('/')) { const parts = docId.split('/'); return parts.length > 1 ? `/${parts[1]}` : docId; } return docId; } /** * Extract relative file path from document ID * Used for search result URL generation */ export function extractRelativeFileFromPath(docId: string): string { if (docId.includes('/')) { const parts = docId.split('/'); return parts.length > 2 ? parts.slice(2).join('/') : ''; } return ''; } /** * Format a single search result with URL generation and excerpt truncation * Shared utility for consistent search result formatting across servers */ export function formatSearchResult( result: any, excerptLength: number, urlGenerator?: { generateDocumentationUrl: (libraryId: string, relFile: string, content: string, config: any) => string | null; getDocUrlConfig: (libraryId: string) => any; } ): string { // Extract library ID and relative file path to generate URL const libraryId = result.sourceId ? `/${result.sourceId}` : extractLibraryIdFromPath(result.id); const relFile = extractRelativeFileFromPath(result.id); // Try to generate documentation URL let urlInfo = ''; if (urlGenerator) { try { const config = urlGenerator.getDocUrlConfig && urlGenerator.getDocUrlConfig(libraryId); if (config && urlGenerator.generateDocumentationUrl) { const docUrl = urlGenerator.generateDocumentationUrl(libraryId, relFile, result.text || '', config); if (docUrl) { urlInfo = `\n 🔗 ${docUrl}`; } } } catch (error) { // Silently fail URL generation console.warn(`URL generation failed for ${libraryId}/${relFile}:`, error); } } return `⭐️ **${result.id}** (Score: ${result.finalScore.toFixed(2)})\n ${(result.text || '').substring(0, excerptLength)}${urlInfo}\n Use in fetch\n`; }

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/marianfoo/mcp-sap-docs'

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