Skip to main content
Glama

MacroMCP

Un servidor MCP que convierte un LLM en un asistente de nutrición con memoria real.

Le hablas como le hablarías a una persona: "7 oz de pollo y una taza de arroz". Pregunta lo que necesita, confirma, registra y te lee los números. Más tarde preguntas "¿cómo va mi proteína esta semana?" y responde desde una base de datos en lugar de adivinar.


El problema

Las aplicaciones de seguimiento nutricional fallan de una de dos maneras.

Registradores manuales (MyFitnessPal y similares) son precisos pero agotadores. Busca en una base de datos, elige entre seis entradas casi idénticas, establece el tamaño de la porción, repite con cada ingrediente. La fricción es la característica principal del producto y su principal causa de abandono.

Envoltorios de chat con LLM son sin fricción y silenciosamente incorrectos. Dices "pollo y arroz", el modelo inventa números plausibles, y no hay persistencia, ni procedencia, ni nada que verificar. Pregúntale una semana después qué comiste y no tiene idea.

MacroMCP es el punto intermedio: ingesta conversacional con el modelo haciendo la comprensión, una base de datos haciendo la aplicación y la aritmética, y un paso de confirmación estructurado en medio.


La tensión central

El rigor y la fricción están directamente opuestos. Cada pregunta aclaratoria mejora la calidad de los datos y hace que sea ligeramente menos probable que registres mañana. La mayor parte del trabajo de diseño consiste en comprar precisión sin pagarla en turnos.

El segundo principio organizador:

La aplicación pertenece al servidor, no al prompt del sistema. Un prompt que dice "nunca adivines cantidades" se mantiene por un tiempo y luego falla silenciosamente en el turno 40 de una conversación larga. Una función de commit que devuelve un rechazo con la lista específica de problemas no puede fallar. El prompt maneja el tono y la redacción de las preguntas; la base de datos maneja lo que es representable.


Cómo funciona

Ingesta: parsear → clasificar → resolver → confirmar → commit → leer de vuelta

Parsear convierte una expresión en un borrador estructurado. Su trabajo es transcripción y segmentación, no inferencia. "Pollo y arroz" produce dos elementos con la mayoría de los campos vacíos, y esa es la salida correcta.

Dos invariantes, ambos aplicados en el commit:

  • Cobertura de span. Cada elemento apunta a un rango de caracteres de lo que dijiste. El texto que contiene comida que no produjo ningún elemento se informa, de modo que los elementos omitidos se detectan mecánicamente en lugar de notarse en un total semanal tres meses después.

  • Sin elementos sin anclar. Un elemento sin span es una alucinación y se rechaza. Esto es específicamente lo que impide que el modelo agregue útilmente aceite de cocina que nunca mencionaste.

Clasificar etiqueta cada vacío por tipo, para que el asistente haga una pregunta específica en lugar de un genérico "¿cuánto?". La taxonomía es la parte interesante:

Vacío

Ejemplo

Por qué importa

Recipiente vago

"un tazón", "una taza"

¿Es una taza medidora o una de tu armario?

Dimensión ambigua

"8 oz"

Fluido para leche, peso para pollo. Decidido por la comida.

Estado de preparación

"arroz"

Seco vs cocido es ~3x. La mayor fuente de error individual.

Grasa de cocción

"a la plancha"

Rutinariamente omitida, rutinariamente 100–200 kcal

Variante

"pollo", "leche"

Pechuga vs muslo es un cambio de 2x en grasa

Compuesto

"un sándwich"

Descomponlo, o es una suposición

Alcance de cantidad

"dos huevos y salchichas"

¿El 2 se distribuye?

La puerta de materialidad es lo que evita que esto se convierta en un interrogatorio. Cada vacío lleva el rango calórico entre sus interpretaciones principales. Por debajo del umbral, toma la mejor lectura, márcala como estimada y no gastes turno. Recipiente-vs-medida en café negro es ruido; en arroz son 200 kcal.

Resolver produce gramos y densidades de macronutrientes. Confirmar muestra todo el plan en un bloque y hace todas las preguntas pendientes en un turno — el interrogatorio en serie es lo que hace que las aplicaciones de seguimiento se abandonen.

Commit es la puerta. Rechaza elementos no resueltos, elementos sin anclar, spans omitidos, vacíos materiales abiertos y macronutrientes que fallan una verificación de coherencia.

Leer de vuelta devuelve la entrada confirmada con gramos, macronutrientes por elemento, totales de comida y totales del día. El asistente informa números que el servidor calculó, así que si el borrador se desvió durante una conversación larga, aquí es donde se muestra.

Almacenamiento: cuatro niveles

meals               the eating event.  "chicken and rice", dinner, Aug 19
  meal_logs         one submission.    eaten_at + logged_at
    log_items       one named thing.   "cheeseburger", fraction 1/2
      item_ingredients                 bun 60g, patty 113g, cheese 19g

Por qué existe cada nivel:

  • meals porque un evento de comer tiene un nombre y puede registrarse más de una vez. "Olvidé la salsa" se adjunta a la comida en lugar de crear una segunda cena. Este es también el nivel en el que una comida es propiedad — ver "Multi-usuario" abajo.

  • meal_logs porque olvidar algo es normal, y porque cuando comiste y cuando le dijiste al sistema son hechos diferentes que vale la pena mantener separados.

  • log_items porque la fracción de porción vive aquí. "La mitad de la hamburguesa y todas las papas fritas" no es representable si la fracción está en el log. Los compuestos también reciben un nombre, de modo que la lectura de vuelta dice "hamburguesa con queso, 263 kcal" en lugar de tres filas que tienes que reensamblar.

  • item_ingredients porque una hamburguesa con queso es un pan, una hamburguesa y queso. Cada elemento tiene ingredientes, incluidos los simples — "una taza de arroz" es un elemento con un ingrediente — lo que cuesta una fila de envoltura y compra una única ruta de acumulación sin polimorfismo en ningún lugar.

Todo lo que está por debajo de meals es solo de añadidura. Las correcciones son nuevas filas que reemplazan a las antiguas. meals.name es el único campo mutable en todo el log.

Consulta

El modelo nunca hace aritmética. Cada total, promedio y tendencia se calcula en SQL y se devuelve como JSON estructurado. Un LLM sumando 40 números se equivocará ocasionalmente y silenciosamente, lo que anula el propósito de tener una base de datos.

Los resúmenes van de ingrediente → elemento → log → comida → día, sin redondear en todo el camino, redondeados una vez en la visualización. Los componentes que visiblemente no suman el total destruyen la confianza más rápido que cualquier entrada incorrecta individual.


Multi-usuario

MacroMCP comenzó como de un solo usuario y ahora está construido para un grupo pequeño — un hogar o unos pocos amigos compartiendo una instancia autoalojada, no un producto público multi-tenant.

Cada comida es propiedad de un usuario. meals.user_id es la fuente de verdad; todo lo que está por debajo (meal_logs, log_items, item_ingredients) está limitado uniéndose hacia arriba en lugar de llevar su propia copia. Cada función en la ruta de commit — commit_log, rename_meal, supersede_log, find_attachable_meals — toma el id del usuario que llama como argumento explícito y verifica la propiedad antes de hacer nada, de la misma manera que staging_id es acuñado por el servidor en lugar de confiarse del modelo.

Lo que compra el multi-usuario: dos personas pueden compartir una instancia sin que sus registros, detección de duplicados o tendencias colisionen. Sam registrando el mismo pollo y arroz que Luke registró cinco minutos antes no es un duplicado. El total del martes de Luke no se fusiona silenciosamente con el de Sam.

Lo que deliberadamente no incluye: autenticación. No hay contraseña ni token en este esquema — user_id se confía tal como se da, y resolver quién está llamando realmente (clave API, sesión de inicio de sesión, un servidor MCP por persona) es una decisión de la capa de API, no de la base de datos. Tampoco hay modelo de compartición: los usuarios están completamente aislados entre sí, no son miembros de un hogar que pueden ver los registros de los demás. Si la visibilidad compartida resulta importante, es una característica aditiva sobre esto, no un rediseño.

Consulta docs/design-notes.md para la lista completa de lo que recibió una protección entre usuarios y por qué, y las compensaciones detrás de omitir la seguridad a nivel de fila por ahora.


La apuesta v0

Sin base de datos de referencia. Sin ingesta de USDA, sin Open Food Facts, sin ruta de código de barras, sin tablas de porciones. Las densidades de macronutrientes provienen del conocimiento del propio modelo o de ti, y se almacenan en el ingrediente.

Esta es una apuesta real, así que aquí están ambos lados.

A favor: los modelos modernos saben que la pechuga de pollo es ~165 kcal/100g y que una taza de arroz cocido es ~158g. Buscar eso cuesta latencia, y una ingesta lenta significa no ingesta. Elimina un pipeline de ingesta completo. Y el historial se congela en el momento del registro — ninguna fuente de datos externa puede cambiar silenciosamente lo que dicen tus registros pasados.

En contra: nada externo verifica los números. La única verificación automatizada que queda es la identidad de Atwater — kcal debería ≈ 4·proteína + 4·carbohidratos + 9·grasa — que detecta dígitos transpuestos y suposiciones incoherentes pero no puede detectar una respuesta incorrecta autoconsistente. Un bagel ingresado a 100 kcal/100g con macronutrientes plausibles se registrará; un bagel real es ~270. El paso de confirmación es donde eso se detecta, por eso el bloque de confirmación muestra macronutrientes y no solo gramos.

Dos cosas hacen que la apuesta sea sobrevivible:

Los macronutrientes se envían por 100g, nunca absolutos. "El pollo es 165 kcal por 100g" es recuerdo; "213g de pollo son 351 kcal" es aritmética. Los modelos son confiables en lo primero y poco confiables en lo segundo. Per-100g también significa que el servidor aún hace cada multiplicación, así que las fracciones de elementos siguen funcionando.

La procedencia se registra en cada ingrediente: llm_knowledge, llm_estimate o user_stated. v_daily_data_quality informa qué parte de las calorías de un día provino de cada una. Un día que es 80% adivinado por el modelo merece una confianza diferente a uno que es 80% leído de etiquetas, y esto es lo único que puede decirte cuál tuviste.

Nota: la búsqueda de código de barras está en la lista diferida abajo, y es posterior a esta misma apuesta, no un corte separado — no hay tabla de búsqueda UPC→macros porque no hay base de datos de referencia en absoluto. Construir una es lo que des-difiere ambos a la vez.


Decisiones de diseño que vale la pena conocer

El staging vive en la ventana de contexto. Sin tablas de borrador, sin Redis. La conversación ya lleva el estado en curso. Redis con escritura directa es el siguiente paso planificado; la puerta de commit no cambiará cuando llegue, porque ya toma el payload como argumento en lugar de leer una tabla.

Los duplicados se identifican por contenido, no por el reloj. Una clave (meal, timestamp) rechazaría "ah, y un plátano" — el patrón de registro más común que existe. En su lugar: hashea los ingredientes resueltos, compara dentro de una ventana contra la marca de tiempo de la fila existente, limitado a un usuario. Dos alcances (misma comida / otra comida), ambos suaves, porque dos batidos de proteína idénticos en un día es real.

La idempotencia proviene de una columna única. El servidor acuña un staging_id; es UNIQUE en todos los usuarios. Reintentos, re-disparos del bucle del agente y llamadas concurrentes devuelven la entrada existente en lugar de duplicar.

La adjunción nunca es silenciosa. Adjuntar "olvidé la salsa" a la comida equivocada es peor que crear una espuria, porque corrompe una comida que ya era correcta. El servidor propone candidatos dentro de las propias comidas del llamador; una coincidencia única aún requiere confirmación.

Los nombres nunca se regeneran. Agrega la salsa que olvidaste y "pollo y arroz" sigue siendo "pollo y arroz" en lugar de convertirse en "pollo, arroz y sriracha". Un nombre que cambia bajo tus pies es peor que uno ligeramente incompleto.

Los días cambian a las 4am, no a medianoche. Un refrigerio a la 1:30am pertenece al día en el que aún estás despierto. log_date se materializa en el commit y se deriva del primer registro de la comida, así que una comida nunca puede dividirse entre dos días.

La precisión no es exactitud. La aritmética racional exacta en una porción calculada a ojo aún se registra como estimated. El sistema nunca lava una en la otra.


Lo que deliberadamente no está en v0

  • Búsqueda por código de barras, y la base de datos de alimentos de referencia de la que depende. Sin ingesta de USDA/OFF, sin tabla de búsqueda de UPC — este es el mismo recorte que "no base de datos de referencia" arriba, no dos omisiones separadas.

  • Reutilización de resolución previa ("¿lo mismo que la última vez?") — la principal solución a la fricción, y su ausencia significa que cada comida paga el coste completo de confirmación

  • Seguimiento por lotes (nada garantiza que las fracciones de un plato sumen ≤ 1)

  • Plantillas de recetas

  • Micronutrientes — cuando vuelvan, añade una tabla separada de formato largo en lugar de migrar de vuelta, ya que los macros y los micros tienen diferentes formas y patrones de consulta

  • Autenticación y uso compartido entre usuarios — ver "Multi-usuario" arriba. El alcance por user_id existe; verificar quién es realmente un user_id, y cualquier noción de usuarios que compartan visibilidad sobre los registros de cada uno, no existe.


Stack

FastAPI + Postgres 16, pequeño multi-usuario, autoalojado. Expuesto a través de MCP para que cualquier cliente MCP pueda ser el front end.

Ejecutarlo

El servidor MCP (server/) es un adaptador fino: registra cada herramienta del contrato de docs/intake-agent.md, resuelve el user_id de este proceso una vez al inicio, y llama a la función o vista SQL correspondiente para cada llamada. No tiene lógica propia más allá de eso — la base de datos sigue siendo donde se aplica realmente cada invariante.

Un proceso de servidor = un usuario (ver server/config.py). Esta es la respuesta a la pregunta de "¿cómo resuelve una llamada a un user_id?" de docs/ design-notes.md: para MCP específicamente, cada persona ejecuta su propia instancia del servidor, de la misma manera que Claude Desktop/Code lanzan un subproceso por herramienta configurada.

python3 -m venv .venv && source .venv/bin/activate
pip install -e .

createdb macromcp                       # first time only
psql -d macromcp -f db/schema.sql       # first time only
psql -d macromcp -c "INSERT INTO users (username, display_name) VALUES ('luke','Luke');"

cp .env.example .env   # edit MACROMCP_USERNAME to match the user you just created
export $(cat .env | xargs)
python -m server.server

Apunta un cliente MCP (Claude Desktop, Claude Code, un puente de llamada a funciones de OpenAI Realtime) a python -m server.server con ese entorno, y cada herramienta de docs/intake-agent.md está activa.

El frontend de voz GPT Realtime mini descrito en docs/intake-agent.md aún no está conectado — este servidor solo necesita algún cliente que hable MCP o que llame a funciones delante de él para ser útil de extremo a extremo.

Archivos

  • db/schema.sql — DDL completo, puerta de confirmación multi-usuario, vistas de resumen. Se carga limpio en PG16.

  • db/tests.sql — 18 pruebas de invariantes (13 núcleo, 5 comprobaciones de aislamiento entre usuarios), todas pasan.

  • docs/design-notes.md — justificación completa del diseño, las compensaciones multi-usuario, los bordes afilados.

  • docs/intake-agent.md — el prompt del sistema y el contrato de llamada a herramientas/funciones para el frontend conversacional (GPT Realtime mini), emparejado campo por campo con la carga útil de fn_commit_log.

  • docs/erd/ — diagrama del esquema (todavía muestra la forma de un solo usuario; aún no regenerado para multi-usuario).

  • server/ — el servidor MCP que implementa el contrato de herramientas (db.py acceso a Postgres, models.py validación de carga útil, tools.py lógica de negocio, server.py registro de herramientas).

  • historial de diseño previo de un solo usuario: git log db/schema.sql.

-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lukew0824/MacroMCPv2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server