gogcli-mcp 4.4.0 → 4.5.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/dist/index.js CHANGED
@@ -956,14 +956,14 @@ function promiseAllObject(promisesObj) {
956
956
  }
957
957
  function randomString(length = 10) {
958
958
  const chars = "abcdefghijklmnopqrstuvwxyz";
959
- let str = "";
959
+ let str2 = "";
960
960
  for (let i = 0; i < length; i++) {
961
- str += chars[Math.floor(Math.random() * chars.length)];
961
+ str2 += chars[Math.floor(Math.random() * chars.length)];
962
962
  }
963
- return str;
963
+ return str2;
964
964
  }
965
- function esc(str) {
966
- return JSON.stringify(str);
965
+ function esc(str2) {
966
+ return JSON.stringify(str2);
967
967
  }
968
968
  function slugify(input2) {
969
969
  return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -1077,8 +1077,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
1077
1077
  "symbol",
1078
1078
  "undefined"
1079
1079
  ]);
1080
- function escapeRegex(str) {
1081
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1080
+ function escapeRegex(str2) {
1081
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1082
1082
  }
1083
1083
  function clone(inst, def, params) {
1084
1084
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -1332,13 +1332,13 @@ function getSizableOrigin(input2) {
1332
1332
  return "unknown";
1333
1333
  }
1334
1334
  var highSurrogate = /[\uD800-\uDBFF]/;
1335
- function codePointLength(str) {
1336
- const units = str.length;
1337
- if (!highSurrogate.test(str))
1335
+ function codePointLength(str2) {
1336
+ const units = str2.length;
1337
+ if (!highSurrogate.test(str2))
1338
1338
  return units;
1339
1339
  let count = units;
1340
1340
  for (let i = 0; i < units - 1; i++) {
1341
- if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
1341
+ if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
1342
1342
  count--;
1343
1343
  i++;
1344
1344
  }
@@ -25451,7 +25451,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25451
25451
  }
25452
25452
  exports._ = _;
25453
25453
  const plus = new _Code("+");
25454
- function str(strs, ...args) {
25454
+ function str2(strs, ...args) {
25455
25455
  const expr = [safeStringify(strs[0])];
25456
25456
  let i = 0;
25457
25457
  while (i < args.length) {
@@ -25462,7 +25462,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25462
25462
  optimize(expr);
25463
25463
  return new _Code(expr);
25464
25464
  }
25465
- exports.str = str;
25465
+ exports.str = str2;
25466
25466
  function addCodeArg(code, arg) {
25467
25467
  if (arg instanceof _Code) code.push(...arg._items);
25468
25468
  else if (arg instanceof Name) code.push(arg);
@@ -25495,7 +25495,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
25495
25495
  if (typeof b == "string" && b[0] === '"' && !(a instanceof Name)) return `"${a}${b.slice(1)}`;
25496
25496
  }
25497
25497
  function strConcat(c1, c2) {
25498
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
25498
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
25499
25499
  }
25500
25500
  exports.strConcat = strConcat;
25501
25501
  function interpolate(x) {
@@ -26374,21 +26374,21 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
26374
26374
  return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
26375
26375
  }
26376
26376
  exports.schemaRefOrVal = schemaRefOrVal;
26377
- function unescapeFragment(str) {
26378
- return unescapeJsonPointer(decodeURIComponent(str));
26377
+ function unescapeFragment(str2) {
26378
+ return unescapeJsonPointer(decodeURIComponent(str2));
26379
26379
  }
26380
26380
  exports.unescapeFragment = unescapeFragment;
26381
- function escapeFragment(str) {
26382
- return encodeURIComponent(escapeJsonPointer(str));
26381
+ function escapeFragment(str2) {
26382
+ return encodeURIComponent(escapeJsonPointer(str2));
26383
26383
  }
26384
26384
  exports.escapeFragment = escapeFragment;
26385
- function escapeJsonPointer(str) {
26386
- if (typeof str == "number") return `${str}`;
26387
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
26385
+ function escapeJsonPointer(str2) {
26386
+ if (typeof str2 == "number") return `${str2}`;
26387
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
26388
26388
  }
26389
26389
  exports.escapeJsonPointer = escapeJsonPointer;
26390
- function unescapeJsonPointer(str) {
26391
- return str.replace(/~1/g, "/").replace(/~0/g, "~");
26390
+ function unescapeJsonPointer(str2) {
26391
+ return str2.replace(/~1/g, "/").replace(/~0/g, "~");
26392
26392
  }
26393
26393
  exports.unescapeJsonPointer = unescapeJsonPointer;
26394
26394
  function eachItem(xs, f) {
@@ -27274,8 +27274,8 @@ var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin(((exports, modu
27274
27274
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
27275
27275
  }
27276
27276
  }
27277
- function escapeJsonPtr(str) {
27278
- return str.replace(/~/g, "~0").replace(/\//g, "~1");
27277
+ function escapeJsonPtr(str2) {
27278
+ return str2.replace(/~/g, "~0").replace(/\//g, "~1");
27279
27279
  }
27280
27280
  }));
27281
27281
  var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -28167,9 +28167,9 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28167
28167
  isIPV6: false
28168
28168
  };
28169
28169
  }
28170
- function findToken(str, token) {
28170
+ function findToken(str2, token) {
28171
28171
  let ind = 0;
28172
- for (let i = 0; i < str.length; i++) if (str[i] === token) ind++;
28172
+ for (let i = 0; i < str2.length; i++) if (str2[i] === token) ind++;
28173
28173
  return ind;
28174
28174
  }
28175
28175
  function removeDotSegments(path) {
@@ -28684,7 +28684,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
28684
28684
  const util_1 = require_util();
28685
28685
  const $dataRefSchema = require_data();
28686
28686
  const uri_1 = require_uri();
28687
- const defaultRegExp = (str, flags) => new RegExp(str, flags);
28687
+ const defaultRegExp = (str2, flags) => new RegExp(str2, flags);
28688
28688
  defaultRegExp.code = "new RegExp";
28689
28689
  const META_IGNORE_OPTIONS = [
28690
28690
  "removeAdditional",
@@ -29390,16 +29390,16 @@ var require_multipleOf = /* @__PURE__ */ __commonJSMin(((exports) => {
29390
29390
  }));
29391
29391
  var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
29392
29392
  Object.defineProperty(exports, "__esModule", { value: true });
29393
- function ucs2length(str) {
29394
- const len = str.length;
29393
+ function ucs2length(str2) {
29394
+ const len = str2.length;
29395
29395
  let length = 0;
29396
29396
  let pos2 = 0;
29397
29397
  let value;
29398
29398
  while (pos2 < len) {
29399
29399
  length++;
29400
- value = str.charCodeAt(pos2++);
29400
+ value = str2.charCodeAt(pos2++);
29401
29401
  if (value >= 55296 && value <= 56319 && pos2 < len) {
29402
- value = str.charCodeAt(pos2);
29402
+ value = str2.charCodeAt(pos2);
29403
29403
  if ((value & 64512) === 56320) pos2++;
29404
29404
  }
29405
29405
  }
@@ -32110,8 +32110,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32110
32110
  30,
32111
32111
  31
32112
32112
  ];
32113
- function date5(str) {
32114
- const matches = DATE.exec(str);
32113
+ function date5(str2) {
32114
+ const matches = DATE.exec(str2);
32115
32115
  if (!matches) return false;
32116
32116
  const year = +matches[1];
32117
32117
  const month = +matches[2];
@@ -32126,8 +32126,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32126
32126
  }
32127
32127
  const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
32128
32128
  function getTime(strictTimeZone) {
32129
- return function time3(str) {
32130
- const matches = TIME.exec(str);
32129
+ return function time3(str2) {
32130
+ const matches = TIME.exec(str2);
32131
32131
  if (!matches) return false;
32132
32132
  const hr = +matches[1];
32133
32133
  const min = +matches[2];
@@ -32164,8 +32164,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32164
32164
  const DATE_TIME_SEPARATOR = /t|\s/i;
32165
32165
  function getDateTime(strictTimeZone) {
32166
32166
  const time3 = getTime(strictTimeZone);
32167
- return function date_time(str) {
32168
- const dateTime = str.split(DATE_TIME_SEPARATOR);
32167
+ return function date_time(str2) {
32168
+ const dateTime = str2.split(DATE_TIME_SEPARATOR);
32169
32169
  return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
32170
32170
  };
32171
32171
  }
@@ -32186,13 +32186,13 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32186
32186
  }
32187
32187
  const NOT_URI_FRAGMENT = /\/|:/;
32188
32188
  const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
32189
- function uri(str) {
32190
- return NOT_URI_FRAGMENT.test(str) && URI.test(str);
32189
+ function uri(str2) {
32190
+ return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
32191
32191
  }
32192
32192
  const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
32193
- function byte(str) {
32193
+ function byte(str2) {
32194
32194
  BYTE.lastIndex = 0;
32195
- return BYTE.test(str);
32195
+ return BYTE.test(str2);
32196
32196
  }
32197
32197
  const MIN_INT32 = -(2 ** 31);
32198
32198
  const MAX_INT32 = 2 ** 31 - 1;
@@ -32206,10 +32206,10 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
32206
32206
  return true;
32207
32207
  }
32208
32208
  const Z_ANCHOR = /[^\\]\\Z/;
32209
- function regex(str) {
32210
- if (Z_ANCHOR.test(str)) return false;
32209
+ function regex(str2) {
32210
+ if (Z_ANCHOR.test(str2)) return false;
32211
32211
  try {
32212
- new RegExp(str);
32212
+ new RegExp(str2);
32213
32213
  return true;
32214
32214
  } catch (e) {
32215
32215
  return false;
@@ -34554,10 +34554,8 @@ function errorResult(message) {
34554
34554
 
34555
34555
  // ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
34556
34556
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
34557
- var DEFAULT_REQUEST_KEY = "confirmation";
34558
- var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
34559
- var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
34560
- var STATE_PREFIX = "mcpu.confirm.v1.";
34557
+
34558
+ // ../../node_modules/@chrischall/mcp-utils/dist/server/canonical.js
34561
34559
  function canonicalJson(value) {
34562
34560
  if (value === null || typeof value === "string" || typeof value === "boolean")
34563
34561
  return JSON.stringify(value);
@@ -34568,7 +34566,7 @@ function canonicalJson(value) {
34568
34566
  if (typeof value === "bigint")
34569
34567
  return `{"$bigint":"${value.toString()}"}`;
34570
34568
  if (typeof value === "function" || typeof value === "symbol") {
34571
- throw new TypeError(`requireConfirmation: cannot bind a ${typeof value} argument value.`);
34569
+ throw new TypeError(`confirmation: cannot bind a ${typeof value} argument value.`);
34572
34570
  }
34573
34571
  if (Array.isArray(value))
34574
34572
  return `[${value.map(canonicalJson).join(",")}]`;
@@ -34590,7 +34588,7 @@ function canonicalJson(value) {
34590
34588
  }
34591
34589
  const proto = Object.getPrototypeOf(value);
34592
34590
  if (proto !== Object.prototype && proto !== null) {
34593
- throw new TypeError("requireConfirmation: cannot bind a non-plain object argument value (class instance).");
34591
+ throw new TypeError("confirmation: cannot bind a non-plain object argument value (class instance).");
34594
34592
  }
34595
34593
  const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
34596
34594
  return `{${entries.map(([k, v]) => `${JSON.stringify(escapeKey(k))}:${canonicalJson(v)}`).join(",")}}`;
@@ -34598,6 +34596,12 @@ function canonicalJson(value) {
34598
34596
  function escapeKey(key) {
34599
34597
  return key.startsWith("$") ? `$${key}` : key;
34600
34598
  }
34599
+
34600
+ // ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
34601
+ var DEFAULT_REQUEST_KEY = "confirmation";
34602
+ var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
34603
+ var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
34604
+ var STATE_PREFIX = "mcpu.confirm.v1.";
34601
34605
  function bindingKey(binding) {
34602
34606
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
34603
34607
  throw new RangeError("requireConfirmation: binding.ttlSeconds must be a finite number greater than 0.");
@@ -34700,6 +34704,264 @@ ${JSON.stringify(preview, null, 2)}`,
34700
34704
  });
34701
34705
  }
34702
34706
 
34707
+ // ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-token.js
34708
+ import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
34709
+ function createSpentTokenStore() {
34710
+ const spent2 = /* @__PURE__ */ new Map();
34711
+ return {
34712
+ has: (nonce) => spent2.has(nonce),
34713
+ add: (nonce, exp) => {
34714
+ spent2.set(nonce, exp);
34715
+ },
34716
+ prune: (now) => {
34717
+ for (const [nonce, exp] of spent2)
34718
+ if (exp < now)
34719
+ spent2.delete(nonce);
34720
+ },
34721
+ clear: () => spent2.clear(),
34722
+ get size() {
34723
+ return spent2.size;
34724
+ }
34725
+ };
34726
+ }
34727
+ var PROCESS_SPENT = createSpentTokenStore();
34728
+ var PREFIX = "mcpu.token.v1.";
34729
+ var DEFAULT_TTL_SECONDS = 600;
34730
+ function tokenKey(key) {
34731
+ const bytes = typeof key === "string" ? Buffer.from(key, "utf8") : Buffer.from(key);
34732
+ if (bytes.length < 32)
34733
+ throw new RangeError("confirm token: key must be at least 32 bytes.");
34734
+ return bytes;
34735
+ }
34736
+ function ttlOf(ttlSeconds) {
34737
+ if (ttlSeconds === void 0)
34738
+ return DEFAULT_TTL_SECONDS;
34739
+ if (!(Number.isFinite(ttlSeconds) && ttlSeconds > 0)) {
34740
+ throw new RangeError("confirm token: ttlSeconds must be a finite number greater than 0.");
34741
+ }
34742
+ return ttlSeconds;
34743
+ }
34744
+ function sign(key, body) {
34745
+ return createHmac2("sha256", key).update(`${PREFIX}${body}`).digest();
34746
+ }
34747
+ function hashConfirmPayload(payload) {
34748
+ return createHash2("sha256").update(canonicalJson(payload)).digest("base64url");
34749
+ }
34750
+ function issueConfirmToken(key, binding, options = {}) {
34751
+ const k = tokenKey(key);
34752
+ const now = options.now ?? Date.now();
34753
+ const exp = now + ttlOf(options.ttlSeconds) * 1e3;
34754
+ const claims = {
34755
+ t: binding.tool,
34756
+ ...binding.account === void 0 ? {} : { a: binding.account },
34757
+ g: binding.target,
34758
+ ...binding.revision === void 0 ? {} : { r: binding.revision },
34759
+ h: binding.payloadHash,
34760
+ exp,
34761
+ n: randomBytes(16).toString("base64url")
34762
+ };
34763
+ const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
34764
+ return { token: `${PREFIX}${body}.${sign(k, body).toString("base64url")}`, expiresAt: new Date(exp).toISOString() };
34765
+ }
34766
+ function parseClaims(body) {
34767
+ try {
34768
+ const claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
34769
+ return claims && typeof claims === "object" ? claims : void 0;
34770
+ } catch {
34771
+ return void 0;
34772
+ }
34773
+ }
34774
+ function verifyConfirmToken(key, token, binding, options = {}) {
34775
+ const k = tokenKey(key);
34776
+ const now = options.now ?? Date.now();
34777
+ const spent2 = options.spent ?? PROCESS_SPENT;
34778
+ spent2.prune(now);
34779
+ if (!token.startsWith(PREFIX))
34780
+ return { ok: false, error: "TOKEN_INVALID" };
34781
+ const rest = token.slice(PREFIX.length);
34782
+ const dot = rest.indexOf(".");
34783
+ const body = dot < 0 ? "" : rest.slice(0, dot);
34784
+ const mac3 = Buffer.from(dot < 0 ? "" : rest.slice(dot + 1), "base64url");
34785
+ const expected = sign(k, body);
34786
+ if (!body || mac3.length !== expected.length || !timingSafeEqual2(mac3, expected))
34787
+ return { ok: false, error: "TOKEN_INVALID" };
34788
+ const claims = parseClaims(body);
34789
+ if (!claims)
34790
+ return { ok: false, error: "TOKEN_INVALID" };
34791
+ if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
34792
+ return { ok: false, error: "TOKEN_INVALID" };
34793
+ }
34794
+ if (spent2.has(claims.n))
34795
+ return { ok: false, error: "TOKEN_REUSED" };
34796
+ if (now > claims.exp)
34797
+ return { ok: false, error: "TOKEN_EXPIRED" };
34798
+ if (claims.r !== binding.revision)
34799
+ return { ok: false, error: "DRAFT_CHANGED", reason: "revision-changed" };
34800
+ if (claims.h !== binding.payloadHash)
34801
+ return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
34802
+ spent2.add(claims.n, claims.exp);
34803
+ return { ok: true };
34804
+ }
34805
+ var CONFIRM_TOKEN_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
34806
+ var confirmTokenParam = external_exports.string().optional().describe(`ONLY for the two-step confirmation fallback (a client without MCP elicitation). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`);
34807
+ var TOKEN_ERROR_NOTE = {
34808
+ TOKEN_EXPIRED: "Nothing was sent or changed: the confirmToken expired. Call again WITHOUT confirmToken for a fresh preview, and ask the user to approve it again.",
34809
+ TOKEN_REUSED: "Nothing was sent or changed by this call: this confirmToken was already used, and one approval acts once. If doing it again is really intended, call again WITHOUT confirmToken and get a new approval.",
34810
+ TOKEN_INVALID: "Nothing was sent or changed: this confirmToken was not issued by this server for this tool, account and target (or the server has restarted since). Call again WITHOUT confirmToken for a fresh preview and approval."
34811
+ };
34812
+ var CHANGED_NOTE = {
34813
+ "revision-changed": "Nothing was sent or changed: the target was edited since the user approved it (its version rotated), so what would happen is not what they saw.",
34814
+ "payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
34815
+ };
34816
+ function isToolResult(value) {
34817
+ return Array.isArray(value.content);
34818
+ }
34819
+ function rejection2(data) {
34820
+ return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
34821
+ }
34822
+ async function tokenConfirmation(action, fb) {
34823
+ const subject = await fb.subject();
34824
+ if (isToolResult(subject))
34825
+ return subject;
34826
+ const binding = {
34827
+ tool: fb.tool,
34828
+ ...fb.account === void 0 ? {} : { account: fb.account },
34829
+ target: subject.target,
34830
+ ...subject.revision === void 0 ? {} : { revision: subject.revision },
34831
+ payloadHash: hashConfirmPayload(subject.payload)
34832
+ };
34833
+ const phaseOne = () => {
34834
+ const { token, expiresAt } = issueConfirmToken(fb.key, binding, { ttlSeconds: fb.ttlSeconds });
34835
+ return {
34836
+ action,
34837
+ preview: subject.preview,
34838
+ confirmToken: token,
34839
+ expiresAt,
34840
+ ttlSeconds: ttlOf(fb.ttlSeconds),
34841
+ instruction: fb.instruction ?? CONFIRM_TOKEN_INSTRUCTION
34842
+ };
34843
+ };
34844
+ if (!fb.confirmToken) {
34845
+ return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
34846
+ }
34847
+ const verdict = verifyConfirmToken(fb.key, fb.confirmToken, binding, { spent: fb.spent });
34848
+ if (verdict.ok)
34849
+ return void 0;
34850
+ if (verdict.error === "DRAFT_CHANGED") {
34851
+ return rejection2({
34852
+ error: "DRAFT_CHANGED",
34853
+ reason: verdict.reason,
34854
+ note: `${CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
34855
+ ...phaseOne()
34856
+ });
34857
+ }
34858
+ return rejection2({ error: verdict.error, action, note: TOKEN_ERROR_NOTE[verdict.error] });
34859
+ }
34860
+ async function requireConfirmationWithFallback(ctx, options) {
34861
+ const { tokenFallback, ...confirmation } = options;
34862
+ if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
34863
+ return tokenConfirmation(options.action, tokenFallback);
34864
+ }
34865
+ return requireConfirmation(ctx, confirmation);
34866
+ }
34867
+
34868
+ // ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js
34869
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
34870
+
34871
+ // ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
34872
+ import { homedir } from "node:os";
34873
+ import { isAbsolute, join, resolve } from "node:path";
34874
+ var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
34875
+ function readEnvVar(key, opts = {}) {
34876
+ const env = opts.env ?? process.env;
34877
+ const raw = env[key];
34878
+ if (typeof raw === "string") {
34879
+ const trimmed = raw.trim();
34880
+ if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
34881
+ return trimmed;
34882
+ }
34883
+ }
34884
+ return opts.default;
34885
+ }
34886
+ var TRUE_TOKENS = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
34887
+ var FALSE_TOKENS = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
34888
+ function parseBoolEnv(key, opts = {}) {
34889
+ const fallback = opts.default ?? false;
34890
+ const raw = readEnvVar(key, { env: opts.env });
34891
+ if (raw === void 0)
34892
+ return fallback;
34893
+ const token = raw.toLowerCase();
34894
+ if (TRUE_TOKENS.has(token))
34895
+ return true;
34896
+ if (FALSE_TOKENS.has(token))
34897
+ return false;
34898
+ return fallback;
34899
+ }
34900
+ function expandPath(p) {
34901
+ let expanded = p;
34902
+ if (p === "~") {
34903
+ expanded = homedir();
34904
+ } else if (p.startsWith("~/")) {
34905
+ expanded = join(homedir(), p.slice(2));
34906
+ }
34907
+ return isAbsolute(expanded) ? expanded : resolve(expanded);
34908
+ }
34909
+
34910
+ // ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js
34911
+ var MODES = /* @__PURE__ */ new Set(["ask-user", "auto", "refuse"]);
34912
+ var DEFAULT_TTL_SECONDS2 = 600;
34913
+ var warned = /* @__PURE__ */ new Set();
34914
+ function readConfirmMode(env = process.env) {
34915
+ const raw = readEnvVar("MCP_CONFIRM_MODE", { env })?.trim().toLowerCase();
34916
+ if (!raw)
34917
+ return "ask-user";
34918
+ if (MODES.has(raw))
34919
+ return raw;
34920
+ if (!warned.has(raw)) {
34921
+ warned.add(raw);
34922
+ process.stderr.write(`MCP_CONFIRM_MODE="${raw}" is not one of ask-user, auto, refuse; treating it as refuse.
34923
+ `);
34924
+ }
34925
+ return "refuse";
34926
+ }
34927
+ function confirmTtlFromEnv(env = process.env) {
34928
+ const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
34929
+ return raw && /^[1-9]\d*$/.test(raw) ? Number(raw) : DEFAULT_TTL_SECONDS2;
34930
+ }
34931
+ var processKey;
34932
+ function confirmKeyFromEnv(env = process.env) {
34933
+ const secret = readEnvVar("MCP_CONFIRM_SECRET", { env });
34934
+ if (secret)
34935
+ return createHash3("sha256").update(secret, "utf8").digest();
34936
+ processKey ??= randomBytes2(32);
34937
+ return processKey;
34938
+ }
34939
+ var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
34940
+ var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
34941
+ function confirmationFromEnv(options) {
34942
+ const { tool, account, confirmToken, subject, instruction, spent: spent2, env = process.env, ...confirmation } = options;
34943
+ const mode = readConfirmMode(env);
34944
+ if (mode === "refuse") {
34945
+ return {
34946
+ ...confirmation,
34947
+ unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${REFUSE_HINT}` : REFUSE_HINT
34948
+ };
34949
+ }
34950
+ return {
34951
+ ...confirmation,
34952
+ tokenFallback: {
34953
+ key: confirmKeyFromEnv(env),
34954
+ tool,
34955
+ ...account === void 0 ? {} : { account },
34956
+ ...confirmToken === void 0 ? {} : { confirmToken },
34957
+ subject,
34958
+ ttlSeconds: confirmTtlFromEnv(env),
34959
+ instruction: mode === "auto" ? CONFIRM_TOKEN_AUTO_INSTRUCTION : instruction ?? CONFIRM_TOKEN_INSTRUCTION,
34960
+ ...spent2 === void 0 ? {} : { spent: spent2 }
34961
+ }
34962
+ };
34963
+ }
34964
+
34703
34965
  // ../../node_modules/@chrischall/mcp-utils/dist/server/index.js
34704
34966
  var SERVER_PROTOCOL_VERSIONS = Object.freeze([
34705
34967
  "2026-07-28",
@@ -34875,45 +35137,6 @@ function runMcp(opts) {
34875
35137
  return handle;
34876
35138
  }
34877
35139
 
34878
- // ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
34879
- import { homedir } from "node:os";
34880
- import { isAbsolute, join, resolve } from "node:path";
34881
- var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
34882
- function readEnvVar(key, opts = {}) {
34883
- const env = opts.env ?? process.env;
34884
- const raw = env[key];
34885
- if (typeof raw === "string") {
34886
- const trimmed = raw.trim();
34887
- if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
34888
- return trimmed;
34889
- }
34890
- }
34891
- return opts.default;
34892
- }
34893
- var TRUE_TOKENS = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
34894
- var FALSE_TOKENS = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
34895
- function parseBoolEnv(key, opts = {}) {
34896
- const fallback = opts.default ?? false;
34897
- const raw = readEnvVar(key, { env: opts.env });
34898
- if (raw === void 0)
34899
- return fallback;
34900
- const token = raw.toLowerCase();
34901
- if (TRUE_TOKENS.has(token))
34902
- return true;
34903
- if (FALSE_TOKENS.has(token))
34904
- return false;
34905
- return fallback;
34906
- }
34907
- function expandPath(p) {
34908
- let expanded = p;
34909
- if (p === "~") {
34910
- expanded = homedir();
34911
- } else if (p.startsWith("~/")) {
34912
- expanded = join(homedir(), p.slice(2));
34913
- }
34914
- return isAbsolute(expanded) ? expanded : resolve(expanded);
34915
- }
34916
-
34917
35140
  // ../../node_modules/@chrischall/mcp-utils/dist/fs/confine.js
34918
35141
  import { existsSync, realpathSync } from "node:fs";
34919
35142
  import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
@@ -35748,6 +35971,7 @@ async function diagnose(err) {
35748
35971
  const isAuthError = !isTransientError && AUTH_ERROR_PATTERN.test(errText);
35749
35972
  const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
35750
35973
  const hint = isInvalidGrant ? INVALID_GRANT_HINT : isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
35974
+ if (!isAuthError && !isInvalidGrant) return errorResult(`${errText}${hint}`);
35751
35975
  try {
35752
35976
  const accounts = formatAccountList(await run(["auth", "list"]));
35753
35977
  return errorResult(`${errText}
@@ -35844,95 +36068,13 @@ function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
35844
36068
  }
35845
36069
 
35846
36070
  // src/dispatch-confirmation.ts
35847
- import { createHash as createHash3 } from "node:crypto";
36071
+ import { createHash as createHash4 } from "node:crypto";
35848
36072
  import { readFileSync } from "node:fs";
35849
36073
 
35850
36074
  // src/send-confirm-token.ts
35851
- import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
35852
- var CONFIRM_TOKEN_TTL_DEFAULT_SECONDS = 600;
35853
- var PREFIX = "gct1";
35854
- var secret;
35855
- var spent = /* @__PURE__ */ new Map();
35856
- function sendConfirmFallbackEnabled() {
35857
- return readEnvVar("GOG_SEND_CONFIRM_FALLBACK")?.trim().toLowerCase() === "token";
35858
- }
35859
- function confirmTokenTtlSeconds() {
35860
- const raw = readEnvVar("GOG_CONFIRM_TTL_SECONDS")?.trim();
35861
- if (raw && /^\d+$/.test(raw)) {
35862
- const n = Number(raw);
35863
- if (n > 0) return n;
35864
- }
35865
- return CONFIRM_TOKEN_TTL_DEFAULT_SECONDS;
35866
- }
35867
- function hmacKey() {
35868
- if (!secret) {
35869
- const configured = readEnvVar("GOG_CONFIRM_SECRET");
35870
- secret = configured ? Buffer.from(configured, "utf8") : randomBytes(32);
35871
- }
35872
- return secret;
35873
- }
35874
- function canonicalize(value) {
35875
- if (Array.isArray(value)) return value.map(canonicalize);
35876
- if (value && typeof value === "object") {
35877
- const out = {};
35878
- for (const key of Object.keys(value).sort()) {
35879
- const v = value[key];
35880
- if (v !== void 0) out[key] = canonicalize(v);
35881
- }
35882
- return out;
35883
- }
35884
- return value;
35885
- }
35886
- function hashSendPayload(payload) {
35887
- return createHash2("sha256").update(JSON.stringify(canonicalize(payload))).digest("hex");
35888
- }
35889
- function sign(body) {
35890
- return createHmac2("sha256", hmacKey()).update(`${PREFIX}.${body}`).digest("base64url");
35891
- }
35892
- function issueConfirmToken(binding, now = Date.now()) {
35893
- const exp = now + confirmTokenTtlSeconds() * 1e3;
35894
- const claims = {
35895
- t: binding.tool,
35896
- a: binding.account,
35897
- g: binding.target,
35898
- ...binding.revision === void 0 ? {} : { r: binding.revision },
35899
- h: binding.payloadHash,
35900
- iat: now,
35901
- exp,
35902
- n: randomBytes(16).toString("base64url")
35903
- };
35904
- const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
35905
- return { token: `${PREFIX}.${body}.${sign(body)}`, expiresAt: new Date(exp).toISOString() };
35906
- }
35907
- function signatureMatches(body, sig) {
35908
- const expected = Buffer.from(sign(body), "utf8");
35909
- const actual = Buffer.from(sig, "utf8");
35910
- return expected.length === actual.length && timingSafeEqual2(expected, actual);
35911
- }
35912
- function parseClaims(body) {
35913
- try {
35914
- return JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
35915
- } catch {
35916
- return void 0;
35917
- }
35918
- }
35919
- function verifyConfirmToken(token, binding, now = Date.now()) {
35920
- for (const [nonce, exp] of spent) if (exp < now) spent.delete(nonce);
35921
- const parts = token.split(".");
35922
- if (parts.length !== 3 || parts[0] !== PREFIX || !parts[1] || !parts[2]) return { ok: false, error: "TOKEN_INVALID" };
35923
- const [, body, sig] = parts;
35924
- if (!signatureMatches(body, sig)) return { ok: false, error: "TOKEN_INVALID" };
35925
- const claims = parseClaims(body);
35926
- if (!claims) return { ok: false, error: "TOKEN_INVALID" };
35927
- if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
35928
- return { ok: false, error: "TOKEN_INVALID" };
35929
- }
35930
- if (spent.has(claims.n)) return { ok: false, error: "TOKEN_REUSED" };
35931
- if (now > claims.exp) return { ok: false, error: "TOKEN_EXPIRED" };
35932
- if (claims.r !== binding.revision) return { ok: false, error: "DRAFT_CHANGED", reason: "message-id-rotated" };
35933
- if (claims.h !== binding.payloadHash) return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
35934
- spent.set(claims.n, claims.exp);
35935
- return { ok: true };
36075
+ var spent = createSpentTokenStore();
36076
+ function confirmSpentStore() {
36077
+ return spent;
35936
36078
  }
35937
36079
 
35938
36080
  // src/dispatch-confirmation.ts
@@ -35954,13 +36096,13 @@ function attachmentDetails(paths, inline) {
35954
36096
  } catch {
35955
36097
  bytes = void 0;
35956
36098
  }
35957
- out.push(bytes ? { name: path, size: bytes.length, sha256: createHash3("sha256").update(bytes).digest("hex") } : { name: path, size: null });
36099
+ out.push(bytes ? { name: path, size: bytes.length, sha256: createHash4("sha256").update(bytes).digest("hex") } : { name: path, size: null });
35958
36100
  }
35959
36101
  for (const a of inline ?? []) {
35960
36102
  out.push({
35961
36103
  name: a.filename,
35962
36104
  size: Buffer.from(a.contentBase64, "base64").length,
35963
- sha256: createHash3("sha256").update(a.contentBase64).digest("hex")
36105
+ sha256: createHash4("sha256").update(a.contentBase64).digest("hex")
35964
36106
  });
35965
36107
  }
35966
36108
  return out;
@@ -35972,82 +36114,74 @@ function attachmentPreview(details) {
35972
36114
  return details.map(({ name, size }) => ({ name, size }));
35973
36115
  }
35974
36116
  var CONFIRM_SEND_INSTRUCTION = "Show this preview to the user verbatim and send only after they explicitly approve in chat. Then call again with confirmToken.";
35975
- var CONFIRM_ACTION_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
35976
- var FALLBACK_HINT = "Or set GOG_SEND_CONFIRM_FALLBACK=token to enable two-step confirmation.";
35977
- var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai) and the server sets GOG_SEND_CONFIRM_FALLBACK=token, a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Show that preview to the user verbatim; only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken. The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing.';
35978
- var confirmTokenParam = external_exports.string().optional().describe(
35979
- `ONLY for the two-step fallback (client without MCP elicitation, server with GOG_SEND_CONFIRM_FALLBACK=token). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`
35980
- );
35981
- var TOKEN_ERROR_NOTE = {
35982
- TOKEN_EXPIRED: "Nothing was sent or changed: the confirmToken expired. Call again WITHOUT confirmToken for a fresh preview, and ask the user to approve it again.",
35983
- TOKEN_REUSED: "Nothing was sent or changed by this call: this confirmToken was already used, and one approval acts once. If doing it again is really intended, call again WITHOUT confirmToken and get a new approval.",
35984
- TOKEN_INVALID: "Nothing was sent or changed: this confirmToken was not issued by this server for this tool, account and target (or the server has restarted since). Call again WITHOUT confirmToken for a fresh preview and approval."
35985
- };
35986
- var DRAFT_CHANGED_NOTE = {
35987
- "message-id-rotated": "Nothing was sent or changed: the target was edited since the user approved it (a draft's messageId or an event's version rotated), so what would happen is not what they saw.",
35988
- "payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
35989
- };
35990
- function isToolResult(value) {
35991
- return Array.isArray(value.content);
35992
- }
35993
- function rejection2(data) {
35994
- return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
35995
- }
35996
- async function tokenConfirmation(op, fallback) {
35997
- const subject = await fallback.subject();
35998
- if (isToolResult(subject)) return subject;
35999
- const binding = {
36000
- tool: fallback.tool,
36001
- account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
36002
- target: subject.target,
36003
- ...subject.revision === void 0 ? {} : { revision: subject.revision },
36004
- payloadHash: hashSendPayload(subject.payload)
36005
- };
36006
- const phaseOne = () => {
36007
- const { token, expiresAt } = issueConfirmToken(binding);
36008
- return {
36009
- action: op,
36010
- preview: subject.preview,
36011
- confirmToken: token,
36012
- expiresAt,
36013
- ttlSeconds: confirmTokenTtlSeconds(),
36014
- instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION
36015
- };
36016
- };
36017
- if (!fallback.confirmToken) {
36018
- return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
36019
- }
36020
- const verdict = verifyConfirmToken(fallback.confirmToken, binding);
36021
- if (verdict.ok) return void 0;
36022
- if (verdict.error === "DRAFT_CHANGED") {
36023
- return rejection2({
36024
- error: "DRAFT_CHANGED",
36025
- reason: verdict.reason,
36026
- note: `${DRAFT_CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
36027
- ...phaseOne()
36028
- });
36029
- }
36030
- return rejection2({ error: verdict.error, action: op, note: TOKEN_ERROR_NOTE[verdict.error] });
36117
+ var CONFIRM_ACTION_INSTRUCTION = CONFIRM_TOKEN_INSTRUCTION;
36118
+ var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai), a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Follow its instruction (by default: show the preview to the user verbatim and only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken). The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing. A server set to MCP_CONFIRM_MODE=refuse refuses instead.';
36119
+ function refusedInRun(what, via, does, instead) {
36120
+ return `${what} ${does} and is not available through ${via}. ${instead}`;
36031
36121
  }
36032
36122
  function gatedElsewhere(what, via, does, tool) {
36033
- return `${what} ${does} and is not available through ${via}. Use ${tool}, which asks the user to confirm.`;
36123
+ return refusedInRun(what, via, does, `Use ${tool}, which asks the user to confirm.`);
36034
36124
  }
36035
36125
  function hasCommandWord(args, words) {
36036
36126
  return args.find((a) => words.has(a.toLowerCase()));
36037
36127
  }
36128
+ function flagValue(args, name) {
36129
+ const flag = `--${name}`;
36130
+ for (let i = 0; i < args.length; i++) {
36131
+ const a = args[i];
36132
+ if (a === flag) return args[i + 1] ?? "";
36133
+ if (a.startsWith(`${flag}=`)) return a.slice(flag.length + 1);
36134
+ }
36135
+ return void 0;
36136
+ }
36137
+ var COMMENT_ADD_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
36138
+ var COMMENT_REPLY_WORDS = /* @__PURE__ */ new Set(["reply", "respond"]);
36139
+ function hasTrueFlag(args, flag) {
36140
+ const name = `--${flag}`;
36141
+ return args.some((a) => {
36142
+ const lower = a.toLowerCase();
36143
+ if (lower === name) return true;
36144
+ return lower.startsWith(`${name}=`) && lower.slice(name.length + 1) !== "false";
36145
+ });
36146
+ }
36147
+ function vetCommentsRun(service, subcommand, args) {
36148
+ if (subcommand.toLowerCase() !== "comments") return void 0;
36149
+ const reply = hasCommandWord(args, COMMENT_REPLY_WORDS);
36150
+ if (reply) {
36151
+ return gatedElsewhere(
36152
+ `gog ${service} comments ${reply.toLowerCase()}`,
36153
+ `gog_${service}_run`,
36154
+ "notifies the comment thread",
36155
+ `gog_${service}_comments_reply`
36156
+ );
36157
+ }
36158
+ const add = hasCommandWord(args, COMMENT_ADD_WORDS);
36159
+ return add ? gatedElsewhere(
36160
+ `gog ${service} comments ${add.toLowerCase()}`,
36161
+ `gog_${service}_run`,
36162
+ "notifies the file's owner and anyone it mentions",
36163
+ `gog_${service}_comments_add`
36164
+ ) : void 0;
36165
+ }
36038
36166
  async function requireDispatchConfirmation(ctx, options) {
36039
36167
  const { action, fallback } = options;
36040
- if (fallback && callerAcceptsFormElicitation(ctx) === false && sendConfirmFallbackEnabled()) {
36041
- return tokenConfirmation(action, fallback);
36042
- }
36043
- const note = fallback ? [options.unsupportedNote, FALLBACK_HINT].filter(Boolean).join(" ") : options.unsupportedNote;
36044
- return requireConfirmation(ctx, {
36168
+ const confirmation = {
36045
36169
  action,
36046
36170
  message: options.message,
36047
36171
  details: options.details,
36048
36172
  confirmationLabel: options.confirmationLabel,
36049
- ...note ? { unsupportedNote: note } : {}
36050
- });
36173
+ ...options.unsupportedNote ? { unsupportedNote: options.unsupportedNote } : {}
36174
+ };
36175
+ if (!fallback) return requireConfirmationWithFallback(ctx, confirmation);
36176
+ return requireConfirmationWithFallback(ctx, confirmationFromEnv({
36177
+ ...confirmation,
36178
+ tool: fallback.tool,
36179
+ account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
36180
+ confirmToken: fallback.confirmToken,
36181
+ subject: fallback.subject,
36182
+ instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
36183
+ spent: confirmSpentStore()
36184
+ }));
36051
36185
  }
36052
36186
  function resultText(result) {
36053
36187
  const first = result.content[0];
@@ -36057,16 +36191,33 @@ function resultText(result) {
36057
36191
  // src/tools/api.ts
36058
36192
  var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
36059
36193
  var DISPATCH_API_BLOCKED = {
36060
- chat: [{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" }],
36061
- drive: [{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" }],
36194
+ chat: [
36195
+ { method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" },
36196
+ { method: /(?:^|\.)spaces\.(?:setup|members\.create)$/i, does: "adds people to a space", tool: "gog_chat_spaces_create" }
36197
+ ],
36198
+ drive: [
36199
+ { method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" },
36200
+ { method: /(?:^|\.)comments\.create$/i, does: "notifies the file's owner and anyone it mentions", tool: "gog_drive_comments_add / gog_docs_comments_add" },
36201
+ { method: /(?:^|\.)replies\.create$/i, does: "notifies the comment thread", tool: "gog_drive_comments_reply / gog_docs_comments_reply" }
36202
+ ],
36062
36203
  classroom: [
36063
36204
  { method: /(?:^|\.)courses\.announcements\.create$/i, does: "posts to a class", tool: "gog_classroom_announcements_create" },
36064
- { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
36205
+ { method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
36206
+ { method: /(?:^|\.)courses\.students\.create$/i, does: "adds someone to a class", tool: "gog_classroom_students_add" },
36207
+ { method: /(?:^|\.)courses\.teachers\.create$/i, does: "gives someone teacher access to a class", tool: "gog_classroom_teachers_add" },
36208
+ { method: /(?:^|\.)courses\.coursework\.create$/i, does: "posts coursework to a class", tool: "gog_classroom_coursework_create" },
36209
+ { method: /(?:^|\.)studentsubmissions\.return$/i, does: "returns work to a student", tool: "gog_classroom_submissions_return" }
36065
36210
  ],
36066
36211
  calendar: [
36067
36212
  { method: /(?:^|\.)events\.(?:insert|import|quickadd)$/i, does: "can put an event on guests' calendars", tool: "gog_calendar_create" },
36068
- { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" }
36069
- ]
36213
+ { method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" },
36214
+ { method: /(?:^|\.)events\.move$/i, does: "can email every guest", tool: "gog_calendar_move" }
36215
+ ],
36216
+ gmail: [
36217
+ { method: /(?:^|\.)settings\.sendas\.create$/i, does: "makes Google email the address and adds a sending identity", tool: "gog_gmail_sendas_create" },
36218
+ { method: /(?:^|\.)settings\.updatevacation$/i, does: "can turn on an auto-reply to every sender", tool: "gog_gmail_vacation_update" }
36219
+ ],
36220
+ script: [{ method: /(?:^|\.)scripts\.run$/i, does: "executes code with this account's authority", tool: "gog_appscript_run_function" }]
36070
36221
  };
36071
36222
  function refusedApiCall(api, method) {
36072
36223
  const name = api.trim().toLowerCase();
@@ -36077,6 +36228,14 @@ function refusedApiCall(api, method) {
36077
36228
  const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
36078
36229
  return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
36079
36230
  }
36231
+ function previewJson(text) {
36232
+ if (text.length > BODY_PREVIEW_MAX) return bodyPreview(text);
36233
+ try {
36234
+ return JSON.parse(text);
36235
+ } catch {
36236
+ return text;
36237
+ }
36238
+ }
36080
36239
  function registerApiTools(server) {
36081
36240
  server.registerTool("gog_api_list", {
36082
36241
  description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
@@ -36105,7 +36264,7 @@ function registerApiTools(server) {
36105
36264
  return runOrDiagnose(args, { account });
36106
36265
  });
36107
36266
  server.registerTool("gog_api_call", {
36108
- description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true \u2014 keep it false to preview, or set dryRun=true to print the intended request without sending it. Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch.",
36267
+ description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true, and every write then asks the MCP host to show the user a confirmation prompt with the exact api/version/method/params/body before anything is sent; set dryRun=true to print the intended request without sending it (no prompt, no changes). Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused outright \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about with a better preview: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch." + CONFIRM_FALLBACK_DESCRIPTION,
36109
36268
  annotations: { destructiveHint: true },
36110
36269
  inputSchema: external_exports.object({
36111
36270
  api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
@@ -36114,11 +36273,12 @@ function registerApiTools(server) {
36114
36273
  params: external_exports.string().optional().describe('Query/path parameters as a JSON object string (e.g. {"fileId":"abc","fields":"name"})'),
36115
36274
  body: external_exports.string().optional().describe("Request body as a JSON string (for write methods)"),
36116
36275
  scope: external_exports.string().optional().describe("Override the OAuth scope used for the call"),
36117
- allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE). Without it, gog refuses write methods. Leave unset for read-only calls."),
36118
- dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made)"),
36119
- account: accountParam
36276
+ allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE); the user is asked to confirm the exact request first. Without it, gog refuses write methods. Leave unset for read-only calls."),
36277
+ dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made, no confirmation prompt)"),
36278
+ account: accountParam,
36279
+ confirmToken: confirmTokenParam
36120
36280
  })
36121
- }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
36281
+ }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account, confirmToken }, ctx) => {
36122
36282
  try {
36123
36283
  assertSafeForwardedArgs([api, version2, method]);
36124
36284
  const refusal = refusedApiCall(api, method);
@@ -36128,6 +36288,27 @@ function registerApiTools(server) {
36128
36288
  } catch (err) {
36129
36289
  return errorResult(errorText(err));
36130
36290
  }
36291
+ if (allowWrite && !dryRun) {
36292
+ const request = { api, version: version2, method, params, body, scope };
36293
+ const view = { api, version: version2, method };
36294
+ if (params !== void 0) view.params = previewJson(params);
36295
+ if (body !== void 0) view.body = previewJson(body);
36296
+ if (scope !== void 0) view.scope = scope;
36297
+ const confirmation = await requireDispatchConfirmation(ctx, {
36298
+ action: "api.call",
36299
+ message: `Review and confirm this raw Google API write (${api} ${version2} ${method}):`,
36300
+ confirmationLabel: "Confirm that this API method should be invoked with exactly these parameters.",
36301
+ details: view,
36302
+ unsupportedNote: "Set dryRun=true to see the request gog would send without sending it, or use the dedicated gog_* tool for this operation.",
36303
+ fallback: {
36304
+ tool: "gog_api_call",
36305
+ account,
36306
+ confirmToken,
36307
+ subject: () => ({ target: `${api}/${version2}/${method}`, payload: request, preview: view })
36308
+ }
36309
+ });
36310
+ if (confirmation) return confirmation;
36311
+ }
36131
36312
  const args = ["api", "call", pos(api), pos(version2), pos(method)];
36132
36313
  if (params) args.push(`--params=${params}`);
36133
36314
  if (body) args.push(`--body=${body}`);
@@ -36140,6 +36321,22 @@ function registerApiTools(server) {
36140
36321
  }
36141
36322
 
36142
36323
  // src/tools/appscript.ts
36324
+ function vetAppScriptRun(subcommand, _args) {
36325
+ return subcommand.toLowerCase() === "run" ? gatedElsewhere("gog appscript run", "gog_appscript_run", "executes code with this account's authority", "gog_appscript_run_function") : void 0;
36326
+ }
36327
+ function projectSnapshot(raw, scriptId) {
36328
+ let project;
36329
+ try {
36330
+ project = JSON.parse(raw)?.project;
36331
+ } catch {
36332
+ project = void 0;
36333
+ }
36334
+ return {
36335
+ scriptId,
36336
+ ...typeof project?.title === "string" ? { title: project.title } : {},
36337
+ ...typeof project?.updateTime === "string" ? { updateTime: project.updateTime } : {}
36338
+ };
36339
+ }
36143
36340
  function registerAppScriptTools(server) {
36144
36341
  const scriptIdParam = external_exports.string().describe(
36145
36342
  "Apps Script project ID \u2014 the long ID in script.google.com/\u2026/projects/<scriptId>/\u2026, NOT the Drive file ID of a container document"
@@ -36220,16 +36417,18 @@ function registerAppScriptTools(server) {
36220
36417
  return runOrDiagnose(args, { account });
36221
36418
  });
36222
36419
  server.registerTool("gog_appscript_run_function", {
36223
- description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that." + apiEnableNote,
36420
+ description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that. Because the wrapper cannot tell what the code will do, it reads the project and asks the MCP host to show the user a confirmation prompt with the project, the function and its arguments first; nothing runs unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + apiEnableNote,
36224
36421
  annotations: { destructiveHint: true },
36225
36422
  inputSchema: external_exports.object({
36226
36423
  scriptId: scriptIdParam,
36227
36424
  functionName: external_exports.string().describe('Name of the function to call, e.g. "doWork"'),
36228
36425
  params: external_exports.string().optional().describe(`Function parameters as a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 not an object`),
36229
36426
  devMode: external_exports.boolean().optional().describe("Run the latest saved code rather than the deployed version (owner only)"),
36230
- account: accountParam
36427
+ account: accountParam,
36428
+ confirmToken: confirmTokenParam
36231
36429
  })
36232
- }, async ({ scriptId, functionName, params, devMode, account }) => {
36430
+ }, async ({ scriptId, functionName, params, devMode, account, confirmToken }, ctx) => {
36431
+ let parsedParams;
36233
36432
  if (params !== void 0) {
36234
36433
  let parsed;
36235
36434
  try {
@@ -36240,7 +36439,26 @@ function registerAppScriptTools(server) {
36240
36439
  if (!Array.isArray(parsed)) {
36241
36440
  throw new Error(`params must be a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 Apps Script takes positional arguments, not named ones. Received: ${params}`);
36242
36441
  }
36442
+ parsedParams = parsed;
36243
36443
  }
36444
+ const got = await runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
36445
+ if (got.isError) return got;
36446
+ const { updateTime, ...project } = projectSnapshot(resultText(got), scriptId);
36447
+ const execution = { project, functionName, params: parsedParams, devMode: Boolean(devMode), runsAs: senderPreview(account) };
36448
+ const confirmation = await requireDispatchConfirmation(ctx, {
36449
+ action: "appscript.run-function",
36450
+ message: "Review and confirm running this Apps Script function with the account's full authority:",
36451
+ confirmationLabel: "Confirm that this code should run now.",
36452
+ details: execution,
36453
+ unsupportedNote: "Ask the user to run it themselves from the Apps Script editor.",
36454
+ fallback: {
36455
+ tool: "gog_appscript_run_function",
36456
+ account,
36457
+ confirmToken,
36458
+ subject: () => ({ target: `${scriptId}/${functionName}`, revision: updateTime, payload: execution, preview: execution })
36459
+ }
36460
+ });
36461
+ if (confirmation) return confirmation;
36244
36462
  const args = ["appscript", "run", pos(scriptId), pos(functionName)];
36245
36463
  if (params !== void 0) args.push(`--params=${params}`);
36246
36464
  if (devMode) args.push("--dev-mode");
@@ -36249,6 +36467,7 @@ function registerAppScriptTools(server) {
36249
36467
  registerRunTool(server, {
36250
36468
  service: "appscript",
36251
36469
  examples: '"get", "content", "deployments"',
36470
+ vet: vetAppScriptRun,
36252
36471
  note: "To execute a function, use gog_appscript_run_function \u2014 this tool is the generic escape hatch."
36253
36472
  });
36254
36473
  }
@@ -36388,21 +36607,47 @@ function registerAuthTools(server) {
36388
36607
  }
36389
36608
 
36390
36609
  // src/tools/calendar.ts
36391
- var CALENDAR_GATED = {
36392
- create: "gog_calendar_create",
36393
- add: "gog_calendar_create",
36394
- new: "gog_calendar_create",
36395
- update: "gog_calendar_update",
36396
- edit: "gog_calendar_update",
36397
- set: "gog_calendar_update",
36398
- respond: "gog_calendar_respond",
36399
- rsvp: "gog_calendar_respond",
36400
- reply: "gog_calendar_respond"
36610
+ var ASKS = (tool) => `Use ${tool}, which asks the user to confirm.`;
36611
+ var CALENDAR_REFUSED = {
36612
+ create: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36613
+ add: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36614
+ new: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
36615
+ update: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36616
+ edit: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36617
+ set: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
36618
+ respond: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36619
+ rsvp: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36620
+ reply: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
36621
+ move: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
36622
+ transfer: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
36623
+ delete: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36624
+ del: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36625
+ rm: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36626
+ remove: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
36627
+ "delete-calendar": { does: "deletes a calendar and every event on it", instead: ASKS("gog_calendar_delete_calendar") },
36628
+ "out-of-office": { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") },
36629
+ ooo: { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") }
36401
36630
  };
36402
- function vetCalendarRun(subcommand, _args) {
36631
+ function vetCalendarRun(subcommand, args) {
36403
36632
  const sub = subcommand.toLowerCase();
36404
- const tool = Object.hasOwn(CALENDAR_GATED, sub) ? CALENDAR_GATED[sub] : void 0;
36405
- return tool ? gatedElsewhere(`gog calendar ${sub}`, "gog_calendar_run", "can change what guests see", tool) : void 0;
36633
+ const what = `gog calendar ${sub}`;
36634
+ const via = "gog_calendar_run";
36635
+ if (Object.hasOwn(CALENDAR_REFUSED, sub)) {
36636
+ const { does, instead } = CALENDAR_REFUSED[sub];
36637
+ return refusedInRun(what, via, does, instead);
36638
+ }
36639
+ if (sub === "propose-time" && (flagValue(args, "decline") !== void 0 || flagValue(args, "comment") !== void 0)) {
36640
+ return gatedElsewhere(`${what} --decline`, via, "declines the event and notifies the organizer", "gog_calendar_respond");
36641
+ }
36642
+ if ((sub === "focus-time" || sub === "focus") && flagValue(args, "auto-decline")?.toLowerCase() !== "none") {
36643
+ return refusedInRun(
36644
+ what,
36645
+ via,
36646
+ "auto-declines invitations and notifies their organizers (gog defaults --auto-decline to all)",
36647
+ "Pass --auto-decline=none, or ask the user to create it from Google Calendar."
36648
+ );
36649
+ }
36650
+ return void 0;
36406
36651
  }
36407
36652
  function parseAttendees(value) {
36408
36653
  if (!value) return [];
@@ -36419,15 +36664,17 @@ function eventSnapshot(raw) {
36419
36664
  } catch {
36420
36665
  event = void 0;
36421
36666
  }
36422
- const str = (v) => typeof v === "string" ? v : void 0;
36667
+ const str2 = (v) => typeof v === "string" ? v : void 0;
36423
36668
  return {
36424
- summary: str(event?.summary),
36669
+ summary: str2(event?.summary),
36425
36670
  start: timeOf(event?.start),
36426
36671
  end: timeOf(event?.end),
36427
- organizer: str(event?.organizer?.email),
36672
+ organizer: str2(event?.organizer?.email),
36428
36673
  // Guests are the OTHER people: not this account's own attendee row, not rooms.
36429
36674
  guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
36430
- etag: str(event?.etag)
36675
+ // A series, or one instance of one: gog's delete --scope defaults to all.
36676
+ ...Array.isArray(event?.recurrence) && event.recurrence.length > 0 || typeof event?.recurringEventId === "string" ? { recurring: true } : {},
36677
+ etag: str2(event?.etag)
36431
36678
  };
36432
36679
  }
36433
36680
  async function readEvent(calendarId, eventId, account) {
@@ -36648,14 +36895,35 @@ function registerCalendarTools(server) {
36648
36895
  return runOrDiagnose(args, { account });
36649
36896
  });
36650
36897
  server.registerTool("gog_calendar_delete", {
36651
- description: "Delete a calendar event.",
36898
+ description: "Delete a calendar event. It disappears from every guest's calendar too, and for a recurring event gog deletes the WHOLE series, so when the event has guests or recurs this reads it and asks the MCP host to show the user a confirmation prompt with the event as it stands; a guest-free, one-off event is deleted without asking." + CONFIRM_FALLBACK_DESCRIPTION,
36652
36899
  annotations: { destructiveHint: true },
36653
36900
  inputSchema: external_exports.object({
36654
36901
  calendarId: external_exports.string().describe("Calendar ID"),
36655
36902
  eventId: external_exports.string().describe("Event ID"),
36656
- account: accountParam
36903
+ account: accountParam,
36904
+ confirmToken: confirmTokenParam
36657
36905
  })
36658
- }, async ({ calendarId, eventId, account }) => {
36906
+ }, async ({ calendarId, eventId, account, confirmToken }, ctx) => {
36907
+ const read = await readEvent(calendarId, eventId, account);
36908
+ if (read.error) return read.error;
36909
+ const { etag, ...event } = read.event;
36910
+ if (event.guests.length > 0 || event.recurring) {
36911
+ const view = { calendarId, eventId, event, scope: event.recurring ? "the whole recurring series" : "this event" };
36912
+ const confirmation = await requireDispatchConfirmation(ctx, {
36913
+ action: "calendar.delete",
36914
+ message: event.recurring ? "Review and confirm deleting this ENTIRE recurring series:" : "Review and confirm deleting this event from every guest's calendar:",
36915
+ confirmationLabel: "Confirm that this event should be deleted now.",
36916
+ details: view,
36917
+ unsupportedNote: "Ask the user to delete it from Google Calendar.",
36918
+ fallback: {
36919
+ tool: "gog_calendar_delete",
36920
+ account,
36921
+ confirmToken,
36922
+ subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
36923
+ }
36924
+ });
36925
+ if (confirmation) return confirmation;
36926
+ }
36659
36927
  return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
36660
36928
  });
36661
36929
  server.registerTool("gog_calendar_respond", {
@@ -36777,8 +37045,14 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
36777
37045
 
36778
37046
  // src/tools/chat.ts
36779
37047
  var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
37048
+ var CHAT_SPACE_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
36780
37049
  function vetChatRun(subcommand, args) {
36781
37050
  const sub = subcommand.toLowerCase();
37051
+ if (sub === "spaces") {
37052
+ const word2 = hasCommandWord(args, CHAT_SPACE_CREATE_WORDS);
37053
+ const withMembers = args.some((a) => /^--members?(?:=|$)/i.test(a));
37054
+ return word2 && withMembers ? gatedElsewhere(`gog chat spaces ${word2.toLowerCase()} --member`, "gog_chat_run", "adds people to a space", "gog_chat_spaces_create") : void 0;
37055
+ }
36782
37056
  if (sub !== "messages" && sub !== "dm") return void 0;
36783
37057
  const word = hasCommandWord(args, CHAT_SEND_WORDS);
36784
37058
  if (!word) return void 0;
@@ -36829,16 +37103,34 @@ function registerChatTools(server) {
36829
37103
  return runOrDiagnose(args, { account });
36830
37104
  });
36831
37105
  server.registerTool("gog_chat_spaces_create", {
36832
- description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified \u2014 this is visible to other people the moment it runs, so confirm the member list before calling it." + workspaceOnlyNote,
37106
+ description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified, so with members this asks the MCP host to show the user a confirmation prompt with the space name and every member first; nothing is created unless they accept. A space with no members reaches nobody and is created without asking." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
36833
37107
  annotations: { destructiveHint: true },
36834
37108
  inputSchema: external_exports.object({
36835
37109
  displayName: external_exports.string().describe("Display name for the new space"),
36836
37110
  members: external_exports.array(external_exports.string()).optional().describe('Initial members, as email addresses or "users/..." resource names'),
36837
- account: accountParam
37111
+ account: accountParam,
37112
+ confirmToken: confirmTokenParam
36838
37113
  })
36839
- }, async ({ displayName, members: members2, account }) => {
37114
+ }, async ({ displayName, members: members2, account, confirmToken }, ctx) => {
36840
37115
  const args = ["chat", "spaces", "create", pos(displayName)];
36841
37116
  if (members2) for (const member of members2) args.push(`--member=${member}`);
37117
+ if (members2?.length) {
37118
+ const space = { displayName, members: members2 };
37119
+ const confirmation = await requireDispatchConfirmation(ctx, {
37120
+ action: "chat.space-create",
37121
+ message: "Review and confirm this Chat space \u2014 every member is added and notified:",
37122
+ confirmationLabel: "Confirm that these people should be added to a new space now.",
37123
+ details: space,
37124
+ unsupportedNote: "Create the space without members instead; the user can add people from Google Chat.",
37125
+ fallback: {
37126
+ tool: "gog_chat_spaces_create",
37127
+ account,
37128
+ confirmToken,
37129
+ subject: () => ({ target: displayName, payload: space, preview: space })
37130
+ }
37131
+ });
37132
+ if (confirmation) return confirmation;
37133
+ }
36842
37134
  return runOrDiagnose(args, { account });
36843
37135
  });
36844
37136
  server.registerTool("gog_chat_threads_list", {
@@ -37032,20 +37324,85 @@ function registerChatTools(server) {
37032
37324
 
37033
37325
  // src/tools/classroom.ts
37034
37326
  var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
37035
- var CLASSROOM_GATED = {
37036
- announcements: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37037
- announcement: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37038
- ann: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
37039
- invitations: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
37040
- invitation: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
37041
- invites: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
37327
+ var CLASSROOM_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
37328
+ var CLASSROOM_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
37329
+ var CLASSROOM_RETURN_WORDS = /* @__PURE__ */ new Set(["return", "send"]);
37330
+ var CLASSROOM_ASSIGNEE_WORDS = /* @__PURE__ */ new Set(["assignees", "assign"]);
37331
+ var CLASSROOM_GROUPS = {
37332
+ courses: "courses",
37333
+ course: "courses",
37334
+ announcements: "announcements",
37335
+ announcement: "announcements",
37336
+ ann: "announcements",
37337
+ invitations: "invitations",
37338
+ invitation: "invitations",
37339
+ invites: "invitations",
37340
+ coursework: "coursework",
37341
+ work: "coursework",
37342
+ materials: "materials",
37343
+ material: "materials",
37344
+ students: "students",
37345
+ student: "students",
37346
+ teachers: "teachers",
37347
+ teacher: "teachers",
37348
+ submissions: "submissions",
37349
+ submission: "submissions",
37350
+ "guardian-invitations": "guardian-invitations",
37351
+ "guardian-invites": "guardian-invitations"
37042
37352
  };
37353
+ function publishesToStudents(args) {
37354
+ return flagValue(args, "state")?.toUpperCase() === "PUBLISHED" || flagValue(args, "scheduled") !== void 0;
37355
+ }
37356
+ function createsVisibleWork(args) {
37357
+ return flagValue(args, "state")?.toUpperCase() !== "DRAFT" || flagValue(args, "scheduled") !== void 0;
37358
+ }
37359
+ function widensAssignees(args) {
37360
+ return flagValue(args, "mode")?.toUpperCase() === "ALL_STUDENTS" || flagValue(args, "add-student") !== void 0;
37361
+ }
37043
37362
  function vetClassroomRun(subcommand, args) {
37044
37363
  const sub = subcommand.toLowerCase();
37045
- const gated = Object.hasOwn(CLASSROOM_GATED, sub) ? CLASSROOM_GATED[sub] : void 0;
37046
- if (!gated) return void 0;
37047
- const word = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
37048
- return word ? gatedElsewhere(`gog classroom ${sub} ${word.toLowerCase()}`, "gog_classroom_run", gated.does, gated.tool) : void 0;
37364
+ if (!Object.hasOwn(CLASSROOM_GROUPS, sub)) return void 0;
37365
+ const group = CLASSROOM_GROUPS[sub];
37366
+ const via = "gog_classroom_run";
37367
+ const what = (word) => `gog classroom ${sub} ${word.toLowerCase()}`;
37368
+ const create = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
37369
+ const update = hasCommandWord(args, CLASSROOM_UPDATE_WORDS);
37370
+ const remove = hasCommandWord(args, CLASSROOM_DELETE_WORDS);
37371
+ const widenedAssignees = () => {
37372
+ const word = hasCommandWord(args, CLASSROOM_ASSIGNEE_WORDS);
37373
+ return word && widensAssignees(args) ? refusedInRun(what(word), via, "shows it to more students", "Ask the user to change who it is assigned to from Classroom.") : void 0;
37374
+ };
37375
+ switch (group) {
37376
+ case "courses":
37377
+ return remove ? gatedElsewhere(what(remove), via, "deletes a course", "gog_classroom_courses_delete") : void 0;
37378
+ case "announcements":
37379
+ if (create) return gatedElsewhere(what(create), via, "posts to a class", "gog_classroom_announcements_create");
37380
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes an announcement to a class", "gog_classroom_announcements_update");
37381
+ return widenedAssignees();
37382
+ case "invitations":
37383
+ return create ? gatedElsewhere(what(create), via, "invites someone to a class", "gog_classroom_invitations_create") : void 0;
37384
+ case "coursework":
37385
+ if (create && createsVisibleWork(args)) return gatedElsewhere(what(create), via, "posts coursework to a class", "gog_classroom_coursework_create");
37386
+ if (remove) return gatedElsewhere(what(remove), via, "deletes coursework and every submission to it", "gog_classroom_coursework_delete");
37387
+ if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes work to students", "gog_classroom_coursework_update");
37388
+ return widenedAssignees();
37389
+ case "materials":
37390
+ if (create && createsVisibleWork(args)) {
37391
+ return refusedInRun(what(create), via, "publishes material to students", "Create it with --state=DRAFT and no --scheduled; ask the user to publish it from Classroom.");
37392
+ }
37393
+ if (update && publishesToStudents(args)) return refusedInRun(what(update), via, "publishes material to students", "Ask the user to publish it from Classroom.");
37394
+ return void 0;
37395
+ case "students":
37396
+ return create ? gatedElsewhere(what(create), via, "enrols someone in a class", "gog_classroom_students_add") : void 0;
37397
+ case "teachers":
37398
+ return create ? gatedElsewhere(what(create), via, "gives someone a teacher's access to a class and its roster", "gog_classroom_teachers_add") : void 0;
37399
+ case "submissions": {
37400
+ const word = hasCommandWord(args, CLASSROOM_RETURN_WORDS);
37401
+ return word ? gatedElsewhere(what(word), via, "returns work to a student, who is notified", "gog_classroom_submissions_return") : void 0;
37402
+ }
37403
+ case "guardian-invitations":
37404
+ return create ? refusedInRun(what(create), via, "emails a guardian invitation", "Ask the user to invite the guardian from Classroom.") : void 0;
37405
+ }
37049
37406
  }
37050
37407
  async function readCourse(courseId, account, runner = runOrDiagnose) {
37051
37408
  const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
@@ -37064,6 +37421,52 @@ async function readCourse(courseId, account, runner = runOrDiagnose) {
37064
37421
  }
37065
37422
  };
37066
37423
  }
37424
+ function nested(raw, key) {
37425
+ try {
37426
+ const parsed = JSON.parse(raw);
37427
+ const inner = parsed?.[key];
37428
+ return inner && typeof inner === "object" ? inner : void 0;
37429
+ } catch {
37430
+ return void 0;
37431
+ }
37432
+ }
37433
+ var str = (v) => typeof v === "string" ? v : void 0;
37434
+ var num = (v) => typeof v === "number" ? v : void 0;
37435
+ async function readCoursework(courseId, courseworkId, account, runner = runOrDiagnose) {
37436
+ const got = await runner(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
37437
+ if (got.isError) return { error: got };
37438
+ const work = nested(resultText(got), "coursework");
37439
+ const title = str(work?.title);
37440
+ return { coursework: { id: courseworkId, ...title !== void 0 ? { title } : {} } };
37441
+ }
37442
+ async function readSubmission(courseId, courseworkId, submissionId, account, runner = runOrDiagnose) {
37443
+ const got = await runner(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
37444
+ if (got.isError) return { error: got };
37445
+ const sub = nested(resultText(got), "submission");
37446
+ const fields = {
37447
+ student: str(sub?.userId),
37448
+ state: str(sub?.state),
37449
+ draftGrade: num(sub?.draftGrade),
37450
+ assignedGrade: num(sub?.assignedGrade)
37451
+ };
37452
+ const updateTime = str(sub?.updateTime);
37453
+ return {
37454
+ submission: {
37455
+ id: submissionId,
37456
+ ...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0))
37457
+ },
37458
+ ...updateTime !== void 0 ? { updateTime } : {}
37459
+ };
37460
+ }
37461
+ async function studentLabel(courseId, userId, account, runner = runOrDiagnose) {
37462
+ const got = await runner(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
37463
+ if (got.isError) return userId;
37464
+ const profile = nested(resultText(got), "student")?.profile;
37465
+ const name = str(profile?.name?.fullName);
37466
+ const email3 = str(profile?.emailAddress);
37467
+ if (name && email3) return `${name} <${email3}>`;
37468
+ return name ?? email3 ?? userId;
37469
+ }
37067
37470
  function registerClassroomTools(server) {
37068
37471
  server.registerTool("gog_classroom_courses_list", {
37069
37472
  description: "List Google Classroom courses.",
@@ -37276,15 +37679,38 @@ function registerClassroomTools(server) {
37276
37679
  return runOrDiagnose(args, { account });
37277
37680
  });
37278
37681
  server.registerTool("gog_classroom_submissions_return", {
37279
- description: "Return a graded submission to the student.",
37682
+ description: "Return a graded submission to the student, who is notified and sees the grade. Reads the course, the coursework and the submission and asks the MCP host to show the user a confirmation prompt with the class, the assignment, the student and the grade being returned; nothing is returned unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
37280
37683
  annotations: { destructiveHint: true },
37281
37684
  inputSchema: external_exports.object({
37282
37685
  courseId: external_exports.string().describe("Course ID"),
37283
37686
  courseworkId: external_exports.string().describe("Coursework ID"),
37284
37687
  submissionId: external_exports.string().describe("Submission ID"),
37285
- account: accountParam
37688
+ account: accountParam,
37689
+ confirmToken: confirmTokenParam
37286
37690
  })
37287
- }, async ({ courseId, courseworkId, submissionId, account }) => {
37691
+ }, async ({ courseId, courseworkId, submissionId, account, confirmToken }, ctx) => {
37692
+ const course = await readCourse(courseId, account);
37693
+ if (course.error) return course.error;
37694
+ const work = await readCoursework(courseId, courseworkId, account);
37695
+ if (work.error) return work.error;
37696
+ const sub = await readSubmission(courseId, courseworkId, submissionId, account);
37697
+ if (sub.error) return sub.error;
37698
+ const submission = sub.submission.student ? { ...sub.submission, student: await studentLabel(courseId, sub.submission.student, account) } : sub.submission;
37699
+ const view = { course: course.course, coursework: work.coursework, submission };
37700
+ const confirmation = await requireDispatchConfirmation(ctx, {
37701
+ action: "classroom.submission-return",
37702
+ message: "Review and confirm returning this work \u2014 the student is notified and sees the grade:",
37703
+ confirmationLabel: "Confirm that this submission should be returned to the student now.",
37704
+ details: view,
37705
+ unsupportedNote: "Ask the user to return it from Classroom.",
37706
+ fallback: {
37707
+ tool: "gog_classroom_submissions_return",
37708
+ account,
37709
+ confirmToken,
37710
+ subject: () => ({ target: `${courseId}/${courseworkId}/${submissionId}`, revision: sub.updateTime, payload: view, preview: view })
37711
+ }
37712
+ });
37713
+ if (confirmation) return confirmation;
37288
37714
  return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
37289
37715
  });
37290
37716
  server.registerTool("gog_classroom_submissions_turn_in", {
@@ -37346,13 +37772,13 @@ function registerClassroomTools(server) {
37346
37772
  return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
37347
37773
  });
37348
37774
  server.registerTool("gog_classroom_announcements_create", {
37349
- description: "Create an announcement in a Google Classroom course. Unless state is DRAFT (which students cannot see), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT." + CONFIRM_FALLBACK_DESCRIPTION,
37775
+ description: "Create an announcement in a Google Classroom course. Unless state is DRAFT with no scheduled time (which students cannot see; a scheduled draft publishes itself), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT and no scheduled time." + CONFIRM_FALLBACK_DESCRIPTION,
37350
37776
  annotations: { destructiveHint: true },
37351
37777
  inputSchema: external_exports.object({
37352
37778
  courseId: external_exports.string().describe("Course ID"),
37353
37779
  text: external_exports.string().describe("Announcement text"),
37354
- state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
37355
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
37780
+ state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (an unscheduled DRAFT is visible only to teachers and needs no confirmation)"),
37781
+ scheduled: external_exports.string().optional().describe("Scheduled publish time (asks the user to confirm, even for a DRAFT)"),
37356
37782
  account: accountParam,
37357
37783
  confirmToken: confirmTokenParam
37358
37784
  })
@@ -37360,7 +37786,7 @@ function registerClassroomTools(server) {
37360
37786
  const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
37361
37787
  if (state) args.push(`--state=${state}`);
37362
37788
  if (scheduled) args.push(`--scheduled=${scheduled}`);
37363
- if (state !== "DRAFT") {
37789
+ if (state !== "DRAFT" || scheduled) {
37364
37790
  const read = await readCourse(courseId, account);
37365
37791
  if (read.error) return read.error;
37366
37792
  const publishes = scheduled ? `at ${scheduled}` : "immediately";
@@ -37532,6 +37958,9 @@ function registerContactsTools(server) {
37532
37958
  }
37533
37959
 
37534
37960
  // src/tools/docs.ts
37961
+ function vetDocsRun(subcommand, args) {
37962
+ return vetCommentsRun("docs", subcommand, args);
37963
+ }
37535
37964
  function registerDocsTools(server) {
37536
37965
  server.registerTool("gog_docs_info", {
37537
37966
  description: "Get Google Doc metadata: title, ID, and other properties.",
@@ -37628,12 +38057,29 @@ function registerDocsTools(server) {
37628
38057
  }, async ({ docId, account }) => {
37629
38058
  return runOrDiagnose(["docs", "structure", pos(docId)], { account });
37630
38059
  });
37631
- registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
38060
+ registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"', vet: vetDocsRun });
37632
38061
  }
37633
38062
 
37634
38063
  // src/tools/drive.ts
37635
- function vetDriveRun(subcommand, _args) {
37636
- return subcommand.toLowerCase() === "share" ? gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share") : void 0;
38064
+ var DRIVE_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del"]);
38065
+ function vetDriveRun(subcommand, args) {
38066
+ const sub = subcommand.toLowerCase();
38067
+ if (sub === "share") return gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share");
38068
+ if (DRIVE_DELETE_WORDS.has(sub) && hasTrueFlag(args, "permanent")) {
38069
+ return gatedElsewhere(`gog drive ${sub} --permanent`, "gog_drive_run", "deletes a file for good, bypassing the trash", "gog_drive_delete");
38070
+ }
38071
+ if (sub === "bulk") {
38072
+ return gatedElsewhere(
38073
+ "gog drive bulk",
38074
+ "gog_drive_run",
38075
+ "rewrites sharing permissions across every matching file in a tree",
38076
+ "gog_drive_share (one file at a time)"
38077
+ );
38078
+ }
38079
+ if (sub === "unshare") {
38080
+ return refusedInRun("gog drive unshare", "gog_drive_run", "removes someone's access to a file", "Use gog_drive_unshare.");
38081
+ }
38082
+ return vetCommentsRun("drive", sub, args);
37637
38083
  }
37638
38084
  var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
37639
38085
  function shareTargetMeta(raw) {
@@ -37752,16 +38198,36 @@ function registerDriveTools(server) {
37752
38198
  return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
37753
38199
  });
37754
38200
  server.registerTool("gog_drive_delete", {
37755
- description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
38201
+ description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible). A permanent delete reads the file and asks the MCP host to show the user a confirmation prompt naming it first; nothing is deleted unless they accept. Moving to trash is recoverable and does not ask." + CONFIRM_FALLBACK_DESCRIPTION,
37756
38202
  annotations: { destructiveHint: true },
37757
38203
  inputSchema: external_exports.object({
37758
38204
  fileId: external_exports.string().describe("File ID to delete"),
37759
- permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible)"),
37760
- account: accountParam
38205
+ permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
38206
+ account: accountParam,
38207
+ confirmToken: confirmTokenParam
37761
38208
  })
37762
- }, async ({ fileId, permanent, account }) => {
38209
+ }, async ({ fileId, permanent, account, confirmToken }, ctx) => {
37763
38210
  const args = ["drive", "delete", pos(fileId)];
37764
- if (permanent) args.push("--permanent");
38211
+ if (permanent) {
38212
+ const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
38213
+ if (got.isError) return got;
38214
+ const target = { file: { id: fileId, ...shareTargetMeta(resultText(got)) }, permanent: true };
38215
+ const confirmation = await requireDispatchConfirmation(ctx, {
38216
+ action: "drive.delete-permanent",
38217
+ message: "Review and confirm PERMANENTLY deleting this file \u2014 it skips the trash and cannot be recovered:",
38218
+ confirmationLabel: "Confirm that this file should be permanently deleted now.",
38219
+ details: target,
38220
+ unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
38221
+ fallback: {
38222
+ tool: "gog_drive_delete",
38223
+ account,
38224
+ confirmToken,
38225
+ subject: () => ({ target: fileId, payload: target, preview: target })
38226
+ }
38227
+ });
38228
+ if (confirmation) return confirmation;
38229
+ args.push("--permanent");
38230
+ }
37765
38231
  args.push("--force");
37766
38232
  return runOrDiagnose(args, { account });
37767
38233
  });
@@ -38087,18 +38553,22 @@ function trustedDomains(account) {
38087
38553
  }
38088
38554
  function logGmailDispatch(tool, recipients, account) {
38089
38555
  const domains = trustedDomains(account);
38090
- const externalRecipients = recipients.filter((recipient) => {
38556
+ const externalDomains = /* @__PURE__ */ new Set();
38557
+ let externalRecipientCount = 0;
38558
+ for (const recipient of recipients) {
38091
38559
  const at = recipient.indexOf("@");
38092
- const domain2 = at > -1 ? recipient.slice(at + 1) : "";
38093
- return !domain2 || !domains.has(domain2);
38094
- });
38560
+ const domain2 = at > -1 ? recipient.slice(at + 1).toLowerCase() : "";
38561
+ if (domain2 && domains.has(domain2)) continue;
38562
+ externalRecipientCount += 1;
38563
+ externalDomains.add(domain2 || "(no domain)");
38564
+ }
38095
38565
  const event = {
38096
38566
  event: "gmail_dispatch",
38097
38567
  tool,
38098
38568
  recipientCount: recipients.length,
38099
- externalRecipientCount: externalRecipients.length,
38100
- hasExternalRecipients: externalRecipients.length > 0,
38101
- externalRecipients,
38569
+ externalRecipientCount,
38570
+ hasExternalRecipients: externalRecipientCount > 0,
38571
+ externalDomains: [...externalDomains].sort(),
38102
38572
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
38103
38573
  };
38104
38574
  process.stderr.write(`${JSON.stringify(event)}
@@ -38265,6 +38735,39 @@ async function sendReply(kind, toolName, messageId, account, flags, ctx, confirm
38265
38735
  return result;
38266
38736
  }
38267
38737
  var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
38738
+ var GMAIL_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
38739
+ var GMAIL_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
38740
+ var GMAIL_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
38741
+ var SENDAS = /* @__PURE__ */ new Set(["sendas"]);
38742
+ var VACATION = /* @__PURE__ */ new Set(["vacation"]);
38743
+ function vetGmailGatedRun(sub, args) {
38744
+ if (sub === "batch") {
38745
+ const word = hasCommandWord(args, GMAIL_DELETE_WORDS);
38746
+ return word ? gatedElsewhere(`gog gmail batch ${word.toLowerCase()}`, "gog_gmail_run", "deletes mail for good, bypassing the Trash", "gog_gmail_batch_delete") : void 0;
38747
+ }
38748
+ if (sub !== "settings" && sub !== "sendas" && sub !== "vacation") return void 0;
38749
+ const words = sub === "settings" ? args : [sub, ...args];
38750
+ if (hasCommandWord(words, SENDAS)) {
38751
+ const word = hasCommandWord(args, GMAIL_CREATE_WORDS);
38752
+ if (word) {
38753
+ return gatedElsewhere(
38754
+ `gog gmail sendas ${word.toLowerCase()}`,
38755
+ "gog_gmail_run",
38756
+ "makes Google email the address and adds a sending identity",
38757
+ "gog_gmail_sendas_create"
38758
+ );
38759
+ }
38760
+ }
38761
+ if (hasCommandWord(words, VACATION) && hasCommandWord(args, GMAIL_UPDATE_WORDS) && hasTrueFlag(args, "enable")) {
38762
+ return gatedElsewhere(
38763
+ "gog gmail vacation update --enable",
38764
+ "gog_gmail_run",
38765
+ "turns on an auto-reply to every sender",
38766
+ "gog_gmail_vacation_update"
38767
+ );
38768
+ }
38769
+ return void 0;
38770
+ }
38268
38771
  function vetGmailRun(subcommand, args) {
38269
38772
  if (subcommand === "autoreply") {
38270
38773
  return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
@@ -38272,6 +38775,8 @@ function vetGmailRun(subcommand, args) {
38272
38775
  if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
38273
38776
  return `gog gmail ${subcommand} can forward or hand over mail and is not available through gog_gmail_run. Use the dedicated gog_gmail_* tool instead.`;
38274
38777
  }
38778
+ const gated = vetGmailGatedRun(subcommand.toLowerCase(), args);
38779
+ if (gated) return gated;
38275
38780
  if (subcommand === "settings") {
38276
38781
  const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
38277
38782
  if (blocked) {
@@ -38483,6 +38988,13 @@ function countNonEmptyCells(getOutput) {
38483
38988
  }
38484
38989
 
38485
38990
  // src/tools/sheets.ts
38991
+ var DATASOURCE_ALIASES = /* @__PURE__ */ new Set(["datasource", "data-source", "data-sources", "connected-sheets"]);
38992
+ var DATASOURCE_BILLED = /* @__PURE__ */ new Set(["add", "update", "refresh"]);
38993
+ function vetSheetsRun(subcommand, args) {
38994
+ if (!DATASOURCE_ALIASES.has(subcommand.toLowerCase())) return void 0;
38995
+ const word = hasCommandWord(args, DATASOURCE_BILLED)?.toLowerCase();
38996
+ return word ? gatedElsewhere(`gog sheets datasource ${word}`, "gog_sheets_run", "runs a billed BigQuery job", `gog_sheets_datasource_${word}`) : void 0;
38997
+ }
38486
38998
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
38487
38999
  var dryRunParam = external_exports.boolean().optional().describe(
38488
39000
  "Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
@@ -38598,7 +39110,7 @@ function registerSheetsTools(server) {
38598
39110
  }, async ({ spreadsheetId, find, replace, account }) => {
38599
39111
  return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
38600
39112
  });
38601
- registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
39113
+ registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"', vet: vetSheetsRun });
38602
39114
  }
38603
39115
 
38604
39116
  // src/tools/slides.ts
@@ -38762,7 +39274,7 @@ function registerTasksTools(server) {
38762
39274
  }
38763
39275
 
38764
39276
  // src/server.ts
38765
- var VERSION = true ? "4.4.0" : "0.0.0";
39277
+ var VERSION = true ? "4.5.1" : "0.0.0";
38766
39278
  var BASE_TOOL_REGISTRARS = [
38767
39279
  registerApiTools,
38768
39280
  registerAppScriptTools,
@@ -38814,12 +39326,12 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
38814
39326
  const [clientId, clientSecret, refreshToken, account] = values;
38815
39327
  const { spawner } = options;
38816
39328
  try {
38817
- const { createHash: createHash4 } = await import("node:crypto");
39329
+ const { createHash: createHash5 } = await import("node:crypto");
38818
39330
  const { mkdir, readFile, writeFile, chmod } = await import("node:fs/promises");
38819
39331
  const home = options.home ?? (await import("node:os")).homedir();
38820
39332
  const dir = join5(home, ".gogcli-mcp");
38821
39333
  const marker = join5(dir, AUTH_BOOTSTRAP_MARKER);
38822
- const fingerprint = createHash4("sha256").update(values.join("\0")).digest("hex");
39334
+ const fingerprint = createHash5("sha256").update(values.join("\0")).digest("hex");
38823
39335
  const previous = await readFile(marker, "utf8").catch(() => void 0);
38824
39336
  if (previous === fingerprint && await accountListed(account, spawner)) return "present";
38825
39337
  await run(