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