gogcli-mcp-contacts 4.0.0 → 4.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Extended Google Contacts [MCP](https://modelcontextprotocol.io) server via [gogc
8
8
  ## Requirements
9
9
 
10
10
  - [gogcli](https://github.com/openclaw/gogcli) installed and authenticated
11
- - Node.js 18+
11
+ - Node.js 22+
12
12
 
13
13
  ```bash
14
14
  brew install gogcli
package/SKILL.md CHANGED
@@ -12,7 +12,7 @@ Extended Google Contacts MCP server via [gogcli](https://github.com/openclaw/gog
12
12
  ## Requirements
13
13
 
14
14
  - [gogcli](https://github.com/openclaw/gogcli) installed and authenticated
15
- - Node.js 18 or later
15
+ - Node.js 22 or later
16
16
 
17
17
  ## Setup
18
18
 
package/dist/index.js CHANGED
@@ -20845,6 +20845,7 @@ function missingClientCapabilities(required2, declared) {
20845
20845
  return Object.keys(missing).length > 0 ? missing : void 0;
20846
20846
  }
20847
20847
  var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
20848
+ var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
20848
20849
  function isModernProtocolVersion(version2) {
20849
20850
  return version2 >= FIRST_MODERN_PROTOCOL_VERSION;
20850
20851
  }
@@ -23403,6 +23404,25 @@ function isSpecNotificationMethod(method) {
23403
23404
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
23404
23405
  }
23405
23406
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
23407
+ function isPlainObject$3(value) {
23408
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23409
+ }
23410
+ function requestMetaOf(params) {
23411
+ if (!isPlainObject$3(params)) return void 0;
23412
+ const meta3 = params["_meta"];
23413
+ return isPlainObject$3(meta3) ? meta3 : void 0;
23414
+ }
23415
+ function hasEnvelopeClaim(params) {
23416
+ const meta3 = requestMetaOf(params);
23417
+ return meta3 !== void 0 && PROTOCOL_VERSION_META_KEY in meta3;
23418
+ }
23419
+ function envelopeClaimVersion(params) {
23420
+ const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
23421
+ return typeof value === "string" ? value : void 0;
23422
+ }
23423
+ function validateEnvelopeMeta(meta3) {
23424
+ return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta3);
23425
+ }
23406
23426
  var schemas_exports3 = /* @__PURE__ */ __exportAll({
23407
23427
  AnnotationsSchema: () => AnnotationsSchema,
23408
23428
  AudioContentSchema: () => AudioContentSchema,
@@ -23753,6 +23773,45 @@ var LADDER_ERROR_HTTP_STATUS = {
23753
23773
  [ProtocolErrorCode.MissingRequiredClientCapability]: 400,
23754
23774
  [HEADER_MISMATCH_ERROR_CODE]: 400
23755
23775
  };
23776
+ function rejection(rung, cell, httpStatus, error62, settled) {
23777
+ return {
23778
+ kind: "reject",
23779
+ rung,
23780
+ cell,
23781
+ httpStatus,
23782
+ code: error62.code,
23783
+ message: error62.message,
23784
+ ...error62.data !== void 0 && { data: error62.data },
23785
+ settled
23786
+ };
23787
+ }
23788
+ function carriesValidModernEnvelopeClaim(params) {
23789
+ if (!hasEnvelopeClaim(params)) return false;
23790
+ const claimedVersion = envelopeClaimVersion(params);
23791
+ if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
23792
+ const meta3 = requestMetaOf(params);
23793
+ return meta3 !== void 0 && validateEnvelopeMeta(meta3).length === 0;
23794
+ }
23795
+ function modernOnlyStrictRejection(route, supportedVersions) {
23796
+ switch (route.reason) {
23797
+ case "http-method":
23798
+ return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
23799
+ case "batch":
23800
+ 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);
23801
+ case "response":
23802
+ 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);
23803
+ case "notification":
23804
+ return;
23805
+ case "initialize":
23806
+ case "no-claim": {
23807
+ const requested = route.requestedVersion;
23808
+ 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({
23809
+ supported: [...supportedVersions],
23810
+ requested
23811
+ }), true);
23812
+ }
23813
+ }
23814
+ }
23756
23815
  function parseSchema(schema, data) {
23757
23816
  return safeParse2(schema, data);
23758
23817
  }
@@ -24314,6 +24373,9 @@ function withRequestStateValue(ctx, value) {
24314
24373
  };
24315
24374
  }
24316
24375
  var writeNegotiatedProtocolVersion;
24376
+ function setNegotiatedProtocolVersion(instance, version2) {
24377
+ writeNegotiatedProtocolVersion(instance, version2);
24378
+ }
24317
24379
  var Protocol = class {
24318
24380
  _transport;
24319
24381
  _requestMessageId = 0;
@@ -32257,6 +32319,190 @@ function getCompleter(schema) {
32257
32319
  return schema[COMPLETABLE_SYMBOL]?.complete;
32258
32320
  }
32259
32321
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
32322
+ function listenFilterAccepts(filter, event) {
32323
+ switch (event.kind) {
32324
+ case "tools_list_changed":
32325
+ return filter.toolsListChanged === true;
32326
+ case "prompts_list_changed":
32327
+ return filter.promptsListChanged === true;
32328
+ case "resources_list_changed":
32329
+ return filter.resourcesListChanged === true;
32330
+ case "resource_updated":
32331
+ return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
32332
+ }
32333
+ }
32334
+ function honoredSubset(requested, capabilities) {
32335
+ const honored = {};
32336
+ const allow = (bit) => capabilities === void 0 || bit === true;
32337
+ if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
32338
+ if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
32339
+ if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
32340
+ if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
32341
+ return honored;
32342
+ }
32343
+ var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
32344
+ function stampSubscriptionId(notification, subscriptionId) {
32345
+ return {
32346
+ method: notification.method,
32347
+ params: {
32348
+ ...notification.params,
32349
+ _meta: {
32350
+ ...notification.params?._meta,
32351
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId
32352
+ }
32353
+ }
32354
+ };
32355
+ }
32356
+ function parseListenFilter(message) {
32357
+ const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
32358
+ return outcome.ok ? outcome.value.params?.notifications : void 0;
32359
+ }
32360
+ var CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
32361
+ "notifications/tools/list_changed",
32362
+ "notifications/prompts/list_changed",
32363
+ "notifications/resources/list_changed",
32364
+ "notifications/resources/updated"
32365
+ ]);
32366
+ var StdioListenRouter = class {
32367
+ /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
32368
+ _subs = /* @__PURE__ */ new Map();
32369
+ /**
32370
+ * The serving instance's declared capabilities. Filled in by the entry
32371
+ * once the modern instance is constructed (the router is created before
32372
+ * the instance exists), so the acknowledged filter is narrowed against
32373
+ * what the server can actually deliver.
32374
+ */
32375
+ _serverCapabilities;
32376
+ /**
32377
+ * The serving instance's identity, stamped onto the graceful-close
32378
+ * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
32379
+ * `ResultMetaObject`). Handed over together with the capabilities.
32380
+ */
32381
+ _serverInfo;
32382
+ constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
32383
+ this._maxSubscriptions = _maxSubscriptions;
32384
+ this._serverCapabilities = serverCapabilities;
32385
+ this._serverInfo = serverInfo;
32386
+ }
32387
+ /**
32388
+ * Record the serving instance's declared capabilities and identity once
32389
+ * it has been constructed. Called by `serveStdio`'s connect path;
32390
+ * subsequent `serve()` calls narrow the honored filter against the
32391
+ * capabilities, and `teardownAll()` stamps the identity.
32392
+ */
32393
+ setServerCapabilities(capabilities, serverInfo) {
32394
+ this._serverCapabilities = capabilities;
32395
+ if (serverInfo !== void 0) this._serverInfo = serverInfo;
32396
+ }
32397
+ /** Whether `id` is an active listen subscription on this connection. */
32398
+ has(id) {
32399
+ return this._subs.has(id);
32400
+ }
32401
+ /**
32402
+ * Serve one inbound `subscriptions/listen` request: registers the
32403
+ * subscription and returns the stamped acknowledged notification (or, on
32404
+ * capacity / params rejection, the in-band JSON-RPC error response).
32405
+ *
32406
+ * @throws when called before {@linkcode setServerCapabilities} (or the
32407
+ * constructor) has supplied the serving instance's capabilities. Honoring a
32408
+ * filter without knowing the server's advertised capabilities would fail
32409
+ * open (deliver unadvertised types); the entry guarantees capabilities are
32410
+ * set before any listen request is routed here.
32411
+ */
32412
+ serve(message) {
32413
+ if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
32414
+ if (this._subs.size >= this._maxSubscriptions) return {
32415
+ jsonrpc: "2.0",
32416
+ id: message.id,
32417
+ error: {
32418
+ code: -32603,
32419
+ message: "Subscription limit reached"
32420
+ }
32421
+ };
32422
+ const filter = parseListenFilter(message);
32423
+ if (filter === void 0) return {
32424
+ jsonrpc: "2.0",
32425
+ id: message.id,
32426
+ error: {
32427
+ code: -32602,
32428
+ message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
32429
+ }
32430
+ };
32431
+ const honored = honoredSubset(filter, this._serverCapabilities);
32432
+ this._subs.set(message.id, honored);
32433
+ return stampSubscriptionId({
32434
+ method: "notifications/subscriptions/acknowledged",
32435
+ params: { notifications: honored }
32436
+ }, message.id);
32437
+ }
32438
+ /**
32439
+ * Tear down one subscription (inbound `notifications/cancelled`). Returns
32440
+ * `true` when a subscription was removed. After this call NOTHING further
32441
+ * is delivered for that subscription id (the post-cancel hardening).
32442
+ */
32443
+ cancel(id) {
32444
+ return this._subs.delete(id);
32445
+ }
32446
+ /**
32447
+ * Route an outbound notification through the active subscriptions.
32448
+ *
32449
+ * - For a subscription-gated change notification, returns one stamped copy
32450
+ * per subscription that opted in to it (an empty array means it is
32451
+ * dropped — the modern era never delivers an un-requested change type).
32452
+ * - For any other outbound message, returns `'passthrough'` (the entry
32453
+ * forwards it as-is).
32454
+ */
32455
+ routeOutbound(message) {
32456
+ if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
32457
+ const uriParam = message.params?.["uri"];
32458
+ const uri = typeof uriParam === "string" ? uriParam : void 0;
32459
+ const event = notificationToServerEvent(message.method, uri);
32460
+ const out = [];
32461
+ for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
32462
+ method: message.method,
32463
+ params: message.params ?? {}
32464
+ }, subscriptionId));
32465
+ return out;
32466
+ }
32467
+ /**
32468
+ * Server-side graceful teardown of every active subscription: returns the
32469
+ * empty `subscriptions/listen` JSON-RPC result for each subscription id —
32470
+ * the spec's graceful-close signal, `_meta` carrying the subscription id
32471
+ * and the serving instance's identity — for the entry to emit before
32472
+ * closing the wire. Clears the set so nothing further is delivered.
32473
+ */
32474
+ teardownAll() {
32475
+ const out = [];
32476
+ for (const id of this._subs.keys()) out.push({
32477
+ jsonrpc: "2.0",
32478
+ id,
32479
+ result: {
32480
+ resultType: "complete",
32481
+ _meta: {
32482
+ [SUBSCRIPTION_ID_META_KEY]: id,
32483
+ ...this._serverInfo !== void 0 && { [SERVER_INFO_META_KEY]: this._serverInfo }
32484
+ }
32485
+ }
32486
+ });
32487
+ this._subs.clear();
32488
+ return out;
32489
+ }
32490
+ };
32491
+ function notificationToServerEvent(method, uri) {
32492
+ switch (method) {
32493
+ case "notifications/tools/list_changed":
32494
+ return { kind: "tools_list_changed" };
32495
+ case "notifications/prompts/list_changed":
32496
+ return { kind: "prompts_list_changed" };
32497
+ case "notifications/resources/list_changed":
32498
+ return { kind: "resources_list_changed" };
32499
+ default:
32500
+ return {
32501
+ kind: "resource_updated",
32502
+ uri: uri ?? ""
32503
+ };
32504
+ }
32505
+ }
32260
32506
  var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
32261
32507
  var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
32262
32508
  function resolveLegacyShimOptions(options) {
@@ -32394,6 +32640,12 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
32394
32640
  var writeClientIdentity;
32395
32641
  var installDiscoverHandler;
32396
32642
  var readServerIdentity;
32643
+ function installModernOnlyHandlers(server, servedModernVersions) {
32644
+ installDiscoverHandler(server, servedModernVersions);
32645
+ }
32646
+ function serverIdentityOf(server) {
32647
+ return readServerIdentity(server);
32648
+ }
32397
32649
  var Server = class extends Protocol {
32398
32650
  _clientCapabilities;
32399
32651
  _clientVersion;
@@ -33668,6 +33920,400 @@ var StdioServerTransport = class {
33668
33920
  });
33669
33921
  }
33670
33922
  };
33923
+ var DISCARD_ANSWER_TIMEOUT_MS = 3e3;
33924
+ var StdioConnectionChannel = class {
33925
+ onclose;
33926
+ onerror;
33927
+ onmessage;
33928
+ _closed = false;
33929
+ /** Request ids the entry delivered to the instance that the instance has not yet answered. */
33930
+ _pendingRequests = /* @__PURE__ */ new Set();
33931
+ _drainWaiters = [];
33932
+ constructor(_wire, _onInstanceClose, _outboundIntercept) {
33933
+ this._wire = _wire;
33934
+ this._onInstanceClose = _onInstanceClose;
33935
+ this._outboundIntercept = _outboundIntercept;
33936
+ }
33937
+ async start() {
33938
+ }
33939
+ async send(message, options) {
33940
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
33941
+ const { id } = message;
33942
+ if (id !== void 0) this._settle(id);
33943
+ }
33944
+ if (this._closed) return;
33945
+ if (this._outboundIntercept?.(message) === "handled") return;
33946
+ return this._wire.send(message, options);
33947
+ }
33948
+ setProtocolVersion = (version2) => {
33949
+ this._wire.setProtocolVersion?.(version2);
33950
+ };
33951
+ /** Forwards one inbound message to the connected instance. */
33952
+ deliver(message, extra) {
33953
+ if (this._closed) return;
33954
+ if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
33955
+ else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
33956
+ const cancelledId = message.params?.requestId;
33957
+ if (cancelledId !== void 0) this._settle(cancelledId);
33958
+ }
33959
+ this.onmessage?.(message, extra);
33960
+ }
33961
+ /**
33962
+ * Resolves once every request delivered to the instance has been answered
33963
+ * through {@linkcode send}, settled by a delivered cancellation, or the
33964
+ * channel has been closed and nothing further can be answered. The wait is
33965
+ * bounded by `timeoutMs` as a backstop so no edge can hold the caller
33966
+ * indefinitely; resolves `false` only when the bound elapsed with requests
33967
+ * still unanswered. Used by the probe-discard path so a probe request the
33968
+ * entry accepted is never silently dropped.
33969
+ */
33970
+ async whenRequestsAnswered(timeoutMs) {
33971
+ if (this._closed || this._pendingRequests.size === 0) return true;
33972
+ return await new Promise((resolve) => {
33973
+ const waiter = () => {
33974
+ clearTimeout(timer);
33975
+ resolve(true);
33976
+ };
33977
+ const timer = setTimeout(() => {
33978
+ this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
33979
+ resolve(false);
33980
+ }, timeoutMs);
33981
+ this._drainWaiters.push(waiter);
33982
+ });
33983
+ }
33984
+ async close() {
33985
+ if (this._closed) return;
33986
+ this._closed = true;
33987
+ this._pendingRequests.clear();
33988
+ this._releaseDrainWaiters();
33989
+ try {
33990
+ this._onInstanceClose();
33991
+ } finally {
33992
+ this.onclose?.();
33993
+ }
33994
+ }
33995
+ _settle(id) {
33996
+ this._pendingRequests.delete(id);
33997
+ if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
33998
+ }
33999
+ _releaseDrainWaiters() {
34000
+ const waiters = this._drainWaiters;
34001
+ this._drainWaiters = [];
34002
+ for (const waiter of waiters) waiter();
34003
+ }
34004
+ };
34005
+ function classifyOpeningMessage(message) {
34006
+ const params = message.params;
34007
+ if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
34008
+ const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
34009
+ return {
34010
+ kind: "legacy",
34011
+ reason: "initialize",
34012
+ ...requestedVersion !== void 0 && { requestedVersion }
34013
+ };
34014
+ }
34015
+ if (!hasEnvelopeClaim(params)) return {
34016
+ kind: "legacy",
34017
+ reason: "no-claim"
34018
+ };
34019
+ const meta3 = requestMetaOf(params);
34020
+ const firstIssue = (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0];
34021
+ if (firstIssue !== void 0) return {
34022
+ kind: "invalid-envelope",
34023
+ issue: firstIssue
34024
+ };
34025
+ const claimedVersion = envelopeClaimVersion(params);
34026
+ if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
34027
+ kind: "unsupported-revision",
34028
+ requested: claimedVersion ?? "unknown"
34029
+ };
34030
+ return {
34031
+ kind: "modern",
34032
+ revision: claimedVersion,
34033
+ classification: {
34034
+ era: "modern",
34035
+ revision: claimedVersion
34036
+ }
34037
+ };
34038
+ }
34039
+ function serveStdio(factory, options = {}) {
34040
+ const legacyMode = options.legacy ?? "serve";
34041
+ const wire = options.transport ?? new StdioServerTransport();
34042
+ let state = { phase: "opening" };
34043
+ let discarding;
34044
+ let closing = false;
34045
+ const isTornDown = () => closing || state.phase === "closed";
34046
+ const reportError = (error62) => {
34047
+ try {
34048
+ options.onerror?.(error62);
34049
+ } catch {
34050
+ }
34051
+ };
34052
+ const writeErrorResponse = (id, code, message, data) => wire.send({
34053
+ jsonrpc: "2.0",
34054
+ id,
34055
+ error: {
34056
+ code,
34057
+ message,
34058
+ ...data !== void 0 && { data }
34059
+ }
34060
+ }).catch((error62) => reportError(toError(error62)));
34061
+ const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
34062
+ const modernOutboundIntercept = (message) => {
34063
+ if (!isJSONRPCNotification(message)) return void 0;
34064
+ const routed = listenRouter.routeOutbound(message);
34065
+ if (routed === "passthrough") return void 0;
34066
+ for (const stamped of routed) wire.send({
34067
+ jsonrpc: "2.0",
34068
+ ...stamped
34069
+ }).catch((error62) => reportError(toError(error62)));
34070
+ return "handled";
34071
+ };
34072
+ const tryServeListen = async (message) => {
34073
+ if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
34074
+ const meta3 = requestMetaOf(message.params);
34075
+ const issue2 = hasEnvelopeClaim(message.params) ? (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0] : {
34076
+ key: "_meta",
34077
+ problem: "the per-request envelope is required on protocol revision 2026-07-28"
34078
+ };
34079
+ const claimedVersion = envelopeClaimVersion(message.params);
34080
+ let reply;
34081
+ if (issue2 !== void 0) reply = {
34082
+ jsonrpc: "2.0",
34083
+ id: message.id,
34084
+ error: {
34085
+ code: -32602,
34086
+ message: `Invalid _meta envelope: ${issue2.key}: ${issue2.problem}`
34087
+ }
34088
+ };
34089
+ else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
34090
+ const error62 = new UnsupportedProtocolVersionError({
34091
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
34092
+ requested: claimedVersion ?? "unknown"
34093
+ });
34094
+ reply = {
34095
+ jsonrpc: "2.0",
34096
+ id: message.id,
34097
+ error: {
34098
+ code: error62.code,
34099
+ message: error62.message,
34100
+ data: error62.data
34101
+ }
34102
+ };
34103
+ } else reply = listenRouter.serve(message);
34104
+ await wire.send("error" in reply ? reply : {
34105
+ jsonrpc: "2.0",
34106
+ method: reply.method,
34107
+ params: reply.params
34108
+ }).catch((error62) => reportError(toError(error62)));
34109
+ return true;
34110
+ }
34111
+ if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
34112
+ const cancelledId = message.params?.requestId;
34113
+ if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
34114
+ }
34115
+ return false;
34116
+ };
34117
+ const answerLegacyRejection = (request, reason, requestedVersion) => {
34118
+ const rejection2 = modernOnlyStrictRejection({
34119
+ kind: "legacy",
34120
+ reason,
34121
+ ...requestedVersion !== void 0 && { requestedVersion }
34122
+ }, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
34123
+ if (rejection2 === void 0) return Promise.resolve();
34124
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection2.cell}): ${rejection2.message}`));
34125
+ return writeErrorResponse(request.id, rejection2.code, rejection2.message, rejection2.data);
34126
+ };
34127
+ const onInstanceClosed = (channel) => {
34128
+ if (closing || channel === discarding) return;
34129
+ closeAll();
34130
+ };
34131
+ const connectInstance = async (era, revision) => {
34132
+ const product = await factory({ era });
34133
+ const server = product instanceof McpServer ? product.server : product;
34134
+ if (era === "modern") {
34135
+ setNegotiatedProtocolVersion(server, revision);
34136
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
34137
+ listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
34138
+ }
34139
+ const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
34140
+ await product.connect(channel);
34141
+ return {
34142
+ product,
34143
+ channel
34144
+ };
34145
+ };
34146
+ const disposeLateInstance = (instance) => instance.product.close().catch((error62) => reportError(toError(error62)));
34147
+ const discardProbeInstance = async (instance) => {
34148
+ discarding = instance.channel;
34149
+ try {
34150
+ 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`));
34151
+ await instance.product.close();
34152
+ } catch (error62) {
34153
+ reportError(toError(error62));
34154
+ } finally {
34155
+ discarding = void 0;
34156
+ }
34157
+ };
34158
+ const processMessage = async (message) => {
34159
+ if (state.phase === "closed") return;
34160
+ if (state.phase === "pinned") {
34161
+ if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
34162
+ await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
34163
+ return;
34164
+ }
34165
+ if (state.era === "modern" && await tryServeListen(message)) return;
34166
+ state.instance.channel.deliver(message);
34167
+ return;
34168
+ }
34169
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
34170
+ reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
34171
+ return;
34172
+ }
34173
+ const opening = classifyOpeningMessage(message);
34174
+ switch (opening.kind) {
34175
+ case "invalid-envelope": {
34176
+ const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
34177
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
34178
+ else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
34179
+ return;
34180
+ }
34181
+ case "unsupported-revision":
34182
+ if (isJSONRPCRequest(message)) {
34183
+ const error62 = new UnsupportedProtocolVersionError({
34184
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
34185
+ requested: opening.requested
34186
+ });
34187
+ reportError(error62);
34188
+ await writeErrorResponse(message.id, error62.code, error62.message, error62.data);
34189
+ } else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
34190
+ return;
34191
+ case "modern":
34192
+ if (isJSONRPCRequest(message) && message.method === "server/discover") {
34193
+ if (state.phase === "probe") {
34194
+ state.instance.channel.deliver(message, { classification: opening.classification });
34195
+ return;
34196
+ }
34197
+ const instance = await connectInstance("modern", opening.revision);
34198
+ if (isTornDown()) {
34199
+ await disposeLateInstance(instance);
34200
+ return;
34201
+ }
34202
+ state = {
34203
+ phase: "probe",
34204
+ instance
34205
+ };
34206
+ instance.channel.deliver(message, { classification: opening.classification });
34207
+ return;
34208
+ }
34209
+ if (state.phase === "probe") {
34210
+ if (isJSONRPCNotification(message)) {
34211
+ state.instance.channel.deliver(message, { classification: opening.classification });
34212
+ return;
34213
+ }
34214
+ state = {
34215
+ phase: "pinned",
34216
+ era: "modern",
34217
+ instance: state.instance
34218
+ };
34219
+ } else {
34220
+ const instance = await connectInstance("modern", opening.revision);
34221
+ if (isTornDown()) {
34222
+ await disposeLateInstance(instance);
34223
+ return;
34224
+ }
34225
+ state = {
34226
+ phase: "pinned",
34227
+ era: "modern",
34228
+ instance
34229
+ };
34230
+ }
34231
+ if (await tryServeListen(message)) return;
34232
+ state.instance.channel.deliver(message, { classification: opening.classification });
34233
+ return;
34234
+ case "legacy": {
34235
+ if (legacyMode === "reject") {
34236
+ if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
34237
+ return;
34238
+ }
34239
+ if (state.phase === "probe") {
34240
+ await discardProbeInstance(state.instance);
34241
+ if (isTornDown()) return;
34242
+ state = { phase: "opening" };
34243
+ }
34244
+ const instance = await connectInstance("legacy");
34245
+ if (isTornDown()) {
34246
+ await disposeLateInstance(instance);
34247
+ return;
34248
+ }
34249
+ state = {
34250
+ phase: "pinned",
34251
+ era: "legacy",
34252
+ instance
34253
+ };
34254
+ state.instance.channel.deliver(message);
34255
+ return;
34256
+ }
34257
+ }
34258
+ };
34259
+ const queue = [];
34260
+ let pumping = false;
34261
+ const pump = async () => {
34262
+ if (pumping) return;
34263
+ pumping = true;
34264
+ try {
34265
+ while (queue.length > 0) {
34266
+ const message = queue.shift();
34267
+ try {
34268
+ await processMessage(message);
34269
+ } catch (error62) {
34270
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
34271
+ reportError(toError(error62));
34272
+ }
34273
+ }
34274
+ } finally {
34275
+ pumping = false;
34276
+ }
34277
+ };
34278
+ const closeAll = async () => {
34279
+ if (closing || state.phase === "closed") return;
34280
+ closing = true;
34281
+ const current = state;
34282
+ state = { phase: "closed" };
34283
+ for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error62) => reportError(toError(error62)));
34284
+ if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error62) => reportError(toError(error62)));
34285
+ await wire.close().catch((error62) => reportError(toError(error62)));
34286
+ };
34287
+ wire.onmessage = (message) => {
34288
+ queue.push(message);
34289
+ pump();
34290
+ };
34291
+ wire.onerror = (error62) => {
34292
+ reportError(error62);
34293
+ if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error62);
34294
+ };
34295
+ wire.onclose = () => {
34296
+ if (closing || state.phase === "closed") return;
34297
+ closing = true;
34298
+ const current = state;
34299
+ state = { phase: "closed" };
34300
+ if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error62) => reportError(toError(error62)));
34301
+ };
34302
+ const started = wire.start().catch((error62) => {
34303
+ reportError(toError(error62));
34304
+ throw error62;
34305
+ });
34306
+ started.catch(() => {
34307
+ });
34308
+ return { close: async () => {
34309
+ await started.catch(() => {
34310
+ });
34311
+ await closeAll();
34312
+ } };
34313
+ }
34314
+ function toError(value) {
34315
+ return value instanceof Error ? value : new Error(String(value));
34316
+ }
33671
34317
 
33672
34318
  // ../../node_modules/@chrischall/mcp-utils/dist/errors/index.js
33673
34319
  var McpToolError = class extends Error {
@@ -33812,7 +34458,7 @@ async function createMcpServer(opts) {
33812
34458
  }
33813
34459
  return server;
33814
34460
  }
33815
- function withGracefulShutdown(server, opts = {}) {
34461
+ function withGracefulShutdown(target, opts = {}) {
33816
34462
  const shouldExit = opts.exit ?? true;
33817
34463
  let shuttingDown = false;
33818
34464
  const handler = (signal) => {
@@ -33823,7 +34469,7 @@ function withGracefulShutdown(server, opts = {}) {
33823
34469
  try {
33824
34470
  if (opts.onSignal)
33825
34471
  await opts.onSignal(signal);
33826
- await server.close();
34472
+ await target.close();
33827
34473
  } catch (err) {
33828
34474
  console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
33829
34475
  } finally {
@@ -33835,16 +34481,26 @@ function withGracefulShutdown(server, opts = {}) {
33835
34481
  process.on("SIGINT", () => handler("SIGINT"));
33836
34482
  process.on("SIGTERM", () => handler("SIGTERM"));
33837
34483
  }
33838
- async function runMcp(opts) {
33839
- const server = await createMcpServer(opts);
34484
+ function runMcp(opts) {
34485
+ if (opts.banner !== void 0) {
34486
+ console.error(opts.banner);
34487
+ }
34488
+ const spec = opts.transport ?? "stdio";
34489
+ const handle = serveStdio(() => createMcpServer({ ...opts, banner: void 0 }), {
34490
+ legacy: opts.legacy ?? "serve",
34491
+ onerror: opts.onerror ?? ((error62) => {
34492
+ console.error(`[mcp-utils] stdio server error: ${redactSecrets(error62.message)}`);
34493
+ }),
34494
+ // `'stdio'` means "let the entry make its own StdioServerTransport over
34495
+ // this process"; anything else is the caller's transport, which the entry
34496
+ // then owns (it starts it, pumps it, and closes it with the handle).
34497
+ ...spec === "stdio" ? {} : { transport: spec }
34498
+ });
33840
34499
  const shutdown = opts.shutdown ?? true;
33841
34500
  if (shutdown !== false) {
33842
- withGracefulShutdown(server, shutdown === true ? {} : shutdown);
34501
+ withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
33843
34502
  }
33844
- const spec = opts.transport ?? "stdio";
33845
- const transport = spec === "stdio" ? new StdioServerTransport() : spec;
33846
- await server.connect(transport);
33847
- return server;
34503
+ return handle;
33848
34504
  }
33849
34505
 
33850
34506
  // ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
@@ -33896,222 +34552,26 @@ var pageSchema = {
33896
34552
 
33897
34553
  // ../gogcli-mcp/src/runner.ts
33898
34554
  import { delimiter, join } from "node:path";
33899
- function isGogFileArg(arg) {
33900
- return typeof arg !== "string";
33901
- }
33902
- var TIMEOUT_MS = 3e4;
33903
- function readonlyEnvEnabled() {
33904
- return readEnvVar("GOG_READONLY") !== void 0 && parseBoolEnv("GOG_READONLY", { default: true });
33905
- }
33906
- function sanitizedEnv() {
33907
- const result = {};
33908
- for (const [key, value] of Object.entries(process.env)) {
33909
- if (key === "GOG_ACCESS_TOKEN") continue;
33910
- if (key === "GOOGLE_APPLICATION_CREDENTIALS") continue;
33911
- if (/(_TOKEN|_SECRET|_KEY|_CREDENTIALS)$/.test(key)) continue;
33912
- result[key] = value;
34555
+
34556
+ // ../gogcli-mcp/src/timestamps.ts
34557
+ var DEFAULT_DISPLAY_TZ = "America/New_York";
34558
+ function isValidTimeZone(tz) {
34559
+ try {
34560
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
34561
+ return true;
34562
+ } catch {
34563
+ return false;
33913
34564
  }
33914
- return result;
33915
34565
  }
33916
- var TOKEN_LEFT_BOUNDARY = "(?<![A-Za-z0-9+/])";
33917
- var GOOGLE_TOKEN_PATTERNS = [
33918
- new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
33919
- // OAuth2 access tokens
33920
- new RegExp(`${TOKEN_LEFT_BOUNDARY}1//[A-Za-z0-9._\\-]+`, "g")
33921
- // OAuth2 refresh tokens
33922
- ];
33923
- function redactGoogleTokens(text) {
33924
- let redacted = text;
33925
- for (const re of GOOGLE_TOKEN_PATTERNS) {
33926
- redacted = redacted.replace(re, "[REDACTED]");
33927
- }
33928
- return redacted;
34566
+ function displayTimeZone() {
34567
+ const configured = readEnvVar("DISPLAY_TZ");
34568
+ if (configured && isValidTimeZone(configured)) return configured;
34569
+ return DEFAULT_DISPLAY_TZ;
33929
34570
  }
33930
- function redactSecrets2(text) {
33931
- return redactGoogleTokens(redactSecrets(text));
33932
- }
33933
- var OPAQUE_FIELD_VALUE = "[A-Za-z0-9+/_-]{16,}={0,2}";
33934
- var opaquePlaceholder = (i) => `\0gogOpaque${i}\0`;
33935
- function redactPreservingOpaqueFields(text, fields, redact) {
33936
- const lifted = [];
33937
- let staged = text;
33938
- for (const field of fields) {
33939
- const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33940
- const re = new RegExp(`("${escaped}"\\s*:\\s*")(${OPAQUE_FIELD_VALUE})(")`, "g");
33941
- staged = staged.replace(re, (_m, open2, value, close) => {
33942
- lifted.push(value);
33943
- return `${open2}${opaquePlaceholder(lifted.length - 1)}${close}`;
33944
- });
33945
- }
33946
- if (lifted.length === 0) return redact(text);
33947
- let redacted = redact(staged);
33948
- lifted.forEach((value, i) => {
33949
- redacted = redacted.split(opaquePlaceholder(i)).join(value);
33950
- });
33951
- return redacted;
33952
- }
33953
- function augmentedPath() {
33954
- const home = process.env.HOME;
33955
- const candidates = [
33956
- process.env.PATH ?? "",
33957
- "/opt/homebrew/bin",
33958
- "/usr/local/bin",
33959
- home ? `${home}/.local/bin` : "",
33960
- home ? `${home}/go/bin` : ""
33961
- ];
33962
- const seen = /* @__PURE__ */ new Set();
33963
- const parts = [];
33964
- for (const c of candidates) {
33965
- if (!c) continue;
33966
- for (const dir of c.split(delimiter)) {
33967
- if (!dir || seen.has(dir)) continue;
33968
- seen.add(dir);
33969
- parts.push(dir);
33970
- }
33971
- }
33972
- return parts.join(delimiter);
33973
- }
33974
- function formatTimeout(ms) {
33975
- const seconds = Math.round(ms / 1e3);
33976
- if (seconds >= 60) {
33977
- const minutes = Math.round(seconds / 60);
33978
- return `${ms}ms (${minutes} minute${minutes !== 1 ? "s" : ""})`;
33979
- }
33980
- return `${ms}ms`;
33981
- }
33982
- async function spawnWithTempFiles(args, opts) {
33983
- const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
33984
- const { tmpdir } = await import("node:os");
33985
- const dir = await mkdtemp(join(tmpdir(), "gogcli-mcp-"));
33986
- try {
33987
- const argv = [];
33988
- let seq = 0;
33989
- for (const arg of args) {
33990
- if (!isGogFileArg(arg)) {
33991
- argv.push(arg);
33992
- continue;
33993
- }
33994
- const sub = join(dir, String(seq));
33995
- seq += 1;
33996
- await mkdir(sub, { recursive: true, mode: 448 });
33997
- const path = join(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
33998
- const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
33999
- await writeFile(path, data, { mode: 384 });
34000
- argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
34001
- }
34002
- return await spawnGog(argv, opts);
34003
- } finally {
34004
- await rm(dir, { recursive: true, force: true }).catch(() => {
34005
- });
34006
- }
34007
- }
34008
- function spawnExecutor(args, opts) {
34009
- if (args.some(isGogFileArg)) {
34010
- return spawnWithTempFiles(args, opts);
34011
- }
34012
- return spawnGog(args, opts);
34013
- }
34014
- async function spawnGog(fullArgs, opts) {
34015
- const { timeout, interactive = false, spawner, binary = false } = opts;
34016
- const spawn = spawner ?? (await import("node:child_process")).spawn;
34017
- const effectiveTimeout = timeout ?? TIMEOUT_MS;
34018
- return new Promise((resolve, reject) => {
34019
- const childEnv = { ...sanitizedEnv(), PATH: augmentedPath() };
34020
- const child = spawn(readEnvVar("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
34021
- const stdoutChunks = [];
34022
- const stderrChunks = [];
34023
- let settled = false;
34024
- const timer = setTimeout(() => {
34025
- settled = true;
34026
- child.kill();
34027
- reject(new Error(`gog timed out after ${formatTimeout(effectiveTimeout)}`));
34028
- }, effectiveTimeout);
34029
- child.stdout.on("data", (chunk) => {
34030
- stdoutChunks.push(chunk);
34031
- });
34032
- child.stderr.on("data", (chunk) => {
34033
- stderrChunks.push(chunk);
34034
- });
34035
- child.on("close", (code) => {
34036
- clearTimeout(timer);
34037
- if (settled) return;
34038
- settled = true;
34039
- const stderr = Buffer.concat(stderrChunks).toString().trim();
34040
- if (code === 0) {
34041
- if (binary) {
34042
- resolve(Buffer.concat(stdoutChunks).toString("base64"));
34043
- return;
34044
- }
34045
- const stdout = Buffer.concat(stdoutChunks).toString();
34046
- if (interactive && stderr) {
34047
- resolve(stdout + "\n" + stderr);
34048
- } else {
34049
- resolve(stdout);
34050
- }
34051
- } else {
34052
- reject(new Error(stderr || `gog exited with code ${code}`));
34053
- }
34054
- });
34055
- child.on("error", (err) => {
34056
- clearTimeout(timer);
34057
- if (settled) return;
34058
- settled = true;
34059
- if (err.code === "ENOENT") {
34060
- reject(new Error(
34061
- "gog executable not found. Install gogcli (https://github.com/openclaw/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
34062
- ));
34063
- return;
34064
- }
34065
- reject(err);
34066
- });
34067
- });
34068
- }
34069
- function assembleArgs(args, opts) {
34070
- const effectiveAccount = opts.account ?? readEnvVar("GOG_ACCOUNT");
34071
- const fullArgs = ["--json", "--color=never"];
34072
- if (!opts.interactive) {
34073
- fullArgs.push("--no-input");
34074
- }
34075
- if (opts.readonly || readonlyEnvEnabled()) {
34076
- fullArgs.push("--readonly");
34077
- }
34078
- if (effectiveAccount) {
34079
- fullArgs.push("--account", effectiveAccount);
34080
- }
34081
- fullArgs.push(...args);
34082
- return fullArgs;
34083
- }
34084
- async function run(args, options = {}) {
34085
- const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
34086
- const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
34087
- const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
34088
- const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
34089
- try {
34090
- return redact(await spawnExecutor(fullArgs, { timeout, interactive, spawner }));
34091
- } catch (err) {
34092
- throw new Error(base(err instanceof Error ? err.message : String(err)));
34093
- }
34094
- }
34095
-
34096
- // ../gogcli-mcp/src/timestamps.ts
34097
- var DEFAULT_DISPLAY_TZ = "America/New_York";
34098
- function isValidTimeZone(tz) {
34099
- try {
34100
- new Intl.DateTimeFormat("en-US", { timeZone: tz });
34101
- return true;
34102
- } catch {
34103
- return false;
34104
- }
34105
- }
34106
- function displayTimeZone() {
34107
- const configured = readEnvVar("DISPLAY_TZ");
34108
- if (configured && isValidTimeZone(configured)) return configured;
34109
- return DEFAULT_DISPLAY_TZ;
34110
- }
34111
- function naiveSourceTimeZone() {
34112
- const configured = readEnvVar("GOG_TIMEZONE");
34113
- if (configured && isValidTimeZone(configured)) return configured;
34114
- return displayTimeZone();
34571
+ function naiveSourceTimeZone() {
34572
+ const configured = readEnvVar("GOG_TIMEZONE");
34573
+ if (configured && isValidTimeZone(configured)) return configured;
34574
+ return displayTimeZone();
34115
34575
  }
34116
34576
  function offsetAt(instant, tz) {
34117
34577
  const w = wallPartsIn(instant, tz);
@@ -34292,6 +34752,204 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
34292
34752
  return JSON.stringify(parsed, null, detectIndent(text));
34293
34753
  }
34294
34754
 
34755
+ // ../gogcli-mcp/src/runner.ts
34756
+ function isGogFileArg(arg) {
34757
+ return typeof arg !== "string";
34758
+ }
34759
+ var TIMEOUT_MS = 3e4;
34760
+ function readonlyEnvEnabled() {
34761
+ return readEnvVar("GOG_READONLY") !== void 0 && parseBoolEnv("GOG_READONLY", { default: true });
34762
+ }
34763
+ function sanitizedEnv() {
34764
+ const result = {};
34765
+ for (const [key, value] of Object.entries(process.env)) {
34766
+ if (key === "GOG_ACCESS_TOKEN") continue;
34767
+ if (key === "GOOGLE_APPLICATION_CREDENTIALS") continue;
34768
+ if (/(_TOKEN|_SECRET|_KEY|_CREDENTIALS)$/.test(key)) continue;
34769
+ result[key] = value;
34770
+ }
34771
+ return result;
34772
+ }
34773
+ var TOKEN_LEFT_BOUNDARY = "(?<![A-Za-z0-9+/])";
34774
+ var GOOGLE_TOKEN_PATTERNS = [
34775
+ new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
34776
+ // OAuth2 access tokens
34777
+ new RegExp(`${TOKEN_LEFT_BOUNDARY}1//[A-Za-z0-9._\\-]+`, "g")
34778
+ // OAuth2 refresh tokens
34779
+ ];
34780
+ function redactGoogleTokens(text) {
34781
+ let redacted = text;
34782
+ for (const re of GOOGLE_TOKEN_PATTERNS) {
34783
+ redacted = redacted.replace(re, "[REDACTED]");
34784
+ }
34785
+ return redacted;
34786
+ }
34787
+ function redactSecrets2(text) {
34788
+ return redactGoogleTokens(redactSecrets(text));
34789
+ }
34790
+ var OPAQUE_FIELD_VALUE = "[A-Za-z0-9+/_-]{16,}={0,2}";
34791
+ var opaquePlaceholder = (i) => `\0gogOpaque${i}\0`;
34792
+ function redactPreservingOpaqueFields(text, fields, redact) {
34793
+ const lifted = [];
34794
+ let staged = text;
34795
+ for (const field of fields) {
34796
+ const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34797
+ const re = new RegExp(`("${escaped}"\\s*:\\s*")(${OPAQUE_FIELD_VALUE})(")`, "g");
34798
+ staged = staged.replace(re, (_m, open2, value, close) => {
34799
+ lifted.push(value);
34800
+ return `${open2}${opaquePlaceholder(lifted.length - 1)}${close}`;
34801
+ });
34802
+ }
34803
+ if (lifted.length === 0) return redact(text);
34804
+ let redacted = redact(staged);
34805
+ lifted.forEach((value, i) => {
34806
+ redacted = redacted.split(opaquePlaceholder(i)).join(value);
34807
+ });
34808
+ return redacted;
34809
+ }
34810
+ function augmentedPath() {
34811
+ const home = process.env.HOME;
34812
+ const candidates = [
34813
+ process.env.PATH ?? "",
34814
+ "/opt/homebrew/bin",
34815
+ "/usr/local/bin",
34816
+ home ? `${home}/.local/bin` : "",
34817
+ home ? `${home}/go/bin` : ""
34818
+ ];
34819
+ const seen = /* @__PURE__ */ new Set();
34820
+ const parts = [];
34821
+ for (const c of candidates) {
34822
+ if (!c) continue;
34823
+ for (const dir of c.split(delimiter)) {
34824
+ if (!dir || seen.has(dir)) continue;
34825
+ seen.add(dir);
34826
+ parts.push(dir);
34827
+ }
34828
+ }
34829
+ return parts.join(delimiter);
34830
+ }
34831
+ function formatTimeout(ms) {
34832
+ const seconds = Math.round(ms / 1e3);
34833
+ if (seconds >= 60) {
34834
+ const minutes = Math.round(seconds / 60);
34835
+ return `${ms}ms (${minutes} minute${minutes !== 1 ? "s" : ""})`;
34836
+ }
34837
+ return `${ms}ms`;
34838
+ }
34839
+ async function spawnWithTempFiles(args, opts) {
34840
+ const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
34841
+ const { tmpdir } = await import("node:os");
34842
+ const dir = await mkdtemp(join(tmpdir(), "gogcli-mcp-"));
34843
+ try {
34844
+ const argv = [];
34845
+ let seq = 0;
34846
+ for (const arg of args) {
34847
+ if (!isGogFileArg(arg)) {
34848
+ argv.push(arg);
34849
+ continue;
34850
+ }
34851
+ const sub = join(dir, String(seq));
34852
+ seq += 1;
34853
+ await mkdir(sub, { recursive: true, mode: 448 });
34854
+ const path = join(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
34855
+ const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
34856
+ await writeFile(path, data, { mode: 384 });
34857
+ argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
34858
+ }
34859
+ return await spawnGog(argv, opts);
34860
+ } finally {
34861
+ await rm(dir, { recursive: true, force: true }).catch(() => {
34862
+ });
34863
+ }
34864
+ }
34865
+ function spawnExecutor(args, opts) {
34866
+ if (args.some(isGogFileArg)) {
34867
+ return spawnWithTempFiles(args, opts);
34868
+ }
34869
+ return spawnGog(args, opts);
34870
+ }
34871
+ async function spawnGog(fullArgs, opts) {
34872
+ const { timeout, interactive = false, spawner, binary = false } = opts;
34873
+ const spawn = spawner ?? (await import("node:child_process")).spawn;
34874
+ const effectiveTimeout = timeout ?? TIMEOUT_MS;
34875
+ return new Promise((resolve, reject) => {
34876
+ const childEnv = { ...sanitizedEnv(), GOG_TIMEZONE: naiveSourceTimeZone(), PATH: augmentedPath() };
34877
+ const child = spawn(readEnvVar("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
34878
+ const stdoutChunks = [];
34879
+ const stderrChunks = [];
34880
+ let settled = false;
34881
+ const timer = setTimeout(() => {
34882
+ settled = true;
34883
+ child.kill();
34884
+ reject(new Error(`gog timed out after ${formatTimeout(effectiveTimeout)}`));
34885
+ }, effectiveTimeout);
34886
+ child.stdout.on("data", (chunk) => {
34887
+ stdoutChunks.push(chunk);
34888
+ });
34889
+ child.stderr.on("data", (chunk) => {
34890
+ stderrChunks.push(chunk);
34891
+ });
34892
+ child.on("close", (code) => {
34893
+ clearTimeout(timer);
34894
+ if (settled) return;
34895
+ settled = true;
34896
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
34897
+ if (code === 0) {
34898
+ if (binary) {
34899
+ resolve(Buffer.concat(stdoutChunks).toString("base64"));
34900
+ return;
34901
+ }
34902
+ const stdout = Buffer.concat(stdoutChunks).toString();
34903
+ if (interactive && stderr) {
34904
+ resolve(stdout + "\n" + stderr);
34905
+ } else {
34906
+ resolve(stdout);
34907
+ }
34908
+ } else {
34909
+ reject(new Error(stderr || `gog exited with code ${code}`));
34910
+ }
34911
+ });
34912
+ child.on("error", (err) => {
34913
+ clearTimeout(timer);
34914
+ if (settled) return;
34915
+ settled = true;
34916
+ if (err.code === "ENOENT") {
34917
+ reject(new Error(
34918
+ "gog executable not found. Install gogcli (https://github.com/openclaw/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
34919
+ ));
34920
+ return;
34921
+ }
34922
+ reject(err);
34923
+ });
34924
+ });
34925
+ }
34926
+ function assembleArgs(args, opts) {
34927
+ const effectiveAccount = opts.account ?? readEnvVar("GOG_ACCOUNT");
34928
+ const fullArgs = ["--json", "--color=never"];
34929
+ if (!opts.interactive) {
34930
+ fullArgs.push("--no-input");
34931
+ }
34932
+ if (opts.readonly || readonlyEnvEnabled()) {
34933
+ fullArgs.push("--readonly");
34934
+ }
34935
+ if (effectiveAccount) {
34936
+ fullArgs.push("--account", effectiveAccount);
34937
+ }
34938
+ fullArgs.push(...args);
34939
+ return fullArgs;
34940
+ }
34941
+ async function run(args, options = {}) {
34942
+ const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
34943
+ const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
34944
+ const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
34945
+ const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
34946
+ try {
34947
+ return redact(await spawnExecutor(fullArgs, { timeout, interactive, spawner }));
34948
+ } catch (err) {
34949
+ throw new Error(base(err instanceof Error ? err.message : String(err)));
34950
+ }
34951
+ }
34952
+
34295
34953
  // ../gogcli-mcp/src/pagination.ts
34296
34954
  function detectIndent2(text) {
34297
34955
  const match = /\n(\s+)\S/.exec(text);
@@ -34742,7 +35400,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
34742
35400
  );
34743
35401
 
34744
35402
  // ../gogcli-mcp/src/server.ts
34745
- var VERSION = true ? "4.0.0" : "0.0.0";
35403
+ var VERSION = true ? "4.1.0" : "0.0.0";
34746
35404
 
34747
35405
  // ../gogcli-mcp/src/bootstrap-auth.ts
34748
35406
  import { join as join2 } from "node:path";
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-contacts",
5
5
  "display_name": "gogcli (Contacts)",
6
- "version": "4.0.0",
6
+ "version": "4.1.0",
7
7
  "description": "Extended Google Contacts for Claude via gogcli — auth + Contacts + Workspace directory (People API)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -162,7 +162,7 @@
162
162
  "win32"
163
163
  ],
164
164
  "runtimes": {
165
- "node": ">=18.0.0"
165
+ "node": ">=22.0.0"
166
166
  }
167
167
  }
168
168
  }
package/mint.yaml CHANGED
@@ -73,10 +73,10 @@ env:
73
73
  - name: GOG_TIMEZONE
74
74
  required: false
75
75
  help: >-
76
- The IANA zone gog itself formats its naive timestamps in. The wrapper
77
- reads it (naiveSourceTimeZone) to re-attach the correct offset, so it
78
- should match gog's own configuration. Falls back to DISPLAY_TZ, then to
79
- America/New_York.
76
+ The IANA zone gog formats its naive timestamps in. The wrapper passes
77
+ it to every spawned gog and reads it back (naiveSourceTimeZone) to
78
+ re-attach the offset, so the two always agree. Unset or invalid, both
79
+ use DISPLAY_TZ, then America/New_York — never the host's local zone.
80
80
  - name: DISPLAY_TZ
81
81
  required: false
82
82
  help: >-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-contacts",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-contacts",
5
5
  "description": "Extended Google Contacts + People MCP server via gogcli — auth + Contacts + Workspace directory (People API)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -24,7 +24,7 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^0.28.0",
27
+ "@chrischall/mcp-utils": "^1.0.0",
28
28
  "@modelcontextprotocol/server": "^2.0.0",
29
29
  "zod": "^4.6.1"
30
30
  },