Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ The `server` object defines how to run the MCP server:

The `mcp_config` object in the server configuration defines how the implementing app should execute the MCP server. This replaces the manual JSON configuration users currently need to write.

**Python Example:**
**Python Example (Traditional Bundling):**

```json
"mcp_config": {
Expand All @@ -431,6 +431,22 @@ The `mcp_config` object in the server configuration defines how the implementing
}
```

**Python Example (PyPI + uvx):**

For packages published to PyPI, use `uvx` to fetch dependencies dynamically:

```json
"mcp_config": {
"command": "uvx",
"args": ["--native-tls", "your-package-name@latest"],
"env": {
"API_KEY": "${user_config.api_key}"
}
}
```

Requires: Package on PyPI with `[project.scripts]` entry point, users have `uv` installed.

**Node.js Example:**

```json
Expand Down
166 changes: 166 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ bundle.mcpb (ZIP file)

### Bundling Dependencies

#### Traditional Bundling (Recommended for Maximum Compatibility)

**Python Bundles:**

- Bundle all required packages in `server/lib/` directory
Expand All @@ -129,6 +131,170 @@ bundle.mcpb (ZIP file)
- Include all required shared libraries if dynamic linking used
- Test on clean systems without development tools

#### Alternative: PyPI-Based Deployment for Python (Advanced)

For Python packages published to PyPI, you can use `uvx` to dynamically fetch dependencies instead of bundling them.

**Requirements:**
- Package published to PyPI with `[project.scripts]` entry point
- Users must have `uv` installed (`pip install uv` or `brew install uv`)
- Internet connection at first launch

**Manifest configuration:**

```json
{
"server": {
"type": "python",
"entry_point": "src/your_package/main.py",
"mcp_config": {
"command": "uvx",
"args": ["--native-tls", "your-package-name@latest"],
"env": {
"API_KEY": "${user_config.api_key}"
}
}
}
}
```

**Trade-offs:**

| Aspect | Traditional Bundling | PyPI + uvx |
|--------|---------------------|------------|
| Bundle size | 50-100 MB | < 1 MB |
| User requirements | None | `uv` must be installed |
| Updates | Requires new bundle | `@latest` auto-updates |

See `examples/pypi-python/` for a complete reference implementation.

### Using Variable Substitution for Portability

The manifest supports variable substitution for cross-platform compatibility:

**Available variables:**
- `${__dirname}` - Extension's installation directory
- `${HOME}` - User's home directory
- `${DESKTOP}` - User's desktop folder
- `${DOCUMENTS}` - User's documents folder
- `${DOWNLOADS}` - User's downloads folder
- `${pathSeparator}` or `${/}` - Platform-specific separator
- `${user_config.KEY}` - User-configured values

**Common portability mistakes:**
```javascript
// ❌ WRONG - Hardcoded absolute paths
spawn('/usr/local/bin/node', ['script.js']);
spawn('C:\\Program Files\\tool\\bin.exe');

// ✅ CORRECT - Use runtime's executables
spawn(process.execPath, ['script.js']);

// ❌ WRONG - Assuming global packages
spawn('npx', ['some-command']);

// ✅ CORRECT - Bundle and reference locally
spawn('${__dirname}/node_modules/.bin/tool');
```

**Testing portability:**
1. Fresh VM without development tools
2. Different OS than development machine
3. Verify variable substitution works
4. Check all paths resolve correctly

## Testing Your MCPB

Before distributing your MCPB, follow this four-phase testing approach:

### Phase 1: Development Testing
- Unit tests for your server code
- Manual testing on your development machine
- Tool functionality verification
- Error handling validation

### Phase 2: Clean Environment Testing ⚠️ Critical
Test on a fresh system without development tools to catch portability issues:

**Using Docker (recommended):**
```bash
# Create clean test environment
docker run -it node:20 bash

# Copy ONLY your MCPB bundle (no global packages)
# Test installation exactly as users would
```

**What this catches:**
- Missing bundled dependencies
- Hardcoded paths
- Global package assumptions
- Platform-specific code issues

**Common issues found:**
- ❌ `spawn('/usr/local/bin/node')` - Hardcoded path
- ✅ `spawn(process.execPath)` - Runtime's own Node.js
- ❌ Assuming `npx` is globally installed
- ✅ Bundling all required executables

### Phase 3: Cross-Platform Testing
- Test on different OS than you developed on
- Verify paths work correctly on both macOS and Windows
- Use forward slashes in paths (automatically converted)
- Test platform-specific features with fallbacks

### Phase 4: Integration Testing
- Install in actual host application (Claude Desktop, etc.)
- Test complete end-to-end workflows
- Verify error messages are helpful
- Check performance and responsiveness

## Error Message Best Practices

Well-crafted error messages dramatically reduce support burden. Include three components:

### 1. What went wrong (specific diagnosis)
```javascript
// ❌ Generic
throw new Error("An error occurred");

// ✅ Specific
throw new Error("Failed to read config file at path/to/config.json");
```

### 2. Why it happened (context)
```javascript
// ❌ Vague
return { isError: true, content: [{ type: "text", text: "Authentication failed" }] };

// ✅ Clear
return {
isError: true,
content: [{
type: "text",
text: "API key is invalid or has expired. Generate a new key at Settings → API"
}]
};
```

### 3. How to fix it (actionable steps)
```javascript
// ❌ Unhelpful
throw new Error("Check your settings");

// ✅ Actionable
throw new Error("Missing API key. Add it in Settings → Extensions → [Your Extension] → API Key field");
```

### Error Categories
Return structured errors via MCP protocol with clear `isError` flags:

- **Configuration errors** - Missing or invalid settings
- **Authentication errors** - Invalid credentials with regeneration instructions
- **Resource errors** - File not found, network unavailable with paths/URLs
- **Permission errors** - Access denied with required permission details
- **Validation errors** - Invalid input with expected format

# Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
Expand Down
34 changes: 34 additions & 0 deletions examples/pypi-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# PyPI-Based MCP Server Example

This example demonstrates creating an MCP Bundle that uses PyPI and `uvx` for dynamic dependency resolution.

## How It Works

1. Bundle contains only source code and package metadata (< 1 MB)
2. Claude Desktop runs `uvx --native-tls example-pypi-mcp@latest`
3. `uvx` fetches dependencies from PyPI and caches them locally

## Key Configuration

**manifest.json:**
```json
{
"mcp_config": {
"command": "uvx",
"args": ["--native-tls", "example-pypi-mcp@latest"]
}
}
```

**pyproject.toml:**
```toml
[project]
name = "example-pypi-mcp"

[project.scripts]
example-pypi-mcp = "example_pypi_mcp.main:main"
```

## Usage

Users need `uv` installed (`pip install uv`). When Claude Desktop runs the server, `uvx` automatically fetches the package from PyPI on first use.
66 changes: 66 additions & 0 deletions examples/pypi-python/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"$schema": "../../dist/mcpb-manifest.schema.json",
"manifest_version": "0.3",
"name": "example-pypi-mcp",
"display_name": "PyPI Example MCP Server",
"version": "1.0.0",
"description": "Example MCP server demonstrating PyPI-based deployment with uvx",
"long_description": "This example demonstrates how to create an MCP Bundle that uses PyPI and uvx for dynamic dependency resolution instead of bundling dependencies. This approach results in smaller bundle sizes (< 1 MB) and enables automatic updates, but requires users to have 'uv' installed globally.",
"author": {
"name": "Anthropic",
"email": "support@anthropic.com",
"url": "https://github.com/anthropics"
},
"server": {
"type": "python",
"entry_point": "src/example_pypi_mcp/main.py",
"mcp_config": {
"command": "uvx",
"args": [
"--native-tls",
"example-pypi-mcp@latest"
],
"env": {
"API_KEY": "${user_config.api_key}",
"DEBUG": "${user_config.debug_mode}"
}
}
},
"tools": [
{
"name": "echo",
"description": "Echo back a message (demonstrates basic tool functionality)"
},
{
"name": "get_timestamp",
"description": "Get current timestamp in ISO format"
}
],
"keywords": ["example", "pypi", "uvx", "python", "deployment"],
"license": "MIT",
"user_config": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "Example API key for demonstration purposes",
"sensitive": true,
"required": false,
"default": "demo-key-12345"
},
"debug_mode": {
"type": "boolean",
"title": "Debug Mode",
"description": "Enable debug logging output",
"default": false,
"required": false
}
},
"compatibility": {
"claude_desktop": ">=0.10.0",
"platforms": ["darwin", "win32", "linux"],
"runtimes": {
"python": ">=3.10.0 <4"
}
},
"privacy_policies": []
}
38 changes: 38 additions & 0 deletions examples/pypi-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "example-pypi-mcp"
version = "1.0.0"
description = "Example MCP server demonstrating PyPI-based deployment"
readme = "README.md"
requires-python = ">=3.10"
authors = [
{name = "Anthropic", email = "support@anthropic.com"}
]
license = {text = "MIT"}
keywords = ["mcp", "model-context-protocol", "example", "pypi"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Private :: Do Not Upload" # Prevents accidental upload to PyPI
]

dependencies = [
"mcp[cli]>=1.11.0",
]

[project.scripts]
example-pypi-mcp = "example_pypi_mcp.main:main"

[tool.setuptools]
packages = ["src/example_pypi_mcp"]

[tool.setuptools.package-dir]
"example_pypi_mcp" = "src/example_pypi_mcp"
3 changes: 3 additions & 0 deletions examples/pypi-python/src/example_pypi_mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Example PyPI-based MCP Server."""

__version__ = "1.0.0"
Loading