untappd-mcp 2.0.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +761 -30
- package/dist/tools/cache.js +3 -3
- package/dist/tools/checkin.js +5 -5
- package/dist/tools/friends.js +5 -1
- package/dist/tools/wishlist.js +2 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Untappd — beers, breweries, venues, check-ins, wishlists, and your friend feed",
|
|
10
|
-
"version": "2.
|
|
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.
|
|
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.
|
|
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;
|
|
@@ -24476,6 +24490,7 @@ function missingClientCapabilities(required2, declared) {
|
|
|
24476
24490
|
return Object.keys(missing).length > 0 ? missing : void 0;
|
|
24477
24491
|
}
|
|
24478
24492
|
var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
24493
|
+
var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
|
|
24479
24494
|
function isModernProtocolVersion(version2) {
|
|
24480
24495
|
return version2 >= FIRST_MODERN_PROTOCOL_VERSION;
|
|
24481
24496
|
}
|
|
@@ -27034,6 +27049,25 @@ function isSpecNotificationMethod(method) {
|
|
|
27034
27049
|
return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
|
|
27035
27050
|
}
|
|
27036
27051
|
var ALL_CODECS = [rev2025Codec, rev2026Codec];
|
|
27052
|
+
function isPlainObject$3(value) {
|
|
27053
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27054
|
+
}
|
|
27055
|
+
function requestMetaOf(params) {
|
|
27056
|
+
if (!isPlainObject$3(params)) return void 0;
|
|
27057
|
+
const meta3 = params["_meta"];
|
|
27058
|
+
return isPlainObject$3(meta3) ? meta3 : void 0;
|
|
27059
|
+
}
|
|
27060
|
+
function hasEnvelopeClaim(params) {
|
|
27061
|
+
const meta3 = requestMetaOf(params);
|
|
27062
|
+
return meta3 !== void 0 && PROTOCOL_VERSION_META_KEY in meta3;
|
|
27063
|
+
}
|
|
27064
|
+
function envelopeClaimVersion(params) {
|
|
27065
|
+
const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
|
|
27066
|
+
return typeof value === "string" ? value : void 0;
|
|
27067
|
+
}
|
|
27068
|
+
function validateEnvelopeMeta(meta3) {
|
|
27069
|
+
return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta3);
|
|
27070
|
+
}
|
|
27037
27071
|
var schemas_exports = /* @__PURE__ */ __exportAll({
|
|
27038
27072
|
AnnotationsSchema: () => AnnotationsSchema,
|
|
27039
27073
|
AudioContentSchema: () => AudioContentSchema,
|
|
@@ -27384,6 +27418,45 @@ var LADDER_ERROR_HTTP_STATUS = {
|
|
|
27384
27418
|
[ProtocolErrorCode.MissingRequiredClientCapability]: 400,
|
|
27385
27419
|
[HEADER_MISMATCH_ERROR_CODE]: 400
|
|
27386
27420
|
};
|
|
27421
|
+
function rejection(rung, cell, httpStatus, error62, settled) {
|
|
27422
|
+
return {
|
|
27423
|
+
kind: "reject",
|
|
27424
|
+
rung,
|
|
27425
|
+
cell,
|
|
27426
|
+
httpStatus,
|
|
27427
|
+
code: error62.code,
|
|
27428
|
+
message: error62.message,
|
|
27429
|
+
...error62.data !== void 0 && { data: error62.data },
|
|
27430
|
+
settled
|
|
27431
|
+
};
|
|
27432
|
+
}
|
|
27433
|
+
function carriesValidModernEnvelopeClaim(params) {
|
|
27434
|
+
if (!hasEnvelopeClaim(params)) return false;
|
|
27435
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
27436
|
+
if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
|
|
27437
|
+
const meta3 = requestMetaOf(params);
|
|
27438
|
+
return meta3 !== void 0 && validateEnvelopeMeta(meta3).length === 0;
|
|
27439
|
+
}
|
|
27440
|
+
function modernOnlyStrictRejection(route, supportedVersions) {
|
|
27441
|
+
switch (route.reason) {
|
|
27442
|
+
case "http-method":
|
|
27443
|
+
return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
|
|
27444
|
+
case "batch":
|
|
27445
|
+
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);
|
|
27446
|
+
case "response":
|
|
27447
|
+
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);
|
|
27448
|
+
case "notification":
|
|
27449
|
+
return;
|
|
27450
|
+
case "initialize":
|
|
27451
|
+
case "no-claim": {
|
|
27452
|
+
const requested = route.requestedVersion;
|
|
27453
|
+
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({
|
|
27454
|
+
supported: [...supportedVersions],
|
|
27455
|
+
requested
|
|
27456
|
+
}), true);
|
|
27457
|
+
}
|
|
27458
|
+
}
|
|
27459
|
+
}
|
|
27387
27460
|
function parseSchema(schema, data) {
|
|
27388
27461
|
return z2.safeParse(schema, data);
|
|
27389
27462
|
}
|
|
@@ -27945,6 +28018,9 @@ function withRequestStateValue(ctx, value) {
|
|
|
27945
28018
|
};
|
|
27946
28019
|
}
|
|
27947
28020
|
var writeNegotiatedProtocolVersion;
|
|
28021
|
+
function setNegotiatedProtocolVersion(instance, version2) {
|
|
28022
|
+
writeNegotiatedProtocolVersion(instance, version2);
|
|
28023
|
+
}
|
|
27948
28024
|
var Protocol = class {
|
|
27949
28025
|
_transport;
|
|
27950
28026
|
_requestMessageId = 0;
|
|
@@ -35888,6 +35964,190 @@ function getCompleter(schema) {
|
|
|
35888
35964
|
return schema[COMPLETABLE_SYMBOL]?.complete;
|
|
35889
35965
|
}
|
|
35890
35966
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
35967
|
+
function listenFilterAccepts(filter, event) {
|
|
35968
|
+
switch (event.kind) {
|
|
35969
|
+
case "tools_list_changed":
|
|
35970
|
+
return filter.toolsListChanged === true;
|
|
35971
|
+
case "prompts_list_changed":
|
|
35972
|
+
return filter.promptsListChanged === true;
|
|
35973
|
+
case "resources_list_changed":
|
|
35974
|
+
return filter.resourcesListChanged === true;
|
|
35975
|
+
case "resource_updated":
|
|
35976
|
+
return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
|
|
35977
|
+
}
|
|
35978
|
+
}
|
|
35979
|
+
function honoredSubset(requested, capabilities) {
|
|
35980
|
+
const honored = {};
|
|
35981
|
+
const allow = (bit) => capabilities === void 0 || bit === true;
|
|
35982
|
+
if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
|
|
35983
|
+
if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
|
|
35984
|
+
if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
|
|
35985
|
+
if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
|
|
35986
|
+
return honored;
|
|
35987
|
+
}
|
|
35988
|
+
var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
|
|
35989
|
+
function stampSubscriptionId(notification, subscriptionId) {
|
|
35990
|
+
return {
|
|
35991
|
+
method: notification.method,
|
|
35992
|
+
params: {
|
|
35993
|
+
...notification.params,
|
|
35994
|
+
_meta: {
|
|
35995
|
+
...notification.params?._meta,
|
|
35996
|
+
[SUBSCRIPTION_ID_META_KEY]: subscriptionId
|
|
35997
|
+
}
|
|
35998
|
+
}
|
|
35999
|
+
};
|
|
36000
|
+
}
|
|
36001
|
+
function parseListenFilter(message) {
|
|
36002
|
+
const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
|
|
36003
|
+
return outcome.ok ? outcome.value.params?.notifications : void 0;
|
|
36004
|
+
}
|
|
36005
|
+
var CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
|
|
36006
|
+
"notifications/tools/list_changed",
|
|
36007
|
+
"notifications/prompts/list_changed",
|
|
36008
|
+
"notifications/resources/list_changed",
|
|
36009
|
+
"notifications/resources/updated"
|
|
36010
|
+
]);
|
|
36011
|
+
var StdioListenRouter = class {
|
|
36012
|
+
/** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
|
|
36013
|
+
_subs = /* @__PURE__ */ new Map();
|
|
36014
|
+
/**
|
|
36015
|
+
* The serving instance's declared capabilities. Filled in by the entry
|
|
36016
|
+
* once the modern instance is constructed (the router is created before
|
|
36017
|
+
* the instance exists), so the acknowledged filter is narrowed against
|
|
36018
|
+
* what the server can actually deliver.
|
|
36019
|
+
*/
|
|
36020
|
+
_serverCapabilities;
|
|
36021
|
+
/**
|
|
36022
|
+
* The serving instance's identity, stamped onto the graceful-close
|
|
36023
|
+
* results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
|
|
36024
|
+
* `ResultMetaObject`). Handed over together with the capabilities.
|
|
36025
|
+
*/
|
|
36026
|
+
_serverInfo;
|
|
36027
|
+
constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
|
|
36028
|
+
this._maxSubscriptions = _maxSubscriptions;
|
|
36029
|
+
this._serverCapabilities = serverCapabilities;
|
|
36030
|
+
this._serverInfo = serverInfo;
|
|
36031
|
+
}
|
|
36032
|
+
/**
|
|
36033
|
+
* Record the serving instance's declared capabilities and identity once
|
|
36034
|
+
* it has been constructed. Called by `serveStdio`'s connect path;
|
|
36035
|
+
* subsequent `serve()` calls narrow the honored filter against the
|
|
36036
|
+
* capabilities, and `teardownAll()` stamps the identity.
|
|
36037
|
+
*/
|
|
36038
|
+
setServerCapabilities(capabilities, serverInfo) {
|
|
36039
|
+
this._serverCapabilities = capabilities;
|
|
36040
|
+
if (serverInfo !== void 0) this._serverInfo = serverInfo;
|
|
36041
|
+
}
|
|
36042
|
+
/** Whether `id` is an active listen subscription on this connection. */
|
|
36043
|
+
has(id) {
|
|
36044
|
+
return this._subs.has(id);
|
|
36045
|
+
}
|
|
36046
|
+
/**
|
|
36047
|
+
* Serve one inbound `subscriptions/listen` request: registers the
|
|
36048
|
+
* subscription and returns the stamped acknowledged notification (or, on
|
|
36049
|
+
* capacity / params rejection, the in-band JSON-RPC error response).
|
|
36050
|
+
*
|
|
36051
|
+
* @throws when called before {@linkcode setServerCapabilities} (or the
|
|
36052
|
+
* constructor) has supplied the serving instance's capabilities. Honoring a
|
|
36053
|
+
* filter without knowing the server's advertised capabilities would fail
|
|
36054
|
+
* open (deliver unadvertised types); the entry guarantees capabilities are
|
|
36055
|
+
* set before any listen request is routed here.
|
|
36056
|
+
*/
|
|
36057
|
+
serve(message) {
|
|
36058
|
+
if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
|
|
36059
|
+
if (this._subs.size >= this._maxSubscriptions) return {
|
|
36060
|
+
jsonrpc: "2.0",
|
|
36061
|
+
id: message.id,
|
|
36062
|
+
error: {
|
|
36063
|
+
code: -32603,
|
|
36064
|
+
message: "Subscription limit reached"
|
|
36065
|
+
}
|
|
36066
|
+
};
|
|
36067
|
+
const filter = parseListenFilter(message);
|
|
36068
|
+
if (filter === void 0) return {
|
|
36069
|
+
jsonrpc: "2.0",
|
|
36070
|
+
id: message.id,
|
|
36071
|
+
error: {
|
|
36072
|
+
code: -32602,
|
|
36073
|
+
message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
|
|
36074
|
+
}
|
|
36075
|
+
};
|
|
36076
|
+
const honored = honoredSubset(filter, this._serverCapabilities);
|
|
36077
|
+
this._subs.set(message.id, honored);
|
|
36078
|
+
return stampSubscriptionId({
|
|
36079
|
+
method: "notifications/subscriptions/acknowledged",
|
|
36080
|
+
params: { notifications: honored }
|
|
36081
|
+
}, message.id);
|
|
36082
|
+
}
|
|
36083
|
+
/**
|
|
36084
|
+
* Tear down one subscription (inbound `notifications/cancelled`). Returns
|
|
36085
|
+
* `true` when a subscription was removed. After this call NOTHING further
|
|
36086
|
+
* is delivered for that subscription id (the post-cancel hardening).
|
|
36087
|
+
*/
|
|
36088
|
+
cancel(id) {
|
|
36089
|
+
return this._subs.delete(id);
|
|
36090
|
+
}
|
|
36091
|
+
/**
|
|
36092
|
+
* Route an outbound notification through the active subscriptions.
|
|
36093
|
+
*
|
|
36094
|
+
* - For a subscription-gated change notification, returns one stamped copy
|
|
36095
|
+
* per subscription that opted in to it (an empty array means it is
|
|
36096
|
+
* dropped — the modern era never delivers an un-requested change type).
|
|
36097
|
+
* - For any other outbound message, returns `'passthrough'` (the entry
|
|
36098
|
+
* forwards it as-is).
|
|
36099
|
+
*/
|
|
36100
|
+
routeOutbound(message) {
|
|
36101
|
+
if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
|
|
36102
|
+
const uriParam = message.params?.["uri"];
|
|
36103
|
+
const uri = typeof uriParam === "string" ? uriParam : void 0;
|
|
36104
|
+
const event = notificationToServerEvent(message.method, uri);
|
|
36105
|
+
const out = [];
|
|
36106
|
+
for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
|
|
36107
|
+
method: message.method,
|
|
36108
|
+
params: message.params ?? {}
|
|
36109
|
+
}, subscriptionId));
|
|
36110
|
+
return out;
|
|
36111
|
+
}
|
|
36112
|
+
/**
|
|
36113
|
+
* Server-side graceful teardown of every active subscription: returns the
|
|
36114
|
+
* empty `subscriptions/listen` JSON-RPC result for each subscription id —
|
|
36115
|
+
* the spec's graceful-close signal, `_meta` carrying the subscription id
|
|
36116
|
+
* and the serving instance's identity — for the entry to emit before
|
|
36117
|
+
* closing the wire. Clears the set so nothing further is delivered.
|
|
36118
|
+
*/
|
|
36119
|
+
teardownAll() {
|
|
36120
|
+
const out = [];
|
|
36121
|
+
for (const id of this._subs.keys()) out.push({
|
|
36122
|
+
jsonrpc: "2.0",
|
|
36123
|
+
id,
|
|
36124
|
+
result: {
|
|
36125
|
+
resultType: "complete",
|
|
36126
|
+
_meta: {
|
|
36127
|
+
[SUBSCRIPTION_ID_META_KEY]: id,
|
|
36128
|
+
...this._serverInfo !== void 0 && { [SERVER_INFO_META_KEY]: this._serverInfo }
|
|
36129
|
+
}
|
|
36130
|
+
}
|
|
36131
|
+
});
|
|
36132
|
+
this._subs.clear();
|
|
36133
|
+
return out;
|
|
36134
|
+
}
|
|
36135
|
+
};
|
|
36136
|
+
function notificationToServerEvent(method, uri) {
|
|
36137
|
+
switch (method) {
|
|
36138
|
+
case "notifications/tools/list_changed":
|
|
36139
|
+
return { kind: "tools_list_changed" };
|
|
36140
|
+
case "notifications/prompts/list_changed":
|
|
36141
|
+
return { kind: "prompts_list_changed" };
|
|
36142
|
+
case "notifications/resources/list_changed":
|
|
36143
|
+
return { kind: "resources_list_changed" };
|
|
36144
|
+
default:
|
|
36145
|
+
return {
|
|
36146
|
+
kind: "resource_updated",
|
|
36147
|
+
uri: uri ?? ""
|
|
36148
|
+
};
|
|
36149
|
+
}
|
|
36150
|
+
}
|
|
35891
36151
|
var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
|
|
35892
36152
|
var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
|
|
35893
36153
|
function resolveLegacyShimOptions(options) {
|
|
@@ -36025,6 +36285,12 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
|
|
|
36025
36285
|
var writeClientIdentity;
|
|
36026
36286
|
var installDiscoverHandler;
|
|
36027
36287
|
var readServerIdentity;
|
|
36288
|
+
function installModernOnlyHandlers(server, servedModernVersions) {
|
|
36289
|
+
installDiscoverHandler(server, servedModernVersions);
|
|
36290
|
+
}
|
|
36291
|
+
function serverIdentityOf(server) {
|
|
36292
|
+
return readServerIdentity(server);
|
|
36293
|
+
}
|
|
36028
36294
|
var Server = class extends Protocol {
|
|
36029
36295
|
_clientCapabilities;
|
|
36030
36296
|
_clientVersion;
|
|
@@ -37299,6 +37565,400 @@ var StdioServerTransport = class {
|
|
|
37299
37565
|
});
|
|
37300
37566
|
}
|
|
37301
37567
|
};
|
|
37568
|
+
var DISCARD_ANSWER_TIMEOUT_MS = 3e3;
|
|
37569
|
+
var StdioConnectionChannel = class {
|
|
37570
|
+
onclose;
|
|
37571
|
+
onerror;
|
|
37572
|
+
onmessage;
|
|
37573
|
+
_closed = false;
|
|
37574
|
+
/** Request ids the entry delivered to the instance that the instance has not yet answered. */
|
|
37575
|
+
_pendingRequests = /* @__PURE__ */ new Set();
|
|
37576
|
+
_drainWaiters = [];
|
|
37577
|
+
constructor(_wire, _onInstanceClose, _outboundIntercept) {
|
|
37578
|
+
this._wire = _wire;
|
|
37579
|
+
this._onInstanceClose = _onInstanceClose;
|
|
37580
|
+
this._outboundIntercept = _outboundIntercept;
|
|
37581
|
+
}
|
|
37582
|
+
async start() {
|
|
37583
|
+
}
|
|
37584
|
+
async send(message, options) {
|
|
37585
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
37586
|
+
const { id } = message;
|
|
37587
|
+
if (id !== void 0) this._settle(id);
|
|
37588
|
+
}
|
|
37589
|
+
if (this._closed) return;
|
|
37590
|
+
if (this._outboundIntercept?.(message) === "handled") return;
|
|
37591
|
+
return this._wire.send(message, options);
|
|
37592
|
+
}
|
|
37593
|
+
setProtocolVersion = (version2) => {
|
|
37594
|
+
this._wire.setProtocolVersion?.(version2);
|
|
37595
|
+
};
|
|
37596
|
+
/** Forwards one inbound message to the connected instance. */
|
|
37597
|
+
deliver(message, extra) {
|
|
37598
|
+
if (this._closed) return;
|
|
37599
|
+
if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
|
|
37600
|
+
else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
37601
|
+
const cancelledId = message.params?.requestId;
|
|
37602
|
+
if (cancelledId !== void 0) this._settle(cancelledId);
|
|
37603
|
+
}
|
|
37604
|
+
this.onmessage?.(message, extra);
|
|
37605
|
+
}
|
|
37606
|
+
/**
|
|
37607
|
+
* Resolves once every request delivered to the instance has been answered
|
|
37608
|
+
* through {@linkcode send}, settled by a delivered cancellation, or the
|
|
37609
|
+
* channel has been closed and nothing further can be answered. The wait is
|
|
37610
|
+
* bounded by `timeoutMs` as a backstop so no edge can hold the caller
|
|
37611
|
+
* indefinitely; resolves `false` only when the bound elapsed with requests
|
|
37612
|
+
* still unanswered. Used by the probe-discard path so a probe request the
|
|
37613
|
+
* entry accepted is never silently dropped.
|
|
37614
|
+
*/
|
|
37615
|
+
async whenRequestsAnswered(timeoutMs) {
|
|
37616
|
+
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
37617
|
+
return await new Promise((resolve) => {
|
|
37618
|
+
const waiter = () => {
|
|
37619
|
+
clearTimeout(timer);
|
|
37620
|
+
resolve(true);
|
|
37621
|
+
};
|
|
37622
|
+
const timer = setTimeout(() => {
|
|
37623
|
+
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
37624
|
+
resolve(false);
|
|
37625
|
+
}, timeoutMs);
|
|
37626
|
+
this._drainWaiters.push(waiter);
|
|
37627
|
+
});
|
|
37628
|
+
}
|
|
37629
|
+
async close() {
|
|
37630
|
+
if (this._closed) return;
|
|
37631
|
+
this._closed = true;
|
|
37632
|
+
this._pendingRequests.clear();
|
|
37633
|
+
this._releaseDrainWaiters();
|
|
37634
|
+
try {
|
|
37635
|
+
this._onInstanceClose();
|
|
37636
|
+
} finally {
|
|
37637
|
+
this.onclose?.();
|
|
37638
|
+
}
|
|
37639
|
+
}
|
|
37640
|
+
_settle(id) {
|
|
37641
|
+
this._pendingRequests.delete(id);
|
|
37642
|
+
if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
|
|
37643
|
+
}
|
|
37644
|
+
_releaseDrainWaiters() {
|
|
37645
|
+
const waiters = this._drainWaiters;
|
|
37646
|
+
this._drainWaiters = [];
|
|
37647
|
+
for (const waiter of waiters) waiter();
|
|
37648
|
+
}
|
|
37649
|
+
};
|
|
37650
|
+
function classifyOpeningMessage(message) {
|
|
37651
|
+
const params = message.params;
|
|
37652
|
+
if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
|
|
37653
|
+
const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
|
|
37654
|
+
return {
|
|
37655
|
+
kind: "legacy",
|
|
37656
|
+
reason: "initialize",
|
|
37657
|
+
...requestedVersion !== void 0 && { requestedVersion }
|
|
37658
|
+
};
|
|
37659
|
+
}
|
|
37660
|
+
if (!hasEnvelopeClaim(params)) return {
|
|
37661
|
+
kind: "legacy",
|
|
37662
|
+
reason: "no-claim"
|
|
37663
|
+
};
|
|
37664
|
+
const meta3 = requestMetaOf(params);
|
|
37665
|
+
const firstIssue = (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0];
|
|
37666
|
+
if (firstIssue !== void 0) return {
|
|
37667
|
+
kind: "invalid-envelope",
|
|
37668
|
+
issue: firstIssue
|
|
37669
|
+
};
|
|
37670
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
37671
|
+
if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
|
|
37672
|
+
kind: "unsupported-revision",
|
|
37673
|
+
requested: claimedVersion ?? "unknown"
|
|
37674
|
+
};
|
|
37675
|
+
return {
|
|
37676
|
+
kind: "modern",
|
|
37677
|
+
revision: claimedVersion,
|
|
37678
|
+
classification: {
|
|
37679
|
+
era: "modern",
|
|
37680
|
+
revision: claimedVersion
|
|
37681
|
+
}
|
|
37682
|
+
};
|
|
37683
|
+
}
|
|
37684
|
+
function serveStdio(factory, options = {}) {
|
|
37685
|
+
const legacyMode = options.legacy ?? "serve";
|
|
37686
|
+
const wire = options.transport ?? new StdioServerTransport();
|
|
37687
|
+
let state = { phase: "opening" };
|
|
37688
|
+
let discarding;
|
|
37689
|
+
let closing = false;
|
|
37690
|
+
const isTornDown = () => closing || state.phase === "closed";
|
|
37691
|
+
const reportError = (error62) => {
|
|
37692
|
+
try {
|
|
37693
|
+
options.onerror?.(error62);
|
|
37694
|
+
} catch {
|
|
37695
|
+
}
|
|
37696
|
+
};
|
|
37697
|
+
const writeErrorResponse = (id, code, message, data) => wire.send({
|
|
37698
|
+
jsonrpc: "2.0",
|
|
37699
|
+
id,
|
|
37700
|
+
error: {
|
|
37701
|
+
code,
|
|
37702
|
+
message,
|
|
37703
|
+
...data !== void 0 && { data }
|
|
37704
|
+
}
|
|
37705
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37706
|
+
const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
|
|
37707
|
+
const modernOutboundIntercept = (message) => {
|
|
37708
|
+
if (!isJSONRPCNotification(message)) return void 0;
|
|
37709
|
+
const routed = listenRouter.routeOutbound(message);
|
|
37710
|
+
if (routed === "passthrough") return void 0;
|
|
37711
|
+
for (const stamped of routed) wire.send({
|
|
37712
|
+
jsonrpc: "2.0",
|
|
37713
|
+
...stamped
|
|
37714
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37715
|
+
return "handled";
|
|
37716
|
+
};
|
|
37717
|
+
const tryServeListen = async (message) => {
|
|
37718
|
+
if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
|
|
37719
|
+
const meta3 = requestMetaOf(message.params);
|
|
37720
|
+
const issue2 = hasEnvelopeClaim(message.params) ? (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0] : {
|
|
37721
|
+
key: "_meta",
|
|
37722
|
+
problem: "the per-request envelope is required on protocol revision 2026-07-28"
|
|
37723
|
+
};
|
|
37724
|
+
const claimedVersion = envelopeClaimVersion(message.params);
|
|
37725
|
+
let reply;
|
|
37726
|
+
if (issue2 !== void 0) reply = {
|
|
37727
|
+
jsonrpc: "2.0",
|
|
37728
|
+
id: message.id,
|
|
37729
|
+
error: {
|
|
37730
|
+
code: -32602,
|
|
37731
|
+
message: `Invalid _meta envelope: ${issue2.key}: ${issue2.problem}`
|
|
37732
|
+
}
|
|
37733
|
+
};
|
|
37734
|
+
else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
|
|
37735
|
+
const error62 = new UnsupportedProtocolVersionError({
|
|
37736
|
+
supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
37737
|
+
requested: claimedVersion ?? "unknown"
|
|
37738
|
+
});
|
|
37739
|
+
reply = {
|
|
37740
|
+
jsonrpc: "2.0",
|
|
37741
|
+
id: message.id,
|
|
37742
|
+
error: {
|
|
37743
|
+
code: error62.code,
|
|
37744
|
+
message: error62.message,
|
|
37745
|
+
data: error62.data
|
|
37746
|
+
}
|
|
37747
|
+
};
|
|
37748
|
+
} else reply = listenRouter.serve(message);
|
|
37749
|
+
await wire.send("error" in reply ? reply : {
|
|
37750
|
+
jsonrpc: "2.0",
|
|
37751
|
+
method: reply.method,
|
|
37752
|
+
params: reply.params
|
|
37753
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37754
|
+
return true;
|
|
37755
|
+
}
|
|
37756
|
+
if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
37757
|
+
const cancelledId = message.params?.requestId;
|
|
37758
|
+
if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
|
|
37759
|
+
}
|
|
37760
|
+
return false;
|
|
37761
|
+
};
|
|
37762
|
+
const answerLegacyRejection = (request, reason, requestedVersion) => {
|
|
37763
|
+
const rejection2 = modernOnlyStrictRejection({
|
|
37764
|
+
kind: "legacy",
|
|
37765
|
+
reason,
|
|
37766
|
+
...requestedVersion !== void 0 && { requestedVersion }
|
|
37767
|
+
}, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
37768
|
+
if (rejection2 === void 0) return Promise.resolve();
|
|
37769
|
+
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection2.cell}): ${rejection2.message}`));
|
|
37770
|
+
return writeErrorResponse(request.id, rejection2.code, rejection2.message, rejection2.data);
|
|
37771
|
+
};
|
|
37772
|
+
const onInstanceClosed = (channel) => {
|
|
37773
|
+
if (closing || channel === discarding) return;
|
|
37774
|
+
closeAll();
|
|
37775
|
+
};
|
|
37776
|
+
const connectInstance = async (era, revision) => {
|
|
37777
|
+
const product = await factory({ era });
|
|
37778
|
+
const server = product instanceof McpServer ? product.server : product;
|
|
37779
|
+
if (era === "modern") {
|
|
37780
|
+
setNegotiatedProtocolVersion(server, revision);
|
|
37781
|
+
installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
37782
|
+
listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
|
|
37783
|
+
}
|
|
37784
|
+
const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
|
|
37785
|
+
await product.connect(channel);
|
|
37786
|
+
return {
|
|
37787
|
+
product,
|
|
37788
|
+
channel
|
|
37789
|
+
};
|
|
37790
|
+
};
|
|
37791
|
+
const disposeLateInstance = (instance) => instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
37792
|
+
const discardProbeInstance = async (instance) => {
|
|
37793
|
+
discarding = instance.channel;
|
|
37794
|
+
try {
|
|
37795
|
+
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`));
|
|
37796
|
+
await instance.product.close();
|
|
37797
|
+
} catch (error62) {
|
|
37798
|
+
reportError(toError(error62));
|
|
37799
|
+
} finally {
|
|
37800
|
+
discarding = void 0;
|
|
37801
|
+
}
|
|
37802
|
+
};
|
|
37803
|
+
const processMessage = async (message) => {
|
|
37804
|
+
if (state.phase === "closed") return;
|
|
37805
|
+
if (state.phase === "pinned") {
|
|
37806
|
+
if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
|
|
37807
|
+
await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
|
|
37808
|
+
return;
|
|
37809
|
+
}
|
|
37810
|
+
if (state.era === "modern" && await tryServeListen(message)) return;
|
|
37811
|
+
state.instance.channel.deliver(message);
|
|
37812
|
+
return;
|
|
37813
|
+
}
|
|
37814
|
+
if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
|
|
37815
|
+
reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
|
|
37816
|
+
return;
|
|
37817
|
+
}
|
|
37818
|
+
const opening = classifyOpeningMessage(message);
|
|
37819
|
+
switch (opening.kind) {
|
|
37820
|
+
case "invalid-envelope": {
|
|
37821
|
+
const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
|
|
37822
|
+
if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
|
|
37823
|
+
else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
|
|
37824
|
+
return;
|
|
37825
|
+
}
|
|
37826
|
+
case "unsupported-revision":
|
|
37827
|
+
if (isJSONRPCRequest(message)) {
|
|
37828
|
+
const error62 = new UnsupportedProtocolVersionError({
|
|
37829
|
+
supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
37830
|
+
requested: opening.requested
|
|
37831
|
+
});
|
|
37832
|
+
reportError(error62);
|
|
37833
|
+
await writeErrorResponse(message.id, error62.code, error62.message, error62.data);
|
|
37834
|
+
} else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
|
|
37835
|
+
return;
|
|
37836
|
+
case "modern":
|
|
37837
|
+
if (isJSONRPCRequest(message) && message.method === "server/discover") {
|
|
37838
|
+
if (state.phase === "probe") {
|
|
37839
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37840
|
+
return;
|
|
37841
|
+
}
|
|
37842
|
+
const instance = await connectInstance("modern", opening.revision);
|
|
37843
|
+
if (isTornDown()) {
|
|
37844
|
+
await disposeLateInstance(instance);
|
|
37845
|
+
return;
|
|
37846
|
+
}
|
|
37847
|
+
state = {
|
|
37848
|
+
phase: "probe",
|
|
37849
|
+
instance
|
|
37850
|
+
};
|
|
37851
|
+
instance.channel.deliver(message, { classification: opening.classification });
|
|
37852
|
+
return;
|
|
37853
|
+
}
|
|
37854
|
+
if (state.phase === "probe") {
|
|
37855
|
+
if (isJSONRPCNotification(message)) {
|
|
37856
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37857
|
+
return;
|
|
37858
|
+
}
|
|
37859
|
+
state = {
|
|
37860
|
+
phase: "pinned",
|
|
37861
|
+
era: "modern",
|
|
37862
|
+
instance: state.instance
|
|
37863
|
+
};
|
|
37864
|
+
} else {
|
|
37865
|
+
const instance = await connectInstance("modern", opening.revision);
|
|
37866
|
+
if (isTornDown()) {
|
|
37867
|
+
await disposeLateInstance(instance);
|
|
37868
|
+
return;
|
|
37869
|
+
}
|
|
37870
|
+
state = {
|
|
37871
|
+
phase: "pinned",
|
|
37872
|
+
era: "modern",
|
|
37873
|
+
instance
|
|
37874
|
+
};
|
|
37875
|
+
}
|
|
37876
|
+
if (await tryServeListen(message)) return;
|
|
37877
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37878
|
+
return;
|
|
37879
|
+
case "legacy": {
|
|
37880
|
+
if (legacyMode === "reject") {
|
|
37881
|
+
if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
|
|
37882
|
+
return;
|
|
37883
|
+
}
|
|
37884
|
+
if (state.phase === "probe") {
|
|
37885
|
+
await discardProbeInstance(state.instance);
|
|
37886
|
+
if (isTornDown()) return;
|
|
37887
|
+
state = { phase: "opening" };
|
|
37888
|
+
}
|
|
37889
|
+
const instance = await connectInstance("legacy");
|
|
37890
|
+
if (isTornDown()) {
|
|
37891
|
+
await disposeLateInstance(instance);
|
|
37892
|
+
return;
|
|
37893
|
+
}
|
|
37894
|
+
state = {
|
|
37895
|
+
phase: "pinned",
|
|
37896
|
+
era: "legacy",
|
|
37897
|
+
instance
|
|
37898
|
+
};
|
|
37899
|
+
state.instance.channel.deliver(message);
|
|
37900
|
+
return;
|
|
37901
|
+
}
|
|
37902
|
+
}
|
|
37903
|
+
};
|
|
37904
|
+
const queue = [];
|
|
37905
|
+
let pumping = false;
|
|
37906
|
+
const pump = async () => {
|
|
37907
|
+
if (pumping) return;
|
|
37908
|
+
pumping = true;
|
|
37909
|
+
try {
|
|
37910
|
+
while (queue.length > 0) {
|
|
37911
|
+
const message = queue.shift();
|
|
37912
|
+
try {
|
|
37913
|
+
await processMessage(message);
|
|
37914
|
+
} catch (error62) {
|
|
37915
|
+
if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
|
|
37916
|
+
reportError(toError(error62));
|
|
37917
|
+
}
|
|
37918
|
+
}
|
|
37919
|
+
} finally {
|
|
37920
|
+
pumping = false;
|
|
37921
|
+
}
|
|
37922
|
+
};
|
|
37923
|
+
const closeAll = async () => {
|
|
37924
|
+
if (closing || state.phase === "closed") return;
|
|
37925
|
+
closing = true;
|
|
37926
|
+
const current = state;
|
|
37927
|
+
state = { phase: "closed" };
|
|
37928
|
+
for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error62) => reportError(toError(error62)));
|
|
37929
|
+
if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
37930
|
+
await wire.close().catch((error62) => reportError(toError(error62)));
|
|
37931
|
+
};
|
|
37932
|
+
wire.onmessage = (message) => {
|
|
37933
|
+
queue.push(message);
|
|
37934
|
+
pump();
|
|
37935
|
+
};
|
|
37936
|
+
wire.onerror = (error62) => {
|
|
37937
|
+
reportError(error62);
|
|
37938
|
+
if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error62);
|
|
37939
|
+
};
|
|
37940
|
+
wire.onclose = () => {
|
|
37941
|
+
if (closing || state.phase === "closed") return;
|
|
37942
|
+
closing = true;
|
|
37943
|
+
const current = state;
|
|
37944
|
+
state = { phase: "closed" };
|
|
37945
|
+
if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
37946
|
+
};
|
|
37947
|
+
const started = wire.start().catch((error62) => {
|
|
37948
|
+
reportError(toError(error62));
|
|
37949
|
+
throw error62;
|
|
37950
|
+
});
|
|
37951
|
+
started.catch(() => {
|
|
37952
|
+
});
|
|
37953
|
+
return { close: async () => {
|
|
37954
|
+
await started.catch(() => {
|
|
37955
|
+
});
|
|
37956
|
+
await closeAll();
|
|
37957
|
+
} };
|
|
37958
|
+
}
|
|
37959
|
+
function toError(value) {
|
|
37960
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
37961
|
+
}
|
|
37302
37962
|
|
|
37303
37963
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
37304
37964
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
@@ -57103,17 +57763,73 @@ Hint: ${err.hint}`);
|
|
|
57103
57763
|
}
|
|
57104
57764
|
throw err;
|
|
57105
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
|
+
}
|
|
57106
57799
|
function surfaceToolHints(server) {
|
|
57107
57800
|
const register2 = server.registerTool.bind(server);
|
|
57108
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
57109
|
-
|
|
57110
|
-
|
|
57111
|
-
|
|
57112
|
-
|
|
57113
|
-
|
|
57114
|
-
|
|
57115
|
-
|
|
57116
|
-
|
|
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
|
+
));
|
|
57117
57833
|
}
|
|
57118
57834
|
async function createMcpServer(opts) {
|
|
57119
57835
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -57128,7 +57844,7 @@ async function createMcpServer(opts) {
|
|
|
57128
57844
|
}
|
|
57129
57845
|
return server;
|
|
57130
57846
|
}
|
|
57131
|
-
function withGracefulShutdown(
|
|
57847
|
+
function withGracefulShutdown(target, opts = {}) {
|
|
57132
57848
|
const shouldExit = opts.exit ?? true;
|
|
57133
57849
|
let shuttingDown = false;
|
|
57134
57850
|
const handler = (signal) => {
|
|
@@ -57139,7 +57855,7 @@ function withGracefulShutdown(server, opts = {}) {
|
|
|
57139
57855
|
try {
|
|
57140
57856
|
if (opts.onSignal)
|
|
57141
57857
|
await opts.onSignal(signal);
|
|
57142
|
-
await
|
|
57858
|
+
await target.close();
|
|
57143
57859
|
} catch (err) {
|
|
57144
57860
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
57145
57861
|
} finally {
|
|
@@ -57151,16 +57867,26 @@ function withGracefulShutdown(server, opts = {}) {
|
|
|
57151
57867
|
process.on("SIGINT", () => handler("SIGINT"));
|
|
57152
57868
|
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
57153
57869
|
}
|
|
57154
|
-
|
|
57155
|
-
|
|
57870
|
+
function runMcp(opts) {
|
|
57871
|
+
if (opts.banner !== void 0) {
|
|
57872
|
+
console.error(opts.banner);
|
|
57873
|
+
}
|
|
57874
|
+
const spec = opts.transport ?? "stdio";
|
|
57875
|
+
const handle = serveStdio(() => createMcpServer({ ...opts, banner: void 0 }), {
|
|
57876
|
+
legacy: opts.legacy ?? "serve",
|
|
57877
|
+
onerror: opts.onerror ?? ((error62) => {
|
|
57878
|
+
console.error(`[mcp-utils] stdio server error: ${redactSecrets(error62.message)}`);
|
|
57879
|
+
}),
|
|
57880
|
+
// `'stdio'` means "let the entry make its own StdioServerTransport over
|
|
57881
|
+
// this process"; anything else is the caller's transport, which the entry
|
|
57882
|
+
// then owns (it starts it, pumps it, and closes it with the handle).
|
|
57883
|
+
...spec === "stdio" ? {} : { transport: spec }
|
|
57884
|
+
});
|
|
57156
57885
|
const shutdown = opts.shutdown ?? true;
|
|
57157
57886
|
if (shutdown !== false) {
|
|
57158
|
-
withGracefulShutdown(
|
|
57887
|
+
withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
|
|
57159
57888
|
}
|
|
57160
|
-
|
|
57161
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
57162
|
-
await server.connect(transport);
|
|
57163
|
-
return server;
|
|
57889
|
+
return handle;
|
|
57164
57890
|
}
|
|
57165
57891
|
|
|
57166
57892
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
@@ -57256,12 +57982,13 @@ function toolAnnotations(opts = {}) {
|
|
|
57256
57982
|
...opts.title !== void 0 ? { title: opts.title } : {},
|
|
57257
57983
|
readOnlyHint: opts.readOnly ?? true,
|
|
57258
57984
|
...opts.idempotent !== void 0 ? { idempotentHint: opts.idempotent } : {},
|
|
57985
|
+
...opts.destructive !== void 0 ? { destructiveHint: opts.destructive } : {},
|
|
57259
57986
|
...opts.openWorld !== void 0 ? { openWorldHint: opts.openWorld } : {}
|
|
57260
57987
|
};
|
|
57261
57988
|
}
|
|
57262
57989
|
|
|
57263
57990
|
// src/version.ts
|
|
57264
|
-
var VERSION = "2.
|
|
57991
|
+
var VERSION = "2.1.1";
|
|
57265
57992
|
|
|
57266
57993
|
// src/client.ts
|
|
57267
57994
|
import { dirname, join } from "path";
|
|
@@ -58830,7 +59557,11 @@ function registerFriendActionTools(server, client2) {
|
|
|
58830
59557
|
{
|
|
58831
59558
|
title: action.title,
|
|
58832
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.`,
|
|
58833
|
-
|
|
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 }),
|
|
58834
59565
|
inputSchema: external_exports.object({
|
|
58835
59566
|
target_uid: TargetUidSchema,
|
|
58836
59567
|
confirm: schemaConfirm
|
|
@@ -58860,7 +59591,7 @@ function registerWishlistTools(server, client2) {
|
|
|
58860
59591
|
{
|
|
58861
59592
|
title: "Add a beer to your wishlist",
|
|
58862
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.",
|
|
58863
|
-
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 }),
|
|
58864
59595
|
inputSchema: external_exports.object({
|
|
58865
59596
|
bid: BidSchema2,
|
|
58866
59597
|
confirm: schemaConfirm
|
|
@@ -58884,7 +59615,7 @@ function registerWishlistTools(server, client2) {
|
|
|
58884
59615
|
{
|
|
58885
59616
|
title: "Remove a beer from your wishlist",
|
|
58886
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.",
|
|
58887
|
-
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 }),
|
|
58888
59619
|
inputSchema: external_exports.object({
|
|
58889
59620
|
bid: BidSchema2,
|
|
58890
59621
|
confirm: schemaConfirm
|
|
@@ -58926,7 +59657,7 @@ function registerCheckinTools(server, client2) {
|
|
|
58926
59657
|
{
|
|
58927
59658
|
title: "Toast an Untappd check-in",
|
|
58928
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.",
|
|
58929
|
-
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 }),
|
|
58930
59661
|
inputSchema: external_exports.object({
|
|
58931
59662
|
checkin_id: CheckinIdSchema,
|
|
58932
59663
|
confirm: schemaConfirm
|
|
@@ -58950,7 +59681,7 @@ function registerCheckinTools(server, client2) {
|
|
|
58950
59681
|
{
|
|
58951
59682
|
title: "Comment on an Untappd check-in",
|
|
58952
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.",
|
|
58953
|
-
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 }),
|
|
58954
59685
|
inputSchema: external_exports.object({
|
|
58955
59686
|
checkin_id: CheckinIdSchema,
|
|
58956
59687
|
comment: external_exports.string().min(1).max(2e3).describe("Comment text to post"),
|
|
@@ -58976,7 +59707,7 @@ function registerCheckinTools(server, client2) {
|
|
|
58976
59707
|
{
|
|
58977
59708
|
title: "Delete a comment from an Untappd check-in",
|
|
58978
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.",
|
|
58979
|
-
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 }),
|
|
58980
59711
|
inputSchema: external_exports.object({
|
|
58981
59712
|
comment_id: external_exports.number().int().positive().describe("Untappd comment id (from a check-in's comments.items)"),
|
|
58982
59713
|
confirm: schemaConfirm
|
|
@@ -59000,7 +59731,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59000
59731
|
{
|
|
59001
59732
|
title: "Delete an Untappd check-in",
|
|
59002
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.",
|
|
59003
|
-
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 }),
|
|
59004
59735
|
inputSchema: external_exports.object({
|
|
59005
59736
|
checkin_id: CheckinIdSchema,
|
|
59006
59737
|
confirm: schemaConfirm
|
|
@@ -59024,7 +59755,7 @@ function registerCheckinTools(server, client2) {
|
|
|
59024
59755
|
{
|
|
59025
59756
|
title: "Check in a beer on Untappd",
|
|
59026
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.",
|
|
59027
|
-
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 }),
|
|
59028
59759
|
inputSchema: external_exports.object({
|
|
59029
59760
|
bid: external_exports.number().int().positive().describe("Untappd beer id to check in (from untappd_search_beer)"),
|
|
59030
59761
|
rating: RatingSchema.optional().describe("Rating 0\u20135 in 0.25 increments (omit for no rating)"),
|
|
@@ -59538,7 +60269,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
59538
60269
|
{
|
|
59539
60270
|
title: "Sync a user's check-ins into the cache",
|
|
59540
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.",
|
|
59541
|
-
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 }),
|
|
59542
60273
|
inputSchema: external_exports.object({
|
|
59543
60274
|
username: UsernameArg2,
|
|
59544
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."),
|
|
@@ -59559,7 +60290,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
59559
60290
|
{
|
|
59560
60291
|
title: "Sync a user's complete distinct-beers list into the cache",
|
|
59561
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.`,
|
|
59562
|
-
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 }),
|
|
59563
60294
|
inputSchema: external_exports.object({
|
|
59564
60295
|
username: UsernameArg2,
|
|
59565
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.")
|
|
@@ -59693,7 +60424,7 @@ function registerCacheTools(server, client2, cacheProvider) {
|
|
|
59693
60424
|
{
|
|
59694
60425
|
title: "Top-rated beers a user has NOT had, from a candidate list",
|
|
59695
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.',
|
|
59696
|
-
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 }),
|
|
59697
60428
|
inputSchema: external_exports.object({
|
|
59698
60429
|
username: UsernameArg2,
|
|
59699
60430
|
bids: external_exports.array(external_exports.number().int().positive()).min(1).max(100).describe("Candidate beer ids (1\u2013100)"),
|
package/dist/tools/cache.js
CHANGED
|
@@ -92,7 +92,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
92
92
|
'untappd_sync_user_beers for full has-had coverage. backfill_complete is only reported once ~all of ' +
|
|
93
93
|
'total_checkins is cached. Pass force_backfill: true to reset a cache wrongly marked complete and re-page ' +
|
|
94
94
|
'the whole history (cached rows are kept). Omit username for your own account.',
|
|
95
|
-
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
95
|
+
annotations: toolAnnotations({ title: "Sync a user's check-ins into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
96
96
|
inputSchema: z.object({
|
|
97
97
|
username: UsernameArg,
|
|
98
98
|
max_pages: z
|
|
@@ -123,7 +123,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
123
123
|
'user/checkins, it pages fully for any public/friend account. Offset-paged and resumable: fetches max_pages ' +
|
|
124
124
|
'per call and persists progress; run again until another_run_needed is false. Feeds the same ' +
|
|
125
125
|
'untappd_cache_has_had / has_had_many / not_had tools. Omit username for your own account.',
|
|
126
|
-
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true }),
|
|
126
|
+
annotations: toolAnnotations({ title: "Sync a user's complete distinct-beers list into the cache", readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
127
127
|
inputSchema: z.object({
|
|
128
128
|
username: UsernameArg,
|
|
129
129
|
max_pages: z
|
|
@@ -271,7 +271,7 @@ export function registerCacheTools(server, client, cacheProvider) {
|
|
|
271
271
|
'run (~100 calls/hour limit) — if more are needed it returns partial: true / another_run_needed: true, so ' +
|
|
272
272
|
're-running fills the rest. Reports the same freshness/caveat block as untappd_cache_not_had. Omit username ' +
|
|
273
273
|
'for your own account.',
|
|
274
|
-
annotations: toolAnnotations({ title: 'Top-rated beers a user has NOT had, from a candidate list', readOnly: false, idempotent: false, openWorld: true }),
|
|
274
|
+
annotations: toolAnnotations({ title: 'Top-rated beers a user has NOT had, from a candidate list', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
275
275
|
inputSchema: z.object({
|
|
276
276
|
username: UsernameArg,
|
|
277
277
|
bids: z.array(z.number().int().positive()).min(1).max(100).describe('Candidate beer ids (1–100)'),
|
package/dist/tools/checkin.js
CHANGED
|
@@ -28,7 +28,7 @@ export function registerCheckinTools(server, client) {
|
|
|
28
28
|
description: "Toast (like) a check-in on YOUR account. This endpoint is a TOGGLE: calling it on a check-in you have " +
|
|
29
29
|
'already toasted removes the toast. Without confirm: true it returns a dry-run preview and makes NO network ' +
|
|
30
30
|
'call; with confirm: true it posts. Writes to your Untappd account and is visible to others.',
|
|
31
|
-
annotations: toolAnnotations({ title: 'Toast an Untappd check-in', readOnly: false, idempotent: false, openWorld: true }),
|
|
31
|
+
annotations: toolAnnotations({ title: 'Toast an Untappd check-in', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
32
32
|
inputSchema: z.object({
|
|
33
33
|
checkin_id: CheckinIdSchema,
|
|
34
34
|
confirm: schemaConfirm,
|
|
@@ -49,7 +49,7 @@ export function registerCheckinTools(server, client) {
|
|
|
49
49
|
title: 'Comment on an Untappd check-in',
|
|
50
50
|
description: 'Post a comment on a check-in from YOUR account. Without confirm: true it returns a dry-run preview and ' +
|
|
51
51
|
'makes NO network call; with confirm: true it posts. Writes to your Untappd account and is visible to others.',
|
|
52
|
-
annotations: toolAnnotations({ title: 'Comment on an Untappd check-in', readOnly: false, idempotent: false, openWorld: true }),
|
|
52
|
+
annotations: toolAnnotations({ title: 'Comment on an Untappd check-in', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
53
53
|
inputSchema: z.object({
|
|
54
54
|
checkin_id: CheckinIdSchema,
|
|
55
55
|
comment: z.string().min(1).max(2000).describe('Comment text to post'),
|
|
@@ -72,7 +72,7 @@ export function registerCheckinTools(server, client) {
|
|
|
72
72
|
title: 'Delete a comment from an Untappd check-in',
|
|
73
73
|
description: 'Delete one of YOUR comments by its comment id (the id from a check-in\'s comments list). Without ' +
|
|
74
74
|
'confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.',
|
|
75
|
-
annotations: toolAnnotations({ title: 'Delete a comment from an Untappd check-in', readOnly: false, idempotent: true, openWorld: true }),
|
|
75
|
+
annotations: toolAnnotations({ title: 'Delete a comment from an Untappd check-in', readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
76
76
|
inputSchema: z.object({
|
|
77
77
|
comment_id: z.number().int().positive().describe('Untappd comment id (from a check-in\'s comments.items)'),
|
|
78
78
|
confirm: schemaConfirm,
|
|
@@ -93,7 +93,7 @@ export function registerCheckinTools(server, client) {
|
|
|
93
93
|
title: 'Delete an Untappd check-in',
|
|
94
94
|
description: 'Permanently delete one of YOUR check-ins by its id. This is destructive and cannot be undone. Without ' +
|
|
95
95
|
'confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it deletes.',
|
|
96
|
-
annotations: toolAnnotations({ title: 'Delete an Untappd check-in', readOnly: false, idempotent: true, openWorld: true }),
|
|
96
|
+
annotations: toolAnnotations({ title: 'Delete an Untappd check-in', readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
97
97
|
inputSchema: z.object({
|
|
98
98
|
checkin_id: CheckinIdSchema,
|
|
99
99
|
confirm: schemaConfirm,
|
|
@@ -116,7 +116,7 @@ export function registerCheckinTools(server, client) {
|
|
|
116
116
|
'(bid) from untappd_search_beer; optionally a rating (0–5 in 0.25 steps), a shout (comment), a venue via ' +
|
|
117
117
|
'foursquare_id, and a local photo via photo_path (JPEG/PNG). Without confirm: true it returns a dry-run ' +
|
|
118
118
|
'preview of the exact fields and makes NO network call; with confirm: true it posts.',
|
|
119
|
-
annotations: toolAnnotations({ title: 'Check in a beer on Untappd', readOnly: false, idempotent: false, openWorld: true }),
|
|
119
|
+
annotations: toolAnnotations({ title: 'Check in a beer on Untappd', readOnly: false, idempotent: false, openWorld: true, destructive: false }),
|
|
120
120
|
inputSchema: z.object({
|
|
121
121
|
bid: z.number().int().positive().describe('Untappd beer id to check in (from untappd_search_beer)'),
|
|
122
122
|
rating: RatingSchema.optional().describe('Rating 0–5 in 0.25 increments (omit for no rating)'),
|
package/dist/tools/friends.js
CHANGED
|
@@ -50,7 +50,11 @@ export function registerFriendActionTools(server, client) {
|
|
|
50
50
|
'Without confirm: true it returns a dry-run preview and makes NO network call; with confirm: true it ' +
|
|
51
51
|
'performs the action. Note: this endpoint path is taken from the Untappd app but is not otherwise ' +
|
|
52
52
|
'independently verified.',
|
|
53
|
-
|
|
53
|
+
// All four reach ANOTHER PERSON — the description says so itself: "affects
|
|
54
|
+
// a real relationship with another person". A friend request cannot be
|
|
55
|
+
// un-sent, and a rejection cannot be un-rejected, so none of them has an
|
|
56
|
+
// inverse in this tool set.
|
|
57
|
+
annotations: toolAnnotations({ title: action.title, readOnly: false, idempotent: true, openWorld: true, destructive: true }),
|
|
54
58
|
inputSchema: z.object({
|
|
55
59
|
target_uid: TargetUidSchema,
|
|
56
60
|
confirm: schemaConfirm,
|
package/dist/tools/wishlist.js
CHANGED
|
@@ -6,7 +6,7 @@ export function registerWishlistTools(server, client) {
|
|
|
6
6
|
title: 'Add a beer to your wishlist',
|
|
7
7
|
description: 'Add a beer to YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview and ' +
|
|
8
8
|
'makes NO network call; with confirm: true it adds. Writes to your account.',
|
|
9
|
-
annotations: toolAnnotations({ title: 'Add a beer to your wishlist', readOnly: false, idempotent: true, openWorld: true }),
|
|
9
|
+
annotations: toolAnnotations({ title: 'Add a beer to your wishlist', readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
bid: BidSchema,
|
|
12
12
|
confirm: schemaConfirm,
|
|
@@ -27,7 +27,7 @@ export function registerWishlistTools(server, client) {
|
|
|
27
27
|
title: 'Remove a beer from your wishlist',
|
|
28
28
|
description: 'Remove a beer from YOUR Untappd wishlist by its bid. Without confirm: true it returns a dry-run preview ' +
|
|
29
29
|
'and makes NO network call; with confirm: true it removes. Writes to your account.',
|
|
30
|
-
annotations: toolAnnotations({ title: 'Remove a beer from your wishlist', readOnly: false, idempotent: true, openWorld: true }),
|
|
30
|
+
annotations: toolAnnotations({ title: 'Remove a beer from your wishlist', readOnly: false, idempotent: true, openWorld: true, destructive: false }),
|
|
31
31
|
inputSchema: z.object({
|
|
32
32
|
bid: BidSchema,
|
|
33
33
|
confirm: schemaConfirm,
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// json's `extra-files`), and `versionSyncTest` guards that it stays equal to
|
|
4
4
|
// package.json. Import VERSION wherever the version is needed rather than
|
|
5
5
|
// re-declaring it.
|
|
6
|
-
export const VERSION = '2.
|
|
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.
|
|
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": "^
|
|
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.
|
|
9
|
+
"version": "2.1.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "untappd-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.1.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|