untappd-mcp 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0.0"
10
+ "version": "2.1.0"
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.0.0",
18
+ "version": "2.1.0",
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.0.0",
4
+ "version": "2.1.0",
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
@@ -24476,6 +24476,7 @@ function missingClientCapabilities(required2, declared) {
24476
24476
  return Object.keys(missing).length > 0 ? missing : void 0;
24477
24477
  }
24478
24478
  var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
24479
+ var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
24479
24480
  function isModernProtocolVersion(version2) {
24480
24481
  return version2 >= FIRST_MODERN_PROTOCOL_VERSION;
24481
24482
  }
@@ -27034,6 +27035,25 @@ function isSpecNotificationMethod(method) {
27034
27035
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
27035
27036
  }
27036
27037
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
27038
+ function isPlainObject$3(value) {
27039
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27040
+ }
27041
+ function requestMetaOf(params) {
27042
+ if (!isPlainObject$3(params)) return void 0;
27043
+ const meta3 = params["_meta"];
27044
+ return isPlainObject$3(meta3) ? meta3 : void 0;
27045
+ }
27046
+ function hasEnvelopeClaim(params) {
27047
+ const meta3 = requestMetaOf(params);
27048
+ return meta3 !== void 0 && PROTOCOL_VERSION_META_KEY in meta3;
27049
+ }
27050
+ function envelopeClaimVersion(params) {
27051
+ const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
27052
+ return typeof value === "string" ? value : void 0;
27053
+ }
27054
+ function validateEnvelopeMeta(meta3) {
27055
+ return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta3);
27056
+ }
27037
27057
  var schemas_exports = /* @__PURE__ */ __exportAll({
27038
27058
  AnnotationsSchema: () => AnnotationsSchema,
27039
27059
  AudioContentSchema: () => AudioContentSchema,
@@ -27384,6 +27404,45 @@ var LADDER_ERROR_HTTP_STATUS = {
27384
27404
  [ProtocolErrorCode.MissingRequiredClientCapability]: 400,
27385
27405
  [HEADER_MISMATCH_ERROR_CODE]: 400
27386
27406
  };
27407
+ function rejection(rung, cell, httpStatus, error62, settled) {
27408
+ return {
27409
+ kind: "reject",
27410
+ rung,
27411
+ cell,
27412
+ httpStatus,
27413
+ code: error62.code,
27414
+ message: error62.message,
27415
+ ...error62.data !== void 0 && { data: error62.data },
27416
+ settled
27417
+ };
27418
+ }
27419
+ function carriesValidModernEnvelopeClaim(params) {
27420
+ if (!hasEnvelopeClaim(params)) return false;
27421
+ const claimedVersion = envelopeClaimVersion(params);
27422
+ if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
27423
+ const meta3 = requestMetaOf(params);
27424
+ return meta3 !== void 0 && validateEnvelopeMeta(meta3).length === 0;
27425
+ }
27426
+ function modernOnlyStrictRejection(route, supportedVersions) {
27427
+ switch (route.reason) {
27428
+ case "http-method":
27429
+ return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
27430
+ case "batch":
27431
+ return rejection("jsonrpc-shape", "modern-only-batch-not-supported", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches are not supported by this endpoint"), true);
27432
+ case "response":
27433
+ return rejection("jsonrpc-shape", "modern-only-response-post", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC responses cannot be posted to this endpoint"), true);
27434
+ case "notification":
27435
+ return;
27436
+ case "initialize":
27437
+ case "no-claim": {
27438
+ const requested = route.requestedVersion;
27439
+ return rejection("era-classification", "modern-only-missing-envelope", 400, requested === void 0 ? new ProtocolError(ProtocolErrorCode.UnsupportedProtocolVersion, "Unsupported protocol version: the request did not name a protocol version", { supported: [...supportedVersions] }) : new UnsupportedProtocolVersionError({
27440
+ supported: [...supportedVersions],
27441
+ requested
27442
+ }), true);
27443
+ }
27444
+ }
27445
+ }
27387
27446
  function parseSchema(schema, data) {
27388
27447
  return z2.safeParse(schema, data);
27389
27448
  }
@@ -27945,6 +28004,9 @@ function withRequestStateValue(ctx, value) {
27945
28004
  };
27946
28005
  }
27947
28006
  var writeNegotiatedProtocolVersion;
28007
+ function setNegotiatedProtocolVersion(instance, version2) {
28008
+ writeNegotiatedProtocolVersion(instance, version2);
28009
+ }
27948
28010
  var Protocol = class {
27949
28011
  _transport;
27950
28012
  _requestMessageId = 0;
@@ -35888,6 +35950,190 @@ function getCompleter(schema) {
35888
35950
  return schema[COMPLETABLE_SYMBOL]?.complete;
35889
35951
  }
35890
35952
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
35953
+ function listenFilterAccepts(filter, event) {
35954
+ switch (event.kind) {
35955
+ case "tools_list_changed":
35956
+ return filter.toolsListChanged === true;
35957
+ case "prompts_list_changed":
35958
+ return filter.promptsListChanged === true;
35959
+ case "resources_list_changed":
35960
+ return filter.resourcesListChanged === true;
35961
+ case "resource_updated":
35962
+ return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
35963
+ }
35964
+ }
35965
+ function honoredSubset(requested, capabilities) {
35966
+ const honored = {};
35967
+ const allow = (bit) => capabilities === void 0 || bit === true;
35968
+ if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
35969
+ if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
35970
+ if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
35971
+ if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
35972
+ return honored;
35973
+ }
35974
+ var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
35975
+ function stampSubscriptionId(notification, subscriptionId) {
35976
+ return {
35977
+ method: notification.method,
35978
+ params: {
35979
+ ...notification.params,
35980
+ _meta: {
35981
+ ...notification.params?._meta,
35982
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId
35983
+ }
35984
+ }
35985
+ };
35986
+ }
35987
+ function parseListenFilter(message) {
35988
+ const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
35989
+ return outcome.ok ? outcome.value.params?.notifications : void 0;
35990
+ }
35991
+ var CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
35992
+ "notifications/tools/list_changed",
35993
+ "notifications/prompts/list_changed",
35994
+ "notifications/resources/list_changed",
35995
+ "notifications/resources/updated"
35996
+ ]);
35997
+ var StdioListenRouter = class {
35998
+ /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
35999
+ _subs = /* @__PURE__ */ new Map();
36000
+ /**
36001
+ * The serving instance's declared capabilities. Filled in by the entry
36002
+ * once the modern instance is constructed (the router is created before
36003
+ * the instance exists), so the acknowledged filter is narrowed against
36004
+ * what the server can actually deliver.
36005
+ */
36006
+ _serverCapabilities;
36007
+ /**
36008
+ * The serving instance's identity, stamped onto the graceful-close
36009
+ * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
36010
+ * `ResultMetaObject`). Handed over together with the capabilities.
36011
+ */
36012
+ _serverInfo;
36013
+ constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
36014
+ this._maxSubscriptions = _maxSubscriptions;
36015
+ this._serverCapabilities = serverCapabilities;
36016
+ this._serverInfo = serverInfo;
36017
+ }
36018
+ /**
36019
+ * Record the serving instance's declared capabilities and identity once
36020
+ * it has been constructed. Called by `serveStdio`'s connect path;
36021
+ * subsequent `serve()` calls narrow the honored filter against the
36022
+ * capabilities, and `teardownAll()` stamps the identity.
36023
+ */
36024
+ setServerCapabilities(capabilities, serverInfo) {
36025
+ this._serverCapabilities = capabilities;
36026
+ if (serverInfo !== void 0) this._serverInfo = serverInfo;
36027
+ }
36028
+ /** Whether `id` is an active listen subscription on this connection. */
36029
+ has(id) {
36030
+ return this._subs.has(id);
36031
+ }
36032
+ /**
36033
+ * Serve one inbound `subscriptions/listen` request: registers the
36034
+ * subscription and returns the stamped acknowledged notification (or, on
36035
+ * capacity / params rejection, the in-band JSON-RPC error response).
36036
+ *
36037
+ * @throws when called before {@linkcode setServerCapabilities} (or the
36038
+ * constructor) has supplied the serving instance's capabilities. Honoring a
36039
+ * filter without knowing the server's advertised capabilities would fail
36040
+ * open (deliver unadvertised types); the entry guarantees capabilities are
36041
+ * set before any listen request is routed here.
36042
+ */
36043
+ serve(message) {
36044
+ if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
36045
+ if (this._subs.size >= this._maxSubscriptions) return {
36046
+ jsonrpc: "2.0",
36047
+ id: message.id,
36048
+ error: {
36049
+ code: -32603,
36050
+ message: "Subscription limit reached"
36051
+ }
36052
+ };
36053
+ const filter = parseListenFilter(message);
36054
+ if (filter === void 0) return {
36055
+ jsonrpc: "2.0",
36056
+ id: message.id,
36057
+ error: {
36058
+ code: -32602,
36059
+ message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
36060
+ }
36061
+ };
36062
+ const honored = honoredSubset(filter, this._serverCapabilities);
36063
+ this._subs.set(message.id, honored);
36064
+ return stampSubscriptionId({
36065
+ method: "notifications/subscriptions/acknowledged",
36066
+ params: { notifications: honored }
36067
+ }, message.id);
36068
+ }
36069
+ /**
36070
+ * Tear down one subscription (inbound `notifications/cancelled`). Returns
36071
+ * `true` when a subscription was removed. After this call NOTHING further
36072
+ * is delivered for that subscription id (the post-cancel hardening).
36073
+ */
36074
+ cancel(id) {
36075
+ return this._subs.delete(id);
36076
+ }
36077
+ /**
36078
+ * Route an outbound notification through the active subscriptions.
36079
+ *
36080
+ * - For a subscription-gated change notification, returns one stamped copy
36081
+ * per subscription that opted in to it (an empty array means it is
36082
+ * dropped — the modern era never delivers an un-requested change type).
36083
+ * - For any other outbound message, returns `'passthrough'` (the entry
36084
+ * forwards it as-is).
36085
+ */
36086
+ routeOutbound(message) {
36087
+ if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
36088
+ const uriParam = message.params?.["uri"];
36089
+ const uri = typeof uriParam === "string" ? uriParam : void 0;
36090
+ const event = notificationToServerEvent(message.method, uri);
36091
+ const out = [];
36092
+ for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
36093
+ method: message.method,
36094
+ params: message.params ?? {}
36095
+ }, subscriptionId));
36096
+ return out;
36097
+ }
36098
+ /**
36099
+ * Server-side graceful teardown of every active subscription: returns the
36100
+ * empty `subscriptions/listen` JSON-RPC result for each subscription id —
36101
+ * the spec's graceful-close signal, `_meta` carrying the subscription id
36102
+ * and the serving instance's identity — for the entry to emit before
36103
+ * closing the wire. Clears the set so nothing further is delivered.
36104
+ */
36105
+ teardownAll() {
36106
+ const out = [];
36107
+ for (const id of this._subs.keys()) out.push({
36108
+ jsonrpc: "2.0",
36109
+ id,
36110
+ result: {
36111
+ resultType: "complete",
36112
+ _meta: {
36113
+ [SUBSCRIPTION_ID_META_KEY]: id,
36114
+ ...this._serverInfo !== void 0 && { [SERVER_INFO_META_KEY]: this._serverInfo }
36115
+ }
36116
+ }
36117
+ });
36118
+ this._subs.clear();
36119
+ return out;
36120
+ }
36121
+ };
36122
+ function notificationToServerEvent(method, uri) {
36123
+ switch (method) {
36124
+ case "notifications/tools/list_changed":
36125
+ return { kind: "tools_list_changed" };
36126
+ case "notifications/prompts/list_changed":
36127
+ return { kind: "prompts_list_changed" };
36128
+ case "notifications/resources/list_changed":
36129
+ return { kind: "resources_list_changed" };
36130
+ default:
36131
+ return {
36132
+ kind: "resource_updated",
36133
+ uri: uri ?? ""
36134
+ };
36135
+ }
36136
+ }
35891
36137
  var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
35892
36138
  var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
35893
36139
  function resolveLegacyShimOptions(options) {
@@ -36025,6 +36271,12 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
36025
36271
  var writeClientIdentity;
36026
36272
  var installDiscoverHandler;
36027
36273
  var readServerIdentity;
36274
+ function installModernOnlyHandlers(server, servedModernVersions) {
36275
+ installDiscoverHandler(server, servedModernVersions);
36276
+ }
36277
+ function serverIdentityOf(server) {
36278
+ return readServerIdentity(server);
36279
+ }
36028
36280
  var Server = class extends Protocol {
36029
36281
  _clientCapabilities;
36030
36282
  _clientVersion;
@@ -37299,6 +37551,400 @@ var StdioServerTransport = class {
37299
37551
  });
37300
37552
  }
37301
37553
  };
37554
+ var DISCARD_ANSWER_TIMEOUT_MS = 3e3;
37555
+ var StdioConnectionChannel = class {
37556
+ onclose;
37557
+ onerror;
37558
+ onmessage;
37559
+ _closed = false;
37560
+ /** Request ids the entry delivered to the instance that the instance has not yet answered. */
37561
+ _pendingRequests = /* @__PURE__ */ new Set();
37562
+ _drainWaiters = [];
37563
+ constructor(_wire, _onInstanceClose, _outboundIntercept) {
37564
+ this._wire = _wire;
37565
+ this._onInstanceClose = _onInstanceClose;
37566
+ this._outboundIntercept = _outboundIntercept;
37567
+ }
37568
+ async start() {
37569
+ }
37570
+ async send(message, options) {
37571
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
37572
+ const { id } = message;
37573
+ if (id !== void 0) this._settle(id);
37574
+ }
37575
+ if (this._closed) return;
37576
+ if (this._outboundIntercept?.(message) === "handled") return;
37577
+ return this._wire.send(message, options);
37578
+ }
37579
+ setProtocolVersion = (version2) => {
37580
+ this._wire.setProtocolVersion?.(version2);
37581
+ };
37582
+ /** Forwards one inbound message to the connected instance. */
37583
+ deliver(message, extra) {
37584
+ if (this._closed) return;
37585
+ if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
37586
+ else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
37587
+ const cancelledId = message.params?.requestId;
37588
+ if (cancelledId !== void 0) this._settle(cancelledId);
37589
+ }
37590
+ this.onmessage?.(message, extra);
37591
+ }
37592
+ /**
37593
+ * Resolves once every request delivered to the instance has been answered
37594
+ * through {@linkcode send}, settled by a delivered cancellation, or the
37595
+ * channel has been closed and nothing further can be answered. The wait is
37596
+ * bounded by `timeoutMs` as a backstop so no edge can hold the caller
37597
+ * indefinitely; resolves `false` only when the bound elapsed with requests
37598
+ * still unanswered. Used by the probe-discard path so a probe request the
37599
+ * entry accepted is never silently dropped.
37600
+ */
37601
+ async whenRequestsAnswered(timeoutMs) {
37602
+ if (this._closed || this._pendingRequests.size === 0) return true;
37603
+ return await new Promise((resolve) => {
37604
+ const waiter = () => {
37605
+ clearTimeout(timer);
37606
+ resolve(true);
37607
+ };
37608
+ const timer = setTimeout(() => {
37609
+ this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
37610
+ resolve(false);
37611
+ }, timeoutMs);
37612
+ this._drainWaiters.push(waiter);
37613
+ });
37614
+ }
37615
+ async close() {
37616
+ if (this._closed) return;
37617
+ this._closed = true;
37618
+ this._pendingRequests.clear();
37619
+ this._releaseDrainWaiters();
37620
+ try {
37621
+ this._onInstanceClose();
37622
+ } finally {
37623
+ this.onclose?.();
37624
+ }
37625
+ }
37626
+ _settle(id) {
37627
+ this._pendingRequests.delete(id);
37628
+ if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
37629
+ }
37630
+ _releaseDrainWaiters() {
37631
+ const waiters = this._drainWaiters;
37632
+ this._drainWaiters = [];
37633
+ for (const waiter of waiters) waiter();
37634
+ }
37635
+ };
37636
+ function classifyOpeningMessage(message) {
37637
+ const params = message.params;
37638
+ if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
37639
+ const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
37640
+ return {
37641
+ kind: "legacy",
37642
+ reason: "initialize",
37643
+ ...requestedVersion !== void 0 && { requestedVersion }
37644
+ };
37645
+ }
37646
+ if (!hasEnvelopeClaim(params)) return {
37647
+ kind: "legacy",
37648
+ reason: "no-claim"
37649
+ };
37650
+ const meta3 = requestMetaOf(params);
37651
+ const firstIssue = (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0];
37652
+ if (firstIssue !== void 0) return {
37653
+ kind: "invalid-envelope",
37654
+ issue: firstIssue
37655
+ };
37656
+ const claimedVersion = envelopeClaimVersion(params);
37657
+ if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
37658
+ kind: "unsupported-revision",
37659
+ requested: claimedVersion ?? "unknown"
37660
+ };
37661
+ return {
37662
+ kind: "modern",
37663
+ revision: claimedVersion,
37664
+ classification: {
37665
+ era: "modern",
37666
+ revision: claimedVersion
37667
+ }
37668
+ };
37669
+ }
37670
+ function serveStdio(factory, options = {}) {
37671
+ const legacyMode = options.legacy ?? "serve";
37672
+ const wire = options.transport ?? new StdioServerTransport();
37673
+ let state = { phase: "opening" };
37674
+ let discarding;
37675
+ let closing = false;
37676
+ const isTornDown = () => closing || state.phase === "closed";
37677
+ const reportError = (error62) => {
37678
+ try {
37679
+ options.onerror?.(error62);
37680
+ } catch {
37681
+ }
37682
+ };
37683
+ const writeErrorResponse = (id, code, message, data) => wire.send({
37684
+ jsonrpc: "2.0",
37685
+ id,
37686
+ error: {
37687
+ code,
37688
+ message,
37689
+ ...data !== void 0 && { data }
37690
+ }
37691
+ }).catch((error62) => reportError(toError(error62)));
37692
+ const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
37693
+ const modernOutboundIntercept = (message) => {
37694
+ if (!isJSONRPCNotification(message)) return void 0;
37695
+ const routed = listenRouter.routeOutbound(message);
37696
+ if (routed === "passthrough") return void 0;
37697
+ for (const stamped of routed) wire.send({
37698
+ jsonrpc: "2.0",
37699
+ ...stamped
37700
+ }).catch((error62) => reportError(toError(error62)));
37701
+ return "handled";
37702
+ };
37703
+ const tryServeListen = async (message) => {
37704
+ if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
37705
+ const meta3 = requestMetaOf(message.params);
37706
+ const issue2 = hasEnvelopeClaim(message.params) ? (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0] : {
37707
+ key: "_meta",
37708
+ problem: "the per-request envelope is required on protocol revision 2026-07-28"
37709
+ };
37710
+ const claimedVersion = envelopeClaimVersion(message.params);
37711
+ let reply;
37712
+ if (issue2 !== void 0) reply = {
37713
+ jsonrpc: "2.0",
37714
+ id: message.id,
37715
+ error: {
37716
+ code: -32602,
37717
+ message: `Invalid _meta envelope: ${issue2.key}: ${issue2.problem}`
37718
+ }
37719
+ };
37720
+ else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
37721
+ const error62 = new UnsupportedProtocolVersionError({
37722
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
37723
+ requested: claimedVersion ?? "unknown"
37724
+ });
37725
+ reply = {
37726
+ jsonrpc: "2.0",
37727
+ id: message.id,
37728
+ error: {
37729
+ code: error62.code,
37730
+ message: error62.message,
37731
+ data: error62.data
37732
+ }
37733
+ };
37734
+ } else reply = listenRouter.serve(message);
37735
+ await wire.send("error" in reply ? reply : {
37736
+ jsonrpc: "2.0",
37737
+ method: reply.method,
37738
+ params: reply.params
37739
+ }).catch((error62) => reportError(toError(error62)));
37740
+ return true;
37741
+ }
37742
+ if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
37743
+ const cancelledId = message.params?.requestId;
37744
+ if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
37745
+ }
37746
+ return false;
37747
+ };
37748
+ const answerLegacyRejection = (request, reason, requestedVersion) => {
37749
+ const rejection2 = modernOnlyStrictRejection({
37750
+ kind: "legacy",
37751
+ reason,
37752
+ ...requestedVersion !== void 0 && { requestedVersion }
37753
+ }, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
37754
+ if (rejection2 === void 0) return Promise.resolve();
37755
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection2.cell}): ${rejection2.message}`));
37756
+ return writeErrorResponse(request.id, rejection2.code, rejection2.message, rejection2.data);
37757
+ };
37758
+ const onInstanceClosed = (channel) => {
37759
+ if (closing || channel === discarding) return;
37760
+ closeAll();
37761
+ };
37762
+ const connectInstance = async (era, revision) => {
37763
+ const product = await factory({ era });
37764
+ const server = product instanceof McpServer ? product.server : product;
37765
+ if (era === "modern") {
37766
+ setNegotiatedProtocolVersion(server, revision);
37767
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
37768
+ listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
37769
+ }
37770
+ const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
37771
+ await product.connect(channel);
37772
+ return {
37773
+ product,
37774
+ channel
37775
+ };
37776
+ };
37777
+ const disposeLateInstance = (instance) => instance.product.close().catch((error62) => reportError(toError(error62)));
37778
+ const discardProbeInstance = async (instance) => {
37779
+ discarding = instance.channel;
37780
+ try {
37781
+ if (!await instance.channel.whenRequestsAnswered(DISCARD_ANSWER_TIMEOUT_MS)) reportError(/* @__PURE__ */ new Error(`Discarded the probe instance with requests still unanswered after ${DISCARD_ANSWER_TIMEOUT_MS}ms; continuing with the fallback`));
37782
+ await instance.product.close();
37783
+ } catch (error62) {
37784
+ reportError(toError(error62));
37785
+ } finally {
37786
+ discarding = void 0;
37787
+ }
37788
+ };
37789
+ const processMessage = async (message) => {
37790
+ if (state.phase === "closed") return;
37791
+ if (state.phase === "pinned") {
37792
+ if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
37793
+ await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
37794
+ return;
37795
+ }
37796
+ if (state.era === "modern" && await tryServeListen(message)) return;
37797
+ state.instance.channel.deliver(message);
37798
+ return;
37799
+ }
37800
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
37801
+ reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
37802
+ return;
37803
+ }
37804
+ const opening = classifyOpeningMessage(message);
37805
+ switch (opening.kind) {
37806
+ case "invalid-envelope": {
37807
+ const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
37808
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
37809
+ else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
37810
+ return;
37811
+ }
37812
+ case "unsupported-revision":
37813
+ if (isJSONRPCRequest(message)) {
37814
+ const error62 = new UnsupportedProtocolVersionError({
37815
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
37816
+ requested: opening.requested
37817
+ });
37818
+ reportError(error62);
37819
+ await writeErrorResponse(message.id, error62.code, error62.message, error62.data);
37820
+ } else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
37821
+ return;
37822
+ case "modern":
37823
+ if (isJSONRPCRequest(message) && message.method === "server/discover") {
37824
+ if (state.phase === "probe") {
37825
+ state.instance.channel.deliver(message, { classification: opening.classification });
37826
+ return;
37827
+ }
37828
+ const instance = await connectInstance("modern", opening.revision);
37829
+ if (isTornDown()) {
37830
+ await disposeLateInstance(instance);
37831
+ return;
37832
+ }
37833
+ state = {
37834
+ phase: "probe",
37835
+ instance
37836
+ };
37837
+ instance.channel.deliver(message, { classification: opening.classification });
37838
+ return;
37839
+ }
37840
+ if (state.phase === "probe") {
37841
+ if (isJSONRPCNotification(message)) {
37842
+ state.instance.channel.deliver(message, { classification: opening.classification });
37843
+ return;
37844
+ }
37845
+ state = {
37846
+ phase: "pinned",
37847
+ era: "modern",
37848
+ instance: state.instance
37849
+ };
37850
+ } else {
37851
+ const instance = await connectInstance("modern", opening.revision);
37852
+ if (isTornDown()) {
37853
+ await disposeLateInstance(instance);
37854
+ return;
37855
+ }
37856
+ state = {
37857
+ phase: "pinned",
37858
+ era: "modern",
37859
+ instance
37860
+ };
37861
+ }
37862
+ if (await tryServeListen(message)) return;
37863
+ state.instance.channel.deliver(message, { classification: opening.classification });
37864
+ return;
37865
+ case "legacy": {
37866
+ if (legacyMode === "reject") {
37867
+ if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
37868
+ return;
37869
+ }
37870
+ if (state.phase === "probe") {
37871
+ await discardProbeInstance(state.instance);
37872
+ if (isTornDown()) return;
37873
+ state = { phase: "opening" };
37874
+ }
37875
+ const instance = await connectInstance("legacy");
37876
+ if (isTornDown()) {
37877
+ await disposeLateInstance(instance);
37878
+ return;
37879
+ }
37880
+ state = {
37881
+ phase: "pinned",
37882
+ era: "legacy",
37883
+ instance
37884
+ };
37885
+ state.instance.channel.deliver(message);
37886
+ return;
37887
+ }
37888
+ }
37889
+ };
37890
+ const queue = [];
37891
+ let pumping = false;
37892
+ const pump = async () => {
37893
+ if (pumping) return;
37894
+ pumping = true;
37895
+ try {
37896
+ while (queue.length > 0) {
37897
+ const message = queue.shift();
37898
+ try {
37899
+ await processMessage(message);
37900
+ } catch (error62) {
37901
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
37902
+ reportError(toError(error62));
37903
+ }
37904
+ }
37905
+ } finally {
37906
+ pumping = false;
37907
+ }
37908
+ };
37909
+ const closeAll = async () => {
37910
+ if (closing || state.phase === "closed") return;
37911
+ closing = true;
37912
+ const current = state;
37913
+ state = { phase: "closed" };
37914
+ for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error62) => reportError(toError(error62)));
37915
+ if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error62) => reportError(toError(error62)));
37916
+ await wire.close().catch((error62) => reportError(toError(error62)));
37917
+ };
37918
+ wire.onmessage = (message) => {
37919
+ queue.push(message);
37920
+ pump();
37921
+ };
37922
+ wire.onerror = (error62) => {
37923
+ reportError(error62);
37924
+ if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error62);
37925
+ };
37926
+ wire.onclose = () => {
37927
+ if (closing || state.phase === "closed") return;
37928
+ closing = true;
37929
+ const current = state;
37930
+ state = { phase: "closed" };
37931
+ if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error62) => reportError(toError(error62)));
37932
+ };
37933
+ const started = wire.start().catch((error62) => {
37934
+ reportError(toError(error62));
37935
+ throw error62;
37936
+ });
37937
+ started.catch(() => {
37938
+ });
37939
+ return { close: async () => {
37940
+ await started.catch(() => {
37941
+ });
37942
+ await closeAll();
37943
+ } };
37944
+ }
37945
+ function toError(value) {
37946
+ return value instanceof Error ? value : new Error(String(value));
37947
+ }
37302
37948
 
37303
37949
  // node_modules/@chrischall/mcp-utils/dist/errors/index.js
37304
37950
  var DEFAULT_ERROR_MESSAGE_MAX = 500;
@@ -57128,7 +57774,7 @@ async function createMcpServer(opts) {
57128
57774
  }
57129
57775
  return server;
57130
57776
  }
57131
- function withGracefulShutdown(server, opts = {}) {
57777
+ function withGracefulShutdown(target, opts = {}) {
57132
57778
  const shouldExit = opts.exit ?? true;
57133
57779
  let shuttingDown = false;
57134
57780
  const handler = (signal) => {
@@ -57139,7 +57785,7 @@ function withGracefulShutdown(server, opts = {}) {
57139
57785
  try {
57140
57786
  if (opts.onSignal)
57141
57787
  await opts.onSignal(signal);
57142
- await server.close();
57788
+ await target.close();
57143
57789
  } catch (err) {
57144
57790
  console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
57145
57791
  } finally {
@@ -57151,16 +57797,26 @@ function withGracefulShutdown(server, opts = {}) {
57151
57797
  process.on("SIGINT", () => handler("SIGINT"));
57152
57798
  process.on("SIGTERM", () => handler("SIGTERM"));
57153
57799
  }
57154
- async function runMcp(opts) {
57155
- const server = await createMcpServer(opts);
57800
+ function runMcp(opts) {
57801
+ if (opts.banner !== void 0) {
57802
+ console.error(opts.banner);
57803
+ }
57804
+ const spec = opts.transport ?? "stdio";
57805
+ const handle = serveStdio(() => createMcpServer({ ...opts, banner: void 0 }), {
57806
+ legacy: opts.legacy ?? "serve",
57807
+ onerror: opts.onerror ?? ((error62) => {
57808
+ console.error(`[mcp-utils] stdio server error: ${redactSecrets(error62.message)}`);
57809
+ }),
57810
+ // `'stdio'` means "let the entry make its own StdioServerTransport over
57811
+ // this process"; anything else is the caller's transport, which the entry
57812
+ // then owns (it starts it, pumps it, and closes it with the handle).
57813
+ ...spec === "stdio" ? {} : { transport: spec }
57814
+ });
57156
57815
  const shutdown = opts.shutdown ?? true;
57157
57816
  if (shutdown !== false) {
57158
- withGracefulShutdown(server, shutdown === true ? {} : shutdown);
57817
+ withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
57159
57818
  }
57160
- const spec = opts.transport ?? "stdio";
57161
- const transport = spec === "stdio" ? new StdioServerTransport() : spec;
57162
- await server.connect(transport);
57163
- return server;
57819
+ return handle;
57164
57820
  }
57165
57821
 
57166
57822
  // node_modules/@chrischall/mcp-utils/dist/config/index.js
@@ -57261,7 +57917,7 @@ function toolAnnotations(opts = {}) {
57261
57917
  }
57262
57918
 
57263
57919
  // src/version.ts
57264
- var VERSION = "2.0.0";
57920
+ var VERSION = "2.1.0";
57265
57921
 
57266
57922
  // src/client.ts
57267
57923
  import { dirname, join } from "path";
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.0.0'; // x-release-please-version
6
+ export const VERSION = '2.1.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untappd-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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": "^0.28.0",
48
+ "@chrischall/mcp-utils": "^1.0.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.0.0",
9
+ "version": "2.1.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "untappd-mcp",
14
- "version": "2.0.0",
14
+ "version": "2.1.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },