diff --git a/src/schemas/0.4.ts b/src/schemas/0.4.ts index ae97d17..5ffce66 100644 --- a/src/schemas/0.4.ts +++ b/src/schemas/0.4.ts @@ -33,11 +33,23 @@ export const McpbManifestMcpConfigSchema = McpServerConfigSchema.extend({ .optional(), }); -export const McpbManifestServerSchema = z.strictObject({ - type: z.enum(["python", "node", "binary", "uv"]), - entry_point: z.string(), - mcp_config: McpbManifestMcpConfigSchema, -}); +export const McpbManifestServerSchema = z + .strictObject({ + type: z.enum(["python", "node", "binary", "uv"]), + entry_point: z.string(), + mcp_config: McpbManifestMcpConfigSchema.optional(), + }) + .superRefine((server, ctx) => { + // mcp_config is optional only for the "uv" server type (the host manages + // execution — see MANIFEST.md). All other server types require it. + if (server.type !== "uv" && server.mcp_config === undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["mcp_config"], + message: "Required", + }); + } + }); export const McpbManifestCompatibilitySchema = z.strictObject({ claude_desktop: z.string().optional(), diff --git a/test/schemas.test.ts b/test/schemas.test.ts index 52943e0..752055a 100644 --- a/test/schemas.test.ts +++ b/test/schemas.test.ts @@ -1,7 +1,7 @@ import { readFileSync } from "fs"; import { join } from "path"; -import { v0_2, v0_3 } from "../src/schemas/index.js"; +import { v0_2, v0_3, v0_4 } from "../src/schemas/index.js"; describe("McpbManifestSchema", () => { it("should validate a valid manifest", () => { @@ -335,4 +335,39 @@ describe("McpbManifestSchema", () => { expect(result.success).toBe(true); }); }); + + describe("0.4 uv server mcp_config", () => { + const base = { + manifest_version: "0.4", + name: "uv-extension", + version: "1.0.0", + description: "UV extension", + author: { name: "Test Author" }, + compatibility: { claude_desktop: "0.1.0" }, + }; + + it("accepts a uv server with no mcp_config (host manages execution)", () => { + const manifest = { + ...base, + server: { type: "uv", entry_point: "main.py" }, + }; + const result = v0_4.McpbManifestSchema.safeParse(manifest); + expect(result.success).toBe(true); + }); + + it("still requires mcp_config for non-uv server types", () => { + const manifest = { + ...base, + server: { type: "node", entry_point: "server.js" }, + }; + const result = v0_4.McpbManifestSchema.safeParse(manifest); + expect(result.success).toBe(false); + if (!result.success) { + const errors = result.error.issues.map((issue) => + issue.path.join("."), + ); + expect(errors).toContain("server.mcp_config"); + } + }); + }); });