Skip to main content
Glama
nudge-digital-lab

TiendaTech Support MCP Server

README.md
# Agente de soporte con servidor MCP propio

Proyecto 1 de una serie de 7 proyectos chicos, cada uno enfocado en una habilidad puntual que aparece hoy en avisos de trabajo para desarrolladores de agentes de IA. Este cubre: **construir y consumir un servidor MCP (Model Context Protocol) desde cero.**

## El problema que resuelve

Una tienda de electrónica ("TiendaTech") necesita un agente de soporte que pueda:

- Responder preguntas de clientes sobre políticas (devoluciones, garantía, envíos) sin inventar información.
- Consultar y crear tickets de soporte reales, no simulados.

En vez de meter toda esa lógica adentro del prompt o hardcodeada en el cliente del LLM, ese acceso a datos vive en un **servidor MCP separado**: un proceso independiente que expone qué información y qué acciones están disponibles, con un contrato bien definido. El agente (el cliente que habla con Claude) no sabe nada de archivos ni de JSON — solo sabe que existe un servidor con ciertos recursos y herramientas.

Esto es exactamente el problema que MCP fue diseñado para resolver: separar "qué puede hacer un agente" de "qué modelo lo está operando", para no tener que reescribir la integración de datos cada vez que cambiás de LLM o de cliente.

## Arquitectura

```mermaid
flowchart LR
    subgraph Cliente["src/agent.ts (proceso 1)"]
        CLI[REPL en terminal]
        MCPClient[Cliente MCP]
        Claude[Anthropic SDK]
    end

    subgraph Servidor["src/server.ts (proceso 2, hijo)"]
        MCPServer[Servidor MCP]
        Tools[Tools: search_tickets / get_ticket / create_ticket]
        Resources[Resources: artículos de la KB]
    end

    DB[(data/tickets.json)]
    KB[(data/kb/*.md)]

    CLI --> MCPClient
    MCPClient <-->|stdio JSON-RPC| MCPServer
    MCPServer --> Tools
    MCPServer --> Resources
    Tools <--> DB
    Resources <--> KB
    MCPClient --> Claude
    Claude -->|decide qué tool llamar| MCPClient
```

Son **dos procesos Node separados** que se comunican por stdio con el protocolo MCP (JSON-RPC 2.0):

1. **`src/server.ts`** — el servidor MCP. No sabe nada de Claude ni de IA. Solo expone:
   - **Resources** (`kb://politica-devoluciones.md`, etc.): los 3 artículos de la base de conocimiento, como contenido de solo lectura pensado para dar contexto.
   - **Tools** (`search_tickets`, `get_ticket`, `create_ticket`): acciones que leen y escriben sobre `data/tickets.json`, una base de tickets de soporte.
2. **`src/agent.ts`** — el agente. Levanta el servidor como proceso hijo, se conecta como cliente MCP, y hace de puente entre MCP y la API de Claude:
   - Al arrancar, lee todos los *resources* y los inyecta en el system prompt como contexto fijo.
   - Traduce la lista de *tools* del servidor al formato de tool-use de la API de Claude.
   - Corre un loop de conversación: si Claude decide llamar una tool, el agente ejecuta esa llamada contra el servidor MCP real y le devuelve el resultado, hasta que Claude da una respuesta final en texto.

## Decisiones técnicas y por qué

- **Resources vs. Tools, a propósito.** MCP distingue explícitamente entre datos de contexto (*resources*, decisión de cuándo leerlos la toma el host/agente) y acciones invocables por el modelo (*tools*, decisión de cuándo usarlas la toma el LLM). Usar ambos primitivos — y no meter todo como tools — muestra que la separación es intencional, no que "tools es lo único que existe en MCP".
- **Transporte stdio, no HTTP/SSE.** Para un servidor MCP local de un solo cliente, stdio es más simple: no hay puertos, no hay CORS, no hay que levantar un server HTTP aparte. El SDK oficial de Anthropic soporta stdio, SSE y Streamable HTTP — stdio es el punto de entrada correcto para entender el protocolo antes de pasar a un despliegue remoto.
- **JSON planos en vez de SQLite/Postgres.** El foco del proyecto es demostrar el protocolo MCP (cómo se define un tool, cómo se valida su input, cómo un cliente lo descubre y lo invoca), no la capa de persistencia. Cambiar `data/tickets.json` por una base real implica tocar únicamente `readTickets`/`writeTickets` en `server.ts` — el contrato MCP hacia el agente no cambia.
- **TypeScript + SDK oficiales.** `@modelcontextprotocol/sdk` (servidor y cliente) y `@anthropic-ai/sdk` son los paquetes oficiales de Anthropic. Se usa la API de alto nivel `McpServer` (`registerTool`/`registerResource`) en vez de la API de bajo nivel con `setRequestHandler`, porque valida automáticamente el input contra el schema de Zod y da mensajes de error más claros — importante cuando el que "llena" los argumentos es un LLM y no un humano.
- **Sin frameworks de agentes (LangChain, etc.).** El loop de tool-use (llamar al modelo → detectar `tool_use` → ejecutar → devolver `tool_result` → repetir) se escribe a mano en ~40 líneas. Para un solo agente con 3 tools, un framework agrega abstracción sin resolver un problema real; y entender ese loop sin magia es la habilidad que se quiere mostrar acá.

## Stack

- Node.js 22 + TypeScript
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) — servidor y cliente MCP
- [`@anthropic-ai/sdk`](https://www.npmjs.com/package/@anthropic-ai/sdk) — Claude (modelo `claude-sonnet-4-5`)
- `zod` — validación de inputs de las tools
- Sin base de datos externa: `data/tickets.json` + `data/kb/*.md`

## Cómo correrlo

```bash
npm install
cp .env.example .env   # completar ANTHROPIC_API_KEY
npm start               # compila y levanta el agente (que a su vez levanta el servidor MCP)
```

Ejemplo de conversación:

```
Vos: hola, tengo un teclado que no me terminó gustando, ¿lo puedo devolver?
Agente: Sí, podés devolverlo dentro de los 30 días de la compra siempre que esté sin uso...

Vos: se me rompió el auricular izquierdo de mis auriculares, ¿tienen algún ticket mío?
  → llamando herramienta MCP: search_tickets({"query":"auricular"})
Agente: Sí, encontré el ticket #1 a nombre de Marcos Videla, sigue abierto...
```

Si querés inspeccionar el servidor MCP de forma aislada (sin Claude), se puede correr con el [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) oficial:

```bash
npm run build
npx @modelcontextprotocol/inspector node dist/server.js
```

## Estructura

```
src/
  server.ts     # servidor MCP: resources + tools
  agent.ts      # cliente MCP + integración con Claude + REPL
data/
  kb/*.md       # base de conocimiento (resources)
  tickets.json  # base de tickets (tools)
```

## Qué demuestra este proyecto

- Entender MCP como protocolo (no solo como "una librería más"): distinción resources/tools, transporte, ciclo de vida de conexión.
- Diseñar el input schema de una tool con Zod pensando en que quien la va a llamar es un LLM, no un formulario.
- Integrar el tool-use de la API de Claude con una fuente de tools que no está hardcodeada, sino descubierta dinámicamente en runtime contra un servidor externo.