gogcli-mcp 4.2.5 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +1340 -262
- package/dist/lib.js +1381 -260
- package/manifest.json +9 -2
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/arg-guard.ts +68 -0
- package/src/argv.ts +27 -0
- package/src/attachment-root.ts +111 -0
- package/src/attachments.ts +1 -0
- package/src/blob-upload.ts +4 -2
- package/src/dispatch-confirmation.ts +277 -0
- package/src/file-roots.ts +66 -0
- package/src/gmail-dispatch-guard.ts +70 -30
- package/src/gmail-results.ts +21 -2
- package/src/lib.ts +15 -0
- package/src/run-path-guard.ts +118 -0
- package/src/runner.ts +86 -18
- package/src/send-confirm-token.ts +167 -0
- package/src/tools/api.ts +60 -7
- package/src/tools/appscript.ts +12 -8
- package/src/tools/auth.ts +13 -3
- package/src/tools/calendar.ts +178 -15
- package/src/tools/chat.ts +94 -18
- package/src/tools/classroom.ts +110 -28
- package/src/tools/contacts.ts +5 -3
- package/src/tools/docs.ts +8 -6
- package/src/tools/drive.ts +96 -20
- package/src/tools/gmail.ts +175 -24
- package/src/tools/sheets.ts +10 -8
- package/src/tools/slides.ts +14 -9
- package/src/tools/tasks.ts +7 -5
- package/src/tools/utils.ts +52 -6
- package/tests/arg-guard.test.ts +80 -0
- package/tests/attachment-root.test.ts +130 -0
- package/tests/attachments.test.ts +8 -0
- package/tests/blob-upload.test.ts +4 -3
- package/tests/file-roots.test.ts +101 -0
- package/tests/gmail-dispatch-guard.test.ts +270 -11
- package/tests/gmail-results.test.ts +35 -1
- package/tests/run-path-guard.test.ts +142 -0
- package/tests/runner.test.ts +136 -0
- package/tests/send-confirm-token.test.ts +200 -0
- package/tests/tools/api.test.ts +74 -8
- package/tests/tools/appscript.test.ts +34 -8
- package/tests/tools/auth.test.ts +44 -17
- package/tests/tools/calendar.test.ts +33 -28
- package/tests/tools/chat.test.ts +50 -21
- package/tests/tools/classroom.test.ts +43 -39
- package/tests/tools/contacts.test.ts +3 -2
- package/tests/tools/dispatch-gates.test.ts +396 -0
- package/tests/tools/docs.test.ts +44 -15
- package/tests/tools/drive.test.ts +110 -20
- package/tests/tools/gmail-confirm-token.test.ts +274 -0
- package/tests/tools/gmail.test.ts +227 -29
- package/tests/tools/run-tool-examples.test.ts +69 -0
- package/tests/tools/run-vets.test.ts +131 -0
- package/tests/tools/sheets.test.ts +16 -15
- package/tests/tools/slides.test.ts +47 -11
- package/tests/tools/tasks.test.ts +7 -6
- package/tests/tools/utils.test.ts +32 -31
- 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 = (
|
|
2005
|
-
const escapedDelim = escapeRegex(
|
|
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,
|
|
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,
|
|
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,
|
|
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 ?? (
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
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
|
|
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)
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
28177
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
28178
28178
|
const target = {};
|
|
28179
28179
|
if (!skipNormalization) {
|
|
28180
28180
|
base = parse3(serialize(base, options), options);
|
|
28181
|
-
|
|
28181
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
28182
28182
|
}
|
|
28183
28183
|
options = options || {};
|
|
28184
|
-
if (!options.tolerant &&
|
|
28185
|
-
target.scheme =
|
|
28186
|
-
target.userinfo =
|
|
28187
|
-
target.host =
|
|
28188
|
-
target.port =
|
|
28189
|
-
target.path = removeDotSegments(
|
|
28190
|
-
target.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 (
|
|
28193
|
-
target.userinfo =
|
|
28194
|
-
target.host =
|
|
28195
|
-
target.port =
|
|
28196
|
-
target.path = removeDotSegments(
|
|
28197
|
-
target.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 (!
|
|
28199
|
+
if (!relative3.path) {
|
|
28200
28200
|
target.path = base.path;
|
|
28201
|
-
if (
|
|
28201
|
+
if (relative3.query !== void 0) target.query = relative3.query;
|
|
28202
28202
|
else target.query = base.query;
|
|
28203
28203
|
} else {
|
|
28204
|
-
if (
|
|
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 = "/" +
|
|
28207
|
-
else if (!base.path) target.path =
|
|
28208
|
-
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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
|
|
29127
|
+
let pos2 = 0;
|
|
29128
29128
|
let value;
|
|
29129
|
-
while (
|
|
29129
|
+
while (pos2 < len) {
|
|
29130
29130
|
length++;
|
|
29131
|
-
value = str.charCodeAt(
|
|
29132
|
-
if (value >= 55296 && value <= 56319 &&
|
|
29133
|
-
value = str.charCodeAt(
|
|
29134
|
-
if ((value & 64512) === 56320)
|
|
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 = /([?&](?:
|
|
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 =
|
|
32906
|
-
|
|
32907
|
-
|
|
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
|
|
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";
|
|
@@ -33126,9 +33289,9 @@ function offsetAt(instant, tz) {
|
|
|
33126
33289
|
const w = wallPartsIn(instant, tz);
|
|
33127
33290
|
const asUTC = Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second, instant.getUTCMilliseconds());
|
|
33128
33291
|
const minutes = Math.round((asUTC - instant.getTime()) / 6e4);
|
|
33129
|
-
const
|
|
33292
|
+
const sign3 = minutes < 0 ? "-" : "+";
|
|
33130
33293
|
const abs = Math.abs(minutes);
|
|
33131
|
-
return `${
|
|
33294
|
+
return `${sign3}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
33132
33295
|
}
|
|
33133
33296
|
function wallPartsIn(instant, tz) {
|
|
33134
33297
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
@@ -33301,9 +33464,48 @@ 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
33511
|
var MIN_GOG_VERSION = "0.41.0";
|
|
@@ -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(
|
|
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 =
|
|
33603
|
+
const sub = join2(dir, String(seq));
|
|
33402
33604
|
seq += 1;
|
|
33403
33605
|
await mkdir(sub, { recursive: true, mode: 448 });
|
|
33404
|
-
const path =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
33677
|
+
resolve4(stdout + "\n" + stderr);
|
|
33466
33678
|
} else {
|
|
33467
|
-
|
|
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(...
|
|
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
|
|
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
|
-
|
|
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) {
|
|
@@ -33766,7 +34199,240 @@ function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
|
33766
34199
|
}
|
|
33767
34200
|
}
|
|
33768
34201
|
|
|
34202
|
+
// src/dispatch-confirmation.ts
|
|
34203
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
34204
|
+
import { readFileSync } from "node:fs";
|
|
34205
|
+
|
|
34206
|
+
// src/send-confirm-token.ts
|
|
34207
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
34208
|
+
var CONFIRM_TOKEN_TTL_DEFAULT_SECONDS = 600;
|
|
34209
|
+
var PREFIX = "gct1";
|
|
34210
|
+
var secret;
|
|
34211
|
+
var spent = /* @__PURE__ */ new Map();
|
|
34212
|
+
function sendConfirmFallbackEnabled() {
|
|
34213
|
+
return readEnvVar("GOG_SEND_CONFIRM_FALLBACK")?.trim().toLowerCase() === "token";
|
|
34214
|
+
}
|
|
34215
|
+
function confirmTokenTtlSeconds() {
|
|
34216
|
+
const raw = readEnvVar("GOG_CONFIRM_TTL_SECONDS")?.trim();
|
|
34217
|
+
if (raw && /^\d+$/.test(raw)) {
|
|
34218
|
+
const n = Number(raw);
|
|
34219
|
+
if (n > 0) return n;
|
|
34220
|
+
}
|
|
34221
|
+
return CONFIRM_TOKEN_TTL_DEFAULT_SECONDS;
|
|
34222
|
+
}
|
|
34223
|
+
function hmacKey() {
|
|
34224
|
+
if (!secret) {
|
|
34225
|
+
const configured = readEnvVar("GOG_CONFIRM_SECRET");
|
|
34226
|
+
secret = configured ? Buffer.from(configured, "utf8") : randomBytes(32);
|
|
34227
|
+
}
|
|
34228
|
+
return secret;
|
|
34229
|
+
}
|
|
34230
|
+
function canonicalize(value) {
|
|
34231
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
34232
|
+
if (value && typeof value === "object") {
|
|
34233
|
+
const out = {};
|
|
34234
|
+
for (const key of Object.keys(value).sort()) {
|
|
34235
|
+
const v = value[key];
|
|
34236
|
+
if (v !== void 0) out[key] = canonicalize(v);
|
|
34237
|
+
}
|
|
34238
|
+
return out;
|
|
34239
|
+
}
|
|
34240
|
+
return value;
|
|
34241
|
+
}
|
|
34242
|
+
function hashSendPayload(payload) {
|
|
34243
|
+
return createHash2("sha256").update(JSON.stringify(canonicalize(payload))).digest("hex");
|
|
34244
|
+
}
|
|
34245
|
+
function sign(body) {
|
|
34246
|
+
return createHmac2("sha256", hmacKey()).update(`${PREFIX}.${body}`).digest("base64url");
|
|
34247
|
+
}
|
|
34248
|
+
function issueConfirmToken(binding, now = Date.now()) {
|
|
34249
|
+
const exp = now + confirmTokenTtlSeconds() * 1e3;
|
|
34250
|
+
const claims = {
|
|
34251
|
+
t: binding.tool,
|
|
34252
|
+
a: binding.account,
|
|
34253
|
+
g: binding.target,
|
|
34254
|
+
...binding.revision === void 0 ? {} : { r: binding.revision },
|
|
34255
|
+
h: binding.payloadHash,
|
|
34256
|
+
iat: now,
|
|
34257
|
+
exp,
|
|
34258
|
+
n: randomBytes(16).toString("base64url")
|
|
34259
|
+
};
|
|
34260
|
+
const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
|
|
34261
|
+
return { token: `${PREFIX}.${body}.${sign(body)}`, expiresAt: new Date(exp).toISOString() };
|
|
34262
|
+
}
|
|
34263
|
+
function signatureMatches(body, sig) {
|
|
34264
|
+
const expected = Buffer.from(sign(body), "utf8");
|
|
34265
|
+
const actual = Buffer.from(sig, "utf8");
|
|
34266
|
+
return expected.length === actual.length && timingSafeEqual2(expected, actual);
|
|
34267
|
+
}
|
|
34268
|
+
function parseClaims(body) {
|
|
34269
|
+
try {
|
|
34270
|
+
return JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
34271
|
+
} catch {
|
|
34272
|
+
return void 0;
|
|
34273
|
+
}
|
|
34274
|
+
}
|
|
34275
|
+
function verifyConfirmToken(token, binding, now = Date.now()) {
|
|
34276
|
+
for (const [nonce, exp] of spent) if (exp < now) spent.delete(nonce);
|
|
34277
|
+
const parts = token.split(".");
|
|
34278
|
+
if (parts.length !== 3 || parts[0] !== PREFIX || !parts[1] || !parts[2]) return { ok: false, error: "TOKEN_INVALID" };
|
|
34279
|
+
const [, body, sig] = parts;
|
|
34280
|
+
if (!signatureMatches(body, sig)) return { ok: false, error: "TOKEN_INVALID" };
|
|
34281
|
+
const claims = parseClaims(body);
|
|
34282
|
+
if (!claims) return { ok: false, error: "TOKEN_INVALID" };
|
|
34283
|
+
if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
|
|
34284
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
34285
|
+
}
|
|
34286
|
+
if (spent.has(claims.n)) return { ok: false, error: "TOKEN_REUSED" };
|
|
34287
|
+
if (now > claims.exp) return { ok: false, error: "TOKEN_EXPIRED" };
|
|
34288
|
+
if (claims.r !== binding.revision) return { ok: false, error: "DRAFT_CHANGED", reason: "message-id-rotated" };
|
|
34289
|
+
if (claims.h !== binding.payloadHash) return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
|
|
34290
|
+
spent.set(claims.n, claims.exp);
|
|
34291
|
+
return { ok: true };
|
|
34292
|
+
}
|
|
34293
|
+
|
|
34294
|
+
// src/dispatch-confirmation.ts
|
|
34295
|
+
var BODY_PREVIEW_MAX = 2048;
|
|
34296
|
+
function bodyPreview(text) {
|
|
34297
|
+
if (!text) return void 0;
|
|
34298
|
+
if (text.length <= BODY_PREVIEW_MAX) return text;
|
|
34299
|
+
return `${text.slice(0, BODY_PREVIEW_MAX)}\u2026 [${text.length - BODY_PREVIEW_MAX} more characters not shown]`;
|
|
34300
|
+
}
|
|
34301
|
+
function attachmentNames(paths, inline) {
|
|
34302
|
+
return [...paths ?? [], ...(inline ?? []).map((a) => a.filename)];
|
|
34303
|
+
}
|
|
34304
|
+
function attachmentDetails(paths, inline) {
|
|
34305
|
+
const out = [];
|
|
34306
|
+
for (const path of paths ?? []) {
|
|
34307
|
+
let bytes;
|
|
34308
|
+
try {
|
|
34309
|
+
bytes = readFileSync(path);
|
|
34310
|
+
} catch {
|
|
34311
|
+
bytes = void 0;
|
|
34312
|
+
}
|
|
34313
|
+
out.push(bytes ? { name: path, size: bytes.length, sha256: createHash3("sha256").update(bytes).digest("hex") } : { name: path, size: null });
|
|
34314
|
+
}
|
|
34315
|
+
for (const a of inline ?? []) {
|
|
34316
|
+
out.push({
|
|
34317
|
+
name: a.filename,
|
|
34318
|
+
size: Buffer.from(a.contentBase64, "base64").length,
|
|
34319
|
+
sha256: createHash3("sha256").update(a.contentBase64).digest("hex")
|
|
34320
|
+
});
|
|
34321
|
+
}
|
|
34322
|
+
return out;
|
|
34323
|
+
}
|
|
34324
|
+
function senderPreview(account, from) {
|
|
34325
|
+
return from ?? account ?? readEnvVar("GOG_ACCOUNT") ?? "the gog account's default address";
|
|
34326
|
+
}
|
|
34327
|
+
function attachmentPreview(details) {
|
|
34328
|
+
return details.map(({ name, size }) => ({ name, size }));
|
|
34329
|
+
}
|
|
34330
|
+
var CONFIRM_SEND_INSTRUCTION = "Show this preview to the user verbatim and send only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
34331
|
+
var CONFIRM_ACTION_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
34332
|
+
var FALLBACK_HINT = "Or set GOG_SEND_CONFIRM_FALLBACK=token to enable two-step confirmation.";
|
|
34333
|
+
var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai) and the server sets GOG_SEND_CONFIRM_FALLBACK=token, a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Show that preview to the user verbatim; only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken. The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing.';
|
|
34334
|
+
var confirmTokenParam = external_exports.string().optional().describe(
|
|
34335
|
+
`ONLY for the two-step fallback (client without MCP elicitation, server with GOG_SEND_CONFIRM_FALLBACK=token). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`
|
|
34336
|
+
);
|
|
34337
|
+
var TOKEN_ERROR_NOTE = {
|
|
34338
|
+
TOKEN_EXPIRED: "Nothing was sent or changed: the confirmToken expired. Call again WITHOUT confirmToken for a fresh preview, and ask the user to approve it again.",
|
|
34339
|
+
TOKEN_REUSED: "Nothing was sent or changed by this call: this confirmToken was already used, and one approval acts once. If doing it again is really intended, call again WITHOUT confirmToken and get a new approval.",
|
|
34340
|
+
TOKEN_INVALID: "Nothing was sent or changed: this confirmToken was not issued by this server for this tool, account and target (or the server has restarted since). Call again WITHOUT confirmToken for a fresh preview and approval."
|
|
34341
|
+
};
|
|
34342
|
+
var DRAFT_CHANGED_NOTE = {
|
|
34343
|
+
"message-id-rotated": "Nothing was sent or changed: the target was edited since the user approved it (a draft's messageId or an event's version rotated), so what would happen is not what they saw.",
|
|
34344
|
+
"payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
|
|
34345
|
+
};
|
|
34346
|
+
function isToolResult(value) {
|
|
34347
|
+
return Array.isArray(value.content);
|
|
34348
|
+
}
|
|
34349
|
+
function rejection(data) {
|
|
34350
|
+
return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
|
|
34351
|
+
}
|
|
34352
|
+
async function tokenConfirmation(op, fallback) {
|
|
34353
|
+
const subject = await fallback.subject();
|
|
34354
|
+
if (isToolResult(subject)) return subject;
|
|
34355
|
+
const binding = {
|
|
34356
|
+
tool: fallback.tool,
|
|
34357
|
+
account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
|
|
34358
|
+
target: subject.target,
|
|
34359
|
+
...subject.revision === void 0 ? {} : { revision: subject.revision },
|
|
34360
|
+
payloadHash: hashSendPayload(subject.payload)
|
|
34361
|
+
};
|
|
34362
|
+
const phaseOne = () => {
|
|
34363
|
+
const { token, expiresAt } = issueConfirmToken(binding);
|
|
34364
|
+
return {
|
|
34365
|
+
action: op,
|
|
34366
|
+
preview: subject.preview,
|
|
34367
|
+
confirmToken: token,
|
|
34368
|
+
expiresAt,
|
|
34369
|
+
ttlSeconds: confirmTokenTtlSeconds(),
|
|
34370
|
+
instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION
|
|
34371
|
+
};
|
|
34372
|
+
};
|
|
34373
|
+
if (!fallback.confirmToken) {
|
|
34374
|
+
return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
|
|
34375
|
+
}
|
|
34376
|
+
const verdict = verifyConfirmToken(fallback.confirmToken, binding);
|
|
34377
|
+
if (verdict.ok) return void 0;
|
|
34378
|
+
if (verdict.error === "DRAFT_CHANGED") {
|
|
34379
|
+
return rejection({
|
|
34380
|
+
error: "DRAFT_CHANGED",
|
|
34381
|
+
reason: verdict.reason,
|
|
34382
|
+
note: `${DRAFT_CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
|
|
34383
|
+
...phaseOne()
|
|
34384
|
+
});
|
|
34385
|
+
}
|
|
34386
|
+
return rejection({ error: verdict.error, action: op, note: TOKEN_ERROR_NOTE[verdict.error] });
|
|
34387
|
+
}
|
|
34388
|
+
function gatedElsewhere(what, via, does, tool) {
|
|
34389
|
+
return `${what} ${does} and is not available through ${via}. Use ${tool}, which asks the user to confirm.`;
|
|
34390
|
+
}
|
|
34391
|
+
function hasCommandWord(args, words) {
|
|
34392
|
+
return args.find((a) => words.has(a.toLowerCase()));
|
|
34393
|
+
}
|
|
34394
|
+
async function requireDispatchConfirmation(ctx, options) {
|
|
34395
|
+
const { action, fallback } = options;
|
|
34396
|
+
if (fallback && callerAcceptsFormElicitation(ctx) === false && sendConfirmFallbackEnabled()) {
|
|
34397
|
+
return tokenConfirmation(action, fallback);
|
|
34398
|
+
}
|
|
34399
|
+
const note = fallback ? [options.unsupportedNote, FALLBACK_HINT].filter(Boolean).join(" ") : options.unsupportedNote;
|
|
34400
|
+
return requireConfirmation(ctx, {
|
|
34401
|
+
action,
|
|
34402
|
+
message: options.message,
|
|
34403
|
+
details: options.details,
|
|
34404
|
+
confirmationLabel: options.confirmationLabel,
|
|
34405
|
+
...note ? { unsupportedNote: note } : {}
|
|
34406
|
+
});
|
|
34407
|
+
}
|
|
34408
|
+
function resultText(result) {
|
|
34409
|
+
const first = result.content[0];
|
|
34410
|
+
return first && first.type === "text" && typeof first.text === "string" ? first.text : "{}";
|
|
34411
|
+
}
|
|
34412
|
+
|
|
33769
34413
|
// src/tools/api.ts
|
|
34414
|
+
var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
|
|
34415
|
+
var DISPATCH_API_BLOCKED = {
|
|
34416
|
+
chat: [{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" }],
|
|
34417
|
+
drive: [{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" }],
|
|
34418
|
+
classroom: [
|
|
34419
|
+
{ method: /(?:^|\.)courses\.announcements\.create$/i, does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
34420
|
+
{ method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
|
|
34421
|
+
],
|
|
34422
|
+
calendar: [
|
|
34423
|
+
{ method: /(?:^|\.)events\.(?:insert|import|quickadd)$/i, does: "can put an event on guests' calendars", tool: "gog_calendar_create" },
|
|
34424
|
+
{ method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" }
|
|
34425
|
+
]
|
|
34426
|
+
};
|
|
34427
|
+
function refusedApiCall(api, method) {
|
|
34428
|
+
const name = api.trim().toLowerCase();
|
|
34429
|
+
const m = method.trim();
|
|
34430
|
+
if (name === "gmail" && GMAIL_API_BLOCKED.test(m)) {
|
|
34431
|
+
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.`;
|
|
34432
|
+
}
|
|
34433
|
+
const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
|
|
34434
|
+
return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
|
|
34435
|
+
}
|
|
33770
34436
|
function registerApiTools(server) {
|
|
33771
34437
|
server.registerTool("gog_api_list", {
|
|
33772
34438
|
description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
|
|
@@ -33790,12 +34456,12 @@ function registerApiTools(server) {
|
|
|
33790
34456
|
account: accountParam
|
|
33791
34457
|
})
|
|
33792
34458
|
}, async ({ api, version: version2, method, account }) => {
|
|
33793
|
-
const args = ["api", "describe", api, version2];
|
|
33794
|
-
if (method) args.push(method);
|
|
34459
|
+
const args = ["api", "describe", pos(api), pos(version2)];
|
|
34460
|
+
if (method) args.push(pos(method));
|
|
33795
34461
|
return runOrDiagnose(args, { account });
|
|
33796
34462
|
});
|
|
33797
34463
|
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.",
|
|
34464
|
+
description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true \u2014 keep it false to preview, or set dryRun=true to print the intended request without sending it. Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch.",
|
|
33799
34465
|
annotations: { destructiveHint: true },
|
|
33800
34466
|
inputSchema: external_exports.object({
|
|
33801
34467
|
api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
|
|
@@ -33809,14 +34475,23 @@ function registerApiTools(server) {
|
|
|
33809
34475
|
account: accountParam
|
|
33810
34476
|
})
|
|
33811
34477
|
}, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
|
|
33812
|
-
|
|
34478
|
+
try {
|
|
34479
|
+
assertSafeForwardedArgs([api, version2, method]);
|
|
34480
|
+
const refusal = refusedApiCall(api, method);
|
|
34481
|
+
if (refusal) throw new Error(refusal);
|
|
34482
|
+
if (body) confineAtFile(body, "body");
|
|
34483
|
+
if (params) confineAtFile(params, "params");
|
|
34484
|
+
} catch (err) {
|
|
34485
|
+
return errorResult(errorText(err));
|
|
34486
|
+
}
|
|
34487
|
+
const args = ["api", "call", pos(api), pos(version2), pos(method)];
|
|
33813
34488
|
if (params) args.push(`--params=${params}`);
|
|
33814
34489
|
if (body) args.push(`--body=${body}`);
|
|
33815
34490
|
if (scope) args.push(`--scope=${scope}`);
|
|
33816
34491
|
if (allowWrite) args.push("--allow-write");
|
|
33817
34492
|
if (dryRun) args.push("--dry-run");
|
|
33818
34493
|
if (allowWrite) args.push("--force");
|
|
33819
|
-
return runOrDiagnose(args, { account });
|
|
34494
|
+
return runOrDiagnose(args, { account, gmailNoSend: true });
|
|
33820
34495
|
});
|
|
33821
34496
|
}
|
|
33822
34497
|
|
|
@@ -33834,7 +34509,7 @@ function registerAppScriptTools(server) {
|
|
|
33834
34509
|
account: accountParam
|
|
33835
34510
|
})
|
|
33836
34511
|
}, async ({ scriptId, account }) => {
|
|
33837
|
-
return runOrDiagnose(["appscript", "get", scriptId], { account });
|
|
34512
|
+
return runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
|
|
33838
34513
|
});
|
|
33839
34514
|
server.registerTool("gog_appscript_content", {
|
|
33840
34515
|
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 +34519,20 @@ function registerAppScriptTools(server) {
|
|
|
33844
34519
|
account: accountParam
|
|
33845
34520
|
})
|
|
33846
34521
|
}, async ({ scriptId, account }) => {
|
|
33847
|
-
return runOrDiagnose(["appscript", "content", scriptId], { account });
|
|
34522
|
+
return runOrDiagnose(["appscript", "content", pos(scriptId)], { account });
|
|
33848
34523
|
});
|
|
33849
34524
|
server.registerTool("gog_appscript_pull", {
|
|
33850
34525
|
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
34526
|
annotations: { destructiveHint: true },
|
|
33852
34527
|
inputSchema: external_exports.object({
|
|
33853
34528
|
scriptId: scriptIdParam,
|
|
33854
|
-
dir: external_exports.string().describe("Destination directory, resolved on the machine where gog runs"),
|
|
34529
|
+
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
34530
|
overwrite: external_exports.boolean().optional().describe("Overwrite files that already exist in dir"),
|
|
33856
34531
|
account: accountParam
|
|
33857
34532
|
})
|
|
33858
34533
|
}, async ({ scriptId, dir, overwrite, account }) => {
|
|
33859
|
-
|
|
34534
|
+
confinePath(dir, "dir");
|
|
34535
|
+
const args = ["appscript", "pull", pos(scriptId), pos(dir)];
|
|
33860
34536
|
if (overwrite) args.push("--overwrite");
|
|
33861
34537
|
return runOrDiagnose(args, { account });
|
|
33862
34538
|
});
|
|
@@ -33882,7 +34558,7 @@ function registerAppScriptTools(server) {
|
|
|
33882
34558
|
account: accountParam
|
|
33883
34559
|
})
|
|
33884
34560
|
}, async ({ scriptId, max, pageToken, page, all, account }) => {
|
|
33885
|
-
const args = ["appscript", "deployments", scriptId];
|
|
34561
|
+
const args = ["appscript", "deployments", pos(scriptId)];
|
|
33886
34562
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
33887
34563
|
return runOrDiagnose(args, { account });
|
|
33888
34564
|
});
|
|
@@ -33895,7 +34571,7 @@ function registerAppScriptTools(server) {
|
|
|
33895
34571
|
account: accountParam
|
|
33896
34572
|
})
|
|
33897
34573
|
}, async ({ scriptId, max, pageToken, page, all, account }) => {
|
|
33898
|
-
const args = ["appscript", "versions", scriptId];
|
|
34574
|
+
const args = ["appscript", "versions", pos(scriptId)];
|
|
33899
34575
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
33900
34576
|
return runOrDiagnose(args, { account });
|
|
33901
34577
|
});
|
|
@@ -33921,7 +34597,7 @@ function registerAppScriptTools(server) {
|
|
|
33921
34597
|
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
34598
|
}
|
|
33923
34599
|
}
|
|
33924
|
-
const args = ["appscript", "run", scriptId, functionName];
|
|
34600
|
+
const args = ["appscript", "run", pos(scriptId), pos(functionName)];
|
|
33925
34601
|
if (params !== void 0) args.push(`--params=${params}`);
|
|
33926
34602
|
if (devMode) args.push("--dev-mode");
|
|
33927
34603
|
return runOrDiagnose(args, { account });
|
|
@@ -33934,6 +34610,7 @@ function registerAppScriptTools(server) {
|
|
|
33934
34610
|
}
|
|
33935
34611
|
|
|
33936
34612
|
// src/tools/auth.ts
|
|
34613
|
+
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
|
|
33937
34614
|
function registerAuthToolsWith(server, defaultServices) {
|
|
33938
34615
|
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
34616
|
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 +34668,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
33991
34668
|
})
|
|
33992
34669
|
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
33993
34670
|
try {
|
|
33994
|
-
const args = ["auth", "add", email3, "--services", services];
|
|
34671
|
+
const args = ["auth", "add", pos(email3), "--services", services];
|
|
33995
34672
|
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
|
|
33996
34673
|
return rawTextResult(await run(args, {
|
|
33997
34674
|
interactive: true,
|
|
@@ -34011,7 +34688,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
34011
34688
|
})
|
|
34012
34689
|
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
34013
34690
|
try {
|
|
34014
|
-
const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
34691
|
+
const args = ["auth", "add", pos(email3), "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
34015
34692
|
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
34016
34693
|
return rawTextResult(await run(args, { redactMode: "tokens" }));
|
|
34017
34694
|
} catch (err) {
|
|
@@ -34038,7 +34715,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
34038
34715
|
const args = [
|
|
34039
34716
|
"auth",
|
|
34040
34717
|
"add",
|
|
34041
|
-
email3,
|
|
34718
|
+
pos(email3),
|
|
34042
34719
|
"--remote",
|
|
34043
34720
|
"--step",
|
|
34044
34721
|
"2",
|
|
@@ -34058,6 +34735,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
34058
34735
|
service: "auth",
|
|
34059
34736
|
examples: '"remove", "alias", "list"',
|
|
34060
34737
|
omitAccount: true,
|
|
34738
|
+
allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
|
|
34061
34739
|
note: "For browser-based authorization, use gog_auth_add instead."
|
|
34062
34740
|
});
|
|
34063
34741
|
}
|
|
@@ -34069,6 +34747,54 @@ function authToolsFor(defaultServices) {
|
|
|
34069
34747
|
}
|
|
34070
34748
|
|
|
34071
34749
|
// src/tools/calendar.ts
|
|
34750
|
+
var CALENDAR_GATED = {
|
|
34751
|
+
create: "gog_calendar_create",
|
|
34752
|
+
add: "gog_calendar_create",
|
|
34753
|
+
new: "gog_calendar_create",
|
|
34754
|
+
update: "gog_calendar_update",
|
|
34755
|
+
edit: "gog_calendar_update",
|
|
34756
|
+
set: "gog_calendar_update",
|
|
34757
|
+
respond: "gog_calendar_respond",
|
|
34758
|
+
rsvp: "gog_calendar_respond",
|
|
34759
|
+
reply: "gog_calendar_respond"
|
|
34760
|
+
};
|
|
34761
|
+
function vetCalendarRun(subcommand, _args) {
|
|
34762
|
+
const sub = subcommand.toLowerCase();
|
|
34763
|
+
const tool = Object.hasOwn(CALENDAR_GATED, sub) ? CALENDAR_GATED[sub] : void 0;
|
|
34764
|
+
return tool ? gatedElsewhere(`gog calendar ${sub}`, "gog_calendar_run", "can change what guests see", tool) : void 0;
|
|
34765
|
+
}
|
|
34766
|
+
function parseAttendees(value) {
|
|
34767
|
+
if (!value) return [];
|
|
34768
|
+
return value.split(",").map((entry) => entry.split(";")).filter((parts) => !parts.slice(1).some((m) => m.trim().toLowerCase() === "resource")).map((parts) => parts[0].trim()).filter(Boolean);
|
|
34769
|
+
}
|
|
34770
|
+
function timeOf(t) {
|
|
34771
|
+
const v = t?.dateTime ?? t?.date;
|
|
34772
|
+
return typeof v === "string" ? v : void 0;
|
|
34773
|
+
}
|
|
34774
|
+
function eventSnapshot(raw) {
|
|
34775
|
+
let event;
|
|
34776
|
+
try {
|
|
34777
|
+
event = JSON.parse(raw)?.event;
|
|
34778
|
+
} catch {
|
|
34779
|
+
event = void 0;
|
|
34780
|
+
}
|
|
34781
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
34782
|
+
return {
|
|
34783
|
+
summary: str(event?.summary),
|
|
34784
|
+
start: timeOf(event?.start),
|
|
34785
|
+
end: timeOf(event?.end),
|
|
34786
|
+
organizer: str(event?.organizer?.email),
|
|
34787
|
+
// Guests are the OTHER people: not this account's own attendee row, not rooms.
|
|
34788
|
+
guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
|
|
34789
|
+
etag: str(event?.etag)
|
|
34790
|
+
};
|
|
34791
|
+
}
|
|
34792
|
+
async function readEvent(calendarId, eventId, account) {
|
|
34793
|
+
const got = await runOrDiagnose(["calendar", "event", pos(calendarId), pos(eventId)], { account });
|
|
34794
|
+
if (got.isError) return { error: got };
|
|
34795
|
+
return { event: eventSnapshot(resultText(got)) };
|
|
34796
|
+
}
|
|
34797
|
+
var CALENDAR_CONFIRM_LABEL = "Confirm that this change, which other people will see, should be made now.";
|
|
34072
34798
|
var reminderParams = {
|
|
34073
34799
|
reminders: external_exports.array(external_exports.string()).max(5).optional().describe(
|
|
34074
34800
|
`Reminders as method:duration, e.g. ["popup:30m", "email:1d"]. Method is popup or email; duration accepts m/h/d (max 40320 minutes = 4 weeks). Google allows at most 5. These REPLACE the event's reminders \u2014 on update, pass an EMPTY array to drop custom reminders and go back to the calendar's defaults. Cannot be combined with noReminders.`
|
|
@@ -34122,7 +34848,7 @@ function registerCalendarTools(server) {
|
|
|
34122
34848
|
})
|
|
34123
34849
|
}, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, view, account }) => {
|
|
34124
34850
|
const args = ["calendar", "events"];
|
|
34125
|
-
if (calendarId) args.push(calendarId);
|
|
34851
|
+
if (calendarId) args.push(pos(calendarId));
|
|
34126
34852
|
if (from) args.push(`--from=${from}`);
|
|
34127
34853
|
if (to) args.push(`--to=${to}`);
|
|
34128
34854
|
if (days !== void 0) args.push(`--days=${days}`);
|
|
@@ -34151,12 +34877,12 @@ function registerCalendarTools(server) {
|
|
|
34151
34877
|
account: accountParam
|
|
34152
34878
|
})
|
|
34153
34879
|
}, async ({ calendarId, eventId, timezone, account }) => {
|
|
34154
|
-
const args = ["calendar", "event", calendarId, eventId];
|
|
34880
|
+
const args = ["calendar", "event", pos(calendarId), pos(eventId)];
|
|
34155
34881
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
34156
34882
|
return runOrDiagnose(args, { account });
|
|
34157
34883
|
});
|
|
34158
34884
|
server.registerTool("gog_calendar_create", {
|
|
34159
|
-
description: "Create a calendar event. Set withZoom=true to attach a Zoom meeting (requires Zoom S2S OAuth setup via gog_zoom_auth_setup; the join URL + meeting ID + passcode are appended to the event description \u2014 Google rejects native conference card writes from non-Workspace-Marketplace OAuth clients).",
|
|
34885
|
+
description: "Create a calendar event. Set withZoom=true to attach a Zoom meeting (requires Zoom S2S OAuth setup via gog_zoom_auth_setup; the join URL + meeting ID + passcode are appended to the event description \u2014 Google rejects native conference card writes from non-Workspace-Marketplace OAuth clients). With attendees, the event lands on other people's calendars (no invitation email is sent), so the MCP host is asked to show the user a confirmation prompt with the guests, time and details first; a guest-free event is created without asking." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
34160
34886
|
annotations: { destructiveHint: false },
|
|
34161
34887
|
inputSchema: external_exports.object({
|
|
34162
34888
|
calendarId: external_exports.string().describe('Calendar ID (use "primary" for the default calendar)'),
|
|
@@ -34170,10 +34896,11 @@ function registerCalendarTools(server) {
|
|
|
34170
34896
|
timezone: external_exports.string().optional().describe("IANA timezone metadata applied to from/to (e.g. America/New_York). Sets both start and end timezone unless start/end timezone are overridden."),
|
|
34171
34897
|
withZoom: external_exports.boolean().optional().describe("Create a Zoom video conference for this event (requires Zoom S2S OAuth setup)"),
|
|
34172
34898
|
...reminderParams,
|
|
34173
|
-
account: accountParam
|
|
34899
|
+
account: accountParam,
|
|
34900
|
+
confirmToken: confirmTokenParam
|
|
34174
34901
|
})
|
|
34175
|
-
}, 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}`];
|
|
34902
|
+
}, async ({ calendarId, summary, from, to, description, location, attendees, allDay, timezone, withZoom, reminders, noReminders, account, confirmToken }, ctx) => {
|
|
34903
|
+
const args = ["calendar", "create", pos(calendarId), `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
|
|
34177
34904
|
if (description) args.push(`--description=${description}`);
|
|
34178
34905
|
if (location) args.push(`--location=${location}`);
|
|
34179
34906
|
if (attendees) args.push(`--attendees=${attendees}`);
|
|
@@ -34181,10 +34908,28 @@ function registerCalendarTools(server) {
|
|
|
34181
34908
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
34182
34909
|
if (withZoom) args.push("--with-zoom");
|
|
34183
34910
|
pushReminderFlags(args, { reminders, noReminders });
|
|
34911
|
+
const guests = parseAttendees(attendees);
|
|
34912
|
+
if (guests.length > 0) {
|
|
34913
|
+
const event = { calendarId, summary, from, to, allDay: Boolean(allDay), timezone, location, description, guests, attendees, withZoom: Boolean(withZoom) };
|
|
34914
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
34915
|
+
action: "calendar.create",
|
|
34916
|
+
message: "Review and confirm this event, which will appear on the guests' calendars:",
|
|
34917
|
+
confirmationLabel: CALENDAR_CONFIRM_LABEL,
|
|
34918
|
+
details: event,
|
|
34919
|
+
unsupportedNote: "Create it without attendees instead; the user can add the guests in Google Calendar.",
|
|
34920
|
+
fallback: {
|
|
34921
|
+
tool: "gog_calendar_create",
|
|
34922
|
+
account,
|
|
34923
|
+
confirmToken,
|
|
34924
|
+
subject: () => ({ target: calendarId, payload: event, preview: event })
|
|
34925
|
+
}
|
|
34926
|
+
});
|
|
34927
|
+
if (confirmation) return confirmation;
|
|
34928
|
+
}
|
|
34184
34929
|
return runOrDiagnose(args, { account });
|
|
34185
34930
|
});
|
|
34186
34931
|
server.registerTool("gog_calendar_update", {
|
|
34187
|
-
description: "Update an existing calendar event. Zoom: withZoom adds a Zoom meeting, regenerateZoom replaces the existing one, removeZoom strips it. removeMeet clears the event's Google Meet conference data (e.g. before attaching another provider). Conference flags are independent \u2014 use one per call.",
|
|
34932
|
+
description: "Update an existing calendar event. Zoom: withZoom adds a Zoom meeting, regenerateZoom replaces the existing one, removeZoom strips it. removeMeet clears the event's Google Meet conference data (e.g. before attaching another provider). Conference flags are independent \u2014 use one per call. A change guests can see (time, title, place, description, attendees, attachments, conferencing) on an event that has guests \u2014 or gains them \u2014 reads the event and asks the MCP host to show the user a confirmation prompt with the event as it stands and the change; no invitation email is sent. Reminder-only changes and guest-free events are updated without asking." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
34188
34933
|
annotations: { destructiveHint: false },
|
|
34189
34934
|
inputSchema: external_exports.object({
|
|
34190
34935
|
calendarId: external_exports.string().describe("Calendar ID"),
|
|
@@ -34202,10 +34947,11 @@ function registerCalendarTools(server) {
|
|
|
34202
34947
|
removeZoom: external_exports.boolean().optional().describe("Remove the event's Zoom video conference"),
|
|
34203
34948
|
removeMeet: external_exports.boolean().optional().describe("Remove the event's Google Meet video conference (clears conference data only)"),
|
|
34204
34949
|
...reminderParams,
|
|
34205
|
-
account: accountParam
|
|
34950
|
+
account: accountParam,
|
|
34951
|
+
confirmToken: confirmTokenParam
|
|
34206
34952
|
})
|
|
34207
|
-
}, 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];
|
|
34953
|
+
}, async ({ calendarId, eventId, summary, from, to, description, location, attendees, addAttendees, attachments, withZoom, regenerateZoom, removeZoom, removeMeet, reminders, noReminders, account, confirmToken }, ctx) => {
|
|
34954
|
+
const args = ["calendar", "update", pos(calendarId), pos(eventId)];
|
|
34209
34955
|
if (summary !== void 0) args.push(`--summary=${summary}`);
|
|
34210
34956
|
if (from !== void 0) args.push(`--from=${from}`);
|
|
34211
34957
|
if (to !== void 0) args.push(`--to=${to}`);
|
|
@@ -34219,6 +34965,45 @@ function registerCalendarTools(server) {
|
|
|
34219
34965
|
if (removeZoom) args.push("--remove-zoom");
|
|
34220
34966
|
if (removeMeet) args.push("--remove-meet");
|
|
34221
34967
|
pushReminderFlags(args, { reminders, noReminders });
|
|
34968
|
+
const changes = Object.fromEntries(Object.entries({
|
|
34969
|
+
summary,
|
|
34970
|
+
from,
|
|
34971
|
+
to,
|
|
34972
|
+
description,
|
|
34973
|
+
location,
|
|
34974
|
+
attendees,
|
|
34975
|
+
addAttendees,
|
|
34976
|
+
attachments,
|
|
34977
|
+
withZoom,
|
|
34978
|
+
regenerateZoom,
|
|
34979
|
+
removeZoom,
|
|
34980
|
+
removeMeet
|
|
34981
|
+
}).filter(([, v]) => v !== void 0 && v !== false));
|
|
34982
|
+
if (Object.keys(changes).length > 0) {
|
|
34983
|
+
const read = await readEvent(calendarId, eventId, account);
|
|
34984
|
+
if (read.error) return read.error;
|
|
34985
|
+
const { etag, ...current } = read.event;
|
|
34986
|
+
const added = [...parseAttendees(attendees), ...parseAttendees(addAttendees)];
|
|
34987
|
+
if (current.guests.length > 0 || added.length > 0) {
|
|
34988
|
+
const view = { calendarId, eventId, current, changes };
|
|
34989
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
34990
|
+
action: "calendar.update",
|
|
34991
|
+
message: "Review and confirm this change to an event other people are on:",
|
|
34992
|
+
confirmationLabel: CALENDAR_CONFIRM_LABEL,
|
|
34993
|
+
details: view,
|
|
34994
|
+
unsupportedNote: "Ask the user to make this change in Google Calendar.",
|
|
34995
|
+
fallback: {
|
|
34996
|
+
tool: "gog_calendar_update",
|
|
34997
|
+
account,
|
|
34998
|
+
confirmToken,
|
|
34999
|
+
// The etag rotates on any edit, so an event changed elsewhere between
|
|
35000
|
+
// the two phases is DRAFT_CHANGED rather than edited over.
|
|
35001
|
+
subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
|
|
35002
|
+
}
|
|
35003
|
+
});
|
|
35004
|
+
if (confirmation) return confirmation;
|
|
35005
|
+
}
|
|
35006
|
+
}
|
|
34222
35007
|
return runOrDiagnose(args, { account });
|
|
34223
35008
|
});
|
|
34224
35009
|
server.registerTool("gog_calendar_delete", {
|
|
@@ -34230,24 +35015,43 @@ function registerCalendarTools(server) {
|
|
|
34230
35015
|
account: accountParam
|
|
34231
35016
|
})
|
|
34232
35017
|
}, async ({ calendarId, eventId, account }) => {
|
|
34233
|
-
return runOrDiagnose(["calendar", "delete", calendarId, eventId, "--force"], { account });
|
|
35018
|
+
return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
|
|
34234
35019
|
});
|
|
34235
35020
|
server.registerTool("gog_calendar_respond", {
|
|
34236
|
-
description: "Respond to a calendar event invitation.",
|
|
35021
|
+
description: "Respond to a calendar event invitation. The organizer sees the response, so this reads the event and asks the MCP host to show the user a confirmation prompt with the event, organizer, response and comment; nothing is recorded unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
34237
35022
|
annotations: { destructiveHint: true },
|
|
34238
35023
|
inputSchema: external_exports.object({
|
|
34239
35024
|
calendarId: external_exports.string().describe("Calendar ID"),
|
|
34240
35025
|
eventId: external_exports.string().describe("Event ID"),
|
|
34241
35026
|
status: external_exports.enum(["accepted", "declined", "tentative"]).describe("Response status"),
|
|
34242
35027
|
comment: external_exports.string().optional().describe("Optional comment to include with response"),
|
|
34243
|
-
account: accountParam
|
|
35028
|
+
account: accountParam,
|
|
35029
|
+
confirmToken: confirmTokenParam
|
|
34244
35030
|
})
|
|
34245
|
-
}, async ({ calendarId, eventId, status, comment, account }) => {
|
|
34246
|
-
const args = ["calendar", "respond", calendarId, eventId, `--status=${status}`];
|
|
35031
|
+
}, async ({ calendarId, eventId, status, comment, account, confirmToken }, ctx) => {
|
|
35032
|
+
const args = ["calendar", "respond", pos(calendarId), pos(eventId), `--status=${status}`];
|
|
34247
35033
|
if (comment) args.push(`--comment=${comment}`);
|
|
35034
|
+
const read = await readEvent(calendarId, eventId, account);
|
|
35035
|
+
if (read.error) return read.error;
|
|
35036
|
+
const { etag, ...event } = read.event;
|
|
35037
|
+
const view = { calendarId, eventId, event, response: status, ...comment ? { comment } : {} };
|
|
35038
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35039
|
+
action: "calendar.respond",
|
|
35040
|
+
message: "Review and confirm this response, which the organizer will see:",
|
|
35041
|
+
confirmationLabel: "Confirm that this response should be recorded now.",
|
|
35042
|
+
details: view,
|
|
35043
|
+
unsupportedNote: "Ask the user to respond from Google Calendar.",
|
|
35044
|
+
fallback: {
|
|
35045
|
+
tool: "gog_calendar_respond",
|
|
35046
|
+
account,
|
|
35047
|
+
confirmToken,
|
|
35048
|
+
subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
|
|
35049
|
+
}
|
|
35050
|
+
});
|
|
35051
|
+
if (confirmation) return confirmation;
|
|
34248
35052
|
return runOrDiagnose(args, { account });
|
|
34249
35053
|
});
|
|
34250
|
-
registerRunTool(server, { service: "calendar", examples: '"calendars", "freebusy"' });
|
|
35054
|
+
registerRunTool(server, { service: "calendar", examples: '"calendars", "freebusy"', vet: vetCalendarRun });
|
|
34251
35055
|
}
|
|
34252
35056
|
|
|
34253
35057
|
// src/attachments.ts
|
|
@@ -34258,6 +35062,7 @@ var MAX_REQUEST_PAYLOAD_WIRE_BYTES = REQUEST_MAX_BODY_BYTES - REQUEST_BODY_JSON_
|
|
|
34258
35062
|
var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
|
|
34259
35063
|
function wireBytesOf(arg) {
|
|
34260
35064
|
if (typeof arg === "string") return Buffer.byteLength(arg, "utf8");
|
|
35065
|
+
if (arg.kind === "positional") return Buffer.byteLength(arg.value, "utf8");
|
|
34261
35066
|
return arg.encoding === "base64" ? arg.contents.length : Buffer.byteLength(arg.contents, "utf8");
|
|
34262
35067
|
}
|
|
34263
35068
|
var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
|
|
@@ -34330,6 +35135,23 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
|
|
|
34330
35135
|
}
|
|
34331
35136
|
|
|
34332
35137
|
// src/tools/chat.ts
|
|
35138
|
+
var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
|
|
35139
|
+
function vetChatRun(subcommand, args) {
|
|
35140
|
+
const sub = subcommand.toLowerCase();
|
|
35141
|
+
if (sub !== "messages" && sub !== "dm") return void 0;
|
|
35142
|
+
const word = hasCommandWord(args, CHAT_SEND_WORDS);
|
|
35143
|
+
if (!word) return void 0;
|
|
35144
|
+
return gatedElsewhere(
|
|
35145
|
+
`gog chat ${sub} ${word.toLowerCase()}`,
|
|
35146
|
+
"gog_chat_run",
|
|
35147
|
+
"posts a message",
|
|
35148
|
+
sub === "dm" ? "gog_chat_dm_send" : "gog_chat_messages_send"
|
|
35149
|
+
);
|
|
35150
|
+
}
|
|
35151
|
+
var CHAT_CONFIRM = {
|
|
35152
|
+
message: "Review and confirm this Google Chat message:",
|
|
35153
|
+
confirmationLabel: "Confirm that this Chat message should be posted now."
|
|
35154
|
+
};
|
|
34333
35155
|
function registerChatTools(server) {
|
|
34334
35156
|
const workspaceOnlyNote = ' WORKSPACE ONLY: Google Chat has no API for consumer accounts, so this fails on an @gmail.com account with "chat requires a Google Workspace account". That is the ACCOUNT, not the token \u2014 re-authorizing or adding scopes will not help.';
|
|
34335
35157
|
const spaceParam = external_exports.string().describe(
|
|
@@ -34360,7 +35182,7 @@ function registerChatTools(server) {
|
|
|
34360
35182
|
account: accountParam
|
|
34361
35183
|
})
|
|
34362
35184
|
}, async ({ displayName, exact, max, account }) => {
|
|
34363
|
-
const args = ["chat", "spaces", "find", displayName];
|
|
35185
|
+
const args = ["chat", "spaces", "find", pos(displayName)];
|
|
34364
35186
|
if (exact) args.push("--exact");
|
|
34365
35187
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
34366
35188
|
return runOrDiagnose(args, { account });
|
|
@@ -34374,7 +35196,7 @@ function registerChatTools(server) {
|
|
|
34374
35196
|
account: accountParam
|
|
34375
35197
|
})
|
|
34376
35198
|
}, async ({ displayName, members: members2, account }) => {
|
|
34377
|
-
const args = ["chat", "spaces", "create", displayName];
|
|
35199
|
+
const args = ["chat", "spaces", "create", pos(displayName)];
|
|
34378
35200
|
if (members2) for (const member of members2) args.push(`--member=${member}`);
|
|
34379
35201
|
return runOrDiagnose(args, { account });
|
|
34380
35202
|
});
|
|
@@ -34387,7 +35209,7 @@ function registerChatTools(server) {
|
|
|
34387
35209
|
account: accountParam
|
|
34388
35210
|
})
|
|
34389
35211
|
}, async ({ space, max, pageToken, page, all, account }) => {
|
|
34390
|
-
const args = ["chat", "threads", "list", space];
|
|
35212
|
+
const args = ["chat", "threads", "list", pos(space)];
|
|
34391
35213
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
34392
35214
|
return runOrDiagnose(args, { account });
|
|
34393
35215
|
});
|
|
@@ -34403,7 +35225,7 @@ function registerChatTools(server) {
|
|
|
34403
35225
|
account: accountParam
|
|
34404
35226
|
})
|
|
34405
35227
|
}, async ({ space, thread, unread, order, max, pageToken, page, all, account }) => {
|
|
34406
|
-
const args = ["chat", "messages", "list", space];
|
|
35228
|
+
const args = ["chat", "messages", "list", pos(space)];
|
|
34407
35229
|
if (thread) args.push(`--thread=${thread}`);
|
|
34408
35230
|
if (unread) args.push("--unread");
|
|
34409
35231
|
if (order) args.push(`--order=${order}`);
|
|
@@ -34427,7 +35249,7 @@ function registerChatTools(server) {
|
|
|
34427
35249
|
account: accountParam
|
|
34428
35250
|
})
|
|
34429
35251
|
}, async ({ query, order, view, markup, max, pageToken, page, all, account }) => {
|
|
34430
|
-
const args = ["chat", "messages", "search", query];
|
|
35252
|
+
const args = ["chat", "messages", "search", pos(query)];
|
|
34431
35253
|
if (order) args.push(`--order=${order}`);
|
|
34432
35254
|
if (view) args.push(`--view=${view}`);
|
|
34433
35255
|
if (markup) args.push(`--markup=${markup}`);
|
|
@@ -34435,41 +35257,79 @@ function registerChatTools(server) {
|
|
|
34435
35257
|
return runOrDiagnose(args, { account });
|
|
34436
35258
|
});
|
|
34437
35259
|
server.registerTool("gog_chat_messages_send", {
|
|
34438
|
-
description: "Post a message to a Chat space. THIS IS IMMEDIATELY VISIBLE TO EVERYONE IN THE SPACE and cannot be unsent through this tool, so treat it like sending mail, not like saving a draft. Pass `thread` to reply inside an existing conversation (from gog_chat_threads_list or a message's thread field); omit it to start a new one. Text supports Chat's markdown-ish formatting (*bold*, _italic_, `code`)." + workspaceOnlyNote,
|
|
35260
|
+
description: "Post a message to a Chat space. THIS IS IMMEDIATELY VISIBLE TO EVERYONE IN THE SPACE and cannot be unsent through this tool, so treat it like sending mail, not like saving a draft. Pass `thread` to reply inside an existing conversation (from gog_chat_threads_list or a message's thread field); omit it to start a new one. Text supports Chat's markdown-ish formatting (*bold*, _italic_, `code`). Asks the MCP host to show the user a confirmation prompt with the space, thread, text and attachments first; nothing is posted unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
|
|
34439
35261
|
annotations: { destructiveHint: true },
|
|
34440
35262
|
inputSchema: external_exports.object({
|
|
34441
35263
|
space: spaceParam,
|
|
34442
35264
|
text: external_exports.string().optional().describe("Message text. Optional only when an attachment is supplied."),
|
|
34443
35265
|
thread: threadParam,
|
|
34444
35266
|
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."
|
|
35267
|
+
"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
35268
|
),
|
|
34447
35269
|
attachInline: attachInlineParam,
|
|
34448
|
-
account: accountParam
|
|
35270
|
+
account: accountParam,
|
|
35271
|
+
confirmToken: confirmTokenParam
|
|
34449
35272
|
})
|
|
34450
|
-
}, async ({ space, text, thread, attach, attachInline, account }) => {
|
|
35273
|
+
}, async ({ space, text, thread, attach, attachInline, account, confirmToken }, ctx) => {
|
|
35274
|
+
confinePaths(attach, "attach");
|
|
34451
35275
|
if (text === void 0 && !attach?.length && !attachInline?.length) {
|
|
34452
35276
|
throw new Error("A Chat message needs text, an attachment, or both.");
|
|
34453
35277
|
}
|
|
34454
|
-
const args = ["chat", "messages", "send", space];
|
|
35278
|
+
const args = ["chat", "messages", "send", pos(space)];
|
|
34455
35279
|
if (text !== void 0) args.push(`--text=${text}`);
|
|
34456
35280
|
if (thread) args.push(`--thread=${thread}`);
|
|
34457
35281
|
if (attach) for (const path of attach) args.push(`--attach=${path}`);
|
|
34458
35282
|
args.push(...inlineAttachmentArgs("attach", attachInline, args));
|
|
35283
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35284
|
+
...CHAT_CONFIRM,
|
|
35285
|
+
action: "chat.message-send",
|
|
35286
|
+
details: { space, thread, textPreview: bodyPreview(text), attachments: attachmentNames(attach, attachInline) },
|
|
35287
|
+
fallback: {
|
|
35288
|
+
tool: "gog_chat_messages_send",
|
|
35289
|
+
account,
|
|
35290
|
+
confirmToken,
|
|
35291
|
+
subject: () => {
|
|
35292
|
+
const attachments = attachmentDetails(attach, attachInline);
|
|
35293
|
+
const from = senderPreview(account);
|
|
35294
|
+
return {
|
|
35295
|
+
target: space,
|
|
35296
|
+
payload: { from, space, thread, text, attachments },
|
|
35297
|
+
preview: { from, space, thread, text, attachments: attachmentPreview(attachments) }
|
|
35298
|
+
};
|
|
35299
|
+
}
|
|
35300
|
+
}
|
|
35301
|
+
});
|
|
35302
|
+
if (confirmation) return confirmation;
|
|
34459
35303
|
return runOrDiagnose(args, { account });
|
|
34460
35304
|
});
|
|
34461
35305
|
server.registerTool("gog_chat_dm_send", {
|
|
34462
|
-
description: "Send a direct message to one person by email address, creating the DM space if this is the first message. Delivered immediately and cannot be unsent through this tool. For a room rather than a person, use gog_chat_messages_send." + workspaceOnlyNote,
|
|
35306
|
+
description: "Send a direct message to one person by email address, creating the DM space if this is the first message. Delivered immediately and cannot be unsent through this tool. For a room rather than a person, use gog_chat_messages_send. Asks the MCP host to show the user a confirmation prompt with the recipient and text first; nothing is sent unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
|
|
34463
35307
|
annotations: { destructiveHint: true },
|
|
34464
35308
|
inputSchema: external_exports.object({
|
|
34465
35309
|
email: external_exports.string().describe("Recipient email address"),
|
|
34466
35310
|
text: external_exports.string().describe("Message text"),
|
|
34467
35311
|
thread: threadParam,
|
|
34468
|
-
account: accountParam
|
|
35312
|
+
account: accountParam,
|
|
35313
|
+
confirmToken: confirmTokenParam
|
|
34469
35314
|
})
|
|
34470
|
-
}, async ({ email: email3, text, thread, account }) => {
|
|
34471
|
-
const args = ["chat", "dm", "send", email3, `--text=${text}`];
|
|
35315
|
+
}, async ({ email: email3, text, thread, account, confirmToken }, ctx) => {
|
|
35316
|
+
const args = ["chat", "dm", "send", pos(email3), `--text=${text}`];
|
|
34472
35317
|
if (thread) args.push(`--thread=${thread}`);
|
|
35318
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35319
|
+
...CHAT_CONFIRM,
|
|
35320
|
+
action: "chat.dm-send",
|
|
35321
|
+
details: { to: email3, thread, textPreview: bodyPreview(text) },
|
|
35322
|
+
fallback: {
|
|
35323
|
+
tool: "gog_chat_dm_send",
|
|
35324
|
+
account,
|
|
35325
|
+
confirmToken,
|
|
35326
|
+
subject: () => {
|
|
35327
|
+
const from = senderPreview(account);
|
|
35328
|
+
return { target: email3, payload: { from, to: email3, thread, text }, preview: { from, to: email3, thread, text } };
|
|
35329
|
+
}
|
|
35330
|
+
}
|
|
35331
|
+
});
|
|
35332
|
+
if (confirmation) return confirmation;
|
|
34473
35333
|
return runOrDiagnose(args, { account });
|
|
34474
35334
|
});
|
|
34475
35335
|
server.registerTool("gog_chat_dm_space", {
|
|
@@ -34480,7 +35340,7 @@ function registerChatTools(server) {
|
|
|
34480
35340
|
account: accountParam
|
|
34481
35341
|
})
|
|
34482
35342
|
}, async ({ email: email3, account }) => {
|
|
34483
|
-
return runOrDiagnose(["chat", "dm", "space", email3], { account });
|
|
35343
|
+
return runOrDiagnose(["chat", "dm", "space", pos(email3)], { account });
|
|
34484
35344
|
});
|
|
34485
35345
|
server.registerTool("gog_chat_reactions_list", {
|
|
34486
35346
|
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 +35352,7 @@ function registerChatTools(server) {
|
|
|
34492
35352
|
account: accountParam
|
|
34493
35353
|
})
|
|
34494
35354
|
}, async ({ message, space, max, pageToken, page, all, account }) => {
|
|
34495
|
-
const args = ["chat", "messages", "reactions", "list", message];
|
|
35355
|
+
const args = ["chat", "messages", "reactions", "list", pos(message)];
|
|
34496
35356
|
if (space) args.push(`--space=${space}`);
|
|
34497
35357
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
34498
35358
|
return runOrDiagnose(args, { account });
|
|
@@ -34507,7 +35367,7 @@ function registerChatTools(server) {
|
|
|
34507
35367
|
account: accountParam
|
|
34508
35368
|
})
|
|
34509
35369
|
}, async ({ message, emoji: emoji3, space, account }) => {
|
|
34510
|
-
const args = ["chat", "messages", "reactions", "create", message, emoji3];
|
|
35370
|
+
const args = ["chat", "messages", "reactions", "create", pos(message), pos(emoji3)];
|
|
34511
35371
|
if (space) args.push(`--space=${space}`);
|
|
34512
35372
|
return runOrDiagnose(args, { account });
|
|
34513
35373
|
});
|
|
@@ -34519,16 +35379,50 @@ function registerChatTools(server) {
|
|
|
34519
35379
|
account: accountParam
|
|
34520
35380
|
})
|
|
34521
35381
|
}, async ({ reaction, account }) => {
|
|
34522
|
-
return runOrDiagnose(["chat", "messages", "reactions", "delete", reaction], { account });
|
|
35382
|
+
return runOrDiagnose(["chat", "messages", "reactions", "delete", pos(reaction)], { account });
|
|
34523
35383
|
});
|
|
34524
35384
|
registerRunTool(server, {
|
|
34525
35385
|
service: "chat",
|
|
34526
35386
|
examples: '"spaces", "messages", "dm"',
|
|
35387
|
+
vet: vetChatRun,
|
|
34527
35388
|
note: "Google Chat has no API for consumer accounts: every chat subcommand fails on an @gmail.com account regardless of scopes."
|
|
34528
35389
|
});
|
|
34529
35390
|
}
|
|
34530
35391
|
|
|
34531
35392
|
// src/tools/classroom.ts
|
|
35393
|
+
var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
35394
|
+
var CLASSROOM_GATED = {
|
|
35395
|
+
announcements: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
35396
|
+
announcement: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
35397
|
+
ann: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
35398
|
+
invitations: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
|
|
35399
|
+
invitation: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
|
|
35400
|
+
invites: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
|
|
35401
|
+
};
|
|
35402
|
+
function vetClassroomRun(subcommand, args) {
|
|
35403
|
+
const sub = subcommand.toLowerCase();
|
|
35404
|
+
const gated = Object.hasOwn(CLASSROOM_GATED, sub) ? CLASSROOM_GATED[sub] : void 0;
|
|
35405
|
+
if (!gated) return void 0;
|
|
35406
|
+
const word = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
|
|
35407
|
+
return word ? gatedElsewhere(`gog classroom ${sub} ${word.toLowerCase()}`, "gog_classroom_run", gated.does, gated.tool) : void 0;
|
|
35408
|
+
}
|
|
35409
|
+
async function readCourse(courseId, account, runner = runOrDiagnose) {
|
|
35410
|
+
const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
|
|
35411
|
+
if (got.isError) return { error: got };
|
|
35412
|
+
let course;
|
|
35413
|
+
try {
|
|
35414
|
+
course = JSON.parse(resultText(got))?.course;
|
|
35415
|
+
} catch {
|
|
35416
|
+
course = void 0;
|
|
35417
|
+
}
|
|
35418
|
+
return {
|
|
35419
|
+
course: {
|
|
35420
|
+
id: courseId,
|
|
35421
|
+
...typeof course?.name === "string" ? { name: course.name } : {},
|
|
35422
|
+
...typeof course?.section === "string" ? { section: course.section } : {}
|
|
35423
|
+
}
|
|
35424
|
+
};
|
|
35425
|
+
}
|
|
34532
35426
|
function registerClassroomTools(server) {
|
|
34533
35427
|
server.registerTool("gog_classroom_courses_list", {
|
|
34534
35428
|
description: "List Google Classroom courses.",
|
|
@@ -34562,7 +35456,7 @@ function registerClassroomTools(server) {
|
|
|
34562
35456
|
account: accountParam
|
|
34563
35457
|
})
|
|
34564
35458
|
}, async ({ courseId, account }) => {
|
|
34565
|
-
return runOrDiagnose(["classroom", "courses", "get", courseId], { account });
|
|
35459
|
+
return runOrDiagnose(["classroom", "courses", "get", pos(courseId)], { account });
|
|
34566
35460
|
});
|
|
34567
35461
|
server.registerTool("gog_classroom_students_list", {
|
|
34568
35462
|
description: "List students enrolled in a Google Classroom course.",
|
|
@@ -34576,7 +35470,7 @@ function registerClassroomTools(server) {
|
|
|
34576
35470
|
account: accountParam
|
|
34577
35471
|
})
|
|
34578
35472
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
34579
|
-
const args = ["classroom", "students", "list", courseId];
|
|
35473
|
+
const args = ["classroom", "students", "list", pos(courseId)];
|
|
34580
35474
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
34581
35475
|
const token = resolvePageToken({ pageToken, page });
|
|
34582
35476
|
if (token) args.push(`--page=${token}`);
|
|
@@ -34592,7 +35486,7 @@ function registerClassroomTools(server) {
|
|
|
34592
35486
|
account: accountParam
|
|
34593
35487
|
})
|
|
34594
35488
|
}, async ({ courseId, userId, account }) => {
|
|
34595
|
-
return runOrDiagnose(["classroom", "students", "get", courseId, userId], { account });
|
|
35489
|
+
return runOrDiagnose(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
|
|
34596
35490
|
});
|
|
34597
35491
|
server.registerTool("gog_classroom_teachers_list", {
|
|
34598
35492
|
description: "List teachers in a Google Classroom course.",
|
|
@@ -34606,7 +35500,7 @@ function registerClassroomTools(server) {
|
|
|
34606
35500
|
account: accountParam
|
|
34607
35501
|
})
|
|
34608
35502
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
34609
|
-
const args = ["classroom", "teachers", "list", courseId];
|
|
35503
|
+
const args = ["classroom", "teachers", "list", pos(courseId)];
|
|
34610
35504
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
34611
35505
|
const token = resolvePageToken({ pageToken, page });
|
|
34612
35506
|
if (token) args.push(`--page=${token}`);
|
|
@@ -34622,7 +35516,7 @@ function registerClassroomTools(server) {
|
|
|
34622
35516
|
account: accountParam
|
|
34623
35517
|
})
|
|
34624
35518
|
}, async ({ courseId, userId, account }) => {
|
|
34625
|
-
return runOrDiagnose(["classroom", "teachers", "get", courseId, userId], { account });
|
|
35519
|
+
return runOrDiagnose(["classroom", "teachers", "get", pos(courseId), pos(userId)], { account });
|
|
34626
35520
|
});
|
|
34627
35521
|
server.registerTool("gog_classroom_roster", {
|
|
34628
35522
|
description: "List the full roster (students and/or teachers) of a Google Classroom course. Omit both flags to return both groups.",
|
|
@@ -34638,7 +35532,7 @@ function registerClassroomTools(server) {
|
|
|
34638
35532
|
account: accountParam
|
|
34639
35533
|
})
|
|
34640
35534
|
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
34641
|
-
const args = ["classroom", "roster", courseId];
|
|
35535
|
+
const args = ["classroom", "roster", pos(courseId)];
|
|
34642
35536
|
if (students) args.push("--students");
|
|
34643
35537
|
if (teachers) args.push("--teachers");
|
|
34644
35538
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
@@ -34663,7 +35557,7 @@ function registerClassroomTools(server) {
|
|
|
34663
35557
|
account: accountParam
|
|
34664
35558
|
})
|
|
34665
35559
|
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
34666
|
-
const args = ["classroom", "coursework", "list", courseId];
|
|
35560
|
+
const args = ["classroom", "coursework", "list", pos(courseId)];
|
|
34667
35561
|
if (state) args.push(`--state=${state}`);
|
|
34668
35562
|
if (topic) args.push(`--topic=${topic}`);
|
|
34669
35563
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
@@ -34683,7 +35577,7 @@ function registerClassroomTools(server) {
|
|
|
34683
35577
|
account: accountParam
|
|
34684
35578
|
})
|
|
34685
35579
|
}, async ({ courseId, courseworkId, account }) => {
|
|
34686
|
-
return runOrDiagnose(["classroom", "coursework", "get", courseId, courseworkId], { account });
|
|
35580
|
+
return runOrDiagnose(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
|
|
34687
35581
|
});
|
|
34688
35582
|
server.registerTool("gog_classroom_submissions_list", {
|
|
34689
35583
|
description: "List student submissions for a coursework item.",
|
|
@@ -34701,7 +35595,7 @@ function registerClassroomTools(server) {
|
|
|
34701
35595
|
account: accountParam
|
|
34702
35596
|
})
|
|
34703
35597
|
}, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
|
|
34704
|
-
const args = ["classroom", "submissions", "list", courseId, courseworkId];
|
|
35598
|
+
const args = ["classroom", "submissions", "list", pos(courseId), pos(courseworkId)];
|
|
34705
35599
|
if (state) args.push(`--state=${state}`);
|
|
34706
35600
|
if (late) args.push(`--late=${late}`);
|
|
34707
35601
|
if (user) args.push(`--user=${user}`);
|
|
@@ -34721,7 +35615,7 @@ function registerClassroomTools(server) {
|
|
|
34721
35615
|
account: accountParam
|
|
34722
35616
|
})
|
|
34723
35617
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
34724
|
-
return runOrDiagnose(["classroom", "submissions", "get", courseId, courseworkId, submissionId], { account });
|
|
35618
|
+
return runOrDiagnose(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
34725
35619
|
});
|
|
34726
35620
|
server.registerTool("gog_classroom_submissions_grade", {
|
|
34727
35621
|
description: "Grade a student submission. Set draft and/or assigned grade values.",
|
|
@@ -34735,7 +35629,7 @@ function registerClassroomTools(server) {
|
|
|
34735
35629
|
account: accountParam
|
|
34736
35630
|
})
|
|
34737
35631
|
}, async ({ courseId, courseworkId, submissionId, draft, assigned, account }) => {
|
|
34738
|
-
const args = ["classroom", "submissions", "grade", courseId, courseworkId, submissionId];
|
|
35632
|
+
const args = ["classroom", "submissions", "grade", pos(courseId), pos(courseworkId), pos(submissionId)];
|
|
34739
35633
|
if (draft) args.push(`--draft=${draft}`);
|
|
34740
35634
|
if (assigned) args.push(`--assigned=${assigned}`);
|
|
34741
35635
|
return runOrDiagnose(args, { account });
|
|
@@ -34750,7 +35644,7 @@ function registerClassroomTools(server) {
|
|
|
34750
35644
|
account: accountParam
|
|
34751
35645
|
})
|
|
34752
35646
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
34753
|
-
return runOrDiagnose(["classroom", "submissions", "return", courseId, courseworkId, submissionId], { account });
|
|
35647
|
+
return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
34754
35648
|
});
|
|
34755
35649
|
server.registerTool("gog_classroom_submissions_turn_in", {
|
|
34756
35650
|
description: "Turn in a student submission (student action).",
|
|
@@ -34762,7 +35656,7 @@ function registerClassroomTools(server) {
|
|
|
34762
35656
|
account: accountParam
|
|
34763
35657
|
})
|
|
34764
35658
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
34765
|
-
return runOrDiagnose(["classroom", "submissions", "turn-in", courseId, courseworkId, submissionId], { account });
|
|
35659
|
+
return runOrDiagnose(["classroom", "submissions", "turn-in", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
34766
35660
|
});
|
|
34767
35661
|
server.registerTool("gog_classroom_submissions_reclaim", {
|
|
34768
35662
|
description: "Reclaim a turned-in submission (student action to edit a submission).",
|
|
@@ -34774,7 +35668,7 @@ function registerClassroomTools(server) {
|
|
|
34774
35668
|
account: accountParam
|
|
34775
35669
|
})
|
|
34776
35670
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
34777
|
-
return runOrDiagnose(["classroom", "submissions", "reclaim", courseId, courseworkId, submissionId], { account });
|
|
35671
|
+
return runOrDiagnose(["classroom", "submissions", "reclaim", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
34778
35672
|
});
|
|
34779
35673
|
server.registerTool("gog_classroom_announcements_list", {
|
|
34780
35674
|
description: "List announcements in a Google Classroom course.",
|
|
@@ -34790,7 +35684,7 @@ function registerClassroomTools(server) {
|
|
|
34790
35684
|
account: accountParam
|
|
34791
35685
|
})
|
|
34792
35686
|
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
34793
|
-
const args = ["classroom", "announcements", "list", courseId];
|
|
35687
|
+
const args = ["classroom", "announcements", "list", pos(courseId)];
|
|
34794
35688
|
if (state) args.push(`--state=${state}`);
|
|
34795
35689
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
34796
35690
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
@@ -34808,22 +35702,45 @@ function registerClassroomTools(server) {
|
|
|
34808
35702
|
account: accountParam
|
|
34809
35703
|
})
|
|
34810
35704
|
}, async ({ courseId, announcementId, account }) => {
|
|
34811
|
-
return runOrDiagnose(["classroom", "announcements", "get", courseId, announcementId], { account });
|
|
35705
|
+
return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
|
|
34812
35706
|
});
|
|
34813
35707
|
server.registerTool("gog_classroom_announcements_create", {
|
|
34814
|
-
description: "Create an announcement in a Google Classroom course.",
|
|
35708
|
+
description: "Create an announcement in a Google Classroom course. Unless state is DRAFT (which students cannot see), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
34815
35709
|
annotations: { destructiveHint: true },
|
|
34816
35710
|
inputSchema: external_exports.object({
|
|
34817
35711
|
courseId: external_exports.string().describe("Course ID"),
|
|
34818
35712
|
text: external_exports.string().describe("Announcement text"),
|
|
34819
|
-
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State"),
|
|
35713
|
+
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
|
|
34820
35714
|
scheduled: external_exports.string().optional().describe("Scheduled publish time"),
|
|
34821
|
-
account: accountParam
|
|
35715
|
+
account: accountParam,
|
|
35716
|
+
confirmToken: confirmTokenParam
|
|
34822
35717
|
})
|
|
34823
|
-
}, async ({ courseId, text, state, scheduled, account }) => {
|
|
34824
|
-
const args = ["classroom", "announcements", "create", courseId, `--text=${text}`];
|
|
35718
|
+
}, async ({ courseId, text, state, scheduled, account, confirmToken }, ctx) => {
|
|
35719
|
+
const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
|
|
34825
35720
|
if (state) args.push(`--state=${state}`);
|
|
34826
35721
|
if (scheduled) args.push(`--scheduled=${scheduled}`);
|
|
35722
|
+
if (state !== "DRAFT") {
|
|
35723
|
+
const read = await readCourse(courseId, account);
|
|
35724
|
+
if (read.error) return read.error;
|
|
35725
|
+
const publishes = scheduled ? `at ${scheduled}` : "immediately";
|
|
35726
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35727
|
+
action: "classroom.announcement-create",
|
|
35728
|
+
message: "Review and confirm this Classroom announcement:",
|
|
35729
|
+
confirmationLabel: "Confirm that this announcement should be posted to the class.",
|
|
35730
|
+
details: { course: read.course, publishes, textPreview: bodyPreview(text) },
|
|
35731
|
+
unsupportedNote: "Create it with state DRAFT instead; the user can review and post it from Classroom.",
|
|
35732
|
+
fallback: {
|
|
35733
|
+
tool: "gog_classroom_announcements_create",
|
|
35734
|
+
account,
|
|
35735
|
+
confirmToken,
|
|
35736
|
+
subject: () => {
|
|
35737
|
+
const view = { course: read.course, publishes, text };
|
|
35738
|
+
return { target: courseId, payload: view, preview: view };
|
|
35739
|
+
}
|
|
35740
|
+
}
|
|
35741
|
+
});
|
|
35742
|
+
if (confirmation) return confirmation;
|
|
35743
|
+
}
|
|
34827
35744
|
return runOrDiagnose(args, { account });
|
|
34828
35745
|
});
|
|
34829
35746
|
server.registerTool("gog_classroom_topics_list", {
|
|
@@ -34838,7 +35755,7 @@ function registerClassroomTools(server) {
|
|
|
34838
35755
|
account: accountParam
|
|
34839
35756
|
})
|
|
34840
35757
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
34841
|
-
const args = ["classroom", "topics", "list", courseId];
|
|
35758
|
+
const args = ["classroom", "topics", "list", pos(courseId)];
|
|
34842
35759
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
34843
35760
|
const token = resolvePageToken({ pageToken, page });
|
|
34844
35761
|
if (token) args.push(`--page=${token}`);
|
|
@@ -34854,7 +35771,7 @@ function registerClassroomTools(server) {
|
|
|
34854
35771
|
account: accountParam
|
|
34855
35772
|
})
|
|
34856
35773
|
}, async ({ courseId, topicId, account }) => {
|
|
34857
|
-
return runOrDiagnose(["classroom", "topics", "get", courseId, topicId], { account });
|
|
35774
|
+
return runOrDiagnose(["classroom", "topics", "get", pos(courseId), pos(topicId)], { account });
|
|
34858
35775
|
});
|
|
34859
35776
|
server.registerTool("gog_classroom_invitations_list", {
|
|
34860
35777
|
description: "List Google Classroom invitations.",
|
|
@@ -34886,7 +35803,7 @@ function registerClassroomTools(server) {
|
|
|
34886
35803
|
account: accountParam
|
|
34887
35804
|
})
|
|
34888
35805
|
}, async ({ invitationId, account }) => {
|
|
34889
|
-
return runOrDiagnose(["classroom", "invitations", "get", invitationId], { account });
|
|
35806
|
+
return runOrDiagnose(["classroom", "invitations", "get", pos(invitationId)], { account });
|
|
34890
35807
|
});
|
|
34891
35808
|
server.registerTool("gog_classroom_invitations_accept", {
|
|
34892
35809
|
description: "Accept a Google Classroom invitation.",
|
|
@@ -34896,7 +35813,7 @@ function registerClassroomTools(server) {
|
|
|
34896
35813
|
account: accountParam
|
|
34897
35814
|
})
|
|
34898
35815
|
}, async ({ invitationId, account }) => {
|
|
34899
|
-
return runOrDiagnose(["classroom", "invitations", "accept", invitationId], { account });
|
|
35816
|
+
return runOrDiagnose(["classroom", "invitations", "accept", pos(invitationId)], { account });
|
|
34900
35817
|
});
|
|
34901
35818
|
server.registerTool("gog_classroom_profile_get", {
|
|
34902
35819
|
description: "Get a Google Classroom user profile. Omit userId to fetch the authenticated user.",
|
|
@@ -34907,12 +35824,13 @@ function registerClassroomTools(server) {
|
|
|
34907
35824
|
})
|
|
34908
35825
|
}, async ({ userId, account }) => {
|
|
34909
35826
|
const args = ["classroom", "profile", "get"];
|
|
34910
|
-
if (userId) args.push(userId);
|
|
35827
|
+
if (userId) args.push(pos(userId));
|
|
34911
35828
|
return runOrDiagnose(args, { account });
|
|
34912
35829
|
});
|
|
34913
35830
|
registerRunTool(server, {
|
|
34914
35831
|
service: "classroom",
|
|
34915
35832
|
examples: '"guardians", "materials", "guardian-invitations"',
|
|
35833
|
+
vet: vetClassroomRun,
|
|
34916
35834
|
note: "Covers anything not wrapped by the dedicated tools (guardians, guardian-invitations, materials, coursework assignees, announcement assignees, etc.)."
|
|
34917
35835
|
});
|
|
34918
35836
|
}
|
|
@@ -34927,7 +35845,7 @@ function registerContactsTools(server) {
|
|
|
34927
35845
|
account: accountParam
|
|
34928
35846
|
})
|
|
34929
35847
|
}, async ({ query, account }) => {
|
|
34930
|
-
return runOrDiagnose(["contacts", "search", query], { account });
|
|
35848
|
+
return runOrDiagnose(["contacts", "search", pos(query)], { account });
|
|
34931
35849
|
});
|
|
34932
35850
|
server.registerTool("gog_contacts_list", {
|
|
34933
35851
|
description: "List all Google Contacts.",
|
|
@@ -34946,7 +35864,7 @@ function registerContactsTools(server) {
|
|
|
34946
35864
|
account: accountParam
|
|
34947
35865
|
})
|
|
34948
35866
|
}, async ({ resourceName, account }) => {
|
|
34949
|
-
return runOrDiagnose(["contacts", "get", resourceName], { account });
|
|
35867
|
+
return runOrDiagnose(["contacts", "get", pos(resourceName)], { account });
|
|
34950
35868
|
});
|
|
34951
35869
|
server.registerTool("gog_contacts_create", {
|
|
34952
35870
|
description: "Create a new Google Contact.",
|
|
@@ -34982,7 +35900,7 @@ function registerDocsTools(server) {
|
|
|
34982
35900
|
account: accountParam
|
|
34983
35901
|
})
|
|
34984
35902
|
}, async ({ docId, account }) => {
|
|
34985
|
-
return runOrDiagnose(["docs", "info", docId], { account });
|
|
35903
|
+
return runOrDiagnose(["docs", "info", pos(docId)], { account });
|
|
34986
35904
|
});
|
|
34987
35905
|
server.registerTool("gog_docs_cat", {
|
|
34988
35906
|
description: "Read a Google Doc as plain text.",
|
|
@@ -34993,7 +35911,7 @@ function registerDocsTools(server) {
|
|
|
34993
35911
|
account: accountParam
|
|
34994
35912
|
})
|
|
34995
35913
|
}, async ({ docId, chips, account }) => {
|
|
34996
|
-
const args = ["docs", "cat", docId];
|
|
35914
|
+
const args = ["docs", "cat", pos(docId)];
|
|
34997
35915
|
if (chips) args.push("--chips");
|
|
34998
35916
|
return runOrDiagnose(args, { account });
|
|
34999
35917
|
});
|
|
@@ -35005,7 +35923,7 @@ function registerDocsTools(server) {
|
|
|
35005
35923
|
account: accountParam
|
|
35006
35924
|
})
|
|
35007
35925
|
}, async ({ title, account }) => {
|
|
35008
|
-
return runOrDiagnose(["docs", "create", title], { account });
|
|
35926
|
+
return runOrDiagnose(["docs", "create", pos(title)], { account });
|
|
35009
35927
|
});
|
|
35010
35928
|
server.registerTool("gog_docs_write", {
|
|
35011
35929
|
description: "Write text content to a Google Doc, replacing existing body content by default. Set append=true to add after existing content.",
|
|
@@ -35030,7 +35948,7 @@ function registerDocsTools(server) {
|
|
|
35030
35948
|
account: accountParam
|
|
35031
35949
|
})
|
|
35032
35950
|
}, 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}`];
|
|
35951
|
+
const args = ["docs", "write", pos(docId), `--text=${text}`];
|
|
35034
35952
|
if (append) args.push("--append");
|
|
35035
35953
|
if (checkOrphans) args.push("--check-orphans");
|
|
35036
35954
|
if (bullets) args.push("--bullets");
|
|
@@ -35057,7 +35975,7 @@ function registerDocsTools(server) {
|
|
|
35057
35975
|
account: accountParam
|
|
35058
35976
|
})
|
|
35059
35977
|
}, async ({ docId, find, replace, account }) => {
|
|
35060
|
-
return runOrDiagnose(["docs", "find-replace", docId, find, replace], { account });
|
|
35978
|
+
return runOrDiagnose(["docs", "find-replace", pos(docId), pos(find), pos(replace)], { account });
|
|
35061
35979
|
});
|
|
35062
35980
|
server.registerTool("gog_docs_structure", {
|
|
35063
35981
|
description: "Show a Google Doc's structure with numbered paragraphs. Useful for understanding the document layout before making index-based edits.",
|
|
@@ -35067,18 +35985,31 @@ function registerDocsTools(server) {
|
|
|
35067
35985
|
account: accountParam
|
|
35068
35986
|
})
|
|
35069
35987
|
}, async ({ docId, account }) => {
|
|
35070
|
-
return runOrDiagnose(["docs", "structure", docId], { account });
|
|
35988
|
+
return runOrDiagnose(["docs", "structure", pos(docId)], { account });
|
|
35071
35989
|
});
|
|
35072
35990
|
registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
|
|
35073
35991
|
}
|
|
35074
35992
|
|
|
35075
35993
|
// src/tools/drive.ts
|
|
35994
|
+
function vetDriveRun(subcommand, _args) {
|
|
35995
|
+
return subcommand.toLowerCase() === "share" ? gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share") : void 0;
|
|
35996
|
+
}
|
|
35076
35997
|
var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
|
|
35998
|
+
function shareTargetMeta(raw) {
|
|
35999
|
+
try {
|
|
36000
|
+
const { name, mimeType } = fileMeta(raw);
|
|
36001
|
+
return { name, mimeType };
|
|
36002
|
+
} catch {
|
|
36003
|
+
return {};
|
|
36004
|
+
}
|
|
36005
|
+
}
|
|
35077
36006
|
function fileMeta(raw) {
|
|
35078
36007
|
const parsed = JSON.parse(raw);
|
|
35079
36008
|
const f = parsed.file ?? parsed;
|
|
35080
|
-
|
|
36009
|
+
const size = f.size === void 0 ? void 0 : Number(f.size);
|
|
36010
|
+
return { name: f.name, mimeType: f.mimeType, size: Number.isFinite(size) ? size : void 0 };
|
|
35081
36011
|
}
|
|
36012
|
+
var MAX_DRIVE_READ_BYTES = MAX_INLINE_ATTACHMENT_BYTES;
|
|
35082
36013
|
var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime,size,webViewLink)";
|
|
35083
36014
|
function registerDriveTools(server) {
|
|
35084
36015
|
server.registerTool("gog_drive_ls", {
|
|
@@ -35122,7 +36053,7 @@ function registerDriveTools(server) {
|
|
|
35122
36053
|
account: accountParam
|
|
35123
36054
|
})
|
|
35124
36055
|
}, async ({ query, view, account }) => {
|
|
35125
|
-
return runOrDiagnose(["drive", "search", query], {
|
|
36056
|
+
return runOrDiagnose(["drive", "search", pos(query)], {
|
|
35126
36057
|
account,
|
|
35127
36058
|
stripMedia: resolveView(view, ["compact", "full"]) === "compact"
|
|
35128
36059
|
});
|
|
@@ -35142,7 +36073,7 @@ function registerDriveTools(server) {
|
|
|
35142
36073
|
account: accountParam
|
|
35143
36074
|
})
|
|
35144
36075
|
}, async ({ fileId, view, account }) => {
|
|
35145
|
-
return runOrDiagnose(["drive", "get", fileId], {
|
|
36076
|
+
return runOrDiagnose(["drive", "get", pos(fileId)], {
|
|
35146
36077
|
account,
|
|
35147
36078
|
stripMedia: resolveView(view, ["compact", "full"]) === "compact"
|
|
35148
36079
|
});
|
|
@@ -35155,7 +36086,7 @@ function registerDriveTools(server) {
|
|
|
35155
36086
|
account: accountParam
|
|
35156
36087
|
})
|
|
35157
36088
|
}, async ({ name, account }) => {
|
|
35158
|
-
return runOrDiagnose(["drive", "mkdir", name], { account });
|
|
36089
|
+
return runOrDiagnose(["drive", "mkdir", pos(name)], { account });
|
|
35159
36090
|
});
|
|
35160
36091
|
server.registerTool("gog_drive_rename", {
|
|
35161
36092
|
description: "Rename a file or folder in Google Drive.",
|
|
@@ -35166,7 +36097,7 @@ function registerDriveTools(server) {
|
|
|
35166
36097
|
account: accountParam
|
|
35167
36098
|
})
|
|
35168
36099
|
}, async ({ fileId, newName, account }) => {
|
|
35169
|
-
return runOrDiagnose(["drive", "rename", fileId, newName], { account });
|
|
36100
|
+
return runOrDiagnose(["drive", "rename", pos(fileId), pos(newName)], { account });
|
|
35170
36101
|
});
|
|
35171
36102
|
server.registerTool("gog_drive_move", {
|
|
35172
36103
|
description: "Move a file to a different folder in Google Drive.",
|
|
@@ -35177,7 +36108,7 @@ function registerDriveTools(server) {
|
|
|
35177
36108
|
account: accountParam
|
|
35178
36109
|
})
|
|
35179
36110
|
}, async ({ fileId, parentId, account }) => {
|
|
35180
|
-
return runOrDiagnose(["drive", "move", fileId, `--parent=${parentId}`], { account });
|
|
36111
|
+
return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
|
|
35181
36112
|
});
|
|
35182
36113
|
server.registerTool("gog_drive_delete", {
|
|
35183
36114
|
description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
|
|
@@ -35188,13 +36119,13 @@ function registerDriveTools(server) {
|
|
|
35188
36119
|
account: accountParam
|
|
35189
36120
|
})
|
|
35190
36121
|
}, async ({ fileId, permanent, account }) => {
|
|
35191
|
-
const args = ["drive", "delete", fileId];
|
|
36122
|
+
const args = ["drive", "delete", pos(fileId)];
|
|
35192
36123
|
if (permanent) args.push("--permanent");
|
|
35193
36124
|
args.push("--force");
|
|
35194
36125
|
return runOrDiagnose(args, { account });
|
|
35195
36126
|
});
|
|
35196
36127
|
server.registerTool("gog_drive_share", {
|
|
35197
|
-
description: "Share a Google Drive file or folder.",
|
|
36128
|
+
description: "Share a Google Drive file or folder. Granting access is the risk even though no email is sent (gog does not notify by default), so this reads the file and asks the MCP host to show the user a confirmation prompt naming it, who gets access and with what role; nothing is shared unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35198
36129
|
annotations: { destructiveHint: true },
|
|
35199
36130
|
inputSchema: external_exports.object({
|
|
35200
36131
|
fileId: external_exports.string().describe("File or folder ID"),
|
|
@@ -35202,10 +36133,36 @@ function registerDriveTools(server) {
|
|
|
35202
36133
|
email: external_exports.string().optional().describe("User email (required when to=user)"),
|
|
35203
36134
|
domain: external_exports.string().optional().describe("Domain (required when to=domain)"),
|
|
35204
36135
|
role: external_exports.enum(["reader", "writer"]).optional().describe("Permission role (default: reader)"),
|
|
35205
|
-
account: accountParam
|
|
36136
|
+
account: accountParam,
|
|
36137
|
+
confirmToken: confirmTokenParam
|
|
35206
36138
|
})
|
|
35207
|
-
}, async ({ fileId, to, email: email3, domain: domain2, role, account }) => {
|
|
35208
|
-
const
|
|
36139
|
+
}, async ({ fileId, to, email: email3, domain: domain2, role, account, confirmToken }, ctx) => {
|
|
36140
|
+
const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
|
|
36141
|
+
if (got.isError) return got;
|
|
36142
|
+
const file2 = { id: fileId, ...shareTargetMeta(resultText(got)) };
|
|
36143
|
+
const grant = {
|
|
36144
|
+
file: file2,
|
|
36145
|
+
to,
|
|
36146
|
+
...email3 ? { email: email3 } : {},
|
|
36147
|
+
...domain2 ? { domain: domain2 } : {},
|
|
36148
|
+
role: role ?? "reader",
|
|
36149
|
+
publicLink: to === "anyone"
|
|
36150
|
+
};
|
|
36151
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36152
|
+
action: "drive.share",
|
|
36153
|
+
message: to === "anyone" ? "Review and confirm making this file available to ANYONE with the link:" : "Review and confirm this Drive share:",
|
|
36154
|
+
confirmationLabel: "Confirm that this access should be granted now.",
|
|
36155
|
+
details: grant,
|
|
36156
|
+
unsupportedNote: "Ask the user to share it themselves from Google Drive.",
|
|
36157
|
+
fallback: {
|
|
36158
|
+
tool: "gog_drive_share",
|
|
36159
|
+
account,
|
|
36160
|
+
confirmToken,
|
|
36161
|
+
subject: () => ({ target: fileId, payload: grant, preview: grant })
|
|
36162
|
+
}
|
|
36163
|
+
});
|
|
36164
|
+
if (confirmation) return confirmation;
|
|
36165
|
+
const args = ["drive", "share", pos(fileId), `--to=${to}`];
|
|
35209
36166
|
if (email3) args.push(`--email=${email3}`);
|
|
35210
36167
|
if (domain2) args.push(`--domain=${domain2}`);
|
|
35211
36168
|
if (role) args.push(`--role=${role}`);
|
|
@@ -35228,7 +36185,7 @@ function registerDriveTools(server) {
|
|
|
35228
36185
|
}, async ({ fileId, ocrLanguage, offset = 0, maxChars, account }) => {
|
|
35229
36186
|
let tempDocId;
|
|
35230
36187
|
try {
|
|
35231
|
-
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
36188
|
+
const { name, mimeType } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
|
|
35232
36189
|
let sourceId = fileId;
|
|
35233
36190
|
if (mimeType !== GOOGLE_DOC_MIME) {
|
|
35234
36191
|
const params = JSON.stringify({ fileId, ...ocrLanguage ? { ocrLanguage } : {} });
|
|
@@ -35273,13 +36230,13 @@ function registerDriveTools(server) {
|
|
|
35273
36230
|
return diagnose(err);
|
|
35274
36231
|
} finally {
|
|
35275
36232
|
if (tempDocId) {
|
|
35276
|
-
await run(["drive", "delete", tempDocId, "--permanent", "--force"], { account }).catch(() => {
|
|
36233
|
+
await run(["drive", "delete", pos(tempDocId), "--permanent", "--force"], { account }).catch(() => {
|
|
35277
36234
|
});
|
|
35278
36235
|
}
|
|
35279
36236
|
}
|
|
35280
36237
|
});
|
|
35281
36238
|
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.",
|
|
36239
|
+
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
36240
|
annotations: { readOnlyHint: true },
|
|
35284
36241
|
inputSchema: external_exports.object({
|
|
35285
36242
|
fileId: external_exports.string().describe("Drive file ID"),
|
|
@@ -35287,9 +36244,17 @@ function registerDriveTools(server) {
|
|
|
35287
36244
|
})
|
|
35288
36245
|
}, async ({ fileId, account }) => {
|
|
35289
36246
|
try {
|
|
35290
|
-
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
36247
|
+
const { name, mimeType, size } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
|
|
36248
|
+
if (size !== void 0 && size > MAX_DRIVE_READ_BYTES) {
|
|
36249
|
+
return errorResult(
|
|
36250
|
+
`${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.`
|
|
36251
|
+
);
|
|
36252
|
+
}
|
|
35291
36253
|
const params = JSON.stringify({ fileId, alt: "media" });
|
|
35292
|
-
const blob = await runBinary(
|
|
36254
|
+
const blob = await runBinary(
|
|
36255
|
+
["api", "call", "drive", "v3", "files.get", `--params=${params}`],
|
|
36256
|
+
{ account, maxOutputBytes: MAX_DRIVE_READ_BYTES }
|
|
36257
|
+
);
|
|
35293
36258
|
const type = mimeType ?? "application/octet-stream";
|
|
35294
36259
|
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
36260
|
return {
|
|
@@ -35309,7 +36274,7 @@ function registerDriveTools(server) {
|
|
|
35309
36274
|
return diagnose(err);
|
|
35310
36275
|
}
|
|
35311
36276
|
});
|
|
35312
|
-
registerRunTool(server, { service: "drive", examples: '"copy", "
|
|
36277
|
+
registerRunTool(server, { service: "drive", examples: '"copy", "download", "permissions"', vet: vetDriveRun });
|
|
35313
36278
|
}
|
|
35314
36279
|
|
|
35315
36280
|
// src/gmail-results.ts
|
|
@@ -35337,7 +36302,7 @@ async function countMatches(method, itemsKey, query, account) {
|
|
|
35337
36302
|
maxResults: COUNT_PROBE_PAGE_SIZE,
|
|
35338
36303
|
fields: `${itemsKey}/id,nextPageToken`
|
|
35339
36304
|
});
|
|
35340
|
-
const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
|
|
36305
|
+
const raw = await run(["api", "call", "gmail", "v1", pos(method), `--params=${params}`], { account });
|
|
35341
36306
|
const parsed = JSON.parse(raw);
|
|
35342
36307
|
const items = parsed[itemsKey];
|
|
35343
36308
|
if (!Array.isArray(items)) return {};
|
|
@@ -35376,7 +36341,7 @@ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
|
35376
36341
|
const result = await runPage(token);
|
|
35377
36342
|
const parsed = parsePage(result, itemsKey);
|
|
35378
36343
|
if (parsed === void 0) {
|
|
35379
|
-
return base === void 0 ? result : finish(base, itemsKey, merged, token);
|
|
36344
|
+
return base === void 0 ? result : finish(base, itemsKey, merged, token, pageErrorText(result, itemsKey, pages + 1));
|
|
35380
36345
|
}
|
|
35381
36346
|
base = parsed;
|
|
35382
36347
|
merged.push(...parsed[itemsKey]);
|
|
@@ -35407,10 +36372,18 @@ function parsePage(result, itemsKey) {
|
|
|
35407
36372
|
const obj = parsed;
|
|
35408
36373
|
return Array.isArray(obj[itemsKey]) ? obj : void 0;
|
|
35409
36374
|
}
|
|
35410
|
-
|
|
36375
|
+
var PAGE_ERROR_MAX = 2e3;
|
|
36376
|
+
function pageErrorText(result, itemsKey, n) {
|
|
36377
|
+
const first = result.content[0];
|
|
36378
|
+
const text = first?.type === "text" ? first.text.slice(0, PAGE_ERROR_MAX) : "";
|
|
36379
|
+
if (result.isError) return `page ${n} failed${text ? `: ${text}` : ""}`;
|
|
36380
|
+
return `page ${n} returned output that is not a ${itemsKey} list`;
|
|
36381
|
+
}
|
|
36382
|
+
function finish(base, itemsKey, merged, token, pageError) {
|
|
35411
36383
|
const out = { ...base, [itemsKey]: merged };
|
|
35412
36384
|
if (token === void 0) delete out.nextPageToken;
|
|
35413
36385
|
else out.nextPageToken = token;
|
|
36386
|
+
if (pageError !== void 0) out.pageError = pageError;
|
|
35414
36387
|
return rawTextResult(JSON.stringify(out));
|
|
35415
36388
|
}
|
|
35416
36389
|
|
|
@@ -35424,16 +36397,19 @@ var STAGING_TWIN = {
|
|
|
35424
36397
|
"gmail.reply-all": "gog_gmail_drafts_reply_all",
|
|
35425
36398
|
"gmail.send": "gog_gmail_drafts_create"
|
|
35426
36399
|
};
|
|
35427
|
-
|
|
36400
|
+
var UNSUPPORTED_NOTE2 = {
|
|
36401
|
+
"gmail.drafts-send": "The draft is still saved: ask the user to review it and send it from Gmail."
|
|
36402
|
+
};
|
|
36403
|
+
async function requireGmailDispatchConfirmation(ctx, op, details, fallback) {
|
|
35428
36404
|
const twin = STAGING_TWIN[op];
|
|
35429
|
-
|
|
36405
|
+
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];
|
|
36406
|
+
return requireDispatchConfirmation(ctx, {
|
|
35430
36407
|
action: op,
|
|
35431
|
-
message: "Review and confirm this email dispatch:",
|
|
36408
|
+
message: op === "gmail.filter-forward" ? "Review and confirm this mail-forwarding filter:" : "Review and confirm this email dispatch:",
|
|
35432
36409
|
details,
|
|
35433
|
-
confirmationLabel: "Confirm that this email should be sent now.",
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
} : {}
|
|
36410
|
+
confirmationLabel: op === "gmail.filter-forward" ? "Confirm that matching mail should be forwarded automatically from now on." : "Confirm that this email should be sent now.",
|
|
36411
|
+
unsupportedNote: note,
|
|
36412
|
+
...fallback ? { fallback: { instruction: CONFIRM_SEND_INSTRUCTION, ...fallback } } : {}
|
|
35437
36413
|
});
|
|
35438
36414
|
}
|
|
35439
36415
|
var EMAIL_PATTERN = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+/gi;
|
|
@@ -35487,10 +36463,6 @@ function logGmailDispatch(tool, recipients, account) {
|
|
|
35487
36463
|
process.stderr.write(`${JSON.stringify(event)}
|
|
35488
36464
|
`);
|
|
35489
36465
|
}
|
|
35490
|
-
function resultText(result) {
|
|
35491
|
-
const first = result.content[0];
|
|
35492
|
-
return first && first.type === "text" && typeof first.text === "string" ? first.text : "{}";
|
|
35493
|
-
}
|
|
35494
36466
|
|
|
35495
36467
|
// src/tools/gmail.ts
|
|
35496
36468
|
var replySchema = {
|
|
@@ -35504,7 +36476,7 @@ var replySchema = {
|
|
|
35504
36476
|
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
36477
|
subject: external_exports.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
|
|
35506
36478
|
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.`),
|
|
36479
|
+
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
36480
|
attachInline: attachInlineParam,
|
|
35509
36481
|
from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
|
|
35510
36482
|
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 +36485,14 @@ var replySchema = {
|
|
|
35513
36485
|
signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
|
|
35514
36486
|
account: accountParam
|
|
35515
36487
|
};
|
|
36488
|
+
function confineReplyPaths(f) {
|
|
36489
|
+
confinePaths(f.attach, "attach");
|
|
36490
|
+
if (f.bodyHtmlFile) confinePath(f.bodyHtmlFile, "bodyHtmlFile");
|
|
36491
|
+
if (f.signatureFile) confinePath(f.signatureFile, "signatureFile");
|
|
36492
|
+
}
|
|
35516
36493
|
function appendReplyFlags(args, f) {
|
|
35517
36494
|
assertNotBoth("bodyHtml", "bodyHtmlFile", f.bodyHtml, f.bodyHtmlFile);
|
|
36495
|
+
confineReplyPaths(f);
|
|
35518
36496
|
if (f.body) args.push(payloadArg("body", "body-file", f.body));
|
|
35519
36497
|
if (f.bodyHtml) args.push(payloadArg("body-html", "body-html-file", f.bodyHtml, "html"));
|
|
35520
36498
|
else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
|
|
@@ -35532,7 +36510,7 @@ function appendReplyFlags(args, f) {
|
|
|
35532
36510
|
if (f.signatureFile) args.push(`--signature-file=${f.signatureFile}`);
|
|
35533
36511
|
args.push(f.autoFromAddressedAlias ? "--auto-from-addressed-alias" : "--auto-from-addressed-alias=false");
|
|
35534
36512
|
}
|
|
35535
|
-
var sendReplySchema = external_exports.object(replySchema);
|
|
36513
|
+
var sendReplySchema = external_exports.object({ ...replySchema, confirmToken: confirmTokenParam });
|
|
35536
36514
|
function parseMetadataHeaders(raw) {
|
|
35537
36515
|
let parsed;
|
|
35538
36516
|
try {
|
|
@@ -35557,27 +36535,110 @@ function computeReplyRecipients(kind, headers, flags) {
|
|
|
35557
36535
|
}
|
|
35558
36536
|
return emails;
|
|
35559
36537
|
}
|
|
35560
|
-
|
|
35561
|
-
|
|
36538
|
+
function metadataThreadId(raw) {
|
|
36539
|
+
try {
|
|
36540
|
+
const threadId = JSON.parse(raw)?.message?.threadId;
|
|
36541
|
+
return typeof threadId === "string" ? threadId : void 0;
|
|
36542
|
+
} catch {
|
|
36543
|
+
return void 0;
|
|
36544
|
+
}
|
|
36545
|
+
}
|
|
36546
|
+
function replyTokenSubject(kind, messageId, account, headers, threadId, flags) {
|
|
36547
|
+
const removed = new Set(extractEmails(...flags.remove ?? []));
|
|
36548
|
+
const keep = (emails) => emails.filter((e) => !removed.has(e));
|
|
36549
|
+
const to = keep(extractEmails(headers.from, ...kind === "reply-all" ? [headers.to] : [], ...flags.to ?? []));
|
|
36550
|
+
const cc = keep(extractEmails(...kind === "reply-all" ? [headers.cc] : [], ...flags.cc ?? []));
|
|
36551
|
+
const bcc = keep(extractEmails(...flags.bcc ?? []));
|
|
36552
|
+
const attachments = attachmentDetails(flags.attach, flags.attachInline);
|
|
36553
|
+
const subject = flags.subject || (headers.subject ? `Re: ${headers.subject}` : void 0);
|
|
36554
|
+
const from = flags.from ?? (flags.autoFromAddressedAlias ? "the send-as alias the original was addressed to" : senderPreview(account));
|
|
36555
|
+
const localFiles = attachmentDetails([flags.bodyHtmlFile, flags.signatureFile].filter((p) => Boolean(p)), void 0);
|
|
36556
|
+
return {
|
|
36557
|
+
target: messageId,
|
|
36558
|
+
payload: {
|
|
36559
|
+
kind,
|
|
36560
|
+
to,
|
|
36561
|
+
cc,
|
|
36562
|
+
bcc,
|
|
36563
|
+
subject,
|
|
36564
|
+
from,
|
|
36565
|
+
body: flags.body,
|
|
36566
|
+
bodyHtml: flags.bodyHtml,
|
|
36567
|
+
localFiles,
|
|
36568
|
+
signature: Boolean(flags.signature),
|
|
36569
|
+
signatureFrom: flags.signatureFrom,
|
|
36570
|
+
attachments,
|
|
36571
|
+
quote: !flags.noQuote,
|
|
36572
|
+
threadId,
|
|
36573
|
+
inReplyTo: headers.message_id,
|
|
36574
|
+
references: headers.references
|
|
36575
|
+
},
|
|
36576
|
+
preview: {
|
|
36577
|
+
from,
|
|
36578
|
+
to,
|
|
36579
|
+
cc,
|
|
36580
|
+
bcc,
|
|
36581
|
+
subject,
|
|
36582
|
+
body: flags.body,
|
|
36583
|
+
...flags.bodyHtml ? { bodyHtml: flags.bodyHtml } : {},
|
|
36584
|
+
...flags.bodyHtmlFile ? { bodyHtmlFile: flags.bodyHtmlFile } : {},
|
|
36585
|
+
...flags.signature || flags.signatureFrom || flags.signatureFile ? { signature: flags.signatureFile ?? flags.signatureFrom ?? "the Gmail signature of the sending address" } : {},
|
|
36586
|
+
quotesOriginal: !flags.noQuote,
|
|
36587
|
+
attachments: attachmentPreview(attachments),
|
|
36588
|
+
replyingToMessageId: messageId,
|
|
36589
|
+
threadId,
|
|
36590
|
+
inReplyTo: headers.message_id
|
|
36591
|
+
}
|
|
36592
|
+
};
|
|
36593
|
+
}
|
|
36594
|
+
async function sendReply(kind, toolName, messageId, account, flags, ctx, confirmToken) {
|
|
36595
|
+
const metaResult = await runOrDiagnose(["gmail", "get", pos(messageId), "--format=metadata"], { account });
|
|
35562
36596
|
if (metaResult.isError) return metaResult;
|
|
35563
|
-
const
|
|
36597
|
+
const metaText = resultText(metaResult);
|
|
36598
|
+
const headers = parseMetadataHeaders(metaText);
|
|
35564
36599
|
const recipients = computeReplyRecipients(kind, headers, flags);
|
|
35565
|
-
const confirmation = requireGmailDispatchConfirmation(ctx, replyDispatchOp(kind), {
|
|
36600
|
+
const confirmation = await requireGmailDispatchConfirmation(ctx, replyDispatchOp(kind), {
|
|
35566
36601
|
messageId,
|
|
35567
36602
|
recipients,
|
|
35568
36603
|
recipientCount: recipients.length,
|
|
35569
36604
|
subject: flags.subject || (headers.subject ? `Re: ${headers.subject}` : void 0),
|
|
35570
36605
|
quoting: !flags.noQuote,
|
|
35571
36606
|
bodyLength: (flags.body ?? flags.bodyHtml ?? "").length,
|
|
35572
|
-
|
|
36607
|
+
bodyPreview: bodyPreview(flags.body ?? flags.bodyHtml),
|
|
36608
|
+
bodyHtmlFile: flags.bodyHtmlFile,
|
|
36609
|
+
attachmentCount: (flags.attach?.length ?? 0) + (flags.attachInline?.length ?? 0),
|
|
36610
|
+
attachments: attachmentNames(flags.attach, flags.attachInline)
|
|
36611
|
+
}, {
|
|
36612
|
+
tool: toolName,
|
|
36613
|
+
account,
|
|
36614
|
+
confirmToken,
|
|
36615
|
+
// The metadata read above IS the phase-2 re-read: it runs on every call,
|
|
36616
|
+
// so the original's Message-ID/References are fresh here.
|
|
36617
|
+
subject: () => replyTokenSubject(kind, messageId, account, headers, metadataThreadId(metaText), flags)
|
|
35573
36618
|
});
|
|
35574
36619
|
if (confirmation) return confirmation;
|
|
35575
|
-
const args = ["gmail", kind, messageId];
|
|
36620
|
+
const args = ["gmail", kind, pos(messageId)];
|
|
35576
36621
|
appendReplyFlags(args, flags);
|
|
35577
36622
|
const result = await runOrDiagnose(args, { account });
|
|
35578
36623
|
if (!result.isError) logGmailDispatch(toolName, recipients, account);
|
|
35579
36624
|
return result;
|
|
35580
36625
|
}
|
|
36626
|
+
var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
|
|
36627
|
+
function vetGmailRun(subcommand, args) {
|
|
36628
|
+
if (subcommand === "autoreply") {
|
|
36629
|
+
return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
|
|
36630
|
+
}
|
|
36631
|
+
if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
|
|
36632
|
+
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.`;
|
|
36633
|
+
}
|
|
36634
|
+
if (subcommand === "settings") {
|
|
36635
|
+
const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
|
|
36636
|
+
if (blocked) {
|
|
36637
|
+
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.`;
|
|
36638
|
+
}
|
|
36639
|
+
}
|
|
36640
|
+
return void 0;
|
|
36641
|
+
}
|
|
35581
36642
|
function registerGmailTools(server) {
|
|
35582
36643
|
server.registerTool("gog_gmail_search", {
|
|
35583
36644
|
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 +36654,7 @@ function registerGmailTools(server) {
|
|
|
35593
36654
|
account: accountParam
|
|
35594
36655
|
})
|
|
35595
36656
|
}, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
|
|
35596
|
-
const args = ["gmail", "search", query];
|
|
36657
|
+
const args = ["gmail", "search", pos(query)];
|
|
35597
36658
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
35598
36659
|
if (all) args.push("--all");
|
|
35599
36660
|
if (fromContact) args.push(`--from-contact=${fromContact}`);
|
|
@@ -35624,13 +36685,13 @@ function registerGmailTools(server) {
|
|
|
35624
36685
|
account: accountParam
|
|
35625
36686
|
})
|
|
35626
36687
|
}, async ({ messageId, format, sanitizeContent, account }) => {
|
|
35627
|
-
const args = ["gmail", "get", messageId];
|
|
36688
|
+
const args = ["gmail", "get", pos(messageId)];
|
|
35628
36689
|
if (format) args.push(`--format=${format}`);
|
|
35629
36690
|
if (sanitizeContent) args.push("--sanitize-content");
|
|
35630
36691
|
return runOrDiagnose(args, { account });
|
|
35631
36692
|
});
|
|
35632
36693
|
server.registerTool("gog_gmail_send", {
|
|
35633
|
-
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the recipients, subject,
|
|
36694
|
+
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.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35634
36695
|
annotations: { destructiveHint: true },
|
|
35635
36696
|
inputSchema: external_exports.object({
|
|
35636
36697
|
to: external_exports.string().describe("Recipient(s), comma-separated"),
|
|
@@ -35641,11 +36702,13 @@ function registerGmailTools(server) {
|
|
|
35641
36702
|
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
36703
|
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
36704
|
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.`),
|
|
36705
|
+
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
36706
|
attachInline: attachInlineParam,
|
|
35646
|
-
account: accountParam
|
|
36707
|
+
account: accountParam,
|
|
36708
|
+
confirmToken: confirmTokenParam
|
|
35647
36709
|
})
|
|
35648
|
-
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account }, ctx) => {
|
|
36710
|
+
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account, confirmToken }, ctx) => {
|
|
36711
|
+
confinePaths(attach, "attach");
|
|
35649
36712
|
const args = ["gmail", "send", `--to=${to}`, `--subject=${subject}`, payloadArg("body", "body-file", body)];
|
|
35650
36713
|
if (cc) args.push(`--cc=${cc}`);
|
|
35651
36714
|
if (bcc) args.push(`--bcc=${bcc}`);
|
|
@@ -35656,7 +36719,7 @@ function registerGmailTools(server) {
|
|
|
35656
36719
|
const inline = inlineAttachmentArgs("attach", attachInline, args);
|
|
35657
36720
|
args.push(...inline);
|
|
35658
36721
|
const recipients = extractEmails(to, cc, bcc);
|
|
35659
|
-
const confirmation = requireGmailDispatchConfirmation(ctx, "gmail.send", {
|
|
36722
|
+
const confirmation = await requireGmailDispatchConfirmation(ctx, "gmail.send", {
|
|
35660
36723
|
to,
|
|
35661
36724
|
cc,
|
|
35662
36725
|
bcc,
|
|
@@ -35664,9 +36727,37 @@ function registerGmailTools(server) {
|
|
|
35664
36727
|
recipientCount: recipients.length,
|
|
35665
36728
|
subject,
|
|
35666
36729
|
bodyLength: body.length,
|
|
36730
|
+
bodyPreview: bodyPreview(body),
|
|
35667
36731
|
threaded: Boolean(replyToMessageId || threadId),
|
|
35668
36732
|
quoting: Boolean(quote),
|
|
35669
|
-
attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0)
|
|
36733
|
+
attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0),
|
|
36734
|
+
attachments: attachmentNames(attach, attachInline)
|
|
36735
|
+
}, {
|
|
36736
|
+
tool: "gog_gmail_send",
|
|
36737
|
+
account,
|
|
36738
|
+
confirmToken,
|
|
36739
|
+
// Nothing is stored between the phases: the payload IS the arguments,
|
|
36740
|
+
// so phase 2 must repeat them and any difference is a changed send.
|
|
36741
|
+
subject: () => {
|
|
36742
|
+
const attachments = attachmentDetails(attach, attachInline);
|
|
36743
|
+
const from = senderPreview(account);
|
|
36744
|
+
return {
|
|
36745
|
+
target: replyToMessageId ?? threadId ?? "",
|
|
36746
|
+
payload: { from, to, cc, bcc, subject, body, attachments, threadId, inReplyTo: replyToMessageId, quote: Boolean(quote) },
|
|
36747
|
+
preview: {
|
|
36748
|
+
from,
|
|
36749
|
+
to,
|
|
36750
|
+
cc,
|
|
36751
|
+
bcc,
|
|
36752
|
+
subject,
|
|
36753
|
+
body,
|
|
36754
|
+
attachments: attachmentPreview(attachments),
|
|
36755
|
+
threadId,
|
|
36756
|
+
inReplyTo: replyToMessageId,
|
|
36757
|
+
quotesOriginal: Boolean(quote)
|
|
36758
|
+
}
|
|
36759
|
+
};
|
|
36760
|
+
}
|
|
35670
36761
|
});
|
|
35671
36762
|
if (confirmation) return confirmation;
|
|
35672
36763
|
const result = await runOrDiagnose(args, { account });
|
|
@@ -35674,22 +36765,32 @@ function registerGmailTools(server) {
|
|
|
35674
36765
|
return result;
|
|
35675
36766
|
});
|
|
35676
36767
|
server.registerTool("gog_gmail_reply", {
|
|
35677
|
-
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipient, subject,
|
|
36768
|
+
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.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35678
36769
|
annotations: { destructiveHint: true },
|
|
35679
36770
|
inputSchema: sendReplySchema
|
|
35680
|
-
}, async ({ messageId, account, ...flags }, ctx) => {
|
|
36771
|
+
}, async ({ messageId, account, confirmToken, ...flags }, ctx) => {
|
|
35681
36772
|
assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
|
|
35682
|
-
|
|
36773
|
+
confineReplyPaths(flags);
|
|
36774
|
+
return sendReply("reply", "gog_gmail_reply", messageId, account, flags, ctx, confirmToken);
|
|
35683
36775
|
});
|
|
35684
36776
|
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,
|
|
36777
|
+
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.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35686
36778
|
annotations: { destructiveHint: true },
|
|
35687
36779
|
inputSchema: sendReplySchema
|
|
35688
|
-
}, async ({ messageId, account, ...flags }, ctx) => {
|
|
36780
|
+
}, async ({ messageId, account, confirmToken, ...flags }, ctx) => {
|
|
35689
36781
|
assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
|
|
35690
|
-
|
|
36782
|
+
confineReplyPaths(flags);
|
|
36783
|
+
return sendReply("reply-all", "gog_gmail_reply_all", messageId, account, flags, ctx, confirmToken);
|
|
36784
|
+
});
|
|
36785
|
+
registerRunTool(server, {
|
|
36786
|
+
service: "gmail",
|
|
36787
|
+
examples: '"archive", "mark-read", "labels"',
|
|
36788
|
+
// gog's --gmail-no-send blocks send/reply/forward/drafts send and all of
|
|
36789
|
+
// their aliases at runtime (verified on gog 0.41.0). Sending goes through the
|
|
36790
|
+
// confirmed tools, never this escape hatch.
|
|
36791
|
+
gmailNoSend: true,
|
|
36792
|
+
vet: vetGmailRun
|
|
35691
36793
|
});
|
|
35692
|
-
registerRunTool(server, { service: "gmail", examples: '"archive", "mark-read", "labels"' });
|
|
35693
36794
|
}
|
|
35694
36795
|
|
|
35695
36796
|
// src/tools/sheets-a1.ts
|
|
@@ -35758,7 +36859,7 @@ function registerSheetsTools(server) {
|
|
|
35758
36859
|
account: accountParam
|
|
35759
36860
|
})
|
|
35760
36861
|
}, async ({ spreadsheetId, range, account }) => {
|
|
35761
|
-
return runOrDiagnose(["sheets", "get", spreadsheetId, range], { account });
|
|
36862
|
+
return runOrDiagnose(["sheets", "get", pos(spreadsheetId), pos(range)], { account });
|
|
35762
36863
|
});
|
|
35763
36864
|
server.registerTool("gog_sheets_update", {
|
|
35764
36865
|
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 +36879,7 @@ function registerSheetsTools(server) {
|
|
|
35778
36879
|
const readRange = expandAnchorRange(range, values.length, cols);
|
|
35779
36880
|
let existing;
|
|
35780
36881
|
try {
|
|
35781
|
-
existing = await run(["sheets", "get", spreadsheetId, readRange], { account });
|
|
36882
|
+
existing = await run(["sheets", "get", pos(spreadsheetId), pos(readRange)], { account });
|
|
35782
36883
|
} catch (err) {
|
|
35783
36884
|
return diagnose(err);
|
|
35784
36885
|
}
|
|
@@ -35790,7 +36891,7 @@ function registerSheetsTools(server) {
|
|
|
35790
36891
|
);
|
|
35791
36892
|
}
|
|
35792
36893
|
}
|
|
35793
|
-
const args = ["sheets", "update", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
36894
|
+
const args = ["sheets", "update", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
|
|
35794
36895
|
if (dry_run) args.push("--dry-run");
|
|
35795
36896
|
if (fail_on_formula_error) args.push("--fail-on-formula-error");
|
|
35796
36897
|
return runOrDiagnose(args, { account });
|
|
@@ -35806,7 +36907,7 @@ function registerSheetsTools(server) {
|
|
|
35806
36907
|
account: accountParam
|
|
35807
36908
|
})
|
|
35808
36909
|
}, async ({ spreadsheetId, range, values, account, dry_run }) => {
|
|
35809
|
-
const args = ["sheets", "append", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
36910
|
+
const args = ["sheets", "append", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
|
|
35810
36911
|
if (dry_run) args.push("--dry-run");
|
|
35811
36912
|
return runOrDiagnose(args, { account });
|
|
35812
36913
|
});
|
|
@@ -35820,7 +36921,7 @@ function registerSheetsTools(server) {
|
|
|
35820
36921
|
account: accountParam
|
|
35821
36922
|
})
|
|
35822
36923
|
}, async ({ spreadsheetId, range, account, dry_run }) => {
|
|
35823
|
-
const args = ["sheets", "clear", spreadsheetId, range];
|
|
36924
|
+
const args = ["sheets", "clear", pos(spreadsheetId), pos(range)];
|
|
35824
36925
|
if (dry_run) args.push("--dry-run");
|
|
35825
36926
|
return runOrDiagnose(args, { account });
|
|
35826
36927
|
});
|
|
@@ -35832,7 +36933,7 @@ function registerSheetsTools(server) {
|
|
|
35832
36933
|
account: accountParam
|
|
35833
36934
|
})
|
|
35834
36935
|
}, async ({ spreadsheetId, account }) => {
|
|
35835
|
-
return runOrDiagnose(["sheets", "metadata", spreadsheetId], { account });
|
|
36936
|
+
return runOrDiagnose(["sheets", "metadata", pos(spreadsheetId)], { account });
|
|
35836
36937
|
});
|
|
35837
36938
|
server.registerTool("gog_sheets_create", {
|
|
35838
36939
|
description: "Create a new Google Spreadsheet. Returns JSON with the new spreadsheetId and URL.",
|
|
@@ -35842,7 +36943,7 @@ function registerSheetsTools(server) {
|
|
|
35842
36943
|
account: accountParam
|
|
35843
36944
|
})
|
|
35844
36945
|
}, async ({ title, account }) => {
|
|
35845
|
-
return runOrDiagnose(["sheets", "create", title], { account });
|
|
36946
|
+
return runOrDiagnose(["sheets", "create", pos(title)], { account });
|
|
35846
36947
|
});
|
|
35847
36948
|
server.registerTool("gog_sheets_find_replace", {
|
|
35848
36949
|
description: "Find and replace text across an entire Google Spreadsheet.",
|
|
@@ -35854,7 +36955,7 @@ function registerSheetsTools(server) {
|
|
|
35854
36955
|
account: accountParam
|
|
35855
36956
|
})
|
|
35856
36957
|
}, async ({ spreadsheetId, find, replace, account }) => {
|
|
35857
|
-
return runOrDiagnose(["sheets", "find-replace", spreadsheetId, find, replace], { account });
|
|
36958
|
+
return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
|
|
35858
36959
|
});
|
|
35859
36960
|
registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
|
|
35860
36961
|
}
|
|
@@ -35862,8 +36963,9 @@ function registerSheetsTools(server) {
|
|
|
35862
36963
|
// src/tools/slides.ts
|
|
35863
36964
|
function registerSlidesTools(server) {
|
|
35864
36965
|
server.registerTool("gog_slides_export", {
|
|
35865
|
-
description: "Export a Google Slides presentation to a local file (pdf or pptx).",
|
|
35866
|
-
|
|
36966
|
+
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.",
|
|
36967
|
+
// Writes a file on the gog host and can overwrite one: not read-only.
|
|
36968
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
35867
36969
|
inputSchema: external_exports.object({
|
|
35868
36970
|
presentationId: external_exports.string().describe("Presentation ID"),
|
|
35869
36971
|
out: external_exports.string().optional().describe("Output file path"),
|
|
@@ -35872,7 +36974,8 @@ function registerSlidesTools(server) {
|
|
|
35872
36974
|
account: accountParam
|
|
35873
36975
|
})
|
|
35874
36976
|
}, async ({ presentationId, out, format, overwrite, account }) => {
|
|
35875
|
-
|
|
36977
|
+
if (out) confinePath(out, "out");
|
|
36978
|
+
const args = ["slides", "export", pos(presentationId)];
|
|
35876
36979
|
if (out) args.push(`--out=${out}`);
|
|
35877
36980
|
if (format) args.push(`--format=${format}`);
|
|
35878
36981
|
if (overwrite) args.push("--overwrite");
|
|
@@ -35886,7 +36989,7 @@ function registerSlidesTools(server) {
|
|
|
35886
36989
|
account: accountParam
|
|
35887
36990
|
})
|
|
35888
36991
|
}, async ({ presentationId, account }) => {
|
|
35889
|
-
return runOrDiagnose(["slides", "info", presentationId], { account });
|
|
36992
|
+
return runOrDiagnose(["slides", "info", pos(presentationId)], { account });
|
|
35890
36993
|
});
|
|
35891
36994
|
server.registerTool("gog_slides_create", {
|
|
35892
36995
|
description: "Create a new Google Slides presentation, optionally in a folder or copying from a template.",
|
|
@@ -35898,7 +37001,7 @@ function registerSlidesTools(server) {
|
|
|
35898
37001
|
account: accountParam
|
|
35899
37002
|
})
|
|
35900
37003
|
}, async ({ title, parent, template, account }) => {
|
|
35901
|
-
const args = ["slides", "create", title];
|
|
37004
|
+
const args = ["slides", "create", pos(title)];
|
|
35902
37005
|
if (parent) args.push(`--parent=${parent}`);
|
|
35903
37006
|
if (template) args.push(`--template=${template}`);
|
|
35904
37007
|
return runOrDiagnose(args, { account });
|
|
@@ -35913,7 +37016,7 @@ function registerSlidesTools(server) {
|
|
|
35913
37016
|
account: accountParam
|
|
35914
37017
|
})
|
|
35915
37018
|
}, async ({ presentationId, title, parent, account }) => {
|
|
35916
|
-
const args = ["slides", "copy", presentationId, title];
|
|
37019
|
+
const args = ["slides", "copy", pos(presentationId), pos(title)];
|
|
35917
37020
|
if (parent) args.push(`--parent=${parent}`);
|
|
35918
37021
|
return runOrDiagnose(args, { account });
|
|
35919
37022
|
});
|
|
@@ -35925,7 +37028,7 @@ function registerSlidesTools(server) {
|
|
|
35925
37028
|
account: accountParam
|
|
35926
37029
|
})
|
|
35927
37030
|
}, async ({ presentationId, account }) => {
|
|
35928
|
-
return runOrDiagnose(["slides", "list-slides", presentationId], { account });
|
|
37031
|
+
return runOrDiagnose(["slides", "list-slides", pos(presentationId)], { account });
|
|
35929
37032
|
});
|
|
35930
37033
|
server.registerTool("gog_slides_read_slide", {
|
|
35931
37034
|
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 +37040,11 @@ function registerSlidesTools(server) {
|
|
|
35937
37040
|
account: accountParam
|
|
35938
37041
|
})
|
|
35939
37042
|
}, async ({ presentationId, slideId, detail, account }) => {
|
|
35940
|
-
const args = ["slides", "read-slide", presentationId, slideId];
|
|
37043
|
+
const args = ["slides", "read-slide", pos(presentationId), pos(slideId)];
|
|
35941
37044
|
if (detail) args.push("--detail");
|
|
35942
37045
|
return runOrDiagnose(args, { account });
|
|
35943
37046
|
});
|
|
35944
|
-
registerRunTool(server, { service: "slides", examples: '"
|
|
37047
|
+
registerRunTool(server, { service: "slides", examples: '"delete-slide", "update-notes"' });
|
|
35945
37048
|
}
|
|
35946
37049
|
|
|
35947
37050
|
// src/tools/tasks.ts
|
|
@@ -35963,7 +37066,7 @@ function registerTasksTools(server) {
|
|
|
35963
37066
|
account: accountParam
|
|
35964
37067
|
})
|
|
35965
37068
|
}, async ({ tasklistId, account }) => {
|
|
35966
|
-
return runOrDiagnose(["tasks", "list", tasklistId], { account });
|
|
37069
|
+
return runOrDiagnose(["tasks", "list", pos(tasklistId)], { account });
|
|
35967
37070
|
});
|
|
35968
37071
|
server.registerTool("gog_tasks_get", {
|
|
35969
37072
|
description: "Get a specific task by ID.",
|
|
@@ -35974,7 +37077,7 @@ function registerTasksTools(server) {
|
|
|
35974
37077
|
account: accountParam
|
|
35975
37078
|
})
|
|
35976
37079
|
}, async ({ tasklistId, taskId, account }) => {
|
|
35977
|
-
return runOrDiagnose(["tasks", "get", tasklistId, taskId], { account });
|
|
37080
|
+
return runOrDiagnose(["tasks", "get", pos(tasklistId), pos(taskId)], { account });
|
|
35978
37081
|
});
|
|
35979
37082
|
server.registerTool("gog_tasks_add", {
|
|
35980
37083
|
description: "Add a task to a Google Task list.",
|
|
@@ -35987,7 +37090,7 @@ function registerTasksTools(server) {
|
|
|
35987
37090
|
account: accountParam
|
|
35988
37091
|
})
|
|
35989
37092
|
}, async ({ tasklistId, title, notes, due, account }) => {
|
|
35990
|
-
const args = ["tasks", "add", tasklistId, `--title=${title}`];
|
|
37093
|
+
const args = ["tasks", "add", pos(tasklistId), `--title=${title}`];
|
|
35991
37094
|
if (notes) args.push(`--notes=${notes}`);
|
|
35992
37095
|
if (due) args.push(`--due=${due}`);
|
|
35993
37096
|
return runOrDiagnose(args, { account });
|
|
@@ -36001,7 +37104,7 @@ function registerTasksTools(server) {
|
|
|
36001
37104
|
account: accountParam
|
|
36002
37105
|
})
|
|
36003
37106
|
}, async ({ tasklistId, taskId, account }) => {
|
|
36004
|
-
return runOrDiagnose(["tasks", "done", tasklistId, taskId], { account });
|
|
37107
|
+
return runOrDiagnose(["tasks", "done", pos(tasklistId), pos(taskId)], { account });
|
|
36005
37108
|
});
|
|
36006
37109
|
server.registerTool("gog_tasks_delete", {
|
|
36007
37110
|
description: "Delete a task.",
|
|
@@ -36012,13 +37115,13 @@ function registerTasksTools(server) {
|
|
|
36012
37115
|
account: accountParam
|
|
36013
37116
|
})
|
|
36014
37117
|
}, async ({ tasklistId, taskId, account }) => {
|
|
36015
|
-
return runOrDiagnose(["tasks", "delete", tasklistId, taskId, "--force"], { account });
|
|
37118
|
+
return runOrDiagnose(["tasks", "delete", pos(tasklistId), pos(taskId), "--force"], { account });
|
|
36016
37119
|
});
|
|
36017
37120
|
registerRunTool(server, { service: "tasks", examples: '"update", "undo", "clear"' });
|
|
36018
37121
|
}
|
|
36019
37122
|
|
|
36020
37123
|
// src/server.ts
|
|
36021
|
-
var VERSION = true ? "4.
|
|
37124
|
+
var VERSION = true ? "4.4.0" : "0.0.0";
|
|
36022
37125
|
var BASE_TOOL_REGISTRARS = [
|
|
36023
37126
|
registerApiTools,
|
|
36024
37127
|
registerAppScriptTools,
|
|
@@ -36036,7 +37139,7 @@ var BASE_TOOL_REGISTRARS = [
|
|
|
36036
37139
|
];
|
|
36037
37140
|
|
|
36038
37141
|
// src/bootstrap-auth.ts
|
|
36039
|
-
import { join as
|
|
37142
|
+
import { join as join5 } from "node:path";
|
|
36040
37143
|
var AUTH_BOOTSTRAP_MARKER = "auth-bootstrap.sha256";
|
|
36041
37144
|
var VARS = ["GOG_CLIENT_ID", "GOG_CLIENT_SECRET", "GOG_REFRESH_TOKEN", "GOG_ACCOUNT"];
|
|
36042
37145
|
var log = (line) => {
|
|
@@ -36070,12 +37173,12 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
|
|
|
36070
37173
|
const [clientId, clientSecret, refreshToken, account] = values;
|
|
36071
37174
|
const { spawner } = options;
|
|
36072
37175
|
try {
|
|
36073
|
-
const { createHash } = await import("node:crypto");
|
|
37176
|
+
const { createHash: createHash4 } = await import("node:crypto");
|
|
36074
37177
|
const { mkdir, readFile, writeFile, chmod } = await import("node:fs/promises");
|
|
36075
37178
|
const home = options.home ?? (await import("node:os")).homedir();
|
|
36076
|
-
const dir =
|
|
36077
|
-
const marker =
|
|
36078
|
-
const fingerprint =
|
|
37179
|
+
const dir = join5(home, ".gogcli-mcp");
|
|
37180
|
+
const marker = join5(dir, AUTH_BOOTSTRAP_MARKER);
|
|
37181
|
+
const fingerprint = createHash4("sha256").update(values.join("\0")).digest("hex");
|
|
36079
37182
|
const previous = await readFile(marker, "utf8").catch(() => void 0);
|
|
36080
37183
|
if (previous === fingerprint && await accountListed(account, spawner)) return "present";
|
|
36081
37184
|
await run(
|
|
@@ -36100,7 +37203,7 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
|
|
|
36100
37203
|
}
|
|
36101
37204
|
|
|
36102
37205
|
// src/blob-urls.ts
|
|
36103
|
-
import { createHmac } from "node:crypto";
|
|
37206
|
+
import { createHmac as createHmac3 } from "node:crypto";
|
|
36104
37207
|
var BLOB_URL_MAX_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
36105
37208
|
var BLOB_URL_CEILING_MARGIN_MS = 60 * 1e3;
|
|
36106
37209
|
var BLOB_URL_DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -36112,8 +37215,8 @@ ${exp}`;
|
|
|
36112
37215
|
function writePayload(objectKey, contentType, exp) {
|
|
36113
37216
|
return `put\0${objectKey}\0${contentType}\0${exp}`;
|
|
36114
37217
|
}
|
|
36115
|
-
function
|
|
36116
|
-
return
|
|
37218
|
+
function sign2(signingKey, payload) {
|
|
37219
|
+
return createHmac3("sha256", signingKey).update(payload, "utf8").digest("base64url");
|
|
36117
37220
|
}
|
|
36118
37221
|
function objectPathSegments(rest) {
|
|
36119
37222
|
const segments = rest.split("/");
|
|
@@ -36149,7 +37252,7 @@ function createBlobUrlMinter(config2) {
|
|
|
36149
37252
|
function mint(rest, exp, payload) {
|
|
36150
37253
|
const segments = objectPathSegments(rest);
|
|
36151
37254
|
const url2 = `${basePrefix}/${segments.map(encodeURIComponent).join("/")}`;
|
|
36152
|
-
const signature =
|
|
37255
|
+
const signature = sign2(config2.signingKey, payload(`${registrationId}/${segments.join("/")}`));
|
|
36153
37256
|
return `${url2}?exp=${exp}&sig=${signature}`;
|
|
36154
37257
|
}
|
|
36155
37258
|
return {
|
|
@@ -36175,7 +37278,6 @@ function blobStoreFromEnv() {
|
|
|
36175
37278
|
}
|
|
36176
37279
|
|
|
36177
37280
|
// src/blob-upload.ts
|
|
36178
|
-
var ATTACHMENT_DOWNLOAD_ROOT = "/tmp/gog-attachments";
|
|
36179
37281
|
var MAX_BLOB_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
36180
37282
|
var BLOB_UPLOAD_TIMEOUT_MS = 12e4;
|
|
36181
37283
|
var ERROR_BODY_SNIPPET = 512;
|
|
@@ -36190,10 +37292,10 @@ var UploadError = class extends Error {
|
|
|
36190
37292
|
async function resolveConfined(root, requested) {
|
|
36191
37293
|
const path = await import("node:path");
|
|
36192
37294
|
const { realpath, stat } = await import("node:fs/promises");
|
|
36193
|
-
const
|
|
37295
|
+
const isInside3 = (base, candidate2) => candidate2 === base || candidate2.startsWith(base + path.sep);
|
|
36194
37296
|
const lexicalRoot = path.resolve(root);
|
|
36195
37297
|
const candidate = path.resolve(lexicalRoot, requested);
|
|
36196
|
-
if (!
|
|
37298
|
+
if (!isInside3(lexicalRoot, candidate)) {
|
|
36197
37299
|
throw new UploadError(`the attachment path must be inside ${lexicalRoot}`);
|
|
36198
37300
|
}
|
|
36199
37301
|
let realFile;
|
|
@@ -36204,7 +37306,7 @@ async function resolveConfined(root, requested) {
|
|
|
36204
37306
|
} catch (err) {
|
|
36205
37307
|
throw new UploadError(`the downloaded attachment could not be found: ${err.message}`);
|
|
36206
37308
|
}
|
|
36207
|
-
if (!
|
|
37309
|
+
if (!isInside3(realRoot, realFile)) {
|
|
36208
37310
|
throw new UploadError(`the attachment path must be inside ${realRoot}`);
|
|
36209
37311
|
}
|
|
36210
37312
|
const info = await stat(realFile);
|
|
@@ -36231,7 +37333,7 @@ async function putFile(target, file2, bytes, contentType, timeoutMs) {
|
|
|
36231
37333
|
const { createReadStream } = await import("node:fs");
|
|
36232
37334
|
const { pipeline } = await import("node:stream");
|
|
36233
37335
|
const transport = target.protocol === "https:" ? await import("node:https") : await import("node:http");
|
|
36234
|
-
return new Promise((
|
|
37336
|
+
return new Promise((resolve4, reject) => {
|
|
36235
37337
|
let settled = false;
|
|
36236
37338
|
const source = createReadStream(file2);
|
|
36237
37339
|
const req = transport.request(target, {
|
|
@@ -36261,7 +37363,7 @@ async function putFile(target, file2, bytes, contentType, timeoutMs) {
|
|
|
36261
37363
|
held += chunk.length;
|
|
36262
37364
|
});
|
|
36263
37365
|
res.on("error", fail);
|
|
36264
|
-
res.on("end", () => finish2(() =>
|
|
37366
|
+
res.on("end", () => finish2(() => resolve4({
|
|
36265
37367
|
status: res.statusCode,
|
|
36266
37368
|
body: Buffer.concat(chunks).toString().slice(0, ERROR_BODY_SNIPPET)
|
|
36267
37369
|
})));
|
|
@@ -36293,11 +37395,14 @@ async function uploadToBlobStore(request, options = {}) {
|
|
|
36293
37395
|
}
|
|
36294
37396
|
export {
|
|
36295
37397
|
ATTACHMENT_DOWNLOAD_ROOT,
|
|
37398
|
+
ATTACHMENT_TTL_MS,
|
|
36296
37399
|
AUTH_BOOTSTRAP_MARKER,
|
|
36297
37400
|
BASE_TOOL_REGISTRARS,
|
|
36298
37401
|
BLOB_UPLOAD_TIMEOUT_MS,
|
|
36299
37402
|
BLOB_URL_DEFAULT_TTL_MS,
|
|
36300
37403
|
BLOB_URL_MAX_TTL_MS,
|
|
37404
|
+
CONFIRM_FALLBACK_DESCRIPTION,
|
|
37405
|
+
FILE_ROOTS_ENV,
|
|
36301
37406
|
INLINE_ATTACHMENT_LIMITS_TEXT,
|
|
36302
37407
|
MAX_BLOB_UPLOAD_BYTES,
|
|
36303
37408
|
MAX_INLINE_ATTACHMENT_BYTES,
|
|
@@ -36311,14 +37416,24 @@ export {
|
|
|
36311
37416
|
appendReplyFlags,
|
|
36312
37417
|
assertNotBoth,
|
|
36313
37418
|
attachInlineParam,
|
|
37419
|
+
attachmentDetails,
|
|
37420
|
+
attachmentNames,
|
|
37421
|
+
attachmentPreview,
|
|
36314
37422
|
authToolsFor,
|
|
36315
37423
|
blobStoreFromEnv,
|
|
37424
|
+
bodyPreview,
|
|
36316
37425
|
bootstrapGogAuth,
|
|
37426
|
+
confineAtFile,
|
|
37427
|
+
confinePath,
|
|
37428
|
+
confinePaths,
|
|
37429
|
+
confirmTokenParam,
|
|
36317
37430
|
createBlobUrlMinter,
|
|
37431
|
+
defaultFileRoot,
|
|
36318
37432
|
diagnose,
|
|
36319
37433
|
errorText,
|
|
36320
37434
|
extractEmails,
|
|
36321
37435
|
fetchGmailPages,
|
|
37436
|
+
fileRoots,
|
|
36322
37437
|
finalizeGmailSearch,
|
|
36323
37438
|
ids,
|
|
36324
37439
|
inlineAttachmentArgs,
|
|
@@ -36331,7 +37446,10 @@ export {
|
|
|
36331
37446
|
pageTokenParam,
|
|
36332
37447
|
paginationParams,
|
|
36333
37448
|
payloadArg,
|
|
37449
|
+
pos,
|
|
37450
|
+
prepareDownloadRoot,
|
|
36334
37451
|
pushPaginationFlags,
|
|
37452
|
+
readCourse,
|
|
36335
37453
|
registerApiTools,
|
|
36336
37454
|
registerAppScriptTools,
|
|
36337
37455
|
registerAuthTools,
|
|
@@ -36346,13 +37464,16 @@ export {
|
|
|
36346
37464
|
registerSheetsTools,
|
|
36347
37465
|
registerSlidesTools,
|
|
36348
37466
|
registerTasksTools,
|
|
37467
|
+
removeDownload,
|
|
36349
37468
|
replySchema,
|
|
37469
|
+
requireDispatchConfirmation,
|
|
36350
37470
|
requireGmailDispatchConfirmation,
|
|
36351
37471
|
resolvePageToken,
|
|
36352
37472
|
resultText,
|
|
36353
37473
|
run,
|
|
36354
37474
|
runBinary,
|
|
36355
37475
|
runOrDiagnose,
|
|
37476
|
+
senderPreview,
|
|
36356
37477
|
stripConsumedPageToken,
|
|
36357
37478
|
uploadToBlobStore
|
|
36358
37479
|
};
|