[EN] uv: The Python tool that you learned about MCP Server

Julian | Jun 12, 2026 min read

You are currently setting up an MCP server for Claude Code or Claude Desktop, copying a config from the documentation - and stumble upon this command:

{
  "mcpServers": {
    "fetch": {
      "command": "uvx",
      "args": ["mcp-server-fetch"]
    }
  }
}

uvx. No pip install, no python -m venv, no “first create the requirements.txt”. Just uvx something and it works.

If you come from the Java or Node world, you know the feeling: Python tooling has had a reputation for being fiddly for years. Virtualenvs that you forget to activate. Three different ways to install packages depending on which tutorial you’re reading. This is exactly the problem that uv has tackled - and has gotten so good at it that it is now basically the standard way to start Python-based MCP servers.

Let’s take a look at what uv actually is, why it’s popping up everywhere in MCP setups, and how to use it in your own Python projects.

What is uv anyway?

uv is a Python package and project manager developed by Astral – the team behind Ruff, the linter that has similarly rapidly taken over the Python world in recent years. uv is written in Rust, and that is exactly the main reason for its success: speed, which was previously unusual in the Python world.

The really remarkable thing about uv isn’t just the performance, but that it’s a single tool for tasks that you previously needed five different tools for:

TaskPrevious toolWith uv
Install packagespipuv pip / uv add
Virtual environmentsvenv / virtualenvuv venv (automatic)
Dependency lockingpip-toolsuv lock
Project & Dependency Managementpoetryuv (pyproject.toml)
Run tools in isolationpipxuvx
Manage Python versionspyenvuv python

So you no longer install five tools with five different configuration files and five different mental models - you install etc.

On the speed promise: uv typically resolves dependency installations 10 to 100 times faster than pip. This is due to three things: the Rust implementation, a global cache (a package is only downloaded once system-wide and then only linked) and massive parallelization during downloading. With a small project you hardly notice this - but with a monorepo with several services and hundreds of dependencies the difference is between “getting coffee” and “continuing to work”.

Why uvx is the star of MCP servers

If you’ve ever set up a node-based MCP server, you’ll be familiar with npx: instead of installing a package globally, you download it once, run it, and leave the rest behind you. That’s exactly what uvx is for the Python world - except that there simply wasn’t a good equivalent to it before.

Take another look at the config from above:

{
  "mcpServers": {
    "fetch": {
      "command": "uvx",
      "args": ["mcp-server-fetch"]
    }
  }
}

What is happening here in the background: uvx mcp-server-fetch loads the mcp-server-fetch package from PyPI, builds an ephemeral, isolated environment for it (cached so that the second start is immediate) and executes the command contained therein. You don’t need globally installed Python packages, no version conflicts between different MCP servers, no “which Python is this?”

The difference to the classic way

Without uv, the setup for the same MCP server would look like this:

# Anti-Pattern: der klassische Weg
git clone https://github.com/example/mcp-server-fetch
cd mcp-server-fetch
python -m venv .venv
source .venv/bin/activate
pip install -e .
python -m mcp_server_fetch

Six steps, an activated venv that you shouldn’t forget - and in the Claude config you would have to store the full path to the Python interpreter in the venv. Prone to errors, and on a second computer you start from scratch again.

With uvx, this becomes a line that works the same everywhere - whether on your Mac, in a Docker container or on a colleague’s computer:

uvx mcp-server-fetch

This very property - one command, no prep, reproducible - is why virtually every MCP server documentation now shows uvx as the recommended path. MCP servers are small, often short-lived tools that you try out, swap or only use occasionally. This usage pattern is exactly what uvx is built for.

uv in everyday project life: Anti-Pattern vs. Best Practice

Anti-pattern: the venv-pip-requirements dance

This is what the classic start of a Python project looks like – you probably know it exactly like this:

mkdir my-project && cd my-project
python -m venv .venv
source .venv/bin/activate          # unter Windows: .venv\Scripts\activate
pip install requests pandas
pip freeze > requirements.txt
python main.py

The problem isn’t that it doesn’t work. The problem is anything that can go wrong without you noticing:

  • You forget source .venv/bin/activate in a new terminal tab and suddenly install packages globally.
  • pip freeze > requirements.txt also writes in all transitive dependencies with exact versions - an update of pandas is enough and the file is a single diff war.
  • requirements.txt does not differentiate between “this is what I need for development” (tests, linter) and “this is what I need in production”.

Best Practice: uv init, uv add, uv run

With uv the same start looks like this:

uv init my-project
cd my-project
uv add requests pandas
uv run main.py

What happens here:

  1. uv init creates a new project - with pyproject.toml, .python-version and a basic structure. Not an empty directory, but a project with standard configuration.
  2. uv add requests pandas installs the packages and enters them into the pyproject.toml and writes a uv.lock file with exactly resolved versions - automatically, in one step.
  3. uv run main.py runs the script in the project’s own virtual environment. You don’t need to activate anything - uv detects the .venv directory in the project and just uses it.

The pyproject.toml then looks like this:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "requests>=2.32.0",
    "pandas>=2.2.0",
]

You separate dev dependencies (tests, linter) cleanly:

uv add --dev pytest ruff

The killer feature: scripts with inline dependencies

It becomes really practical for individual scripts that you just want to run without setting up an entire project. uv supports PEP 723 – Dependencies are declared directly in the script as a comment:

# script.py
# /// script
# dependencies = [
#   "requests<3",
#   "rich",
# ]
# ///

import requests
from rich import print

resp = requests.get("https://api.github.com/repos/astral-sh/uv")
print(resp.json())

Run with:

uv run script.py

uv reads the comment block, builds a suitable, cached environment in the background and runs the script - without venv, without pip install, without you having to prepare anything. Exactly the same principle that uvx uses for MCP servers, just for your own scripts.

Pro Tips/Warnings

Pro tip: uv.lock belongs in the Git repo

The uv.lock file is the equivalent of package-lock.json (npm) or poetry.lock. It not only fixes the versions of your direct dependencies, but of the entire dependency chain - exactly, including hashes. If you commit them, uv sync is guaranteed to install the same versions on every computer (and in every CI pipeline). If you leave them out, you lose exactly the advantage you just gained with uv: reproducible builds.

Pro tip: Migration without Big Bang – the uv pip interface

You don’t have to switch your entire project to pyproject.toml and uv add overnight. uv comes with a drop-in compatible pip interface:

uv venv
uv pip install -r requirements.txt
uv pip compile requirements.in -o requirements.txt

This is exactly the same syntax as pip, but runs at uv speed. This allows you to convert existing projects step by step - first speed up the installation, then later (if at all) switch to the full project workflow with uv add/uv run.

Attention: Do not manage Python versions twice

uv can install and manage Python versions itself - uv python install 3.12 downloads a standalone Python version, independent of your system Python. In combination with a .python-version file in the project, this ensures that uv run always uses the correct version.

The trap: If you also have pyenv active, both tools compete for the same task - and you spend your time trying to figure out which tool just “won”. Choose one. For new projects, uv python is the more pragmatic way because it is directly integrated into the rest of the workflow.

Conclusion

uv is one of those tools that you don’t actively seek out - you stumble upon it because it quietly establishes itself in the workflows you already use. In your case it was uvx in an MCP server config. For someone else, maybe it’s a GitHub repo whose README suddenly shows uv sync instead of pip install -r requirements.txt.

The really interesting thing about it: uv doesn’t solve any new problems. Installing packages, managing virtual environments, pinning versions – Python could do all of this before. uv just makes it so easy and quick that all the little workarounds you were used to are suddenly unnecessary. This is not a leap forward, but rather a tidying up of a toolbox that has grown over 15 years.

The next time you’re ready for your next Python project - or the next time you type requirements.txt - try uv init and uv add instead. The transfer takes less than five minutes and you won’t miss the venv dance.