diff --git a/MANIFEST.md b/MANIFEST.md index c4cdd52..1bb7f50 100644 --- a/MANIFEST.md +++ b/MANIFEST.md @@ -1,7 +1,7 @@ # MCPB Manifest.json Spec Current version: `0.3` -Last updated: 2025-10-24 +Last updated: 2025-12-02 ## Manifest Schema @@ -398,6 +398,55 @@ The `server` object defines how to run the MCP server: ### Server Types +Four server types are supported: + +- **`node`**: Node.js server with bundled dependencies +- **`python`**: Python server with bundled dependencies +- **`binary`**: Compiled executable +- **`uv`**: Python server using UV runtime (experimental, v0.4+) + +### UV Runtime (Experimental, v0.4+) + +> **Note:** UV runtime support is experimental and may change in future versions. + +The `uv` server type enables cross-platform Python extensions without bundling dependencies. Instead, dependencies are declared in `pyproject.toml` and installed by the host application using UV. + +**Benefits:** +- Cross-platform support (Windows, macOS, Linux; Intel, ARM) +- Small bundle size (~100 KB vs 5-10 MB) +- Handles compiled dependencies (pydantic, numpy, etc.) +- No user Python installation required + +**Example:** +```json +{ + "manifest_version": "0.4", + "server": { + "type": "uv", + "entry_point": "src/server.py" + } +} +``` + +**Requirements:** +- Must include `pyproject.toml` with dependencies +- Must NOT include `server/lib/` or `server/venv/` +- `mcp_config` is optional (host manages execution) + +**Package structure:** +``` +extension.mcpb +├── manifest.json # server.type = "uv" +├── pyproject.toml # Dependencies +├── .mcpbignore # Exclude .venv, server/lib +└── src/ + └── server.py +``` + +See `examples/hello-world-uv` for a complete example. + +### Node, Python, and Binary Types + 1. **Python**: `server.type = "python"` - Requires `entry_point` to Python file - All dependencies must be bundled in the MCPB diff --git a/README.md b/README.md index 608023e..f47446c 100644 --- a/README.md +++ b/README.md @@ -109,12 +109,22 @@ bundle.mcpb (ZIP file) ### Bundling Dependencies -**Python Bundles:** +**UV Runtime (Experimental - v0.4+):** +- Use `server.type = "uv"` in manifest +- Include `pyproject.toml` with dependencies (no bundled packages needed) +- Host application manages Python and dependencies automatically +- Works cross-platform without user Python installation +- See `examples/hello-world-uv` + +**Python Bundles (Traditional):** + +- Use `server.type = "python"` in manifest - Bundle all required packages in `server/lib/` directory - OR bundle a complete virtual environment in `server/venv/` - Use tools like `pip-tools`, `poetry`, or `pipenv` to create reproducible bundles - Set `PYTHONPATH` to include bundled packages via `mcp_config.env` +- **Limitation**: Cannot portably bundle compiled dependencies (e.g., pydantic, which the MCP Python SDK requires) **Node.js Bundles:** diff --git a/examples/hello-world-uv/.mcpbignore b/examples/hello-world-uv/.mcpbignore new file mode 100644 index 0000000..eec9004 --- /dev/null +++ b/examples/hello-world-uv/.mcpbignore @@ -0,0 +1,8 @@ +.venv/ +__pycache__/ +*.pyc +.pytest_cache/ +.mypy_cache/ +*.egg-info/ +server/lib/ +server/venv/ diff --git a/examples/hello-world-uv/README.md b/examples/hello-world-uv/README.md new file mode 100644 index 0000000..ae898df --- /dev/null +++ b/examples/hello-world-uv/README.md @@ -0,0 +1,62 @@ +# Hello World UV Runtime Example (Experimental) + +> **Note:** UV runtime support is experimental and may change in future versions. + +This example demonstrates a minimal MCP server using **UV runtime**. + +## What is UV Runtime? + +UV runtime lets Claude Desktop automatically manage Python and dependencies for your extension: +- Downloads the correct Python version for the user's platform +- Creates an isolated virtual environment +- Installs dependencies from `pyproject.toml` +- Works cross-platform (Windows, macOS, Linux) without user setup + +## Structure + +``` +hello-world-uv/ +├── manifest.json # server.type = "uv" +├── pyproject.toml # Dependencies listed here +├── .mcpbignore # Exclude build artifacts +└── src/ + └── server.py # MCP server implementation +``` + +## Key Differences from Python Runtime + +**UV Runtime** (this example): +- `server.type = "uv"` +- No bundled dependencies +- No `mcp_config` needed +- Small bundle size (~2 KB) +- Works on any platform + +**Python Runtime** (traditional): +- `server.type = "python"` +- Must bundle dependencies in `server/lib/` +- Requires `mcp_config` with PYTHONPATH +- Larger bundle size +- Only works with pure Python (no compiled deps) + +## Installing + +```bash +mcpb pack +``` + +Install the generated `.mcpb` file in Claude Desktop. + +## Testing Locally + +```bash +# Install dependencies +uv sync + +# Run server +uv run src/server.py +``` + +## Tools + +- **say_hello** - Greets a person by name diff --git a/examples/hello-world-uv/icon.png b/examples/hello-world-uv/icon.png new file mode 100644 index 0000000..08cd6f2 Binary files /dev/null and b/examples/hello-world-uv/icon.png differ diff --git a/examples/hello-world-uv/manifest.json b/examples/hello-world-uv/manifest.json new file mode 100644 index 0000000..f3d3b4a --- /dev/null +++ b/examples/hello-world-uv/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": "0.4", + "name": "hello-world-uv", + "display_name": "Hello World (UV Runtime)", + "version": "1.0.0", + "description": "Simple MCP server using UV runtime", + "author": { + "name": "Anthropic" + }, + "icon": "icon.png", + "server": { + "type": "uv", + "entry_point": "src/server.py" + }, + "compatibility": { + "platforms": ["darwin", "linux", "win32"], + "runtimes": { + "python": ">=3.10" + } + }, + "keywords": ["example", "hello-world", "uv"], + "license": "MIT" +} diff --git a/examples/hello-world-uv/pyproject.toml b/examples/hello-world-uv/pyproject.toml new file mode 100644 index 0000000..0eca14c --- /dev/null +++ b/examples/hello-world-uv/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "hello-world-uv" +version = "1.0.0" +description = "Simple MCP server using UV runtime" +requires-python = ">=3.10" +dependencies = [ + "mcp>=1.0.0", +] diff --git a/examples/hello-world-uv/src/server.py b/examples/hello-world-uv/src/server.py new file mode 100644 index 0000000..ef871d2 --- /dev/null +++ b/examples/hello-world-uv/src/server.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Hello World MCP server using UV runtime.""" + +import asyncio +from mcp.server.models import InitializationOptions +import mcp.types as types +from mcp.server import NotificationOptions, Server +import mcp.server.stdio + + +server = Server("hello-world-uv") + + +@server.list_tools() +async def handle_list_tools() -> list[types.Tool]: + """List available tools.""" + return [ + types.Tool( + name="say_hello", + description="Say hello to someone", + inputSchema={ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name to greet", + } + }, + "required": ["name"], + }, + ) + ] + + +@server.call_tool() +async def handle_call_tool( + name: str, arguments: dict | None +) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + """Handle tool calls.""" + if name == "say_hello": + person_name = arguments.get("name", "World") if arguments else "World" + return [types.TextContent(type="text", text=f"Hello, {person_name}!")] + + raise ValueError(f"Unknown tool: {name}") + + +async def main(): + """Run the server.""" + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="hello-world-uv", + server_version="1.0.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/schemas/mcpb-manifest-v0.4.schema.json b/schemas/mcpb-manifest-v0.4.schema.json new file mode 100644 index 0000000..69496ba --- /dev/null +++ b/schemas/mcpb-manifest-v0.4.schema.json @@ -0,0 +1,376 @@ +{ + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "dxt_version": { + "type": "string", + "const": "0.4", + "description": "@deprecated Use manifest_version instead" + }, + "manifest_version": { + "type": "string", + "const": "0.4" + }, + "name": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "repository": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false + }, + "homepage": { + "type": "string", + "format": "uri" + }, + "documentation": { + "type": "string", + "format": "uri" + }, + "support": { + "type": "string", + "format": "uri" + }, + "icon": { + "type": "string" + }, + "icons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "size": { + "type": "string", + "pattern": "^\\d+x\\d+$" + }, + "theme": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "src", + "size" + ], + "additionalProperties": false + } + }, + "screenshots": { + "type": "array", + "items": { + "type": "string" + } + }, + "localization": { + "type": "object", + "properties": { + "resources": { + "type": "string", + "pattern": "\\$\\{locale\\}" + }, + "default_locale": { + "type": "string", + "pattern": "^[A-Za-z0-9]{2,8}(?:-[A-Za-z0-9]{1,8})*$" + } + }, + "required": [ + "resources", + "default_locale" + ], + "additionalProperties": false + }, + "server": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "python", + "node", + "binary", + "uv" + ] + }, + "entry_point": { + "type": "string" + }, + "mcp_config": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "platform_overrides": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "command": { + "$ref": "#/properties/server/properties/mcp_config/properties/command" + }, + "args": { + "$ref": "#/properties/server/properties/mcp_config/properties/args" + }, + "env": { + "$ref": "#/properties/server/properties/mcp_config/properties/env" + } + }, + "additionalProperties": false + } + } + }, + "required": [ + "command" + ], + "additionalProperties": false + } + }, + "required": [ + "type", + "entry_point" + ], + "additionalProperties": false + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "tools_generated": { + "type": "boolean" + }, + "prompts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "arguments": { + "type": "array", + "items": { + "type": "string" + } + }, + "text": { + "type": "string" + } + }, + "required": [ + "name", + "text" + ], + "additionalProperties": false + } + }, + "prompts_generated": { + "type": "boolean" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "type": "string" + }, + "privacy_policies": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "compatibility": { + "type": "object", + "properties": { + "claude_desktop": { + "type": "string" + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "darwin", + "win32", + "linux" + ] + } + }, + "runtimes": { + "type": "object", + "properties": { + "python": { + "type": "string" + }, + "node": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "user_config": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "directory", + "file" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "default": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "multiple": { + "type": "boolean" + }, + "sensitive": { + "type": "boolean" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + } + }, + "required": [ + "type", + "title", + "description" + ], + "additionalProperties": false + } + }, + "_meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + } + } + }, + "required": [ + "name", + "version", + "description", + "author", + "server" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/scripts/build-mcpb-schema.js b/scripts/build-mcpb-schema.js index 6d884e1..7f906eb 100644 --- a/scripts/build-mcpb-schema.js +++ b/scripts/build-mcpb-schema.js @@ -1,6 +1,7 @@ import { McpbManifestSchema as McpbManifestSchema_v0_1 } from "../dist/schemas/0.1.js"; import { McpbManifestSchema as McpbManifestSchema_v0_2 } from "../dist/schemas/0.2.js"; import { McpbManifestSchema as McpbManifestSchema_v0_3 } from "../dist/schemas/0.3.js"; +import { McpbManifestSchema as McpbManifestSchema_v0_4 } from "../dist/schemas/0.4.js"; import { McpbSignatureInfoSchema } from "../dist/shared/common.js"; import { zodToJsonSchema } from "zod-to-json-schema"; import fs from "node:fs/promises"; @@ -14,6 +15,7 @@ const versionedManifestSchemas = { "mcpb-manifest-v0.1": McpbManifestSchema_v0_1, "mcpb-manifest-v0.2": McpbManifestSchema_v0_2, "mcpb-manifest-v0.3": McpbManifestSchema_v0_3, + "mcpb-manifest-v0.4": McpbManifestSchema_v0_4, }; // Other schemas diff --git a/src/schemas/0.4.ts b/src/schemas/0.4.ts new file mode 100644 index 0000000..e706e26 --- /dev/null +++ b/src/schemas/0.4.ts @@ -0,0 +1,146 @@ +// v0.4: Adds UV runtime support for Python extensions +// NOTE: This schema version is experimental and subject to change. +import * as z from "zod"; + +export const MANIFEST_VERSION = "0.4"; + +const LOCALE_PLACEHOLDER_REGEX = /\$\{locale\}/i; +const BCP47_REGEX = /^[A-Za-z0-9]{2,8}(?:-[A-Za-z0-9]{1,8})*$/; +const ICON_SIZE_REGEX = /^\d+x\d+$/; + +export const McpServerConfigSchema = z.strictObject({ + command: z.string(), + args: z.array(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), +}); + +export const McpbManifestAuthorSchema = z.strictObject({ + name: z.string(), + email: z.string().email().optional(), + url: z.string().url().optional(), +}); + +export const McpbManifestRepositorySchema = z.strictObject({ + type: z.string(), + url: z.string().url(), +}); + +export const McpbManifestPlatformOverrideSchema = + McpServerConfigSchema.partial(); + +export const McpbManifestMcpConfigSchema = McpServerConfigSchema.extend({ + platform_overrides: z + .record(z.string(), McpbManifestPlatformOverrideSchema) + .optional(), +}); + +export const McpbManifestServerSchema = z.strictObject({ + type: z.enum(["python", "node", "binary", "uv"]), + entry_point: z.string(), + // mcp_config is optional for UV type (UV handles execution) + mcp_config: McpbManifestMcpConfigSchema.optional(), +}); + +export const McpbManifestCompatibilitySchema = z.strictObject({ + claude_desktop: z.string().optional(), + platforms: z.array(z.enum(["darwin", "win32", "linux"])).optional(), + runtimes: z + .strictObject({ + python: z.string().optional(), + node: z.string().optional(), + }) + .optional(), +}); + +export const McpbManifestToolSchema = z.strictObject({ + name: z.string(), + description: z.string().optional(), +}); + +export const McpbManifestPromptSchema = z.strictObject({ + name: z.string(), + description: z.string().optional(), + arguments: z.array(z.string()).optional(), + text: z.string(), +}); + +export const McpbUserConfigurationOptionSchema = z.strictObject({ + type: z.enum(["string", "number", "boolean", "directory", "file"]), + title: z.string(), + description: z.string(), + required: z.boolean().optional(), + default: z + .union([z.string(), z.number(), z.boolean(), z.array(z.string())]) + .optional(), + multiple: z.boolean().optional(), + sensitive: z.boolean().optional(), + min: z.number().optional(), + max: z.number().optional(), +}); + +export const McpbManifestLocalizationSchema = z.strictObject({ + resources: z + .string() + .regex( + LOCALE_PLACEHOLDER_REGEX, + 'resources must include a "${locale}" placeholder', + ), + default_locale: z + .string() + .regex( + BCP47_REGEX, + "default_locale must be a valid BCP 47 locale identifier", + ), +}); + +export const McpbManifestIconSchema = z.strictObject({ + src: z.string(), + size: z + .string() + .regex( + ICON_SIZE_REGEX, + 'size must be in the format "WIDTHxHEIGHT" (e.g., "16x16")', + ), + theme: z.string().min(1, "theme cannot be empty when provided").optional(), +}); + +export const McpbManifestSchema = z + .strictObject({ + $schema: z.string().optional(), + dxt_version: z + .literal(MANIFEST_VERSION) + .optional() + .describe("@deprecated Use manifest_version instead"), + manifest_version: z.literal(MANIFEST_VERSION).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(), + icons: z.array(McpbManifestIconSchema).optional(), + screenshots: z.array(z.string()).optional(), + localization: McpbManifestLocalizationSchema.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(), + privacy_policies: z.array(z.string().url()).optional(), + compatibility: McpbManifestCompatibilitySchema.optional(), + user_config: z + .record(z.string(), McpbUserConfigurationOptionSchema) + .optional(), + _meta: z.record(z.string(), z.record(z.string(), z.any())).optional(), + }) + .refine((data) => !!(data.dxt_version || data.manifest_version), { + message: + "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + }); diff --git a/src/schemas/any.ts b/src/schemas/any.ts index bd7cec8..1b374c7 100644 --- a/src/schemas/any.ts +++ b/src/schemas/any.ts @@ -3,13 +3,15 @@ import * as z from "zod"; import * as v0_1 from "./0.1.js"; import * as v0_2 from "./0.2.js"; import * as v0_3 from "./0.3.js"; +import * as v0_4 from "./0.4.js"; /** - * Union schema that accepts any supported manifest version (0.1, 0.2, 0.3). + * Union schema that accepts any supported manifest version. * Use this when you need to validate manifests of any version. */ export const McpbManifestSchema = z.union([ v0_1.McpbManifestSchema, v0_2.McpbManifestSchema, v0_3.McpbManifestSchema, + v0_4.McpbManifestSchema, ]); diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 897b95b..6d1405b 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -1,10 +1,12 @@ import { McpbManifestSchema as ManifestSchemaV0_1 } from "./0.1.js"; import { McpbManifestSchema as ManifestSchemaV0_2 } from "./0.2.js"; import { McpbManifestSchema as ManifestSchemaV0_3 } from "./0.3.js"; +import { McpbManifestSchema as ManifestSchemaV0_4 } from "./0.4.js"; export * as v0_1 from "./0.1.js"; export * as v0_2 from "./0.2.js"; export * as v0_3 from "./0.3.js"; +export * as v0_4 from "./0.4.js"; export * as vAny from "./any.js"; /** @@ -14,4 +16,5 @@ export const VERSIONED_MANIFEST_SCHEMAS = { "0.1": ManifestSchemaV0_1, "0.2": ManifestSchemaV0_2, "0.3": ManifestSchemaV0_3, + "0.4": ManifestSchemaV0_4, } as const; diff --git a/src/schemas_loose/0.4.ts b/src/schemas_loose/0.4.ts new file mode 100644 index 0000000..d75eed3 --- /dev/null +++ b/src/schemas_loose/0.4.ts @@ -0,0 +1,153 @@ +// v0.4: Adds UV runtime support for Python extensions +// NOTE: This schema version is experimental and subject to change. +import * as z from "zod"; + +export const MANIFEST_VERSION = "0.4"; + +const LOCALE_PLACEHOLDER_REGEX = /\$\{locale\}/i; +const BCP47_REGEX = /^[A-Za-z0-9]{2,8}(?:-[A-Za-z0-9]{1,8})*$/; +const ICON_SIZE_REGEX = /^\d+x\d+$/; + +export const McpServerConfigSchema = z.object({ + command: z.string(), + args: z.array(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), +}); + +export const McpbManifestAuthorSchema = z.object({ + name: z.string(), + email: z.string().email().optional(), + url: z.string().url().optional(), +}); + +export const McpbManifestRepositorySchema = z.object({ + type: z.string(), + url: z.string().url(), +}); + +export const McpbManifestPlatformOverrideSchema = + McpServerConfigSchema.partial(); + +export const McpbManifestMcpConfigSchema = McpServerConfigSchema.extend({ + platform_overrides: z + .record(z.string(), McpbManifestPlatformOverrideSchema) + .optional(), +}); + +export const McpbManifestServerSchema = z.object({ + type: z.enum(["python", "node", "binary", "uv"]), + entry_point: z.string(), + // mcp_config is optional for UV type (UV handles execution) + mcp_config: McpbManifestMcpConfigSchema.optional(), +}); + +export const McpbManifestCompatibilitySchema = z + .object({ + claude_desktop: z.string().optional(), + platforms: z.array(z.enum(["darwin", "win32", "linux"])).optional(), + runtimes: z + .object({ + python: z.string().optional(), + node: z.string().optional(), + }) + .optional(), + }) + .passthrough(); + +export const McpbManifestToolSchema = z.object({ + name: z.string(), + description: z.string().optional(), +}); + +export const McpbManifestPromptSchema = z.object({ + name: z.string(), + description: z.string().optional(), + arguments: z.array(z.string()).optional(), + text: z.string(), +}); + +export const McpbUserConfigurationOptionSchema = z.object({ + type: z.enum(["string", "number", "boolean", "directory", "file"]), + title: z.string(), + description: z.string(), + required: z.boolean().optional(), + default: z + .union([z.string(), z.number(), z.boolean(), z.array(z.string())]) + .optional(), + multiple: z.boolean().optional(), + sensitive: z.boolean().optional(), + min: z.number().optional(), + max: z.number().optional(), +}); + +export const McpbManifestLocalizationSchema = z + .object({ + resources: z + .string() + .regex( + LOCALE_PLACEHOLDER_REGEX, + 'resources must include a "${locale}" placeholder', + ), + default_locale: z + .string() + .regex( + BCP47_REGEX, + "default_locale must be a valid BCP 47 locale identifier", + ), + }) + .passthrough(); + +export const McpbManifestIconSchema = z + .object({ + src: z.string(), + size: z + .string() + .regex( + ICON_SIZE_REGEX, + 'size must be in the format "WIDTHxHEIGHT" (e.g., "16x16")', + ), + theme: z.string().min(1).optional(), + }) + .passthrough(); + +export const McpbManifestSchema = z + .object({ + $schema: z.string().optional(), + dxt_version: z + .literal(MANIFEST_VERSION) + .optional() + .describe("@deprecated Use manifest_version instead"), + manifest_version: z.literal(MANIFEST_VERSION).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(), + icons: z.array(McpbManifestIconSchema).optional(), + screenshots: z.array(z.string()).optional(), + localization: McpbManifestLocalizationSchema.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(), + privacy_policies: z.array(z.string().url()).optional(), + compatibility: McpbManifestCompatibilitySchema.optional(), + user_config: z + .record(z.string(), McpbUserConfigurationOptionSchema) + .optional(), + _meta: z.record(z.string(), z.record(z.string(), z.any())).optional(), + }) + .passthrough() + .refine((data) => !!(data.dxt_version || data.manifest_version), { + message: + "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + }); diff --git a/src/schemas_loose/index.ts b/src/schemas_loose/index.ts index f84f84c..c2f1a01 100644 --- a/src/schemas_loose/index.ts +++ b/src/schemas_loose/index.ts @@ -1,3 +1,4 @@ export * as v0_1 from "./0.1.js"; export * as v0_2 from "./0.2.js"; export * as v0_3 from "./0.3.js"; +export * as v0_4 from "./0.4.js"; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 53b08ed..4dd48a5 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,14 +1,16 @@ import { McpbManifestSchema as ManifestSchemaV0_1 } from "../schemas/0.1.js"; import { McpbManifestSchema as ManifestSchemaV0_2 } from "../schemas/0.2.js"; import { McpbManifestSchema as ManifestSchemaV0_3 } from "../schemas/0.3.js"; +import { McpbManifestSchema as ManifestSchemaV0_4 } from "../schemas/0.4.js"; import { McpbManifestSchema as LooseManifestSchemaV0_1 } from "../schemas_loose/0.1.js"; import { McpbManifestSchema as LooseManifestSchemaV0_2 } from "../schemas_loose/0.2.js"; import { McpbManifestSchema as LooseManifestSchemaV0_3 } from "../schemas_loose/0.3.js"; +import { McpbManifestSchema as LooseManifestSchemaV0_4 } from "../schemas_loose/0.4.js"; /** * Latest manifest version - indicates the maximum supported version by vAny schema */ -export const LATEST_MANIFEST_VERSION = "0.3" as const; +export const LATEST_MANIFEST_VERSION = "0.4" as const; /** * Default manifest version for new packages @@ -22,6 +24,7 @@ export const MANIFEST_SCHEMAS = { "0.1": ManifestSchemaV0_1, "0.2": ManifestSchemaV0_2, "0.3": ManifestSchemaV0_3, + "0.4": ManifestSchemaV0_4, } as const; /** @@ -31,4 +34,5 @@ export const MANIFEST_SCHEMAS_LOOSE = { "0.1": LooseManifestSchemaV0_1, "0.2": LooseManifestSchemaV0_2, "0.3": LooseManifestSchemaV0_3, + "0.4": LooseManifestSchemaV0_4, } as const;