Mostrador MCP
by schbs
README.md
# Mostrador MCP · Stock, precios y presupuestos de una ferretería, como tools para agentes
Servidor MCP en TypeScript (SDK oficial) para una ferretería ficticia: busca productos, consulta stock, calcula el
precio con las reglas de descuento y arma presupuestos. Lo usan Claude Desktop o Claude Code por stdio y un chat de
mostrador en Next.js por HTTP, con un agente que trabaja solo con esas tools.
· [English version](README.en.md)





> **Proyecto de demostración.** "Ferretería El Tornillo" es una marca ficticia. Los 300 productos, las marcas, los
> precios y los clientes (`@example.com`) son inventados y se generan por script.
## El problema
En el mostrador de una ferretería la pregunta es siempre la misma: *¿tienen esto?, ¿cuánto sale?, ¿hay stock?, ¿me
hacés un presupuesto?*. La respuesta está en el sistema de stock, pero hay que buscarla a mano, aplicar los descuentos
por cantidad y por medio de pago, y pasarla a un presupuesto. Un asistente de IA puede hacerlo, siempre que lea los
datos reales y no invente precios.
## La solución
- **Servidor MCP** con el SDK oficial de TypeScript (v2). Cuatro tools y dos resources:
| Tool | Qué hace | Tipo |
|---|---|---|
| `buscar_producto(q, categoria?, limit, offset)` | Busca por nombre, marca o categoría sin importar acentos; máximo 50 por página, con `next_offset` | lectura |
| `consultar_stock(sku)` | Stock actual, con salida estructurada | lectura |
| `precio(sku, cantidad, medio_pago)` | Precio final con el detalle de las reglas (volumen, medio de pago, tope) | lectura |
| `crear_presupuesto(items, cliente_ficticio, medio_pago)` | Guarda un presupuesto válido por 7 días; valida stock | **escritura, solo con `FLAG_WRITE=true`** |
Resources: `catalogo://categorias` (JSON) y `politicas://envios` (markdown).
- **Reglas de precio** (función pura con tests): 5 % desde 10 unidades y 10 % desde 50; efectivo −10 %, transferencia
−5 %, débito igual, crédito en 3 cuotas +10 %. Los descuentos se combinan con un tope de 15 %.
- **Dos transportes:** stdio (Claude Desktop y Claude Code lo lanzan como subproceso) y HTTP streamable con token
Bearer (el chat web). Guía: [`docs/claude-desktop.md`](docs/claude-desktop.md).
- **Chat de mostrador** (Next.js): el agente (Gemini con tool use) recibe la lista de tools que anuncia el servidor,
llama a las que necesita por HTTP y contesta con una función de salida estructurada (`responder_al_cliente`:
respuesta + SKUs). El **tablero perforado** muestra los productos con el precio y el stock que devolvieron las
tools, nunca los del texto del modelo, y el presupuesto con su número. Cada respuesta muestra las llamadas al
servidor con su argumento principal y el JSON de los argumentos. Antes de la primera consulta, el tablero muestra un
ejemplo grabado de una respuesta real (se graba con `node scripts/grabar-ejemplo.mjs`). En español y en inglés
(`/en`).
- **Base:** Postgres. Con `DATABASE_URL` usa Supabase u otro Postgres; sin ella, **PGlite** (Postgres en WASM) con la
misma migración, así corre en cualquier PC sin cuentas.
## Demo
| | |
|---|---|
|  |  |
|  |  |
|  |  |
Capturas en inglés en [`docs/capturas/en/`](docs/capturas/en/). Las respuestas son reales del agente.
## Arquitectura
```mermaid
flowchart LR
CL[Claude Desktop / Claude Code] -->|stdio| SRV
subgraph WEB[App Next.js]
UI[Chat de mostrador + tablero] -->|POST /api/chat| AG[Agente: Gemini con tool use]
AG -->|HTTP streamable + Bearer| EP["/api/mcp"]
end
EP --> SRV[Servidor MCP<br/>tools + resources]
SRV --> DB[(Postgres / PGlite:<br/>productos, presupuestos)]
SRV --> AUD[(Auditoría:<br/>tool, args, ok, duración)]
```
```mermaid
sequenceDiagram
participant C as Cliente
participant A as Agente (Gemini)
participant M as Servidor MCP
C->>A: "¿Cuánto salen 12 martillos ET-HM-0001 con transferencia?"
A->>M: tools/list
M-->>A: buscar_producto, consultar_stock, precio, crear_presupuesto
A->>M: tools/call precio {sku, cantidad: 12, medio_pago: transferencia}
M->>M: Zod (strictObject) → reglas → auditoría
M-->>A: structuredContent {total: 99148, descuento_total_pct: 9.8, ...}
A-->>C: responder_al_cliente {respuesta, skus: [ET-HM-0001]}
Note over C: El tablero dibuja el precio del structuredContent, no del texto
```
## Seguridad
- **Solo lectura por defecto.** `crear_presupuesto` se registra únicamente con `FLAG_WRITE=true`; sin el flag ni
aparece en `tools/list` (lo cubre un test). En la demo web está prendido sobre una base en memoria con datos
ficticios, que se reinicia sola.
- **Validación estricta.** Todas las entradas son `z.strictObject` (un argumento de más se rechaza), los SKU tienen
formato fijo, las cantidades van de 1 a 1000, los resultados se cortan en 50 y las respuestas largas se recortan.
Los presupuestos solo aceptan emails `@example.com` y validan stock antes de guardar.
- **Auditoría.** Cada llamada a una tool queda registrada con sus argumentos, si salió bien o mal, el detalle y la
duración y el transporte.
- **HTTP con token.** `/api/mcp` exige `Authorization: Bearer <MCP_TOKEN>` (comparación en tiempo constante); sin
token configurado, no atiende a nadie. El servidor HTTP propio escucha solo en `127.0.0.1` y valida Host y Origin.
- **Errores que guían.** Los errores dicen cómo seguir ("buscalo con buscar_producto") en lugar de devolver trazas.
- **Chat:** entradas de hasta 600 caracteres, límite de 12 pedidos por minuto por IP, y la clave de Gemini solo en el
servidor. El prompt le prohíbe decir que hizo algo que ninguna tool hizo (por ejemplo, mandar el presupuesto por mail).
- **Consultas parametrizadas** en todas las tools; ninguna arma SQL con texto del usuario.
## Stack y skills demostradas
MCP (servidor y cliente con el SDK oficial v2, stdio y HTTP streamable, tools con `outputSchema` y anotaciones,
resources) · agente con tool use y salida estructurada (Gemini) con modelo de respaldo · Zod 4 · Postgres / PGlite ·
Next.js 16 (App Router, route handlers) · TypeScript estricto · Tailwind 4 · ES/EN · Vitest · Playwright · axe-core ·
Lighthouse.
## Cómo correrlo
```bash
npm install
npm test # 16 tests con el cliente MCP del SDK en memoria y PGlite, sin red
npm run build # servidor MCP en dist/
npm run cliente # cliente de prueba por stdio: lista las tools y ejecuta buscar_producto y precio
```
Para Claude Desktop o Claude Code, ver [`docs/claude-desktop.md`](docs/claude-desktop.md).
Chat web (necesita una clave de Gemini, gratis en aistudio.google.com):
```bash
cp .env.example .env.local # GEMINI_API_KEY y un MCP_TOKEN largo; npm run check-env dice OK/FALTA sin mostrar valores
npm run build:web
npm run start:web # http://localhost:3208 (y /en)
npm run casos # los 10 casos del agente
npm run test:e2e # e2e y axe (Brave o Chrome)
npm run lighthouse # móvil, mediana de 3
```
## Tests y resultados medidos (01/11/2026)
| Prueba | Resultado |
|---|---|
| Tests (`npm test`) | **16/16**: reglas de precio (3) y servidor con el cliente MCP en memoria (13): lista de tools con y sin flag, resources, búsqueda sin acentos y paginada, tope de 50, entradas inválidas, SKU inexistente, descuento con tope, **escritura bloqueada sin flag**, presupuesto con flag y auditoría |
| Casos del agente (`npm run casos`, [`tests/agent-cases.json`](tests/agent-cases.json)) | **10/10**: usó la tool esperada y la respuesta trae el dato correcto. Además, **13/13 importes** citados en las respuestas salen de un resultado de tool o de la política de envíos (ninguno inventado) |
| e2e y accesibilidad (`npm run test:e2e`) | **8/8**: precio y presupuesto reales de punta a punta (navegador → agente → MCP → tablero), cambio de idioma, 401 sin token y 400 con entradas inválidas; axe **0 violaciones serias o críticas** en celular y compu, vacío y con el tablero lleno |
| Lighthouse móvil (mediana de 3) | `/` **98**/100/100/100 · `/en` **98**/100/100/100 |
| Latencia del chat | **p50 2,4 s** por consulta (entre 1 y 3 llamadas al modelo) |
| Costo por consulta | **USD 0,0016** con Gemini 3.5 Flash-Lite (precio del plan pago); USD 0 en el plan gratis, que es con el que se midió |
## Costos de operación estimados
| Concepto | Costo |
|---|---|
| Gemini 3.5 Flash-Lite (plan pago) | ≈ USD 0,0016 por consulta (USD 1,60 cada 1.000) |
| Gemini en el plan gratis | USD 0, con cupo por minuto y por día |
| Servidor MCP local (Claude Desktop / Code) | USD 0: corre en la PC con PGlite |
| Hosting (Vercel Hobby) y Supabase gratis | USD 0 para la demo |
## Límites y próximos pasos
- En la web, la base es PGlite en memoria por instancia: los presupuestos de la demo no se comparten entre visitas.
Con `DATABASE_URL` (Supabase) se vuelven persistentes.
- El agente usa Gemini; el servidor MCP es el mismo para cualquier cliente (Claude, OpenAI Agents, etc.).
- Siguiente: OAuth para el endpoint HTTP en lugar del token fijo, más tools (pedidos, proveedores) y un stock que se
descuente al confirmar el presupuesto.
## Autoría
Desarrollado por Germán Schouabs.
## Licencia
MIT. Ver [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues