From 632d54978f22ce87fe250e1286f6cd3703e41546 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Sat, 1 Nov 2025 12:24:41 -0700 Subject: [PATCH] feat: add schema version option to init and pack commands --- src/cli/cli.ts | 82 ++++++++++++++++++++++++++--------------- src/cli/init.ts | 24 ++++++++++-- src/cli/pack.ts | 28 +++++++++++--- src/shared/constants.ts | 25 ++++++++++++- 4 files changed, 119 insertions(+), 40 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index ac38b52..51e1732 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -51,19 +51,32 @@ program .command("init [directory]") .description("Create a new MCPB extension manifest") .option("-y, --yes", "Accept all defaults (non-interactive mode)") - .action((directory?: string, options?: { yes?: boolean }) => { - void (async () => { - try { - const success = await initExtension(directory, options?.yes); - process.exit(success ? 0 : 1); - } catch (error) { - console.error( - `ERROR: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - process.exit(1); - } - })(); - }); + .option( + "-s, --schema-version ", + "Schema version to use (0.1, 0.2, or 0.3)", + ) + .action( + ( + directory?: string, + options?: { yes?: boolean; schemaVersion?: string }, + ) => { + void (async () => { + try { + const success = await initExtension( + directory, + options?.yes, + options?.schemaVersion, + ); + process.exit(success ? 0 : 1); + } catch (error) { + console.error( + `ERROR: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + process.exit(1); + } + })(); + }, + ); // Validate command program @@ -88,22 +101,33 @@ program program .command("pack [directory] [output]") .description("Pack a directory into an MCPB extension") - .action((directory: string = process.cwd(), output?: string) => { - void (async () => { - try { - const success = await packExtension({ - extensionPath: directory, - outputPath: output, - }); - process.exit(success ? 0 : 1); - } catch (error) { - console.error( - `ERROR: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - process.exit(1); - } - })(); - }); + .option( + "-s, --schema-version ", + "Schema version to use (0.1, 0.2, or 0.3)", + ) + .action( + ( + directory: string = process.cwd(), + output?: string, + options?: { schemaVersion?: string }, + ) => { + void (async () => { + try { + const success = await packExtension({ + extensionPath: directory, + outputPath: output, + schemaVersion: options?.schemaVersion, + }); + process.exit(success ? 0 : 1); + } catch (error) { + console.error( + `ERROR: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + process.exit(1); + } + })(); + }, + ); // Unpack command program diff --git a/src/cli/init.ts b/src/cli/init.ts index 58a7317..23e964a 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -2,8 +2,12 @@ import { confirm, input, select } from "@inquirer/prompts"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { basename, join, resolve } from "path"; -import { LATEST_MANIFEST_VERSION } from "../shared/constants.js"; -import type { McpbManifestLatest } from "../types.js"; +import { + DEFAULT_MANIFEST_VERSION, + LATEST_MANIFEST_VERSION, + VALID_MANIFEST_VERSIONS, +} from "../shared/constants.js"; +import type { McpbManifest } from "../types.js"; interface PackageJson { name?: string; @@ -878,14 +882,15 @@ export function buildManifest( resources: string; default_locale: string; }, -): McpbManifestLatest { + schemaVersion: string = DEFAULT_MANIFEST_VERSION, +): McpbManifest { const { name, displayName, version, description, authorName } = basicInfo; const { authorEmail, authorUrl } = authorInfo; const { serverType, entryPoint, mcp_config } = serverConfig; const { keywords, license, repository } = optionalFields; return { - manifest_version: LATEST_MANIFEST_VERSION, + manifest_version: schemaVersion as "0.1" | "0.2" | "0.3", name, ...(displayName && displayName !== name ? { display_name: displayName } @@ -942,6 +947,7 @@ export function printNextSteps() { export async function initExtension( targetPath: string = process.cwd(), nonInteractive = false, + schemaVersion: string = DEFAULT_MANIFEST_VERSION, ): Promise { const resolvedPath = resolve(targetPath); const manifestPath = join(resolvedPath, "manifest.json"); @@ -1011,6 +1017,15 @@ export async function initExtension( ? getDefaultOptionalFields(packageData) : await promptOptionalFields(packageData); + // Validate schema version + if (!VALID_MANIFEST_VERSIONS.includes(schemaVersion as any)) { + console.error(`ERROR: Invalid schema version: ${schemaVersion}`); + console.error( + `Valid versions are: ${VALID_MANIFEST_VERSIONS.join(", ")}`, + ); + return false; + } + // Build manifest const manifest = buildManifest( basicInfo, @@ -1027,6 +1042,7 @@ export async function initExtension( userConfig, optionalFields, localization, + schemaVersion, ); // Write manifest diff --git a/src/cli/pack.ts b/src/cli/pack.ts index 58b3c1c..0255847 100644 --- a/src/cli/pack.ts +++ b/src/cli/pack.ts @@ -14,8 +14,10 @@ import { basename, join, relative, resolve, sep } from "path"; import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js"; import { validateManifest } from "../node/validate.js"; import { - LATEST_MANIFEST_SCHEMA, + DEFAULT_MANIFEST_VERSION, LATEST_MANIFEST_VERSION, + MANIFEST_SCHEMAS, + VALID_MANIFEST_VERSIONS, } from "../shared/constants.js"; import { getLogger } from "../shared/log.js"; import { initExtension } from "./init.js"; @@ -24,6 +26,7 @@ interface PackOptions { extensionPath: string; outputPath?: string; silent?: boolean; + schemaVersion?: string; } function formatFileSize(bytes: number): string { @@ -52,10 +55,20 @@ export async function packExtension({ extensionPath, outputPath, silent, + schemaVersion = DEFAULT_MANIFEST_VERSION, }: PackOptions): Promise { const resolvedPath = resolve(extensionPath); const logger = getLogger({ silent }); + // Validate schema version + if (!VALID_MANIFEST_VERSIONS.includes(schemaVersion as any)) { + logger.error(`ERROR: Invalid schema version: ${schemaVersion}`); + logger.error( + `Valid versions are: ${VALID_MANIFEST_VERSIONS.join(", ")}`, + ); + return false; + } + // Check if directory exists if (!existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) { logger.error(`ERROR: Directory not found: ${extensionPath}`); @@ -72,7 +85,7 @@ export async function packExtension({ }); if (shouldInit) { - const success = await initExtension(extensionPath); + const success = await initExtension(extensionPath, false, schemaVersion); if (!success) { logger.error("ERROR: Failed to create manifest"); return false; @@ -95,7 +108,10 @@ export async function packExtension({ try { const manifestContent = readFileSync(manifestPath, "utf-8"); const manifestData = JSON.parse(manifestContent); - manifest = LATEST_MANIFEST_SCHEMA.parse(manifestData); + + // Get the schema for the specified version + const schema = MANIFEST_SCHEMAS[schemaVersion as keyof typeof MANIFEST_SCHEMAS]; + manifest = schema.parse(manifestData); } catch (error) { logger.error("ERROR: Failed to parse manifest.json"); if (error instanceof Error) { @@ -105,12 +121,12 @@ export async function packExtension({ } const manifestVersion = manifest.manifest_version || manifest.dxt_version; - if (manifestVersion !== LATEST_MANIFEST_VERSION) { + if (manifestVersion !== schemaVersion) { logger.error( - `ERROR: Manifest version mismatch. Expected "${LATEST_MANIFEST_VERSION}", found "${manifestVersion}"`, + `ERROR: Manifest version mismatch. Expected "${schemaVersion}", found "${manifestVersion}"`, ); logger.error( - ` Please update the manifest_version in your manifest.json to "${LATEST_MANIFEST_VERSION}"`, + ` Please update the manifest_version in your manifest.json to "${schemaVersion}"`, ); return false; } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 95a5cf4..d4a70be 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -8,7 +8,13 @@ import { McpbManifestSchema as LooseManifestSchemaV0_3 } from "../schemas_loose/ import { McpbManifestSchema as CurrentLooseManifestSchema } from "../schemas_loose/latest.js"; /** - * Latest manifest version - the version that new manifests should use + * Default manifest version - the version that new manifests should use + * This is the version that most clients currently support + */ +export const DEFAULT_MANIFEST_VERSION = "0.2" as const; + +/** + * Latest manifest version - the newest version of the manifest schema */ export const LATEST_MANIFEST_VERSION = "0.3" as const; @@ -21,6 +27,13 @@ export const MANIFEST_SCHEMAS = { "0.3": ManifestSchemaV0_3, } as const; +/** + * Valid manifest versions + */ +export const VALID_MANIFEST_VERSIONS = Object.keys( + MANIFEST_SCHEMAS, +) as Array; + /** * Map of manifest versions to their loose schemas (with passthrough) */ @@ -39,3 +52,13 @@ export const LATEST_MANIFEST_SCHEMA = CurrentManifestSchema; * Get the latest loose manifest schema based on LATEST_MANIFEST_VERSION */ export const LATEST_MANIFEST_SCHEMA_LOOSE = CurrentLooseManifestSchema; + +/** + * Get the default manifest schema based on DEFAULT_MANIFEST_VERSION + */ +export const DEFAULT_MANIFEST_SCHEMA = ManifestSchemaV0_2; + +/** + * Get the default loose manifest schema based on DEFAULT_MANIFEST_VERSION + */ +export const DEFAULT_MANIFEST_SCHEMA_LOOSE = LooseManifestSchemaV0_2;