Skip to main content
Glama
README.md
# kenotex-mcp

**Let your coding agent manage your real todos, calendar, and habits.**

[Kenotex](https://kenxcomp.com/kenotex/?ct=npm-mcp) is a native planner for Mac and iPhone.
This package is its MCP server: point Claude Code, Claude Desktop, Cursor, Zed, or any MCP
client at it, and the agent works on the actual items in the app — creating todos with
categories and recurrence, scheduling calendar events, ticking off daily or interval check-in habits.
Not a scratchpad, not a markdown file. Everything lives in a local SQLite database on your
own machine.

> **Your agent writes on your Mac. It's on your iPhone via iCloud.**

Works with **Claude Code**, **Claude Desktop**, **Cursor**, **Zed**, **OpenClaw**, and
anything else that speaks MCP over stdio.

## Get the app

These tools drive the Kenotex **macOS** app — it hosts the local server this package talks
to, so you need it installed and running. Free to download, with in-app purchases.

**The MCP integration is not one of those purchases.** Every tool in this package talks to
the app's local HTTP server, which has no subscription gate — the whole integration works
on the free tier.

**→ [kenxcomp.com/kenotex](https://kenxcomp.com/kenotex/?ct=npm-mcp)**

## Prerequisites

- **macOS 26 or later** — the app's minimum. The iPhone app syncs over iCloud, but the MCP
  server talks to the Mac.
- The Kenotex macOS app must be **running** (it hosts the local HTTP server on port 21519).
- Node.js 20+ or Bun.

## Install

Globally:

```bash
npm install -g kenotex-mcp
# or
bun add -g kenotex-mcp
```

Or use it directly via npx / bunx (no install):

```bash
npx kenotex-mcp
```

## Configure in an MCP client

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```jsonc
{
  "mcpServers": {
    "kenotex": {
      "command": "npx",
      "args": ["-y", "kenotex-mcp"]
    }
  }
}
```

Restart Claude Desktop. You'll see the Kenotex tools appear in the 🛠️ menu.

### Cursor / Zed / OpenClaw

Same pattern: point the MCP config at `kenotex-mcp` as the `command`.

### Environment overrides

- `KENOTEX_HOST` — override base URL (default `http://127.0.0.1:21519`).
- `KENOTEX_TOKEN` — skip filesystem token discovery and use this token.

## Tools exposed

| Category   | Tool                                                |
| ---------- | --------------------------------------------------- |
| Todo       | `create_todo` `list_todos` `get_todo` `update_todo` `complete_todo` `delete_todo` |
| Event      | `create_event` `list_events` `get_event` `update_event` `delete_event` |
| Habit      | `create_habit` `list_habits` `get_habit` `update_habit` `set_habit_check` `list_habit_checks` `delete_habit` |
| Category   | `list_categories` `create_category`                 |
| Reminder   | `add_reminder` `remove_reminder`                    |

**`create_todo` requires a `categoryId`** — todos can no longer be created uncategorized. The flow is: call `list_categories` and reuse a matching category's id; if none fits, call `create_category` and use the returned id, then `create_todo`.

**Habits are check-in trackers** (medication, water, stretching) — **daily** by default (up to 6 dose times/day), or **interval** (Q times every N days / N times a week / N times a month) via `cadence`. Use `create_habit` for routines the user ticks off; use `create_todo` + recurrence for one-off tasks or *fixed-slot* schedules (a particular weekday, a particular day of the month) — `cadence` counts how many times per period, recurrence pins which day. Writes take `times: ["HH:mm", …]`; reads return `times: [{id, time}]` — pass a slot's `id` as `slotId` to `set_habit_check` for a multi-dose habit (omit it to check all of today's doses). `cadence: {"unit":"day"|"week"|"month","interval":N,"quota"?:Q,"startDate"?:"yyyy-MM-dd"}` makes an interval habit — `quota` defaults to 1, out-of-range is a 422 and is never clamped, `startDate` defaults to today and can never be changed afterwards, and an interval habit takes at most one dose time:

| `unit` | `interval` | `quota` | period |
|---|---|---|---|
| `day` | 1–365 | 1–`interval` | N calendar days, so `quota ≤ N`; `day/1` takes quota 1 = explicitly daily |
| `week` | 1–8 | 1–7×`interval` | N week(s) |
| `month` | 1–12 | 1–min(31×`interval`, 365) | N whole **calendar** months (1st → last day), so the due day is always month end and the period's length follows the calendar rather than a fixed 30 days |

Examples: `{"unit":"day","interval":3}` = every 3 days · `{"unit":"day","interval":5,"quota":2}` = twice every 5 days · `{"unit":"week","interval":1,"quota":2}` = twice a week · `{"unit":"month","interval":1,"quota":2}` = twice a month.

Reads add `cadence`, `isIntervalHabit` and `currentPeriod {start,end,dueDay,doneCount,quota,done}` (`quota` is the period's effective quota — the configured quota clamped to the period's checkable days from `startDate` on, so a 31-a-month habit reports 28 in February and 17 for a first month started on the 14th) — use `currentPeriod.done` for "is this period satisfied" (`checkedToday` stays a calendar-day fact). `update_habit {"cadence": null}` turns an interval habit back into a daily one (anchor kept). Interval habits are checked per calendar day (no `slotId`; `date` never in the future). Checking is explicit **set-state** (`checked: true|false`, `date` defaults to today, idempotent, `false` un-checks). **`delete_habit` permanently deletes the habit and its entire check-in history** — set an `endDate` via `update_habit` to stop-but-keep-records instead.

**App version**: habit tools require the Kenotex app release that ships `/v1/habits` (1.2.9+, local API v2). `cadence` additionally requires the release that advertises local API **v3** — against an older app a `cadence` write is refused up front with `VERSION_SKEW` rather than being silently ignored, and once the app is updated you just retry (the check re-reads the running app's version, no MCP restart needed). **Nothing else is gated**: on a v2 app every other tool, habit tools included, behaves exactly as it did before — only `cadence` is unavailable.

**Recurrence DSL**: `'daily'` / `'weekly:mon,wed,fri'` / `'monthly:15'` / `'yearly'` / `'after:daily'` (todos only) / `'none'`.

**Virtual event occurrences**: `get_event` accepts ids like `{parentId}__occ__{yyyy-MM-dd}`. Recurring parents materialize past occurrences on delete.

## How auth works

On each HTTP request the server reads the Kenotex bearer token from (in order):

1. `~/Library/Containers/com.kenxcomp.kenotex/Data/Library/Application Support/Kenotex/local-http-token`
2. `~/Library/Application Support/Kenotex/local-http-token`
3. `~/.kenotex-local-token`

The token is rotated each time the Kenotex app starts, so a stale MCP session just re-reads the file on its next tool call.

## Troubleshooting

**"Kenotex app not running or token not accessible"** — launch Kenotex.app. If running, the token file should exist at one of the three paths above.

**Requests time out** — check that no firewall is blocking localhost:21519; tools are hardcoded to 10-second timeout.

## License

MIT © kenxcomp