diff --git a/.gitignore b/.gitignore index c1ab374..bc63d59 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,6 @@ _out/ dist self-signed-cert.pem self-signed-key.pem -*.dxt +*.mcpb invalid-json.json **/server/lib/** \ No newline at end of file diff --git a/CLI.md b/CLI.md index 904b86b..02787f8 100644 --- a/CLI.md +++ b/CLI.md @@ -1,45 +1,45 @@ -# DXT CLI Documentation +# MCPB CLI Documentation -The DXT CLI provides tools for building Desktop Extensions. +The MCPB CLI provides tools for building MCP Bundles. ## Installation ```bash -npm install -g @anthropic-ai/dxt +npm install -g @anthropic-ai/mcpb ``` ``` -Usage: dxt [options] [command] +Usage: mcpb [options] [command] -Tools for building Desktop Extensions +Tools for building MCP Bundles Options: -V, --version output the version number -h, --help display help for command Commands: - init [directory] Create a new DXT extension manifest - validate Validate a DXT manifest file - pack [output] Pack a directory into a DXT extension - sign [options] Sign a DXT extension file - verify Verify the signature of a DXT extension file - info Display information about a DXT extension file - unsign Remove signature from a DXT extension file + init [directory] Create a new MCPB extension manifest + validate Validate a MCPB manifest file + pack [output] Pack a directory into a MCPB extension + sign [options] Sign a MCPB extension file + verify Verify the signature of a MCPB extension file + info Display information about a MCPB extension file + unsign Remove signature from a MCPB extension file help [command] display help for command ``` ## Commands -### `dxt init [directory]` +### `mcpb init [directory]` -Creates a new DXT extension manifest interactively. +Creates a new MCPB extension manifest interactively. ```bash # Initialize in current directory -dxt init +mcpb init # Initialize in a specific directory -dxt init my-extension/ +mcpb init my-extension/ ``` The command will prompt you for: @@ -58,53 +58,53 @@ The command will prompt you for: After creating the manifest, it provides helpful next steps based on your server type. -### `dxt validate ` +### `mcpb validate ` -Validates a DXT manifest file against the schema. You can provide either a direct path to a manifest.json file or a directory containing one. +Validates a MCPB manifest file against the schema. You can provide either a direct path to a manifest.json file or a directory containing one. ```bash # Validate specific manifest file -dxt validate manifest.json +mcpb validate manifest.json # Validate manifest in directory -dxt validate ./my-extension -dxt validate . +mcpb validate ./my-extension +mcpb validate . ``` -### `dxt pack [output]` +### `mcpb pack [output]` -Packs a directory into a DXT extension file. +Packs a directory into a MCPB extension file. ```bash -# Pack current directory into extension.dxt -dxt pack . +# Pack current directory into extension.mcpb +mcpb pack . # Pack with custom output filename -dxt pack my-extension/ my-extension-v1.0.dxt +mcpb pack my-extension/ my-extension-v1.0.mcpb ``` The command automatically: - Validates the manifest.json - Excludes common development files (.git, node_modules/.cache, .DS_Store, etc.) -- Creates a compressed .dxt file (ZIP with maximum compression) +- Creates a compressed .mcpb file (ZIP with maximum compression) -### `dxt sign ` +### `mcpb sign ` -Signs a DXT extension file with a certificate. +Signs a MCPB extension file with a certificate. ```bash # Sign with default certificate paths -dxt sign my-extension.dxt +mcpb sign my-extension.mcpb # Sign with custom certificate and key -dxt sign my-extension.dxt --cert /path/to/cert.pem --key /path/to/key.pem +mcpb sign my-extension.mcpb --cert /path/to/cert.pem --key /path/to/key.pem # Sign with intermediate certificates -dxt sign my-extension.dxt --cert cert.pem --key key.pem --intermediate intermediate1.pem intermediate2.pem +mcpb sign my-extension.mcpb --cert cert.pem --key key.pem --intermediate intermediate1.pem intermediate2.pem # Create and use a self-signed certificate -dxt sign my-extension.dxt --self-signed +mcpb sign my-extension.mcpb --self-signed ``` Options: @@ -114,12 +114,12 @@ Options: - `--intermediate, -i`: Paths to intermediate certificate files - `--self-signed`: Create a self-signed certificate if none exists -### `dxt verify ` +### `mcpb verify ` -Verifies the signature of a signed DXT extension file. +Verifies the signature of a signed MCPB extension file. ```bash -dxt verify my-extension.dxt +mcpb verify my-extension.mcpb ``` Output includes: @@ -130,12 +130,12 @@ Output includes: - Certificate fingerprint - Warning if self-signed -### `dxt info ` +### `mcpb info ` -Displays information about a DXT extension file. +Displays information about a MCPB extension file. ```bash -dxt info my-extension.dxt +mcpb info my-extension.mcpb ``` Shows: @@ -144,12 +144,12 @@ Shows: - Signature status - Certificate details (if signed) -### `dxt unsign ` +### `mcpb unsign ` -Removes the signature from a DXT extension file (for development/testing). +Removes the signature from a MCPB extension file (for development/testing). ```bash -dxt unsign my-extension.dxt +mcpb unsign my-extension.mcpb ``` ## Certificate Requirements @@ -176,7 +176,7 @@ mkdir my-awesome-extension cd my-awesome-extension # 2. Initialize the extension -dxt init +mcpb init # 3. Follow the prompts to configure your extension # The tool will create a manifest.json with all necessary fields @@ -184,10 +184,10 @@ dxt init # 4. Create your server implementation based on the entry point you specified # 5. Pack the extension -dxt pack . +mcpb pack . # 6. (Optional) Sign the extension -dxt sign my-awesome-extension.dxt --self-signed +mcpb sign my-awesome-extension.mcpb --self-signed ``` ### Development Workflow @@ -197,8 +197,8 @@ dxt sign my-awesome-extension.dxt --self-signed mkdir my-extension cd my-extension -# 2. Initialize with dxt init or create manifest.json manually -dxt init +# 2. Initialize with mcpb init or create manifest.json manually +mcpb init # 3. Implement your server # For Node.js: create server/index.js @@ -206,35 +206,35 @@ dxt init # For Binary: add your executable # 4. Validate manifest -dxt validate manifest.json +mcpb validate manifest.json # 5. Pack extension -dxt pack . my-extension.dxt +mcpb pack . my-extension.mcpb # 6. (Optional) Sign for testing -dxt sign my-extension.dxt --self-signed +mcpb sign my-extension.mcpb --self-signed # 7. Verify signature -dxt verify my-extension.dxt +mcpb verify my-extension.mcpb # 8. Check extension info -dxt info my-extension.dxt +mcpb info my-extension.mcpb ``` ### Production Workflow ```bash # 1. Pack your extension -dxt pack my-extension/ +mcpb pack my-extension/ # 2. Sign with production certificate -dxt sign my-extension.dxt \ +mcpb sign my-extension.mcpb \ --cert production-cert.pem \ --key production-key.pem \ --intermediate intermediate-ca.pem root-ca.pem # 3. Verify before distribution -dxt verify my-extension.dxt +mcpb verify my-extension.mcpb ``` ## Excluded Files @@ -250,12 +250,12 @@ When packing an extension, the following files/patterns are automatically exclud - `.env.local`, `.env.*.local` - `package-lock.json`, `yarn.lock` -### Custom Exclusions with .dxtignore +### Custom Exclusions with .mcpbignore -You can create a `.dxtignore` file in your extension directory to specify additional files and patterns to exclude during packing. This works similar to `.npmignore` or `.gitignore`: +You can create a `.mcpbignore` file in your extension directory to specify additional files and patterns to exclude during packing. This works similar to `.npmignore` or `.gitignore`: ``` -# .dxtignore example +# .mcpbignore example # Comments start with # *.test.js src/**/*.test.ts @@ -266,7 +266,7 @@ temp/ docs/ ``` -The `.dxtignore` file supports: +The `.mcpbignore` file supports: - **Exact matches**: `filename.txt` - **Simple globs**: `*.log`, `temp/*` @@ -274,30 +274,30 @@ The `.dxtignore` file supports: - **Comments**: Lines starting with `#` are ignored - **Empty lines**: Blank lines are ignored -When a `.dxtignore` file is found, the CLI will display the number of additional patterns being applied. These patterns are combined with the default exclusion list. +When a `.mcpbignore` file is found, the CLI will display the number of additional patterns being applied. These patterns are combined with the default exclusion list. ## Technical Details ### Signature Format -DXT uses PKCS#7 (Cryptographic Message Syntax) for digital signatures: +MCPB uses PKCS#7 (Cryptographic Message Syntax) for digital signatures: - Signatures are stored in DER-encoded PKCS#7 SignedData format -- The signature is appended to the DXT file with markers (`DXT_SIG_V1` and `DXT_SIG_END`) -- The entire DXT content (excluding the signature block) is signed +- The signature is appended to the MCPB file with markers (`MCPB_SIG_V1` and `MCPB_SIG_END`) +- The entire MCPB content (excluding the signature block) is signed - Detached signature format - the original ZIP content remains unmodified ### Signature Structure ``` -[Original DXT ZIP content] -DXT_SIG_V1 +[Original MCPB ZIP content] +MCPB_SIG_V1 [Base64-encoded PKCS#7 signature] -DXT_SIG_END +MCPB_SIG_END ``` This approach allows: -- Backward compatibility (unsigned DXT files are valid ZIP files) +- Backward compatibility (unsigned MCPB files are valid ZIP files) - Easy signature verification and removal - Support for certificate chains with intermediate certificates diff --git a/MANIFEST.md b/MANIFEST.md index 6dc92d5..08d88fd 100644 --- a/MANIFEST.md +++ b/MANIFEST.md @@ -1,4 +1,4 @@ -# DXT Manifest.json Spec +# MCPB Manifest.json Spec Current version: `0.1` Last updated: 2025-06-17 @@ -11,7 +11,7 @@ A basic `manifest.json` with just the required fields looks like this: ```json { - "dxt_version": "0.1", // DXT spec version this manifest conforms to + "manifest_version": "0.1", // 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 { - "dxt_version": "0.1", + "manifest_version": "0.1", "name": "my-extension", "version": "1.0.0", "description": "A simple MCP extension", @@ -71,7 +71,7 @@ A full `manifest.json` with most of the optional fields looks like this: ```json { - "dxt_version": "0.1", + "manifest_version": "0.1", "name": "My MCP Extension", "display_name": "My Awesome MCP Extension", "version": "1.0.0", @@ -162,7 +162,7 @@ A full `manifest.json` with most of the optional fields looks like this: ### Required Fields -- **dxt_version**: Specification version this extension conforms to +- **manifest_version**: Specification version this extension conforms to - **name**: Machine-readable name (used for CLI, APIs) - **version**: Semantic version (semver) - **description**: Brief description @@ -190,7 +190,7 @@ A full `manifest.json` with most of the optional fields looks like this: ## Compatibility -The `compatibility` object specifies all requirements for running the extension. All fields, including the `compatibility` field itself, are optional. If you specify nothing, clients implementing DXT are encouraged to run the extension on any system. +The `compatibility` object specifies all requirements for running the extension. All fields, including the `compatibility` field itself, are optional. If you specify nothing, clients implementing MCPB are encouraged to run the extension on any system. ```json { @@ -291,7 +291,7 @@ The `server` object defines how to run the MCP server: 1. **Python**: `server.type = "python"` - Requires `entry_point` to Python file - - All dependencies must be bundled in the DXT + - All dependencies must be bundled in the MCPB - Can use `server/lib` for packages or `server/venv` for full virtual environment - Python runtime version specified in `compatibility.runtimes.python` diff --git a/README.md b/README.md index e08dee5..1d768f2 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,157 @@ -# Desktop Extensions (DXT) +# MCP Bundles (MCPB) -Desktop Extensions (`.dxt`) are zip archives containing a local MCP server and a `manifest.json` that describes the server and its capabilities. The format is spiritually similar to Chrome extensions (`.crx`) or VS Code extensions (`.vsix`), enabling end users to install local MCP servers with a single click. +> **⚠️ 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` -This repository provides three components: The extension specification in [MANIFEST.md](MANIFEST.md), a CLI tool for creating extensions (see [CLI.md](CLI.md)), and the code used by Claude for macOS and Windows to load and verify DXT extensions ([src/index.ts](src/index.ts)). +MCP Bundles (`.mcpb`) are zip archives containing a local MCP server and a `manifest.json` that describes the server and its capabilities. The format is spiritually similar to Chrome extensions (`.crx`) or VS Code extensions (`.vsix`), enabling end users to install local MCP servers with a single click. + +This repository provides three components: The bundle specification in [MANIFEST.md](MANIFEST.md), a CLI tool for creating bundles (see [CLI.md](CLI.md)), and the code used by Claude for macOS and Windows to load and verify MCPB bundles ([src/index.ts](src/index.ts)). - For developers of local MCP servers, we aim to make distribution and installation of said servers convenient -- For developers of apps supporting local MCP servers, we aim to make it easy to add support for DXT extensions +- For developers of apps supporting local MCP servers, we aim to make it easy to add support for MCPB bundles -Claude for macOS and Windows uses the code in this repository to enable single-click installation of local MCP servers, including a number of end user-friendly features - such as automatic updates, easy configuration of MCP servers and the variables and parameters they need, and a curated directory. We are committed to the open ecosystem around MCP servers and believe that its ability to be universally adopted by multiple applications and services has benefits developers aiming to connect AI tools to other apps and services. Consequently, we’re open-sourcing the Desktop Extension specification, toolchain, and the schemas and key functions used by Claude for macOS and Windows to implement its own support of Desktop Extensions. It is our hope that the `dxt` format doesn’t just make local MCP servers more portable for Claude, but other AI desktop applications, too. +Claude for macOS and Windows uses the code in this repository to enable single-click installation of local MCP servers, including a number of end user-friendly features - such as automatic updates, easy configuration of MCP servers and the variables and parameters they need, and a curated directory. We are committed to the open ecosystem around MCP servers and believe that its ability to be universally adopted by multiple applications and services has benefits developers aiming to connect AI tools to other apps and services. Consequently, we're open-sourcing the MCP Bundle specification, toolchain, and the schemas and key functions used by Claude for macOS and Windows to implement its own support of MCP Bundles. It is our hope that the `mcpb` format doesn't just make local MCP servers more portable for Claude, but other AI desktop applications, too. -# For Extension Developers +# For Bundle Developers -At the core, DXT are simple zip files containing your entire MCP server and a `manifest.json`. Consequently, turning a local MCP server into an extension is straightforward: You just have to put all your required files in a folder, create a `manifest.json`, and then create an archive. +At the core, MCPB are simple zip files containing your entire MCP server and a `manifest.json`. Consequently, turning a local MCP server into a bundle is straightforward: You just have to put all your required files in a folder, create a `manifest.json`, and then create an archive. -To make this process easier, this package offers a CLI that helps you with the creation of both the `manifest.json` and the final `.dxt` file. To install it, run: +To make this process easier, this package offers a CLI that helps you with the creation of both the `manifest.json` and the final `.mcpb` file. To install it, run: ```sh -npm install -g @anthropic-ai/dxt +npm install -g @anthropic-ai/mcpb ``` -1. In a folder containing your local MCP server, run `dxt init`. This command will guide you through the creation of a `manifest.json`. -2. Run `dxt pack` to create a `dxt` file. -3. Now, any app implementing support for DXT can run your local MCP server. As an example, open the file with Claude for macOS and Windows to show an installation dialog. +1. In a folder containing your local MCP server, run `mcpb init`. This command will guide you through the creation of a `manifest.json`. +2. Run `mcpb pack` to create a `mcpb` file. +3. Now, any app implementing support for MCPB can run your local MCP server. As an example, open the file with Claude for macOS and Windows to show an installation dialog. -You can find the full spec for the `manifest.json` and all its mandatory and optional fields in [MANIFEST.md](MANIFEST.md). Examples for extensions can be found in [examples](./examples/). +You can find the full spec for the `manifest.json` and all its mandatory and optional fields in [MANIFEST.md](MANIFEST.md). Examples for bundles can be found in [examples](./examples/). ## Prompt Template for AI Tools -AI tools like Claude Code are particularly good at creating desktop extensions when informed about the spec. When prompting an AI coding tool to build an extension, briefly explain what your extension aims to do - then add the following context to your instructions. +AI tools like Claude Code are particularly good at creating MCP bundles when informed about the spec. When prompting an AI coding tool to build a bundle, briefly explain what your bundle aims to do - then add the following context to your instructions. -> I want to build this as a Desktop Extension, abbreviated as "DXT". Please follow these steps: +> I want to build this as a MCP Bundle, abbreviated as "MCPB". Please follow these steps: > > 1. **Read the specifications thoroughly:** -> - https://github.com/anthropics/dxt/blob/main/README.md - DXT architecture overview, capabilities, and integration +> - https://github.com/anthropics/mcpb/blob/main/README.md - MCPB architecture overview, capabilities, and integration > patterns -> - https://github.com/anthropics/dxt/blob/main/MANIFEST.md - Complete extension manifest structure and field definitions -> - https://github.com/anthropics/dxt/tree/main/examples - Reference implementations including a "Hello World" example -> 2. **Create a proper extension structure:** +> - https://github.com/anthropics/mcpb/blob/main/MANIFEST.md - Complete bundle manifest structure and field definitions +> - https://github.com/anthropics/mcpb/tree/main/examples - Reference implementations including a "Hello World" example +> 2. **Create a proper bundle structure:** > - Generate a valid manifest.json following the MANIFEST.md spec > - Implement an MCP server using @modelcontextprotocol/sdk with proper tool definitions > - Include proper error handling, security measures, and timeout management > 3. **Follow best development practices:** > - Implement proper MCP protocol communication via stdio transport > - Structure tools with clear schemas, validation, and consistent JSON responses -> - Make use of the fact that this extension will be running locally +> - Make use of the fact that this bundle will be running locally > - Add appropriate logging and debugging capabilities > - Include proper documentation and setup instructions > 4. **Test considerations:** > - Validate that all tool calls return properly structured responses > - Verify manifest loads correctly and host integration works > -> Generate complete, production-ready code that can be immediately tested. Focus on defensive programming, clear error messages, and following the exact DXT specifications to ensure compatibility with the ecosystem. +> Generate complete, production-ready code that can be immediately tested. Focus on defensive programming, clear error messages, and following the exact MCPB specifications to ensure compatibility with the ecosystem. ## Directory Structures -### Minimal Extension +### Minimal Bundle A `manifest.json` is the only required file. -### Example: Node.js Extension +### Example: Node.js Bundle ``` -extension.dxt (ZIP file) -├── manifest.json # Required: Extension metadata and configuration +bundle.mcpb (ZIP file) +├── manifest.json # Required: Bundle metadata and configuration ├── server/ # Server files │ └── index.js # Main entry point ├── node_modules/ # Bundled dependencies ├── package.json # Optional: NPM package definition -├── icon.png # Optional: Extension icon +├── icon.png # Optional: Bundle icon └── assets/ # Optional: Additional assets ``` -### Example: Python Extension +### Example: Python Bundle ``` -extension.dxt (ZIP file) -├── manifest.json # Required: Extension metadata and configuration +bundle.mcpb (ZIP file) +├── manifest.json # Required: Bundle metadata and configuration ├── server/ # Server files │ ├── main.py # Main entry point │ └── utils.py # Additional modules ├── lib/ # Bundled Python packages ├── requirements.txt # Optional: Python dependencies list -└── icon.png # Optional: Extension icon +└── icon.png # Optional: Bundle icon ``` -### Example: Binary Extension +### Example: Binary Bundle ``` -extension.dxt (ZIP file) -├── manifest.json # Required: Extension metadata and configuration +bundle.mcpb (ZIP file) +├── manifest.json # Required: Bundle metadata and configuration ├── server/ # Server files │ ├── my-server # Unix executable │ ├── my-server.exe # Windows executable -└── icon.png # Optional: Extension icon +└── icon.png # Optional: Bundle icon ``` ### Bundling Dependencies -**Python Extensions:** +**Python Bundles:** - 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` -**Node.js Extensions:** +**Node.js Bundles:** - Run `npm install --production` to create `node_modules` -- Bundle the entire `node_modules` directory with your extension +- Bundle the entire `node_modules` directory with your bundle - Use `npm ci` or `yarn install --frozen-lockfile` for reproducible builds - Server entry point specified in manifest.json's `server.entry_point` -**Binary Extensions:** +**Binary Bundles:** - Static linking preferred for maximum compatibility - Include all required shared libraries if dynamic linking used - Test on clean systems without development tools + +# Contributing + +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. + +## Development Setup + +```sh +# Clone the repository +git clone https://github.com/anthropics/mcpb.git +cd mcpb + +# Install dependencies +npm install + +# Build the project +npm run build + +# Run tests +npm test +``` + +## Release Process + +1. Update version in `package.json` +2. Create a pull request with version bump +3. After merge, create a GitHub release +4. Package will be automatically published to npm + +# License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index ca2ce4a..24480ed 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,12 @@ -# DXT Examples +# MCPB Examples -This directory contains example Desktop Extensions that demonstrate the DXT format and manifest structure. These are **reference implementations** designed to illustrate how to build DXT extensions. +This directory contains example MCP Bundles that demonstrate the MCPB format and manifest structure. These are **reference implementations** designed to illustrate how to build MCPB extensions. ## ⚠️ Not Production Ready **Important:** These examples are **NOT intended for production use**. They serve as: -- Demonstrations of the DXT manifest format +- Demonstrations of the MCPB manifest format - Templates for building your own extensions - Simple MCP server implementations for testing diff --git a/examples/chrome-applescript/manifest.json b/examples/chrome-applescript/manifest.json index b9fbcdd..cb9261e 100644 --- a/examples/chrome-applescript/manifest.json +++ b/examples/chrome-applescript/manifest.json @@ -1,11 +1,11 @@ { - "$schema": "../../dist/dxt-manifest.schema.json", - "dxt_version": "0.1", + "$schema": "../../dist/mcpb-manifest.schema.json", + "manifest_version": "0.1", "name": "chrome-applescript", "display_name": "Control Chrome with AppleScript", "version": "0.1.0", "description": "Control Google Chrome browser tabs, windows, and navigation", - "long_description": "This extension allows you to control Google Chrome browser tabs, windows, and navigation through Chrome's official AppleScript API. You can open URLs, manage tabs, execute JavaScript, and retrieve page content.\n\nThis extension is meant to be a demonstration of Desktop Extensions.\n\nThis extension is not affiliated with Google or Chrome.", + "long_description": "This extension allows you to control Google Chrome browser tabs, windows, and navigation through Chrome's official AppleScript API. You can open URLs, manage tabs, execute JavaScript, and retrieve page content.\n\nThis extension is meant to be a demonstration of MCP Bundles.\n\nThis extension is not affiliated with Google or Chrome.", "author": { "name": "Anthropic", "email": "support@anthropic.com", diff --git a/examples/file-manager-python/manifest.json b/examples/file-manager-python/manifest.json index 1c9f610..89546f3 100644 --- a/examples/file-manager-python/manifest.json +++ b/examples/file-manager-python/manifest.json @@ -1,11 +1,11 @@ { - "$schema": "../../dist/dxt-manifest.schema.json", - "dxt_version": "0.1", + "$schema": "../../dist/mcpb-manifest.schema.json", + "manifest_version": "0.1", "name": "file-manager-python", "display_name": "Python File Manager MCP", "version": "0.1.0", "description": "A Python MCP server for file operations", - "long_description": "This extension provides file management capabilities through a Python MCP server. It demonstrates Python-based Desktop Extension development, including file operations, directory management, and proper MCP protocol implementation.", + "long_description": "This extension provides file management capabilities through a Python MCP server. It demonstrates Python-based MCP Bundle development, including file operations, directory management, and proper MCP protocol implementation.", "author": { "name": "Anthropic", "email": "support@anthropic.com", diff --git a/examples/file-system-node/manifest.json b/examples/file-system-node/manifest.json index e7d935e..890ecdb 100644 --- a/examples/file-system-node/manifest.json +++ b/examples/file-system-node/manifest.json @@ -1,5 +1,5 @@ { - "dxt_version": "0.1", + "manifest_version": "0.1", "id": "anthropic.demo.filesystem", "name": "Filesystem", "display_name": "Filesystem", diff --git a/examples/hello-world-node/manifest.json b/examples/hello-world-node/manifest.json index 557f67c..30837eb 100644 --- a/examples/hello-world-node/manifest.json +++ b/examples/hello-world-node/manifest.json @@ -1,11 +1,11 @@ { - "$schema": "../../dist/dxt-manifest.schema.json", - "dxt_version": "0.1", + "$schema": "../../dist/mcpb-manifest.schema.json", + "manifest_version": "0.1", "name": "hello-world-node", "display_name": "Hello World MCP Server (Reference Extension)", "version": "0.1.0", "description": "A reference MCP extension demonstrating best practices and all available features", - "long_description": "This is a reference implementation of a Desktop Extension (DXT). It demonstrates all available manifest features, user configuration options, and security best practices. Use this extension as a template when creating your own MCP servers. The extension includes examples of all user configuration types, proper request token verification, and comprehensive metadata fields.", + "long_description": "This is a reference implementation of a MCP Bundle (DXT). It demonstrates all available manifest features, user configuration options, and security best practices. Use this extension as a template when creating your own MCP servers. The extension includes examples of all user configuration types, proper request token verification, and comprehensive metadata fields.", "author": { "name": "Acme Inc", "email": "support@acme.void", diff --git a/package.json b/package.json index 0bab1fa..04844f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@anthropic-ai/dxt", - "description": "Tools for building Desktop Extensions", - "version": "0.2.6", + "name": "@anthropic-ai/mcpb", + "description": "Tools for building MCP Bundles", + "version": "1.0.0", "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -29,7 +29,7 @@ } }, "bin": { - "dxt": "dist/cli/cli.js" + "mcpb": "dist/cli/cli.js" }, "files": [ "dist" @@ -37,7 +37,7 @@ "scripts": { "build": "yarn run build:code", "build:code": "tsc", - "build:schema": "node ./scripts/build-dxt-schema.js", + "build:schema": "node ./scripts/build-mcpb-schema.js", "dev": "tsc --watch", "test": "jest", "test:watch": "jest --watch", diff --git a/scripts/build-dxt-schema.js b/scripts/build-mcpb-schema.js similarity index 73% rename from scripts/build-dxt-schema.js rename to scripts/build-mcpb-schema.js index b9c0c74..b24856f 100644 --- a/scripts/build-dxt-schema.js +++ b/scripts/build-mcpb-schema.js @@ -1,11 +1,11 @@ -import { DxtManifestSchema, DxtSignatureInfoSchema } 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"; const schemasToWrite = { - "dxt-manifest": DxtManifestSchema, - "dxt-signature-info": DxtSignatureInfoSchema, + "mcpb-manifest": McpbManifestSchema, + "mcpb-signature-info": McpbSignatureInfoSchema, }; await fs.mkdir(path.join(import.meta.dirname, "../dist"), { recursive: true }); diff --git a/src/cli/cli.ts b/src/cli/cli.ts index b9a7fd0..ac38b52 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -6,8 +6,8 @@ import { existsSync, readFileSync, statSync } from "fs"; import { basename, dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; -import { signDxtFile, unsignDxtFile, verifyDxtFile } from "../node/sign.js"; -import { cleanDxt, validateManifest } from "../node/validate.js"; +import { signMcpbFile, unsignMcpbFile, verifyMcpbFile } from "../node/sign.js"; +import { cleanMcpb, validateManifest } from "../node/validate.js"; import { initExtension } from "./init.js"; import { packExtension } from "./pack.js"; import { unpackExtension } from "./unpack.js"; @@ -22,10 +22,10 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); const version = packageJson.version; /** - * Create a self-signed certificate for signing DXT extensions + * Create a self-signed certificate for signing MCPB extensions */ function createSelfSignedCertificate(certPath: string, keyPath: string): void { - const subject = "/CN=DXT Self-Signed Certificate/O=DXT Extensions/C=US"; + const subject = "/CN=MCPB Self-Signed Certificate/O=MCPB Extensions/C=US"; try { // Generate a self-signed certificate valid for 10 years, no password @@ -42,14 +42,14 @@ function createSelfSignedCertificate(certPath: string, keyPath: string): void { const program = new Command(); program - .name("dxt") - .description("Tools for building Desktop Extensions") + .name("mcpb") + .description("Tools for building MCP Bundles") .version(version); // Init command program .command("init [directory]") - .description("Create a new DXT extension manifest") + .description("Create a new MCPB extension manifest") .option("-y, --yes", "Accept all defaults (non-interactive mode)") .action((directory?: string, options?: { yes?: boolean }) => { void (async () => { @@ -68,7 +68,7 @@ program // Validate command program .command("validate ") - .description("Validate a DXT manifest file") + .description("Validate an MCPB manifest file") .action((manifestPath: string) => { const success = validateManifest(manifestPath); process.exit(success ? 0 : 1); @@ -76,18 +76,18 @@ program // Clean command program - .command("clean ") + .command("clean ") .description( - "Cleans a DXT file, validates the manifest, and minimizes bundle size", + "Cleans an MCPB file, validates the manifest, and minimizes bundle size", ) - .action(async (dxtFile: string) => { - await cleanDxt(dxtFile); + .action(async (mcpbFile: string) => { + await cleanMcpb(mcpbFile); }); // Pack command program .command("pack [directory] [output]") - .description("Pack a directory into a DXT extension") + .description("Pack a directory into an MCPB extension") .action((directory: string = process.cwd(), output?: string) => { void (async () => { try { @@ -107,13 +107,13 @@ program // Unpack command program - .command("unpack [output]") - .description("Unpack a DXT extension file") - .action((dxtFile: string, output?: string) => { + .command("unpack [output]") + .description("Unpack an MCPB extension file") + .action((mcpbFile: string, output?: string) => { void (async () => { try { const success = await unpackExtension({ - dxtPath: dxtFile, + mcpbPath: mcpbFile, outputDir: output, }); process.exit(success ? 0 : 1); @@ -128,8 +128,8 @@ program // Sign command program - .command("sign ") - .description("Sign a DXT extension file") + .command("sign ") + .description("Sign an MCPB extension file") .option( "-c, --cert ", "Path to certificate file (PEM format)", @@ -147,7 +147,7 @@ program .option("--self-signed", "Create a self-signed certificate if none exists") .action( ( - dxtFile: string, + mcpbFile: string, options: { cert: string; key: string; @@ -157,10 +157,10 @@ program ) => { void (async () => { try { - const dxtPath = resolve(dxtFile); + const mcpbPath = resolve(mcpbFile); - if (!existsSync(dxtPath)) { - console.error(`ERROR: DXT file not found: ${dxtFile}`); + if (!existsSync(mcpbPath)) { + console.error(`ERROR: MCPB file not found: ${mcpbFile}`); process.exit(1); } @@ -169,9 +169,9 @@ program // Create self-signed certificate if requested if (options.selfSigned) { - const dxtDir = resolve(__dirname, ".."); - certPath = join(dxtDir, "self-signed-cert.pem"); - keyPath = join(dxtDir, "self-signed-key.pem"); + const mcpbDir = resolve(__dirname, ".."); + certPath = join(mcpbDir, "self-signed-cert.pem"); + keyPath = join(mcpbDir, "self-signed-key.pem"); if (!existsSync(certPath) || !existsSync(keyPath)) { console.log("Creating self-signed certificate..."); @@ -196,12 +196,12 @@ program } } - console.log(`Signing ${basename(dxtPath)}...`); - signDxtFile(dxtPath, certPath, keyPath, options.intermediate); - console.log(`Successfully signed ${basename(dxtPath)}`); + console.log(`Signing ${basename(mcpbPath)}...`); + signMcpbFile(mcpbPath, certPath, keyPath, options.intermediate); + console.log(`Successfully signed ${basename(mcpbPath)}`); // Display certificate info - const signatureInfo = await verifyDxtFile(dxtPath); + const signatureInfo = await verifyMcpbFile(mcpbPath); if ( signatureInfo.status === "signed" || signatureInfo.status === "self-signed" @@ -224,20 +224,20 @@ program // Verify command program - .command("verify ") - .description("Verify the signature of a DXT extension file") - .action((dxtFile: string) => { + .command("verify ") + .description("Verify the signature of an MCPB extension file") + .action((mcpbFile: string) => { void (async () => { try { - const dxtPath = resolve(dxtFile); + const mcpbPath = resolve(mcpbFile); - if (!existsSync(dxtPath)) { - console.error(`ERROR: DXT file not found: ${dxtFile}`); + if (!existsSync(mcpbPath)) { + console.error(`ERROR: MCPB file not found: ${mcpbFile}`); process.exit(1); } - console.log(`Verifying ${basename(dxtPath)}...`); - const result = await verifyDxtFile(dxtPath); + console.log(`Verifying ${basename(mcpbPath)}...`); + const result = await verifyMcpbFile(mcpbPath); if (result.status === "signed") { console.log(`Signature is valid`); @@ -270,24 +270,24 @@ program // Info command program - .command("info ") - .description("Display information about a DXT extension file") - .action((dxtFile: string) => { + .command("info ") + .description("Display information about an MCPB extension file") + .action((mcpbFile: string) => { void (async () => { try { - const dxtPath = resolve(dxtFile); + const mcpbPath = resolve(mcpbFile); - if (!existsSync(dxtPath)) { - console.error(`ERROR: DXT file not found: ${dxtFile}`); + if (!existsSync(mcpbPath)) { + console.error(`ERROR: MCPB file not found: ${mcpbFile}`); process.exit(1); } - const stat = statSync(dxtPath); - console.log(`File: ${basename(dxtPath)}`); + const stat = statSync(mcpbPath); + console.log(`File: ${basename(mcpbPath)}`); console.log(`Size: ${(stat.size / 1024).toFixed(2)} KB`); // Check if signed - const signatureInfo = await verifyDxtFile(dxtPath); + const signatureInfo = await verifyMcpbFile(mcpbPath); if (signatureInfo.status === "signed") { console.log(`\nSignature Information:`); console.log(` Subject: ${signatureInfo.publisher}`); @@ -311,7 +311,7 @@ program } } catch (error) { console.log( - `ERROR: Failed to read DXT info: ${error instanceof Error ? error.message : "Unknown error"}`, + `ERROR: Failed to read MCPB info: ${error instanceof Error ? error.message : "Unknown error"}`, ); process.exit(1); } @@ -320,19 +320,19 @@ program // Unsign command (for development/testing) program - .command("unsign ") - .description("Remove signature from a DXT extension file") - .action((dxtFile: string) => { + .command("unsign ") + .description("Remove signature from a MCPB bundle file") + .action((mcpbFile: string) => { try { - const dxtPath = resolve(dxtFile); + const mcpbPath = resolve(mcpbFile); - if (!existsSync(dxtPath)) { - console.error(`ERROR: DXT file not found: ${dxtFile}`); + if (!existsSync(mcpbPath)) { + console.error(`ERROR: MCPB file not found: ${mcpbFile}`); process.exit(1); } - console.log(`Removing signature from ${basename(dxtPath)}...`); - unsignDxtFile(dxtPath); + console.log(`Removing signature from ${basename(mcpbPath)}...`); + unsignMcpbFile(mcpbPath); console.log(`Signature removed`); } catch (error) { console.log( diff --git a/src/cli/init.ts b/src/cli/init.ts index fa29525..4f104be 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -2,7 +2,7 @@ import { confirm, input, select } from "@inquirer/prompts"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { basename, join, resolve } from "path"; -import type { DxtManifest } from "../types.js"; +import type { McpbManifest } from "../types.js"; interface PackageJson { name?: string; @@ -62,7 +62,7 @@ export function getDefaultBasicInfo( const authorName = getDefaultAuthorName(packageData) || "Unknown Author"; const displayName = name; const version = packageData.version || "1.0.0"; - const description = packageData.description || "A DXT extension"; + const description = packageData.description || "A MCPB bundle"; return { name, authorName, displayName, version, description }; } @@ -766,14 +766,14 @@ export function buildManifest( license: string; repository?: { type: string; url: string }; }, -): DxtManifest { +): McpbManifest { const { name, displayName, version, description, authorName } = basicInfo; const { authorEmail, authorUrl } = authorInfo; const { serverType, entryPoint, mcp_config } = serverConfig; const { keywords, license, repository } = optionalFields; return { - dxt_version: "0.1", + manifest_version: "0.1", name, ...(displayName && displayName !== name ? { display_name: displayName } @@ -822,7 +822,7 @@ export function printNextSteps() { console.log( `1. Ensure all your production dependencies are in this directory`, ); - console.log(`2. Run 'dxt pack' to create your .dxt file`); + console.log(`2. Run 'mcpb pack' to create your .mcpb file`); } export async function initExtension( @@ -851,7 +851,7 @@ export async function initExtension( if (!nonInteractive) { console.log( - "This utility will help you create a manifest.json file for your DXT extension.", + "This utility will help you create a manifest.json file for your MCPB bundle.", ); console.log("Press ^C at any time to quit.\n"); } else { diff --git a/src/cli/pack.ts b/src/cli/pack.ts index 4527867..d095e5c 100644 --- a/src/cli/pack.ts +++ b/src/cli/pack.ts @@ -11,9 +11,9 @@ import { } from "fs"; import { basename, join, relative, resolve, sep } from "path"; -import { getAllFilesWithCount, readDxtIgnorePatterns } from "../node/files.js"; +import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js"; import { validateManifest } from "../node/validate.js"; -import { DxtManifestSchema } from "../schemas.js"; +import { McpbManifestSchema } from "../schemas.js"; import { getLogger } from "../shared/log.js"; import { initExtension } from "./init.js"; @@ -92,7 +92,7 @@ export async function packExtension({ try { const manifestContent = readFileSync(manifestPath, "utf-8"); const manifestData = JSON.parse(manifestContent); - manifest = DxtManifestSchema.parse(manifestData); + manifest = McpbManifestSchema.parse(manifestData); } catch (error) { logger.error("ERROR: Failed to parse manifest.json"); if (error instanceof Error) { @@ -105,22 +105,22 @@ export async function packExtension({ const extensionName = basename(resolvedPath); const finalOutputPath = outputPath ? resolve(outputPath) - : resolve(`${extensionName}.dxt`); + : resolve(`${extensionName}.mcpb`); // Ensure output directory exists const outputDir = join(finalOutputPath, ".."); mkdirSync(outputDir, { recursive: true }); try { - // Read .dxtignore patterns if present - const dxtIgnorePatterns = readDxtIgnorePatterns(resolvedPath); + // Read .mcpbignore patterns if present + const mcpbIgnorePatterns = readMcpbIgnorePatterns(resolvedPath); // Get all files in the extension directory const { files, ignoredCount } = getAllFilesWithCount( resolvedPath, resolvedPath, {}, - dxtIgnorePatterns, + mcpbIgnorePatterns, ); // Print package header @@ -218,7 +218,7 @@ export async function packExtension({ // Print archive details const sanitizedName = sanitizeNameForFilename(manifest.name); - const archiveName = `${sanitizedName}-${manifest.version}.dxt`; + const archiveName = `${sanitizedName}-${manifest.version}.mcpb`; logger.log("\nArchive Details"); logger.log(`name: ${manifest.name}`); logger.log(`version: ${manifest.version}`); @@ -227,7 +227,7 @@ export async function packExtension({ logger.log(`unpacked size: ${formatFileSize(totalUnpackedSize)}`); logger.log(`shasum: ${shasum}`); logger.log(`total files: ${fileEntries.length}`); - logger.log(`ignored (.dxtignore) files: ${ignoredCount}`); + logger.log(`ignored (.mcpbignore) files: ${ignoredCount}`); logger.log(`\nOutput: ${finalOutputPath}`); return true; diff --git a/src/cli/unpack.ts b/src/cli/unpack.ts index 2c9c51c..2bf78c2 100644 --- a/src/cli/unpack.ts +++ b/src/cli/unpack.ts @@ -12,21 +12,21 @@ import { extractSignatureBlock } from "../node/sign.js"; import { getLogger } from "../shared/log.js"; interface UnpackOptions { - dxtPath: string; + mcpbPath: string; outputDir?: string; silent?: boolean; } export async function unpackExtension({ - dxtPath, + mcpbPath, outputDir, silent, }: UnpackOptions): Promise { const logger = getLogger({ silent }); - const resolvedDxtPath = resolve(dxtPath); + const resolvedMcpbPath = resolve(mcpbPath); - if (!existsSync(resolvedDxtPath)) { - logger.error(`ERROR: DXT file not found: ${dxtPath}`); + if (!existsSync(resolvedMcpbPath)) { + logger.error(`ERROR: MCPB file not found: ${mcpbPath}`); return false; } @@ -37,7 +37,7 @@ export async function unpackExtension({ } try { - const fileContent = readFileSync(resolvedDxtPath); + const fileContent = readFileSync(resolvedMcpbPath); const { originalContent } = extractSignatureBlock(fileContent); // Parse file attributes from ZIP central directory diff --git a/src/node/files.ts b/src/node/files.ts index 2adbafe..36e64f8 100644 --- a/src/node/files.ts +++ b/src/node/files.ts @@ -8,7 +8,7 @@ export const EXCLUDE_PATTERNS = [ "Thumbs.db", ".gitignore", ".git", - ".dxtignore", + ".mcpbignore", "*.log", ".env*", ".npm", @@ -33,30 +33,30 @@ export const EXCLUDE_PATTERNS = [ "yarn-error.log*", "package-lock.json", "yarn.lock", - "*.dxt", + "*.mcpb", "*.d.ts", "*.tsbuildinfo", "tsconfig.json", ]; /** - * Read and parse .dxtignore file patterns + * Read and parse .mcpbignore file patterns */ -export function readDxtIgnorePatterns(baseDir: string): string[] { - const dxtIgnorePath = join(baseDir, ".dxtignore"); - if (!existsSync(dxtIgnorePath)) { +export function readMcpbIgnorePatterns(baseDir: string): string[] { + const mcpbIgnorePath = join(baseDir, ".mcpbignore"); + if (!existsSync(mcpbIgnorePath)) { return []; } try { - const content = readFileSync(dxtIgnorePath, "utf-8"); + const content = readFileSync(mcpbIgnorePath, "utf-8"); return content .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("#")); } catch (error) { console.warn( - `Warning: Could not read .dxtignore file: ${error instanceof Error ? error.message : "Unknown error"}`, + `Warning: Could not read .mcpbignore file: ${error instanceof Error ? error.message : "Unknown error"}`, ); return []; } diff --git a/src/node/sign.ts b/src/node/sign.ts index f87c570..bb83389 100644 --- a/src/node/sign.ts +++ b/src/node/sign.ts @@ -6,30 +6,30 @@ import { tmpdir } from "os"; import { join } from "path"; import { promisify } from "util"; -import type { DxtSignatureInfo } from "../types.js"; +import type { McpbSignatureInfo } from "../types.js"; // Signature block markers -const SIGNATURE_HEADER = "DXT_SIG_V1"; -const SIGNATURE_FOOTER = "DXT_SIG_END"; +const SIGNATURE_HEADER = "MCPB_SIG_V1"; +const SIGNATURE_FOOTER = "MCPB_SIG_END"; const execFileAsync = promisify(execFile); /** - * Signs a DXT file with the given certificate and private key using PKCS#7 + * Signs a MCPB file with the given certificate and private key using PKCS#7 * - * @param dxtPath Path to the DXT file to sign + * @param mcpbPath Path to the MCPB file to sign * @param certPath Path to the certificate file (PEM format) * @param keyPath Path to the private key file (PEM format) * @param intermediates Optional array of intermediate certificate paths */ -export function signDxtFile( - dxtPath: string, +export function signMcpbFile( + mcpbPath: string, certPath: string, keyPath: string, intermediates?: string[], ): void { - // Read the original DXT file - const dxtContent = readFileSync(dxtPath); + // Read the original MCPB file + const mcpbContent = readFileSync(mcpbPath); // Read certificate and key const certificatePem = readFileSync(certPath, "utf-8"); @@ -42,7 +42,7 @@ export function signDxtFile( // Create PKCS#7 signed data const p7 = forge.pkcs7.createSignedData(); - p7.content = forge.util.createBuffer(dxtContent); + p7.content = forge.util.createBuffer(mcpbContent); // Parse and add certificates const signingCert = forge.pki.certificateFromPem(certificatePem); @@ -88,22 +88,22 @@ export function signDxtFile( // Create signature block with PKCS#7 data const signatureBlock = createSignatureBlock(pkcs7Signature); - // Append signature block to DXT file - const signedContent = Buffer.concat([dxtContent, signatureBlock]); - writeFileSync(dxtPath, signedContent); + // Append signature block to MCPB file + const signedContent = Buffer.concat([mcpbContent, signatureBlock]); + writeFileSync(mcpbPath, signedContent); } /** - * Verifies a signed DXT file using OS certificate store + * Verifies a signed MCPB file using OS certificate store * - * @param dxtPath Path to the signed DXT file + * @param mcpbPath Path to the signed MCPB file * @returns Signature information including verification status */ -export async function verifyDxtFile( - dxtPath: string, -): Promise { +export async function verifyMcpbFile( + mcpbPath: string, +): Promise { try { - const fileContent = readFileSync(dxtPath); + const fileContent = readFileSync(mcpbPath); // Find and extract signature block const { originalContent, pkcs7Signature } = @@ -213,7 +213,7 @@ export async function verifyDxtFile( .toHex(), }; } catch (error) { - throw new Error(`Failed to verify DXT file: ${error}`); + throw new Error(`Failed to verify MCPB file: ${error}`); } } @@ -239,7 +239,7 @@ function createSignatureBlock(pkcs7Signature: Buffer): Buffer { } /** - * Extracts the signature block from a signed DXT file + * Extracts the signature block from a signed MCPB file */ export function extractSignatureBlock(fileContent: Buffer): { originalContent: Buffer; @@ -302,7 +302,7 @@ export async function verifyCertificateChain( let tempDir: string | null = null; try { - tempDir = await mkdtemp(join(tmpdir(), "dxt-verify-")); + tempDir = await mkdtemp(join(tmpdir(), "mcpb-verify-")); const certChainPath = join(tempDir, "chain.pem"); const certChain = [certificate, ...(intermediates || [])].join("\n"); await writeFile(certChainPath, certChain); @@ -400,10 +400,10 @@ export async function verifyCertificateChain( } /** - * Removes signature from a DXT file + * Removes signature from a MCPB file */ -export function unsignDxtFile(dxtPath: string): void { - const fileContent = readFileSync(dxtPath); +export function unsignMcpbFile(mcpbPath: string): void { + const fileContent = readFileSync(mcpbPath); const { originalContent } = extractSignatureBlock(fileContent); - writeFileSync(dxtPath, originalContent); + writeFileSync(mcpbPath, originalContent); } diff --git a/src/node/validate.ts b/src/node/validate.ts index 0dd6d79..19727ff 100644 --- a/src/node/validate.ts +++ b/src/node/validate.ts @@ -6,8 +6,8 @@ import { join, resolve } from "path"; import prettyBytes from "pretty-bytes"; import { unpackExtension } from "../cli/unpack.js"; -import { DxtManifestSchema } from "../schemas.js"; -import { DxtManifestSchema as LooseDxtManifestSchema } from "../schemas-loose.js"; +import { McpbManifestSchema } from "../schemas.js"; +import { McpbManifestSchema as LooseMcpbManifestSchema } from "../schemas-loose.js"; export function validateManifest(inputPath: string): boolean { try { @@ -22,7 +22,7 @@ export function validateManifest(inputPath: string): boolean { const manifestContent = readFileSync(manifestPath, "utf-8"); const manifestData = JSON.parse(manifestContent); - const result = DxtManifestSchema.safeParse(manifestData); + const result = McpbManifestSchema.safeParse(manifestData); if (result.success) { console.log("Manifest is valid!"); @@ -57,26 +57,26 @@ export function validateManifest(inputPath: string): boolean { } } -export async function cleanDxt(inputPath: string) { - const tmpDir = await fs.mkdtemp(resolve(os.tmpdir(), "dxt-clean-")); - const dxtPath = resolve(tmpDir, "in.dxt"); +export async function cleanMcpb(inputPath: string) { + const tmpDir = await fs.mkdtemp(resolve(os.tmpdir(), "mcpb-clean-")); + const mcpbPath = resolve(tmpDir, "in.mcpb"); const unpackPath = resolve(tmpDir, "out"); - console.log(" -- Cleaning DXT..."); + console.log(" -- Cleaning MCPB..."); try { - await fs.copyFile(inputPath, dxtPath); - console.log(" -- Unpacking DXT..."); - await unpackExtension({ dxtPath, silent: true, outputDir: unpackPath }); + await fs.copyFile(inputPath, mcpbPath); + console.log(" -- Unpacking MCPB..."); + await unpackExtension({ mcpbPath, silent: true, outputDir: unpackPath }); const manifestPath = resolve(unpackPath, "manifest.json"); const originalManifest = await fs.readFile(manifestPath, "utf-8"); const manifestData = JSON.parse(originalManifest); - const result = LooseDxtManifestSchema.safeParse(manifestData); + const result = LooseMcpbManifestSchema.safeParse(manifestData); if (!result.success) { throw new Error( - `Unrecoverable manifest issues, please run "dxt validate"`, + `Unrecoverable manifest issues, please run "mcpb validate"`, ); } await fs.writeFile(manifestPath, JSON.stringify(result.data, null, 2)); @@ -85,9 +85,9 @@ export async function cleanDxt(inputPath: string) { originalManifest.trim() !== (await fs.readFile(manifestPath, "utf8")).trim() ) { - console.log(" -- Update manifest to be valid per DXT schema"); + console.log(" -- Update manifest to be valid per MCPB schema"); } else { - console.log(" -- Manifest already valid per DXT schema"); + console.log(" -- Manifest already valid per MCPB schema"); } const nodeModulesPath = resolve(unpackPath, "node_modules"); diff --git a/src/schemas-loose.ts b/src/schemas-loose.ts index 151286f..882b1a5 100644 --- a/src/schemas-loose.ts +++ b/src/schemas-loose.ts @@ -6,33 +6,33 @@ export const McpServerConfigSchema = z.object({ env: z.record(z.string(), z.string()).optional(), }); -export const DxtManifestAuthorSchema = z.object({ +export const McpbManifestAuthorSchema = z.object({ name: z.string(), email: z.string().email().optional(), url: z.string().url().optional(), }); -export const DxtManifestRepositorySchema = z.object({ +export const McpbManifestRepositorySchema = z.object({ type: z.string(), url: z.string().url(), }); -export const DxtManifestPlatformOverrideSchema = +export const McpbManifestPlatformOverrideSchema = McpServerConfigSchema.partial(); -export const DxtManifestMcpConfigSchema = McpServerConfigSchema.extend({ +export const McpbManifestMcpConfigSchema = McpServerConfigSchema.extend({ platform_overrides: z - .record(z.string(), DxtManifestPlatformOverrideSchema) + .record(z.string(), McpbManifestPlatformOverrideSchema) .optional(), }); -export const DxtManifestServerSchema = z.object({ +export const McpbManifestServerSchema = z.object({ type: z.enum(["python", "node", "binary"]), entry_point: z.string(), - mcp_config: DxtManifestMcpConfigSchema, + mcp_config: McpbManifestMcpConfigSchema, }); -export const DxtManifestCompatibilitySchema = z +export const McpbManifestCompatibilitySchema = z .object({ claude_desktop: z.string().optional(), platforms: z.array(z.enum(["darwin", "win32", "linux"])).optional(), @@ -45,19 +45,19 @@ export const DxtManifestCompatibilitySchema = z }) .passthrough(); -export const DxtManifestToolSchema = z.object({ +export const McpbManifestToolSchema = z.object({ name: z.string(), description: z.string().optional(), }); -export const DxtManifestPromptSchema = z.object({ +export const McpbManifestPromptSchema = z.object({ name: z.string(), description: z.string().optional(), arguments: z.array(z.string()).optional(), text: z.string(), }); -export const DxtUserConfigurationOptionSchema = z.object({ +export const McpbUserConfigurationOptionSchema = z.object({ type: z.enum(["string", "number", "boolean", "directory", "file"]), title: z.string(), description: z.string(), @@ -71,40 +71,48 @@ export const DxtUserConfigurationOptionSchema = z.object({ max: z.number().optional(), }); -export const DxtUserConfigValuesSchema = z.record( +export const McpbUserConfigValuesSchema = z.record( z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]), ); -export const DxtManifestSchema = z.object({ - $schema: z.string().optional(), - dxt_version: z.string(), +export const McpbManifestSchema = z + .object({ + $schema: z.string().optional(), + 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: DxtManifestAuthorSchema, - repository: DxtManifestRepositorySchema.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: DxtManifestServerSchema, - tools: z.array(DxtManifestToolSchema).optional(), + server: McpbManifestServerSchema, + tools: z.array(McpbManifestToolSchema).optional(), tools_generated: z.boolean().optional(), - prompts: z.array(DxtManifestPromptSchema).optional(), + prompts: z.array(McpbManifestPromptSchema).optional(), prompts_generated: z.boolean().optional(), keywords: z.array(z.string()).optional(), license: z.string().optional(), - compatibility: DxtManifestCompatibilitySchema.optional(), + compatibility: McpbManifestCompatibilitySchema.optional(), user_config: z - .record(z.string(), DxtUserConfigurationOptionSchema) + .record(z.string(), McpbUserConfigurationOptionSchema) .optional(), -}); + }) + .refine( + (data) => !!(data.dxt_version || data.manifest_version), + { + message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + } + ); -export const DxtSignatureInfoSchema = z.object({ +export const McpbSignatureInfoSchema = z.object({ status: z.enum(["signed", "unsigned", "self-signed"]), publisher: z.string().optional(), issuer: z.string().optional(), diff --git a/src/schemas.ts b/src/schemas.ts index e444fe4..2b59709 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -6,33 +6,33 @@ export const McpServerConfigSchema = z.strictObject({ env: z.record(z.string(), z.string()).optional(), }); -export const DxtManifestAuthorSchema = z.strictObject({ +export const McpbManifestAuthorSchema = z.strictObject({ name: z.string(), email: z.string().email().optional(), url: z.string().url().optional(), }); -export const DxtManifestRepositorySchema = z.strictObject({ +export const McpbManifestRepositorySchema = z.strictObject({ type: z.string(), url: z.string().url(), }); -export const DxtManifestPlatformOverrideSchema = +export const McpbManifestPlatformOverrideSchema = McpServerConfigSchema.partial(); -export const DxtManifestMcpConfigSchema = McpServerConfigSchema.extend({ +export const McpbManifestMcpConfigSchema = McpServerConfigSchema.extend({ platform_overrides: z - .record(z.string(), DxtManifestPlatformOverrideSchema) + .record(z.string(), McpbManifestPlatformOverrideSchema) .optional(), }); -export const DxtManifestServerSchema = z.strictObject({ +export const McpbManifestServerSchema = z.strictObject({ type: z.enum(["python", "node", "binary"]), entry_point: z.string(), - mcp_config: DxtManifestMcpConfigSchema, + mcp_config: McpbManifestMcpConfigSchema, }); -export const DxtManifestCompatibilitySchema = z +export const McpbManifestCompatibilitySchema = z .strictObject({ claude_desktop: z.string().optional(), platforms: z.array(z.enum(["darwin", "win32", "linux"])).optional(), @@ -45,19 +45,19 @@ export const DxtManifestCompatibilitySchema = z }) .passthrough(); -export const DxtManifestToolSchema = z.strictObject({ +export const McpbManifestToolSchema = z.strictObject({ name: z.string(), description: z.string().optional(), }); -export const DxtManifestPromptSchema = z.strictObject({ +export const McpbManifestPromptSchema = z.strictObject({ name: z.string(), description: z.string().optional(), arguments: z.array(z.string()).optional(), text: z.string(), }); -export const DxtUserConfigurationOptionSchema = z.strictObject({ +export const McpbUserConfigurationOptionSchema = z.strictObject({ type: z.enum(["string", "number", "boolean", "directory", "file"]), title: z.string(), description: z.string(), @@ -71,40 +71,48 @@ export const DxtUserConfigurationOptionSchema = z.strictObject({ max: z.number().optional(), }); -export const DxtUserConfigValuesSchema = z.record( +export const McpbUserConfigValuesSchema = z.record( z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]), ); -export const DxtManifestSchema = z.strictObject({ - $schema: z.string().optional(), - dxt_version: z.string(), - name: z.string(), - display_name: z.string().optional(), - version: z.string(), - description: z.string(), - long_description: z.string().optional(), - author: DxtManifestAuthorSchema, - repository: DxtManifestRepositorySchema.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: DxtManifestServerSchema, - tools: z.array(DxtManifestToolSchema).optional(), - tools_generated: z.boolean().optional(), - prompts: z.array(DxtManifestPromptSchema).optional(), - prompts_generated: z.boolean().optional(), - keywords: z.array(z.string()).optional(), - license: z.string().optional(), - compatibility: DxtManifestCompatibilitySchema.optional(), - user_config: z - .record(z.string(), DxtUserConfigurationOptionSchema) - .optional(), -}); +export const McpbManifestSchema = z + .strictObject({ + $schema: z.string().optional(), + 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(), + }) + .refine( + (data) => !!(data.dxt_version || data.manifest_version), + { + message: "Either 'dxt_version' (deprecated) or 'manifest_version' must be provided", + } + ); -export const DxtSignatureInfoSchema = z.strictObject({ +export const McpbSignatureInfoSchema = z.strictObject({ status: z.enum(["signed", "unsigned", "self-signed"]), publisher: z.string().optional(), issuer: z.string().optional(), diff --git a/src/shared/config.ts b/src/shared/config.ts index 83fc696..20e4262 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -1,12 +1,12 @@ import type { - DxtManifest, - DxtUserConfigValues, Logger, + McpbManifest, + McpbUserConfigValues, McpServerConfig, } from "../types.js"; /** - * This file contains utility functions for handling DXT configuration, + * This file contains utility functions for handling MCPB configuration, * including variable replacement and MCP server configuration generation. */ @@ -85,10 +85,10 @@ export function replaceVariables( } interface GetMcpConfigForManifestOptions { - manifest: DxtManifest; + manifest: McpbManifest; extensionPath: string; systemDirs: Record; - userConfig: DxtUserConfigValues; + userConfig: McpbUserConfigValues; pathSeparator: string; logger?: Logger; } @@ -179,8 +179,8 @@ export async function getMcpConfigForManifest( } interface HasRequiredConfigMissingOptions { - manifest: DxtManifest; - userConfig?: DxtUserConfigValues; + manifest: McpbManifest; + userConfig?: McpbUserConfigValues; } function isInvalidSingleValue(value: unknown): boolean { diff --git a/src/types.ts b/src/types.ts index 5813089..edd3f8c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,55 +1,57 @@ import type * as z from "zod"; import type { - DxtManifestAuthorSchema, - DxtManifestCompatibilitySchema, - DxtManifestMcpConfigSchema, - DxtManifestPlatformOverrideSchema, - DxtManifestPromptSchema, - DxtManifestRepositorySchema, - DxtManifestSchema, - DxtManifestServerSchema, - DxtManifestToolSchema, - DxtSignatureInfoSchema, - DxtUserConfigurationOptionSchema, - DxtUserConfigValuesSchema, + McpbManifestAuthorSchema, + McpbManifestCompatibilitySchema, + McpbManifestMcpConfigSchema, + McpbManifestPlatformOverrideSchema, + McpbManifestPromptSchema, + McpbManifestRepositorySchema, + McpbManifestSchema, + McpbManifestServerSchema, + McpbManifestToolSchema, + McpbSignatureInfoSchema, + McpbUserConfigurationOptionSchema, + McpbUserConfigValuesSchema, McpServerConfigSchema, } from "./schemas.js"; export type McpServerConfig = z.infer; -export type DxtManifestAuthor = z.infer; +export type McpbManifestAuthor = z.infer; -export type DxtManifestRepository = z.infer; +export type McpbManifestRepository = z.infer< + typeof McpbManifestRepositorySchema +>; -export type DxtManifestPlatformOverride = z.infer< - typeof DxtManifestPlatformOverrideSchema +export type McpbManifestPlatformOverride = z.infer< + typeof McpbManifestPlatformOverrideSchema >; -export type DxtManifestMcpConfig = z.infer; +export type McpbManifestMcpConfig = z.infer; -export type DxtManifestServer = z.infer; +export type McpbManifestServer = z.infer; -export type DxtManifestCompatibility = z.infer< - typeof DxtManifestCompatibilitySchema +export type McpbManifestCompatibility = z.infer< + typeof McpbManifestCompatibilitySchema >; -export type DxtManifestTool = z.infer; +export type McpbManifestTool = z.infer; -export type DxtManifestPrompt = z.infer; +export type McpbManifestPrompt = z.infer; -export type DxtUserConfigurationOption = z.infer< - typeof DxtUserConfigurationOptionSchema +export type McpbUserConfigurationOption = z.infer< + typeof McpbUserConfigurationOptionSchema >; -export type DxtUserConfigValues = z.infer; +export type McpbUserConfigValues = z.infer; -export type DxtManifest = z.infer; +export type McpbManifest = z.infer; /** - * Information about a DXT package signature + * Information about a MCPB package signature */ -export type DxtSignatureInfo = z.infer; +export type McpbSignatureInfo = z.infer; export interface Logger { log: (...args: unknown[]) => void; diff --git a/test/cli.test.ts b/test/cli.test.ts index b760484..d250b5c 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -41,7 +41,7 @@ describe("DXT CLI", () => { // Commander outputs help to stderr when no command is provided const execError = error as ExecSyncError; expect(execError.stderr.toString()).toContain( - "Usage: dxt [options] [command]", + "Usage: mcpb [options] [command]", ); } }); @@ -91,12 +91,12 @@ describe("DXT CLI", () => { fs.writeFileSync( join(tempDir, "manifest.json"), JSON.stringify({ - dxt_version: "1.0", + manifest_version: "1.0", name: "Test Extension", version: "1.0.0", description: "A test extension", author: { - name: "DXT", + name: "MCPB", }, server: { type: "node", @@ -188,12 +188,12 @@ describe("DXT CLI", () => { fs.writeFileSync( join(tempExecDir, "manifest.json"), JSON.stringify({ - dxt_version: "1.0", + manifest_version: "1.0", name: "Test Executable Extension", version: "1.0.0", description: "A test extension with executable files", author: { - name: "DXT", + name: "MCPB", }, server: { type: "node", diff --git a/test/config.test.ts b/test/config.test.ts index 541993e..6461c5a 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -3,7 +3,7 @@ import { hasRequiredConfigMissing, replaceVariables, } from "../src/shared/config"; -import type { DxtManifest, Logger } from "../src/types"; +import type { Logger, McpbManifest } from "../src/types"; describe("replaceVariables", () => { it("should replace variables in strings", () => { @@ -89,8 +89,8 @@ describe("getMcpConfigForManifest", () => { data: "/data", }; - const baseManifest: DxtManifest = { - dxt_version: "1.0.0", + const baseManifest: McpbManifest = { + manifest_version: "1.0.0", name: "test-extension", version: "1.0.0", description: "Test extension", @@ -114,7 +114,7 @@ describe("getMcpConfigForManifest", () => { const manifest = { ...baseManifest, server: undefined, - } as unknown as DxtManifest; + } as unknown as McpbManifest; const result = await getMcpConfigForManifest({ manifest, @@ -129,7 +129,7 @@ describe("getMcpConfigForManifest", () => { }); it("should return undefined when required config is missing", async () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { apiKey: { @@ -171,7 +171,7 @@ describe("getMcpConfigForManifest", () => { }); it("should apply platform overrides", async () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, server: { type: "node", @@ -203,7 +203,7 @@ describe("getMcpConfigForManifest", () => { }); it("should handle user config variable replacement with defaults", async () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { port: { @@ -236,7 +236,7 @@ describe("getMcpConfigForManifest", () => { }); it("should handle user config variable replacement with user values", async () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { paths: { @@ -270,7 +270,7 @@ describe("getMcpConfigForManifest", () => { }); it("should convert boolean user config values to strings", async () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { verbose: { @@ -304,8 +304,8 @@ describe("getMcpConfigForManifest", () => { }); describe("hasRequiredConfigMissing", () => { - const baseManifest: DxtManifest = { - dxt_version: "1.0.0", + const baseManifest: McpbManifest = { + manifest_version: "1.0.0", name: "test-extension", version: "1.0.0", description: "Test extension", @@ -329,7 +329,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return false when no config fields are required", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { port: { @@ -349,7 +349,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return false when required config is provided", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { apiKey: { @@ -369,7 +369,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return true when required config is undefined", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { apiKey: { @@ -389,7 +389,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return true when required config is empty string", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { apiKey: { @@ -409,7 +409,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return true when required config is array with invalid values", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { paths: { @@ -430,7 +430,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should return true when required config is empty array", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { paths: { @@ -459,7 +459,7 @@ describe("hasRequiredConfigMissing", () => { }); it("should handle multiple required config fields", () => { - const manifest: DxtManifest = { + const manifest: McpbManifest = { ...baseManifest, user_config: { apiKey: { diff --git a/test/init.test.ts b/test/init.test.ts index 8ac580a..68730ce 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -217,7 +217,7 @@ describe("init functions", () => { ); expect(manifest).toEqual({ - dxt_version: "0.1", + manifest_version: "0.1", name: "test-extension", version: "1.0.0", description: "Test description", @@ -303,7 +303,7 @@ describe("init functions", () => { ); expect(manifest).toEqual({ - dxt_version: "0.1", + manifest_version: "0.1", name: "test-extension", display_name: "Test Extension", version: "1.0.0", diff --git a/test/invalid-manifest.json b/test/invalid-manifest.json index 21a2013..ccfcd3a 100644 --- a/test/invalid-manifest.json +++ b/test/invalid-manifest.json @@ -1,5 +1,5 @@ { - "dxt_version": "1.0", + "manifest_version": "1.0", "name": "test-extension", "version": "1.0.0", "description": "A test DXT extension", diff --git a/test/dxtignore.test.ts b/test/mcpbignore.test.ts similarity index 84% rename from test/dxtignore.test.ts rename to test/mcpbignore.test.ts index c75be76..26e5ee1 100644 --- a/test/dxtignore.test.ts +++ b/test/mcpbignore.test.ts @@ -4,18 +4,18 @@ import * as path from "path"; import { getAllFiles, - readDxtIgnorePatterns, + readMcpbIgnorePatterns, shouldExclude, } from "../src/node/files.js"; -describe("DxtIgnore functionality", () => { +describe("McpbIgnore functionality", () => { let tempDir: string; - let dxtIgnorePath: string; + let mcpbIgnorePath: string; beforeEach(() => { // Create a temp directory for each test - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "dxt-test-")); - dxtIgnorePath = path.join(tempDir, ".dxtignore"); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "mcpb-test-")); + mcpbIgnorePath = path.join(tempDir, ".mcpbignore"); }); afterEach(() => { @@ -23,20 +23,20 @@ describe("DxtIgnore functionality", () => { fs.rmSync(tempDir, { recursive: true, force: true }); }); - describe("readDxtIgnorePatterns", () => { - it("should return empty array when .dxtignore doesn't exist", () => { - const patterns = readDxtIgnorePatterns(tempDir); + describe("readMcpbIgnorePatterns", () => { + it("should return empty array when .mcpbignore doesn't exist", () => { + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual([]); }); - it("should read patterns from .dxtignore file", () => { + it("should read patterns from .mcpbignore file", () => { const content = `*.log node_modules/ temp/ .env`; - fs.writeFileSync(dxtIgnorePath, content); + fs.writeFileSync(mcpbIgnorePath, content); - const patterns = readDxtIgnorePatterns(tempDir); + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual(["*.log", "node_modules/", "temp/", ".env"]); }); @@ -48,9 +48,9 @@ temp/ node_modules/ .env`; - fs.writeFileSync(dxtIgnorePath, content); + fs.writeFileSync(mcpbIgnorePath, content); - const patterns = readDxtIgnorePatterns(tempDir); + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual(["*.log", "node_modules/", ".env"]); }); @@ -58,24 +58,24 @@ node_modules/ const content = ` *.log node_modules/ temp/`; - fs.writeFileSync(dxtIgnorePath, content); + fs.writeFileSync(mcpbIgnorePath, content); - const patterns = readDxtIgnorePatterns(tempDir); + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual(["*.log", "node_modules/", "temp/"]); }); it("should handle Windows line endings", () => { const content = `*.log\r\nnode_modules/\r\ntemp/`; - fs.writeFileSync(dxtIgnorePath, content); + fs.writeFileSync(mcpbIgnorePath, content); - const patterns = readDxtIgnorePatterns(tempDir); + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual(["*.log", "node_modules/", "temp/"]); }); it("should return empty array if file cannot be read", () => { - fs.mkdirSync(dxtIgnorePath); // Make a dir so readFile fails + fs.mkdirSync(mcpbIgnorePath); // Make a dir so readFile fails - const patterns = readDxtIgnorePatterns(tempDir); + const patterns = readMcpbIgnorePatterns(tempDir); expect(patterns).toEqual([]); }); }); @@ -118,7 +118,7 @@ temp/`; }); }); - describe("getAllFiles with .dxtignore", () => { + describe("getAllFiles with .mcpbignore", () => { let testStructure: string; beforeEach(() => { @@ -145,17 +145,17 @@ temp/`; fs.writeFileSync(path.join(testStructure, "debug.log"), "log"); }); - it("should exclude files matching .dxtignore patterns", () => { - // Create .dxtignore - const dxtIgnoreContent = `*.log + it("should exclude files matching .mcpbignore patterns", () => { + // Create .mcpbignore + const mcpbIgnoreContent = `*.log tests/ coverage/`; fs.writeFileSync( - path.join(testStructure, ".dxtignore"), - dxtIgnoreContent, + path.join(testStructure, ".mcpbignore"), + mcpbIgnoreContent, ); - const patterns = readDxtIgnorePatterns(testStructure); + const patterns = readMcpbIgnorePatterns(testStructure); const files = getAllFiles(testStructure, testStructure, {}, patterns); const fileNames = Object.keys(files); @@ -169,21 +169,21 @@ coverage/`; expect(fileNames).not.toContain("logs/debug.log"); expect(fileNames).not.toContain("tests/test.js"); expect(fileNames).not.toContain("coverage/report.txt"); - expect(fileNames).not.toContain(".dxtignore"); + expect(fileNames).not.toContain(".mcpbignore"); }); - it("should exclude both default patterns and .dxtignore patterns", () => { - // Create .dxtignore - const dxtIgnoreContent = `tests/`; + it("should exclude both default patterns and .mcpbignore patterns", () => { + // Create .mcpbignore + const mcpbIgnoreContent = `tests/`; fs.writeFileSync( - path.join(testStructure, ".dxtignore"), - dxtIgnoreContent, + path.join(testStructure, ".mcpbignore"), + mcpbIgnoreContent, ); // Create a .git file (default exclusion) fs.writeFileSync(path.join(testStructure, ".gitignore"), "ignore"); - const patterns = readDxtIgnorePatterns(testStructure); + const patterns = readMcpbIgnorePatterns(testStructure); const files = getAllFiles(testStructure, testStructure, {}, patterns); const fileNames = Object.keys(files); @@ -192,14 +192,14 @@ coverage/`; expect(fileNames).toContain("manifest.json"); expect(fileNames).toContain("src/index.js"); - // Should exclude (from .dxtignore) + // Should exclude (from .mcpbignore) expect(fileNames).not.toContain("tests/test.js"); // Should exclude (from default patterns) expect(fileNames).not.toContain(".gitignore"); }); - it("should work without .dxtignore file", () => { + it("should work without .mcpbignore file", () => { const files = getAllFiles(testStructure, testStructure, {}, []); const fileNames = Object.keys(files); diff --git a/test/schemas.test.ts b/test/schemas.test.ts index e8a22e7..a9297d8 100644 --- a/test/schemas.test.ts +++ b/test/schemas.test.ts @@ -1,15 +1,15 @@ import { readFileSync } from "fs"; import { join } from "path"; -import { DxtManifestSchema } from "../src/schemas.js"; +import { McpbManifestSchema } from "../src/schemas.js"; -describe("DxtManifestSchema", () => { +describe("McpbManifestSchema", () => { it("should validate a valid manifest", () => { const manifestPath = join(__dirname, "valid-manifest.json"); const manifestContent = readFileSync(manifestPath, "utf-8"); const manifestData = JSON.parse(manifestContent); - const result = DxtManifestSchema.safeParse(manifestData); + const result = McpbManifestSchema.safeParse(manifestData); expect(result.success).toBe(true); if (result.success) { @@ -23,7 +23,7 @@ describe("DxtManifestSchema", () => { const manifestContent = readFileSync(manifestPath, "utf-8"); const manifestData = JSON.parse(manifestContent); - const result = DxtManifestSchema.safeParse(manifestData); + const result = McpbManifestSchema.safeParse(manifestData); expect(result.success).toBe(false); if (!result.success) { @@ -37,7 +37,7 @@ describe("DxtManifestSchema", () => { it("should validate manifest with all optional fields", () => { const fullManifest = { - dxt_version: "1.0", + manifest_version: "1.0", name: "full-extension", display_name: "Full Featured Extension", version: "2.0.0", @@ -101,7 +101,7 @@ describe("DxtManifestSchema", () => { }, }; - const result = DxtManifestSchema.safeParse(fullManifest); + const result = McpbManifestSchema.safeParse(fullManifest); expect(result.success).toBe(true); if (result.success) { @@ -117,7 +117,7 @@ describe("DxtManifestSchema", () => { serverTypes.forEach((type) => { const manifest = { - dxt_version: "1.0", + manifest_version: "1.0", name: "test", version: "1.0.0", description: "Test", @@ -132,7 +132,7 @@ describe("DxtManifestSchema", () => { }, }; - const result = DxtManifestSchema.safeParse(manifest); + const result = McpbManifestSchema.safeParse(manifest); expect(result.success).toBe(true); }); }); diff --git a/test/sign.e2e.test.ts b/test/sign.e2e.test.ts index 3a61f7f..cbf1c4f 100755 --- a/test/sign.e2e.test.ts +++ b/test/sign.e2e.test.ts @@ -1,10 +1,10 @@ /** - * End-to-end test for DXT signing and verification + * End-to-end test for MCPB signing and verification * * This test: - * 1. Creates a test DXT file + * 1. Creates a test MCPB file * 2. Generates a self-signed certificate for testing - * 3. Signs the DXT file + * 3. Signs the MCPB file * 4. Verifies the signature * 5. Tests various failure scenarios */ @@ -13,7 +13,11 @@ import * as fs from "fs"; import forge from "node-forge"; import * as path from "path"; -import { signDxtFile, unsignDxtFile, verifyDxtFile } from "../src/node/sign.js"; +import { + signMcpbFile, + unsignMcpbFile, + verifyMcpbFile, +} from "../src/node/sign.js"; // Test directory const TEST_DIR = path.join(__dirname, "test-output"); @@ -24,7 +28,7 @@ if (!fs.existsSync(TEST_DIR)) { } // Test file paths -const TEST_DXT = path.join(TEST_DIR, "test.dxt"); +const TEST_MCPB = path.join(TEST_DIR, "test.mcpb"); const SELF_SIGNED_CERT = path.join(TEST_DIR, "self-signed.crt"); const SELF_SIGNED_KEY = path.join(TEST_DIR, "self-signed.key"); const CA_CERT = path.join(TEST_DIR, "ca.crt"); @@ -46,7 +50,7 @@ function generateSelfSignedCert() { cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); const attrs = [ - { name: "commonName", value: "Test DXT Publisher" }, + { name: "commonName", value: "Test MCPB Publisher" }, { name: "countryName", value: "US" }, { name: "organizationName", value: "Test Org" }, ]; @@ -168,7 +172,7 @@ function generateCASignedCert() { } /** - * Create a test DXT file (just a simple ZIP) + * Create a test MCPB file (just a simple ZIP) */ function createTestDxt() { // Create a simple ZIP file content @@ -318,7 +322,7 @@ function createTestDxt() { 0x00, // Comment length ]); - fs.writeFileSync(TEST_DXT, zipContent); + fs.writeFileSync(TEST_MCPB, zipContent); } /** @@ -327,15 +331,15 @@ function createTestDxt() { async function testSelfSignedSigning() { // Create a copy for this test const testFile = path.join(TEST_DIR, "test-self-signed.dxt"); - fs.copyFileSync(TEST_DXT, testFile); + fs.copyFileSync(TEST_MCPB, testFile); // Sign the file expect(() => - signDxtFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY), + signMcpbFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY), ).not.toThrow(); // Verify the signature - const result = await verifyDxtFile(testFile); + const result = await verifyMcpbFile(testFile); // Self-signed certs may not be trusted by OS, so we accept either status expect(["self-signed", "unsigned"]).toContain(result.status); @@ -350,15 +354,15 @@ async function testSelfSignedSigning() { async function testCASignedSigning() { // Create a copy for this test const testFile = path.join(TEST_DIR, "test-ca-signed.dxt"); - fs.copyFileSync(TEST_DXT, testFile); + fs.copyFileSync(TEST_MCPB, testFile); // Sign the file with intermediate expect(() => - signDxtFile(testFile, SIGNED_CERT, SIGNED_KEY, [CA_CERT]), + signMcpbFile(testFile, SIGNED_CERT, SIGNED_KEY, [CA_CERT]), ).not.toThrow(); // Verify the signature - const result = await verifyDxtFile(testFile); + const result = await verifyMcpbFile(testFile); // CA-signed status depends on whether test CA is trusted by OS expect(["signed", "unsigned"]).toContain(result.status); @@ -373,8 +377,8 @@ async function testCASignedSigning() { async function testTamperingDetection() { // Create a copy and sign it const testFile = path.join(TEST_DIR, "test-tampered.dxt"); - fs.copyFileSync(TEST_DXT, testFile); - signDxtFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY); + fs.copyFileSync(TEST_MCPB, testFile); + signMcpbFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY); // Read the signed file const signedContent = fs.readFileSync(testFile); @@ -385,7 +389,7 @@ async function testTamperingDetection() { fs.writeFileSync(testFile, tamperedContent); // Try to verify - should fail - const result = await verifyDxtFile(testFile); + const result = await verifyMcpbFile(testFile); expect(result.status).toBe("unsigned"); // Clean up @@ -396,7 +400,7 @@ async function testTamperingDetection() { * Test unsigned file verification */ async function testUnsignedFile() { - const result = await verifyDxtFile(TEST_DXT); + const result = await verifyMcpbFile(TEST_MCPB); expect(result.status).toBe("unsigned"); } @@ -406,24 +410,24 @@ async function testUnsignedFile() { async function testSignatureRemoval() { // Create a copy and sign it const testFile = path.join(TEST_DIR, "test-remove-sig.dxt"); - fs.copyFileSync(TEST_DXT, testFile); - signDxtFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY); + fs.copyFileSync(TEST_MCPB, testFile); + signMcpbFile(testFile, SELF_SIGNED_CERT, SELF_SIGNED_KEY); // Verify it's signed (or at least has signature data) - await verifyDxtFile(testFile); + await verifyMcpbFile(testFile); // Remove signature - expect(() => unsignDxtFile(testFile)).not.toThrow(); + expect(() => unsignMcpbFile(testFile)).not.toThrow(); // Verify it's unsigned - const afterResult = await verifyDxtFile(testFile); + const afterResult = await verifyMcpbFile(testFile); expect(afterResult.status).toBe("unsigned"); // Clean up fs.unlinkSync(testFile); } -describe("DXT Signing E2E Tests", () => { +describe("MCPB Signing E2E Tests", () => { beforeAll(() => { // Ensure test directory exists if (!fs.existsSync(TEST_DIR)) { diff --git a/test/valid-manifest.json b/test/valid-manifest.json index a472aa6..a91e78f 100644 --- a/test/valid-manifest.json +++ b/test/valid-manifest.json @@ -1,5 +1,5 @@ { - "dxt_version": "1.0", + "manifest_version": "1.0", "name": "test-extension", "display_name": "Test Extension", "version": "1.0.0",