Untappd MCP
Talks to Untappd's mobile (v4) API using your own account. Logs in via the app's
xauth endpoint (username + password → access token) and calls the same
endpoints the iPad app uses.
Setup
Set four environment variables (see the README for details):
UNTAPPD_USERNAME,UNTAPPD_PASSWORD— your Untappd login.UNTAPPD_CLIENT_ID,UNTAPPD_CLIENT_SECRET— the Untappd mobile app client credentials (obtained by intercepting the app's traffic; see README).
Run untappd_healthcheck to confirm login works.
Read tools
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 ofvenue_infofor "what's on tap" —venue_inforeturns only the first section of each menu and under-reports large boards. This pages sections under a per-callmax_pagesbudget (like the sync tools) and is resumable: it returnsanother_run_needed+next_section_offsetwhen the budget runs out, ortruncatedif the upstream stops returning sections short of coverage.untappd_user_info— a user's profile (omitusernamefor your own).untappd_user_checkins— recent check-ins (page withmax_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 withmax_id).untappd_checkin_info— full detail of one check-in.
Most user tools default username to your configured account when omitted.
Response shape (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.
- Check-ins (
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 longbeer_descriptionand the nested brewery record go.untappd_user_wishlist: the same beer core plusadded_at.untappd_user_beers: the same beer core plusyour_count,your_rating,global_rating,last_had.untappd_brewery_beers: the same beer core plusrating,rating_count,checkin_count,have_had,your_count. It has its own projector rather than sharinguntappd_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 fieldstotal_count/has_hadwhere/search/beersayscheckin_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 dropssorting_optionsand 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.
The 33 tools without view
Each 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 localuntappd_cache_forget) answer with a confirmation preview or a receipt. Nothing in a receipt is decoration. -
untappd_sync_checkins/untappd_sync_user_beersanswer 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 thefreshnessblock that says whether a "not found" might be a false negative. There is no upstream payload behind them to project away from. -
untappd_checkin_infoexists 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_menuis already its own hand-written projection, with a per-call page budget and a resumablenext_section_offsetcontract. A second projection on top of it would fight the first. -
untappd_resolve,untappd_open_url,untappd_healthcheckreturn 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_beersused to be tenth on this list and no longer is (#161) — it takes aviewnow, and its field shape is documented with the other projections above.
Write tools (confirmation-gated — these post to your public account)
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 abidfromuntappd_search_beer; optionalrating0–5 in 0.25 steps,shout, venue).
Photo attachment and wishlist add/remove are not yet supported.
Check-in cache
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 reportshistory_truncated(it won't falsely claimbackfill_complete).force_backfill: trueresets 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 exactbidor a case-insensitivebeer_namesubstring.untappd_cache_has_had_many— batch had/not-had for a list ofbids.untappd_cache_not_had— from a list ofbids, the ones NOT had.untappd_top_not_had— from a list ofbids, the top N NOT-had beers by Untappd rating, with an optionalstylefilter ("what should I order off this tap list?"). Uses abeer_metacache; callsbeer/infoonly on miss/stale, capped atapi_budgetper 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.
