Skip to content

Commit 970f2d5

Browse files
Merge pull request #158 from modelcontextprotocol/joan/uv-runtime-support
feat: add experimental UV runtime support in manifest v0.4
2 parents dd9502e + ca4a881 commit 970f2d5

File tree

16 files changed

+916
-4
lines changed

16 files changed

+916
-4
lines changed

MANIFEST.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# MCPB Manifest.json Spec
22

33
Current version: `0.3`
4-
Last updated: 2025-10-24
4+
Last updated: 2025-12-02
55

66
## Manifest Schema
77

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

399399
### Server Types
400400

401+
Four server types are supported:
402+
403+
- **`node`**: Node.js server with bundled dependencies
404+
- **`python`**: Python server with bundled dependencies
405+
- **`binary`**: Compiled executable
406+
- **`uv`**: Python server using UV runtime (experimental, v0.4+)
407+
408+
### UV Runtime (Experimental, v0.4+)
409+
410+
> **Note:** UV runtime support is experimental and may change in future versions.
411+
412+
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.
413+
414+
**Benefits:**
415+
- Cross-platform support (Windows, macOS, Linux; Intel, ARM)
416+
- Small bundle size (~100 KB vs 5-10 MB)
417+
- Handles compiled dependencies (pydantic, numpy, etc.)
418+
- No user Python installation required
419+
420+
**Example:**
421+
```json
422+
{
423+
"manifest_version": "0.4",
424+
"server": {
425+
"type": "uv",
426+
"entry_point": "src/server.py"
427+
}
428+
}
429+
```
430+
431+
**Requirements:**
432+
- Must include `pyproject.toml` with dependencies
433+
- Must NOT include `server/lib/` or `server/venv/`
434+
- `mcp_config` is optional (host manages execution)
435+
436+
**Package structure:**
437+
```
438+
extension.mcpb
439+
├── manifest.json # server.type = "uv"
440+
├── pyproject.toml # Dependencies
441+
├── .mcpbignore # Exclude .venv, server/lib
442+
└── src/
443+
└── server.py
444+
```
445+
446+
See `examples/hello-world-uv` for a complete example.
447+
448+
### Node, Python, and Binary Types
449+
401450
1. **Python**: `server.type = "python"`
402451
- Requires `entry_point` to Python file
403452
- All dependencies must be bundled in the MCPB

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,22 @@ bundle.mcpb (ZIP file)
109109

110110
### Bundling Dependencies
111111

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

114+
- Use `server.type = "uv"` in manifest
115+
- Include `pyproject.toml` with dependencies (no bundled packages needed)
116+
- Host application manages Python and dependencies automatically
117+
- Works cross-platform without user Python installation
118+
- See `examples/hello-world-uv`
119+
120+
**Python Bundles (Traditional):**
121+
122+
- Use `server.type = "python"` in manifest
114123
- Bundle all required packages in `server/lib/` directory
115124
- OR bundle a complete virtual environment in `server/venv/`
116125
- Use tools like `pip-tools`, `poetry`, or `pipenv` to create reproducible bundles
117126
- Set `PYTHONPATH` to include bundled packages via `mcp_config.env`
127+
- **Limitation**: Cannot portably bundle compiled dependencies (e.g., pydantic, which the MCP Python SDK requires)
118128

119129
**Node.js Bundles:**
120130

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.venv/
2+
__pycache__/
3+
*.pyc
4+
.pytest_cache/
5+
.mypy_cache/
6+
*.egg-info/
7+
server/lib/
8+
server/venv/

examples/hello-world-uv/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Hello World UV Runtime Example (Experimental)
2+
3+
> **Note:** UV runtime support is experimental and may change in future versions.
4+
5+
This example demonstrates a minimal MCP server using **UV runtime**.
6+
7+
## What is UV Runtime?
8+
9+
UV runtime lets Claude Desktop automatically manage Python and dependencies for your extension:
10+
- Downloads the correct Python version for the user's platform
11+
- Creates an isolated virtual environment
12+
- Installs dependencies from `pyproject.toml`
13+
- Works cross-platform (Windows, macOS, Linux) without user setup
14+
15+
## Structure
16+
17+
```
18+
hello-world-uv/
19+
├── manifest.json # server.type = "uv"
20+
├── pyproject.toml # Dependencies listed here
21+
├── .mcpbignore # Exclude build artifacts
22+
└── src/
23+
└── server.py # MCP server implementation
24+
```
25+
26+
## Key Differences from Python Runtime
27+
28+
**UV Runtime** (this example):
29+
- `server.type = "uv"`
30+
- No bundled dependencies
31+
- No `mcp_config` needed
32+
- Small bundle size (~2 KB)
33+
- Works on any platform
34+
35+
**Python Runtime** (traditional):
36+
- `server.type = "python"`
37+
- Must bundle dependencies in `server/lib/`
38+
- Requires `mcp_config` with PYTHONPATH
39+
- Larger bundle size
40+
- Only works with pure Python (no compiled deps)
41+
42+
## Installing
43+
44+
```bash
45+
mcpb pack
46+
```
47+
48+
Install the generated `.mcpb` file in Claude Desktop.
49+
50+
## Testing Locally
51+
52+
```bash
53+
# Install dependencies
54+
uv sync
55+
56+
# Run server
57+
uv run src/server.py
58+
```
59+
60+
## Tools
61+
62+
- **say_hello** - Greets a person by name

examples/hello-world-uv/icon.png

70 Bytes
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"manifest_version": "0.4",
3+
"name": "hello-world-uv",
4+
"display_name": "Hello World (UV Runtime)",
5+
"version": "1.0.0",
6+
"description": "Simple MCP server using UV runtime",
7+
"author": {
8+
"name": "Anthropic"
9+
},
10+
"icon": "icon.png",
11+
"server": {
12+
"type": "uv",
13+
"entry_point": "src/server.py"
14+
},
15+
"compatibility": {
16+
"platforms": ["darwin", "linux", "win32"],
17+
"runtimes": {
18+
"python": ">=3.10"
19+
}
20+
},
21+
"keywords": ["example", "hello-world", "uv"],
22+
"license": "MIT"
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
name = "hello-world-uv"
3+
version = "1.0.0"
4+
description = "Simple MCP server using UV runtime"
5+
requires-python = ">=3.10"
6+
dependencies = [
7+
"mcp>=1.0.0",
8+
]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
"""Hello World MCP server using UV runtime."""
3+
4+
import asyncio
5+
from mcp.server.models import InitializationOptions
6+
import mcp.types as types
7+
from mcp.server import NotificationOptions, Server
8+
import mcp.server.stdio
9+
10+
11+
server = Server("hello-world-uv")
12+
13+
14+
@server.list_tools()
15+
async def handle_list_tools() -> list[types.Tool]:
16+
"""List available tools."""
17+
return [
18+
types.Tool(
19+
name="say_hello",
20+
description="Say hello to someone",
21+
inputSchema={
22+
"type": "object",
23+
"properties": {
24+
"name": {
25+
"type": "string",
26+
"description": "Name to greet",
27+
}
28+
},
29+
"required": ["name"],
30+
},
31+
)
32+
]
33+
34+
35+
@server.call_tool()
36+
async def handle_call_tool(
37+
name: str, arguments: dict | None
38+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
39+
"""Handle tool calls."""
40+
if name == "say_hello":
41+
person_name = arguments.get("name", "World") if arguments else "World"
42+
return [types.TextContent(type="text", text=f"Hello, {person_name}!")]
43+
44+
raise ValueError(f"Unknown tool: {name}")
45+
46+
47+
async def main():
48+
"""Run the server."""
49+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
50+
await server.run(
51+
read_stream,
52+
write_stream,
53+
InitializationOptions(
54+
server_name="hello-world-uv",
55+
server_version="1.0.0",
56+
capabilities=server.get_capabilities(
57+
notification_options=NotificationOptions(),
58+
experimental_capabilities={},
59+
),
60+
),
61+
)
62+
63+
64+
if __name__ == "__main__":
65+
asyncio.run(main())

0 commit comments

Comments
 (0)