untappd-mcp 2.1.0 → 2.1.2
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/dist/bundle.js +266 -88
- package/dist/tools/cache.js +3 -3
- package/dist/tools/checkin.js +5 -5
- package/dist/tools/friends.js +5 -1
- package/dist/tools/wishlist.js +2 -2
- package/dist/version.js +1 -1
- package/package.json +4 -4
- 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": "2.1.
|
|
10
|
+
"version": "2.1.2"
|
|
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": "2.1.
|
|
18
|
+
"version": "2.1.2",
|
|
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": "2.1.
|
|
4
|
+
"version": "2.1.2",
|
|
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/dist/bundle.js
CHANGED
|
@@ -2282,7 +2282,7 @@ var require_versions = __commonJS({
|
|
|
2282
2282
|
exports.version = {
|
|
2283
2283
|
major: 4,
|
|
2284
2284
|
minor: 6,
|
|
2285
|
-
patch:
|
|
2285
|
+
patch: 5
|
|
2286
2286
|
};
|
|
2287
2287
|
(function() {
|
|
2288
2288
|
var keys = Object.getOwnPropertyNames(exports);
|
|
@@ -4851,7 +4851,7 @@ var require_memoizer = __commonJS({
|
|
|
4851
4851
|
var NONE2 = 0;
|
|
4852
4852
|
var ASSUMED2 = 1;
|
|
4853
4853
|
var PROVEN2 = 2;
|
|
4854
|
-
function isRecursive2(inst, stack,
|
|
4854
|
+
function isRecursive2(inst, stack, resolve3) {
|
|
4855
4855
|
const cached2 = recursive2.get(inst);
|
|
4856
4856
|
if (cached2 !== void 0)
|
|
4857
4857
|
return cached2 ? PROVEN2 : NONE2;
|
|
@@ -4861,7 +4861,7 @@ var require_memoizer = __commonJS({
|
|
|
4861
4861
|
let result = NONE2;
|
|
4862
4862
|
const check2 = (child) => {
|
|
4863
4863
|
if (result !== PROVEN2 && child?._zod) {
|
|
4864
|
-
const answer = isRecursive2(child, stack,
|
|
4864
|
+
const answer = isRecursive2(child, stack, resolve3);
|
|
4865
4865
|
if (answer > result)
|
|
4866
4866
|
result = answer;
|
|
4867
4867
|
}
|
|
@@ -4872,7 +4872,7 @@ var require_memoizer = __commonJS({
|
|
|
4872
4872
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
4873
4873
|
if (spread && !desc.enumerable)
|
|
4874
4874
|
continue;
|
|
4875
|
-
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack,
|
|
4875
|
+
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve3) : NONE2;
|
|
4876
4876
|
if (child > answer)
|
|
4877
4877
|
answer = child;
|
|
4878
4878
|
}
|
|
@@ -4936,7 +4936,7 @@ var require_memoizer = __commonJS({
|
|
|
4936
4936
|
break;
|
|
4937
4937
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
4938
4938
|
case "lazy": {
|
|
4939
|
-
const inner = def._cachedInner ?? (
|
|
4939
|
+
const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
|
|
4940
4940
|
merge2(inner ? isRecursive2(inner, stack, false) : ASSUMED2);
|
|
4941
4941
|
break;
|
|
4942
4942
|
}
|
|
@@ -23302,6 +23302,20 @@ var require_v4 = __commonJS({
|
|
|
23302
23302
|
}
|
|
23303
23303
|
});
|
|
23304
23304
|
|
|
23305
|
+
// node_modules/@chrischall/mcp-utils/dist/cancel/index.js
|
|
23306
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23307
|
+
var storage = new AsyncLocalStorage();
|
|
23308
|
+
function withCallSignal(signal, fn, request) {
|
|
23309
|
+
return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
|
|
23310
|
+
}
|
|
23311
|
+
|
|
23312
|
+
// node_modules/@chrischall/mcp-utils/dist/caller/index.js
|
|
23313
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
23314
|
+
var storage2 = new AsyncLocalStorage2();
|
|
23315
|
+
function withCallerCapabilities(capabilities, fn) {
|
|
23316
|
+
return capabilities ? storage2.run(capabilities, fn) : fn();
|
|
23317
|
+
}
|
|
23318
|
+
|
|
23305
23319
|
// node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
23306
23320
|
var __create2 = Object.create;
|
|
23307
23321
|
var __defProp2 = Object.defineProperty;
|
|
@@ -27704,14 +27718,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
27704
27718
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
27705
27719
|
}
|
|
27706
27720
|
function sleep(ms, signal) {
|
|
27707
|
-
return new Promise((
|
|
27721
|
+
return new Promise((resolve3, reject) => {
|
|
27708
27722
|
if (signal?.aborted) {
|
|
27709
27723
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
27710
27724
|
return;
|
|
27711
27725
|
}
|
|
27712
27726
|
const timer = setTimeout(() => {
|
|
27713
27727
|
signal?.removeEventListener("abort", onAbort);
|
|
27714
|
-
|
|
27728
|
+
resolve3();
|
|
27715
27729
|
}, ms);
|
|
27716
27730
|
const onAbort = () => {
|
|
27717
27731
|
clearTimeout(timer);
|
|
@@ -28505,7 +28519,7 @@ var Protocol = class {
|
|
|
28505
28519
|
const flowStartedAt = Date.now();
|
|
28506
28520
|
let onAbort;
|
|
28507
28521
|
let cleanupMessageId;
|
|
28508
|
-
return new Promise((
|
|
28522
|
+
return new Promise((resolve3, reject) => {
|
|
28509
28523
|
const earlyReject = (error62) => {
|
|
28510
28524
|
reject(error62);
|
|
28511
28525
|
};
|
|
@@ -28573,7 +28587,7 @@ var Protocol = class {
|
|
|
28573
28587
|
}
|
|
28574
28588
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
28575
28589
|
if (decoded.kind === "input_required") {
|
|
28576
|
-
if (options?.allowInputRequired === true) return
|
|
28590
|
+
if (options?.allowInputRequired === true) return resolve3(manualInputRequiredValue(decoded));
|
|
28577
28591
|
const flow = {
|
|
28578
28592
|
codec: codec2,
|
|
28579
28593
|
request,
|
|
@@ -28585,11 +28599,11 @@ var Protocol = class {
|
|
|
28585
28599
|
params
|
|
28586
28600
|
}, resultSchema, legOptions)
|
|
28587
28601
|
};
|
|
28588
|
-
return
|
|
28602
|
+
return resolve3(this._resolveNonCompleteResult(decoded, flow));
|
|
28589
28603
|
}
|
|
28590
28604
|
const result = decoded.result;
|
|
28591
28605
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
28592
|
-
if (parseResult.success)
|
|
28606
|
+
if (parseResult.success) resolve3(parseResult.data);
|
|
28593
28607
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
28594
28608
|
}, reject);
|
|
28595
28609
|
});
|
|
@@ -31501,7 +31515,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31501
31515
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
31502
31516
|
const schOrFunc = root.refs[ref];
|
|
31503
31517
|
if (schOrFunc) return schOrFunc;
|
|
31504
|
-
let _sch =
|
|
31518
|
+
let _sch = resolve3.call(this, root, ref);
|
|
31505
31519
|
if (_sch === void 0) {
|
|
31506
31520
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
31507
31521
|
const { schemaId } = this.opts;
|
|
@@ -31527,7 +31541,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31527
31541
|
function sameSchemaEnv(s1, s2) {
|
|
31528
31542
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
31529
31543
|
}
|
|
31530
|
-
function
|
|
31544
|
+
function resolve3(root, ref) {
|
|
31531
31545
|
let sch;
|
|
31532
31546
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
31533
31547
|
return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
|
|
@@ -31977,47 +31991,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
31977
31991
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
31978
31992
|
return uri;
|
|
31979
31993
|
}
|
|
31980
|
-
function
|
|
31994
|
+
function resolve3(baseURI, relativeURI, options) {
|
|
31981
31995
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
31982
31996
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
31983
31997
|
schemelessOptions.skipEscape = true;
|
|
31984
31998
|
return serialize(resolved, schemelessOptions);
|
|
31985
31999
|
}
|
|
31986
|
-
function resolveComponent(base,
|
|
32000
|
+
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
31987
32001
|
const target = {};
|
|
31988
32002
|
if (!skipNormalization) {
|
|
31989
32003
|
base = parse3(serialize(base, options), options);
|
|
31990
|
-
|
|
32004
|
+
relative2 = parse3(serialize(relative2, options), options);
|
|
31991
32005
|
}
|
|
31992
32006
|
options = options || {};
|
|
31993
|
-
if (!options.tolerant &&
|
|
31994
|
-
target.scheme =
|
|
31995
|
-
target.userinfo =
|
|
31996
|
-
target.host =
|
|
31997
|
-
target.port =
|
|
31998
|
-
target.path = removeDotSegments(
|
|
31999
|
-
target.query =
|
|
32007
|
+
if (!options.tolerant && relative2.scheme) {
|
|
32008
|
+
target.scheme = relative2.scheme;
|
|
32009
|
+
target.userinfo = relative2.userinfo;
|
|
32010
|
+
target.host = relative2.host;
|
|
32011
|
+
target.port = relative2.port;
|
|
32012
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
32013
|
+
target.query = relative2.query;
|
|
32000
32014
|
} else {
|
|
32001
|
-
if (
|
|
32002
|
-
target.userinfo =
|
|
32003
|
-
target.host =
|
|
32004
|
-
target.port =
|
|
32005
|
-
target.path = removeDotSegments(
|
|
32006
|
-
target.query =
|
|
32015
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
32016
|
+
target.userinfo = relative2.userinfo;
|
|
32017
|
+
target.host = relative2.host;
|
|
32018
|
+
target.port = relative2.port;
|
|
32019
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
32020
|
+
target.query = relative2.query;
|
|
32007
32021
|
} else {
|
|
32008
|
-
if (!
|
|
32022
|
+
if (!relative2.path) {
|
|
32009
32023
|
target.path = base.path;
|
|
32010
|
-
if (
|
|
32024
|
+
if (relative2.query !== void 0) target.query = relative2.query;
|
|
32011
32025
|
else target.query = base.query;
|
|
32012
32026
|
} else {
|
|
32013
|
-
if (
|
|
32027
|
+
if (relative2.path[0] === "/") target.path = removeDotSegments(relative2.path);
|
|
32014
32028
|
else {
|
|
32015
|
-
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" +
|
|
32016
|
-
else if (!base.path) target.path =
|
|
32017
|
-
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
32029
|
+
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative2.path;
|
|
32030
|
+
else if (!base.path) target.path = relative2.path;
|
|
32031
|
+
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
|
|
32018
32032
|
target.path = removeDotSegments(target.path);
|
|
32019
32033
|
}
|
|
32020
|
-
target.query =
|
|
32034
|
+
target.query = relative2.query;
|
|
32021
32035
|
}
|
|
32022
32036
|
target.userinfo = base.userinfo;
|
|
32023
32037
|
target.host = base.host;
|
|
@@ -32025,7 +32039,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
32025
32039
|
}
|
|
32026
32040
|
target.scheme = base.scheme;
|
|
32027
32041
|
}
|
|
32028
|
-
target.fragment =
|
|
32042
|
+
target.fragment = relative2.fragment;
|
|
32029
32043
|
return target;
|
|
32030
32044
|
}
|
|
32031
32045
|
function equal(uriA, uriB, options) {
|
|
@@ -32151,7 +32165,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
32151
32165
|
const fastUri = {
|
|
32152
32166
|
SCHEMES,
|
|
32153
32167
|
normalize,
|
|
32154
|
-
resolve,
|
|
32168
|
+
resolve: resolve3,
|
|
32155
32169
|
resolveComponent,
|
|
32156
32170
|
equal,
|
|
32157
32171
|
serialize,
|
|
@@ -37524,7 +37538,7 @@ var StdioServerTransport = class {
|
|
|
37524
37538
|
}
|
|
37525
37539
|
send(message) {
|
|
37526
37540
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
37527
|
-
return new Promise((
|
|
37541
|
+
return new Promise((resolve3, reject) => {
|
|
37528
37542
|
const json2 = serializeMessage(message);
|
|
37529
37543
|
let settled = false;
|
|
37530
37544
|
const onError = (error62) => {
|
|
@@ -37539,14 +37553,14 @@ var StdioServerTransport = class {
|
|
|
37539
37553
|
settled = true;
|
|
37540
37554
|
this._stdout.off("error", onError);
|
|
37541
37555
|
this._stdout.off("drain", onDrain);
|
|
37542
|
-
|
|
37556
|
+
resolve3();
|
|
37543
37557
|
};
|
|
37544
37558
|
this._stdout.once("error", onError);
|
|
37545
37559
|
if (this._stdout.write(json2)) {
|
|
37546
37560
|
if (settled) return;
|
|
37547
37561
|
settled = true;
|
|
37548
37562
|
this._stdout.off("error", onError);
|
|
37549
|
-
|
|
37563
|
+
resolve3();
|
|
37550
37564
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
37551
37565
|
});
|
|
37552
37566
|
}
|
|
@@ -37600,14 +37614,14 @@ var StdioConnectionChannel = class {
|
|
|
37600
37614
|
*/
|
|
37601
37615
|
async whenRequestsAnswered(timeoutMs) {
|
|
37602
37616
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
37603
|
-
return await new Promise((
|
|
37617
|
+
return await new Promise((resolve3) => {
|
|
37604
37618
|
const waiter = () => {
|
|
37605
37619
|
clearTimeout(timer);
|
|
37606
|
-
|
|
37620
|
+
resolve3(true);
|
|
37607
37621
|
};
|
|
37608
37622
|
const timer = setTimeout(() => {
|
|
37609
37623
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
37610
|
-
|
|
37624
|
+
resolve3(false);
|
|
37611
37625
|
}, timeoutMs);
|
|
37612
37626
|
this._drainWaiters.push(waiter);
|
|
37613
37627
|
});
|
|
@@ -38005,13 +38019,28 @@ var API_KEY_RE = new RegExp([
|
|
|
38005
38019
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
38006
38020
|
// webhook signing secret (Stripe-style)
|
|
38007
38021
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
38008
|
-
var QUERY_SECRET_RE = /([?&](?:
|
|
38022
|
+
var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
38023
|
+
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
38024
|
+
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
38025
|
+
var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
|
|
38009
38026
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
38010
|
-
var JSON_SECRET_KEYS =
|
|
38011
|
-
|
|
38012
|
-
|
|
38027
|
+
var JSON_SECRET_KEYS = [
|
|
38028
|
+
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
38029
|
+
"client[_-]?secret",
|
|
38030
|
+
"api[_-]?secret",
|
|
38031
|
+
"(?:x[_-])?api[_-]?key",
|
|
38032
|
+
"private[_-]?key",
|
|
38033
|
+
"x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
|
|
38034
|
+
"(?:proxy-)?authorization",
|
|
38035
|
+
"password",
|
|
38036
|
+
"passwd",
|
|
38037
|
+
"secret",
|
|
38038
|
+
"token"
|
|
38039
|
+
].join("|");
|
|
38040
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
38041
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
38013
38042
|
function redactSecrets(text) {
|
|
38014
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
38043
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
38015
38044
|
}
|
|
38016
38045
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
38017
38046
|
const str2 = text === null || text === void 0 ? "" : String(text);
|
|
@@ -40616,7 +40645,7 @@ ${content.join("\n")}
|
|
|
40616
40645
|
var version = {
|
|
40617
40646
|
major: 4,
|
|
40618
40647
|
minor: 6,
|
|
40619
|
-
patch:
|
|
40648
|
+
patch: 5
|
|
40620
40649
|
};
|
|
40621
40650
|
|
|
40622
40651
|
// node_modules/zod/v4/core/schemas.js
|
|
@@ -43052,7 +43081,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
43052
43081
|
var NONE = 0;
|
|
43053
43082
|
var ASSUMED = 1;
|
|
43054
43083
|
var PROVEN = 2;
|
|
43055
|
-
function isRecursive(inst, stack,
|
|
43084
|
+
function isRecursive(inst, stack, resolve3) {
|
|
43056
43085
|
const cached2 = recursive.get(inst);
|
|
43057
43086
|
if (cached2 !== void 0)
|
|
43058
43087
|
return cached2 ? PROVEN : NONE;
|
|
@@ -43062,7 +43091,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
43062
43091
|
let result = NONE;
|
|
43063
43092
|
const check2 = (child) => {
|
|
43064
43093
|
if (result !== PROVEN && child?._zod) {
|
|
43065
|
-
const answer = isRecursive(child, stack,
|
|
43094
|
+
const answer = isRecursive(child, stack, resolve3);
|
|
43066
43095
|
if (answer > result)
|
|
43067
43096
|
result = answer;
|
|
43068
43097
|
}
|
|
@@ -43073,7 +43102,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
43073
43102
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
43074
43103
|
if (spread && !desc.enumerable)
|
|
43075
43104
|
continue;
|
|
43076
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
43105
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve3) : NONE;
|
|
43077
43106
|
if (child > answer)
|
|
43078
43107
|
answer = child;
|
|
43079
43108
|
}
|
|
@@ -43137,7 +43166,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
43137
43166
|
break;
|
|
43138
43167
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
43139
43168
|
case "lazy": {
|
|
43140
|
-
const inner = def._cachedInner ?? (
|
|
43169
|
+
const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
|
|
43141
43170
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
43142
43171
|
break;
|
|
43143
43172
|
}
|
|
@@ -57749,17 +57778,73 @@ Hint: ${err.hint}`);
|
|
|
57749
57778
|
}
|
|
57750
57779
|
throw err;
|
|
57751
57780
|
}
|
|
57781
|
+
function mcpRequestFrom(args) {
|
|
57782
|
+
const ctx = args.at(-1);
|
|
57783
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
57784
|
+
return void 0;
|
|
57785
|
+
const req = ctx.mcpReq;
|
|
57786
|
+
if (typeof req !== "object" || req === null)
|
|
57787
|
+
return void 0;
|
|
57788
|
+
return typeof req.notify === "function" ? req : void 0;
|
|
57789
|
+
}
|
|
57790
|
+
function callSignalFrom(args) {
|
|
57791
|
+
const ctx = args.at(-1);
|
|
57792
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
57793
|
+
return void 0;
|
|
57794
|
+
const req = ctx.mcpReq;
|
|
57795
|
+
if (typeof req !== "object" || req === null)
|
|
57796
|
+
return void 0;
|
|
57797
|
+
const signal = req.signal;
|
|
57798
|
+
return signal instanceof AbortSignal ? signal : void 0;
|
|
57799
|
+
}
|
|
57800
|
+
function declaredCapabilitiesFrom(server) {
|
|
57801
|
+
const inner = server.server;
|
|
57802
|
+
if (typeof inner !== "object" || inner === null)
|
|
57803
|
+
return void 0;
|
|
57804
|
+
const read = inner.getClientCapabilities;
|
|
57805
|
+
if (typeof read !== "function")
|
|
57806
|
+
return void 0;
|
|
57807
|
+
try {
|
|
57808
|
+
const declared = read.call(inner);
|
|
57809
|
+
return typeof declared === "object" && declared !== null ? declared : void 0;
|
|
57810
|
+
} catch {
|
|
57811
|
+
return void 0;
|
|
57812
|
+
}
|
|
57813
|
+
}
|
|
57752
57814
|
function surfaceToolHints(server) {
|
|
57753
57815
|
const register2 = server.registerTool.bind(server);
|
|
57754
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
57755
|
-
|
|
57756
|
-
|
|
57757
|
-
|
|
57758
|
-
|
|
57759
|
-
|
|
57760
|
-
|
|
57761
|
-
|
|
57762
|
-
|
|
57816
|
+
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
|
|
57817
|
+
// THE CALLER'S CANCELLATION, made ambient for the whole handler
|
|
57818
|
+
// (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
|
|
57819
|
+
// measured against @modelcontextprotocol/server 2.0.0, a cancelled
|
|
57820
|
+
// call aborts `ctx.mcpReq.signal` with the caller's reason and the
|
|
57821
|
+
// handler runs to completion regardless — so the HTTP request stays
|
|
57822
|
+
// in flight, the child keeps burning metered CPU, and the upstream
|
|
57823
|
+
// keeps being hit for somebody who has gone. claude.ai sent 101
|
|
57824
|
+
// cancellations in the week to 2026-09-20.
|
|
57825
|
+
//
|
|
57826
|
+
// Here because this is the one wrapper every tool already passes
|
|
57827
|
+
// through: threading the signal by hand would mean editing several
|
|
57828
|
+
// hundred handlers and missing exactly the ones nobody edits.
|
|
57829
|
+
withCallSignal(callSignalFrom(args), () => (
|
|
57830
|
+
// WHAT THE CALLER CAN DO, made ambient for the same reason and in
|
|
57831
|
+
// the same place (`caller/index.ts`). Only the 2025-era half needs
|
|
57832
|
+
// the wrapper — a 2026-07-28 request answers for itself off its own
|
|
57833
|
+
// envelope — but a guarded tool that cannot tell whether the caller
|
|
57834
|
+
// can be shown a prompt returns one the SDK then refuses to deliver,
|
|
57835
|
+
// and that refusal reaches the caller as an unexplained protocol
|
|
57836
|
+
// error it had no chance to catch.
|
|
57837
|
+
withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
|
|
57838
|
+
let result;
|
|
57839
|
+
try {
|
|
57840
|
+
result = cb(...args);
|
|
57841
|
+
} catch (err) {
|
|
57842
|
+
return hintResultOrRethrow(err);
|
|
57843
|
+
}
|
|
57844
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
57845
|
+
})
|
|
57846
|
+
), mcpRequestFrom(args))
|
|
57847
|
+
));
|
|
57763
57848
|
}
|
|
57764
57849
|
async function createMcpServer(opts) {
|
|
57765
57850
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -57774,13 +57859,47 @@ async function createMcpServer(opts) {
|
|
|
57774
57859
|
}
|
|
57775
57860
|
return server;
|
|
57776
57861
|
}
|
|
57862
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
57863
|
+
var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
|
|
57777
57864
|
function withGracefulShutdown(target, opts = {}) {
|
|
57778
57865
|
const shouldExit = opts.exit ?? true;
|
|
57866
|
+
if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
|
|
57867
|
+
throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
|
|
57868
|
+
}
|
|
57869
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
57870
|
+
if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
|
|
57871
|
+
throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
|
|
57872
|
+
}
|
|
57873
|
+
const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
|
|
57779
57874
|
let shuttingDown = false;
|
|
57875
|
+
let firstSignalAt = 0;
|
|
57876
|
+
let exited = false;
|
|
57877
|
+
const exitOnce = () => {
|
|
57878
|
+
if (exited)
|
|
57879
|
+
return;
|
|
57880
|
+
exited = true;
|
|
57881
|
+
process.exit(0);
|
|
57882
|
+
};
|
|
57780
57883
|
const handler = (signal) => {
|
|
57781
|
-
if (shuttingDown)
|
|
57884
|
+
if (shuttingDown) {
|
|
57885
|
+
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
57886
|
+
return;
|
|
57887
|
+
if (shouldExit) {
|
|
57888
|
+
console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
|
|
57889
|
+
exitOnce();
|
|
57890
|
+
}
|
|
57782
57891
|
return;
|
|
57892
|
+
}
|
|
57783
57893
|
shuttingDown = true;
|
|
57894
|
+
firstSignalAt = Date.now();
|
|
57895
|
+
let timer;
|
|
57896
|
+
if (shouldExit) {
|
|
57897
|
+
timer = setTimeout(() => {
|
|
57898
|
+
console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
|
|
57899
|
+
exitOnce();
|
|
57900
|
+
}, timeoutMs);
|
|
57901
|
+
timer.unref?.();
|
|
57902
|
+
}
|
|
57784
57903
|
void (async () => {
|
|
57785
57904
|
try {
|
|
57786
57905
|
if (opts.onSignal)
|
|
@@ -57789,8 +57908,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
57789
57908
|
} catch (err) {
|
|
57790
57909
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
57791
57910
|
} finally {
|
|
57911
|
+
if (timer !== void 0)
|
|
57912
|
+
clearTimeout(timer);
|
|
57792
57913
|
if (shouldExit)
|
|
57793
|
-
|
|
57914
|
+
exitOnce();
|
|
57794
57915
|
}
|
|
57795
57916
|
})();
|
|
57796
57917
|
};
|
|
@@ -57820,6 +57941,8 @@ function runMcp(opts) {
|
|
|
57820
57941
|
}
|
|
57821
57942
|
|
|
57822
57943
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
57944
|
+
import { homedir } from "node:os";
|
|
57945
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
57823
57946
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
57824
57947
|
function readEnvVar(key, opts = {}) {
|
|
57825
57948
|
const env = opts.env ?? process.env;
|
|
@@ -57832,6 +57955,15 @@ function readEnvVar(key, opts = {}) {
|
|
|
57832
57955
|
}
|
|
57833
57956
|
return opts.default;
|
|
57834
57957
|
}
|
|
57958
|
+
function expandPath(p) {
|
|
57959
|
+
let expanded = p;
|
|
57960
|
+
if (p === "~") {
|
|
57961
|
+
expanded = homedir();
|
|
57962
|
+
} else if (p.startsWith("~/")) {
|
|
57963
|
+
expanded = join(homedir(), p.slice(2));
|
|
57964
|
+
}
|
|
57965
|
+
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
57966
|
+
}
|
|
57835
57967
|
async function loadDotenvSafely(opts = {}) {
|
|
57836
57968
|
try {
|
|
57837
57969
|
const mod = await import(
|
|
@@ -57851,10 +57983,48 @@ async function loadDotenvSafely(opts = {}) {
|
|
|
57851
57983
|
|
|
57852
57984
|
// node_modules/@chrischall/mcp-utils/dist/fs/index.js
|
|
57853
57985
|
import { openAsBlob } from "node:fs";
|
|
57986
|
+
|
|
57987
|
+
// node_modules/@chrischall/mcp-utils/dist/fs/confine.js
|
|
57988
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
57989
|
+
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
|
|
57990
|
+
function isInside(root, target) {
|
|
57991
|
+
const rel = relative(root, target);
|
|
57992
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute2(rel);
|
|
57993
|
+
}
|
|
57994
|
+
function realPathAllowingMissing(p) {
|
|
57995
|
+
const abs = resolve2(p);
|
|
57996
|
+
let existing = abs;
|
|
57997
|
+
const tail = [];
|
|
57998
|
+
while (!existsSync(existing)) {
|
|
57999
|
+
const parent = dirname(existing);
|
|
58000
|
+
if (parent === existing)
|
|
58001
|
+
break;
|
|
58002
|
+
tail.unshift(existing.slice(parent.length).replace(/^[/\\]+/, ""));
|
|
58003
|
+
existing = parent;
|
|
58004
|
+
}
|
|
58005
|
+
let real;
|
|
58006
|
+
try {
|
|
58007
|
+
real = realpathSync(existing);
|
|
58008
|
+
} catch {
|
|
58009
|
+
real = existing;
|
|
58010
|
+
}
|
|
58011
|
+
return tail.length > 0 ? resolve2(real, ...tail) : real;
|
|
58012
|
+
}
|
|
58013
|
+
function assertPathWithinRoots(path, roots) {
|
|
58014
|
+
const target = realPathAllowingMissing(expandPath(path));
|
|
58015
|
+
for (const root of roots) {
|
|
58016
|
+
if (isInside(realPathAllowingMissing(expandPath(root)), target))
|
|
58017
|
+
return target;
|
|
58018
|
+
}
|
|
58019
|
+
throw new Error(`Path is outside the allowed directories: ${path}`);
|
|
58020
|
+
}
|
|
58021
|
+
|
|
58022
|
+
// node_modules/@chrischall/mcp-utils/dist/fs/index.js
|
|
57854
58023
|
async function fileBlob(path, opts = {}) {
|
|
58024
|
+
const target = opts.allowedRoots ? assertPathWithinRoots(path, opts.allowedRoots) : path;
|
|
57855
58025
|
let blob;
|
|
57856
58026
|
try {
|
|
57857
|
-
blob = await openAsBlob(
|
|
58027
|
+
blob = await openAsBlob(target, opts.type !== void 0 ? { type: opts.type } : void 0);
|
|
57858
58028
|
} catch {
|
|
57859
58029
|
throw new Error(`Cannot read file for upload: ${path}`);
|
|
57860
58030
|
}
|
|
@@ -57908,23 +58078,27 @@ var pageSchema = {
|
|
|
57908
58078
|
page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
|
|
57909
58079
|
};
|
|
57910
58080
|
function toolAnnotations(opts = {}) {
|
|
58081
|
+
if (opts.readOnly === true && opts.destructive === true) {
|
|
58082
|
+
throw new Error("toolAnnotations: readOnly: true contradicts destructive: true \u2014 a destructive tool is not read-only.");
|
|
58083
|
+
}
|
|
57911
58084
|
return {
|
|
57912
58085
|
...opts.title !== void 0 ? { title: opts.title } : {},
|
|
57913
|
-
readOnlyHint: opts.readOnly ??
|
|
58086
|
+
readOnlyHint: opts.readOnly ?? opts.destructive === void 0,
|
|
57914
58087
|
...opts.idempotent !== void 0 ? { idempotentHint: opts.idempotent } : {},
|
|
58088
|
+
...opts.destructive !== void 0 ? { destructiveHint: opts.destructive } : {},
|
|
57915
58089
|
...opts.openWorld !== void 0 ? { openWorldHint: opts.openWorld } : {}
|
|
57916
58090
|
};
|
|
57917
58091
|
}
|
|
57918
58092
|
|
|
57919
58093
|
// src/version.ts
|
|
57920
|
-
var VERSION = "2.1.
|
|
58094
|
+
var VERSION = "2.1.2";
|
|
57921
58095
|
|
|
57922
58096
|
// src/client.ts
|
|
57923
|
-
import { dirname, join } from "path";
|
|
58097
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
57924
58098
|
import { fileURLToPath } from "url";
|
|
57925
58099
|
if (import.meta.url) {
|
|
57926
|
-
const __dirname =
|
|
57927
|
-
await loadDotenvSafely({ path:
|
|
58100
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
58101
|
+
await loadDotenvSafely({ path: join2(__dirname, "..", ".env"), override: false });
|
|
57928
58102
|
}
|
|
57929
58103
|
var BASE_URL = "https://api.untappd.com/v4";
|
|
57930
58104
|
var SERVICE = "Untappd";
|
|
@@ -59486,7 +59660,11 @@ function registerFriendActionTools(server, client2) {
|
|
|
59486
59660
|
{
|
|
59487
59661
|
title: action.title,
|
|
59488
59662
|
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.`,
|
|
59489
|
-
|
|
59663
|
+
// All four reach ANOTHER PERSON — the description says so itself: "affects
|
|
59664
|
+
// a real relationship with another person". A friend request cannot be
|
|
59665
|
+
// un-sent, and a rejection cannot be un-rejected, so none of them has an
|
|
59666
|
+
// inverse in this tool set.
|
|
59667
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
59490
59668
|
inputSchema: external_exports.object({
|
|
59491
59669
|
target_uid: TargetUidSchema,
|
|
59492
59670
|
confirm: schemaConfirm
|
|
@@ -59516,7 +59694,7 @@ function registerWishlistTools(server, client2) {
|
|
|
59516
59694
|
{
|
|
59517
59695
|
title: "Add a beer to your wishlist",
|
|
59518
59696
|
description: "Add a beer to YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it adds. Writes to your account.",
|
|
59519
|
-
annotations: toolAnnotations({ title: "Add a beer to your wishlist", readOnly: false, idempotent: true, openWorld: true }),
|
|
59697
|
+
annotations: toolAnnotations({ title: "Add a beer to your wishlist", readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
59520
59698
|
inputSchema: external_exports.object({
|
|
59521
59699
|
bid: BidSchema2,
|
|
59522
59700
|
confirm: schemaConfirm
|
|
@@ -59540,7 +59718,7 @@ function registerWishlistTools(server, client2) {
|
|
|
59540
59718
|
{
|
|
59541
59719
|
title: "Remove a beer from your wishlist",
|
|
59542
59720
|
description: "Remove a beer from YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it removes. Writes to your account.",
|
|
59543
|
-
annotations: toolAnnotations({ title: "Remove a beer from your wishlist", readOnly: false, idempotent: true, openWorld: true }),
|
|
59721
|
+
annotations: toolAnnotations({ title: "Remove a beer from your wishlist", readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
59544
59722
|
inputSchema: external_exports.object({
|
|
59545
59723
|
bid: BidSchema2,
|
|
59546
59724
|
confirm: schemaConfirm
|
|
@@ -59562,7 +59740,7 @@ function registerWishlistTools(server, client2) {
|
|
|
59562
59740
|
}
|
|
59563
59741
|
|
|
59564
59742
|
// src/tools/checkin.ts
|
|
59565
|
-
import { existsSync } from "node:fs";
|
|
59743
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
59566
59744
|
import { extname } from "node:path";
|
|
59567
59745
|
var CheckinIdSchema = external_exports.number().int().positive().describe("Untappd check-in id");
|
|
59568
59746
|
var PHOTO_CONTENT_TYPES = { jpg: "image/jpeg", png: "image/png" };
|
|
@@ -59582,7 +59760,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59582
59760
|
{
|
|
59583
59761
|
title: "Toast an Untappd check-in",
|
|
59584
59762
|
description: "Toast (like) a check-in on YOUR account. This endpoint is a TOGGLE: calling it on a check-in you have already toasted removes the toast. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it posts. Writes to your Untappd account and is visible to others.",
|
|
59585
|
-
annotations: toolAnnotations({ title: "Toast an Untappd check-in", readOnly: false, idempotent: false, openWorld: true }),
|
|
59763
|
+
annotations: toolAnnotations({ title: "Toast an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
59586
59764
|
inputSchema: external_exports.object({
|
|
59587
59765
|
checkin_id: CheckinIdSchema,
|
|
59588
59766
|
confirm: schemaConfirm
|
|
@@ -59606,7 +59784,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59606
59784
|
{
|
|
59607
59785
|
title: "Comment on an Untappd check-in",
|
|
59608
59786
|
description: "Post a comment on a check-in from YOUR account. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it posts. Writes to your Untappd account and is visible to others.",
|
|
59609
|
-
annotations: toolAnnotations({ title: "Comment on an Untappd check-in", readOnly: false, idempotent: false, openWorld: true }),
|
|
59787
|
+
annotations: toolAnnotations({ title: "Comment on an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
59610
59788
|
inputSchema: external_exports.object({
|
|
59611
59789
|
checkin_id: CheckinIdSchema,
|
|
59612
59790
|
comment: external_exports.string().min(1).max(2e3).describe("Comment text to post"),
|
|
@@ -59632,7 +59810,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59632
59810
|
{
|
|
59633
59811
|
title: "Delete a comment from an Untappd check-in",
|
|
59634
59812
|
description: "Delete one of YOUR comments by its comment id (the id from a check-in's comments list). Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.",
|
|
59635
|
-
annotations: toolAnnotations({ title: "Delete a comment from an Untappd check-in", readOnly: false, idempotent: true, openWorld: true }),
|
|
59813
|
+
annotations: toolAnnotations({ title: "Delete a comment from an Untappd check-in", readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
59636
59814
|
inputSchema: external_exports.object({
|
|
59637
59815
|
comment_id: external_exports.number().int().positive().describe("Untappd comment id (from a check-in's comments.items)"),
|
|
59638
59816
|
confirm: schemaConfirm
|
|
@@ -59656,7 +59834,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59656
59834
|
{
|
|
59657
59835
|
title: "Delete an Untappd check-in",
|
|
59658
59836
|
description: "Permanently delete one of YOUR check-ins by its id. This is destructive and cannot be undone. Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.",
|
|
59659
|
-
annotations: toolAnnotations({ title: "Delete an Untappd check-in", readOnly: false, idempotent: true, openWorld: true }),
|
|
59837
|
+
annotations: toolAnnotations({ title: "Delete an Untappd check-in", readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
59660
59838
|
inputSchema: external_exports.object({
|
|
59661
59839
|
checkin_id: CheckinIdSchema,
|
|
59662
59840
|
confirm: schemaConfirm
|
|
@@ -59680,7 +59858,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59680
59858
|
{
|
|
59681
59859
|
title: "Check in a beer on Untappd",
|
|
59682
59860
|
description: "Post a NEW beer check-in to YOUR Untappd account \u2014 this publishes to your public feed. Provide the beer id (bid) from untappd_search_beer; optionally a rating (0\u20135 in 0.25 steps), a shout (comment), a venue via foursquare_id, and a local photo via photo_path (JPEG/PNG). Without confirm: true it returns a dry-run preview of the exact fields and makes NO network call; with confirm: true it posts.",
|
|
59683
|
-
annotations: toolAnnotations({ title: "Check in a beer on Untappd", readOnly: false, idempotent: false, openWorld: true }),
|
|
59861
|
+
annotations: toolAnnotations({ title: "Check in a beer on Untappd", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
59684
59862
|
inputSchema: external_exports.object({
|
|
59685
59863
|
bid: external_exports.number().int().positive().describe("Untappd beer id to check in (from untappd_search_beer)"),
|
|
59686
59864
|
rating: RatingSchema.optional().describe("Rating 0\u20135 in 0.25 increments (omit for no rating)"),
|
|
@@ -59729,7 +59907,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59729
59907
|
}
|
|
59730
59908
|
let blob;
|
|
59731
59909
|
if (photo_path !== void 0) {
|
|
59732
|
-
if (!
|
|
59910
|
+
if (!existsSync2(photo_path)) throw new McpToolError(`Photo file not found: ${photo_path}`);
|
|
59733
59911
|
blob = await fileBlob(photo_path);
|
|
59734
59912
|
}
|
|
59735
59913
|
const data = await client2.write("POST", "/checkin/add", { form });
|
|
@@ -60194,7 +60372,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
60194
60372
|
{
|
|
60195
60373
|
title: "Sync a user's check-ins into the cache",
|
|
60196
60374
|
description: "Fetch a user's detailed check-ins (venue, date, comment) into the cache from user/checkins. Incremental and resumable: pages backwards up to max_pages per call, persisting progress every page; run again until another_run_needed is false. NOTE: Untappd only returns the ~50 most recent check-ins for accounts other than your own and will not page further \u2014 such a sync reports history_truncated and you should use untappd_sync_user_beers for full has-had coverage. backfill_complete is only reported once ~all of total_checkins is cached. Pass force_backfill: true to reset a cache wrongly marked complete and re-page the whole history (cached rows are kept). Omit username for your own account.",
|
|
60197
|
-
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
60375
|
+
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
60198
60376
|
inputSchema: external_exports.object({
|
|
60199
60377
|
username: UsernameArg2,
|
|
60200
60378
|
max_pages: external_exports.number().int().min(1).max(50).optional().describe("Pages (50 check-ins each) to fetch this run (default 10). Keep modest to respect the ~100 calls/hour rate limit."),
|
|
@@ -60215,7 +60393,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
60215
60393
|
{
|
|
60216
60394
|
title: "Sync a user's complete distinct-beers list into the cache",
|
|
60217
60395
|
description: `Fetch a user's COMPLETE distinct-beers list (every unique beer they've ever had, with their rating, times-had count, and first/last dates) into the cache from user/beers. This is the cheapest way to get full "has had" coverage \u2014 thousands of beers instead of tens of thousands of check-ins \u2014 and, unlike user/checkins, it pages fully for any public/friend account. Offset-paged and resumable: fetches max_pages per call and persists progress; run again until another_run_needed is false. Feeds the same untappd_cache_has_had / has_had_many / not_had tools. Omit username for your own account.`,
|
|
60218
|
-
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
60396
|
+
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
60219
60397
|
inputSchema: external_exports.object({
|
|
60220
60398
|
username: UsernameArg2,
|
|
60221
60399
|
max_pages: external_exports.number().int().min(1).max(50).optional().describe("Pages (50 beers each) to fetch this run (default 10). Keep modest to respect the ~100 calls/hour rate limit.")
|
|
@@ -60349,7 +60527,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
60349
60527
|
{
|
|
60350
60528
|
title: "Top-rated beers a user has NOT had, from a candidate list",
|
|
60351
60529
|
description: 'The "what should I order off this tap list?" tool. From a list of candidate beer ids, return the top N the user has NOT yet had, ranked by Untappd global rating, with an optional style filter. Not-had filtering uses the cache only (both sources, no API call). Beer ratings/styles come from a metadata cache; a beer/info API call is made only on a cache miss or if the cached metadata is >30 days old, capped at api_budget calls per run (~100 calls/hour limit) \u2014 if more are needed it returns partial: true / another_run_needed: true, so re-running fills the rest. Reports the same freshness/caveat block as untappd_cache_not_had. Omit username for your own account.',
|
|
60352
|
-
annotations: toolAnnotations({ title: "Top-rated beers a user has NOT had, from a candidate list", readOnly: false, idempotent: false, openWorld: true }),
|
|
60530
|
+
annotations: toolAnnotations({ title: "Top-rated beers a user has NOT had, from a candidate list", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
60353
60531
|
inputSchema: external_exports.object({
|
|
60354
60532
|
username: UsernameArg2,
|
|
60355
60533
|
bids: external_exports.array(external_exports.number().int().positive()).min(1).max(100).describe("Candidate beer ids (1\u2013100)"),
|
|
@@ -60443,8 +60621,8 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
60443
60621
|
// src/cache/db.ts
|
|
60444
60622
|
import { DatabaseSync } from "node:sqlite";
|
|
60445
60623
|
import { mkdirSync } from "node:fs";
|
|
60446
|
-
import { dirname as
|
|
60447
|
-
import { homedir } from "node:os";
|
|
60624
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
60625
|
+
import { homedir as homedir2 } from "node:os";
|
|
60448
60626
|
var NodeSqlDriver = class {
|
|
60449
60627
|
constructor(db) {
|
|
60450
60628
|
this.db = db;
|
|
@@ -60481,7 +60659,7 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
|
|
|
60481
60659
|
db;
|
|
60482
60660
|
/** Open (creating parent dirs) a file-backed cache, or `:memory:` for tests. */
|
|
60483
60661
|
static open(path) {
|
|
60484
|
-
if (path !== ":memory:") mkdirSync(
|
|
60662
|
+
if (path !== ":memory:") mkdirSync(dirname3(path), { recursive: true });
|
|
60485
60663
|
return new _CheckinCache(new DatabaseSync(path));
|
|
60486
60664
|
}
|
|
60487
60665
|
close() {
|
|
@@ -60489,7 +60667,7 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
|
|
|
60489
60667
|
}
|
|
60490
60668
|
};
|
|
60491
60669
|
function defaultCachePath() {
|
|
60492
|
-
return readEnvVar("UNTAPPD_CACHE_DB") ??
|
|
60670
|
+
return readEnvVar("UNTAPPD_CACHE_DB") ?? join3(homedir2(), ".untappd-mcp", "checkins.db");
|
|
60493
60671
|
}
|
|
60494
60672
|
|
|
60495
60673
|
// src/index.ts
|
package/dist/tools/cache.js
CHANGED
|
@@ -92,7 +92,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
92
92
|
'untappd_sync_user_beers for full has-had coverage. backfill_complete is only reported once ~all of ' +
|
|
93
93
|
'total_checkins is cached. Pass force_backfill: true to reset a cache wrongly marked complete and re-page ' +
|
|
94
94
|
'the whole history (cached rows are kept). Omit username for your own account.',
|
|
95
|
-
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
95
|
+
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
96
96
|
inputSchema: z.object({
|
|
97
97
|
username: UsernameArg,
|
|
98
98
|
max_pages: z
|
|
@@ -123,7 +123,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
123
123
|
'user/checkins, it pages fully for any public/friend account. Offset-paged and resumable: fetches max_pages ' +
|
|
124
124
|
'per call and persists progress; run again until another_run_needed is false. Feeds the same ' +
|
|
125
125
|
'untappd_cache_has_had / has_had_many / not_had tools. Omit username for your own account.',
|
|
126
|
-
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
126
|
+
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
127
127
|
inputSchema: z.object({
|
|
128
128
|
username: UsernameArg,
|
|
129
129
|
max_pages: z
|
|
@@ -271,7 +271,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
271
271
|
'run (~100 calls/hour limit) — if more are needed it returns partial: true / another_run_needed: true, so ' +
|
|
272
272
|
're-running fills the rest. Reports the same freshness/caveat block as untappd_cache_not_had. Omit username ' +
|
|
273
273
|
'for your own account.',
|
|
274
|
-
annotations: toolAnnotations({ title: 'Top-rated beers a user has NOT had, from a candidate list', readOnly: false, idempotent: false, openWorld: true }),
|
|
274
|
+
annotations: toolAnnotations({ title: 'Top-rated beers a user has NOT had, from a candidate list', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
275
275
|
inputSchema: z.object({
|
|
276
276
|
username: UsernameArg,
|
|
277
277
|
bids: z.array(z.number().int().positive()).min(1).max(100).describe('Candidate beer ids (1–100)'),
|
package/dist/tools/checkin.js
CHANGED
|
@@ -28,7 +28,7 @@ export function registerCheckinTools(server, client) {
|
|
|
28
28
|
description: "Toast (like) a check-in on YOUR account. This endpoint is a TOGGLE: calling it on a check-in you have " +
|
|
29
29
|
'already toasted removes the toast. Without confirm: true it returns a dry-run preview and makes NO network ' +
|
|
30
30
|
'call; with confirm: true it posts. Writes to your Untappd account and is visible to others.',
|
|
31
|
-
annotations: toolAnnotations({ title: 'Toast an Untappd check-in', readOnly: false, idempotent: false, openWorld: true }),
|
|
31
|
+
annotations: toolAnnotations({ title: 'Toast an Untappd check-in', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
32
32
|
inputSchema: z.object({
|
|
33
33
|
checkin_id: CheckinIdSchema,
|
|
34
34
|
confirm: schemaConfirm,
|
|
@@ -49,7 +49,7 @@ export function registerCheckinTools(server, client) {
|
|
|
49
49
|
title: 'Comment on an Untappd check-in',
|
|
50
50
|
description: 'Post a comment on a check-in from YOUR account. Without confirm: true it returns a dry-run preview and ' +
|
|
51
51
|
'makes NO network call; with confirm: true it posts. Writes to your Untappd account and is visible to others.',
|
|
52
|
-
annotations: toolAnnotations({ title: 'Comment on an Untappd check-in', readOnly: false, idempotent: false, openWorld: true }),
|
|
52
|
+
annotations: toolAnnotations({ title: 'Comment on an Untappd check-in', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
53
53
|
inputSchema: z.object({
|
|
54
54
|
checkin_id: CheckinIdSchema,
|
|
55
55
|
comment: z.string().min(1).max(2000).describe('Comment text to post'),
|
|
@@ -72,7 +72,7 @@ export function registerCheckinTools(server, client) {
|
|
|
72
72
|
title: 'Delete a comment from an Untappd check-in',
|
|
73
73
|
description: 'Delete one of YOUR comments by its comment id (the id from a check-in\'s comments list). Without ' +
|
|
74
74
|
'confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.',
|
|
75
|
-
annotations: toolAnnotations({ title: 'Delete a comment from an Untappd check-in', readOnly: false, idempotent: true, openWorld: true }),
|
|
75
|
+
annotations: toolAnnotations({ title: 'Delete a comment from an Untappd check-in', readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
76
76
|
inputSchema: z.object({
|
|
77
77
|
comment_id: z.number().int().positive().describe('Untappd comment id (from a check-in\'s comments.items)'),
|
|
78
78
|
confirm: schemaConfirm,
|
|
@@ -93,7 +93,7 @@ export function registerCheckinTools(server, client) {
|
|
|
93
93
|
title: 'Delete an Untappd check-in',
|
|
94
94
|
description: 'Permanently delete one of YOUR check-ins by its id. This is destructive and cannot be undone. Without ' +
|
|
95
95
|
'confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.',
|
|
96
|
-
annotations: toolAnnotations({ title: 'Delete an Untappd check-in', readOnly: false, idempotent: true, openWorld: true }),
|
|
96
|
+
annotations: toolAnnotations({ title: 'Delete an Untappd check-in', readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
97
97
|
inputSchema: z.object({
|
|
98
98
|
checkin_id: CheckinIdSchema,
|
|
99
99
|
confirm: schemaConfirm,
|
|
@@ -116,7 +116,7 @@ export function registerCheckinTools(server, client) {
|
|
|
116
116
|
'(bid) from untappd_search_beer; optionally a rating (0–5 in 0.25 steps), a shout (comment), a venue via ' +
|
|
117
117
|
'foursquare_id, and a local photo via photo_path (JPEG/PNG). Without confirm: true it returns a dry-run ' +
|
|
118
118
|
'preview of the exact fields and makes NO network call; with confirm: true it posts.',
|
|
119
|
-
annotations: toolAnnotations({ title: 'Check in a beer on Untappd', readOnly: false, idempotent: false, openWorld: true }),
|
|
119
|
+
annotations: toolAnnotations({ title: 'Check in a beer on Untappd', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
120
120
|
inputSchema: z.object({
|
|
121
121
|
bid: z.number().int().positive().describe('Untappd beer id to check in (from untappd_search_beer)'),
|
|
122
122
|
rating: RatingSchema.optional().describe('Rating 0–5 in 0.25 increments (omit for no rating)'),
|
package/dist/tools/friends.js
CHANGED
|
@@ -50,7 +50,11 @@ export function registerFriendActionTools(server, client) {
|
|
|
50
50
|
'Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it ' +
|
|
51
51
|
'performs the action. Note: this endpoint path is taken from the Untappd app but is not otherwise ' +
|
|
52
52
|
'independently verified.',
|
|
53
|
-
|
|
53
|
+
// All four reach ANOTHER PERSON — the description says so itself: "affects
|
|
54
|
+
// a real relationship with another person". A friend request cannot be
|
|
55
|
+
// un-sent, and a rejection cannot be un-rejected, so none of them has an
|
|
56
|
+
// inverse in this tool set.
|
|
57
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
54
58
|
inputSchema: z.object({
|
|
55
59
|
target_uid: TargetUidSchema,
|
|
56
60
|
confirm: schemaConfirm,
|
package/dist/tools/wishlist.js
CHANGED
|
@@ -6,7 +6,7 @@ export function registerWishlistTools(server, client) {
|
|
|
6
6
|
title: 'Add a beer to your wishlist',
|
|
7
7
|
description: 'Add a beer to YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview and ' +
|
|
8
8
|
'makes NO network call; with confirm: true it adds. Writes to your account.',
|
|
9
|
-
annotations: toolAnnotations({ title: 'Add a beer to your wishlist', readOnly: false, idempotent: true, openWorld: true }),
|
|
9
|
+
annotations: toolAnnotations({ title: 'Add a beer to your wishlist', readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
bid: BidSchema,
|
|
12
12
|
confirm: schemaConfirm,
|
|
@@ -27,7 +27,7 @@ export function registerWishlistTools(server, client) {
|
|
|
27
27
|
title: 'Remove a beer from your wishlist',
|
|
28
28
|
description: 'Remove a beer from YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview ' +
|
|
29
29
|
'and makes NO network call; with confirm: true it removes. Writes to your account.',
|
|
30
|
-
annotations: toolAnnotations({ title: 'Remove a beer from your wishlist', readOnly: false, idempotent: true, openWorld: true }),
|
|
30
|
+
annotations: toolAnnotations({ title: 'Remove a beer from your wishlist', readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
31
31
|
inputSchema: z.object({
|
|
32
32
|
bid: BidSchema,
|
|
33
33
|
confirm: schemaConfirm,
|
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 = '2.1.
|
|
6
|
+
export const VERSION = '2.1.2'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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>",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^
|
|
48
|
+
"@chrischall/mcp-utils": "^2.4.0",
|
|
49
49
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
50
50
|
"ai": "^7.0.19",
|
|
51
|
-
"dotenv": "^
|
|
52
|
-
"zod": "^4.6.
|
|
51
|
+
"dotenv": "^18.0.1",
|
|
52
|
+
"zod": "^4.6.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@modelcontextprotocol/client": "^2.0.0",
|
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": "2.1.
|
|
9
|
+
"version": "2.1.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "untappd-mcp",
|
|
14
|
-
"version": "2.1.
|
|
14
|
+
"version": "2.1.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|