gogcli-mcp-gmail 2.25.0 → 2.26.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 +180 -127
- package/manifest.json +1 -1
- package/mint.yaml +107 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -31000,9 +31000,87 @@ var StdioServerTransport = class {
|
|
|
31000
31000
|
}
|
|
31001
31001
|
};
|
|
31002
31002
|
|
|
31003
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
31004
|
+
var McpToolError = class extends Error {
|
|
31005
|
+
/** Actionable remediation text, when one applies. */
|
|
31006
|
+
hint;
|
|
31007
|
+
constructor(message, opts) {
|
|
31008
|
+
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
31009
|
+
this.name = "McpToolError";
|
|
31010
|
+
if (opts?.hint !== void 0)
|
|
31011
|
+
this.hint = opts.hint;
|
|
31012
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
31013
|
+
}
|
|
31014
|
+
};
|
|
31015
|
+
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
31016
|
+
var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
31017
|
+
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
31018
|
+
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,\s]+)=[^;,\s]*/gi;
|
|
31019
|
+
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,\s]+=[^;,\s]*)(?:;\s*[^=;,\s]+=[^;,\s]*)*)/gi;
|
|
31020
|
+
var API_KEY_RE = new RegExp([
|
|
31021
|
+
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
31022
|
+
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
31023
|
+
"gh[pousr]_[A-Za-z0-9]{36,}\\b",
|
|
31024
|
+
// GitHub ghp_/gho_/ghu_/ghs_/ghr_
|
|
31025
|
+
"xox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])",
|
|
31026
|
+
// Slack
|
|
31027
|
+
"AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])",
|
|
31028
|
+
// Google API key (39 chars total)
|
|
31029
|
+
"AKIA[0-9A-Z]{16}\\b",
|
|
31030
|
+
// AWS access key id (20 chars total)
|
|
31031
|
+
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
31032
|
+
// webhook signing secret (Stripe-style)
|
|
31033
|
+
].map((p) => `\\b${p}`).join("|"), "g");
|
|
31034
|
+
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
31035
|
+
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
31036
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
31037
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
31038
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
31039
|
+
function redactSecrets(text) {
|
|
31040
|
+
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]");
|
|
31041
|
+
}
|
|
31042
|
+
|
|
31043
|
+
// ../../node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
31044
|
+
function textResult(data) {
|
|
31045
|
+
return {
|
|
31046
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
31047
|
+
};
|
|
31048
|
+
}
|
|
31049
|
+
function rawTextResult(text) {
|
|
31050
|
+
return { content: [{ type: "text", text }] };
|
|
31051
|
+
}
|
|
31052
|
+
function errorResult(message) {
|
|
31053
|
+
return {
|
|
31054
|
+
content: [{ type: "text", text: redactSecrets(message) }],
|
|
31055
|
+
isError: true
|
|
31056
|
+
};
|
|
31057
|
+
}
|
|
31058
|
+
|
|
31003
31059
|
// ../../node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
31060
|
+
function hintResultOrRethrow(err) {
|
|
31061
|
+
if (err instanceof McpToolError && err.hint) {
|
|
31062
|
+
return errorResult(`${err.message}
|
|
31063
|
+
|
|
31064
|
+
Hint: ${err.hint}`);
|
|
31065
|
+
}
|
|
31066
|
+
throw err;
|
|
31067
|
+
}
|
|
31068
|
+
function surfaceToolHints(server) {
|
|
31069
|
+
const register = server.registerTool.bind(server);
|
|
31070
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
31071
|
+
let result;
|
|
31072
|
+
try {
|
|
31073
|
+
result = cb(...args);
|
|
31074
|
+
} catch (err) {
|
|
31075
|
+
return hintResultOrRethrow(err);
|
|
31076
|
+
}
|
|
31077
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
31078
|
+
});
|
|
31079
|
+
}
|
|
31004
31080
|
async function createMcpServer(opts) {
|
|
31005
31081
|
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31082
|
+
if (opts.surfaceHints !== false)
|
|
31083
|
+
surfaceToolHints(server);
|
|
31006
31084
|
if (opts.banner !== void 0) {
|
|
31007
31085
|
console.error(opts.banner);
|
|
31008
31086
|
}
|
|
@@ -31047,51 +31125,6 @@ async function runMcp(opts) {
|
|
|
31047
31125
|
return server;
|
|
31048
31126
|
}
|
|
31049
31127
|
|
|
31050
|
-
// ../../node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
31051
|
-
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
31052
|
-
var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
31053
|
-
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
31054
|
-
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,\s]+)=[^;,\s]*/gi;
|
|
31055
|
-
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,\s]+=[^;,\s]*)(?:;\s*[^=;,\s]+=[^;,\s]*)*)/gi;
|
|
31056
|
-
var API_KEY_RE = new RegExp([
|
|
31057
|
-
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
31058
|
-
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
31059
|
-
"gh[pousr]_[A-Za-z0-9]{36,}\\b",
|
|
31060
|
-
// GitHub ghp_/gho_/ghu_/ghs_/ghr_
|
|
31061
|
-
"xox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])",
|
|
31062
|
-
// Slack
|
|
31063
|
-
"AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])",
|
|
31064
|
-
// Google API key (39 chars total)
|
|
31065
|
-
"AKIA[0-9A-Z]{16}\\b",
|
|
31066
|
-
// AWS access key id (20 chars total)
|
|
31067
|
-
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
31068
|
-
// webhook signing secret (Stripe-style)
|
|
31069
|
-
].map((p) => `\\b${p}`).join("|"), "g");
|
|
31070
|
-
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
31071
|
-
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
31072
|
-
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
31073
|
-
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
31074
|
-
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
31075
|
-
function redactSecrets(text) {
|
|
31076
|
-
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]");
|
|
31077
|
-
}
|
|
31078
|
-
|
|
31079
|
-
// ../../node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
31080
|
-
function textResult(data) {
|
|
31081
|
-
return {
|
|
31082
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
31083
|
-
};
|
|
31084
|
-
}
|
|
31085
|
-
function rawTextResult(text) {
|
|
31086
|
-
return { content: [{ type: "text", text }] };
|
|
31087
|
-
}
|
|
31088
|
-
function errorResult(message) {
|
|
31089
|
-
return {
|
|
31090
|
-
content: [{ type: "text", text: redactSecrets(message) }],
|
|
31091
|
-
isError: true
|
|
31092
|
-
};
|
|
31093
|
-
}
|
|
31094
|
-
|
|
31095
31128
|
// ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
31096
31129
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
31097
31130
|
function readEnvVar(key, opts = {}) {
|
|
@@ -31908,6 +31941,95 @@ function authToolsFor(defaultServices) {
|
|
|
31908
31941
|
return (server) => registerAuthToolsWith(server, defaultServices);
|
|
31909
31942
|
}
|
|
31910
31943
|
|
|
31944
|
+
// ../gogcli-mcp/src/tools/calendar.ts
|
|
31945
|
+
var reminderParams = {
|
|
31946
|
+
reminders: external_exports.array(external_exports.string()).max(5).optional().describe(
|
|
31947
|
+
`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.`
|
|
31948
|
+
),
|
|
31949
|
+
noReminders: external_exports.boolean().optional().describe(
|
|
31950
|
+
"Give the event no reminders at all, overriding the calendar's defaults. Different from an empty reminders array, which RESTORES those defaults. Cannot be combined with reminders."
|
|
31951
|
+
)
|
|
31952
|
+
};
|
|
31953
|
+
|
|
31954
|
+
// ../gogcli-mcp/src/attachments.ts
|
|
31955
|
+
var MAX_INLINE_ATTACHMENT_BYTES = 8 * 1024 * 1024;
|
|
31956
|
+
var RUNNER_MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
31957
|
+
var RUNNER_BODY_JSON_RESERVE_BYTES = 256 * 1024;
|
|
31958
|
+
var MAX_REQUEST_PAYLOAD_WIRE_BYTES = RUNNER_MAX_BODY_BYTES - RUNNER_BODY_JSON_RESERVE_BYTES;
|
|
31959
|
+
var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
|
|
31960
|
+
function wireBytesOf(arg) {
|
|
31961
|
+
if (typeof arg === "string") return Buffer.byteLength(arg, "utf8");
|
|
31962
|
+
return arg.encoding === "base64" ? arg.contents.length : Buffer.byteLength(arg.contents, "utf8");
|
|
31963
|
+
}
|
|
31964
|
+
var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
|
|
31965
|
+
var INLINE_ATTACHMENT_LIMITS_TEXT = `up to ${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)} per file and ${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)} in total`;
|
|
31966
|
+
var inlineAttachmentSchema = external_exports.object({
|
|
31967
|
+
filename: external_exports.string().min(1).describe(
|
|
31968
|
+
`Filename the recipient will see, e.g. "pendant-layouts.png". gog infers the attachment's MIME type from this extension, so give it the right one \u2014 a .png sent as "layouts" arrives as an untyped blob. Must be a single filename, not a path.`
|
|
31969
|
+
),
|
|
31970
|
+
contentBase64: external_exports.string().min(1).describe(
|
|
31971
|
+
"The file's bytes, base64-encoded (standard alphabet, with padding). This is the whole point of this parameter: the bytes travel with the request, so nothing needs to exist on the gog server's filesystem."
|
|
31972
|
+
)
|
|
31973
|
+
});
|
|
31974
|
+
var attachInlineParam = external_exports.array(inlineAttachmentSchema).optional().describe(
|
|
31975
|
+
`Attachments supplied as BYTES rather than as server-side paths \u2014 use this whenever you hold a file and the gog server does not, which is always the case on the hosted connector and on any remote deployment. Each entry is {filename, contentBase64} (${INLINE_ATTACHMENT_LIMITS_TEXT}). Can be combined with \`attach\`: the two name disjoint files (paths read on the server vs. bytes sent with the call), and both end up as ordinary attachments on the message.`
|
|
31976
|
+
);
|
|
31977
|
+
function validateFilename(filename, where) {
|
|
31978
|
+
if (/[/\\]/.test(filename)) {
|
|
31979
|
+
throw new Error(
|
|
31980
|
+
`${where}: filename ${JSON.stringify(filename)} must be a bare filename, not a path. Pass just the name the recipient should see, e.g. "report.pdf".`
|
|
31981
|
+
);
|
|
31982
|
+
}
|
|
31983
|
+
if (/[\x00-\x1f]/.test(filename) || /^\.+$/.test(filename) || filename.length > 200) {
|
|
31984
|
+
throw new Error(
|
|
31985
|
+
`${where}: filename ${JSON.stringify(filename)} is not a usable filename (no control characters, not "."/"..", 200 characters max).`
|
|
31986
|
+
);
|
|
31987
|
+
}
|
|
31988
|
+
}
|
|
31989
|
+
function decodedLength(contentBase64) {
|
|
31990
|
+
const buf = Buffer.from(contentBase64, "base64");
|
|
31991
|
+
return buf.toString("base64") === contentBase64 ? buf.length : null;
|
|
31992
|
+
}
|
|
31993
|
+
function inlineFileArg(flag, attachment, opts = {}) {
|
|
31994
|
+
const { filename, contentBase64 } = attachment;
|
|
31995
|
+
const where = opts.where ?? `attachInline entry ${JSON.stringify(filename)}`;
|
|
31996
|
+
validateFilename(filename, where);
|
|
31997
|
+
const bytes = decodedLength(contentBase64);
|
|
31998
|
+
if (bytes === null) {
|
|
31999
|
+
throw new Error(
|
|
32000
|
+
`${where}: contents are not valid base64. Send the standard alphabet with padding and no line breaks \u2014 the value must survive a decode/re-encode round trip unchanged.`
|
|
32001
|
+
);
|
|
32002
|
+
}
|
|
32003
|
+
if (bytes > MAX_INLINE_ATTACHMENT_BYTES) {
|
|
32004
|
+
throw new Error(
|
|
32005
|
+
`${where}: ${bytes} bytes exceeds the ${MAX_INLINE_ATTACHMENT_BYTES}-byte (${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)}) per-file limit for inline content. Upload it to Drive and link to it instead, or send it from a local (stdio) deployment using a real server-side path.`
|
|
32006
|
+
);
|
|
32007
|
+
}
|
|
32008
|
+
const arg = { kind: "file", flag, contents: contentBase64, encoding: "base64", filename };
|
|
32009
|
+
if (opts.positional) arg.positional = true;
|
|
32010
|
+
return { arg, bytes };
|
|
32011
|
+
}
|
|
32012
|
+
function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
|
|
32013
|
+
if (!attachments?.length) return [];
|
|
32014
|
+
const args = [];
|
|
32015
|
+
const siblingWire = siblingArgs.reduce((sum, arg) => sum + wireBytesOf(arg), 0);
|
|
32016
|
+
let attachmentWire = 0;
|
|
32017
|
+
let decodedTotal = 0;
|
|
32018
|
+
for (const attachment of attachments) {
|
|
32019
|
+
const { arg, bytes } = inlineFileArg(flag, attachment);
|
|
32020
|
+
attachmentWire += arg.contents.length;
|
|
32021
|
+
decodedTotal += bytes;
|
|
32022
|
+
if (siblingWire + attachmentWire > MAX_REQUEST_PAYLOAD_WIRE_BYTES) {
|
|
32023
|
+
const blame = attachmentWire <= MAX_REQUEST_PAYLOAD_WIRE_BYTES ? ` These attachments would fit on their own; the rest of the message (its body, mostly) spends ${siblingWire} bytes of the same budget.` : "";
|
|
32024
|
+
throw new Error(
|
|
32025
|
+
`This message is too large to send: ${decodedTotal} bytes of attachments (${attachmentWire} bytes once base64-encoded for transit) exceed the ${MAX_REQUEST_PAYLOAD_WIRE_BYTES}-byte request limit.${blame} The ceiling for attachments alone is ${MAX_INLINE_ATTACHMENT_TOTAL_BYTES} bytes (${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)}); a long body lowers it. Send fewer or smaller files per message, shorten the body, or upload the large files to Drive and link them.`
|
|
32026
|
+
);
|
|
32027
|
+
}
|
|
32028
|
+
args.push(arg);
|
|
32029
|
+
}
|
|
32030
|
+
return args;
|
|
32031
|
+
}
|
|
32032
|
+
|
|
31911
32033
|
// ../gogcli-mcp/src/gmail-results.ts
|
|
31912
32034
|
function sortKey(item) {
|
|
31913
32035
|
for (const raw of [item.internalDateIso, item.date]) {
|
|
@@ -31967,6 +32089,7 @@ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
|
31967
32089
|
const merged = [];
|
|
31968
32090
|
let base;
|
|
31969
32091
|
let token = startToken;
|
|
32092
|
+
const fetched = new Set(startToken === void 0 ? [] : [startToken]);
|
|
31970
32093
|
for (let pages = 0; pages < maxPages; pages++) {
|
|
31971
32094
|
const result = await runPage(token);
|
|
31972
32095
|
const parsed = parsePage(result, itemsKey);
|
|
@@ -31975,8 +32098,17 @@ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
|
31975
32098
|
}
|
|
31976
32099
|
base = parsed;
|
|
31977
32100
|
merged.push(...parsed[itemsKey]);
|
|
31978
|
-
|
|
31979
|
-
if (
|
|
32101
|
+
const next = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
|
|
32102
|
+
if (next === void 0) {
|
|
32103
|
+
token = void 0;
|
|
32104
|
+
break;
|
|
32105
|
+
}
|
|
32106
|
+
if (fetched.has(next)) {
|
|
32107
|
+
token = next;
|
|
32108
|
+
break;
|
|
32109
|
+
}
|
|
32110
|
+
fetched.add(next);
|
|
32111
|
+
token = next;
|
|
31980
32112
|
}
|
|
31981
32113
|
return finish(base, itemsKey, merged, token);
|
|
31982
32114
|
}
|
|
@@ -32000,85 +32132,6 @@ function finish(base, itemsKey, merged, token) {
|
|
|
32000
32132
|
return rawTextResult(JSON.stringify(out));
|
|
32001
32133
|
}
|
|
32002
32134
|
|
|
32003
|
-
// ../gogcli-mcp/src/attachments.ts
|
|
32004
|
-
var MAX_INLINE_ATTACHMENT_BYTES = 8 * 1024 * 1024;
|
|
32005
|
-
var RUNNER_MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
32006
|
-
var RUNNER_BODY_JSON_RESERVE_BYTES = 256 * 1024;
|
|
32007
|
-
var MAX_REQUEST_PAYLOAD_WIRE_BYTES = RUNNER_MAX_BODY_BYTES - RUNNER_BODY_JSON_RESERVE_BYTES;
|
|
32008
|
-
var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
|
|
32009
|
-
function wireBytesOf(arg) {
|
|
32010
|
-
if (typeof arg === "string") return Buffer.byteLength(arg, "utf8");
|
|
32011
|
-
return arg.encoding === "base64" ? arg.contents.length : Buffer.byteLength(arg.contents, "utf8");
|
|
32012
|
-
}
|
|
32013
|
-
var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
|
|
32014
|
-
var INLINE_ATTACHMENT_LIMITS_TEXT = `up to ${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)} per file and ${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)} in total`;
|
|
32015
|
-
var inlineAttachmentSchema = external_exports.object({
|
|
32016
|
-
filename: external_exports.string().min(1).describe(
|
|
32017
|
-
`Filename the recipient will see, e.g. "pendant-layouts.png". gog infers the attachment's MIME type from this extension, so give it the right one \u2014 a .png sent as "layouts" arrives as an untyped blob. Must be a single filename, not a path.`
|
|
32018
|
-
),
|
|
32019
|
-
contentBase64: external_exports.string().min(1).describe(
|
|
32020
|
-
"The file's bytes, base64-encoded (standard alphabet, with padding). This is the whole point of this parameter: the bytes travel with the request, so nothing needs to exist on the gog server's filesystem."
|
|
32021
|
-
)
|
|
32022
|
-
});
|
|
32023
|
-
var attachInlineParam = external_exports.array(inlineAttachmentSchema).optional().describe(
|
|
32024
|
-
`Attachments supplied as BYTES rather than as server-side paths \u2014 use this whenever you hold a file and the gog server does not, which is always the case on the hosted connector and on any remote deployment. Each entry is {filename, contentBase64} (${INLINE_ATTACHMENT_LIMITS_TEXT}). Can be combined with \`attach\`: the two name disjoint files (paths read on the server vs. bytes sent with the call), and both end up as ordinary attachments on the message.`
|
|
32025
|
-
);
|
|
32026
|
-
function validateFilename(filename, where) {
|
|
32027
|
-
if (/[/\\]/.test(filename)) {
|
|
32028
|
-
throw new Error(
|
|
32029
|
-
`${where}: filename ${JSON.stringify(filename)} must be a bare filename, not a path. Pass just the name the recipient should see, e.g. "report.pdf".`
|
|
32030
|
-
);
|
|
32031
|
-
}
|
|
32032
|
-
if (/[\x00-\x1f]/.test(filename) || /^\.+$/.test(filename) || filename.length > 200) {
|
|
32033
|
-
throw new Error(
|
|
32034
|
-
`${where}: filename ${JSON.stringify(filename)} is not a usable filename (no control characters, not "."/"..", 200 characters max).`
|
|
32035
|
-
);
|
|
32036
|
-
}
|
|
32037
|
-
}
|
|
32038
|
-
function decodedLength(contentBase64) {
|
|
32039
|
-
const buf = Buffer.from(contentBase64, "base64");
|
|
32040
|
-
return buf.toString("base64") === contentBase64 ? buf.length : null;
|
|
32041
|
-
}
|
|
32042
|
-
function inlineFileArg(flag, attachment, opts = {}) {
|
|
32043
|
-
const { filename, contentBase64 } = attachment;
|
|
32044
|
-
const where = opts.where ?? `attachInline entry ${JSON.stringify(filename)}`;
|
|
32045
|
-
validateFilename(filename, where);
|
|
32046
|
-
const bytes = decodedLength(contentBase64);
|
|
32047
|
-
if (bytes === null) {
|
|
32048
|
-
throw new Error(
|
|
32049
|
-
`${where}: contents are not valid base64. Send the standard alphabet with padding and no line breaks \u2014 the value must survive a decode/re-encode round trip unchanged.`
|
|
32050
|
-
);
|
|
32051
|
-
}
|
|
32052
|
-
if (bytes > MAX_INLINE_ATTACHMENT_BYTES) {
|
|
32053
|
-
throw new Error(
|
|
32054
|
-
`${where}: ${bytes} bytes exceeds the ${MAX_INLINE_ATTACHMENT_BYTES}-byte (${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)}) per-file limit for inline content. Upload it to Drive and link to it instead, or send it from a local (stdio) deployment using a real server-side path.`
|
|
32055
|
-
);
|
|
32056
|
-
}
|
|
32057
|
-
const arg = { kind: "file", flag, contents: contentBase64, encoding: "base64", filename };
|
|
32058
|
-
if (opts.positional) arg.positional = true;
|
|
32059
|
-
return { arg, bytes };
|
|
32060
|
-
}
|
|
32061
|
-
function inlineAttachmentArgs(flag, attachments, siblingArgs = []) {
|
|
32062
|
-
if (!attachments?.length) return [];
|
|
32063
|
-
const args = [];
|
|
32064
|
-
const siblingWire = siblingArgs.reduce((sum, arg) => sum + wireBytesOf(arg), 0);
|
|
32065
|
-
let attachmentWire = 0;
|
|
32066
|
-
let decodedTotal = 0;
|
|
32067
|
-
for (const attachment of attachments) {
|
|
32068
|
-
const { arg, bytes } = inlineFileArg(flag, attachment);
|
|
32069
|
-
attachmentWire += arg.contents.length;
|
|
32070
|
-
decodedTotal += bytes;
|
|
32071
|
-
if (siblingWire + attachmentWire > MAX_REQUEST_PAYLOAD_WIRE_BYTES) {
|
|
32072
|
-
const blame = attachmentWire <= MAX_REQUEST_PAYLOAD_WIRE_BYTES ? ` These attachments would fit on their own; the rest of the message (its body, mostly) spends ${siblingWire} bytes of the same budget.` : "";
|
|
32073
|
-
throw new Error(
|
|
32074
|
-
`This message is too large to send: ${decodedTotal} bytes of attachments (${attachmentWire} bytes once base64-encoded for transit) exceed the ${MAX_REQUEST_PAYLOAD_WIRE_BYTES}-byte request limit.${blame} The ceiling for attachments alone is ${MAX_INLINE_ATTACHMENT_TOTAL_BYTES} bytes (${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)}); a long body lowers it. Send fewer or smaller files per message, shorten the body, or upload the large files to Drive and link them.`
|
|
32075
|
-
);
|
|
32076
|
-
}
|
|
32077
|
-
args.push(arg);
|
|
32078
|
-
}
|
|
32079
|
-
return args;
|
|
32080
|
-
}
|
|
32081
|
-
|
|
32082
32135
|
// ../gogcli-mcp/src/tools/gmail.ts
|
|
32083
32136
|
function registerGmailTools(server) {
|
|
32084
32137
|
server.registerTool("gog_gmail_search", {
|
|
@@ -32170,7 +32223,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
32170
32223
|
);
|
|
32171
32224
|
|
|
32172
32225
|
// ../gogcli-mcp/src/server.ts
|
|
32173
|
-
var VERSION = true ? "2.
|
|
32226
|
+
var VERSION = true ? "2.26.0" : "0.0.0";
|
|
32174
32227
|
|
|
32175
32228
|
// ../gogcli-mcp/src/auth-log.ts
|
|
32176
32229
|
var FAILURES = /* @__PURE__ */ new Set([
|
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.26.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/mint.yaml
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
name: Google Gmail (gogcli)
|
|
3
|
+
slug: gogcli-mcp-gmail
|
|
4
|
+
summary: >-
|
|
5
|
+
Extended Gmail MCP server via gogcli — auth + full Gmail support (threads,
|
|
6
|
+
labels, drafts, attachments, forward, autoreply, bulk operations)
|
|
7
|
+
command:
|
|
8
|
+
# This package publishes a single bin; naming it keeps the install
|
|
9
|
+
# unambiguous alongside its eight sibling packages.
|
|
10
|
+
bin: gogcli-mcp-gmail
|
|
11
|
+
env:
|
|
12
|
+
- name: GOG_CLIENT_ID
|
|
13
|
+
required: false
|
|
14
|
+
help: >-
|
|
15
|
+
Google OAuth client id. NOTE: on the local-spawn path the child `gog`
|
|
16
|
+
receives a sanitized env — runner.ts drops GOG_ACCESS_TOKEN and every
|
|
17
|
+
*_TOKEN / *_SECRET / *_API_KEY / *_PRIVATE_KEY variable — so the CLI
|
|
18
|
+
authenticates from its own stored credentials under $HOME (see
|
|
19
|
+
state.dataDir), not from these variables being passed through.
|
|
20
|
+
- name: GOG_CLIENT_SECRET
|
|
21
|
+
secret: true
|
|
22
|
+
required: false
|
|
23
|
+
help: >-
|
|
24
|
+
Google OAuth client secret. Stripped from the spawned CLI's environment
|
|
25
|
+
by runner.ts's *_SECRET rule — see GOG_CLIENT_ID.
|
|
26
|
+
- name: GOG_REFRESH_TOKEN
|
|
27
|
+
secret: true
|
|
28
|
+
required: false
|
|
29
|
+
help: >-
|
|
30
|
+
Google OAuth refresh token. Stripped from the spawned CLI's environment
|
|
31
|
+
by runner.ts's *_TOKEN rule — see GOG_CLIENT_ID. A hosted deployment must
|
|
32
|
+
therefore carry gog's authorised-account state in its persisted data dir,
|
|
33
|
+
or drive a remote runner via GOG_RUNNER_URL.
|
|
34
|
+
- name: GOG_ACCESS_TOKEN
|
|
35
|
+
secret: true
|
|
36
|
+
required: false
|
|
37
|
+
help: >-
|
|
38
|
+
Deliberately removed from the spawned CLI's environment by runner.ts, so
|
|
39
|
+
that a stale directly-passed token cannot shadow gog's stored refresh
|
|
40
|
+
credential. Leave unset.
|
|
41
|
+
- name: GOG_ACCOUNT
|
|
42
|
+
required: false
|
|
43
|
+
help: >-
|
|
44
|
+
Which configured Google account to act as, when more than one is
|
|
45
|
+
authorised. Defaults to the single/most recent account.
|
|
46
|
+
- name: GOG_READONLY
|
|
47
|
+
required: false
|
|
48
|
+
help: >-
|
|
49
|
+
Set to 1 to refuse every mutating operation. Recommended when the
|
|
50
|
+
connector is shared or you only need reads.
|
|
51
|
+
- name: GOG_PATH
|
|
52
|
+
required: false
|
|
53
|
+
help: >-
|
|
54
|
+
Path to the `gog` binary. Leave unset when the dependency below supplies
|
|
55
|
+
it; set it only to point at a binary you manage yourself.
|
|
56
|
+
- name: GOG_RUNNER_URL
|
|
57
|
+
required: false
|
|
58
|
+
help: >-
|
|
59
|
+
URL of a remote gog runner to execute against instead of spawning the
|
|
60
|
+
local binary. If you set this, add its host to egress.allow.
|
|
61
|
+
- name: GOG_RUNNER_KEY
|
|
62
|
+
secret: true
|
|
63
|
+
required: false
|
|
64
|
+
help: >-
|
|
65
|
+
Shared key authenticating calls to GOG_RUNNER_URL. Required whenever that
|
|
66
|
+
is set.
|
|
67
|
+
- name: GOG_TIMEZONE
|
|
68
|
+
required: false
|
|
69
|
+
help: >-
|
|
70
|
+
The IANA zone gog itself formats its naive timestamps in. The wrapper
|
|
71
|
+
reads it (naiveSourceTimeZone) to re-attach the correct offset, so it
|
|
72
|
+
should match gog's own configuration. Falls back to DISPLAY_TZ, then to
|
|
73
|
+
America/New_York.
|
|
74
|
+
- name: DISPLAY_TZ
|
|
75
|
+
required: false
|
|
76
|
+
help: >-
|
|
77
|
+
The IANA zone every rendered *Display field uses (displayTimeZone,
|
|
78
|
+
default America/New_York). It does not read GOG_TIMEZONE — the fallback
|
|
79
|
+
runs the other way, so this is also what GOG_TIMEZONE falls back to. An
|
|
80
|
+
invalid value degrades to the default rather than throwing.
|
|
81
|
+
dependencies:
|
|
82
|
+
# Every tool shells out to the `gog` CLI; without it the server starts and
|
|
83
|
+
# then fails on the first call. This tag must track
|
|
84
|
+
# packages/gogcli-mcp/src/runner.ts's MIN_GOG_VERSION (the floor the tools
|
|
85
|
+
# assume) and the fly-gog-runner/Dockerfile GOG_VERSION build arg. See
|
|
86
|
+
# CLAUDE.md "Required gog version" — bumping the floor means bumping these
|
|
87
|
+
# nine pins too.
|
|
88
|
+
- kind: github-release
|
|
89
|
+
repo: openclaw/gogcli
|
|
90
|
+
tag: v0.38.1
|
|
91
|
+
asset: "gogcli_*_linux_amd64.tar.gz"
|
|
92
|
+
bin: [gog]
|
|
93
|
+
state:
|
|
94
|
+
dataDir: true
|
|
95
|
+
reason: >-
|
|
96
|
+
`gog` keeps its authorised-account state and token cache under $HOME.
|
|
97
|
+
Without a persistent data dir every cold start has no account configured and
|
|
98
|
+
every tool call fails until the credentials are re-supplied.
|
|
99
|
+
egress:
|
|
100
|
+
allow:
|
|
101
|
+
# Google OAuth token exchange and the Google REST APIs the CLI calls.
|
|
102
|
+
- oauth2.googleapis.com
|
|
103
|
+
- www.googleapis.com
|
|
104
|
+
- googleapis.com
|
|
105
|
+
#
|
|
106
|
+
# NOTE: if you set GOG_RUNNER_URL to run against a remote gog runner, add
|
|
107
|
+
# that host here too — it is supplied at runtime and cannot be declared.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-gmail",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.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>",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@chrischall/mcp-utils": "^0.
|
|
27
|
+
"@chrischall/mcp-utils": "^0.15.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|