Install
openclaw skills install @chrischall/untappd-mcpSearch Untappd beers, breweries, and venues; read user profiles, check-ins, wishlists, distinct beers, badges, friends, and your friend activity feed; and post check-ins, toasts, and comments to your own Untappd account. Use when the user asks about beer ratings, what a beer or brewery is like on Untappd, someone's Untappd check-ins or wishlist, or wants to log/toast/comment on a beer.
openclaw skills install @chrischall/untappd-mcpTalks to Untappd's mobile (v4) API using your own account, calling the same
endpoints the iPad app uses. Authenticates with an access token you already
hold, or mints one via the app's xauth endpoint (username + password).
Environment variables (the README has the full table and how to capture the client credentials):
| Variable | Needed | Purpose |
|---|---|---|
UNTAPPD_ACCESS_TOKEN | no | A token you already hold. With it, no password is needed — xauth is skipped. When it expires, the server says so; supply a fresh one. |
UNTAPPD_USERNAME, UNTAPPD_PASSWORD | if no token | Your Untappd login, used only to mint a token. Accounts with 2FA can't log in this way. |
UNTAPPD_CLIENT_ID, UNTAPPD_CLIENT_SECRET | for login & writes | The Untappd mobile app's client credentials. A pre-seeded token can read without them. |
UNTAPPD_TIMEZONE | no | IANA zone check-ins are stamped with when a call omits timezone. Defaults to the server's zone. |
UNTAPPD_PHOTO_DIR | no | Directory (or :-separated list) a check-in's photo_path must be inside. Default ~/Downloads/untappd-mcp. |
UNTAPPD_CACHE_DB | no | Local check-in cache file (default ~/.untappd-mcp/checkins.db). Local/stdio only. |
Two things the server enforces that you should plan around:
untappd_checkin's photo_path must be a file inside
UNTAPPD_PHOTO_DIR (default ~/Downloads/untappd-mcp); anything else (e.g.
~/Pictures/beer.jpg) is refused. Ask the user to move the photo there first.timezone (the drinker's IANA zone, e.g. America/New_York) on
untappd_checkin unless UNTAPPD_TIMEZONE is set — otherwise the check-in is
stamped in the server's zone.Run untappd_healthcheck to confirm auth works.
untappd_search_beer / untappd_beer_info — find beers, then get full detail by bid.untappd_search_brewery / untappd_brewery_info — breweries.untappd_search_venue / untappd_venue_info — bars, breweries, restaurants.untappd_venue_menu — a venue's verified beer menu, flattened. Use instead of
venue_info for "what's on tap" — venue_info returns only the first section
of each menu and under-reports large boards. This pages sections under a
per-call max_pages budget (like the sync tools) and is resumable: it returns
another_run_needed + next_section_offset when the budget runs out, or
truncated if the upstream stops returning sections short of coverage.untappd_user_info — a user's profile (omit username for your own).untappd_user_checkins — recent check-ins (page with max_id).untappd_user_wishlist — wishlist beers.untappd_user_beers — distinct beers ever checked in.untappd_user_badges / untappd_user_friends.untappd_activity_feed — your friend feed (page with max_id).untappd_checkin_info — full detail of one check-in.Most user tools default username to your configured account when omitted.
view)Thirteen of this server's 46 tools take view: "compact" | "full", and
compact is the DEFAULT — the slim rung is what you get without asking
for it.
If you were told to pass compact: true, that advice is stale. It used to
be true: the projection shipped as an opt-in boolean defaulting to false,
and the tool descriptions asked the caller to please turn it on. An efficiency
you have to request is one that mostly is not requested — and the caller
paying for the fat record was the one least able to know a slim rung existed.
So it flipped. The old parameter is gone; it is not declared anywhere, so zod
drops the unknown key and the call succeeds. You get the compact rung — right
answer, wrong reason. The same silence bites the other way: compact: false no longer buys you the full record either. It is dropped identically
and you still get compact. view: "full" is the only way back.
Compact comes in two tiers here, and they are not the same thing:
A hand-written field projection — 9 tools.
untappd_activity_feed, untappd_user_checkins,
untappd_beer_activity, untappd_venue_activity,
untappd_local_checkins): each ~5 KB record becomes {checkin_id, created_at, user, rating, comment, beer{bid, name, style, abv}, brewery, venue, toasts, comments, has_photo}. A default page of 25 was upwards of
100 KB to learn what someone drank.untappd_search_beer: each ~1.2 KB match becomes {bid, name, style, abv, ibu, brewery, checkin_count, have_had} — the long beer_description and
the nested brewery record go.untappd_user_wishlist: the same beer core plus added_at.untappd_user_beers: the same beer core plus your_count, your_rating,
global_rating, last_had.untappd_brewery_beers: the same beer core plus rating, rating_count,
checkin_count, have_had, your_count. It has its own projector
rather than sharing untappd_search_beer's, and the reason is a trap worth
knowing: the item shape is identically {beer:{…}}-wrapped, but this
endpoint spells the two surrounding fields total_count / has_had where
/search/beer says checkin_count / have_had. Sharing the projector
would return beers with no check-ins that you had never had, and the
drift guard would not fire — the container was where it was expected. Per
page it also drops sorting_options and the copy of the same brewery
object repeated on every row.An upstream request for less — 4 tools. untappd_user_info,
untappd_beer_info, untappd_brewery_info and untappd_venue_info forward
compact=true to Untappd's own endpoint, which drops the embedded
activity/list blocks server side. There is no local projection on these:
the entity's own fields are byte-identical on both rungs. What compact saves
is Untappd's embedded blocks — and the bandwidth, not just the context. Do not
expect a shorter beer record from untappd_beer_info; expect the recent-
activity block to be absent.
The projectors are drift-safe. Each keys off a documented container
(checkins.items, beers.items); if that array is not where it is expected —
this is a reverse-engineered API — the projector warns on stderr and returns
the RAW response rather than an empty or wrong one. A short answer is never
silently manufactured.
view: "full" returns Untappd's response untouched. There is deliberately
no raw rung: nothing here re-serialises or normalises a payload, so
full already IS the upstream response and a third value would silently alias
one that exists.
viewEach for its own reason — and none of them will tell you it ignored the parameter, because an undeclared key is dropped by zod without a warning:
The 12 confirmation-gated writes (untappd_checkin, untappd_toast,
untappd_add_comment, the two deletes, the wishlist pair, the four friend
actions, and the local untappd_cache_forget) answer with a confirmation preview or a receipt. Nothing in a receipt is
decoration.
untappd_sync_checkins / untappd_sync_user_beers answer with sync
PROGRESS — pages walked, another_run_needed, backfill_complete. Slimming
a progress report is how you lose the field that says it is not finished.
The five cache tools (untappd_cache_has_had, …_has_had_many,
…_not_had, untappd_cache_query, untappd_top_not_had) answer from the
local SQLite mirror in a shape this repo already wrote — a verdict plus the
freshness block that says whether a "not found" might be a false negative.
There is no upstream payload behind them to project away from.
untappd_checkin_info exists to return the FULL record for one check-in
— photos, badges earned, toasts, comments. The ~5 KB the check-in projection
removes is exactly what you called this tool to get.
untappd_venue_menu is already its own hand-written projection, with a
per-call page budget and a resumable next_section_offset contract. A
second projection on top of it would fight the first.
untappd_resolve, untappd_open_url, untappd_healthcheck return a
verdict or a diagnostic.
The remaining nine reads — untappd_trending, untappd_notifications,
untappd_search_brewery, untappd_search_venue, untappd_user_badges,
untappd_user_friends, untappd_user_venues, untappd_pending_friends,
untappd_venue_by_foursquare — hand back Untappd's payload as it arrived.
No projector has been written for their shapes, so there is no rung to ask
for and no slim option exists.
untappd_brewery_beers used to be tenth on this list and no longer is
(#161) — it takes a view now, and its field shape is documented with the
other projections above.
Each asks the user to confirm first. Where the client can show a confirmation
prompt, it does (unless the server sets MCP_CONFIRM_ELICITATION=off). Otherwise the first call makes NO network call and returns
status: "confirmation-required" with a preview of the exact request and a
confirmToken; show the preview to the user, and only after they approve call
the tool again with the same arguments plus confirmToken. A token works once
and only for those arguments (a change returns DRAFT_CHANGED with a fresh
preview). MCP_CONFIRM_MODE on the server can make this auto or refuse.
untappd_toast — toast/like a check-in (this endpoint is a toggle).untappd_add_comment — comment on a check-in.untappd_checkin — post a new beer check-in to your feed (needs a bid from
untappd_search_beer; optional rating 0–5 in 0.25 steps, shout, venue).Photo attachment and wishlist add/remove are not yet supported.
The API can't answer "has this user ever had beer X?" without paging their whole
history (50/page, rate-limited). These tools keep a SQLite mirror so that
question is answered instantly with no API calls. On the stdio server the
mirror is a local file (UNTAPPD_CACHE_DB, default ~/.untappd-mcp/checkins.db);
the store is injectable, so another deployment can back it differently. Same tools either way.
Sync first, then query. Two sources; both resumable (max_pages/call,
progress saved per page, another_run_needed until done). Omit username for
your own account; another user must be public or your friend.
untappd_sync_user_beers — start here for has-had questions. Pages the
user's COMPLETE distinct-beers list (cheap, and pages fully for anyone).untappd_sync_checkins — detailed check-ins (venue/date). Only your OWN
account pages fully; for anyone else Untappd returns just the recent ~50 and
the result reports history_truncated (it won't falsely claim
backfill_complete). force_backfill: true resets a cache wrongly marked
complete and re-pages (rows kept).Query tools (has-had ones consult BOTH sources — a hit in either = had):
untappd_cache_has_had — has the user had a beer? By exact bid or a
case-insensitive beer_name substring.untappd_cache_has_had_many — batch had/not-had for a list of bids.untappd_cache_not_had — from a list of bids, the ones NOT had.untappd_top_not_had — from a list of bids, the top N NOT-had beers by
Untappd rating, with an optional style filter ("what should I order off this
tap list?"). Uses a beer_meta cache; calls beer/info only on miss/stale,
capped at api_budget per run (partial/another_run_needed when exceeded).untappd_cache_query — filter cached check-ins by brewery, style, min_rating,
venue, and date range.Retention. The cache keeps what you sync until you remove it — including
friends' dated check-ins, comments and venues. untappd_cache_forget (confirmation-gated)
deletes one user's check-ins, distinct beers and sync state from the LOCAL cache
only; Untappd is untouched and a later sync can re-fetch. The file is created
owner-only (dir 0700, file 0600).
Every cache read returns a freshness block reporting each source's completeness
separately (plus coverage_complete and a caveat when incomplete), so you can
flag a "not found" as possibly a false negative until the relevant sync finishes.