gogcli-mcp-drive 4.6.3 → 4.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -3
- package/dist/index.js +127 -62
- package/manifest.json +23 -2
- package/package.json +2 -2
- package/server.json +20 -2
- package/src/tools/drive-extra.ts +33 -31
- package/tests/tool-annotations.test.ts +10 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
|
|
10
|
-
"version": "4.
|
|
10
|
+
"version": "4.7.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli (Drive)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
|
|
18
|
-
"version": "4.
|
|
18
|
+
"version": "4.7.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-drive",
|
|
3
3
|
"displayName": "gogcli (Drive)",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.7.1",
|
|
5
5
|
"description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"comments",
|
|
22
22
|
"shared-drives"
|
|
23
23
|
],
|
|
24
|
-
"skills": "
|
|
25
|
-
"
|
|
24
|
+
"skills": ".",
|
|
25
|
+
"mcpServers": "./.mcp.json"
|
|
26
26
|
}
|
package/dist/index.js
CHANGED
|
@@ -34537,11 +34537,19 @@ var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
|
34537
34537
|
var McpToolError = class extends Error {
|
|
34538
34538
|
/** Actionable remediation text, when one applies. */
|
|
34539
34539
|
hint;
|
|
34540
|
+
/** The upstream HTTP status this failure came from, when known. */
|
|
34541
|
+
status;
|
|
34542
|
+
/** What kind of failure this is, when the thrower knows. */
|
|
34543
|
+
kind;
|
|
34540
34544
|
constructor(message, opts) {
|
|
34541
34545
|
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
34542
34546
|
this.name = "McpToolError";
|
|
34543
34547
|
if (opts?.hint !== void 0)
|
|
34544
34548
|
this.hint = opts.hint;
|
|
34549
|
+
if (opts?.status !== void 0)
|
|
34550
|
+
this.status = opts.status;
|
|
34551
|
+
if (opts?.kind !== void 0)
|
|
34552
|
+
this.kind = opts.kind;
|
|
34545
34553
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
34546
34554
|
}
|
|
34547
34555
|
};
|
|
@@ -34566,7 +34574,7 @@ var API_KEY_RE = new RegExp([
|
|
|
34566
34574
|
// webhook signing secret (Stripe-style)
|
|
34567
34575
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34568
34576
|
var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
34569
|
-
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
34577
|
+
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
34570
34578
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
34571
34579
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
34572
34580
|
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -34577,6 +34585,8 @@ var JSON_SECRET_KEYS = [
|
|
|
34577
34585
|
"api[_-]?secret",
|
|
34578
34586
|
"(?:x[_-])?api[_-]?key",
|
|
34579
34587
|
"private[_-]?key",
|
|
34588
|
+
"code[_-]?verifier",
|
|
34589
|
+
// PKCE; a bare `verifier` key is too generic for JSON
|
|
34580
34590
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
34581
34591
|
// bounded like HEADER_SECRET_RE
|
|
34582
34592
|
"(?:proxy-)?authorization",
|
|
@@ -34607,6 +34617,11 @@ function redactSecrets(text) {
|
|
|
34607
34617
|
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(GOOGLE_OAUTH_TOKEN_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_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
34608
34618
|
}
|
|
34609
34619
|
var ERROR_REDACTION_INPUT_MAX = 64 * 1024;
|
|
34620
|
+
function messageOf(err) {
|
|
34621
|
+
if (err instanceof Error)
|
|
34622
|
+
return err.message;
|
|
34623
|
+
return String(err);
|
|
34624
|
+
}
|
|
34610
34625
|
|
|
34611
34626
|
// ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
|
|
34612
34627
|
var VIEWS = ["compact", "full", "raw"];
|
|
@@ -34752,6 +34767,7 @@ function escapeKey(key) {
|
|
|
34752
34767
|
var DEFAULT_REQUEST_KEY = "confirmation";
|
|
34753
34768
|
var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
34754
34769
|
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";
|
|
34770
|
+
var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
|
|
34755
34771
|
var STATE_PREFIX = "mcpu.confirm.v1.";
|
|
34756
34772
|
function bindingKey(binding) {
|
|
34757
34773
|
if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
|
|
@@ -34824,6 +34840,15 @@ ${JSON.stringify(preview, null, 2)}`,
|
|
|
34824
34840
|
};
|
|
34825
34841
|
const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
|
|
34826
34842
|
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
34843
|
+
if (options.elicitation === false) {
|
|
34844
|
+
return textResult({
|
|
34845
|
+
confirmed: false,
|
|
34846
|
+
dispatched: false,
|
|
34847
|
+
action: options.action,
|
|
34848
|
+
reason: "confirmation-unsupported",
|
|
34849
|
+
note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
|
|
34850
|
+
});
|
|
34851
|
+
}
|
|
34827
34852
|
if (response.kind === "missing") {
|
|
34828
34853
|
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
34829
34854
|
return textResult({
|
|
@@ -35018,7 +35043,7 @@ async function tokenConfirmation(action, fb) {
|
|
|
35018
35043
|
}
|
|
35019
35044
|
async function requireConfirmationWithFallback(ctx, options) {
|
|
35020
35045
|
const { tokenFallback, ...confirmation } = options;
|
|
35021
|
-
if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
|
|
35046
|
+
if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
|
|
35022
35047
|
return tokenConfirmation(options.action, tokenFallback);
|
|
35023
35048
|
}
|
|
35024
35049
|
return requireConfirmation(ctx, confirmation);
|
|
@@ -35038,7 +35063,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
35038
35063
|
if (typeof raw === "string") {
|
|
35039
35064
|
const trimmed = raw.trim();
|
|
35040
35065
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
35041
|
-
return trimmed;
|
|
35066
|
+
return opts.trim === false ? raw : trimmed;
|
|
35042
35067
|
}
|
|
35043
35068
|
}
|
|
35044
35069
|
return opts.default;
|
|
@@ -35164,6 +35189,20 @@ function readConfirmMode(env = process.env) {
|
|
|
35164
35189
|
}
|
|
35165
35190
|
return "refuse";
|
|
35166
35191
|
}
|
|
35192
|
+
function readConfirmElicitation(env = process.env) {
|
|
35193
|
+
const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
|
|
35194
|
+
if (!raw || raw === "on")
|
|
35195
|
+
return "on";
|
|
35196
|
+
if (raw === "off")
|
|
35197
|
+
return "off";
|
|
35198
|
+
const key = `elicitation:${raw}`;
|
|
35199
|
+
if (!warned.has(key)) {
|
|
35200
|
+
warned.add(key);
|
|
35201
|
+
process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
|
|
35202
|
+
`);
|
|
35203
|
+
}
|
|
35204
|
+
return "on";
|
|
35205
|
+
}
|
|
35167
35206
|
function readConfirmTtl(env) {
|
|
35168
35207
|
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
35169
35208
|
if (!raw)
|
|
@@ -35208,6 +35247,7 @@ function spentTokenStoreFromEnv(env = process.env) {
|
|
|
35208
35247
|
}
|
|
35209
35248
|
var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
35210
35249
|
var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
35250
|
+
var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
|
|
35211
35251
|
var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
|
|
35212
35252
|
function boundArgs(args) {
|
|
35213
35253
|
if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
|
|
@@ -35218,19 +35258,29 @@ function boundArgs(args) {
|
|
|
35218
35258
|
}
|
|
35219
35259
|
function confirmationFromEnv(options) {
|
|
35220
35260
|
const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
|
|
35261
|
+
if (args === void 0 || args === null) {
|
|
35262
|
+
throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
|
|
35263
|
+
}
|
|
35221
35264
|
const spent2 = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
35222
35265
|
const mode = readConfirmMode(env);
|
|
35223
35266
|
const ttlSeconds = readConfirmTtl(env);
|
|
35224
|
-
const
|
|
35225
|
-
const
|
|
35267
|
+
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
35268
|
+
const bound = boundArgs(args);
|
|
35269
|
+
const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
|
|
35270
|
+
const bindingAdded = rest.binding ? rest : {
|
|
35271
|
+
...rest,
|
|
35272
|
+
binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
|
|
35273
|
+
};
|
|
35274
|
+
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
35226
35275
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
35227
|
-
const
|
|
35276
|
+
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
35277
|
+
const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
|
|
35228
35278
|
return {
|
|
35229
35279
|
...confirmation,
|
|
35230
35280
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
35231
35281
|
};
|
|
35232
35282
|
}
|
|
35233
|
-
const boundSubject =
|
|
35283
|
+
const boundSubject = async () => {
|
|
35234
35284
|
const read = await subject();
|
|
35235
35285
|
if ("content" in read)
|
|
35236
35286
|
return read;
|
|
@@ -35257,12 +35307,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
|
35257
35307
|
...SUPPORTED_PROTOCOL_VERSIONS
|
|
35258
35308
|
]);
|
|
35259
35309
|
function hintResultOrRethrow(err) {
|
|
35310
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
35311
|
+
throw err;
|
|
35260
35312
|
if (err instanceof McpToolError && err.hint) {
|
|
35261
35313
|
return errorResult(`${err.message}
|
|
35262
35314
|
|
|
35263
35315
|
Hint: ${err.hint}`);
|
|
35264
35316
|
}
|
|
35265
|
-
|
|
35317
|
+
return errorResult(messageOf(err));
|
|
35266
35318
|
}
|
|
35267
35319
|
function mcpRequestFrom(args) {
|
|
35268
35320
|
const ctx = args.at(-1);
|
|
@@ -35496,7 +35548,7 @@ var PositiveInt = external_exports.number().int().positive();
|
|
|
35496
35548
|
var NonNegInt = external_exports.number().int().nonnegative();
|
|
35497
35549
|
var NonEmptyString = external_exports.string().min(1);
|
|
35498
35550
|
var IsoDate = external_exports.iso.date();
|
|
35499
|
-
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
35551
|
+
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
|
|
35500
35552
|
var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
35501
35553
|
var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
35502
35554
|
var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
@@ -36194,7 +36246,7 @@ function registerRunTool(server, options) {
|
|
|
36194
36246
|
}
|
|
36195
36247
|
server.registerTool(`gog_${service}_run`, {
|
|
36196
36248
|
description,
|
|
36197
|
-
annotations: { destructiveHint: true },
|
|
36249
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36198
36250
|
inputSchema: external_exports.object(inputSchema)
|
|
36199
36251
|
}, async (rawArgs) => {
|
|
36200
36252
|
const { subcommand, args, account } = rawArgs;
|
|
@@ -36398,11 +36450,20 @@ async function requireDispatchConfirmation(ctx, options) {
|
|
|
36398
36450
|
confirmationLabel: options.confirmationLabel,
|
|
36399
36451
|
...options.unsupportedNote ? { unsupportedNote: options.unsupportedNote } : {}
|
|
36400
36452
|
};
|
|
36401
|
-
if (!fallback)
|
|
36453
|
+
if (!fallback) {
|
|
36454
|
+
if (readConfirmElicitation() !== "off") return requireConfirmationWithFallback(ctx, confirmation);
|
|
36455
|
+
const offNote = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
|
|
36456
|
+
return requireConfirmationWithFallback(ctx, {
|
|
36457
|
+
...confirmation,
|
|
36458
|
+
elicitation: false,
|
|
36459
|
+
unsupportedNote: confirmation.unsupportedNote ? `${offNote} ${confirmation.unsupportedNote}` : offNote
|
|
36460
|
+
});
|
|
36461
|
+
}
|
|
36402
36462
|
return requireConfirmationWithFallback(ctx, confirmationFromEnv({
|
|
36403
36463
|
...confirmation,
|
|
36404
36464
|
tool: fallback.tool,
|
|
36405
36465
|
account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
|
|
36466
|
+
args: fallback.args,
|
|
36406
36467
|
confirmToken: fallback.confirmToken,
|
|
36407
36468
|
subject: fallback.subject,
|
|
36408
36469
|
instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
|
|
@@ -36415,13 +36476,13 @@ function resultText(result) {
|
|
|
36415
36476
|
}
|
|
36416
36477
|
|
|
36417
36478
|
// ../gogcli-mcp/src/tools/auth.ts
|
|
36418
|
-
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "
|
|
36479
|
+
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "alias"];
|
|
36419
36480
|
function registerAuthToolsWith(server, defaultServices) {
|
|
36420
36481
|
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.`;
|
|
36421
36482
|
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.";
|
|
36422
36483
|
server.registerTool("gog_auth_list", {
|
|
36423
36484
|
description: "List the Google accounts stored in gogcli, with their scopes. This reads local configuration only \u2014 it does not contact Google and does NOT tell you whether an account still works: a signed-out account whose refresh token expired or was revoked is listed here exactly like a healthy one, scopes and all. Use gog_auth_health to check whether an account can actually authenticate.",
|
|
36424
|
-
annotations: { readOnlyHint: true },
|
|
36485
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36425
36486
|
inputSchema: external_exports.object({})
|
|
36426
36487
|
}, async () => {
|
|
36427
36488
|
try {
|
|
@@ -36432,7 +36493,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36432
36493
|
});
|
|
36433
36494
|
server.registerTool("gog_auth_status", {
|
|
36434
36495
|
description: "Show gogcli auth CONFIGURATION: keyring backend, credential files, and auth setup. Despite the name this is not a health check \u2014 it reads local setup and does not contact Google, so it says nothing about whether an account can still authenticate. Use gog_auth_health for that.",
|
|
36435
|
-
annotations: { readOnlyHint: true },
|
|
36496
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36436
36497
|
inputSchema: external_exports.object({})
|
|
36437
36498
|
}, async () => {
|
|
36438
36499
|
try {
|
|
@@ -36443,7 +36504,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36443
36504
|
});
|
|
36444
36505
|
server.registerTool("gog_auth_health", {
|
|
36445
36506
|
description: `Check the LIVE health of each stored Google account. Unlike gog_auth_status (which only reports keyring/config setup), this performs a real token refresh against Google, so it detects expired or revoked (invalid_grant) refresh tokens \u2014 the account-wide sign-out that blocks every service. Reports per account: whether the token is currently valid, the mapped cause when it is not, how long ago it was authorized, and a warning as it approaches the 7-day refresh-token limit that applies to OAuth apps whose consent screen is still in "Testing" mode. Run it proactively to re-authorize on your own schedule instead of mid-task. On a hosted deployment this is the ONLY check that measures Google: a connector showing "connected" or "refreshed" has verified the client's connection to the MCP host, and nothing else \u2014 the Google credential lives in gog's keyring on that host and can be dead while the connection looks perfectly healthy.`,
|
|
36446
|
-
annotations: { readOnlyHint: true },
|
|
36507
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36447
36508
|
inputSchema: external_exports.object({})
|
|
36448
36509
|
}, async () => {
|
|
36449
36510
|
try {
|
|
@@ -36454,7 +36515,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36454
36515
|
});
|
|
36455
36516
|
server.registerTool("gog_auth_services", {
|
|
36456
36517
|
description: "List all Google services supported by gogcli and the OAuth scopes each requires.",
|
|
36457
|
-
annotations: { readOnlyHint: true },
|
|
36518
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36458
36519
|
inputSchema: external_exports.object({})
|
|
36459
36520
|
}, async () => {
|
|
36460
36521
|
try {
|
|
@@ -36465,7 +36526,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36465
36526
|
});
|
|
36466
36527
|
server.registerTool("gog_auth_add", {
|
|
36467
36528
|
description: "Authorize a Google account via browser-based OAuth. Opens a browser window where the user must sign in and grant access. Blocks for up to 5 minutes waiting for the user to complete authorization. If the browser does not open automatically, a fallback URL is included in the response. Use gog_auth_list to check which accounts are already configured.",
|
|
36468
|
-
annotations: { destructiveHint: true },
|
|
36529
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36469
36530
|
inputSchema: external_exports.object({
|
|
36470
36531
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
36471
36532
|
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
@@ -36485,7 +36546,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36485
36546
|
});
|
|
36486
36547
|
server.registerTool("gog_auth_add_url", {
|
|
36487
36548
|
description: "Begin REMOTE/headless Google authorization (step 1 of 2). Returns a sign-in URL to open in any browser \u2014 no local server or terminal on the gogcli host is needed, so this works on a hosted deployment where the interactive gog_auth_add cannot. Hand the URL to the user; after they sign in, the browser is redirected to a localhost URL that fails to load \u2014 that is expected. They copy that full redirected URL (from the address bar) and you pass it to gog_auth_add_complete. The link is valid for 10 minutes. If you pass a custom `services` here, pass the SAME value to gog_auth_add_complete or the second step will not match this one.",
|
|
36488
|
-
annotations: { readOnlyHint: true },
|
|
36549
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36489
36550
|
inputSchema: external_exports.object({
|
|
36490
36551
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
36491
36552
|
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
@@ -36502,7 +36563,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36502
36563
|
});
|
|
36503
36564
|
server.registerTool("gog_auth_add_complete", {
|
|
36504
36565
|
description: "Complete REMOTE/headless Google authorization (step 2 of 2). Takes the full redirected localhost URL the user copied after finishing gog_auth_add_url, exchanges it for a refresh token, and stores it. Use the SAME `services` value you passed to gog_auth_add_url. Must run within 10 minutes of step 1 and against the same gogcli host.",
|
|
36505
|
-
annotations: { destructiveHint: true },
|
|
36566
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36506
36567
|
inputSchema: external_exports.object({
|
|
36507
36568
|
email: external_exports.string().describe("Google account email being authorized (same as step 1)"),
|
|
36508
36569
|
redirectUrl: external_exports.string().describe(
|
|
@@ -36538,7 +36599,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36538
36599
|
});
|
|
36539
36600
|
registerRunTool(server, {
|
|
36540
36601
|
service: "auth",
|
|
36541
|
-
examples: '"
|
|
36602
|
+
examples: '"alias", "list", "status"',
|
|
36542
36603
|
omitAccount: true,
|
|
36543
36604
|
allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
|
|
36544
36605
|
note: "For browser-based authorization, use gog_auth_add instead."
|
|
@@ -36695,7 +36756,7 @@ var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime
|
|
|
36695
36756
|
function registerDriveTools(server) {
|
|
36696
36757
|
server.registerTool("gog_drive_ls", {
|
|
36697
36758
|
description: "List files in a Google Drive folder (default: root).",
|
|
36698
|
-
annotations: { readOnlyHint: true },
|
|
36759
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36699
36760
|
inputSchema: external_exports.object({
|
|
36700
36761
|
folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
|
|
36701
36762
|
max: external_exports.number().optional().describe("Max results (default: 20)"),
|
|
@@ -36724,7 +36785,7 @@ function registerDriveTools(server) {
|
|
|
36724
36785
|
});
|
|
36725
36786
|
server.registerTool("gog_drive_search", {
|
|
36726
36787
|
description: "Search Google Drive files by full-text query.",
|
|
36727
|
-
annotations: { readOnlyHint: true },
|
|
36788
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36728
36789
|
inputSchema: external_exports.object({
|
|
36729
36790
|
query: external_exports.string().describe("Search query"),
|
|
36730
36791
|
// gog's `drive search` accepts no --fields mask, so unlike gog_drive_ls
|
|
@@ -36741,7 +36802,7 @@ function registerDriveTools(server) {
|
|
|
36741
36802
|
});
|
|
36742
36803
|
server.registerTool("gog_drive_get", {
|
|
36743
36804
|
description: "Get metadata for a Google Drive file.",
|
|
36744
|
-
annotations: { readOnlyHint: true },
|
|
36805
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36745
36806
|
inputSchema: external_exports.object({
|
|
36746
36807
|
fileId: external_exports.string().describe("File ID"),
|
|
36747
36808
|
// A --fields mask saves only 7% here: the default set is already narrow,
|
|
@@ -36761,7 +36822,7 @@ function registerDriveTools(server) {
|
|
|
36761
36822
|
});
|
|
36762
36823
|
server.registerTool("gog_drive_mkdir", {
|
|
36763
36824
|
description: "Create a new folder in Google Drive.",
|
|
36764
|
-
annotations: { destructiveHint: false },
|
|
36825
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
36765
36826
|
inputSchema: external_exports.object({
|
|
36766
36827
|
name: external_exports.string().describe("Folder name"),
|
|
36767
36828
|
account: accountParam
|
|
@@ -36771,7 +36832,7 @@ function registerDriveTools(server) {
|
|
|
36771
36832
|
});
|
|
36772
36833
|
server.registerTool("gog_drive_rename", {
|
|
36773
36834
|
description: "Rename a file or folder in Google Drive.",
|
|
36774
|
-
annotations: { destructiveHint: false },
|
|
36835
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
36775
36836
|
inputSchema: external_exports.object({
|
|
36776
36837
|
fileId: external_exports.string().describe("File or folder ID"),
|
|
36777
36838
|
newName: external_exports.string().describe("New name"),
|
|
@@ -36782,7 +36843,7 @@ function registerDriveTools(server) {
|
|
|
36782
36843
|
});
|
|
36783
36844
|
server.registerTool("gog_drive_move", {
|
|
36784
36845
|
description: "Move a file to a different folder in Google Drive.",
|
|
36785
|
-
annotations: { destructiveHint: false },
|
|
36846
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
36786
36847
|
inputSchema: external_exports.object({
|
|
36787
36848
|
fileId: external_exports.string().describe("File ID to move"),
|
|
36788
36849
|
parentId: external_exports.string().describe("Destination folder ID"),
|
|
@@ -36793,7 +36854,7 @@ function registerDriveTools(server) {
|
|
|
36793
36854
|
});
|
|
36794
36855
|
server.registerTool("gog_drive_delete", {
|
|
36795
36856
|
description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible). A permanent delete reads the file and asks the MCP host to show the user a confirmation prompt naming it first; nothing is deleted unless they accept. Moving to trash is recoverable and does not ask." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
36796
|
-
annotations: { destructiveHint: true },
|
|
36857
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36797
36858
|
inputSchema: external_exports.object({
|
|
36798
36859
|
fileId: external_exports.string().describe("File ID to delete"),
|
|
36799
36860
|
permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
|
|
@@ -36813,6 +36874,7 @@ function registerDriveTools(server) {
|
|
|
36813
36874
|
details: target,
|
|
36814
36875
|
unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
|
|
36815
36876
|
fallback: {
|
|
36877
|
+
args: { fileId, permanent, account },
|
|
36816
36878
|
tool: "gog_drive_delete",
|
|
36817
36879
|
account,
|
|
36818
36880
|
confirmToken,
|
|
@@ -36827,7 +36889,7 @@ function registerDriveTools(server) {
|
|
|
36827
36889
|
});
|
|
36828
36890
|
server.registerTool("gog_drive_share", {
|
|
36829
36891
|
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,
|
|
36830
|
-
annotations: { destructiveHint: true },
|
|
36892
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36831
36893
|
inputSchema: external_exports.object({
|
|
36832
36894
|
fileId: external_exports.string().describe("File or folder ID"),
|
|
36833
36895
|
to: external_exports.enum(["user", "anyone", "domain"]).describe("Share target type"),
|
|
@@ -36856,6 +36918,7 @@ function registerDriveTools(server) {
|
|
|
36856
36918
|
details: grant,
|
|
36857
36919
|
unsupportedNote: "Ask the user to share it themselves from Google Drive.",
|
|
36858
36920
|
fallback: {
|
|
36921
|
+
args: { fileId, to, email: email3, domain: domain2, role, account },
|
|
36859
36922
|
tool: "gog_drive_share",
|
|
36860
36923
|
account,
|
|
36861
36924
|
confirmToken,
|
|
@@ -36873,7 +36936,7 @@ function registerDriveTools(server) {
|
|
|
36873
36936
|
server.registerTool("gog_drive_extract_text", {
|
|
36874
36937
|
description: `Extract readable TEXT from a Drive file \u2014 PDF, image, docx, or a native Google Doc \u2014 and return it to you directly (closing the webViewLink dead end). Accepts a Drive file id, including the id from gog_gmail_attachment's deliveredVia:"drive" response. For non-Docs it copies the file to a temporary Google Doc so Drive OCRs it (works for scanned PDFs too), exports plain text, then deletes the temp Doc. Operates entirely via the Drive API within the existing drive scope \u2014 no host filesystem, no scope widening. For a large file, page through with offset/maxChars.`,
|
|
36875
36938
|
// Creates and deletes a temporary Doc for non-native files, so not read-only.
|
|
36876
|
-
annotations: { destructiveHint: false },
|
|
36939
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
36877
36940
|
inputSchema: external_exports.object({
|
|
36878
36941
|
fileId: external_exports.string().describe("Drive file ID (e.g. the id returned by gog_gmail_attachment)"),
|
|
36879
36942
|
ocrLanguage: external_exports.string().optional().describe(
|
|
@@ -36938,7 +37001,7 @@ function registerDriveTools(server) {
|
|
|
36938
37001
|
});
|
|
36939
37002
|
server.registerTool("gog_drive_read_bytes", {
|
|
36940
37003
|
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.",
|
|
36941
|
-
annotations: { readOnlyHint: true },
|
|
37004
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36942
37005
|
inputSchema: external_exports.object({
|
|
36943
37006
|
fileId: external_exports.string().describe("Drive file ID"),
|
|
36944
37007
|
account: accountParam
|
|
@@ -37011,7 +37074,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
37011
37074
|
);
|
|
37012
37075
|
|
|
37013
37076
|
// ../gogcli-mcp/src/server.ts
|
|
37014
|
-
var VERSION = true ? "4.
|
|
37077
|
+
var VERSION = true ? "4.7.1" : "0.0.0";
|
|
37015
37078
|
|
|
37016
37079
|
// ../gogcli-mcp/src/bootstrap-auth.ts
|
|
37017
37080
|
import { join as join7 } from "node:path";
|
|
@@ -37090,7 +37153,7 @@ function registerExtraDriveTools(server) {
|
|
|
37090
37153
|
server.registerTool("gog_drive_download", {
|
|
37091
37154
|
description: "Download a Drive file to the local filesystem. For Google Docs formats, specify an export format (pdf, csv, xlsx, pptx, txt, png, docx, md).",
|
|
37092
37155
|
// Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
|
|
37093
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
37156
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
37094
37157
|
inputSchema: external_exports.object({
|
|
37095
37158
|
fileId: external_exports.string().describe("File ID to download"),
|
|
37096
37159
|
out: external_exports.string().optional().describe("Output file path (default: gogcli config dir)"),
|
|
@@ -37108,7 +37171,7 @@ function registerExtraDriveTools(server) {
|
|
|
37108
37171
|
});
|
|
37109
37172
|
server.registerTool("gog_drive_upload", {
|
|
37110
37173
|
description: `Upload a file to Drive. Supply it EITHER as localPath (a path on the gog server's own filesystem \u2014 local stdio deployments only) OR as content (the bytes, base64-encoded, carried with the request), which is what works when this server runs remotely from you and no path you can name exists on it. Use replace to replace the content of an existing file (preserves link/permissions), or convert to auto-convert to a Google format. Pair replace with ifVersion to make the overwrite conditional: gog sends an atomic If-Match precondition and reports a conflict \u2014 applying nothing \u2014 if the file changed since you read it, instead of silently clobbering a concurrent edit. No read tool surfaces the version number: fetch it immediately before uploading with gog_drive_run { subcommand: "raw", args: ["<fileId>", "--fields=version"] } \u2014 it comes back as a JSON string, so pass it on as a number. Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.`,
|
|
37111
|
-
annotations: { destructiveHint: false },
|
|
37174
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37112
37175
|
inputSchema: external_exports.object({
|
|
37113
37176
|
localPath: external_exports.string().optional().describe(`Path to the file to upload, 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) this path does not exist and the call fails with "no such file or directory" \u2014 use content there. Exactly one of localPath / content is required. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
|
|
37114
37177
|
content: external_exports.string().optional().describe("The file's bytes, base64-encoded (standard alphabet, with padding) \u2014 upload a file you hold without it existing anywhere on the gog server. This is the only route that works when the caller and gog share no filesystem. Requires name (there is no path to take a filename from). Max 8 MiB; for anything larger use localPath from a local deployment. Exactly one of localPath / content is required \u2014 supplying both is an error, not a precedence rule."),
|
|
@@ -37159,7 +37222,7 @@ function registerExtraDriveTools(server) {
|
|
|
37159
37222
|
});
|
|
37160
37223
|
server.registerTool("gog_drive_sync_push", {
|
|
37161
37224
|
description: "Recursively push a local directory's contents into an existing Drive folder \u2014 uploads new files, updates changed ones (matched by name + MD5), and creates missing subfolders. One-way and additive: it never deletes anything on Drive. Use dryRun to preview the planned actions first.",
|
|
37162
|
-
annotations: { destructiveHint: true },
|
|
37225
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37163
37226
|
inputSchema: external_exports.object({
|
|
37164
37227
|
localPath: external_exports.string().describe("Local directory to push (its contents are mirrored into the parent folder). Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files)."),
|
|
37165
37228
|
parent: external_exports.string().describe("Existing destination Drive folder ID"),
|
|
@@ -37176,7 +37239,7 @@ function registerExtraDriveTools(server) {
|
|
|
37176
37239
|
});
|
|
37177
37240
|
server.registerTool("gog_drive_copy", {
|
|
37178
37241
|
description: "Copy a Drive file to a new file with the given name.",
|
|
37179
|
-
annotations: { destructiveHint: false },
|
|
37242
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37180
37243
|
inputSchema: external_exports.object({
|
|
37181
37244
|
fileId: external_exports.string().describe("File ID to copy"),
|
|
37182
37245
|
name: external_exports.string().describe("Name for the new copy"),
|
|
@@ -37190,7 +37253,7 @@ function registerExtraDriveTools(server) {
|
|
|
37190
37253
|
});
|
|
37191
37254
|
server.registerTool("gog_drive_url", {
|
|
37192
37255
|
description: "Print shareable web URLs for one or more Drive files.",
|
|
37193
|
-
annotations: { readOnlyHint: true },
|
|
37256
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37194
37257
|
inputSchema: external_exports.object({
|
|
37195
37258
|
fileIds: external_exports.array(external_exports.string()).min(1).describe("One or more file IDs to get URLs for"),
|
|
37196
37259
|
account: accountParam
|
|
@@ -37200,7 +37263,7 @@ function registerExtraDriveTools(server) {
|
|
|
37200
37263
|
});
|
|
37201
37264
|
server.registerTool("gog_drive_permissions", {
|
|
37202
37265
|
description: "List permissions on a Drive file.",
|
|
37203
|
-
annotations: { readOnlyHint: true },
|
|
37266
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37204
37267
|
inputSchema: external_exports.object({
|
|
37205
37268
|
fileId: external_exports.string().describe("File ID"),
|
|
37206
37269
|
max: external_exports.number().optional().describe("Max results (default: 100)"),
|
|
@@ -37217,7 +37280,7 @@ function registerExtraDriveTools(server) {
|
|
|
37217
37280
|
});
|
|
37218
37281
|
server.registerTool("gog_drive_unshare", {
|
|
37219
37282
|
description: "Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.",
|
|
37220
|
-
annotations: { destructiveHint: true },
|
|
37283
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37221
37284
|
inputSchema: external_exports.object({
|
|
37222
37285
|
fileId: external_exports.string().describe("File ID"),
|
|
37223
37286
|
permissionId: external_exports.string().describe("Permission ID to remove"),
|
|
@@ -37228,7 +37291,7 @@ function registerExtraDriveTools(server) {
|
|
|
37228
37291
|
});
|
|
37229
37292
|
server.registerTool("gog_drive_drives_list", {
|
|
37230
37293
|
description: "List shared drives (Team Drives) accessible to the account.",
|
|
37231
|
-
annotations: { readOnlyHint: true },
|
|
37294
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37232
37295
|
inputSchema: external_exports.object({
|
|
37233
37296
|
max: external_exports.number().optional().describe("Max results (default: 100, max allowed: 100)"),
|
|
37234
37297
|
pageToken: pageTokenParam,
|
|
@@ -37248,7 +37311,7 @@ function registerExtraDriveTools(server) {
|
|
|
37248
37311
|
});
|
|
37249
37312
|
server.registerTool("gog_drive_comments_list", {
|
|
37250
37313
|
description: "List comments on a Drive file.",
|
|
37251
|
-
annotations: { readOnlyHint: true },
|
|
37314
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37252
37315
|
inputSchema: external_exports.object({
|
|
37253
37316
|
fileId: external_exports.string().describe("File ID"),
|
|
37254
37317
|
since: external_exports.string().optional().describe("Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)"),
|
|
@@ -37265,7 +37328,7 @@ function registerExtraDriveTools(server) {
|
|
|
37265
37328
|
});
|
|
37266
37329
|
server.registerTool("gog_drive_comments_get", {
|
|
37267
37330
|
description: "Get a single comment on a Drive file by ID.",
|
|
37268
|
-
annotations: { readOnlyHint: true },
|
|
37331
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37269
37332
|
inputSchema: external_exports.object({
|
|
37270
37333
|
fileId: external_exports.string().describe("File ID"),
|
|
37271
37334
|
commentId: external_exports.string().describe("Comment ID"),
|
|
@@ -37276,7 +37339,7 @@ function registerExtraDriveTools(server) {
|
|
|
37276
37339
|
});
|
|
37277
37340
|
server.registerTool("gog_drive_comments_add", {
|
|
37278
37341
|
description: "Add a new comment to a Drive file. Drive notifies the file's owner and everyone the text +mentions, so this reads the file and asks the MCP host to show the user a confirmation prompt with the file, the text and who is mentioned first; nothing is posted unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
|
|
37279
|
-
annotations: { destructiveHint: false },
|
|
37342
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37280
37343
|
inputSchema: external_exports.object({
|
|
37281
37344
|
fileId: external_exports.string().describe("File ID"),
|
|
37282
37345
|
content: external_exports.string().describe("Comment text"),
|
|
@@ -37296,6 +37359,7 @@ function registerExtraDriveTools(server) {
|
|
|
37296
37359
|
details: { file: file2, textPreview: bodyPreview(content), mentions },
|
|
37297
37360
|
unsupportedNote: "Ask the user to comment from Google Drive.",
|
|
37298
37361
|
fallback: {
|
|
37362
|
+
args: { fileId, content, account },
|
|
37299
37363
|
tool: "gog_drive_comments_add",
|
|
37300
37364
|
account,
|
|
37301
37365
|
confirmToken,
|
|
@@ -37310,7 +37374,7 @@ function registerExtraDriveTools(server) {
|
|
|
37310
37374
|
});
|
|
37311
37375
|
server.registerTool("gog_drive_comments_update", {
|
|
37312
37376
|
description: "Update the text of an existing comment.",
|
|
37313
|
-
annotations: { destructiveHint: false },
|
|
37377
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37314
37378
|
inputSchema: external_exports.object({
|
|
37315
37379
|
fileId: external_exports.string().describe("File ID"),
|
|
37316
37380
|
commentId: external_exports.string().describe("Comment ID to update"),
|
|
@@ -37322,7 +37386,7 @@ function registerExtraDriveTools(server) {
|
|
|
37322
37386
|
});
|
|
37323
37387
|
server.registerTool("gog_drive_comments_delete", {
|
|
37324
37388
|
description: "Delete a comment from a Drive file.",
|
|
37325
|
-
annotations: { destructiveHint: true },
|
|
37389
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37326
37390
|
inputSchema: external_exports.object({
|
|
37327
37391
|
fileId: external_exports.string().describe("File ID"),
|
|
37328
37392
|
commentId: external_exports.string().describe("Comment ID to delete"),
|
|
@@ -37333,7 +37397,7 @@ function registerExtraDriveTools(server) {
|
|
|
37333
37397
|
});
|
|
37334
37398
|
server.registerTool("gog_drive_comments_reply", {
|
|
37335
37399
|
description: 'Reply to an existing comment on a Drive file. Pass `action: "resolve"` or `"reopen"` to atomically flip the parent comment\'s resolved state via the Drive API\'s Reply.action field \u2014 avoids the older workaround of deleting the comment (which destroys review-thread context). Everyone on the thread and anyone the reply +mentions is notified, so this reads the file and the comment and asks the MCP host to show the user a confirmation prompt with both and the reply first; nothing is posted unless they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
37336
|
-
annotations: { destructiveHint: false },
|
|
37400
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37337
37401
|
inputSchema: external_exports.object({
|
|
37338
37402
|
fileId: external_exports.string().describe("File ID"),
|
|
37339
37403
|
commentId: external_exports.string().describe("Comment ID to reply to"),
|
|
@@ -37360,6 +37424,7 @@ function registerExtraDriveTools(server) {
|
|
|
37360
37424
|
details: { file: file2, replyingTo, textPreview: bodyPreview(content), mentions, ...action ? { action } : {} },
|
|
37361
37425
|
unsupportedNote: "Ask the user to reply from Google Drive.",
|
|
37362
37426
|
fallback: {
|
|
37427
|
+
args: { fileId, commentId, content, action, account },
|
|
37363
37428
|
tool: "gog_drive_comments_reply",
|
|
37364
37429
|
account,
|
|
37365
37430
|
confirmToken,
|
|
@@ -37374,7 +37439,7 @@ function registerExtraDriveTools(server) {
|
|
|
37374
37439
|
});
|
|
37375
37440
|
server.registerTool("gog_drive_comments_resolve", {
|
|
37376
37441
|
description: 'Resolve a comment on a Drive file (mark as done) without posting a reply. To resolve while replying, use gog_drive_comments_reply with action: "resolve".',
|
|
37377
|
-
annotations: { destructiveHint: true },
|
|
37442
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37378
37443
|
inputSchema: external_exports.object({
|
|
37379
37444
|
fileId: external_exports.string().describe("File ID"),
|
|
37380
37445
|
commentId: external_exports.string().describe("Comment ID to resolve"),
|
|
@@ -37385,7 +37450,7 @@ function registerExtraDriveTools(server) {
|
|
|
37385
37450
|
});
|
|
37386
37451
|
server.registerTool("gog_drive_comments_reopen", {
|
|
37387
37452
|
description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
|
|
37388
|
-
annotations: { destructiveHint: true },
|
|
37453
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37389
37454
|
inputSchema: external_exports.object({
|
|
37390
37455
|
fileId: external_exports.string().describe("File ID"),
|
|
37391
37456
|
commentId: external_exports.string().describe("Comment ID to reopen"),
|
|
@@ -37396,7 +37461,7 @@ function registerExtraDriveTools(server) {
|
|
|
37396
37461
|
});
|
|
37397
37462
|
server.registerTool("gog_drive_du", {
|
|
37398
37463
|
description: "Summarize Drive folder sizes (disk-usage style) starting from a folder.",
|
|
37399
|
-
annotations: { readOnlyHint: true },
|
|
37464
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37400
37465
|
inputSchema: external_exports.object({
|
|
37401
37466
|
parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
|
|
37402
37467
|
depth: external_exports.number().optional().describe("Depth for folder totals (default: 1)"),
|
|
@@ -37418,7 +37483,7 @@ function registerExtraDriveTools(server) {
|
|
|
37418
37483
|
});
|
|
37419
37484
|
server.registerTool("gog_drive_tree", {
|
|
37420
37485
|
description: "Print a read-only folder tree starting from a folder.",
|
|
37421
|
-
annotations: { readOnlyHint: true },
|
|
37486
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37422
37487
|
inputSchema: external_exports.object({
|
|
37423
37488
|
parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
|
|
37424
37489
|
depth: external_exports.number().optional().describe("Max depth (0 = unlimited; default: 2)"),
|
|
@@ -37436,7 +37501,7 @@ function registerExtraDriveTools(server) {
|
|
|
37436
37501
|
});
|
|
37437
37502
|
server.registerTool("gog_drive_changes_start_token", {
|
|
37438
37503
|
description: "Get a Drive changes start page token \u2014 the cursor you pass to gog_drive_changes_list to enumerate changes since this point.",
|
|
37439
|
-
annotations: { readOnlyHint: true },
|
|
37504
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37440
37505
|
inputSchema: external_exports.object({
|
|
37441
37506
|
drive: external_exports.string().optional().describe("Shared drive ID for a shared-drive change log"),
|
|
37442
37507
|
account: accountParam
|
|
@@ -37448,7 +37513,7 @@ function registerExtraDriveTools(server) {
|
|
|
37448
37513
|
});
|
|
37449
37514
|
server.registerTool("gog_drive_changes_list", {
|
|
37450
37515
|
description: "List Drive changes since a page token (for sync/automation). Get the initial token from gog_drive_changes_start_token; the response includes a newStartPageToken to persist for the next poll.",
|
|
37451
|
-
annotations: { readOnlyHint: true },
|
|
37516
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37452
37517
|
inputSchema: external_exports.object({
|
|
37453
37518
|
token: external_exports.string().describe("Start page token or next page token"),
|
|
37454
37519
|
max: external_exports.number().optional().describe("Max results (default: 100)"),
|
|
@@ -37471,7 +37536,7 @@ function registerExtraDriveTools(server) {
|
|
|
37471
37536
|
});
|
|
37472
37537
|
server.registerTool("gog_drive_revisions_list", {
|
|
37473
37538
|
description: "List a file's revision history \u2014 paged revision metadata plus provider export links for each revision.",
|
|
37474
|
-
annotations: { readOnlyHint: true },
|
|
37539
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37475
37540
|
inputSchema: external_exports.object({
|
|
37476
37541
|
fileId: external_exports.string().describe("File ID"),
|
|
37477
37542
|
...paginationParams,
|
|
@@ -37484,7 +37549,7 @@ function registerExtraDriveTools(server) {
|
|
|
37484
37549
|
});
|
|
37485
37550
|
server.registerTool("gog_drive_revisions_get", {
|
|
37486
37551
|
description: "Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.",
|
|
37487
|
-
annotations: { readOnlyHint: true },
|
|
37552
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37488
37553
|
inputSchema: external_exports.object({
|
|
37489
37554
|
fileId: external_exports.string().describe("File ID"),
|
|
37490
37555
|
revisionId: external_exports.string().describe("Revision ID"),
|
|
@@ -37495,7 +37560,7 @@ function registerExtraDriveTools(server) {
|
|
|
37495
37560
|
});
|
|
37496
37561
|
server.registerTool("gog_drive_shortcut_create", {
|
|
37497
37562
|
description: "Create a Drive shortcut to a file or folder inside a destination folder. Shortcuts are classified distinctly in listing/tree output and are never followed by tree scans.",
|
|
37498
|
-
annotations: { destructiveHint: false },
|
|
37563
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37499
37564
|
inputSchema: external_exports.object({
|
|
37500
37565
|
targetId: external_exports.string().describe("File or folder ID the shortcut points to"),
|
|
37501
37566
|
parent: external_exports.string().describe("Destination folder ID for the shortcut"),
|
|
@@ -37509,7 +37574,7 @@ function registerExtraDriveTools(server) {
|
|
|
37509
37574
|
});
|
|
37510
37575
|
server.registerTool("gog_drive_labels_list", {
|
|
37511
37576
|
description: "List Drive label schemas available to the account.",
|
|
37512
|
-
annotations: { readOnlyHint: true },
|
|
37577
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37513
37578
|
inputSchema: external_exports.object({
|
|
37514
37579
|
language: external_exports.string().optional().describe("BCP-47 language code"),
|
|
37515
37580
|
view: external_exports.enum(["LABEL_VIEW_BASIC", "LABEL_VIEW_FULL"]).optional().describe("Label view (default: LABEL_VIEW_BASIC)"),
|
|
@@ -37535,7 +37600,7 @@ function registerExtraDriveTools(server) {
|
|
|
37535
37600
|
});
|
|
37536
37601
|
server.registerTool("gog_drive_labels_get", {
|
|
37537
37602
|
description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
|
|
37538
|
-
annotations: { readOnlyHint: true },
|
|
37603
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37539
37604
|
inputSchema: external_exports.object({
|
|
37540
37605
|
name: external_exports.string().describe("Label schema name (e.g. labels/abc123)"),
|
|
37541
37606
|
language: external_exports.string().optional().describe("BCP-47 language code"),
|
|
@@ -37552,7 +37617,7 @@ function registerExtraDriveTools(server) {
|
|
|
37552
37617
|
});
|
|
37553
37618
|
server.registerTool("gog_drive_labels_file_list", {
|
|
37554
37619
|
description: "List labels applied to a Drive file.",
|
|
37555
|
-
annotations: { readOnlyHint: true },
|
|
37620
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37556
37621
|
inputSchema: external_exports.object({
|
|
37557
37622
|
fileId: external_exports.string().describe("File ID"),
|
|
37558
37623
|
max: external_exports.number().optional().describe("Max results (default: 100)"),
|
|
@@ -37569,7 +37634,7 @@ function registerExtraDriveTools(server) {
|
|
|
37569
37634
|
});
|
|
37570
37635
|
server.registerTool("gog_drive_labels_file_apply", {
|
|
37571
37636
|
description: 'Apply or update a label on a Drive file, optionally setting field values. Each field flag takes "fieldId=value" entries (repeatable). selection/integer/date/user values may be comma-separated within one entry for multi-valued fields.',
|
|
37572
|
-
annotations: { destructiveHint: false },
|
|
37637
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
37573
37638
|
inputSchema: external_exports.object({
|
|
37574
37639
|
fileId: external_exports.string().describe("File ID"),
|
|
37575
37640
|
labelId: external_exports.string().describe("Label ID to apply"),
|
|
@@ -37595,7 +37660,7 @@ function registerExtraDriveTools(server) {
|
|
|
37595
37660
|
});
|
|
37596
37661
|
server.registerTool("gog_drive_labels_file_remove", {
|
|
37597
37662
|
description: "Remove a label from a Drive file.",
|
|
37598
|
-
annotations: { destructiveHint: true },
|
|
37663
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
37599
37664
|
inputSchema: external_exports.object({
|
|
37600
37665
|
fileId: external_exports.string().describe("File ID"),
|
|
37601
37666
|
labelId: external_exports.string().describe("Label ID to remove"),
|
|
@@ -37606,7 +37671,7 @@ function registerExtraDriveTools(server) {
|
|
|
37606
37671
|
});
|
|
37607
37672
|
server.registerTool("gog_drive_activity", {
|
|
37608
37673
|
description: "Query the Drive Activity API for audit events (edits, creates, deletes, moves, shares, etc.) scoped to a file or folder and/or time range.",
|
|
37609
|
-
annotations: { readOnlyHint: true },
|
|
37674
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37610
37675
|
inputSchema: external_exports.object({
|
|
37611
37676
|
file: external_exports.string().optional().describe("Drive file ID to query"),
|
|
37612
37677
|
folder: external_exports.string().optional().describe("Drive folder ID; includes descendants"),
|
|
@@ -37638,7 +37703,7 @@ function registerExtraDriveTools(server) {
|
|
|
37638
37703
|
});
|
|
37639
37704
|
server.registerTool("gog_drive_audit_sharing", {
|
|
37640
37705
|
description: "Audit Drive sharing without mutation \u2014 find public (anyone-with-link) or external permissions across a folder tree or a single file.",
|
|
37641
|
-
annotations: { readOnlyHint: true },
|
|
37706
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37642
37707
|
inputSchema: external_exports.object({
|
|
37643
37708
|
file: external_exports.string().optional().describe("Audit one file ID instead of a folder tree"),
|
|
37644
37709
|
parent: external_exports.string().optional().describe("Folder ID to scan (default: root)"),
|
|
@@ -37664,7 +37729,7 @@ function registerExtraDriveTools(server) {
|
|
|
37664
37729
|
});
|
|
37665
37730
|
server.registerTool("gog_drive_audit_user", {
|
|
37666
37731
|
description: "Audit Drive sharing without mutation \u2014 find permissions granted to a specific user across a folder tree or a single file.",
|
|
37667
|
-
annotations: { readOnlyHint: true },
|
|
37732
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
37668
37733
|
inputSchema: external_exports.object({
|
|
37669
37734
|
user: external_exports.string().describe("User email to audit permissions for"),
|
|
37670
37735
|
file: external_exports.string().optional().describe("Audit one file ID instead of a folder tree"),
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-drive",
|
|
5
5
|
"display_name": "gogcli (Drive)",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.7.1",
|
|
7
7
|
"description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
"env": {
|
|
40
40
|
"GOG_ACCOUNT": "${user_config.gog_account}",
|
|
41
41
|
"GOG_PATH": "${user_config.gog_path}",
|
|
42
|
-
"GOG_FILE_ROOTS": "${user_config.gog_file_roots}"
|
|
42
|
+
"GOG_FILE_ROOTS": "${user_config.gog_file_roots}",
|
|
43
|
+
"DISPLAY_TZ": "${user_config.display_tz}",
|
|
44
|
+
"GOG_READONLY": "${user_config.gog_readonly}",
|
|
45
|
+
"GOG_TIMEZONE": "${user_config.gog_timezone}"
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
},
|
|
@@ -61,6 +64,24 @@
|
|
|
61
64
|
"title": "Allowed file directories",
|
|
62
65
|
"description": "Directories tools may read files from (attachments, uploads) or write files to (exports, downloads), separated by ':' (';' on Windows). Optional — defaults to ~/gogcli-mcp-files",
|
|
63
66
|
"required": false
|
|
67
|
+
},
|
|
68
|
+
"display_tz": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"title": "Display time zone",
|
|
71
|
+
"description": "zone for *Display fields and for interpreting naive gog values; defaults to America/New_York",
|
|
72
|
+
"required": false
|
|
73
|
+
},
|
|
74
|
+
"gog_readonly": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"title": "Read-only mode",
|
|
77
|
+
"description": "block all mutating gog API requests (injects gog's --readonly); set to 0/false/no/off (or unset) to allow writes",
|
|
78
|
+
"required": false
|
|
79
|
+
},
|
|
80
|
+
"gog_timezone": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"title": "gog time zone",
|
|
83
|
+
"description": "zone gog formats naive dates in; unset, runner.ts hands gog DISPLAY_TZ so the two cannot diverge",
|
|
84
|
+
"required": false
|
|
64
85
|
}
|
|
65
86
|
},
|
|
66
87
|
"tools": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-drive",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-drive",
|
|
5
5
|
"description": "Extended Google Drive MCP server via gogcli — auth + full Drive support (upload/download/permissions/comments/shared drives)",
|
|
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": "^
|
|
27
|
+
"@chrischall/mcp-utils": "^3.0.0",
|
|
28
28
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
29
29
|
"zod": "^4.6.1"
|
|
30
30
|
},
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp-drive"
|
|
9
9
|
},
|
|
10
|
-
"version": "4.
|
|
10
|
+
"version": "4.7.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-drive",
|
|
15
|
-
"version": "4.
|
|
15
|
+
"version": "4.7.1",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -28,6 +28,24 @@
|
|
|
28
28
|
"description": "Override path to the gogcli binary (auto-discovered otherwise)",
|
|
29
29
|
"isRequired": false,
|
|
30
30
|
"format": "string"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "DISPLAY_TZ",
|
|
34
|
+
"description": "zone for *Display fields and for interpreting naive gog values; defaults to America/New_York",
|
|
35
|
+
"isRequired": false,
|
|
36
|
+
"format": "string"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "GOG_READONLY",
|
|
40
|
+
"description": "block all mutating gog API requests (injects gog's --readonly); set to 0/false/no/off (or unset) to allow writes",
|
|
41
|
+
"isRequired": false,
|
|
42
|
+
"format": "string"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "GOG_TIMEZONE",
|
|
46
|
+
"description": "zone gog formats naive dates in; unset, runner.ts hands gog DISPLAY_TZ so the two cannot diverge",
|
|
47
|
+
"isRequired": false,
|
|
48
|
+
"format": "string"
|
|
31
49
|
}
|
|
32
50
|
]
|
|
33
51
|
}
|
package/src/tools/drive-extra.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
26
26
|
server.registerTool('gog_drive_download', {
|
|
27
27
|
description: 'Download a Drive file to the local filesystem. For Google Docs formats, specify an export format (pdf, csv, xlsx, pptx, txt, png, docx, md).',
|
|
28
28
|
// Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
|
|
29
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
29
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
30
30
|
inputSchema: z.object({
|
|
31
31
|
fileId: z.string().describe('File ID to download'),
|
|
32
32
|
out: z.string().optional().describe('Output file path (default: gogcli config dir)'),
|
|
@@ -55,7 +55,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
55
55
|
'No read tool surfaces the version number: fetch it immediately before uploading with ' +
|
|
56
56
|
'gog_drive_run { subcommand: "raw", args: ["<fileId>", "--fields=version"] } — it comes back as a JSON string, so pass it on as a number. ' +
|
|
57
57
|
'Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.',
|
|
58
|
-
annotations: { destructiveHint: false },
|
|
58
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
59
59
|
inputSchema: z.object({
|
|
60
60
|
localPath: z.string().optional().describe('Path to the file to upload, resolved ON THE GOG SERVER\'s filesystem — 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) this path does not exist and the call fails with "no such file or directory" — use content there. Exactly one of localPath / content is required. Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
|
|
61
61
|
content: z.string().optional().describe('The file\'s bytes, base64-encoded (standard alphabet, with padding) — upload a file you hold without it existing anywhere on the gog server. This is the only route that works when the caller and gog share no filesystem. Requires name (there is no path to take a filename from). Max 8 MiB; for anything larger use localPath from a local deployment. Exactly one of localPath / content is required — supplying both is an error, not a precedence rule.'),
|
|
@@ -117,7 +117,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
117
117
|
|
|
118
118
|
server.registerTool('gog_drive_sync_push', {
|
|
119
119
|
description: 'Recursively push a local directory\'s contents into an existing Drive folder — uploads new files, updates changed ones (matched by name + MD5), and creates missing subfolders. One-way and additive: it never deletes anything on Drive. Use dryRun to preview the planned actions first.',
|
|
120
|
-
annotations: { destructiveHint: true },
|
|
120
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
121
121
|
inputSchema: z.object({
|
|
122
122
|
localPath: z.string().describe('Local directory to push (its contents are mirrored into the parent folder). Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
|
|
123
123
|
parent: z.string().describe('Existing destination Drive folder ID'),
|
|
@@ -135,7 +135,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
135
135
|
|
|
136
136
|
server.registerTool('gog_drive_copy', {
|
|
137
137
|
description: 'Copy a Drive file to a new file with the given name.',
|
|
138
|
-
annotations: { destructiveHint: false },
|
|
138
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
139
139
|
inputSchema: z.object({
|
|
140
140
|
fileId: z.string().describe('File ID to copy'),
|
|
141
141
|
name: z.string().describe('Name for the new copy'),
|
|
@@ -150,7 +150,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
150
150
|
|
|
151
151
|
server.registerTool('gog_drive_url', {
|
|
152
152
|
description: 'Print shareable web URLs for one or more Drive files.',
|
|
153
|
-
annotations: { readOnlyHint: true },
|
|
153
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
154
154
|
inputSchema: z.object({
|
|
155
155
|
fileIds: z.array(z.string()).min(1).describe('One or more file IDs to get URLs for'),
|
|
156
156
|
account: accountParam,
|
|
@@ -161,7 +161,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
161
161
|
|
|
162
162
|
server.registerTool('gog_drive_permissions', {
|
|
163
163
|
description: 'List permissions on a Drive file.',
|
|
164
|
-
annotations: { readOnlyHint: true },
|
|
164
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
165
165
|
inputSchema: z.object({
|
|
166
166
|
fileId: z.string().describe('File ID'),
|
|
167
167
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
@@ -179,7 +179,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
179
179
|
|
|
180
180
|
server.registerTool('gog_drive_unshare', {
|
|
181
181
|
description: 'Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.',
|
|
182
|
-
annotations: { destructiveHint: true },
|
|
182
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
183
183
|
inputSchema: z.object({
|
|
184
184
|
fileId: z.string().describe('File ID'),
|
|
185
185
|
permissionId: z.string().describe('Permission ID to remove'),
|
|
@@ -191,7 +191,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
191
191
|
|
|
192
192
|
server.registerTool('gog_drive_drives_list', {
|
|
193
193
|
description: 'List shared drives (Team Drives) accessible to the account.',
|
|
194
|
-
annotations: { readOnlyHint: true },
|
|
194
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
195
195
|
inputSchema: z.object({
|
|
196
196
|
max: z.number().optional().describe('Max results (default: 100, max allowed: 100)'),
|
|
197
197
|
pageToken: pageTokenParam,
|
|
@@ -212,7 +212,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
212
212
|
|
|
213
213
|
server.registerTool('gog_drive_comments_list', {
|
|
214
214
|
description: 'List comments on a Drive file.',
|
|
215
|
-
annotations: { readOnlyHint: true },
|
|
215
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
216
216
|
inputSchema: z.object({
|
|
217
217
|
fileId: z.string().describe('File ID'),
|
|
218
218
|
since: z.string().optional().describe('Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)'),
|
|
@@ -230,7 +230,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
230
230
|
|
|
231
231
|
server.registerTool('gog_drive_comments_get', {
|
|
232
232
|
description: 'Get a single comment on a Drive file by ID.',
|
|
233
|
-
annotations: { readOnlyHint: true },
|
|
233
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
234
234
|
inputSchema: z.object({
|
|
235
235
|
fileId: z.string().describe('File ID'),
|
|
236
236
|
commentId: z.string().describe('Comment ID'),
|
|
@@ -244,7 +244,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
244
244
|
description: 'Add a new comment to a Drive file. Drive notifies the file\'s owner and everyone the text +mentions, so '
|
|
245
245
|
+ 'this reads the file and asks the MCP host to show the user a confirmation prompt with the file, the text and who '
|
|
246
246
|
+ 'is mentioned first; nothing is posted unless they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
247
|
-
annotations: { destructiveHint: false },
|
|
247
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
248
248
|
inputSchema: z.object({
|
|
249
249
|
fileId: z.string().describe('File ID'),
|
|
250
250
|
content: z.string().describe('Comment text'),
|
|
@@ -264,6 +264,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
264
264
|
details: { file, textPreview: bodyPreview(content), mentions },
|
|
265
265
|
unsupportedNote: 'Ask the user to comment from Google Drive.',
|
|
266
266
|
fallback: {
|
|
267
|
+
args: { fileId, content, account },
|
|
267
268
|
tool: 'gog_drive_comments_add',
|
|
268
269
|
account,
|
|
269
270
|
confirmToken,
|
|
@@ -279,7 +280,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
279
280
|
|
|
280
281
|
server.registerTool('gog_drive_comments_update', {
|
|
281
282
|
description: 'Update the text of an existing comment.',
|
|
282
|
-
annotations: { destructiveHint: false },
|
|
283
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
283
284
|
inputSchema: z.object({
|
|
284
285
|
fileId: z.string().describe('File ID'),
|
|
285
286
|
commentId: z.string().describe('Comment ID to update'),
|
|
@@ -292,7 +293,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
292
293
|
|
|
293
294
|
server.registerTool('gog_drive_comments_delete', {
|
|
294
295
|
description: 'Delete a comment from a Drive file.',
|
|
295
|
-
annotations: { destructiveHint: true },
|
|
296
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
296
297
|
inputSchema: z.object({
|
|
297
298
|
fileId: z.string().describe('File ID'),
|
|
298
299
|
commentId: z.string().describe('Comment ID to delete'),
|
|
@@ -307,7 +308,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
307
308
|
+ 'Everyone on the thread and anyone the reply +mentions is notified, so this reads the file and the comment and '
|
|
308
309
|
+ 'asks the MCP host to show the user a confirmation prompt with both and the reply first; nothing is posted unless '
|
|
309
310
|
+ 'they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
|
|
310
|
-
annotations: { destructiveHint: false },
|
|
311
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
311
312
|
inputSchema: z.object({
|
|
312
313
|
fileId: z.string().describe('File ID'),
|
|
313
314
|
commentId: z.string().describe('Comment ID to reply to'),
|
|
@@ -334,6 +335,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
334
335
|
details: { file, replyingTo, textPreview: bodyPreview(content), mentions, ...(action ? { action } : {}) },
|
|
335
336
|
unsupportedNote: 'Ask the user to reply from Google Drive.',
|
|
336
337
|
fallback: {
|
|
338
|
+
args: { fileId, commentId, content, action, account },
|
|
337
339
|
tool: 'gog_drive_comments_reply',
|
|
338
340
|
account,
|
|
339
341
|
confirmToken,
|
|
@@ -349,7 +351,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
349
351
|
|
|
350
352
|
server.registerTool('gog_drive_comments_resolve', {
|
|
351
353
|
description: 'Resolve a comment on a Drive file (mark as done) without posting a reply. To resolve while replying, use gog_drive_comments_reply with action: "resolve".',
|
|
352
|
-
annotations: { destructiveHint: true },
|
|
354
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
353
355
|
inputSchema: z.object({
|
|
354
356
|
fileId: z.string().describe('File ID'),
|
|
355
357
|
commentId: z.string().describe('Comment ID to resolve'),
|
|
@@ -361,7 +363,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
361
363
|
|
|
362
364
|
server.registerTool('gog_drive_comments_reopen', {
|
|
363
365
|
description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
|
|
364
|
-
annotations: { destructiveHint: true },
|
|
366
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
365
367
|
inputSchema: z.object({
|
|
366
368
|
fileId: z.string().describe('File ID'),
|
|
367
369
|
commentId: z.string().describe('Comment ID to reopen'),
|
|
@@ -375,7 +377,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
375
377
|
|
|
376
378
|
server.registerTool('gog_drive_du', {
|
|
377
379
|
description: 'Summarize Drive folder sizes (disk-usage style) starting from a folder.',
|
|
378
|
-
annotations: { readOnlyHint: true },
|
|
380
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
379
381
|
inputSchema: z.object({
|
|
380
382
|
parent: z.string().optional().describe('Folder ID to start from (default: root)'),
|
|
381
383
|
depth: z.number().optional().describe('Depth for folder totals (default: 1)'),
|
|
@@ -398,7 +400,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
398
400
|
|
|
399
401
|
server.registerTool('gog_drive_tree', {
|
|
400
402
|
description: 'Print a read-only folder tree starting from a folder.',
|
|
401
|
-
annotations: { readOnlyHint: true },
|
|
403
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
402
404
|
inputSchema: z.object({
|
|
403
405
|
parent: z.string().optional().describe('Folder ID to start from (default: root)'),
|
|
404
406
|
depth: z.number().optional().describe('Max depth (0 = unlimited; default: 2)'),
|
|
@@ -417,7 +419,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
417
419
|
|
|
418
420
|
server.registerTool('gog_drive_changes_start_token', {
|
|
419
421
|
description: 'Get a Drive changes start page token — the cursor you pass to gog_drive_changes_list to enumerate changes since this point.',
|
|
420
|
-
annotations: { readOnlyHint: true },
|
|
422
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
421
423
|
inputSchema: z.object({
|
|
422
424
|
drive: z.string().optional().describe('Shared drive ID for a shared-drive change log'),
|
|
423
425
|
account: accountParam,
|
|
@@ -430,7 +432,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
430
432
|
|
|
431
433
|
server.registerTool('gog_drive_changes_list', {
|
|
432
434
|
description: 'List Drive changes since a page token (for sync/automation). Get the initial token from gog_drive_changes_start_token; the response includes a newStartPageToken to persist for the next poll.',
|
|
433
|
-
annotations: { readOnlyHint: true },
|
|
435
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
434
436
|
inputSchema: z.object({
|
|
435
437
|
token: z.string().describe('Start page token or next page token'),
|
|
436
438
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
@@ -456,7 +458,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
456
458
|
|
|
457
459
|
server.registerTool('gog_drive_revisions_list', {
|
|
458
460
|
description: 'List a file\'s revision history — paged revision metadata plus provider export links for each revision.',
|
|
459
|
-
annotations: { readOnlyHint: true },
|
|
461
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
460
462
|
inputSchema: z.object({
|
|
461
463
|
fileId: z.string().describe('File ID'),
|
|
462
464
|
...paginationParams,
|
|
@@ -470,7 +472,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
470
472
|
|
|
471
473
|
server.registerTool('gog_drive_revisions_get', {
|
|
472
474
|
description: 'Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.',
|
|
473
|
-
annotations: { readOnlyHint: true },
|
|
475
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
474
476
|
inputSchema: z.object({
|
|
475
477
|
fileId: z.string().describe('File ID'),
|
|
476
478
|
revisionId: z.string().describe('Revision ID'),
|
|
@@ -482,7 +484,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
482
484
|
|
|
483
485
|
server.registerTool('gog_drive_shortcut_create', {
|
|
484
486
|
description: 'Create a Drive shortcut to a file or folder inside a destination folder. Shortcuts are classified distinctly in listing/tree output and are never followed by tree scans.',
|
|
485
|
-
annotations: { destructiveHint: false },
|
|
487
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
486
488
|
inputSchema: z.object({
|
|
487
489
|
targetId: z.string().describe('File or folder ID the shortcut points to'),
|
|
488
490
|
parent: z.string().describe('Destination folder ID for the shortcut'),
|
|
@@ -497,7 +499,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
497
499
|
|
|
498
500
|
server.registerTool('gog_drive_labels_list', {
|
|
499
501
|
description: 'List Drive label schemas available to the account.',
|
|
500
|
-
annotations: { readOnlyHint: true },
|
|
502
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
501
503
|
inputSchema: z.object({
|
|
502
504
|
language: z.string().optional().describe('BCP-47 language code'),
|
|
503
505
|
view: z.enum(['LABEL_VIEW_BASIC', 'LABEL_VIEW_FULL']).optional().describe('Label view (default: LABEL_VIEW_BASIC)'),
|
|
@@ -524,7 +526,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
524
526
|
|
|
525
527
|
server.registerTool('gog_drive_labels_get', {
|
|
526
528
|
description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
|
|
527
|
-
annotations: { readOnlyHint: true },
|
|
529
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
528
530
|
inputSchema: z.object({
|
|
529
531
|
name: z.string().describe('Label schema name (e.g. labels/abc123)'),
|
|
530
532
|
language: z.string().optional().describe('BCP-47 language code'),
|
|
@@ -542,7 +544,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
542
544
|
|
|
543
545
|
server.registerTool('gog_drive_labels_file_list', {
|
|
544
546
|
description: 'List labels applied to a Drive file.',
|
|
545
|
-
annotations: { readOnlyHint: true },
|
|
547
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
546
548
|
inputSchema: z.object({
|
|
547
549
|
fileId: z.string().describe('File ID'),
|
|
548
550
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
@@ -560,7 +562,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
560
562
|
|
|
561
563
|
server.registerTool('gog_drive_labels_file_apply', {
|
|
562
564
|
description: 'Apply or update a label on a Drive file, optionally setting field values. Each field flag takes "fieldId=value" entries (repeatable). selection/integer/date/user values may be comma-separated within one entry for multi-valued fields.',
|
|
563
|
-
annotations: { destructiveHint: false },
|
|
565
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
564
566
|
inputSchema: z.object({
|
|
565
567
|
fileId: z.string().describe('File ID'),
|
|
566
568
|
labelId: z.string().describe('Label ID to apply'),
|
|
@@ -587,7 +589,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
587
589
|
|
|
588
590
|
server.registerTool('gog_drive_labels_file_remove', {
|
|
589
591
|
description: 'Remove a label from a Drive file.',
|
|
590
|
-
annotations: { destructiveHint: true },
|
|
592
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
591
593
|
inputSchema: z.object({
|
|
592
594
|
fileId: z.string().describe('File ID'),
|
|
593
595
|
labelId: z.string().describe('Label ID to remove'),
|
|
@@ -599,7 +601,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
599
601
|
|
|
600
602
|
server.registerTool('gog_drive_activity', {
|
|
601
603
|
description: 'Query the Drive Activity API for audit events (edits, creates, deletes, moves, shares, etc.) scoped to a file or folder and/or time range.',
|
|
602
|
-
annotations: { readOnlyHint: true },
|
|
604
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
603
605
|
inputSchema: z.object({
|
|
604
606
|
file: z.string().optional().describe('Drive file ID to query'),
|
|
605
607
|
folder: z.string().optional().describe('Drive folder ID; includes descendants'),
|
|
@@ -632,7 +634,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
632
634
|
|
|
633
635
|
server.registerTool('gog_drive_audit_sharing', {
|
|
634
636
|
description: 'Audit Drive sharing without mutation — find public (anyone-with-link) or external permissions across a folder tree or a single file.',
|
|
635
|
-
annotations: { readOnlyHint: true },
|
|
637
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
636
638
|
inputSchema: z.object({
|
|
637
639
|
file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
|
|
638
640
|
parent: z.string().optional().describe('Folder ID to scan (default: root)'),
|
|
@@ -659,7 +661,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
659
661
|
|
|
660
662
|
server.registerTool('gog_drive_audit_user', {
|
|
661
663
|
description: 'Audit Drive sharing without mutation — find permissions granted to a specific user across a folder tree or a single file.',
|
|
662
|
-
annotations: { readOnlyHint: true },
|
|
664
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
663
665
|
inputSchema: z.object({
|
|
664
666
|
user: z.string().describe('User email to audit permissions for'),
|
|
665
667
|
file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { authToolsFor, registerDriveTools } from '../../gogcli-mcp/src/lib.js';
|
|
2
|
+
import { registerExtraDriveTools } from '../src/tools/drive-extra.js';
|
|
3
|
+
import { describeToolAnnotations, LOCAL_AUTH_TOOLS } from '../../gogcli-mcp/tests/support/tool-annotations.js';
|
|
4
|
+
|
|
5
|
+
// The same registrar list src/index.ts hands to runMcp.
|
|
6
|
+
describeToolAnnotations([authToolsFor('drive,driveactivity,drivelabels'), registerDriveTools, registerExtraDriveTools] as never, {
|
|
7
|
+
tools: 50,
|
|
8
|
+
destructive: 13,
|
|
9
|
+
local: [...LOCAL_AUTH_TOOLS],
|
|
10
|
+
});
|