untappd-mcp 1.0.0 → 1.2.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 +19 -3
- package/dist/bundle.js +469 -175
- package/dist/client.js +126 -96
- package/dist/compact.js +90 -0
- package/dist/index.js +18 -14
- package/dist/tools/beer.js +15 -6
- package/dist/tools/brewery.js +1 -2
- package/dist/tools/checkin.js +1 -2
- package/dist/tools/discover.js +8 -4
- package/dist/tools/feed.js +8 -4
- package/dist/tools/friends.js +71 -0
- package/dist/tools/resolve.js +91 -0
- package/dist/tools/user.js +59 -16
- package/dist/tools/utilities.js +1 -2
- package/dist/tools/venue.js +20 -4
- package/dist/tools/wishlist.js +1 -2
- package/dist/version.js +1 -1
- package/package.json +11 -3
- package/server.json +2 -2
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,13 +31184,15 @@ function toolAnnotations(opts = {}) {
|
|
|
31184
31184
|
}
|
|
31185
31185
|
|
|
31186
31186
|
// src/version.ts
|
|
31187
|
-
var VERSION = "1.
|
|
31187
|
+
var VERSION = "1.2.0";
|
|
31188
31188
|
|
|
31189
31189
|
// src/client.ts
|
|
31190
31190
|
import { dirname, join } from "path";
|
|
31191
31191
|
import { fileURLToPath } from "url";
|
|
31192
|
-
|
|
31193
|
-
|
|
31192
|
+
if (import.meta.url) {
|
|
31193
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31194
|
+
await loadDotenvSafely({ path: join(__dirname, "..", ".env"), override: false });
|
|
31195
|
+
}
|
|
31194
31196
|
var BASE_URL = "https://api.untappd.com/v4";
|
|
31195
31197
|
var SERVICE = "Untappd";
|
|
31196
31198
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -31205,15 +31207,72 @@ var DEFAULTS = {
|
|
|
31205
31207
|
// works. Override with UNTAPPD_DEVICE_ID to pin your own.
|
|
31206
31208
|
deviceUdid: "A1B2C3D4-0000-4000-8000-0000000000AA"
|
|
31207
31209
|
};
|
|
31208
|
-
function
|
|
31209
|
-
const
|
|
31210
|
-
const
|
|
31211
|
-
|
|
31212
|
-
|
|
31213
|
-
|
|
31214
|
-
|
|
31210
|
+
async function sendRequest(fetchImpl, method, url2, init) {
|
|
31211
|
+
const controller = new AbortController();
|
|
31212
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
31213
|
+
try {
|
|
31214
|
+
return await fetchImpl(url2, {
|
|
31215
|
+
method,
|
|
31216
|
+
headers: init.headers,
|
|
31217
|
+
...init.body !== void 0 ? { body: init.body } : {},
|
|
31218
|
+
signal: controller.signal
|
|
31219
|
+
});
|
|
31220
|
+
} catch {
|
|
31221
|
+
throw new UnreachableError(SERVICE);
|
|
31222
|
+
} finally {
|
|
31223
|
+
clearTimeout(timer);
|
|
31215
31224
|
}
|
|
31216
|
-
|
|
31225
|
+
}
|
|
31226
|
+
function xauthHeaders(userAgent) {
|
|
31227
|
+
return {
|
|
31228
|
+
"User-Agent": userAgent,
|
|
31229
|
+
Accept: "application/json",
|
|
31230
|
+
"x-untappd-app": "ios",
|
|
31231
|
+
"x-untappd-app-version": DEFAULTS.appVersion
|
|
31232
|
+
};
|
|
31233
|
+
}
|
|
31234
|
+
async function xauthLogin(creds, opts = {}) {
|
|
31235
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
31236
|
+
const utv = opts.utv ?? readEnvVar("UNTAPPD_UTV") ?? DEFAULTS.utv;
|
|
31237
|
+
const deviceUdid = opts.deviceUdid ?? readEnvVar("UNTAPPD_DEVICE_ID") ?? DEFAULTS.deviceUdid;
|
|
31238
|
+
const userAgent = opts.userAgent ?? readEnvVar("UNTAPPD_USER_AGENT") ?? DEFAULTS.userAgent;
|
|
31239
|
+
const qs = buildQueryString({ client_id: creds.clientId, client_secret: creds.clientSecret, utv });
|
|
31240
|
+
const form = new URLSearchParams({
|
|
31241
|
+
user_name: creds.username,
|
|
31242
|
+
user_password: creds.password,
|
|
31243
|
+
device_udid: deviceUdid,
|
|
31244
|
+
device_name: DEFAULTS.deviceName,
|
|
31245
|
+
device_version: DEFAULTS.deviceVersion,
|
|
31246
|
+
device_platform: DEFAULTS.devicePlatform,
|
|
31247
|
+
app_version: DEFAULTS.appVersion,
|
|
31248
|
+
multi_account: "true"
|
|
31249
|
+
});
|
|
31250
|
+
const res = await sendRequest(fetchImpl, "POST", `${BASE_URL}/xauth${qs}`, {
|
|
31251
|
+
headers: { ...xauthHeaders(userAgent), "Content-Type": "application/x-www-form-urlencoded" },
|
|
31252
|
+
body: form.toString()
|
|
31253
|
+
});
|
|
31254
|
+
const text = await res.text();
|
|
31255
|
+
if (!res.ok) {
|
|
31256
|
+
if (res.status === 401 || res.status === 400) {
|
|
31257
|
+
throw createHelpfulError(`Untappd login failed (${res.status}).`, {
|
|
31258
|
+
hint: "Check UNTAPPD_USERNAME / UNTAPPD_PASSWORD, and that UNTAPPD_CLIENT_ID / UNTAPPD_CLIENT_SECRET are the mobile app credentials."
|
|
31259
|
+
});
|
|
31260
|
+
}
|
|
31261
|
+
throw new McpToolError(formatApiError(res.status, "POST", "/xauth", text, { service: SERVICE }));
|
|
31262
|
+
}
|
|
31263
|
+
let data;
|
|
31264
|
+
try {
|
|
31265
|
+
data = JSON.parse(text);
|
|
31266
|
+
} catch {
|
|
31267
|
+
throw new McpToolError("Untappd login returned a non-JSON response.");
|
|
31268
|
+
}
|
|
31269
|
+
const token = data?.response?.access_token;
|
|
31270
|
+
if (!token) {
|
|
31271
|
+
throw createHelpfulError("Untappd login did not return an access token.", {
|
|
31272
|
+
hint: data?.response?.two_factor_enabled ? "This account has two-factor authentication enabled, which xauth login cannot satisfy." : "The credentials may be incorrect."
|
|
31273
|
+
});
|
|
31274
|
+
}
|
|
31275
|
+
return token;
|
|
31217
31276
|
}
|
|
31218
31277
|
function missingCredsError() {
|
|
31219
31278
|
const missing = ["UNTAPPD_USERNAME", "UNTAPPD_PASSWORD", "UNTAPPD_CLIENT_ID", "UNTAPPD_CLIENT_SECRET"].filter((k) => !readEnvVar(k));
|
|
@@ -31226,8 +31285,11 @@ function missingCredsError() {
|
|
|
31226
31285
|
}
|
|
31227
31286
|
var UntappdClient = class {
|
|
31228
31287
|
fetchImpl;
|
|
31229
|
-
|
|
31230
|
-
|
|
31288
|
+
clientId;
|
|
31289
|
+
clientSecret;
|
|
31290
|
+
username;
|
|
31291
|
+
password;
|
|
31292
|
+
_loginName;
|
|
31231
31293
|
utv = readEnvVar("UNTAPPD_UTV") ?? DEFAULTS.utv;
|
|
31232
31294
|
deviceUdid = readEnvVar("UNTAPPD_DEVICE_ID") ?? DEFAULTS.deviceUdid;
|
|
31233
31295
|
userAgent = readEnvVar("UNTAPPD_USER_AGENT") ?? DEFAULTS.userAgent;
|
|
@@ -31241,26 +31303,31 @@ var UntappdClient = class {
|
|
|
31241
31303
|
constructor(opts = {}) {
|
|
31242
31304
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
31243
31305
|
this.token = opts.token ?? null;
|
|
31244
|
-
|
|
31245
|
-
|
|
31246
|
-
|
|
31247
|
-
|
|
31248
|
-
|
|
31249
|
-
this.creds = null;
|
|
31250
|
-
this.configError = missingCredsError();
|
|
31251
|
-
}
|
|
31306
|
+
this.clientId = opts.clientId ?? readEnvVar("UNTAPPD_CLIENT_ID") ?? null;
|
|
31307
|
+
this.clientSecret = opts.clientSecret ?? readEnvVar("UNTAPPD_CLIENT_SECRET") ?? null;
|
|
31308
|
+
this.username = opts.username ?? readEnvVar("UNTAPPD_USERNAME") ?? null;
|
|
31309
|
+
this.password = opts.password ?? readEnvVar("UNTAPPD_PASSWORD") ?? null;
|
|
31310
|
+
this._loginName = opts.loginName ?? this.username;
|
|
31252
31311
|
}
|
|
31253
|
-
/**
|
|
31312
|
+
/** True when enough is configured to make authenticated calls. */
|
|
31254
31313
|
get configured() {
|
|
31255
|
-
return this.
|
|
31314
|
+
if (this.token) return Boolean(this.clientId && this.clientSecret);
|
|
31315
|
+
return Boolean(this.clientId && this.clientSecret && this.username && this.password);
|
|
31256
31316
|
}
|
|
31257
31317
|
/** The configured login name — the default `username` for user-scoped tools. */
|
|
31258
31318
|
get loginName() {
|
|
31259
|
-
return this.
|
|
31319
|
+
return this._loginName;
|
|
31320
|
+
}
|
|
31321
|
+
requireAppCreds() {
|
|
31322
|
+
if (!this.clientId || !this.clientSecret) {
|
|
31323
|
+
throw missingCredsError();
|
|
31324
|
+
}
|
|
31325
|
+
return { clientId: this.clientId, clientSecret: this.clientSecret };
|
|
31260
31326
|
}
|
|
31261
|
-
|
|
31262
|
-
|
|
31263
|
-
|
|
31327
|
+
requireLogin() {
|
|
31328
|
+
const app = this.requireAppCreds();
|
|
31329
|
+
if (!this.username || !this.password) throw missingCredsError();
|
|
31330
|
+
return { ...app, username: this.username, password: this.password };
|
|
31264
31331
|
}
|
|
31265
31332
|
baseHeaders() {
|
|
31266
31333
|
return {
|
|
@@ -31270,23 +31337,10 @@ var UntappdClient = class {
|
|
|
31270
31337
|
"x-untappd-app-version": DEFAULTS.appVersion
|
|
31271
31338
|
};
|
|
31272
31339
|
}
|
|
31273
|
-
// One HTTP attempt with a hard timeout
|
|
31274
|
-
//
|
|
31340
|
+
// One HTTP attempt with a hard timeout, delegating to the module-level
|
|
31341
|
+
// helper shared with `xauthLogin()`.
|
|
31275
31342
|
async send(method, url2, init) {
|
|
31276
|
-
|
|
31277
|
-
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
31278
|
-
try {
|
|
31279
|
-
return await this.fetchImpl(url2, {
|
|
31280
|
-
method,
|
|
31281
|
-
headers: init.headers,
|
|
31282
|
-
...init.body !== void 0 ? { body: init.body } : {},
|
|
31283
|
-
signal: controller.signal
|
|
31284
|
-
});
|
|
31285
|
-
} catch {
|
|
31286
|
-
throw new UnreachableError(SERVICE);
|
|
31287
|
-
} finally {
|
|
31288
|
-
clearTimeout(timer);
|
|
31289
|
-
}
|
|
31343
|
+
return sendRequest(this.fetchImpl, method, url2, init);
|
|
31290
31344
|
}
|
|
31291
31345
|
/**
|
|
31292
31346
|
* Obtain an access token, logging in via xauth (username/password) on demand.
|
|
@@ -31303,43 +31357,13 @@ var UntappdClient = class {
|
|
|
31303
31357
|
return this.loginInFlight;
|
|
31304
31358
|
}
|
|
31305
31359
|
async login() {
|
|
31306
|
-
const c = this.
|
|
31307
|
-
const
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
|
|
31311
|
-
|
|
31312
|
-
device_name: DEFAULTS.deviceName,
|
|
31313
|
-
device_version: DEFAULTS.deviceVersion,
|
|
31314
|
-
device_platform: DEFAULTS.devicePlatform,
|
|
31315
|
-
app_version: DEFAULTS.appVersion,
|
|
31316
|
-
multi_account: "true"
|
|
31360
|
+
const c = this.requireLogin();
|
|
31361
|
+
const token = await xauthLogin(c, {
|
|
31362
|
+
fetchImpl: this.fetchImpl,
|
|
31363
|
+
utv: this.utv,
|
|
31364
|
+
deviceUdid: this.deviceUdid,
|
|
31365
|
+
userAgent: this.userAgent
|
|
31317
31366
|
});
|
|
31318
|
-
const res = await this.send("POST", `${BASE_URL}/xauth${qs}`, {
|
|
31319
|
-
headers: { ...this.baseHeaders(), "Content-Type": "application/x-www-form-urlencoded" },
|
|
31320
|
-
body: form.toString()
|
|
31321
|
-
});
|
|
31322
|
-
const text = await res.text();
|
|
31323
|
-
if (!res.ok) {
|
|
31324
|
-
if (res.status === 401 || res.status === 400) {
|
|
31325
|
-
throw createHelpfulError(`Untappd login failed (${res.status}).`, {
|
|
31326
|
-
hint: "Check UNTAPPD_USERNAME / UNTAPPD_PASSWORD, and that UNTAPPD_CLIENT_ID / UNTAPPD_CLIENT_SECRET are the mobile app credentials."
|
|
31327
|
-
});
|
|
31328
|
-
}
|
|
31329
|
-
throw new McpToolError(formatApiError(res.status, "POST", "/xauth", text, { service: SERVICE }));
|
|
31330
|
-
}
|
|
31331
|
-
let data;
|
|
31332
|
-
try {
|
|
31333
|
-
data = JSON.parse(text);
|
|
31334
|
-
} catch {
|
|
31335
|
-
throw new McpToolError("Untappd login returned a non-JSON response.");
|
|
31336
|
-
}
|
|
31337
|
-
const token = data?.response?.access_token;
|
|
31338
|
-
if (!token) {
|
|
31339
|
-
throw createHelpfulError("Untappd login did not return an access token.", {
|
|
31340
|
-
hint: data?.response?.two_factor_enabled ? "This account has two-factor authentication enabled, which xauth login cannot satisfy." : "The credentials may be incorrect."
|
|
31341
|
-
});
|
|
31342
|
-
}
|
|
31343
31367
|
this.token = token;
|
|
31344
31368
|
return token;
|
|
31345
31369
|
}
|
|
@@ -31372,7 +31396,7 @@ var UntappdClient = class {
|
|
|
31372
31396
|
const headers = this.baseHeaders();
|
|
31373
31397
|
let query;
|
|
31374
31398
|
if (opts.auth === "bearer") {
|
|
31375
|
-
const c = this.
|
|
31399
|
+
const c = this.requireAppCreds();
|
|
31376
31400
|
headers["Authorization"] = `Bearer ${token}`;
|
|
31377
31401
|
query = { ...opts.query, client_id: c.clientId, client_secret: c.clientSecret, utv: this.utv };
|
|
31378
31402
|
} else {
|
|
@@ -31420,11 +31444,86 @@ var UntappdClient = class {
|
|
|
31420
31444
|
}
|
|
31421
31445
|
}
|
|
31422
31446
|
};
|
|
31423
|
-
|
|
31447
|
+
|
|
31448
|
+
// src/compact.ts
|
|
31449
|
+
function asDict(v) {
|
|
31450
|
+
return v && typeof v === "object" ? v : void 0;
|
|
31451
|
+
}
|
|
31452
|
+
function count(v) {
|
|
31453
|
+
const d = asDict(v);
|
|
31454
|
+
if (!d) return void 0;
|
|
31455
|
+
return d.count ?? d.total_count;
|
|
31456
|
+
}
|
|
31457
|
+
function compactCheckin(item) {
|
|
31458
|
+
const c = asDict(item) ?? {};
|
|
31459
|
+
const beer = asDict(c.beer);
|
|
31460
|
+
const brewery = asDict(c.brewery);
|
|
31461
|
+
const venue = asDict(c.venue);
|
|
31462
|
+
const user = asDict(c.user);
|
|
31463
|
+
return {
|
|
31464
|
+
checkin_id: c.checkin_id,
|
|
31465
|
+
created_at: c.created_at,
|
|
31466
|
+
user: user?.user_name,
|
|
31467
|
+
rating: c.rating_score,
|
|
31468
|
+
comment: c.checkin_comment || void 0,
|
|
31469
|
+
beer: beer && { bid: beer.bid, name: beer.beer_name, style: beer.beer_style, abv: beer.beer_abv },
|
|
31470
|
+
brewery: brewery?.brewery_name,
|
|
31471
|
+
venue: venue?.venue_name || void 0,
|
|
31472
|
+
toasts: count(c.toasts),
|
|
31473
|
+
comments: count(c.comments),
|
|
31474
|
+
has_photo: (count(c.media) ?? 0) > 0
|
|
31475
|
+
};
|
|
31476
|
+
}
|
|
31477
|
+
function beerCore(item) {
|
|
31478
|
+
const beer = asDict(item.beer) ?? {};
|
|
31479
|
+
const brewery = asDict(item.brewery);
|
|
31480
|
+
return {
|
|
31481
|
+
bid: beer.bid,
|
|
31482
|
+
name: beer.beer_name,
|
|
31483
|
+
style: beer.beer_style,
|
|
31484
|
+
abv: beer.beer_abv,
|
|
31485
|
+
ibu: beer.beer_ibu,
|
|
31486
|
+
brewery: brewery?.brewery_name
|
|
31487
|
+
};
|
|
31488
|
+
}
|
|
31489
|
+
function compactBeerResult(item) {
|
|
31490
|
+
const i = asDict(item) ?? {};
|
|
31491
|
+
return { ...beerCore(i), checkin_count: i.checkin_count, have_had: i.have_had };
|
|
31492
|
+
}
|
|
31493
|
+
function compactWishlistBeer(item) {
|
|
31494
|
+
const i = asDict(item) ?? {};
|
|
31495
|
+
return { ...beerCore(i), added_at: i.created_at };
|
|
31496
|
+
}
|
|
31497
|
+
function compactUserBeer(item) {
|
|
31498
|
+
const i = asDict(item) ?? {};
|
|
31499
|
+
return {
|
|
31500
|
+
...beerCore(i),
|
|
31501
|
+
your_count: i.count,
|
|
31502
|
+
your_rating: i.user_auth_rating_score || void 0,
|
|
31503
|
+
global_rating: i.rating_score || void 0,
|
|
31504
|
+
last_had: i.recent_created_at
|
|
31505
|
+
};
|
|
31506
|
+
}
|
|
31507
|
+
function projectItems(resp, container, mapFn) {
|
|
31508
|
+
const r = asDict(resp);
|
|
31509
|
+
const box = asDict(r?.[container]);
|
|
31510
|
+
const items = box?.items;
|
|
31511
|
+
if (!r || !box || !Array.isArray(items)) {
|
|
31512
|
+
process.stderr.write(`[untappd-mcp] compact: expected ${container}.items array; returning raw response
|
|
31513
|
+
`);
|
|
31514
|
+
return resp;
|
|
31515
|
+
}
|
|
31516
|
+
return { ...r, [container]: { ...box, items: items.map(mapFn) } };
|
|
31517
|
+
}
|
|
31518
|
+
var compactCheckins = (resp) => projectItems(resp, "checkins", compactCheckin);
|
|
31519
|
+
var compactBeerSearch = (resp) => projectItems(resp, "beers", compactBeerResult);
|
|
31520
|
+
var compactWishlist = (resp) => projectItems(resp, "beers", compactWishlistBeer);
|
|
31521
|
+
var compactUserBeers = (resp) => projectItems(resp, "beers", compactUserBeer);
|
|
31424
31522
|
|
|
31425
31523
|
// src/tools/beer.ts
|
|
31426
31524
|
var BidSchema = external_exports.number().int().positive().describe("Untappd beer id (bid)");
|
|
31427
|
-
|
|
31525
|
+
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)");
|
|
31526
|
+
function registerBeerTools(server, client2) {
|
|
31428
31527
|
server.registerTool(
|
|
31429
31528
|
"untappd_search_beer",
|
|
31430
31529
|
{
|
|
@@ -31435,12 +31534,13 @@ function registerBeerTools(server) {
|
|
|
31435
31534
|
query: external_exports.string().min(1).describe("Beer name to search for"),
|
|
31436
31535
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max results (1\u201350, default 25)"),
|
|
31437
31536
|
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")
|
|
31537
|
+
sort: external_exports.enum(["checkin", "name", "count"]).optional().describe("Sort order: checkin (relevance, default), name, or count"),
|
|
31538
|
+
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
31539
|
}
|
|
31440
31540
|
},
|
|
31441
|
-
async ({ query, limit, offset, sort }) => {
|
|
31442
|
-
const data = await
|
|
31443
|
-
return textResult(data);
|
|
31541
|
+
async ({ query, limit, offset, sort, compact }) => {
|
|
31542
|
+
const data = await client2.get("/search/beer", { q: query, limit, offset, sort });
|
|
31543
|
+
return textResult(compact ? compactBeerSearch(data) : data);
|
|
31444
31544
|
}
|
|
31445
31545
|
);
|
|
31446
31546
|
server.registerTool(
|
|
@@ -31455,7 +31555,7 @@ function registerBeerTools(server) {
|
|
|
31455
31555
|
}
|
|
31456
31556
|
},
|
|
31457
31557
|
async ({ bid, compact }) => {
|
|
31458
|
-
const data = await
|
|
31558
|
+
const data = await client2.get(`/beer/info/${bid}`, { compact: compact ? "true" : void 0 });
|
|
31459
31559
|
return textResult(data);
|
|
31460
31560
|
}
|
|
31461
31561
|
);
|
|
@@ -31468,18 +31568,19 @@ function registerBeerTools(server) {
|
|
|
31468
31568
|
inputSchema: {
|
|
31469
31569
|
bid: BidSchema,
|
|
31470
31570
|
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)")
|
|
31571
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31572
|
+
compact: CompactCheckins
|
|
31472
31573
|
}
|
|
31473
31574
|
},
|
|
31474
|
-
async ({ bid, limit, max_id }) => {
|
|
31475
|
-
const data = await
|
|
31476
|
-
return textResult(data);
|
|
31575
|
+
async ({ bid, limit, max_id, compact }) => {
|
|
31576
|
+
const data = await client2.get(`/beer/checkins/${bid}`, { limit, max_id });
|
|
31577
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31477
31578
|
}
|
|
31478
31579
|
);
|
|
31479
31580
|
}
|
|
31480
31581
|
|
|
31481
31582
|
// src/tools/brewery.ts
|
|
31482
|
-
function registerBreweryTools(server) {
|
|
31583
|
+
function registerBreweryTools(server, client2) {
|
|
31483
31584
|
server.registerTool(
|
|
31484
31585
|
"untappd_search_brewery",
|
|
31485
31586
|
{
|
|
@@ -31493,7 +31594,7 @@ function registerBreweryTools(server) {
|
|
|
31493
31594
|
}
|
|
31494
31595
|
},
|
|
31495
31596
|
async ({ query, limit, offset }) => {
|
|
31496
|
-
const data = await
|
|
31597
|
+
const data = await client2.get("/search/brewery", { q: query, limit, offset });
|
|
31497
31598
|
return textResult(data);
|
|
31498
31599
|
}
|
|
31499
31600
|
);
|
|
@@ -31509,7 +31610,7 @@ function registerBreweryTools(server) {
|
|
|
31509
31610
|
}
|
|
31510
31611
|
},
|
|
31511
31612
|
async ({ brewery_id, compact }) => {
|
|
31512
|
-
const data = await
|
|
31613
|
+
const data = await client2.get(`/brewery/info/${brewery_id}`, { compact: compact ? "true" : void 0 });
|
|
31513
31614
|
return textResult(data);
|
|
31514
31615
|
}
|
|
31515
31616
|
);
|
|
@@ -31527,14 +31628,14 @@ function registerBreweryTools(server) {
|
|
|
31527
31628
|
}
|
|
31528
31629
|
},
|
|
31529
31630
|
async ({ brewery_id, limit, offset, sort }) => {
|
|
31530
|
-
const data = await
|
|
31631
|
+
const data = await client2.get(`/brewery/beer_list/${brewery_id}`, { limit, offset, sort });
|
|
31531
31632
|
return textResult(data);
|
|
31532
31633
|
}
|
|
31533
31634
|
);
|
|
31534
31635
|
}
|
|
31535
31636
|
|
|
31536
31637
|
// src/tools/venue.ts
|
|
31537
|
-
function registerVenueTools(server) {
|
|
31638
|
+
function registerVenueTools(server, client2) {
|
|
31538
31639
|
server.registerTool(
|
|
31539
31640
|
"untappd_search_venue",
|
|
31540
31641
|
{
|
|
@@ -31547,7 +31648,7 @@ function registerVenueTools(server) {
|
|
|
31547
31648
|
}
|
|
31548
31649
|
},
|
|
31549
31650
|
async ({ query, limit }) => {
|
|
31550
|
-
const data = await
|
|
31651
|
+
const data = await client2.get("/search/venue", { q: query, limit });
|
|
31551
31652
|
return textResult(data);
|
|
31552
31653
|
}
|
|
31553
31654
|
);
|
|
@@ -31563,7 +31664,22 @@ function registerVenueTools(server) {
|
|
|
31563
31664
|
}
|
|
31564
31665
|
},
|
|
31565
31666
|
async ({ venue_id, compact }) => {
|
|
31566
|
-
const data = await
|
|
31667
|
+
const data = await client2.get(`/venue/info/${venue_id}`, { compact: compact ? "true" : void 0 });
|
|
31668
|
+
return textResult(data);
|
|
31669
|
+
}
|
|
31670
|
+
);
|
|
31671
|
+
server.registerTool(
|
|
31672
|
+
"untappd_venue_by_foursquare",
|
|
31673
|
+
{
|
|
31674
|
+
title: "Look up an Untappd venue by Foursquare id",
|
|
31675
|
+
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.",
|
|
31676
|
+
annotations: toolAnnotations({ title: "Look up an Untappd venue by Foursquare id", readOnly: true, idempotent: true, openWorld: true }),
|
|
31677
|
+
inputSchema: {
|
|
31678
|
+
foursquare_id: external_exports.string().min(1).describe("Foursquare venue id")
|
|
31679
|
+
}
|
|
31680
|
+
},
|
|
31681
|
+
async ({ foursquare_id }) => {
|
|
31682
|
+
const data = await client2.get(`/venue/foursquare_lookup/${encodeURIComponent(foursquare_id)}`);
|
|
31567
31683
|
return textResult(data);
|
|
31568
31684
|
}
|
|
31569
31685
|
);
|
|
@@ -31576,20 +31692,21 @@ function registerVenueTools(server) {
|
|
|
31576
31692
|
inputSchema: {
|
|
31577
31693
|
venue_id: external_exports.number().int().positive().describe("Untappd venue id"),
|
|
31578
31694
|
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)")
|
|
31695
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31696
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31580
31697
|
}
|
|
31581
31698
|
},
|
|
31582
|
-
async ({ venue_id, limit, max_id }) => {
|
|
31583
|
-
const data = await
|
|
31584
|
-
return textResult(data);
|
|
31699
|
+
async ({ venue_id, limit, max_id, compact }) => {
|
|
31700
|
+
const data = await client2.get(`/venue/checkins/${venue_id}`, { limit, max_id });
|
|
31701
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31585
31702
|
}
|
|
31586
31703
|
);
|
|
31587
31704
|
}
|
|
31588
31705
|
|
|
31589
31706
|
// src/tools/user.ts
|
|
31590
31707
|
var UsernameArg = external_exports.string().min(1).optional().describe("Untappd username. Omit to use your own configured account (UNTAPPD_USERNAME).");
|
|
31591
|
-
function resolveUser(username) {
|
|
31592
|
-
const u = username ??
|
|
31708
|
+
function resolveUser(username, loginName) {
|
|
31709
|
+
const u = username ?? loginName;
|
|
31593
31710
|
if (!u) {
|
|
31594
31711
|
throw createHelpfulError("No username given and no configured account to fall back to.", {
|
|
31595
31712
|
hint: "Pass `username`, or set UNTAPPD_USERNAME so user tools default to your own account."
|
|
@@ -31597,7 +31714,7 @@ function resolveUser(username) {
|
|
|
31597
31714
|
}
|
|
31598
31715
|
return encodeURIComponent(u);
|
|
31599
31716
|
}
|
|
31600
|
-
function registerUserTools(server) {
|
|
31717
|
+
function registerUserTools(server, client2) {
|
|
31601
31718
|
server.registerTool(
|
|
31602
31719
|
"untappd_user_info",
|
|
31603
31720
|
{
|
|
@@ -31610,7 +31727,7 @@ function registerUserTools(server) {
|
|
|
31610
31727
|
}
|
|
31611
31728
|
},
|
|
31612
31729
|
async ({ username, compact }) => {
|
|
31613
|
-
const data = await
|
|
31730
|
+
const data = await client2.get(`/user/info/${resolveUser(username, client2.loginName)}`, { compact: compact ? "true" : void 0 });
|
|
31614
31731
|
return textResult(data);
|
|
31615
31732
|
}
|
|
31616
31733
|
);
|
|
@@ -31623,12 +31740,13 @@ function registerUserTools(server) {
|
|
|
31623
31740
|
inputSchema: {
|
|
31624
31741
|
username: UsernameArg,
|
|
31625
31742
|
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)")
|
|
31743
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31744
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31627
31745
|
}
|
|
31628
31746
|
},
|
|
31629
|
-
async ({ username, limit, max_id }) => {
|
|
31630
|
-
const data = await
|
|
31631
|
-
return textResult(data);
|
|
31747
|
+
async ({ username, limit, max_id, compact }) => {
|
|
31748
|
+
const data = await client2.get(`/user/checkins/${resolveUser(username, client2.loginName)}`, { limit, max_id });
|
|
31749
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31632
31750
|
}
|
|
31633
31751
|
);
|
|
31634
31752
|
server.registerTool(
|
|
@@ -31641,12 +31759,13 @@ function registerUserTools(server) {
|
|
|
31641
31759
|
username: UsernameArg,
|
|
31642
31760
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max beers (1\u201350, default 25)"),
|
|
31643
31761
|
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)")
|
|
31762
|
+
sort: external_exports.enum(["date", "name", "brewery", "style", "rating", "abv"]).optional().describe("Sort order (default date added, newest first)"),
|
|
31763
|
+
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
31764
|
}
|
|
31646
31765
|
},
|
|
31647
|
-
async ({ username, limit, offset, sort }) => {
|
|
31648
|
-
const data = await
|
|
31649
|
-
return textResult(data);
|
|
31766
|
+
async ({ username, limit, offset, sort, compact }) => {
|
|
31767
|
+
const data = await client2.get(`/user/wishlist/${resolveUser(username, client2.loginName)}`, { limit, offset, sort });
|
|
31768
|
+
return textResult(compact ? compactWishlist(data) : data);
|
|
31650
31769
|
}
|
|
31651
31770
|
);
|
|
31652
31771
|
server.registerTool(
|
|
@@ -31659,12 +31778,13 @@ function registerUserTools(server) {
|
|
|
31659
31778
|
username: UsernameArg,
|
|
31660
31779
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max beers (1\u201350, default 25)"),
|
|
31661
31780
|
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)")
|
|
31781
|
+
sort: external_exports.enum(["date", "checkin", "highest_rated", "lowest_rated", "name", "this_month", "highest_abv"]).optional().describe("Sort order (default date, most recent first)"),
|
|
31782
|
+
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
31783
|
}
|
|
31664
31784
|
},
|
|
31665
|
-
async ({ username, limit, offset, sort }) => {
|
|
31666
|
-
const data = await
|
|
31667
|
-
return textResult(data);
|
|
31785
|
+
async ({ username, limit, offset, sort, compact }) => {
|
|
31786
|
+
const data = await client2.get(`/user/beers/${resolveUser(username, client2.loginName)}`, { limit, offset, sort });
|
|
31787
|
+
return textResult(compact ? compactUserBeers(data) : data);
|
|
31668
31788
|
}
|
|
31669
31789
|
);
|
|
31670
31790
|
server.registerTool(
|
|
@@ -31680,7 +31800,7 @@ function registerUserTools(server) {
|
|
|
31680
31800
|
}
|
|
31681
31801
|
},
|
|
31682
31802
|
async ({ username, limit, offset }) => {
|
|
31683
|
-
const data = await
|
|
31803
|
+
const data = await client2.get(`/user/badges/${resolveUser(username, client2.loginName)}`, { limit, offset });
|
|
31684
31804
|
return textResult(data);
|
|
31685
31805
|
}
|
|
31686
31806
|
);
|
|
@@ -31697,14 +31817,48 @@ function registerUserTools(server) {
|
|
|
31697
31817
|
}
|
|
31698
31818
|
},
|
|
31699
31819
|
async ({ username, limit, offset }) => {
|
|
31700
|
-
const data = await
|
|
31820
|
+
const data = await client2.get(`/user/friends/${resolveUser(username, client2.loginName)}`, { limit, offset });
|
|
31821
|
+
return textResult(data);
|
|
31822
|
+
}
|
|
31823
|
+
);
|
|
31824
|
+
server.registerTool(
|
|
31825
|
+
"untappd_user_venues",
|
|
31826
|
+
{
|
|
31827
|
+
title: "Get venues a user has checked in at",
|
|
31828
|
+
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.",
|
|
31829
|
+
annotations: toolAnnotations({ title: "Get venues a user has checked in at", readOnly: true, idempotent: true, openWorld: true }),
|
|
31830
|
+
inputSchema: {
|
|
31831
|
+
username: UsernameArg,
|
|
31832
|
+
limit: external_exports.number().int().min(1).max(50).optional().describe("Max venues (1\u201350, default 25)"),
|
|
31833
|
+
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)"),
|
|
31834
|
+
sort: external_exports.enum(["date", "name", "checkin", "highest_rated"]).optional().describe("Sort order (default most recent)")
|
|
31835
|
+
}
|
|
31836
|
+
},
|
|
31837
|
+
async ({ username, limit, offset, sort }) => {
|
|
31838
|
+
const data = await client2.get(`/user/venues/${resolveUser(username, client2.loginName)}`, { limit, offset, sort });
|
|
31839
|
+
return textResult(data);
|
|
31840
|
+
}
|
|
31841
|
+
);
|
|
31842
|
+
server.registerTool(
|
|
31843
|
+
"untappd_pending_friends",
|
|
31844
|
+
{
|
|
31845
|
+
title: "Get your pending friend requests",
|
|
31846
|
+
description: "Get the incoming friend requests waiting on YOUR account \u2014 the users who have requested to be your friend. Read-only.",
|
|
31847
|
+
annotations: toolAnnotations({ title: "Get your pending friend requests", readOnly: true, idempotent: true, openWorld: true }),
|
|
31848
|
+
inputSchema: {
|
|
31849
|
+
limit: external_exports.number().int().min(1).max(50).optional().describe("Max requests (1\u201350, default 25)"),
|
|
31850
|
+
offset: external_exports.number().int().min(0).optional().describe("Result offset for paging (default 0)")
|
|
31851
|
+
}
|
|
31852
|
+
},
|
|
31853
|
+
async ({ limit, offset }) => {
|
|
31854
|
+
const data = await client2.get("/user/pending", { limit, offset });
|
|
31701
31855
|
return textResult(data);
|
|
31702
31856
|
}
|
|
31703
31857
|
);
|
|
31704
31858
|
}
|
|
31705
31859
|
|
|
31706
31860
|
// src/tools/feed.ts
|
|
31707
|
-
function registerFeedTools(server) {
|
|
31861
|
+
function registerFeedTools(server, client2) {
|
|
31708
31862
|
server.registerTool(
|
|
31709
31863
|
"untappd_activity_feed",
|
|
31710
31864
|
{
|
|
@@ -31713,12 +31867,13 @@ function registerFeedTools(server) {
|
|
|
31713
31867
|
annotations: toolAnnotations({ title: "Get Untappd friend activity feed", readOnly: true, idempotent: false, openWorld: true }),
|
|
31714
31868
|
inputSchema: {
|
|
31715
31869
|
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)")
|
|
31870
|
+
max_id: external_exports.number().int().positive().optional().describe("Return check-ins older than this id (for paging)"),
|
|
31871
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31717
31872
|
}
|
|
31718
31873
|
},
|
|
31719
|
-
async ({ limit, max_id }) => {
|
|
31720
|
-
const data = await
|
|
31721
|
-
return textResult(data);
|
|
31874
|
+
async ({ limit, max_id, compact }) => {
|
|
31875
|
+
const data = await client2.get("/checkin/recent", { limit, max_id });
|
|
31876
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31722
31877
|
}
|
|
31723
31878
|
);
|
|
31724
31879
|
server.registerTool(
|
|
@@ -31732,14 +31887,88 @@ function registerFeedTools(server) {
|
|
|
31732
31887
|
}
|
|
31733
31888
|
},
|
|
31734
31889
|
async ({ checkin_id }) => {
|
|
31735
|
-
const data = await
|
|
31890
|
+
const data = await client2.get(`/checkin/view/${checkin_id}`);
|
|
31736
31891
|
return textResult(data);
|
|
31737
31892
|
}
|
|
31738
31893
|
);
|
|
31739
31894
|
}
|
|
31740
31895
|
|
|
31896
|
+
// src/tools/resolve.ts
|
|
31897
|
+
function parseUntappdUrl(input) {
|
|
31898
|
+
let url2;
|
|
31899
|
+
try {
|
|
31900
|
+
url2 = new URL(input.trim());
|
|
31901
|
+
} catch {
|
|
31902
|
+
return null;
|
|
31903
|
+
}
|
|
31904
|
+
if (!/(^|\.)untappd\.com$/i.test(url2.hostname)) return null;
|
|
31905
|
+
const segs = url2.pathname.split("/").filter(Boolean);
|
|
31906
|
+
if (segs[0] === "user" && segs[2] === "checkin" && /^\d+$/.test(segs[3] ?? "")) {
|
|
31907
|
+
return { type: "checkin", id: Number(segs[3]), username: segs[1], tool: "untappd_checkin_info" };
|
|
31908
|
+
}
|
|
31909
|
+
if (segs[0] === "user" && segs[1]) {
|
|
31910
|
+
return { type: "user", username: segs[1], tool: "untappd_user_info" };
|
|
31911
|
+
}
|
|
31912
|
+
const short = {
|
|
31913
|
+
b: { type: "beer", tool: "untappd_beer_info" },
|
|
31914
|
+
w: { type: "brewery", tool: "untappd_brewery_info" },
|
|
31915
|
+
v: { type: "venue", tool: "untappd_venue_info" }
|
|
31916
|
+
};
|
|
31917
|
+
if (segs[0] && short[segs[0]]) {
|
|
31918
|
+
const last = segs[segs.length - 1];
|
|
31919
|
+
if (/^\d+$/.test(last)) {
|
|
31920
|
+
return { ...short[segs[0]], id: Number(last) };
|
|
31921
|
+
}
|
|
31922
|
+
}
|
|
31923
|
+
return null;
|
|
31924
|
+
}
|
|
31925
|
+
function registerResolveTools(server, client2) {
|
|
31926
|
+
server.registerTool(
|
|
31927
|
+
"untappd_resolve",
|
|
31928
|
+
{
|
|
31929
|
+
title: "Resolve an Untappd URL",
|
|
31930
|
+
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.",
|
|
31931
|
+
annotations: toolAnnotations({ title: "Resolve an Untappd URL", readOnly: true, idempotent: true, openWorld: false }),
|
|
31932
|
+
inputSchema: {
|
|
31933
|
+
url: external_exports.string().min(1).describe("An untappd.com URL to resolve")
|
|
31934
|
+
}
|
|
31935
|
+
},
|
|
31936
|
+
async ({ url: url2 }) => {
|
|
31937
|
+
const resolved = parseUntappdUrl(url2);
|
|
31938
|
+
if (!resolved) {
|
|
31939
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url2}`, {
|
|
31940
|
+
hint: "Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>."
|
|
31941
|
+
});
|
|
31942
|
+
}
|
|
31943
|
+
return textResult(resolved);
|
|
31944
|
+
}
|
|
31945
|
+
);
|
|
31946
|
+
server.registerTool(
|
|
31947
|
+
"untappd_open_url",
|
|
31948
|
+
{
|
|
31949
|
+
title: "Open an Untappd URL (resolve + fetch)",
|
|
31950
|
+
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.",
|
|
31951
|
+
annotations: toolAnnotations({ title: "Open an Untappd URL", readOnly: true, idempotent: true, openWorld: true }),
|
|
31952
|
+
inputSchema: {
|
|
31953
|
+
url: external_exports.string().min(1).describe("An untappd.com URL to resolve and fetch")
|
|
31954
|
+
}
|
|
31955
|
+
},
|
|
31956
|
+
async ({ url: url2 }) => {
|
|
31957
|
+
const resolved = parseUntappdUrl(url2);
|
|
31958
|
+
if (!resolved) {
|
|
31959
|
+
throw createHelpfulError(`Not a recognised Untappd entity URL: ${url2}`, {
|
|
31960
|
+
hint: "Expected an untappd.com link like /b/<slug>/<bid>, /w/<slug>/<id>, /v/<slug>/<id>, or /user/<name>."
|
|
31961
|
+
});
|
|
31962
|
+
}
|
|
31963
|
+
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)}`;
|
|
31964
|
+
const detail = await client2.get(path);
|
|
31965
|
+
return textResult({ resolved, detail });
|
|
31966
|
+
}
|
|
31967
|
+
);
|
|
31968
|
+
}
|
|
31969
|
+
|
|
31741
31970
|
// src/tools/discover.ts
|
|
31742
|
-
function registerDiscoverTools(server) {
|
|
31971
|
+
function registerDiscoverTools(server, client2) {
|
|
31743
31972
|
server.registerTool(
|
|
31744
31973
|
"untappd_trending",
|
|
31745
31974
|
{
|
|
@@ -31749,7 +31978,7 @@ function registerDiscoverTools(server) {
|
|
|
31749
31978
|
inputSchema: {}
|
|
31750
31979
|
},
|
|
31751
31980
|
async () => {
|
|
31752
|
-
const data = await
|
|
31981
|
+
const data = await client2.get("/beer/trending");
|
|
31753
31982
|
return textResult(data);
|
|
31754
31983
|
}
|
|
31755
31984
|
);
|
|
@@ -31765,7 +31994,7 @@ function registerDiscoverTools(server) {
|
|
|
31765
31994
|
}
|
|
31766
31995
|
},
|
|
31767
31996
|
async ({ limit, offset }) => {
|
|
31768
|
-
const data = await
|
|
31997
|
+
const data = await client2.get("/notifications", { limit, offset });
|
|
31769
31998
|
return textResult(data);
|
|
31770
31999
|
}
|
|
31771
32000
|
);
|
|
@@ -31779,19 +32008,81 @@ function registerDiscoverTools(server) {
|
|
|
31779
32008
|
lat: external_exports.number().min(-90).max(90).describe("Latitude of the location"),
|
|
31780
32009
|
lng: external_exports.number().min(-180).max(180).describe("Longitude of the location"),
|
|
31781
32010
|
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)")
|
|
32011
|
+
radius: external_exports.number().int().min(1).max(25).optional().describe("Search radius (default per Untappd)"),
|
|
32012
|
+
compact: external_exports.boolean().optional().describe("Project each check-in to a slim summary to save context (default false)")
|
|
31783
32013
|
}
|
|
31784
32014
|
},
|
|
31785
|
-
async ({ lat, lng, limit, radius }) => {
|
|
31786
|
-
const data = await
|
|
31787
|
-
return textResult(data);
|
|
32015
|
+
async ({ lat, lng, limit, radius, compact }) => {
|
|
32016
|
+
const data = await client2.get("/thepub/local", { lat, lng, limit, radius });
|
|
32017
|
+
return textResult(compact ? compactCheckins(data) : data);
|
|
31788
32018
|
}
|
|
31789
32019
|
);
|
|
31790
32020
|
}
|
|
31791
32021
|
|
|
32022
|
+
// src/tools/friends.ts
|
|
32023
|
+
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");
|
|
32024
|
+
var ACTIONS = [
|
|
32025
|
+
{
|
|
32026
|
+
tool: "untappd_add_friend",
|
|
32027
|
+
path: "request",
|
|
32028
|
+
title: "Send an Untappd friend request",
|
|
32029
|
+
verb: "send a friend request to",
|
|
32030
|
+
detail: "Sends a friend request to another user, who will be notified."
|
|
32031
|
+
},
|
|
32032
|
+
{
|
|
32033
|
+
tool: "untappd_accept_friend",
|
|
32034
|
+
path: "accept",
|
|
32035
|
+
title: "Accept an Untappd friend request",
|
|
32036
|
+
verb: "accept the incoming friend request from",
|
|
32037
|
+
detail: "Accepts an incoming friend request (see untappd_pending_friends for pending uids)."
|
|
32038
|
+
},
|
|
32039
|
+
{
|
|
32040
|
+
tool: "untappd_reject_friend",
|
|
32041
|
+
path: "reject",
|
|
32042
|
+
title: "Reject an Untappd friend request",
|
|
32043
|
+
verb: "reject the incoming friend request from",
|
|
32044
|
+
detail: "Rejects/ignores an incoming friend request (see untappd_pending_friends for pending uids)."
|
|
32045
|
+
},
|
|
32046
|
+
{
|
|
32047
|
+
tool: "untappd_remove_friend",
|
|
32048
|
+
path: "remove",
|
|
32049
|
+
title: "Remove an Untappd friend",
|
|
32050
|
+
verb: "remove your friendship with",
|
|
32051
|
+
detail: "Removes an existing friend, or cancels a friend request you sent."
|
|
32052
|
+
}
|
|
32053
|
+
];
|
|
32054
|
+
function registerFriendActionTools(server, client2) {
|
|
32055
|
+
for (const action of ACTIONS) {
|
|
32056
|
+
server.registerTool(
|
|
32057
|
+
action.tool,
|
|
32058
|
+
{
|
|
32059
|
+
title: action.title,
|
|
32060
|
+
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.`,
|
|
32061
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true }),
|
|
32062
|
+
inputSchema: {
|
|
32063
|
+
target_uid: TargetUidSchema,
|
|
32064
|
+
confirm: schemaConfirm
|
|
32065
|
+
}
|
|
32066
|
+
},
|
|
32067
|
+
async ({ target_uid, confirm }) => {
|
|
32068
|
+
if (confirm !== true) {
|
|
32069
|
+
return textResult({
|
|
32070
|
+
dryRun: true,
|
|
32071
|
+
action: action.path,
|
|
32072
|
+
target_uid,
|
|
32073
|
+
note: `Dry run \u2014 re-run with confirm: true to ${action.verb} user ${target_uid}.`
|
|
32074
|
+
});
|
|
32075
|
+
}
|
|
32076
|
+
const data = await client2.write("POST", `/friend/${action.path}/${target_uid}`);
|
|
32077
|
+
return textResult({ done: true, action: action.path, target_uid, result: data?.result });
|
|
32078
|
+
}
|
|
32079
|
+
);
|
|
32080
|
+
}
|
|
32081
|
+
}
|
|
32082
|
+
|
|
31792
32083
|
// src/tools/wishlist.ts
|
|
31793
32084
|
var BidSchema2 = external_exports.number().int().positive().describe("Untappd beer id (bid) \u2014 from untappd_search_beer");
|
|
31794
|
-
function registerWishlistTools(server) {
|
|
32085
|
+
function registerWishlistTools(server, client2) {
|
|
31795
32086
|
server.registerTool(
|
|
31796
32087
|
"untappd_wishlist_add",
|
|
31797
32088
|
{
|
|
@@ -31812,7 +32103,7 @@ function registerWishlistTools(server) {
|
|
|
31812
32103
|
note: "Dry run \u2014 re-run with confirm: true to add this beer to your wishlist."
|
|
31813
32104
|
});
|
|
31814
32105
|
}
|
|
31815
|
-
const data = await
|
|
32106
|
+
const data = await client2.write("GET", "/user/wishlist/add", { query: { bid } });
|
|
31816
32107
|
return textResult({ added: true, bid, result: data?.result });
|
|
31817
32108
|
}
|
|
31818
32109
|
);
|
|
@@ -31836,7 +32127,7 @@ function registerWishlistTools(server) {
|
|
|
31836
32127
|
note: "Dry run \u2014 re-run with confirm: true to remove this beer from your wishlist."
|
|
31837
32128
|
});
|
|
31838
32129
|
}
|
|
31839
|
-
const data = await
|
|
32130
|
+
const data = await client2.write("GET", "/user/wishlist/delete", { query: { bid } });
|
|
31840
32131
|
return textResult({ removed: true, bid, result: data?.result });
|
|
31841
32132
|
}
|
|
31842
32133
|
);
|
|
@@ -31857,7 +32148,7 @@ function localTimezone() {
|
|
|
31857
32148
|
const gmt_offset = -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60;
|
|
31858
32149
|
return { timezone, gmt_offset };
|
|
31859
32150
|
}
|
|
31860
|
-
function registerCheckinTools(server) {
|
|
32151
|
+
function registerCheckinTools(server, client2) {
|
|
31861
32152
|
server.registerTool(
|
|
31862
32153
|
"untappd_toast",
|
|
31863
32154
|
{
|
|
@@ -31878,7 +32169,7 @@ function registerCheckinTools(server) {
|
|
|
31878
32169
|
note: "Dry run \u2014 re-run with confirm: true to toggle your toast on this check-in."
|
|
31879
32170
|
});
|
|
31880
32171
|
}
|
|
31881
|
-
const data = await
|
|
32172
|
+
const data = await client2.write("POST", `/checkin/toast/${checkin_id}`);
|
|
31882
32173
|
return textResult({ toggled: true, checkin_id, result: data?.result, like_type: data?.like_type });
|
|
31883
32174
|
}
|
|
31884
32175
|
);
|
|
@@ -31904,7 +32195,7 @@ function registerCheckinTools(server) {
|
|
|
31904
32195
|
note: "Dry run \u2014 re-run with confirm: true to post this comment to your Untappd account."
|
|
31905
32196
|
});
|
|
31906
32197
|
}
|
|
31907
|
-
const data = await
|
|
32198
|
+
const data = await client2.write("POST", `/checkin/addcomment/${checkin_id}`, { form: { comment } });
|
|
31908
32199
|
return textResult({ posted: true, checkin_id, response: data });
|
|
31909
32200
|
}
|
|
31910
32201
|
);
|
|
@@ -31928,7 +32219,7 @@ function registerCheckinTools(server) {
|
|
|
31928
32219
|
note: "Dry run \u2014 re-run with confirm: true to delete this comment from your Untappd account."
|
|
31929
32220
|
});
|
|
31930
32221
|
}
|
|
31931
|
-
const data = await
|
|
32222
|
+
const data = await client2.write("POST", `/checkin/deletecomment/${comment_id}`);
|
|
31932
32223
|
return textResult({ deleted: true, comment_id, result: data?.result });
|
|
31933
32224
|
}
|
|
31934
32225
|
);
|
|
@@ -31952,7 +32243,7 @@ function registerCheckinTools(server) {
|
|
|
31952
32243
|
note: "Dry run \u2014 re-run with confirm: true to PERMANENTLY delete this check-in. This cannot be undone."
|
|
31953
32244
|
});
|
|
31954
32245
|
}
|
|
31955
|
-
const data = await
|
|
32246
|
+
const data = await client2.write("POST", `/checkin/delete/${checkin_id}`);
|
|
31956
32247
|
return textResult({ deleted: true, checkin_id, result: data?.result });
|
|
31957
32248
|
}
|
|
31958
32249
|
);
|
|
@@ -32013,14 +32304,14 @@ function registerCheckinTools(server) {
|
|
|
32013
32304
|
if (!existsSync(photo_path)) throw new McpToolError(`Photo file not found: ${photo_path}`);
|
|
32014
32305
|
blob = await fileBlob(photo_path);
|
|
32015
32306
|
}
|
|
32016
|
-
const data = await
|
|
32307
|
+
const data = await client2.write("POST", "/checkin/add", { form });
|
|
32017
32308
|
let photo_attached = false;
|
|
32018
32309
|
let photo_error;
|
|
32019
32310
|
if (photo_path !== void 0) {
|
|
32020
32311
|
if (data?.photo_upload?.url && data.checkin_id) {
|
|
32021
32312
|
try {
|
|
32022
|
-
await
|
|
32023
|
-
await
|
|
32313
|
+
await client2.putBinary(data.photo_upload.url, blob, PHOTO_CONTENT_TYPES[ext]);
|
|
32314
|
+
await client2.write("POST", "/photo/uploadComplete", {
|
|
32024
32315
|
form: { checkin_id: data.checkin_id, destination_url: data.photo_upload.destination_url }
|
|
32025
32316
|
});
|
|
32026
32317
|
photo_attached = true;
|
|
@@ -32043,7 +32334,7 @@ function registerCheckinTools(server) {
|
|
|
32043
32334
|
}
|
|
32044
32335
|
|
|
32045
32336
|
// src/tools/utilities.ts
|
|
32046
|
-
function registerUtilityTools(server) {
|
|
32337
|
+
function registerUtilityTools(server, client2) {
|
|
32047
32338
|
server.registerTool(
|
|
32048
32339
|
"untappd_healthcheck",
|
|
32049
32340
|
{
|
|
@@ -32053,18 +32344,18 @@ function registerUtilityTools(server) {
|
|
|
32053
32344
|
inputSchema: {}
|
|
32054
32345
|
},
|
|
32055
32346
|
async () => {
|
|
32056
|
-
if (!
|
|
32347
|
+
if (!client2.configured) {
|
|
32057
32348
|
return textResult({
|
|
32058
32349
|
ok: false,
|
|
32059
32350
|
configured: false,
|
|
32060
32351
|
note: "Untappd credentials are not set. Configure UNTAPPD_USERNAME, UNTAPPD_PASSWORD, UNTAPPD_CLIENT_ID, and UNTAPPD_CLIENT_SECRET."
|
|
32061
32352
|
});
|
|
32062
32353
|
}
|
|
32063
|
-
const feed = await
|
|
32354
|
+
const feed = await client2.get("/checkin/recent", { limit: 1 });
|
|
32064
32355
|
return textResult({
|
|
32065
32356
|
ok: true,
|
|
32066
32357
|
configured: true,
|
|
32067
|
-
account:
|
|
32358
|
+
account: client2.loginName,
|
|
32068
32359
|
feed_reachable: feed?.checkins !== void 0,
|
|
32069
32360
|
note: "Logged in to Untappd and fetched the friend feed successfully."
|
|
32070
32361
|
});
|
|
@@ -32073,19 +32364,22 @@ function registerUtilityTools(server) {
|
|
|
32073
32364
|
}
|
|
32074
32365
|
|
|
32075
32366
|
// src/index.ts
|
|
32367
|
+
var client = new UntappdClient();
|
|
32076
32368
|
await runMcp({
|
|
32077
32369
|
name: "untappd-mcp",
|
|
32078
32370
|
version: VERSION,
|
|
32079
32371
|
banner: "[untappd-mcp] This project was developed and is maintained by AI (Claude Opus 4.8). Use at your own discretion.",
|
|
32080
32372
|
tools: [
|
|
32081
|
-
registerBeerTools,
|
|
32082
|
-
registerBreweryTools,
|
|
32083
|
-
registerVenueTools,
|
|
32084
|
-
registerUserTools,
|
|
32085
|
-
registerFeedTools,
|
|
32086
|
-
|
|
32087
|
-
|
|
32088
|
-
|
|
32089
|
-
|
|
32373
|
+
(s) => registerBeerTools(s, client),
|
|
32374
|
+
(s) => registerBreweryTools(s, client),
|
|
32375
|
+
(s) => registerVenueTools(s, client),
|
|
32376
|
+
(s) => registerUserTools(s, client),
|
|
32377
|
+
(s) => registerFeedTools(s, client),
|
|
32378
|
+
(s) => registerResolveTools(s, client),
|
|
32379
|
+
(s) => registerDiscoverTools(s, client),
|
|
32380
|
+
(s) => registerFriendActionTools(s, client),
|
|
32381
|
+
(s) => registerWishlistTools(s, client),
|
|
32382
|
+
(s) => registerCheckinTools(s, client),
|
|
32383
|
+
(s) => registerUtilityTools(s, client)
|
|
32090
32384
|
]
|
|
32091
32385
|
});
|