gogcli-mcp 4.4.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +827 -315
- package/dist/lib.js +832 -281
- package/manifest.json +7 -7
- package/package.json +3 -3
- package/server.json +2 -2
- package/src/dispatch-confirmation.ts +102 -115
- package/src/gmail-dispatch-guard.ts +28 -21
- package/src/lib.ts +6 -2
- package/src/send-confirm-token.ts +17 -155
- package/src/tools/api.ts +74 -8
- package/src/tools/appscript.ts +63 -2
- package/src/tools/calendar.ts +83 -13
- package/src/tools/chat.ts +35 -4
- package/src/tools/classroom.ts +254 -25
- package/src/tools/docs.ts +7 -1
- package/src/tools/drive.ts +82 -10
- package/src/tools/gmail.ts +37 -1
- package/src/tools/sheets.ts +16 -1
- package/src/tools/utils.ts +10 -4
- package/tests/gmail-dispatch-guard.test.ts +60 -25
- package/tests/send-confirm-token.test.ts +17 -197
- package/tests/tools/api.test.ts +130 -5
- package/tests/tools/appscript.test.ts +6 -2
- package/tests/tools/dispatch-gates-more.test.ts +447 -0
- package/tests/tools/dispatch-gates.test.ts +74 -13
- package/tests/tools/docs.test.ts +5 -6
- package/tests/tools/drive.test.ts +2 -5
- package/tests/tools/gmail-confirm-token.test.ts +13 -11
- package/tests/tools/run-vets-more.test.ts +269 -0
- package/tests/tools/run-vets.test.ts +158 -4
- package/tests/tools/sheets.test.ts +10 -11
- package/tests/tools/utils.test.ts +38 -41
package/dist/lib.js
CHANGED
|
@@ -838,14 +838,14 @@ function promiseAllObject(promisesObj) {
|
|
|
838
838
|
}
|
|
839
839
|
function randomString(length = 10) {
|
|
840
840
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
841
|
-
let
|
|
841
|
+
let str2 = "";
|
|
842
842
|
for (let i = 0; i < length; i++) {
|
|
843
|
-
|
|
843
|
+
str2 += chars[Math.floor(Math.random() * chars.length)];
|
|
844
844
|
}
|
|
845
|
-
return
|
|
845
|
+
return str2;
|
|
846
846
|
}
|
|
847
|
-
function esc(
|
|
848
|
-
return JSON.stringify(
|
|
847
|
+
function esc(str2) {
|
|
848
|
+
return JSON.stringify(str2);
|
|
849
849
|
}
|
|
850
850
|
function slugify(input2) {
|
|
851
851
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -959,8 +959,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
959
959
|
"symbol",
|
|
960
960
|
"undefined"
|
|
961
961
|
]);
|
|
962
|
-
function escapeRegex(
|
|
963
|
-
return
|
|
962
|
+
function escapeRegex(str2) {
|
|
963
|
+
return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
964
964
|
}
|
|
965
965
|
function clone(inst, def, params) {
|
|
966
966
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -1214,13 +1214,13 @@ function getSizableOrigin(input2) {
|
|
|
1214
1214
|
return "unknown";
|
|
1215
1215
|
}
|
|
1216
1216
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
1217
|
-
function codePointLength(
|
|
1218
|
-
const units =
|
|
1219
|
-
if (!highSurrogate.test(
|
|
1217
|
+
function codePointLength(str2) {
|
|
1218
|
+
const units = str2.length;
|
|
1219
|
+
if (!highSurrogate.test(str2))
|
|
1220
1220
|
return units;
|
|
1221
1221
|
let count = units;
|
|
1222
1222
|
for (let i = 0; i < units - 1; i++) {
|
|
1223
|
-
if ((
|
|
1223
|
+
if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
1224
1224
|
count--;
|
|
1225
1225
|
i++;
|
|
1226
1226
|
}
|
|
@@ -25182,7 +25182,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
25182
25182
|
}
|
|
25183
25183
|
exports._ = _;
|
|
25184
25184
|
const plus = new _Code("+");
|
|
25185
|
-
function
|
|
25185
|
+
function str2(strs, ...args) {
|
|
25186
25186
|
const expr = [safeStringify(strs[0])];
|
|
25187
25187
|
let i = 0;
|
|
25188
25188
|
while (i < args.length) {
|
|
@@ -25193,7 +25193,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
25193
25193
|
optimize(expr);
|
|
25194
25194
|
return new _Code(expr);
|
|
25195
25195
|
}
|
|
25196
|
-
exports.str =
|
|
25196
|
+
exports.str = str2;
|
|
25197
25197
|
function addCodeArg(code, arg) {
|
|
25198
25198
|
if (arg instanceof _Code) code.push(...arg._items);
|
|
25199
25199
|
else if (arg instanceof Name) code.push(arg);
|
|
@@ -25226,7 +25226,7 @@ var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
25226
25226
|
if (typeof b == "string" && b[0] === '"' && !(a instanceof Name)) return `"${a}${b.slice(1)}`;
|
|
25227
25227
|
}
|
|
25228
25228
|
function strConcat(c1, c2) {
|
|
25229
|
-
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 :
|
|
25229
|
+
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
|
|
25230
25230
|
}
|
|
25231
25231
|
exports.strConcat = strConcat;
|
|
25232
25232
|
function interpolate(x) {
|
|
@@ -26105,21 +26105,21 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
26105
26105
|
return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
|
|
26106
26106
|
}
|
|
26107
26107
|
exports.schemaRefOrVal = schemaRefOrVal;
|
|
26108
|
-
function unescapeFragment(
|
|
26109
|
-
return unescapeJsonPointer(decodeURIComponent(
|
|
26108
|
+
function unescapeFragment(str2) {
|
|
26109
|
+
return unescapeJsonPointer(decodeURIComponent(str2));
|
|
26110
26110
|
}
|
|
26111
26111
|
exports.unescapeFragment = unescapeFragment;
|
|
26112
|
-
function escapeFragment(
|
|
26113
|
-
return encodeURIComponent(escapeJsonPointer(
|
|
26112
|
+
function escapeFragment(str2) {
|
|
26113
|
+
return encodeURIComponent(escapeJsonPointer(str2));
|
|
26114
26114
|
}
|
|
26115
26115
|
exports.escapeFragment = escapeFragment;
|
|
26116
|
-
function escapeJsonPointer(
|
|
26117
|
-
if (typeof
|
|
26118
|
-
return
|
|
26116
|
+
function escapeJsonPointer(str2) {
|
|
26117
|
+
if (typeof str2 == "number") return `${str2}`;
|
|
26118
|
+
return str2.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
26119
26119
|
}
|
|
26120
26120
|
exports.escapeJsonPointer = escapeJsonPointer;
|
|
26121
|
-
function unescapeJsonPointer(
|
|
26122
|
-
return
|
|
26121
|
+
function unescapeJsonPointer(str2) {
|
|
26122
|
+
return str2.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
26123
26123
|
}
|
|
26124
26124
|
exports.unescapeJsonPointer = unescapeJsonPointer;
|
|
26125
26125
|
function eachItem(xs, f) {
|
|
@@ -27005,8 +27005,8 @@ var require_json_schema_traverse = /* @__PURE__ */ __commonJSMin(((exports, modu
|
|
|
27005
27005
|
post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
|
|
27006
27006
|
}
|
|
27007
27007
|
}
|
|
27008
|
-
function escapeJsonPtr(
|
|
27009
|
-
return
|
|
27008
|
+
function escapeJsonPtr(str2) {
|
|
27009
|
+
return str2.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
27010
27010
|
}
|
|
27011
27011
|
}));
|
|
27012
27012
|
var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -27898,9 +27898,9 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
27898
27898
|
isIPV6: false
|
|
27899
27899
|
};
|
|
27900
27900
|
}
|
|
27901
|
-
function findToken(
|
|
27901
|
+
function findToken(str2, token) {
|
|
27902
27902
|
let ind = 0;
|
|
27903
|
-
for (let i = 0; i <
|
|
27903
|
+
for (let i = 0; i < str2.length; i++) if (str2[i] === token) ind++;
|
|
27904
27904
|
return ind;
|
|
27905
27905
|
}
|
|
27906
27906
|
function removeDotSegments(path) {
|
|
@@ -28415,7 +28415,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
28415
28415
|
const util_1 = require_util();
|
|
28416
28416
|
const $dataRefSchema = require_data();
|
|
28417
28417
|
const uri_1 = require_uri();
|
|
28418
|
-
const defaultRegExp = (
|
|
28418
|
+
const defaultRegExp = (str2, flags) => new RegExp(str2, flags);
|
|
28419
28419
|
defaultRegExp.code = "new RegExp";
|
|
28420
28420
|
const META_IGNORE_OPTIONS = [
|
|
28421
28421
|
"removeAdditional",
|
|
@@ -29121,16 +29121,16 @@ var require_multipleOf = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
29121
29121
|
}));
|
|
29122
29122
|
var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
29123
29123
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29124
|
-
function ucs2length(
|
|
29125
|
-
const len =
|
|
29124
|
+
function ucs2length(str2) {
|
|
29125
|
+
const len = str2.length;
|
|
29126
29126
|
let length = 0;
|
|
29127
29127
|
let pos2 = 0;
|
|
29128
29128
|
let value;
|
|
29129
29129
|
while (pos2 < len) {
|
|
29130
29130
|
length++;
|
|
29131
|
-
value =
|
|
29131
|
+
value = str2.charCodeAt(pos2++);
|
|
29132
29132
|
if (value >= 55296 && value <= 56319 && pos2 < len) {
|
|
29133
|
-
value =
|
|
29133
|
+
value = str2.charCodeAt(pos2);
|
|
29134
29134
|
if ((value & 64512) === 56320) pos2++;
|
|
29135
29135
|
}
|
|
29136
29136
|
}
|
|
@@ -31841,8 +31841,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31841
31841
|
30,
|
|
31842
31842
|
31
|
|
31843
31843
|
];
|
|
31844
|
-
function date5(
|
|
31845
|
-
const matches = DATE.exec(
|
|
31844
|
+
function date5(str2) {
|
|
31845
|
+
const matches = DATE.exec(str2);
|
|
31846
31846
|
if (!matches) return false;
|
|
31847
31847
|
const year = +matches[1];
|
|
31848
31848
|
const month = +matches[2];
|
|
@@ -31857,8 +31857,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31857
31857
|
}
|
|
31858
31858
|
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
31859
31859
|
function getTime(strictTimeZone) {
|
|
31860
|
-
return function time3(
|
|
31861
|
-
const matches = TIME.exec(
|
|
31860
|
+
return function time3(str2) {
|
|
31861
|
+
const matches = TIME.exec(str2);
|
|
31862
31862
|
if (!matches) return false;
|
|
31863
31863
|
const hr = +matches[1];
|
|
31864
31864
|
const min = +matches[2];
|
|
@@ -31895,8 +31895,8 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31895
31895
|
const DATE_TIME_SEPARATOR = /t|\s/i;
|
|
31896
31896
|
function getDateTime(strictTimeZone) {
|
|
31897
31897
|
const time3 = getTime(strictTimeZone);
|
|
31898
|
-
return function date_time(
|
|
31899
|
-
const dateTime =
|
|
31898
|
+
return function date_time(str2) {
|
|
31899
|
+
const dateTime = str2.split(DATE_TIME_SEPARATOR);
|
|
31900
31900
|
return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
|
|
31901
31901
|
};
|
|
31902
31902
|
}
|
|
@@ -31917,13 +31917,13 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31917
31917
|
}
|
|
31918
31918
|
const NOT_URI_FRAGMENT = /\/|:/;
|
|
31919
31919
|
const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
|
|
31920
|
-
function uri(
|
|
31921
|
-
return NOT_URI_FRAGMENT.test(
|
|
31920
|
+
function uri(str2) {
|
|
31921
|
+
return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
|
|
31922
31922
|
}
|
|
31923
31923
|
const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
31924
|
-
function byte(
|
|
31924
|
+
function byte(str2) {
|
|
31925
31925
|
BYTE.lastIndex = 0;
|
|
31926
|
-
return BYTE.test(
|
|
31926
|
+
return BYTE.test(str2);
|
|
31927
31927
|
}
|
|
31928
31928
|
const MIN_INT32 = -(2 ** 31);
|
|
31929
31929
|
const MAX_INT32 = 2 ** 31 - 1;
|
|
@@ -31937,10 +31937,10 @@ var require_formats = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
31937
31937
|
return true;
|
|
31938
31938
|
}
|
|
31939
31939
|
const Z_ANCHOR = /[^\\]\\Z/;
|
|
31940
|
-
function regex(
|
|
31941
|
-
if (Z_ANCHOR.test(
|
|
31940
|
+
function regex(str2) {
|
|
31941
|
+
if (Z_ANCHOR.test(str2)) return false;
|
|
31942
31942
|
try {
|
|
31943
|
-
new RegExp(
|
|
31943
|
+
new RegExp(str2);
|
|
31944
31944
|
return true;
|
|
31945
31945
|
} catch (e) {
|
|
31946
31946
|
return false;
|
|
@@ -33017,10 +33017,8 @@ function errorResult(message) {
|
|
|
33017
33017
|
|
|
33018
33018
|
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
|
|
33019
33019
|
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
33020
|
-
|
|
33021
|
-
|
|
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.";
|
|
33020
|
+
|
|
33021
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/canonical.js
|
|
33024
33022
|
function canonicalJson(value) {
|
|
33025
33023
|
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
33026
33024
|
return JSON.stringify(value);
|
|
@@ -33031,7 +33029,7 @@ function canonicalJson(value) {
|
|
|
33031
33029
|
if (typeof value === "bigint")
|
|
33032
33030
|
return `{"$bigint":"${value.toString()}"}`;
|
|
33033
33031
|
if (typeof value === "function" || typeof value === "symbol") {
|
|
33034
|
-
throw new TypeError(`
|
|
33032
|
+
throw new TypeError(`confirmation: cannot bind a ${typeof value} argument value.`);
|
|
33035
33033
|
}
|
|
33036
33034
|
if (Array.isArray(value))
|
|
33037
33035
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
@@ -33053,7 +33051,7 @@ function canonicalJson(value) {
|
|
|
33053
33051
|
}
|
|
33054
33052
|
const proto = Object.getPrototypeOf(value);
|
|
33055
33053
|
if (proto !== Object.prototype && proto !== null) {
|
|
33056
|
-
throw new TypeError("
|
|
33054
|
+
throw new TypeError("confirmation: cannot bind a non-plain object argument value (class instance).");
|
|
33057
33055
|
}
|
|
33058
33056
|
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
33059
33057
|
return `{${entries.map(([k, v]) => `${JSON.stringify(escapeKey(k))}:${canonicalJson(v)}`).join(",")}}`;
|
|
@@ -33061,6 +33059,12 @@ function canonicalJson(value) {
|
|
|
33061
33059
|
function escapeKey(key) {
|
|
33062
33060
|
return key.startsWith("$") ? `$${key}` : key;
|
|
33063
33061
|
}
|
|
33062
|
+
|
|
33063
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
|
|
33064
|
+
var DEFAULT_REQUEST_KEY = "confirmation";
|
|
33065
|
+
var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
33066
|
+
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";
|
|
33067
|
+
var STATE_PREFIX = "mcpu.confirm.v1.";
|
|
33064
33068
|
function bindingKey(binding) {
|
|
33065
33069
|
if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
|
|
33066
33070
|
throw new RangeError("requireConfirmation: binding.ttlSeconds must be a finite number greater than 0.");
|
|
@@ -33163,11 +33167,169 @@ ${JSON.stringify(preview, null, 2)}`,
|
|
|
33163
33167
|
});
|
|
33164
33168
|
}
|
|
33165
33169
|
|
|
33166
|
-
// ../../node_modules/@chrischall/mcp-utils/dist/server/
|
|
33167
|
-
|
|
33168
|
-
|
|
33169
|
-
|
|
33170
|
-
|
|
33170
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-token.js
|
|
33171
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
33172
|
+
function createSpentTokenStore() {
|
|
33173
|
+
const spent2 = /* @__PURE__ */ new Map();
|
|
33174
|
+
return {
|
|
33175
|
+
has: (nonce) => spent2.has(nonce),
|
|
33176
|
+
add: (nonce, exp) => {
|
|
33177
|
+
spent2.set(nonce, exp);
|
|
33178
|
+
},
|
|
33179
|
+
prune: (now) => {
|
|
33180
|
+
for (const [nonce, exp] of spent2)
|
|
33181
|
+
if (exp < now)
|
|
33182
|
+
spent2.delete(nonce);
|
|
33183
|
+
},
|
|
33184
|
+
clear: () => spent2.clear(),
|
|
33185
|
+
get size() {
|
|
33186
|
+
return spent2.size;
|
|
33187
|
+
}
|
|
33188
|
+
};
|
|
33189
|
+
}
|
|
33190
|
+
var PROCESS_SPENT = createSpentTokenStore();
|
|
33191
|
+
var PREFIX = "mcpu.token.v1.";
|
|
33192
|
+
var DEFAULT_TTL_SECONDS = 600;
|
|
33193
|
+
function tokenKey(key) {
|
|
33194
|
+
const bytes = typeof key === "string" ? Buffer.from(key, "utf8") : Buffer.from(key);
|
|
33195
|
+
if (bytes.length < 32)
|
|
33196
|
+
throw new RangeError("confirm token: key must be at least 32 bytes.");
|
|
33197
|
+
return bytes;
|
|
33198
|
+
}
|
|
33199
|
+
function ttlOf(ttlSeconds) {
|
|
33200
|
+
if (ttlSeconds === void 0)
|
|
33201
|
+
return DEFAULT_TTL_SECONDS;
|
|
33202
|
+
if (!(Number.isFinite(ttlSeconds) && ttlSeconds > 0)) {
|
|
33203
|
+
throw new RangeError("confirm token: ttlSeconds must be a finite number greater than 0.");
|
|
33204
|
+
}
|
|
33205
|
+
return ttlSeconds;
|
|
33206
|
+
}
|
|
33207
|
+
function sign(key, body) {
|
|
33208
|
+
return createHmac2("sha256", key).update(`${PREFIX}${body}`).digest();
|
|
33209
|
+
}
|
|
33210
|
+
function hashConfirmPayload(payload) {
|
|
33211
|
+
return createHash2("sha256").update(canonicalJson(payload)).digest("base64url");
|
|
33212
|
+
}
|
|
33213
|
+
function issueConfirmToken(key, binding, options = {}) {
|
|
33214
|
+
const k = tokenKey(key);
|
|
33215
|
+
const now = options.now ?? Date.now();
|
|
33216
|
+
const exp = now + ttlOf(options.ttlSeconds) * 1e3;
|
|
33217
|
+
const claims = {
|
|
33218
|
+
t: binding.tool,
|
|
33219
|
+
...binding.account === void 0 ? {} : { a: binding.account },
|
|
33220
|
+
g: binding.target,
|
|
33221
|
+
...binding.revision === void 0 ? {} : { r: binding.revision },
|
|
33222
|
+
h: binding.payloadHash,
|
|
33223
|
+
exp,
|
|
33224
|
+
n: randomBytes(16).toString("base64url")
|
|
33225
|
+
};
|
|
33226
|
+
const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
|
|
33227
|
+
return { token: `${PREFIX}${body}.${sign(k, body).toString("base64url")}`, expiresAt: new Date(exp).toISOString() };
|
|
33228
|
+
}
|
|
33229
|
+
function parseClaims(body) {
|
|
33230
|
+
try {
|
|
33231
|
+
const claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
33232
|
+
return claims && typeof claims === "object" ? claims : void 0;
|
|
33233
|
+
} catch {
|
|
33234
|
+
return void 0;
|
|
33235
|
+
}
|
|
33236
|
+
}
|
|
33237
|
+
function verifyConfirmToken(key, token, binding, options = {}) {
|
|
33238
|
+
const k = tokenKey(key);
|
|
33239
|
+
const now = options.now ?? Date.now();
|
|
33240
|
+
const spent2 = options.spent ?? PROCESS_SPENT;
|
|
33241
|
+
spent2.prune(now);
|
|
33242
|
+
if (!token.startsWith(PREFIX))
|
|
33243
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
33244
|
+
const rest = token.slice(PREFIX.length);
|
|
33245
|
+
const dot = rest.indexOf(".");
|
|
33246
|
+
const body = dot < 0 ? "" : rest.slice(0, dot);
|
|
33247
|
+
const mac3 = Buffer.from(dot < 0 ? "" : rest.slice(dot + 1), "base64url");
|
|
33248
|
+
const expected = sign(k, body);
|
|
33249
|
+
if (!body || mac3.length !== expected.length || !timingSafeEqual2(mac3, expected))
|
|
33250
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
33251
|
+
const claims = parseClaims(body);
|
|
33252
|
+
if (!claims)
|
|
33253
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
33254
|
+
if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
|
|
33255
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
33256
|
+
}
|
|
33257
|
+
if (spent2.has(claims.n))
|
|
33258
|
+
return { ok: false, error: "TOKEN_REUSED" };
|
|
33259
|
+
if (now > claims.exp)
|
|
33260
|
+
return { ok: false, error: "TOKEN_EXPIRED" };
|
|
33261
|
+
if (claims.r !== binding.revision)
|
|
33262
|
+
return { ok: false, error: "DRAFT_CHANGED", reason: "revision-changed" };
|
|
33263
|
+
if (claims.h !== binding.payloadHash)
|
|
33264
|
+
return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
|
|
33265
|
+
spent2.add(claims.n, claims.exp);
|
|
33266
|
+
return { ok: true };
|
|
33267
|
+
}
|
|
33268
|
+
var CONFIRM_TOKEN_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
33269
|
+
var confirmTokenParam = external_exports.string().optional().describe(`ONLY for the two-step confirmation fallback (a client without MCP elicitation). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`);
|
|
33270
|
+
var TOKEN_ERROR_NOTE = {
|
|
33271
|
+
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.",
|
|
33272
|
+
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.",
|
|
33273
|
+
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."
|
|
33274
|
+
};
|
|
33275
|
+
var CHANGED_NOTE = {
|
|
33276
|
+
"revision-changed": "Nothing was sent or changed: the target was edited since the user approved it (its version rotated), so what would happen is not what they saw.",
|
|
33277
|
+
"payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
|
|
33278
|
+
};
|
|
33279
|
+
function isToolResult(value) {
|
|
33280
|
+
return Array.isArray(value.content);
|
|
33281
|
+
}
|
|
33282
|
+
function rejection(data) {
|
|
33283
|
+
return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
|
|
33284
|
+
}
|
|
33285
|
+
async function tokenConfirmation(action, fb) {
|
|
33286
|
+
const subject = await fb.subject();
|
|
33287
|
+
if (isToolResult(subject))
|
|
33288
|
+
return subject;
|
|
33289
|
+
const binding = {
|
|
33290
|
+
tool: fb.tool,
|
|
33291
|
+
...fb.account === void 0 ? {} : { account: fb.account },
|
|
33292
|
+
target: subject.target,
|
|
33293
|
+
...subject.revision === void 0 ? {} : { revision: subject.revision },
|
|
33294
|
+
payloadHash: hashConfirmPayload(subject.payload)
|
|
33295
|
+
};
|
|
33296
|
+
const phaseOne = () => {
|
|
33297
|
+
const { token, expiresAt } = issueConfirmToken(fb.key, binding, { ttlSeconds: fb.ttlSeconds });
|
|
33298
|
+
return {
|
|
33299
|
+
action,
|
|
33300
|
+
preview: subject.preview,
|
|
33301
|
+
confirmToken: token,
|
|
33302
|
+
expiresAt,
|
|
33303
|
+
ttlSeconds: ttlOf(fb.ttlSeconds),
|
|
33304
|
+
instruction: fb.instruction ?? CONFIRM_TOKEN_INSTRUCTION
|
|
33305
|
+
};
|
|
33306
|
+
};
|
|
33307
|
+
if (!fb.confirmToken) {
|
|
33308
|
+
return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
|
|
33309
|
+
}
|
|
33310
|
+
const verdict = verifyConfirmToken(fb.key, fb.confirmToken, binding, { spent: fb.spent });
|
|
33311
|
+
if (verdict.ok)
|
|
33312
|
+
return void 0;
|
|
33313
|
+
if (verdict.error === "DRAFT_CHANGED") {
|
|
33314
|
+
return rejection({
|
|
33315
|
+
error: "DRAFT_CHANGED",
|
|
33316
|
+
reason: verdict.reason,
|
|
33317
|
+
note: `${CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
|
|
33318
|
+
...phaseOne()
|
|
33319
|
+
});
|
|
33320
|
+
}
|
|
33321
|
+
return rejection({ error: verdict.error, action, note: TOKEN_ERROR_NOTE[verdict.error] });
|
|
33322
|
+
}
|
|
33323
|
+
async function requireConfirmationWithFallback(ctx, options) {
|
|
33324
|
+
const { tokenFallback, ...confirmation } = options;
|
|
33325
|
+
if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
|
|
33326
|
+
return tokenConfirmation(options.action, tokenFallback);
|
|
33327
|
+
}
|
|
33328
|
+
return requireConfirmation(ctx, confirmation);
|
|
33329
|
+
}
|
|
33330
|
+
|
|
33331
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js
|
|
33332
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
|
|
33171
33333
|
|
|
33172
33334
|
// ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
33173
33335
|
import { homedir } from "node:os";
|
|
@@ -33208,6 +33370,67 @@ function expandPath(p) {
|
|
|
33208
33370
|
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
33209
33371
|
}
|
|
33210
33372
|
|
|
33373
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js
|
|
33374
|
+
var MODES = /* @__PURE__ */ new Set(["ask-user", "auto", "refuse"]);
|
|
33375
|
+
var DEFAULT_TTL_SECONDS2 = 600;
|
|
33376
|
+
var warned = /* @__PURE__ */ new Set();
|
|
33377
|
+
function readConfirmMode(env = process.env) {
|
|
33378
|
+
const raw = readEnvVar("MCP_CONFIRM_MODE", { env })?.trim().toLowerCase();
|
|
33379
|
+
if (!raw)
|
|
33380
|
+
return "ask-user";
|
|
33381
|
+
if (MODES.has(raw))
|
|
33382
|
+
return raw;
|
|
33383
|
+
if (!warned.has(raw)) {
|
|
33384
|
+
warned.add(raw);
|
|
33385
|
+
process.stderr.write(`MCP_CONFIRM_MODE="${raw}" is not one of ask-user, auto, refuse; treating it as refuse.
|
|
33386
|
+
`);
|
|
33387
|
+
}
|
|
33388
|
+
return "refuse";
|
|
33389
|
+
}
|
|
33390
|
+
function confirmTtlFromEnv(env = process.env) {
|
|
33391
|
+
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
33392
|
+
return raw && /^[1-9]\d*$/.test(raw) ? Number(raw) : DEFAULT_TTL_SECONDS2;
|
|
33393
|
+
}
|
|
33394
|
+
var processKey;
|
|
33395
|
+
function confirmKeyFromEnv(env = process.env) {
|
|
33396
|
+
const secret = readEnvVar("MCP_CONFIRM_SECRET", { env });
|
|
33397
|
+
if (secret)
|
|
33398
|
+
return createHash3("sha256").update(secret, "utf8").digest();
|
|
33399
|
+
processKey ??= randomBytes2(32);
|
|
33400
|
+
return processKey;
|
|
33401
|
+
}
|
|
33402
|
+
var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
33403
|
+
var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
33404
|
+
function confirmationFromEnv(options) {
|
|
33405
|
+
const { tool, account, confirmToken, subject, instruction, spent: spent2, env = process.env, ...confirmation } = options;
|
|
33406
|
+
const mode = readConfirmMode(env);
|
|
33407
|
+
if (mode === "refuse") {
|
|
33408
|
+
return {
|
|
33409
|
+
...confirmation,
|
|
33410
|
+
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${REFUSE_HINT}` : REFUSE_HINT
|
|
33411
|
+
};
|
|
33412
|
+
}
|
|
33413
|
+
return {
|
|
33414
|
+
...confirmation,
|
|
33415
|
+
tokenFallback: {
|
|
33416
|
+
key: confirmKeyFromEnv(env),
|
|
33417
|
+
tool,
|
|
33418
|
+
...account === void 0 ? {} : { account },
|
|
33419
|
+
...confirmToken === void 0 ? {} : { confirmToken },
|
|
33420
|
+
subject,
|
|
33421
|
+
ttlSeconds: confirmTtlFromEnv(env),
|
|
33422
|
+
instruction: mode === "auto" ? CONFIRM_TOKEN_AUTO_INSTRUCTION : instruction ?? CONFIRM_TOKEN_INSTRUCTION,
|
|
33423
|
+
...spent2 === void 0 ? {} : { spent: spent2 }
|
|
33424
|
+
}
|
|
33425
|
+
};
|
|
33426
|
+
}
|
|
33427
|
+
|
|
33428
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
33429
|
+
var SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
33430
|
+
"2026-07-28",
|
|
33431
|
+
...SUPPORTED_PROTOCOL_VERSIONS
|
|
33432
|
+
]);
|
|
33433
|
+
|
|
33211
33434
|
// ../../node_modules/@chrischall/mcp-utils/dist/fs/confine.js
|
|
33212
33435
|
import { existsSync, realpathSync } from "node:fs";
|
|
33213
33436
|
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
|
|
@@ -34104,6 +34327,7 @@ async function diagnose(err) {
|
|
|
34104
34327
|
const isAuthError = !isTransientError && AUTH_ERROR_PATTERN.test(errText);
|
|
34105
34328
|
const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
|
|
34106
34329
|
const hint = isInvalidGrant ? INVALID_GRANT_HINT : isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
|
|
34330
|
+
if (!isAuthError && !isInvalidGrant) return errorResult(`${errText}${hint}`);
|
|
34107
34331
|
try {
|
|
34108
34332
|
const accounts = formatAccountList(await run(["auth", "list"]));
|
|
34109
34333
|
return errorResult(`${errText}
|
|
@@ -34200,95 +34424,13 @@ function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
|
34200
34424
|
}
|
|
34201
34425
|
|
|
34202
34426
|
// src/dispatch-confirmation.ts
|
|
34203
|
-
import { createHash as
|
|
34427
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
34204
34428
|
import { readFileSync } from "node:fs";
|
|
34205
34429
|
|
|
34206
34430
|
// src/send-confirm-token.ts
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
|
|
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 };
|
|
34431
|
+
var spent = createSpentTokenStore();
|
|
34432
|
+
function confirmSpentStore() {
|
|
34433
|
+
return spent;
|
|
34292
34434
|
}
|
|
34293
34435
|
|
|
34294
34436
|
// src/dispatch-confirmation.ts
|
|
@@ -34310,13 +34452,13 @@ function attachmentDetails(paths, inline) {
|
|
|
34310
34452
|
} catch {
|
|
34311
34453
|
bytes = void 0;
|
|
34312
34454
|
}
|
|
34313
|
-
out.push(bytes ? { name: path, size: bytes.length, sha256:
|
|
34455
|
+
out.push(bytes ? { name: path, size: bytes.length, sha256: createHash4("sha256").update(bytes).digest("hex") } : { name: path, size: null });
|
|
34314
34456
|
}
|
|
34315
34457
|
for (const a of inline ?? []) {
|
|
34316
34458
|
out.push({
|
|
34317
34459
|
name: a.filename,
|
|
34318
34460
|
size: Buffer.from(a.contentBase64, "base64").length,
|
|
34319
|
-
sha256:
|
|
34461
|
+
sha256: createHash4("sha256").update(a.contentBase64).digest("hex")
|
|
34320
34462
|
});
|
|
34321
34463
|
}
|
|
34322
34464
|
return out;
|
|
@@ -34328,82 +34470,74 @@ function attachmentPreview(details) {
|
|
|
34328
34470
|
return details.map(({ name, size }) => ({ name, size }));
|
|
34329
34471
|
}
|
|
34330
34472
|
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 =
|
|
34332
|
-
var
|
|
34333
|
-
|
|
34334
|
-
|
|
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] });
|
|
34473
|
+
var CONFIRM_ACTION_INSTRUCTION = CONFIRM_TOKEN_INSTRUCTION;
|
|
34474
|
+
var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai), a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Follow its instruction (by default: show the preview to the user verbatim and only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken). The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing. A server set to MCP_CONFIRM_MODE=refuse refuses instead.';
|
|
34475
|
+
function refusedInRun(what, via, does, instead) {
|
|
34476
|
+
return `${what} ${does} and is not available through ${via}. ${instead}`;
|
|
34387
34477
|
}
|
|
34388
34478
|
function gatedElsewhere(what, via, does, tool) {
|
|
34389
|
-
return
|
|
34479
|
+
return refusedInRun(what, via, does, `Use ${tool}, which asks the user to confirm.`);
|
|
34390
34480
|
}
|
|
34391
34481
|
function hasCommandWord(args, words) {
|
|
34392
34482
|
return args.find((a) => words.has(a.toLowerCase()));
|
|
34393
34483
|
}
|
|
34484
|
+
function flagValue(args, name) {
|
|
34485
|
+
const flag = `--${name}`;
|
|
34486
|
+
for (let i = 0; i < args.length; i++) {
|
|
34487
|
+
const a = args[i];
|
|
34488
|
+
if (a === flag) return args[i + 1] ?? "";
|
|
34489
|
+
if (a.startsWith(`${flag}=`)) return a.slice(flag.length + 1);
|
|
34490
|
+
}
|
|
34491
|
+
return void 0;
|
|
34492
|
+
}
|
|
34493
|
+
var COMMENT_ADD_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
34494
|
+
var COMMENT_REPLY_WORDS = /* @__PURE__ */ new Set(["reply", "respond"]);
|
|
34495
|
+
function hasTrueFlag(args, flag) {
|
|
34496
|
+
const name = `--${flag}`;
|
|
34497
|
+
return args.some((a) => {
|
|
34498
|
+
const lower = a.toLowerCase();
|
|
34499
|
+
if (lower === name) return true;
|
|
34500
|
+
return lower.startsWith(`${name}=`) && lower.slice(name.length + 1) !== "false";
|
|
34501
|
+
});
|
|
34502
|
+
}
|
|
34503
|
+
function vetCommentsRun(service, subcommand, args) {
|
|
34504
|
+
if (subcommand.toLowerCase() !== "comments") return void 0;
|
|
34505
|
+
const reply = hasCommandWord(args, COMMENT_REPLY_WORDS);
|
|
34506
|
+
if (reply) {
|
|
34507
|
+
return gatedElsewhere(
|
|
34508
|
+
`gog ${service} comments ${reply.toLowerCase()}`,
|
|
34509
|
+
`gog_${service}_run`,
|
|
34510
|
+
"notifies the comment thread",
|
|
34511
|
+
`gog_${service}_comments_reply`
|
|
34512
|
+
);
|
|
34513
|
+
}
|
|
34514
|
+
const add = hasCommandWord(args, COMMENT_ADD_WORDS);
|
|
34515
|
+
return add ? gatedElsewhere(
|
|
34516
|
+
`gog ${service} comments ${add.toLowerCase()}`,
|
|
34517
|
+
`gog_${service}_run`,
|
|
34518
|
+
"notifies the file's owner and anyone it mentions",
|
|
34519
|
+
`gog_${service}_comments_add`
|
|
34520
|
+
) : void 0;
|
|
34521
|
+
}
|
|
34394
34522
|
async function requireDispatchConfirmation(ctx, options) {
|
|
34395
34523
|
const { action, fallback } = options;
|
|
34396
|
-
|
|
34397
|
-
return tokenConfirmation(action, fallback);
|
|
34398
|
-
}
|
|
34399
|
-
const note = fallback ? [options.unsupportedNote, FALLBACK_HINT].filter(Boolean).join(" ") : options.unsupportedNote;
|
|
34400
|
-
return requireConfirmation(ctx, {
|
|
34524
|
+
const confirmation = {
|
|
34401
34525
|
action,
|
|
34402
34526
|
message: options.message,
|
|
34403
34527
|
details: options.details,
|
|
34404
34528
|
confirmationLabel: options.confirmationLabel,
|
|
34405
|
-
...
|
|
34406
|
-
}
|
|
34529
|
+
...options.unsupportedNote ? { unsupportedNote: options.unsupportedNote } : {}
|
|
34530
|
+
};
|
|
34531
|
+
if (!fallback) return requireConfirmationWithFallback(ctx, confirmation);
|
|
34532
|
+
return requireConfirmationWithFallback(ctx, confirmationFromEnv({
|
|
34533
|
+
...confirmation,
|
|
34534
|
+
tool: fallback.tool,
|
|
34535
|
+
account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
|
|
34536
|
+
confirmToken: fallback.confirmToken,
|
|
34537
|
+
subject: fallback.subject,
|
|
34538
|
+
instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
|
|
34539
|
+
spent: confirmSpentStore()
|
|
34540
|
+
}));
|
|
34407
34541
|
}
|
|
34408
34542
|
function resultText(result) {
|
|
34409
34543
|
const first = result.content[0];
|
|
@@ -34413,16 +34547,33 @@ function resultText(result) {
|
|
|
34413
34547
|
// src/tools/api.ts
|
|
34414
34548
|
var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
|
|
34415
34549
|
var DISPATCH_API_BLOCKED = {
|
|
34416
|
-
chat: [
|
|
34417
|
-
|
|
34550
|
+
chat: [
|
|
34551
|
+
{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" },
|
|
34552
|
+
{ method: /(?:^|\.)spaces\.(?:setup|members\.create)$/i, does: "adds people to a space", tool: "gog_chat_spaces_create" }
|
|
34553
|
+
],
|
|
34554
|
+
drive: [
|
|
34555
|
+
{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" },
|
|
34556
|
+
{ method: /(?:^|\.)comments\.create$/i, does: "notifies the file's owner and anyone it mentions", tool: "gog_drive_comments_add / gog_docs_comments_add" },
|
|
34557
|
+
{ method: /(?:^|\.)replies\.create$/i, does: "notifies the comment thread", tool: "gog_drive_comments_reply / gog_docs_comments_reply" }
|
|
34558
|
+
],
|
|
34418
34559
|
classroom: [
|
|
34419
34560
|
{ 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" }
|
|
34561
|
+
{ method: /(?:^|\.)invitations\.create$/i, does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
|
|
34562
|
+
{ method: /(?:^|\.)courses\.students\.create$/i, does: "adds someone to a class", tool: "gog_classroom_students_add" },
|
|
34563
|
+
{ method: /(?:^|\.)courses\.teachers\.create$/i, does: "gives someone teacher access to a class", tool: "gog_classroom_teachers_add" },
|
|
34564
|
+
{ method: /(?:^|\.)courses\.coursework\.create$/i, does: "posts coursework to a class", tool: "gog_classroom_coursework_create" },
|
|
34565
|
+
{ method: /(?:^|\.)studentsubmissions\.return$/i, does: "returns work to a student", tool: "gog_classroom_submissions_return" }
|
|
34421
34566
|
],
|
|
34422
34567
|
calendar: [
|
|
34423
34568
|
{ 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
|
-
|
|
34569
|
+
{ method: /(?:^|\.)events\.(?:update|patch)$/i, does: "can change what guests see", tool: "gog_calendar_update / gog_calendar_respond" },
|
|
34570
|
+
{ method: /(?:^|\.)events\.move$/i, does: "can email every guest", tool: "gog_calendar_move" }
|
|
34571
|
+
],
|
|
34572
|
+
gmail: [
|
|
34573
|
+
{ method: /(?:^|\.)settings\.sendas\.create$/i, does: "makes Google email the address and adds a sending identity", tool: "gog_gmail_sendas_create" },
|
|
34574
|
+
{ method: /(?:^|\.)settings\.updatevacation$/i, does: "can turn on an auto-reply to every sender", tool: "gog_gmail_vacation_update" }
|
|
34575
|
+
],
|
|
34576
|
+
script: [{ method: /(?:^|\.)scripts\.run$/i, does: "executes code with this account's authority", tool: "gog_appscript_run_function" }]
|
|
34426
34577
|
};
|
|
34427
34578
|
function refusedApiCall(api, method) {
|
|
34428
34579
|
const name = api.trim().toLowerCase();
|
|
@@ -34433,6 +34584,14 @@ function refusedApiCall(api, method) {
|
|
|
34433
34584
|
const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
|
|
34434
34585
|
return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
|
|
34435
34586
|
}
|
|
34587
|
+
function previewJson(text) {
|
|
34588
|
+
if (text.length > BODY_PREVIEW_MAX) return bodyPreview(text);
|
|
34589
|
+
try {
|
|
34590
|
+
return JSON.parse(text);
|
|
34591
|
+
} catch {
|
|
34592
|
+
return text;
|
|
34593
|
+
}
|
|
34594
|
+
}
|
|
34436
34595
|
function registerApiTools(server) {
|
|
34437
34596
|
server.registerTool("gog_api_list", {
|
|
34438
34597
|
description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
|
|
@@ -34461,7 +34620,7 @@ function registerApiTools(server) {
|
|
|
34461
34620
|
return runOrDiagnose(args, { account });
|
|
34462
34621
|
});
|
|
34463
34622
|
server.registerTool("gog_api_call", {
|
|
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
|
|
34623
|
+
description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true, and every write then asks the MCP host to show the user a confirmation prompt with the exact api/version/method/params/body before anything is sent; set dryRun=true to print the intended request without sending it (no prompt, no changes). Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused outright \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about with a better preview: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
34465
34624
|
annotations: { destructiveHint: true },
|
|
34466
34625
|
inputSchema: external_exports.object({
|
|
34467
34626
|
api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
|
|
@@ -34470,11 +34629,12 @@ function registerApiTools(server) {
|
|
|
34470
34629
|
params: external_exports.string().optional().describe('Query/path parameters as a JSON object string (e.g. {"fileId":"abc","fields":"name"})'),
|
|
34471
34630
|
body: external_exports.string().optional().describe("Request body as a JSON string (for write methods)"),
|
|
34472
34631
|
scope: external_exports.string().optional().describe("Override the OAuth scope used for the call"),
|
|
34473
|
-
allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE). Without it, gog refuses write methods. Leave unset for read-only calls."),
|
|
34474
|
-
dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made)"),
|
|
34475
|
-
account: accountParam
|
|
34632
|
+
allowWrite: external_exports.boolean().optional().describe("Required to invoke a mutating method (POST/PUT/PATCH/DELETE); the user is asked to confirm the exact request first. Without it, gog refuses write methods. Leave unset for read-only calls."),
|
|
34633
|
+
dryRun: external_exports.boolean().optional().describe("Print the intended request and exit without sending it (no changes made, no confirmation prompt)"),
|
|
34634
|
+
account: accountParam,
|
|
34635
|
+
confirmToken: confirmTokenParam
|
|
34476
34636
|
})
|
|
34477
|
-
}, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
|
|
34637
|
+
}, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account, confirmToken }, ctx) => {
|
|
34478
34638
|
try {
|
|
34479
34639
|
assertSafeForwardedArgs([api, version2, method]);
|
|
34480
34640
|
const refusal = refusedApiCall(api, method);
|
|
@@ -34484,6 +34644,27 @@ function registerApiTools(server) {
|
|
|
34484
34644
|
} catch (err) {
|
|
34485
34645
|
return errorResult(errorText(err));
|
|
34486
34646
|
}
|
|
34647
|
+
if (allowWrite && !dryRun) {
|
|
34648
|
+
const request = { api, version: version2, method, params, body, scope };
|
|
34649
|
+
const view = { api, version: version2, method };
|
|
34650
|
+
if (params !== void 0) view.params = previewJson(params);
|
|
34651
|
+
if (body !== void 0) view.body = previewJson(body);
|
|
34652
|
+
if (scope !== void 0) view.scope = scope;
|
|
34653
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
34654
|
+
action: "api.call",
|
|
34655
|
+
message: `Review and confirm this raw Google API write (${api} ${version2} ${method}):`,
|
|
34656
|
+
confirmationLabel: "Confirm that this API method should be invoked with exactly these parameters.",
|
|
34657
|
+
details: view,
|
|
34658
|
+
unsupportedNote: "Set dryRun=true to see the request gog would send without sending it, or use the dedicated gog_* tool for this operation.",
|
|
34659
|
+
fallback: {
|
|
34660
|
+
tool: "gog_api_call",
|
|
34661
|
+
account,
|
|
34662
|
+
confirmToken,
|
|
34663
|
+
subject: () => ({ target: `${api}/${version2}/${method}`, payload: request, preview: view })
|
|
34664
|
+
}
|
|
34665
|
+
});
|
|
34666
|
+
if (confirmation) return confirmation;
|
|
34667
|
+
}
|
|
34487
34668
|
const args = ["api", "call", pos(api), pos(version2), pos(method)];
|
|
34488
34669
|
if (params) args.push(`--params=${params}`);
|
|
34489
34670
|
if (body) args.push(`--body=${body}`);
|
|
@@ -34496,6 +34677,22 @@ function registerApiTools(server) {
|
|
|
34496
34677
|
}
|
|
34497
34678
|
|
|
34498
34679
|
// src/tools/appscript.ts
|
|
34680
|
+
function vetAppScriptRun(subcommand, _args) {
|
|
34681
|
+
return subcommand.toLowerCase() === "run" ? gatedElsewhere("gog appscript run", "gog_appscript_run", "executes code with this account's authority", "gog_appscript_run_function") : void 0;
|
|
34682
|
+
}
|
|
34683
|
+
function projectSnapshot(raw, scriptId) {
|
|
34684
|
+
let project;
|
|
34685
|
+
try {
|
|
34686
|
+
project = JSON.parse(raw)?.project;
|
|
34687
|
+
} catch {
|
|
34688
|
+
project = void 0;
|
|
34689
|
+
}
|
|
34690
|
+
return {
|
|
34691
|
+
scriptId,
|
|
34692
|
+
...typeof project?.title === "string" ? { title: project.title } : {},
|
|
34693
|
+
...typeof project?.updateTime === "string" ? { updateTime: project.updateTime } : {}
|
|
34694
|
+
};
|
|
34695
|
+
}
|
|
34499
34696
|
function registerAppScriptTools(server) {
|
|
34500
34697
|
const scriptIdParam = external_exports.string().describe(
|
|
34501
34698
|
"Apps Script project ID \u2014 the long ID in script.google.com/\u2026/projects/<scriptId>/\u2026, NOT the Drive file ID of a container document"
|
|
@@ -34576,16 +34773,18 @@ function registerAppScriptTools(server) {
|
|
|
34576
34773
|
return runOrDiagnose(args, { account });
|
|
34577
34774
|
});
|
|
34578
34775
|
server.registerTool("gog_appscript_run_function", {
|
|
34579
|
-
description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that." + apiEnableNote,
|
|
34776
|
+
description: "Execute a function in a deployed Apps Script project. TREAT THIS AS ARBITRARY CODE EXECUTION: the script runs with this Google account's authority and can send mail, edit Drive files or call external services, and the wrapper cannot tell a read from a write \u2014 read the code with gog_appscript_content first if you did not write it. Requires the project to be deployed as an API executable and to share the OAuth client with the calling credentials, otherwise Google refuses regardless of scopes. devMode runs the latest saved code instead of the deployed version, and only works if the account owns the script. This is NOT the escape hatch \u2014 gog_appscript_run is that. Because the wrapper cannot tell what the code will do, it reads the project and asks the MCP host to show the user a confirmation prompt with the project, the function and its arguments first; nothing runs unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + apiEnableNote,
|
|
34580
34777
|
annotations: { destructiveHint: true },
|
|
34581
34778
|
inputSchema: external_exports.object({
|
|
34582
34779
|
scriptId: scriptIdParam,
|
|
34583
34780
|
functionName: external_exports.string().describe('Name of the function to call, e.g. "doWork"'),
|
|
34584
34781
|
params: external_exports.string().optional().describe(`Function parameters as a JSON ARRAY of positional arguments, e.g. '["a", 1]' \u2014 not an object`),
|
|
34585
34782
|
devMode: external_exports.boolean().optional().describe("Run the latest saved code rather than the deployed version (owner only)"),
|
|
34586
|
-
account: accountParam
|
|
34783
|
+
account: accountParam,
|
|
34784
|
+
confirmToken: confirmTokenParam
|
|
34587
34785
|
})
|
|
34588
|
-
}, async ({ scriptId, functionName, params, devMode, account }) => {
|
|
34786
|
+
}, async ({ scriptId, functionName, params, devMode, account, confirmToken }, ctx) => {
|
|
34787
|
+
let parsedParams;
|
|
34589
34788
|
if (params !== void 0) {
|
|
34590
34789
|
let parsed;
|
|
34591
34790
|
try {
|
|
@@ -34596,7 +34795,26 @@ function registerAppScriptTools(server) {
|
|
|
34596
34795
|
if (!Array.isArray(parsed)) {
|
|
34597
34796
|
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}`);
|
|
34598
34797
|
}
|
|
34798
|
+
parsedParams = parsed;
|
|
34599
34799
|
}
|
|
34800
|
+
const got = await runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
|
|
34801
|
+
if (got.isError) return got;
|
|
34802
|
+
const { updateTime, ...project } = projectSnapshot(resultText(got), scriptId);
|
|
34803
|
+
const execution = { project, functionName, params: parsedParams, devMode: Boolean(devMode), runsAs: senderPreview(account) };
|
|
34804
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
34805
|
+
action: "appscript.run-function",
|
|
34806
|
+
message: "Review and confirm running this Apps Script function with the account's full authority:",
|
|
34807
|
+
confirmationLabel: "Confirm that this code should run now.",
|
|
34808
|
+
details: execution,
|
|
34809
|
+
unsupportedNote: "Ask the user to run it themselves from the Apps Script editor.",
|
|
34810
|
+
fallback: {
|
|
34811
|
+
tool: "gog_appscript_run_function",
|
|
34812
|
+
account,
|
|
34813
|
+
confirmToken,
|
|
34814
|
+
subject: () => ({ target: `${scriptId}/${functionName}`, revision: updateTime, payload: execution, preview: execution })
|
|
34815
|
+
}
|
|
34816
|
+
});
|
|
34817
|
+
if (confirmation) return confirmation;
|
|
34600
34818
|
const args = ["appscript", "run", pos(scriptId), pos(functionName)];
|
|
34601
34819
|
if (params !== void 0) args.push(`--params=${params}`);
|
|
34602
34820
|
if (devMode) args.push("--dev-mode");
|
|
@@ -34605,6 +34823,7 @@ function registerAppScriptTools(server) {
|
|
|
34605
34823
|
registerRunTool(server, {
|
|
34606
34824
|
service: "appscript",
|
|
34607
34825
|
examples: '"get", "content", "deployments"',
|
|
34826
|
+
vet: vetAppScriptRun,
|
|
34608
34827
|
note: "To execute a function, use gog_appscript_run_function \u2014 this tool is the generic escape hatch."
|
|
34609
34828
|
});
|
|
34610
34829
|
}
|
|
@@ -34747,21 +34966,47 @@ function authToolsFor(defaultServices) {
|
|
|
34747
34966
|
}
|
|
34748
34967
|
|
|
34749
34968
|
// src/tools/calendar.ts
|
|
34750
|
-
var
|
|
34751
|
-
|
|
34752
|
-
|
|
34753
|
-
|
|
34754
|
-
|
|
34755
|
-
|
|
34756
|
-
|
|
34757
|
-
|
|
34758
|
-
|
|
34759
|
-
|
|
34969
|
+
var ASKS = (tool) => `Use ${tool}, which asks the user to confirm.`;
|
|
34970
|
+
var CALENDAR_REFUSED = {
|
|
34971
|
+
create: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
|
|
34972
|
+
add: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
|
|
34973
|
+
new: { does: "can change what guests see", instead: ASKS("gog_calendar_create") },
|
|
34974
|
+
update: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
|
|
34975
|
+
edit: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
|
|
34976
|
+
set: { does: "can change what guests see", instead: ASKS("gog_calendar_update") },
|
|
34977
|
+
respond: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
|
|
34978
|
+
rsvp: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
|
|
34979
|
+
reply: { does: "can change what guests see", instead: ASKS("gog_calendar_respond") },
|
|
34980
|
+
move: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
|
|
34981
|
+
transfer: { does: "moves an event off guests' calendars (and can email them)", instead: ASKS("gog_calendar_move") },
|
|
34982
|
+
delete: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
|
|
34983
|
+
del: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
|
|
34984
|
+
rm: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
|
|
34985
|
+
remove: { does: "removes an event from every guest's calendar (the whole series by default)", instead: ASKS("gog_calendar_delete") },
|
|
34986
|
+
"delete-calendar": { does: "deletes a calendar and every event on it", instead: ASKS("gog_calendar_delete_calendar") },
|
|
34987
|
+
"out-of-office": { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") },
|
|
34988
|
+
ooo: { does: "auto-declines invitations and notifies their organizers", instead: ASKS("gog_calendar_out_of_office") }
|
|
34760
34989
|
};
|
|
34761
|
-
function vetCalendarRun(subcommand,
|
|
34990
|
+
function vetCalendarRun(subcommand, args) {
|
|
34762
34991
|
const sub = subcommand.toLowerCase();
|
|
34763
|
-
const
|
|
34764
|
-
|
|
34992
|
+
const what = `gog calendar ${sub}`;
|
|
34993
|
+
const via = "gog_calendar_run";
|
|
34994
|
+
if (Object.hasOwn(CALENDAR_REFUSED, sub)) {
|
|
34995
|
+
const { does, instead } = CALENDAR_REFUSED[sub];
|
|
34996
|
+
return refusedInRun(what, via, does, instead);
|
|
34997
|
+
}
|
|
34998
|
+
if (sub === "propose-time" && (flagValue(args, "decline") !== void 0 || flagValue(args, "comment") !== void 0)) {
|
|
34999
|
+
return gatedElsewhere(`${what} --decline`, via, "declines the event and notifies the organizer", "gog_calendar_respond");
|
|
35000
|
+
}
|
|
35001
|
+
if ((sub === "focus-time" || sub === "focus") && flagValue(args, "auto-decline")?.toLowerCase() !== "none") {
|
|
35002
|
+
return refusedInRun(
|
|
35003
|
+
what,
|
|
35004
|
+
via,
|
|
35005
|
+
"auto-declines invitations and notifies their organizers (gog defaults --auto-decline to all)",
|
|
35006
|
+
"Pass --auto-decline=none, or ask the user to create it from Google Calendar."
|
|
35007
|
+
);
|
|
35008
|
+
}
|
|
35009
|
+
return void 0;
|
|
34765
35010
|
}
|
|
34766
35011
|
function parseAttendees(value) {
|
|
34767
35012
|
if (!value) return [];
|
|
@@ -34778,15 +35023,17 @@ function eventSnapshot(raw) {
|
|
|
34778
35023
|
} catch {
|
|
34779
35024
|
event = void 0;
|
|
34780
35025
|
}
|
|
34781
|
-
const
|
|
35026
|
+
const str2 = (v) => typeof v === "string" ? v : void 0;
|
|
34782
35027
|
return {
|
|
34783
|
-
summary:
|
|
35028
|
+
summary: str2(event?.summary),
|
|
34784
35029
|
start: timeOf(event?.start),
|
|
34785
35030
|
end: timeOf(event?.end),
|
|
34786
|
-
organizer:
|
|
35031
|
+
organizer: str2(event?.organizer?.email),
|
|
34787
35032
|
// Guests are the OTHER people: not this account's own attendee row, not rooms.
|
|
34788
35033
|
guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
|
|
34789
|
-
|
|
35034
|
+
// A series, or one instance of one: gog's delete --scope defaults to all.
|
|
35035
|
+
...Array.isArray(event?.recurrence) && event.recurrence.length > 0 || typeof event?.recurringEventId === "string" ? { recurring: true } : {},
|
|
35036
|
+
etag: str2(event?.etag)
|
|
34790
35037
|
};
|
|
34791
35038
|
}
|
|
34792
35039
|
async function readEvent(calendarId, eventId, account) {
|
|
@@ -35007,14 +35254,35 @@ function registerCalendarTools(server) {
|
|
|
35007
35254
|
return runOrDiagnose(args, { account });
|
|
35008
35255
|
});
|
|
35009
35256
|
server.registerTool("gog_calendar_delete", {
|
|
35010
|
-
description: "Delete a calendar event.",
|
|
35257
|
+
description: "Delete a calendar event. It disappears from every guest's calendar too, and for a recurring event gog deletes the WHOLE series, so when the event has guests or recurs this reads it and asks the MCP host to show the user a confirmation prompt with the event as it stands; a guest-free, one-off event is deleted without asking." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35011
35258
|
annotations: { destructiveHint: true },
|
|
35012
35259
|
inputSchema: external_exports.object({
|
|
35013
35260
|
calendarId: external_exports.string().describe("Calendar ID"),
|
|
35014
35261
|
eventId: external_exports.string().describe("Event ID"),
|
|
35015
|
-
account: accountParam
|
|
35262
|
+
account: accountParam,
|
|
35263
|
+
confirmToken: confirmTokenParam
|
|
35016
35264
|
})
|
|
35017
|
-
}, async ({ calendarId, eventId, account }) => {
|
|
35265
|
+
}, async ({ calendarId, eventId, account, confirmToken }, ctx) => {
|
|
35266
|
+
const read = await readEvent(calendarId, eventId, account);
|
|
35267
|
+
if (read.error) return read.error;
|
|
35268
|
+
const { etag, ...event } = read.event;
|
|
35269
|
+
if (event.guests.length > 0 || event.recurring) {
|
|
35270
|
+
const view = { calendarId, eventId, event, scope: event.recurring ? "the whole recurring series" : "this event" };
|
|
35271
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35272
|
+
action: "calendar.delete",
|
|
35273
|
+
message: event.recurring ? "Review and confirm deleting this ENTIRE recurring series:" : "Review and confirm deleting this event from every guest's calendar:",
|
|
35274
|
+
confirmationLabel: "Confirm that this event should be deleted now.",
|
|
35275
|
+
details: view,
|
|
35276
|
+
unsupportedNote: "Ask the user to delete it from Google Calendar.",
|
|
35277
|
+
fallback: {
|
|
35278
|
+
tool: "gog_calendar_delete",
|
|
35279
|
+
account,
|
|
35280
|
+
confirmToken,
|
|
35281
|
+
subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
|
|
35282
|
+
}
|
|
35283
|
+
});
|
|
35284
|
+
if (confirmation) return confirmation;
|
|
35285
|
+
}
|
|
35018
35286
|
return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
|
|
35019
35287
|
});
|
|
35020
35288
|
server.registerTool("gog_calendar_respond", {
|
|
@@ -35136,8 +35404,14 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
|
|
|
35136
35404
|
|
|
35137
35405
|
// src/tools/chat.ts
|
|
35138
35406
|
var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
|
|
35407
|
+
var CHAT_SPACE_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
35139
35408
|
function vetChatRun(subcommand, args) {
|
|
35140
35409
|
const sub = subcommand.toLowerCase();
|
|
35410
|
+
if (sub === "spaces") {
|
|
35411
|
+
const word2 = hasCommandWord(args, CHAT_SPACE_CREATE_WORDS);
|
|
35412
|
+
const withMembers = args.some((a) => /^--members?(?:=|$)/i.test(a));
|
|
35413
|
+
return word2 && withMembers ? gatedElsewhere(`gog chat spaces ${word2.toLowerCase()} --member`, "gog_chat_run", "adds people to a space", "gog_chat_spaces_create") : void 0;
|
|
35414
|
+
}
|
|
35141
35415
|
if (sub !== "messages" && sub !== "dm") return void 0;
|
|
35142
35416
|
const word = hasCommandWord(args, CHAT_SEND_WORDS);
|
|
35143
35417
|
if (!word) return void 0;
|
|
@@ -35188,16 +35462,34 @@ function registerChatTools(server) {
|
|
|
35188
35462
|
return runOrDiagnose(args, { account });
|
|
35189
35463
|
});
|
|
35190
35464
|
server.registerTool("gog_chat_spaces_create", {
|
|
35191
|
-
description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified
|
|
35465
|
+
description: "Create a named Chat space, optionally seeding its membership. Members are added immediately and are notified, so with members this asks the MCP host to show the user a confirmation prompt with the space name and every member first; nothing is created unless they accept. A space with no members reaches nobody and is created without asking." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
|
|
35192
35466
|
annotations: { destructiveHint: true },
|
|
35193
35467
|
inputSchema: external_exports.object({
|
|
35194
35468
|
displayName: external_exports.string().describe("Display name for the new space"),
|
|
35195
35469
|
members: external_exports.array(external_exports.string()).optional().describe('Initial members, as email addresses or "users/..." resource names'),
|
|
35196
|
-
account: accountParam
|
|
35470
|
+
account: accountParam,
|
|
35471
|
+
confirmToken: confirmTokenParam
|
|
35197
35472
|
})
|
|
35198
|
-
}, async ({ displayName, members: members2, account }) => {
|
|
35473
|
+
}, async ({ displayName, members: members2, account, confirmToken }, ctx) => {
|
|
35199
35474
|
const args = ["chat", "spaces", "create", pos(displayName)];
|
|
35200
35475
|
if (members2) for (const member of members2) args.push(`--member=${member}`);
|
|
35476
|
+
if (members2?.length) {
|
|
35477
|
+
const space = { displayName, members: members2 };
|
|
35478
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
35479
|
+
action: "chat.space-create",
|
|
35480
|
+
message: "Review and confirm this Chat space \u2014 every member is added and notified:",
|
|
35481
|
+
confirmationLabel: "Confirm that these people should be added to a new space now.",
|
|
35482
|
+
details: space,
|
|
35483
|
+
unsupportedNote: "Create the space without members instead; the user can add people from Google Chat.",
|
|
35484
|
+
fallback: {
|
|
35485
|
+
tool: "gog_chat_spaces_create",
|
|
35486
|
+
account,
|
|
35487
|
+
confirmToken,
|
|
35488
|
+
subject: () => ({ target: displayName, payload: space, preview: space })
|
|
35489
|
+
}
|
|
35490
|
+
});
|
|
35491
|
+
if (confirmation) return confirmation;
|
|
35492
|
+
}
|
|
35201
35493
|
return runOrDiagnose(args, { account });
|
|
35202
35494
|
});
|
|
35203
35495
|
server.registerTool("gog_chat_threads_list", {
|
|
@@ -35391,20 +35683,85 @@ function registerChatTools(server) {
|
|
|
35391
35683
|
|
|
35392
35684
|
// src/tools/classroom.ts
|
|
35393
35685
|
var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
35394
|
-
var
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
|
|
35398
|
-
|
|
35399
|
-
|
|
35400
|
-
|
|
35686
|
+
var CLASSROOM_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
|
|
35687
|
+
var CLASSROOM_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
|
|
35688
|
+
var CLASSROOM_RETURN_WORDS = /* @__PURE__ */ new Set(["return", "send"]);
|
|
35689
|
+
var CLASSROOM_ASSIGNEE_WORDS = /* @__PURE__ */ new Set(["assignees", "assign"]);
|
|
35690
|
+
var CLASSROOM_GROUPS = {
|
|
35691
|
+
courses: "courses",
|
|
35692
|
+
course: "courses",
|
|
35693
|
+
announcements: "announcements",
|
|
35694
|
+
announcement: "announcements",
|
|
35695
|
+
ann: "announcements",
|
|
35696
|
+
invitations: "invitations",
|
|
35697
|
+
invitation: "invitations",
|
|
35698
|
+
invites: "invitations",
|
|
35699
|
+
coursework: "coursework",
|
|
35700
|
+
work: "coursework",
|
|
35701
|
+
materials: "materials",
|
|
35702
|
+
material: "materials",
|
|
35703
|
+
students: "students",
|
|
35704
|
+
student: "students",
|
|
35705
|
+
teachers: "teachers",
|
|
35706
|
+
teacher: "teachers",
|
|
35707
|
+
submissions: "submissions",
|
|
35708
|
+
submission: "submissions",
|
|
35709
|
+
"guardian-invitations": "guardian-invitations",
|
|
35710
|
+
"guardian-invites": "guardian-invitations"
|
|
35401
35711
|
};
|
|
35712
|
+
function publishesToStudents(args) {
|
|
35713
|
+
return flagValue(args, "state")?.toUpperCase() === "PUBLISHED" || flagValue(args, "scheduled") !== void 0;
|
|
35714
|
+
}
|
|
35715
|
+
function createsVisibleWork(args) {
|
|
35716
|
+
return flagValue(args, "state")?.toUpperCase() !== "DRAFT" || flagValue(args, "scheduled") !== void 0;
|
|
35717
|
+
}
|
|
35718
|
+
function widensAssignees(args) {
|
|
35719
|
+
return flagValue(args, "mode")?.toUpperCase() === "ALL_STUDENTS" || flagValue(args, "add-student") !== void 0;
|
|
35720
|
+
}
|
|
35402
35721
|
function vetClassroomRun(subcommand, args) {
|
|
35403
35722
|
const sub = subcommand.toLowerCase();
|
|
35404
|
-
|
|
35405
|
-
|
|
35406
|
-
const
|
|
35407
|
-
|
|
35723
|
+
if (!Object.hasOwn(CLASSROOM_GROUPS, sub)) return void 0;
|
|
35724
|
+
const group = CLASSROOM_GROUPS[sub];
|
|
35725
|
+
const via = "gog_classroom_run";
|
|
35726
|
+
const what = (word) => `gog classroom ${sub} ${word.toLowerCase()}`;
|
|
35727
|
+
const create = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
|
|
35728
|
+
const update = hasCommandWord(args, CLASSROOM_UPDATE_WORDS);
|
|
35729
|
+
const remove = hasCommandWord(args, CLASSROOM_DELETE_WORDS);
|
|
35730
|
+
const widenedAssignees = () => {
|
|
35731
|
+
const word = hasCommandWord(args, CLASSROOM_ASSIGNEE_WORDS);
|
|
35732
|
+
return word && widensAssignees(args) ? refusedInRun(what(word), via, "shows it to more students", "Ask the user to change who it is assigned to from Classroom.") : void 0;
|
|
35733
|
+
};
|
|
35734
|
+
switch (group) {
|
|
35735
|
+
case "courses":
|
|
35736
|
+
return remove ? gatedElsewhere(what(remove), via, "deletes a course", "gog_classroom_courses_delete") : void 0;
|
|
35737
|
+
case "announcements":
|
|
35738
|
+
if (create) return gatedElsewhere(what(create), via, "posts to a class", "gog_classroom_announcements_create");
|
|
35739
|
+
if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes an announcement to a class", "gog_classroom_announcements_update");
|
|
35740
|
+
return widenedAssignees();
|
|
35741
|
+
case "invitations":
|
|
35742
|
+
return create ? gatedElsewhere(what(create), via, "invites someone to a class", "gog_classroom_invitations_create") : void 0;
|
|
35743
|
+
case "coursework":
|
|
35744
|
+
if (create && createsVisibleWork(args)) return gatedElsewhere(what(create), via, "posts coursework to a class", "gog_classroom_coursework_create");
|
|
35745
|
+
if (remove) return gatedElsewhere(what(remove), via, "deletes coursework and every submission to it", "gog_classroom_coursework_delete");
|
|
35746
|
+
if (update && publishesToStudents(args)) return gatedElsewhere(what(update), via, "publishes work to students", "gog_classroom_coursework_update");
|
|
35747
|
+
return widenedAssignees();
|
|
35748
|
+
case "materials":
|
|
35749
|
+
if (create && createsVisibleWork(args)) {
|
|
35750
|
+
return refusedInRun(what(create), via, "publishes material to students", "Create it with --state=DRAFT and no --scheduled; ask the user to publish it from Classroom.");
|
|
35751
|
+
}
|
|
35752
|
+
if (update && publishesToStudents(args)) return refusedInRun(what(update), via, "publishes material to students", "Ask the user to publish it from Classroom.");
|
|
35753
|
+
return void 0;
|
|
35754
|
+
case "students":
|
|
35755
|
+
return create ? gatedElsewhere(what(create), via, "enrols someone in a class", "gog_classroom_students_add") : void 0;
|
|
35756
|
+
case "teachers":
|
|
35757
|
+
return create ? gatedElsewhere(what(create), via, "gives someone a teacher's access to a class and its roster", "gog_classroom_teachers_add") : void 0;
|
|
35758
|
+
case "submissions": {
|
|
35759
|
+
const word = hasCommandWord(args, CLASSROOM_RETURN_WORDS);
|
|
35760
|
+
return word ? gatedElsewhere(what(word), via, "returns work to a student, who is notified", "gog_classroom_submissions_return") : void 0;
|
|
35761
|
+
}
|
|
35762
|
+
case "guardian-invitations":
|
|
35763
|
+
return create ? refusedInRun(what(create), via, "emails a guardian invitation", "Ask the user to invite the guardian from Classroom.") : void 0;
|
|
35764
|
+
}
|
|
35408
35765
|
}
|
|
35409
35766
|
async function readCourse(courseId, account, runner = runOrDiagnose) {
|
|
35410
35767
|
const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
|
|
@@ -35423,6 +35780,63 @@ async function readCourse(courseId, account, runner = runOrDiagnose) {
|
|
|
35423
35780
|
}
|
|
35424
35781
|
};
|
|
35425
35782
|
}
|
|
35783
|
+
function nested(raw, key) {
|
|
35784
|
+
try {
|
|
35785
|
+
const parsed = JSON.parse(raw);
|
|
35786
|
+
const inner = parsed?.[key];
|
|
35787
|
+
return inner && typeof inner === "object" ? inner : void 0;
|
|
35788
|
+
} catch {
|
|
35789
|
+
return void 0;
|
|
35790
|
+
}
|
|
35791
|
+
}
|
|
35792
|
+
var str = (v) => typeof v === "string" ? v : void 0;
|
|
35793
|
+
var num = (v) => typeof v === "number" ? v : void 0;
|
|
35794
|
+
async function readCoursework(courseId, courseworkId, account, runner = runOrDiagnose) {
|
|
35795
|
+
const got = await runner(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
|
|
35796
|
+
if (got.isError) return { error: got };
|
|
35797
|
+
const work = nested(resultText(got), "coursework");
|
|
35798
|
+
const title = str(work?.title);
|
|
35799
|
+
return { coursework: { id: courseworkId, ...title !== void 0 ? { title } : {} } };
|
|
35800
|
+
}
|
|
35801
|
+
async function readClassroomWork(kind, courseId, itemId, account, runner = runOrDiagnose) {
|
|
35802
|
+
const got = await runner(["classroom", kind, "get", pos(courseId), pos(itemId)], { account });
|
|
35803
|
+
if (got.isError) return { error: got };
|
|
35804
|
+
const item = nested(resultText(got), kind === "announcements" ? "announcement" : "coursework");
|
|
35805
|
+
const work = { id: itemId };
|
|
35806
|
+
for (const key of ["text", "title", "description", "state", "updateTime"]) {
|
|
35807
|
+
const value = str(item?.[key]);
|
|
35808
|
+
if (value !== void 0) work[key] = value;
|
|
35809
|
+
}
|
|
35810
|
+
return { work };
|
|
35811
|
+
}
|
|
35812
|
+
async function readSubmission(courseId, courseworkId, submissionId, account, runner = runOrDiagnose) {
|
|
35813
|
+
const got = await runner(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
35814
|
+
if (got.isError) return { error: got };
|
|
35815
|
+
const sub = nested(resultText(got), "submission");
|
|
35816
|
+
const fields = {
|
|
35817
|
+
student: str(sub?.userId),
|
|
35818
|
+
state: str(sub?.state),
|
|
35819
|
+
draftGrade: num(sub?.draftGrade),
|
|
35820
|
+
assignedGrade: num(sub?.assignedGrade)
|
|
35821
|
+
};
|
|
35822
|
+
const updateTime = str(sub?.updateTime);
|
|
35823
|
+
return {
|
|
35824
|
+
submission: {
|
|
35825
|
+
id: submissionId,
|
|
35826
|
+
...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0))
|
|
35827
|
+
},
|
|
35828
|
+
...updateTime !== void 0 ? { updateTime } : {}
|
|
35829
|
+
};
|
|
35830
|
+
}
|
|
35831
|
+
async function studentLabel(courseId, userId, account, runner = runOrDiagnose) {
|
|
35832
|
+
const got = await runner(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
|
|
35833
|
+
if (got.isError) return userId;
|
|
35834
|
+
const profile = nested(resultText(got), "student")?.profile;
|
|
35835
|
+
const name = str(profile?.name?.fullName);
|
|
35836
|
+
const email3 = str(profile?.emailAddress);
|
|
35837
|
+
if (name && email3) return `${name} <${email3}>`;
|
|
35838
|
+
return name ?? email3 ?? userId;
|
|
35839
|
+
}
|
|
35426
35840
|
function registerClassroomTools(server) {
|
|
35427
35841
|
server.registerTool("gog_classroom_courses_list", {
|
|
35428
35842
|
description: "List Google Classroom courses.",
|
|
@@ -35635,15 +36049,38 @@ function registerClassroomTools(server) {
|
|
|
35635
36049
|
return runOrDiagnose(args, { account });
|
|
35636
36050
|
});
|
|
35637
36051
|
server.registerTool("gog_classroom_submissions_return", {
|
|
35638
|
-
description: "Return a graded submission to the student.",
|
|
36052
|
+
description: "Return a graded submission to the student, who is notified and sees the grade. Reads the course, the coursework and the submission and asks the MCP host to show the user a confirmation prompt with the class, the assignment, the student and the grade being returned; nothing is returned unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35639
36053
|
annotations: { destructiveHint: true },
|
|
35640
36054
|
inputSchema: external_exports.object({
|
|
35641
36055
|
courseId: external_exports.string().describe("Course ID"),
|
|
35642
36056
|
courseworkId: external_exports.string().describe("Coursework ID"),
|
|
35643
36057
|
submissionId: external_exports.string().describe("Submission ID"),
|
|
35644
|
-
account: accountParam
|
|
36058
|
+
account: accountParam,
|
|
36059
|
+
confirmToken: confirmTokenParam
|
|
35645
36060
|
})
|
|
35646
|
-
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
36061
|
+
}, async ({ courseId, courseworkId, submissionId, account, confirmToken }, ctx) => {
|
|
36062
|
+
const course = await readCourse(courseId, account);
|
|
36063
|
+
if (course.error) return course.error;
|
|
36064
|
+
const work = await readCoursework(courseId, courseworkId, account);
|
|
36065
|
+
if (work.error) return work.error;
|
|
36066
|
+
const sub = await readSubmission(courseId, courseworkId, submissionId, account);
|
|
36067
|
+
if (sub.error) return sub.error;
|
|
36068
|
+
const submission = sub.submission.student ? { ...sub.submission, student: await studentLabel(courseId, sub.submission.student, account) } : sub.submission;
|
|
36069
|
+
const view = { course: course.course, coursework: work.coursework, submission };
|
|
36070
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36071
|
+
action: "classroom.submission-return",
|
|
36072
|
+
message: "Review and confirm returning this work \u2014 the student is notified and sees the grade:",
|
|
36073
|
+
confirmationLabel: "Confirm that this submission should be returned to the student now.",
|
|
36074
|
+
details: view,
|
|
36075
|
+
unsupportedNote: "Ask the user to return it from Classroom.",
|
|
36076
|
+
fallback: {
|
|
36077
|
+
tool: "gog_classroom_submissions_return",
|
|
36078
|
+
account,
|
|
36079
|
+
confirmToken,
|
|
36080
|
+
subject: () => ({ target: `${courseId}/${courseworkId}/${submissionId}`, revision: sub.updateTime, payload: view, preview: view })
|
|
36081
|
+
}
|
|
36082
|
+
});
|
|
36083
|
+
if (confirmation) return confirmation;
|
|
35647
36084
|
return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
35648
36085
|
});
|
|
35649
36086
|
server.registerTool("gog_classroom_submissions_turn_in", {
|
|
@@ -35705,13 +36142,13 @@ function registerClassroomTools(server) {
|
|
|
35705
36142
|
return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
|
|
35706
36143
|
});
|
|
35707
36144
|
server.registerTool("gog_classroom_announcements_create", {
|
|
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,
|
|
36145
|
+
description: "Create an announcement in a Google Classroom course. Unless state is DRAFT with no scheduled time (which students cannot see; a scheduled draft publishes itself), this reads the course and asks the MCP host to show the user a confirmation prompt with the class, the full text and when it publishes; nothing is posted unless they accept. To stage one without asking, pass state DRAFT and no scheduled time." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35709
36146
|
annotations: { destructiveHint: true },
|
|
35710
36147
|
inputSchema: external_exports.object({
|
|
35711
36148
|
courseId: external_exports.string().describe("Course ID"),
|
|
35712
36149
|
text: external_exports.string().describe("Announcement text"),
|
|
35713
|
-
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
|
|
35714
|
-
scheduled: external_exports.string().optional().describe("Scheduled publish time"),
|
|
36150
|
+
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (an unscheduled DRAFT is visible only to teachers and needs no confirmation)"),
|
|
36151
|
+
scheduled: external_exports.string().optional().describe("Scheduled publish time (asks the user to confirm, even for a DRAFT)"),
|
|
35715
36152
|
account: accountParam,
|
|
35716
36153
|
confirmToken: confirmTokenParam
|
|
35717
36154
|
})
|
|
@@ -35719,7 +36156,7 @@ function registerClassroomTools(server) {
|
|
|
35719
36156
|
const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
|
|
35720
36157
|
if (state) args.push(`--state=${state}`);
|
|
35721
36158
|
if (scheduled) args.push(`--scheduled=${scheduled}`);
|
|
35722
|
-
if (state !== "DRAFT") {
|
|
36159
|
+
if (state !== "DRAFT" || scheduled) {
|
|
35723
36160
|
const read = await readCourse(courseId, account);
|
|
35724
36161
|
if (read.error) return read.error;
|
|
35725
36162
|
const publishes = scheduled ? `at ${scheduled}` : "immediately";
|
|
@@ -35891,6 +36328,9 @@ function registerContactsTools(server) {
|
|
|
35891
36328
|
}
|
|
35892
36329
|
|
|
35893
36330
|
// src/tools/docs.ts
|
|
36331
|
+
function vetDocsRun(subcommand, args) {
|
|
36332
|
+
return vetCommentsRun("docs", subcommand, args);
|
|
36333
|
+
}
|
|
35894
36334
|
function registerDocsTools(server) {
|
|
35895
36335
|
server.registerTool("gog_docs_info", {
|
|
35896
36336
|
description: "Get Google Doc metadata: title, ID, and other properties.",
|
|
@@ -35987,12 +36427,29 @@ function registerDocsTools(server) {
|
|
|
35987
36427
|
}, async ({ docId, account }) => {
|
|
35988
36428
|
return runOrDiagnose(["docs", "structure", pos(docId)], { account });
|
|
35989
36429
|
});
|
|
35990
|
-
registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
|
|
36430
|
+
registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"', vet: vetDocsRun });
|
|
35991
36431
|
}
|
|
35992
36432
|
|
|
35993
36433
|
// src/tools/drive.ts
|
|
35994
|
-
|
|
35995
|
-
|
|
36434
|
+
var DRIVE_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del"]);
|
|
36435
|
+
function vetDriveRun(subcommand, args) {
|
|
36436
|
+
const sub = subcommand.toLowerCase();
|
|
36437
|
+
if (sub === "share") return gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share");
|
|
36438
|
+
if (DRIVE_DELETE_WORDS.has(sub) && hasTrueFlag(args, "permanent")) {
|
|
36439
|
+
return gatedElsewhere(`gog drive ${sub} --permanent`, "gog_drive_run", "deletes a file for good, bypassing the trash", "gog_drive_delete");
|
|
36440
|
+
}
|
|
36441
|
+
if (sub === "bulk") {
|
|
36442
|
+
return gatedElsewhere(
|
|
36443
|
+
"gog drive bulk",
|
|
36444
|
+
"gog_drive_run",
|
|
36445
|
+
"rewrites sharing permissions across every matching file in a tree",
|
|
36446
|
+
"gog_drive_share (one file at a time)"
|
|
36447
|
+
);
|
|
36448
|
+
}
|
|
36449
|
+
if (sub === "unshare") {
|
|
36450
|
+
return refusedInRun("gog drive unshare", "gog_drive_run", "removes someone's access to a file", "Use gog_drive_unshare.");
|
|
36451
|
+
}
|
|
36452
|
+
return vetCommentsRun("drive", sub, args);
|
|
35996
36453
|
}
|
|
35997
36454
|
var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
|
|
35998
36455
|
function shareTargetMeta(raw) {
|
|
@@ -36003,6 +36460,26 @@ function shareTargetMeta(raw) {
|
|
|
36003
36460
|
return {};
|
|
36004
36461
|
}
|
|
36005
36462
|
}
|
|
36463
|
+
function commentSnapshot(raw) {
|
|
36464
|
+
let comment;
|
|
36465
|
+
try {
|
|
36466
|
+
const parsed = JSON.parse(raw);
|
|
36467
|
+
comment = parsed?.comment ?? parsed ?? void 0;
|
|
36468
|
+
} catch {
|
|
36469
|
+
comment = void 0;
|
|
36470
|
+
}
|
|
36471
|
+
const name = typeof comment?.author?.displayName === "string" ? comment.author.displayName : void 0;
|
|
36472
|
+
const email3 = typeof comment?.author?.emailAddress === "string" ? comment.author.emailAddress : void 0;
|
|
36473
|
+
const author = name && email3 ? `${name} <${email3}>` : name ?? email3;
|
|
36474
|
+
return {
|
|
36475
|
+
...author ? { author } : {},
|
|
36476
|
+
...typeof comment?.content === "string" ? { content: comment.content } : {},
|
|
36477
|
+
...typeof comment?.resolved === "boolean" ? { resolved: comment.resolved } : {}
|
|
36478
|
+
};
|
|
36479
|
+
}
|
|
36480
|
+
function commentMentions(text) {
|
|
36481
|
+
return [...new Set(text.match(/(?<=[+@])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g) ?? [])];
|
|
36482
|
+
}
|
|
36006
36483
|
function fileMeta(raw) {
|
|
36007
36484
|
const parsed = JSON.parse(raw);
|
|
36008
36485
|
const f = parsed.file ?? parsed;
|
|
@@ -36111,16 +36588,36 @@ function registerDriveTools(server) {
|
|
|
36111
36588
|
return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
|
|
36112
36589
|
});
|
|
36113
36590
|
server.registerTool("gog_drive_delete", {
|
|
36114
|
-
description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
|
|
36591
|
+
description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible). A permanent delete reads the file and asks the MCP host to show the user a confirmation prompt naming it first; nothing is deleted unless they accept. Moving to trash is recoverable and does not ask." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
36115
36592
|
annotations: { destructiveHint: true },
|
|
36116
36593
|
inputSchema: external_exports.object({
|
|
36117
36594
|
fileId: external_exports.string().describe("File ID to delete"),
|
|
36118
|
-
permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible)"),
|
|
36119
|
-
account: accountParam
|
|
36595
|
+
permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
|
|
36596
|
+
account: accountParam,
|
|
36597
|
+
confirmToken: confirmTokenParam
|
|
36120
36598
|
})
|
|
36121
|
-
}, async ({ fileId, permanent, account }) => {
|
|
36599
|
+
}, async ({ fileId, permanent, account, confirmToken }, ctx) => {
|
|
36122
36600
|
const args = ["drive", "delete", pos(fileId)];
|
|
36123
|
-
if (permanent)
|
|
36601
|
+
if (permanent) {
|
|
36602
|
+
const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
|
|
36603
|
+
if (got.isError) return got;
|
|
36604
|
+
const target = { file: { id: fileId, ...shareTargetMeta(resultText(got)) }, permanent: true };
|
|
36605
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36606
|
+
action: "drive.delete-permanent",
|
|
36607
|
+
message: "Review and confirm PERMANENTLY deleting this file \u2014 it skips the trash and cannot be recovered:",
|
|
36608
|
+
confirmationLabel: "Confirm that this file should be permanently deleted now.",
|
|
36609
|
+
details: target,
|
|
36610
|
+
unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
|
|
36611
|
+
fallback: {
|
|
36612
|
+
tool: "gog_drive_delete",
|
|
36613
|
+
account,
|
|
36614
|
+
confirmToken,
|
|
36615
|
+
subject: () => ({ target: fileId, payload: target, preview: target })
|
|
36616
|
+
}
|
|
36617
|
+
});
|
|
36618
|
+
if (confirmation) return confirmation;
|
|
36619
|
+
args.push("--permanent");
|
|
36620
|
+
}
|
|
36124
36621
|
args.push("--force");
|
|
36125
36622
|
return runOrDiagnose(args, { account });
|
|
36126
36623
|
});
|
|
@@ -36446,18 +36943,22 @@ function trustedDomains(account) {
|
|
|
36446
36943
|
}
|
|
36447
36944
|
function logGmailDispatch(tool, recipients, account) {
|
|
36448
36945
|
const domains = trustedDomains(account);
|
|
36449
|
-
const
|
|
36946
|
+
const externalDomains = /* @__PURE__ */ new Set();
|
|
36947
|
+
let externalRecipientCount = 0;
|
|
36948
|
+
for (const recipient of recipients) {
|
|
36450
36949
|
const at = recipient.indexOf("@");
|
|
36451
|
-
const domain2 = at > -1 ? recipient.slice(at + 1) : "";
|
|
36452
|
-
|
|
36453
|
-
|
|
36950
|
+
const domain2 = at > -1 ? recipient.slice(at + 1).toLowerCase() : "";
|
|
36951
|
+
if (domain2 && domains.has(domain2)) continue;
|
|
36952
|
+
externalRecipientCount += 1;
|
|
36953
|
+
externalDomains.add(domain2 || "(no domain)");
|
|
36954
|
+
}
|
|
36454
36955
|
const event = {
|
|
36455
36956
|
event: "gmail_dispatch",
|
|
36456
36957
|
tool,
|
|
36457
36958
|
recipientCount: recipients.length,
|
|
36458
|
-
externalRecipientCount
|
|
36459
|
-
hasExternalRecipients:
|
|
36460
|
-
|
|
36959
|
+
externalRecipientCount,
|
|
36960
|
+
hasExternalRecipients: externalRecipientCount > 0,
|
|
36961
|
+
externalDomains: [...externalDomains].sort(),
|
|
36461
36962
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
36462
36963
|
};
|
|
36463
36964
|
process.stderr.write(`${JSON.stringify(event)}
|
|
@@ -36624,6 +37125,39 @@ async function sendReply(kind, toolName, messageId, account, flags, ctx, confirm
|
|
|
36624
37125
|
return result;
|
|
36625
37126
|
}
|
|
36626
37127
|
var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
|
|
37128
|
+
var GMAIL_DELETE_WORDS = /* @__PURE__ */ new Set(["delete", "rm", "del", "remove"]);
|
|
37129
|
+
var GMAIL_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
37130
|
+
var GMAIL_UPDATE_WORDS = /* @__PURE__ */ new Set(["update", "edit", "set"]);
|
|
37131
|
+
var SENDAS = /* @__PURE__ */ new Set(["sendas"]);
|
|
37132
|
+
var VACATION = /* @__PURE__ */ new Set(["vacation"]);
|
|
37133
|
+
function vetGmailGatedRun(sub, args) {
|
|
37134
|
+
if (sub === "batch") {
|
|
37135
|
+
const word = hasCommandWord(args, GMAIL_DELETE_WORDS);
|
|
37136
|
+
return word ? gatedElsewhere(`gog gmail batch ${word.toLowerCase()}`, "gog_gmail_run", "deletes mail for good, bypassing the Trash", "gog_gmail_batch_delete") : void 0;
|
|
37137
|
+
}
|
|
37138
|
+
if (sub !== "settings" && sub !== "sendas" && sub !== "vacation") return void 0;
|
|
37139
|
+
const words = sub === "settings" ? args : [sub, ...args];
|
|
37140
|
+
if (hasCommandWord(words, SENDAS)) {
|
|
37141
|
+
const word = hasCommandWord(args, GMAIL_CREATE_WORDS);
|
|
37142
|
+
if (word) {
|
|
37143
|
+
return gatedElsewhere(
|
|
37144
|
+
`gog gmail sendas ${word.toLowerCase()}`,
|
|
37145
|
+
"gog_gmail_run",
|
|
37146
|
+
"makes Google email the address and adds a sending identity",
|
|
37147
|
+
"gog_gmail_sendas_create"
|
|
37148
|
+
);
|
|
37149
|
+
}
|
|
37150
|
+
}
|
|
37151
|
+
if (hasCommandWord(words, VACATION) && hasCommandWord(args, GMAIL_UPDATE_WORDS) && hasTrueFlag(args, "enable")) {
|
|
37152
|
+
return gatedElsewhere(
|
|
37153
|
+
"gog gmail vacation update --enable",
|
|
37154
|
+
"gog_gmail_run",
|
|
37155
|
+
"turns on an auto-reply to every sender",
|
|
37156
|
+
"gog_gmail_vacation_update"
|
|
37157
|
+
);
|
|
37158
|
+
}
|
|
37159
|
+
return void 0;
|
|
37160
|
+
}
|
|
36627
37161
|
function vetGmailRun(subcommand, args) {
|
|
36628
37162
|
if (subcommand === "autoreply") {
|
|
36629
37163
|
return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
|
|
@@ -36631,6 +37165,8 @@ function vetGmailRun(subcommand, args) {
|
|
|
36631
37165
|
if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
|
|
36632
37166
|
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
37167
|
}
|
|
37168
|
+
const gated = vetGmailGatedRun(subcommand.toLowerCase(), args);
|
|
37169
|
+
if (gated) return gated;
|
|
36634
37170
|
if (subcommand === "settings") {
|
|
36635
37171
|
const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
|
|
36636
37172
|
if (blocked) {
|
|
@@ -36842,6 +37378,13 @@ function countNonEmptyCells(getOutput) {
|
|
|
36842
37378
|
}
|
|
36843
37379
|
|
|
36844
37380
|
// src/tools/sheets.ts
|
|
37381
|
+
var DATASOURCE_ALIASES = /* @__PURE__ */ new Set(["datasource", "data-source", "data-sources", "connected-sheets"]);
|
|
37382
|
+
var DATASOURCE_BILLED = /* @__PURE__ */ new Set(["add", "update", "refresh"]);
|
|
37383
|
+
function vetSheetsRun(subcommand, args) {
|
|
37384
|
+
if (!DATASOURCE_ALIASES.has(subcommand.toLowerCase())) return void 0;
|
|
37385
|
+
const word = hasCommandWord(args, DATASOURCE_BILLED)?.toLowerCase();
|
|
37386
|
+
return word ? gatedElsewhere(`gog sheets datasource ${word}`, "gog_sheets_run", "runs a billed BigQuery job", `gog_sheets_datasource_${word}`) : void 0;
|
|
37387
|
+
}
|
|
36845
37388
|
var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
|
|
36846
37389
|
var dryRunParam = external_exports.boolean().optional().describe(
|
|
36847
37390
|
"Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
|
|
@@ -36957,7 +37500,7 @@ function registerSheetsTools(server) {
|
|
|
36957
37500
|
}, async ({ spreadsheetId, find, replace, account }) => {
|
|
36958
37501
|
return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
|
|
36959
37502
|
});
|
|
36960
|
-
registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
|
|
37503
|
+
registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"', vet: vetSheetsRun });
|
|
36961
37504
|
}
|
|
36962
37505
|
|
|
36963
37506
|
// src/tools/slides.ts
|
|
@@ -37121,7 +37664,7 @@ function registerTasksTools(server) {
|
|
|
37121
37664
|
}
|
|
37122
37665
|
|
|
37123
37666
|
// src/server.ts
|
|
37124
|
-
var VERSION = true ? "4.
|
|
37667
|
+
var VERSION = true ? "4.5.1" : "0.0.0";
|
|
37125
37668
|
var BASE_TOOL_REGISTRARS = [
|
|
37126
37669
|
registerApiTools,
|
|
37127
37670
|
registerAppScriptTools,
|
|
@@ -37173,12 +37716,12 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
|
|
|
37173
37716
|
const [clientId, clientSecret, refreshToken, account] = values;
|
|
37174
37717
|
const { spawner } = options;
|
|
37175
37718
|
try {
|
|
37176
|
-
const { createHash:
|
|
37719
|
+
const { createHash: createHash5 } = await import("node:crypto");
|
|
37177
37720
|
const { mkdir, readFile, writeFile, chmod } = await import("node:fs/promises");
|
|
37178
37721
|
const home = options.home ?? (await import("node:os")).homedir();
|
|
37179
37722
|
const dir = join5(home, ".gogcli-mcp");
|
|
37180
37723
|
const marker = join5(dir, AUTH_BOOTSTRAP_MARKER);
|
|
37181
|
-
const fingerprint =
|
|
37724
|
+
const fingerprint = createHash5("sha256").update(values.join("\0")).digest("hex");
|
|
37182
37725
|
const previous = await readFile(marker, "utf8").catch(() => void 0);
|
|
37183
37726
|
if (previous === fingerprint && await accountListed(account, spawner)) return "present";
|
|
37184
37727
|
await run(
|
|
@@ -37423,6 +37966,8 @@ export {
|
|
|
37423
37966
|
blobStoreFromEnv,
|
|
37424
37967
|
bodyPreview,
|
|
37425
37968
|
bootstrapGogAuth,
|
|
37969
|
+
commentMentions,
|
|
37970
|
+
commentSnapshot,
|
|
37426
37971
|
confineAtFile,
|
|
37427
37972
|
confinePath,
|
|
37428
37973
|
confinePaths,
|
|
@@ -37431,6 +37976,7 @@ export {
|
|
|
37431
37976
|
defaultFileRoot,
|
|
37432
37977
|
diagnose,
|
|
37433
37978
|
errorText,
|
|
37979
|
+
eventSnapshot,
|
|
37434
37980
|
extractEmails,
|
|
37435
37981
|
fetchGmailPages,
|
|
37436
37982
|
fileRoots,
|
|
@@ -37445,11 +37991,14 @@ export {
|
|
|
37445
37991
|
pageAliasParam,
|
|
37446
37992
|
pageTokenParam,
|
|
37447
37993
|
paginationParams,
|
|
37994
|
+
parseMetadataHeaders,
|
|
37448
37995
|
payloadArg,
|
|
37449
37996
|
pos,
|
|
37450
37997
|
prepareDownloadRoot,
|
|
37451
37998
|
pushPaginationFlags,
|
|
37999
|
+
readClassroomWork,
|
|
37452
38000
|
readCourse,
|
|
38001
|
+
readCoursework,
|
|
37453
38002
|
registerApiTools,
|
|
37454
38003
|
registerAppScriptTools,
|
|
37455
38004
|
registerAuthTools,
|
|
@@ -37474,7 +38023,9 @@ export {
|
|
|
37474
38023
|
runBinary,
|
|
37475
38024
|
runOrDiagnose,
|
|
37476
38025
|
senderPreview,
|
|
38026
|
+
shareTargetMeta,
|
|
37477
38027
|
stripConsumedPageToken,
|
|
38028
|
+
studentLabel,
|
|
37478
38029
|
uploadToBlobStore
|
|
37479
38030
|
};
|
|
37480
38031
|
/*! Bundled license information:
|