PTENES
MÓDULO 5.1

📦 Cómo empaquetar tu MCP Server

Transforma tu server en un paquete profesional de Python: instalable, distribuible y listo para publicar.

6
Temas
30
Minutos
Avanzado
Nivel
Práctico
Tipo
1

📦 Por qué empaquetar

Tener un MCP Server funcionando como script es excelente para el desarrollo, pero para distribuirlo, versionarlo y permitir que otros usen tu trabajo, necesitas transformarlo en un paquete profesional de Python. Empaquetar significa crear una estructura que permita instalar con pip o uv, declarar dependencias, definir versiones y compartir con la comunidad de forma confiable.

💎 Concepto principal

Empaquetar tu MCP Server significa transformarlo de un script suelto en un proyecto Python con una estructura formal: metadatos declarados en el pyproject.toml, dependencias explícitas, versionado semántico y un entry point que permite ejecutar el server con un solo comando en la terminal. Un paquete se puede instalar con pip install o uv pip install, distribuido a través de PyPI o GitHub y versionado con tags Git. La diferencia entre un script suelto y un paquete profesional es la diferencia entre un prototipo y un producto.

❌ Evitar
  • •Distribuirlo como un archivo .py suelto por correo electrónico
  • •Instalar dependencias manualmente en cada máquina
  • •Sin control de versiones: es imposible saber qué versión se está ejecutando
  • •Instrucciones de instalación: "copia este archivo y reza"
✅ Hacer
  • •Estructura con pyproject.toml y src layout
  • •Dependencias declaradas y versionadas
  • •Instalación con un comando: uv pip install .
  • •Versión clara y rastreable mediante semver
💡 Consejo práctico

Piensa en el empaquetado como poner tu server en una caja profesional con manual de instrucciones. Sin la caja, tienes piezas sueltas que solo tú sabes cómo ensamblar. Con la caja, cualquiera puede instalar y usar todo con un solo comando. Empieza con uv init para crear la estructura básica automáticamente.

2

📄 pyproject.toml

O pyproject.toml es el archivo central de cualquier proyecto moderno de Python. Reemplaza los antiguos setup.py e setup.cfg con una sintaxis declarativa clara, estandarizada por PEP 621. Aquí defines el nombre del paquete, la versión, la descripción, las dependencias y cómo debe construirse el paquete.

💎 Concepto principal

Un pyproject.toml típico para un MCP Server:

[project]
name = "meu-mcp-server"
version = "0.1.0"
description = "MCP Server para consulta de dados"
requires-python = ">=3.10"
dependencies = [
    "fastmcp>=0.1.0",
    "httpx>=0.25",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project.scripts]
meu-server = "meu_server.server:main"

La sección [project] define metadatos, [build-system] define cómo construir y [project.scripts] crea comandos ejecutables en la terminal.

💡 Consejo práctico

Usa hatchling como build backend: es moderno, rápido y funciona perfectamente con uv. Si prefieres, setuptools también funciona, pero hatchling es la opción recomendada para proyectos nuevos. Nunca más escribas un setup.py — o pyproject.toml es el estándar oficial desde PEP 621.

📊 Datos e investigación

La PEP 621 (2021) estandarizó los metadatos de proyectos Python en pyproject.toml. Desde entonces, más de 85% de los paquetes nuevos publicados en PyPI adoptaron este formato. El setup.py se considera legado: funciona, pero no se recomienda para proyectos nuevos. Herramientas modernas como uv, hatch e rye trabajan de forma nativa con pyproject.toml.

3

📁 Organización de directorios

La organización de directorios de un paquete de Python sigue convenciones que facilitan la creación, las pruebas y la distribución. El patrón src layout es el enfoque recomendado para evitar problemas de importación y garantizar que las pruebas se ejecuten contra el paquete instalado, no contra el código fuente local.

💎 Concepto principal

Estructura recomendada para un MCP Server empaquetado:

meu-mcp-server/
├── pyproject.toml
├── README.md
├── src/
│   └── meu_server/
│       ├── __init__.py
│       └── server.py
└── tests/
    └── test_server.py

El directorio src/ aísla el código fuente. El __init__.py marca el directorio como paquete de Python. El server.py contiene la lógica del servidor MCP con la función main().

❌ Evitar
  • •Poner server.py en la raíz del proyecto
  • •Olvidar el __init__.py en el paquete
  • •Mezclar pruebas con código fuente
  • •Usar un diseño flat sin src/ para paquetes distribuibles
✅ Hacer
  • •Usar el layout src para un aislamiento correcto
  • •Separar las pruebas en un directorio propio
  • •Incluir README.md en la raíz
  • •Mantener pyproject.toml siempre en la raíz
📋 Pasos para crear la estructura
Paso 1

Crear el directorio raíz del proyecto e inicializarlo con uv init meu-mcp-server.

Paso 2

Crear src/meu_server/ con __init__.py y mover el código del servidor a server.py.

Paso 3

Crear directorio tests/ y configurar el pyproject.toml con metadatos, dependencias y entry points.

4

🔧 Puntos de entrada

Los entry points son la magia que permite transformar tu MCP Server en un comando ejecutable en la terminal. En lugar de ejecutar python -m meu_server.server, el usuario simplemente escribe meu-server y se ejecuta el paquete. Esto se configura en la sección [project.scripts] del pyproject.toml.

💎 Concepto principal

La configuración del entry point en pyproject.toml:

[project.scripts]
meu-server = "meu_server.server:main"

Esto le dice a Python: "cuando alguien escriba meu-server en la terminal, ejecuta la función main() del archivo server.py dentro del paquete meu_server." Después de la instalación, el comando queda disponible globalmente en el entorno virtual activo.

💡 Consejo práctico

Asegúrate de que tu función main() en el server.py llama mcp.run(). Esta es la función que se invocará cuando el usuario ejecute el comando en la terminal. Prueba siempre con uv pip install -e . (modo editable) durante el desarrollo, así los cambios en el código se reflejan de inmediato sin necesidad de reinstalar.

🚨 Alerta

El nombre del comando (antes de =) usa guiones: meu-server. La ruta del módulo (después de =) usa underscores: meu_server.server:main. Mezclar ambos es un error común que resulta en ModuleNotFoundError. Recuerda: guiones en la terminal, underscores en Python.

5

📋 Dependencias y versiones

Declarar correctamente las dependencias es esencial para que tu paquete funcione en cualquier máquina. En pyproject.toml, enumeras todas las bibliotecas que necesita tu server, con restricciones de versión que garantizan la compatibilidad sin bloquear las actualizaciones. El uv lock genera un lockfile que fija las versiones exactas para garantizar la reproducibilidad.

💎 Concepto principal

Las dependencias se declaran en la sección [project] con versionado semántico (semver):

dependencies = [
    "fastmcp>=0.1.0",      # mínimo 0.1.0
    "httpx>=0.25,<1.0",    # entre 0.25 e 1.0
    "pydantic>=2.0",       # mínimo 2.0
]

Versionado semántico (versionado semántico) usa el formato MAJOR.MINOR.PATCH: MAJOR cambia cuando hay breaking changes, MINOR para nuevas features, PATCH para correcciones de errores. Usa >= para valores mínimos y < para límites de compatibilidad.

❌ Evitar
  • •Fijar versiones exactas: fastmcp==0.1.0
  • •No declarar una versión mínima: httpx
  • •Olvidar dependencias que "ya están instaladas"
  • •No ejecutar uv lock para generar un lockfile
✅ Hacer
  • •Usar rangos: >=0.1.0,<1.0
  • •Listar TODAS las dependencias directas
  • •Ejecutar uv lock y confirmar con un commit el uv.lock
  • •Probar en un entorno limpio antes de publicar
📊 Datos e investigación

Según los análisis de PyPI, 23% de los problemas de instalación de paquetes de Python se deben a dependencias mal especificadas: versiones incompatibles, dependencias faltantes o restricciones demasiado estrictas. El uso de archivos de bloqueo (uv.lock) reduce los fallos de reproducibilidad en más de 90%, lo que garantiza que todos los desarrolladores y entornos de producción usen exactamente las mismas versiones.

6

🧪 Probando el paquete

Antes de distribuir tu paquete, necesitas verificar que funcione cuando esté instalado — no solo cuando se ejecuta directamente desde el código fuente. Esto significa construir el paquete con uv build, instalar el archivo .whl generado y probar que el entry point funcione correctamente.

💎 Concepto principal

El proceso de build genera archivos distribuibles en la carpeta dist/:

# Construir o pacote
$ uv build
Successfully built meu_server-0.1.0-py3-none-any.whl

# Instalar o .whl gerado
$ uv pip install dist/meu_server-0.1.0-py3-none-any.whl

# Testar o entry point
$ meu-server
MCP Server rodando...

El archivo .whl (wheel) es el formato estándar de distribución de Python. Contiene tu código, metadatos e información de entry points — todo lo que pip/uv necesita para instalarlo correctamente.

📋 Lista de verificación de prueba del paquete
Paso 1

Ejecutar uv build y verificar que el .whl se genere sin errores en la carpeta dist/.

Paso 2

Crear un entorno virtual limpio e instalar el .whl con uv pip install — simula una instalación real.

Paso 3

Ejecutar el comando del entry point y verificar que el server se inicie correctamente y responda a solicitudes MCP.

💡 Consejo práctico

Siempre prueba en un entorno virtual limpio — no en el mismo entorno donde desarrollaste. Esto garantiza que todas las dependencias estén declaradas correctamente en el pyproject.toml. Si algo funciona en tu entorno de desarrollo, pero falla en uno limpio, significa que olvidaste declarar una dependencia.

📝 Resumen del módulo

  • ✓ Empaquetar transforma tu server de script suelto en un paquete profesional: instalable, distribuible y con control de versiones.
  • ✓ pyproject.toml es el archivo central: reúne metadatos, dependencias, build system y entry points en un solo lugar.
  • ✓ El src layout aísla el código fuente y evita problemas de importación durante las pruebas y la distribución.
  • ✓ Los entry points crean comandos ejecutables en la terminal: el usuario ejecuta su server con un solo comando.
  • ✓ Las dependencias deben declararse con rangos de versiones y bloquearse con uv lock para que sean reproducibles.
  • ✓ Siempre prueba el paquete compilado en un entorno limpio antes de distribuirlo.

Siguiente Módulo: 5.2 — Publicando en GitHub