Elite MCP
by natyavidhan
README.md
# Elite MCP
An MCP (Model Context Protocol) server that gives an AI agent direct, real-time read/write access to a self-hosted [Elite](https://github.com/natyavidhan/elite) instance — a personal fitness tracker for workouts, food, cardio, and body weight.
**If you are an agent reading this to decide how to use this server: read the whole file before calling any tool.** It covers what each tool does, what its arguments mean, what units/formats are expected, and where the sharp edges are (mainly: writes are immediate and real, there is no undo). The "Data model notes" and "Safety notes" sections below matter as much as the tool list itself.
## What this is, and what it is not
Elite (the app) stores everything in its own SQLite database behind a plain REST API — no local cache, no offline queue, one source of truth. This MCP server is a thin protocol adapter over that API: every tool call here is one (or two) HTTP requests to a **specific, already-running Elite instance** that you point it at. It holds no state of its own and does no caching — call `get_workout_history` twice in a row and you'll get two fresh reads.
It is not a general fitness API, it does not talk to any other app, and it does not do food-database lookups (no OpenFoodFacts/USDA search) — that lookup logic lives client-side in the Elite web app itself and isn't exposed over HTTP. If you need to log a food that isn't already in this user's data, use `log_food` with macros you already know or the user gives you; there's no "search for a food by name" tool here.
## Requirements
- A running Elite instance you have the URL for (e.g. `http://192.168.1.50:8080`, or wherever it's self-hosted). See the [Elite repo](https://github.com/natyavidhan/elite) if you need to stand one up.
- Node.js 18+.
- If that Elite instance was started with `API_TOKEN` set, you'll need that same token.
## Setup
```bash
git clone https://github.com/natyavidhan/elite-mcp.git
cd elite-mcp
npm install
cp .env.example .env # then fill in ELITE_BASE_URL (and ELITE_API_TOKEN if the server needs one)
```
This is a standard stdio MCP server — it's meant to be **spawned by an MCP client's own config**, not run standalone and left open. Point your agent/client at `node /path/to/elite-mcp/index.js` with `ELITE_BASE_URL` (and optionally `ELITE_API_TOKEN`) as environment variables. For a client that reads a JSON config (Claude Desktop, Claude Code, and most others follow this shape):
```json
{
"mcpServers": {
"elite": {
"command": "node",
"args": ["/path/to/elite-mcp/index.js"],
"env": {
"ELITE_BASE_URL": "http://192.168.1.50:8080",
"ELITE_API_TOKEN": ""
}
}
}
}
```
For an agent runtime without a JSON-config MCP client (a custom orchestrator, for instance), the same two env vars plus spawning `node index.js` over stdio is the whole contract — see `index.js` and `src/client.js`, both short.
If `ELITE_BASE_URL` isn't set, the process logs an error to stderr and exits immediately rather than starting in a broken state.
## Tools
20 tools total: 1 connection check, 11 read-only analytics tools, 1 lookup tool, and 7 write tools. Every tool returns its result as a JSON text block; a failure (Elite unreachable, bad token, 404, validation error) comes back as a normal tool result with `isError: true` and a `{"error": "..."}` body — it does not crash the MCP connection, so check for that rather than assuming success.
### Connection
- **`check_connection`** — no arguments. Hits Elite's `/api/health`. Call this first if anything else is failing; it tells you whether the server is reachable at all and whether its AI Coach is enabled (irrelevant to this MCP server, but a useful signal that you're talking to the right instance).
### Analytics (read-only)
These mirror exactly what Elite's own built-in AI Coach calls internally — same functions, same math, so numbers here will always match what the user sees in the app.
- **`get_workout_history({ days? })`** — sessions over the last N days (default 30): date, exercises, sets, total volume.
- **`get_exercise_trend({ exerciseName, limit? })`** — best weight per session for one exercise over time, plus its all-time PR. `exerciseName` is matched fuzzily (exact id, exact name, or substring) — you don't need `list_exercises` first just to read a trend.
- **`get_personal_records({ limit? })`** — best weight and best single-set volume per exercise, heaviest first.
- **`get_muscle_volume({ date })`** — muscle-by-muscle volume for one specific day (primary muscles full credit, secondary half credit).
- **`get_weekly_muscle_summary({ days? })`** — total volume per muscle over the last N days (default 7), ranked — use this to find what's undertrained.
- **`get_muscle_exercise_split({ muscle, days? })`** — which exercises make up one muscle's volume and what share each is (e.g. "what's my tricep split"). `muscle` must be one of the enum values listed below.
- **`get_food_log({ date })`** — every entry logged on one date, with macros, plus the day's totals.
- **`get_nutrition_trend({ days? })`** — daily calorie/macro totals over the last N days (default 7) plus the user's configured daily goals.
- **`get_cardio_summary({ days? })`** — cardio sessions over the last N days (default 30) plus personal bests.
- **`get_body_weight_trend({ days? })`** — entries over the last N days (default 90) plus current/starting/change/7-day-average.
- **`get_consistency({ days? })`** — per-day whether the user logged a workout, food, cardio, and body weight, over the last N days (default 14).
### Lookup
- **`list_exercises({ query? })`** — the full exercise catalog (built-in + this instance's custom exercises), optionally filtered by a case-insensitive substring match on id or name. **Call this before `log_workout_set` if you don't already know the exact `exerciseId`** — the catalog uses specific ids like `barbell_bench_press`, not free text, and `log_workout_set` will reject anything that isn't a real id.
### Writes
Every write here takes effect immediately on the live Elite instance — see **Safety notes** below before using these on someone's real data.
- **`log_workout_set({ date, exerciseId, reps, weightKg, rpe? })`** — logs one set. Creates that day's workout session automatically if it doesn't exist yet. Returns the created set and `isPR: true/false`. `rpe` (rate of perceived exertion, 1–10) is optional.
- **`delete_workout_set({ setId })`** — deletes one logged set.
- **`delete_workout_session({ sessionId })`** — deletes an entire session **and every set under it**. There is no confirmation step — this tool does exactly what it says.
- **`log_food({ date, mealType, name, quantityG, calories, protein?, carbs?, fat? })`** — logs a food entry. Macros are the **totals for `quantityG`**, not per-100g (this tool does that conversion for you). Creates a `manual`-source food item behind the scenes.
- **`delete_food_log({ logId })`** — deletes one logged food entry.
- **`log_cardio_session({ date, activityType, durationSeconds, distanceKm?, avgHeartRate?, caloriesBurned?, notes? })`** — logs a cardio session.
- **`log_body_weight({ date, weightKg, bodyFatPct?, notes? })`** — **upserts** by date: logging again for a date that already has an entry overwrites it rather than creating a duplicate. This is intentional (it's how the Elite app itself behaves), not a bug.
## Data model notes
- **Dates** are always `YYYY-MM-DD` strings, no time component, no timezone. There is no "today" helper on this server — if a user says "log this for today," resolve today's date yourself before calling a tool.
- **IDs** (`sessionId`, `setId`, `logId`, `exerciseId`, etc.) are opaque strings minted by the Elite server (or, for exercises, defined in its catalog) — never construct or guess one. Get them from a prior tool's result (a `log_workout_set` response gives you a real `set.id` and `sessionId`) or from `list_exercises`.
- **`muscle`** enum (for `get_muscle_exercise_split`): `chest`, `triceps`, `shoulder`, `lats`, `bicep`, `forearm`, `traps`, `quads`, `hamstrings`, `glutes`, `calves`, `abs`.
- **`mealType`**: `breakfast`, `lunch`, `dinner`, `snack`.
- **`activityType`**: `run`, `walk`, `cycle`, `swim`, `other`.
- Weights are kilograms, distances are kilometers, durations are seconds — always, regardless of what unit system the user has Elite's UI set to display in.
## Safety notes
- **There is no undo.** `delete_workout_set`, `delete_workout_session`, and `delete_food_log` are real, immediate deletes against the user's actual training/nutrition history. Don't call a delete tool speculatively or "just to check what happens" — confirm with the user first unless they've explicitly asked for the deletion.
- **`log_body_weight` silently overwrites** an existing entry for that date rather than erroring or asking — if you're not sure whether the user already logged today's weight, `get_body_weight_trend` first.
- This server does no authorization beyond the single shared `ELITE_API_TOKEN` (if the target instance uses one) — it has exactly as much access as that token grants, which by default is everything. Treat it accordingly.
## Repo layout
```
index.js entry point — starts the stdio MCP server
src/client.js fetch wrapper around the target Elite instance's REST API
src/tools.js every tool's schema + implementation
```
## Related
- [natyavidhan/elite](https://github.com/natyavidhan/elite) — the tracker itself. Its README documents the full REST API this server is built on (`/api/data/*`, `/api/workout/*`, `/api/analytics/*`, etc.) if you need something this MCP server doesn't already expose as a tool.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues