untappd-mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -3
- package/dist/bundle.js +313 -43
- package/dist/compact.js +90 -0
- package/dist/index.js +4 -0
- package/dist/tools/beer.js +14 -4
- package/dist/tools/discover.js +7 -2
- package/dist/tools/feed.js +7 -2
- package/dist/tools/friends.js +72 -0
- package/dist/tools/resolve.js +92 -0
- package/dist/tools/user.js +50 -6
- package/dist/tools/venue.js +19 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Untappd — beers, breweries, venues, check-ins, wishlists, and your friend feed",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.1.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Untappd",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Untappd — search beers/breweries/venues, read profiles/check-ins/wishlists, and post check-ins, toasts, and comments",
|
|
18
|
-
"version": "1.
|
|
18
|
+
"version": "1.1.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
3
|
"displayName": "Untappd",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "MCP server for Untappd — search beers/breweries/venues, read check-ins and wishlists, and post check-ins, toasts, and comments",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/README.md
CHANGED
|
@@ -57,13 +57,14 @@ Reads: `untappd_search_beer`, `untappd_beer_info`, `untappd_beer_activity`,
|
|
|
57
57
|
`untappd_search_brewery`, `untappd_brewery_info`, `untappd_brewery_beers`,
|
|
58
58
|
`untappd_search_venue`, `untappd_venue_info`, `untappd_venue_activity`,
|
|
59
59
|
`untappd_user_info`, `untappd_user_checkins`, `untappd_user_wishlist`,
|
|
60
|
-
`untappd_user_beers`, `untappd_user_badges`, `untappd_user_friends`,
|
|
61
|
-
`untappd_activity_feed`, `untappd_checkin_info`, `untappd_trending`,
|
|
60
|
+
`untappd_user_beers`, `untappd_user_badges`, `untappd_user_friends`, `untappd_pending_friends`,
|
|
61
|
+
`untappd_activity_feed`, `untappd_checkin_info`, `untappd_resolve`, `untappd_open_url`, `untappd_user_venues`, `untappd_venue_by_foursquare`, `untappd_trending`,
|
|
62
62
|
`untappd_notifications`, `untappd_local_checkins`, `untappd_healthcheck`.
|
|
63
63
|
|
|
64
64
|
Writes (confirm-gated — return a dry-run preview unless called with
|
|
65
65
|
`confirm: true`): `untappd_toast`, `untappd_add_comment`, `untappd_delete_comment`, `untappd_checkin`,
|
|
66
|
-
`untappd_wishlist_add`, `untappd_wishlist_remove`, `untappd_delete_checkin
|
|
66
|
+
`untappd_wishlist_add`, `untappd_wishlist_remove`, `untappd_delete_checkin`,
|
|
67
|
+
`untappd_add_friend`, `untappd_accept_friend`, `untappd_reject_friend`, `untappd_remove_friend`.
|
|
67
68
|
|
|
68
69
|
## Development
|
|
69
70
|
|
package/dist/bundle.js
CHANGED
|
@@ -2220,20 +2220,20 @@ var require_resolve = __commonJS({
|
|
|
2220
2220
|
return false;
|
|
2221
2221
|
}
|
|
2222
2222
|
function countKeys(schema) {
|
|
2223
|
-
let
|
|
2223
|
+
let count2 = 0;
|
|
2224
2224
|
for (const key in schema) {
|
|
2225
2225
|
if (key === "$ref")
|
|
2226
2226
|
return Infinity;
|
|
2227
|
-
|
|
2227
|
+
count2++;
|
|
2228
2228
|
if (SIMPLE_INLINED.has(key))
|
|
2229
2229
|
continue;
|
|
2230
2230
|
if (typeof schema[key] == "object") {
|
|
2231
|
-
(0, util_1.eachItem)(schema[key], (sch) =>
|
|
2231
|
+
(0, util_1.eachItem)(schema[key], (sch) => count2 += countKeys(sch));
|
|
2232
2232
|
}
|
|
2233
|
-
if (
|
|
2233
|
+
if (count2 === Infinity)
|
|
2234
2234
|
return Infinity;
|
|
2235
2235
|
}
|
|
2236
|
-
return
|
|
2236
|
+
return count2;
|
|
2237
2237
|
}
|
|
2238
2238
|
function getFullPath(resolver, id = "", normalize) {
|
|
2239
2239
|
if (normalize !== false)
|
|
@@ -5405,8 +5405,8 @@ var require_contains = __commonJS({
|
|
|
5405
5405
|
cxt.result(valid, () => cxt.reset());
|
|
5406
5406
|
function validateItemsWithCount() {
|
|
5407
5407
|
const schValid = gen.name("_valid");
|
|
5408
|
-
const
|
|
5409
|
-
validateItems(schValid, () => gen.if(schValid, () => checkLimits(
|
|
5408
|
+
const count2 = gen.let("count", 0);
|
|
5409
|
+
validateItems(schValid, () => gen.if(schValid, () => checkLimits(count2)));
|
|
5410
5410
|
}
|
|
5411
5411
|
function validateItems(_valid, block) {
|
|
5412
5412
|
gen.forRange("i", 0, len, (i) => {
|
|
@@ -5419,16 +5419,16 @@ var require_contains = __commonJS({
|
|
|
5419
5419
|
block();
|
|
5420
5420
|
});
|
|
5421
5421
|
}
|
|
5422
|
-
function checkLimits(
|
|
5423
|
-
gen.code((0, codegen_1._)`${
|
|
5422
|
+
function checkLimits(count2) {
|
|
5423
|
+
gen.code((0, codegen_1._)`${count2}++`);
|
|
5424
5424
|
if (max === void 0) {
|
|
5425
|
-
gen.if((0, codegen_1._)`${
|
|
5425
|
+
gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true).break());
|
|
5426
5426
|
} else {
|
|
5427
|
-
gen.if((0, codegen_1._)`${
|
|
5427
|
+
gen.if((0, codegen_1._)`${count2} > ${max}`, () => gen.assign(valid, false).break());
|
|
5428
5428
|
if (min === 1)
|
|
5429
5429
|
gen.assign(valid, true);
|
|
5430
5430
|
else
|
|
5431
|
-
gen.if((0, codegen_1._)`${
|
|
5431
|
+
gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true));
|
|
5432
5432
|
}
|
|
5433
5433
|
}
|
|
5434
5434
|
}
|
|
@@ -15158,8 +15158,8 @@ function az_default() {
|
|
|
15158
15158
|
}
|
|
15159
15159
|
|
|
15160
15160
|
// node_modules/zod/v4/locales/be.js
|
|
15161
|
-
function getBelarusianPlural(
|
|
15162
|
-
const absCount = Math.abs(
|
|
15161
|
+
function getBelarusianPlural(count2, one, few, many) {
|
|
15162
|
+
const absCount = Math.abs(count2);
|
|
15163
15163
|
const lastDigit = absCount % 10;
|
|
15164
15164
|
const lastTwoDigits = absCount % 100;
|
|
15165
15165
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -17237,8 +17237,8 @@ function hu_default() {
|
|
|
17237
17237
|
}
|
|
17238
17238
|
|
|
17239
17239
|
// node_modules/zod/v4/locales/hy.js
|
|
17240
|
-
function getArmenianPlural(
|
|
17241
|
-
return Math.abs(
|
|
17240
|
+
function getArmenianPlural(count2, one, many) {
|
|
17241
|
+
return Math.abs(count2) === 1 ? one : many;
|
|
17242
17242
|
}
|
|
17243
17243
|
function withDefiniteArticle(word) {
|
|
17244
17244
|
if (!word)
|
|
@@ -19366,8 +19366,8 @@ function ro_default() {
|
|
|
19366
19366
|
}
|
|
19367
19367
|
|
|
19368
19368
|
// node_modules/zod/v4/locales/ru.js
|
|
19369
|
-
function getRussianPlural(
|
|
19370
|
-
const absCount = Math.abs(
|
|
19369
|
+
function getRussianPlural(count2, one, few, many) {
|
|
19370
|
+
const absCount = Math.abs(count2);
|
|
19371
19371
|
const lastDigit = absCount % 10;
|
|
19372
19372
|
const lastTwoDigits = absCount % 100;
|
|
19373
19373
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -31184,7 +31184,7 @@ function toolAnnotations(opts = {}) {
|
|
|
31184
31184
|
}
|
|
31185
31185
|
|
|
31186
31186
|
// src/version.ts
|
|
31187
|
-
var VERSION = "1.
|
|
31187
|
+
var VERSION = "1.1.0";
|
|
31188
31188
|
|
|
31189
31189
|
// src/client.ts
|
|
31190
31190
|
import { dirname, join } from "path";
|
|
@@ -31422,8 +31422,84 @@ var UntappdClient = class {
|
|
|
31422
31422
|
};
|
|
31423
31423
|
var client = new UntappdClient();
|
|
31424
31424
|
|
|
31425
|
+
// src/compact.ts
|
|
31426
|
+
function asDict(v) {
|
|
31427
|
+
return v && typeof v === "object" ? v : void 0;
|
|
31428
|
+
}
|
|
31429
|
+
function count(v) {
|
|
31430
|
+
const d = asDict(v);
|
|
31431
|
+
if (!d) return void 0;
|
|
31432
|
+
return d.count ?? d.total_count;
|
|
31433
|
+
}
|
|
31434
|
+
function compactCheckin(item) {
|
|
31435
|
+
const c = asDict(item) ?? {};
|
|
31436
|
+
const beer = asDict(c.beer);
|
|
31437
|
+
const brewery = asDict(c.brewery);
|
|
31438
|
+
const venue = asDict(c.venue);
|
|
31439
|
+
const user = asDict(c.user);
|
|
31440
|
+
return {
|
|
31441
|
+
checkin_id: c.checkin_id,
|
|
31442
|
+
created_at: c.created_at,
|
|
31443
|
+
user: user?.user_name,
|
|
31444
|
+
rating: c.rating_score,
|
|
31445
|
+
comment: c.checkin_comment || void 0,
|
|
31446
|
+
beer: beer && { bid: beer.bid, name: beer.beer_name, style: beer.beer_style, abv: beer.beer_abv },
|
|
31447
|
+
brewery: brewery?.brewery_name,
|
|
31448
|
+
venue: venue?.venue_name || void 0,
|
|
31449
|
+
toasts: count(c.toasts),
|
|
31450
|
+
comments: count(c.comments),
|
|
31451
|
+
has_photo: (count(c.media) ?? 0) > 0
|
|
31452
|
+
};
|
|
31453
|
+
}
|
|
31454
|
+
function beerCore(item) {
|
|
31455
|
+
const beer = asDict(item.beer) ?? {};
|
|
31456
|
+
const brewery = asDict(item.brewery);
|
|
31457
|
+
return {
|
|
31458
|
+
bid: beer.bid,
|
|
31459
|
+
name: beer.beer_name,
|
|
31460
|
+
style: beer.beer_style,
|
|
31461
|
+
abv: beer.beer_abv,
|
|
31462
|
+
ibu: beer.beer_ibu,
|
|
31463
|
+
brewery: brewery?.brewery_name
|
|
31464
|
+
};
|
|
31465
|
+
}
|
|
31466
|
+
function compactBeerResult(item) {
|
|
31467
|
+
const i = asDict(item) ?? {};
|
|
31468
|
+
return { ...beerCore(i), checkin_count: i.checkin_count, have_had: i.have_had };
|
|
31469
|
+
}
|
|
31470
|
+
function compactWishlistBeer(item) {
|
|
31471
|
+
const i = asDict(item) ?? {};
|
|
31472
|
+
return { ...beerCore(i), added_at: i.created_at };
|
|
31473
|
+
}
|
|
31474
|
+
function compactUserBeer(item) {
|
|
31475
|
+
const i = asDict(item) ?? {};
|
|
31476
|
+
return {
|
|
31477
|
+
...beerCore(i),
|
|
31478
|
+
your_count: i.count,
|
|
31479
|
+
your_rating: i.user_auth_rating_score || void 0,
|
|
31480
|
+
global_rating: i.rating_score || void 0,
|
|
31481
|
+
last_had: i.recent_created_at
|
|
31482
|
+
};
|
|
31483
|
+
}
|
|
31484
|
+
function projectItems(resp, container, mapFn) {
|
|
31485
|
+
const r = asDict(resp);
|
|
31486
|
+
const box = asDict(r?.[container]);
|
|
31487
|
+
const items = box?.items;
|
|
31488
|
+
if (!r || !box || !Array.isArray(items)) {
|
|
31489
|
+
process.stderr.write(`[untappd-mcp] compact: expected ${container}.items array; returning raw response
|
|
31490
|
+
`);
|
|
31491
|
+
return resp;
|
|
31492
|
+
}
|
|
31493
|
+
return { ...r, [container]: { ...box, items: items.map(mapFn) } };
|
|
31494
|
+
}
|
|
31495
|
+
var compactCheckins = (resp) => projectItems(resp, "checkins", compactCheckin);
|
|
31496
|
+
var compactBeerSearch = (resp) => projectItems(resp, "beers", compactBeerResult);
|
|
31497
|
+
var compactWishlist = (resp) => projectItems(resp, "beers", compactWishlistBeer);
|
|
31498
|
+
var compactUserBeers = (resp) => projectItems(resp, "beers", compactUserBeer);
|
|
31499
|
+
|
|
31425
31500
|
// src/tools/beer.ts
|
|
31426
31501
|
var BidSchema = external_exports.number().int().positive().describe("Untappd beer id (bid)");
|
|
31502
|
+
var CompactCheckins = external_exports.boolean().optional().describe("Project each check-in to a slim summary (id, user, beer, rating, comment, venue, toast/comment counts) to save context (default false)");
|
|
31427
31503
|
function registerBeerTools(server) {
|
|
31428
31504
|
server.registerTool(
|
|
31429
31505
|
"untappd_search_beer",
|
|
@@ -31435,12 +31511,13 @@ function registerBeerTools(server) {
|
|
|
31435
31511
|
query: external_exports.string().min(1).describe("Beer name to search for"),
|
|
31436
31512
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max results (1\u201350, default 25)"),
|
|
31437
31513
|
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)"),
|
|
31438
|
-
sort: external_exports.enum(["checkin", "name", "count"]).optional().describe("Sort order: checkin (relevance, default), name, or count")
|
|
31514
|
+
sort: external_exports.enum(["checkin", "name", "count"]).optional().describe("Sort order: checkin (relevance, default), name, or count"),
|
|
31515
|
+
compact: external_exports.boolean().optional().describe("Project each result to a slim summary (bid, name, brewery, style, abv, ibu, counts) to save context (default false)")
|
|
31439
31516
|
}
|
|
31440
31517
|
},
|
|
31441
|
-
async ({ query, limit, offset, sort }) => {
|
|
31518
|
+
async ({ query, limit, offset, sort, compact }) => {
|
|
31442
31519
|
const data = await client.get("/search/beer", { q: query, limit, offset, sort });
|
|
31443
|
-
return textResult(data);
|
|
31520
|
+
return textResult(compact ? compactBeerSearch(data) : data);
|
|
31444
31521
|
}
|
|
31445
31522
|
);
|
|
31446
31523
|
server.registerTool(
|
|
@@ -31468,12 +31545,13 @@ function registerBeerTools(server) {
|
|
|
31468
31545
|
inputSchema: {
|
|
31469
31546
|
bid: BidSchema,
|
|
31470
31547
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max check-ins (1\u201350, default 25)"),
|
|
31471
|
-
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)")
|
|
31548
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31549
|
+
compact: CompactCheckins
|
|
31472
31550
|
}
|
|
31473
31551
|
},
|
|
31474
|
-
async ({ bid, limit, max_id }) => {
|
|
31552
|
+
async ({ bid, limit, max_id, compact }) => {
|
|
31475
31553
|
const data = await client.get(`/beer/checkins/${bid}`, { limit, max_id });
|
|
31476
|
-
return textResult(data);
|
|
31554
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31477
31555
|
}
|
|
31478
31556
|
);
|
|
31479
31557
|
}
|
|
@@ -31567,6 +31645,21 @@ function registerVenueTools(server) {
|
|
|
31567
31645
|
return textResult(data);
|
|
31568
31646
|
}
|
|
31569
31647
|
);
|
|
31648
|
+
server.registerTool(
|
|
31649
|
+
"untappd_venue_by_foursquare",
|
|
31650
|
+
{
|
|
31651
|
+
title: "Look up an Untappd venue by Foursquare id",
|
|
31652
|
+
description: "Resolve a Foursquare venue id to its Untappd venue. Useful to turn a foursquare_id (e.g. from a check-in) into an Untappd venue you can pass to untappd_venue_info / untappd_venue_activity. Read-only.",
|
|
31653
|
+
annotations: toolAnnotations({ title: "Look up an Untappd venue by Foursquare id", readOnly: true, idempotent: true, openWorld: true }),
|
|
31654
|
+
inputSchema: {
|
|
31655
|
+
foursquare_id: external_exports.string().min(1).describe("Foursquare venue id")
|
|
31656
|
+
}
|
|
31657
|
+
},
|
|
31658
|
+
async ({ foursquare_id }) => {
|
|
31659
|
+
const data = await client.get(`/venue/foursquare_lookup/${encodeURIComponent(foursquare_id)}`);
|
|
31660
|
+
return textResult(data);
|
|
31661
|
+
}
|
|
31662
|
+
);
|
|
31570
31663
|
server.registerTool(
|
|
31571
31664
|
"untappd_venue_activity",
|
|
31572
31665
|
{
|
|
@@ -31576,12 +31669,13 @@ function registerVenueTools(server) {
|
|
|
31576
31669
|
inputSchema: {
|
|
31577
31670
|
venue_id: external_exports.number().int().positive().describe("Untappd venue id"),
|
|
31578
31671
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max check-ins (1\u201350, default 25)"),
|
|
31579
|
-
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)")
|
|
31672
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31673
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31580
31674
|
}
|
|
31581
31675
|
},
|
|
31582
|
-
async ({ venue_id, limit, max_id }) => {
|
|
31676
|
+
async ({ venue_id, limit, max_id, compact }) => {
|
|
31583
31677
|
const data = await client.get(`/venue/checkins/${venue_id}`, { limit, max_id });
|
|
31584
|
-
return textResult(data);
|
|
31678
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31585
31679
|
}
|
|
31586
31680
|
);
|
|
31587
31681
|
}
|
|
@@ -31623,12 +31717,13 @@ function registerUserTools(server) {
|
|
|
31623
31717
|
inputSchema: {
|
|
31624
31718
|
username: UsernameArg,
|
|
31625
31719
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max check-ins (1\u201350, default 25)"),
|
|
31626
|
-
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)")
|
|
31720
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31721
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31627
31722
|
}
|
|
31628
31723
|
},
|
|
31629
|
-
async ({ username, limit, max_id }) => {
|
|
31724
|
+
async ({ username, limit, max_id, compact }) => {
|
|
31630
31725
|
const data = await client.get(`/user/checkins/${resolveUser(username)}`, { limit, max_id });
|
|
31631
|
-
return textResult(data);
|
|
31726
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31632
31727
|
}
|
|
31633
31728
|
);
|
|
31634
31729
|
server.registerTool(
|
|
@@ -31641,12 +31736,13 @@ function registerUserTools(server) {
|
|
|
31641
31736
|
username: UsernameArg,
|
|
31642
31737
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max beers (1\u201350, default 25)"),
|
|
31643
31738
|
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)"),
|
|
31644
|
-
sort: external_exports.enum(["date", "name", "brewery", "style", "rating", "abv"]).optional().describe("Sort order (default date added, newest first)")
|
|
31739
|
+
sort: external_exports.enum(["date", "name", "brewery", "style", "rating", "abv"]).optional().describe("Sort order (default date added, newest first)"),
|
|
31740
|
+
compact: external_exports.boolean().optional().describe("Project each beer to a slim summary (bid, name, brewery, style, abv, added_at) to save context (default false)")
|
|
31645
31741
|
}
|
|
31646
31742
|
},
|
|
31647
|
-
async ({ username, limit, offset, sort }) => {
|
|
31743
|
+
async ({ username, limit, offset, sort, compact }) => {
|
|
31648
31744
|
const data = await client.get(`/user/wishlist/${resolveUser(username)}`, { limit, offset, sort });
|
|
31649
|
-
return textResult(data);
|
|
31745
|
+
return textResult(compact ? compactWishlist(data) : data);
|
|
31650
31746
|
}
|
|
31651
31747
|
);
|
|
31652
31748
|
server.registerTool(
|
|
@@ -31659,12 +31755,13 @@ function registerUserTools(server) {
|
|
|
31659
31755
|
username: UsernameArg,
|
|
31660
31756
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max beers (1\u201350, default 25)"),
|
|
31661
31757
|
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)"),
|
|
31662
|
-
sort: external_exports.enum(["date", "checkin", "highest_rated", "lowest_rated", "name", "this_month", "highest_abv"]).optional().describe("Sort order (default date, most recent first)")
|
|
31758
|
+
sort: external_exports.enum(["date", "checkin", "highest_rated", "lowest_rated", "name", "this_month", "highest_abv"]).optional().describe("Sort order (default date, most recent first)"),
|
|
31759
|
+
compact: external_exports.boolean().optional().describe("Project each beer to a slim summary (bid, name, brewery, style, abv, ibu, your_count, your_rating, global_rating, last_had) to save context (default false)")
|
|
31663
31760
|
}
|
|
31664
31761
|
},
|
|
31665
|
-
async ({ username, limit, offset, sort }) => {
|
|
31762
|
+
async ({ username, limit, offset, sort, compact }) => {
|
|
31666
31763
|
const data = await client.get(`/user/beers/${resolveUser(username)}`, { limit, offset, sort });
|
|
31667
|
-
return textResult(data);
|
|
31764
|
+
return textResult(compact ? compactUserBeers(data) : data);
|
|
31668
31765
|
}
|
|
31669
31766
|
);
|
|
31670
31767
|
server.registerTool(
|
|
@@ -31701,6 +31798,40 @@ function registerUserTools(server) {
|
|
|
31701
31798
|
return textResult(data);
|
|
31702
31799
|
}
|
|
31703
31800
|
);
|
|
31801
|
+
server.registerTool(
|
|
31802
|
+
"untappd_user_venues",
|
|
31803
|
+
{
|
|
31804
|
+
title: "Get venues a user has checked in at",
|
|
31805
|
+
description: "Get the venues a user has checked in at, most recent first, with per-venue check-in counts. Supports sorting and paging. Omit username for your own account. Read-only.",
|
|
31806
|
+
annotations: toolAnnotations({ title: "Get venues a user has checked in at", readOnly: true, idempotent: true, openWorld: true }),
|
|
31807
|
+
inputSchema: {
|
|
31808
|
+
username: UsernameArg,
|
|
31809
|
+
limit: external_exports.number().int().min(1).max(50).optional().describe("Max venues (1\u201350, default 25)"),
|
|
31810
|
+
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)"),
|
|
31811
|
+
sort: external_exports.enum(["date", "name", "checkin", "highest_rated"]).optional().describe("Sort order (default most recent)")
|
|
31812
|
+
}
|
|
31813
|
+
},
|
|
31814
|
+
async ({ username, limit, offset, sort }) => {
|
|
31815
|
+
const data = await client.get(`/user/venues/${resolveUser(username)}`, { limit, offset, sort });
|
|
31816
|
+
return textResult(data);
|
|
31817
|
+
}
|
|
31818
|
+
);
|
|
31819
|
+
server.registerTool(
|
|
31820
|
+
"untappd_pending_friends",
|
|
31821
|
+
{
|
|
31822
|
+
title: "Get your pending friend requests",
|
|
31823
|
+
description: "Get the incoming friend requests waiting on YOUR account \u2014 the users who have requested to be your friend. Read-only.",
|
|
31824
|
+
annotations: toolAnnotations({ title: "Get your pending friend requests", readOnly: true, idempotent: true, openWorld: true }),
|
|
31825
|
+
inputSchema: {
|
|
31826
|
+
limit: external_exports.number().int().min(1).max(50).optional().describe("Max requests (1\u201350, default 25)"),
|
|
31827
|
+
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)")
|
|
31828
|
+
}
|
|
31829
|
+
},
|
|
31830
|
+
async ({ limit, offset }) => {
|
|
31831
|
+
const data = await client.get("/user/pending", { limit, offset });
|
|
31832
|
+
return textResult(data);
|
|
31833
|
+
}
|
|
31834
|
+
);
|
|
31704
31835
|
}
|
|
31705
31836
|
|
|
31706
31837
|
// src/tools/feed.ts
|
|
@@ -31713,12 +31844,13 @@ function registerFeedTools(server) {
|
|
|
31713
31844
|
annotations: toolAnnotations({ title: "Get Untappd friend activity feed", readOnly: true, idempotent: false, openWorld: true }),
|
|
31714
31845
|
inputSchema: {
|
|
31715
31846
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max check-ins (1\u201350, default 25)"),
|
|
31716
|
-
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)")
|
|
31847
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31848
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31717
31849
|
}
|
|
31718
31850
|
},
|
|
31719
|
-
async ({ limit, max_id }) => {
|
|
31851
|
+
async ({ limit, max_id, compact }) => {
|
|
31720
31852
|
const data = await client.get("/checkin/recent", { limit, max_id });
|
|
31721
|
-
return textResult(data);
|
|
31853
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31722
31854
|
}
|
|
31723
31855
|
);
|
|
31724
31856
|
server.registerTool(
|
|
@@ -31738,6 +31870,80 @@ function registerFeedTools(server) {
|
|
|
31738
31870
|
);
|
|
31739
31871
|
}
|
|
31740
31872
|
|
|
31873
|
+
// src/tools/resolve.ts
|
|
31874
|
+
function parseUntappdUrl(input) {
|
|
31875
|
+
let url2;
|
|
31876
|
+
try {
|
|
31877
|
+
url2 = new URL(input.trim());
|
|
31878
|
+
} catch {
|
|
31879
|
+
return null;
|
|
31880
|
+
}
|
|
31881
|
+
if (!/(^|\.)untappd\.com$/i.test(url2.hostname)) return null;
|
|
31882
|
+
const segs = url2.pathname.split("/").filter(Boolean);
|
|
31883
|
+
if (segs[0] === "user" && segs[2] === "checkin" && /^\d+$/.test(segs[3] ?? "")) {
|
|
31884
|
+
return { type: "checkin", id: Number(segs[3]), username: segs[1], tool: "untappd_checkin_info" };
|
|
31885
|
+
}
|
|
31886
|
+
if (segs[0] === "user" && segs[1]) {
|
|
31887
|
+
return { type: "user", username: segs[1], tool: "untappd_user_info" };
|
|
31888
|
+
}
|
|
31889
|
+
const short = {
|
|
31890
|
+
b: { type: "beer", tool: "untappd_beer_info" },
|
|
31891
|
+
w: { type: "brewery", tool: "untappd_brewery_info" },
|
|
31892
|
+
v: { type: "venue", tool: "untappd_venue_info" }
|
|
31893
|
+
};
|
|
31894
|
+
if (segs[0] && short[segs[0]]) {
|
|
31895
|
+
const last = segs[segs.length - 1];
|
|
31896
|
+
if (/^\d+$/.test(last)) {
|
|
31897
|
+
return { ...short[segs[0]], id: Number(last) };
|
|
31898
|
+
}
|
|
31899
|
+
}
|
|
31900
|
+
return null;
|
|
31901
|
+
}
|
|
31902
|
+
function registerResolveTools(server) {
|
|
31903
|
+
server.registerTool(
|
|
31904
|
+
"untappd_resolve",
|
|
31905
|
+
{
|
|
31906
|
+
title: "Resolve an Untappd URL",
|
|
31907
|
+
description: "Parse an untappd.com URL (a beer /b/, brewery /w/, venue /v/, user /user/, or check-in link) into its entity type and id, and name the tool to call next. Pure local parsing \u2014 no network. Read-only.",
|
|
31908
|
+
annotations: toolAnnotations({ title: "Resolve an Untappd URL", readOnly: true, idempotent: true, openWorld: false }),
|
|
31909
|
+
inputSchema: {
|
|
31910
|
+
url: external_exports.string().min(1).describe("An untappd.com URL to resolve")
|
|
31911
|
+
}
|
|
31912
|
+
},
|
|
31913
|
+
async ({ url: url2 }) => {
|
|
31914
|
+
const resolved = parseUntappdUrl(url2);
|
|
31915
|
+
if (!resolved) {
|
|
31916
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url2}`, {
|
|
31917
|
+
hint: "Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>."
|
|
31918
|
+
});
|
|
31919
|
+
}
|
|
31920
|
+
return textResult(resolved);
|
|
31921
|
+
}
|
|
31922
|
+
);
|
|
31923
|
+
server.registerTool(
|
|
31924
|
+
"untappd_open_url",
|
|
31925
|
+
{
|
|
31926
|
+
title: "Open an Untappd URL (resolve + fetch)",
|
|
31927
|
+
description: "Resolve an untappd.com URL AND fetch the entity detail in one call \u2014 the convenience combination of untappd_resolve + the matching info tool. Returns { resolved, detail }. Read-only.",
|
|
31928
|
+
annotations: toolAnnotations({ title: "Open an Untappd URL", readOnly: true, idempotent: true, openWorld: true }),
|
|
31929
|
+
inputSchema: {
|
|
31930
|
+
url: external_exports.string().min(1).describe("An untappd.com URL to resolve and fetch")
|
|
31931
|
+
}
|
|
31932
|
+
},
|
|
31933
|
+
async ({ url: url2 }) => {
|
|
31934
|
+
const resolved = parseUntappdUrl(url2);
|
|
31935
|
+
if (!resolved) {
|
|
31936
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url2}`, {
|
|
31937
|
+
hint: "Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>."
|
|
31938
|
+
});
|
|
31939
|
+
}
|
|
31940
|
+
const path = resolved.type === "beer" ? `/beer/info/${resolved.id}` : resolved.type === "brewery" ? `/brewery/info/${resolved.id}` : resolved.type === "venue" ? `/venue/info/${resolved.id}` : resolved.type === "checkin" ? `/checkin/view/${resolved.id}` : `/user/info/${encodeURIComponent(resolved.username)}`;
|
|
31941
|
+
const detail = await client.get(path);
|
|
31942
|
+
return textResult({ resolved, detail });
|
|
31943
|
+
}
|
|
31944
|
+
);
|
|
31945
|
+
}
|
|
31946
|
+
|
|
31741
31947
|
// src/tools/discover.ts
|
|
31742
31948
|
function registerDiscoverTools(server) {
|
|
31743
31949
|
server.registerTool(
|
|
@@ -31779,12 +31985,13 @@ function registerDiscoverTools(server) {
|
|
|
31779
31985
|
lat: external_exports.number().min(-90).max(90).describe("Latitude of the location"),
|
|
31780
31986
|
lng: external_exports.number().min(-180).max(180).describe("Longitude of the location"),
|
|
31781
31987
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max check-ins (1\u201350, default 25)"),
|
|
31782
|
-
radius: external_exports.number().int().min(1).max(25).optional().describe("Search radius (default per Untappd)")
|
|
31988
|
+
radius: external_exports.number().int().min(1).max(25).optional().describe("Search radius (default per Untappd)"),
|
|
31989
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31783
31990
|
}
|
|
31784
31991
|
},
|
|
31785
|
-
async ({ lat, lng, limit, radius }) => {
|
|
31992
|
+
async ({ lat, lng, limit, radius, compact }) => {
|
|
31786
31993
|
const data = await client.get("/thepub/local", { lat, lng, limit, radius });
|
|
31787
|
-
return textResult(data);
|
|
31994
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31788
31995
|
}
|
|
31789
31996
|
);
|
|
31790
31997
|
}
|
|
@@ -31842,6 +32049,67 @@ function registerWishlistTools(server) {
|
|
|
31842
32049
|
);
|
|
31843
32050
|
}
|
|
31844
32051
|
|
|
32052
|
+
// src/tools/friends.ts
|
|
32053
|
+
var TargetUidSchema = external_exports.number().int().positive().describe("The target user id (uid) \u2014 from a user profile (response.user.uid) or a friends-list item");
|
|
32054
|
+
var ACTIONS = [
|
|
32055
|
+
{
|
|
32056
|
+
tool: "untappd_add_friend",
|
|
32057
|
+
path: "request",
|
|
32058
|
+
title: "Send an Untappd friend request",
|
|
32059
|
+
verb: "send a friend request to",
|
|
32060
|
+
detail: "Sends a friend request to another user, who will be notified."
|
|
32061
|
+
},
|
|
32062
|
+
{
|
|
32063
|
+
tool: "untappd_accept_friend",
|
|
32064
|
+
path: "accept",
|
|
32065
|
+
title: "Accept an Untappd friend request",
|
|
32066
|
+
verb: "accept the incoming friend request from",
|
|
32067
|
+
detail: "Accepts an incoming friend request (see untappd_pending_friends for pending uids)."
|
|
32068
|
+
},
|
|
32069
|
+
{
|
|
32070
|
+
tool: "untappd_reject_friend",
|
|
32071
|
+
path: "reject",
|
|
32072
|
+
title: "Reject an Untappd friend request",
|
|
32073
|
+
verb: "reject the incoming friend request from",
|
|
32074
|
+
detail: "Rejects/ignores an incoming friend request (see untappd_pending_friends for pending uids)."
|
|
32075
|
+
},
|
|
32076
|
+
{
|
|
32077
|
+
tool: "untappd_remove_friend",
|
|
32078
|
+
path: "remove",
|
|
32079
|
+
title: "Remove an Untappd friend",
|
|
32080
|
+
verb: "remove your friendship with",
|
|
32081
|
+
detail: "Removes an existing friend, or cancels a friend request you sent."
|
|
32082
|
+
}
|
|
32083
|
+
];
|
|
32084
|
+
function registerFriendActionTools(server) {
|
|
32085
|
+
for (const action of ACTIONS) {
|
|
32086
|
+
server.registerTool(
|
|
32087
|
+
action.tool,
|
|
32088
|
+
{
|
|
32089
|
+
title: action.title,
|
|
32090
|
+
description: `${action.detail} Acts on YOUR account and affects a real relationship with another person. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it performs the action. Note: this endpoint path is taken from the Untappd app but is not otherwise independently verified.`,
|
|
32091
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true }),
|
|
32092
|
+
inputSchema: {
|
|
32093
|
+
target_uid: TargetUidSchema,
|
|
32094
|
+
confirm: schemaConfirm
|
|
32095
|
+
}
|
|
32096
|
+
},
|
|
32097
|
+
async ({ target_uid, confirm }) => {
|
|
32098
|
+
if (confirm !== true) {
|
|
32099
|
+
return textResult({
|
|
32100
|
+
dryRun: true,
|
|
32101
|
+
action: action.path,
|
|
32102
|
+
target_uid,
|
|
32103
|
+
note: `Dry run \u2014 re-run with confirm: true to ${action.verb} user ${target_uid}.`
|
|
32104
|
+
});
|
|
32105
|
+
}
|
|
32106
|
+
const data = await client.write("POST", `/friend/${action.path}/${target_uid}`);
|
|
32107
|
+
return textResult({ done: true, action: action.path, target_uid, result: data?.result });
|
|
32108
|
+
}
|
|
32109
|
+
);
|
|
32110
|
+
}
|
|
32111
|
+
}
|
|
32112
|
+
|
|
31845
32113
|
// src/tools/checkin.ts
|
|
31846
32114
|
import { existsSync } from "node:fs";
|
|
31847
32115
|
import { extname } from "node:path";
|
|
@@ -32083,8 +32351,10 @@ await runMcp({
|
|
|
32083
32351
|
registerVenueTools,
|
|
32084
32352
|
registerUserTools,
|
|
32085
32353
|
registerFeedTools,
|
|
32354
|
+
registerResolveTools,
|
|
32086
32355
|
registerDiscoverTools,
|
|
32087
32356
|
registerWishlistTools,
|
|
32357
|
+
registerFriendActionTools,
|
|
32088
32358
|
registerCheckinTools,
|
|
32089
32359
|
registerUtilityTools
|
|
32090
32360
|
]
|
package/dist/compact.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Opt-in slim projections for the fat reverse-engineered list responses. A
|
|
2
|
+
// single check-in record is ~5 KB and a beer search item ~1.2 KB, so a page of
|
|
3
|
+
// 25 is a lot of agent context. These project each item down to the fields an
|
|
4
|
+
// agent usually needs, keyed off DOCUMENTED fields only, and derive nothing it
|
|
5
|
+
// can't. Every projector degrades safely: if the expected `<container>.items`
|
|
6
|
+
// array isn't present (the undocumented API drifted), it warns to stderr and
|
|
7
|
+
// returns the RAW response rather than an empty/wrong projection.
|
|
8
|
+
function asDict(v) {
|
|
9
|
+
return v && typeof v === 'object' ? v : undefined;
|
|
10
|
+
}
|
|
11
|
+
function count(v) {
|
|
12
|
+
const d = asDict(v);
|
|
13
|
+
if (!d)
|
|
14
|
+
return undefined;
|
|
15
|
+
return (d.count ?? d.total_count);
|
|
16
|
+
}
|
|
17
|
+
/** Slim a single check-in record. */
|
|
18
|
+
export function compactCheckin(item) {
|
|
19
|
+
const c = asDict(item) ?? {};
|
|
20
|
+
const beer = asDict(c.beer);
|
|
21
|
+
const brewery = asDict(c.brewery);
|
|
22
|
+
const venue = asDict(c.venue);
|
|
23
|
+
const user = asDict(c.user);
|
|
24
|
+
return {
|
|
25
|
+
checkin_id: c.checkin_id,
|
|
26
|
+
created_at: c.created_at,
|
|
27
|
+
user: user?.user_name,
|
|
28
|
+
rating: c.rating_score,
|
|
29
|
+
comment: c.checkin_comment || undefined,
|
|
30
|
+
beer: beer && { bid: beer.bid, name: beer.beer_name, style: beer.beer_style, abv: beer.beer_abv },
|
|
31
|
+
brewery: brewery?.brewery_name,
|
|
32
|
+
venue: venue?.venue_name || undefined,
|
|
33
|
+
toasts: count(c.toasts),
|
|
34
|
+
comments: count(c.comments),
|
|
35
|
+
has_photo: (count(c.media) ?? 0) > 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** The beer fields common to every beer-list item (search, wishlist, distinct). */
|
|
39
|
+
function beerCore(item) {
|
|
40
|
+
const beer = asDict(item.beer) ?? {};
|
|
41
|
+
const brewery = asDict(item.brewery);
|
|
42
|
+
return {
|
|
43
|
+
bid: beer.bid,
|
|
44
|
+
name: beer.beer_name,
|
|
45
|
+
style: beer.beer_style,
|
|
46
|
+
abv: beer.beer_abv,
|
|
47
|
+
ibu: beer.beer_ibu,
|
|
48
|
+
brewery: brewery?.brewery_name,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Slim a single beer-search result item. */
|
|
52
|
+
export function compactBeerResult(item) {
|
|
53
|
+
const i = asDict(item) ?? {};
|
|
54
|
+
return { ...beerCore(i), checkin_count: i.checkin_count, have_had: i.have_had };
|
|
55
|
+
}
|
|
56
|
+
/** Slim a single wishlist item (adds when it was wishlisted). */
|
|
57
|
+
export function compactWishlistBeer(item) {
|
|
58
|
+
const i = asDict(item) ?? {};
|
|
59
|
+
return { ...beerCore(i), added_at: i.created_at };
|
|
60
|
+
}
|
|
61
|
+
/** Slim a single distinct-beers (user/beers) item — your count, ratings, last had. */
|
|
62
|
+
export function compactUserBeer(item) {
|
|
63
|
+
const i = asDict(item) ?? {};
|
|
64
|
+
return {
|
|
65
|
+
...beerCore(i),
|
|
66
|
+
your_count: i.count,
|
|
67
|
+
your_rating: i.user_auth_rating_score || undefined,
|
|
68
|
+
global_rating: i.rating_score || undefined,
|
|
69
|
+
last_had: i.recent_created_at,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Project `<container>.items` in a response with `mapFn`, preserving the rest of
|
|
74
|
+
* the container (pagination etc.). Drift-safe: returns the raw response
|
|
75
|
+
* unchanged (with a stderr warning) when the array isn't where expected.
|
|
76
|
+
*/
|
|
77
|
+
export function projectItems(resp, container, mapFn) {
|
|
78
|
+
const r = asDict(resp);
|
|
79
|
+
const box = asDict(r?.[container]);
|
|
80
|
+
const items = box?.items;
|
|
81
|
+
if (!r || !box || !Array.isArray(items)) {
|
|
82
|
+
process.stderr.write(`[untappd-mcp] compact: expected ${container}.items array; returning raw response\n`);
|
|
83
|
+
return resp;
|
|
84
|
+
}
|
|
85
|
+
return { ...r, [container]: { ...box, items: items.map(mapFn) } };
|
|
86
|
+
}
|
|
87
|
+
export const compactCheckins = (resp) => projectItems(resp, 'checkins', compactCheckin);
|
|
88
|
+
export const compactBeerSearch = (resp) => projectItems(resp, 'beers', compactBeerResult);
|
|
89
|
+
export const compactWishlist = (resp) => projectItems(resp, 'beers', compactWishlistBeer);
|
|
90
|
+
export const compactUserBeers = (resp) => projectItems(resp, 'beers', compactUserBeer);
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,10 @@ import { registerBreweryTools } from './tools/brewery.js';
|
|
|
6
6
|
import { registerVenueTools } from './tools/venue.js';
|
|
7
7
|
import { registerUserTools } from './tools/user.js';
|
|
8
8
|
import { registerFeedTools } from './tools/feed.js';
|
|
9
|
+
import { registerResolveTools } from './tools/resolve.js';
|
|
9
10
|
import { registerDiscoverTools } from './tools/discover.js';
|
|
10
11
|
import { registerWishlistTools } from './tools/wishlist.js';
|
|
12
|
+
import { registerFriendActionTools } from './tools/friends.js';
|
|
11
13
|
import { registerCheckinTools } from './tools/checkin.js';
|
|
12
14
|
import { registerUtilityTools } from './tools/utilities.js';
|
|
13
15
|
// The Untappd client is a module-level singleton (imported by each tool module)
|
|
@@ -25,8 +27,10 @@ await runMcp({
|
|
|
25
27
|
registerVenueTools,
|
|
26
28
|
registerUserTools,
|
|
27
29
|
registerFeedTools,
|
|
30
|
+
registerResolveTools,
|
|
28
31
|
registerDiscoverTools,
|
|
29
32
|
registerWishlistTools,
|
|
33
|
+
registerFriendActionTools,
|
|
30
34
|
registerCheckinTools,
|
|
31
35
|
registerUtilityTools,
|
|
32
36
|
],
|
package/dist/tools/beer.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { compactBeerSearch, compactCheckins } from '../compact.js';
|
|
4
5
|
const BidSchema = z.number().int().positive().describe('Untappd beer id (bid)');
|
|
6
|
+
const CompactCheckins = z
|
|
7
|
+
.boolean()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe('Project each check-in to a slim summary (id, user, beer, rating, comment, venue, toast/comment counts) to save context (default false)');
|
|
5
10
|
export function registerBeerTools(server) {
|
|
6
11
|
server.registerTool('untappd_search_beer', {
|
|
7
12
|
title: 'Search Untappd beers',
|
|
@@ -17,10 +22,14 @@ export function registerBeerTools(server) {
|
|
|
17
22
|
.enum(['checkin', 'name', 'count'])
|
|
18
23
|
.optional()
|
|
19
24
|
.describe('Sort order: checkin (relevance, default), name, or count'),
|
|
25
|
+
compact: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Project each result to a slim summary (bid, name, brewery, style, abv, ibu, counts) to save context (default false)'),
|
|
20
29
|
},
|
|
21
|
-
}, async ({ query, limit, offset, sort }) => {
|
|
30
|
+
}, async ({ query, limit, offset, sort, compact }) => {
|
|
22
31
|
const data = await client.get('/search/beer', { q: query, limit, offset, sort });
|
|
23
|
-
return textResult(data);
|
|
32
|
+
return textResult(compact ? compactBeerSearch(data) : data);
|
|
24
33
|
});
|
|
25
34
|
server.registerTool('untappd_beer_info', {
|
|
26
35
|
title: 'Get Untappd beer detail',
|
|
@@ -47,9 +56,10 @@ export function registerBeerTools(server) {
|
|
|
47
56
|
bid: BidSchema,
|
|
48
57
|
limit: z.number().int().min(1).max(50).optional().describe('Max check-ins (1–50, default 25)'),
|
|
49
58
|
max_id: z.number().int().positive().optional().describe('Return check-ins older than this id (for paging)'),
|
|
59
|
+
compact: CompactCheckins,
|
|
50
60
|
},
|
|
51
|
-
}, async ({ bid, limit, max_id }) => {
|
|
61
|
+
}, async ({ bid, limit, max_id, compact }) => {
|
|
52
62
|
const data = await client.get(`/beer/checkins/${bid}`, { limit, max_id });
|
|
53
|
-
return textResult(data);
|
|
63
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
54
64
|
});
|
|
55
65
|
}
|
package/dist/tools/discover.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { compactCheckins } from '../compact.js';
|
|
4
5
|
export function registerDiscoverTools(server) {
|
|
5
6
|
server.registerTool('untappd_trending', {
|
|
6
7
|
title: 'Get trending beers',
|
|
@@ -35,9 +36,13 @@ export function registerDiscoverTools(server) {
|
|
|
35
36
|
lng: z.number().min(-180).max(180).describe('Longitude of the location'),
|
|
36
37
|
limit: z.number().int().min(1).max(50).optional().describe('Max check-ins (1–50, default 25)'),
|
|
37
38
|
radius: z.number().int().min(1).max(25).optional().describe('Search radius (default per Untappd)'),
|
|
39
|
+
compact: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Project each check-in to a slim summary to save context (default false)'),
|
|
38
43
|
},
|
|
39
|
-
}, async ({ lat, lng, limit, radius }) => {
|
|
44
|
+
}, async ({ lat, lng, limit, radius, compact }) => {
|
|
40
45
|
const data = await client.get('/thepub/local', { lat, lng, limit, radius });
|
|
41
|
-
return textResult(data);
|
|
46
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
42
47
|
});
|
|
43
48
|
}
|
package/dist/tools/feed.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { compactCheckins } from '../compact.js';
|
|
4
5
|
export function registerFeedTools(server) {
|
|
5
6
|
server.registerTool('untappd_activity_feed', {
|
|
6
7
|
title: 'Get Untappd friend activity feed',
|
|
@@ -10,10 +11,14 @@ export function registerFeedTools(server) {
|
|
|
10
11
|
inputSchema: {
|
|
11
12
|
limit: z.number().int().min(1).max(50).optional().describe('Max check-ins (1–50, default 25)'),
|
|
12
13
|
max_id: z.number().int().positive().optional().describe('Return check-ins older than this id (for paging)'),
|
|
14
|
+
compact: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Project each check-in to a slim summary to save context (default false)'),
|
|
13
18
|
},
|
|
14
|
-
}, async ({ limit, max_id }) => {
|
|
19
|
+
}, async ({ limit, max_id, compact }) => {
|
|
15
20
|
const data = await client.get('/checkin/recent', { limit, max_id });
|
|
16
|
-
return textResult(data);
|
|
21
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
17
22
|
});
|
|
18
23
|
server.registerTool('untappd_checkin_info', {
|
|
19
24
|
title: 'Get Untappd check-in detail',
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations, schemaConfirm } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
// The endpoint PATHS below are confirmed from the Untappd app's own JS bundle
|
|
5
|
+
// (`friend/request`, `friend/accept`, `friend/reject`, `friend/remove`). The
|
|
6
|
+
// HTTP method (POST) mirrors the other captured action-writes (toast/comment),
|
|
7
|
+
// but — unlike the rest of this server's writes — these were NOT live-verified
|
|
8
|
+
// against the API, because doing so would send real friend requests to / alter
|
|
9
|
+
// real relationships with other people. They are confirm-gated so nothing fires
|
|
10
|
+
// without an explicit confirm: true.
|
|
11
|
+
const TargetUidSchema = z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.positive()
|
|
15
|
+
.describe('The target user id (uid) — from a user profile (response.user.uid) or a friends-list item');
|
|
16
|
+
const ACTIONS = [
|
|
17
|
+
{
|
|
18
|
+
tool: 'untappd_add_friend',
|
|
19
|
+
path: 'request',
|
|
20
|
+
title: 'Send an Untappd friend request',
|
|
21
|
+
verb: 'send a friend request to',
|
|
22
|
+
detail: 'Sends a friend request to another user, who will be notified.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
tool: 'untappd_accept_friend',
|
|
26
|
+
path: 'accept',
|
|
27
|
+
title: 'Accept an Untappd friend request',
|
|
28
|
+
verb: 'accept the incoming friend request from',
|
|
29
|
+
detail: 'Accepts an incoming friend request (see untappd_pending_friends for pending uids).',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
tool: 'untappd_reject_friend',
|
|
33
|
+
path: 'reject',
|
|
34
|
+
title: 'Reject an Untappd friend request',
|
|
35
|
+
verb: 'reject the incoming friend request from',
|
|
36
|
+
detail: 'Rejects/ignores an incoming friend request (see untappd_pending_friends for pending uids).',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
tool: 'untappd_remove_friend',
|
|
40
|
+
path: 'remove',
|
|
41
|
+
title: 'Remove an Untappd friend',
|
|
42
|
+
verb: 'remove your friendship with',
|
|
43
|
+
detail: 'Removes an existing friend, or cancels a friend request you sent.',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
export function registerFriendActionTools(server) {
|
|
47
|
+
for (const action of ACTIONS) {
|
|
48
|
+
server.registerTool(action.tool, {
|
|
49
|
+
title: action.title,
|
|
50
|
+
description: `${action.detail} Acts on YOUR account and affects a real relationship with another person. ` +
|
|
51
|
+
'Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it ' +
|
|
52
|
+
'performs the action. Note: this endpoint path is taken from the Untappd app but is not otherwise ' +
|
|
53
|
+
'independently verified.',
|
|
54
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true }),
|
|
55
|
+
inputSchema: {
|
|
56
|
+
target_uid: TargetUidSchema,
|
|
57
|
+
confirm: schemaConfirm,
|
|
58
|
+
},
|
|
59
|
+
}, async ({ target_uid, confirm }) => {
|
|
60
|
+
if (confirm !== true) {
|
|
61
|
+
return textResult({
|
|
62
|
+
dryRun: true,
|
|
63
|
+
action: action.path,
|
|
64
|
+
target_uid,
|
|
65
|
+
note: `Dry run — re-run with confirm: true to ${action.verb} user ${target_uid}.`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const data = await client.write('POST', `/friend/${action.path}/${target_uid}`);
|
|
69
|
+
return textResult({ done: true, action: action.path, target_uid, result: data?.result });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations, createHelpfulError } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
/**
|
|
5
|
+
* Parse an untappd.com URL into its entity type and id. Path shapes (confirmed
|
|
6
|
+
* from the app + site):
|
|
7
|
+
* /b/<slug>/<bid> → beer
|
|
8
|
+
* /w/<slug>/<brewery_id> → brewery
|
|
9
|
+
* /v/<slug>/<venue_id> → venue
|
|
10
|
+
* /user/<username> → user
|
|
11
|
+
* /user/<username>/checkin/<id>→ checkin
|
|
12
|
+
* Returns null if the URL isn't a recognised Untappd entity URL.
|
|
13
|
+
*/
|
|
14
|
+
export function parseUntappdUrl(input) {
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(input.trim());
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!/(^|\.)untappd\.com$/i.test(url.hostname))
|
|
23
|
+
return null;
|
|
24
|
+
const segs = url.pathname.split('/').filter(Boolean);
|
|
25
|
+
// /user/<username>/checkin/<id>
|
|
26
|
+
if (segs[0] === 'user' && segs[2] === 'checkin' && /^\d+$/.test(segs[3] ?? '')) {
|
|
27
|
+
return { type: 'checkin', id: Number(segs[3]), username: segs[1], tool: 'untappd_checkin_info' };
|
|
28
|
+
}
|
|
29
|
+
// /user/<username>
|
|
30
|
+
if (segs[0] === 'user' && segs[1]) {
|
|
31
|
+
return { type: 'user', username: segs[1], tool: 'untappd_user_info' };
|
|
32
|
+
}
|
|
33
|
+
const short = {
|
|
34
|
+
b: { type: 'beer', tool: 'untappd_beer_info' },
|
|
35
|
+
w: { type: 'brewery', tool: 'untappd_brewery_info' },
|
|
36
|
+
v: { type: 'venue', tool: 'untappd_venue_info' },
|
|
37
|
+
};
|
|
38
|
+
// /b|w|v/<slug>/<id> (id is the last numeric segment)
|
|
39
|
+
if (segs[0] && short[segs[0]]) {
|
|
40
|
+
const last = segs[segs.length - 1];
|
|
41
|
+
if (/^\d+$/.test(last)) {
|
|
42
|
+
return { ...short[segs[0]], id: Number(last) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
export function registerResolveTools(server) {
|
|
48
|
+
server.registerTool('untappd_resolve', {
|
|
49
|
+
title: 'Resolve an Untappd URL',
|
|
50
|
+
description: 'Parse an untappd.com URL (a beer /b/, brewery /w/, venue /v/, user /user/, or check-in link) into its ' +
|
|
51
|
+
'entity type and id, and name the tool to call next. Pure local parsing — no network. Read-only.',
|
|
52
|
+
annotations: toolAnnotations({ title: 'Resolve an Untappd URL', readOnly: true, idempotent: true, openWorld: false }),
|
|
53
|
+
inputSchema: {
|
|
54
|
+
url: z.string().min(1).describe('An untappd.com URL to resolve'),
|
|
55
|
+
},
|
|
56
|
+
}, async ({ url }) => {
|
|
57
|
+
const resolved = parseUntappdUrl(url);
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url}`, {
|
|
60
|
+
hint: 'Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>.',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return textResult(resolved);
|
|
64
|
+
});
|
|
65
|
+
server.registerTool('untappd_open_url', {
|
|
66
|
+
title: 'Open an Untappd URL (resolve + fetch)',
|
|
67
|
+
description: 'Resolve an untappd.com URL AND fetch the entity detail in one call — the convenience combination of ' +
|
|
68
|
+
'untappd_resolve + the matching info tool. Returns { resolved, detail }. Read-only.',
|
|
69
|
+
annotations: toolAnnotations({ title: 'Open an Untappd URL', readOnly: true, idempotent: true, openWorld: true }),
|
|
70
|
+
inputSchema: {
|
|
71
|
+
url: z.string().min(1).describe('An untappd.com URL to resolve and fetch'),
|
|
72
|
+
},
|
|
73
|
+
}, async ({ url }) => {
|
|
74
|
+
const resolved = parseUntappdUrl(url);
|
|
75
|
+
if (!resolved) {
|
|
76
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url}`, {
|
|
77
|
+
hint: 'Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>.',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const path = resolved.type === 'beer'
|
|
81
|
+
? `/beer/info/${resolved.id}`
|
|
82
|
+
: resolved.type === 'brewery'
|
|
83
|
+
? `/brewery/info/${resolved.id}`
|
|
84
|
+
: resolved.type === 'venue'
|
|
85
|
+
? `/venue/info/${resolved.id}`
|
|
86
|
+
: resolved.type === 'checkin'
|
|
87
|
+
? `/checkin/view/${resolved.id}`
|
|
88
|
+
: `/user/info/${encodeURIComponent(resolved.username)}`;
|
|
89
|
+
const detail = await client.get(path);
|
|
90
|
+
return textResult({ resolved, detail });
|
|
91
|
+
});
|
|
92
|
+
}
|
package/dist/tools/user.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult, toolAnnotations, createHelpfulError } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { compactCheckins, compactWishlist, compactUserBeers } from '../compact.js';
|
|
4
5
|
const UsernameArg = z
|
|
5
6
|
.string()
|
|
6
7
|
.min(1)
|
|
@@ -39,10 +40,14 @@ export function registerUserTools(server) {
|
|
|
39
40
|
username: UsernameArg,
|
|
40
41
|
limit: z.number().int().min(1).max(50).optional().describe('Max check-ins (1–50, default 25)'),
|
|
41
42
|
max_id: z.number().int().positive().optional().describe('Return check-ins older than this id (for paging)'),
|
|
43
|
+
compact: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Project each check-in to a slim summary to save context (default false)'),
|
|
42
47
|
},
|
|
43
|
-
}, async ({ username, limit, max_id }) => {
|
|
48
|
+
}, async ({ username, limit, max_id, compact }) => {
|
|
44
49
|
const data = await client.get(`/user/checkins/${resolveUser(username)}`, { limit, max_id });
|
|
45
|
-
return textResult(data);
|
|
50
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
46
51
|
});
|
|
47
52
|
server.registerTool('untappd_user_wishlist', {
|
|
48
53
|
title: 'Get Untappd user wishlist',
|
|
@@ -56,10 +61,14 @@ export function registerUserTools(server) {
|
|
|
56
61
|
.enum(['date', 'name', 'brewery', 'style', 'rating', 'abv'])
|
|
57
62
|
.optional()
|
|
58
63
|
.describe('Sort order (default date added, newest first)'),
|
|
64
|
+
compact: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Project each beer to a slim summary (bid, name, brewery, style, abv, added_at) to save context (default false)'),
|
|
59
68
|
},
|
|
60
|
-
}, async ({ username, limit, offset, sort }) => {
|
|
69
|
+
}, async ({ username, limit, offset, sort, compact }) => {
|
|
61
70
|
const data = await client.get(`/user/wishlist/${resolveUser(username)}`, { limit, offset, sort });
|
|
62
|
-
return textResult(data);
|
|
71
|
+
return textResult(compact ? compactWishlist(data) : data);
|
|
63
72
|
});
|
|
64
73
|
server.registerTool('untappd_user_beers', {
|
|
65
74
|
title: 'Get Untappd distinct beers',
|
|
@@ -74,10 +83,14 @@ export function registerUserTools(server) {
|
|
|
74
83
|
.enum(['date', 'checkin', 'highest_rated', 'lowest_rated', 'name', 'this_month', 'highest_abv'])
|
|
75
84
|
.optional()
|
|
76
85
|
.describe('Sort order (default date, most recent first)'),
|
|
86
|
+
compact: z
|
|
87
|
+
.boolean()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Project each beer to a slim summary (bid, name, brewery, style, abv, ibu, your_count, your_rating, global_rating, last_had) to save context (default false)'),
|
|
77
90
|
},
|
|
78
|
-
}, async ({ username, limit, offset, sort }) => {
|
|
91
|
+
}, async ({ username, limit, offset, sort, compact }) => {
|
|
79
92
|
const data = await client.get(`/user/beers/${resolveUser(username)}`, { limit, offset, sort });
|
|
80
|
-
return textResult(data);
|
|
93
|
+
return textResult(compact ? compactUserBeers(data) : data);
|
|
81
94
|
});
|
|
82
95
|
server.registerTool('untappd_user_badges', {
|
|
83
96
|
title: 'Get Untappd user badges',
|
|
@@ -105,4 +118,35 @@ export function registerUserTools(server) {
|
|
|
105
118
|
const data = await client.get(`/user/friends/${resolveUser(username)}`, { limit, offset });
|
|
106
119
|
return textResult(data);
|
|
107
120
|
});
|
|
121
|
+
server.registerTool('untappd_user_venues', {
|
|
122
|
+
title: 'Get venues a user has checked in at',
|
|
123
|
+
description: "Get the venues a user has checked in at, most recent first, with per-venue check-in counts. Supports " +
|
|
124
|
+
'sorting and paging. Omit username for your own account. Read-only.',
|
|
125
|
+
annotations: toolAnnotations({ title: 'Get venues a user has checked in at', readOnly: true, idempotent: true, openWorld: true }),
|
|
126
|
+
inputSchema: {
|
|
127
|
+
username: UsernameArg,
|
|
128
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max venues (1–50, default 25)'),
|
|
129
|
+
offset: z.number().int().min(0).optional().describe('Result offset for paging (default 0)'),
|
|
130
|
+
sort: z
|
|
131
|
+
.enum(['date', 'name', 'checkin', 'highest_rated'])
|
|
132
|
+
.optional()
|
|
133
|
+
.describe('Sort order (default most recent)'),
|
|
134
|
+
},
|
|
135
|
+
}, async ({ username, limit, offset, sort }) => {
|
|
136
|
+
const data = await client.get(`/user/venues/${resolveUser(username)}`, { limit, offset, sort });
|
|
137
|
+
return textResult(data);
|
|
138
|
+
});
|
|
139
|
+
server.registerTool('untappd_pending_friends', {
|
|
140
|
+
title: 'Get your pending friend requests',
|
|
141
|
+
description: 'Get the incoming friend requests waiting on YOUR account — the users who have requested to be your ' +
|
|
142
|
+
'friend. Read-only.',
|
|
143
|
+
annotations: toolAnnotations({ title: 'Get your pending friend requests', readOnly: true, idempotent: true, openWorld: true }),
|
|
144
|
+
inputSchema: {
|
|
145
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max requests (1–50, default 25)'),
|
|
146
|
+
offset: z.number().int().min(0).optional().describe('Result offset for paging (default 0)'),
|
|
147
|
+
},
|
|
148
|
+
}, async ({ limit, offset }) => {
|
|
149
|
+
const data = await client.get('/user/pending', { limit, offset });
|
|
150
|
+
return textResult(data);
|
|
151
|
+
});
|
|
108
152
|
}
|
package/dist/tools/venue.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { compactCheckins } from '../compact.js';
|
|
4
5
|
export function registerVenueTools(server) {
|
|
5
6
|
server.registerTool('untappd_search_venue', {
|
|
6
7
|
title: 'Search Untappd venues',
|
|
@@ -28,6 +29,18 @@ export function registerVenueTools(server) {
|
|
|
28
29
|
const data = await client.get(`/venue/info/${venue_id}`, { compact: compact ? 'true' : undefined });
|
|
29
30
|
return textResult(data);
|
|
30
31
|
});
|
|
32
|
+
server.registerTool('untappd_venue_by_foursquare', {
|
|
33
|
+
title: 'Look up an Untappd venue by Foursquare id',
|
|
34
|
+
description: 'Resolve a Foursquare venue id to its Untappd venue. Useful to turn a foursquare_id (e.g. from a check-in) ' +
|
|
35
|
+
'into an Untappd venue you can pass to untappd_venue_info / untappd_venue_activity. Read-only.',
|
|
36
|
+
annotations: toolAnnotations({ title: 'Look up an Untappd venue by Foursquare id', readOnly: true, idempotent: true, openWorld: true }),
|
|
37
|
+
inputSchema: {
|
|
38
|
+
foursquare_id: z.string().min(1).describe('Foursquare venue id'),
|
|
39
|
+
},
|
|
40
|
+
}, async ({ foursquare_id }) => {
|
|
41
|
+
const data = await client.get(`/venue/foursquare_lookup/${encodeURIComponent(foursquare_id)}`);
|
|
42
|
+
return textResult(data);
|
|
43
|
+
});
|
|
31
44
|
server.registerTool('untappd_venue_activity', {
|
|
32
45
|
title: 'Get recent check-ins at a venue',
|
|
33
46
|
description: 'Get the recent public check-ins at a venue by its id — who was there, what they drank, and their ratings. ' +
|
|
@@ -37,9 +50,13 @@ export function registerVenueTools(server) {
|
|
|
37
50
|
venue_id: z.number().int().positive().describe('Untappd venue id'),
|
|
38
51
|
limit: z.number().int().min(1).max(50).optional().describe('Max check-ins (1–50, default 25)'),
|
|
39
52
|
max_id: z.number().int().positive().optional().describe('Return check-ins older than this id (for paging)'),
|
|
53
|
+
compact: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Project each check-in to a slim summary to save context (default false)'),
|
|
40
57
|
},
|
|
41
|
-
}, async ({ venue_id, limit, max_id }) => {
|
|
58
|
+
}, async ({ venue_id, limit, max_id, compact }) => {
|
|
42
59
|
const data = await client.get(`/venue/checkins/${venue_id}`, { limit, max_id });
|
|
43
|
-
return textResult(data);
|
|
60
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
44
61
|
});
|
|
45
62
|
}
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// json's `extra-files`), and `versionSyncTest` guards that it stays equal to
|
|
4
4
|
// package.json. Import VERSION wherever the version is needed rather than
|
|
5
5
|
// re-declaring it.
|
|
6
|
-
export const VERSION = '1.
|
|
6
|
+
export const VERSION = '1.1.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/untappd-mcp",
|
|
5
5
|
"description": "Untappd MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/untappd-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.1.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "untappd-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.1.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|