untappd-mcp 2.1.0 → 2.1.1

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.1.0"
10
+ "version": "2.1.1"
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.0",
18
+ "version": "2.1.1",
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.0",
4
+ "version": "2.1.1",
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
@@ -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;
@@ -57749,17 +57763,73 @@ Hint: ${err.hint}`);
57749
57763
  }
57750
57764
  throw err;
57751
57765
  }
57766
+ function mcpRequestFrom(args) {
57767
+ const ctx = args.at(-1);
57768
+ if (typeof ctx !== "object" || ctx === null)
57769
+ return void 0;
57770
+ const req = ctx.mcpReq;
57771
+ if (typeof req !== "object" || req === null)
57772
+ return void 0;
57773
+ return typeof req.notify === "function" ? req : void 0;
57774
+ }
57775
+ function callSignalFrom(args) {
57776
+ const ctx = args.at(-1);
57777
+ if (typeof ctx !== "object" || ctx === null)
57778
+ return void 0;
57779
+ const req = ctx.mcpReq;
57780
+ if (typeof req !== "object" || req === null)
57781
+ return void 0;
57782
+ const signal = req.signal;
57783
+ return signal instanceof AbortSignal ? signal : void 0;
57784
+ }
57785
+ function declaredCapabilitiesFrom(server) {
57786
+ const inner = server.server;
57787
+ if (typeof inner !== "object" || inner === null)
57788
+ return void 0;
57789
+ const read = inner.getClientCapabilities;
57790
+ if (typeof read !== "function")
57791
+ return void 0;
57792
+ try {
57793
+ const declared = read.call(inner);
57794
+ return typeof declared === "object" && declared !== null ? declared : void 0;
57795
+ } catch {
57796
+ return void 0;
57797
+ }
57798
+ }
57752
57799
  function surfaceToolHints(server) {
57753
57800
  const register2 = server.registerTool.bind(server);
57754
- server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => {
57755
- let result;
57756
- try {
57757
- result = cb(...args);
57758
- } catch (err) {
57759
- return hintResultOrRethrow(err);
57760
- }
57761
- return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
57762
- });
57801
+ server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
57802
+ // THE CALLER'S CANCELLATION, made ambient for the whole handler
57803
+ // (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
57804
+ // measured against @modelcontextprotocol/server 2.0.0, a cancelled
57805
+ // call aborts `ctx.mcpReq.signal` with the caller's reason and the
57806
+ // handler runs to completion regardless — so the HTTP request stays
57807
+ // in flight, the child keeps burning metered CPU, and the upstream
57808
+ // keeps being hit for somebody who has gone. claude.ai sent 101
57809
+ // cancellations in the week to 2026-09-20.
57810
+ //
57811
+ // Here because this is the one wrapper every tool already passes
57812
+ // through: threading the signal by hand would mean editing several
57813
+ // hundred handlers and missing exactly the ones nobody edits.
57814
+ withCallSignal(callSignalFrom(args), () => (
57815
+ // WHAT THE CALLER CAN DO, made ambient for the same reason and in
57816
+ // the same place (`caller/index.ts`). Only the 2025-era half needs
57817
+ // the wrapper — a 2026-07-28 request answers for itself off its own
57818
+ // envelope — but a guarded tool that cannot tell whether the caller
57819
+ // can be shown a prompt returns one the SDK then refuses to deliver,
57820
+ // and that refusal reaches the caller as an unexplained protocol
57821
+ // error it had no chance to catch.
57822
+ withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
57823
+ let result;
57824
+ try {
57825
+ result = cb(...args);
57826
+ } catch (err) {
57827
+ return hintResultOrRethrow(err);
57828
+ }
57829
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
57830
+ })
57831
+ ), mcpRequestFrom(args))
57832
+ ));
57763
57833
  }
57764
57834
  async function createMcpServer(opts) {
57765
57835
  const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
@@ -57912,12 +57982,13 @@ function toolAnnotations(opts = {}) {
57912
57982
  ...opts.title !== void 0 ? { title: opts.title } : {},
57913
57983
  readOnlyHint: opts.readOnly ?? true,
57914
57984
  ...opts.idempotent !== void 0 ? { idempotentHint: opts.idempotent } : {},
57985
+ ...opts.destructive !== void 0 ? { destructiveHint: opts.destructive } : {},
57915
57986
  ...opts.openWorld !== void 0 ? { openWorldHint: opts.openWorld } : {}
57916
57987
  };
57917
57988
  }
57918
57989
 
57919
57990
  // src/version.ts
57920
- var VERSION = "2.1.0";
57991
+ var VERSION = "2.1.1";
57921
57992
 
57922
57993
  // src/client.ts
57923
57994
  import { dirname, join } from "path";
@@ -59486,7 +59557,11 @@ function registerFriendActionTools(server, client2) {
59486
59557
  {
59487
59558
  title: action.title,
59488
59559
  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
- annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true }),
59560
+ // All four reach ANOTHER PERSON — the description says so itself: "affects
59561
+ // a real relationship with another person". A friend request cannot be
59562
+ // un-sent, and a rejection cannot be un-rejected, so none of them has an
59563
+ // inverse in this tool set.
59564
+ annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true, destructive: true }),
59490
59565
  inputSchema: external_exports.object({
59491
59566
  target_uid: TargetUidSchema,
59492
59567
  confirm: schemaConfirm
@@ -59516,7 +59591,7 @@ function registerWishlistTools(server, client2) {
59516
59591
  {
59517
59592
  title: "Add a beer to your wishlist",
59518
59593
  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 }),
59594
+ annotations: toolAnnotations({ title: "Add a beer to your wishlist", readOnly: false, idempotent: true, openWorld: true, destructive: false }),
59520
59595
  inputSchema: external_exports.object({
59521
59596
  bid: BidSchema2,
59522
59597
  confirm: schemaConfirm
@@ -59540,7 +59615,7 @@ function registerWishlistTools(server, client2) {
59540
59615
  {
59541
59616
  title: "Remove a beer from your wishlist",
59542
59617
  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 }),
59618
+ annotations: toolAnnotations({ title: "Remove a beer from your wishlist", readOnly: false, idempotent: true, openWorld: true, destructive: false }),
59544
59619
  inputSchema: external_exports.object({
59545
59620
  bid: BidSchema2,
59546
59621
  confirm: schemaConfirm
@@ -59582,7 +59657,7 @@ function registerCheckinTools(server, client2) {
59582
59657
  {
59583
59658
  title: "Toast an Untappd check-in",
59584
59659
  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 }),
59660
+ annotations: toolAnnotations({ title: "Toast an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
59586
59661
  inputSchema: external_exports.object({
59587
59662
  checkin_id: CheckinIdSchema,
59588
59663
  confirm: schemaConfirm
@@ -59606,7 +59681,7 @@ function registerCheckinTools(server, client2) {
59606
59681
  {
59607
59682
  title: "Comment on an Untappd check-in",
59608
59683
  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 }),
59684
+ annotations: toolAnnotations({ title: "Comment on an Untappd check-in", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
59610
59685
  inputSchema: external_exports.object({
59611
59686
  checkin_id: CheckinIdSchema,
59612
59687
  comment: external_exports.string().min(1).max(2e3).describe("Comment text to post"),
@@ -59632,7 +59707,7 @@ function registerCheckinTools(server, client2) {
59632
59707
  {
59633
59708
  title: "Delete a comment from an Untappd check-in",
59634
59709
  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 }),
59710
+ annotations: toolAnnotations({ title: "Delete a comment from an Untappd check-in", readOnly: false, idempotent: true, openWorld: true, destructive: true }),
59636
59711
  inputSchema: external_exports.object({
59637
59712
  comment_id: external_exports.number().int().positive().describe("Untappd comment id (from a check-in's comments.items)"),
59638
59713
  confirm: schemaConfirm
@@ -59656,7 +59731,7 @@ function registerCheckinTools(server, client2) {
59656
59731
  {
59657
59732
  title: "Delete an Untappd check-in",
59658
59733
  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 }),
59734
+ annotations: toolAnnotations({ title: "Delete an Untappd check-in", readOnly: false, idempotent: true, openWorld: true, destructive: true }),
59660
59735
  inputSchema: external_exports.object({
59661
59736
  checkin_id: CheckinIdSchema,
59662
59737
  confirm: schemaConfirm
@@ -59680,7 +59755,7 @@ function registerCheckinTools(server, client2) {
59680
59755
  {
59681
59756
  title: "Check in a beer on Untappd",
59682
59757
  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 }),
59758
+ annotations: toolAnnotations({ title: "Check in a beer on Untappd", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
59684
59759
  inputSchema: external_exports.object({
59685
59760
  bid: external_exports.number().int().positive().describe("Untappd beer id to check in (from untappd_search_beer)"),
59686
59761
  rating: RatingSchema.optional().describe("Rating 0\u20135 in 0.25 increments (omit for no rating)"),
@@ -60194,7 +60269,7 @@ function registerCacheTools(server, client2, cacheProvider) {
60194
60269
  {
60195
60270
  title: "Sync a user's check-ins into the cache",
60196
60271
  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 }),
60272
+ annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
60198
60273
  inputSchema: external_exports.object({
60199
60274
  username: UsernameArg2,
60200
60275
  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 +60290,7 @@ function registerCacheTools(server, client2, cacheProvider) {
60215
60290
  {
60216
60291
  title: "Sync a user's complete distinct-beers list into the cache",
60217
60292
  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 }),
60293
+ annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
60219
60294
  inputSchema: external_exports.object({
60220
60295
  username: UsernameArg2,
60221
60296
  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 +60424,7 @@ function registerCacheTools(server, client2, cacheProvider) {
60349
60424
  {
60350
60425
  title: "Top-rated beers a user has NOT had, from a candidate list",
60351
60426
  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 }),
60427
+ annotations: toolAnnotations({ title: "Top-rated beers a user has NOT had, from a candidate list", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
60353
60428
  inputSchema: external_exports.object({
60354
60429
  username: UsernameArg2,
60355
60430
  bids: external_exports.array(external_exports.number().int().positive()).min(1).max(100).describe("Candidate beer ids (1\u2013100)"),
@@ -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)'),
@@ -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)'),
@@ -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
- annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true }),
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,
@@ -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.0'; // x-release-please-version
6
+ export const VERSION = '2.1.1'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untappd-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
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": "^1.0.0",
48
+ "@chrischall/mcp-utils": "^2.2.0",
49
49
  "@modelcontextprotocol/server": "^2.0.0",
50
50
  "ai": "^7.0.19",
51
51
  "dotenv": "^17.4.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.0",
9
+ "version": "2.1.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "untappd-mcp",
14
- "version": "2.1.0",
14
+ "version": "2.1.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },