gogcli-mcp 4.2.5 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +1340 -262
- package/dist/lib.js +1381 -260
- package/manifest.json +9 -2
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/arg-guard.ts +68 -0
- package/src/argv.ts +27 -0
- package/src/attachment-root.ts +111 -0
- package/src/attachments.ts +1 -0
- package/src/blob-upload.ts +4 -2
- package/src/dispatch-confirmation.ts +277 -0
- package/src/file-roots.ts +66 -0
- package/src/gmail-dispatch-guard.ts +70 -30
- package/src/gmail-results.ts +21 -2
- package/src/lib.ts +15 -0
- package/src/run-path-guard.ts +118 -0
- package/src/runner.ts +86 -18
- package/src/send-confirm-token.ts +167 -0
- package/src/tools/api.ts +60 -7
- package/src/tools/appscript.ts +12 -8
- package/src/tools/auth.ts +13 -3
- package/src/tools/calendar.ts +178 -15
- package/src/tools/chat.ts +94 -18
- package/src/tools/classroom.ts +110 -28
- package/src/tools/contacts.ts +5 -3
- package/src/tools/docs.ts +8 -6
- package/src/tools/drive.ts +96 -20
- package/src/tools/gmail.ts +175 -24
- package/src/tools/sheets.ts +10 -8
- package/src/tools/slides.ts +14 -9
- package/src/tools/tasks.ts +7 -5
- package/src/tools/utils.ts +52 -6
- package/tests/arg-guard.test.ts +80 -0
- package/tests/attachment-root.test.ts +130 -0
- package/tests/attachments.test.ts +8 -0
- package/tests/blob-upload.test.ts +4 -3
- package/tests/file-roots.test.ts +101 -0
- package/tests/gmail-dispatch-guard.test.ts +270 -11
- package/tests/gmail-results.test.ts +35 -1
- package/tests/run-path-guard.test.ts +142 -0
- package/tests/runner.test.ts +136 -0
- package/tests/send-confirm-token.test.ts +200 -0
- package/tests/tools/api.test.ts +74 -8
- package/tests/tools/appscript.test.ts +34 -8
- package/tests/tools/auth.test.ts +44 -17
- package/tests/tools/calendar.test.ts +33 -28
- package/tests/tools/chat.test.ts +50 -21
- package/tests/tools/classroom.test.ts +43 -39
- package/tests/tools/contacts.test.ts +3 -2
- package/tests/tools/dispatch-gates.test.ts +396 -0
- package/tests/tools/docs.test.ts +44 -15
- package/tests/tools/drive.test.ts +110 -20
- package/tests/tools/gmail-confirm-token.test.ts +274 -0
- package/tests/tools/gmail.test.ts +227 -29
- package/tests/tools/run-tool-examples.test.ts +69 -0
- package/tests/tools/run-vets.test.ts +131 -0
- package/tests/tools/sheets.test.ts +16 -15
- package/tests/tools/slides.test.ts +47 -11
- package/tests/tools/tasks.test.ts +7 -6
- package/tests/tools/utils.test.ts +32 -31
- package/vitest.config.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -2119,8 +2119,8 @@ function emoji() {
|
|
|
2119
2119
|
}
|
|
2120
2120
|
var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
2121
2121
|
var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
2122
|
-
var mac = (
|
|
2123
|
-
const escapedDelim = escapeRegex(
|
|
2122
|
+
var mac = (delimiter3) => {
|
|
2123
|
+
const escapedDelim = escapeRegex(delimiter3 ?? ":");
|
|
2124
2124
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
2125
2125
|
};
|
|
2126
2126
|
var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -5148,7 +5148,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5148
5148
|
var NONE = 0;
|
|
5149
5149
|
var ASSUMED = 1;
|
|
5150
5150
|
var PROVEN = 2;
|
|
5151
|
-
function isRecursive(inst, stack,
|
|
5151
|
+
function isRecursive(inst, stack, resolve4) {
|
|
5152
5152
|
const cached2 = recursive.get(inst);
|
|
5153
5153
|
if (cached2 !== void 0)
|
|
5154
5154
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5158,7 +5158,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5158
5158
|
let result = NONE;
|
|
5159
5159
|
const check2 = (child) => {
|
|
5160
5160
|
if (result !== PROVEN && child?._zod) {
|
|
5161
|
-
const answer = isRecursive(child, stack,
|
|
5161
|
+
const answer = isRecursive(child, stack, resolve4);
|
|
5162
5162
|
if (answer > result)
|
|
5163
5163
|
result = answer;
|
|
5164
5164
|
}
|
|
@@ -5169,7 +5169,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5169
5169
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5170
5170
|
if (spread && !desc.enumerable)
|
|
5171
5171
|
continue;
|
|
5172
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
5172
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
|
|
5173
5173
|
if (child > answer)
|
|
5174
5174
|
answer = child;
|
|
5175
5175
|
}
|
|
@@ -5233,7 +5233,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5233
5233
|
break;
|
|
5234
5234
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5235
5235
|
case "lazy": {
|
|
5236
|
-
const inner = def._cachedInner ?? (
|
|
5236
|
+
const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
|
|
5237
5237
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5238
5238
|
break;
|
|
5239
5239
|
}
|
|
@@ -24164,14 +24164,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
24164
24164
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
24165
24165
|
}
|
|
24166
24166
|
function sleep(ms, signal) {
|
|
24167
|
-
return new Promise((
|
|
24167
|
+
return new Promise((resolve4, reject) => {
|
|
24168
24168
|
if (signal?.aborted) {
|
|
24169
24169
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
24170
24170
|
return;
|
|
24171
24171
|
}
|
|
24172
24172
|
const timer = setTimeout(() => {
|
|
24173
24173
|
signal?.removeEventListener("abort", onAbort);
|
|
24174
|
-
|
|
24174
|
+
resolve4();
|
|
24175
24175
|
}, ms);
|
|
24176
24176
|
const onAbort = () => {
|
|
24177
24177
|
clearTimeout(timer);
|
|
@@ -24965,7 +24965,7 @@ var Protocol = class {
|
|
|
24965
24965
|
const flowStartedAt = Date.now();
|
|
24966
24966
|
let onAbort;
|
|
24967
24967
|
let cleanupMessageId;
|
|
24968
|
-
return new Promise((
|
|
24968
|
+
return new Promise((resolve4, reject) => {
|
|
24969
24969
|
const earlyReject = (error62) => {
|
|
24970
24970
|
reject(error62);
|
|
24971
24971
|
};
|
|
@@ -25033,7 +25033,7 @@ var Protocol = class {
|
|
|
25033
25033
|
}
|
|
25034
25034
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
25035
25035
|
if (decoded.kind === "input_required") {
|
|
25036
|
-
if (options?.allowInputRequired === true) return
|
|
25036
|
+
if (options?.allowInputRequired === true) return resolve4(manualInputRequiredValue(decoded));
|
|
25037
25037
|
const flow = {
|
|
25038
25038
|
codec: codec2,
|
|
25039
25039
|
request,
|
|
@@ -25045,11 +25045,11 @@ var Protocol = class {
|
|
|
25045
25045
|
params
|
|
25046
25046
|
}, resultSchema, legOptions)
|
|
25047
25047
|
};
|
|
25048
|
-
return
|
|
25048
|
+
return resolve4(this._resolveNonCompleteResult(decoded, flow));
|
|
25049
25049
|
}
|
|
25050
25050
|
const result = decoded.result;
|
|
25051
25051
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
25052
|
-
if (parseResult.success)
|
|
25052
|
+
if (parseResult.success) resolve4(parseResult.data);
|
|
25053
25053
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
25054
25054
|
}, reject);
|
|
25055
25055
|
});
|
|
@@ -27961,7 +27961,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
27961
27961
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
27962
27962
|
const schOrFunc = root.refs[ref];
|
|
27963
27963
|
if (schOrFunc) return schOrFunc;
|
|
27964
|
-
let _sch =
|
|
27964
|
+
let _sch = resolve4.call(this, root, ref);
|
|
27965
27965
|
if (_sch === void 0) {
|
|
27966
27966
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
27967
27967
|
const { schemaId } = this.opts;
|
|
@@ -27987,7 +27987,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
27987
27987
|
function sameSchemaEnv(s1, s2) {
|
|
27988
27988
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
27989
27989
|
}
|
|
27990
|
-
function
|
|
27990
|
+
function resolve4(root, ref) {
|
|
27991
27991
|
let sch;
|
|
27992
27992
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
27993
27993
|
return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
|
|
@@ -28437,47 +28437,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
28437
28437
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
28438
28438
|
return uri;
|
|
28439
28439
|
}
|
|
28440
|
-
function
|
|
28440
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
28441
28441
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
28442
28442
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
28443
28443
|
schemelessOptions.skipEscape = true;
|
|
28444
28444
|
return serialize(resolved, schemelessOptions);
|
|
28445
28445
|
}
|
|
28446
|
-
function resolveComponent(base,
|
|
28446
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
28447
28447
|
const target = {};
|
|
28448
28448
|
if (!skipNormalization) {
|
|
28449
28449
|
base = parse3(serialize(base, options), options);
|
|
28450
|
-
|
|
28450
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
28451
28451
|
}
|
|
28452
28452
|
options = options || {};
|
|
28453
|
-
if (!options.tolerant &&
|
|
28454
|
-
target.scheme =
|
|
28455
|
-
target.userinfo =
|
|
28456
|
-
target.host =
|
|
28457
|
-
target.port =
|
|
28458
|
-
target.path = removeDotSegments(
|
|
28459
|
-
target.query =
|
|
28453
|
+
if (!options.tolerant && relative3.scheme) {
|
|
28454
|
+
target.scheme = relative3.scheme;
|
|
28455
|
+
target.userinfo = relative3.userinfo;
|
|
28456
|
+
target.host = relative3.host;
|
|
28457
|
+
target.port = relative3.port;
|
|
28458
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
28459
|
+
target.query = relative3.query;
|
|
28460
28460
|
} else {
|
|
28461
|
-
if (
|
|
28462
|
-
target.userinfo =
|
|
28463
|
-
target.host =
|
|
28464
|
-
target.port =
|
|
28465
|
-
target.path = removeDotSegments(
|
|
28466
|
-
target.query =
|
|
28461
|
+
if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
|
|
28462
|
+
target.userinfo = relative3.userinfo;
|
|
28463
|
+
target.host = relative3.host;
|
|
28464
|
+
target.port = relative3.port;
|
|
28465
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
28466
|
+
target.query = relative3.query;
|
|
28467
28467
|
} else {
|
|
28468
|
-
if (!
|
|
28468
|
+
if (!relative3.path) {
|
|
28469
28469
|
target.path = base.path;
|
|
28470
|
-
if (
|
|
28470
|
+
if (relative3.query !== void 0) target.query = relative3.query;
|
|
28471
28471
|
else target.query = base.query;
|
|
28472
28472
|
} else {
|
|
28473
|
-
if (
|
|
28473
|
+
if (relative3.path[0] === "/") target.path = removeDotSegments(relative3.path);
|
|
28474
28474
|
else {
|
|
28475
|
-
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" +
|
|
28476
|
-
else if (!base.path) target.path =
|
|
28477
|
-
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
28475
|
+
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative3.path;
|
|
28476
|
+
else if (!base.path) target.path = relative3.path;
|
|
28477
|
+
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
|
|
28478
28478
|
target.path = removeDotSegments(target.path);
|
|
28479
28479
|
}
|
|
28480
|
-
target.query =
|
|
28480
|
+
target.query = relative3.query;
|
|
28481
28481
|
}
|
|
28482
28482
|
target.userinfo = base.userinfo;
|
|
28483
28483
|
target.host = base.host;
|
|
@@ -28485,7 +28485,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
28485
28485
|
}
|
|
28486
28486
|
target.scheme = base.scheme;
|
|
28487
28487
|
}
|
|
28488
|
-
target.fragment =
|
|
28488
|
+
target.fragment = relative3.fragment;
|
|
28489
28489
|
return target;
|
|
28490
28490
|
}
|
|
28491
28491
|
function equal(uriA, uriB, options) {
|
|
@@ -28611,7 +28611,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
28611
28611
|
const fastUri = {
|
|
28612
28612
|
SCHEMES,
|
|
28613
28613
|
normalize,
|
|
28614
|
-
resolve,
|
|
28614
|
+
resolve: resolve4,
|
|
28615
28615
|
resolveComponent,
|
|
28616
28616
|
equal,
|
|
28617
28617
|
serialize,
|
|
@@ -29393,14 +29393,14 @@ var require_ucs2length = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
29393
29393
|
function ucs2length(str) {
|
|
29394
29394
|
const len = str.length;
|
|
29395
29395
|
let length = 0;
|
|
29396
|
-
let
|
|
29396
|
+
let pos2 = 0;
|
|
29397
29397
|
let value;
|
|
29398
|
-
while (
|
|
29398
|
+
while (pos2 < len) {
|
|
29399
29399
|
length++;
|
|
29400
|
-
value = str.charCodeAt(
|
|
29401
|
-
if (value >= 55296 && value <= 56319 &&
|
|
29402
|
-
value = str.charCodeAt(
|
|
29403
|
-
if ((value & 64512) === 56320)
|
|
29400
|
+
value = str.charCodeAt(pos2++);
|
|
29401
|
+
if (value >= 55296 && value <= 56319 && pos2 < len) {
|
|
29402
|
+
value = str.charCodeAt(pos2);
|
|
29403
|
+
if ((value & 64512) === 56320) pos2++;
|
|
29404
29404
|
}
|
|
29405
29405
|
}
|
|
29406
29406
|
return length;
|
|
@@ -33984,7 +33984,7 @@ var StdioServerTransport = class {
|
|
|
33984
33984
|
}
|
|
33985
33985
|
send(message) {
|
|
33986
33986
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
33987
|
-
return new Promise((
|
|
33987
|
+
return new Promise((resolve4, reject) => {
|
|
33988
33988
|
const json2 = serializeMessage(message);
|
|
33989
33989
|
let settled = false;
|
|
33990
33990
|
const onError = (error62) => {
|
|
@@ -33999,14 +33999,14 @@ var StdioServerTransport = class {
|
|
|
33999
33999
|
settled = true;
|
|
34000
34000
|
this._stdout.off("error", onError);
|
|
34001
34001
|
this._stdout.off("drain", onDrain);
|
|
34002
|
-
|
|
34002
|
+
resolve4();
|
|
34003
34003
|
};
|
|
34004
34004
|
this._stdout.once("error", onError);
|
|
34005
34005
|
if (this._stdout.write(json2)) {
|
|
34006
34006
|
if (settled) return;
|
|
34007
34007
|
settled = true;
|
|
34008
34008
|
this._stdout.off("error", onError);
|
|
34009
|
-
|
|
34009
|
+
resolve4();
|
|
34010
34010
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
34011
34011
|
});
|
|
34012
34012
|
}
|
|
@@ -34060,14 +34060,14 @@ var StdioConnectionChannel = class {
|
|
|
34060
34060
|
*/
|
|
34061
34061
|
async whenRequestsAnswered(timeoutMs) {
|
|
34062
34062
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
34063
|
-
return await new Promise((
|
|
34063
|
+
return await new Promise((resolve4) => {
|
|
34064
34064
|
const waiter = () => {
|
|
34065
34065
|
clearTimeout(timer);
|
|
34066
|
-
|
|
34066
|
+
resolve4(true);
|
|
34067
34067
|
};
|
|
34068
34068
|
const timer = setTimeout(() => {
|
|
34069
34069
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
34070
|
-
|
|
34070
|
+
resolve4(false);
|
|
34071
34071
|
}, timeoutMs);
|
|
34072
34072
|
this._drainWaiters.push(waiter);
|
|
34073
34073
|
});
|
|
@@ -34206,14 +34206,14 @@ function serveStdio(factory, options = {}) {
|
|
|
34206
34206
|
return false;
|
|
34207
34207
|
};
|
|
34208
34208
|
const answerLegacyRejection = (request, reason, requestedVersion) => {
|
|
34209
|
-
const
|
|
34209
|
+
const rejection3 = modernOnlyStrictRejection({
|
|
34210
34210
|
kind: "legacy",
|
|
34211
34211
|
reason,
|
|
34212
34212
|
...requestedVersion !== void 0 && { requestedVersion }
|
|
34213
34213
|
}, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
34214
|
-
if (
|
|
34215
|
-
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${
|
|
34216
|
-
return writeErrorResponse(request.id,
|
|
34214
|
+
if (rejection3 === void 0) return Promise.resolve();
|
|
34215
|
+
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection3.cell}): ${rejection3.message}`));
|
|
34216
|
+
return writeErrorResponse(request.id, rejection3.code, rejection3.message, rejection3.data);
|
|
34217
34217
|
};
|
|
34218
34218
|
const onInstanceClosed = (channel) => {
|
|
34219
34219
|
if (closing || channel === discarding) return;
|
|
@@ -34437,13 +34437,28 @@ var API_KEY_RE = new RegExp([
|
|
|
34437
34437
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
34438
34438
|
// webhook signing secret (Stripe-style)
|
|
34439
34439
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34440
|
-
var QUERY_SECRET_RE = /([?&](?:
|
|
34440
|
+
var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
34441
|
+
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
34442
|
+
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
34443
|
+
var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
|
|
34441
34444
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
34442
|
-
var JSON_SECRET_KEYS =
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
+
var JSON_SECRET_KEYS = [
|
|
34446
|
+
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
34447
|
+
"client[_-]?secret",
|
|
34448
|
+
"api[_-]?secret",
|
|
34449
|
+
"(?:x[_-])?api[_-]?key",
|
|
34450
|
+
"private[_-]?key",
|
|
34451
|
+
"x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
|
|
34452
|
+
"(?:proxy-)?authorization",
|
|
34453
|
+
"password",
|
|
34454
|
+
"passwd",
|
|
34455
|
+
"secret",
|
|
34456
|
+
"token"
|
|
34457
|
+
].join("|");
|
|
34458
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
34459
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
34445
34460
|
function redactSecrets(text) {
|
|
34446
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
34461
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
34447
34462
|
}
|
|
34448
34463
|
|
|
34449
34464
|
// ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
|
|
@@ -34538,26 +34553,105 @@ function errorResult(message) {
|
|
|
34538
34553
|
}
|
|
34539
34554
|
|
|
34540
34555
|
// ../../node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
|
|
34556
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
34541
34557
|
var DEFAULT_REQUEST_KEY = "confirmation";
|
|
34542
34558
|
var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
34543
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.";
|
|
34561
|
+
function canonicalJson(value) {
|
|
34562
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
34563
|
+
return JSON.stringify(value);
|
|
34564
|
+
if (value === void 0)
|
|
34565
|
+
return "null";
|
|
34566
|
+
if (typeof value === "number")
|
|
34567
|
+
return Number.isFinite(value) ? JSON.stringify(value) : `{"$num":"${String(value)}"}`;
|
|
34568
|
+
if (typeof value === "bigint")
|
|
34569
|
+
return `{"$bigint":"${value.toString()}"}`;
|
|
34570
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
34571
|
+
throw new TypeError(`requireConfirmation: cannot bind a ${typeof value} argument value.`);
|
|
34572
|
+
}
|
|
34573
|
+
if (Array.isArray(value))
|
|
34574
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
34575
|
+
if (value instanceof Date) {
|
|
34576
|
+
return `{"$date":${JSON.stringify(Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString())}}`;
|
|
34577
|
+
}
|
|
34578
|
+
if (ArrayBuffer.isView(value)) {
|
|
34579
|
+
const bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
34580
|
+
return `{"$bytes":${JSON.stringify(bytes.toString("base64"))}}`;
|
|
34581
|
+
}
|
|
34582
|
+
if (value instanceof ArrayBuffer)
|
|
34583
|
+
return `{"$bytes":${JSON.stringify(Buffer.from(value).toString("base64"))}}`;
|
|
34584
|
+
if (value instanceof Map) {
|
|
34585
|
+
const entries2 = [...value.entries()].map(([k, v]) => `[${canonicalJson(k)},${canonicalJson(v)}]`).sort();
|
|
34586
|
+
return `{"$map":[${entries2.join(",")}]}`;
|
|
34587
|
+
}
|
|
34588
|
+
if (value instanceof Set) {
|
|
34589
|
+
return `{"$set":[${[...value].map(canonicalJson).sort().join(",")}]}`;
|
|
34590
|
+
}
|
|
34591
|
+
const proto = Object.getPrototypeOf(value);
|
|
34592
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
34593
|
+
throw new TypeError("requireConfirmation: cannot bind a non-plain object argument value (class instance).");
|
|
34594
|
+
}
|
|
34595
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
34596
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(escapeKey(k))}:${canonicalJson(v)}`).join(",")}}`;
|
|
34597
|
+
}
|
|
34598
|
+
function escapeKey(key) {
|
|
34599
|
+
return key.startsWith("$") ? `$${key}` : key;
|
|
34600
|
+
}
|
|
34601
|
+
function bindingKey(binding) {
|
|
34602
|
+
if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
|
|
34603
|
+
throw new RangeError("requireConfirmation: binding.ttlSeconds must be a finite number greater than 0.");
|
|
34604
|
+
}
|
|
34605
|
+
const key = typeof binding.key === "string" ? Buffer.from(binding.key, "utf8") : Buffer.from(binding.key);
|
|
34606
|
+
if (key.length < 32) {
|
|
34607
|
+
throw new RangeError("requireConfirmation: binding.key must be at least 32 bytes.");
|
|
34608
|
+
}
|
|
34609
|
+
return key;
|
|
34610
|
+
}
|
|
34611
|
+
function commitment(action, args) {
|
|
34612
|
+
return createHash("sha256").update(`${action}\0${canonicalJson(args)}`).digest("base64url");
|
|
34613
|
+
}
|
|
34614
|
+
function mintState(key, action, binding) {
|
|
34615
|
+
const exp = Math.floor(Date.now() / 1e3) + (binding.ttlSeconds ?? 600);
|
|
34616
|
+
const body = Buffer.from(JSON.stringify({ c: commitment(action, binding.args), exp })).toString("base64url");
|
|
34617
|
+
const mac3 = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest("base64url");
|
|
34618
|
+
return `${STATE_PREFIX}${body}.${mac3}`;
|
|
34619
|
+
}
|
|
34620
|
+
function verifyState(key, action, binding, state) {
|
|
34621
|
+
if (typeof state !== "string" || !state.startsWith(STATE_PREFIX))
|
|
34622
|
+
return false;
|
|
34623
|
+
const rest = state.slice(STATE_PREFIX.length);
|
|
34624
|
+
const dot = rest.indexOf(".");
|
|
34625
|
+
if (dot < 0)
|
|
34626
|
+
return false;
|
|
34627
|
+
const body = rest.slice(0, dot);
|
|
34628
|
+
const mac3 = Buffer.from(rest.slice(dot + 1), "base64url");
|
|
34629
|
+
const expected = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest();
|
|
34630
|
+
if (mac3.length !== expected.length || !timingSafeEqual(mac3, expected))
|
|
34631
|
+
return false;
|
|
34632
|
+
let payload;
|
|
34633
|
+
try {
|
|
34634
|
+
payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
34635
|
+
} catch {
|
|
34636
|
+
return false;
|
|
34637
|
+
}
|
|
34638
|
+
if (typeof payload.exp !== "number" || payload.exp * 1e3 <= Date.now())
|
|
34639
|
+
return false;
|
|
34640
|
+
const want = Buffer.from(commitment(action, binding.args));
|
|
34641
|
+
const got = Buffer.from(typeof payload.c === "string" ? payload.c : "");
|
|
34642
|
+
return got.length === want.length && timingSafeEqual(got, want);
|
|
34643
|
+
}
|
|
34644
|
+
function echoedState(ctx) {
|
|
34645
|
+
const accessor = ctx.mcpReq.requestState;
|
|
34646
|
+
return typeof accessor === "function" ? accessor() : void 0;
|
|
34647
|
+
}
|
|
34544
34648
|
function requireConfirmation(ctx, options) {
|
|
34545
34649
|
const requestKey = options.requestKey ?? DEFAULT_REQUEST_KEY;
|
|
34650
|
+
const key = options.binding ? bindingKey(options.binding) : void 0;
|
|
34546
34651
|
const confirmationSchema = external_exports.object({
|
|
34547
34652
|
confirmed: external_exports.boolean().describe(options.confirmationLabel ?? DEFAULT_CONFIRMATION_LABEL)
|
|
34548
34653
|
});
|
|
34549
|
-
const
|
|
34550
|
-
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
34551
|
-
if (response.kind === "missing") {
|
|
34552
|
-
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
34553
|
-
return textResult({
|
|
34554
|
-
confirmed: false,
|
|
34555
|
-
dispatched: false,
|
|
34556
|
-
action: options.action,
|
|
34557
|
-
reason: "confirmation-unsupported",
|
|
34558
|
-
note: options.unsupportedNote ? `${UNSUPPORTED_NOTE}. ${options.unsupportedNote}` : `${UNSUPPORTED_NOTE}.`
|
|
34559
|
-
});
|
|
34560
|
-
}
|
|
34654
|
+
const ask = () => {
|
|
34561
34655
|
const preview = {
|
|
34562
34656
|
action: options.action,
|
|
34563
34657
|
...options.details === void 0 ? {} : { details: options.details }
|
|
@@ -34569,10 +34663,33 @@ function requireConfirmation(ctx, options) {
|
|
|
34569
34663
|
${JSON.stringify(preview, null, 2)}`,
|
|
34570
34664
|
requestedSchema: confirmationSchema
|
|
34571
34665
|
})
|
|
34572
|
-
}
|
|
34666
|
+
},
|
|
34667
|
+
...key && options.binding ? { requestState: mintState(key, options.action, options.binding) } : {}
|
|
34573
34668
|
});
|
|
34669
|
+
};
|
|
34670
|
+
const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
|
|
34671
|
+
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
34672
|
+
if (response.kind === "missing") {
|
|
34673
|
+
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
34674
|
+
return textResult({
|
|
34675
|
+
confirmed: false,
|
|
34676
|
+
dispatched: false,
|
|
34677
|
+
action: options.action,
|
|
34678
|
+
reason: "confirmation-unsupported",
|
|
34679
|
+
note: options.unsupportedNote ? `${UNSUPPORTED_NOTE}. ${options.unsupportedNote}` : `${UNSUPPORTED_NOTE}.`
|
|
34680
|
+
});
|
|
34681
|
+
}
|
|
34682
|
+
return ask();
|
|
34574
34683
|
}
|
|
34575
34684
|
if (response.kind === "elicit" && response.action === "accept" && accepted?.confirmed === true) {
|
|
34685
|
+
if (key && options.binding) {
|
|
34686
|
+
const state = echoedState(ctx);
|
|
34687
|
+
if (state === void 0 || state === null) {
|
|
34688
|
+
return errorResult(`Confirmation for ${options.action} was accepted, but the retry carried no requestState, so it cannot be checked against the prompt that was shown. Nothing was done. The likely cause is that the MCP client or host does not round-trip requestState (it must echo the requestState from the input_required result back on the retry); asking again would loop.`);
|
|
34689
|
+
}
|
|
34690
|
+
if (!verifyState(key, options.action, options.binding, state))
|
|
34691
|
+
return ask();
|
|
34692
|
+
}
|
|
34576
34693
|
return void 0;
|
|
34577
34694
|
}
|
|
34578
34695
|
return textResult({
|
|
@@ -34677,13 +34794,47 @@ async function createMcpServer(opts) {
|
|
|
34677
34794
|
}
|
|
34678
34795
|
return server;
|
|
34679
34796
|
}
|
|
34797
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
34798
|
+
var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
|
|
34680
34799
|
function withGracefulShutdown(target, opts = {}) {
|
|
34681
34800
|
const shouldExit = opts.exit ?? true;
|
|
34801
|
+
if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
|
|
34802
|
+
throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
|
|
34803
|
+
}
|
|
34804
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
34805
|
+
if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
|
|
34806
|
+
throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
|
|
34807
|
+
}
|
|
34808
|
+
const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
|
|
34682
34809
|
let shuttingDown = false;
|
|
34810
|
+
let firstSignalAt = 0;
|
|
34811
|
+
let exited = false;
|
|
34812
|
+
const exitOnce = () => {
|
|
34813
|
+
if (exited)
|
|
34814
|
+
return;
|
|
34815
|
+
exited = true;
|
|
34816
|
+
process.exit(0);
|
|
34817
|
+
};
|
|
34683
34818
|
const handler = (signal) => {
|
|
34684
|
-
if (shuttingDown)
|
|
34819
|
+
if (shuttingDown) {
|
|
34820
|
+
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
34821
|
+
return;
|
|
34822
|
+
if (shouldExit) {
|
|
34823
|
+
console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
|
|
34824
|
+
exitOnce();
|
|
34825
|
+
}
|
|
34685
34826
|
return;
|
|
34827
|
+
}
|
|
34686
34828
|
shuttingDown = true;
|
|
34829
|
+
firstSignalAt = Date.now();
|
|
34830
|
+
let timer;
|
|
34831
|
+
if (shouldExit) {
|
|
34832
|
+
timer = setTimeout(() => {
|
|
34833
|
+
console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
|
|
34834
|
+
exitOnce();
|
|
34835
|
+
}, timeoutMs);
|
|
34836
|
+
timer.unref?.();
|
|
34837
|
+
}
|
|
34687
34838
|
void (async () => {
|
|
34688
34839
|
try {
|
|
34689
34840
|
if (opts.onSignal)
|
|
@@ -34692,8 +34843,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
34692
34843
|
} catch (err) {
|
|
34693
34844
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34694
34845
|
} finally {
|
|
34846
|
+
if (timer !== void 0)
|
|
34847
|
+
clearTimeout(timer);
|
|
34695
34848
|
if (shouldExit)
|
|
34696
|
-
|
|
34849
|
+
exitOnce();
|
|
34697
34850
|
}
|
|
34698
34851
|
})();
|
|
34699
34852
|
};
|
|
@@ -34723,6 +34876,8 @@ function runMcp(opts) {
|
|
|
34723
34876
|
}
|
|
34724
34877
|
|
|
34725
34878
|
// ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34879
|
+
import { homedir } from "node:os";
|
|
34880
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
34726
34881
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
34727
34882
|
function readEnvVar(key, opts = {}) {
|
|
34728
34883
|
const env = opts.env ?? process.env;
|
|
@@ -34749,6 +34904,50 @@ function parseBoolEnv(key, opts = {}) {
|
|
|
34749
34904
|
return false;
|
|
34750
34905
|
return fallback;
|
|
34751
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
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/fs/confine.js
|
|
34918
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
34919
|
+
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
|
|
34920
|
+
function isInside(root, target) {
|
|
34921
|
+
const rel = relative(root, target);
|
|
34922
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute2(rel);
|
|
34923
|
+
}
|
|
34924
|
+
function realPathAllowingMissing(p) {
|
|
34925
|
+
const abs = resolve2(p);
|
|
34926
|
+
let existing = abs;
|
|
34927
|
+
const tail = [];
|
|
34928
|
+
while (!existsSync(existing)) {
|
|
34929
|
+
const parent = dirname(existing);
|
|
34930
|
+
if (parent === existing)
|
|
34931
|
+
break;
|
|
34932
|
+
tail.unshift(existing.slice(parent.length).replace(/^[/\\]+/, ""));
|
|
34933
|
+
existing = parent;
|
|
34934
|
+
}
|
|
34935
|
+
let real;
|
|
34936
|
+
try {
|
|
34937
|
+
real = realpathSync(existing);
|
|
34938
|
+
} catch {
|
|
34939
|
+
real = existing;
|
|
34940
|
+
}
|
|
34941
|
+
return tail.length > 0 ? resolve2(real, ...tail) : real;
|
|
34942
|
+
}
|
|
34943
|
+
function assertPathWithinRoots(path, roots) {
|
|
34944
|
+
const target = realPathAllowingMissing(expandPath(path));
|
|
34945
|
+
for (const root of roots) {
|
|
34946
|
+
if (isInside(realPathAllowingMissing(expandPath(root)), target))
|
|
34947
|
+
return target;
|
|
34948
|
+
}
|
|
34949
|
+
throw new Error(`Path is outside the allowed directories: ${path}`);
|
|
34950
|
+
}
|
|
34752
34951
|
|
|
34753
34952
|
// ../../node_modules/@chrischall/mcp-utils/dist/zod/index.js
|
|
34754
34953
|
var PositiveInt = external_exports.number().int().positive();
|
|
@@ -34770,7 +34969,7 @@ var pageSchema = {
|
|
|
34770
34969
|
};
|
|
34771
34970
|
|
|
34772
34971
|
// src/runner.ts
|
|
34773
|
-
import { delimiter, join } from "node:path";
|
|
34972
|
+
import { delimiter, join as join2 } from "node:path";
|
|
34774
34973
|
|
|
34775
34974
|
// src/timestamps.ts
|
|
34776
34975
|
var DEFAULT_DISPLAY_TZ = "America/New_York";
|
|
@@ -34796,9 +34995,9 @@ function offsetAt(instant, tz) {
|
|
|
34796
34995
|
const w = wallPartsIn(instant, tz);
|
|
34797
34996
|
const asUTC = Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second, instant.getUTCMilliseconds());
|
|
34798
34997
|
const minutes = Math.round((asUTC - instant.getTime()) / 6e4);
|
|
34799
|
-
const
|
|
34998
|
+
const sign2 = minutes < 0 ? "-" : "+";
|
|
34800
34999
|
const abs = Math.abs(minutes);
|
|
34801
|
-
return `${
|
|
35000
|
+
return `${sign2}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
34802
35001
|
}
|
|
34803
35002
|
function wallPartsIn(instant, tz) {
|
|
34804
35003
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
@@ -34971,9 +35170,48 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
|
|
|
34971
35170
|
return JSON.stringify(parsed, null, detectIndent(text));
|
|
34972
35171
|
}
|
|
34973
35172
|
|
|
35173
|
+
// src/arg-guard.ts
|
|
35174
|
+
var FORBIDDEN_LONG_FLAG = /^--(readonly|enable-commands|enable-commands-exact|disable-commands|access-token|home|account|acct|client|gmail-no-send|no-input|non-interactive|noninteractive)(=|$)/i;
|
|
35175
|
+
var SHORT_ACCOUNT_CLUSTER = /^-(?!-)[A-Za-z]*a/;
|
|
35176
|
+
function forbiddenArgReason(arg) {
|
|
35177
|
+
if (arg === "--") {
|
|
35178
|
+
return 'The argument "--" is not allowed: it ends flag parsing and would disable safety flags appended after it.';
|
|
35179
|
+
}
|
|
35180
|
+
const match = FORBIDDEN_LONG_FLAG.exec(arg);
|
|
35181
|
+
if (match) {
|
|
35182
|
+
return `The flag ${arg} is not allowed here: --${match[1].toLowerCase()} is a safety/credential control set by the server operator, not a per-call option.`;
|
|
35183
|
+
}
|
|
35184
|
+
if (SHORT_ACCOUNT_CLUSTER.test(arg)) {
|
|
35185
|
+
return `The flag ${arg} is not allowed here: -a selects the account. Use the tool's account parameter instead.`;
|
|
35186
|
+
}
|
|
35187
|
+
return void 0;
|
|
35188
|
+
}
|
|
35189
|
+
function assertSafeForwardedArgs(args) {
|
|
35190
|
+
for (const arg of args) {
|
|
35191
|
+
const reason = forbiddenArgReason(arg);
|
|
35192
|
+
if (reason) throw new Error(reason);
|
|
35193
|
+
}
|
|
35194
|
+
}
|
|
35195
|
+
var SUBCOMMAND_SHAPE = /^[a-z][a-z0-9-]*$/;
|
|
35196
|
+
function assertSafeSubcommand(subcommand) {
|
|
35197
|
+
if (!SUBCOMMAND_SHAPE.test(subcommand)) {
|
|
35198
|
+
throw new Error(
|
|
35199
|
+
`Invalid subcommand ${JSON.stringify(subcommand)}: pass a single gog subcommand name such as "list" or "mark-read", and put its arguments in args.`
|
|
35200
|
+
);
|
|
35201
|
+
}
|
|
35202
|
+
}
|
|
35203
|
+
|
|
35204
|
+
// src/argv.ts
|
|
35205
|
+
function pos(value) {
|
|
35206
|
+
return { kind: "positional", value };
|
|
35207
|
+
}
|
|
35208
|
+
function isGogPositional(arg) {
|
|
35209
|
+
return typeof arg === "object" && arg !== null && arg.kind === "positional";
|
|
35210
|
+
}
|
|
35211
|
+
|
|
34974
35212
|
// src/runner.ts
|
|
34975
35213
|
function isGogFileArg(arg) {
|
|
34976
|
-
return typeof arg !== "string";
|
|
35214
|
+
return typeof arg !== "string" && arg.kind === "file";
|
|
34977
35215
|
}
|
|
34978
35216
|
var TIMEOUT_MS = 3e4;
|
|
34979
35217
|
function readonlyEnvEnabled() {
|
|
@@ -35057,8 +35295,8 @@ function formatTimeout(ms) {
|
|
|
35057
35295
|
}
|
|
35058
35296
|
async function spawnWithTempFiles(args, opts) {
|
|
35059
35297
|
const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
|
|
35060
|
-
const { tmpdir } = await import("node:os");
|
|
35061
|
-
const dir = await mkdtemp(
|
|
35298
|
+
const { tmpdir: tmpdir2 } = await import("node:os");
|
|
35299
|
+
const dir = await mkdtemp(join2(tmpdir2(), "gogcli-mcp-"));
|
|
35062
35300
|
try {
|
|
35063
35301
|
const argv = [];
|
|
35064
35302
|
let seq = 0;
|
|
@@ -35067,10 +35305,10 @@ async function spawnWithTempFiles(args, opts) {
|
|
|
35067
35305
|
argv.push(arg);
|
|
35068
35306
|
continue;
|
|
35069
35307
|
}
|
|
35070
|
-
const sub =
|
|
35308
|
+
const sub = join2(dir, String(seq));
|
|
35071
35309
|
seq += 1;
|
|
35072
35310
|
await mkdir(sub, { recursive: true, mode: 448 });
|
|
35073
|
-
const path =
|
|
35311
|
+
const path = join2(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
|
|
35074
35312
|
const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
|
|
35075
35313
|
await writeFile(path, data, { mode: 384 });
|
|
35076
35314
|
argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
|
|
@@ -35088,10 +35326,10 @@ function spawnExecutor(args, opts) {
|
|
|
35088
35326
|
return spawnGog(args, opts);
|
|
35089
35327
|
}
|
|
35090
35328
|
async function spawnGog(fullArgs, opts) {
|
|
35091
|
-
const { timeout, interactive = false, spawner, binary = false } = opts;
|
|
35329
|
+
const { timeout, interactive = false, spawner, binary = false, maxOutputBytes } = opts;
|
|
35092
35330
|
const spawn = spawner ?? (await import("node:child_process")).spawn;
|
|
35093
35331
|
const effectiveTimeout = timeout ?? TIMEOUT_MS;
|
|
35094
|
-
return new Promise((
|
|
35332
|
+
return new Promise((resolve4, reject) => {
|
|
35095
35333
|
const childEnv = { ...sanitizedEnv(), GOG_TIMEZONE: naiveSourceTimeZone(), PATH: augmentedPath() };
|
|
35096
35334
|
const child = spawn(readEnvVar("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
|
|
35097
35335
|
const stdoutChunks = [];
|
|
@@ -35109,7 +35347,17 @@ async function spawnGog(fullArgs, opts) {
|
|
|
35109
35347
|
child.kill();
|
|
35110
35348
|
reject(new Error(`gog timed out after ${formatTimeout(effectiveTimeout)}`));
|
|
35111
35349
|
}, effectiveTimeout);
|
|
35350
|
+
let stdoutBytes = 0;
|
|
35112
35351
|
child.stdout.on("data", (chunk) => {
|
|
35352
|
+
if (settled) return;
|
|
35353
|
+
stdoutBytes += chunk.length;
|
|
35354
|
+
if (maxOutputBytes !== void 0 && stdoutBytes > maxOutputBytes) {
|
|
35355
|
+
settled = true;
|
|
35356
|
+
stopWatching();
|
|
35357
|
+
child.kill();
|
|
35358
|
+
reject(new Error(`gog output exceeded the limit: more than ${maxOutputBytes} bytes`));
|
|
35359
|
+
return;
|
|
35360
|
+
}
|
|
35113
35361
|
stdoutChunks.push(chunk);
|
|
35114
35362
|
});
|
|
35115
35363
|
child.stderr.on("data", (chunk) => {
|
|
@@ -35126,14 +35374,14 @@ async function spawnGog(fullArgs, opts) {
|
|
|
35126
35374
|
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
35127
35375
|
if (code === 0) {
|
|
35128
35376
|
if (binary) {
|
|
35129
|
-
|
|
35377
|
+
resolve4(Buffer.concat(stdoutChunks).toString("base64"));
|
|
35130
35378
|
return;
|
|
35131
35379
|
}
|
|
35132
35380
|
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
35133
35381
|
if (interactive && stderr) {
|
|
35134
|
-
|
|
35382
|
+
resolve4(stdout + "\n" + stderr);
|
|
35135
35383
|
} else {
|
|
35136
|
-
|
|
35384
|
+
resolve4(stdout);
|
|
35137
35385
|
}
|
|
35138
35386
|
} else {
|
|
35139
35387
|
reject(new Error(stderr || `gog exited with code ${code}`));
|
|
@@ -35153,7 +35401,33 @@ async function spawnGog(fullArgs, opts) {
|
|
|
35153
35401
|
});
|
|
35154
35402
|
});
|
|
35155
35403
|
}
|
|
35404
|
+
function placePositionals(args) {
|
|
35405
|
+
const flags = [];
|
|
35406
|
+
const positionals = [];
|
|
35407
|
+
let afterSeparator = false;
|
|
35408
|
+
for (const arg of args) {
|
|
35409
|
+
if (arg === "--") {
|
|
35410
|
+
afterSeparator = true;
|
|
35411
|
+
} else if (isGogPositional(arg)) {
|
|
35412
|
+
positionals.push(arg.value);
|
|
35413
|
+
} else if (afterSeparator || isGogFileArg(arg) && arg.positional) {
|
|
35414
|
+
positionals.push(arg);
|
|
35415
|
+
} else {
|
|
35416
|
+
flags.push(arg);
|
|
35417
|
+
}
|
|
35418
|
+
}
|
|
35419
|
+
return { flags, positionals };
|
|
35420
|
+
}
|
|
35421
|
+
function assertNoSafetyOverrides(flags) {
|
|
35422
|
+
for (const arg of flags) {
|
|
35423
|
+
if (typeof arg !== "string") continue;
|
|
35424
|
+
const reason = forbiddenArgReason(arg);
|
|
35425
|
+
if (reason) throw new Error(reason);
|
|
35426
|
+
}
|
|
35427
|
+
}
|
|
35156
35428
|
function assembleArgs(args, opts) {
|
|
35429
|
+
const { flags, positionals } = placePositionals(args);
|
|
35430
|
+
assertNoSafetyOverrides(flags);
|
|
35157
35431
|
const effectiveAccount = opts.account ?? readEnvVar("GOG_ACCOUNT");
|
|
35158
35432
|
const fullArgs = ["--json", "--color=never"];
|
|
35159
35433
|
if (!opts.interactive) {
|
|
@@ -35162,17 +35436,21 @@ function assembleArgs(args, opts) {
|
|
|
35162
35436
|
if (opts.readonly || readonlyEnvEnabled()) {
|
|
35163
35437
|
fullArgs.push("--readonly");
|
|
35164
35438
|
}
|
|
35439
|
+
if (opts.gmailNoSend) {
|
|
35440
|
+
fullArgs.push("--gmail-no-send");
|
|
35441
|
+
}
|
|
35165
35442
|
if (effectiveAccount) {
|
|
35166
35443
|
fullArgs.push("--account", effectiveAccount);
|
|
35167
35444
|
}
|
|
35168
|
-
fullArgs.push(...
|
|
35445
|
+
fullArgs.push(...flags);
|
|
35446
|
+
if (positionals.length > 0) fullArgs.push("--", ...positionals);
|
|
35169
35447
|
return fullArgs;
|
|
35170
35448
|
}
|
|
35171
35449
|
async function run(args, options = {}) {
|
|
35172
|
-
const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
|
|
35450
|
+
const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields, gmailNoSend = false } = options;
|
|
35173
35451
|
const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
|
|
35174
35452
|
const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
|
|
35175
|
-
const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
|
|
35453
|
+
const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2, gmailNoSend });
|
|
35176
35454
|
try {
|
|
35177
35455
|
return redact(await spawnExecutor(fullArgs, { timeout, interactive, spawner }));
|
|
35178
35456
|
} catch (err) {
|
|
@@ -35180,9 +35458,9 @@ async function run(args, options = {}) {
|
|
|
35180
35458
|
}
|
|
35181
35459
|
}
|
|
35182
35460
|
async function runBinary(args, options = {}) {
|
|
35183
|
-
const { account, spawner, timeout, readonly: readonly2 = false } = options;
|
|
35184
|
-
const fullArgs = assembleArgs(args, { account, interactive: false, readonly: readonly2 });
|
|
35185
|
-
return spawnExecutor(fullArgs, { timeout, interactive: false, spawner, binary: true });
|
|
35461
|
+
const { account, spawner, timeout, readonly: readonly2 = false, gmailNoSend = false, maxOutputBytes } = options;
|
|
35462
|
+
const fullArgs = assembleArgs(args, { account, interactive: false, readonly: readonly2, gmailNoSend });
|
|
35463
|
+
return spawnExecutor(fullArgs, { timeout, interactive: false, spawner, binary: true, maxOutputBytes });
|
|
35186
35464
|
}
|
|
35187
35465
|
|
|
35188
35466
|
// src/pagination.ts
|
|
@@ -35234,6 +35512,114 @@ function annotateTruncatedList(result, itemsKey) {
|
|
|
35234
35512
|
return rawTextResult(JSON.stringify(out));
|
|
35235
35513
|
}
|
|
35236
35514
|
|
|
35515
|
+
// src/file-roots.ts
|
|
35516
|
+
import { homedir as homedir2 } from "node:os";
|
|
35517
|
+
import { delimiter as delimiter2, join as join4 } from "node:path";
|
|
35518
|
+
|
|
35519
|
+
// src/attachment-root.ts
|
|
35520
|
+
import { tmpdir, userInfo } from "node:os";
|
|
35521
|
+
import { dirname as dirname2, join as join3, relative as relative2, resolve as resolve3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
|
|
35522
|
+
var ATTACHMENT_DOWNLOAD_ROOT = join3(tmpdir(), `gogcli-mcp-attachments-${userInfo().uid}`);
|
|
35523
|
+
var ATTACHMENT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
35524
|
+
|
|
35525
|
+
// src/file-roots.ts
|
|
35526
|
+
var FILE_ROOTS_ENV = "GOG_FILE_ROOTS";
|
|
35527
|
+
function defaultFileRoot() {
|
|
35528
|
+
return join4(homedir2(), "gogcli-mcp-files");
|
|
35529
|
+
}
|
|
35530
|
+
function fileRoots() {
|
|
35531
|
+
const raw = readEnvVar(FILE_ROOTS_ENV);
|
|
35532
|
+
const roots = (raw ?? "").split(delimiter2).map((r) => r.trim()).filter(Boolean);
|
|
35533
|
+
return roots.length > 0 ? roots : [defaultFileRoot()];
|
|
35534
|
+
}
|
|
35535
|
+
function confinePath(path, param, opts = {}) {
|
|
35536
|
+
if (opts.allowDash && path === "-") return path;
|
|
35537
|
+
const roots = [...fileRoots(), ATTACHMENT_DOWNLOAD_ROOT];
|
|
35538
|
+
try {
|
|
35539
|
+
assertPathWithinRoots(path, roots);
|
|
35540
|
+
} catch {
|
|
35541
|
+
throw new Error(
|
|
35542
|
+
`${param} ${JSON.stringify(path)} is outside the directories this server may read or write (${roots.join(", ")}). Put the file in one of them, or ask the server operator to widen ${FILE_ROOTS_ENV}.`
|
|
35543
|
+
);
|
|
35544
|
+
}
|
|
35545
|
+
return path;
|
|
35546
|
+
}
|
|
35547
|
+
function confinePaths(paths, param) {
|
|
35548
|
+
for (const path of paths ?? []) confinePath(path, param);
|
|
35549
|
+
}
|
|
35550
|
+
function confineAtFile(value, param) {
|
|
35551
|
+
if (value.startsWith("@")) confinePath(value.slice(1), param);
|
|
35552
|
+
return value;
|
|
35553
|
+
}
|
|
35554
|
+
|
|
35555
|
+
// src/run-path-guard.ts
|
|
35556
|
+
var POSITIONAL_PATH_SUBCOMMANDS = {
|
|
35557
|
+
appscript: { pull: "gog_appscript_pull" },
|
|
35558
|
+
drive: { upload: "gog_drive_upload", sync: "gog_drive_sync_push" },
|
|
35559
|
+
gmail: { import: "gog_gmail_import" },
|
|
35560
|
+
slides: {
|
|
35561
|
+
"add-slide": "gog_slides_add_slide",
|
|
35562
|
+
"insert-image": "gog_slides_insert_image",
|
|
35563
|
+
"replace-slide": "gog_slides_replace_slide"
|
|
35564
|
+
}
|
|
35565
|
+
};
|
|
35566
|
+
var PATH_FLAGS = /* @__PURE__ */ new Set(["out", "output", "out-dir", "output-dir", "dir", "worker-dir", "attach", "file", "key", "cert", "replacements"]);
|
|
35567
|
+
var ID_FLAGS = {
|
|
35568
|
+
drive: /* @__PURE__ */ new Set(["file", "filter-file"])
|
|
35569
|
+
};
|
|
35570
|
+
var EXEC_FLAGS = /* @__PURE__ */ new Set(["on-change", "on-new", "mmdc"]);
|
|
35571
|
+
var SHORT_PATH_CLUSTER = /^-(?!-)[A-Za-z]*[of]/;
|
|
35572
|
+
var LONG_FLAG = /^--([A-Za-z0-9][A-Za-z0-9-]*)(?:=([\s\S]*))?$/;
|
|
35573
|
+
function isPathFlag(service, name) {
|
|
35574
|
+
if (ID_FLAGS[service]?.has(name)) return false;
|
|
35575
|
+
return PATH_FLAGS.has(name) || name.endsWith("-file");
|
|
35576
|
+
}
|
|
35577
|
+
function confineFlagValue(flag, value) {
|
|
35578
|
+
if (value === "-") return;
|
|
35579
|
+
confinePath(value, flag);
|
|
35580
|
+
for (const part of value.split(",")) {
|
|
35581
|
+
if (part !== "" && part !== "-") confinePath(part, flag);
|
|
35582
|
+
}
|
|
35583
|
+
}
|
|
35584
|
+
function positionalPathTool(service, subcommand) {
|
|
35585
|
+
return POSITIONAL_PATH_SUBCOMMANDS[service]?.[subcommand];
|
|
35586
|
+
}
|
|
35587
|
+
function assertRunPathsConfined(service, subcommand, args) {
|
|
35588
|
+
const dedicated = positionalPathTool(service, subcommand);
|
|
35589
|
+
if (dedicated) {
|
|
35590
|
+
throw new Error(
|
|
35591
|
+
`gog ${service} ${subcommand} reads or writes a local path given as a positional argument, so it is not available through gog_${service}_run. Use ${dedicated}, which confines the path to GOG_FILE_ROOTS.`
|
|
35592
|
+
);
|
|
35593
|
+
}
|
|
35594
|
+
for (let i = 0; i < args.length; i++) {
|
|
35595
|
+
const arg = args[i];
|
|
35596
|
+
if (SHORT_PATH_CLUSTER.test(arg)) {
|
|
35597
|
+
throw new Error(
|
|
35598
|
+
`The flag ${arg} is not allowed here: spell a path flag out as --out or --file (e.g. --out=<path>) so its path can be checked against GOG_FILE_ROOTS.`
|
|
35599
|
+
);
|
|
35600
|
+
}
|
|
35601
|
+
const match = LONG_FLAG.exec(arg);
|
|
35602
|
+
if (!match) continue;
|
|
35603
|
+
const name = match[1].toLowerCase();
|
|
35604
|
+
if (EXEC_FLAGS.has(name)) {
|
|
35605
|
+
throw new Error(`The flag --${name} is not allowed here: it runs a local program chosen by the caller.`);
|
|
35606
|
+
}
|
|
35607
|
+
const isJson = name.endsWith("-json");
|
|
35608
|
+
if (!isJson && !isPathFlag(service, name)) continue;
|
|
35609
|
+
let value = match[2];
|
|
35610
|
+
if (value === void 0) {
|
|
35611
|
+
value = args[i + 1];
|
|
35612
|
+
if (value === void 0) continue;
|
|
35613
|
+
i++;
|
|
35614
|
+
}
|
|
35615
|
+
if (isJson) {
|
|
35616
|
+
if (value.startsWith("@")) confineFlagValue(`--${name}`, value.slice(1));
|
|
35617
|
+
continue;
|
|
35618
|
+
}
|
|
35619
|
+
confineFlagValue(`--${name}`, value);
|
|
35620
|
+
}
|
|
35621
|
+
}
|
|
35622
|
+
|
|
35237
35623
|
// src/tools/utils.ts
|
|
35238
35624
|
var PAYLOAD_INLINE_MAX = 4096;
|
|
35239
35625
|
function payloadArg(inlineFlag, fileFlag, value, ext) {
|
|
@@ -35291,9 +35677,17 @@ function pushPaginationFlags(args, p) {
|
|
|
35291
35677
|
if (p.all) args.push("--all");
|
|
35292
35678
|
}
|
|
35293
35679
|
function registerRunTool(server, options) {
|
|
35294
|
-
const { service, examples, omitAccount = false, note } = options;
|
|
35680
|
+
const { service, examples, omitAccount = false, note, allowedSubcommands, vet, gmailNoSend = false } = options;
|
|
35681
|
+
for (const [, example] of examples.matchAll(/"([^"]+)"/g)) {
|
|
35682
|
+
const refused = allowedSubcommands && !allowedSubcommands.includes(example) || vet?.(example, []) || positionalPathTool(service, example);
|
|
35683
|
+
if (refused) {
|
|
35684
|
+
throw new Error(`gog_${service}_run example "${example}" is refused by the tool itself; list a subcommand it runs.`);
|
|
35685
|
+
}
|
|
35686
|
+
}
|
|
35295
35687
|
const baseDescription = `Run any gog ${service} subcommand not covered by the other tools. Run \`gog ${service} --help\` for the full list of subcommands, or \`gog ${service} <subcommand> --help\` for flags on a specific subcommand.`;
|
|
35296
|
-
const
|
|
35688
|
+
const restriction = allowedSubcommands ? ` Only these subcommands are available: ${allowedSubcommands.join(", ")}.` : "";
|
|
35689
|
+
const safety = ' Flags that override server safety controls (--readonly, --enable-commands, --disable-commands, --account, --access-token, --home, --client, --gmail-no-send, --no-input) and a bare "--" are refused. Local file paths (--out, --out-dir, --attach, --file, --*-file, @file JSON) must be inside GOG_FILE_ROOTS; subcommands that take a local path positionally (e.g. drive upload) are refused in favour of their dedicated tools.';
|
|
35690
|
+
const description = `${baseDescription}${restriction}${safety}${note ? ` ${note}` : ""}`;
|
|
35297
35691
|
const inputSchema = {
|
|
35298
35692
|
subcommand: external_exports.string().describe(`The gog ${service} subcommand to run, e.g. ${examples}`),
|
|
35299
35693
|
args: external_exports.array(external_exports.string()).describe("Additional positional args and flags")
|
|
@@ -35307,7 +35701,21 @@ function registerRunTool(server, options) {
|
|
|
35307
35701
|
inputSchema: external_exports.object(inputSchema)
|
|
35308
35702
|
}, async (rawArgs) => {
|
|
35309
35703
|
const { subcommand, args, account } = rawArgs;
|
|
35310
|
-
|
|
35704
|
+
try {
|
|
35705
|
+
assertSafeSubcommand(subcommand);
|
|
35706
|
+
assertSafeForwardedArgs(args);
|
|
35707
|
+
if (allowedSubcommands && !allowedSubcommands.includes(subcommand)) {
|
|
35708
|
+
throw new Error(
|
|
35709
|
+
`gog ${service} ${subcommand} is not available through gog_${service}_run. Allowed: ${allowedSubcommands.join(", ")}.`
|
|
35710
|
+
);
|
|
35711
|
+
}
|
|
35712
|
+
const refusal = vet?.(subcommand, args);
|
|
35713
|
+
if (refusal) throw new Error(refusal);
|
|
35714
|
+
assertRunPathsConfined(service, subcommand, args);
|
|
35715
|
+
} catch (err) {
|
|
35716
|
+
return errorResult(errorText(err));
|
|
35717
|
+
}
|
|
35718
|
+
return runOrDiagnose([service, subcommand, ...args], gmailNoSend ? { account, gmailNoSend } : { account });
|
|
35311
35719
|
});
|
|
35312
35720
|
}
|
|
35313
35721
|
function errorText(err) {
|
|
@@ -35435,7 +35843,240 @@ function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
|
35435
35843
|
}
|
|
35436
35844
|
}
|
|
35437
35845
|
|
|
35846
|
+
// src/dispatch-confirmation.ts
|
|
35847
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
35848
|
+
import { readFileSync } from "node:fs";
|
|
35849
|
+
|
|
35850
|
+
// src/send-confirm-token.ts
|
|
35851
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
35852
|
+
var CONFIRM_TOKEN_TTL_DEFAULT_SECONDS = 600;
|
|
35853
|
+
var PREFIX = "gct1";
|
|
35854
|
+
var secret;
|
|
35855
|
+
var spent = /* @__PURE__ */ new Map();
|
|
35856
|
+
function sendConfirmFallbackEnabled() {
|
|
35857
|
+
return readEnvVar("GOG_SEND_CONFIRM_FALLBACK")?.trim().toLowerCase() === "token";
|
|
35858
|
+
}
|
|
35859
|
+
function confirmTokenTtlSeconds() {
|
|
35860
|
+
const raw = readEnvVar("GOG_CONFIRM_TTL_SECONDS")?.trim();
|
|
35861
|
+
if (raw && /^\d+$/.test(raw)) {
|
|
35862
|
+
const n = Number(raw);
|
|
35863
|
+
if (n > 0) return n;
|
|
35864
|
+
}
|
|
35865
|
+
return CONFIRM_TOKEN_TTL_DEFAULT_SECONDS;
|
|
35866
|
+
}
|
|
35867
|
+
function hmacKey() {
|
|
35868
|
+
if (!secret) {
|
|
35869
|
+
const configured = readEnvVar("GOG_CONFIRM_SECRET");
|
|
35870
|
+
secret = configured ? Buffer.from(configured, "utf8") : randomBytes(32);
|
|
35871
|
+
}
|
|
35872
|
+
return secret;
|
|
35873
|
+
}
|
|
35874
|
+
function canonicalize(value) {
|
|
35875
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
35876
|
+
if (value && typeof value === "object") {
|
|
35877
|
+
const out = {};
|
|
35878
|
+
for (const key of Object.keys(value).sort()) {
|
|
35879
|
+
const v = value[key];
|
|
35880
|
+
if (v !== void 0) out[key] = canonicalize(v);
|
|
35881
|
+
}
|
|
35882
|
+
return out;
|
|
35883
|
+
}
|
|
35884
|
+
return value;
|
|
35885
|
+
}
|
|
35886
|
+
function hashSendPayload(payload) {
|
|
35887
|
+
return createHash2("sha256").update(JSON.stringify(canonicalize(payload))).digest("hex");
|
|
35888
|
+
}
|
|
35889
|
+
function sign(body) {
|
|
35890
|
+
return createHmac2("sha256", hmacKey()).update(`${PREFIX}.${body}`).digest("base64url");
|
|
35891
|
+
}
|
|
35892
|
+
function issueConfirmToken(binding, now = Date.now()) {
|
|
35893
|
+
const exp = now + confirmTokenTtlSeconds() * 1e3;
|
|
35894
|
+
const claims = {
|
|
35895
|
+
t: binding.tool,
|
|
35896
|
+
a: binding.account,
|
|
35897
|
+
g: binding.target,
|
|
35898
|
+
...binding.revision === void 0 ? {} : { r: binding.revision },
|
|
35899
|
+
h: binding.payloadHash,
|
|
35900
|
+
iat: now,
|
|
35901
|
+
exp,
|
|
35902
|
+
n: randomBytes(16).toString("base64url")
|
|
35903
|
+
};
|
|
35904
|
+
const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
|
|
35905
|
+
return { token: `${PREFIX}.${body}.${sign(body)}`, expiresAt: new Date(exp).toISOString() };
|
|
35906
|
+
}
|
|
35907
|
+
function signatureMatches(body, sig) {
|
|
35908
|
+
const expected = Buffer.from(sign(body), "utf8");
|
|
35909
|
+
const actual = Buffer.from(sig, "utf8");
|
|
35910
|
+
return expected.length === actual.length && timingSafeEqual2(expected, actual);
|
|
35911
|
+
}
|
|
35912
|
+
function parseClaims(body) {
|
|
35913
|
+
try {
|
|
35914
|
+
return JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
35915
|
+
} catch {
|
|
35916
|
+
return void 0;
|
|
35917
|
+
}
|
|
35918
|
+
}
|
|
35919
|
+
function verifyConfirmToken(token, binding, now = Date.now()) {
|
|
35920
|
+
for (const [nonce, exp] of spent) if (exp < now) spent.delete(nonce);
|
|
35921
|
+
const parts = token.split(".");
|
|
35922
|
+
if (parts.length !== 3 || parts[0] !== PREFIX || !parts[1] || !parts[2]) return { ok: false, error: "TOKEN_INVALID" };
|
|
35923
|
+
const [, body, sig] = parts;
|
|
35924
|
+
if (!signatureMatches(body, sig)) return { ok: false, error: "TOKEN_INVALID" };
|
|
35925
|
+
const claims = parseClaims(body);
|
|
35926
|
+
if (!claims) return { ok: false, error: "TOKEN_INVALID" };
|
|
35927
|
+
if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
|
|
35928
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
35929
|
+
}
|
|
35930
|
+
if (spent.has(claims.n)) return { ok: false, error: "TOKEN_REUSED" };
|
|
35931
|
+
if (now > claims.exp) return { ok: false, error: "TOKEN_EXPIRED" };
|
|
35932
|
+
if (claims.r !== binding.revision) return { ok: false, error: "DRAFT_CHANGED", reason: "message-id-rotated" };
|
|
35933
|
+
if (claims.h !== binding.payloadHash) return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
|
|
35934
|
+
spent.set(claims.n, claims.exp);
|
|
35935
|
+
return { ok: true };
|
|
35936
|
+
}
|
|
35937
|
+
|
|
35938
|
+
// src/dispatch-confirmation.ts
|
|
35939
|
+
var BODY_PREVIEW_MAX = 2048;
|
|
35940
|
+
function bodyPreview(text) {
|
|
35941
|
+
if (!text) return void 0;
|
|
35942
|
+
if (text.length <= BODY_PREVIEW_MAX) return text;
|
|
35943
|
+
return `${text.slice(0, BODY_PREVIEW_MAX)}\u2026 [${text.length - BODY_PREVIEW_MAX} more characters not shown]`;
|
|
35944
|
+
}
|
|
35945
|
+
function attachmentNames(paths, inline) {
|
|
35946
|
+
return [...paths ?? [], ...(inline ?? []).map((a) => a.filename)];
|
|
35947
|
+
}
|
|
35948
|
+
function attachmentDetails(paths, inline) {
|
|
35949
|
+
const out = [];
|
|
35950
|
+
for (const path of paths ?? []) {
|
|
35951
|
+
let bytes;
|
|
35952
|
+
try {
|
|
35953
|
+
bytes = readFileSync(path);
|
|
35954
|
+
} catch {
|
|
35955
|
+
bytes = void 0;
|
|
35956
|
+
}
|
|
35957
|
+
out.push(bytes ? { name: path, size: bytes.length, sha256: createHash3("sha256").update(bytes).digest("hex") } : { name: path, size: null });
|
|
35958
|
+
}
|
|
35959
|
+
for (const a of inline ?? []) {
|
|
35960
|
+
out.push({
|
|
35961
|
+
name: a.filename,
|
|
35962
|
+
size: Buffer.from(a.contentBase64, "base64").length,
|
|
35963
|
+
sha256: createHash3("sha256").update(a.contentBase64).digest("hex")
|
|
35964
|
+
});
|
|
35965
|
+
}
|
|
35966
|
+
return out;
|
|
35967
|
+
}
|
|
35968
|
+
function senderPreview(account, from) {
|
|
35969
|
+
return from ?? account ?? readEnvVar("GOG_ACCOUNT") ?? "the gog account's default address";
|
|
35970
|
+
}
|
|
35971
|
+
function attachmentPreview(details) {
|
|
35972
|
+
return details.map(({ name, size }) => ({ name, size }));
|
|
35973
|
+
}
|
|
35974
|
+
var CONFIRM_SEND_INSTRUCTION = "Show this preview to the user verbatim and send only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
35975
|
+
var CONFIRM_ACTION_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
35976
|
+
var FALLBACK_HINT = "Or set GOG_SEND_CONFIRM_FALLBACK=token to enable two-step confirmation.";
|
|
35977
|
+
var CONFIRM_FALLBACK_DESCRIPTION = ' If the client cannot show that prompt (no MCP elicitation, e.g. claude.ai) and the server sets GOG_SEND_CONFIRM_FALLBACK=token, a two-step flow applies instead: call WITHOUT confirmToken and nothing is sent or changed \u2014 the result has status "confirmation-required", the full preview and a confirmToken. Show that preview to the user verbatim; only after they explicitly approve it in chat, call again with the SAME arguments plus confirmToken. The tool re-reads what it would act on and refuses (DRAFT_CHANGED, with a fresh preview and token) if it changed; TOKEN_EXPIRED / TOKEN_REUSED / TOKEN_INVALID also do nothing.';
|
|
35978
|
+
var confirmTokenParam = external_exports.string().optional().describe(
|
|
35979
|
+
`ONLY for the two-step fallback (client without MCP elicitation, server with GOG_SEND_CONFIRM_FALLBACK=token). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`
|
|
35980
|
+
);
|
|
35981
|
+
var TOKEN_ERROR_NOTE = {
|
|
35982
|
+
TOKEN_EXPIRED: "Nothing was sent or changed: the confirmToken expired. Call again WITHOUT confirmToken for a fresh preview, and ask the user to approve it again.",
|
|
35983
|
+
TOKEN_REUSED: "Nothing was sent or changed by this call: this confirmToken was already used, and one approval acts once. If doing it again is really intended, call again WITHOUT confirmToken and get a new approval.",
|
|
35984
|
+
TOKEN_INVALID: "Nothing was sent or changed: this confirmToken was not issued by this server for this tool, account and target (or the server has restarted since). Call again WITHOUT confirmToken for a fresh preview and approval."
|
|
35985
|
+
};
|
|
35986
|
+
var DRAFT_CHANGED_NOTE = {
|
|
35987
|
+
"message-id-rotated": "Nothing was sent or changed: the target was edited since the user approved it (a draft's messageId or an event's version rotated), so what would happen is not what they saw.",
|
|
35988
|
+
"payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
|
|
35989
|
+
};
|
|
35990
|
+
function isToolResult(value) {
|
|
35991
|
+
return Array.isArray(value.content);
|
|
35992
|
+
}
|
|
35993
|
+
function rejection2(data) {
|
|
35994
|
+
return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
|
|
35995
|
+
}
|
|
35996
|
+
async function tokenConfirmation(op, fallback) {
|
|
35997
|
+
const subject = await fallback.subject();
|
|
35998
|
+
if (isToolResult(subject)) return subject;
|
|
35999
|
+
const binding = {
|
|
36000
|
+
tool: fallback.tool,
|
|
36001
|
+
account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
|
|
36002
|
+
target: subject.target,
|
|
36003
|
+
...subject.revision === void 0 ? {} : { revision: subject.revision },
|
|
36004
|
+
payloadHash: hashSendPayload(subject.payload)
|
|
36005
|
+
};
|
|
36006
|
+
const phaseOne = () => {
|
|
36007
|
+
const { token, expiresAt } = issueConfirmToken(binding);
|
|
36008
|
+
return {
|
|
36009
|
+
action: op,
|
|
36010
|
+
preview: subject.preview,
|
|
36011
|
+
confirmToken: token,
|
|
36012
|
+
expiresAt,
|
|
36013
|
+
ttlSeconds: confirmTokenTtlSeconds(),
|
|
36014
|
+
instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION
|
|
36015
|
+
};
|
|
36016
|
+
};
|
|
36017
|
+
if (!fallback.confirmToken) {
|
|
36018
|
+
return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
|
|
36019
|
+
}
|
|
36020
|
+
const verdict = verifyConfirmToken(fallback.confirmToken, binding);
|
|
36021
|
+
if (verdict.ok) return void 0;
|
|
36022
|
+
if (verdict.error === "DRAFT_CHANGED") {
|
|
36023
|
+
return rejection2({
|
|
36024
|
+
error: "DRAFT_CHANGED",
|
|
36025
|
+
reason: verdict.reason,
|
|
36026
|
+
note: `${DRAFT_CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
|
|
36027
|
+
...phaseOne()
|
|
36028
|
+
});
|
|
36029
|
+
}
|
|
36030
|
+
return rejection2({ error: verdict.error, action: op, note: TOKEN_ERROR_NOTE[verdict.error] });
|
|
36031
|
+
}
|
|
36032
|
+
function gatedElsewhere(what, via, does, tool) {
|
|
36033
|
+
return `${what} ${does} and is not available through ${via}. Use ${tool}, which asks the user to confirm.`;
|
|
36034
|
+
}
|
|
36035
|
+
function hasCommandWord(args, words) {
|
|
36036
|
+
return args.find((a) => words.has(a.toLowerCase()));
|
|
36037
|
+
}
|
|
36038
|
+
async function requireDispatchConfirmation(ctx, options) {
|
|
36039
|
+
const { action, fallback } = options;
|
|
36040
|
+
if (fallback && callerAcceptsFormElicitation(ctx) === false && sendConfirmFallbackEnabled()) {
|
|
36041
|
+
return tokenConfirmation(action, fallback);
|
|
36042
|
+
}
|
|
36043
|
+
const note = fallback ? [options.unsupportedNote, FALLBACK_HINT].filter(Boolean).join(" ") : options.unsupportedNote;
|
|
36044
|
+
return requireConfirmation(ctx, {
|
|
36045
|
+
action,
|
|
36046
|
+
message: options.message,
|
|
36047
|
+
details: options.details,
|
|
36048
|
+
confirmationLabel: options.confirmationLabel,
|
|
36049
|
+
...note ? { unsupportedNote: note } : {}
|
|
36050
|
+
});
|
|
36051
|
+
}
|
|
36052
|
+
function resultText(result) {
|
|
36053
|
+
const first = result.content[0];
|
|
36054
|
+
return first && first.type === "text" && typeof first.text === "string" ? first.text : "{}";
|
|
36055
|
+
}
|
|
36056
|
+
|
|
35438
36057
|
// src/tools/api.ts
|
|
36058
|
+
var GMAIL_API_BLOCKED = /(?:\.send$|forwarding|filters\.create|filters\.update|delegates\.create)/i;
|
|
36059
|
+
var DISPATCH_API_BLOCKED = {
|
|
36060
|
+
chat: [{ method: /(?:^|\.)spaces\.messages\.create$/i, does: "posts a Chat message", tool: "gog_chat_messages_send / gog_chat_dm_send" }],
|
|
36061
|
+
drive: [{ method: /(?:^|\.)permissions\.(?:create|update)$/i, does: "grants access to a file", tool: "gog_drive_share" }],
|
|
36062
|
+
classroom: [
|
|
36063
|
+
{ 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" }
|
|
36065
|
+
],
|
|
36066
|
+
calendar: [
|
|
36067
|
+
{ 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
|
+
]
|
|
36070
|
+
};
|
|
36071
|
+
function refusedApiCall(api, method) {
|
|
36072
|
+
const name = api.trim().toLowerCase();
|
|
36073
|
+
const m = method.trim();
|
|
36074
|
+
if (name === "gmail" && GMAIL_API_BLOCKED.test(m)) {
|
|
36075
|
+
return `gmail ${method} is not available through gog_api_call: it sends or forwards mail. Use gog_gmail_send / gog_gmail_drafts_send (which ask the user to confirm) or the dedicated gog_gmail_* tool.`;
|
|
36076
|
+
}
|
|
36077
|
+
const hit = (Object.hasOwn(DISPATCH_API_BLOCKED, name) ? DISPATCH_API_BLOCKED[name] : []).find((b) => b.method.test(m));
|
|
36078
|
+
return hit ? gatedElsewhere(`${name} ${m}`, "gog_api_call", hit.does, hit.tool) : void 0;
|
|
36079
|
+
}
|
|
35439
36080
|
function registerApiTools(server) {
|
|
35440
36081
|
server.registerTool("gog_api_list", {
|
|
35441
36082
|
description: "List the Google Discovery APIs available for gog_api_call / gog_api_describe (name + version + title).",
|
|
@@ -35459,12 +36100,12 @@ function registerApiTools(server) {
|
|
|
35459
36100
|
account: accountParam
|
|
35460
36101
|
})
|
|
35461
36102
|
}, async ({ api, version: version2, method, account }) => {
|
|
35462
|
-
const args = ["api", "describe", api, version2];
|
|
35463
|
-
if (method) args.push(method);
|
|
36103
|
+
const args = ["api", "describe", pos(api), pos(version2)];
|
|
36104
|
+
if (method) args.push(pos(method));
|
|
35464
36105
|
return runOrDiagnose(args, { account });
|
|
35465
36106
|
});
|
|
35466
36107
|
server.registerTool("gog_api_call", {
|
|
35467
|
-
description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true \u2014 keep it false to preview, or set dryRun=true to print the intended request without sending it.",
|
|
36108
|
+
description: "Call any Discovery-described Google API method directly \u2014 an escape hatch for endpoints gog has no dedicated tool for. Find the exact api/version/method/params with gog_api_describe first. Read methods (GET/LIST) run as-is. Mutating methods (POST/PUT/PATCH/DELETE) are refused unless you set allowWrite=true \u2014 keep it false to preview, or set dryRun=true to print the intended request without sending it. Gmail send and forwarding methods (users.messages.send, users.drafts.send, forwarding/auto-forwarding, filters, delegates) are refused \u2014 use the dedicated gog_gmail_* tools, which ask the user to confirm. So are the other methods a dedicated tool asks about: Chat spaces.messages.create, Drive permissions.create/update, Classroom courses.announcements.create and invitations.create, and Calendar events.insert/import/quickAdd/update/patch.",
|
|
35468
36109
|
annotations: { destructiveHint: true },
|
|
35469
36110
|
inputSchema: external_exports.object({
|
|
35470
36111
|
api: external_exports.string().describe("Discovery API name (e.g. drive, gmail, calendar)"),
|
|
@@ -35478,14 +36119,23 @@ function registerApiTools(server) {
|
|
|
35478
36119
|
account: accountParam
|
|
35479
36120
|
})
|
|
35480
36121
|
}, async ({ api, version: version2, method, params, body, scope, allowWrite, dryRun, account }) => {
|
|
35481
|
-
|
|
36122
|
+
try {
|
|
36123
|
+
assertSafeForwardedArgs([api, version2, method]);
|
|
36124
|
+
const refusal = refusedApiCall(api, method);
|
|
36125
|
+
if (refusal) throw new Error(refusal);
|
|
36126
|
+
if (body) confineAtFile(body, "body");
|
|
36127
|
+
if (params) confineAtFile(params, "params");
|
|
36128
|
+
} catch (err) {
|
|
36129
|
+
return errorResult(errorText(err));
|
|
36130
|
+
}
|
|
36131
|
+
const args = ["api", "call", pos(api), pos(version2), pos(method)];
|
|
35482
36132
|
if (params) args.push(`--params=${params}`);
|
|
35483
36133
|
if (body) args.push(`--body=${body}`);
|
|
35484
36134
|
if (scope) args.push(`--scope=${scope}`);
|
|
35485
36135
|
if (allowWrite) args.push("--allow-write");
|
|
35486
36136
|
if (dryRun) args.push("--dry-run");
|
|
35487
36137
|
if (allowWrite) args.push("--force");
|
|
35488
|
-
return runOrDiagnose(args, { account });
|
|
36138
|
+
return runOrDiagnose(args, { account, gmailNoSend: true });
|
|
35489
36139
|
});
|
|
35490
36140
|
}
|
|
35491
36141
|
|
|
@@ -35503,7 +36153,7 @@ function registerAppScriptTools(server) {
|
|
|
35503
36153
|
account: accountParam
|
|
35504
36154
|
})
|
|
35505
36155
|
}, async ({ scriptId, account }) => {
|
|
35506
|
-
return runOrDiagnose(["appscript", "get", scriptId], { account });
|
|
36156
|
+
return runOrDiagnose(["appscript", "get", pos(scriptId)], { account });
|
|
35507
36157
|
});
|
|
35508
36158
|
server.registerTool("gog_appscript_content", {
|
|
35509
36159
|
description: `Read a project's source \u2014 every .gs file and its appsscript.json manifest \u2014 INLINE in the response. This is the tool to reach for when the question is "what does this script do"; it needs no filesystem, so it works the same on a hosted deployment as it does locally, unlike gog_appscript_pull.` + apiEnableNote,
|
|
@@ -35513,19 +36163,20 @@ function registerAppScriptTools(server) {
|
|
|
35513
36163
|
account: accountParam
|
|
35514
36164
|
})
|
|
35515
36165
|
}, async ({ scriptId, account }) => {
|
|
35516
|
-
return runOrDiagnose(["appscript", "content", scriptId], { account });
|
|
36166
|
+
return runOrDiagnose(["appscript", "content", pos(scriptId)], { account });
|
|
35517
36167
|
});
|
|
35518
36168
|
server.registerTool("gog_appscript_pull", {
|
|
35519
36169
|
description: "Write a project's files into a local directory, for editing a script as ordinary files. THE DIRECTORY IS RESOLVED WHERE GOG RUNS, which is the caller's own machine only on a local (stdio) deployment: on a hosted deployment (e.g. mcp-host) the files land on that server where the caller cannot reach them. Use gog_appscript_content there instead \u2014 it returns the same source in the response. Existing files are left alone unless overwrite is set. Read-only as far as Google is concerned: nothing is pushed back." + apiEnableNote,
|
|
35520
36170
|
annotations: { destructiveHint: true },
|
|
35521
36171
|
inputSchema: external_exports.object({
|
|
35522
36172
|
scriptId: scriptIdParam,
|
|
35523
|
-
dir: external_exports.string().describe("Destination directory, resolved on the machine where gog runs"),
|
|
36173
|
+
dir: external_exports.string().describe("Destination directory, resolved on the machine where gog runs; must be inside the server's GOG_FILE_ROOTS directories"),
|
|
35524
36174
|
overwrite: external_exports.boolean().optional().describe("Overwrite files that already exist in dir"),
|
|
35525
36175
|
account: accountParam
|
|
35526
36176
|
})
|
|
35527
36177
|
}, async ({ scriptId, dir, overwrite, account }) => {
|
|
35528
|
-
|
|
36178
|
+
confinePath(dir, "dir");
|
|
36179
|
+
const args = ["appscript", "pull", pos(scriptId), pos(dir)];
|
|
35529
36180
|
if (overwrite) args.push("--overwrite");
|
|
35530
36181
|
return runOrDiagnose(args, { account });
|
|
35531
36182
|
});
|
|
@@ -35551,7 +36202,7 @@ function registerAppScriptTools(server) {
|
|
|
35551
36202
|
account: accountParam
|
|
35552
36203
|
})
|
|
35553
36204
|
}, async ({ scriptId, max, pageToken, page, all, account }) => {
|
|
35554
|
-
const args = ["appscript", "deployments", scriptId];
|
|
36205
|
+
const args = ["appscript", "deployments", pos(scriptId)];
|
|
35555
36206
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
35556
36207
|
return runOrDiagnose(args, { account });
|
|
35557
36208
|
});
|
|
@@ -35564,7 +36215,7 @@ function registerAppScriptTools(server) {
|
|
|
35564
36215
|
account: accountParam
|
|
35565
36216
|
})
|
|
35566
36217
|
}, async ({ scriptId, max, pageToken, page, all, account }) => {
|
|
35567
|
-
const args = ["appscript", "versions", scriptId];
|
|
36218
|
+
const args = ["appscript", "versions", pos(scriptId)];
|
|
35568
36219
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
35569
36220
|
return runOrDiagnose(args, { account });
|
|
35570
36221
|
});
|
|
@@ -35590,7 +36241,7 @@ function registerAppScriptTools(server) {
|
|
|
35590
36241
|
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}`);
|
|
35591
36242
|
}
|
|
35592
36243
|
}
|
|
35593
|
-
const args = ["appscript", "run", scriptId, functionName];
|
|
36244
|
+
const args = ["appscript", "run", pos(scriptId), pos(functionName)];
|
|
35594
36245
|
if (params !== void 0) args.push(`--params=${params}`);
|
|
35595
36246
|
if (devMode) args.push("--dev-mode");
|
|
35596
36247
|
return runOrDiagnose(args, { account });
|
|
@@ -35603,6 +36254,7 @@ function registerAppScriptTools(server) {
|
|
|
35603
36254
|
}
|
|
35604
36255
|
|
|
35605
36256
|
// src/tools/auth.ts
|
|
36257
|
+
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
|
|
35606
36258
|
function registerAuthToolsWith(server, defaultServices) {
|
|
35607
36259
|
const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
|
|
35608
36260
|
const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
|
|
@@ -35660,7 +36312,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
35660
36312
|
})
|
|
35661
36313
|
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
35662
36314
|
try {
|
|
35663
|
-
const args = ["auth", "add", email3, "--services", services];
|
|
36315
|
+
const args = ["auth", "add", pos(email3), "--services", services];
|
|
35664
36316
|
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
|
|
35665
36317
|
return rawTextResult(await run(args, {
|
|
35666
36318
|
interactive: true,
|
|
@@ -35680,7 +36332,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
35680
36332
|
})
|
|
35681
36333
|
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
35682
36334
|
try {
|
|
35683
|
-
const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
36335
|
+
const args = ["auth", "add", pos(email3), "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
35684
36336
|
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
35685
36337
|
return rawTextResult(await run(args, { redactMode: "tokens" }));
|
|
35686
36338
|
} catch (err) {
|
|
@@ -35707,7 +36359,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
35707
36359
|
const args = [
|
|
35708
36360
|
"auth",
|
|
35709
36361
|
"add",
|
|
35710
|
-
email3,
|
|
36362
|
+
pos(email3),
|
|
35711
36363
|
"--remote",
|
|
35712
36364
|
"--step",
|
|
35713
36365
|
"2",
|
|
@@ -35727,6 +36379,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
35727
36379
|
service: "auth",
|
|
35728
36380
|
examples: '"remove", "alias", "list"',
|
|
35729
36381
|
omitAccount: true,
|
|
36382
|
+
allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
|
|
35730
36383
|
note: "For browser-based authorization, use gog_auth_add instead."
|
|
35731
36384
|
});
|
|
35732
36385
|
}
|
|
@@ -35735,6 +36388,54 @@ function registerAuthTools(server) {
|
|
|
35735
36388
|
}
|
|
35736
36389
|
|
|
35737
36390
|
// src/tools/calendar.ts
|
|
36391
|
+
var CALENDAR_GATED = {
|
|
36392
|
+
create: "gog_calendar_create",
|
|
36393
|
+
add: "gog_calendar_create",
|
|
36394
|
+
new: "gog_calendar_create",
|
|
36395
|
+
update: "gog_calendar_update",
|
|
36396
|
+
edit: "gog_calendar_update",
|
|
36397
|
+
set: "gog_calendar_update",
|
|
36398
|
+
respond: "gog_calendar_respond",
|
|
36399
|
+
rsvp: "gog_calendar_respond",
|
|
36400
|
+
reply: "gog_calendar_respond"
|
|
36401
|
+
};
|
|
36402
|
+
function vetCalendarRun(subcommand, _args) {
|
|
36403
|
+
const sub = subcommand.toLowerCase();
|
|
36404
|
+
const tool = Object.hasOwn(CALENDAR_GATED, sub) ? CALENDAR_GATED[sub] : void 0;
|
|
36405
|
+
return tool ? gatedElsewhere(`gog calendar ${sub}`, "gog_calendar_run", "can change what guests see", tool) : void 0;
|
|
36406
|
+
}
|
|
36407
|
+
function parseAttendees(value) {
|
|
36408
|
+
if (!value) return [];
|
|
36409
|
+
return value.split(",").map((entry) => entry.split(";")).filter((parts) => !parts.slice(1).some((m) => m.trim().toLowerCase() === "resource")).map((parts) => parts[0].trim()).filter(Boolean);
|
|
36410
|
+
}
|
|
36411
|
+
function timeOf(t) {
|
|
36412
|
+
const v = t?.dateTime ?? t?.date;
|
|
36413
|
+
return typeof v === "string" ? v : void 0;
|
|
36414
|
+
}
|
|
36415
|
+
function eventSnapshot(raw) {
|
|
36416
|
+
let event;
|
|
36417
|
+
try {
|
|
36418
|
+
event = JSON.parse(raw)?.event;
|
|
36419
|
+
} catch {
|
|
36420
|
+
event = void 0;
|
|
36421
|
+
}
|
|
36422
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
36423
|
+
return {
|
|
36424
|
+
summary: str(event?.summary),
|
|
36425
|
+
start: timeOf(event?.start),
|
|
36426
|
+
end: timeOf(event?.end),
|
|
36427
|
+
organizer: str(event?.organizer?.email),
|
|
36428
|
+
// Guests are the OTHER people: not this account's own attendee row, not rooms.
|
|
36429
|
+
guests: (Array.isArray(event?.attendees) ? event.attendees : []).filter((a) => a.self !== true && a.resource !== true && typeof a.email === "string").map((a) => a.email),
|
|
36430
|
+
etag: str(event?.etag)
|
|
36431
|
+
};
|
|
36432
|
+
}
|
|
36433
|
+
async function readEvent(calendarId, eventId, account) {
|
|
36434
|
+
const got = await runOrDiagnose(["calendar", "event", pos(calendarId), pos(eventId)], { account });
|
|
36435
|
+
if (got.isError) return { error: got };
|
|
36436
|
+
return { event: eventSnapshot(resultText(got)) };
|
|
36437
|
+
}
|
|
36438
|
+
var CALENDAR_CONFIRM_LABEL = "Confirm that this change, which other people will see, should be made now.";
|
|
35738
36439
|
var reminderParams = {
|
|
35739
36440
|
reminders: external_exports.array(external_exports.string()).max(5).optional().describe(
|
|
35740
36441
|
`Reminders as method:duration, e.g. ["popup:30m", "email:1d"]. Method is popup or email; duration accepts m/h/d (max 40320 minutes = 4 weeks). Google allows at most 5. These REPLACE the event's reminders \u2014 on update, pass an EMPTY array to drop custom reminders and go back to the calendar's defaults. Cannot be combined with noReminders.`
|
|
@@ -35788,7 +36489,7 @@ function registerCalendarTools(server) {
|
|
|
35788
36489
|
})
|
|
35789
36490
|
}, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, view, account }) => {
|
|
35790
36491
|
const args = ["calendar", "events"];
|
|
35791
|
-
if (calendarId) args.push(calendarId);
|
|
36492
|
+
if (calendarId) args.push(pos(calendarId));
|
|
35792
36493
|
if (from) args.push(`--from=${from}`);
|
|
35793
36494
|
if (to) args.push(`--to=${to}`);
|
|
35794
36495
|
if (days !== void 0) args.push(`--days=${days}`);
|
|
@@ -35817,12 +36518,12 @@ function registerCalendarTools(server) {
|
|
|
35817
36518
|
account: accountParam
|
|
35818
36519
|
})
|
|
35819
36520
|
}, async ({ calendarId, eventId, timezone, account }) => {
|
|
35820
|
-
const args = ["calendar", "event", calendarId, eventId];
|
|
36521
|
+
const args = ["calendar", "event", pos(calendarId), pos(eventId)];
|
|
35821
36522
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
35822
36523
|
return runOrDiagnose(args, { account });
|
|
35823
36524
|
});
|
|
35824
36525
|
server.registerTool("gog_calendar_create", {
|
|
35825
|
-
description: "Create a calendar event. Set withZoom=true to attach a Zoom meeting (requires Zoom S2S OAuth setup via gog_zoom_auth_setup; the join URL + meeting ID + passcode are appended to the event description \u2014 Google rejects native conference card writes from non-Workspace-Marketplace OAuth clients).",
|
|
36526
|
+
description: "Create a calendar event. Set withZoom=true to attach a Zoom meeting (requires Zoom S2S OAuth setup via gog_zoom_auth_setup; the join URL + meeting ID + passcode are appended to the event description \u2014 Google rejects native conference card writes from non-Workspace-Marketplace OAuth clients). With attendees, the event lands on other people's calendars (no invitation email is sent), so the MCP host is asked to show the user a confirmation prompt with the guests, time and details first; a guest-free event is created without asking." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35826
36527
|
annotations: { destructiveHint: false },
|
|
35827
36528
|
inputSchema: external_exports.object({
|
|
35828
36529
|
calendarId: external_exports.string().describe('Calendar ID (use "primary" for the default calendar)'),
|
|
@@ -35836,10 +36537,11 @@ function registerCalendarTools(server) {
|
|
|
35836
36537
|
timezone: external_exports.string().optional().describe("IANA timezone metadata applied to from/to (e.g. America/New_York). Sets both start and end timezone unless start/end timezone are overridden."),
|
|
35837
36538
|
withZoom: external_exports.boolean().optional().describe("Create a Zoom video conference for this event (requires Zoom S2S OAuth setup)"),
|
|
35838
36539
|
...reminderParams,
|
|
35839
|
-
account: accountParam
|
|
36540
|
+
account: accountParam,
|
|
36541
|
+
confirmToken: confirmTokenParam
|
|
35840
36542
|
})
|
|
35841
|
-
}, async ({ calendarId, summary, from, to, description, location, attendees, allDay, timezone, withZoom, reminders, noReminders, account }) => {
|
|
35842
|
-
const args = ["calendar", "create", calendarId, `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
|
|
36543
|
+
}, async ({ calendarId, summary, from, to, description, location, attendees, allDay, timezone, withZoom, reminders, noReminders, account, confirmToken }, ctx) => {
|
|
36544
|
+
const args = ["calendar", "create", pos(calendarId), `--summary=${summary}`, `--from=${from}`, `--to=${to}`];
|
|
35843
36545
|
if (description) args.push(`--description=${description}`);
|
|
35844
36546
|
if (location) args.push(`--location=${location}`);
|
|
35845
36547
|
if (attendees) args.push(`--attendees=${attendees}`);
|
|
@@ -35847,10 +36549,28 @@ function registerCalendarTools(server) {
|
|
|
35847
36549
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
35848
36550
|
if (withZoom) args.push("--with-zoom");
|
|
35849
36551
|
pushReminderFlags(args, { reminders, noReminders });
|
|
36552
|
+
const guests = parseAttendees(attendees);
|
|
36553
|
+
if (guests.length > 0) {
|
|
36554
|
+
const event = { calendarId, summary, from, to, allDay: Boolean(allDay), timezone, location, description, guests, attendees, withZoom: Boolean(withZoom) };
|
|
36555
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36556
|
+
action: "calendar.create",
|
|
36557
|
+
message: "Review and confirm this event, which will appear on the guests' calendars:",
|
|
36558
|
+
confirmationLabel: CALENDAR_CONFIRM_LABEL,
|
|
36559
|
+
details: event,
|
|
36560
|
+
unsupportedNote: "Create it without attendees instead; the user can add the guests in Google Calendar.",
|
|
36561
|
+
fallback: {
|
|
36562
|
+
tool: "gog_calendar_create",
|
|
36563
|
+
account,
|
|
36564
|
+
confirmToken,
|
|
36565
|
+
subject: () => ({ target: calendarId, payload: event, preview: event })
|
|
36566
|
+
}
|
|
36567
|
+
});
|
|
36568
|
+
if (confirmation) return confirmation;
|
|
36569
|
+
}
|
|
35850
36570
|
return runOrDiagnose(args, { account });
|
|
35851
36571
|
});
|
|
35852
36572
|
server.registerTool("gog_calendar_update", {
|
|
35853
|
-
description: "Update an existing calendar event. Zoom: withZoom adds a Zoom meeting, regenerateZoom replaces the existing one, removeZoom strips it. removeMeet clears the event's Google Meet conference data (e.g. before attaching another provider). Conference flags are independent \u2014 use one per call.",
|
|
36573
|
+
description: "Update an existing calendar event. Zoom: withZoom adds a Zoom meeting, regenerateZoom replaces the existing one, removeZoom strips it. removeMeet clears the event's Google Meet conference data (e.g. before attaching another provider). Conference flags are independent \u2014 use one per call. A change guests can see (time, title, place, description, attendees, attachments, conferencing) on an event that has guests \u2014 or gains them \u2014 reads the event and asks the MCP host to show the user a confirmation prompt with the event as it stands and the change; no invitation email is sent. Reminder-only changes and guest-free events are updated without asking." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35854
36574
|
annotations: { destructiveHint: false },
|
|
35855
36575
|
inputSchema: external_exports.object({
|
|
35856
36576
|
calendarId: external_exports.string().describe("Calendar ID"),
|
|
@@ -35868,10 +36588,11 @@ function registerCalendarTools(server) {
|
|
|
35868
36588
|
removeZoom: external_exports.boolean().optional().describe("Remove the event's Zoom video conference"),
|
|
35869
36589
|
removeMeet: external_exports.boolean().optional().describe("Remove the event's Google Meet video conference (clears conference data only)"),
|
|
35870
36590
|
...reminderParams,
|
|
35871
|
-
account: accountParam
|
|
36591
|
+
account: accountParam,
|
|
36592
|
+
confirmToken: confirmTokenParam
|
|
35872
36593
|
})
|
|
35873
|
-
}, async ({ calendarId, eventId, summary, from, to, description, location, attendees, addAttendees, attachments, withZoom, regenerateZoom, removeZoom, removeMeet, reminders, noReminders, account }) => {
|
|
35874
|
-
const args = ["calendar", "update", calendarId, eventId];
|
|
36594
|
+
}, async ({ calendarId, eventId, summary, from, to, description, location, attendees, addAttendees, attachments, withZoom, regenerateZoom, removeZoom, removeMeet, reminders, noReminders, account, confirmToken }, ctx) => {
|
|
36595
|
+
const args = ["calendar", "update", pos(calendarId), pos(eventId)];
|
|
35875
36596
|
if (summary !== void 0) args.push(`--summary=${summary}`);
|
|
35876
36597
|
if (from !== void 0) args.push(`--from=${from}`);
|
|
35877
36598
|
if (to !== void 0) args.push(`--to=${to}`);
|
|
@@ -35885,6 +36606,45 @@ function registerCalendarTools(server) {
|
|
|
35885
36606
|
if (removeZoom) args.push("--remove-zoom");
|
|
35886
36607
|
if (removeMeet) args.push("--remove-meet");
|
|
35887
36608
|
pushReminderFlags(args, { reminders, noReminders });
|
|
36609
|
+
const changes = Object.fromEntries(Object.entries({
|
|
36610
|
+
summary,
|
|
36611
|
+
from,
|
|
36612
|
+
to,
|
|
36613
|
+
description,
|
|
36614
|
+
location,
|
|
36615
|
+
attendees,
|
|
36616
|
+
addAttendees,
|
|
36617
|
+
attachments,
|
|
36618
|
+
withZoom,
|
|
36619
|
+
regenerateZoom,
|
|
36620
|
+
removeZoom,
|
|
36621
|
+
removeMeet
|
|
36622
|
+
}).filter(([, v]) => v !== void 0 && v !== false));
|
|
36623
|
+
if (Object.keys(changes).length > 0) {
|
|
36624
|
+
const read = await readEvent(calendarId, eventId, account);
|
|
36625
|
+
if (read.error) return read.error;
|
|
36626
|
+
const { etag, ...current } = read.event;
|
|
36627
|
+
const added = [...parseAttendees(attendees), ...parseAttendees(addAttendees)];
|
|
36628
|
+
if (current.guests.length > 0 || added.length > 0) {
|
|
36629
|
+
const view = { calendarId, eventId, current, changes };
|
|
36630
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36631
|
+
action: "calendar.update",
|
|
36632
|
+
message: "Review and confirm this change to an event other people are on:",
|
|
36633
|
+
confirmationLabel: CALENDAR_CONFIRM_LABEL,
|
|
36634
|
+
details: view,
|
|
36635
|
+
unsupportedNote: "Ask the user to make this change in Google Calendar.",
|
|
36636
|
+
fallback: {
|
|
36637
|
+
tool: "gog_calendar_update",
|
|
36638
|
+
account,
|
|
36639
|
+
confirmToken,
|
|
36640
|
+
// The etag rotates on any edit, so an event changed elsewhere between
|
|
36641
|
+
// the two phases is DRAFT_CHANGED rather than edited over.
|
|
36642
|
+
subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
|
|
36643
|
+
}
|
|
36644
|
+
});
|
|
36645
|
+
if (confirmation) return confirmation;
|
|
36646
|
+
}
|
|
36647
|
+
}
|
|
35888
36648
|
return runOrDiagnose(args, { account });
|
|
35889
36649
|
});
|
|
35890
36650
|
server.registerTool("gog_calendar_delete", {
|
|
@@ -35896,24 +36656,43 @@ function registerCalendarTools(server) {
|
|
|
35896
36656
|
account: accountParam
|
|
35897
36657
|
})
|
|
35898
36658
|
}, async ({ calendarId, eventId, account }) => {
|
|
35899
|
-
return runOrDiagnose(["calendar", "delete", calendarId, eventId, "--force"], { account });
|
|
36659
|
+
return runOrDiagnose(["calendar", "delete", pos(calendarId), pos(eventId), "--force"], { account });
|
|
35900
36660
|
});
|
|
35901
36661
|
server.registerTool("gog_calendar_respond", {
|
|
35902
|
-
description: "Respond to a calendar event invitation.",
|
|
36662
|
+
description: "Respond to a calendar event invitation. The organizer sees the response, so this reads the event and asks the MCP host to show the user a confirmation prompt with the event, organizer, response and comment; nothing is recorded unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
35903
36663
|
annotations: { destructiveHint: true },
|
|
35904
36664
|
inputSchema: external_exports.object({
|
|
35905
36665
|
calendarId: external_exports.string().describe("Calendar ID"),
|
|
35906
36666
|
eventId: external_exports.string().describe("Event ID"),
|
|
35907
36667
|
status: external_exports.enum(["accepted", "declined", "tentative"]).describe("Response status"),
|
|
35908
36668
|
comment: external_exports.string().optional().describe("Optional comment to include with response"),
|
|
35909
|
-
account: accountParam
|
|
36669
|
+
account: accountParam,
|
|
36670
|
+
confirmToken: confirmTokenParam
|
|
35910
36671
|
})
|
|
35911
|
-
}, async ({ calendarId, eventId, status, comment, account }) => {
|
|
35912
|
-
const args = ["calendar", "respond", calendarId, eventId, `--status=${status}`];
|
|
36672
|
+
}, async ({ calendarId, eventId, status, comment, account, confirmToken }, ctx) => {
|
|
36673
|
+
const args = ["calendar", "respond", pos(calendarId), pos(eventId), `--status=${status}`];
|
|
35913
36674
|
if (comment) args.push(`--comment=${comment}`);
|
|
36675
|
+
const read = await readEvent(calendarId, eventId, account);
|
|
36676
|
+
if (read.error) return read.error;
|
|
36677
|
+
const { etag, ...event } = read.event;
|
|
36678
|
+
const view = { calendarId, eventId, event, response: status, ...comment ? { comment } : {} };
|
|
36679
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36680
|
+
action: "calendar.respond",
|
|
36681
|
+
message: "Review and confirm this response, which the organizer will see:",
|
|
36682
|
+
confirmationLabel: "Confirm that this response should be recorded now.",
|
|
36683
|
+
details: view,
|
|
36684
|
+
unsupportedNote: "Ask the user to respond from Google Calendar.",
|
|
36685
|
+
fallback: {
|
|
36686
|
+
tool: "gog_calendar_respond",
|
|
36687
|
+
account,
|
|
36688
|
+
confirmToken,
|
|
36689
|
+
subject: () => ({ target: `${calendarId}/${eventId}`, revision: etag, payload: view, preview: view })
|
|
36690
|
+
}
|
|
36691
|
+
});
|
|
36692
|
+
if (confirmation) return confirmation;
|
|
35914
36693
|
return runOrDiagnose(args, { account });
|
|
35915
36694
|
});
|
|
35916
|
-
registerRunTool(server, { service: "calendar", examples: '"calendars", "freebusy"' });
|
|
36695
|
+
registerRunTool(server, { service: "calendar", examples: '"calendars", "freebusy"', vet: vetCalendarRun });
|
|
35917
36696
|
}
|
|
35918
36697
|
|
|
35919
36698
|
// src/attachments.ts
|
|
@@ -35924,6 +36703,7 @@ var MAX_REQUEST_PAYLOAD_WIRE_BYTES = REQUEST_MAX_BODY_BYTES - REQUEST_BODY_JSON_
|
|
|
35924
36703
|
var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
|
|
35925
36704
|
function wireBytesOf(arg) {
|
|
35926
36705
|
if (typeof arg === "string") return Buffer.byteLength(arg, "utf8");
|
|
36706
|
+
if (arg.kind === "positional") return Buffer.byteLength(arg.value, "utf8");
|
|
35927
36707
|
return arg.encoding === "base64" ? arg.contents.length : Buffer.byteLength(arg.contents, "utf8");
|
|
35928
36708
|
}
|
|
35929
36709
|
var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
|
|
@@ -35996,6 +36776,23 @@ function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
|
|
|
35996
36776
|
}
|
|
35997
36777
|
|
|
35998
36778
|
// src/tools/chat.ts
|
|
36779
|
+
var CHAT_SEND_WORDS = /* @__PURE__ */ new Set(["send", "create", "post"]);
|
|
36780
|
+
function vetChatRun(subcommand, args) {
|
|
36781
|
+
const sub = subcommand.toLowerCase();
|
|
36782
|
+
if (sub !== "messages" && sub !== "dm") return void 0;
|
|
36783
|
+
const word = hasCommandWord(args, CHAT_SEND_WORDS);
|
|
36784
|
+
if (!word) return void 0;
|
|
36785
|
+
return gatedElsewhere(
|
|
36786
|
+
`gog chat ${sub} ${word.toLowerCase()}`,
|
|
36787
|
+
"gog_chat_run",
|
|
36788
|
+
"posts a message",
|
|
36789
|
+
sub === "dm" ? "gog_chat_dm_send" : "gog_chat_messages_send"
|
|
36790
|
+
);
|
|
36791
|
+
}
|
|
36792
|
+
var CHAT_CONFIRM = {
|
|
36793
|
+
message: "Review and confirm this Google Chat message:",
|
|
36794
|
+
confirmationLabel: "Confirm that this Chat message should be posted now."
|
|
36795
|
+
};
|
|
35999
36796
|
function registerChatTools(server) {
|
|
36000
36797
|
const workspaceOnlyNote = ' WORKSPACE ONLY: Google Chat has no API for consumer accounts, so this fails on an @gmail.com account with "chat requires a Google Workspace account". That is the ACCOUNT, not the token \u2014 re-authorizing or adding scopes will not help.';
|
|
36001
36798
|
const spaceParam = external_exports.string().describe(
|
|
@@ -36026,7 +36823,7 @@ function registerChatTools(server) {
|
|
|
36026
36823
|
account: accountParam
|
|
36027
36824
|
})
|
|
36028
36825
|
}, async ({ displayName, exact, max, account }) => {
|
|
36029
|
-
const args = ["chat", "spaces", "find", displayName];
|
|
36826
|
+
const args = ["chat", "spaces", "find", pos(displayName)];
|
|
36030
36827
|
if (exact) args.push("--exact");
|
|
36031
36828
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
36032
36829
|
return runOrDiagnose(args, { account });
|
|
@@ -36040,7 +36837,7 @@ function registerChatTools(server) {
|
|
|
36040
36837
|
account: accountParam
|
|
36041
36838
|
})
|
|
36042
36839
|
}, async ({ displayName, members: members2, account }) => {
|
|
36043
|
-
const args = ["chat", "spaces", "create", displayName];
|
|
36840
|
+
const args = ["chat", "spaces", "create", pos(displayName)];
|
|
36044
36841
|
if (members2) for (const member of members2) args.push(`--member=${member}`);
|
|
36045
36842
|
return runOrDiagnose(args, { account });
|
|
36046
36843
|
});
|
|
@@ -36053,7 +36850,7 @@ function registerChatTools(server) {
|
|
|
36053
36850
|
account: accountParam
|
|
36054
36851
|
})
|
|
36055
36852
|
}, async ({ space, max, pageToken, page, all, account }) => {
|
|
36056
|
-
const args = ["chat", "threads", "list", space];
|
|
36853
|
+
const args = ["chat", "threads", "list", pos(space)];
|
|
36057
36854
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
36058
36855
|
return runOrDiagnose(args, { account });
|
|
36059
36856
|
});
|
|
@@ -36069,7 +36866,7 @@ function registerChatTools(server) {
|
|
|
36069
36866
|
account: accountParam
|
|
36070
36867
|
})
|
|
36071
36868
|
}, async ({ space, thread, unread, order, max, pageToken, page, all, account }) => {
|
|
36072
|
-
const args = ["chat", "messages", "list", space];
|
|
36869
|
+
const args = ["chat", "messages", "list", pos(space)];
|
|
36073
36870
|
if (thread) args.push(`--thread=${thread}`);
|
|
36074
36871
|
if (unread) args.push("--unread");
|
|
36075
36872
|
if (order) args.push(`--order=${order}`);
|
|
@@ -36093,7 +36890,7 @@ function registerChatTools(server) {
|
|
|
36093
36890
|
account: accountParam
|
|
36094
36891
|
})
|
|
36095
36892
|
}, async ({ query, order, view, markup, max, pageToken, page, all, account }) => {
|
|
36096
|
-
const args = ["chat", "messages", "search", query];
|
|
36893
|
+
const args = ["chat", "messages", "search", pos(query)];
|
|
36097
36894
|
if (order) args.push(`--order=${order}`);
|
|
36098
36895
|
if (view) args.push(`--view=${view}`);
|
|
36099
36896
|
if (markup) args.push(`--markup=${markup}`);
|
|
@@ -36101,41 +36898,79 @@ function registerChatTools(server) {
|
|
|
36101
36898
|
return runOrDiagnose(args, { account });
|
|
36102
36899
|
});
|
|
36103
36900
|
server.registerTool("gog_chat_messages_send", {
|
|
36104
|
-
description: "Post a message to a Chat space. THIS IS IMMEDIATELY VISIBLE TO EVERYONE IN THE SPACE and cannot be unsent through this tool, so treat it like sending mail, not like saving a draft. Pass `thread` to reply inside an existing conversation (from gog_chat_threads_list or a message's thread field); omit it to start a new one. Text supports Chat's markdown-ish formatting (*bold*, _italic_, `code`)." + workspaceOnlyNote,
|
|
36901
|
+
description: "Post a message to a Chat space. THIS IS IMMEDIATELY VISIBLE TO EVERYONE IN THE SPACE and cannot be unsent through this tool, so treat it like sending mail, not like saving a draft. Pass `thread` to reply inside an existing conversation (from gog_chat_threads_list or a message's thread field); omit it to start a new one. Text supports Chat's markdown-ish formatting (*bold*, _italic_, `code`). Asks the MCP host to show the user a confirmation prompt with the space, thread, text and attachments first; nothing is posted unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
|
|
36105
36902
|
annotations: { destructiveHint: true },
|
|
36106
36903
|
inputSchema: external_exports.object({
|
|
36107
36904
|
space: spaceParam,
|
|
36108
36905
|
text: external_exports.string().optional().describe("Message text. Optional only when an attachment is supplied."),
|
|
36109
36906
|
thread: threadParam,
|
|
36110
36907
|
attach: external_exports.array(external_exports.string()).optional().describe(
|
|
36111
|
-
"Attachment file paths, read WHERE GOG RUNS. On a hosted or remote deployment that is not your machine \u2014 use attachInline there instead."
|
|
36908
|
+
"Attachment file paths, read WHERE GOG RUNS. On a hosted or remote deployment that is not your machine \u2014 use attachInline there instead. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files)."
|
|
36112
36909
|
),
|
|
36113
36910
|
attachInline: attachInlineParam,
|
|
36114
|
-
account: accountParam
|
|
36911
|
+
account: accountParam,
|
|
36912
|
+
confirmToken: confirmTokenParam
|
|
36115
36913
|
})
|
|
36116
|
-
}, async ({ space, text, thread, attach, attachInline, account }) => {
|
|
36914
|
+
}, async ({ space, text, thread, attach, attachInline, account, confirmToken }, ctx) => {
|
|
36915
|
+
confinePaths(attach, "attach");
|
|
36117
36916
|
if (text === void 0 && !attach?.length && !attachInline?.length) {
|
|
36118
36917
|
throw new Error("A Chat message needs text, an attachment, or both.");
|
|
36119
36918
|
}
|
|
36120
|
-
const args = ["chat", "messages", "send", space];
|
|
36919
|
+
const args = ["chat", "messages", "send", pos(space)];
|
|
36121
36920
|
if (text !== void 0) args.push(`--text=${text}`);
|
|
36122
36921
|
if (thread) args.push(`--thread=${thread}`);
|
|
36123
36922
|
if (attach) for (const path of attach) args.push(`--attach=${path}`);
|
|
36124
36923
|
args.push(...inlineAttachmentArgs("attach", attachInline, args));
|
|
36924
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36925
|
+
...CHAT_CONFIRM,
|
|
36926
|
+
action: "chat.message-send",
|
|
36927
|
+
details: { space, thread, textPreview: bodyPreview(text), attachments: attachmentNames(attach, attachInline) },
|
|
36928
|
+
fallback: {
|
|
36929
|
+
tool: "gog_chat_messages_send",
|
|
36930
|
+
account,
|
|
36931
|
+
confirmToken,
|
|
36932
|
+
subject: () => {
|
|
36933
|
+
const attachments = attachmentDetails(attach, attachInline);
|
|
36934
|
+
const from = senderPreview(account);
|
|
36935
|
+
return {
|
|
36936
|
+
target: space,
|
|
36937
|
+
payload: { from, space, thread, text, attachments },
|
|
36938
|
+
preview: { from, space, thread, text, attachments: attachmentPreview(attachments) }
|
|
36939
|
+
};
|
|
36940
|
+
}
|
|
36941
|
+
}
|
|
36942
|
+
});
|
|
36943
|
+
if (confirmation) return confirmation;
|
|
36125
36944
|
return runOrDiagnose(args, { account });
|
|
36126
36945
|
});
|
|
36127
36946
|
server.registerTool("gog_chat_dm_send", {
|
|
36128
|
-
description: "Send a direct message to one person by email address, creating the DM space if this is the first message. Delivered immediately and cannot be unsent through this tool. For a room rather than a person, use gog_chat_messages_send." + workspaceOnlyNote,
|
|
36947
|
+
description: "Send a direct message to one person by email address, creating the DM space if this is the first message. Delivered immediately and cannot be unsent through this tool. For a room rather than a person, use gog_chat_messages_send. Asks the MCP host to show the user a confirmation prompt with the recipient and text first; nothing is sent unless they accept." + CONFIRM_FALLBACK_DESCRIPTION + workspaceOnlyNote,
|
|
36129
36948
|
annotations: { destructiveHint: true },
|
|
36130
36949
|
inputSchema: external_exports.object({
|
|
36131
36950
|
email: external_exports.string().describe("Recipient email address"),
|
|
36132
36951
|
text: external_exports.string().describe("Message text"),
|
|
36133
36952
|
thread: threadParam,
|
|
36134
|
-
account: accountParam
|
|
36953
|
+
account: accountParam,
|
|
36954
|
+
confirmToken: confirmTokenParam
|
|
36135
36955
|
})
|
|
36136
|
-
}, async ({ email: email3, text, thread, account }) => {
|
|
36137
|
-
const args = ["chat", "dm", "send", email3, `--text=${text}`];
|
|
36956
|
+
}, async ({ email: email3, text, thread, account, confirmToken }, ctx) => {
|
|
36957
|
+
const args = ["chat", "dm", "send", pos(email3), `--text=${text}`];
|
|
36138
36958
|
if (thread) args.push(`--thread=${thread}`);
|
|
36959
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
36960
|
+
...CHAT_CONFIRM,
|
|
36961
|
+
action: "chat.dm-send",
|
|
36962
|
+
details: { to: email3, thread, textPreview: bodyPreview(text) },
|
|
36963
|
+
fallback: {
|
|
36964
|
+
tool: "gog_chat_dm_send",
|
|
36965
|
+
account,
|
|
36966
|
+
confirmToken,
|
|
36967
|
+
subject: () => {
|
|
36968
|
+
const from = senderPreview(account);
|
|
36969
|
+
return { target: email3, payload: { from, to: email3, thread, text }, preview: { from, to: email3, thread, text } };
|
|
36970
|
+
}
|
|
36971
|
+
}
|
|
36972
|
+
});
|
|
36973
|
+
if (confirmation) return confirmation;
|
|
36139
36974
|
return runOrDiagnose(args, { account });
|
|
36140
36975
|
});
|
|
36141
36976
|
server.registerTool("gog_chat_dm_space", {
|
|
@@ -36146,7 +36981,7 @@ function registerChatTools(server) {
|
|
|
36146
36981
|
account: accountParam
|
|
36147
36982
|
})
|
|
36148
36983
|
}, async ({ email: email3, account }) => {
|
|
36149
|
-
return runOrDiagnose(["chat", "dm", "space", email3], { account });
|
|
36984
|
+
return runOrDiagnose(["chat", "dm", "space", pos(email3)], { account });
|
|
36150
36985
|
});
|
|
36151
36986
|
server.registerTool("gog_chat_reactions_list", {
|
|
36152
36987
|
description: "List the emoji reactions on one message, with who reacted. gog_chat_messages_list already returns a reaction SUMMARY per message; come here when you need the individual reactors, or the reaction resource names that gog_chat_reactions_delete takes." + workspaceOnlyNote,
|
|
@@ -36158,7 +36993,7 @@ function registerChatTools(server) {
|
|
|
36158
36993
|
account: accountParam
|
|
36159
36994
|
})
|
|
36160
36995
|
}, async ({ message, space, max, pageToken, page, all, account }) => {
|
|
36161
|
-
const args = ["chat", "messages", "reactions", "list", message];
|
|
36996
|
+
const args = ["chat", "messages", "reactions", "list", pos(message)];
|
|
36162
36997
|
if (space) args.push(`--space=${space}`);
|
|
36163
36998
|
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
36164
36999
|
return runOrDiagnose(args, { account });
|
|
@@ -36173,7 +37008,7 @@ function registerChatTools(server) {
|
|
|
36173
37008
|
account: accountParam
|
|
36174
37009
|
})
|
|
36175
37010
|
}, async ({ message, emoji: emoji3, space, account }) => {
|
|
36176
|
-
const args = ["chat", "messages", "reactions", "create", message, emoji3];
|
|
37011
|
+
const args = ["chat", "messages", "reactions", "create", pos(message), pos(emoji3)];
|
|
36177
37012
|
if (space) args.push(`--space=${space}`);
|
|
36178
37013
|
return runOrDiagnose(args, { account });
|
|
36179
37014
|
});
|
|
@@ -36185,16 +37020,50 @@ function registerChatTools(server) {
|
|
|
36185
37020
|
account: accountParam
|
|
36186
37021
|
})
|
|
36187
37022
|
}, async ({ reaction, account }) => {
|
|
36188
|
-
return runOrDiagnose(["chat", "messages", "reactions", "delete", reaction], { account });
|
|
37023
|
+
return runOrDiagnose(["chat", "messages", "reactions", "delete", pos(reaction)], { account });
|
|
36189
37024
|
});
|
|
36190
37025
|
registerRunTool(server, {
|
|
36191
37026
|
service: "chat",
|
|
36192
37027
|
examples: '"spaces", "messages", "dm"',
|
|
37028
|
+
vet: vetChatRun,
|
|
36193
37029
|
note: "Google Chat has no API for consumer accounts: every chat subcommand fails on an @gmail.com account regardless of scopes."
|
|
36194
37030
|
});
|
|
36195
37031
|
}
|
|
36196
37032
|
|
|
36197
37033
|
// src/tools/classroom.ts
|
|
37034
|
+
var CLASSROOM_CREATE_WORDS = /* @__PURE__ */ new Set(["create", "add", "new"]);
|
|
37035
|
+
var CLASSROOM_GATED = {
|
|
37036
|
+
announcements: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
37037
|
+
announcement: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
37038
|
+
ann: { does: "posts to a class", tool: "gog_classroom_announcements_create" },
|
|
37039
|
+
invitations: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
|
|
37040
|
+
invitation: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" },
|
|
37041
|
+
invites: { does: "invites someone to a class", tool: "gog_classroom_invitations_create" }
|
|
37042
|
+
};
|
|
37043
|
+
function vetClassroomRun(subcommand, args) {
|
|
37044
|
+
const sub = subcommand.toLowerCase();
|
|
37045
|
+
const gated = Object.hasOwn(CLASSROOM_GATED, sub) ? CLASSROOM_GATED[sub] : void 0;
|
|
37046
|
+
if (!gated) return void 0;
|
|
37047
|
+
const word = hasCommandWord(args, CLASSROOM_CREATE_WORDS);
|
|
37048
|
+
return word ? gatedElsewhere(`gog classroom ${sub} ${word.toLowerCase()}`, "gog_classroom_run", gated.does, gated.tool) : void 0;
|
|
37049
|
+
}
|
|
37050
|
+
async function readCourse(courseId, account, runner = runOrDiagnose) {
|
|
37051
|
+
const got = await runner(["classroom", "courses", "get", pos(courseId)], { account });
|
|
37052
|
+
if (got.isError) return { error: got };
|
|
37053
|
+
let course;
|
|
37054
|
+
try {
|
|
37055
|
+
course = JSON.parse(resultText(got))?.course;
|
|
37056
|
+
} catch {
|
|
37057
|
+
course = void 0;
|
|
37058
|
+
}
|
|
37059
|
+
return {
|
|
37060
|
+
course: {
|
|
37061
|
+
id: courseId,
|
|
37062
|
+
...typeof course?.name === "string" ? { name: course.name } : {},
|
|
37063
|
+
...typeof course?.section === "string" ? { section: course.section } : {}
|
|
37064
|
+
}
|
|
37065
|
+
};
|
|
37066
|
+
}
|
|
36198
37067
|
function registerClassroomTools(server) {
|
|
36199
37068
|
server.registerTool("gog_classroom_courses_list", {
|
|
36200
37069
|
description: "List Google Classroom courses.",
|
|
@@ -36228,7 +37097,7 @@ function registerClassroomTools(server) {
|
|
|
36228
37097
|
account: accountParam
|
|
36229
37098
|
})
|
|
36230
37099
|
}, async ({ courseId, account }) => {
|
|
36231
|
-
return runOrDiagnose(["classroom", "courses", "get", courseId], { account });
|
|
37100
|
+
return runOrDiagnose(["classroom", "courses", "get", pos(courseId)], { account });
|
|
36232
37101
|
});
|
|
36233
37102
|
server.registerTool("gog_classroom_students_list", {
|
|
36234
37103
|
description: "List students enrolled in a Google Classroom course.",
|
|
@@ -36242,7 +37111,7 @@ function registerClassroomTools(server) {
|
|
|
36242
37111
|
account: accountParam
|
|
36243
37112
|
})
|
|
36244
37113
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
36245
|
-
const args = ["classroom", "students", "list", courseId];
|
|
37114
|
+
const args = ["classroom", "students", "list", pos(courseId)];
|
|
36246
37115
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
36247
37116
|
const token = resolvePageToken({ pageToken, page });
|
|
36248
37117
|
if (token) args.push(`--page=${token}`);
|
|
@@ -36258,7 +37127,7 @@ function registerClassroomTools(server) {
|
|
|
36258
37127
|
account: accountParam
|
|
36259
37128
|
})
|
|
36260
37129
|
}, async ({ courseId, userId, account }) => {
|
|
36261
|
-
return runOrDiagnose(["classroom", "students", "get", courseId, userId], { account });
|
|
37130
|
+
return runOrDiagnose(["classroom", "students", "get", pos(courseId), pos(userId)], { account });
|
|
36262
37131
|
});
|
|
36263
37132
|
server.registerTool("gog_classroom_teachers_list", {
|
|
36264
37133
|
description: "List teachers in a Google Classroom course.",
|
|
@@ -36272,7 +37141,7 @@ function registerClassroomTools(server) {
|
|
|
36272
37141
|
account: accountParam
|
|
36273
37142
|
})
|
|
36274
37143
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
36275
|
-
const args = ["classroom", "teachers", "list", courseId];
|
|
37144
|
+
const args = ["classroom", "teachers", "list", pos(courseId)];
|
|
36276
37145
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
36277
37146
|
const token = resolvePageToken({ pageToken, page });
|
|
36278
37147
|
if (token) args.push(`--page=${token}`);
|
|
@@ -36288,7 +37157,7 @@ function registerClassroomTools(server) {
|
|
|
36288
37157
|
account: accountParam
|
|
36289
37158
|
})
|
|
36290
37159
|
}, async ({ courseId, userId, account }) => {
|
|
36291
|
-
return runOrDiagnose(["classroom", "teachers", "get", courseId, userId], { account });
|
|
37160
|
+
return runOrDiagnose(["classroom", "teachers", "get", pos(courseId), pos(userId)], { account });
|
|
36292
37161
|
});
|
|
36293
37162
|
server.registerTool("gog_classroom_roster", {
|
|
36294
37163
|
description: "List the full roster (students and/or teachers) of a Google Classroom course. Omit both flags to return both groups.",
|
|
@@ -36304,7 +37173,7 @@ function registerClassroomTools(server) {
|
|
|
36304
37173
|
account: accountParam
|
|
36305
37174
|
})
|
|
36306
37175
|
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
36307
|
-
const args = ["classroom", "roster", courseId];
|
|
37176
|
+
const args = ["classroom", "roster", pos(courseId)];
|
|
36308
37177
|
if (students) args.push("--students");
|
|
36309
37178
|
if (teachers) args.push("--teachers");
|
|
36310
37179
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
@@ -36329,7 +37198,7 @@ function registerClassroomTools(server) {
|
|
|
36329
37198
|
account: accountParam
|
|
36330
37199
|
})
|
|
36331
37200
|
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
36332
|
-
const args = ["classroom", "coursework", "list", courseId];
|
|
37201
|
+
const args = ["classroom", "coursework", "list", pos(courseId)];
|
|
36333
37202
|
if (state) args.push(`--state=${state}`);
|
|
36334
37203
|
if (topic) args.push(`--topic=${topic}`);
|
|
36335
37204
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
@@ -36349,7 +37218,7 @@ function registerClassroomTools(server) {
|
|
|
36349
37218
|
account: accountParam
|
|
36350
37219
|
})
|
|
36351
37220
|
}, async ({ courseId, courseworkId, account }) => {
|
|
36352
|
-
return runOrDiagnose(["classroom", "coursework", "get", courseId, courseworkId], { account });
|
|
37221
|
+
return runOrDiagnose(["classroom", "coursework", "get", pos(courseId), pos(courseworkId)], { account });
|
|
36353
37222
|
});
|
|
36354
37223
|
server.registerTool("gog_classroom_submissions_list", {
|
|
36355
37224
|
description: "List student submissions for a coursework item.",
|
|
@@ -36367,7 +37236,7 @@ function registerClassroomTools(server) {
|
|
|
36367
37236
|
account: accountParam
|
|
36368
37237
|
})
|
|
36369
37238
|
}, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
|
|
36370
|
-
const args = ["classroom", "submissions", "list", courseId, courseworkId];
|
|
37239
|
+
const args = ["classroom", "submissions", "list", pos(courseId), pos(courseworkId)];
|
|
36371
37240
|
if (state) args.push(`--state=${state}`);
|
|
36372
37241
|
if (late) args.push(`--late=${late}`);
|
|
36373
37242
|
if (user) args.push(`--user=${user}`);
|
|
@@ -36387,7 +37256,7 @@ function registerClassroomTools(server) {
|
|
|
36387
37256
|
account: accountParam
|
|
36388
37257
|
})
|
|
36389
37258
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
36390
|
-
return runOrDiagnose(["classroom", "submissions", "get", courseId, courseworkId, submissionId], { account });
|
|
37259
|
+
return runOrDiagnose(["classroom", "submissions", "get", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
36391
37260
|
});
|
|
36392
37261
|
server.registerTool("gog_classroom_submissions_grade", {
|
|
36393
37262
|
description: "Grade a student submission. Set draft and/or assigned grade values.",
|
|
@@ -36401,7 +37270,7 @@ function registerClassroomTools(server) {
|
|
|
36401
37270
|
account: accountParam
|
|
36402
37271
|
})
|
|
36403
37272
|
}, async ({ courseId, courseworkId, submissionId, draft, assigned, account }) => {
|
|
36404
|
-
const args = ["classroom", "submissions", "grade", courseId, courseworkId, submissionId];
|
|
37273
|
+
const args = ["classroom", "submissions", "grade", pos(courseId), pos(courseworkId), pos(submissionId)];
|
|
36405
37274
|
if (draft) args.push(`--draft=${draft}`);
|
|
36406
37275
|
if (assigned) args.push(`--assigned=${assigned}`);
|
|
36407
37276
|
return runOrDiagnose(args, { account });
|
|
@@ -36416,7 +37285,7 @@ function registerClassroomTools(server) {
|
|
|
36416
37285
|
account: accountParam
|
|
36417
37286
|
})
|
|
36418
37287
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
36419
|
-
return runOrDiagnose(["classroom", "submissions", "return", courseId, courseworkId, submissionId], { account });
|
|
37288
|
+
return runOrDiagnose(["classroom", "submissions", "return", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
36420
37289
|
});
|
|
36421
37290
|
server.registerTool("gog_classroom_submissions_turn_in", {
|
|
36422
37291
|
description: "Turn in a student submission (student action).",
|
|
@@ -36428,7 +37297,7 @@ function registerClassroomTools(server) {
|
|
|
36428
37297
|
account: accountParam
|
|
36429
37298
|
})
|
|
36430
37299
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
36431
|
-
return runOrDiagnose(["classroom", "submissions", "turn-in", courseId, courseworkId, submissionId], { account });
|
|
37300
|
+
return runOrDiagnose(["classroom", "submissions", "turn-in", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
36432
37301
|
});
|
|
36433
37302
|
server.registerTool("gog_classroom_submissions_reclaim", {
|
|
36434
37303
|
description: "Reclaim a turned-in submission (student action to edit a submission).",
|
|
@@ -36440,7 +37309,7 @@ function registerClassroomTools(server) {
|
|
|
36440
37309
|
account: accountParam
|
|
36441
37310
|
})
|
|
36442
37311
|
}, async ({ courseId, courseworkId, submissionId, account }) => {
|
|
36443
|
-
return runOrDiagnose(["classroom", "submissions", "reclaim", courseId, courseworkId, submissionId], { account });
|
|
37312
|
+
return runOrDiagnose(["classroom", "submissions", "reclaim", pos(courseId), pos(courseworkId), pos(submissionId)], { account });
|
|
36444
37313
|
});
|
|
36445
37314
|
server.registerTool("gog_classroom_announcements_list", {
|
|
36446
37315
|
description: "List announcements in a Google Classroom course.",
|
|
@@ -36456,7 +37325,7 @@ function registerClassroomTools(server) {
|
|
|
36456
37325
|
account: accountParam
|
|
36457
37326
|
})
|
|
36458
37327
|
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
36459
|
-
const args = ["classroom", "announcements", "list", courseId];
|
|
37328
|
+
const args = ["classroom", "announcements", "list", pos(courseId)];
|
|
36460
37329
|
if (state) args.push(`--state=${state}`);
|
|
36461
37330
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
36462
37331
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
@@ -36474,22 +37343,45 @@ function registerClassroomTools(server) {
|
|
|
36474
37343
|
account: accountParam
|
|
36475
37344
|
})
|
|
36476
37345
|
}, async ({ courseId, announcementId, account }) => {
|
|
36477
|
-
return runOrDiagnose(["classroom", "announcements", "get", courseId, announcementId], { account });
|
|
37346
|
+
return runOrDiagnose(["classroom", "announcements", "get", pos(courseId), pos(announcementId)], { account });
|
|
36478
37347
|
});
|
|
36479
37348
|
server.registerTool("gog_classroom_announcements_create", {
|
|
36480
|
-
description: "Create an announcement in a Google Classroom course.",
|
|
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,
|
|
36481
37350
|
annotations: { destructiveHint: true },
|
|
36482
37351
|
inputSchema: external_exports.object({
|
|
36483
37352
|
courseId: external_exports.string().describe("Course ID"),
|
|
36484
37353
|
text: external_exports.string().describe("Announcement text"),
|
|
36485
|
-
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State"),
|
|
37354
|
+
state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State (DRAFT is visible only to teachers and needs no confirmation)"),
|
|
36486
37355
|
scheduled: external_exports.string().optional().describe("Scheduled publish time"),
|
|
36487
|
-
account: accountParam
|
|
37356
|
+
account: accountParam,
|
|
37357
|
+
confirmToken: confirmTokenParam
|
|
36488
37358
|
})
|
|
36489
|
-
}, async ({ courseId, text, state, scheduled, account }) => {
|
|
36490
|
-
const args = ["classroom", "announcements", "create", courseId, `--text=${text}`];
|
|
37359
|
+
}, async ({ courseId, text, state, scheduled, account, confirmToken }, ctx) => {
|
|
37360
|
+
const args = ["classroom", "announcements", "create", pos(courseId), `--text=${text}`];
|
|
36491
37361
|
if (state) args.push(`--state=${state}`);
|
|
36492
37362
|
if (scheduled) args.push(`--scheduled=${scheduled}`);
|
|
37363
|
+
if (state !== "DRAFT") {
|
|
37364
|
+
const read = await readCourse(courseId, account);
|
|
37365
|
+
if (read.error) return read.error;
|
|
37366
|
+
const publishes = scheduled ? `at ${scheduled}` : "immediately";
|
|
37367
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
37368
|
+
action: "classroom.announcement-create",
|
|
37369
|
+
message: "Review and confirm this Classroom announcement:",
|
|
37370
|
+
confirmationLabel: "Confirm that this announcement should be posted to the class.",
|
|
37371
|
+
details: { course: read.course, publishes, textPreview: bodyPreview(text) },
|
|
37372
|
+
unsupportedNote: "Create it with state DRAFT instead; the user can review and post it from Classroom.",
|
|
37373
|
+
fallback: {
|
|
37374
|
+
tool: "gog_classroom_announcements_create",
|
|
37375
|
+
account,
|
|
37376
|
+
confirmToken,
|
|
37377
|
+
subject: () => {
|
|
37378
|
+
const view = { course: read.course, publishes, text };
|
|
37379
|
+
return { target: courseId, payload: view, preview: view };
|
|
37380
|
+
}
|
|
37381
|
+
}
|
|
37382
|
+
});
|
|
37383
|
+
if (confirmation) return confirmation;
|
|
37384
|
+
}
|
|
36493
37385
|
return runOrDiagnose(args, { account });
|
|
36494
37386
|
});
|
|
36495
37387
|
server.registerTool("gog_classroom_topics_list", {
|
|
@@ -36504,7 +37396,7 @@ function registerClassroomTools(server) {
|
|
|
36504
37396
|
account: accountParam
|
|
36505
37397
|
})
|
|
36506
37398
|
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
36507
|
-
const args = ["classroom", "topics", "list", courseId];
|
|
37399
|
+
const args = ["classroom", "topics", "list", pos(courseId)];
|
|
36508
37400
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
36509
37401
|
const token = resolvePageToken({ pageToken, page });
|
|
36510
37402
|
if (token) args.push(`--page=${token}`);
|
|
@@ -36520,7 +37412,7 @@ function registerClassroomTools(server) {
|
|
|
36520
37412
|
account: accountParam
|
|
36521
37413
|
})
|
|
36522
37414
|
}, async ({ courseId, topicId, account }) => {
|
|
36523
|
-
return runOrDiagnose(["classroom", "topics", "get", courseId, topicId], { account });
|
|
37415
|
+
return runOrDiagnose(["classroom", "topics", "get", pos(courseId), pos(topicId)], { account });
|
|
36524
37416
|
});
|
|
36525
37417
|
server.registerTool("gog_classroom_invitations_list", {
|
|
36526
37418
|
description: "List Google Classroom invitations.",
|
|
@@ -36552,7 +37444,7 @@ function registerClassroomTools(server) {
|
|
|
36552
37444
|
account: accountParam
|
|
36553
37445
|
})
|
|
36554
37446
|
}, async ({ invitationId, account }) => {
|
|
36555
|
-
return runOrDiagnose(["classroom", "invitations", "get", invitationId], { account });
|
|
37447
|
+
return runOrDiagnose(["classroom", "invitations", "get", pos(invitationId)], { account });
|
|
36556
37448
|
});
|
|
36557
37449
|
server.registerTool("gog_classroom_invitations_accept", {
|
|
36558
37450
|
description: "Accept a Google Classroom invitation.",
|
|
@@ -36562,7 +37454,7 @@ function registerClassroomTools(server) {
|
|
|
36562
37454
|
account: accountParam
|
|
36563
37455
|
})
|
|
36564
37456
|
}, async ({ invitationId, account }) => {
|
|
36565
|
-
return runOrDiagnose(["classroom", "invitations", "accept", invitationId], { account });
|
|
37457
|
+
return runOrDiagnose(["classroom", "invitations", "accept", pos(invitationId)], { account });
|
|
36566
37458
|
});
|
|
36567
37459
|
server.registerTool("gog_classroom_profile_get", {
|
|
36568
37460
|
description: "Get a Google Classroom user profile. Omit userId to fetch the authenticated user.",
|
|
@@ -36573,12 +37465,13 @@ function registerClassroomTools(server) {
|
|
|
36573
37465
|
})
|
|
36574
37466
|
}, async ({ userId, account }) => {
|
|
36575
37467
|
const args = ["classroom", "profile", "get"];
|
|
36576
|
-
if (userId) args.push(userId);
|
|
37468
|
+
if (userId) args.push(pos(userId));
|
|
36577
37469
|
return runOrDiagnose(args, { account });
|
|
36578
37470
|
});
|
|
36579
37471
|
registerRunTool(server, {
|
|
36580
37472
|
service: "classroom",
|
|
36581
37473
|
examples: '"guardians", "materials", "guardian-invitations"',
|
|
37474
|
+
vet: vetClassroomRun,
|
|
36582
37475
|
note: "Covers anything not wrapped by the dedicated tools (guardians, guardian-invitations, materials, coursework assignees, announcement assignees, etc.)."
|
|
36583
37476
|
});
|
|
36584
37477
|
}
|
|
@@ -36593,7 +37486,7 @@ function registerContactsTools(server) {
|
|
|
36593
37486
|
account: accountParam
|
|
36594
37487
|
})
|
|
36595
37488
|
}, async ({ query, account }) => {
|
|
36596
|
-
return runOrDiagnose(["contacts", "search", query], { account });
|
|
37489
|
+
return runOrDiagnose(["contacts", "search", pos(query)], { account });
|
|
36597
37490
|
});
|
|
36598
37491
|
server.registerTool("gog_contacts_list", {
|
|
36599
37492
|
description: "List all Google Contacts.",
|
|
@@ -36612,7 +37505,7 @@ function registerContactsTools(server) {
|
|
|
36612
37505
|
account: accountParam
|
|
36613
37506
|
})
|
|
36614
37507
|
}, async ({ resourceName, account }) => {
|
|
36615
|
-
return runOrDiagnose(["contacts", "get", resourceName], { account });
|
|
37508
|
+
return runOrDiagnose(["contacts", "get", pos(resourceName)], { account });
|
|
36616
37509
|
});
|
|
36617
37510
|
server.registerTool("gog_contacts_create", {
|
|
36618
37511
|
description: "Create a new Google Contact.",
|
|
@@ -36648,7 +37541,7 @@ function registerDocsTools(server) {
|
|
|
36648
37541
|
account: accountParam
|
|
36649
37542
|
})
|
|
36650
37543
|
}, async ({ docId, account }) => {
|
|
36651
|
-
return runOrDiagnose(["docs", "info", docId], { account });
|
|
37544
|
+
return runOrDiagnose(["docs", "info", pos(docId)], { account });
|
|
36652
37545
|
});
|
|
36653
37546
|
server.registerTool("gog_docs_cat", {
|
|
36654
37547
|
description: "Read a Google Doc as plain text.",
|
|
@@ -36659,7 +37552,7 @@ function registerDocsTools(server) {
|
|
|
36659
37552
|
account: accountParam
|
|
36660
37553
|
})
|
|
36661
37554
|
}, async ({ docId, chips, account }) => {
|
|
36662
|
-
const args = ["docs", "cat", docId];
|
|
37555
|
+
const args = ["docs", "cat", pos(docId)];
|
|
36663
37556
|
if (chips) args.push("--chips");
|
|
36664
37557
|
return runOrDiagnose(args, { account });
|
|
36665
37558
|
});
|
|
@@ -36671,7 +37564,7 @@ function registerDocsTools(server) {
|
|
|
36671
37564
|
account: accountParam
|
|
36672
37565
|
})
|
|
36673
37566
|
}, async ({ title, account }) => {
|
|
36674
|
-
return runOrDiagnose(["docs", "create", title], { account });
|
|
37567
|
+
return runOrDiagnose(["docs", "create", pos(title)], { account });
|
|
36675
37568
|
});
|
|
36676
37569
|
server.registerTool("gog_docs_write", {
|
|
36677
37570
|
description: "Write text content to a Google Doc, replacing existing body content by default. Set append=true to add after existing content.",
|
|
@@ -36696,7 +37589,7 @@ function registerDocsTools(server) {
|
|
|
36696
37589
|
account: accountParam
|
|
36697
37590
|
})
|
|
36698
37591
|
}, async ({ docId, text, append, checkOrphans, bullets, bulletPreset, ordered, noBullets, indentStart, indentEnd, indentFirstLine, spaceAbove, spaceBelow, keepLinesTogether, keepWithNext, batch, account }) => {
|
|
36699
|
-
const args = ["docs", "write", docId, `--text=${text}`];
|
|
37592
|
+
const args = ["docs", "write", pos(docId), `--text=${text}`];
|
|
36700
37593
|
if (append) args.push("--append");
|
|
36701
37594
|
if (checkOrphans) args.push("--check-orphans");
|
|
36702
37595
|
if (bullets) args.push("--bullets");
|
|
@@ -36723,7 +37616,7 @@ function registerDocsTools(server) {
|
|
|
36723
37616
|
account: accountParam
|
|
36724
37617
|
})
|
|
36725
37618
|
}, async ({ docId, find, replace, account }) => {
|
|
36726
|
-
return runOrDiagnose(["docs", "find-replace", docId, find, replace], { account });
|
|
37619
|
+
return runOrDiagnose(["docs", "find-replace", pos(docId), pos(find), pos(replace)], { account });
|
|
36727
37620
|
});
|
|
36728
37621
|
server.registerTool("gog_docs_structure", {
|
|
36729
37622
|
description: "Show a Google Doc's structure with numbered paragraphs. Useful for understanding the document layout before making index-based edits.",
|
|
@@ -36733,18 +37626,31 @@ function registerDocsTools(server) {
|
|
|
36733
37626
|
account: accountParam
|
|
36734
37627
|
})
|
|
36735
37628
|
}, async ({ docId, account }) => {
|
|
36736
|
-
return runOrDiagnose(["docs", "structure", docId], { account });
|
|
37629
|
+
return runOrDiagnose(["docs", "structure", pos(docId)], { account });
|
|
36737
37630
|
});
|
|
36738
37631
|
registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
|
|
36739
37632
|
}
|
|
36740
37633
|
|
|
36741
37634
|
// src/tools/drive.ts
|
|
37635
|
+
function vetDriveRun(subcommand, _args) {
|
|
37636
|
+
return subcommand.toLowerCase() === "share" ? gatedElsewhere("gog drive share", "gog_drive_run", "grants access to a file", "gog_drive_share") : void 0;
|
|
37637
|
+
}
|
|
36742
37638
|
var GOOGLE_DOC_MIME = "application/vnd.google-apps.document";
|
|
37639
|
+
function shareTargetMeta(raw) {
|
|
37640
|
+
try {
|
|
37641
|
+
const { name, mimeType } = fileMeta(raw);
|
|
37642
|
+
return { name, mimeType };
|
|
37643
|
+
} catch {
|
|
37644
|
+
return {};
|
|
37645
|
+
}
|
|
37646
|
+
}
|
|
36743
37647
|
function fileMeta(raw) {
|
|
36744
37648
|
const parsed = JSON.parse(raw);
|
|
36745
37649
|
const f = parsed.file ?? parsed;
|
|
36746
|
-
|
|
37650
|
+
const size = f.size === void 0 ? void 0 : Number(f.size);
|
|
37651
|
+
return { name: f.name, mimeType: f.mimeType, size: Number.isFinite(size) ? size : void 0 };
|
|
36747
37652
|
}
|
|
37653
|
+
var MAX_DRIVE_READ_BYTES = MAX_INLINE_ATTACHMENT_BYTES;
|
|
36748
37654
|
var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime,size,webViewLink)";
|
|
36749
37655
|
function registerDriveTools(server) {
|
|
36750
37656
|
server.registerTool("gog_drive_ls", {
|
|
@@ -36788,7 +37694,7 @@ function registerDriveTools(server) {
|
|
|
36788
37694
|
account: accountParam
|
|
36789
37695
|
})
|
|
36790
37696
|
}, async ({ query, view, account }) => {
|
|
36791
|
-
return runOrDiagnose(["drive", "search", query], {
|
|
37697
|
+
return runOrDiagnose(["drive", "search", pos(query)], {
|
|
36792
37698
|
account,
|
|
36793
37699
|
stripMedia: resolveView(view, ["compact", "full"]) === "compact"
|
|
36794
37700
|
});
|
|
@@ -36808,7 +37714,7 @@ function registerDriveTools(server) {
|
|
|
36808
37714
|
account: accountParam
|
|
36809
37715
|
})
|
|
36810
37716
|
}, async ({ fileId, view, account }) => {
|
|
36811
|
-
return runOrDiagnose(["drive", "get", fileId], {
|
|
37717
|
+
return runOrDiagnose(["drive", "get", pos(fileId)], {
|
|
36812
37718
|
account,
|
|
36813
37719
|
stripMedia: resolveView(view, ["compact", "full"]) === "compact"
|
|
36814
37720
|
});
|
|
@@ -36821,7 +37727,7 @@ function registerDriveTools(server) {
|
|
|
36821
37727
|
account: accountParam
|
|
36822
37728
|
})
|
|
36823
37729
|
}, async ({ name, account }) => {
|
|
36824
|
-
return runOrDiagnose(["drive", "mkdir", name], { account });
|
|
37730
|
+
return runOrDiagnose(["drive", "mkdir", pos(name)], { account });
|
|
36825
37731
|
});
|
|
36826
37732
|
server.registerTool("gog_drive_rename", {
|
|
36827
37733
|
description: "Rename a file or folder in Google Drive.",
|
|
@@ -36832,7 +37738,7 @@ function registerDriveTools(server) {
|
|
|
36832
37738
|
account: accountParam
|
|
36833
37739
|
})
|
|
36834
37740
|
}, async ({ fileId, newName, account }) => {
|
|
36835
|
-
return runOrDiagnose(["drive", "rename", fileId, newName], { account });
|
|
37741
|
+
return runOrDiagnose(["drive", "rename", pos(fileId), pos(newName)], { account });
|
|
36836
37742
|
});
|
|
36837
37743
|
server.registerTool("gog_drive_move", {
|
|
36838
37744
|
description: "Move a file to a different folder in Google Drive.",
|
|
@@ -36843,7 +37749,7 @@ function registerDriveTools(server) {
|
|
|
36843
37749
|
account: accountParam
|
|
36844
37750
|
})
|
|
36845
37751
|
}, async ({ fileId, parentId, account }) => {
|
|
36846
|
-
return runOrDiagnose(["drive", "move", fileId, `--parent=${parentId}`], { account });
|
|
37752
|
+
return runOrDiagnose(["drive", "move", pos(fileId), `--parent=${parentId}`], { account });
|
|
36847
37753
|
});
|
|
36848
37754
|
server.registerTool("gog_drive_delete", {
|
|
36849
37755
|
description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible).",
|
|
@@ -36854,13 +37760,13 @@ function registerDriveTools(server) {
|
|
|
36854
37760
|
account: accountParam
|
|
36855
37761
|
})
|
|
36856
37762
|
}, async ({ fileId, permanent, account }) => {
|
|
36857
|
-
const args = ["drive", "delete", fileId];
|
|
37763
|
+
const args = ["drive", "delete", pos(fileId)];
|
|
36858
37764
|
if (permanent) args.push("--permanent");
|
|
36859
37765
|
args.push("--force");
|
|
36860
37766
|
return runOrDiagnose(args, { account });
|
|
36861
37767
|
});
|
|
36862
37768
|
server.registerTool("gog_drive_share", {
|
|
36863
|
-
description: "Share a Google Drive file or folder.",
|
|
37769
|
+
description: "Share a Google Drive file or folder. Granting access is the risk even though no email is sent (gog does not notify by default), so this reads the file and asks the MCP host to show the user a confirmation prompt naming it, who gets access and with what role; nothing is shared unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
36864
37770
|
annotations: { destructiveHint: true },
|
|
36865
37771
|
inputSchema: external_exports.object({
|
|
36866
37772
|
fileId: external_exports.string().describe("File or folder ID"),
|
|
@@ -36868,10 +37774,36 @@ function registerDriveTools(server) {
|
|
|
36868
37774
|
email: external_exports.string().optional().describe("User email (required when to=user)"),
|
|
36869
37775
|
domain: external_exports.string().optional().describe("Domain (required when to=domain)"),
|
|
36870
37776
|
role: external_exports.enum(["reader", "writer"]).optional().describe("Permission role (default: reader)"),
|
|
36871
|
-
account: accountParam
|
|
37777
|
+
account: accountParam,
|
|
37778
|
+
confirmToken: confirmTokenParam
|
|
36872
37779
|
})
|
|
36873
|
-
}, async ({ fileId, to, email: email3, domain: domain2, role, account }) => {
|
|
36874
|
-
const
|
|
37780
|
+
}, async ({ fileId, to, email: email3, domain: domain2, role, account, confirmToken }, ctx) => {
|
|
37781
|
+
const got = await runOrDiagnose(["drive", "get", pos(fileId)], { account });
|
|
37782
|
+
if (got.isError) return got;
|
|
37783
|
+
const file2 = { id: fileId, ...shareTargetMeta(resultText(got)) };
|
|
37784
|
+
const grant = {
|
|
37785
|
+
file: file2,
|
|
37786
|
+
to,
|
|
37787
|
+
...email3 ? { email: email3 } : {},
|
|
37788
|
+
...domain2 ? { domain: domain2 } : {},
|
|
37789
|
+
role: role ?? "reader",
|
|
37790
|
+
publicLink: to === "anyone"
|
|
37791
|
+
};
|
|
37792
|
+
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
37793
|
+
action: "drive.share",
|
|
37794
|
+
message: to === "anyone" ? "Review and confirm making this file available to ANYONE with the link:" : "Review and confirm this Drive share:",
|
|
37795
|
+
confirmationLabel: "Confirm that this access should be granted now.",
|
|
37796
|
+
details: grant,
|
|
37797
|
+
unsupportedNote: "Ask the user to share it themselves from Google Drive.",
|
|
37798
|
+
fallback: {
|
|
37799
|
+
tool: "gog_drive_share",
|
|
37800
|
+
account,
|
|
37801
|
+
confirmToken,
|
|
37802
|
+
subject: () => ({ target: fileId, payload: grant, preview: grant })
|
|
37803
|
+
}
|
|
37804
|
+
});
|
|
37805
|
+
if (confirmation) return confirmation;
|
|
37806
|
+
const args = ["drive", "share", pos(fileId), `--to=${to}`];
|
|
36875
37807
|
if (email3) args.push(`--email=${email3}`);
|
|
36876
37808
|
if (domain2) args.push(`--domain=${domain2}`);
|
|
36877
37809
|
if (role) args.push(`--role=${role}`);
|
|
@@ -36894,7 +37826,7 @@ function registerDriveTools(server) {
|
|
|
36894
37826
|
}, async ({ fileId, ocrLanguage, offset = 0, maxChars, account }) => {
|
|
36895
37827
|
let tempDocId;
|
|
36896
37828
|
try {
|
|
36897
|
-
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
37829
|
+
const { name, mimeType } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
|
|
36898
37830
|
let sourceId = fileId;
|
|
36899
37831
|
if (mimeType !== GOOGLE_DOC_MIME) {
|
|
36900
37832
|
const params = JSON.stringify({ fileId, ...ocrLanguage ? { ocrLanguage } : {} });
|
|
@@ -36939,13 +37871,13 @@ function registerDriveTools(server) {
|
|
|
36939
37871
|
return diagnose(err);
|
|
36940
37872
|
} finally {
|
|
36941
37873
|
if (tempDocId) {
|
|
36942
|
-
await run(["drive", "delete", tempDocId, "--permanent", "--force"], { account }).catch(() => {
|
|
37874
|
+
await run(["drive", "delete", pos(tempDocId), "--permanent", "--force"], { account }).catch(() => {
|
|
36943
37875
|
});
|
|
36944
37876
|
}
|
|
36945
37877
|
}
|
|
36946
37878
|
});
|
|
36947
37879
|
server.registerTool("gog_drive_read_bytes", {
|
|
36948
|
-
description: "Fetch a Drive file's raw bytes and return them base64-encoded as an embedded resource \u2014 the generic fallback for callers that want the file itself (to parse locally) rather than extracted text. For readable text from a PDF, prefer gog_drive_extract_text.",
|
|
37880
|
+
description: "Fetch a Drive file's raw bytes and return them base64-encoded as an embedded resource \u2014 the generic fallback for callers that want the file itself (to parse locally) rather than extracted text. Files over 8 MiB are refused (checked before downloading). For readable text from a PDF, prefer gog_drive_extract_text.",
|
|
36949
37881
|
annotations: { readOnlyHint: true },
|
|
36950
37882
|
inputSchema: external_exports.object({
|
|
36951
37883
|
fileId: external_exports.string().describe("Drive file ID"),
|
|
@@ -36953,9 +37885,17 @@ function registerDriveTools(server) {
|
|
|
36953
37885
|
})
|
|
36954
37886
|
}, async ({ fileId, account }) => {
|
|
36955
37887
|
try {
|
|
36956
|
-
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
37888
|
+
const { name, mimeType, size } = fileMeta(await run(["drive", "get", pos(fileId)], { account }));
|
|
37889
|
+
if (size !== void 0 && size > MAX_DRIVE_READ_BYTES) {
|
|
37890
|
+
return errorResult(
|
|
37891
|
+
`${name ?? fileId} is ${size} bytes, over the 8 MiB this tool returns inline. Use gog_drive_extract_text for its text, or share its webViewLink (gog_drive_get) instead.`
|
|
37892
|
+
);
|
|
37893
|
+
}
|
|
36957
37894
|
const params = JSON.stringify({ fileId, alt: "media" });
|
|
36958
|
-
const blob = await runBinary(
|
|
37895
|
+
const blob = await runBinary(
|
|
37896
|
+
["api", "call", "drive", "v3", "files.get", `--params=${params}`],
|
|
37897
|
+
{ account, maxOutputBytes: MAX_DRIVE_READ_BYTES }
|
|
37898
|
+
);
|
|
36959
37899
|
const type = mimeType ?? "application/octet-stream";
|
|
36960
37900
|
const unrenderableHint = type.startsWith("image/") ? "" : ` If your client does not render a ${type} resource, gog_drive_extract_text returns the same file as text (PDFs included, via OCR).`;
|
|
36961
37901
|
return {
|
|
@@ -36975,7 +37915,7 @@ function registerDriveTools(server) {
|
|
|
36975
37915
|
return diagnose(err);
|
|
36976
37916
|
}
|
|
36977
37917
|
});
|
|
36978
|
-
registerRunTool(server, { service: "drive", examples: '"copy", "
|
|
37918
|
+
registerRunTool(server, { service: "drive", examples: '"copy", "download", "permissions"', vet: vetDriveRun });
|
|
36979
37919
|
}
|
|
36980
37920
|
|
|
36981
37921
|
// src/gmail-results.ts
|
|
@@ -37003,7 +37943,7 @@ async function countMatches(method, itemsKey, query, account) {
|
|
|
37003
37943
|
maxResults: COUNT_PROBE_PAGE_SIZE,
|
|
37004
37944
|
fields: `${itemsKey}/id,nextPageToken`
|
|
37005
37945
|
});
|
|
37006
|
-
const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
|
|
37946
|
+
const raw = await run(["api", "call", "gmail", "v1", pos(method), `--params=${params}`], { account });
|
|
37007
37947
|
const parsed = JSON.parse(raw);
|
|
37008
37948
|
const items = parsed[itemsKey];
|
|
37009
37949
|
if (!Array.isArray(items)) return {};
|
|
@@ -37042,7 +37982,7 @@ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
|
37042
37982
|
const result = await runPage(token);
|
|
37043
37983
|
const parsed = parsePage(result, itemsKey);
|
|
37044
37984
|
if (parsed === void 0) {
|
|
37045
|
-
return base === void 0 ? result : finish(base, itemsKey, merged, token);
|
|
37985
|
+
return base === void 0 ? result : finish(base, itemsKey, merged, token, pageErrorText(result, itemsKey, pages + 1));
|
|
37046
37986
|
}
|
|
37047
37987
|
base = parsed;
|
|
37048
37988
|
merged.push(...parsed[itemsKey]);
|
|
@@ -37073,10 +38013,18 @@ function parsePage(result, itemsKey) {
|
|
|
37073
38013
|
const obj = parsed;
|
|
37074
38014
|
return Array.isArray(obj[itemsKey]) ? obj : void 0;
|
|
37075
38015
|
}
|
|
37076
|
-
|
|
38016
|
+
var PAGE_ERROR_MAX = 2e3;
|
|
38017
|
+
function pageErrorText(result, itemsKey, n) {
|
|
38018
|
+
const first = result.content[0];
|
|
38019
|
+
const text = first?.type === "text" ? first.text.slice(0, PAGE_ERROR_MAX) : "";
|
|
38020
|
+
if (result.isError) return `page ${n} failed${text ? `: ${text}` : ""}`;
|
|
38021
|
+
return `page ${n} returned output that is not a ${itemsKey} list`;
|
|
38022
|
+
}
|
|
38023
|
+
function finish(base, itemsKey, merged, token, pageError) {
|
|
37077
38024
|
const out = { ...base, [itemsKey]: merged };
|
|
37078
38025
|
if (token === void 0) delete out.nextPageToken;
|
|
37079
38026
|
else out.nextPageToken = token;
|
|
38027
|
+
if (pageError !== void 0) out.pageError = pageError;
|
|
37080
38028
|
return rawTextResult(JSON.stringify(out));
|
|
37081
38029
|
}
|
|
37082
38030
|
|
|
@@ -37090,16 +38038,19 @@ var STAGING_TWIN = {
|
|
|
37090
38038
|
"gmail.reply-all": "gog_gmail_drafts_reply_all",
|
|
37091
38039
|
"gmail.send": "gog_gmail_drafts_create"
|
|
37092
38040
|
};
|
|
37093
|
-
|
|
38041
|
+
var UNSUPPORTED_NOTE2 = {
|
|
38042
|
+
"gmail.drafts-send": "The draft is still saved: ask the user to review it and send it from Gmail."
|
|
38043
|
+
};
|
|
38044
|
+
async function requireGmailDispatchConfirmation(ctx, op, details, fallback) {
|
|
37094
38045
|
const twin = STAGING_TWIN[op];
|
|
37095
|
-
|
|
38046
|
+
const note = twin ? `Stage it with ${twin} instead; the user can review the draft and send it from Gmail (gog_gmail_drafts_send also asks for confirmation).` : UNSUPPORTED_NOTE2[op];
|
|
38047
|
+
return requireDispatchConfirmation(ctx, {
|
|
37096
38048
|
action: op,
|
|
37097
|
-
message: "Review and confirm this email dispatch:",
|
|
38049
|
+
message: op === "gmail.filter-forward" ? "Review and confirm this mail-forwarding filter:" : "Review and confirm this email dispatch:",
|
|
37098
38050
|
details,
|
|
37099
|
-
confirmationLabel: "Confirm that this email should be sent now.",
|
|
37100
|
-
|
|
37101
|
-
|
|
37102
|
-
} : {}
|
|
38051
|
+
confirmationLabel: op === "gmail.filter-forward" ? "Confirm that matching mail should be forwarded automatically from now on." : "Confirm that this email should be sent now.",
|
|
38052
|
+
unsupportedNote: note,
|
|
38053
|
+
...fallback ? { fallback: { instruction: CONFIRM_SEND_INSTRUCTION, ...fallback } } : {}
|
|
37103
38054
|
});
|
|
37104
38055
|
}
|
|
37105
38056
|
var EMAIL_PATTERN = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+/gi;
|
|
@@ -37153,10 +38104,6 @@ function logGmailDispatch(tool, recipients, account) {
|
|
|
37153
38104
|
process.stderr.write(`${JSON.stringify(event)}
|
|
37154
38105
|
`);
|
|
37155
38106
|
}
|
|
37156
|
-
function resultText(result) {
|
|
37157
|
-
const first = result.content[0];
|
|
37158
|
-
return first && first.type === "text" && typeof first.text === "string" ? first.text : "{}";
|
|
37159
|
-
}
|
|
37160
38107
|
|
|
37161
38108
|
// src/tools/gmail.ts
|
|
37162
38109
|
var replySchema = {
|
|
@@ -37170,7 +38117,7 @@ var replySchema = {
|
|
|
37170
38117
|
remove: external_exports.array(external_exports.string()).optional().describe("Remove these recipients from all fields (repeatable) \u2014 e.g. to drop someone from a reply-all."),
|
|
37171
38118
|
subject: external_exports.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
|
|
37172
38119
|
noQuote: external_exports.boolean().optional().describe("Do not include the original message quoted below the reply (default: the original is quoted)"),
|
|
37173
|
-
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension.`),
|
|
38120
|
+
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
|
|
37174
38121
|
attachInline: attachInlineParam,
|
|
37175
38122
|
from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
|
|
37176
38123
|
autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
|
|
@@ -37179,8 +38126,14 @@ var replySchema = {
|
|
|
37179
38126
|
signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
|
|
37180
38127
|
account: accountParam
|
|
37181
38128
|
};
|
|
38129
|
+
function confineReplyPaths(f) {
|
|
38130
|
+
confinePaths(f.attach, "attach");
|
|
38131
|
+
if (f.bodyHtmlFile) confinePath(f.bodyHtmlFile, "bodyHtmlFile");
|
|
38132
|
+
if (f.signatureFile) confinePath(f.signatureFile, "signatureFile");
|
|
38133
|
+
}
|
|
37182
38134
|
function appendReplyFlags(args, f) {
|
|
37183
38135
|
assertNotBoth("bodyHtml", "bodyHtmlFile", f.bodyHtml, f.bodyHtmlFile);
|
|
38136
|
+
confineReplyPaths(f);
|
|
37184
38137
|
if (f.body) args.push(payloadArg("body", "body-file", f.body));
|
|
37185
38138
|
if (f.bodyHtml) args.push(payloadArg("body-html", "body-html-file", f.bodyHtml, "html"));
|
|
37186
38139
|
else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
|
|
@@ -37198,7 +38151,7 @@ function appendReplyFlags(args, f) {
|
|
|
37198
38151
|
if (f.signatureFile) args.push(`--signature-file=${f.signatureFile}`);
|
|
37199
38152
|
args.push(f.autoFromAddressedAlias ? "--auto-from-addressed-alias" : "--auto-from-addressed-alias=false");
|
|
37200
38153
|
}
|
|
37201
|
-
var sendReplySchema = external_exports.object(replySchema);
|
|
38154
|
+
var sendReplySchema = external_exports.object({ ...replySchema, confirmToken: confirmTokenParam });
|
|
37202
38155
|
function parseMetadataHeaders(raw) {
|
|
37203
38156
|
let parsed;
|
|
37204
38157
|
try {
|
|
@@ -37223,27 +38176,110 @@ function computeReplyRecipients(kind, headers, flags) {
|
|
|
37223
38176
|
}
|
|
37224
38177
|
return emails;
|
|
37225
38178
|
}
|
|
37226
|
-
|
|
37227
|
-
|
|
38179
|
+
function metadataThreadId(raw) {
|
|
38180
|
+
try {
|
|
38181
|
+
const threadId = JSON.parse(raw)?.message?.threadId;
|
|
38182
|
+
return typeof threadId === "string" ? threadId : void 0;
|
|
38183
|
+
} catch {
|
|
38184
|
+
return void 0;
|
|
38185
|
+
}
|
|
38186
|
+
}
|
|
38187
|
+
function replyTokenSubject(kind, messageId, account, headers, threadId, flags) {
|
|
38188
|
+
const removed = new Set(extractEmails(...flags.remove ?? []));
|
|
38189
|
+
const keep = (emails) => emails.filter((e) => !removed.has(e));
|
|
38190
|
+
const to = keep(extractEmails(headers.from, ...kind === "reply-all" ? [headers.to] : [], ...flags.to ?? []));
|
|
38191
|
+
const cc = keep(extractEmails(...kind === "reply-all" ? [headers.cc] : [], ...flags.cc ?? []));
|
|
38192
|
+
const bcc = keep(extractEmails(...flags.bcc ?? []));
|
|
38193
|
+
const attachments = attachmentDetails(flags.attach, flags.attachInline);
|
|
38194
|
+
const subject = flags.subject || (headers.subject ? `Re: ${headers.subject}` : void 0);
|
|
38195
|
+
const from = flags.from ?? (flags.autoFromAddressedAlias ? "the send-as alias the original was addressed to" : senderPreview(account));
|
|
38196
|
+
const localFiles = attachmentDetails([flags.bodyHtmlFile, flags.signatureFile].filter((p) => Boolean(p)), void 0);
|
|
38197
|
+
return {
|
|
38198
|
+
target: messageId,
|
|
38199
|
+
payload: {
|
|
38200
|
+
kind,
|
|
38201
|
+
to,
|
|
38202
|
+
cc,
|
|
38203
|
+
bcc,
|
|
38204
|
+
subject,
|
|
38205
|
+
from,
|
|
38206
|
+
body: flags.body,
|
|
38207
|
+
bodyHtml: flags.bodyHtml,
|
|
38208
|
+
localFiles,
|
|
38209
|
+
signature: Boolean(flags.signature),
|
|
38210
|
+
signatureFrom: flags.signatureFrom,
|
|
38211
|
+
attachments,
|
|
38212
|
+
quote: !flags.noQuote,
|
|
38213
|
+
threadId,
|
|
38214
|
+
inReplyTo: headers.message_id,
|
|
38215
|
+
references: headers.references
|
|
38216
|
+
},
|
|
38217
|
+
preview: {
|
|
38218
|
+
from,
|
|
38219
|
+
to,
|
|
38220
|
+
cc,
|
|
38221
|
+
bcc,
|
|
38222
|
+
subject,
|
|
38223
|
+
body: flags.body,
|
|
38224
|
+
...flags.bodyHtml ? { bodyHtml: flags.bodyHtml } : {},
|
|
38225
|
+
...flags.bodyHtmlFile ? { bodyHtmlFile: flags.bodyHtmlFile } : {},
|
|
38226
|
+
...flags.signature || flags.signatureFrom || flags.signatureFile ? { signature: flags.signatureFile ?? flags.signatureFrom ?? "the Gmail signature of the sending address" } : {},
|
|
38227
|
+
quotesOriginal: !flags.noQuote,
|
|
38228
|
+
attachments: attachmentPreview(attachments),
|
|
38229
|
+
replyingToMessageId: messageId,
|
|
38230
|
+
threadId,
|
|
38231
|
+
inReplyTo: headers.message_id
|
|
38232
|
+
}
|
|
38233
|
+
};
|
|
38234
|
+
}
|
|
38235
|
+
async function sendReply(kind, toolName, messageId, account, flags, ctx, confirmToken) {
|
|
38236
|
+
const metaResult = await runOrDiagnose(["gmail", "get", pos(messageId), "--format=metadata"], { account });
|
|
37228
38237
|
if (metaResult.isError) return metaResult;
|
|
37229
|
-
const
|
|
38238
|
+
const metaText = resultText(metaResult);
|
|
38239
|
+
const headers = parseMetadataHeaders(metaText);
|
|
37230
38240
|
const recipients = computeReplyRecipients(kind, headers, flags);
|
|
37231
|
-
const confirmation = requireGmailDispatchConfirmation(ctx, replyDispatchOp(kind), {
|
|
38241
|
+
const confirmation = await requireGmailDispatchConfirmation(ctx, replyDispatchOp(kind), {
|
|
37232
38242
|
messageId,
|
|
37233
38243
|
recipients,
|
|
37234
38244
|
recipientCount: recipients.length,
|
|
37235
38245
|
subject: flags.subject || (headers.subject ? `Re: ${headers.subject}` : void 0),
|
|
37236
38246
|
quoting: !flags.noQuote,
|
|
37237
38247
|
bodyLength: (flags.body ?? flags.bodyHtml ?? "").length,
|
|
37238
|
-
|
|
38248
|
+
bodyPreview: bodyPreview(flags.body ?? flags.bodyHtml),
|
|
38249
|
+
bodyHtmlFile: flags.bodyHtmlFile,
|
|
38250
|
+
attachmentCount: (flags.attach?.length ?? 0) + (flags.attachInline?.length ?? 0),
|
|
38251
|
+
attachments: attachmentNames(flags.attach, flags.attachInline)
|
|
38252
|
+
}, {
|
|
38253
|
+
tool: toolName,
|
|
38254
|
+
account,
|
|
38255
|
+
confirmToken,
|
|
38256
|
+
// The metadata read above IS the phase-2 re-read: it runs on every call,
|
|
38257
|
+
// so the original's Message-ID/References are fresh here.
|
|
38258
|
+
subject: () => replyTokenSubject(kind, messageId, account, headers, metadataThreadId(metaText), flags)
|
|
37239
38259
|
});
|
|
37240
38260
|
if (confirmation) return confirmation;
|
|
37241
|
-
const args = ["gmail", kind, messageId];
|
|
38261
|
+
const args = ["gmail", kind, pos(messageId)];
|
|
37242
38262
|
appendReplyFlags(args, flags);
|
|
37243
38263
|
const result = await runOrDiagnose(args, { account });
|
|
37244
38264
|
if (!result.isError) logGmailDispatch(toolName, recipients, account);
|
|
37245
38265
|
return result;
|
|
37246
38266
|
}
|
|
38267
|
+
var GMAIL_RUN_BLOCKED_SETTINGS = /* @__PURE__ */ new Set(["forwarding", "autoforward", "filters", "delegates"]);
|
|
38268
|
+
function vetGmailRun(subcommand, args) {
|
|
38269
|
+
if (subcommand === "autoreply") {
|
|
38270
|
+
return "gog gmail autoreply sends mail and is not available through gog_gmail_run. Use gog_gmail_autoreply, which asks the user to confirm.";
|
|
38271
|
+
}
|
|
38272
|
+
if (GMAIL_RUN_BLOCKED_SETTINGS.has(subcommand)) {
|
|
38273
|
+
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
|
+
}
|
|
38275
|
+
if (subcommand === "settings") {
|
|
38276
|
+
const blocked = hasCommandWord(args, GMAIL_RUN_BLOCKED_SETTINGS);
|
|
38277
|
+
if (blocked) {
|
|
38278
|
+
return `gog gmail settings ${blocked} can forward or hand over mail and is not available through gog_gmail_run. Use the dedicated gog_gmail_* tool instead.`;
|
|
38279
|
+
}
|
|
38280
|
+
}
|
|
38281
|
+
return void 0;
|
|
38282
|
+
}
|
|
37247
38283
|
function registerGmailTools(server) {
|
|
37248
38284
|
server.registerTool("gog_gmail_search", {
|
|
37249
38285
|
description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
|
|
@@ -37259,7 +38295,7 @@ function registerGmailTools(server) {
|
|
|
37259
38295
|
account: accountParam
|
|
37260
38296
|
})
|
|
37261
38297
|
}, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
|
|
37262
|
-
const args = ["gmail", "search", query];
|
|
38298
|
+
const args = ["gmail", "search", pos(query)];
|
|
37263
38299
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
37264
38300
|
if (all) args.push("--all");
|
|
37265
38301
|
if (fromContact) args.push(`--from-contact=${fromContact}`);
|
|
@@ -37290,13 +38326,13 @@ function registerGmailTools(server) {
|
|
|
37290
38326
|
account: accountParam
|
|
37291
38327
|
})
|
|
37292
38328
|
}, async ({ messageId, format, sanitizeContent, account }) => {
|
|
37293
|
-
const args = ["gmail", "get", messageId];
|
|
38329
|
+
const args = ["gmail", "get", pos(messageId)];
|
|
37294
38330
|
if (format) args.push(`--format=${format}`);
|
|
37295
38331
|
if (sanitizeContent) args.push("--sanitize-content");
|
|
37296
38332
|
return runOrDiagnose(args, { account });
|
|
37297
38333
|
});
|
|
37298
38334
|
server.registerTool("gog_gmail_send", {
|
|
37299
|
-
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the recipients, subject,
|
|
38335
|
+
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the recipients, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. Two ways to attach a file: `attach` takes paths READ ON THE GOG SERVER, and `attachInline` takes the bytes themselves. Use attachInline unless you know the file exists on the same machine gog runs on \u2014 on the hosted connector and any remote deployment there is no shared filesystem, so no path you can name resolves there and `attach` will fail with "no such file or directory". When either is used, the JSON result echoes the attached filenames and byte sizes \u2014 check it to confirm the files were embedded. NOT the tool for answering a message: replyToMessageId only files this in the right thread \u2014 the subject, recipients and body are entirely yours, and the original is not quoted unless you set quote. Use gog_gmail_reply / gog_gmail_reply_all instead, which inherit all three.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
37300
38336
|
annotations: { destructiveHint: true },
|
|
37301
38337
|
inputSchema: external_exports.object({
|
|
37302
38338
|
to: external_exports.string().describe("Recipient(s), comma-separated"),
|
|
@@ -37307,11 +38343,13 @@ function registerGmailTools(server) {
|
|
|
37307
38343
|
replyToMessageId: external_exports.string().optional().describe('Message ID to thread this message against \u2014 sets In-Reply-To/References only. It does NOT quote the original (pass quote for that), inherit its recipients, or prefix the subject with "Re:". For an actual reply use gog_gmail_reply.'),
|
|
37308
38344
|
threadId: external_exports.string().optional().describe("Thread ID to thread this message within. Same caveat as replyToMessageId: threading only, no quote and no inherited subject or recipients."),
|
|
37309
38345
|
quote: external_exports.boolean().optional().describe("Include the original message quoted below the body. Requires replyToMessageId or threadId. gog quotes by DEFAULT on gmail reply but never on gmail send, so without this a threaded send arrives with the original nowhere in it."),
|
|
37310
|
-
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Each file is read on the server, base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment.`),
|
|
38346
|
+
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Each file is read on the server, base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
|
|
37311
38347
|
attachInline: attachInlineParam,
|
|
37312
|
-
account: accountParam
|
|
38348
|
+
account: accountParam,
|
|
38349
|
+
confirmToken: confirmTokenParam
|
|
37313
38350
|
})
|
|
37314
|
-
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account }, ctx) => {
|
|
38351
|
+
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account, confirmToken }, ctx) => {
|
|
38352
|
+
confinePaths(attach, "attach");
|
|
37315
38353
|
const args = ["gmail", "send", `--to=${to}`, `--subject=${subject}`, payloadArg("body", "body-file", body)];
|
|
37316
38354
|
if (cc) args.push(`--cc=${cc}`);
|
|
37317
38355
|
if (bcc) args.push(`--bcc=${bcc}`);
|
|
@@ -37322,7 +38360,7 @@ function registerGmailTools(server) {
|
|
|
37322
38360
|
const inline = inlineAttachmentArgs("attach", attachInline, args);
|
|
37323
38361
|
args.push(...inline);
|
|
37324
38362
|
const recipients = extractEmails(to, cc, bcc);
|
|
37325
|
-
const confirmation = requireGmailDispatchConfirmation(ctx, "gmail.send", {
|
|
38363
|
+
const confirmation = await requireGmailDispatchConfirmation(ctx, "gmail.send", {
|
|
37326
38364
|
to,
|
|
37327
38365
|
cc,
|
|
37328
38366
|
bcc,
|
|
@@ -37330,9 +38368,37 @@ function registerGmailTools(server) {
|
|
|
37330
38368
|
recipientCount: recipients.length,
|
|
37331
38369
|
subject,
|
|
37332
38370
|
bodyLength: body.length,
|
|
38371
|
+
bodyPreview: bodyPreview(body),
|
|
37333
38372
|
threaded: Boolean(replyToMessageId || threadId),
|
|
37334
38373
|
quoting: Boolean(quote),
|
|
37335
|
-
attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0)
|
|
38374
|
+
attachmentCount: (attach?.length ?? 0) + (attachInline?.length ?? 0),
|
|
38375
|
+
attachments: attachmentNames(attach, attachInline)
|
|
38376
|
+
}, {
|
|
38377
|
+
tool: "gog_gmail_send",
|
|
38378
|
+
account,
|
|
38379
|
+
confirmToken,
|
|
38380
|
+
// Nothing is stored between the phases: the payload IS the arguments,
|
|
38381
|
+
// so phase 2 must repeat them and any difference is a changed send.
|
|
38382
|
+
subject: () => {
|
|
38383
|
+
const attachments = attachmentDetails(attach, attachInline);
|
|
38384
|
+
const from = senderPreview(account);
|
|
38385
|
+
return {
|
|
38386
|
+
target: replyToMessageId ?? threadId ?? "",
|
|
38387
|
+
payload: { from, to, cc, bcc, subject, body, attachments, threadId, inReplyTo: replyToMessageId, quote: Boolean(quote) },
|
|
38388
|
+
preview: {
|
|
38389
|
+
from,
|
|
38390
|
+
to,
|
|
38391
|
+
cc,
|
|
38392
|
+
bcc,
|
|
38393
|
+
subject,
|
|
38394
|
+
body,
|
|
38395
|
+
attachments: attachmentPreview(attachments),
|
|
38396
|
+
threadId,
|
|
38397
|
+
inReplyTo: replyToMessageId,
|
|
38398
|
+
quotesOriginal: Boolean(quote)
|
|
38399
|
+
}
|
|
38400
|
+
};
|
|
38401
|
+
}
|
|
37336
38402
|
});
|
|
37337
38403
|
if (confirmation) return confirmation;
|
|
37338
38404
|
const result = await runOrDiagnose(args, { account });
|
|
@@ -37340,22 +38406,32 @@ function registerGmailTools(server) {
|
|
|
37340
38406
|
return result;
|
|
37341
38407
|
});
|
|
37342
38408
|
server.registerTool("gog_gmail_reply", {
|
|
37343
|
-
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipient, subject,
|
|
38409
|
+
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipient, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. To STAGE a reply instead of sending it, use gog_gmail_drafts_reply (gogcli-mcp-gmail only), which never needs confirmation. Reply to a Gmail message (goes to the original sender only). USE THIS, not gog_gmail_send, whenever you are answering a message: it threads off the original AND inherits its "Re:" subject and quotes its body below yours, which gog_gmail_send does not \u2014 a send with replyToMessageId lands in the right thread but reads as a brand-new message, with the original nowhere in it. To answer every participant use gog_gmail_reply_all. The gogcli-mcp-gmail package adds two more routes with the same composition: gog_gmail_autoreply to reply across every message matching a query, and gog_gmail_drafts_reply to stage this exact reply as a draft instead of sending it.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
37344
38410
|
annotations: { destructiveHint: true },
|
|
37345
38411
|
inputSchema: sendReplySchema
|
|
37346
|
-
}, async ({ messageId, account, ...flags }, ctx) => {
|
|
38412
|
+
}, async ({ messageId, account, confirmToken, ...flags }, ctx) => {
|
|
37347
38413
|
assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
|
|
37348
|
-
|
|
38414
|
+
confineReplyPaths(flags);
|
|
38415
|
+
return sendReply("reply", "gog_gmail_reply", messageId, account, flags, ctx, confirmToken);
|
|
37349
38416
|
});
|
|
37350
38417
|
server.registerTool("gog_gmail_reply_all", {
|
|
37351
|
-
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipients, subject,
|
|
38418
|
+
description: 'SENDS MAIL \u2014 asks the MCP host to show a confirmation prompt with the resolved recipients, subject, a preview of the body and the attachment names. Mail is sent only after the user accepts that prompt. To STAGE a reply-all instead of sending it, use gog_gmail_drafts_reply_all (gogcli-mcp-gmail only), which never needs confirmation. Reply to all participants of a Gmail message (the sender plus every To/Cc recipient). Same inherited "Re:" subject and quoted original as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
37352
38419
|
annotations: { destructiveHint: true },
|
|
37353
38420
|
inputSchema: sendReplySchema
|
|
37354
|
-
}, async ({ messageId, account, ...flags }, ctx) => {
|
|
38421
|
+
}, async ({ messageId, account, confirmToken, ...flags }, ctx) => {
|
|
37355
38422
|
assertNotBoth("bodyHtml", "bodyHtmlFile", flags.bodyHtml, flags.bodyHtmlFile);
|
|
37356
|
-
|
|
38423
|
+
confineReplyPaths(flags);
|
|
38424
|
+
return sendReply("reply-all", "gog_gmail_reply_all", messageId, account, flags, ctx, confirmToken);
|
|
38425
|
+
});
|
|
38426
|
+
registerRunTool(server, {
|
|
38427
|
+
service: "gmail",
|
|
38428
|
+
examples: '"archive", "mark-read", "labels"',
|
|
38429
|
+
// gog's --gmail-no-send blocks send/reply/forward/drafts send and all of
|
|
38430
|
+
// their aliases at runtime (verified on gog 0.41.0). Sending goes through the
|
|
38431
|
+
// confirmed tools, never this escape hatch.
|
|
38432
|
+
gmailNoSend: true,
|
|
38433
|
+
vet: vetGmailRun
|
|
37357
38434
|
});
|
|
37358
|
-
registerRunTool(server, { service: "gmail", examples: '"archive", "mark-read", "labels"' });
|
|
37359
38435
|
}
|
|
37360
38436
|
|
|
37361
38437
|
// src/tools/sheets-a1.ts
|
|
@@ -37424,7 +38500,7 @@ function registerSheetsTools(server) {
|
|
|
37424
38500
|
account: accountParam
|
|
37425
38501
|
})
|
|
37426
38502
|
}, async ({ spreadsheetId, range, account }) => {
|
|
37427
|
-
return runOrDiagnose(["sheets", "get", spreadsheetId, range], { account });
|
|
38503
|
+
return runOrDiagnose(["sheets", "get", pos(spreadsheetId), pos(range)], { account });
|
|
37428
38504
|
});
|
|
37429
38505
|
server.registerTool("gog_sheets_update", {
|
|
37430
38506
|
description: 'Write values to a Google Sheets range, overwriting existing content. Values may be strings, numbers, booleans, or null. Strings starting with "=" are interpreted as formulas (e.g. "=SUM(A1:A10)").',
|
|
@@ -37444,7 +38520,7 @@ function registerSheetsTools(server) {
|
|
|
37444
38520
|
const readRange = expandAnchorRange(range, values.length, cols);
|
|
37445
38521
|
let existing;
|
|
37446
38522
|
try {
|
|
37447
|
-
existing = await run(["sheets", "get", spreadsheetId, readRange], { account });
|
|
38523
|
+
existing = await run(["sheets", "get", pos(spreadsheetId), pos(readRange)], { account });
|
|
37448
38524
|
} catch (err) {
|
|
37449
38525
|
return diagnose(err);
|
|
37450
38526
|
}
|
|
@@ -37456,7 +38532,7 @@ function registerSheetsTools(server) {
|
|
|
37456
38532
|
);
|
|
37457
38533
|
}
|
|
37458
38534
|
}
|
|
37459
|
-
const args = ["sheets", "update", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
38535
|
+
const args = ["sheets", "update", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
|
|
37460
38536
|
if (dry_run) args.push("--dry-run");
|
|
37461
38537
|
if (fail_on_formula_error) args.push("--fail-on-formula-error");
|
|
37462
38538
|
return runOrDiagnose(args, { account });
|
|
@@ -37472,7 +38548,7 @@ function registerSheetsTools(server) {
|
|
|
37472
38548
|
account: accountParam
|
|
37473
38549
|
})
|
|
37474
38550
|
}, async ({ spreadsheetId, range, values, account, dry_run }) => {
|
|
37475
|
-
const args = ["sheets", "append", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
38551
|
+
const args = ["sheets", "append", pos(spreadsheetId), pos(range), `--values-json=${JSON.stringify(values)}`];
|
|
37476
38552
|
if (dry_run) args.push("--dry-run");
|
|
37477
38553
|
return runOrDiagnose(args, { account });
|
|
37478
38554
|
});
|
|
@@ -37486,7 +38562,7 @@ function registerSheetsTools(server) {
|
|
|
37486
38562
|
account: accountParam
|
|
37487
38563
|
})
|
|
37488
38564
|
}, async ({ spreadsheetId, range, account, dry_run }) => {
|
|
37489
|
-
const args = ["sheets", "clear", spreadsheetId, range];
|
|
38565
|
+
const args = ["sheets", "clear", pos(spreadsheetId), pos(range)];
|
|
37490
38566
|
if (dry_run) args.push("--dry-run");
|
|
37491
38567
|
return runOrDiagnose(args, { account });
|
|
37492
38568
|
});
|
|
@@ -37498,7 +38574,7 @@ function registerSheetsTools(server) {
|
|
|
37498
38574
|
account: accountParam
|
|
37499
38575
|
})
|
|
37500
38576
|
}, async ({ spreadsheetId, account }) => {
|
|
37501
|
-
return runOrDiagnose(["sheets", "metadata", spreadsheetId], { account });
|
|
38577
|
+
return runOrDiagnose(["sheets", "metadata", pos(spreadsheetId)], { account });
|
|
37502
38578
|
});
|
|
37503
38579
|
server.registerTool("gog_sheets_create", {
|
|
37504
38580
|
description: "Create a new Google Spreadsheet. Returns JSON with the new spreadsheetId and URL.",
|
|
@@ -37508,7 +38584,7 @@ function registerSheetsTools(server) {
|
|
|
37508
38584
|
account: accountParam
|
|
37509
38585
|
})
|
|
37510
38586
|
}, async ({ title, account }) => {
|
|
37511
|
-
return runOrDiagnose(["sheets", "create", title], { account });
|
|
38587
|
+
return runOrDiagnose(["sheets", "create", pos(title)], { account });
|
|
37512
38588
|
});
|
|
37513
38589
|
server.registerTool("gog_sheets_find_replace", {
|
|
37514
38590
|
description: "Find and replace text across an entire Google Spreadsheet.",
|
|
@@ -37520,7 +38596,7 @@ function registerSheetsTools(server) {
|
|
|
37520
38596
|
account: accountParam
|
|
37521
38597
|
})
|
|
37522
38598
|
}, async ({ spreadsheetId, find, replace, account }) => {
|
|
37523
|
-
return runOrDiagnose(["sheets", "find-replace", spreadsheetId, find, replace], { account });
|
|
38599
|
+
return runOrDiagnose(["sheets", "find-replace", pos(spreadsheetId), pos(find), pos(replace)], { account });
|
|
37524
38600
|
});
|
|
37525
38601
|
registerRunTool(server, { service: "sheets", examples: '"freeze", "add-tab", "rename-tab"' });
|
|
37526
38602
|
}
|
|
@@ -37528,8 +38604,9 @@ function registerSheetsTools(server) {
|
|
|
37528
38604
|
// src/tools/slides.ts
|
|
37529
38605
|
function registerSlidesTools(server) {
|
|
37530
38606
|
server.registerTool("gog_slides_export", {
|
|
37531
|
-
description: "Export a Google Slides presentation to a local file (pdf or pptx).",
|
|
37532
|
-
|
|
38607
|
+
description: "Export a Google Slides presentation to a local file (pdf or pptx). The out path must be inside the server's GOG_FILE_ROOTS directories.",
|
|
38608
|
+
// Writes a file on the gog host and can overwrite one: not read-only.
|
|
38609
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
37533
38610
|
inputSchema: external_exports.object({
|
|
37534
38611
|
presentationId: external_exports.string().describe("Presentation ID"),
|
|
37535
38612
|
out: external_exports.string().optional().describe("Output file path"),
|
|
@@ -37538,7 +38615,8 @@ function registerSlidesTools(server) {
|
|
|
37538
38615
|
account: accountParam
|
|
37539
38616
|
})
|
|
37540
38617
|
}, async ({ presentationId, out, format, overwrite, account }) => {
|
|
37541
|
-
|
|
38618
|
+
if (out) confinePath(out, "out");
|
|
38619
|
+
const args = ["slides", "export", pos(presentationId)];
|
|
37542
38620
|
if (out) args.push(`--out=${out}`);
|
|
37543
38621
|
if (format) args.push(`--format=${format}`);
|
|
37544
38622
|
if (overwrite) args.push("--overwrite");
|
|
@@ -37552,7 +38630,7 @@ function registerSlidesTools(server) {
|
|
|
37552
38630
|
account: accountParam
|
|
37553
38631
|
})
|
|
37554
38632
|
}, async ({ presentationId, account }) => {
|
|
37555
|
-
return runOrDiagnose(["slides", "info", presentationId], { account });
|
|
38633
|
+
return runOrDiagnose(["slides", "info", pos(presentationId)], { account });
|
|
37556
38634
|
});
|
|
37557
38635
|
server.registerTool("gog_slides_create", {
|
|
37558
38636
|
description: "Create a new Google Slides presentation, optionally in a folder or copying from a template.",
|
|
@@ -37564,7 +38642,7 @@ function registerSlidesTools(server) {
|
|
|
37564
38642
|
account: accountParam
|
|
37565
38643
|
})
|
|
37566
38644
|
}, async ({ title, parent, template, account }) => {
|
|
37567
|
-
const args = ["slides", "create", title];
|
|
38645
|
+
const args = ["slides", "create", pos(title)];
|
|
37568
38646
|
if (parent) args.push(`--parent=${parent}`);
|
|
37569
38647
|
if (template) args.push(`--template=${template}`);
|
|
37570
38648
|
return runOrDiagnose(args, { account });
|
|
@@ -37579,7 +38657,7 @@ function registerSlidesTools(server) {
|
|
|
37579
38657
|
account: accountParam
|
|
37580
38658
|
})
|
|
37581
38659
|
}, async ({ presentationId, title, parent, account }) => {
|
|
37582
|
-
const args = ["slides", "copy", presentationId, title];
|
|
38660
|
+
const args = ["slides", "copy", pos(presentationId), pos(title)];
|
|
37583
38661
|
if (parent) args.push(`--parent=${parent}`);
|
|
37584
38662
|
return runOrDiagnose(args, { account });
|
|
37585
38663
|
});
|
|
@@ -37591,7 +38669,7 @@ function registerSlidesTools(server) {
|
|
|
37591
38669
|
account: accountParam
|
|
37592
38670
|
})
|
|
37593
38671
|
}, async ({ presentationId, account }) => {
|
|
37594
|
-
return runOrDiagnose(["slides", "list-slides", presentationId], { account });
|
|
38672
|
+
return runOrDiagnose(["slides", "list-slides", pos(presentationId)], { account });
|
|
37595
38673
|
});
|
|
37596
38674
|
server.registerTool("gog_slides_read_slide", {
|
|
37597
38675
|
description: "Read the content of a slide (text, shapes, speaker notes). Set detail=true to also include normalized element geometry, styled text runs, paragraphs, table-cell content, and image source URLs.",
|
|
@@ -37603,11 +38681,11 @@ function registerSlidesTools(server) {
|
|
|
37603
38681
|
account: accountParam
|
|
37604
38682
|
})
|
|
37605
38683
|
}, async ({ presentationId, slideId, detail, account }) => {
|
|
37606
|
-
const args = ["slides", "read-slide", presentationId, slideId];
|
|
38684
|
+
const args = ["slides", "read-slide", pos(presentationId), pos(slideId)];
|
|
37607
38685
|
if (detail) args.push("--detail");
|
|
37608
38686
|
return runOrDiagnose(args, { account });
|
|
37609
38687
|
});
|
|
37610
|
-
registerRunTool(server, { service: "slides", examples: '"
|
|
38688
|
+
registerRunTool(server, { service: "slides", examples: '"delete-slide", "update-notes"' });
|
|
37611
38689
|
}
|
|
37612
38690
|
|
|
37613
38691
|
// src/tools/tasks.ts
|
|
@@ -37629,7 +38707,7 @@ function registerTasksTools(server) {
|
|
|
37629
38707
|
account: accountParam
|
|
37630
38708
|
})
|
|
37631
38709
|
}, async ({ tasklistId, account }) => {
|
|
37632
|
-
return runOrDiagnose(["tasks", "list", tasklistId], { account });
|
|
38710
|
+
return runOrDiagnose(["tasks", "list", pos(tasklistId)], { account });
|
|
37633
38711
|
});
|
|
37634
38712
|
server.registerTool("gog_tasks_get", {
|
|
37635
38713
|
description: "Get a specific task by ID.",
|
|
@@ -37640,7 +38718,7 @@ function registerTasksTools(server) {
|
|
|
37640
38718
|
account: accountParam
|
|
37641
38719
|
})
|
|
37642
38720
|
}, async ({ tasklistId, taskId, account }) => {
|
|
37643
|
-
return runOrDiagnose(["tasks", "get", tasklistId, taskId], { account });
|
|
38721
|
+
return runOrDiagnose(["tasks", "get", pos(tasklistId), pos(taskId)], { account });
|
|
37644
38722
|
});
|
|
37645
38723
|
server.registerTool("gog_tasks_add", {
|
|
37646
38724
|
description: "Add a task to a Google Task list.",
|
|
@@ -37653,7 +38731,7 @@ function registerTasksTools(server) {
|
|
|
37653
38731
|
account: accountParam
|
|
37654
38732
|
})
|
|
37655
38733
|
}, async ({ tasklistId, title, notes, due, account }) => {
|
|
37656
|
-
const args = ["tasks", "add", tasklistId, `--title=${title}`];
|
|
38734
|
+
const args = ["tasks", "add", pos(tasklistId), `--title=${title}`];
|
|
37657
38735
|
if (notes) args.push(`--notes=${notes}`);
|
|
37658
38736
|
if (due) args.push(`--due=${due}`);
|
|
37659
38737
|
return runOrDiagnose(args, { account });
|
|
@@ -37667,7 +38745,7 @@ function registerTasksTools(server) {
|
|
|
37667
38745
|
account: accountParam
|
|
37668
38746
|
})
|
|
37669
38747
|
}, async ({ tasklistId, taskId, account }) => {
|
|
37670
|
-
return runOrDiagnose(["tasks", "done", tasklistId, taskId], { account });
|
|
38748
|
+
return runOrDiagnose(["tasks", "done", pos(tasklistId), pos(taskId)], { account });
|
|
37671
38749
|
});
|
|
37672
38750
|
server.registerTool("gog_tasks_delete", {
|
|
37673
38751
|
description: "Delete a task.",
|
|
@@ -37678,13 +38756,13 @@ function registerTasksTools(server) {
|
|
|
37678
38756
|
account: accountParam
|
|
37679
38757
|
})
|
|
37680
38758
|
}, async ({ tasklistId, taskId, account }) => {
|
|
37681
|
-
return runOrDiagnose(["tasks", "delete", tasklistId, taskId, "--force"], { account });
|
|
38759
|
+
return runOrDiagnose(["tasks", "delete", pos(tasklistId), pos(taskId), "--force"], { account });
|
|
37682
38760
|
});
|
|
37683
38761
|
registerRunTool(server, { service: "tasks", examples: '"update", "undo", "clear"' });
|
|
37684
38762
|
}
|
|
37685
38763
|
|
|
37686
38764
|
// src/server.ts
|
|
37687
|
-
var VERSION = true ? "4.
|
|
38765
|
+
var VERSION = true ? "4.4.0" : "0.0.0";
|
|
37688
38766
|
var BASE_TOOL_REGISTRARS = [
|
|
37689
38767
|
registerApiTools,
|
|
37690
38768
|
registerAppScriptTools,
|
|
@@ -37702,7 +38780,7 @@ var BASE_TOOL_REGISTRARS = [
|
|
|
37702
38780
|
];
|
|
37703
38781
|
|
|
37704
38782
|
// src/bootstrap-auth.ts
|
|
37705
|
-
import { join as
|
|
38783
|
+
import { join as join5 } from "node:path";
|
|
37706
38784
|
var AUTH_BOOTSTRAP_MARKER = "auth-bootstrap.sha256";
|
|
37707
38785
|
var VARS = ["GOG_CLIENT_ID", "GOG_CLIENT_SECRET", "GOG_REFRESH_TOKEN", "GOG_ACCOUNT"];
|
|
37708
38786
|
var log = (line) => {
|
|
@@ -37736,12 +38814,12 @@ async function bootstrapGogAuth(env = process.env, options = {}) {
|
|
|
37736
38814
|
const [clientId, clientSecret, refreshToken, account] = values;
|
|
37737
38815
|
const { spawner } = options;
|
|
37738
38816
|
try {
|
|
37739
|
-
const { createHash } = await import("node:crypto");
|
|
38817
|
+
const { createHash: createHash4 } = await import("node:crypto");
|
|
37740
38818
|
const { mkdir, readFile, writeFile, chmod } = await import("node:fs/promises");
|
|
37741
38819
|
const home = options.home ?? (await import("node:os")).homedir();
|
|
37742
|
-
const dir =
|
|
37743
|
-
const marker =
|
|
37744
|
-
const fingerprint =
|
|
38820
|
+
const dir = join5(home, ".gogcli-mcp");
|
|
38821
|
+
const marker = join5(dir, AUTH_BOOTSTRAP_MARKER);
|
|
38822
|
+
const fingerprint = createHash4("sha256").update(values.join("\0")).digest("hex");
|
|
37745
38823
|
const previous = await readFile(marker, "utf8").catch(() => void 0);
|
|
37746
38824
|
if (previous === fingerprint && await accountListed(account, spawner)) return "present";
|
|
37747
38825
|
await run(
|