Skip to main content
Glama
firaskudsy

cronometer-api-mcp

by firaskudsy
README.md
# cronometer-api-mcp
<!-- mcp-name: io.github.rwestergren/cronometer-api-mcp -->

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/rwestergren/cronometer-api-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/rwestergren/cronometer-api-mcp/actions/workflows/ci.yml)
[![Build Docker image](https://github.com/rwestergren/cronometer-api-mcp/actions/workflows/docker.yml/badge.svg)](https://github.com/rwestergren/cronometer-api-mcp/actions/workflows/docker.yml)
[![PyPI](https://img.shields.io/pypi/v/cronometer-api-mcp.svg)](https://pypi.org/project/cronometer-api-mcp/)

> **Hosted version for Claude.ai, ChatGPT, and Grok coming soon.** [**Join the waitlist →**](https://tally.so/r/A7WVge?ref=cronometer-api-mcp)

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for [Cronometer](https://cronometer.com/) nutrition tracking, built on the reverse-engineered mobile REST API.

Unlike [cronometer-mcp](https://github.com/cphoskins/cronometer-mcp), which takes a comprehensive GWT-RPC approach against Cronometer's web backend, this server talks to the same JSON REST API used by the Cronometer Android app -- with clean payloads and stable, versioned endpoints.

## Features

- **Food log** -- diary entries with food names, amounts, meal groups
- **Nutrition data** -- daily macro/micro totals and nutrition scores with per-nutrient confidence
- **Food search** -- search the Cronometer food database, get detailed nutrition info
- **Diary management** -- add/remove entries, copy days, mark days complete
- **Custom foods** -- create foods with custom nutrition data
- **Macro targets** -- read weekly schedule and saved templates
- **Fasting** -- view history and aggregate statistics
- **Biometrics** -- weight, body fat, heart rate, and other tracked metrics over a date range
- **Activity & sleep** -- log walks and workouts, record a night's sleep with its stage breakdown

## Quick Start

### 1. Install [uv](https://docs.astral.sh/uv/)

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### 2. Set credentials

```bash
export CRONOMETER_USERNAME="your@email.com"
export CRONOMETER_PASSWORD="your-password"
```

### 3. Configure your MCP client

`uvx` downloads and runs the server on demand -- no separate install step.

#### OpenCode (`opencode.json`)

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "cronometer": {
      "type": "local",
      "command": ["uvx", "cronometer-api-mcp"],
      "environment": {
        "CRONOMETER_USERNAME": "{env:CRONOMETER_USERNAME}",
        "CRONOMETER_PASSWORD": "{env:CRONOMETER_PASSWORD}"
      },
      "enabled": true
    }
  }
}
```

#### Claude Desktop (`claude_desktop_config.json`)

```json
{
  "mcpServers": {
    "cronometer": {
      "command": "uvx",
      "args": ["cronometer-api-mcp"],
      "env": {
        "CRONOMETER_USERNAME": "your@email.com",
        "CRONOMETER_PASSWORD": "your-password"
      }
    }
  }
}
```

## Available Tools

### Food Log & Nutrition

| Tool | Description |
|------|-------------|
| `get_food_log` | Diary entries for a date, each enriched with food name, source, serving measure/count, and that food's per-entry nutrient contribution, plus an energy_summary (target/consumed/remaining kcal) and a nutrition_summary of consumed totals for every tracked nutrient |
| `get_daily_nutrition` | Consumed macro and micronutrient totals for every nutrient tracked in Cronometer |
| `get_nutrition_scores` | Category scores (Vitamins, Minerals, etc.) with per-nutrient consumed amounts and confidence levels |

### Food Search & Details

| Tool | Description |
|------|-------------|
| `search_foods` | Search the Cronometer food database by name |
| `get_food_details` | Full nutrition profile and serving sizes for a food |

### Targets & Tracking

| Tool | Description |
|------|-------------|
| `get_macro_targets` | Weekly macro schedule and saved target templates |
| `get_fasting_history` | Fasting history within a date range |
| `get_fasting_stats` | Aggregate fasting statistics |

All date parameters use `YYYY-MM-DD` format and default to today when omitted.

### Diary Management

| Tool | Description |
|------|-------------|
| `add_food_entry` | Log a food serving to the diary |
| `remove_food_entry` | **Permanently delete** diary entries — no undo |
| `add_custom_food` | Create a custom food with specified nutrition |
| `copy_day` | Copy all entries from the previous day |
| `mark_day_complete` | Mark a diary day as complete or incomplete |

### Activity & Sleep

| Tool | Description |
|------|-------------|
| `log_exercise` | Log a walk or workout — duration, calories burned, optional step count |
| `log_sleep` | Record a night's sleep, with optional deep/light/REM breakdown and score |

Cronometer stores these in two different places: a walk is an **Exercise diary
entry**, a night's sleep is a **Biometric**.

**Cronometer has no steps field.** It is not one of the 54 biometric metrics and
not a field on a diary entry. `log_exercise` writes the step count into the entry
*name*, so it is visible in the diary but is text — Cronometer cannot total or
chart it. For step trends, read them from wherever they are actually recorded.

Exercise calories must be **net of BMR** — Cronometer counts BMR separately, so a
fitness tracker's gross session calories double-count rest. `log_exercise` takes
the burn three ways, best first:

| Argument | Use when | Accuracy |
|---|---|---|
| `calories_gross` | copying from a tracker (Fitbit `caloriesKcal`) | exact — the server nets it against that day's real BMR |
| `calories_burned` | you already have a net figure | exact, if your figure is |
| neither | you only know the duration | a guess (3.5 METs) |

The response reports `calories_source` so you can always tell which one ran.

Both tools **refuse to write a same-day duplicate** unless you pass `force=true` —
if a device integration already syncs walks or sleep, logging on top of it silently
inflates the day's burn or double-counts a night.

Neither has a delete counterpart. **This is deliberate**, not an oversight: the only
known deletion endpoint for these entry types removes far more than it is asked to.
Remove an exercise or biometric entry in the Cronometer app instead. See `CLAUDE.md`
§4.

> **FORK: hardened fork, read/write.** This fork ran read-only through its first
> eight phases; the owner enabled writes on 2026-07-27. Write tools carry
> `readOnlyHint: False` and `remove_food_entry` carries `destructiveHint: True`,
> so clients can warn before mutating. Every tool — read and write — is
> rate-limited and audited. The biometrics tools (`list_biometrics`,
> `get_biometrics`) were restored in `49d1263`. `log_exercise` and `log_sleep` were
> added on 2026-08-06, putting the surface at exactly 17 — 10 read, 7 write.
> `tests/test_tool_surface.py` pins it in both directions.
>
> Date ranges are capped at 90 days per call, calls are rate-limited to 60/hour,
> and every call is logged (shapes and counts only, never contents). See
> `CLAUDE.md` for the constraints, `AUDIT.md` for the upstream security review,
> and `RUNBOOK.md` for operations.

## Remote Deployment

The server supports remote deployment with OAuth 2.1 authorization (PKCE) for use with Claude.ai and other remote MCP clients.

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `CRONOMETER_USERNAME` | Yes | Cronometer account email |
| `CRONOMETER_PASSWORD` | Yes | Cronometer account password |
| `MCP_TRANSPORT` | No | Transport mode: `stdio` (default), `sse`, or `streamable-http` |
| `MCP_AUTH_TOKEN` | **Remote** | HMAC key used to sign and verify access tokens |
| `MCP_OAUTH_CLIENT_ID` | **Remote** | OAuth client ID, verified at `/token` |
| `MCP_OAUTH_CLIENT_SECRET` | **Remote** | OAuth client secret, verified at `/token` |
| `MCP_AUTHORIZE_PASSPHRASE` | **Remote** | FORK: required to complete `/authorize` |
| `MCP_BASE_URL` | **Remote** | Public base URL; must match the deployed URL exactly |
| `MCP_ALLOWED_REDIRECT_ORIGINS` | No | FORK: allowed OAuth redirect origins (default `https://claude.ai,https://claude.com`) |
| `PORT` | No | Listen port for remote transports (default 8000) |

> **FORK:** the four variables marked **Remote** are mandatory whenever
> `MCP_TRANSPORT` is `sse` or `streamable-http` — the server refuses to start
> without them. Upstream treated them as optional and served the diary
> unauthenticated when they were absent, so a forgotten secret was a silent
> downgrade to no authentication at all.

### Dokku / Heroku Deployment

The project includes a `Procfile` and `.python-version` for direct deployment with the Heroku Python buildpack:

```bash
# Create app
dokku apps:create cronometer-api-mcp

# Set environment
dokku config:set cronometer-api-mcp \
  MCP_TRANSPORT=streamable-http \
  MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
  MCP_OAUTH_CLIENT_ID=my-client \
  MCP_OAUTH_CLIENT_SECRET=$(openssl rand -hex 32) \
  MCP_BASE_URL=https://your-domain.com \
  CRONOMETER_USERNAME=your@email.com \
  CRONOMETER_PASSWORD=your-password

# Deploy
git push dokku main
```

### Claude.ai Remote Connection

When deployed remotely with OAuth configured, connect from Claude.ai using:

- **Server URL**: `https://your-domain.com/mcp`
- **OAuth Client ID**: Value of `MCP_OAUTH_CLIENT_ID`
- **OAuth Client Secret**: Value of `MCP_OAUTH_CLIENT_SECRET`

Claude.ai will open a browser tab for authorization. Click **Authorize** to complete the connection.

## Development

For local development, copy `.env.example` to `.env` and fill in your credentials:

```bash
cp .env.example .env
# edit .env
uv run cronometer-api-mcp
```

The CLI auto-loads `.env` on startup (dev convenience only). Real environment variables always win over `.env`, so production deployments and MCP client `env` blocks are unaffected.

## How It Works

This server communicates with `mobile.cronometer.com` -- the same REST API used by the Cronometer Android/Flutter app. The API was reverse-engineered through:

1. Static analysis of `libapp.so` (Dart AOT snapshot) from the APK to discover endpoint names
2. Traffic interception via Frida + mitmproxy to capture exact request/response formats
3. Trial-and-error against the live API to confirm payload shapes

The API uses two protocols:

- **v2 (`POST /api/v2/*`)** -- JSON-body auth, used for most operations (food search, diary read/write, nutrition, fasting, macros, biometrics)
- **v3 (`DELETE /api/v3/user/{id}/*`)** -- Header-based auth (`x-crono-session`), used for diary entry deletion

## Python API

You can use the client directly:

```python
from cronometer_api_mcp.client import CronometerClient
from datetime import date

client = CronometerClient()

# Search for foods
results = client.search_food("chicken breast")

# Get food details
food = client.get_food(results[0]["id"])

# Log a serving
client.add_serving(
    food_id=food["id"],
    measure_id=food["defaultMeasureId"],
    grams=200,
)

# Get today's diary
diary = client.get_diary()

# Get nutrition scores
scores = client.get_nutrition_scores()
```

## License

MIT

TDQS

A4.3/5.0

Scored across 13 tools

Disambiguation4/5

Tools have distinct primary purposes, but get_food_log, get_daily_nutrition, and get_nutrition_scores provide overlapping nutrition data at different granularities. Descriptions help differentiate, but an agent might still be confused about which to use for a specific need.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern with lowercase and underscores (e.g., get_macro_targets, search_foods, add_food_entry). The naming is predictable and clear.

Tool Count5/5

With 13 tools, the surface is well-scoped for a nutrition tracking API. Each tool serves a clear function without unnecessary duplication, covering targets, logging, food management, and fasting.

Completeness4/5

Core CRUD operations are covered (search, add, remove, custom food creation). Day management (copy, mark complete) and multiple read endpoints exist. Missing is an update/editing capability for diary entries, but removal and re-addition can compensate.

Maintenance

ActivitySlowing
ResponsivenessNo issues