Skip to main content
Glama

MCP Doc Server

mdx.ts6.61 kB
import { readdir, readFile } from 'fs/promises' import { join, relative } from 'path' import { parseStringPromise } from 'xml2js' export const AGORA_NEW_DOCS_SITE = 'https://doc.shengwang.cn/' interface MDXFile { path: string content: string } export const DOC_ROUTE_BASE_PATH_MAPPING = { shared: { routeBasePath: '/global-shared', }, default: { routeBasePath: '/doc', }, ['api-reference']: { routeBasePath: '/api-ref', }, faq: { routeBasePath: '/faq', }, basics: { routeBasePath: '/basics', }, } export async function getAllMDXFiles(dir: string): Promise<MDXFile[]> { const files: MDXFile[] = [] async function scan(directory: string) { const entries = await readdir(directory, { withFileTypes: true }) for (const entry of entries) { const fullPath = join(directory, entry.name) if (entry.isDirectory()) { await scan(fullPath) } else if (entry.name.endsWith('.mdx')) { const content = await readFile(fullPath, 'utf-8') const relativePath = relative(dir, fullPath) files.push({ path: relativePath, content, }) } } } await scan(dir) return files } export async function checkFileExists( filePath: string ): Promise<string | false> { try { const content = await readFile(filePath, 'utf-8') return content } catch (error: unknown) { if ((error as any)?.code === 'ENOENT') { return false } throw error } } export async function getMDXFileContent(filePath: string): Promise<string> { try { const isMdFileExist = await checkFileExists(filePath + '.md') const isMdxFileExist = await checkFileExists(filePath + '.mdx') const mdFileContent = isMdFileExist || isMdxFileExist if (!mdFileContent) { throw new Error(`File ${filePath} not found`) } return mdFileContent } catch (error) { console.error(`Error reading file ${filePath}:`, error) throw error } } export type TSiteMap = { urlset: { url: { loc: string[] [x: string]: string[] }[] } } export const url2LocalPath = (url: string): string | undefined => { const urlPath = url.replace(AGORA_NEW_DOCS_SITE, '') const routeBasePath = urlPath.split('/').shift() if (!routeBasePath) return undefined const localFolder = Object.keys(DOC_ROUTE_BASE_PATH_MAPPING).find((i) => { const targetItem = DOC_ROUTE_BASE_PATH_MAPPING[i as keyof typeof DOC_ROUTE_BASE_PATH_MAPPING] return targetItem.routeBasePath.replace('/', '') === routeBasePath }) if (!localFolder) return undefined const localPath = urlPath.replace(routeBasePath, localFolder) return localPath } export async function getSiteMap(filePath: string): Promise< { loc: string folder: string category?: string }[] > { const siteMapFile = await readFile(filePath, 'utf-8') try { const result: TSiteMap = await parseStringPromise(siteMapFile) // console.log('Parsed sitemap XML:', result) let urls: { loc: string folder: string category?: string }[] = [] const allowedPrefixList = Object.values(DOC_ROUTE_BASE_PATH_MAPPING).map( (item) => item.routeBasePath.replace('/', '') ) if (result.urlset && Array.isArray(result.urlset.url)) { urls = result.urlset.url.reduce((prev, curr) => { const targetLoc = curr?.loc?.[0] const parsedLoc = targetLoc?.replace(AGORA_NEW_DOCS_SITE, '') const allowedPrefix = allowedPrefixList.find((prefix) => parsedLoc?.startsWith(prefix) ) const category = Object.entries(DOC_ROUTE_BASE_PATH_MAPPING).find( ([_, value]) => value.routeBasePath.replace('/', '') === allowedPrefix )?.[0] || allowedPrefix if (allowedPrefix && parsedLoc) { prev.push({ loc: parsedLoc, folder: allowedPrefix, category, }) } return prev }, [] as typeof urls) } return urls } catch (error) { console.error('Failed to parse sitemap XML:', error) return [] } } export async function getDocContentByUri(uri: string): Promise<string> { try { // 解析URI if (!uri.startsWith('docs://')) { throw new Error('Invalid URI format, must start with docs://') } // const encodedPath = uri.replace('docs://', '') // const decodedPath = decodeURIComponent(encodedPath) // const localPath = url2LocalPath(decodedPath) const encodedPath = uri.replace('docs://', '') const localPath = encodedPath if (!localPath) { throw new Error(`Missing local path: ${uri}`) } // 使用DOCS_MD_DIR获取完整路径 const DOCS_MD_DIR = process.env.DOCS_MD_DIR || join(process.cwd(), '.docs') // retrieve the file content const mdxContent = await getMDXFileContent(join(DOCS_MD_DIR, localPath)) return mdxContent } catch (error) { console.error(`Error: ${error}`) throw error } } const getMdxContentAndFrontmatter = (content: string) => { const frontmatterRegex = /---([\s\S]*?)---([\s\S]*)/ const match = content.match(frontmatterRegex) if (match) { const frontmatter = match[1] .split('\n') .map((line) => line.trim()) .filter((line) => line && !line.startsWith('#')) .join('\n') const restContent = match[2].trim() return { frontmatter, content: restContent, } } return { frontmatter: '', content: content.trim(), } } const getMdxFrontmatterVars = (frontmatter: string) => { if (!frontmatter.trim()) return {} return frontmatter.split('\n').reduce((acc, line) => { const [key, value] = line.split(':') acc[key.trim()] = value.trim() return acc }, {} as Record<string, string>) } export const handleMdxContent = (content: string) => { const { frontmatter, content: restContent } = getMdxContentAndFrontmatter(content) const frontmatterVars = getMdxFrontmatterVars(frontmatter) let handledRestContent = restContent for (const key in frontmatterVars) { const keyName = key.trim() const keyValue = frontmatterVars[keyName] const regex1 = new RegExp(`\\$\\{frontMatter\\.${keyName}\\}`, 'g') handledRestContent = handledRestContent.replace(regex1, keyValue) const regex2 = new RegExp(`frontMatter\\.${keyName}`, 'g') handledRestContent = handledRestContent.replace(regex2, keyValue) } return { metadata: { frontmatter, originContent: restContent, frontmatterVars: frontmatterVars, }, content: handledRestContent, } }

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/Shengwang-Community/doc-mcp-server'

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