untappd-mcp 2.3.1 → 2.3.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/.mcp.json +2 -2
- package/README.md +1 -1
- package/dist/bundle.js +136 -32
- package/dist/cache/db.js +15 -6
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/skills/untappd-mcp/SKILL.md +26 -10
|
@@ -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.3.
|
|
10
|
+
"version": "2.3.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.3.
|
|
18
|
+
"version": "2.3.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.3.
|
|
4
|
+
"version": "2.3.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/.mcp.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"untappd": {
|
|
4
|
-
"command": "
|
|
5
|
-
"args": ["
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": ["-y", "untappd-mcp"],
|
|
6
6
|
"env": {
|
|
7
7
|
"UNTAPPD_ACCESS_TOKEN": "${UNTAPPD_ACCESS_TOKEN}",
|
|
8
8
|
"UNTAPPD_USERNAME": "${UNTAPPD_USERNAME}",
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ it goes stale.
|
|
|
37
37
|
| `UNTAPPD_USER_AGENT` | no | Override the User-Agent (default mimics the app). |
|
|
38
38
|
| `UNTAPPD_TIMEZONE` | no | IANA timezone (e.g. `America/New_York`) that check-ins are stamped with when the call doesn't pass `timezone`. Set it when the server runs somewhere other than the drinker's zone (e.g. a hosted connector, usually UTC). Defaults to the server process's zone. |
|
|
39
39
|
| `UNTAPPD_PHOTO_DIR` | no | The directory `untappd_checkin`'s `photo_path` must be inside (several allowed, separated by `:`). Defaults to `~/Downloads/untappd-mcp` — put a photo there to attach it. Any other file is refused, so a prompt-injected shout or comment can't publish an arbitrary image from your disk. |
|
|
40
|
-
| `UNTAPPD_CACHE_DB` | no | Path to the local check-in cache SQLite file (default `~/.untappd-mcp/checkins.db
|
|
40
|
+
| `UNTAPPD_CACHE_DB` | no | Path to the local check-in cache SQLite file (default `~/.untappd-mcp/checkins.db`). The db file is always made owner-only (`0600`), and so are any directories the server creates plus the default `~/.untappd-mcp`; a directory you point this into that already exists keeps its permissions. Local/stdio only. |
|
|
41
41
|
|
|
42
42
|
Copy `.env.example` to `.env` and fill it in for local use.
|
|
43
43
|
|
package/dist/bundle.js
CHANGED
|
@@ -84,6 +84,7 @@ var require_util = __commonJS({
|
|
|
84
84
|
exports.explicitlyAborted = explicitlyAborted2;
|
|
85
85
|
exports.prefixIssues = prefixIssues2;
|
|
86
86
|
exports.unwrapMessage = unwrapMessage2;
|
|
87
|
+
exports.sizeCaps = sizeCaps2;
|
|
87
88
|
exports.attachSchema = attachSchema2;
|
|
88
89
|
exports.finalizeIssue = finalizeIssue2;
|
|
89
90
|
exports.getSizableOrigin = getSizableOrigin2;
|
|
@@ -618,6 +619,18 @@ var require_util = __commonJS({
|
|
|
618
619
|
function unwrapMessage2(message) {
|
|
619
620
|
return typeof message === "string" ? message : message?.message;
|
|
620
621
|
}
|
|
622
|
+
function sizeCaps2(defChecks) {
|
|
623
|
+
const caps = [];
|
|
624
|
+
for (const ch of defChecks ?? []) {
|
|
625
|
+
const d = ch._zod.def;
|
|
626
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
627
|
+
break;
|
|
628
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
629
|
+
if (max >= 0)
|
|
630
|
+
caps.push([max, ch]);
|
|
631
|
+
}
|
|
632
|
+
return caps;
|
|
633
|
+
}
|
|
621
634
|
function attachSchema2(issues, start, inst) {
|
|
622
635
|
var _a3;
|
|
623
636
|
for (let i = start; i < issues.length; i++) {
|
|
@@ -2282,7 +2295,7 @@ var require_versions = __commonJS({
|
|
|
2282
2295
|
exports.version = {
|
|
2283
2296
|
major: 4,
|
|
2284
2297
|
minor: 6,
|
|
2285
|
-
patch:
|
|
2298
|
+
patch: 6
|
|
2286
2299
|
};
|
|
2287
2300
|
(function() {
|
|
2288
2301
|
var keys = Object.getOwnPropertyNames(exports);
|
|
@@ -3116,6 +3129,31 @@ var require_schemas = __commonJS({
|
|
|
3116
3129
|
return payload;
|
|
3117
3130
|
};
|
|
3118
3131
|
});
|
|
3132
|
+
function capSize2(inst, size) {
|
|
3133
|
+
const caps = util.sizeCaps(inst._zod.def.checks);
|
|
3134
|
+
if (!caps.length)
|
|
3135
|
+
return;
|
|
3136
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
3137
|
+
const parse3 = inst._zod.parse;
|
|
3138
|
+
inst._zod.parse = (payload, ctx) => {
|
|
3139
|
+
const n = size(payload.value);
|
|
3140
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
3141
|
+
for (const [max, ch] of util.sizeCaps(inst._zod.def.checks)) {
|
|
3142
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
3143
|
+
const start = payload.issues.length;
|
|
3144
|
+
ch._zod.check(payload);
|
|
3145
|
+
payload.issues[start].continue = false;
|
|
3146
|
+
util.attachSchema(payload.issues, start, inst);
|
|
3147
|
+
return payload;
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return parse3(payload, ctx);
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
var arraySize2 = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
3155
|
+
var mapSize2 = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
3156
|
+
var setSize2 = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
3119
3157
|
function handleArrayResult2(result, final, index) {
|
|
3120
3158
|
if (result.issues.length) {
|
|
3121
3159
|
final.issues.push(...util.prefixIssues(index, result.issues));
|
|
@@ -3159,6 +3197,7 @@ var require_schemas = __commonJS({
|
|
|
3159
3197
|
}
|
|
3160
3198
|
return payload;
|
|
3161
3199
|
};
|
|
3200
|
+
capSize2(inst, arraySize2);
|
|
3162
3201
|
});
|
|
3163
3202
|
function handlePropertyResult2(result, final, key, input2, optin, optout) {
|
|
3164
3203
|
const isPresent = key in input2;
|
|
@@ -4115,6 +4154,7 @@ var require_schemas = __commonJS({
|
|
|
4115
4154
|
return Promise.all(proms).then(() => payload);
|
|
4116
4155
|
return payload;
|
|
4117
4156
|
};
|
|
4157
|
+
capSize2(inst, mapSize2);
|
|
4118
4158
|
});
|
|
4119
4159
|
function handleMapResult2(keyResult, valueResult, final, key, input2, inst, ctx) {
|
|
4120
4160
|
if (keyResult.issues.length) {
|
|
@@ -4181,6 +4221,7 @@ var require_schemas = __commonJS({
|
|
|
4181
4221
|
return Promise.all(proms).then(() => payload);
|
|
4182
4222
|
return payload;
|
|
4183
4223
|
};
|
|
4224
|
+
capSize2(inst, setSize2);
|
|
4184
4225
|
});
|
|
4185
4226
|
function handleSetResult2(result, final) {
|
|
4186
4227
|
if (result.issues.length) {
|
|
@@ -4851,7 +4892,7 @@ var require_memoizer = __commonJS({
|
|
|
4851
4892
|
var NONE2 = 0;
|
|
4852
4893
|
var ASSUMED2 = 1;
|
|
4853
4894
|
var PROVEN2 = 2;
|
|
4854
|
-
function isRecursive2(inst, stack,
|
|
4895
|
+
function isRecursive2(inst, stack, resolve4) {
|
|
4855
4896
|
const cached2 = recursive2.get(inst);
|
|
4856
4897
|
if (cached2 !== void 0)
|
|
4857
4898
|
return cached2 ? PROVEN2 : NONE2;
|
|
@@ -4861,7 +4902,7 @@ var require_memoizer = __commonJS({
|
|
|
4861
4902
|
let result = NONE2;
|
|
4862
4903
|
const check2 = (child) => {
|
|
4863
4904
|
if (result !== PROVEN2 && child?._zod) {
|
|
4864
|
-
const answer = isRecursive2(child, stack,
|
|
4905
|
+
const answer = isRecursive2(child, stack, resolve4);
|
|
4865
4906
|
if (answer > result)
|
|
4866
4907
|
result = answer;
|
|
4867
4908
|
}
|
|
@@ -4872,7 +4913,7 @@ var require_memoizer = __commonJS({
|
|
|
4872
4913
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
4873
4914
|
if (spread && !desc.enumerable)
|
|
4874
4915
|
continue;
|
|
4875
|
-
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack,
|
|
4916
|
+
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve4) : NONE2;
|
|
4876
4917
|
if (child > answer)
|
|
4877
4918
|
answer = child;
|
|
4878
4919
|
}
|
|
@@ -4936,7 +4977,7 @@ var require_memoizer = __commonJS({
|
|
|
4936
4977
|
break;
|
|
4937
4978
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
4938
4979
|
case "lazy": {
|
|
4939
|
-
const inner = def._cachedInner ?? (
|
|
4980
|
+
const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
|
|
4940
4981
|
merge2(inner ? isRecursive2(inner, stack, false) : ASSUMED2);
|
|
4941
4982
|
break;
|
|
4942
4983
|
}
|
|
@@ -16285,9 +16326,15 @@ ${code2}
|
|
|
16285
16326
|
doc.write(`}`);
|
|
16286
16327
|
return outputVar;
|
|
16287
16328
|
}
|
|
16329
|
+
function generateSizeGate2(doc, schema, size) {
|
|
16330
|
+
for (const [max, check2] of util.sizeCaps(schema._zod.def.checks)) {
|
|
16331
|
+
doc.write(`if (${size} > ${numericOperand2(max, check2._zod.def.check)}) return INVALID;`);
|
|
16332
|
+
}
|
|
16333
|
+
}
|
|
16288
16334
|
function generateArrayCheck2(doc, ctx, schema, accessor, buildsValue = true) {
|
|
16289
16335
|
const def = schema._zod.def;
|
|
16290
16336
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
16337
|
+
generateSizeGate2(doc, schema, `${accessor}.length`);
|
|
16291
16338
|
const outputVar = buildsValue ? newVar2(ctx) : null;
|
|
16292
16339
|
const iVar = newVar2(ctx);
|
|
16293
16340
|
const elemVar = newVar2(ctx);
|
|
@@ -16687,6 +16734,7 @@ ${code2}
|
|
|
16687
16734
|
function generateMapCheck2(doc, ctx, schema, accessor) {
|
|
16688
16735
|
const def = schema._zod.def;
|
|
16689
16736
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
16737
|
+
generateSizeGate2(doc, schema, `${accessor}.size`);
|
|
16690
16738
|
const outputVar = newVar2(ctx);
|
|
16691
16739
|
const kVar = newVar2(ctx);
|
|
16692
16740
|
const valVar = newVar2(ctx);
|
|
@@ -16703,6 +16751,7 @@ ${code2}
|
|
|
16703
16751
|
function generateSetCheck2(doc, ctx, schema, accessor) {
|
|
16704
16752
|
const def = schema._zod.def;
|
|
16705
16753
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
16754
|
+
generateSizeGate2(doc, schema, `${accessor}.size`);
|
|
16706
16755
|
const outputVar = newVar2(ctx);
|
|
16707
16756
|
const valVar = newVar2(ctx);
|
|
16708
16757
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -23400,7 +23449,7 @@ function declaredDialect(schema, remedy) {
|
|
|
23400
23449
|
throw new Error(`JSON Schema declares an unsupported dialect ("$schema": "${schema.$schema.slice(0, 200)}"). The default validator supports JSON Schema 2020-12, 2019-09, draft-07, and draft-06; ${remedy}`);
|
|
23401
23450
|
}
|
|
23402
23451
|
|
|
23403
|
-
// node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
|
|
23452
|
+
// node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
|
|
23404
23453
|
var z = __toESM(require_v4(), 1);
|
|
23405
23454
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
23406
23455
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
@@ -27799,14 +27848,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
27799
27848
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
27800
27849
|
}
|
|
27801
27850
|
function sleep(ms, signal) {
|
|
27802
|
-
return new Promise((
|
|
27851
|
+
return new Promise((resolve4, reject) => {
|
|
27803
27852
|
if (signal?.aborted) {
|
|
27804
27853
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
27805
27854
|
return;
|
|
27806
27855
|
}
|
|
27807
27856
|
const timer = setTimeout(() => {
|
|
27808
27857
|
signal?.removeEventListener("abort", onAbort);
|
|
27809
|
-
|
|
27858
|
+
resolve4();
|
|
27810
27859
|
}, ms);
|
|
27811
27860
|
const onAbort = () => {
|
|
27812
27861
|
clearTimeout(timer);
|
|
@@ -28605,7 +28654,7 @@ var Protocol = class {
|
|
|
28605
28654
|
const flowStartedAt = Date.now();
|
|
28606
28655
|
let onAbort;
|
|
28607
28656
|
let cleanupMessageId;
|
|
28608
|
-
return new Promise((
|
|
28657
|
+
return new Promise((resolve4, reject) => {
|
|
28609
28658
|
const earlyReject = (error62) => {
|
|
28610
28659
|
reject(error62);
|
|
28611
28660
|
};
|
|
@@ -28674,7 +28723,7 @@ var Protocol = class {
|
|
|
28674
28723
|
}
|
|
28675
28724
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
28676
28725
|
if (decoded.kind === "input_required") {
|
|
28677
|
-
if (options?.allowInputRequired === true) return
|
|
28726
|
+
if (options?.allowInputRequired === true) return resolve4(manualInputRequiredValue(decoded));
|
|
28678
28727
|
const flow = {
|
|
28679
28728
|
codec: codec2,
|
|
28680
28729
|
request,
|
|
@@ -28686,11 +28735,11 @@ var Protocol = class {
|
|
|
28686
28735
|
params
|
|
28687
28736
|
}, resultSchema, legOptions)
|
|
28688
28737
|
};
|
|
28689
|
-
return
|
|
28738
|
+
return resolve4(this._resolveNonCompleteResult(decoded, flow));
|
|
28690
28739
|
}
|
|
28691
28740
|
const result = decoded.result;
|
|
28692
28741
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
28693
|
-
if (parseResult.success)
|
|
28742
|
+
if (parseResult.success) resolve4(parseResult.data);
|
|
28694
28743
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
28695
28744
|
}, reject);
|
|
28696
28745
|
});
|
|
@@ -31606,7 +31655,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31606
31655
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
31607
31656
|
const schOrFunc = root.refs[ref];
|
|
31608
31657
|
if (schOrFunc) return schOrFunc;
|
|
31609
|
-
let _sch =
|
|
31658
|
+
let _sch = resolve4.call(this, root, ref);
|
|
31610
31659
|
if (_sch === void 0) {
|
|
31611
31660
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
31612
31661
|
const { schemaId } = this.opts;
|
|
@@ -31632,7 +31681,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31632
31681
|
function sameSchemaEnv(s1, s2) {
|
|
31633
31682
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
31634
31683
|
}
|
|
31635
|
-
function
|
|
31684
|
+
function resolve4(root, ref) {
|
|
31636
31685
|
let sch;
|
|
31637
31686
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
31638
31687
|
return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
|
|
@@ -32082,7 +32131,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
32082
32131
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
32083
32132
|
return uri;
|
|
32084
32133
|
}
|
|
32085
|
-
function
|
|
32134
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
32086
32135
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
32087
32136
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
32088
32137
|
schemelessOptions.skipEscape = true;
|
|
@@ -32256,7 +32305,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
32256
32305
|
const fastUri = {
|
|
32257
32306
|
SCHEMES,
|
|
32258
32307
|
normalize,
|
|
32259
|
-
resolve:
|
|
32308
|
+
resolve: resolve4,
|
|
32260
32309
|
resolveComponent,
|
|
32261
32310
|
equal,
|
|
32262
32311
|
serialize,
|
|
@@ -37718,7 +37767,7 @@ var StdioServerTransport = class {
|
|
|
37718
37767
|
}
|
|
37719
37768
|
send(message) {
|
|
37720
37769
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
37721
|
-
return new Promise((
|
|
37770
|
+
return new Promise((resolve4, reject) => {
|
|
37722
37771
|
const json2 = serializeMessage(message);
|
|
37723
37772
|
let settled = false;
|
|
37724
37773
|
const onError = (error62) => {
|
|
@@ -37733,14 +37782,14 @@ var StdioServerTransport = class {
|
|
|
37733
37782
|
settled = true;
|
|
37734
37783
|
this._stdout.off("error", onError);
|
|
37735
37784
|
this._stdout.off("drain", onDrain);
|
|
37736
|
-
|
|
37785
|
+
resolve4();
|
|
37737
37786
|
};
|
|
37738
37787
|
this._stdout.once("error", onError);
|
|
37739
37788
|
if (this._stdout.write(json2)) {
|
|
37740
37789
|
if (settled) return;
|
|
37741
37790
|
settled = true;
|
|
37742
37791
|
this._stdout.off("error", onError);
|
|
37743
|
-
|
|
37792
|
+
resolve4();
|
|
37744
37793
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
37745
37794
|
});
|
|
37746
37795
|
}
|
|
@@ -37794,14 +37843,14 @@ var StdioConnectionChannel = class {
|
|
|
37794
37843
|
*/
|
|
37795
37844
|
async whenRequestsAnswered(timeoutMs) {
|
|
37796
37845
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
37797
|
-
return await new Promise((
|
|
37846
|
+
return await new Promise((resolve4) => {
|
|
37798
37847
|
const waiter = () => {
|
|
37799
37848
|
clearTimeout(timer);
|
|
37800
|
-
|
|
37849
|
+
resolve4(true);
|
|
37801
37850
|
};
|
|
37802
37851
|
const timer = setTimeout(() => {
|
|
37803
37852
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
37804
|
-
|
|
37853
|
+
resolve4(false);
|
|
37805
37854
|
}, timeoutMs);
|
|
37806
37855
|
this._drainWaiters.push(waiter);
|
|
37807
37856
|
});
|
|
@@ -38216,7 +38265,7 @@ var API_KEY_RE = new RegExp([
|
|
|
38216
38265
|
// webhook signing secret (Stripe-style)
|
|
38217
38266
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
38218
38267
|
var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
38219
|
-
var QUERY_SECRET_RE = new RegExp(`([
|
|
38268
|
+
var QUERY_SECRET_RE = new RegExp(`([?&#;](?:(?:access|refresh|id|auth|session|csrf|xsrf|api)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|secret|token|auth|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38220
38269
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
38221
38270
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
38222
38271
|
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -38227,6 +38276,8 @@ var JSON_SECRET_KEYS = [
|
|
|
38227
38276
|
"api[_-]?secret",
|
|
38228
38277
|
"(?:x[_-])?api[_-]?key",
|
|
38229
38278
|
"private[_-]?key",
|
|
38279
|
+
"aws[_-]?secret[_-]?access[_-]?key",
|
|
38280
|
+
// fleet-audit#1297
|
|
38230
38281
|
"code[_-]?verifier",
|
|
38231
38282
|
// PKCE; a bare `verifier` key is too generic for JSON
|
|
38232
38283
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
@@ -38924,6 +38975,7 @@ __export(util_exports, {
|
|
|
38924
38975
|
required: () => required,
|
|
38925
38976
|
safeExtend: () => safeExtend,
|
|
38926
38977
|
shallowClone: () => shallowClone,
|
|
38978
|
+
sizeCaps: () => sizeCaps,
|
|
38927
38979
|
slugify: () => slugify,
|
|
38928
38980
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
38929
38981
|
toZod: () => toZod,
|
|
@@ -39443,6 +39495,18 @@ function prefixIssues(path, issues) {
|
|
|
39443
39495
|
function unwrapMessage(message) {
|
|
39444
39496
|
return typeof message === "string" ? message : message?.message;
|
|
39445
39497
|
}
|
|
39498
|
+
function sizeCaps(defChecks) {
|
|
39499
|
+
const caps = [];
|
|
39500
|
+
for (const ch of defChecks ?? []) {
|
|
39501
|
+
const d = ch._zod.def;
|
|
39502
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
39503
|
+
break;
|
|
39504
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
39505
|
+
if (max >= 0)
|
|
39506
|
+
caps.push([max, ch]);
|
|
39507
|
+
}
|
|
39508
|
+
return caps;
|
|
39509
|
+
}
|
|
39446
39510
|
function attachSchema(issues, start, inst) {
|
|
39447
39511
|
var _a3;
|
|
39448
39512
|
for (let i = start; i < issues.length; i++) {
|
|
@@ -40863,7 +40927,7 @@ ${content.join("\n")}
|
|
|
40863
40927
|
var version = {
|
|
40864
40928
|
major: 4,
|
|
40865
40929
|
minor: 6,
|
|
40866
|
-
patch:
|
|
40930
|
+
patch: 6
|
|
40867
40931
|
};
|
|
40868
40932
|
|
|
40869
40933
|
// node_modules/zod/v4/core/schemas.js
|
|
@@ -41619,6 +41683,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
|
41619
41683
|
return payload;
|
|
41620
41684
|
};
|
|
41621
41685
|
});
|
|
41686
|
+
function capSize(inst, size) {
|
|
41687
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
41688
|
+
if (!caps.length)
|
|
41689
|
+
return;
|
|
41690
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
41691
|
+
const parse3 = inst._zod.parse;
|
|
41692
|
+
inst._zod.parse = (payload, ctx) => {
|
|
41693
|
+
const n = size(payload.value);
|
|
41694
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
41695
|
+
for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
|
|
41696
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
41697
|
+
const start = payload.issues.length;
|
|
41698
|
+
ch._zod.check(payload);
|
|
41699
|
+
payload.issues[start].continue = false;
|
|
41700
|
+
attachSchema(payload.issues, start, inst);
|
|
41701
|
+
return payload;
|
|
41702
|
+
}
|
|
41703
|
+
}
|
|
41704
|
+
}
|
|
41705
|
+
return parse3(payload, ctx);
|
|
41706
|
+
};
|
|
41707
|
+
}
|
|
41708
|
+
var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
41709
|
+
var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
41710
|
+
var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
41622
41711
|
function handleArrayResult(result, final, index) {
|
|
41623
41712
|
if (result.issues.length) {
|
|
41624
41713
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -41662,6 +41751,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
|
41662
41751
|
}
|
|
41663
41752
|
return payload;
|
|
41664
41753
|
};
|
|
41754
|
+
capSize(inst, arraySize);
|
|
41665
41755
|
});
|
|
41666
41756
|
function handlePropertyResult(result, final, key, input2, optin, optout) {
|
|
41667
41757
|
const isPresent = key in input2;
|
|
@@ -42618,6 +42708,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
42618
42708
|
return Promise.all(proms).then(() => payload);
|
|
42619
42709
|
return payload;
|
|
42620
42710
|
};
|
|
42711
|
+
capSize(inst, mapSize);
|
|
42621
42712
|
});
|
|
42622
42713
|
function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
|
|
42623
42714
|
if (keyResult.issues.length) {
|
|
@@ -42684,6 +42775,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
|
42684
42775
|
return Promise.all(proms).then(() => payload);
|
|
42685
42776
|
return payload;
|
|
42686
42777
|
};
|
|
42778
|
+
capSize(inst, setSize);
|
|
42687
42779
|
});
|
|
42688
42780
|
function handleSetResult(result, final) {
|
|
42689
42781
|
if (result.issues.length) {
|
|
@@ -43299,7 +43391,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
43299
43391
|
var NONE = 0;
|
|
43300
43392
|
var ASSUMED = 1;
|
|
43301
43393
|
var PROVEN = 2;
|
|
43302
|
-
function isRecursive(inst, stack,
|
|
43394
|
+
function isRecursive(inst, stack, resolve4) {
|
|
43303
43395
|
const cached2 = recursive.get(inst);
|
|
43304
43396
|
if (cached2 !== void 0)
|
|
43305
43397
|
return cached2 ? PROVEN : NONE;
|
|
@@ -43309,7 +43401,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
43309
43401
|
let result = NONE;
|
|
43310
43402
|
const check2 = (child) => {
|
|
43311
43403
|
if (result !== PROVEN && child?._zod) {
|
|
43312
|
-
const answer = isRecursive(child, stack,
|
|
43404
|
+
const answer = isRecursive(child, stack, resolve4);
|
|
43313
43405
|
if (answer > result)
|
|
43314
43406
|
result = answer;
|
|
43315
43407
|
}
|
|
@@ -43320,7 +43412,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
43320
43412
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
43321
43413
|
if (spread && !desc.enumerable)
|
|
43322
43414
|
continue;
|
|
43323
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
43415
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
|
|
43324
43416
|
if (child > answer)
|
|
43325
43417
|
answer = child;
|
|
43326
43418
|
}
|
|
@@ -43384,7 +43476,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
43384
43476
|
break;
|
|
43385
43477
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
43386
43478
|
case "lazy": {
|
|
43387
|
-
const inner = def._cachedInner ?? (
|
|
43479
|
+
const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
|
|
43388
43480
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
43389
43481
|
break;
|
|
43390
43482
|
}
|
|
@@ -52214,9 +52306,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
52214
52306
|
doc.write(`}`);
|
|
52215
52307
|
return outputVar;
|
|
52216
52308
|
}
|
|
52309
|
+
function generateSizeGate(doc, schema, size) {
|
|
52310
|
+
for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
|
|
52311
|
+
doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
|
|
52312
|
+
}
|
|
52313
|
+
}
|
|
52217
52314
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
52218
52315
|
const def = schema._zod.def;
|
|
52219
52316
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
52317
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
52220
52318
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
52221
52319
|
const iVar = newVar(ctx);
|
|
52222
52320
|
const elemVar = newVar(ctx);
|
|
@@ -52616,6 +52714,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
52616
52714
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
52617
52715
|
const def = schema._zod.def;
|
|
52618
52716
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
52717
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
52619
52718
|
const outputVar = newVar(ctx);
|
|
52620
52719
|
const kVar = newVar(ctx);
|
|
52621
52720
|
const valVar = newVar(ctx);
|
|
@@ -52632,6 +52731,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
52632
52731
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
52633
52732
|
const def = schema._zod.def;
|
|
52634
52733
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
52734
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
52635
52735
|
const outputVar = newVar(ctx);
|
|
52636
52736
|
const valVar = newVar(ctx);
|
|
52637
52737
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -58958,7 +59058,7 @@ function toolAnnotations(opts = {}) {
|
|
|
58958
59058
|
}
|
|
58959
59059
|
|
|
58960
59060
|
// src/version.ts
|
|
58961
|
-
var VERSION = "2.3.
|
|
59061
|
+
var VERSION = "2.3.2";
|
|
58962
59062
|
|
|
58963
59063
|
// src/client.ts
|
|
58964
59064
|
import { dirname as dirname2, join as join4 } from "path";
|
|
@@ -61816,7 +61916,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
61816
61916
|
// src/cache/db.ts
|
|
61817
61917
|
import { DatabaseSync } from "node:sqlite";
|
|
61818
61918
|
import { chmodSync, closeSync as closeSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, openSync as openSync2 } from "node:fs";
|
|
61819
|
-
import { dirname as dirname3, join as join6 } from "node:path";
|
|
61919
|
+
import { dirname as dirname3, join as join6, resolve as resolve3 } from "node:path";
|
|
61820
61920
|
import { homedir as homedir3 } from "node:os";
|
|
61821
61921
|
var NodeSqlDriver = class {
|
|
61822
61922
|
constructor(db) {
|
|
@@ -61869,8 +61969,9 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
|
|
|
61869
61969
|
};
|
|
61870
61970
|
function preparePrivateFile(path) {
|
|
61871
61971
|
const dir = dirname3(path);
|
|
61972
|
+
const dirExisted = existsSync2(dir);
|
|
61872
61973
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
61873
|
-
bestEffortChmod(dir, 448);
|
|
61974
|
+
if (!dirExisted || resolve3(dir) === resolve3(defaultCacheDir())) bestEffortChmod(dir, 448);
|
|
61874
61975
|
if (!existsSync2(path)) closeSync2(openSync2(path, "a", 384));
|
|
61875
61976
|
bestEffortChmod(path, 384);
|
|
61876
61977
|
}
|
|
@@ -61886,7 +61987,10 @@ function bestEffortChmod(path, mode) {
|
|
|
61886
61987
|
}
|
|
61887
61988
|
}
|
|
61888
61989
|
function defaultCachePath() {
|
|
61889
|
-
return readEnvVar("UNTAPPD_CACHE_DB") ?? join6(
|
|
61990
|
+
return readEnvVar("UNTAPPD_CACHE_DB") ?? join6(defaultCacheDir(), "checkins.db");
|
|
61991
|
+
}
|
|
61992
|
+
function defaultCacheDir() {
|
|
61993
|
+
return join6(homedir3(), ".untappd-mcp");
|
|
61890
61994
|
}
|
|
61891
61995
|
|
|
61892
61996
|
// src/index.ts
|
package/dist/cache/db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import { chmodSync, closeSync, existsSync, mkdirSync, openSync } from 'node:fs';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { readEnvVar } from '@chrischall/mcp-utils';
|
|
6
6
|
import { CheckinStoreCore, LocalCacheStore } from './store.js';
|
|
@@ -66,14 +66,19 @@ export class CheckinCache extends LocalCacheStore {
|
|
|
66
66
|
/**
|
|
67
67
|
* Create the cache's directory (0700 — `mode` applies to every dir mkdir
|
|
68
68
|
* creates) and the db file itself (0600) BEFORE SQLite opens it, so the file is
|
|
69
|
-
* never briefly umask-readable.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* never briefly umask-readable. The db file is always tightened, and so is the
|
|
70
|
+
* server's own default directory (`~/.untappd-mcp`, which an older version may
|
|
71
|
+
* have left loose). A directory the user pointed `UNTAPPD_CACHE_DB` into that
|
|
72
|
+
* already existed is left as it is: it may hold other files (a shared Dropbox
|
|
73
|
+
* folder), and making it owner-only would break access to them. chmod failures
|
|
74
|
+
* (e.g. a shared dir the user doesn't own) are best-effort, never fatal.
|
|
72
75
|
*/
|
|
73
76
|
function preparePrivateFile(path) {
|
|
74
77
|
const dir = dirname(path);
|
|
78
|
+
const dirExisted = existsSync(dir);
|
|
75
79
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
76
|
-
|
|
80
|
+
if (!dirExisted || resolve(dir) === resolve(defaultCacheDir()))
|
|
81
|
+
bestEffortChmod(dir, 0o700);
|
|
77
82
|
if (!existsSync(path))
|
|
78
83
|
closeSync(openSync(path, 'a', 0o600));
|
|
79
84
|
bestEffortChmod(path, 0o600);
|
|
@@ -95,8 +100,12 @@ function bestEffortChmod(path, mode) {
|
|
|
95
100
|
}
|
|
96
101
|
/** Default on-disk cache path: `$UNTAPPD_CACHE_DB` or `~/.untappd-mcp/checkins.db`. */
|
|
97
102
|
export function defaultCachePath() {
|
|
98
|
-
return readEnvVar('UNTAPPD_CACHE_DB') ?? join(
|
|
103
|
+
return readEnvVar('UNTAPPD_CACHE_DB') ?? join(defaultCacheDir(), 'checkins.db');
|
|
99
104
|
}
|
|
100
105
|
// Re-export the shared types/helpers tests and tools import from here so their
|
|
101
106
|
// existing import paths keep working.
|
|
102
107
|
export { mapCheckinRow, } from './store.js';
|
|
108
|
+
/** The directory the server owns by default: `~/.untappd-mcp`. */
|
|
109
|
+
function defaultCacheDir() {
|
|
110
|
+
return join(homedir(), '.untappd-mcp');
|
|
111
|
+
}
|
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.3.
|
|
6
|
+
export const VERSION = '2.3.2'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "untappd-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.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,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^3.0.
|
|
48
|
+
"@chrischall/mcp-utils": "^3.0.3",
|
|
49
49
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
50
50
|
"dotenv": "^18.0.1",
|
|
51
51
|
"zod": "^4.6.5"
|
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.3.
|
|
9
|
+
"version": "2.3.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "untappd-mcp",
|
|
14
|
-
"version": "2.3.
|
|
14
|
+
"version": "2.3.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -5,19 +5,35 @@ description: Search Untappd beers, breweries, and venues; read user profiles, ch
|
|
|
5
5
|
|
|
6
6
|
# Untappd MCP
|
|
7
7
|
|
|
8
|
-
Talks to Untappd's mobile (v4) API using your own account
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Talks to Untappd's mobile (v4) API using your own account, calling the same
|
|
9
|
+
endpoints the iPad app uses. Authenticates with an access token you already
|
|
10
|
+
hold, or mints one via the app's `xauth` endpoint (username + password).
|
|
11
11
|
|
|
12
12
|
## Setup
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
Environment variables (the README has the full table and how to capture the
|
|
15
|
+
client credentials):
|
|
16
|
+
|
|
17
|
+
| Variable | Needed | Purpose |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `UNTAPPD_ACCESS_TOKEN` | no | A token you already hold. With it, **no password is needed** — xauth is skipped. When it expires, the server says so; supply a fresh one. |
|
|
20
|
+
| `UNTAPPD_USERNAME`, `UNTAPPD_PASSWORD` | if no token | Your Untappd login, used only to mint a token. Accounts with 2FA can't log in this way. |
|
|
21
|
+
| `UNTAPPD_CLIENT_ID`, `UNTAPPD_CLIENT_SECRET` | for login & writes | The Untappd mobile app's client credentials. A pre-seeded token can read without them. |
|
|
22
|
+
| `UNTAPPD_TIMEZONE` | no | IANA zone check-ins are stamped with when a call omits `timezone`. Defaults to the server's zone. |
|
|
23
|
+
| `UNTAPPD_PHOTO_DIR` | no | Directory (or `:`-separated list) a check-in's `photo_path` must be inside. Default `~/Downloads/untappd-mcp`. |
|
|
24
|
+
| `UNTAPPD_CACHE_DB` | no | Local check-in cache file (default `~/.untappd-mcp/checkins.db`). Local/stdio only. |
|
|
25
|
+
|
|
26
|
+
Two things the server enforces that you should plan around:
|
|
27
|
+
|
|
28
|
+
- **Photos:** `untappd_checkin`'s `photo_path` must be a file inside
|
|
29
|
+
`UNTAPPD_PHOTO_DIR` (default `~/Downloads/untappd-mcp`); anything else (e.g.
|
|
30
|
+
`~/Pictures/beer.jpg`) is refused. Ask the user to move the photo there first.
|
|
31
|
+
- **Time zone:** on a hosted connector the server usually runs in UTC, so pass
|
|
32
|
+
`timezone` (the drinker's IANA zone, e.g. `America/New_York`) on
|
|
33
|
+
`untappd_checkin` unless `UNTAPPD_TIMEZONE` is set — otherwise the check-in is
|
|
34
|
+
stamped in the server's zone.
|
|
35
|
+
|
|
36
|
+
Run `untappd_healthcheck` to confirm auth works.
|
|
21
37
|
|
|
22
38
|
## Read tools
|
|
23
39
|
|