Skip to main content
Glama
Huckleberry MCP

Huckleberry MCP

by arpitg1991
README.md
# huckleberry-mcp

Log baby events in [Huckleberry](https://huckleberrycare.com) by talking to the
Claude or ChatGPT app. For example:

- "She fell asleep 10 minutes ago."
- "Starting a feed on the left." … "Switch sides." … "Done."
- "Wet and dirty diaper, yellow and runny."
- "Bottle, 4 ounces of formula at 2:30."
- "When did she last eat?"

This project is a small web server that you run on [Fly.io](https://fly.io).
The Claude or ChatGPT app calls the server, and the server reads and writes
your Huckleberry log. The standard that connects the AI app to the server is
called MCP (Model Context Protocol).

> **Unofficial.** The server uses
> [py-huckleberry-api](https://github.com/Woyken/py-huckleberry-api), a
> reverse-engineered client. Huckleberry Labs does not support it. It can stop
> working when Huckleberry changes its app.

## Contents

- [How it works](#how-it-works)
- [What you need](#what-you-need)
- [Step 1. Choose your values](#step-1-choose-your-values)
- [Step 2. Create a GitHub OAuth app](#step-2-create-a-github-oauth-app)
- [Step 3. Create the Fly app](#step-3-create-the-fly-app)
- [Step 4. Add the secrets](#step-4-add-the-secrets)
- [Step 5. Check the server](#step-5-check-the-server)
- [Step 6. Connect Claude](#step-6-connect-claude)
- [Step 7. Connect ChatGPT (optional)](#step-7-connect-chatgpt-optional)
- [Step 8. Use it](#step-8-use-it)
- [Troubleshooting](#troubleshooting)
- [Update the server](#update-the-server)
- [Tools](#tools)
- [Settings reference](#settings-reference)
- [Security](#security)
- [Run locally and develop](#run-locally-and-develop)
- [Limits](#limits)

## How it works

```
You speak ──► Claude / ChatGPT app ──► https://YOUR-APP.fly.dev/mcp ──► Huckleberry
                                         │
                                         ├─ Check 1: GitHub sign-in
                                         └─ Check 2: your GitHub user id is on the allowlist
```

- You speak to the app on your phone. The app turns your words into a tool
  call, for example `log_diaper(kind="both", color="yellow")`.
- Claude's or ChatGPT's servers send that call to your Fly server. Your phone
  does not connect to the Fly server directly.
- The server signs in to Huckleberry with your Huckleberry email and password,
  and then saves the event.
- Only you can use the server. Each request must come from a GitHub sign-in,
  and the GitHub account must be on your allowlist.

## What you need

| Item | Why | Notes |
| --- | --- | --- |
| Huckleberry account | The data to read and write | The server signs in with email and password only. It cannot use Apple or Google sign-in. |
| GitHub account | You sign in with it, and Fly reads the code from it | Free plan is enough. |
| A copy of this repository in your GitHub account | Fly builds the server from it | Fork it, or push it to a new repository. |
| Fly.io account | Runs the server | Fly asks for a credit card. With auto-stop on, the server costs little because it runs only when called. |
| Claude plan with custom connectors, or ChatGPT with developer mode | Connects the app to the server | Check your plan's settings page. |

Time needed: about 30 minutes.

## Step 1. Choose your values

Fill in this table before you start. Every later step uses these values.

| Value | What to enter | Example |
| --- | --- | --- |
| **App name** | A name that you choose. Use lowercase letters, numbers, and dashes. It must be unique across all of Fly.io. Fly can also suggest one with a random ending. | `huckleberry-mcp-abc123` |
| **App URL** | `https://` + app name + `.fly.dev`. You know it as soon as you choose the name. The app does not need to exist yet. | `https://huckleberry-mcp-abc123.fly.dev` |
| **Callback URL** | App URL + `/auth/callback` | `https://huckleberry-mcp-abc123.fly.dev/auth/callback` |
| **Connector URL** | App URL + `/mcp` | `https://huckleberry-mcp-abc123.fly.dev/mcp` |
| **GitHub user id** | A number, not your login. See below. | `1234567` |
| **Timezone** | The timezone where your family lives. See below. | `America/Los_Angeles` |
| **Region** | Where Fly runs the server. See below. | `ord` |

**GitHub user id.** Open `https://api.github.com/users/YOUR-GITHUB-LOGIN` in a
browser. Copy the number next to `"id"`. Do not use your login name, because
someone else can take a login after you rename yours. The number never
changes.

**Timezone.** Use the IANA name for your home city. The server uses it to
turn "2:30pm" into the correct moment. It is not related to the region.

| You live in or near | Timezone |
| --- | --- |
| San Francisco, Los Angeles, Seattle | `America/Los_Angeles` |
| Denver, Phoenix (no daylight saving) | `America/Denver`, `America/Phoenix` |
| Chicago, Dallas | `America/Chicago` |
| New York, Boston, Toronto | `America/New_York` |
| London | `Europe/London` |
| Amsterdam, Berlin, Paris | `Europe/Amsterdam`, `Europe/Berlin`, `Europe/Paris` |
| India | `Asia/Kolkata` |
| Sydney | `Australia/Sydney` |

For other places, see the "TZ identifier" column in the
[list of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

**Region.** Use `ord` (Chicago). The calls come from Claude's and ChatGPT's
servers in the US, and Huckleberry's backend is most likely in the US, so a
central US region keeps both connections short. This is true even if you live
outside the US. Fly suggests a region near your own location, and a VPN can
make it suggest another country. Change it to `ord`. Any US region also works.

## Step 2. Create a GitHub OAuth app

The server uses this OAuth app to let you sign in with GitHub.

1. Open <https://github.com/settings/developers>.
2. Click **OAuth Apps**, then click **New OAuth App**.
3. Fill in the form:

   | Field | Value |
   | --- | --- |
   | Application name | Any name, for example `Huckleberry MCP` |
   | Homepage URL | Your **App URL** |
   | Application description | Leave empty |
   | Authorization callback URL | Your **Callback URL**. It must match exactly, with `https` and no `/` at the end. |
   | Enable Device Flow | Leave off |

4. Click **Register application**.
5. Copy the **Client ID**.
6. Click **Generate a new client secret**. Copy the secret now. GitHub shows it
   only once.

Keep the client ID and the secret for Step 4. You can change the URLs later on
the same page.

## Step 3. Create the Fly app

Use the Fly web page (A) or the command line (B). Both read the `fly.toml` file
in this repository.

### A. Fly web page

1. In the Fly dashboard, start a new app from GitHub
   (<https://fly.io/launch>), and pick your copy of this repository.
2. Fill in the form:

   | Field | Value | Why |
   | --- | --- | --- |
   | App name | Your **App name** | Sets your App URL. |
   | Region | `ord - Chicago, Illinois (US)` | See Step 1. |
   | Internal port | **`8000`** | The server listens on port 8000. Fly can show `8080` by default. With 8080, Fly cannot reach the server. |
   | CPU | `shared-cpu-1x` | Enough for one family. |
   | Memory | **`512MB`** | The server uses about 115 MB idle and more during Huckleberry calls. 256 MB can run out. |
   | Environment variables | The three values below | Settings that are not secret. |
   | Database / Managed Postgres | None | The server does not use a database. |
   | Working directory | Leave empty | |
   | Config path | Leave empty | |

   Environment variables:

   ```
   HUCKLEBERRY_TIMEZONE     = your Timezone, for example America/Los_Angeles
   PUBLIC_BASE_URL          = your App URL, for example https://huckleberry-mcp-abc123.fly.dev
   ALLOWED_GITHUB_USER_IDS  = your GitHub user id, for example 1234567
   ```

   Do not put passwords here. Plain environment variables are visible in the
   app settings. Step 4 adds the passwords as encrypted secrets.

3. Click **Deploy**.
4. Fly opens a pull request in your repository that adds its own `fly.toml`
   with your app name. If the pull request is still open, merge it. Then run
   `git pull` on your computer.

### B. Command line

1. Install `flyctl`: `brew install flyctl` (macOS) or
   `curl -L https://fly.io/install.sh | sh` (Linux). Then run `fly auth login`.
2. In `fly.toml`, set `app` to your **App name** and `primary_region` to your
   **Region**.
3. Create the app and its storage volume:

   ```bash
   fly apps create YOUR-APP-NAME
   fly volumes create huckleberry_data --size 1 --region ord --app YOUR-APP-NAME
   ```

4. Set the three values that are not secret:

   ```bash
   fly secrets set --stage --app YOUR-APP-NAME \
     HUCKLEBERRY_TIMEZONE=America/Los_Angeles \
     PUBLIC_BASE_URL=https://YOUR-APP-NAME.fly.dev \
     ALLOWED_GITHUB_USER_IDS=1234567
   ```

5. Do Step 4, and then run `fly deploy`.

### What to expect

The first start fails. The log shows
`Configuration error: Missing required environment variables: ...`, and the
machine stops. This is correct. The server does not start until every
required setting is present. Step 4 fixes it.

## Step 4. Add the secrets

Add these four secrets. Fly encrypts secrets and does not show them again.

| Secret | Value |
| --- | --- |
| `HUCKLEBERRY_EMAIL` | Your Huckleberry email |
| `HUCKLEBERRY_PASSWORD` | Your Huckleberry password |
| `GITHUB_CLIENT_ID` | From Step 2 |
| `GITHUB_CLIENT_SECRET` | From Step 2 |

**Web page:** open <https://fly.io/dashboard>, click your app, click
**Secrets**, and add each one. Saving the secrets restarts the app.

**Command line:** type each value when asked, so the password does not go into
your shell history:

```bash
for name in HUCKLEBERRY_EMAIL HUCKLEBERRY_PASSWORD GITHUB_CLIENT_ID GITHUB_CLIENT_SECRET; do
  printf '%s: ' "$name" >&2; IFS= read -rs value; echo >&2
  printf '%s=%s\n' "$name" "$value"
done | fly secrets import --app YOUR-APP-NAME
```

## Step 5. Check the server

Run these three checks before you connect any app. Replace the URL with your
**App URL**.

1. **The server is up.**

   ```bash
   curl -s -o /dev/null -w '%{http_code}\n' https://YOUR-APP-NAME.fly.dev/health
   ```

   Expected result: `200`. The first call after the server has been idle can
   take up to about 20 seconds while the machine starts.

2. **The server blocks strangers.**

   ```bash
   curl -si -X POST https://YOUR-APP-NAME.fly.dev/mcp | grep -iE '^HTTP|^www-authenticate'
   ```

   Expected result: a `401` line and a `www-authenticate: Bearer ...` line.
   If you get anything else, stop and see [Troubleshooting](#troubleshooting).
   Do not connect an app until you see `401`.

3. **The storage volume is attached.** In the Fly dashboard, open your app
   and click **Volumes**. Expect one volume named `huckleberry_data`, attached
   to a machine. With the command line: `fly volumes list --app YOUR-APP-NAME`.
   Without the volume, the app forgets your sign-in each time the machine
   restarts.

Do not open the Callback URL in a browser to test it. It shows an error unless
GitHub sends you there during sign-in. This is normal.

## Step 6. Connect Claude

1. On a computer, open <https://claude.ai>.
2. Open **Settings → Connectors**, then click **Add custom connector**.
3. Enter a name, for example `Huckleberry`, and your **Connector URL** (it ends
   in `/mcp`). Click **Add**.
4. Click **Connect**. A consent page from your server opens. Approve it.
5. GitHub asks you to authorize your OAuth app. Sign in with the GitHub account
   whose id is in `ALLOWED_GITHUB_USER_IDS`, and click **Authorize**.
6. Claude shows the connector as connected.
7. Open the Claude app on your phone. The connector syncs from your account. In
   a chat, open the tools menu and turn on Huckleberry.
8. The first time Claude uses a tool, it asks for permission. Choose
   **Always allow** for each tool, so you do not have to tap for every event.

## Step 7. Connect ChatGPT (optional)

ChatGPT adds custom MCP servers through **developer mode**, and its menu names
change often. Look under **Settings → Apps & Connectors → Advanced settings**
and turn on developer mode. Then create a connector with your **Connector URL**
and **OAuth** authentication, and sign in with GitHub as in Step 6.

The server accepts ChatGPT's usual callback address. If ChatGPT reports a
redirect error, find the rejected address in the Fly logs. Add it to the
`ALLOWED_REDIRECT_URIS` secret, together with the default values listed in
[Settings reference](#settings-reference).

## Step 8. Use it

- Tap the microphone in the chat box and speak. The app turns your speech into
  text and sends it. Some app versions also let the full voice mode use
  connectors. Test it on your phone.
- Say the time when it was not "now": "at 2:30", "20 minutes ago".
- Say the child's name if the account has more than one child.
- Ask questions: "How long did she sleep last night?", "How many diapers
  today?"
- Claude confirms each saved event in one sentence. Check the Huckleberry app
  the first few times.

## Troubleshooting

| What you see | Cause | Fix |
| --- | --- | --- |
| `curl` says "Could not resolve host", or the App URL does not load | The app has no public IP address, or it has not deployed yet | Run `fly ips list --app YOUR-APP-NAME`. If the list is empty, run `fly ips allocate-v6 --app YOUR-APP-NAME` and `fly ips allocate-v4 --shared --app YOUR-APP-NAME` (both free). If there is no deployment, deploy again (web page or `fly deploy`). |
| The App URL still does not resolve a few minutes after you add the IP addresses | Your computer remembers the earlier "no such host" answer | Wait 5 minutes and try again. To test now: `curl --resolve YOUR-APP-NAME.fly.dev:443:YOUR-IPV4 https://YOUR-APP-NAME.fly.dev/health`. |
| `fly status` shows the machine as `stopped` after you change a secret | The machine crashed before the change, so Fly did not restart it | Run `fly machine start MACHINE-ID --app YOUR-APP-NAME` (the ID is in `fly status`), and then read the log. |
| Log: `Configuration error: Missing required environment variables: ...` | A setting is missing | Add the named values (Step 3 and Step 4). |
| Log: `Configuration error: ...` with another message | A setting has a bad value | Read the message. For example, `ALLOWED_GITHUB_USER_IDS` must be numbers, and `PUBLIC_BASE_URL` must start with `https://` and have no path. |
| Deploy warning: "app is not listening on the expected address", or HTTP `502` | Internal port is not 8000 | In `fly.toml`, set `internal_port = 8000` under `[http_service]`, then `fly deploy`. |
| Log: "Out of memory", or the machine restarts by itself | Memory is too small | Set `memory = '512mb'` under `[[vm]]` in `fly.toml`, then `fly deploy`. Or: `fly scale memory 512 --app YOUR-APP-NAME`. |
| HTTP `421 Misdirected Request` | `PUBLIC_BASE_URL` does not match the URL you used | Set `PUBLIC_BASE_URL` to your exact App URL. |
| `Forbidden Origin` after you click **Approve** on the consent page | `PUBLIC_BASE_URL` does not match the address in the browser | Set `PUBLIC_BASE_URL` to your exact App URL, with `https://`. |
| GitHub says "The redirect_uri is not associated with this application" | The callback URL in the GitHub OAuth app is wrong | Set it to your exact Callback URL (Step 2). |
| `invalid_redirect_uri` error during connect | The AI app uses a callback address that is not allowed | See Step 7. |
| Claude connects, but shows no Huckleberry tools | You signed in with a GitHub account that is not on the allowlist | Check `ALLOWED_GITHUB_USER_IDS` against the id from Step 1. In GitHub, check which account is signed in. |
| Tool error: `Huckleberry request failed (ClientResponseError)` | The Huckleberry email or password is wrong | Sign in to the Huckleberry app with the same email and password. Update the secrets. |
| Times are off by some hours | Wrong timezone | Set `HUCKLEBERRY_TIMEZONE` (Step 1). |
| Claude asks you to connect again after the server was idle | No storage volume | Do check 3 in Step 5. Create the volume: `fly volumes create huckleberry_data --size 1 --region ord --app YOUR-APP-NAME`, then `fly deploy`. |
| The first command after a break is slow | The machine was stopped to save cost and is starting | Normal. To keep it always on, set `min_machines_running = 1` in `fly.toml`. This costs more. |
| Tool error: `There is more than one child` | The account has several children | Say the child's name. |
| Tool error: `No medicine named ...` | The medicine is not set up for this child | Say the name as it appears in Huckleberry, or tell Claude to add it. |

To read the server log: Fly dashboard → your app → **Logs**, or
`fly logs --app YOUR-APP-NAME`.

## Update the server

After you change the code or pull a new version:

```bash
git pull
fly deploy --app YOUR-APP-NAME
```

## Tools

| Tool | What it does |
| --- | --- |
| `get_status` | Shows running sleep and nursing timers, and the last sleep, nursing, and bottle |
| `get_history` | Lists sleeps, feeds, diapers, and pumping for the last N hours (up to 14 days) |
| `sleep_timer` | `start`, `pause`, `resume`, `stop` (saves the sleep), `cancel` (discards it) |
| `nursing_timer` | `start` (with side), `switch_side`, `pause`, `resume`, `stop`, `cancel` |
| `log_sleep` | Saves a sleep that already ended |
| `log_nursing` | Saves a breastfeed that already ended, with minutes per side |
| `log_bottle` | Saves a bottle feed. Units and type default to the last bottle. |
| `log_diaper` | Saves `pee`, `poo`, `both`, or `dry`, with color and consistency |
| `log_pump` | Saves a pumping session |
| `list_medicines`, `log_medicine` | Lists medicine names and saves a dose |
| `log_temperature` | Saves a temperature in `C` or `F` |
| `log_growth` | Saves weight, height, or head size |
| `log_activity` | Saves bath, tummy time, story time, and other activities |
| `list_children` | Lists the children on the account |

Each tool takes `at` (a clock time like `14:30` or `2:30pm`, or an ISO time
like `2026-11-04T14:30`) or `minutes_ago`. With neither, the time is "now". A
clock time later than now means yesterday.

## Settings reference

| Name | Kind | Required | Meaning |
| --- | --- | --- | --- |
| `HUCKLEBERRY_EMAIL` | secret | yes | Huckleberry sign-in email |
| `HUCKLEBERRY_PASSWORD` | secret | yes | Huckleberry password |
| `GITHUB_CLIENT_ID` | secret | yes | From the GitHub OAuth app |
| `GITHUB_CLIENT_SECRET` | secret | yes | From the GitHub OAuth app |
| `HUCKLEBERRY_TIMEZONE` | plain | yes | IANA timezone, for example `America/Los_Angeles` |
| `PUBLIC_BASE_URL` | plain | yes | App URL with `https://` and no path. `http://` is allowed only for `localhost`. |
| `ALLOWED_GITHUB_USER_IDS` | plain | yes | Numeric GitHub user ids, separated by commas |
| `JWT_SIGNING_KEY` | secret | no | At least 32 random characters. Signs sign-in tokens. Default: derived from `GITHUB_CLIENT_SECRET`. |
| `ALLOWED_REDIRECT_URIS` | plain | no | OAuth callback addresses of the AI apps, separated by commas. Default: `https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback,https://chatgpt.com/connector_platform_oauth_redirect` |
| `HOST` | plain | no | Address to listen on. Default `127.0.0.1`. The Docker image sets `0.0.0.0` inside the container. |
| `PORT` | plain | no | Port to listen on. Default `8000`. Must equal `internal_port` in `fly.toml`. |
| `FASTMCP_HOME` | plain | no | Folder for encrypted sign-in data. The Docker image uses `/data`, the volume. |

The server does not start if a required setting is missing or invalid.

## Security

- **Sign-in.** The server uses OAuth 2.1 with GitHub as the identity provider.
  A request without a valid token gets HTTP `401`.
- **Allowlist.** Only the GitHub ids in `ALLOWED_GITHUB_USER_IDS` can see or
  call tools. Other GitHub users can sign in, but they get no tools.
- **Least access.** The server asks GitHub only for `read:user`, which is
  read access to your public profile.
- **Callbacks.** The server sends sign-in codes only to the addresses in
  `ALLOWED_REDIRECT_URIS`.
- **Host check.** The server answers only requests for the host in
  `PUBLIC_BASE_URL`. This blocks DNS-rebinding attacks.
- **Secrets.** Your Huckleberry password lives only in Fly's encrypted secret
  store. The repository and the Docker image contain no credentials.
- **Logs.** HTTP access logs are off, so sign-in codes do not reach the log.
- **Container.** The server runs as a regular user, not as root.

To remove access, delete the connector in Claude or ChatGPT. To block every
existing sign-in at once, set a new `JWT_SIGNING_KEY`. If you never set
`JWT_SIGNING_KEY`, generate a new client secret in the GitHub OAuth app and
update `GITHUB_CLIENT_SECRET` instead.

## Run locally and develop

Install [uv](https://docs.astral.sh/uv/) and Python 3.14.

```bash
uv sync
uv run pytest                                      # tests use a fake Huckleberry API
uv run ruff check . && uv run ruff format --check .
```

To run the server on your computer, create a second GitHub OAuth app with the
callback `http://localhost:8000/auth/callback`. Then:

```bash
cp .env.example .env    # fill it in; set PUBLIC_BASE_URL=http://localhost:8000
set -a; . ./.env; set +a
uv run huckleberry-mcp  # listens on 127.0.0.1:8000 only
```

With Docker, publish the port on your own computer only:

```bash
docker build -t huckleberry-mcp .
docker run --rm --env-file .env -p 127.0.0.1:8000:8000 -v huckleberry_data:/data huckleberry-mcp
```

## Limits

- No tools yet for solids, potty, or live pump and activity timers.
- `get_history` covers at most 14 days per call.
- The Huckleberry connection is unofficial and can break without warning.

## License

MIT. See [LICENSE](LICENSE). Huckleberry is a trademark of Huckleberry Labs, Inc. This project is not affiliated with it.