Zum Inhalt springen
Eine Uhr, ein Telefon und ein Dateistapel, deren Lichtpfade in einen gemeinsamen Knoten münden, als leuchtende Low-Poly-Illustration.
Alle Beiträge
Serie: training mcpTeil 2 von 4
·KIMCPLaravelTraining

Wie die Uhr in den Chat kommt

51 Werkzeuge, fünf Wege für Trainingsdaten und eine eigene App für Apple Health: wie training mcp Einheiten, Vitalwerte, Ziele und Beschwerden in das Gespräch mit dem Sprachmodell bringt.

Ein Modell, das die Daten nicht kennt, rät. Der zweite Teil handelt davon, wie die Daten hinkommen, ohne dass jemand Screenshots kopiert.

Im ersten Teil ging es um den Ansatz: ein MCP-Server, der dem Sprachmodell des Nutzers Daten und Wissen gibt, ohne eigenen Chat. Jetzt zu den Daten. Dabei gibt es zwei Richtungen. Was die Uhr misst, muss zuverlässig auf dem Server landen. Und was dort liegt, muss so beim Modell ankommen, dass es damit arbeiten kann, ohne sich etwas dazuzudenken.


Der Server: ein Endpunkt, 51 Werkzeuge

training mcp ist eine Laravel-Anwendung, der MCP-Teil läuft über das offizielle Paket laravel/mcp. Es gibt genau einen Endpunkt, /mcp, über HTTP. Clients wie Claude oder ChatGPT melden sich per OAuth an: Sie registrieren sich selbst (RFC 7591), schicken den Nutzer auf eine Zustimmungsseite und bekommen danach ein Token mit dem Scope mcp:use. Für Claude Code, Skripte und Clients ohne Browser-Ablauf gibt es ein persönliches Token.

Ein Werkzeug ist eine Klasse. Name, Beschreibung und die Angabe, ob es nur liest, stehen als Attribute daran:

#[Name('absences')]
#[Title('Absences')]
#[Description('Holidays, travel, illness and injuries overlapping a period, with the equipment profile available during travel. Without from/to the next 60 days. Call it before planning.')]
#[IsReadOnly]
#[IsIdempotent]
class AbsencesTool extends TrainingTool
{
    protected function run(Request $request, User $user): Response|ResponseFactory
    {
        // Zeitraum auflösen, Abwesenheiten laden, strukturiert zurückgeben
    }
}

Die Beschreibung ist englisch und richtet sich an das Modell, nicht an Menschen. Der letzte Satz darin, „Call it before planning", ist kein Zufall. Ein Modell ruft ein Werkzeug nur auf, wenn es einen Anlass sieht, und die Beschreibung ist der Ort, an dem man ihm diesen Anlass gibt.

Inzwischen sind es 51 Werkzeuge, 31 davon lesend und 20 schreibend. Sie reichen von activities und daily-vitals über training-load und readiness bis zu plan-week und note-session. Welche ein Konto sieht, hängt vom Abo ab: tools/list liefert nur die freigeschalteten, und beim Aufruf wird ein zweites Mal geprüft. Ohne Abo bleibt genau eines übrig, account-status. Das klingt nach wenig, verhindert aber, dass ein Modell ohne Daten anfängt, Zahlen zu raten.

Leicht übersehen wird, wie viel Platz die Werkzeugliste im Kontext belegt. Jedes Werkzeug hat ein Ausgabeschema, und mit allen Ebenen kam die Liste auf 138.000 Zeichen. Jetzt geht nur die oberste Ebene an den Client, damit sind es 96.000. Das Modell weiß trotzdem, welche Felder zurückkommen, und hat mehr Platz für das Gespräch.


Fünf Wege für die Einheiten

Ein Produkt, dessen Wert die Daten des Nutzers sind, darf nicht an einer einzigen Schnittstelle hängen. Deshalb gibt es mehrere Wege, und sie haben unterschiedliche Stärken.

Der wichtigste Weg ist intervals.icu. Darüber kommen Garmin, Coros, Wahoo, Polar, Suunto, Zwift und einiges mehr, angebunden per OAuth. Neue Einheiten meldet intervals.icu per Webhook. Weil Webhooks auch mal verloren gehen, gleicht ein Job zweimal am Tag um 5 und um 17 Uhr ab, was fehlt.

Garmin direkt ist gebaut, per Push an einen eigenen Endpunkt, die FIT-Datei wird sofort abgeholt. Freigegeben ist der Weg noch nicht. Garmins Bedingungen verlangen für die Weitergabe an Sprachmodelle eine schriftliche Zustimmung, und bis die vorliegt, laufen Garmin-Nutzer über intervals.icu.

Apple Health geht nur über eine App auf dem Telefon, dazu unten mehr. RunGap schickt Einheiten per Webhook, signiert mit HMAC.

FIT-Dateien lassen sich im Web hochladen, auch als ganzes ZIP wie der Datenexport von Garmin. Das ist der Weg für die Historie der letzten Jahre.

Bewusst nicht dabei ist Strava. Dessen API-Bedingungen untersagen seit Juni 2026 die Weitergabe an KI-Anwendungen, und ein Produkt, das genau das tut, baut darauf besser nicht auf.

Hinter allen Wegen liegt dasselbe Muster: sofort annehmen, die Rohdaten unverändert ablegen, einen Job in die Queue stellen und mit 202 antworten. Ein eigener Worker liest dann die FIT-Datei oder das JSON und legt die Einheit an. Fällt beim Auswerten etwas um, liegen die Rohdaten noch da und lassen sich neu einlesen.


Dieselbe Einheit über drei Wege

Wer eine Garmin-Uhr hat, intervals.icu nutzt und die App für Apple Health installiert, bekommt jeden Lauf dreimal geliefert. Das Modell darf ihn nur einmal zählen, sonst verdreifacht sich der Wochenumfang, und jede Belastungskennzahl ist falsch.

Zusammengeführt wird, wenn der Start höchstens zwei Minuten auseinanderliegt und die Dauer höchstens 15 Prozent oder fünf Minuten abweicht. Dazu kommt eine zweite Regel für Aufzeichnungen, die später starten oder früher enden: Liegt die kürzere zu mindestens 80 Prozent in der längeren, ist es dieselbe Einheit.

Welche Fassung führt, entscheidet eine feste Reihenfolge. Zuerst zählt, ob eine Fassung vollständig ist, dann die Datenqualität (eine FIT-Datei schlägt eine Zusammenfassung ohne Datei), erst dann der Weg:

'source_rank' => [
    'garmin',
    'garmin_export',
    'intervals_icu',
    'rungap',
    'upload',
    // ...
    'apple_health',
    'health_connect',
],

Alle Fassungen bleiben gespeichert, gezählt wird die führende. Bei einem Triathlon wird es feiner, dort werden die einzelnen Abschnitte abgeglichen. Den Rahmen liefert die Uhr, die Messkanäle kommen aus der reicheren Fassung. Aufgefüllt oder geschätzt wird dabei nichts.


Die App für Apple Health

HealthKit hat keine Schnittstelle nach außen. Wer an die Daten will, braucht eine App auf dem Telefon, die der Nutzer freigibt. Die App von training mcp ist mit Expo und React Native gebaut, HealthKit kommt über @kingstinct/react-native-healthkit, auf Android Health Connect über das Gegenstück. Natives Swift gibt es außer dem generierten Rahmen nicht.

Beim ersten Start liest die App zwölf Monate ein. Danach meldet HealthKit neue Daten per Background Delivery, und ein Hintergrund-Task holt nach, was liegen geblieben ist. Wann der läuft, entscheidet iOS. Das ist der Teil, den man nicht in der Hand hat, und deshalb steht in der App sichtbar, wann zuletzt synchronisiert wurde.

Jeder Eintrag geht mit seiner HealthKit-UUID an den Server, damit ein zweiter Upload nichts doppelt anlegt. Und was schon auf anderem Weg ankommt, lädt die App gar nicht erst hoch. Der Server nennt ihr die Quellen, die sie auslassen soll, etwa Garmin Connect, das seine Einheiten ebenfalls in Apple Health schreibt.


Was das Modell bekommt

Die Daten liegen jetzt auf dem Server. Damit das Modell sie richtig verwendet, gelten für alle Werkzeuge dieselben Regeln.

Zeitstempel kommen in UTC, daneben steht die Ortszeit in der Zeitzone des Kontos. Ein Lauf um 6:30 Uhr soll im Gespräch auch 6:30 Uhr heißen und nicht 4:30. Zeiträume werden als YYYY-MM-DD angegeben, ohne Angabe gelten die letzten sieben Tage.

Jeder Datensatz nennt seine Quelle. Kommt eine Antwort mit einem Feld attribution, soll das Modell diese Quelle nennen, wenn es die Werte wiedergibt. Garmin verlangt das, und es schadet bei keiner Quelle.

Was fehlt, fehlt. Hat eine Einheit keine Leistungsdaten, steht dort nichts, und keine Null. Ein Modell, das eine Null sieht, rechnet damit.


Was keine Uhr misst

Ziele, Beschwerden, Urlaub, feste Termine, die Notiz nach einer Einheit: Das weiß nur der Nutzer, und es soll das Gespräch überdauern. Dafür gibt es die schreibenden Werkzeuge, mit drei Regeln, die in den Anweisungen an das Modell stehen.

Gespeichert wird nur, was der Nutzer gesagt oder dem er zugestimmt hat, nie gemessene Werte. Gewicht, Strecke und Puls stehen schon in den Rohdaten, und eine Kopie davon liefe früher oder später auseinander.

Gelöscht wird nichts. Was nicht mehr gilt, wird beendet oder durch eine neue Fassung abgelöst, und der Verlauf bleibt lesbar.

Und überschrieben wird nicht still. Schreibt das Modell zu einem Schlüssel etwas anderes als das, was schon dort steht, kommt ein Konflikt zurück, und das Modell muss nachfragen.

Eine Lektion kam erst im Betrieb. Am 13. Oktober fiel auf, dass Claude und ChatGPT Wochen planten, ohne die festen Termine zu kennen. Gespeichert waren sie, aber an einer Stelle, auf die kein Werkzeug verwies. Die Lösung ist planning-context: ein Aufruf, der alles bündelt, was eine Woche vorgibt. Profil, Wochenvorlage, aktuelle Phase, Abwesenheiten und das, was schon geplant ist. Die Beschreibung sagt, dass er vor jeder Planung kommt, und plan-week prüft die gespeicherte Woche dagegen. Was nicht passt, kommt als Warnung zurück, die das Modell dem Nutzer zeigen muss.


Fazit

Das MCP-Protokoll war schnell erledigt. Das Paket nimmt einem den Großteil ab, und ein Werkzeug ist schnell geschrieben. Gedauert haben die Daten dahinter: fünf Wege, die alle ausfallen können, eine Einheit, die dreimal kommt, eine App, deren Hintergrundarbeit das Betriebssystem plant. Dazu kommen die Beschreibungen, die einem Modell sagen, wann es welches Werkzeug braucht.

Im dritten Teil geht es um die zweite Lücke: das Trainingswissen, und wie man ein Modell dazu bringt, dort nachzusehen, bevor es antwortet.


Call to Action

training mcp läuft als Beta in einem kleinen Kreis, der öffentliche Start ist Anfang November. Mehr steht auf https://training-mcp.app.

Wer einen eigenen MCP-Server mit Laravel baut: Die Fragen, an denen ich am längsten saß, waren die Beschreibungen der Werkzeuge und die Größe der Werkzeugliste. Wenn du dazu Erfahrungen hast, schreib mir.