Skip to main content
Glama
davidsandez

Mini-ERP MCP

by davidsandez
README.md
# 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

A3.6/5.0

Scored across 2 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count2/5

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.

Completeness2/5

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.