Skip to main content
Glama

Convex MCP server

Official
by get-convex
modules.ts3.77 kB
import { CronSpec, Module, ResolvedSourcePos, UdfType, Visibility, } from "./common"; import { queryPrivateSystem } from "../secretSystemTables"; import { v } from "convex/values"; import { DEFAULT_ARGS_VALIDATOR } from "../cli/modules"; import { currentSystemUdfInComponent } from "convex/server"; import { DatabaseReader } from "../../_generated/server"; export const listForAllComponents = queryPrivateSystem({ args: {}, handler: async (ctx): Promise<[string | null, [string, Module][]][]> => { // NOTE this UDF calls itself recursively in each component with // `currentSystemUdfInComponent` below. const modulesInCurrentComponent = await listHandler(ctx.db); const result: [string | null, [string, Module][]][] = [ [null, modulesInCurrentComponent], ]; // When this UDF is running in a non-root component, the _components table // is empty, so that's the base case of the recursion. const componentDocs = await ctx.db.query("_components").collect(); for (const doc of componentDocs) { if (!doc.parent) { // Root component, which is the current component. continue; } const ref = currentSystemUdfInComponent(doc._id); for (const [_, modulesInChildComponent] of await ctx.runQuery( ref as any, )) { result.push([doc._id, modulesInChildComponent]); } } return result; }, }); /** * Return all user defined modules + their functions. * * Note that this does not include system modules because they are not stored * in the `_modules` table. */ export const list = queryPrivateSystem({ args: { componentId: v.optional(v.union(v.string(), v.null())), }, handler: async ({ db }): Promise<[string, Module][]> => { return await listHandler(db); }, }); async function listHandler(db: DatabaseReader): Promise<[string, Module][]> { const result: [string, Module][] = []; for await (const module of db.query("_modules")) { const analyzeResult = module.analyzeResult; if (!analyzeResult) { // `Skipping ${module.path}` continue; } const functions = analyzeResult.functions.map(processFunction) ?? []; // Stuff HTTP routes into the functions (the format the dashboard expects). for (const route of analyzeResult.httpRoutes || []) { functions.push(processHttpRoute(route)); } const cronSpecs = processCronSpecs(analyzeResult.cronSpecs); result.push([ module.path, { functions, sourcePackageId: module.sourcePackageId, ...(cronSpecs !== null ? { cronSpecs } : {}), }, ]); } return result; } function processCronSpecs( cronSpecs: null | undefined | Array<{ identifier: string; spec: CronSpec }>, ): Array<[string, CronSpec]> | null { if (cronSpecs === null || cronSpecs === undefined) { return null; } return cronSpecs.map((c) => [c.identifier, c.spec]); } function processHttpRoute(f: { route: { path: string; method: string; }; pos?: ResolvedSourcePos; lineno?: bigint; args?: string; }) { const lineno = f.pos?.start_lineno ?? f.lineno; return { name: `${f.route.method} ${f.route.path}`, lineno: lineno ? Number(lineno) : undefined, udfType: "HttpAction", visibility: { kind: "public" }, argsValidator: f.args || DEFAULT_ARGS_VALIDATOR, } as const; } function processFunction(f: { name: string; pos?: ResolvedSourcePos; lineno?: bigint; udfType: UdfType; visibility?: Visibility | null; args?: string; }) { const lineno = f.pos?.start_lineno ?? f.lineno; return { ...f, lineno: lineno ? Number(lineno) : undefined, visibility: f.visibility ?? { kind: "public" }, argsValidator: f.args || DEFAULT_ARGS_VALIDATOR, }; }

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/get-convex/convex-backend'

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