Swizzler MCP
# swizzler-mcp
An MCP server that lets an AI assistant read your Swizzler cocktail library — search
recipes, pull up a spec, work out what you can make from the bottles on your shelf, and
look at what you've actually been drinking. Works with Claude, Codex, and other
MCP-compatible clients.
Read-only. Nothing here can change your library.
The iOS app is closed source; this server is not. It is the piece that runs on your own
Mac and touches your recipe data, so it is the piece worth being able to read.
## How it works
Swizzler stores recipes on-device in SwiftData, synced through your private CloudKit
database. Neither of those is reachable from a desktop MCP server, so the app publishes a
snapshot instead: turn on **Settings → AI Assistants → Share Library**, and Swizzler
writes a photo-free copy of your library to its own iCloud Drive folder. This server reads
that file from your Mac.
```
iPhone/iPad ──SwiftData──> private CloudKit (app's own sync, untouched)
│
└──snapshot──> iCloud Drive/Swizzler/Library.swizzle ──> swizzler-mcp ──> Claude
```
### Freshness
The snapshot only refreshes while Swizzler is running — it rewrites on every edit and when
the app goes to the background. In practice that covers the single-device case completely,
because your library can only change while the app is open in front of you.
Where it can lag is multi-device: edit on your iPad, then ask Claude on your Mac without
opening Swizzler on any device that has synced. So **every tool result states the
snapshot's age**, and past 24 hours it says so with a warning rather than answering as if
the data were current. Ask Claude to check `snapshot_status` any time you want to know.
## Install
One command, the same for every client. It runs straight from npm — nothing to download,
build, or keep up to date.
**Claude Code**
```bash
claude mcp add swizzler -- npx -y swizzler-mcp
```
**Codex**
```bash
codex mcp add swizzler -- npx -y swizzler-mcp
```
**Claude Desktop** — add this to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"swizzler": {
"command": "npx",
"args": ["-y", "swizzler-mcp"]
}
}
}
```
**Anything else that speaks MCP** — run `npx -y swizzler-mcp` over stdio.
Node 20 or newer is the only requirement, and `npx` ships with it.
### From a clone
```bash
git clone https://github.com/chrisscott/swizzler-mcp.git
cd swizzler-mcp
npm install
npm run build
claude mcp add swizzler -- node "$PWD/dist/index.js"
```
By default it reads:
```
~/Library/Mobile Documents/iCloud~com~getswizzler~app/Documents/Library.swizzle
```
Set `SWIZZLER_LIBRARY_PATH` to point somewhere else.
## Tools
| Tool | What it answers |
|---|---|
| `snapshot_status` | How current is this data, and where did it come from? |
| `diagnose_sync` | Why are recipes missing or stale? Checks iCloud Drive on this Mac. |
| `search_recipes` | By name, ingredient, spirit, collection, or favourites |
| `get_recipe` | The full spec for one drink |
| `whats_next` | The Next Round queue — what you've lined up to make |
| `list_collections` | Your collections and their sizes |
| `what_can_i_make` | Given these bottles, what's within reach (and what's missing)? |
| `recipe_history` | What you've actually made, how often, and how you rated it |
### Smart collections
Smart collections have no stored membership — the app evaluates their rules at query time.
The snapshot resolves them into concrete memberships before writing, so the rule engine
stays in Swift rather than being reimplemented here and drifting. Backup exports keep them
as rules, so re-importing restores a smart collection rather than freezing today's matches.
## Tests
```bash
npm test
```
Drives the built server over stdio with a real MCP client against fixture libraries,
covering search, detail rendering, staleness warnings, and the missing-snapshot path.
## Releasing
On a `v*` tag, `.github/workflows/release.yml` runs the suite, creates a GitHub release,
and publishes to npm. npm is the only distribution channel — there is no download.
```bash
# bump the version in package.json first — the workflow fails the build if it
# disagrees with the tag
git tag v0.2.0 && git push --tags
```
npm publishing uses OIDC trusted publishing, so there is no token in the repo. It needs
one-time setup on npmjs.com under the package's Settings → Trusted Publisher: repository
`chrisscott/swizzler-mcp`, workflow `release.yml`. That can only be configured for a
package that already exists, so the very first publish has to be done by hand.
A version already on the registry is skipped rather than failing the run, so re-running a
release is safe.
`workflow_dispatch` runs the same build and tests without publishing, which is the way to
check the pipeline before tagging.
## Troubleshooting
**"No Swizzler library snapshot at …"** — Check three things, in this order:
1. **iCloud Drive is on for this Mac.** System Settings → your name → iCloud → iCloud Drive.
If it is off, nothing syncs down at all and `~/Library/Mobile Documents/` will hold only
an empty `com~apple~CloudDocs`. To check from a terminal:
```bash
defaults read MobileMeAccounts | grep -A2 MOBILE_DOCUMENTS
```
`Enabled = 0` means iCloud Drive is off.
2. **Sharing is on in the app.** Settings → AI Assistants → Share Library. The status
row underneath reports what the last write actually did.
3. **The snapshot has been published since you turned it on.** Background the app to force
a write, then give iCloud a minute.
**The Swizzler folder doesn't appear in Finder** — iOS caches the `NSUbiquitousContainers`
key, so the folder sometimes only shows up after the app's build number changes.
**Recipes look out of date** — Open Swizzler on the device you last edited on. The
snapshot is republished when the app backgrounds.
TDQS
Scored across 8 tools
Most tools have clearly distinct purposes (ingredient matching, search, history, queue, collections). The main overlap is snapshot_status vs diagnose_sync, both about library freshness/sync, though the descriptions differentiate reporting status from explaining causes.
All names use snake_case consistently, which is good. However, the verb/noun patterns vary: some are verb_noun (search_recipes, get_recipe, list_collections), others are plain nouns (recipe_history, snapshot_status) or question phrasing (what_can_i_make, whats_next).
Eight tools is well-scoped for a cocktail library assistant, each covering a distinct user intent without redundancy or bloat.
Read-side coverage is strong: discovery, search, detail, history, queue, collections, and sync troubleshooting. The surface appears read-only with no write operations (e.g. rating or adding recipes), a minor gap if mutation is in scope.