rename dxt -> mcpb

This commit is contained in:
Joan Xie 2025-08-21 14:56:54 -07:00
commit e1938b9933
No known key found for this signature in database
30 changed files with 575 additions and 427 deletions

2
.gitignore vendored
View file

@ -103,6 +103,6 @@ _out/
dist
self-signed-cert.pem
self-signed-key.pem
*.dxt
*.mcpb
invalid-json.json
**/server/lib/**

134
CLI.md
View file

@ -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 <manifest> Validate a DXT manifest file
pack <directory> [output] Pack a directory into a DXT extension
sign [options] <dxt-file> Sign a DXT extension file
verify <dxt-file> Verify the signature of a DXT extension file
info <dxt-file> Display information about a DXT extension file
unsign <dxt-file> Remove signature from a DXT extension file
init [directory] Create a new MCPB extension manifest
validate <manifest> Validate a MCPB manifest file
pack <directory> [output] Pack a directory into a MCPB extension
sign [options] <mcpb-file> Sign a MCPB extension file
verify <mcpb-file> Verify the signature of a MCPB extension file
info <mcpb-file> Display information about a MCPB extension file
unsign <mcpb-file> 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 <path>`
### `mcpb validate <path>`
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 <directory> [output]`
### `mcpb pack <directory> [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 <dxt-file>`
### `mcpb sign <mcpb-file>`
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 <dxt-file>`
### `mcpb verify <mcpb-file>`
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 <dxt-file>`
### `mcpb info <mcpb-file>`
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 <dxt-file>`
### `mcpb unsign <mcpb-file>`
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

View file

@ -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
"mcpb_version": "0.1", // MCPB 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",
"mcpb_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",
"mcpb_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
- **mcpb_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`

224
README.md
View file

@ -1,118 +1,266 @@
# 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 has been 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 is now `@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
# For App Developers
Supporting MCPB bundles in your application involves three key steps:
1. **Load and validate the bundle manifest**
2. **Extract the bundle contents to a secure location**
3. **Launch the MCP server with the appropriate configuration**
## Installation
```sh
npm install @anthropic-ai/mcpb
```
## Basic Usage
```typescript
import { loadConfig, runMcp } from "@anthropic-ai/mcpb";
// Load a bundle configuration
const config = await loadConfig("/path/to/bundle.mcpb", {
userConfigValues: {
// Optional: provide user configuration values
apiKey: "user-api-key",
},
});
// Run the MCP server
const transport = await runMcp(config, {
serverParams: config.serverParams,
serverPath: config.extractedPath,
env: {
// Optional: additional environment variables
...process.env,
...config.env,
},
});
// Use the transport with the MCP SDK client
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client(
{
name: "example-app",
version: "1.0.0",
},
{
capabilities: {},
},
);
await client.connect(transport);
// List available tools
const tools = await client.listTools();
console.log("Available tools:", tools);
```
## Features
### Manifest Validation
All manifests are validated against the schema to ensure compatibility:
```typescript
import { validateManifest } from "@anthropic-ai/mcpb";
const isValid = validateManifest("/path/to/manifest.json");
```
### Signature Verification
MCPB bundles can be digitally signed for security. The library includes functions for verifying signatures:
```typescript
import { verifyMcpbFile } from "@anthropic-ai/mcpb/node";
const signatureInfo = await verifyMcpbFile("/path/to/bundle.mcpb");
if (signatureInfo.status === "signed") {
console.log("Bundle signed by:", signatureInfo.publisher);
}
```
### User Configuration
Bundles can define configuration options that users need to provide:
```typescript
const config = await loadConfig("/path/to/bundle.mcpb", {
userConfigValues: {
apiKey: "user-provided-key",
dataPath: "/path/to/data",
},
});
```
## Security Considerations
1. **Extraction Location**: Always extract bundles to a secure, isolated directory
2. **Path Validation**: The library includes protection against zip slip attacks
3. **Process Isolation**: Run MCP servers in isolated processes with limited permissions
4. **Environment Variables**: Be careful about which environment variables are passed to servers
5. **Signature Verification**: Consider requiring signed bundles in production
## API Reference
See the [TypeScript definitions](src/index.ts) for complete API documentation.
# 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.

View file

@ -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

View file

@ -1,11 +1,11 @@
{
"$schema": "../../dist/dxt-manifest.schema.json",
"dxt_version": "0.1",
"mcpb_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",

View file

@ -1,11 +1,11 @@
{
"$schema": "../../dist/dxt-manifest.schema.json",
"dxt_version": "0.1",
"mcpb_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",

View file

@ -1,5 +1,5 @@
{
"dxt_version": "0.1",
"mcpb_version": "0.1",
"id": "anthropic.demo.filesystem",
"name": "Filesystem",
"display_name": "Filesystem",

View file

@ -1,11 +1,11 @@
{
"$schema": "../../dist/dxt-manifest.schema.json",
"dxt_version": "0.1",
"mcpb_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",

View file

@ -1,6 +1,6 @@
{
"name": "@anthropic-ai/dxt",
"description": "Tools for building Desktop Extensions",
"name": "@anthropic-ai/mcpb",
"description": "Tools for building MCP Bundles",
"version": "0.2.6",
"type": "module",
"main": "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",

View file

@ -0,0 +1,22 @@
import { DxtManifestSchema, DxtSignatureInfoSchema } 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,
};
await fs.mkdir(path.join(import.meta.dirname, "../dist"), { recursive: true });
for (const key in schemasToWrite) {
const schema = z.toJSONSchema(schemasToWrite[key]);
await fs.writeFile(
path.join(import.meta.dirname, "../dist", `${key}.schema.json`),
JSON.stringify(schema, null, 2),
{
encoding: "utf8",
},
);
}

View file

@ -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 <manifest>")
.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 <dxt>")
.command("clean <mcpb>")
.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 <dxt-file> [output]")
.description("Unpack a DXT extension file")
.action((dxtFile: string, output?: string) => {
.command("unpack <mcpb-file> [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 <dxt-file>")
.description("Sign a DXT extension file")
.command("sign <mcpb-file>")
.description("Sign an MCPB extension file")
.option(
"-c, --cert <path>",
"Path to certificate file (PEM format)",
@ -147,20 +147,20 @@ program
.option("--self-signed", "Create a self-signed certificate if none exists")
.action(
(
dxtFile: string,
mcpbFile: string,
options: {
cert: string;
key: string;
intermediate?: string[];
selfSigned?: boolean;
},
}
) => {
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"
@ -219,25 +219,25 @@ program
process.exit(1);
}
})();
},
}
);
// Verify command
program
.command("verify <dxt-file>")
.description("Verify the signature of a DXT extension file")
.action((dxtFile: string) => {
.command("verify <mcpb-file>")
.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 <dxt-file>")
.description("Display information about a DXT extension file")
.action((dxtFile: string) => {
.command("info <mcpb-file>")
.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 <dxt-file>")
.description("Remove signature from a DXT extension file")
.action((dxtFile: string) => {
.command("unsign <mcpb-file>")
.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(

View file

@ -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",
mcpb_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 {

View file

@ -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;

View file

@ -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<boolean> {
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

View file

@ -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 [];
}

View file

@ -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<DxtSignatureInfo> {
export async function verifyMcpbFile(
mcpbPath: string,
): Promise<McpbSignatureInfo> {
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);
}

View file

@ -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");

View file

@ -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,40 @@ 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({
export const McpbManifestSchema = z.object({
$schema: z.string().optional(),
dxt_version: z.string(),
mcpb_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(),
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(),
});
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(),

View file

@ -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,40 @@ 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({
export const McpbManifestSchema = z.strictObject({
$schema: z.string().optional(),
dxt_version: z.string(),
mcpb_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(),
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(),
});
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(),

Some files were not shown because too many files have changed in this diff Show more