Mini-ERP MCP
# Mini-ERP MCP — proyecto de aprendizaje
Servidor de **Model Context Protocol (MCP)** construido sobre un dominio de
ERP simulado (productos, clientes, ventas), para entender de punta a punta
cómo un modelo de lenguaje puede descubrir y usar herramientas y datos
externos de forma estandarizada, en vez de mediante integraciones a medida
por cada proveedor de LLM.
> **Contexto:** este es un proyecto personal de exploración técnica, no un
> producto en producción ni un desarrollo para cliente. El dominio de ERP es
> deliberadamente mínimo (un archivo JSON como "base de datos") — el objetivo
> no es la lógica de negocio, sino comprender el protocolo.
## Qué hace
Simula el flujo de un mensaje entrante (por ejemplo, de WhatsApp) que llega a
un sistema tipo ERP. El sistema arma el contexto relevante para el modelo,
le ofrece un conjunto de herramientas, y el modelo decide de forma autónoma
qué acción tomar para responder — incluyendo una acción con efecto real
(registrar una venta), que requiere confirmación humana antes de ejecutarse.
## Conceptos que este proyecto pone en práctica
MCP define tres primitivas, y este proyecto usa las tres con un criterio de
diseño explícito para cada una:
| Elemento | Tipo | Por qué |
|---|---|---|
| `catalogo://productos` | **Resource** | Dato estable; lo decide inyectar el host de entrada, sin que el modelo lo pida. |
| `cliente://{id}` | **Resource** (URI con parámetro) | El host ya conoce el ID de quien escribe (vendría del webhook); arma la URI y la inyecta. |
| `consultar_stock(producto_id)` | **Tool** | El modelo decide, en medio de la conversación, si la necesita. |
| `registrar_venta(cliente_id, producto_id, cantidad)` | **Tool destructiva** | Acción con efecto real; requiere confirmación humana antes de ejecutarse. |
| `resumen_ventas(dias)` | **Prompt** | Plantilla reutilizable para una tarea recurrente. |
La distinción central que este proyecto permite comprobar en la práctica:
**con una tool, el host le cede al modelo la decisión de invocarla; con un
resource, el host retiene esa decisión para sí.**
## Arquitectura
```
┌───────────────────────┐ stdio ┌───────────────────────┐
│ Host (host_demo.py) │◄────────►│ Servidor MCP │
│ - Arma el contexto │ │ (server.py, FastMCP) │
│ - Ofrece tools al LLM │ │ - Resources │
│ - Ejecuta el loop de │ │ - Tools │──► data/erp.json
│ tool-calling │ │ - Prompts │
│ - Pide confirmación │ └───────────────────────┘
│ humana en acciones │
│ destructivas │
└──────────┬────────────────┘
│ API compatible OpenAI (router de HF) o API de Anthropic
▼
┌─────────────┐
│ LLM │
└─────────────┘
```
`host_demo.py` cumple, en este proyecto, tanto el rol de **host** como de
**client MCP** (en una integración real, el cliente MCP suele ser un módulo
dentro del host, no una pieza aparte).
## Stack
- **`mcp[cli]`** (FastMCP) — SDK oficial de Python para servidores MCP.
- **Hugging Face Inference Providers** — modelos open source, vía el router
compatible con la API de OpenAI (`openai` SDK apuntando a
`https://router.huggingface.co/v1`).
- **Anthropic SDK** — soporte alternativo, mantenido en paralelo para
comparar el mismo servidor MCP funcionando con dos proveedores distintos.
- **MCP Inspector** — herramienta de desarrollo para probar el servidor sin
necesidad de un cliente real.
## Cómo correrlo en local
```bash
cp .env.example .env # completar HF_TOKEN (y opcionalmente ANTHROPIC_API_KEY)
uv sync
```
### Probar el servidor de forma aislada (sin LLM)
```bash
uv run mcp dev server.py
```
Abre el MCP Inspector en el navegador, donde se pueden invocar las tools y
leer los resources manualmente.
### Correr el flujo completo (host + LLM real)
```bash
uv run host_demo.py
```
Por defecto usa Hugging Face (`procesar_mensaje_huggingface`). El código
para usar Anthropic (`procesar_mensaje`) queda disponible en el mismo
archivo, comentado en el bloque `if __name__ == "__main__":`.
## Variables de entorno
```dotenv
HF_TOKEN=tu_token_de_huggingface
HF_MODEL=openai/gpt-oss-120b
# Opcional, si se quiere probar con Anthropic en vez de Hugging Face
ANTHROPIC_API_KEY=tu_api_key_de_anthropic
```
## Qué aprendí construyendo esto
- El mecanismo real de descubrimiento e invocación de herramientas en MCP:
el modelo nunca habla directo con el servidor MCP, es el host quien media
cada `list_tools()` / `call_tool()` / `read_resource()`.
- La diferencia entre *function calling* nativo de cada proveedor de LLM
(formatos distintos entre Anthropic y APIs compatibles con OpenAI) y cómo
MCP estandariza la exposición de herramientas sin depender de esos
formatos particulares.
- Que no todos los modelos servidos por un proveedor de inferencia soportan
tool-calling de forma confiable, y cómo verificarlo antes de integrar.
- Dónde vive la responsabilidad del control humano sobre acciones con
efecto (no es algo que el protocolo resuelva automáticamente, es una
decisión de diseño del host).
- Portabilidad real: el mismo `server.py`, sin modificaciones, funciona
igual con dos proveedores de LLM distintos.
## Estado del proyecto
Proyecto de aprendizaje, completo y funcional para el alcance definido.
Pendiente como posible extensión futura: conectar el mismo servidor a
Claude Desktop para comprobar la portabilidad contra un cliente real (no
solo el script `host_demo.py`).TDQS
Scored across 2 tools
The two tools are unambiguously distinct: consultar_stock is a read-only stock lookup and registrar_venta is a state-changing sale registration. Descriptions explicitly frame the boundary (read vs. real-effect write), so an agent cannot confuse them.
Both names follow the same Spanish verb_noun pattern (consultar_stock, registrar_venta) with consistent snake_case and imperative verbs. There is no mixed convention.
Two tools is far too thin for a server scoped as a 'Mini-ERP' handling inventory and sales; even a minimal ERP needs product listing, restock/adjustment, and sales history. The surface only covers a single narrow checkout flow.
Only a read-stock plus create-sale pair exists, with no product catalog/listing, no inventory update or restock, no sale retrieval or cancellation, and no reporting. Agents will hit dead ends for any operation beyond checking one product and recording one sale.