Skip to main content
Glama

MacroMCP

MCP-сервер, который превращает LLM в ассистента по питанию с настоящей памятью.

Вы общаетесь с ним так же, как с человеком: «7 унций курицы и чашка риса». Он спрашивает, что ему нужно, подтверждает, фиксирует и считывает цифры. Позже вы спрашиваете «как у меня с белком на этой неделе» — и он отвечает из базы данных, а не наугад.


Проблема

Приложения для отслеживания питания терпят неудачу одним из двух способов.

Ручные журналы (MyFitnessPal и подобные) точны, но изнурительны. Поиск в базе, выбор из шести почти одинаковых записей, установка размера порции, повтор для каждого ингредиента. Трение — это главная особенность продукта и главная причина отказа от него.

LLM-обёртки для чатов — без трения и тихо ошибочны. Вы говорите «курица и рис», модель выдумывает правдоподобные цифры, и нет ни сохранения, ни происхождения данных, ни возможности проверить. Спросите через неделю, что вы ели, — она понятия не имеет.

MacroMCP — это середина: разговорный ввод, где модель выполняет понимание, база данных — контроль и арифметику, а между ними — структурированный шаг подтверждения.


Основное противоречие

Строгость и трение прямо противоположны. Каждый уточняющий вопрос улучшает качество данных и делает вас чуть менее склонным вести журнал завтра. Большая часть работы над дизайном — это покупка точности без оплаты её ходами.

Второй организующий принцип:

Контроль принадлежит серверу, а не системному промпту. Промпт, говорящий «никогда не угадывай количества», действует какое-то время, а затем тихо сбоит на 40-м ходе длинного разговора. Функция коммита, которая возвращает отклонение с конкретным списком проблем, не может сбоить. Промпт отвечает за тон и формулировку вопросов; база данных — за то, что представимо.


Как это работает

Ввод: разбор → классификация → разрешение → подтверждение → коммит → считывание

Разбор превращает высказывание в структурированный черновик. Его задача — транскрипция и сегментация, а не вывод. «Курица и рис» даёт два элемента с большинством пустых полей, и это правильный результат.

Два инварианта, оба контролируются при коммите:

  • Покрытие диапазона. Каждый элемент указывает на диапазон символов в том, что вы сказали. Текст, содержащий еду, но не породивший элемент, сообщается, так что потерянные элементы обнаруживаются механически, а не замечаются в недельном итоге три месяца спустя.

  • Нет непривязанных элементов. Элемент без диапазона — это галлюцинация, и он отклоняется. Именно это не даёт модели «помогать», добавляя масло для жарки, которое вы не упоминали.

Классификация помечает каждый пробел по виду, чтобы ассистент задавал целенаправленный вопрос вместо общего «сколько?». Таксономия — самая интересная часть:

Пробел

Пример

Почему это важно

Неопределённая ёмкость

«миска», «чашка»

Это мерная чашка или та, что из вашего шкафа?

Неоднозначное измерение

«8 унций»

Жидкость для молока, вес для курицы. Решается по продукту.

Состояние приготовления

«рис»

Сухой против варёного — ~3x. Крупнейший источник ошибок.

Жир для готовки

«жареный на сковороде»

Обычно опускается, обычно 100–200 ккал

Вариант

«курица», «молоко»

Грудка против бедра — разница в жире в 2 раза

Составное

«сэндвич»

Разложите его, иначе это догадка

Область количества

«два яйца и сосиски»

Распределяется ли двойка?

Порог материальности — это то, что не даёт этому превратиться в допрос. Каждый пробел несёт разброс калорий между его лучшими интерпретациями. Ниже порога — берём лучшее чтение, помечаем как оценку и не тратим ход. Ёмкость против меры для чёрного кофе — это шум; для риса — 200 ккал.

Разрешение даёт граммы и плотности макронутриентов. Подтверждение показывает весь план одним блоком и задаёт все открытые вопросы за один ход — последовательные вопросы — это то, из-за чего приложения для отслеживания бросают.

Коммит — это ворота. Он отклоняет неразрешённые элементы, непривязанные элементы, потерянные диапазоны, открытые материальные пробелы и макронутриенты, не прошедшие проверку согласованности.

Считывание возвращает зафиксированную запись с граммами, макронутриентами по элементам, итогами по приёму пищи и итогами за день. Ассистент сообщает цифры, вычисленные сервером, так что если черновик сместился во время длинного разговора, это видно здесь.

Хранение: четыре уровня

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

Зачем существует каждый уровень:

  • meals — потому что событие приёма пищи имеет имя и может быть зарегистрировано более одного раза. «Я забыл соус» прикрепляется к приёму пищи, а не создаёт второй ужин. Это также уровень, на котором приём пищи принадлежит — см. «Многопользовательский режим» ниже.

  • meal_logs — потому что забыть что-то — это нормально, и потому что когда вы ели и когда вы сообщили системе — это разные факты, которые стоит хранить раздельно.

  • log_items — потому что доля порции живёт здесь. «Половина бургера и весь картофель фри» не представимо, если доля лежит на журнале. Составные также получают имя, так что считывание говорит «чизбургер, 263 ккал» вместо трёх строк, которые вам нужно собирать заново.

  • item_ingredients — потому что чизбургер — это булочка, котлета и сыр. Каждый элемент имеет ингредиенты, включая простые — «чашка риса» — это элемент с одним ингредиентом — что стоит строки-обёртки и даёт единый путь свёртки без полиморфизма где-либо.

Всё ниже mealsтолько добавление. Исправления — это новые строки, которые заменяют старые. meals.name — единственное изменяемое поле во всём журнале.

Запрос

Модель никогда не выполняет арифметику. Каждый итог, среднее и тренд вычисляются в SQL и возвращаются как структурированный JSON. LLM, суммирующая 40 чисел, будет ошибаться иногда и молча, что сводит на нет весь смысл наличия базы данных.

Свёртки идут ингредиент → элемент → журнал → приём пищи → день, без округления на всём пути, округление один раз при отображении. Компоненты, которые заметно не сходятся с итогом, разрушают доверие быстрее, чем любая отдельная неверная запись.


Многопользовательский режим

MacroMCP начинался как однопользовательский, а теперь рассчитан на небольшую группу — домохозяйство или несколько друзей, использующих один самостоятельно размещённый экземпляр, а не публичный мультитенантный продукт.

Каждый приём пищи принадлежит пользователю. meals.user_id — источник истины; всё ниже (meal_logs, log_items, item_ingredients) ограничено через соединение с ним, а не несёт собственную копию. Каждая функция на пути коммита — commit_log, rename_meal, supersede_log, find_attachable_meals — принимает идентификатор вызывающего пользователя как явный аргумент и проверяет владение перед любым действием, так же как staging_id создаётся сервером, а не доверяется модели.

Что даёт многопользовательский режим: два человека могут использовать один экземпляр без пересечения их журналов, обнаружения дубликатов или трендов. Запись Сэма о той же курице с рисом, которую Люк записал пятью минутами ранее, не является дубликатом. Вторничный итог Люка не тихо сливается с итогом Сэма.

Чего он намеренно не включает: аутентификацию. В этой схеме нет пароля или токена — user_id доверяется как есть, и определение кто на самом деле вызывает (API-ключ, сессия входа, один MCP-сервер на человека) — это решение уровня API, а не базы данных. Также нет модели совместного доступа: пользователи полностью изолированы друг от друга, а не являются членами домохозяйства, которые могут видеть журналы друг друга. Если общая видимость окажется важной, это аддитивная функция поверх этого, а не переработка.

См. docs/design-notes.md для полного списка того, что получило защиту между пользователями и почему, а также компромиссы, связанные с отказом от безопасности на уровне строк на данный момент.


Ставка v0

Нет справочной базы данных. Ни импорта USDA, ни Open Food Facts, ни пути штрихкодов, ни таблиц порций. Плотности макронутриентов берутся из собственных знаний модели или от вас и хранятся на ингредиенте.

Это реальная ставка, поэтому вот обе стороны.

За: современные модели знают, что куриная грудка — это ~165 ккал/100 г, а чашка варёного риса — ~158 г. Поиск этого стоит задержки, а медленный ввод означает отсутствие ввода. Это устраняет целый конвейер импорта. И история замораживается при регистрации — никакой внешний источник данных не может молча изменить то, что говорят ваши прошлые записи.

Против: ничто внешнее не перекрёстно проверяет цифры. Единственная автоматическая проверка, которая осталась, — это тождество Атуотера — ккал должны ≈ 4·белок + 4·углеводы + 9·жир — которое ловит переставленные цифры и бессвязные догадки, но не может поймать самосогласованный неверный ответ. Рогалик, введённый как 100 ккал/100 г с правдоподобными макронутриентами, будет зафиксирован; настоящий рогалик — ~270. Именно на шаге подтверждения это обнаруживается, поэтому блок подтверждения показывает макронутриенты, а не только граммы.

Две вещи делают ставку жизнеспособной:

Макронутриенты отправляются на 100 г, никогда не абсолютные. «Курица — 165 ккал на 100 г» — это воспоминание; «213 г курицы — это 351 ккал» — арифметика. Модели надёжны в первом и ненадёжны во втором. На 100 г также означает, что сервер по-прежнему выполняет каждое умножение, так что доли элементов продолжают работать.

Происхождение записывается для каждого ингредиента: llm_knowledge, llm_estimate, или user_stated. v_daily_data_quality сообщает, какая доля калорий дня пришла из каждого. День, который на 80% состоит из догадок модели, заслуживает иного доверия, чем день, который на 80% состоит из прочитанных этикеток, и только это может сказать вам, какой из них у вас был.

Примечание: поиск по штрихкоду находится в списке отложенных ниже, и он является следствием этой же ставки, а не отдельным срезом — нет таблицы соответствия UPC→макронутриенты, потому что справочной базы данных вообще нет. Построение одной — это то, что одновременно снимает отсрочку с обоих.


Проектные решения, о которых стоит знать

Промежуточное состояние живёт в контекстном окне. Никаких таблиц черновиков, никакого Redis. Разговор уже несёт состояние в процессе. Redis с записью на лету — это запланированный следующий шаг; ворота коммита не изменятся, когда он появится, потому что они уже принимают полезную нагрузку как аргумент, а не читают таблицу.

Дубликаты определяются по содержимому, а не по часам. Ключ (meal, timestamp) отклонил бы «о, и банан» — самый распространённый паттерн регистрации. Вместо этого: хэш разрешённых ингредиентов, сравнение в окне с временной меткой существующей строки, ограниченное одним пользователем. Два уровня (тот же приём пищи / другой приём пищи), оба мягкие, потому что два одинаковых протеиновых коктейля за один день — это реальность.

Идемпотентность обеспечивается одной уникальной колонкой. Сервер создаёт staging_id; он UNIQUE для всех пользователей. Повторные попытки, повторные срабатывания цикла агента и параллельные вызовы возвращают существующую запись вместо дублирования.

Прикрепление никогда не бывает молчаливым. Прикрепление «я забыл соус» к неправильному приёму пищи хуже, чем создание ложного, потому что это портит приём пищи, который уже был правильным. Сервер предлагает кандидатов среди собственных приёмов пищи вызывающего; даже единственное совпадение требует подтверждения.

Имена никогда не перегенерируются. Добавьте забытый соус, и «курица и рис» останется «курица и рис», а не станет «курица, рис и шрирача». Имя, которое меняется у вас под ногами, хуже, чем слегка неполное.

Дни переходят в 4 утра, а не в полночь. Перекус в 1:30 ночи принадлежит дню, в котором вы ещё бодрствуете. log_date материализуется при коммите и выводится из первого журнала приёма пищи, так что приём пищи никогда не может разделиться на два дня.

Точность — это не аккуратность. Точная рациональная арифметика над оценённой порцией всё равно записывается как estimated. Система никогда не превращает одно в другое.


Что намеренно не входит в v0

  • Поиск по штрихкоду и зависимая от него справочная база продуктов. Без импорта USDA/OFF, без таблицы поиска UPC — это тот же срез, что и «без справочной базы» выше, а не два отдельных пропуска.

  • Повторное использование предыдущего разрешения («как в прошлый раз?») — основное исправление трения, и его отсутствие означает, что каждый приём пищи оплачивает полную стоимость подтверждения

  • Пакетное отслеживание (ничто не гарантирует, что доли одного блюда в сумме дают ≤ 1)

  • Шаблоны рецептов

  • Микронутриенты — когда они вернутся, добавьте отдельную таблицу длинного формата, а не мигрируйте обратно, поскольку макро- и микронутриенты имеют разные формы и паттерны запросов

  • Аутентификация и общий доступ между пользователями — см. «Многопользовательский режим» выше. Скоупинг user_id существует; проверка того, кем на самом деле является user_id, и любое понятие общего доступа пользователей к журналам друг друга — нет.


Стек

FastAPI + Postgres 16, небольшой многопользовательский, self-hosted. Доступен через MCP, так что любой MCP-клиент может быть фронтендом.

Запуск

MCP-сервер (server/) — это тонкий адаптер: он регистрирует каждый инструмент из контракта docs/intake-agent.md, разрешает user_id этого процесса один раз при запуске и вызывает соответствующую SQL-функцию или представление для каждого вызова. У него нет собственной логики помимо этого — база данных по-прежнему является местом, где фактически обеспечивается каждый инвариант.

Один процесс сервера = один пользователь (см. server/config.py). Это ответ на вопрос «как вызов разрешается в user_id» из docs/ design-notes.md: для MCP конкретно каждый человек запускает свой собственный экземпляр сервера, так же как Claude Desktop/Code запускает один подпроцесс на настроенный инструмент.

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

Направьте MCP-клиент (Claude Desktop, Claude Code, мост вызова функций OpenAI Realtime) на python -m server.server с этим окружением, и каждый инструмент из docs/intake-agent.md будет активен.

Мини-голосовой фронтенд GPT Realtime, описанный в docs/intake-agent.md, ещё не подключён — этому серверу просто нужен какой-нибудь клиент, говорящий на MCP или вызывающий функции, перед ним, чтобы быть полезным от начала до конца.

Файлы

  • db/schema.sql — полный DDL, многопользовательский коммит-гейт, сводные представления. Чисто загружается на PG16.

  • db/tests.sql — 18 тестов инвариантов (13 основных, 5 проверок изоляции между пользователями), все проходят.

  • docs/design-notes.md — полное обоснование дизайна, многопользовательские компромиссы, острые углы.

  • docs/intake-agent.md — системный промпт и контракт инструментов/вызова функций для разговорного фронтенда (GPT Realtime mini), совпадающий поле-в-поле с полезной нагрузкой fn_commit_log.

  • docs/erd/ — диаграмма схемы (всё ещё показывает однопользовательскую форму; ещё не перегенерирована для многопользовательского режима).

  • server/ — MCP-сервер, реализующий контракт инструментов (db.py доступ к Postgres, models.py валидация полезной нагрузки, tools.py бизнес-логика, server.py регистрация инструментов).

  • предыдущая история дизайна для одного пользователя: 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