MacroMCP
MacroMCP
Ein MCP-Server, der aus einem LLM einen Ernährungsassistenten mit echtem Gedächtnis macht.
Du sprichst mit ihm, wie du mit einem Menschen sprechen würdest: „7oz Hühnchen und eine Tasse Reis." Er fragt, was er fragen muss, bestätigt, speichert und liest die Zahlen zurück. Später fragst du „wie sieht's diese Woche mit meinem Protein aus" und er antwortet aus einer Datenbank, statt zu raten.
Das Problem
Ernährungs-Tracking-Apps scheitern auf eine von zwei Arten.
Manuelle Logger (MyFitnessPal und Konsorten) sind genau, aber ermüdend. Datenbank durchsuchen, aus sechs nahezu identischen Einträgen wählen, Portionsgröße festlegen, für jede Zutat wiederholen. Die Reibung ist das Hauptmerkmal des Produkts und zugleich der Hauptgrund für seine Abwanderung.
LLM-Chat-Wrapper sind reibungslos und leise falsch. Du sagst „Hühnchen und Reis", das Modell erfindet plausible Zahlen, und es gibt keine Persistenz, keine Herkunft und nichts zum Prüfen. Frag es eine Woche später, was du gegessen hast, und es hat keine Ahnung.
MacroMCP ist die Mitte: konversationelle Erfassung, wobei das Modell das Verständnis übernimmt, eine Datenbank die Durchsetzung und die Arithmetik, und ein strukturierter Bestätigungsschritt dazwischen.
Der Kernkonflikt
Strenge und Reibung stehen in direktem Gegensatz. Jede Rückfrage verbessert die Datenqualität und macht es zugleich etwas unwahrscheinlicher, dass du morgen loggst. Der größte Teil der Designarbeit besteht darin, Genauigkeit zu kaufen, ohne sie in Interaktionen zu bezahlen.
Das zweite Ordnungsprinzip:
Durchsetzung gehört in den Server, nicht in den System-Prompt. Ein Prompt, der sagt „rate niemals Mengen", hält eine Weile und versagt dann leise in Runde 40 einer langen Konversation. Eine Commit-Funktion, die eine Ablehnung mit der konkreten Problemliste zurückgibt, kann nicht versagen. Der Prompt übernimmt Ton und Frageformulierung; die Datenbank übernimmt, was darstellbar ist.
So funktioniert es
Erfassung: parsen → klassifizieren → auflösen → bestätigen → committen → zurücklesen
Parsen verwandelt eine Äußerung in einen strukturierten Entwurf. Seine Aufgabe ist Transkription und Segmentierung, keine Inferenz. „Hühnchen und Reis" ergibt zwei Einträge mit den meisten Feldern leer, und das ist das korrekte Ergebnis.
Zwei Invarianten, beide beim Commit durchgesetzt:
Span-Abdeckung. Jeder Eintrag zeigt auf einen Zeichenbereich dessen, was du gesagt hast. Text, der Lebensmittel enthält, aber keinen Eintrag erzeugt hat, wird gemeldet, sodass verlorene Einträge mechanisch erfasst werden, statt in einer Wochensumme drei Monate später aufzufallen.
Keine unverankerten Einträge. Ein Eintrag ohne Span ist eine Halluzination und wird abgelehnt. Genau das verhindert, dass das Modell hilfreich Speiseöl hinzufügt, das du nie erwähnt hast.
Klassifizieren taggt jede Lücke nach Art, damit der Assistent etwas Gezieltes fragt, statt eines generischen „wie viel?". Die Taxonomie ist der interessante Teil:
Lücke | Beispiel | Warum es wichtig ist |
Vages Gefäß | „eine Schüssel", „eine Tasse" | Ist das ein Messbecher oder einer aus deinem Schrank? |
Mehrdeutige Dimension | „8 oz" | Flüssigkeit für Milch, Gewicht für Hühnchen. Entscheidet das Lebensmittel. |
Zubereitungszustand | „Reis" | Trocken vs. gekocht ist ~3x. Größte einzelne Fehlerquelle. |
Garfett | „in der Pfanne gebraten" | Regelmäßig weggelassen, regelmäßig 100–200 kcal |
Variante | „Hühnchen", „Milch" | Brust vs. Schenkel ist ein 2x-Fett-Unterschied |
Verbund | „ein Sandwich" | Zerlege es, oder es ist eine Schätzung |
Mengenbezug | „zwei Eier und Würstchen" | Verteilt sich die 2? |
Die Materialitätsschwelle ist das, was das Ganze nicht zum Verhör werden lässt. Jede Lücke trägt die Kalorienspanne zwischen ihren wichtigsten Interpretationen. Unterhalb der Schwelle: beste Lesart nehmen, als geschätzt markieren, keine Interaktion ausgeben. Gefäß-vs-Messbecher bei schwarzem Kaffee ist Rauschen; bei Reis sind es 200 kcal.
Auflösen erzeugt Gramm und Makro-Dichten. Bestätigen zeigt den gesamten Plan in einem Block und stellt alle offenen Fragen in einer einzigen Interaktion — serielles Nachfragen ist das, wofür Tracking-Apps verlassen werden.
Committen ist das Tor. Es lehnt unaufgelöste Einträge, unverankerte Einträge, verlorene Spans, offene materiale Lücken und Makros ab, die eine Kohärenzprüfung nicht bestehen.
Zurücklesen gibt den committeten Eintrag mit Gramm, Makros pro Eintrag, Mahlzeiten-Summen und Tages-Summen zurück. Der Assistent berichtet Zahlen, die der Server berechnet hat — wenn der Entwurf während einer langen Konversation abgedriftet ist, zeigt es sich hier.
Speicherung: vier Ebenen
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 19gWarum jede Ebene existiert:
meals, weil ein Essereignis einen Namen hat und mehr als einmal geloggt werden kann. „Ich habe die Soße vergessen" hängt sich an die Mahlzeit, statt ein zweites Abendessen zu erzeugen. Dies ist auch die Ebene, auf der eine Mahlzeit besessen wird — siehe „Mehrbenutzer" unten.
meal_logs, weil Vergessen normal ist und weil wann du gegessen hast und wann du es dem System gesagt hast verschiedene Fakten sind, die man getrennt halten sollte.
log_items, weil der Portionsbruch hier lebt. „Den halben Burger und alle Pommes" ist nicht darstellbar, wenn der Bruch auf dem Log sitzt. Verbunde bekommen außerdem einen Namen, sodass das Zurücklesen „Cheeseburger, 263 kcal" sagt, statt drei Zeilen, die du wieder zusammensetzen musst.
item_ingredients, weil ein Cheeseburger ein Brötchen, ein Patty und Käse ist. Jeder Eintrag hat Zutaten, auch einfache — „eine Tasse Reis" ist ein Eintrag mit einer Zutat —, was eine Wrapper-Zeile kostet und einen einzigen Rollup-Pfad ohne Polymorphismus irgendwo kauft.
Alles unterhalb von meals ist append-only. Korrekturen sind neue Zeilen, die alte
ersetzen. meals.name ist das einzige veränderbare Feld im gesamten Log.
Abfrage
Das Modell rechnet nie. Jede Summe, jeder Durchschnitt und jeder Trend wird in SQL berechnet und als strukturiertes JSON zurückgegeben. Ein LLM, das 40 Zahlen summiert, wird gelegentlich und still falsch liegen, was den ganzen Sinn einer Datenbank zunichte macht.
Rollups gehen Zutat → Eintrag → Log → Mahlzeit → Tag, ungerundet durchgehend, gerundet einmal bei der Anzeige. Komponenten, die sichtbar nicht zur Summe passen, zerstören Vertrauen schneller als jeder einzelne falsche Eintrag.
Mehrbenutzer
MacroMCP startete als Einzelbenutzer-Anwendung und ist jetzt für eine kleine Gruppe gebaut — einen Haushalt oder ein paar Freunde, die eine selbst gehostete Instanz teilen, kein öffentliches Multi-Tenant-Produkt.
Jede Mahlzeit gehört einem Benutzer. meals.user_id ist die Quelle der Wahrheit;
alles darunter (meal_logs, log_items, item_ingredients) wird begrenzt, indem man
nach oben verbindet, statt eine eigene Kopie zu tragen. Jede Commit-Pfad-Funktion —
commit_log, rename_meal, supersede_log,
find_attachable_meals — nimmt die Benutzer-ID des Aufrufers als explizites
Argument und prüft den Besitz, bevor sie etwas tut, genauso wie
staging_id serverseitig geprägt wird, statt dem Modell vertraut zu werden.
Was Mehrbenutzer kauft: Zwei Personen können eine Instanz teilen, ohne dass ihre Logs, Duplikaterkennung oder Trends kollidieren. Wenn Sam dasselbe Hühnchen mit Reis loggt, das Luke fünf Minuten zuvor geloggt hat, ist das kein Duplikat. Lukes Dienstagssumme wird nicht still mit Sams verschmolzen.
Was es bewusst nicht enthält: Authentifizierung. Es gibt kein
Passwort und keinen Token in diesem Schema — user_id wird als gegeben vertraut, und
die Auflösung, wer tatsächlich aufruft (API-Key, Login-Sitzung, ein MCP-Server
pro Person), ist eine API-Ebenen-Entscheidung, keine Datenbank-Entscheidung. Es gibt auch
kein Freigabemodell: Benutzer sind vollständig voneinander isoliert, keine Mitglieder
eines Haushalts, die die Logs der anderen sehen können. Falls sich geteilte Sichtbarkeit
als wichtig herausstellt, ist das ein additives Feature obendrauf, kein Umbau.
Siehe docs/design-notes.md für die vollständige Liste, was einen Cross-User-Schutz
bekam und warum, sowie die Abwägungen hinter dem vorläufigen Verzicht auf
Row-Level-Security.
Die v0-Wette
Keine Referenzdatenbank. Kein USDA-Import, kein Open Food Facts, kein Barcode-Pfad, keine Portionstabellen. Makro-Dichten stammen aus dem eigenen Wissen des Modells oder von dir und werden auf der Zutat gespeichert.
Das ist eine echte Wette, also hier beide Seiten.
Dafür: Moderne Modelle wissen, dass Hühnerbrust ~165 kcal/100g hat und dass eine Tasse gekochter Reis ~158g wiegt. Das Nachschlagen kostet Latenz, und langsame Erfassung bedeutet keine Erfassung. Es entfernt eine gesamte Import-Pipeline. Und die Historie wird zum Log-Zeitpunkt eingefroren — keine externe Datenquelle kann still ändern, was deine vergangenen Logs aussagen.
Dagegen: Nichts Externes prüft die Zahlen gegenzu. Die einzige verbleibende automatisierte Prüfung ist die Atwater-Identität — kcal sollte ≈ 4·Protein + 4·Kohlenhydrate + 9·Fett —, die vertauschte Ziffern und inkohärente Schätzungen erwischt, aber nicht eine in sich konsistente falsche Antwort. Ein Bagel, der mit 100 kcal/100g und plausiblen Makros eingegeben wird, wird committet; ein echter Bagel hat ~270. Der Bestätigungsschritt ist, wo das auffällt, weshalb der Bestätigungsblock Makros zeigt und nicht nur Gramm.
Zwei Dinge machen die Wette überlebensfähig:
Makros werden pro 100g gesendet, nie absolut. „Hühnchen hat 165 kcal pro 100g" ist Erinnerung; „213g Hühnchen sind 351 kcal" ist Arithmetik. Modelle sind beim ersten zuverlässig und beim zweiten unzuverlässig. Pro-100g bedeutet außerdem, dass der Server weiterhin jede Multiplikation macht, sodass Eintragsbrüche funktionieren.
Herkunft wird auf jeder Zutat aufgezeichnet: llm_knowledge, llm_estimate
oder user_stated. v_daily_data_quality berichtet, welcher Anteil der Kalorien eines
Tages aus welcher Quelle stammt. Ein Tag, der zu 80% vom Modell geschätzt ist, verdient
anderes Vertrauen als einer, der zu 80% vom Etikett abgelesen ist, und das ist das
Einzige, das dir sagen kann, welchen du hattest.
Hinweis: Barcode-Suche steht auf der aufgeschobenen Liste unten, und sie hängt an derselben Wette, nicht an einem separaten Schnitt — es gibt keine UPC→Makro-Nachschlagetabelle, weil es überhaupt keine Referenzdatenbank gibt. Eine zu bauen ist das, was beides gleichzeitig von der Liste nimmt.
Designentscheidungen, die man kennen sollte
Staging lebt im Kontextfenster. Keine Entwurfs-Tabellen, kein Redis. Die Konversation trägt bereits den laufenden Zustand. Redis mit Write-Through ist der geplante nächste Schritt; das Commit-Tor wird sich nicht ändern, wenn es kommt, weil es die Nutzlast bereits als Argument nimmt, statt eine Tabelle zu lesen.
Duplikate werden nach Inhalt identifiziert, nicht nach Uhrzeit. Ein (meal, timestamp)-Schlüssel
würde „oh, und eine Banane" ablehnen — das häufigste Logging-Muster überhaupt. Stattdessen:
die aufgelösten Zutaten hashen, innerhalb eines Fensters gegen den Zeitstempel der
bestehenden Zeile vergleichen, auf einen Benutzer begrenzt. Zweifach begrenzt (gleiche
Mahlzeit / andere Mahlzeit), beide weich, weil zwei identische Proteinshakes an einem Tag
real sind.
Idempotenz kommt von einer einzigen eindeutigen Spalte. Der Server prägt eine
staging_id; sie ist UNIQUE über alle Benutzer hinweg. Wiederholungen, erneute
Agenten-Schleifen-Auslöser und parallele Aufrufe geben alle den bestehenden Eintrag
zurück, statt zu duplizieren.
Anhängen ist nie still. „Ich habe die Soße vergessen" an die falsche Mahlzeit zu hängen, ist schlimmer, als eine überflüssige zu erzeugen, weil es eine Mahlzeit korrumpiert, die bereits korrekt war. Der Server schlägt Kandidaten innerhalb der eigenen Mahlzeiten des Aufrufers vor; selbst eine einzige Übereinstimmung erfordert Bestätigung.
Namen regenerieren nie. Füge die vergessene Soße hinzu, und „Hühnchen und Reis" bleibt „Hühnchen und Reis", statt zu „Hühnchen, Reis und Sriracha" zu werden. Ein Name, der sich unter dir verschiebt, ist schlimmer als einer, der leicht unvollständig ist.
Tage wechseln um 4 Uhr morgens, nicht um Mitternacht. Ein Snack um 1:30 Uhr gehört zu
dem Tag, an dem du noch wach bist. log_date wird beim Commit materialisiert und von
dem ersten Log der Mahlzeit abgeleitet, sodass eine Mahlzeit nie auf zwei Tage aufgeteilt
werden kann.
Präzision ist nicht Genauigkeit. Exakte rationale Arithmetik auf einer nach Augenmaß
geschätzten Portion wird trotzdem als estimated aufgezeichnet. Das System wäscht nie
eines ins andere.
Was bewusst nicht in v0 ist
Barcode-Suche und die Referenz-Lebensmitteldatenbank, von der sie abhängt. Kein USDA/OFF-Import, keine UPC-Nachschlagetabelle – das ist derselbe Verzicht wie „keine Referenzdatenbank“ oben, nicht zwei getrennte Auslassungen.
Wiederverwendung der vorherigen Auflösung („wie beim letzten Mal?“) – die wichtigste Lösung gegen Reibung, und ihr Fehlen bedeutet, dass jede Mahlzeit die vollen Bestätigungskosten trägt.
Batch-Erfassung (nichts erzwingt, dass die Anteile eines Gerichts zusammen ≤ 1 ergeben)
Rezeptvorlagen
Mikronährstoffe – wenn sie zurückkehren, eine separate Tabelle im Langformat hinzufügen, anstatt zurückzumigrieren, da Makros und Mikros unterschiedliche Formen und Abfragemuster haben.
Authentifizierung und benutzerübergreifendes Teilen – siehe „Multi-User“ oben.
user_id-Scoping existiert; die Verifizierung, wer eineuser_idtatsächlich ist, und jede Vorstellung davon, dass Benutzer einander Einblick in ihre Logs gewähren, existiert nicht.
Stack
FastAPI + Postgres 16, kleiner Multi-User-Betrieb, selbst gehostet. Über MCP bereitgestellt, sodass jeder MCP-Client als Frontend dienen kann.
Ausführen
Der MCP-Server (server/) ist ein dünner Adapter: Er registriert jedes Tool aus dem Vertrag von docs/intake-agent.md, ermittelt die user_id dieses Prozesses einmalig beim Start und ruft für jeden Aufruf die passende SQL-Funktion oder -Sicht auf. Darüber hinaus hat er keine eigene Logik – die Datenbank ist weiterhin der Ort, an dem jede Invariante tatsächlich durchgesetzt wird.
Ein Serverprozess = ein Benutzer (siehe server/config.py). Das ist die Antwort auf die Frage „Wie löst ein Aufruf eine user_id auf?“ aus docs/design-notes.md: Für MCP im Speziellen führt jede Person ihre eigene Serverinstanz aus, genauso wie Claude Desktop/Code einen Subprozess pro konfiguriertem Tool starten.
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.serverRichten Sie einen MCP-Client (Claude Desktop, Claude Code, eine OpenAI-Realtime-Funktionsaufruf-Brücke) mit dieser Umgebung auf python -m server.server aus, und jedes Tool in docs/intake-agent.md ist live.
Das in docs/intake-agent.md beschriebene GPT-Realtime-mini-Sprach-Frontend ist noch nicht angeschlossen – dieser Server braucht nur irgendeinen MCP-fähigen oder funktionsaufrufenden Client davor, um Ende-zu-Ende nützlich zu sein.
Dateien
db/schema.sql– vollständiges DDL, Multi-User-Commit-Gate, Rollup-Sichten. Lädt sauber auf PG16.db/tests.sql– 18 Invariantentests (13 Kern-, 5 Cross-User-Isolationsprüfungen), alle bestehen.docs/design-notes.md– vollständige Design-Begründung, die Multi-User-Abwägungen, scharfe Kanten.docs/intake-agent.md– der System-Prompt und der Tool-/Funktionsaufruf-Vertrag für das konversationelle Frontend (GPT Realtime mini), Feld für Feld auf das Payload vonfn_commit_logabgestimmt.docs/erd/– Schema-Diagramm (zeigt weiterhin die Single-User-Form; noch nicht für Multi-User neu generiert).server/– der MCP-Server, der den Tool-Vertrag implementiert (db.pyPostgres-Zugriff,models.pyPayload-Validierung,tools.pyGeschäftslogik,server.pyTool-Registrierung).frühere Single-User-Designhistorie:
git log db/schema.sql.
This server cannot be installed
Maintenance
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
Cloud-hosted MCP server for durable AI memory
MCP server for AI dialogue using various LLM models via AceDataCloud
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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