Skip to main content
Glama

Convex MCP server

Official
by get-convex
upgrade.ts9.74 kB
import path from "path"; import { Context } from "../../../bundler/context.js"; import { logFailure, logFinishedStep, logVerbose, } from "../../../bundler/log.js"; import { runSystemQuery } from "../run.js"; import { LocalDeploymentKind, deploymentStateDir, loadDeploymentConfig, saveDeploymentConfig, } from "./filePaths.js"; import { ensureBackendStopped, localDeploymentUrl, runLocalBackend, } from "./run.js"; import { downloadSnapshotExport, startSnapshotExport, } from "../convexExport.js"; import { deploymentFetch, logAndHandleFetchError } from "../utils/utils.js"; import { confirmImport, uploadForImport, waitForStableImportState, } from "../convexImport.js"; import { promptOptions, promptYesNo } from "../utils/prompts.js"; import { recursivelyDelete } from "../fsUtils.js"; import { LocalDeploymentError } from "./errors.js"; import { ensureBackendBinaryDownloaded } from "./download.js"; export async function handlePotentialUpgrade( ctx: Context, args: { deploymentKind: LocalDeploymentKind; deploymentName: string; oldVersion: string | null; newBinaryPath: string; newVersion: string; ports: { cloud: number; site: number; }; adminKey: string; instanceSecret: string; forceUpgrade: boolean; }, ): Promise<{ cleanupHandle: string }> { const newConfig = { ports: args.ports, backendVersion: args.newVersion, adminKey: args.adminKey, instanceSecret: args.instanceSecret, }; if (args.oldVersion === null || args.oldVersion === args.newVersion) { // No upgrade needed. Save the current config and start running the backend. saveDeploymentConfig( ctx, args.deploymentKind, args.deploymentName, newConfig, ); return runLocalBackend(ctx, { binaryPath: args.newBinaryPath, deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, ports: args.ports, instanceSecret: args.instanceSecret, isLatestVersion: true, }); } logVerbose( `Considering upgrade from ${args.oldVersion} to ${args.newVersion}`, ); const confirmed = args.forceUpgrade || (await promptYesNo(ctx, { message: `This deployment is using an older version of the Convex backend. Upgrade now?`, default: true, })); if (!confirmed) { const { binaryPath: oldBinaryPath } = await ensureBackendBinaryDownloaded( ctx, { kind: "version", version: args.oldVersion, }, ); // Skipping upgrade, save the config with the old version and run. saveDeploymentConfig(ctx, args.deploymentKind, args.deploymentName, { ...newConfig, backendVersion: args.oldVersion, }); return runLocalBackend(ctx, { binaryPath: oldBinaryPath, ports: args.ports, deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, instanceSecret: args.instanceSecret, isLatestVersion: false, }); } const choice = args.forceUpgrade ? "transfer" : await promptOptions(ctx, { message: "Transfer data from existing deployment?", default: "transfer", choices: [ { name: "transfer data", value: "transfer" }, { name: "start fresh", value: "reset" }, ], }); const deploymentStatePath = deploymentStateDir( args.deploymentKind, args.deploymentName, ); if (choice === "reset") { recursivelyDelete(ctx, deploymentStatePath, { force: true }); saveDeploymentConfig( ctx, args.deploymentKind, args.deploymentName, newConfig, ); return runLocalBackend(ctx, { binaryPath: args.newBinaryPath, deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, ports: args.ports, instanceSecret: args.instanceSecret, isLatestVersion: true, }); } const newAdminKey = args.adminKey; const oldAdminKey = loadDeploymentConfig(ctx, args.deploymentKind, args.deploymentName) ?.adminKey ?? args.adminKey; return handleUpgrade(ctx, { deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, oldVersion: args.oldVersion!, newBinaryPath: args.newBinaryPath, newVersion: args.newVersion, ports: args.ports, oldAdminKey, newAdminKey, instanceSecret: args.instanceSecret, }); } async function handleUpgrade( ctx: Context, args: { deploymentName: string; deploymentKind: LocalDeploymentKind; oldVersion: string; newBinaryPath: string; newVersion: string; ports: { cloud: number; site: number; }; // In most of the cases the admin key is the same for the old and new version. // This is helpful when we start generating new admin key formats that might // be incompatible with older backend versions. oldAdminKey: string; newAdminKey: string; instanceSecret: string; }, ): Promise<{ cleanupHandle: string }> { const { binaryPath: oldBinaryPath } = await ensureBackendBinaryDownloaded( ctx, { kind: "version", version: args.oldVersion, }, ); logVerbose("Running backend on old version"); const { cleanupHandle: oldCleanupHandle } = await runLocalBackend(ctx, { binaryPath: oldBinaryPath, ports: args.ports, deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, instanceSecret: args.instanceSecret, isLatestVersion: false, }); logVerbose("Downloading env vars"); const deploymentUrl = localDeploymentUrl(args.ports.cloud); const envs = (await runSystemQuery(ctx, { deploymentUrl, adminKey: args.oldAdminKey, functionName: "_system/cli/queryEnvironmentVariables", componentPath: undefined, args: {}, })) as Array<{ name: string; value: string; }>; logVerbose("Doing a snapshot export"); const exportPath = path.join( deploymentStateDir(args.deploymentKind, args.deploymentName), "export.zip", ); if (ctx.fs.exists(exportPath)) { ctx.fs.unlink(exportPath); } const snaphsotExportState = await startSnapshotExport(ctx, { deploymentUrl, adminKey: args.oldAdminKey, includeStorage: true, inputPath: exportPath, }); if (snaphsotExportState.state !== "completed") { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: "Failed to export snapshot", }); } await downloadSnapshotExport(ctx, { snapshotExportTs: snaphsotExportState.start_ts, inputPath: exportPath, adminKey: args.oldAdminKey, deploymentUrl, }); logVerbose("Stopping the backend on the old version"); const oldCleanupFunc = ctx.removeCleanup(oldCleanupHandle); if (oldCleanupFunc) { await oldCleanupFunc(0); } await ensureBackendStopped(ctx, { ports: args.ports, maxTimeSecs: 5, deploymentName: args.deploymentName, allowOtherDeployments: false, }); // TODO(ENG-7078) save old artifacts to backup files logVerbose("Running backend on new version"); const { cleanupHandle } = await runLocalBackend(ctx, { binaryPath: args.newBinaryPath, ports: args.ports, deploymentKind: args.deploymentKind, deploymentName: args.deploymentName, instanceSecret: args.instanceSecret, isLatestVersion: true, }); logVerbose("Importing the env vars"); if (envs.length > 0) { const fetch = deploymentFetch(ctx, { deploymentUrl, adminKey: args.newAdminKey, }); try { await fetch("/api/update_environment_variables", { body: JSON.stringify({ changes: envs }), method: "POST", }); } catch (e) { // TODO: this should ideally have a `LocalDeploymentError` return await logAndHandleFetchError(ctx, e); } } logVerbose("Doing a snapshot import"); const importId = await uploadForImport(ctx, { deploymentUrl, adminKey: args.newAdminKey, filePath: exportPath, importArgs: { format: "zip", mode: "replace", tableName: undefined }, onImportFailed: async (e) => { logFailure(`Failed to import snapshot: ${e}`); }, }); logVerbose(`Snapshot import started`); let status = await waitForStableImportState(ctx, { importId, deploymentUrl, adminKey: args.newAdminKey, onProgress: () => { // do nothing for now return 0; }, }); if (status.state !== "waiting_for_confirmation") { const message = "Error while transferring data: Failed to upload snapshot"; return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: message, errForSentry: new LocalDeploymentError(message), }); } await confirmImport(ctx, { importId, adminKey: args.newAdminKey, deploymentUrl, onError: async (e) => { logFailure(`Failed to confirm import: ${e}`); }, }); logVerbose(`Snapshot import confirmed`); status = await waitForStableImportState(ctx, { importId, deploymentUrl, adminKey: args.newAdminKey, onProgress: () => { // do nothing for now return 0; }, }); logVerbose(`Snapshot import status: ${status.state}`); if (status.state !== "completed") { const message = "Error while transferring data: Failed to import snapshot"; return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: message, errForSentry: new LocalDeploymentError(message), }); } logFinishedStep("Successfully upgraded to a new backend version"); saveDeploymentConfig(ctx, args.deploymentKind, args.deploymentName, { ports: args.ports, backendVersion: args.newVersion, adminKey: args.newAdminKey, instanceSecret: args.instanceSecret, }); return { cleanupHandle }; }

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