Skip to main content
Glama

Convex MCP server

Official
by get-convex
external.ts7.84 kB
import { PluginBuild } from "esbuild"; import type { Plugin } from "esbuild"; import { Context } from "./context.js"; import path from "path"; import { findUp } from "find-up"; import { findParentConfigs } from "../cli/lib/utils/utils.js"; /** * Mimics Node.js node_modules resolution. Ideally we would be able to * reuse the logic in esbuild but calling build.resolve() from onResolve() * results in infinite recursion. See https://esbuild.github.io/plugins/#resolve */ async function resolveNodeModule( ctx: Context, moduleDir: string, resolveDir: string, ): Promise<string | null> { let nodeModulesPath: string | undefined; while ( (nodeModulesPath = await findUp("node_modules", { type: "directory", cwd: resolveDir, })) ) { const maybePath = path.join(nodeModulesPath, moduleDir); if (ctx.fs.exists(maybePath)) { return maybePath; } resolveDir = path.dirname(path.dirname(nodeModulesPath)); } return null; } function getModule(importPath: string): { name: string; dirName: string } { // In case of scoped package if (importPath.startsWith("@")) { const split = importPath.split("/"); return { name: `${split[0]}/${split[1]}`, dirName: path.join(split[0], split[1]), }; } else { const moduleName = importPath.split("/")[0]; return { name: moduleName, dirName: moduleName, }; } } export type ExternalPackage = { path: string; }; // Inspired by https://www.npmjs.com/package/esbuild-node-externals. export function createExternalPlugin( ctx: Context, externalPackages: Map<string, ExternalPackage>, ): { plugin: Plugin; externalModuleNames: Set<string>; bundledModuleNames: Set<string>; } { const externalModuleNames = new Set<string>(); const bundledModuleNames = new Set<string>(); return { plugin: { name: "convex-node-externals", setup(build: PluginBuild) { // On every module resolved, we check if the module name should be an external build.onResolve({ namespace: "file", filter: /.*/ }, async (args) => { if (args.path.startsWith(".")) { // Relative import. return null; } const module = getModule(args.path); const externalPackage = externalPackages.get(module.name); if (externalPackage) { const resolved = await resolveNodeModule( ctx, module.dirName, args.resolveDir, ); if (resolved && externalPackage.path === resolved) { // Mark as external. externalModuleNames.add(module.name); return { path: args.path, external: true }; } } bundledModuleNames.add(module.name); return null; }); }, }, externalModuleNames: externalModuleNames, bundledModuleNames: bundledModuleNames, }; } // Returns the versions of the packages referenced by the package.json. export async function computeExternalPackages( ctx: Context, externalPackagesAllowList: string[], ): Promise<Map<string, ExternalPackage>> { if (externalPackagesAllowList.length === 0) { // No external packages in the allow list. return new Map<string, ExternalPackage>(); } const { parentPackageJson: packageJsonPath } = await findParentConfigs(ctx); const externalPackages = new Map<string, ExternalPackage>(); let packageJson: any; try { const packageJsonString = ctx.fs.readUtf8File(packageJsonPath); packageJson = JSON.parse(packageJsonString); } catch (error: any) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Couldn't parse "${packageJsonPath}". Make sure it's a valid JSON. Error: ${error}`, }); } for (const key of [ "dependencies", "devDependencies", "peerDependencies", "optionalDependencies", ]) { for (const [packageName, packageJsonVersion] of Object.entries( packageJson[key] ?? {}, )) { if (externalPackages.has(packageName)) { // Package version and path already found. continue; } if (typeof packageJsonVersion !== "string") { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Invalid "${packageJsonPath}". "${key}.${packageName}" version has type ${typeof packageJsonVersion}.`, }); } if ( !shouldMarkExternal( packageName, packageJsonVersion, externalPackagesAllowList, ) ) { // Package should be bundled. continue; } // Check if the package path is referenced. const packagePath = path.join( path.dirname(packageJsonPath), "node_modules", getModule(packageName).dirName, ); if (ctx.fs.exists(packagePath)) { externalPackages.set(packageName, { path: packagePath, }); } } } return externalPackages; } export function shouldMarkExternal( packageName: string, packageJsonVersion: string, externalPackagesAllowList: string[], ): boolean { // Always bundle convex. if (packageName === "convex") { return false; } if ( packageJsonVersion.startsWith("file:") || packageJsonVersion.startsWith("git+file://") ) { // Bundle instead of marking as external. return false; } if ( packageJsonVersion.startsWith("http://") || packageJsonVersion.startsWith("https://") || packageJsonVersion.startsWith("git://") || packageJsonVersion.startsWith("git+ssh://") || packageJsonVersion.startsWith("git+http://") || packageJsonVersion.startsWith("git+https://") ) { // Installing those might or might not work. There are some corner cases // like http://127.0.0.1/. Lets bundle for time being. return false; } return ( externalPackagesAllowList.includes(packageName) || externalPackagesAllowList.includes("*") ); } export async function findExactVersionAndDependencies( ctx: Context, moduleName: string, modulePath: string, ): Promise<{ version: string; peerAndOptionalDependencies: Set<string>; }> { const modulePackageJsonPath = path.join(modulePath, "package.json"); let modulePackageJson: any; try { const packageJsonString = ctx.fs.readUtf8File(modulePackageJsonPath); modulePackageJson = JSON.parse(packageJsonString); } catch { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Missing "${modulePackageJsonPath}", which is required for installing external package "${moduleName}" configured in convex.json.`, }); } if (modulePackageJson["version"] === undefined) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `"${modulePackageJsonPath}" misses a 'version' field. which is required for installing external package "${moduleName}" configured in convex.json.`, }); } const peerAndOptionalDependencies = new Set<string>(); for (const key of ["peerDependencies", "optionalDependencies"]) { for (const [packageName, packageJsonVersion] of Object.entries( modulePackageJson[key] ?? {}, )) { if (typeof packageJsonVersion !== "string") { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Invalid "${modulePackageJsonPath}". "${key}.${packageName}" version has type ${typeof packageJsonVersion}.`, }); } peerAndOptionalDependencies.add(packageName); } } return { version: modulePackageJson["version"], peerAndOptionalDependencies: peerAndOptionalDependencies, }; }

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