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.
@@ -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.1"
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.1",
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.1",
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": "node",
5
- "args": ["${CLAUDE_PLUGIN_ROOT}/dist/index.js"],
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`; created owner-only — dir `0700`, file `0600`). Local/stdio only. |
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: 5
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, resolve3) {
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, resolve3);
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, resolve3) : NONE2;
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 ?? (resolve3 ? inst._zod.innerType : void 0);
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((resolve3, reject) => {
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
- resolve3();
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((resolve3, reject) => {
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 resolve3(manualInputRequiredValue(decoded));
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 resolve3(this._resolveNonCompleteResult(decoded, flow));
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) resolve3(parseResult.data);
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 = resolve3.call(this, root, ref);
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 resolve3(root, ref) {
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 resolve3(baseURI, relativeURI, options) {
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: resolve3,
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((resolve3, reject) => {
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
- resolve3();
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
- resolve3();
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((resolve3) => {
37846
+ return await new Promise((resolve4) => {
37798
37847
  const waiter = () => {
37799
37848
  clearTimeout(timer);
37800
- resolve3(true);
37849
+ resolve4(true);
37801
37850
  };
37802
37851
  const timer = setTimeout(() => {
37803
37852
  this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
37804
- resolve3(false);
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(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
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: 5
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, resolve3) {
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, resolve3);
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, resolve3) : NONE;
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 ?? (resolve3 ? inst._zod.innerType : void 0);
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.1";
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(homedir3(), ".untappd-mcp", "checkins.db");
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. Pre-existing loose entries from older versions
70
- * are tightened too; chmod failures (e.g. a shared dir the user doesn't own)
71
- * are best-effort, never fatal.
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
- bestEffortChmod(dir, 0o700);
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(homedir(), '.untappd-mcp', 'checkins.db');
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.1'; // x-release-please-version
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.1",
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.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.1",
9
+ "version": "2.3.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "untappd-mcp",
14
- "version": "2.3.1",
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. Logs in via the app's
9
- `xauth` endpoint (username + password → access token) and calls the same
10
- endpoints the iPad app uses.
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
- Set four environment variables (see the README for details):
15
-
16
- - `UNTAPPD_USERNAME`, `UNTAPPD_PASSWORD` — your Untappd login.
17
- - `UNTAPPD_CLIENT_ID`, `UNTAPPD_CLIENT_SECRET` — the Untappd mobile app client
18
- credentials (obtained by intercepting the app's traffic; see README).
19
-
20
- Run `untappd_healthcheck` to confirm login works.
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