Skip to main content
Glama

Asistente de Tareas MCP

Proyecto didáctico que se ejecuta como un único servicio de Docker Compose: envía los mensajes del usuario a un LLM en Groq, y permite que el LLM gestione una lista de tareas en memoria (in-memory) usando cinco herramientas MCP (list_tasks, list_tasks_by_priority, create_task, update_task, delete_task). Cada tarea tiene un campo priority (urgencia: baja/media/alta).

¿Qué hace?

Envías un mensaje en lenguaje natural al endpoint POST /chat (p. ej. "marca la tarea de Docker como completada"). El servidor de chat envía ese mensaje a Groq junto con el esquema de las 5 herramientas MCP disponibles. El modelo puede hacer llamadas a herramientas en secuencia si es necesario (p. ej. llamar primero a list_tasks para encontrar el id); cada llamada se valida contra el JSON Schema, se ejecuta a través del servidor MCP real y su resultado se devuelve al modelo. Al final se devuelven la respuesta en lenguaje natural y un rastro (trace) visible de todo el proceso.

Related MCP server: MCP Project Manager

Arquitectura

Dos procesos Node.js independientes, dentro del mismo contenedor, que se comunican mediante JSON-RPC sobre stdio:

[app sureci]                          [mcp-server sureci]
Express (/chat)                       (child process, stdio ile baslatiliyor)
  |- groq/           --HTTP-->  Groq API
  `- mcp-client/     --stdio/JSON-RPC-->  mcp-server/  -->  task-store/

Archivo

Responsabilidad

src/task-store

CRUD de tareas, almacén en memoria basado en Map, datos semilla

src/mcp-server

Convierte task-store en 5 herramientas MCP con JSON Schema, escucha stdio+JSON-RPC

src/mcp-client

Inicia mcp-server como proceso hijo, mantiene una única conexión (singleton)

src/groq

Envía peticiones a Groq, conversión de esquema MCP a formato de herramienta de Groq

src/app

Endpoint /chat, bucle de llamadas a herramientas, validación con ajv, generación de trace

Instalación y ejecución

1) Obtén una clave de API de Groq

  1. Ve a https://console.groq.com/keys e inicia sesión.

  2. Crea una nueva clave con "Create API Key" y ponle el nombre que quieras (p. ej. mcp-gorev-asistani).

  3. Copia la clave mostrada (gsk_...): no se vuelve a mostrar.

2) Crea el archivo .env

cp .env.example .env

Abre el archivo .env y pega tu clave al final de la línea GROQ_API_KEY=.

Nota: el valor de GROQ_MODEL puede cambiar con el tiempo: Groq añade y retira modelos periódicamente. Para ver la lista actual: curl -s https://api.groq.com/openai/v1/models -H "Authorization: Bearer $GROQ_API_KEY"

3) Ejecuta con Docker Compose

docker compose up --build -d

Para ver los logs:

docker compose logs -f

Cuando veas la línea Chat sunucusu http://localhost:3000 adresinde calisiyor. (el servidor de chat se está ejecutando en http://localhost:3000), ya está listo (puerto 3000 dentro del contenedor, expuesto al exterior como 3001 a través de compose.yaml; si el 3000 está ocupado en tu máquina, puedes cambiar la línea ports en compose.yaml).

Para detenerlo:

docker compose down

Mensajes de prueba

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Hangi görevlerim var?"}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "JSON Schema öğrenmek için bir görev ekle."}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Docker görevini tamamlandı olarak işaretle."}'

curl -X POST http://localhost:3001/chat -H "Content-Type: application/json" \
  -d '{"message": "Tamamlanan görevi sil."}'

Ejemplo de respuesta (tercer mensaje: observa que el id se encuentra primero con list_tasks y luego se pasa a update_task):

{
  "answer": "\"Docker Compose kur\" görevi tamamlandı olarak işaretlendi.",
  "trace": [
    { "tool": "list_tasks", "arguments": {}, "validation": "passed",
      "result": { "tasks": [ { "id": 1, "title": "MCP sartnamesini oku", "completed": false },
        { "id": 2, "title": "Docker Compose kur", "completed": false },
        { "id": 3, "title": "Groq API anahtarini al", "completed": true } ] } },
    { "tool": "update_task", "arguments": { "completed": true, "id": 2 }, "validation": "passed",
      "result": { "id": 2, "title": "Docker Compose kur", "completed": true } }
  ]
}

El cuarto mensaje ("Tamamlanan görevi sil." — "Borra la tarea completada") mostró un comportamiento interesante durante las pruebas: como en los datos semilla ya había una tarea completada (id=3) y tras el quinto mensaje se creó otra tarea completada (id=2), el modelo dudó entre las dos opciones y, en lugar de adivinar, preguntó al usuario cuál quería decir — sin llamar a ninguna herramienta. Esto es un comportamiento esperado/deseado del proyecto (no borrar la tarea equivocada), no un error.

Preguntas frecuentes

¿Por qué añadir una nueva herramienta (p. ej. list_tasks_by_priority) solo requiere modificar 2 archivos? Porque las capas app, mcp-client y groq NO tienen las herramientas codificadas de forma fija (hardcode): app pregunta a mcp-server "¿qué tienes?" con listMcpTools() en cada petición y pasa la lista devuelta tal cual a Groq. Es decir, para definir una nueva herramienta solo hay que (1) añadir la lógica a task-store y (2) el esquema a mcp-server; todo lo demás fluye automáticamente. Es la materialización concreta de la decisión de "separar responsabilidades" del Paso 1.

¿Por qué no hay base de datos y se usan datos en memoria? El pliego de condiciones lo pide explícitamente: el proyecto busca enseñar el protocolo MCP y el flujo de llamadas a herramientas; el almacenamiento persistente es un tema aparte que solo habría añadido complejidad innecesaria. Map + datos semilla proporcionan de forma gratuita el comportamiento de "empezar desde un estado limpio en cada arranque".

¿Por qué Docker Compose? ¿No bastaba con un único comando node? Docker elimina el problema de "en mi máquina funciona" y garantiza que el proyecto se ejecute igual en cualquier equipo. Compose, por su parte, permite arrancar los servicios (aunque aquí solo haya uno) de forma estándar y con un único comando: un ejercicio cercano a los despliegues del mundo real.

¿Por qué hay validación con JSON Schema? ¿No bastaba con confiar en Groq? La salida de un LLM no es determinista: el modelo a veces puede generar argumentos incompletos o de tipo incorrecto. Ir directamente a task-store sin validar con ajv podría provocar errores inesperados o datos incoherentes. La validación es la traducción en código del principio "no te fíes, comprueba" aplicado al LLM.

¿Por qué las definiciones de herramientas van en el campo tools y no en el mensaje de sistema? El campo tools es un contrato estructurado en la API de Groq/OpenAI: el modelo lo interpreta como funciones reales y llamables, y genera la respuesta en el formato estructurado tool_calls. Si lo escribiéramos como texto plano en el mensaje de sistema, el modelo lo leería solo como contexto, sin garantía ni estructura de llamada.

¿Por qué mcp-client no reinicia mcp-server en cada petición? task-store vive en la RAM del proceso mcp-server. Si se iniciara un proceso nuevo en cada petición, los datos volverían a los valores semilla cada vez y se perderían los cambios hechos en mensajes anteriores. Por eso mcp-client mantiene UNA única conexión (singleton) con mcp-server mientras el proceso de app siga activo.

¿Por qué no basta con una sola llamada a Groq y hace falta un bucle (loop)? Cuando el usuario dice "marca la tarea de Docker", el modelo no conoce su id: primero tiene que llamar a list_tasks para encontrar el id correcto y después llamar a la herramienta que hace la operación real con ese id. Eso implica varias llamadas a herramientas consecutivas en una sola petición; el flujo fijo de "pregunta-ejecuta-explica" no lo soporta: hace falta un bucle real.

Limitaciones conocidas / puntos no listos para producción

  • Sin persistencia: si el contenedor se reinicia (o se cae o se vuelve a desplegar), se pierden todos los datos de las tareas. En uso real haría falta una base de datos (Postgres, SQLite, etc.).

  • Sin distinción de múltiples usuarios / sesiones: todos los usuarios comparten el mismo task-store; no hay aislamiento entre usuarios (multi-tenancy).

  • Sin memoria de conversación: cada petición /chat empieza de forma independiente. El usuario no puede referirse a mensajes anteriores ("borra esa también") — el contexto solo se conserva durante el bucle de herramientas dentro de la misma petición.

  • Una sola llamada a herramienta simultánea: aunque el modelo pida varias herramientas en el mismo turno (tool_calls paralelos), solo se procesa la primera.

  • Sin autenticación / autorización: el endpoint /chat está abierto a cualquiera, sin ningún control de acceso.

  • Sin límite de tamaño de entrada / de velocidad (rate limiting): clientes malintencionados o defectuosos pueden enviar peticiones sin límite y la factura de Groq puede dispararse en consecuencia.

  • El esquema de Ajv se recompila en cada petición: ajv.compile(...) podría cachearse por rendimiento (a pequeña escala no se nota).

  • El nombre del modelo puede quedar obsoleto con el tiempo: el catálogo de modelos de Groq cambia (durante este proyecto se retiró llama-3.3-70b-versatile) — hay que revisar GROQ_MODEL periódicamente.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A simple, powerful Todo list manager for Claude Desktop and other MCP-compatible AI assistants. Organize your tasks across different projects with priorities and never lose track of what needs to be done!
    15 npm
    2
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A task manager MCP server that demonstrates all three MCP primitives (tools, resources, prompts). Enables users to manage tasks, read task summaries and details, and run structured planning/review prompts through natural language.
    -