From f92fe4ab69a7d752abbb9c54a0f7ebfce6184456 Mon Sep 17 00:00:00 2001 From: Joan Xie Date: Mon, 22 Sep 2025 14:43:28 -0700 Subject: [PATCH 1/5] feat: validate manifest version during pack command Add validation to ensure the manifest_version matches the current expected version (0.2) during the mcpb pack command. This prevents users from packing extensions with outdated or incorrect manifest versions. Changes: - Add CURRENT_MANIFEST_VERSION constant to centralize version management - Validate manifest version in pack command before creating bundle - Update init command to use version constant - Update tests to use version constant - Fix prettier formatting issues --- README.md | 5 +-- package.json | 2 +- scripts/build-mcpb-schema.js | 5 ++- src/cli/init.ts | 3 +- src/cli/pack.ts | 13 +++++++- src/schemas-loose.ts | 61 ++++++++++++++++++------------------ src/schemas.ts | 17 +++++----- test/cli.test.ts | 6 ++-- test/init.test.ts | 5 +-- 9 files changed, 70 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 1d768f2..0675ef9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # MCP Bundles (MCPB) > **⚠️ IMPORTANT NOTICE: This project is being renamed from DXT (Desktop Extensions) to MCPB (MCP Bundles)** -> +> > If you're looking for the DXT tools, they have been renamed to MCPB. Please update your dependencies and tooling: +> > - `dxt` CLI is now `mcpb` > - `.dxt` files are now `.mcpb` files > - `@anthropic-ai/dxt` package will be moved to `@anthropic-ai/mcpb` @@ -154,4 +155,4 @@ npm test # License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/package.json b/package.json index 6864e7c..b71c19d 100644 --- a/package.json +++ b/package.json @@ -80,4 +80,4 @@ "@babel/parser": "7.27.3" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} \ No newline at end of file +} diff --git a/scripts/build-mcpb-schema.js b/scripts/build-mcpb-schema.js index b24856f..28d813c 100644 --- a/scripts/build-mcpb-schema.js +++ b/scripts/build-mcpb-schema.js @@ -1,4 +1,7 @@ -import { McpbManifestSchema, McpbSignatureInfoSchema } from "../dist/schemas.js"; +import { + McpbManifestSchema, + McpbSignatureInfoSchema, +} from "../dist/schemas.js"; import * as z from "zod/v4"; import fs from "node:fs/promises"; import path from "node:path"; diff --git a/src/cli/init.ts b/src/cli/init.ts index 4f104be..5c16885 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -2,6 +2,7 @@ import { confirm, input, select } from "@inquirer/prompts"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { basename, join, resolve } from "path"; +import { CURRENT_MANIFEST_VERSION } from "../schemas.js"; import type { McpbManifest } from "../types.js"; interface PackageJson { @@ -773,7 +774,7 @@ export function buildManifest( const { keywords, license, repository } = optionalFields; return { - manifest_version: "0.1", + manifest_version: CURRENT_MANIFEST_VERSION, name, ...(displayName && displayName !== name ? { display_name: displayName } diff --git a/src/cli/pack.ts b/src/cli/pack.ts index d095e5c..4e3c670 100644 --- a/src/cli/pack.ts +++ b/src/cli/pack.ts @@ -13,7 +13,7 @@ import { basename, join, relative, resolve, sep } from "path"; import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js"; import { validateManifest } from "../node/validate.js"; -import { McpbManifestSchema } from "../schemas.js"; +import { CURRENT_MANIFEST_VERSION, McpbManifestSchema } from "../schemas.js"; import { getLogger } from "../shared/log.js"; import { initExtension } from "./init.js"; @@ -101,6 +101,17 @@ export async function packExtension({ return false; } + const manifestVersion = manifest.manifest_version || manifest.dxt_version; + if (manifestVersion !== CURRENT_MANIFEST_VERSION) { + logger.error( + `ERROR: Manifest version mismatch. Expected "${CURRENT_MANIFEST_VERSION}", found "${manifestVersion}"`, + ); + logger.error( + ` Please update the manifest_version in your manifest.json to "${CURRENT_MANIFEST_VERSION}"`, + ); + return false; + } + // Determine output path const extensionName = basename(resolvedPath); const finalOutputPath = outputPath diff --git a/src/schemas-loose.ts b/src/schemas-loose.ts index 882b1a5..e44ba2a 100644 --- a/src/schemas-loose.ts +++ b/src/schemas-loose.ts @@ -79,38 +79,39 @@ export const McpbUserConfigValuesSchema = z.record( export const McpbManifestSchema = z .object({ $schema: z.string().optional(), - dxt_version: z.string().optional().describe("@deprecated Use manifest_version instead"), + dxt_version: z + .string() + .optional() + .describe("@deprecated Use manifest_version instead"), manifest_version: z.string().optional(), - name: z.string(), - display_name: z.string().optional(), - version: z.string(), - description: z.string(), - long_description: z.string().optional(), - author: McpbManifestAuthorSchema, - repository: McpbManifestRepositorySchema.optional(), - homepage: z.string().url().optional(), - documentation: z.string().url().optional(), - support: z.string().url().optional(), - icon: z.string().optional(), - screenshots: z.array(z.string()).optional(), - server: McpbManifestServerSchema, - tools: z.array(McpbManifestToolSchema).optional(), - tools_generated: z.boolean().optional(), - prompts: z.array(McpbManifestPromptSchema).optional(), - prompts_generated: z.boolean().optional(), - keywords: z.array(z.string()).optional(), - license: z.string().optional(), - compatibility: McpbManifestCompatibilitySchema.optional(), - user_config: z - .record(z.string(), McpbUserConfigurationOptionSchema) - .optional(), + name: z.string(), + display_name: z.string().optional(), + version: z.string(), + description: z.string(), + long_description: z.string().optional(), + author: McpbManifestAuthorSchema, + repository: McpbManifestRepositorySchema.optional(), + homepage: z.string().url().optional(), + documentation: z.string().url().optional(), + support: z.string().url().optional(), + icon: z.string().optional(), + screenshots: z.array(z.string()).optional(), + server: McpbManifestServerSchema, + tools: z.array(McpbManifestToolSchema).optional(), + tools_generated: z.boolean().optional(), + prompts: z.array(McpbManifestPromptSchema).optional(), + prompts_generated: z.boolean().optional(), + keywords: z.array(z.string()).optional(), + license: z.string().optional(), + compatibility: McpbManifestCompatibilitySchema.optional(), + user_config: z + .record(z.string(), McpbUserConfigurationOptionSchema) + .optional(), }) - .refine( - (data) => !!(data.dxt_version || data.manifest_version), - { - message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", - } - ); + .refine((data) => !!(data.dxt_version || data.manifest_version), { + message: + "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + }); export const McpbSignatureInfoSchema = z.object({ status: z.enum(["signed", "unsigned", "self-signed"]), diff --git a/src/schemas.ts b/src/schemas.ts index adaca4c..1f7ece2 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,5 +1,7 @@ import * as z from "zod"; +export const CURRENT_MANIFEST_VERSION = "0.2"; + export const McpServerConfigSchema = z.strictObject({ command: z.string(), args: z.array(z.string()).optional(), @@ -79,7 +81,10 @@ export const McpbUserConfigValuesSchema = z.record( export const McpbManifestSchema = z .strictObject({ $schema: z.string().optional(), - dxt_version: z.string().optional().describe("@deprecated Use manifest_version instead"), + dxt_version: z + .string() + .optional() + .describe("@deprecated Use manifest_version instead"), manifest_version: z.string().optional(), name: z.string(), display_name: z.string().optional(), @@ -106,12 +111,10 @@ export const McpbManifestSchema = z .record(z.string(), McpbUserConfigurationOptionSchema) .optional(), }) - .refine( - (data) => !!(data.dxt_version || data.manifest_version), - { - message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", - } - ); + .refine((data) => !!(data.dxt_version || data.manifest_version), { + message: + "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + }); export const McpbSignatureInfoSchema = z.strictObject({ status: z.enum(["signed", "unsigned", "self-signed"]), diff --git a/test/cli.test.ts b/test/cli.test.ts index d250b5c..0bc0a36 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -2,6 +2,8 @@ import { execSync } from "node:child_process"; import fs from "node:fs"; import { join } from "node:path"; +import { CURRENT_MANIFEST_VERSION } from "../src/schemas.js"; + interface ExecSyncError extends Error { stdout: Buffer; stderr: Buffer; @@ -91,7 +93,7 @@ describe("DXT CLI", () => { fs.writeFileSync( join(tempDir, "manifest.json"), JSON.stringify({ - manifest_version: "1.0", + manifest_version: CURRENT_MANIFEST_VERSION, name: "Test Extension", version: "1.0.0", description: "A test extension", @@ -188,7 +190,7 @@ describe("DXT CLI", () => { fs.writeFileSync( join(tempExecDir, "manifest.json"), JSON.stringify({ - manifest_version: "1.0", + manifest_version: CURRENT_MANIFEST_VERSION, name: "Test Executable Extension", version: "1.0.0", description: "A test extension with executable files", diff --git a/test/init.test.ts b/test/init.test.ts index 68730ce..f79191e 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -10,6 +10,7 @@ import { getDefaultRepositoryUrl, readPackageJson, } from "../src/cli/init.js"; +import { CURRENT_MANIFEST_VERSION } from "../src/schemas.js"; // Mock the fs module jest.mock("fs", () => ({ @@ -217,7 +218,7 @@ describe("init functions", () => { ); expect(manifest).toEqual({ - manifest_version: "0.1", + manifest_version: CURRENT_MANIFEST_VERSION, name: "test-extension", version: "1.0.0", description: "Test description", @@ -303,7 +304,7 @@ describe("init functions", () => { ); expect(manifest).toEqual({ - manifest_version: "0.1", + manifest_version: CURRENT_MANIFEST_VERSION, name: "test-extension", display_name: "Test Extension", version: "1.0.0", From 16e569a3370bbcf8281478b58e93eeeb79f2292a Mon Sep 17 00:00:00 2001 From: Joan Xie Date: Mon, 22 Sep 2025 14:59:42 -0700 Subject: [PATCH 2/5] fix examples --- MANIFEST.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.md b/MANIFEST.md index eb8ee46..bd0a160 100644 --- a/MANIFEST.md +++ b/MANIFEST.md @@ -11,7 +11,7 @@ A basic `manifest.json` with just the required fields looks like this: ```json { - "manifest_version": "0.1", // Manifest spec version this manifest conforms to + "manifest_version": "0.2", // Manifest spec version this manifest conforms to "name": "my-extension", // Machine-readable name (used for CLI, APIs) "version": "1.0.0", // Semantic version of your extension "description": "A simple MCP extension", // Brief description of what the extension does @@ -37,7 +37,7 @@ A basic `manifest.json` with just the required fields looks like this: ```json { - "manifest_version": "0.1", + "manifest_version": "0.2", "name": "my-extension", "version": "1.0.0", "description": "A simple MCP extension", From 232e6b3f912d9cb0e124623697b57ea73b4712f0 Mon Sep 17 00:00:00 2001 From: Joan Xie Date: Mon, 22 Sep 2025 15:17:16 -0700 Subject: [PATCH 3/5] improve schema validation logs --- src/node/validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/validate.ts b/src/node/validate.ts index 19727ff..2f86e5e 100644 --- a/src/node/validate.ts +++ b/src/node/validate.ts @@ -25,7 +25,7 @@ export function validateManifest(inputPath: string): boolean { const result = McpbManifestSchema.safeParse(manifestData); if (result.success) { - console.log("Manifest is valid!"); + console.log("Manifest schema validation passes!"); return true; } else { console.log("ERROR: Manifest validation failed:\n"); From e772ed753d87ce389ed989f2247aa0ff360d1e54 Mon Sep 17 00:00:00 2001 From: Joan Xie Date: Mon, 22 Sep 2025 15:28:42 -0700 Subject: [PATCH 4/5] fix: update test to match new validation message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- test/cli.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli.test.ts b/test/cli.test.ts index 0bc0a36..dd18635 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -25,7 +25,7 @@ describe("DXT CLI", () => { const result = execSync(`node ${cliPath} validate ${validManifestPath}`, { encoding: "utf-8", }); - expect(result).toContain("Manifest is valid!"); + expect(result).toContain("Manifest schema validation passes!"); }); it("should reject an invalid manifest", () => { From b7a2acb969176acab2daf8a12b991306f56d1f22 Mon Sep 17 00:00:00 2001 From: Joan Xie Date: Mon, 22 Sep 2025 15:32:23 -0700 Subject: [PATCH 5/5] bump version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b71c19d..cb00b16 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@anthropic-ai/mcpb", "description": "Tools for building MCP Bundles", - "version": "1.1.0", + "version": "1.1.1", "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -80,4 +80,4 @@ "@babel/parser": "7.27.3" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} +} \ No newline at end of file