Skip to main content
Glama
mcp-apps.ts6.71 kB
import { apps, appCategories, appTags, categories, tags } from "@repo/db/schema"; import { and, count, eq, gte, like, SQL, inArray } from "drizzle-orm"; import { db } from "../../index"; import { AppType } from "../../types"; export const mcpAppsDataAccess = { getByTypeCategoryAndTag: async (params: { type: AppType; category?: string; tag?: string }) => { const { type, category, tag } = params; // 先查出符合条件的 app id let appIdConditions: SQL[] = [eq(apps.type, type)]; if (category) { appIdConditions.push(eq(categories.slug, category)); } if (tag) { appIdConditions.push(eq(tags.slug, tag)); } // 只查 id const appIdRows = await db .selectDistinct({ id: apps.id }) .from(apps) .leftJoin(categories, eq(apps.categoryId, categories.id)) .leftJoin(appTags, eq(apps.id, appTags.appId)) .leftJoin(tags, eq(appTags.tagId, tags.id)) .where(and(...appIdConditions)); const appIds = appIdRows.map(row => row.id); if (appIds.length === 0) return []; // 再查详情 const appsData = await db .selectDistinct({ id: apps.id, slug: apps.slug, name: apps.name, description: apps.description, descriptionZh: apps.descriptionZh, type: apps.type, icon: apps.icon, website: apps.website, github: apps.github, stars: apps.stars, verified: apps.verified, banner: apps.banner, createdAt: apps.createdAt, primaryLanguage: apps.primaryLanguage, languages: apps.languages, source: apps.source, deployable: apps.deployable, publishStatus: apps.publishStatus, featured: apps.featured, watchers: apps.watchers, forks: apps.forks, license: apps.license, version: apps.version, }) .from(apps) .where(inArray(apps.id, appIds)); return appsData; }, /** * 根据appId、分类、标签、查询条件查询应用。这里主要是用于搜索应用 * @param params * @returns */ getAppsBySearch: async (params: { appId?: string; limit?: number; category?: string; tag?: string; query?: string }) => { const { appId, limit, category, tag, query } = params; let conditions: SQL[] = []; if (appId) { conditions.push(eq(apps.id, appId)); } if (category) { conditions.push(eq(categories.slug, category)); } if (tag) { conditions.push(eq(tags.slug, tag)); } if (query) { conditions.push(like(apps.name, `%${query}%`)); } // 首先查询应用基本信息 const appsData = await db .selectDistinct({ id: apps.id, slug: apps.slug, name: apps.name, description: apps.description, type: apps.type, icon: apps.icon, website: apps.website, github: apps.github, stars: apps.stars, verified: apps.verified, }) .from(apps) .leftJoin(appCategories, eq(apps.id, appCategories.appId)) .leftJoin(categories, eq(appCategories.categoryId, categories.id)) .leftJoin(appTags, eq(apps.id, appTags.appId)) .leftJoin(tags, eq(appTags.tagId, tags.id)) .where(and(...conditions)); // 如果没有找到应用,直接返回空数组 if (!appsData || appsData.length === 0) { return []; } // 获取所有应用的ID const appIds = appsData.map(app => app.id); // 查询所有应用的标签 const appTagsData = await db .select({ appId: appTags.appId, tag: tags, }) .from(appTags) .leftJoin(tags, eq(appTags.tagId, tags.id)) .where(inArray(appTags.appId, appIds)); // 将标签数据按应用ID分组 const tagsByAppId = appTagsData.reduce((acc, { appId, tag }) => { if (!acc[appId]) { acc[appId] = []; } if (tag) { acc[appId].push(tag); } return acc; }, {} as Record<string, typeof tags.$inferSelect[]>); // 合并应用信息和标签信息 return appsData.map(app => ({ ...app, tags: tagsByAppId[app.id] || [], })); }, /** * 根据slug查询应用 * @param slug 应用slug * @returns 应用信息 */ getBySlug: async (slug: string) => { // 查询应用基本信息 const appData = await db.select().from(apps).where(eq(apps.slug, slug)).limit(1); if (!appData || appData.length === 0) { return null; } // 由于我们已经检查了 appData 不为空且长度大于0,所以 app 一定存在 const app = appData[0]!; // 查询应用分类 const appCategoriesData = await db .select({ category: categories, }) .from(appCategories) .leftJoin(categories, eq(appCategories.categoryId, categories.id)) .where(eq(appCategories.appId, app.id)); // 查询应用标签 const appTagsData = await db .select({ tag: tags, }) .from(appTags) .leftJoin(tags, eq(appTags.tagId, tags.id)) .where(eq(appTags.appId, app.id)); // 整理返回数据 return { ...app, categories: appCategoriesData.map((item) => item.category), tags: appTagsData.map((item) => item.tag), }; }, getAppsByTagId: async (tagId: string, limit?: number) => { const query = db .select({ id: apps.id, slug: apps.slug, name: apps.name, description: apps.description, descriptionZh: apps.descriptionZh, type: apps.type, icon: apps.icon, website: apps.website, github: apps.github, stars: apps.stars, verified: apps.verified, }) .from(apps) .innerJoin(appTags, eq(apps.id, appTags.appId)) .where(eq(appTags.tagId, tagId)); return limit ? query.limit(limit) : query; }, getCount: async () => { try { const totalCount = await db.select({ count: count() }).from(apps).where(and(eq(apps.status, "approved"), eq(apps.publishStatus, "online"))); return totalCount[0]?.count ?? 0; } catch (error) { console.error("getCount error", error); return 0; } }, getNewCount: async () => { try { const newCount = await db.select({ count: count() }).from(apps).where(and(eq(apps.status, "approved"), eq(apps.publishStatus, "online"), gte(apps.createdAt, subDays(new Date(), 7)))); return newCount[0]?.count ?? 0; } catch (error) { console.error("getNewCount error", error); return 0; } }, }; function subDays(arg0: Date, arg1: number) { return new Date(arg0.getTime() - arg1 * 24 * 60 * 60 * 1000); }

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/metacode0602/open-mcp'

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