Skip to content
Merged
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
51 changes: 50 additions & 1 deletion MANIFEST.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MCPB Manifest.json Spec

Current version: `0.3`
Last updated: 2025-10-24
Last updated: 2025-12-02

## Manifest Schema

Expand Down Expand Up @@ -398,6 +398,55 @@ The `server` object defines how to run the MCP server:

### Server Types

Four server types are supported:

- **`node`**: Node.js server with bundled dependencies
- **`python`**: Python server with bundled dependencies
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have wording that raw python support may be removed and steer people to uv?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I'm going to mark uv as experimental in case this ends up being the wrong call (for whatever reason, e.g. mcp sdk no longer requires built dependencies?) and we want to backtrack to a bundled approach. Will update if/when we formalize!

- **`binary`**: Compiled executable
- **`uv`**: Python server using UV runtime (experimental, v0.4+)

### UV Runtime (Experimental, v0.4+)

> **Note:** UV runtime support is experimental and may change in future versions.

The `uv` server type enables cross-platform Python extensions without bundling dependencies. Instead, dependencies are declared in `pyproject.toml` and installed by the host application using UV.

**Benefits:**
- Cross-platform support (Windows, macOS, Linux; Intel, ARM)
- Small bundle size (~100 KB vs 5-10 MB)
- Handles compiled dependencies (pydantic, numpy, etc.)
- No user Python installation required

**Example:**
```json
{
"manifest_version": "0.4",
"server": {
"type": "uv",
"entry_point": "src/server.py"
}
}
```

**Requirements:**
- Must include `pyproject.toml` with dependencies
- Must NOT include `server/lib/` or `server/venv/`
- `mcp_config` is optional (host manages execution)

**Package structure:**
```
extension.mcpb
├── manifest.json # server.type = "uv"
├── pyproject.toml # Dependencies
├── .mcpbignore # Exclude .venv, server/lib
└── src/
└── server.py
```

See `examples/hello-world-uv` for a complete example.

### Node, Python, and Binary Types

1. **Python**: `server.type = "python"`
- Requires `entry_point` to Python file
- All dependencies must be bundled in the MCPB
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,22 @@ bundle.mcpb (ZIP file)

### Bundling Dependencies

**Python Bundles:**
**UV Runtime (Experimental - v0.4+):**

- Use `server.type = "uv"` in manifest
- Include `pyproject.toml` with dependencies (no bundled packages needed)
- Host application manages Python and dependencies automatically
- Works cross-platform without user Python installation
- See `examples/hello-world-uv`

**Python Bundles (Traditional):**

- Use `server.type = "python"` in manifest
- Bundle all required packages in `server/lib/` directory
- OR bundle a complete virtual environment in `server/venv/`
- Use tools like `pip-tools`, `poetry`, or `pipenv` to create reproducible bundles
- Set `PYTHONPATH` to include bundled packages via `mcp_config.env`
- **Limitation**: Cannot portably bundle compiled dependencies (e.g., pydantic, which the MCP Python SDK requires)

**Node.js Bundles:**

Expand Down
8 changes: 8 additions & 0 deletions examples/hello-world-uv/.mcpbignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.venv/
__pycache__/
*.pyc
.pytest_cache/
.mypy_cache/
*.egg-info/
server/lib/
server/venv/
62 changes: 62 additions & 0 deletions examples/hello-world-uv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Hello World UV Runtime Example (Experimental)

> **Note:** UV runtime support is experimental and may change in future versions.

This example demonstrates a minimal MCP server using **UV runtime**.

## What is UV Runtime?

UV runtime lets Claude Desktop automatically manage Python and dependencies for your extension:
- Downloads the correct Python version for the user's platform
- Creates an isolated virtual environment
- Installs dependencies from `pyproject.toml`
- Works cross-platform (Windows, macOS, Linux) without user setup

## Structure

```
hello-world-uv/
├── manifest.json # server.type = "uv"
├── pyproject.toml # Dependencies listed here
├── .mcpbignore # Exclude build artifacts
└── src/
└── server.py # MCP server implementation
```

## Key Differences from Python Runtime

**UV Runtime** (this example):
- `server.type = "uv"`
- No bundled dependencies
- No `mcp_config` needed
- Small bundle size (~2 KB)
- Works on any platform

**Python Runtime** (traditional):
- `server.type = "python"`
- Must bundle dependencies in `server/lib/`
- Requires `mcp_config` with PYTHONPATH
- Larger bundle size
- Only works with pure Python (no compiled deps)

## Installing

```bash
mcpb pack
```

Install the generated `.mcpb` file in Claude Desktop.

## Testing Locally

```bash
# Install dependencies
uv sync

# Run server
uv run src/server.py
```

## Tools

- **say_hello** - Greets a person by name
Binary file added examples/hello-world-uv/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions examples/hello-world-uv/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"manifest_version": "0.4",
"name": "hello-world-uv",
"display_name": "Hello World (UV Runtime)",
"version": "1.0.0",
"description": "Simple MCP server using UV runtime",
"author": {
"name": "Anthropic"
},
"icon": "icon.png",
"server": {
"type": "uv",
"entry_point": "src/server.py"
},
"compatibility": {
"platforms": ["darwin", "linux", "win32"],
"runtimes": {
"python": ">=3.10"
}
},
"keywords": ["example", "hello-world", "uv"],
"license": "MIT"
}
8 changes: 8 additions & 0 deletions examples/hello-world-uv/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = "hello-world-uv"
version = "1.0.0"
description = "Simple MCP server using UV runtime"
requires-python = ">=3.10"
dependencies = [
"mcp>=1.0.0",
]
65 changes: 65 additions & 0 deletions examples/hello-world-uv/src/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Hello World MCP server using UV runtime."""

import asyncio
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio


server = Server("hello-world-uv")


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""List available tools."""
return [
types.Tool(
name="say_hello",
description="Say hello to someone",
inputSchema={
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name to greet",
}
},
"required": ["name"],
},
)
]


@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""Handle tool calls."""
if name == "say_hello":
person_name = arguments.get("name", "World") if arguments else "World"
return [types.TextContent(type="text", text=f"Hello, {person_name}!")]

raise ValueError(f"Unknown tool: {name}")


async def main():
"""Run the server."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="hello-world-uv",
server_version="1.0.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)


if __name__ == "__main__":
asyncio.run(main())
Loading
Loading