gogcli-mcp-gmail 2.18.3 → 2.19.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/dist/index.js +279 -4
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -1
- package/src/tools/gmail-extra.ts +2 -1
- package/tests/tools/gmail-extra.test.ts +3 -3
package/dist/index.js
CHANGED
|
@@ -3644,7 +3644,12 @@ var require_fast_uri = __commonJS({
|
|
|
3644
3644
|
}
|
|
3645
3645
|
function resolve(baseURI, relativeURI, options) {
|
|
3646
3646
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3647
|
-
const
|
|
3647
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3648
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3649
|
+
if (baseMalformed || relativeMalformed) {
|
|
3650
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3651
|
+
}
|
|
3652
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3648
3653
|
schemelessOptions.skipEscape = true;
|
|
3649
3654
|
return serialize(resolved, schemelessOptions);
|
|
3650
3655
|
}
|
|
@@ -3770,6 +3775,7 @@ var require_fast_uri = __commonJS({
|
|
|
3770
3775
|
}
|
|
3771
3776
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3772
3777
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3778
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3773
3779
|
function getParseError(parsed, matches) {
|
|
3774
3780
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3775
3781
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3804,6 +3810,20 @@ var require_fast_uri = __commonJS({
|
|
|
3804
3810
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3805
3811
|
malformedAuthorityOrPort = true;
|
|
3806
3812
|
}
|
|
3813
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3814
|
+
if (introducerMatch !== null) {
|
|
3815
|
+
const region = introducerMatch[1];
|
|
3816
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3817
|
+
if (normalizedRegion.length >= 2) {
|
|
3818
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3819
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3820
|
+
malformedAuthorityOrPort = true;
|
|
3821
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3822
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3823
|
+
malformedAuthorityOrPort = true;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3807
3827
|
const matches = uri.match(URI_PARSE);
|
|
3808
3828
|
if (matches) {
|
|
3809
3829
|
parsed.scheme = matches[1];
|
|
@@ -31280,6 +31300,198 @@ async function run(args, options = {}) {
|
|
|
31280
31300
|
}
|
|
31281
31301
|
}
|
|
31282
31302
|
|
|
31303
|
+
// ../gogcli-mcp/src/timestamps.ts
|
|
31304
|
+
var DEFAULT_DISPLAY_TZ = "America/New_York";
|
|
31305
|
+
function isValidTimeZone(tz) {
|
|
31306
|
+
try {
|
|
31307
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
31308
|
+
return true;
|
|
31309
|
+
} catch {
|
|
31310
|
+
return false;
|
|
31311
|
+
}
|
|
31312
|
+
}
|
|
31313
|
+
function displayTimeZone() {
|
|
31314
|
+
const configured = readEnvVar("DISPLAY_TZ");
|
|
31315
|
+
if (configured && isValidTimeZone(configured)) return configured;
|
|
31316
|
+
return DEFAULT_DISPLAY_TZ;
|
|
31317
|
+
}
|
|
31318
|
+
function naiveSourceTimeZone() {
|
|
31319
|
+
const configured = readEnvVar("GOG_TIMEZONE");
|
|
31320
|
+
if (configured && isValidTimeZone(configured)) return configured;
|
|
31321
|
+
return displayTimeZone();
|
|
31322
|
+
}
|
|
31323
|
+
function offsetAt(instant, tz) {
|
|
31324
|
+
const w = wallPartsIn(instant, tz);
|
|
31325
|
+
const asUTC = Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second, instant.getUTCMilliseconds());
|
|
31326
|
+
const minutes = Math.round((asUTC - instant.getTime()) / 6e4);
|
|
31327
|
+
const sign = minutes < 0 ? "-" : "+";
|
|
31328
|
+
const abs = Math.abs(minutes);
|
|
31329
|
+
return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
31330
|
+
}
|
|
31331
|
+
function wallPartsIn(instant, tz) {
|
|
31332
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
31333
|
+
timeZone: tz,
|
|
31334
|
+
year: "numeric",
|
|
31335
|
+
month: "2-digit",
|
|
31336
|
+
day: "2-digit",
|
|
31337
|
+
hour: "2-digit",
|
|
31338
|
+
minute: "2-digit",
|
|
31339
|
+
second: "2-digit",
|
|
31340
|
+
// h23 pins midnight to hour 00; without it some ICU builds render hour 24.
|
|
31341
|
+
hourCycle: "h23"
|
|
31342
|
+
}).formatToParts(instant);
|
|
31343
|
+
const out = {};
|
|
31344
|
+
for (const p of parts) {
|
|
31345
|
+
if (p.type !== "literal") out[p.type] = Number(p.value);
|
|
31346
|
+
}
|
|
31347
|
+
return out;
|
|
31348
|
+
}
|
|
31349
|
+
function wallTimeToInstant(y, mo, d, h, mi, s, ms, tz) {
|
|
31350
|
+
let guess = Date.UTC(y, mo - 1, d, h, mi, s, ms);
|
|
31351
|
+
for (let i = 0; i < 2; i += 1) {
|
|
31352
|
+
const seen = wallPartsIn(new Date(guess), tz);
|
|
31353
|
+
const seenUTC = Date.UTC(seen.year, seen.month - 1, seen.day, seen.hour, seen.minute, seen.second, ms);
|
|
31354
|
+
const drift = Date.UTC(y, mo - 1, d, h, mi, s, ms) - seenUTC;
|
|
31355
|
+
if (drift === 0) break;
|
|
31356
|
+
guess += drift;
|
|
31357
|
+
}
|
|
31358
|
+
return new Date(guess);
|
|
31359
|
+
}
|
|
31360
|
+
function pad(n, width = 2) {
|
|
31361
|
+
return String(n).padStart(width, "0");
|
|
31362
|
+
}
|
|
31363
|
+
function isoWithOffset(instant, tz, offset) {
|
|
31364
|
+
const w = wallPartsIn(instant, tz);
|
|
31365
|
+
const msPart = instant.getUTCMilliseconds();
|
|
31366
|
+
const frac = msPart ? `.${pad(msPart, 3)}` : "";
|
|
31367
|
+
return `${pad(w.year, 4)}-${pad(w.month)}-${pad(w.day)}T${pad(w.hour)}:${pad(w.minute)}:${pad(w.second)}${frac}${offset}`;
|
|
31368
|
+
}
|
|
31369
|
+
function formatInstant(instant, tz = displayTimeZone()) {
|
|
31370
|
+
const offset = offsetAt(instant, tz);
|
|
31371
|
+
const display = new Intl.DateTimeFormat("en-US", {
|
|
31372
|
+
timeZone: tz,
|
|
31373
|
+
weekday: "short",
|
|
31374
|
+
month: "short",
|
|
31375
|
+
day: "numeric",
|
|
31376
|
+
year: "numeric",
|
|
31377
|
+
hour: "numeric",
|
|
31378
|
+
minute: "2-digit",
|
|
31379
|
+
timeZoneName: "short"
|
|
31380
|
+
}).format(instant);
|
|
31381
|
+
return { iso: isoWithOffset(instant, tz, offset), display };
|
|
31382
|
+
}
|
|
31383
|
+
var TIMESTAMP_KEYS = /* @__PURE__ */ new Set([
|
|
31384
|
+
"date",
|
|
31385
|
+
// gog gmail message/thread listings ("2026-07-28 03:36")
|
|
31386
|
+
"dateTime",
|
|
31387
|
+
// Calendar event start/end
|
|
31388
|
+
"internalDate",
|
|
31389
|
+
// Gmail, epoch milliseconds (authoritative)
|
|
31390
|
+
"modifiedTime",
|
|
31391
|
+
// Drive
|
|
31392
|
+
"createdTime",
|
|
31393
|
+
// Drive
|
|
31394
|
+
"createTime",
|
|
31395
|
+
"updateTime",
|
|
31396
|
+
"updated",
|
|
31397
|
+
"originalStartTime",
|
|
31398
|
+
"expirationTime",
|
|
31399
|
+
"lastModified",
|
|
31400
|
+
"sentAt",
|
|
31401
|
+
"viewedAt",
|
|
31402
|
+
"modifiedAt",
|
|
31403
|
+
"fetchedBodyAt",
|
|
31404
|
+
"asOf"
|
|
31405
|
+
]);
|
|
31406
|
+
var ZONE_NAME_KEYS = /* @__PURE__ */ new Set(["timeZone", "timezone"]);
|
|
31407
|
+
var RFC3339_WITH_OFFSET = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,9}))?(Z|[+-]\d{2}:?\d{2})$/;
|
|
31408
|
+
var NAIVE_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,9}))?$/;
|
|
31409
|
+
var EPOCH_MILLIS = /^\d{13}$/;
|
|
31410
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
31411
|
+
function isRealCalendarDate(p) {
|
|
31412
|
+
const utc = new Date(Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second));
|
|
31413
|
+
return utc.getUTCFullYear() === p.year && utc.getUTCMonth() === p.month - 1 && utc.getUTCDate() === p.day && utc.getUTCHours() === p.hour && utc.getUTCMinutes() === p.minute && utc.getUTCSeconds() === p.second;
|
|
31414
|
+
}
|
|
31415
|
+
function parseTimestampValue(key, value, assumeNaiveIn) {
|
|
31416
|
+
if (typeof value !== "string") return null;
|
|
31417
|
+
const raw = value.trim();
|
|
31418
|
+
if (raw === "" || DATE_ONLY.test(raw)) return null;
|
|
31419
|
+
if (key === "internalDate" && EPOCH_MILLIS.test(raw)) {
|
|
31420
|
+
return new Date(Number(raw));
|
|
31421
|
+
}
|
|
31422
|
+
if (RFC3339_WITH_OFFSET.test(raw)) {
|
|
31423
|
+
const parsed = new Date(raw.replace(" ", "T"));
|
|
31424
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
31425
|
+
}
|
|
31426
|
+
const naive = NAIVE_DATE_TIME.exec(raw);
|
|
31427
|
+
if (naive) {
|
|
31428
|
+
const [, y, mo, d, h, mi, s, frac] = naive;
|
|
31429
|
+
const ms = frac ? Number(frac.padEnd(3, "0").slice(0, 3)) : 0;
|
|
31430
|
+
const parts = {
|
|
31431
|
+
year: Number(y),
|
|
31432
|
+
month: Number(mo),
|
|
31433
|
+
day: Number(d),
|
|
31434
|
+
hour: Number(h),
|
|
31435
|
+
minute: Number(mi),
|
|
31436
|
+
second: Number(s ?? "0")
|
|
31437
|
+
};
|
|
31438
|
+
if (!isRealCalendarDate(parts)) return null;
|
|
31439
|
+
return wallTimeToInstant(
|
|
31440
|
+
parts.year,
|
|
31441
|
+
parts.month,
|
|
31442
|
+
parts.day,
|
|
31443
|
+
parts.hour,
|
|
31444
|
+
parts.minute,
|
|
31445
|
+
parts.second,
|
|
31446
|
+
ms,
|
|
31447
|
+
assumeNaiveIn
|
|
31448
|
+
);
|
|
31449
|
+
}
|
|
31450
|
+
return null;
|
|
31451
|
+
}
|
|
31452
|
+
function walk(node, tz, naiveTz) {
|
|
31453
|
+
let changed = false;
|
|
31454
|
+
if (Array.isArray(node)) {
|
|
31455
|
+
for (const item of node) {
|
|
31456
|
+
if (walk(item, tz, naiveTz)) changed = true;
|
|
31457
|
+
}
|
|
31458
|
+
return changed;
|
|
31459
|
+
}
|
|
31460
|
+
if (node === null || typeof node !== "object") return false;
|
|
31461
|
+
const obj = node;
|
|
31462
|
+
for (const key of Object.keys(obj)) {
|
|
31463
|
+
const value = obj[key];
|
|
31464
|
+
if (value !== null && typeof value === "object") {
|
|
31465
|
+
if (walk(value, tz, naiveTz)) changed = true;
|
|
31466
|
+
continue;
|
|
31467
|
+
}
|
|
31468
|
+
if (ZONE_NAME_KEYS.has(key) || !TIMESTAMP_KEYS.has(key)) continue;
|
|
31469
|
+
const instant = parseTimestampValue(key, value, naiveTz);
|
|
31470
|
+
if (!instant) continue;
|
|
31471
|
+
const { iso, display } = formatInstant(instant, tz);
|
|
31472
|
+
obj[key] = iso;
|
|
31473
|
+
obj[`${key}Display`] = display;
|
|
31474
|
+
changed = true;
|
|
31475
|
+
}
|
|
31476
|
+
return changed;
|
|
31477
|
+
}
|
|
31478
|
+
function detectIndent(text) {
|
|
31479
|
+
const match = /\n(\s+)\S/.exec(text);
|
|
31480
|
+
return match ? match[1].replace(/\t/g, " ").length : 0;
|
|
31481
|
+
}
|
|
31482
|
+
function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSourceTimeZone()) {
|
|
31483
|
+
const trimmed = text.trim();
|
|
31484
|
+
if (trimmed === "" || !/^[[{]/.test(trimmed)) return text;
|
|
31485
|
+
let parsed;
|
|
31486
|
+
try {
|
|
31487
|
+
parsed = JSON.parse(trimmed);
|
|
31488
|
+
} catch {
|
|
31489
|
+
return text;
|
|
31490
|
+
}
|
|
31491
|
+
if (!walk(parsed, tz, naiveTz)) return text;
|
|
31492
|
+
return JSON.stringify(parsed, null, detectIndent(text));
|
|
31493
|
+
}
|
|
31494
|
+
|
|
31283
31495
|
// ../gogcli-mcp/src/tools/utils.ts
|
|
31284
31496
|
var PAYLOAD_INLINE_MAX = 4096;
|
|
31285
31497
|
function payloadArg(inlineFlag, fileFlag, value, ext) {
|
|
@@ -31380,7 +31592,8 @@ ${accounts || "(none)"}${hint}`);
|
|
|
31380
31592
|
}
|
|
31381
31593
|
async function runOrDiagnose(args, options) {
|
|
31382
31594
|
try {
|
|
31383
|
-
|
|
31595
|
+
const raw = await run(args, options);
|
|
31596
|
+
return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
|
|
31384
31597
|
} catch (err) {
|
|
31385
31598
|
return diagnose(err);
|
|
31386
31599
|
}
|
|
@@ -31617,7 +31830,68 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31617
31830
|
);
|
|
31618
31831
|
|
|
31619
31832
|
// ../gogcli-mcp/src/server.ts
|
|
31620
|
-
var VERSION = true ? "2.
|
|
31833
|
+
var VERSION = true ? "2.19.0" : "0.0.0";
|
|
31834
|
+
|
|
31835
|
+
// ../gogcli-mcp/src/connector-runtime.ts
|
|
31836
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
31837
|
+
var DEADLINE_GRACE_MS = 5e3;
|
|
31838
|
+
var RUNNER_GOG_FAILED = 422;
|
|
31839
|
+
var RUNNER_DRAINING = 503;
|
|
31840
|
+
function makeFlyExecutor(endpoint, key) {
|
|
31841
|
+
return async (args, opts) => {
|
|
31842
|
+
const deadlineMs = (opts?.timeout ?? DEFAULT_TIMEOUT_MS) + DEADLINE_GRACE_MS;
|
|
31843
|
+
let res;
|
|
31844
|
+
try {
|
|
31845
|
+
res = await fetch(endpoint + "/run", {
|
|
31846
|
+
method: "POST",
|
|
31847
|
+
headers: {
|
|
31848
|
+
Authorization: "Bearer " + key,
|
|
31849
|
+
"Content-Type": "application/json"
|
|
31850
|
+
},
|
|
31851
|
+
body: JSON.stringify({ args }),
|
|
31852
|
+
signal: AbortSignal.timeout(deadlineMs)
|
|
31853
|
+
});
|
|
31854
|
+
} catch (err) {
|
|
31855
|
+
const name = err instanceof Error ? err.name : "";
|
|
31856
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
31857
|
+
throw new Error(
|
|
31858
|
+
`gog-runner did not respond within ${deadlineMs}ms (${endpoint}) \u2014 the Fly backend may be cold or wedged`
|
|
31859
|
+
);
|
|
31860
|
+
}
|
|
31861
|
+
throw err;
|
|
31862
|
+
}
|
|
31863
|
+
if (!res.ok) {
|
|
31864
|
+
const body = await res.json().catch(() => null);
|
|
31865
|
+
const detail = body && typeof body.error === "string" ? body.stderr && body.stderr.trim() && body.stderr.trim() !== body.error.trim() ? `${body.error}
|
|
31866
|
+
${body.stderr}` : body.error : "";
|
|
31867
|
+
if (res.status === RUNNER_GOG_FAILED) {
|
|
31868
|
+
throw new Error(detail || "gog failed on the runner (no detail supplied)");
|
|
31869
|
+
}
|
|
31870
|
+
if (res.status === RUNNER_DRAINING || body?.retryable === true) {
|
|
31871
|
+
throw new Error(
|
|
31872
|
+
`gog-runner is restarting; retry this call.${detail ? ` ${detail}` : ""}`
|
|
31873
|
+
);
|
|
31874
|
+
}
|
|
31875
|
+
if (detail) {
|
|
31876
|
+
throw new Error(detail);
|
|
31877
|
+
}
|
|
31878
|
+
throw new Error(
|
|
31879
|
+
`gog-runner HTTP ${res.status}: the response did not come from the runner, so the request never reached gog. The backend Machine was most likely starting or shutting down \u2014 this is transient, retry the same call.`
|
|
31880
|
+
);
|
|
31881
|
+
}
|
|
31882
|
+
const { stdout } = await res.json();
|
|
31883
|
+
return stdout;
|
|
31884
|
+
};
|
|
31885
|
+
}
|
|
31886
|
+
|
|
31887
|
+
// ../gogcli-mcp/src/remote-runner.ts
|
|
31888
|
+
function useRemoteGogRunner(env = process.env) {
|
|
31889
|
+
const endpoint = readEnvVar("GOG_RUNNER_URL", { env });
|
|
31890
|
+
const key = readEnvVar("GOG_RUNNER_KEY", { env });
|
|
31891
|
+
if (!endpoint || !key) return false;
|
|
31892
|
+
runExecutor.enterWith({ executor: makeFlyExecutor(endpoint.replace(/\/+$/, ""), key) });
|
|
31893
|
+
return true;
|
|
31894
|
+
}
|
|
31621
31895
|
|
|
31622
31896
|
// src/tools/gmail-extra.ts
|
|
31623
31897
|
function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
@@ -31790,7 +32064,7 @@ function registerExtraGmailTools(server) {
|
|
|
31790
32064
|
const args = ["gmail", "raw", messageId];
|
|
31791
32065
|
if (format) args.push(`--format=${format}`);
|
|
31792
32066
|
if (pretty) args.push("--pretty");
|
|
31793
|
-
return runOrDiagnose(args, { account });
|
|
32067
|
+
return runOrDiagnose(args, { account, lossless: true });
|
|
31794
32068
|
});
|
|
31795
32069
|
server.registerTool("gog_gmail_attachment", {
|
|
31796
32070
|
description: `Download a Gmail attachment and deliver its contents so you can actually read them. The real filename and MIME type are resolved from the message part metadata, so the saved file and response are named correctly (e.g. Guest_Copy.pdf), never a generic *.bin. deliver="auto" (default) is transport-aware: images always come back as a native image block; anything else is delivered by the channel that works on your transport \u2014 a readable server-side file PATH on local (stdio) clients that share the filesystem, or a Google Drive link on the remote connector (whose backend filesystem you can't read, and which rejects inline PDF/binary blobs). deliver="inline" forces the bytes inline as an image or embedded resource blob (use only if your client consumes resource blobs; errors if over gog's 3 MiB cap). deliver="drive" always uploads to Drive; deliver="off" writes the file server-side and returns {path, fileName, mimeType, bytes}. Drive delivery creates a file in your Drive (blocked when GOG_READONLY is set).`,
|
|
@@ -32555,6 +32829,7 @@ function registerExtraGmailTools(server) {
|
|
|
32555
32829
|
}
|
|
32556
32830
|
|
|
32557
32831
|
// src/index.ts
|
|
32832
|
+
useRemoteGogRunner();
|
|
32558
32833
|
await runMcp({
|
|
32559
32834
|
name: "gogcli-gmail",
|
|
32560
32835
|
version: VERSION,
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-gmail",
|
|
5
5
|
"display_name": "gogcli (Gmail)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.19.0",
|
|
7
7
|
"description": "Extended Gmail for Claude via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-gmail",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-gmail",
|
|
5
5
|
"description": "Extended Gmail MCP server via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runMcp } from '@chrischall/mcp-utils';
|
|
3
|
-
import { VERSION, authToolsFor, registerGmailTools } from '../../gogcli-mcp/src/lib.js';
|
|
3
|
+
import { VERSION, authToolsFor, registerGmailTools, useRemoteGogRunner } from '../../gogcli-mcp/src/lib.js';
|
|
4
4
|
import { registerExtraGmailTools } from './tools/gmail-extra.js';
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
// Execute `gog` on the Fly backend when the host points us at one; without
|
|
8
|
+
// it, nothing changes and we spawn the local binary as before.
|
|
9
|
+
useRemoteGogRunner();
|
|
10
|
+
|
|
6
11
|
await runMcp({
|
|
7
12
|
name: 'gogcli-gmail',
|
|
8
13
|
version: VERSION,
|
package/src/tools/gmail-extra.ts
CHANGED
|
@@ -327,7 +327,8 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
327
327
|
const args = ['gmail', 'raw', messageId];
|
|
328
328
|
if (format) args.push(`--format=${format}`);
|
|
329
329
|
if (pretty) args.push('--pretty');
|
|
330
|
-
|
|
330
|
+
// Verbatim by contract: see the `lossless` note on runOrDiagnose.
|
|
331
|
+
return runOrDiagnose(args, { account, lossless: true });
|
|
331
332
|
});
|
|
332
333
|
|
|
333
334
|
server.registerTool('gog_gmail_attachment', {
|
|
@@ -27,20 +27,20 @@ beforeEach(async () => {
|
|
|
27
27
|
describe('gog_gmail_raw', () => {
|
|
28
28
|
it('calls runOrDiagnose with messageId', async () => {
|
|
29
29
|
await harness.callTool('gog_gmail_raw', { messageId: 'm1' });
|
|
30
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined });
|
|
30
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined, lossless: true });
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('passes --format and --pretty when provided', async () => {
|
|
34
34
|
await harness.callTool('gog_gmail_raw', { messageId: 'm1', format: 'metadata', pretty: true });
|
|
35
35
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
36
36
|
['gmail', 'raw', 'm1', '--format=metadata', '--pretty'],
|
|
37
|
-
{ account: undefined },
|
|
37
|
+
{ account: undefined, lossless: true },
|
|
38
38
|
);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('omits --pretty when false', async () => {
|
|
42
42
|
await harness.callTool('gog_gmail_raw', { messageId: 'm1', pretty: false });
|
|
43
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined });
|
|
43
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined, lossless: true });
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
|