Skip to main content
Glama

Convex MCP server

Official
by get-convex
deployment.ts8.05 kB
import * as dotenv from "dotenv"; import { Context } from "../../bundler/context.js"; import { changedEnvVarFile, getEnvVarRegex } from "./envvars.js"; import { CONVEX_DEPLOY_KEY_ENV_VAR_NAME, CONVEX_DEPLOYMENT_ENV_VAR_NAME, ENV_VAR_FILE_PATH, } from "./utils/utils.js"; import { DeploymentType } from "./api.js"; // Given a deployment string like "dev:tall-forest-1234" // returns only the slug "tall-forest-1234". // If there's no prefix returns the original string. export function stripDeploymentTypePrefix(deployment: string) { return deployment.split(":").at(-1)!; } // Handling legacy CONVEX_DEPLOYMENT without type prefix as well export function getDeploymentTypeFromConfiguredDeployment(raw: string) { const typeRaw = raw.split(":")[0]; const type = typeRaw === "prod" || typeRaw === "dev" || typeRaw === "preview" || typeRaw === "local" ? typeRaw : null; return type; } export function isAnonymousDeployment(deploymentName: string) { return deploymentName.startsWith("anonymous-"); } export function removeAnonymousPrefix(deploymentName: string) { if (isAnonymousDeployment(deploymentName)) { return deploymentName.slice("anonymous-".length); } return deploymentName; } export async function writeDeploymentEnvVar( ctx: Context, deploymentType: DeploymentType, deployment: { team: string | null; project: string | null; deploymentName: string; }, existingValue: string | null, ): Promise<{ wroteToGitIgnore: boolean; changedDeploymentEnvVar: boolean }> { const existingFile = ctx.fs.exists(ENV_VAR_FILE_PATH) ? ctx.fs.readUtf8File(ENV_VAR_FILE_PATH) : null; const changedFile = changesToEnvVarFile( existingFile, deploymentType, deployment, ); const deploymentEnvVarValue = deploymentType + ":" + deployment.deploymentName; // The `existingValue` that reaches this function is at least sometimes is missing its prefix. // Until this is cleaned up consider either of these values not a change. // Otherwise we spam the init instructions (Welcome to Convex etc.) on every run of `npx convex dev`. const changedDeploymentEnvVar = existingValue !== deployment.deploymentName && existingValue !== deploymentEnvVarValue; if (changedFile !== null) { ctx.fs.writeUtf8File(ENV_VAR_FILE_PATH, changedFile); // Only do this if we're not reinitializing an existing setup return { wroteToGitIgnore: await gitIgnoreEnvVarFile(ctx), changedDeploymentEnvVar, }; } return { wroteToGitIgnore: false, changedDeploymentEnvVar, }; } // Only used in the internal --url flow export async function eraseDeploymentEnvVar(ctx: Context): Promise<boolean> { const existingFile = ctx.fs.exists(ENV_VAR_FILE_PATH) ? ctx.fs.readUtf8File(ENV_VAR_FILE_PATH) : null; if (existingFile === null) { return false; } const config = dotenv.parse(existingFile); const existing = config[CONVEX_DEPLOYMENT_ENV_VAR_NAME]; if (existing === undefined) { return false; } const changedFile = existingFile.replace( getEnvVarRegex(CONVEX_DEPLOYMENT_ENV_VAR_NAME), "", ); ctx.fs.writeUtf8File(ENV_VAR_FILE_PATH, changedFile); return true; } async function gitIgnoreEnvVarFile(ctx: Context): Promise<boolean> { const gitIgnorePath = ".gitignore"; const gitIgnoreContents = ctx.fs.exists(gitIgnorePath) ? ctx.fs.readUtf8File(gitIgnorePath) : ""; const changedGitIgnore = changesToGitIgnore(gitIgnoreContents); if (changedGitIgnore !== null) { ctx.fs.writeUtf8File(gitIgnorePath, changedGitIgnore); return true; } return false; } // exported for tests export function changesToEnvVarFile( existingFile: string | null, deploymentType: DeploymentType, { team, project, deploymentName, }: { team: string | null; project: string | null; deploymentName: string }, ): string | null { const deploymentValue = deploymentType + ":" + deploymentName; const commentOnPreviousLine = "# Deployment used by `npx convex dev`"; const commentAfterValue = team !== null && project !== null ? `team: ${team}, project: ${project}` : null; return changedEnvVarFile({ existingFileContent: existingFile, envVarName: CONVEX_DEPLOYMENT_ENV_VAR_NAME, envVarValue: deploymentValue, commentAfterValue, commentOnPreviousLine, }); } // exported for tests export function changesToGitIgnore(existingFile: string | null): string | null { if (existingFile === null) { return `${ENV_VAR_FILE_PATH}\n`; } const gitIgnoreLines = existingFile.split("\n"); const envVarFileIgnored = gitIgnoreLines.some((line) => { if (line.startsWith("#")) return false; if (line.startsWith("!")) return false; // .gitignore ignores trailing whitespace, and also we need to remove // the trailing `\r` from Windows-style newline since we split on `\n`. const trimmedLine = line.trimEnd(); const envIgnorePatterns = [ /^\.env\.local$/, /^\.env\.\*$/, /^\.env\*$/, /^.*\.local$/, /^\.env\*\.local$/, ]; return envIgnorePatterns.some((pattern) => pattern.test(trimmedLine)); }); if (!envVarFileIgnored) { return `${existingFile}\n${ENV_VAR_FILE_PATH}\n`; } else { return null; } } export async function deploymentNameFromAdminKeyOrCrash( ctx: Context, adminKey: string, ) { const deploymentName = deploymentNameFromAdminKey(adminKey); if (deploymentName === null) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Please set ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME} to a new key which you can find on your Convex dashboard.`, }); } return deploymentName; } function deploymentNameFromAdminKey(adminKey: string) { const parts = adminKey.split("|"); if (parts.length === 1) { return null; } if (isPreviewDeployKey(adminKey)) { // Preview deploy keys do not contain a deployment name. return null; } return stripDeploymentTypePrefix(parts[0]); } // Needed to differentiate a preview deploy key // from a concrete preview deployment's deploy key. // preview deploy key: `preview:team:project|key` // preview deployment's deploy key: `preview:deploymentName|key` export function isPreviewDeployKey(adminKey: string) { const parts = adminKey.split("|"); if (parts.length === 1) { return false; } const [prefix] = parts; const prefixParts = prefix.split(":"); return prefixParts[0] === "preview" && prefixParts.length === 3; } export function isProjectKey(adminKey: string) { return /^project:.*\|/.test(adminKey); } // "dev deploy keys" and "prod deploy keys" are deployment keys. // On the client these are also known as admin keys. export function isDeploymentKey(adminKey: string) { return /^(dev|prod):.*\|/.test(adminKey); } // For current keys returns prod|dev|preview, // for legacy keys returns "prod". // Examples: // "prod:deploymentName|key" -> "prod" // "preview:deploymentName|key" -> "preview" // "dev:deploymentName|key" -> "dev" // "key" -> "prod" export function deploymentTypeFromAdminKey(adminKey: string) { const parts = adminKey.split(":"); if (parts.length === 1) { return "prod"; } return parts.at(0)! as DeploymentType; } export async function getTeamAndProjectFromPreviewAdminKey( ctx: Context, adminKey: string, ) { const parts = adminKey.split("|")[0].split(":"); if (parts.length !== 3) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: "Malformed preview CONVEX_DEPLOY_KEY, get a new key from Project Settings.", }); } const [_preview, teamSlug, projectSlug] = parts; return { teamSlug, projectSlug }; } export type OnDeploymentActivityFunc = ( isOffline: boolean, wasOffline: boolean, ) => Promise<void>; export type CleanupDeploymentFunc = () => Promise<void>; export type DeploymentDetails = { deploymentName: string; deploymentUrl: string; adminKey: string; onActivity: OnDeploymentActivityFunc | null; };

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