Almanac MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Almanac MCP Serverlog my breakfast: 3 eggs, oatmeal, and a banana"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Almanac
A precise, self-hosted fitness record your own AI agent can read.
Almanac keeps an accurate account of what you eat, lift, weigh, and sleep, then hands it to the assistant you already use over MCP. Ask how your cut is going or what to train today, and the answer comes from your own numbers, not a guess. It runs on your hardware, so the record stays yours.
No outside assistant needed: the web dashboard does everything, and two chat surfaces are built in — an AI meal assistant that turns "chicken burrito bowl" into editable macros, and a read-only insights coach. One API, one SQLite file, so every surface sees the same data.
Screenshots
The web dashboard. Nutrition phase and TDEE, today's macros and meals, a seven-day macro grid, weight trend, sleep, and the training panel with a recommended session.
The built-in AI meal assistant. Describe what you ate and it returns editable entries, looking up unfamiliar foods and asking about portion size when it changes the math.
The AI insights coach. A read-only read of your logged history: nutrition adherence, TDEE drift, training volume, and sleep.
Logging and reviewing through an outside assistant over MCP:
Related MCP server: aiTrainer
Architecture
Browser ──► nginx ──► oauth2-proxy ──► almanac-web (Vue 3 SPA)
│
├──► almanac-api (Fastify + SQLite)
│
Claude / ChatGPT ─────────┴──► almanac-mcp (77 tools, 5 resources)
│
└──► almanac-apiFour containers behind nginx. oauth2-proxy handles browser SSO (Google, GitHub, or any OIDC provider it supports); MCP traffic bypasses SSO and authenticates via OAuth 2.1 or personal access tokens (PATs). All auth paths converge on the same PAT format stored in SQLite.
See reference/mcp-auth.md for the full authentication architecture.
Features
Nutrition
Meal logging — log meals with kcal, protein, carbs, and fats. Edit, delete, and review meals by date.
Stored meals — save a meal definition once (name + macros) and log it as eaten in one tap.
Nutrition phases — cut, bulk, or maintenance phases with daily kcal targets (static or TDEE-relative) and macro splits. The create form suggests a split from your bodyweight and target; a guided cold-start collects what a TDEE estimate needs.
Macro analytics — today's totals vs. target, historical summaries by date or range, rolling 7-day averages, and on-track / at-risk / off-track verdicts.
TDEE estimation — three-tier basis: profile baseline (Mifflin BMR × activity), measured intake (back-calculated from weight trend), or user assertion. Calibrates from baseline to measured over ~14 days of weigh-ins.
Body composition
Weight logging — daily weigh-ins with optional notes.
Weight trend — exponential-weighted moving average with 14-day and 30-day views, change rate, and confidence levels.
Training
Workout templates — build reusable templates with ordered exercises and defaults, directly in the app or via an assistant. Start a session from a template, then skip, override, or add exercises as you go. Starter programs (Push/Pull/Legs, Upper/Lower) seed a whole split for new users.
Set tracking — log reps, weight, and RPE (1–10) per set. Duration and estimated kcal per workout.
Exercise library — custom exercises organized into muscle groups. Archive exercises you no longer use.
Stim state / recovery — per-muscle-group recovery tracking (0–100) with phase classification (too_soon → prime → detrained). Multi-phase decay model with hours-since-last and trainable-capacity signals.
Workout recommendations — template recommendation engine scores which workout to do next based on current recovery state across all muscle groups.
Cardio, steps, and alcohol
Cardio — log sessions by modality (bike, run, ruck, etc.) with duration, distance, average HR, and estimated kcal (Keytel / METs formulas).
Steps — daily step count with automatic kcal estimation. Override estimates when you have better data.
Alcohol — session-based logging (start/end, drink count in US standard drinks, kcal estimate). Overlays onto daily energy balance.
Sleep
Sleep logging — hours and quality (1–5) per night, with timezone-aware midnight crossing.
Sleep debt — rolling debt calculation over a configurable window (default 14 days) against a baseline.
Accomplishments
Wins — milestones derived automatically from your logs: logging and workout streaks, calorie-adherence streaks, body-weight milestones off the smoothed trend, strength PRs, sleep recovery, and the moment your TDEE flips to measured. Each shows its previous best and earns the moment a log completes it. Lifetime milestones (100th workout, total kilograms lifted, meals and weigh-ins logged) are backdated to the day you crossed them.
Web UI
Daily dashboard — a calorie ring and draining protein/carb/fat bars, current vs. phase TDEE, deficit/surplus and on-target adherence, today's meals and movement, weekly macro grid, weight sparkline, sleep debt, phase progress, and an earned-wins section above the workout picker.
Editable cards — meals, weight, sleep, cardio, and steps are all add/edit/delete directly in the dashboard, not just in chat. Edits update the ring, bars, week grid, and trends right away.
Phase controls — start, edit, and stop a nutrition phase from the dashboard, with a live TDEE estimate and macro suggestions in the create form.
Workout panel — template picker, build/edit templates and starter programs, active session with live set entry, add-exercise-mid-session, end/save dialog, and last-session reference.
Calendar — month view in Workouts or Intake mode. Workouts mode shows per-template tallies, recovery pills (too_soon, prime, etc.), and a forward recommendation window; Intake mode tints each day by adherence. Tap a day (or step with
‹ ›) to view and edit any past day; the URL reflects the day (?date=…).Copy stats for LLM — one button copies a full markdown briefing of your current picture (phase, TDEE, today, trends, recent workouts, a 14-day history table) to paste into any chat.
AI Meal Assistant — an in-app chat panel where you describe what you ate and get editable proposal cards to log. It matches your stored-meal library first, estimates otherwise, and can web-search unfamiliar foods. A daily token budget shows "~N logs left"; both budget and search have configurable caps. Optional — see the LLM
.envsection.AI insights coach — a second panel that reads your logged history back to you: nutrition adherence, TDEE drift, training volume and split balance, and sleep. Read-only by design (no write tools, no web search). Transcripts persist per day with
◀ ▶navigation, and opening a fresh day auto-asks for a quick read. Runs a stronger model than the meal parser (ALMANAC_LLM_INSIGHTS_MODEL).Settings — profile editing, activity level, timezone and unit (metric/imperial) selectors, PAT creation/revocation, and the MCP URL for connecting an assistant.
Mobile responsive — single 768 px breakpoint, swipeable panels via CSS scroll-snap, contextual sticky header, and 36 px touch targets.
MCP integration
77 tools, 5 resources — full CRUD for every entity (including stored meals and
log_meal_from_stored), plus derived signals (stim state, TDEE, sleep debt, day status, calendar),get_next_best_actionfor onboarding/next-step guidance, andget_accomplishmentsso an assistant can surface your earned wins in chat.Works with Claude (mobile, Desktop, Code) and ChatGPT — say "log a 350 kcal breakfast" and it shows up in the web UI.
OAuth 2.1 — Claude mobile and ChatGPT connect through the standard MCP OAuth flow, using whichever SSO provider you configured. No manual token setup.
PAT auth — personal access tokens for Claude Code or any HTTP client.
Idempotent logging — safe to retry meal, weight, and sleep log calls.
Auth
Three-layer allowlist — oauth2-proxy (browser SSO), API (account provisioning), and MCP (OAuth flow) all enforce the same
allowed-users.txtfile.OAuth tokens are real PATs — minted via the API, stored in SQLite, visible and revocable in the web Settings panel.
Per-user data isolation — every record is scoped to its owner. Reads and writes are enforced against the authenticated user at the data layer, so one account never sees or touches another's data.
Requirements
Node 20 or newer
pnpm 9 or newer
Docker (for production; optional for local dev)
SQLite ships bundled via better-sqlite3.
Quickstart — local dev
The local dev script starts all services (API, web, MCP, oauth2-proxy) in one command:
1. Install
pnpm install2. Configure .env
cp .env.example .envEdit .env and set the required Google OAuth credentials and your dev email. See .env.example for documentation on each variable. The stack ships configured for Google; to use another provider, see the oauth2-proxy provider docs.
3. Start everything
scripts/local-dev/up.shThis starts:
almanac-api on
:3001(Fastify, trusts proxy headers)almanac-web on
:5173(Vite dev server)almanac-mcp on
:3030(Streamable HTTP + OAuth 2.1)oauth2-proxy on
:4180(Docker container, SSO)
Stop everything with scripts/local-dev/down.sh.
Without Docker (no Google OAuth)
If you don't need the real Google sign-in path, skip .env, docker, and
oauth2-proxy entirely:
scripts/local-dev/dev-noauth.sh you@example.com # web on 127.0.0.1
scripts/local-dev/dev-noauth.sh you@example.com --lan # web on 0.0.0.0 (other devices)This runs the API + web with header-trust auth: the Vite dev proxy injects
the x-forwarded-email header that oauth2-proxy would emit in prod, so the UI
needs no login and acts as the email you pass. Migrations run automatically on
API boot. Ctrl-C stops both.
--lancaveat: binding the web server to0.0.0.0means anyone on your network is authenticated as that email. Use it only on a trusted network.
For MCP in this mode, run it in stdio transport against the local API (mint a PAT in the web Settings panel first) — the script prints the exact command on startup.
Demo instance (populated with fake data)
To see the UI fully populated — every panel non-empty, both AI surfaces unlocked — without touching your real data:
scripts/local-dev/demo.sh # 127.0.0.1
scripts/local-dev/demo.sh --lan # LAN, for phone testing
scripts/local-dev/demo.sh --days 90 # longer historyThis seeds a throwaway SQLite file and runs the API + web on :3099/:5199, so
it can run alongside your normal dev stack. The data is anchored relative to
today (an active cut phase, 40 days of meals, weigh-ins, sleep, steps, a PPL
split with session history), so it never goes stale. It sources .env for
ANTHROPIC_API_KEY — without one the UI still renders but the AI panels report
llm_available: false. Ctrl-C stops it; rm -f /tmp/almanac-demo.sqlite* to
delete the data.
Screenshots
scripts/local-dev/screenshot.mjs captures the running UI headlessly, driving
your system Chrome via playwright-core (no bundled browser download). Capture
height is independent of your display, so a full-page dashboard shot works on
any screen:
node scripts/local-dev/screenshot.mjs # full dashboard
node scripts/local-dev/screenshot.mjs --preset both # desktop + mobile
node scripts/local-dev/screenshot.mjs --scene meal-lookup # AI modal (real LLM call)Defaults to 984 px wide at 1×, matching the screenshots above. --scene clicks a
modal open before capturing; --help lists the available scenes.
4. Connect Claude Code to MCP
Register the MCP server by URL:
{
"mcpServers": {
"almanac": {
"type": "url",
"url": "https://almanac.example.com/mcp",
"headers": {
"Authorization": "Bearer alm_XXXXX"
}
}
}
}For local dev with a PAT, point at http://localhost:4180/mcp. For OAuth-capable clients (Claude mobile, ChatGPT), just use the public URL — the OAuth flow handles everything automatically.
5. Verify
Open Claude Code. The almanac tools should show up under the almanac server. Ask Claude to "log a 350 kcal breakfast" — the meal should appear in the web UI and via get_macros_today.
.env reference
Core
Variable | Purpose | Required when |
| SQLite file location | always |
| Where API listens | always |
| Where MCP reaches API | always |
| API trusts | behind a proxy |
| Email allowlist — file path or comma-separated. Shared by the API and MCP server. | production |
| Port the Vite dev server binds (default | local dev |
| Email the Vite dev proxy injects as | local dev |
| Pino level override ( | optional |
MCP
Variable | Purpose | Required when |
|
| always |
| Where MCP listens (HTTP/SSE only) | http/sse |
| Static PAT for stdio transport | stdio |
OAuth 2.1 (MCP + browser SSO)
The stack ships configured for Google as the SSO provider, so the variables below name Google credentials. oauth2-proxy also supports GitHub, GitLab, and any generic OIDC provider — swap --provider in docker-compose.yml and supply that provider's client ID and secret in the same variables.
Variable | Purpose | Required when |
| Google OAuth client ID (shared by oauth2-proxy and MCP) | production |
| Google OAuth client secret | production |
| oauth2-proxy session cookie encryption key | production |
| oauth2-proxy callback URL ( | production |
| Google client ID for MCP OAuth (typically | MCP OAuth mode |
| Google client secret for MCP OAuth | MCP OAuth mode |
| Public URL for MCP OAuth issuer ( | MCP OAuth mode |
| One-shot: binds an existing email-less | only when adopting a pre-auth database |
When do you need
ALMANAC_FIRST_LOGIN_EMAIL? Almost never. When you sign in through OAuth or oauth2-proxy, the API provisions your account from your verified email automatically — no setup. This variable only matters when ausers.id = 1row already exists without an email, which happens if data was created before going through the auth flow: a local-dev database, or records written directly via the API/MCP during early setup. Setting it links that orphaned row to your real email on the next boot, so signing in resolves to your existing data instead of creating a fresh, empty account. On a clean database, or if you only ever use OAuth, you never set it. It's a one-shot: confirm the binding, then remove the var.
Watchtower auto-deploy notifications (deploy-only, optional)
The watchtower compose service emails on container updates/errors via shoutrrr SMTP. Set WATCHTOWER_EMAIL_TO to turn notifications on; leave it blank and watchtower runs silently.
Variable | Purpose | Default |
| Recipient. Blank = notifications off | unset (silent) |
| Sender address | — |
| SMTP host | — |
| SMTP port |
|
| HELO/EHLO hostname — must be an FQDN; a strict postfix rejects shoutrrr's | — |
LLM / AI surfaces (optional)
Read by the API only. Both AI surfaces — the AI Meal Assistant and the AI insights coach — are gated behind the same ALMANAC_LLM_ENABLED switch (off by default), so they stay dark until explicitly turned on. The prod docker-compose.yml already forwards these from the host .env to the almanac-api service.
The two surfaces use separate models: meal parsing is a cheap extraction task and stays on Haiku, while the coach does harder multi-signal reasoning and defaults to Sonnet.
Variable | Purpose | Default |
| Master switch for both AI surfaces (meal chat + insights coach) |
|
| Anthropic key. Without it both AI surfaces are hidden ( | — |
| Provider seam. Only |
|
| Model for the meal assistant (the cheap parser) |
|
| Model for the insights coach — harder reasoning, so a stronger default |
|
| Soft daily token limit — drives "~N logs left"; warns but never blocks | unset (no soft limit) |
| Hard daily token ceiling — 429 circuit-breaker | unset (no hard cap) |
| Flat token charge per web search when no recent search history to average |
|
| Max web searches per user-local day; at the cap search is disabled but meals still log | unset (uncapped) |
Turning it on also needs: (1) the per-user flag
llm_logging_enabled = 1(admin API/MCP tool, orUPDATE users SET llm_logging_enabled = 1 WHERE email = '…'), and (2) web search enabled for your org in the Anthropic Console (Settings → Privacy). Web searches draw a flat charge from the same daily token budget; the real token cost is still recorded but the budget is billed the flat per-search amount.
Production deployment
Almanac runs as four Docker Compose services (web, API, MCP, oauth2-proxy) behind host nginx with TLS. Only oauth2-proxy binds a host port; the three almanac services stay internal to the Docker network. Images build in CI and publish to GHCR on each release tag, so the server pulls prebuilt images and never builds locally, with an optional watchtower service for hands-off updates.
The email allowlist is enforced at three independent layers, all reading the
same allowed-users.txt: oauth2-proxy for browser traffic, the API for account
provisioning, and the MCP server during the OAuth flow.
Full walkthrough (DNS, TLS, nginx, first boot, updates, backups, rollback) is in the deploy runbook.
Testing
pnpm -r test # full test suite across all packages
pnpm -r typecheck # tsc --noEmit workspace-wide
pnpm lint # Biome
pnpm format # BiomePer-package: pnpm --filter @almanac/<pkg> test.
Troubleshooting
API returns 403 for a new user. The email is not in
allowed-users.txt. Add it and restart the API container (or wait for oauth2-proxy to hot-reload the file).ALMANAC_DB_PATHended up in the wrong place. Classicpnpm --filtercwd trap — relative path resolved againstpackages/<pkg>/, not the workspace root. Use an absolute path.
Project layout
almanac/
├── packages/
│ ├── core/ # SQLite, migrations, repos, domain types, signals, schemas
│ ├── api/ # Fastify HTTP server, zod request/response validation
│ ├── mcp/ # MCP server, MCP tools + resources, OAuth 2.1
│ └── web/ # Vue 3 SPA, Vite, Pinia
├── deploy/ # nginx config, post-migration smoke test
├── docs/ # showcase landing page (GitHub Pages)
├── reference/ # MCP architecture + authentication reference
├── scripts/
│ └── local-dev/ # up.sh / down.sh, dev-noauth.sh, demo.sh, screenshot.mjs
├── docker-compose.yml
├── allowed-users.txt # shared email allowlist
├── .env.example
└── pnpm-workspace.yamlLicense
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 Servers
- FlicenseBqualityDmaintenanceA personal fitness tracking server that enables logging and querying workouts, nutrition, and body metrics through a local SQLite database. Integrates with OpenNutrition MCP for food logging and supports exercise history tracking for workout progression.Last updated17
- FlicenseAqualityBmaintenancePersonal workout coach MCP server that logs exercises in natural language, tracks progress with SQLite, and provides coaching signals like estimated 1RM and volume trends.Last updated6
- Flicense-qualityCmaintenanceA personal health and fitness MCP server that provides tools for managing profile data, goals, body measurements, nutrition, workouts, sleep, check-ins, life events, analytics, and coach memories via Supabase Postgres.Last updated1
- Flicense-qualityBmaintenanceHosted MCP server that syncs health data from Apple Health, Fitbit, Oura, and Google Health Connect, enabling Claude and ChatGPT to query workouts, sleep, nutrition, and recovery in plain English with interactive charts.Last updated
Related MCP Connectors
MCP server for Withings health data — sleep, activity, heart, and body metrics.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
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/corcoran/almanac'
If you have feedback or need assistance with the MCP directory API, please join our Discord server