gogcli-mcp 4.2.4 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/index.js +714 -216
  4. package/dist/lib.js +751 -217
  5. package/manifest.json +9 -2
  6. package/mint.yaml +1 -1
  7. package/package.json +2 -2
  8. package/server.json +2 -2
  9. package/src/arg-guard.ts +68 -0
  10. package/src/argv.ts +27 -0
  11. package/src/attachment-root.ts +111 -0
  12. package/src/attachments.ts +1 -0
  13. package/src/blob-upload.ts +4 -2
  14. package/src/file-roots.ts +66 -0
  15. package/src/gmail-dispatch-guard.ts +56 -16
  16. package/src/gmail-results.ts +21 -2
  17. package/src/lib.ts +6 -0
  18. package/src/run-path-guard.ts +118 -0
  19. package/src/runner.ts +87 -19
  20. package/src/tools/api.ts +39 -7
  21. package/src/tools/appscript.ts +12 -8
  22. package/src/tools/auth.ts +13 -3
  23. package/src/tools/calendar.ts +10 -8
  24. package/src/tools/chat.ts +16 -13
  25. package/src/tools/classroom.ts +27 -25
  26. package/src/tools/contacts.ts +5 -3
  27. package/src/tools/docs.ts +8 -6
  28. package/src/tools/drive.ts +42 -18
  29. package/src/tools/gmail.ts +71 -11
  30. package/src/tools/sheets.ts +10 -8
  31. package/src/tools/slides.ts +14 -9
  32. package/src/tools/tasks.ts +7 -5
  33. package/src/tools/utils.ts +52 -6
  34. package/tests/arg-guard.test.ts +80 -0
  35. package/tests/attachment-root.test.ts +130 -0
  36. package/tests/attachments.test.ts +8 -0
  37. package/tests/blob-upload.test.ts +4 -3
  38. package/tests/file-roots.test.ts +101 -0
  39. package/tests/gmail-dispatch-guard.test.ts +46 -0
  40. package/tests/gmail-results.test.ts +35 -1
  41. package/tests/run-path-guard.test.ts +142 -0
  42. package/tests/runner.test.ts +136 -0
  43. package/tests/tools/api.test.ts +74 -8
  44. package/tests/tools/appscript.test.ts +34 -8
  45. package/tests/tools/auth.test.ts +44 -17
  46. package/tests/tools/calendar.test.ts +29 -27
  47. package/tests/tools/chat.test.ts +46 -20
  48. package/tests/tools/classroom.test.ts +39 -38
  49. package/tests/tools/contacts.test.ts +3 -2
  50. package/tests/tools/docs.test.ts +44 -15
  51. package/tests/tools/drive.test.ts +106 -19
  52. package/tests/tools/gmail.test.ts +227 -29
  53. package/tests/tools/run-tool-examples.test.ts +69 -0
  54. package/tests/tools/sheets.test.ts +16 -15
  55. package/tests/tools/slides.test.ts +47 -11
  56. package/tests/tools/tasks.test.ts +7 -6
  57. package/tests/tools/utils.test.ts +32 -31
  58. package/vitest.config.ts +5 -0
package/dist/lib.js CHANGED
@@ -2001,8 +2001,8 @@ function emoji() {
2001
2001
  }
2002
2002
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
2003
2003
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
2004
- var mac = (delimiter2) => {
2005
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
2004
+ var mac = (delimiter3) => {
2005
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
2006
2006
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
2007
2007
  };
2008
2008
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -5030,7 +5030,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
5030
5030
  var NONE = 0;
5031
5031
  var ASSUMED = 1;
5032
5032
  var PROVEN = 2;
5033
- function isRecursive(inst, stack, resolve) {
5033
+ function isRecursive(inst, stack, resolve4) {
5034
5034
  const cached2 = recursive.get(inst);
5035
5035
  if (cached2 !== void 0)
5036
5036
  return cached2 ? PROVEN : NONE;
@@ -5040,7 +5040,7 @@ function isRecursive(inst, stack, resolve) {
5040
5040
  let result = NONE;
5041
5041
  const check2 = (child) => {
5042
5042
  if (result !== PROVEN && child?._zod) {
5043
- const answer = isRecursive(child, stack, resolve);
5043
+ const answer = isRecursive(child, stack, resolve4);
5044
5044
  if (answer > result)
5045
5045
  result = answer;
5046
5046
  }
@@ -5051,7 +5051,7 @@ function isRecursive(inst, stack, resolve) {
5051
5051
  const desc = Object.getOwnPropertyDescriptor(sh, key);
5052
5052
  if (spread && !desc.enumerable)
5053
5053
  continue;
5054
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
5054
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
5055
5055
  if (child > answer)
5056
5056
  answer = child;
5057
5057
  }
@@ -5115,7 +5115,7 @@ function isRecursive(inst, stack, resolve) {
5115
5115
  break;
5116
5116
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5117
5117
  case "lazy": {
5118
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
5118
+ const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
5119
5119
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5120
5120
  break;
5121
5121
  }
@@ -23998,14 +23998,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
23998
23998
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
23999
23999
  }
24000
24000
  function sleep(ms, signal) {
24001
- return new Promise((resolve, reject) => {
24001
+ return new Promise((resolve4, reject) => {
24002
24002
  if (signal?.aborted) {
24003
24003
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
24004
24004
  return;
24005
24005
  }
24006
24006
  const timer = setTimeout(() => {
24007
24007
  signal?.removeEventListener("abort", onAbort);
24008
- resolve();
24008
+ resolve4();
24009
24009
  }, ms);
24010
24010
  const onAbort = () => {
24011
24011
  clearTimeout(timer);
@@ -24796,7 +24796,7 @@ var Protocol = class {
24796
24796
  const flowStartedAt = Date.now();
24797
24797
  let onAbort;
24798
24798
  let cleanupMessageId;
24799
- return new Promise((resolve, reject) => {
24799
+ return new Promise((resolve4, reject) => {
24800
24800
  const earlyReject = (error62) => {
24801
24801
  reject(error62);
24802
24802
  };
@@ -24864,7 +24864,7 @@ var Protocol = class {
24864
24864
  }
24865
24865
  if (decoded.kind === "invalid") return reject(decoded.error);
24866
24866
  if (decoded.kind === "input_required") {
24867
- if (options?.allowInputRequired === true) return resolve(manualInputRequiredValue(decoded));
24867
+ if (options?.allowInputRequired === true) return resolve4(manualInputRequiredValue(decoded));
24868
24868
  const flow = {
24869
24869
  codec: codec2,
24870
24870
  request,
@@ -24876,11 +24876,11 @@ var Protocol = class {
24876
24876
  params
24877
24877
  }, resultSchema, legOptions)
24878
24878
  };
24879
- return resolve(this._resolveNonCompleteResult(decoded, flow));
24879
+ return resolve4(this._resolveNonCompleteResult(decoded, flow));
24880
24880
  }
24881
24881
  const result = decoded.result;
24882
24882
  validateStandardSchema(resultSchema, result).then((parseResult) => {
24883
- if (parseResult.success) resolve(parseResult.data);
24883
+ if (parseResult.success) resolve4(parseResult.data);
24884
24884
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
24885
24885
  }, reject);
24886
24886
  });
@@ -27692,7 +27692,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
27692
27692
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
27693
27693
  const schOrFunc = root.refs[ref];
27694
27694
  if (schOrFunc) return schOrFunc;
27695
- let _sch = resolve.call(this, root, ref);
27695
+ let _sch = resolve4.call(this, root, ref);
27696
27696
  if (_sch === void 0) {
27697
27697
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
27698
27698
  const { schemaId } = this.opts;
@@ -27718,7 +27718,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
27718
27718
  function sameSchemaEnv(s1, s2) {
27719
27719
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
27720
27720
  }
27721
- function resolve(root, ref) {
27721
+ function resolve4(root, ref) {
27722
27722
  let sch;
27723
27723
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
27724
27724
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -28168,47 +28168,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28168
28168
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
28169
28169
  return uri;
28170
28170
  }
28171
- function resolve(baseURI, relativeURI, options) {
28171
+ function resolve4(baseURI, relativeURI, options) {
28172
28172
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
28173
28173
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
28174
28174
  schemelessOptions.skipEscape = true;
28175
28175
  return serialize(resolved, schemelessOptions);
28176
28176
  }
28177
- function resolveComponent(base, relative, options, skipNormalization) {
28177
+ function resolveComponent(base, relative3, options, skipNormalization) {
28178
28178
  const target = {};
28179
28179
  if (!skipNormalization) {
28180
28180
  base = parse3(serialize(base, options), options);
28181
- relative = parse3(serialize(relative, options), options);
28181
+ relative3 = parse3(serialize(relative3, options), options);
28182
28182
  }
28183
28183
  options = options || {};
28184
- if (!options.tolerant && relative.scheme) {
28185
- target.scheme = relative.scheme;
28186
- target.userinfo = relative.userinfo;
28187
- target.host = relative.host;
28188
- target.port = relative.port;
28189
- target.path = removeDotSegments(relative.path || "");
28190
- target.query = relative.query;
28184
+ if (!options.tolerant && relative3.scheme) {
28185
+ target.scheme = relative3.scheme;
28186
+ target.userinfo = relative3.userinfo;
28187
+ target.host = relative3.host;
28188
+ target.port = relative3.port;
28189
+ target.path = removeDotSegments(relative3.path || "");
28190
+ target.query = relative3.query;
28191
28191
  } else {
28192
- if (relative.userinfo !== void 0 || relative.host !== void 0 || relative.port !== void 0) {
28193
- target.userinfo = relative.userinfo;
28194
- target.host = relative.host;
28195
- target.port = relative.port;
28196
- target.path = removeDotSegments(relative.path || "");
28197
- target.query = relative.query;
28192
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
28193
+ target.userinfo = relative3.userinfo;
28194
+ target.host = relative3.host;
28195
+ target.port = relative3.port;
28196
+ target.path = removeDotSegments(relative3.path || "");
28197
+ target.query = relative3.query;
28198
28198
  } else {
28199
- if (!relative.path) {
28199
+ if (!relative3.path) {
28200
28200
  target.path = base.path;
28201
- if (relative.query !== void 0) target.query = relative.query;
28201
+ if (relative3.query !== void 0) target.query = relative3.query;
28202
28202
  else target.query = base.query;
28203
28203
  } else {
28204
- if (relative.path[0] === "/") target.path = removeDotSegments(relative.path);
28204
+ if (relative3.path[0] === "/") target.path = removeDotSegments(relative3.path);
28205
28205
  else {
28206
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative.path;
28207
- else if (!base.path) target.path = relative.path;
28208
- else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
28206
+ if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative3.path;
28207
+ else if (!base.path) target.path = relative3.path;
28208
+ else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
28209
28209
  target.path = removeDotSegments(target.path);
28210
28210
  }
28211
- target.query = relative.query;
28211
+ target.query = relative3.query;
28212
28212
  }
28213
28213
  target.userinfo = base.userinfo;
28214
28214
  target.host = base.host;
@@ -28216,7 +28216,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28216
28216
  }
28217
28217
  target.scheme = base.scheme;
28218
28218
  }
28219
- target.fragment = relative.fragment;
28219
+ target.fragment = relative3.fragment;
28220
28220
  return target;
28221
28221
  }
28222
28222
  function equal(uriA, uriB, options) {
@@ -28342,7 +28342,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28342
28342
  const fastUri = {
28343
28343
  SCHEMES,
28344
28344
  normalize,
28345
- resolve,
28345
+ resolve: resolve4,
28346
28346
  resolveComponent,
28347
28347
  equal,
28348
28348
  serialize,
@@ -29124,14 +29124,14 @@ var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
29124
29124
  function ucs2length(str) {
29125
29125
  const len = str.length;
29126
29126
  let length = 0;
29127
- let pos = 0;
29127
+ let pos2 = 0;
29128
29128
  let value;
29129
- while (pos < len) {
29129
+ while (pos2 < len) {
29130
29130
  length++;
29131
- value = str.charCodeAt(pos++);
29132
- if (value >= 55296 && value <= 56319 && pos < len) {
29133
- value = str.charCodeAt(pos);
29134
- if ((value & 64512) === 56320) pos++;
29131
+ value = str.charCodeAt(pos2++);
29132
+ if (value >= 55296 && value <= 56319 && pos2 < len) {
29133
+ value = str.charCodeAt(pos2);
29134
+ if ((value & 64512) === 56320) pos2++;
29135
29135
  }
29136
29136
  }
29137
29137
  return length;
@@ -32900,13 +32900,28 @@ var API_KEY_RE = new RegExp([
32900
32900
  "whsec_[A-Za-z0-9]{16,}\\b"
32901
32901
  // webhook signing secret (Stripe-style)
32902
32902
  ].map((p) => `\\b${p}`).join("|"), "g");
32903
- var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
32903
+ var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
32904
+ var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
32905
+ var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
32906
+ var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
32904
32907
  var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
32905
- var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
32906
- var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
32907
- var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
32908
+ var JSON_SECRET_KEYS = [
32909
+ "(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
32910
+ "client[_-]?secret",
32911
+ "api[_-]?secret",
32912
+ "(?:x[_-])?api[_-]?key",
32913
+ "private[_-]?key",
32914
+ "x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
32915
+ "(?:proxy-)?authorization",
32916
+ "password",
32917
+ "passwd",
32918
+ "secret",
32919
+ "token"
32920
+ ].join("|");
32921
+ var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
32922
+ var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
32908
32923
  function redactSecrets(text) {
32909
- return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
32924
+ return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
32910
32925
  }
32911
32926
 
32912
32927
  // ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
@@ -33001,26 +33016,105 @@ function errorResult(message) {
33001
33016
  }
33002
33017
 
33003
33018
  // ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
33019
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
33004
33020
  var DEFAULT_REQUEST_KEY = "confirmation";
33005
33021
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
33006
33022
  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";
33023
+ var STATE_PREFIX = "mcpu.confirm.v1.";
33024
+ function canonicalJson(value) {
33025
+ if (value === null || typeof value === "string" || typeof value === "boolean")
33026
+ return JSON.stringify(value);
33027
+ if (value === void 0)
33028
+ return "null";
33029
+ if (typeof value === "number")
33030
+ return Number.isFinite(value) ? JSON.stringify(value) : `{"$num":"${String(value)}"}`;
33031
+ if (typeof value === "bigint")
33032
+ return `{"$bigint":"${value.toString()}"}`;
33033
+ if (typeof value === "function" || typeof value === "symbol") {
33034
+ throw new TypeError(`requireConfirmation: cannot bind a ${typeof value} argument value.`);
33035
+ }
33036
+ if (Array.isArray(value))
33037
+ return `[${value.map(canonicalJson).join(",")}]`;
33038
+ if (value instanceof Date) {
33039
+ return `{"$date":${JSON.stringify(Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString())}}`;
33040
+ }
33041
+ if (ArrayBuffer.isView(value)) {
33042
+ const bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
33043
+ return `{"$bytes":${JSON.stringify(bytes.toString("base64"))}}`;
33044
+ }
33045
+ if (value instanceof ArrayBuffer)
33046
+ return `{"$bytes":${JSON.stringify(Buffer.from(value).toString("base64"))}}`;
33047
+ if (value instanceof Map) {
33048
+ const entries2 = [...value.entries()].map(([k, v]) => `[${canonicalJson(k)},${canonicalJson(v)}]`).sort();
33049
+ return `{"$map":[${entries2.join(",")}]}`;
33050
+ }
33051
+ if (value instanceof Set) {
33052
+ return `{"$set":[${[...value].map(canonicalJson).sort().join(",")}]}`;
33053
+ }
33054
+ const proto = Object.getPrototypeOf(value);
33055
+ if (proto !== Object.prototype && proto !== null) {
33056
+ throw new TypeError("requireConfirmation: cannot bind a non-plain object argument value (class instance).");
33057
+ }
33058
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
33059
+ return `{${entries.map(([k, v]) => `${JSON.stringify(escapeKey(k))}:${canonicalJson(v)}`).join(",")}}`;
33060
+ }
33061
+ function escapeKey(key) {
33062
+ return key.startsWith("$") ? `$${key}` : key;
33063
+ }
33064
+ function bindingKey(binding) {
33065
+ if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
33066
+ throw new RangeError("requireConfirmation: binding.ttlSeconds must be a finite number greater than 0.");
33067
+ }
33068
+ const key = typeof binding.key === "string" ? Buffer.from(binding.key, "utf8") : Buffer.from(binding.key);
33069
+ if (key.length < 32) {
33070
+ throw new RangeError("requireConfirmation: binding.key must be at least 32 bytes.");
33071
+ }
33072
+ return key;
33073
+ }
33074
+ function commitment(action, args) {
33075
+ return createHash("sha256").update(`${action}\0${canonicalJson(args)}`).digest("base64url");
33076
+ }
33077
+ function mintState(key, action, binding) {
33078
+ const exp = Math.floor(Date.now() / 1e3) + (binding.ttlSeconds ?? 600);
33079
+ const body = Buffer.from(JSON.stringify({ c: commitment(action, binding.args), exp })).toString("base64url");
33080
+ const mac3 = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest("base64url");
33081
+ return `${STATE_PREFIX}${body}.${mac3}`;
33082
+ }
33083
+ function verifyState(key, action, binding, state) {
33084
+ if (typeof state !== "string" || !state.startsWith(STATE_PREFIX))
33085
+ return false;
33086
+ const rest = state.slice(STATE_PREFIX.length);
33087
+ const dot = rest.indexOf(".");
33088
+ if (dot < 0)
33089
+ return false;
33090
+ const body = rest.slice(0, dot);
33091
+ const mac3 = Buffer.from(rest.slice(dot + 1), "base64url");
33092
+ const expected = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest();
33093
+ if (mac3.length !== expected.length || !timingSafeEqual(mac3, expected))
33094
+ return false;
33095
+ let payload;
33096
+ try {
33097
+ payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
33098
+ } catch {
33099
+ return false;
33100
+ }
33101
+ if (typeof payload.exp !== "number" || payload.exp * 1e3 <= Date.now())
33102
+ return false;
33103
+ const want = Buffer.from(commitment(action, binding.args));
33104
+ const got = Buffer.from(typeof payload.c === "string" ? payload.c : "");
33105
+ return got.length === want.length && timingSafeEqual(got, want);
33106
+ }
33107
+ function echoedState(ctx) {
33108
+ const accessor = ctx.mcpReq.requestState;
33109
+ return typeof accessor === "function" ? accessor() : void 0;
33110
+ }
33007
33111
  function requireConfirmation(ctx, options) {
33008
33112
  const requestKey = options.requestKey ?? DEFAULT_REQUEST_KEY;
33113
+ const key = options.binding ? bindingKey(options.binding) : void 0;
33009
33114
  const confirmationSchema = external_exports.object({
33010
33115
  confirmed: external_exports.boolean().describe(options.confirmationLabel ?? DEFAULT_CONFIRMATION_LABEL)
33011
33116
  });
33012
- const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
33013
- const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
33014
- if (response.kind === "missing") {
33015
- if (callerAcceptsFormElicitation(ctx) === false) {
33016
- return textResult({
33017
- confirmed: false,
33018
- dispatched: false,
33019
- action: options.action,
33020
- reason: "confirmation-unsupported",
33021
- note: options.unsupportedNote ? `${UNSUPPORTED_NOTE}. ${options.unsupportedNote}` : `${UNSUPPORTED_NOTE}.`
33022
- });
33023
- }
33117
+ const ask = () => {
33024
33118
  const preview = {
33025
33119
  action: options.action,
33026
33120
  ...options.details === void 0 ? {} : { details: options.details }
@@ -33032,10 +33126,33 @@ function requireConfirmation(ctx, options) {
33032
33126
  ${JSON.stringify(preview, null, 2)}`,
33033
33127
  requestedSchema: confirmationSchema
33034
33128
  })
33035
- }
33129
+ },
33130
+ ...key && options.binding ? { requestState: mintState(key, options.action, options.binding) } : {}
33036
33131
  });
33132
+ };
33133
+ const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
33134
+ const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
33135
+ if (response.kind === "missing") {
33136
+ if (callerAcceptsFormElicitation(ctx) === false) {
33137
+ return textResult({
33138
+ confirmed: false,
33139
+ dispatched: false,
33140
+ action: options.action,
33141
+ reason: "confirmation-unsupported",
33142
+ note: options.unsupportedNote ? `${UNSUPPORTED_NOTE}. ${options.unsupportedNote}` : `${UNSUPPORTED_NOTE}.`
33143
+ });
33144
+ }
33145
+ return ask();
33037
33146
  }
33038
33147
  if (response.kind === "elicit" && response.action === "accept" && accepted?.confirmed === true) {
33148
+ if (key && options.binding) {
33149
+ const state = echoedState(ctx);
33150
+ if (state === void 0 || state === null) {
33151
+ return errorResult(`Confirmation for ${options.action} was accepted, but the retry carried no requestState, so it cannot be checked against the prompt that was shown. Nothing was done. The likely cause is that the MCP client or host does not round-trip requestState (it must echo the requestState from the input_required result back on the retry); asking again would loop.`);
33152
+ }
33153
+ if (!verifyState(key, options.action, options.binding, state))
33154
+ return ask();
33155
+ }
33039
33156
  return void 0;
33040
33157
  }
33041
33158
  return textResult({
@@ -33053,6 +33170,8 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
33053
33170
  ]);
33054
33171
 
33055
33172
  // ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
33173
+ import { homedir } from "node:os";
33174
+ import { isAbsolute, join, resolve } from "node:path";
33056
33175
  var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
33057
33176
  function readEnvVar(key, opts = {}) {
33058
33177
  const env = opts.env ?? process.env;
@@ -33079,6 +33198,50 @@ function parseBoolEnv(key, opts = {}) {
33079
33198
  return false;
33080
33199
  return fallback;
33081
33200
  }
33201
+ function expandPath(p) {
33202
+ let expanded = p;
33203
+ if (p === "~") {
33204
+ expanded = homedir();
33205
+ } else if (p.startsWith("~/")) {
33206
+ expanded = join(homedir(), p.slice(2));
33207
+ }
33208
+ return isAbsolute(expanded) ? expanded : resolve(expanded);
33209
+ }
33210
+
33211
+ // ../../node_modules/@chrischall/mcp-utils/dist/fs/confine.js
33212
+ import { existsSync, realpathSync } from "node:fs";
33213
+ import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
33214
+ function isInside(root, target) {
33215
+ const rel = relative(root, target);
33216
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute2(rel);
33217
+ }
33218
+ function realPathAllowingMissing(p) {
33219
+ const abs = resolve2(p);
33220
+ let existing = abs;
33221
+ const tail = [];
33222
+ while (!existsSync(existing)) {
33223
+ const parent = dirname(existing);
33224
+ if (parent === existing)
33225
+ break;
33226
+ tail.unshift(existing.slice(parent.length).replace(/^[/\\]+/, ""));
33227
+ existing = parent;
33228
+ }
33229
+ let real;
33230
+ try {
33231
+ real = realpathSync(existing);
33232
+ } catch {
33233
+ real = existing;
33234
+ }
33235
+ return tail.length > 0 ? resolve2(real, ...tail) : real;
33236
+ }
33237
+ function assertPathWithinRoots(path, roots) {
33238
+ const target = realPathAllowingMissing(expandPath(path));
33239
+ for (const root of roots) {
33240
+ if (isInside(realPathAllowingMissing(expandPath(root)), target))
33241
+ return target;
33242
+ }
33243
+ throw new Error(`Path is outside the allowed directories: ${path}`);
33244
+ }
33082
33245
 
33083
33246
  // ../../node_modules/@chrischall/mcp-utils/dist/zod/index.js
33084
33247
  var PositiveInt = external_exports.number().int().positive();
@@ -33100,7 +33263,7 @@ var pageSchema = {
33100
33263
  };
33101
33264
 
33102
33265
  // src/runner.ts
33103
- import { delimiter, join } from "node:path";
33266
+ import { delimiter, join as join2 } from "node:path";
33104
33267
 
33105
33268
  // src/timestamps.ts
33106
33269
  var DEFAULT_DISPLAY_TZ = "America/New_York";
@@ -33301,12 +33464,51 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
33301
33464
  return JSON.stringify(parsed, null, detectIndent(text));
33302
33465
  }
33303
33466
 
33467
+ // src/arg-guard.ts
33468
+ var FORBIDDEN_LONG_FLAG = /^--(readonly|enable-commands|enable-commands-exact|disable-commands|access-token|home|account|acct|client|gmail-no-send|no-input|non-interactive|noninteractive)(=|$)/i;
33469
+ var SHORT_ACCOUNT_CLUSTER = /^-(?!-)[A-Za-z]*a/;
33470
+ function forbiddenArgReason(arg) {
33471
+ if (arg === "--") {
33472
+ return 'The argument "--" is not allowed: it ends flag parsing and would disable safety flags appended after it.';
33473
+ }
33474
+ const match = FORBIDDEN_LONG_FLAG.exec(arg);
33475
+ if (match) {
33476
+ return `The flag ${arg} is not allowed here: --${match[1].toLowerCase()} is a safety/credential control set by the server operator, not a per-call option.`;
33477
+ }
33478
+ if (SHORT_ACCOUNT_CLUSTER.test(arg)) {
33479
+ return `The flag ${arg} is not allowed here: -a selects the account. Use the tool's account parameter instead.`;
33480
+ }
33481
+ return void 0;
33482
+ }
33483
+ function assertSafeForwardedArgs(args) {
33484
+ for (const arg of args) {
33485
+ const reason = forbiddenArgReason(arg);
33486
+ if (reason) throw new Error(reason);
33487
+ }
33488
+ }
33489
+ var SUBCOMMAND_SHAPE = /^[a-z][a-z0-9-]*$/;
33490
+ function assertSafeSubcommand(subcommand) {
33491
+ if (!SUBCOMMAND_SHAPE.test(subcommand)) {
33492
+ throw new Error(
33493
+ `Invalid subcommand ${JSON.stringify(subcommand)}: pass a single gog subcommand name such as "list" or "mark-read", and put its arguments in args.`
33494
+ );
33495
+ }
33496
+ }
33497
+
33498
+ // src/argv.ts
33499
+ function pos(value) {
33500
+ return { kind: "positional", value };
33501
+ }
33502
+ function isGogPositional(arg) {
33503
+ return typeof arg === "object" && arg !== null && arg.kind === "positional";
33504
+ }
33505
+
33304
33506
  // src/runner.ts
33305
33507
  function isGogFileArg(arg) {
33306
- return typeof arg !== "string";
33508
+ return typeof arg !== "string" && arg.kind === "file";
33307
33509
  }
33308
33510
  var TIMEOUT_MS = 3e4;
33309
- var MIN_GOG_VERSION = "0.40.0";
33511
+ var MIN_GOG_VERSION = "0.41.0";
33310
33512
  function readonlyEnvEnabled() {
33311
33513
  return readEnvVar("GOG_READONLY") !== void 0 && parseBoolEnv("GOG_READONLY", { default: true });
33312
33514
  }
@@ -33388,8 +33590,8 @@ function formatTimeout(ms) {
33388
33590
  }
33389
33591
  async function spawnWithTempFiles(args, opts) {
33390
33592
  const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
33391
- const { tmpdir } = await import("node:os");
33392
- const dir = await mkdtemp(join(tmpdir(), "gogcli-mcp-"));
33593
+ const { tmpdir: tmpdir2 } = await import("node:os");
33594
+ const dir = await mkdtemp(join2(tmpdir2(), "gogcli-mcp-"));
33393
33595
  try {
33394
33596
  const argv = [];
33395
33597
  let seq = 0;
@@ -33398,10 +33600,10 @@ async function spawnWithTempFiles(args, opts) {
33398
33600
  argv.push(arg);
33399
33601
  continue;
33400
33602
  }
33401
- const sub = join(dir, String(seq));
33603
+ const sub = join2(dir, String(seq));
33402
33604
  seq += 1;
33403
33605
  await mkdir(sub, { recursive: true, mode: 448 });
33404
- const path = join(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
33606
+ const path = join2(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
33405
33607
  const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
33406
33608
  await writeFile(path, data, { mode: 384 });
33407
33609
  argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
@@ -33419,10 +33621,10 @@ function spawnExecutor(args, opts) {
33419
33621
  return spawnGog(args, opts);
33420
33622
  }
33421
33623
  async function spawnGog(fullArgs, opts) {
33422
- const { timeout, interactive = false, spawner, binary = false } = opts;
33624
+ const { timeout, interactive = false, spawner, binary = false, maxOutputBytes } = opts;
33423
33625
  const spawn = spawner ?? (await import("node:child_process")).spawn;
33424
33626
  const effectiveTimeout = timeout ?? TIMEOUT_MS;
33425
- return new Promise((resolve, reject) => {
33627
+ return new Promise((resolve4, reject) => {
33426
33628
  const childEnv = { ...sanitizedEnv(), GOG_TIMEZONE: naiveSourceTimeZone(), PATH: augmentedPath() };
33427
33629
  const child = spawn(readEnvVar("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
33428
33630
  const stdoutChunks = [];
@@ -33440,7 +33642,17 @@ async function spawnGog(fullArgs, opts) {
33440
33642
  child.kill();
33441
33643
  reject(new Error(`gog timed out after ${formatTimeout(effectiveTimeout)}`));
33442
33644
  }, effectiveTimeout);
33645
+ let stdoutBytes = 0;
33443
33646
  child.stdout.on("data", (chunk) => {
33647
+ if (settled) return;
33648
+ stdoutBytes += chunk.length;
33649
+ if (maxOutputBytes !== void 0 && stdoutBytes > maxOutputBytes) {
33650
+ settled = true;
33651
+ stopWatching();
33652
+ child.kill();
33653
+ reject(new Error(`gog output exceeded the limit: more than ${maxOutputBytes} bytes`));
33654
+ return;
33655
+ }
33444
33656
  stdoutChunks.push(chunk);
33445
33657
  });
33446
33658
  child.stderr.on("data", (chunk) => {
@@ -33457,14 +33669,14 @@ async function spawnGog(fullArgs, opts) {
33457
33669
  const stderr = Buffer.concat(stderrChunks).toString().trim();
33458
33670
  if (code === 0) {
33459
33671
  if (binary) {
33460
- resolve(Buffer.concat(stdoutChunks).toString("base64"));
33672
+ resolve4(Buffer.concat(stdoutChunks).toString("base64"));
33461
33673
  return;
33462
33674
  }
33463
33675
  const stdout = Buffer.concat(stdoutChunks).toString();
33464
33676
  if (interactive && stderr) {
33465
- resolve(stdout + "\n" + stderr);
33677
+ resolve4(stdout + "\n" + stderr);
33466
33678
  } else {
33467
- resolve(stdout);
33679
+ resolve4(stdout);
33468
33680
  }
33469
33681
  } else {
33470
33682
  reject(new Error(stderr || `gog exited with code ${code}`));
@@ -33484,7 +33696,33 @@ async function spawnGog(fullArgs, opts) {
33484
33696
  });
33485
33697
  });
33486
33698
  }
33699
+ function placePositionals(args) {
33700
+ const flags = [];
33701
+ const positionals = [];
33702
+ let afterSeparator = false;
33703
+ for (const arg of args) {
33704
+ if (arg === "--") {
33705
+ afterSeparator = true;
33706
+ } else if (isGogPositional(arg)) {
33707
+ positionals.push(arg.value);
33708
+ } else if (afterSeparator || isGogFileArg(arg) && arg.positional) {
33709
+ positionals.push(arg);
33710
+ } else {
33711
+ flags.push(arg);
33712
+ }
33713
+ }
33714
+ return { flags, positionals };
33715
+ }
33716
+ function assertNoSafetyOverrides(flags) {
33717
+ for (const arg of flags) {
33718
+ if (typeof arg !== "string") continue;
33719
+ const reason = forbiddenArgReason(arg);
33720
+ if (reason) throw new Error(reason);
33721
+ }
33722
+ }
33487
33723
  function assembleArgs(args, opts) {
33724
+ const { flags, positionals } = placePositionals(args);
33725
+ assertNoSafetyOverrides(flags);
33488
33726
  const effectiveAccount = opts.account ?? readEnvVar("GOG_ACCOUNT");
33489
33727
  const fullArgs = ["--json", "--color=never"];
33490
33728
  if (!opts.interactive) {
@@ -33493,17 +33731,21 @@ function assembleArgs(args, opts) {
33493
33731
  if (opts.readonly || readonlyEnvEnabled()) {
33494
33732
  fullArgs.push("--readonly");
33495
33733
  }
33734
+ if (opts.gmailNoSend) {
33735
+ fullArgs.push("--gmail-no-send");
33736
+ }
33496
33737
  if (effectiveAccount) {
33497
33738
  fullArgs.push("--account", effectiveAccount);
33498
33739
  }
33499
- fullArgs.push(...args);
33740
+ fullArgs.push(...flags);
33741
+ if (positionals.length > 0) fullArgs.push("--", ...positionals);
33500
33742
  return fullArgs;
33501
33743
  }
33502
33744
  async function run(args, options = {}) {
33503
- const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
33745
+ const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields, gmailNoSend = false } = options;
33504
33746
  const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
33505
33747
  const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
33506
- const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
33748
+ const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2, gmailNoSend });
33507
33749
  try {
33508
33750
  return redact(await spawnExecutor(fullArgs, { timeout, interactive, spawner }));
33509
33751
  } catch (err) {
@@ -33511,9 +33753,9 @@ async function run(args, options = {}) {
33511
33753
  }
33512
33754
  }
33513
33755
  async function runBinary(args, options = {}) {
33514
- const { account, spawner, timeout, readonly: readonly2 = false } = options;
33515
- const fullArgs = assembleArgs(args, { account, interactive: false, readonly: readonly2 });
33516
- return spawnExecutor(fullArgs, { timeout, interactive: false, spawner, binary: true });
33756
+ const { account, spawner, timeout, readonly: readonly2 = false, gmailNoSend = false, maxOutputBytes } = options;
33757
+ const fullArgs = assembleArgs(args, { account, interactive: false, readonly: readonly2, gmailNoSend });
33758
+ return spawnExecutor(fullArgs, { timeout, interactive: false, spawner, binary: true, maxOutputBytes });
33517
33759
  }
33518
33760
 
33519
33761
  // src/pagination.ts
@@ -33565,6 +33807,175 @@ function annotateTruncatedList(result, itemsKey) {
33565
33807
  return rawTextResult(JSON.stringify(out));
33566
33808
  }
33567
33809
 
33810
+ // src/file-roots.ts
33811
+ import { homedir as homedir2 } from "node:os";
33812
+ import { delimiter as delimiter2, join as join4 } from "node:path";
33813
+
33814
+ // src/attachment-root.ts
33815
+ import { tmpdir, userInfo } from "node:os";
33816
+ import { dirname as dirname2, join as join3, relative as relative2, resolve as resolve3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
33817
+ var ATTACHMENT_DOWNLOAD_ROOT = join3(tmpdir(), `gogcli-mcp-attachments-${userInfo().uid}`);
33818
+ var ATTACHMENT_TTL_MS = 24 * 60 * 60 * 1e3;
33819
+ function isInside2(root, target) {
33820
+ const rel = relative2(root, target);
33821
+ return rel !== "" && !rel.startsWith(`..${sep2}`) && rel !== ".." && !isAbsolute3(rel);
33822
+ }
33823
+ async function ensurePrivateDir(dir, opts = {}) {
33824
+ const { mkdir, lstat, chmod } = await import("node:fs/promises");
33825
+ try {
33826
+ await mkdir(dir, { mode: 448 });
33827
+ } catch (err) {
33828
+ if (err.code !== "EEXIST") throw err;
33829
+ }
33830
+ const info = await lstat(dir);
33831
+ if (info.isSymbolicLink() || !info.isDirectory()) {
33832
+ throw new Error(`refusing to use ${dir} for attachment downloads: it is not a real directory`);
33833
+ }
33834
+ const uid = opts.uid ?? userInfo().uid;
33835
+ if (info.uid !== uid) {
33836
+ throw new Error(`refusing to use ${dir} for attachment downloads: it is owned by another user`);
33837
+ }
33838
+ if ((info.mode & 63) !== 0) await chmod(dir, 448);
33839
+ }
33840
+ async function sweepExpiredDownloads(root = ATTACHMENT_DOWNLOAD_ROOT, ttlMs = ATTACHMENT_TTL_MS, now = Date.now()) {
33841
+ const { readdir, lstat, rm } = await import("node:fs/promises");
33842
+ let entries;
33843
+ try {
33844
+ entries = await readdir(root);
33845
+ } catch {
33846
+ return 0;
33847
+ }
33848
+ let removed = 0;
33849
+ for (const name of entries) {
33850
+ const path = join3(root, name);
33851
+ try {
33852
+ const info = await lstat(path);
33853
+ if (now - info.mtimeMs > ttlMs) {
33854
+ await rm(path, { recursive: true, force: true });
33855
+ removed += 1;
33856
+ }
33857
+ } catch {
33858
+ }
33859
+ }
33860
+ return removed;
33861
+ }
33862
+ async function removeDownload(path, root = ATTACHMENT_DOWNLOAD_ROOT) {
33863
+ const { rm, rmdir } = await import("node:fs/promises");
33864
+ const base = resolve3(root);
33865
+ const target = resolve3(base, path);
33866
+ if (!isInside2(base, target)) return;
33867
+ await rm(target, { force: true });
33868
+ for (let dir = dirname2(target); isInside2(base, dir); dir = dirname2(dir)) {
33869
+ try {
33870
+ await rmdir(dir);
33871
+ } catch {
33872
+ break;
33873
+ }
33874
+ }
33875
+ }
33876
+ async function prepareDownloadRoot(root = ATTACHMENT_DOWNLOAD_ROOT) {
33877
+ await ensurePrivateDir(root);
33878
+ await sweepExpiredDownloads(root);
33879
+ }
33880
+
33881
+ // src/file-roots.ts
33882
+ var FILE_ROOTS_ENV = "GOG_FILE_ROOTS";
33883
+ function defaultFileRoot() {
33884
+ return join4(homedir2(), "gogcli-mcp-files");
33885
+ }
33886
+ function fileRoots() {
33887
+ const raw = readEnvVar(FILE_ROOTS_ENV);
33888
+ const roots = (raw ?? "").split(delimiter2).map((r) => r.trim()).filter(Boolean);
33889
+ return roots.length > 0 ? roots : [defaultFileRoot()];
33890
+ }
33891
+ function confinePath(path, param, opts = {}) {
33892
+ if (opts.allowDash && path === "-") return path;
33893
+ const roots = [...fileRoots(), ATTACHMENT_DOWNLOAD_ROOT];
33894
+ try {
33895
+ assertPathWithinRoots(path, roots);
33896
+ } catch {
33897
+ throw new Error(
33898
+ `${param} ${JSON.stringify(path)} is outside the directories this server may read or write (${roots.join(", ")}). Put the file in one of them, or ask the server operator to widen ${FILE_ROOTS_ENV}.`
33899
+ );
33900
+ }
33901
+ return path;
33902
+ }
33903
+ function confinePaths(paths, param) {
33904
+ for (const path of paths ?? []) confinePath(path, param);
33905
+ }
33906
+ function confineAtFile(value, param) {
33907
+ if (value.startsWith("@")) confinePath(value.slice(1), param);
33908
+ return value;
33909
+ }
33910
+
33911
+ // src/run-path-guard.ts
33912
+ var POSITIONAL_PATH_SUBCOMMANDS = {
33913
+ appscript: { pull: "gog_appscript_pull" },
33914
+ drive: { upload: "gog_drive_upload", sync: "gog_drive_sync_push" },
33915
+ gmail: { import: "gog_gmail_import" },
33916
+ slides: {
33917
+ "add-slide": "gog_slides_add_slide",
33918
+ "insert-image": "gog_slides_insert_image",
33919
+ "replace-slide": "gog_slides_replace_slide"
33920
+ }
33921
+ };
33922
+ var PATH_FLAGS = /* @__PURE__ */ new Set(["out", "output", "out-dir", "output-dir", "dir", "worker-dir", "attach", "file", "key", "cert", "replacements"]);
33923
+ var ID_FLAGS = {
33924
+ drive: /* @__PURE__ */ new Set(["file", "filter-file"])
33925
+ };
33926
+ var EXEC_FLAGS = /* @__PURE__ */ new Set(["on-change", "on-new", "mmdc"]);
33927
+ var SHORT_PATH_CLUSTER = /^-(?!-)[A-Za-z]*[of]/;
33928
+ var LONG_FLAG = /^--([A-Za-z0-9][A-Za-z0-9-]*)(?:=([\s\S]*))?$/;
33929
+ function isPathFlag(service, name) {
33930
+ if (ID_FLAGS[service]?.has(name)) return false;
33931
+ return PATH_FLAGS.has(name) || name.endsWith("-file");
33932
+ }
33933
+ function confineFlagValue(flag, value) {
33934
+ if (value === "-") return;
33935
+ confinePath(value, flag);
33936
+ for (const part of value.split(",")) {
33937
+ if (part !== "" && part !== "-") confinePath(part, flag);
33938
+ }
33939
+ }
33940
+ function positionalPathTool(service, subcommand) {
33941
+ return POSITIONAL_PATH_SUBCOMMANDS[service]?.[subcommand];
33942
+ }
33943
+ function assertRunPathsConfined(service, subcommand, args) {
33944
+ const dedicated = positionalPathTool(service, subcommand);
33945
+ if (dedicated) {
33946
+ throw new Error(
33947
+ `gog ${service} ${subcommand} reads or writes a local path given as a positional argument, so it is not available through gog_${service}_run. Use ${dedicated}, which confines the path to GOG_FILE_ROOTS.`
33948
+ );
33949
+ }
33950
+ for (let i = 0; i < args.length; i++) {
33951
+ const arg = args[i];
33952
+ if (SHORT_PATH_CLUSTER.test(arg)) {
33953
+ throw new Error(
33954
+ `The flag ${arg} is not allowed here: spell a path flag out as --out or --file (e.g. --out=<path>) so its path can be checked against GOG_FILE_ROOTS.`
33955
+ );
33956
+ }
33957
+ const match = LONG_FLAG.exec(arg);
33958
+ if (!match) continue;
33959
+ const name = match[1].toLowerCase();
33960
+ if (EXEC_FLAGS.has(name)) {
33961
+ throw new Error(`The flag --${name} is not allowed here: it runs a local program chosen by the caller.`);
33962
+ }
33963
+ const isJson = name.endsWith("-json");
33964
+ if (!isJson && !isPathFlag(service, name)) continue;
33965
+ let value = match[2];
33966
+ if (value === void 0) {
33967
+ value = args[i + 1];
33968
+ if (value === void 0) continue;
33969
+ i++;
33970
+ }
33971
+ if (isJson) {
33972
+ if (value.startsWith("@")) confineFlagValue(`--${name}`, value.slice(1));
33973
+ continue;
33974
+ }
33975
+ confineFlagValue(`--${name}`, value);
33976
+ }
33977
+ }
33978
+
33568
33979
  // src/tools/utils.ts
33569
33980
  var PAYLOAD_INLINE_MAX = 4096;
33570
33981
  function payloadArg(inlineFlag, fileFlag, value, ext) {
@@ -33622,9 +34033,17 @@ function pushPaginationFlags(args, p) {
33622
34033
  if (p.all) args.push("--all");
33623
34034
  }
33624
34035
  function registerRunTool(server, options) {
33625
- const { service, examples, omitAccount = false, note } = options;
34036
+ const { service, examples, omitAccount = false, note, allowedSubcommands, vet, gmailNoSend = false } = options;
34037
+ for (const [, example] of examples.matchAll(/"([^"]+)"/g)) {
34038
+ const refused = allowedSubcommands && !allowedSubcommands.includes(example) || vet?.(example, []) || positionalPathTool(service, example);
34039
+ if (refused) {
34040
+ throw new Error(`gog_${service}_run example "${example}" is refused by the tool itself; list a subcommand it runs.`);
34041
+ }
34042
+ }
33626
34043
  const baseDescription = `Run any gog ${service} subcommand not covered by the other tools. Run \`gog ${service} --help\` for the full list of subcommands, or \`gog ${service} <subcommand> --help\` for flags on a specific subcommand.`;
33627
- const description = note ? `${baseDescription} ${note}` : baseDescription;
34044
+ const restriction = allowedSubcommands ? ` Only these subcommands are available: ${allowedSubcommands.join(", ")}.` : "";
34045
+ const safety = ' Flags that override server safety controls (--readonly, --enable-commands, --disable-commands, --account, --access-token, --home, --client, --gmail-no-send, --no-input) and a bare "--" are refused. Local file paths (--out, --out-dir, --attach, --file, --*-file, @file JSON) must be inside GOG_FILE_ROOTS; subcommands that take a local path positionally (e.g. drive upload) are refused in favour of their dedicated tools.';
34046
+ const description = `${baseDescription}${restriction}${safety}${note ? ` ${note}` : ""}`;
33628
34047
  const inputSchema = {
33629
34048
  subcommand: external_exports.string().describe(`The gog ${service} subcommand to run, e.g. ${examples}`),
33630
34049
  args: external_exports.array(external_exports.string()).describe("Additional positional args and flags")
@@ -33638,7 +34057,21 @@ function registerRunTool(server, options) {
33638
34057
  inputSchema: external_exports.object(inputSchema)
33639
34058
  }, async (rawArgs) => {
33640
34059
  const { subcommand, args, account } = rawArgs;
33641
- return runOrDiagnose([service, subcommand, ...args], { account });
34060
+ try {
34061
+ assertSafeSubcommand(subcommand);
34062
+ assertSafeForwardedArgs(args);
34063
+ if (allowedSubcommands && !allowedSubcommands.includes(subcommand)) {
34064
+ throw new Error(
34065
+ `gog ${service} ${subcommand} is not available through gog_${service}_run. Allowed: ${allowedSubcommands.join(", ")}.`
34066
+ );
34067
+ }
34068
+ const refusal = vet?.(subcommand, args);
34069
+ if (refusal) throw new Error(refusal);
34070
+ assertRunPathsConfined(service, subcommand, args);
34071
+ } catch (err) {
34072
+ return errorResult(errorText(err));
34073
+ }
34074
+ return runOrDiagnose([service, subcommand, ...args], gmailNoSend ? { account, gmailNoSend } : { account });
33642
34075
  });
33643
34076
  }
33644
34077
  function errorText(err) {
@@ -33767,6 +34200,13 @@ function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
33767
34200
  }
33768
34201
 
33769
34202
  // src/tools/api.ts
34203
+ var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
34204
+ function refusedApiCall(api, method) {
34205
+ if (api.trim().toLowerCase() === "gmail" && GMAIL_API_BLOCKED.test(method.trim())) {
34206
+ return `gmail ${method} is not available through gog_api_call: it sends or forwards mail. Use gog_gmail_send / gog_gmail_drafts_send (which ask the user to confirm) or the dedicated gog_gmail_* tool.`;
34207
+ }
34208
+ return void 0;
34209
+ }
33770
34210
  function registerApiTools(server) {
33771
34211
  server.registerTool("gog_api_list", {
33772
34212
  description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
@@ -33790,12 +34230,12 @@ function registerApiTools(server) {
33790
34230
  account: accountParam
33791
34231
  })
33792
34232
  }, async ({ api, version: version2, method, account }) => {
33793
- const args = ["api", "describe", api, version2];
33794
- if (method) args.push(method);
34233
+ const args = ["api", "describe", pos(api), pos(version2)];
34234
+ if (method) args.push(pos(method));
33795
34235
  return runOrDiagnose(args, { account });
33796
34236
  });
33797
34237
  server.registerTool("gog_api_call", {
33798
- 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.",
34238
+ 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.",
33799
34239
  annotations: { destructiveHint: true },
33800
34240
  inputSchema: external_exports.object({
33801
34241
  api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
@@ -33809,14 +34249,23 @@ function registerApiTools(server) {
33809
34249
  account: accountParam
33810
34250
  })
33811
34251
  }, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
33812
- const args = ["api", "call", api, version2, method];
34252
+ try {
34253
+ assertSafeForwardedArgs([api, version2, method]);
34254
+ const refusal = refusedApiCall(api, method);
34255
+ if (refusal) throw new Error(refusal);
34256
+ if (body) confineAtFile(body, "body");
34257
+ if (params) confineAtFile(params, "params");
34258
+ } catch (err) {
34259
+ return errorResult(errorText(err));
34260
+ }
34261
+ const args = ["api", "call", pos(api), pos(version2), pos(method)];
33813
34262
  if (params) args.push(`--params=${params}`);
33814
34263
  if (body) args.push(`--body=${body}`);
33815
34264
  if (scope) args.push(`--scope=${scope}`);
33816
34265
  if (allowWrite) args.push("--allow-write");
33817
34266
  if (dryRun) args.push("--dry-run");
33818
34267
  if (allowWrite) args.push("--force");
33819
- return runOrDiagnose(args, { account });
34268
+ return runOrDiagnose(args, { account, gmailNoSend: true });
33820
34269
  });
33821
34270
  }
33822
34271
 
@@ -33834,7 +34283,7 @@ function registerAppScriptTools(server) {
33834
34283
  account: accountParam
33835
34284
  })
33836
34285
  }, async ({ scriptId, account }) => {
33837
- return runOrDiagnose(["appscript", "get", scriptId], { account });
34286
+ return runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
33838
34287
  });
33839
34288
  server.registerTool("gog_appscript_content", {
33840
34289
  description: `Read a project's source \u2014 every .gs file and its appsscript.json manifest \u2014 INLINE in the response. This is the tool to reach for when the question is "what does this script do"; it needs no filesystem, so it works the same on a hosted deployment as it does locally, unlike gog_appscript_pull.` + apiEnableNote,
@@ -33844,19 +34293,20 @@ function registerAppScriptTools(server) {
33844
34293
  account: accountParam
33845
34294
  })
33846
34295
  }, async ({ scriptId, account }) => {
33847
- return runOrDiagnose(["appscript", "content", scriptId], { account });
34296
+ return runOrDiagnose(["appscript", "content", pos(scriptId)], { account });
33848
34297
  });
33849
34298
  server.registerTool("gog_appscript_pull", {
33850
34299
  description: "Write a project's files into a local directory, for editing a script as ordinary files. THE DIRECTORY IS RESOLVED WHERE GOG RUNS, which is the caller's own machine only on a local (stdio) deployment: on a hosted deployment (e.g. mcp-host) the files land on that server where the caller cannot reach them. Use gog_appscript_content there instead \u2014 it returns the same source in the response. Existing files are left alone unless overwrite is set. Read-only as far as Google is concerned: nothing is pushed back." + apiEnableNote,
33851
34300
  annotations: { destructiveHint: true },
33852
34301
  inputSchema: external_exports.object({
33853
34302
  scriptId: scriptIdParam,
33854
- dir: external_exports.string().describe("Destination directory, resolved on the machine where gog runs"),
34303
+ dir: external_exports.string().describe("Destination directory, resolved on the machine where gog runs; must be inside the server's GOG_FILE_ROOTS directories"),
33855
34304
  overwrite: external_exports.boolean().optional().describe("Overwrite files that already exist in dir"),
33856
34305
  account: accountParam
33857
34306
  })
33858
34307
  }, async ({ scriptId, dir, overwrite, account }) => {
33859
- const args = ["appscript", "pull", scriptId, dir];
34308
+ confinePath(dir, "dir");
34309
+ const args = ["appscript", "pull", pos(scriptId), pos(dir)];
33860
34310
  if (overwrite) args.push("--overwrite");
33861
34311
  return runOrDiagnose(args, { account });
33862
34312
  });
@@ -33882,7 +34332,7 @@ function registerAppScriptTools(server) {
33882
34332
  account: accountParam
33883
34333
  })
33884
34334
  }, async ({ scriptId, max, pageToken, page, all, account }) => {
33885
- const args = ["appscript", "deployments", scriptId];
34335
+ const args = ["appscript", "deployments", pos(scriptId)];
33886
34336
  pushPaginationFlags(args, { max, pageToken, page, all });
33887
34337
  return runOrDiagnose(args, { account });
33888
34338
  });
@@ -33895,7 +34345,7 @@ function registerAppScriptTools(server) {
33895
34345
  account: accountParam
33896
34346
  })
33897
34347
  }, async ({ scriptId, max, pageToken, page, all, account }) => {
33898
- const args = ["appscript", "versions", scriptId];
34348
+ const args = ["appscript", "versions", pos(scriptId)];
33899
34349
  pushPaginationFlags(args, { max, pageToken, page, all });
33900
34350
  return runOrDiagnose(args, { account });
33901
34351
  });
@@ -33921,7 +34371,7 @@ function registerAppScriptTools(server) {
33921
34371
  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}`);
33922
34372
  }
33923
34373
  }
33924
- const args = ["appscript", "run", scriptId, functionName];
34374
+ const args = ["appscript", "run", pos(scriptId), pos(functionName)];
33925
34375
  if (params !== void 0) args.push(`--params=${params}`);
33926
34376
  if (devMode) args.push("--dev-mode");
33927
34377
  return runOrDiagnose(args, { account });
@@ -33934,6 +34384,7 @@ function registerAppScriptTools(server) {
33934
34384
  }
33935
34385
 
33936
34386
  // src/tools/auth.ts
34387
+ var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
33937
34388
  function registerAuthToolsWith(server, defaultServices) {
33938
34389
  const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
33939
34390
  const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
@@ -33991,7 +34442,7 @@ function registerAuthToolsWith(server, defaultServices) {
33991
34442
  })
33992
34443
  }, async ({ email: email3, services = defaultServices, extraScopes }) => {
33993
34444
  try {
33994
- const args = ["auth", "add", email3, "--services", services];
34445
+ const args = ["auth", "add", pos(email3), "--services", services];
33995
34446
  if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
33996
34447
  return rawTextResult(await run(args, {
33997
34448
  interactive: true,
@@ -34011,7 +34462,7 @@ function registerAuthToolsWith(server, defaultServices) {
34011
34462
  })
34012
34463
  }, async ({ email: email3, services = defaultServices, extraScopes }) => {
34013
34464
  try {
34014
- const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
34465
+ const args = ["auth", "add", pos(email3), "--remote", "--step", "1", "--services", services, "--force-consent"];
34015
34466
  if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
34016
34467
  return rawTextResult(await run(args, { redactMode: "tokens" }));
34017
34468
  } catch (err) {
@@ -34038,7 +34489,7 @@ function registerAuthToolsWith(server, defaultServices) {
34038
34489
  const args = [
34039
34490
  "auth",
34040
34491
  "add",
34041
- email3,
34492
+ pos(email3),
34042
34493
  "--remote",
34043
34494
  "--step",
34044
34495
  "2",
@@ -34058,6 +34509,7 @@ function registerAuthToolsWith(server, defaultServices) {
34058
34509
  service: "auth",
34059
34510
  examples: '"remove", "alias", "list"',
34060
34511
  omitAccount: true,
34512
+ allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
34061
34513
  note: "For browser-based authorization, use gog_auth_add instead."
34062
34514
  });
34063
34515
  }
@@ -34122,7 +34574,7 @@ function registerCalendarTools(server) {
34122
34574
  })
34123
34575
  }, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, view, account }) => {
34124
34576
  const args = ["calendar", "events"];
34125
- if (calendarId) args.push(calendarId);
34577
+ if (calendarId) args.push(pos(calendarId));
34126
34578
  if (from) args.push(`--from=${from}`);
34127
34579
  if (to) args.push(`--to=${to}`);
34128
34580
  if (days !== void 0) args.push(`--days=${days}`);
@@ -34151,7 +34603,7 @@ function registerCalendarTools(server) {
34151
34603
  account: accountParam
34152
34604
  })
34153
34605
  }, async ({ calendarId, eventId, timezone, account }) => {
34154
- const args = ["calendar", "event", calendarId, eventId];
34606
+ const args = ["calendar", "event", pos(calendarId), pos(eventId)];
34155
34607
  if (timezone) args.push(`--timezone=${timezone}`);
34156
34608
  return runOrDiagnose(args, { account });
34157
34609
  });
@@ -34173,7 +34625,7 @@ function registerCalendarTools(server) {
34173
34625
  account: accountParam
34174
34626
  })
34175
34627
  }, async ({ calendarId, summary, from, to, description, location, attendees, allDay, timezone, withZoom, reminders, noReminders, account }) => {
34176
- const args = ["calendar", "create", calendarId, `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
34628
+ const args = ["calendar", "create", pos(calendarId), `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
34177
34629
  if (description) args.push(`--description=${description}`);
34178
34630
  if (location) args.push(`--location=${location}`);
34179
34631
  if (attendees) args.push(`--attendees=${attendees}`);
@@ -34205,7 +34657,7 @@ function registerCalendarTools(server) {
34205
34657
  account: accountParam
34206
34658
  })
34207
34659
  }, async ({ calendarId, eventId, summary, from, to, description, location, attendees, addAttendees, attachments, withZoom, regenerateZoom, removeZoom, removeMeet, reminders, noReminders, account }) => {
34208
- const args = ["calendar", "update", calendarId, eventId];
34660
+ const args = ["calendar", "update", pos(calendarId), pos(eventId)];
34209
34661
  if (summary !== void 0) args.push(`--summary=${summary}`);
34210
34662
  if (from !== void 0) args.push(`--from=${from}`);
34211
34663
  if (to !== void 0) args.push(`--to=${to}`);
@@ -34230,7 +34682,7 @@ function registerCalendarTools(server) {
34230
34682
  account: accountParam
34231
34683
  })
34232
34684
  }, async ({ calendarId, eventId, account }) => {
34233
- return runOrDiagnose(["calendar", "delete", calendarId, eventId, "--force"], { account });
34685
+ return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
34234
34686
  });
34235
34687
  server.registerTool("gog_calendar_respond", {
34236
34688
  description: "Respond to a calendar event invitation.",
@@ -34243,7 +34695,7 @@ function registerCalendarTools(server) {
34243
34695
  account: accountParam
34244
34696
  })
34245
34697
  }, async ({ calendarId, eventId, status, comment, account }) => {
34246
- const args = ["calendar", "respond", calendarId, eventId, `--status=${status}`];
34698
+ const args = ["calendar", "respond", pos(calendarId), pos(eventId), `--status=${status}`];
34247
34699
  if (comment) args.push(`--comment=${comment}`);
34248
34700
  return runOrDiagnose(args, { account });
34249
34701
  });
@@ -34258,6 +34710,7 @@ var MAX_REQUEST_PAYLOAD_WIRE_BYTES = REQUEST_MAX_BODY_BYTES - REQUEST_BODY_JSON_
34258
34710
  var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
34259
34711
  function wireBytesOf(arg) {
34260
34712
  if (typeof arg === "string") return Buffer.byteLength(arg, "utf8");
34713
+ if (arg.kind === "positional") return Buffer.byteLength(arg.value, "utf8");
34261
34714
  return arg.encoding === "base64" ? arg.contents.length : Buffer.byteLength(arg.contents, "utf8");
34262
34715
  }
34263
34716
  var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
@@ -34360,7 +34813,7 @@ function registerChatTools(server) {
34360
34813
  account: accountParam
34361
34814
  })
34362
34815
  }, async ({ displayName, exact, max, account }) => {
34363
- const args = ["chat", "spaces", "find", displayName];
34816
+ const args = ["chat", "spaces", "find", pos(displayName)];
34364
34817
  if (exact) args.push("--exact");
34365
34818
  if (max !== void 0) args.push(`--max=${max}`);
34366
34819
  return runOrDiagnose(args, { account });
@@ -34374,7 +34827,7 @@ function registerChatTools(server) {
34374
34827
  account: accountParam
34375
34828
  })
34376
34829
  }, async ({ displayName, members: members2, account }) => {
34377
- const args = ["chat", "spaces", "create", displayName];
34830
+ const args = ["chat", "spaces", "create", pos(displayName)];
34378
34831
  if (members2) for (const member of members2) args.push(`--member=${member}`);
34379
34832
  return runOrDiagnose(args, { account });
34380
34833
  });
@@ -34387,7 +34840,7 @@ function registerChatTools(server) {
34387
34840
  account: accountParam
34388
34841
  })
34389
34842
  }, async ({ space, max, pageToken, page, all, account }) => {
34390
- const args = ["chat", "threads", "list", space];
34843
+ const args = ["chat", "threads", "list", pos(space)];
34391
34844
  pushPaginationFlags(args, { max, pageToken, page, all });
34392
34845
  return runOrDiagnose(args, { account });
34393
34846
  });
@@ -34403,7 +34856,7 @@ function registerChatTools(server) {
34403
34856
  account: accountParam
34404
34857
  })
34405
34858
  }, async ({ space, thread, unread, order, max, pageToken, page, all, account }) => {
34406
- const args = ["chat", "messages", "list", space];
34859
+ const args = ["chat", "messages", "list", pos(space)];
34407
34860
  if (thread) args.push(`--thread=${thread}`);
34408
34861
  if (unread) args.push("--unread");
34409
34862
  if (order) args.push(`--order=${order}`);
@@ -34427,7 +34880,7 @@ function registerChatTools(server) {
34427
34880
  account: accountParam
34428
34881
  })
34429
34882
  }, async ({ query, order, view, markup, max, pageToken, page, all, account }) => {
34430
- const args = ["chat", "messages", "search", query];
34883
+ const args = ["chat", "messages", "search", pos(query)];
34431
34884
  if (order) args.push(`--order=${order}`);
34432
34885
  if (view) args.push(`--view=${view}`);
34433
34886
  if (markup) args.push(`--markup=${markup}`);
@@ -34442,16 +34895,17 @@ function registerChatTools(server) {
34442
34895
  text: external_exports.string().optional().describe("Message text. Optional only when an attachment is supplied."),
34443
34896
  thread: threadParam,
34444
34897
  attach: external_exports.array(external_exports.string()).optional().describe(
34445
- "Attachment file paths, read WHERE GOG RUNS. On a hosted or remote deployment that is not your machine \u2014 use attachInline there instead."
34898
+ "Attachment file paths, read WHERE GOG RUNS. On a hosted or remote deployment that is not your machine \u2014 use attachInline there instead. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files)."
34446
34899
  ),
34447
34900
  attachInline: attachInlineParam,
34448
34901
  account: accountParam
34449
34902
  })
34450
34903
  }, async ({ space, text, thread, attach, attachInline, account }) => {
34904
+ confinePaths(attach, "attach");
34451
34905
  if (text === void 0 && !attach?.length && !attachInline?.length) {
34452
34906
  throw new Error("A Chat message needs text, an attachment, or both.");
34453
34907
  }
34454
- const args = ["chat", "messages", "send", space];
34908
+ const args = ["chat", "messages", "send", pos(space)];
34455
34909
  if (text !== void 0) args.push(`--text=${text}`);
34456
34910
  if (thread) args.push(`--thread=${thread}`);
34457
34911
  if (attach) for (const path of attach) args.push(`--attach=${path}`);
@@ -34468,7 +34922,7 @@ function registerChatTools(server) {
34468
34922
  account: accountParam
34469
34923
  })
34470
34924
  }, async ({ email: email3, text, thread, account }) => {
34471
- const args = ["chat", "dm", "send", email3, `--text=${text}`];
34925
+ const args = ["chat", "dm", "send", pos(email3), `--text=${text}`];
34472
34926
  if (thread) args.push(`--thread=${thread}`);
34473
34927
  return runOrDiagnose(args, { account });
34474
34928
  });
@@ -34480,7 +34934,7 @@ function registerChatTools(server) {
34480
34934
  account: accountParam
34481
34935
  })
34482
34936
  }, async ({ email: email3, account }) => {
34483
- return runOrDiagnose(["chat", "dm", "space", email3], { account });
34937
+ return runOrDiagnose(["chat", "dm", "space", pos(email3)], { account });
34484
34938
  });
34485
34939
  server.registerTool("gog_chat_reactions_list", {
34486
34940
  description: "List the emoji reactions on one message, with who reacted. gog_chat_messages_list already returns a reaction SUMMARY per message; come here when you need the individual reactors, or the reaction resource names that gog_chat_reactions_delete takes." + workspaceOnlyNote,
@@ -34492,7 +34946,7 @@ function registerChatTools(server) {
34492
34946
  account: accountParam
34493
34947
  })
34494
34948
  }, async ({ message, space, max, pageToken, page, all, account }) => {
34495
- const args = ["chat", "messages", "reactions", "list", message];
34949
+ const args = ["chat", "messages", "reactions", "list", pos(message)];
34496
34950
  if (space) args.push(`--space=${space}`);
34497
34951
  pushPaginationFlags(args, { max, pageToken, page, all });
34498
34952
  return runOrDiagnose(args, { account });
@@ -34507,7 +34961,7 @@ function registerChatTools(server) {
34507
34961
  account: accountParam
34508
34962
  })
34509
34963
  }, async ({ message, emoji: emoji3, space, account }) => {
34510
- const args = ["chat", "messages", "reactions", "create", message, emoji3];
34964
+ const args = ["chat", "messages", "reactions", "create", pos(message), pos(emoji3)];
34511
34965
  if (space) args.push(`--space=${space}`);
34512
34966
  return runOrDiagnose(args, { account });
34513
34967
  });
@@ -34519,7 +34973,7 @@ function registerChatTools(server) {
34519
34973
  account: accountParam
34520
34974
  })
34521
34975
  }, async ({ reaction, account }) => {
34522
- return runOrDiagnose(["chat", "messages", "reactions", "delete", reaction], { account });
34976
+ return runOrDiagnose(["chat", "messages", "reactions", "delete", pos(reaction)], { account });
34523
34977
  });
34524
34978
  registerRunTool(server, {
34525
34979
  service: "chat",
@@ -34562,7 +35016,7 @@ function registerClassroomTools(server) {
34562
35016
  account: accountParam
34563
35017
  })
34564
35018
  }, async ({ courseId, account }) => {
34565
- return runOrDiagnose(["classroom", "courses", "get", courseId], { account });
35019
+ return runOrDiagnose(["classroom", "courses", "get", pos(courseId)], { account });
34566
35020
  });
34567
35021
  server.registerTool("gog_classroom_students_list", {
34568
35022
  description: "List students enrolled in a Google Classroom course.",
@@ -34576,7 +35030,7 @@ function registerClassroomTools(server) {
34576
35030
  account: accountParam
34577
35031
  })
34578
35032
  }, async ({ courseId, max, pageToken, page, all, account }) => {
34579
- const args = ["classroom", "students", "list", courseId];
35033
+ const args = ["classroom", "students", "list", pos(courseId)];
34580
35034
  if (max !== void 0) args.push(`--max=${max}`);
34581
35035
  const token = resolvePageToken({ pageToken, page });
34582
35036
  if (token) args.push(`--page=${token}`);
@@ -34592,7 +35046,7 @@ function registerClassroomTools(server) {
34592
35046
  account: accountParam
34593
35047
  })
34594
35048
  }, async ({ courseId, userId, account }) => {
34595
- return runOrDiagnose(["classroom", "students", "get", courseId, userId], { account });
35049
+ return runOrDiagnose(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
34596
35050
  });
34597
35051
  server.registerTool("gog_classroom_teachers_list", {
34598
35052
  description: "List teachers in a Google Classroom course.",
@@ -34606,7 +35060,7 @@ function registerClassroomTools(server) {
34606
35060
  account: accountParam
34607
35061
  })
34608
35062
  }, async ({ courseId, max, pageToken, page, all, account }) => {
34609
- const args = ["classroom", "teachers", "list", courseId];
35063
+ const args = ["classroom", "teachers", "list", pos(courseId)];
34610
35064
  if (max !== void 0) args.push(`--max=${max}`);
34611
35065
  const token = resolvePageToken({ pageToken, page });
34612
35066
  if (token) args.push(`--page=${token}`);
@@ -34622,7 +35076,7 @@ function registerClassroomTools(server) {
34622
35076
  account: accountParam
34623
35077
  })
34624
35078
  }, async ({ courseId, userId, account }) => {
34625
- return runOrDiagnose(["classroom", "teachers", "get", courseId, userId], { account });
35079
+ return runOrDiagnose(["classroom", "teachers", "get", pos(courseId), pos(userId)], { account });
34626
35080
  });
34627
35081
  server.registerTool("gog_classroom_roster", {
34628
35082
  description: "List the full roster (students and/or teachers) of a Google Classroom course. Omit both flags to return both groups.",
@@ -34638,7 +35092,7 @@ function registerClassroomTools(server) {
34638
35092
  account: accountParam
34639
35093
  })
34640
35094
  }, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
34641
- const args = ["classroom", "roster", courseId];
35095
+ const args = ["classroom", "roster", pos(courseId)];
34642
35096
  if (students) args.push("--students");
34643
35097
  if (teachers) args.push("--teachers");
34644
35098
  if (max !== void 0) args.push(`--max=${max}`);
@@ -34663,7 +35117,7 @@ function registerClassroomTools(server) {
34663
35117
  account: accountParam
34664
35118
  })
34665
35119
  }, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
34666
- const args = ["classroom", "coursework", "list", courseId];
35120
+ const args = ["classroom", "coursework", "list", pos(courseId)];
34667
35121
  if (state) args.push(`--state=${state}`);
34668
35122
  if (topic) args.push(`--topic=${topic}`);
34669
35123
  if (orderBy) args.push(`--order-by=${orderBy}`);
@@ -34683,7 +35137,7 @@ function registerClassroomTools(server) {
34683
35137
  account: accountParam
34684
35138
  })
34685
35139
  }, async ({ courseId, courseworkId, account }) => {
34686
- return runOrDiagnose(["classroom", "coursework", "get", courseId, courseworkId], { account });
35140
+ return runOrDiagnose(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
34687
35141
  });
34688
35142
  server.registerTool("gog_classroom_submissions_list", {
34689
35143
  description: "List student submissions for a coursework item.",
@@ -34701,7 +35155,7 @@ function registerClassroomTools(server) {
34701
35155
  account: accountParam
34702
35156
  })
34703
35157
  }, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
34704
- const args = ["classroom", "submissions", "list", courseId, courseworkId];
35158
+ const args = ["classroom", "submissions", "list", pos(courseId), pos(courseworkId)];
34705
35159
  if (state) args.push(`--state=${state}`);
34706
35160
  if (late) args.push(`--late=${late}`);
34707
35161
  if (user) args.push(`--user=${user}`);
@@ -34721,7 +35175,7 @@ function registerClassroomTools(server) {
34721
35175
  account: accountParam
34722
35176
  })
34723
35177
  }, async ({ courseId, courseworkId, submissionId, account }) => {
34724
- return runOrDiagnose(["classroom", "submissions", "get", courseId, courseworkId, submissionId], { account });
35178
+ return runOrDiagnose(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
34725
35179
  });
34726
35180
  server.registerTool("gog_classroom_submissions_grade", {
34727
35181
  description: "Grade a student submission. Set draft and/or assigned grade values.",
@@ -34735,7 +35189,7 @@ function registerClassroomTools(server) {
34735
35189
  account: accountParam
34736
35190
  })
34737
35191
  }, async ({ courseId, courseworkId, submissionId, draft, assigned, account }) => {
34738
- const args = ["classroom", "submissions", "grade", courseId, courseworkId, submissionId];
35192
+ const args = ["classroom", "submissions", "grade", pos(courseId), pos(courseworkId), pos(submissionId)];
34739
35193
  if (draft) args.push(`--draft=${draft}`);
34740
35194
  if (assigned) args.push(`--assigned=${assigned}`);
34741
35195
  return runOrDiagnose(args, { account });
@@ -34750,7 +35204,7 @@ function registerClassroomTools(server) {
34750
35204
  account: accountParam
34751
35205
  })
34752
35206
  }, async ({ courseId, courseworkId, submissionId, account }) => {
34753
- return runOrDiagnose(["classroom", "submissions", "return", courseId, courseworkId, submissionId], { account });
35207
+ return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
34754
35208
  });
34755
35209
  server.registerTool("gog_classroom_submissions_turn_in", {
34756
35210
  description: "Turn in a student submission (student action).",
@@ -34762,7 +35216,7 @@ function registerClassroomTools(server) {
34762
35216
  account: accountParam
34763
35217
  })
34764
35218
  }, async ({ courseId, courseworkId, submissionId, account }) => {
34765
- return runOrDiagnose(["classroom", "submissions", "turn-in", courseId, courseworkId, submissionId], { account });
35219
+ return runOrDiagnose(["classroom", "submissions", "turn-in", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
34766
35220
  });
34767
35221
  server.registerTool("gog_classroom_submissions_reclaim", {
34768
35222
  description: "Reclaim a turned-in submission (student action to edit a submission).",
@@ -34774,7 +35228,7 @@ function registerClassroomTools(server) {
34774
35228
  account: accountParam
34775
35229
  })
34776
35230
  }, async ({ courseId, courseworkId, submissionId, account }) => {
34777
- return runOrDiagnose(["classroom", "submissions", "reclaim", courseId, courseworkId, submissionId], { account });
35231
+ return runOrDiagnose(["classroom", "submissions", "reclaim", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
34778
35232
  });
34779
35233
  server.registerTool("gog_classroom_announcements_list", {
34780
35234
  description: "List announcements in a Google Classroom course.",
@@ -34790,7 +35244,7 @@ function registerClassroomTools(server) {
34790
35244
  account: accountParam
34791
35245
  })
34792
35246
  }, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
34793
- const args = ["classroom", "announcements", "list", courseId];
35247
+ const args = ["classroom", "announcements", "list", pos(courseId)];
34794
35248
  if (state) args.push(`--state=${state}`);
34795
35249
  if (orderBy) args.push(`--order-by=${orderBy}`);
34796
35250
  if (max !== void 0) args.push(`--max=${max}`);
@@ -34808,7 +35262,7 @@ function registerClassroomTools(server) {
34808
35262
  account: accountParam
34809
35263
  })
34810
35264
  }, async ({ courseId, announcementId, account }) => {
34811
- return runOrDiagnose(["classroom", "announcements", "get", courseId, announcementId], { account });
35265
+ return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
34812
35266
  });
34813
35267
  server.registerTool("gog_classroom_announcements_create", {
34814
35268
  description: "Create an announcement in a Google Classroom course.",
@@ -34821,7 +35275,7 @@ function registerClassroomTools(server) {
34821
35275
  account: accountParam
34822
35276
  })
34823
35277
  }, async ({ courseId, text, state, scheduled, account }) => {
34824
- const args = ["classroom", "announcements", "create", courseId, `--text=${text}`];
35278
+ const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
34825
35279
  if (state) args.push(`--state=${state}`);
34826
35280
  if (scheduled) args.push(`--scheduled=${scheduled}`);
34827
35281
  return runOrDiagnose(args, { account });
@@ -34838,7 +35292,7 @@ function registerClassroomTools(server) {
34838
35292
  account: accountParam
34839
35293
  })
34840
35294
  }, async ({ courseId, max, pageToken, page, all, account }) => {
34841
- const args = ["classroom", "topics", "list", courseId];
35295
+ const args = ["classroom", "topics", "list", pos(courseId)];
34842
35296
  if (max !== void 0) args.push(`--max=${max}`);
34843
35297
  const token = resolvePageToken({ pageToken, page });
34844
35298
  if (token) args.push(`--page=${token}`);
@@ -34854,7 +35308,7 @@ function registerClassroomTools(server) {
34854
35308
  account: accountParam
34855
35309
  })
34856
35310
  }, async ({ courseId, topicId, account }) => {
34857
- return runOrDiagnose(["classroom", "topics", "get", courseId, topicId], { account });
35311
+ return runOrDiagnose(["classroom", "topics", "get", pos(courseId), pos(topicId)], { account });
34858
35312
  });
34859
35313
  server.registerTool("gog_classroom_invitations_list", {
34860
35314
  description: "List Google Classroom invitations.",
@@ -34886,7 +35340,7 @@ function registerClassroomTools(server) {
34886
35340
  account: accountParam
34887
35341
  })
34888
35342
  }, async ({ invitationId, account }) => {
34889
- return runOrDiagnose(["classroom", "invitations", "get", invitationId], { account });
35343
+ return runOrDiagnose(["classroom", "invitations", "get", pos(invitationId)], { account });
34890
35344
  });
34891
35345
  server.registerTool("gog_classroom_invitations_accept", {
34892
35346
  description: "Accept a Google Classroom invitation.",
@@ -34896,7 +35350,7 @@ function registerClassroomTools(server) {
34896
35350
  account: accountParam
34897
35351
  })
34898
35352
  }, async ({ invitationId, account }) => {
34899
- return runOrDiagnose(["classroom", "invitations", "accept", invitationId], { account });
35353
+ return runOrDiagnose(["classroom", "invitations", "accept", pos(invitationId)], { account });
34900
35354
  });
34901
35355
  server.registerTool("gog_classroom_profile_get", {
34902
35356
  description: "Get a Google Classroom user profile. Omit userId to fetch the authenticated user.",
@@ -34907,7 +35361,7 @@ function registerClassroomTools(server) {
34907
35361
  })
34908
35362
  }, async ({ userId, account }) => {
34909
35363
  const args = ["classroom", "profile", "get"];
34910
- if (userId) args.push(userId);
35364
+ if (userId) args.push(pos(userId));
34911
35365
  return runOrDiagnose(args, { account });
34912
35366
  });
34913
35367
  registerRunTool(server, {
@@ -34927,7 +35381,7 @@ function registerContactsTools(server) {
34927
35381
  account: accountParam
34928
35382
  })
34929
35383
  }, async ({ query, account }) => {
34930
- return runOrDiagnose(["contacts", "search", query], { account });
35384
+ return runOrDiagnose(["contacts", "search", pos(query)], { account });
34931
35385
  });
34932
35386
  server.registerTool("gog_contacts_list", {
34933
35387
  description: "List all Google Contacts.",
@@ -34946,7 +35400,7 @@ function registerContactsTools(server) {
34946
35400
  account: accountParam
34947
35401
  })
34948
35402
  }, async ({ resourceName, account }) => {
34949
- return runOrDiagnose(["contacts", "get", resourceName], { account });
35403
+ return runOrDiagnose(["contacts", "get", pos(resourceName)], { account });
34950
35404
  });
34951
35405
  server.registerTool("gog_contacts_create", {
34952
35406
  description: "Create a new Google Contact.",
@@ -34982,7 +35436,7 @@ function registerDocsTools(server) {
34982
35436
  account: accountParam
34983
35437
  })
34984
35438
  }, async ({ docId, account }) => {
34985
- return runOrDiagnose(["docs", "info", docId], { account });
35439
+ return runOrDiagnose(["docs", "info", pos(docId)], { account });
34986
35440
  });
34987
35441
  server.registerTool("gog_docs_cat", {
34988
35442
  description: "Read a Google Doc as plain text.",
@@ -34993,7 +35447,7 @@ function registerDocsTools(server) {
34993
35447
  account: accountParam
34994
35448
  })
34995
35449
  }, async ({ docId, chips, account }) => {
34996
- const args = ["docs", "cat", docId];
35450
+ const args = ["docs", "cat", pos(docId)];
34997
35451
  if (chips) args.push("--chips");
34998
35452
  return runOrDiagnose(args, { account });
34999
35453
  });
@@ -35005,7 +35459,7 @@ function registerDocsTools(server) {
35005
35459
  account: accountParam
35006
35460
  })
35007
35461
  }, async ({ title, account }) => {
35008
- return runOrDiagnose(["docs", "create", title], { account });
35462
+ return runOrDiagnose(["docs", "create", pos(title)], { account });
35009
35463
  });
35010
35464
  server.registerTool("gog_docs_write", {
35011
35465
  description: "Write text content to a Google Doc, replacing existing body content by default. Set append=true to add after existing content.",
@@ -35030,7 +35484,7 @@ function registerDocsTools(server) {
35030
35484
  account: accountParam
35031
35485
  })
35032
35486
  }, async ({ docId, text, append, checkOrphans, bullets, bulletPreset, ordered, noBullets, indentStart, indentEnd, indentFirstLine, spaceAbove, spaceBelow, keepLinesTogether, keepWithNext, batch, account }) => {
35033
- const args = ["docs", "write", docId, `--text=${text}`];
35487
+ const args = ["docs", "write", pos(docId), `--text=${text}`];
35034
35488
  if (append) args.push("--append");
35035
35489
  if (checkOrphans) args.push("--check-orphans");
35036
35490
  if (bullets) args.push("--bullets");
@@ -35057,7 +35511,7 @@ function registerDocsTools(server) {
35057
35511
  account: accountParam
35058
35512
  })
35059
35513
  }, async ({ docId, find, replace, account }) => {
35060
- return runOrDiagnose(["docs", "find-replace", docId, find, replace], { account });
35514
+ return runOrDiagnose(["docs", "find-replace", pos(docId), pos(find), pos(replace)], { account });
35061
35515
  });
35062
35516
  server.registerTool("gog_docs_structure", {
35063
35517
  description: "Show a Google Doc's structure with numbered paragraphs. Useful for understanding the document layout before making index-based edits.",
@@ -35067,7 +35521,7 @@ function registerDocsTools(server) {
35067
35521
  account: accountParam
35068
35522
  })
35069
35523
  }, async ({ docId, account }) => {
35070
- return runOrDiagnose(["docs", "structure", docId], { account });
35524
+ return runOrDiagnose(["docs", "structure", pos(docId)], { account });
35071
35525
  });
35072
35526
  registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
35073
35527
  }
@@ -35077,8 +35531,10 @@ var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
35077
35531
  function fileMeta(raw) {
35078
35532
  const parsed = JSON.parse(raw);
35079
35533
  const f = parsed.file ?? parsed;
35080
- return { name: f.name, mimeType: f.mimeType };
35534
+ const size = f.size === void 0 ? void 0 : Number(f.size);
35535
+ return { name: f.name, mimeType: f.mimeType, size: Number.isFinite(size) ? size : void 0 };
35081
35536
  }
35537
+ var MAX_DRIVE_READ_BYTES = MAX_INLINE_ATTACHMENT_BYTES;
35082
35538
  var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime,size,webViewLink)";
35083
35539
  function registerDriveTools(server) {
35084
35540
  server.registerTool("gog_drive_ls", {
@@ -35122,7 +35578,7 @@ function registerDriveTools(server) {
35122
35578
  account: accountParam
35123
35579
  })
35124
35580
  }, async ({ query, view, account }) => {
35125
- return runOrDiagnose(["drive", "search", query], {
35581
+ return runOrDiagnose(["drive", "search", pos(query)], {
35126
35582
  account,
35127
35583
  stripMedia: resolveView(view, ["compact", "full"]) === "compact"
35128
35584
  });
@@ -35142,7 +35598,7 @@ function registerDriveTools(server) {
35142
35598
  account: accountParam
35143
35599
  })
35144
35600
  }, async ({ fileId, view, account }) => {
35145
- return runOrDiagnose(["drive", "get", fileId], {
35601
+ return runOrDiagnose(["drive", "get", pos(fileId)], {
35146
35602
  account,
35147
35603
  stripMedia: resolveView(view, ["compact", "full"]) === "compact"
35148
35604
  });
@@ -35155,7 +35611,7 @@ function registerDriveTools(server) {
35155
35611
  account: accountParam
35156
35612
  })
35157
35613
  }, async ({ name, account }) => {
35158
- return runOrDiagnose(["drive", "mkdir", name], { account });
35614
+ return runOrDiagnose(["drive", "mkdir", pos(name)], { account });
35159
35615
  });
35160
35616
  server.registerTool("gog_drive_rename", {
35161
35617
  description: "Rename a file or folder in Google Drive.",
@@ -35166,7 +35622,7 @@ function registerDriveTools(server) {
35166
35622
  account: accountParam
35167
35623
  })
35168
35624
  }, async ({ fileId, newName, account }) => {
35169
- return runOrDiagnose(["drive", "rename", fileId, newName], { account });
35625
+ return runOrDiagnose(["drive", "rename", pos(fileId), pos(newName)], { account });
35170
35626
  });
35171
35627
  server.registerTool("gog_drive_move", {
35172
35628
  description: "Move a file to a different folder in Google Drive.",
@@ -35177,7 +35633,7 @@ function registerDriveTools(server) {
35177
35633
  account: accountParam
35178
35634
  })
35179
35635
  }, async ({ fileId, parentId, account }) => {
35180
- return runOrDiagnose(["drive", "move", fileId, `--parent=${parentId}`], { account });
35636
+ return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
35181
35637
  });
35182
35638
  server.registerTool("gog_drive_delete", {
35183
35639
  description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
@@ -35188,7 +35644,7 @@ function registerDriveTools(server) {
35188
35644
  account: accountParam
35189
35645
  })
35190
35646
  }, async ({ fileId, permanent, account }) => {
35191
- const args = ["drive", "delete", fileId];
35647
+ const args = ["drive", "delete", pos(fileId)];
35192
35648
  if (permanent) args.push("--permanent");
35193
35649
  args.push("--force");
35194
35650
  return runOrDiagnose(args, { account });
@@ -35205,7 +35661,7 @@ function registerDriveTools(server) {
35205
35661
  account: accountParam
35206
35662
  })
35207
35663
  }, async ({ fileId, to, email: email3, domain: domain2, role, account }) => {
35208
- const args = ["drive", "share", fileId, `--to=${to}`];
35664
+ const args = ["drive", "share", pos(fileId), `--to=${to}`];
35209
35665
  if (email3) args.push(`--email=${email3}`);
35210
35666
  if (domain2) args.push(`--domain=${domain2}`);
35211
35667
  if (role) args.push(`--role=${role}`);
@@ -35228,7 +35684,7 @@ function registerDriveTools(server) {
35228
35684
  }, async ({ fileId, ocrLanguage, offset = 0, maxChars, account }) => {
35229
35685
  let tempDocId;
35230
35686
  try {
35231
- const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
35687
+ const { name, mimeType } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
35232
35688
  let sourceId = fileId;
35233
35689
  if (mimeType !== GOOGLE_DOC_MIME) {
35234
35690
  const params = JSON.stringify({ fileId, ...ocrLanguage ? { ocrLanguage } : {} });
@@ -35273,13 +35729,13 @@ function registerDriveTools(server) {
35273
35729
  return diagnose(err);
35274
35730
  } finally {
35275
35731
  if (tempDocId) {
35276
- await run(["drive", "delete", tempDocId, "--permanent", "--force"], { account }).catch(() => {
35732
+ await run(["drive", "delete", pos(tempDocId), "--permanent", "--force"], { account }).catch(() => {
35277
35733
  });
35278
35734
  }
35279
35735
  }
35280
35736
  });
35281
35737
  server.registerTool("gog_drive_read_bytes", {
35282
- description: "Fetch a Drive file's raw bytes and return them base64-encoded as an embedded resource \u2014 the generic fallback for callers that want the file itself (to parse locally) rather than extracted text. For readable text from a PDF, prefer gog_drive_extract_text.",
35738
+ description: "Fetch a Drive file's raw bytes and return them base64-encoded as an embedded resource \u2014 the generic fallback for callers that want the file itself (to parse locally) rather than extracted text. Files over 8 MiB are refused (checked before downloading). For readable text from a PDF, prefer gog_drive_extract_text.",
35283
35739
  annotations: { readOnlyHint: true },
35284
35740
  inputSchema: external_exports.object({
35285
35741
  fileId: external_exports.string().describe("Drive file ID"),
@@ -35287,9 +35743,17 @@ function registerDriveTools(server) {
35287
35743
  })
35288
35744
  }, async ({ fileId, account }) => {
35289
35745
  try {
35290
- const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
35746
+ const { name, mimeType, size } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
35747
+ if (size !== void 0 && size > MAX_DRIVE_READ_BYTES) {
35748
+ return errorResult(
35749
+ `${name ?? fileId} is ${size} bytes, over the 8 MiB this tool returns inline. Use gog_drive_extract_text for its text, or share its webViewLink (gog_drive_get) instead.`
35750
+ );
35751
+ }
35291
35752
  const params = JSON.stringify({ fileId, alt: "media" });
35292
- const blob = await runBinary(["api", "call", "drive", "v3", "files.get", `--params=${params}`], { account });
35753
+ const blob = await runBinary(
35754
+ ["api", "call", "drive", "v3", "files.get", `--params=${params}`],
35755
+ { account, maxOutputBytes: MAX_DRIVE_READ_BYTES }
35756
+ );
35293
35757
  const type = mimeType ?? "application/octet-stream";
35294
35758
  const unrenderableHint = type.startsWith("image/") ? "" : ` If your client does not render a ${type} resource, gog_drive_extract_text returns the same file as text (PDFs included, via OCR).`;
35295
35759
  return {
@@ -35309,7 +35773,7 @@ function registerDriveTools(server) {
35309
35773
  return diagnose(err);
35310
35774
  }
35311
35775
  });
35312
- registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
35776
+ registerRunTool(server, { service: "drive", examples: '"copy", "download", "permissions"' });
35313
35777
  }
35314
35778
 
35315
35779
  // src/gmail-results.ts
@@ -35337,7 +35801,7 @@ async function countMatches(method, itemsKey, query, account) {
35337
35801
  maxResults: COUNT_PROBE_PAGE_SIZE,
35338
35802
  fields: `${itemsKey}/id,nextPageToken`
35339
35803
  });
35340
- const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
35804
+ const raw = await run(["api", "call", "gmail", "v1", pos(method), `--params=${params}`], { account });
35341
35805
  const parsed = JSON.parse(raw);
35342
35806
  const items = parsed[itemsKey];
35343
35807
  if (!Array.isArray(items)) return {};
@@ -35376,7 +35840,7 @@ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
35376
35840
  const result = await runPage(token);
35377
35841
  const parsed = parsePage(result, itemsKey);
35378
35842
  if (parsed === void 0) {
35379
- return base === void 0 ? result : finish(base, itemsKey, merged, token);
35843
+ return base === void 0 ? result : finish(base, itemsKey, merged, token, pageErrorText(result, itemsKey, pages + 1));
35380
35844
  }
35381
35845
  base = parsed;
35382
35846
  merged.push(...parsed[itemsKey]);
@@ -35407,10 +35871,18 @@ function parsePage(result, itemsKey) {
35407
35871
  const obj = parsed;
35408
35872
  return Array.isArray(obj[itemsKey]) ? obj : void 0;
35409
35873
  }
35410
- function finish(base, itemsKey, merged, token) {
35874
+ var PAGE_ERROR_MAX = 2e3;
35875
+ function pageErrorText(result, itemsKey, n) {
35876
+ const first = result.content[0];
35877
+ const text = first?.type === "text" ? first.text.slice(0, PAGE_ERROR_MAX) : "";
35878
+ if (result.isError) return `page ${n} failed${text ? `: ${text}` : ""}`;
35879
+ return `page ${n} returned output that is not a ${itemsKey} list`;
35880
+ }
35881
+ function finish(base, itemsKey, merged, token, pageError) {
35411
35882
  const out = { ...base, [itemsKey]: merged };
35412
35883
  if (token === void 0) delete out.nextPageToken;
35413
35884
  else out.nextPageToken = token;
35885
+ if (pageError !== void 0) out.pageError = pageError;
35414
35886
  return rawTextResult(JSON.stringify(out));
35415
35887
  }
35416
35888
 
@@ -35424,16 +35896,27 @@ var STAGING_TWIN = {
35424
35896
  "gmail.reply-all": "gog_gmail_drafts_reply_all",
35425
35897
  "gmail.send": "gog_gmail_drafts_create"
35426
35898
  };
35899
+ var UNSUPPORTED_NOTE2 = {
35900
+ "gmail.drafts-send": "The draft is still saved: ask the user to review it and send it from Gmail."
35901
+ };
35902
+ var BODY_PREVIEW_MAX = 2048;
35903
+ function bodyPreview(text) {
35904
+ if (!text) return void 0;
35905
+ if (text.length <= BODY_PREVIEW_MAX) return text;
35906
+ return `${text.slice(0, BODY_PREVIEW_MAX)}\u2026 [${text.length - BODY_PREVIEW_MAX} more characters not shown]`;
35907
+ }
35908
+ function attachmentNames(paths, inline) {
35909
+ return [...paths ?? [], ...(inline ?? []).map((a) => a.filename)];
35910
+ }
35427
35911
  function requireGmailDispatchConfirmation(ctx, op, details) {
35428
35912
  const twin = STAGING_TWIN[op];
35913
+ const note = twin ? `Stage it with ${twin} instead; the user can review the draft and send it from Gmail (gog_gmail_drafts_send also asks for confirmation).` : UNSUPPORTED_NOTE2[op];
35429
35914
  return requireConfirmation(ctx, {
35430
35915
  action: op,
35431
- message: "Review and confirm this email dispatch:",
35916
+ message: op === "gmail.filter-forward" ? "Review and confirm this mail-forwarding filter:" : "Review and confirm this email dispatch:",
35432
35917
  details,
35433
- confirmationLabel: "Confirm that this email should be sent now.",
35434
- ...twin ? {
35435
- unsupportedNote: `Stage it with ${twin} instead, review the draft, and send it with gog_gmail_drafts_send.`
35436
- } : {}
35918
+ confirmationLabel: op === "gmail.filter-forward" ? "Confirm that matching mail should be forwarded automatically from now on." : "Confirm that this email should be sent now.",
35919
+ ...note ? { unsupportedNote: note } : {}
35437
35920
  });
35438
35921
  }
35439
35922
  var EMAIL_PATTERN = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+/gi;
@@ -35504,7 +35987,7 @@ var replySchema = {
35504
35987
  remove: external_exports.array(external_exports.string()).optional().describe("Remove these recipients from all fields (repeatable) \u2014 e.g. to drop someone from a reply-all."),
35505
35988
  subject: external_exports.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
35506
35989
  noQuote: external_exports.boolean().optional().describe("Do not include the original message quoted below the reply (default: the original is quoted)"),
35507
- attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension.`),
35990
+ attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
35508
35991
  attachInline: attachInlineParam,
35509
35992
  from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
35510
35993
  autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
@@ -35513,8 +35996,14 @@ var replySchema = {
35513
35996
  signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
35514
35997
  account: accountParam
35515
35998
  };
35999
+ function confineReplyPaths(f) {
36000
+ confinePaths(f.attach, "attach");
36001
+ if (f.bodyHtmlFile) confinePath(f.bodyHtmlFile, "bodyHtmlFile");
36002
+ if (f.signatureFile) confinePath(f.signatureFile, "signatureFile");
36003
+ }
35516
36004
  function appendReplyFlags(args, f) {
35517
36005
  assertNotBoth("bodyHtml", "bodyHtmlFile", f.bodyHtml, f.bodyHtmlFile);
36006
+ confineReplyPaths(f);
35518
36007
  if (f.body) args.push(payloadArg("body", "body-file", f.body));
35519
36008
  if (f.bodyHtml) args.push(payloadArg("body-html", "body-html-file", f.bodyHtml, "html"));
35520
36009
  else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
@@ -35558,7 +36047,7 @@ function computeReplyRecipients(kind, headers, flags) {
35558
36047
  return emails;
35559
36048
  }
35560
36049
  async function sendReply(kind, toolName, messageId, account, flags, ctx) {
35561
- const metaResult = await runOrDiagnose(["gmail", "get", messageId, "--format=metadata"], { account });
36050
+ const metaResult = await runOrDiagnose(["gmail", "get", pos(messageId), "--format=metadata"], { account });
35562
36051
  if (metaResult.isError) return metaResult;
35563
36052
  const headers = parseMetadataHeaders(resultText(metaResult));
35564
36053
  const recipients = computeReplyRecipients(kind, headers, flags);
@@ -35569,15 +36058,34 @@ async function sendReply(kind, toolName, messageId, account, flags, ctx) {
35569
36058
  subject: flags.subject || (headers.subject ? `Re: ${headers.subject}` : void 0),
35570
36059
  quoting: !flags.noQuote,
35571
36060
  bodyLength: (flags.body ?? flags.bodyHtml ?? "").length,
35572
- attachmentCount: (flags.attach?.length ?? 0) + (flags.attachInline?.length ?? 0)
36061
+ bodyPreview: bodyPreview(flags.body ?? flags.bodyHtml),
36062
+ bodyHtmlFile: flags.bodyHtmlFile,
36063
+ attachmentCount: (flags.attach?.length ?? 0) + (flags.attachInline?.length ?? 0),
36064
+ attachments: attachmentNames(flags.attach, flags.attachInline)
35573
36065
  });
35574
36066
  if (confirmation) return confirmation;
35575
- const args = ["gmail", kind, messageId];
36067
+ const args = ["gmail", kind, pos(messageId)];
35576
36068
  appendReplyFlags(args, flags);
35577
36069
  const result = await runOrDiagnose(args, { account });
35578
36070
  if (!result.isError) logGmailDispatch(toolName, recipients, account);
35579
36071
  return result;
35580
36072
  }
36073
+ var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
36074
+ function vetGmailRun(subcommand, args) {
36075
+ if (subcommand === "autoreply") {
36076
+ return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
36077
+ }
36078
+ if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
36079
+ 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.`;
36080
+ }
36081
+ if (subcommand === "settings") {
36082
+ const blocked = args.find((a) => GMAIL_RUN_BLOCKED_SETTINGS.has(a.toLowerCase()));
36083
+ if (blocked) {
36084
+ return `gog gmail settings ${blocked} can forward or hand over mail and is not available through gog_gmail_run. Use the dedicated gog_gmail_* tool instead.`;
36085
+ }
36086
+ }
36087
+ return void 0;
36088
+ }
35581
36089
  function registerGmailTools(server) {
35582
36090
  server.registerTool("gog_gmail_search", {
35583
36091
  description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
@@ -35593,7 +36101,7 @@ function registerGmailTools(server) {
35593
36101
  account: accountParam
35594
36102
  })
35595
36103
  }, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
35596
- const args = ["gmail", "search", query];
36104
+ const args = ["gmail", "search", pos(query)];
35597
36105
  if (max !== void 0) args.push(`--max=${max}`);
35598
36106
  if (all) args.push("--all");
35599
36107
  if (fromContact) args.push(`--from-contact=${fromContact}`);
@@ -35624,13 +36132,13 @@ function registerGmailTools(server) {
35624
36132
  account: accountParam
35625
36133
  })
35626
36134
  }, async ({ messageId, format, sanitizeContent, account }) => {
35627
- const args = ["gmail", "get", messageId];
36135
+ const args = ["gmail", "get", pos(messageId)];
35628
36136
  if (format) args.push(`--format=${format}`);
35629
36137
  if (sanitizeContent) args.push("--sanitize-content");
35630
36138
  return runOrDiagnose(args, { account });
35631
36139
  });
35632
36140
  server.registerTool("gog_gmail_send", {
35633
- description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the recipients, subject, and body size. Mail is sent only after the user accepts that prompt. Two ways to attach a file: `attach` takes paths READ ON THE GOG SERVER, and `attachInline` takes the bytes themselves. Use attachInline unless you know the file exists on the same machine gog runs on \u2014 on the hosted connector and any remote deployment there is no shared filesystem, so no path you can name resolves there and `attach` will fail with "no such file or directory". When either is used, the JSON result echoes the attached filenames and byte sizes \u2014 check it to confirm the files were embedded. NOT the tool for answering a message: replyToMessageId only files this in the right thread \u2014 the subject, recipients and body are entirely yours, and the original is not quoted unless you set quote. Use gog_gmail_reply / gog_gmail_reply_all instead, which inherit all three.',
36141
+ description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the recipients, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. Two ways to attach a file: `attach` takes paths READ ON THE GOG SERVER, and `attachInline` takes the bytes themselves. Use attachInline unless you know the file exists on the same machine gog runs on \u2014 on the hosted connector and any remote deployment there is no shared filesystem, so no path you can name resolves there and `attach` will fail with "no such file or directory". When either is used, the JSON result echoes the attached filenames and byte sizes \u2014 check it to confirm the files were embedded. NOT the tool for answering a message: replyToMessageId only files this in the right thread \u2014 the subject, recipients and body are entirely yours, and the original is not quoted unless you set quote. Use gog_gmail_reply / gog_gmail_reply_all instead, which inherit all three.',
35634
36142
  annotations: { destructiveHint: true },
35635
36143
  inputSchema: external_exports.object({
35636
36144
  to: external_exports.string().describe("Recipient(s), comma-separated"),
@@ -35641,11 +36149,12 @@ function registerGmailTools(server) {
35641
36149
  replyToMessageId: external_exports.string().optional().describe('Message ID to thread this message against \u2014 sets In-Reply-To/References only. It does NOT quote the original (pass quote for that), inherit its recipients, or prefix the subject with "Re:". For an actual reply use gog_gmail_reply.'),
35642
36150
  threadId: external_exports.string().optional().describe("Thread ID to thread this message within. Same caveat as replyToMessageId: threading only, no quote and no inherited subject or recipients."),
35643
36151
  quote: external_exports.boolean().optional().describe("Include the original message quoted below the body. Requires replyToMessageId or threadId. gog quotes by DEFAULT on gmail reply but never on gmail send, so without this a threaded send arrives with the original nowhere in it."),
35644
- attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Each file is read on the server, base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment.`),
36152
+ attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Each file is read on the server, base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
35645
36153
  attachInline: attachInlineParam,
35646
36154
  account: accountParam
35647
36155
  })
35648
36156
  }, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account }, ctx) => {
36157
+ confinePaths(attach, "attach");
35649
36158
  const args = ["gmail", "send", `--to=${to}`, `--subject=${subject}`, payloadArg("body", "body-file", body)];
35650
36159
  if (cc) args.push(`--cc=${cc}`);
35651
36160
  if (bcc) args.push(`--bcc=${bcc}`);
@@ -35664,9 +36173,11 @@ function registerGmailTools(server) {
35664
36173
  recipientCount: recipients.length,
35665
36174
  subject,
35666
36175
  bodyLength: body.length,
36176
+ bodyPreview: bodyPreview(body),
35667
36177
  threaded: Boolean(replyToMessageId || threadId),
35668
36178
  quoting: Boolean(quote),
35669
- attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0)
36179
+ attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0),
36180
+ attachments: attachmentNames(attach, attachInline)
35670
36181
  });
35671
36182
  if (confirmation) return confirmation;
35672
36183
  const result = await runOrDiagnose(args, { account });
@@ -35674,22 +36185,32 @@ function registerGmailTools(server) {
35674
36185
  return result;
35675
36186
  });
35676
36187
  server.registerTool("gog_gmail_reply", {
35677
- description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipient, subject, and body size. Mail is sent only after the user accepts that prompt. To STAGE a reply instead of sending it, use gog_gmail_drafts_reply (gogcli-mcp-gmail only), which never needs confirmation. Reply to a Gmail message (goes to the original sender only). USE THIS, not gog_gmail_send, whenever you are answering a message: it threads off the original AND inherits its "Re:" subject and quotes its body below yours, which gog_gmail_send does not \u2014 a send with replyToMessageId lands in the right thread but reads as a brand-new message, with the original nowhere in it. To answer every participant use gog_gmail_reply_all. The gogcli-mcp-gmail package adds two more routes with the same composition: gog_gmail_autoreply to reply across every message matching a query, and gog_gmail_drafts_reply to stage this exact reply as a draft instead of sending it.',
36188
+ description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipient, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. To STAGE a reply instead of sending it, use gog_gmail_drafts_reply (gogcli-mcp-gmail only), which never needs confirmation. Reply to a Gmail message (goes to the original sender only). USE THIS, not gog_gmail_send, whenever you are answering a message: it threads off the original AND inherits its "Re:" subject and quotes its body below yours, which gog_gmail_send does not \u2014 a send with replyToMessageId lands in the right thread but reads as a brand-new message, with the original nowhere in it. To answer every participant use gog_gmail_reply_all. The gogcli-mcp-gmail package adds two more routes with the same composition: gog_gmail_autoreply to reply across every message matching a query, and gog_gmail_drafts_reply to stage this exact reply as a draft instead of sending it.',
35678
36189
  annotations: { destructiveHint: true },
35679
36190
  inputSchema: sendReplySchema
35680
36191
  }, async ({ messageId, account, ...flags }, ctx) => {
35681
36192
  assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
36193
+ confineReplyPaths(flags);
35682
36194
  return sendReply("reply", "gog_gmail_reply", messageId, account, flags, ctx);
35683
36195
  });
35684
36196
  server.registerTool("gog_gmail_reply_all", {
35685
- description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipients, subject, and body size. Mail is sent only after the user accepts that prompt. To STAGE a reply-all instead of sending it, use gog_gmail_drafts_reply_all (gogcli-mcp-gmail only), which never needs confirmation. Reply to all participants of a Gmail message (the sender plus every To/Cc recipient). Same inherited "Re:" subject and quoted original as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all.',
36197
+ description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipients, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. To STAGE a reply-all instead of sending it, use gog_gmail_drafts_reply_all (gogcli-mcp-gmail only), which never needs confirmation. Reply to all participants of a Gmail message (the sender plus every To/Cc recipient). Same inherited "Re:" subject and quoted original as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all.',
35686
36198
  annotations: { destructiveHint: true },
35687
36199
  inputSchema: sendReplySchema
35688
36200
  }, async ({ messageId, account, ...flags }, ctx) => {
35689
36201
  assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
36202
+ confineReplyPaths(flags);
35690
36203
  return sendReply("reply-all", "gog_gmail_reply_all", messageId, account, flags, ctx);
35691
36204
  });
35692
- registerRunTool(server, { service: "gmail", examples: '"archive", "mark-read", "labels"' });
36205
+ registerRunTool(server, {
36206
+ service: "gmail",
36207
+ examples: '"archive", "mark-read", "labels"',
36208
+ // gog's --gmail-no-send blocks send/reply/forward/drafts send and all of
36209
+ // their aliases at runtime (verified on gog 0.41.0). Sending goes through the
36210
+ // confirmed tools, never this escape hatch.
36211
+ gmailNoSend: true,
36212
+ vet: vetGmailRun
36213
+ });
35693
36214
  }
35694
36215
 
35695
36216
  // src/tools/sheets-a1.ts
@@ -35758,7 +36279,7 @@ function registerSheetsTools(server) {
35758
36279
  account: accountParam
35759
36280
  })
35760
36281
  }, async ({ spreadsheetId, range, account }) => {
35761
- return runOrDiagnose(["sheets", "get", spreadsheetId, range], { account });
36282
+ return runOrDiagnose(["sheets", "get", pos(spreadsheetId), pos(range)], { account });
35762
36283
  });
35763
36284
  server.registerTool("gog_sheets_update", {
35764
36285
  description: 'Write values to a Google Sheets range, overwriting existing content. Values may be strings, numbers, booleans, or null. Strings starting with "=" are interpreted as formulas (e.g. "=SUM(A1:A10)").',
@@ -35778,7 +36299,7 @@ function registerSheetsTools(server) {
35778
36299
  const readRange = expandAnchorRange(range, values.length, cols);
35779
36300
  let existing;
35780
36301
  try {
35781
- existing = await run(["sheets", "get", spreadsheetId, readRange], { account });
36302
+ existing = await run(["sheets", "get", pos(spreadsheetId), pos(readRange)], { account });
35782
36303
  } catch (err) {
35783
36304
  return diagnose(err);
35784
36305
  }
@@ -35790,7 +36311,7 @@ function registerSheetsTools(server) {
35790
36311
  );
35791
36312
  }
35792
36313
  }
35793
- const args = ["sheets", "update", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
36314
+ const args = ["sheets", "update", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
35794
36315
  if (dry_run) args.push("--dry-run");
35795
36316
  if (fail_on_formula_error) args.push("--fail-on-formula-error");
35796
36317
  return runOrDiagnose(args, { account });
@@ -35806,7 +36327,7 @@ function registerSheetsTools(server) {
35806
36327
  account: accountParam
35807
36328
  })
35808
36329
  }, async ({ spreadsheetId, range, values, account, dry_run }) => {
35809
- const args = ["sheets", "append", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
36330
+ const args = ["sheets", "append", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
35810
36331
  if (dry_run) args.push("--dry-run");
35811
36332
  return runOrDiagnose(args, { account });
35812
36333
  });
@@ -35820,7 +36341,7 @@ function registerSheetsTools(server) {
35820
36341
  account: accountParam
35821
36342
  })
35822
36343
  }, async ({ spreadsheetId, range, account, dry_run }) => {
35823
- const args = ["sheets", "clear", spreadsheetId, range];
36344
+ const args = ["sheets", "clear", pos(spreadsheetId), pos(range)];
35824
36345
  if (dry_run) args.push("--dry-run");
35825
36346
  return runOrDiagnose(args, { account });
35826
36347
  });
@@ -35832,7 +36353,7 @@ function registerSheetsTools(server) {
35832
36353
  account: accountParam
35833
36354
  })
35834
36355
  }, async ({ spreadsheetId, account }) => {
35835
- return runOrDiagnose(["sheets", "metadata", spreadsheetId], { account });
36356
+ return runOrDiagnose(["sheets", "metadata", pos(spreadsheetId)], { account });
35836
36357
  });
35837
36358
  server.registerTool("gog_sheets_create", {
35838
36359
  description: "Create a new Google Spreadsheet. Returns JSON with the new spreadsheetId and URL.",
@@ -35842,7 +36363,7 @@ function registerSheetsTools(server) {
35842
36363
  account: accountParam
35843
36364
  })
35844
36365
  }, async ({ title, account }) => {
35845
- return runOrDiagnose(["sheets", "create", title], { account });
36366
+ return runOrDiagnose(["sheets", "create", pos(title)], { account });
35846
36367
  });
35847
36368
  server.registerTool("gog_sheets_find_replace", {
35848
36369
  description: "Find and replace text across an entire Google Spreadsheet.",
@@ -35854,7 +36375,7 @@ function registerSheetsTools(server) {
35854
36375
  account: accountParam
35855
36376
  })
35856
36377
  }, async ({ spreadsheetId, find, replace, account }) => {
35857
- return runOrDiagnose(["sheets", "find-replace", spreadsheetId, find, replace], { account });
36378
+ return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
35858
36379
  });
35859
36380
  registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
35860
36381
  }
@@ -35862,8 +36383,9 @@ function registerSheetsTools(server) {
35862
36383
  // src/tools/slides.ts
35863
36384
  function registerSlidesTools(server) {
35864
36385
  server.registerTool("gog_slides_export", {
35865
- description: "Export a Google Slides presentation to a local file (pdf or pptx).",
35866
- annotations: { readOnlyHint: true },
36386
+ description: "Export a Google Slides presentation to a local file (pdf or pptx). The out path must be inside the server's GOG_FILE_ROOTS directories.",
36387
+ // Writes a file on the gog host and can overwrite one: not read-only.
36388
+ annotations: { readOnlyHint: false, destructiveHint: true },
35867
36389
  inputSchema: external_exports.object({
35868
36390
  presentationId: external_exports.string().describe("Presentation ID"),
35869
36391
  out: external_exports.string().optional().describe("Output file path"),
@@ -35872,7 +36394,8 @@ function registerSlidesTools(server) {
35872
36394
  account: accountParam
35873
36395
  })
35874
36396
  }, async ({ presentationId, out, format, overwrite, account }) => {
35875
- const args = ["slides", "export", presentationId];
36397
+ if (out) confinePath(out, "out");
36398
+ const args = ["slides", "export", pos(presentationId)];
35876
36399
  if (out) args.push(`--out=${out}`);
35877
36400
  if (format) args.push(`--format=${format}`);
35878
36401
  if (overwrite) args.push("--overwrite");
@@ -35886,7 +36409,7 @@ function registerSlidesTools(server) {
35886
36409
  account: accountParam
35887
36410
  })
35888
36411
  }, async ({ presentationId, account }) => {
35889
- return runOrDiagnose(["slides", "info", presentationId], { account });
36412
+ return runOrDiagnose(["slides", "info", pos(presentationId)], { account });
35890
36413
  });
35891
36414
  server.registerTool("gog_slides_create", {
35892
36415
  description: "Create a new Google Slides presentation, optionally in a folder or copying from a template.",
@@ -35898,7 +36421,7 @@ function registerSlidesTools(server) {
35898
36421
  account: accountParam
35899
36422
  })
35900
36423
  }, async ({ title, parent, template, account }) => {
35901
- const args = ["slides", "create", title];
36424
+ const args = ["slides", "create", pos(title)];
35902
36425
  if (parent) args.push(`--parent=${parent}`);
35903
36426
  if (template) args.push(`--template=${template}`);
35904
36427
  return runOrDiagnose(args, { account });
@@ -35913,7 +36436,7 @@ function registerSlidesTools(server) {
35913
36436
  account: accountParam
35914
36437
  })
35915
36438
  }, async ({ presentationId, title, parent, account }) => {
35916
- const args = ["slides", "copy", presentationId, title];
36439
+ const args = ["slides", "copy", pos(presentationId), pos(title)];
35917
36440
  if (parent) args.push(`--parent=${parent}`);
35918
36441
  return runOrDiagnose(args, { account });
35919
36442
  });
@@ -35925,7 +36448,7 @@ function registerSlidesTools(server) {
35925
36448
  account: accountParam
35926
36449
  })
35927
36450
  }, async ({ presentationId, account }) => {
35928
- return runOrDiagnose(["slides", "list-slides", presentationId], { account });
36451
+ return runOrDiagnose(["slides", "list-slides", pos(presentationId)], { account });
35929
36452
  });
35930
36453
  server.registerTool("gog_slides_read_slide", {
35931
36454
  description: "Read the content of a slide (text, shapes, speaker notes). Set detail=true to also include normalized element geometry, styled text runs, paragraphs, table-cell content, and image source URLs.",
@@ -35937,11 +36460,11 @@ function registerSlidesTools(server) {
35937
36460
  account: accountParam
35938
36461
  })
35939
36462
  }, async ({ presentationId, slideId, detail, account }) => {
35940
- const args = ["slides", "read-slide", presentationId, slideId];
36463
+ const args = ["slides", "read-slide", pos(presentationId), pos(slideId)];
35941
36464
  if (detail) args.push("--detail");
35942
36465
  return runOrDiagnose(args, { account });
35943
36466
  });
35944
- registerRunTool(server, { service: "slides", examples: '"add-slide", "delete-slide", "update-notes"' });
36467
+ registerRunTool(server, { service: "slides", examples: '"delete-slide", "update-notes"' });
35945
36468
  }
35946
36469
 
35947
36470
  // src/tools/tasks.ts
@@ -35963,7 +36486,7 @@ function registerTasksTools(server) {
35963
36486
  account: accountParam
35964
36487
  })
35965
36488
  }, async ({ tasklistId, account }) => {
35966
- return runOrDiagnose(["tasks", "list", tasklistId], { account });
36489
+ return runOrDiagnose(["tasks", "list", pos(tasklistId)], { account });
35967
36490
  });
35968
36491
  server.registerTool("gog_tasks_get", {
35969
36492
  description: "Get a specific task by ID.",
@@ -35974,7 +36497,7 @@ function registerTasksTools(server) {
35974
36497
  account: accountParam
35975
36498
  })
35976
36499
  }, async ({ tasklistId, taskId, account }) => {
35977
- return runOrDiagnose(["tasks", "get", tasklistId, taskId], { account });
36500
+ return runOrDiagnose(["tasks", "get", pos(tasklistId), pos(taskId)], { account });
35978
36501
  });
35979
36502
  server.registerTool("gog_tasks_add", {
35980
36503
  description: "Add a task to a Google Task list.",
@@ -35987,7 +36510,7 @@ function registerTasksTools(server) {
35987
36510
  account: accountParam
35988
36511
  })
35989
36512
  }, async ({ tasklistId, title, notes, due, account }) => {
35990
- const args = ["tasks", "add", tasklistId, `--title=${title}`];
36513
+ const args = ["tasks", "add", pos(tasklistId), `--title=${title}`];
35991
36514
  if (notes) args.push(`--notes=${notes}`);
35992
36515
  if (due) args.push(`--due=${due}`);
35993
36516
  return runOrDiagnose(args, { account });
@@ -36001,7 +36524,7 @@ function registerTasksTools(server) {
36001
36524
  account: accountParam
36002
36525
  })
36003
36526
  }, async ({ tasklistId, taskId, account }) => {
36004
- return runOrDiagnose(["tasks", "done", tasklistId, taskId], { account });
36527
+ return runOrDiagnose(["tasks", "done", pos(tasklistId), pos(taskId)], { account });
36005
36528
  });
36006
36529
  server.registerTool("gog_tasks_delete", {
36007
36530
  description: "Delete a task.",
@@ -36012,13 +36535,13 @@ function registerTasksTools(server) {
36012
36535
  account: accountParam
36013
36536
  })
36014
36537
  }, async ({ tasklistId, taskId, account }) => {
36015
- return runOrDiagnose(["tasks", "delete", tasklistId, taskId, "--force"], { account });
36538
+ return runOrDiagnose(["tasks", "delete", pos(tasklistId), pos(taskId), "--force"], { account });
36016
36539
  });
36017
36540
  registerRunTool(server, { service: "tasks", examples: '"update", "undo", "clear"' });
36018
36541
  }
36019
36542
 
36020
36543
  // src/server.ts
36021
- var VERSION = true ? "4.2.4" : "0.0.0";
36544
+ var VERSION = true ? "4.3.0" : "0.0.0";
36022
36545
  var BASE_TOOL_REGISTRARS = [
36023
36546
  registerApiTools,
36024
36547
  registerAppScriptTools,
@@ -36036,7 +36559,7 @@ var BASE_TOOL_REGISTRARS = [
36036
36559
  ];
36037
36560
 
36038
36561
  // src/bootstrap-auth.ts
36039
- import { join as join2 } from "node:path";
36562
+ import { join as join5 } from "node:path";
36040
36563
  var AUTH_BOOTSTRAP_MARKER = "auth-bootstrap.sha256";
36041
36564
  var VARS = ["GOG_CLIENT_ID", "GOG_CLIENT_SECRET", "GOG_REFRESH_TOKEN", "GOG_ACCOUNT"];
36042
36565
  var log = (line) => {
@@ -36070,12 +36593,12 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
36070
36593
  const [clientId, clientSecret, refreshToken, account] = values;
36071
36594
  const { spawner } = options;
36072
36595
  try {
36073
- const { createHash } = await import("node:crypto");
36596
+ const { createHash: createHash2 } = await import("node:crypto");
36074
36597
  const { mkdir, readFile, writeFile, chmod } = await import("node:fs/promises");
36075
36598
  const home = options.home ?? (await import("node:os")).homedir();
36076
- const dir = join2(home, ".gogcli-mcp");
36077
- const marker = join2(dir, AUTH_BOOTSTRAP_MARKER);
36078
- const fingerprint = createHash("sha256").update(values.join("\0")).digest("hex");
36599
+ const dir = join5(home, ".gogcli-mcp");
36600
+ const marker = join5(dir, AUTH_BOOTSTRAP_MARKER);
36601
+ const fingerprint = createHash2("sha256").update(values.join("\0")).digest("hex");
36079
36602
  const previous = await readFile(marker, "utf8").catch(() => void 0);
36080
36603
  if (previous === fingerprint && await accountListed(account, spawner)) return "present";
36081
36604
  await run(
@@ -36100,7 +36623,7 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
36100
36623
  }
36101
36624
 
36102
36625
  // src/blob-urls.ts
36103
- import { createHmac } from "node:crypto";
36626
+ import { createHmac as createHmac2 } from "node:crypto";
36104
36627
  var BLOB_URL_MAX_TTL_MS = 24 * 60 * 60 * 1e3;
36105
36628
  var BLOB_URL_CEILING_MARGIN_MS = 60 * 1e3;
36106
36629
  var BLOB_URL_DEFAULT_TTL_MS = 60 * 60 * 1e3;
@@ -36113,7 +36636,7 @@ function writePayload(objectKey, contentType, exp) {
36113
36636
  return `put\0${objectKey}\0${contentType}\0${exp}`;
36114
36637
  }
36115
36638
  function sign(signingKey, payload) {
36116
- return createHmac("sha256", signingKey).update(payload, "utf8").digest("base64url");
36639
+ return createHmac2("sha256", signingKey).update(payload, "utf8").digest("base64url");
36117
36640
  }
36118
36641
  function objectPathSegments(rest) {
36119
36642
  const segments = rest.split("/");
@@ -36175,7 +36698,6 @@ function blobStoreFromEnv() {
36175
36698
  }
36176
36699
 
36177
36700
  // src/blob-upload.ts
36178
- var ATTACHMENT_DOWNLOAD_ROOT = "/tmp/gog-attachments";
36179
36701
  var MAX_BLOB_UPLOAD_BYTES = 100 * 1024 * 1024;
36180
36702
  var BLOB_UPLOAD_TIMEOUT_MS = 12e4;
36181
36703
  var ERROR_BODY_SNIPPET = 512;
@@ -36190,10 +36712,10 @@ var UploadError = class extends Error {
36190
36712
  async function resolveConfined(root, requested) {
36191
36713
  const path = await import("node:path");
36192
36714
  const { realpath, stat } = await import("node:fs/promises");
36193
- const isInside = (base, candidate2) => candidate2 === base || candidate2.startsWith(base + path.sep);
36715
+ const isInside3 = (base, candidate2) => candidate2 === base || candidate2.startsWith(base + path.sep);
36194
36716
  const lexicalRoot = path.resolve(root);
36195
36717
  const candidate = path.resolve(lexicalRoot, requested);
36196
- if (!isInside(lexicalRoot, candidate)) {
36718
+ if (!isInside3(lexicalRoot, candidate)) {
36197
36719
  throw new UploadError(`the attachment path must be inside ${lexicalRoot}`);
36198
36720
  }
36199
36721
  let realFile;
@@ -36204,7 +36726,7 @@ async function resolveConfined(root, requested) {
36204
36726
  } catch (err) {
36205
36727
  throw new UploadError(`the downloaded attachment could not be found: ${err.message}`);
36206
36728
  }
36207
- if (!isInside(realRoot, realFile)) {
36729
+ if (!isInside3(realRoot, realFile)) {
36208
36730
  throw new UploadError(`the attachment path must be inside ${realRoot}`);
36209
36731
  }
36210
36732
  const info = await stat(realFile);
@@ -36231,7 +36753,7 @@ async function putFile(target, file2, bytes, contentType, timeoutMs) {
36231
36753
  const { createReadStream } = await import("node:fs");
36232
36754
  const { pipeline } = await import("node:stream");
36233
36755
  const transport = target.protocol === "https:" ? await import("node:https") : await import("node:http");
36234
- return new Promise((resolve, reject) => {
36756
+ return new Promise((resolve4, reject) => {
36235
36757
  let settled = false;
36236
36758
  const source = createReadStream(file2);
36237
36759
  const req = transport.request(target, {
@@ -36261,7 +36783,7 @@ async function putFile(target, file2, bytes, contentType, timeoutMs) {
36261
36783
  held += chunk.length;
36262
36784
  });
36263
36785
  res.on("error", fail);
36264
- res.on("end", () => finish2(() => resolve({
36786
+ res.on("end", () => finish2(() => resolve4({
36265
36787
  status: res.statusCode,
36266
36788
  body: Buffer.concat(chunks).toString().slice(0, ERROR_BODY_SNIPPET)
36267
36789
  })));
@@ -36293,11 +36815,13 @@ async function uploadToBlobStore(request, options = {}) {
36293
36815
  }
36294
36816
  export {
36295
36817
  ATTACHMENT_DOWNLOAD_ROOT,
36818
+ ATTACHMENT_TTL_MS,
36296
36819
  AUTH_BOOTSTRAP_MARKER,
36297
36820
  BASE_TOOL_REGISTRARS,
36298
36821
  BLOB_UPLOAD_TIMEOUT_MS,
36299
36822
  BLOB_URL_DEFAULT_TTL_MS,
36300
36823
  BLOB_URL_MAX_TTL_MS,
36824
+ FILE_ROOTS_ENV,
36301
36825
  INLINE_ATTACHMENT_LIMITS_TEXT,
36302
36826
  MAX_BLOB_UPLOAD_BYTES,
36303
36827
  MAX_INLINE_ATTACHMENT_BYTES,
@@ -36311,14 +36835,21 @@ export {
36311
36835
  appendReplyFlags,
36312
36836
  assertNotBoth,
36313
36837
  attachInlineParam,
36838
+ attachmentNames,
36314
36839
  authToolsFor,
36315
36840
  blobStoreFromEnv,
36841
+ bodyPreview,
36316
36842
  bootstrapGogAuth,
36843
+ confineAtFile,
36844
+ confinePath,
36845
+ confinePaths,
36317
36846
  createBlobUrlMinter,
36847
+ defaultFileRoot,
36318
36848
  diagnose,
36319
36849
  errorText,
36320
36850
  extractEmails,
36321
36851
  fetchGmailPages,
36852
+ fileRoots,
36322
36853
  finalizeGmailSearch,
36323
36854
  ids,
36324
36855
  inlineAttachmentArgs,
@@ -36331,6 +36862,8 @@ export {
36331
36862
  pageTokenParam,
36332
36863
  paginationParams,
36333
36864
  payloadArg,
36865
+ pos,
36866
+ prepareDownloadRoot,
36334
36867
  pushPaginationFlags,
36335
36868
  registerApiTools,
36336
36869
  registerAppScriptTools,
@@ -36346,6 +36879,7 @@ export {
36346
36879
  registerSheetsTools,
36347
36880
  registerSlidesTools,
36348
36881
  registerTasksTools,
36882
+ removeDownload,
36349
36883
  replySchema,
36350
36884
  requireGmailDispatchConfirmation,
36351
36885
  resolvePageToken,