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
This commit is contained in:
parent
2825da1d6a
commit
f92fe4ab69
9 changed files with 70 additions and 47 deletions
|
|
@ -1,8 +1,9 @@
|
||||||
# MCP Bundles (MCPB)
|
# MCP Bundles (MCPB)
|
||||||
|
|
||||||
> **⚠️ IMPORTANT NOTICE: This project is being renamed from DXT (Desktop Extensions) to MCPB (MCP Bundles)**
|
> **⚠️ 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:
|
> 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` CLI is now `mcpb`
|
||||||
> - `.dxt` files are now `.mcpb` files
|
> - `.dxt` files are now `.mcpb` files
|
||||||
> - `@anthropic-ai/dxt` package will be moved to `@anthropic-ai/mcpb`
|
> - `@anthropic-ai/dxt` package will be moved to `@anthropic-ai/mcpb`
|
||||||
|
|
@ -154,4 +155,4 @@ npm test
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
|
||||||
|
|
@ -80,4 +80,4 @@
|
||||||
"@babel/parser": "7.27.3"
|
"@babel/parser": "7.27.3"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 * as z from "zod/v4";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { confirm, input, select } from "@inquirer/prompts";
|
||||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { basename, join, resolve } from "path";
|
import { basename, join, resolve } from "path";
|
||||||
|
|
||||||
|
import { CURRENT_MANIFEST_VERSION } from "../schemas.js";
|
||||||
import type { McpbManifest } from "../types.js";
|
import type { McpbManifest } from "../types.js";
|
||||||
|
|
||||||
interface PackageJson {
|
interface PackageJson {
|
||||||
|
|
@ -773,7 +774,7 @@ export function buildManifest(
|
||||||
const { keywords, license, repository } = optionalFields;
|
const { keywords, license, repository } = optionalFields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manifest_version: "0.1",
|
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||||
name,
|
name,
|
||||||
...(displayName && displayName !== name
|
...(displayName && displayName !== name
|
||||||
? { display_name: displayName }
|
? { display_name: displayName }
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { basename, join, relative, resolve, sep } from "path";
|
||||||
|
|
||||||
import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js";
|
import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js";
|
||||||
import { validateManifest } from "../node/validate.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 { getLogger } from "../shared/log.js";
|
||||||
import { initExtension } from "./init.js";
|
import { initExtension } from "./init.js";
|
||||||
|
|
||||||
|
|
@ -101,6 +101,17 @@ export async function packExtension({
|
||||||
return false;
|
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
|
// Determine output path
|
||||||
const extensionName = basename(resolvedPath);
|
const extensionName = basename(resolvedPath);
|
||||||
const finalOutputPath = outputPath
|
const finalOutputPath = outputPath
|
||||||
|
|
|
||||||
|
|
@ -79,38 +79,39 @@ export const McpbUserConfigValuesSchema = z.record(
|
||||||
export const McpbManifestSchema = z
|
export const McpbManifestSchema = z
|
||||||
.object({
|
.object({
|
||||||
$schema: z.string().optional(),
|
$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(),
|
manifest_version: z.string().optional(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
display_name: z.string().optional(),
|
display_name: z.string().optional(),
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
long_description: z.string().optional(),
|
long_description: z.string().optional(),
|
||||||
author: McpbManifestAuthorSchema,
|
author: McpbManifestAuthorSchema,
|
||||||
repository: McpbManifestRepositorySchema.optional(),
|
repository: McpbManifestRepositorySchema.optional(),
|
||||||
homepage: z.string().url().optional(),
|
homepage: z.string().url().optional(),
|
||||||
documentation: z.string().url().optional(),
|
documentation: z.string().url().optional(),
|
||||||
support: z.string().url().optional(),
|
support: z.string().url().optional(),
|
||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
screenshots: z.array(z.string()).optional(),
|
screenshots: z.array(z.string()).optional(),
|
||||||
server: McpbManifestServerSchema,
|
server: McpbManifestServerSchema,
|
||||||
tools: z.array(McpbManifestToolSchema).optional(),
|
tools: z.array(McpbManifestToolSchema).optional(),
|
||||||
tools_generated: z.boolean().optional(),
|
tools_generated: z.boolean().optional(),
|
||||||
prompts: z.array(McpbManifestPromptSchema).optional(),
|
prompts: z.array(McpbManifestPromptSchema).optional(),
|
||||||
prompts_generated: z.boolean().optional(),
|
prompts_generated: z.boolean().optional(),
|
||||||
keywords: z.array(z.string()).optional(),
|
keywords: z.array(z.string()).optional(),
|
||||||
license: z.string().optional(),
|
license: z.string().optional(),
|
||||||
compatibility: McpbManifestCompatibilitySchema.optional(),
|
compatibility: McpbManifestCompatibilitySchema.optional(),
|
||||||
user_config: z
|
user_config: z
|
||||||
.record(z.string(), McpbUserConfigurationOptionSchema)
|
.record(z.string(), McpbUserConfigurationOptionSchema)
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine((data) => !!(data.dxt_version || data.manifest_version), {
|
||||||
(data) => !!(data.dxt_version || data.manifest_version),
|
message:
|
||||||
{
|
"Either 'dxt_version' (deprecated) or 'manifest_version' must be provided",
|
||||||
message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided",
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const McpbSignatureInfoSchema = z.object({
|
export const McpbSignatureInfoSchema = z.object({
|
||||||
status: z.enum(["signed", "unsigned", "self-signed"]),
|
status: z.enum(["signed", "unsigned", "self-signed"]),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
|
|
||||||
|
export const CURRENT_MANIFEST_VERSION = "0.2";
|
||||||
|
|
||||||
export const McpServerConfigSchema = z.strictObject({
|
export const McpServerConfigSchema = z.strictObject({
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
args: z.array(z.string()).optional(),
|
args: z.array(z.string()).optional(),
|
||||||
|
|
@ -79,7 +81,10 @@ export const McpbUserConfigValuesSchema = z.record(
|
||||||
export const McpbManifestSchema = z
|
export const McpbManifestSchema = z
|
||||||
.strictObject({
|
.strictObject({
|
||||||
$schema: z.string().optional(),
|
$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(),
|
manifest_version: z.string().optional(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
display_name: z.string().optional(),
|
display_name: z.string().optional(),
|
||||||
|
|
@ -106,12 +111,10 @@ export const McpbManifestSchema = z
|
||||||
.record(z.string(), McpbUserConfigurationOptionSchema)
|
.record(z.string(), McpbUserConfigurationOptionSchema)
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine((data) => !!(data.dxt_version || data.manifest_version), {
|
||||||
(data) => !!(data.dxt_version || data.manifest_version),
|
message:
|
||||||
{
|
"Either 'dxt_version' (deprecated) or 'manifest_version' must be provided",
|
||||||
message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided",
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const McpbSignatureInfoSchema = z.strictObject({
|
export const McpbSignatureInfoSchema = z.strictObject({
|
||||||
status: z.enum(["signed", "unsigned", "self-signed"]),
|
status: z.enum(["signed", "unsigned", "self-signed"]),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { execSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
import { CURRENT_MANIFEST_VERSION } from "../src/schemas.js";
|
||||||
|
|
||||||
interface ExecSyncError extends Error {
|
interface ExecSyncError extends Error {
|
||||||
stdout: Buffer;
|
stdout: Buffer;
|
||||||
stderr: Buffer;
|
stderr: Buffer;
|
||||||
|
|
@ -91,7 +93,7 @@ describe("DXT CLI", () => {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
join(tempDir, "manifest.json"),
|
join(tempDir, "manifest.json"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
manifest_version: "1.0",
|
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||||
name: "Test Extension",
|
name: "Test Extension",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
description: "A test extension",
|
description: "A test extension",
|
||||||
|
|
@ -188,7 +190,7 @@ describe("DXT CLI", () => {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
join(tempExecDir, "manifest.json"),
|
join(tempExecDir, "manifest.json"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
manifest_version: "1.0",
|
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||||
name: "Test Executable Extension",
|
name: "Test Executable Extension",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
description: "A test extension with executable files",
|
description: "A test extension with executable files",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
getDefaultRepositoryUrl,
|
getDefaultRepositoryUrl,
|
||||||
readPackageJson,
|
readPackageJson,
|
||||||
} from "../src/cli/init.js";
|
} from "../src/cli/init.js";
|
||||||
|
import { CURRENT_MANIFEST_VERSION } from "../src/schemas.js";
|
||||||
|
|
||||||
// Mock the fs module
|
// Mock the fs module
|
||||||
jest.mock("fs", () => ({
|
jest.mock("fs", () => ({
|
||||||
|
|
@ -217,7 +218,7 @@ describe("init functions", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(manifest).toEqual({
|
expect(manifest).toEqual({
|
||||||
manifest_version: "0.1",
|
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||||
name: "test-extension",
|
name: "test-extension",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
description: "Test description",
|
description: "Test description",
|
||||||
|
|
@ -303,7 +304,7 @@ describe("init functions", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(manifest).toEqual({
|
expect(manifest).toEqual({
|
||||||
manifest_version: "0.1",
|
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||||
name: "test-extension",
|
name: "test-extension",
|
||||||
display_name: "Test Extension",
|
display_name: "Test Extension",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue