Skip to main content
Glama
owntracks-mcp

owntracks-mcp

by de-niji
README.md
# OwnTracks for Hermes Agent

Connects [OwnTracks](https://owntracks.org) to [Hermes Agent](https://github.com/NousResearch/hermes-agent)
(Nous Research), so Hermes can answer questions like:

- "Where am I right now?" / "Where is Anna?"
- "Where was I yesterday afternoon?" / "When did I get to the office today?"
- "How far is Anna from home?"
- "How many kilometres did I drive this week?"
- "Remind me to buy milk when I'm at Edeka." / "Tell me when Anna gets home."

The project has two parts:

1. **`owntracks-mcp`**: an MCP server (Python) that queries the REST API of the
   [OwnTracks Recorder](https://github.com/owntracks/recorder). It works with Hermes and
   with any other MCP client (Claude Desktop, Cursor, …).
2. **`skills/owntracks/SKILL.md`**: a Hermes skill that tells the agent when and how to use
   the tools (always state how old a position is, summarise stays, respect privacy).

All access to the Recorder is **read-only**. The Recorder's delete endpoint (`/api/0/kill`) is
never called. The only thing the server writes is its own reminder list.

```
OwnTracks app ──MQTT/HTTP──▶ OwnTracks Recorder ◀──HTTP── owntracks-mcp ◀──MCP (stdio)── Hermes Agent
```

## Tools

| Tool (in Hermes: `mcp_owntracks_…`) | Purpose |
|---|---|
| `get_last_location(user?, device?)` | Latest position: address, regions, age, accuracy, speed, battery, map link. No `user` → everyone. `user="me"` → default user. |
| `get_location_history(user, device?, start?, end?, max_points?, stay_min_minutes?)` | History for a time range: distance travelled, top speed, **stays** (where and how long) and a thinned-out route. |
| `get_distance(lat?, lon?, user?, device?)` | Straight-line distance from the latest position to a point, or to `OWNTRACKS_HOME` when no coordinates are given. |
| `list_devices()` | All users and devices. |
| `reverse_geocode(lat, lon)` | Address from the Recorder's geocache. |
| `owntracks_status()` | Connection check, active configuration (no secrets) and reminder checker health. |
| `create_location_reminder(text, place, trigger?, user?, …)` | "Remind me of X when I arrive at / leave Y". See [Location reminders](#location-reminders). |
| `list_location_reminders(include_inactive?)` | Show reminders. |
| `delete_location_reminder(reminder_id)` | Remove a reminder. |

Time arguments accept `now`, `today`, `yesterday`, relative values like `30m`, `6h`, `2d`,
`1w`, as well as `2026-09-27` or `2026-09-27T14:30` (local time zone).

## Requirements

- A running **OwnTracks Recorder** that the machine running Hermes can reach over HTTP
  (default port `8083`). If you don't have one yet,
  [owntracks/quicksetup](https://github.com/owntracks/quicksetup) is the quickest route,
  or `docker run -p 8083:8083 owntracks/recorder`.
- Python ≥ 3.10, ideally with [`uv`](https://docs.astral.sh/uv/).

## Installing in Hermes

### 1. Register the MCP server

Install the command once (recommended, and needed for location reminders):

```bash
uv tool install git+https://github.com/de-niji/OwnTracksHermes
# update later with: uv tool upgrade owntracks-mcp
```

Then in `~/.hermes/config.yaml` (full example: [`examples/hermes-config.yaml`](examples/hermes-config.yaml)):

```yaml
mcp_servers:
  owntracks:                 # keep this name, the skill expects mcp_owntracks_* tools
    command: owntracks-mcp   # or: uvx --from git+https://github.com/de-niji/OwnTracksHermes owntracks-mcp
    env:
      OWNTRACKS_URL: "http://localhost:8083"
      OWNTRACKS_TIMEZONE: "Europe/Berlin"
      OWNTRACKS_DEFAULT_USER: "alice"
      OWNTRACKS_DEFAULT_DEVICE: "phone"
      OWNTRACKS_HOME: "51.9607,7.6261"
    timeout: 60
```

If Hermes cannot find `owntracks-mcp` on its `PATH`, use the absolute path
(`which owntracks-mcp`, usually `~/.local/bin/owntracks-mcp`).

Put passwords in `~/.hermes/.env` and reference them in the config as `${OWNTRACKS_PASSWORD}`.

### 2. Install the skill

Either of these works:

```bash
# a) copy it
cp -r skills/owntracks ~/.hermes/skills/

# b) or load it straight from the cloned repo, in ~/.hermes/config.yaml:
# skills:
#   external_dirs:
#     - /home/<you>/OwnTracksHermes/skills
```

### 3. Test

```bash
hermes mcp test owntracks      # should report 9 tools
hermes chat
> Where am I right now?
```

## Location reminders

Ask Hermes things like:

- "Remind me to buy milk when I'm at the Edeka on Hammer Straße."
- "When I leave work, remind me to take the parcel."
- "Tell me every time Anna gets home."
- "Remind me to get cash when I'm at the station, but only today."

The place can be a **region from the OwnTracks app** (most reliable, since the phone reports
entering and leaving immediately), `home`, or any address, which Hermes turns into coordinates
first (default radius 150 m).

Reminders are delivered by a Hermes **no-agent cron job**. It runs a small check script every
minute. That costs no tokens and says nothing unless a reminder fires. Set it up once:

```bash
owntracks-mcp setup-reminders
# writes ~/.hermes/scripts/owntracks-reminders.sh and prints the cron command, e.g.:
hermes cron create "every 1m" --no-agent --script owntracks-reminders.sh \
  --deliver telegram --name owntracks-reminders
```

Replace `telegram` with where you want the reminder (`signal:+49…`, `discord:#channel`, …).
A reminder then arrives like:

```
📍 Buy milk
(You arrived at Edeka, 17:42)
```

Details:

- A reminder fires once unless it was created with `repeat`. `expires` ends it automatically.
- If you are already at the place when you create an "arrive" reminder, it waits for the
  next arrival.
- Delay = how often your phone reports (OwnTracks "significant changes" mode can be minutes)
  + up to one minute for the check. For places that matter, define a region in the app.
- If the Recorder is unreachable for 15 minutes, you get one warning, and another message
  when it is back.
- Reminders are stored in `~/.hermes/owntracks/reminders.json`. Because cron scripts don't
  see the MCP server's environment, the server also keeps its Recorder settings in
  `~/.hermes/owntracks/recorder.json` (file mode 0600) for the checker.
  Set `OWNTRACKS_DATA_DIR` to use a different folder.

## Configuration (environment variables)

| Variable | Required | Meaning |
|---|---|---|
| `OWNTRACKS_URL` | yes | Base URL of the Recorder, e.g. `http://localhost:8083` or `https://example.com/owntracks` (without `/api/0`) |
| `OWNTRACKS_USERNAME` / `OWNTRACKS_PASSWORD` | no | HTTP basic auth, if a reverse proxy (nginx, Caddy, Traefik) sits in front |
| `OWNTRACKS_VERIFY_SSL` | no | `true` (default), `false`, or the path to a CA bundle |
| `OWNTRACKS_TIMEZONE` | no | e.g. `Europe/Berlin`. Defaults to the system time zone |
| `OWNTRACKS_DEFAULT_USER` | no | Who "I" / `me` is |
| `OWNTRACKS_DEFAULT_DEVICE` | no | Default device of that user |
| `OWNTRACKS_HOME` | no | `lat,lon` of home, for "how far from home" |
| `OWNTRACKS_TIMEOUT` | no | HTTP timeout in seconds (default 15) |
| `OWNTRACKS_DATA_DIR` | no | Where reminders are stored (default `$HERMES_HOME/owntracks`, i.e. `~/.hermes/owntracks`) |

Addresses only appear if the Recorder does reverse geocoding (`OTR_GEOKEY`).
Named places like "Home" or "Work" come from the **regions** (waypoints) set up in the OwnTracks app.

## Security

The OwnTracks Recorder has **no authentication of its own**. Anyone who can reach its API can
read, and even delete, all data. So:

- Keep the Recorder reachable only locally or over a VPN, or put a reverse proxy with basic
  auth in front of it (and use `OWNTRACKS_USERNAME` / `OWNTRACKS_PASSWORD`).
- Towards the Recorder this MCP server is read-only and never calls the delete endpoint.
  Its read tools are marked `readOnlyHint`; only the reminder tools write (to the local
  reminder file).
- Location data is very personal. The skill instructs Hermes to share it only with the
  person asking.

## Running as an HTTP server (optional)

If Hermes runs on a different machine than the Recorder:

```bash
owntracks-mcp --transport streamable-http --host 0.0.0.0 --port 8765
```

```yaml
mcp_servers:
  owntracks:
    url: "http://recorder-host:8765/mcp"
```

Don't expose this port to the internet unprotected.

## Development

```bash
uv venv && uv pip install -e ".[dev]"
.venv/bin/pytest
```

The tests run against a simulated Recorder (`tests/fake_recorder.py`). One of them launches the
real server over stdio, the way Hermes does. Tested with MCP Python SDK 1.x and 2.x.

## License

MIT

TDQS

A3.8/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: connectivity/config, device listing, current location, distance calculation, historical route, and reverse geocoding. There is no meaningful overlap between any pair of tools.

Naming Consistency4/5

Five of six tools follow a consistent verb_noun pattern (list_devices, get_last_location, get_distance, get_location_history, reverse_geocode). The odd one out, owntracks_status, breaks the pattern but remains unambiguous.

Tool Count5/5

Six tools is well-scoped for a read-only location tracking server. Each tool earns its place and the set is neither thin nor bloated.

Completeness4/5

The core read operations are covered: status, device listing, current location, history, distance, and reverse geocoding. Minor gaps exist—reverse_geocode only works with cached places, and there is no tool for arbitrary geocoding or device management—but these may be outside the server's intended scope.

Maintenance

ActivitySlowing
ResponsivenessNo recent community issues