gogcli-mcp-slides 2.24.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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
10
- "version": "2.24.0"
10
+ "version": "2.26.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Slides)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
18
- "version": "2.24.0",
18
+ "version": "2.26.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-slides",
3
3
  "displayName": "gogcli (Slides)",
4
- "version": "2.24.0",
4
+ "version": "2.26.0",
5
5
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/README.md CHANGED
@@ -44,9 +44,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
44
44
  claude mcp add gogcli-slides -- gogcli-mcp-slides
45
45
  ```
46
46
 
47
- ## Slides Tools (12)
47
+ ## Slides Tools
48
48
 
49
- Plus 5 auth tools and 1 `gog_slides_run` escape hatch for a total of 18 tools.
49
+ The package registers **55** tools: 40 Slides authoring tools, 7 base Slides tools, and 8 auth tools. The table below covers the most-used; `manifest.json` lists every one.
50
50
 
51
51
  | Tool | Description |
52
52
  |------|-------------|
@@ -59,6 +59,8 @@ Plus 5 auth tools and 1 `gog_slides_run` escape hatch for a total of 18 tools.
59
59
  | `gog_slides_add_slide` | Add a new slide from a local image |
60
60
  | `gog_slides_list_slides` | List slides in a presentation |
61
61
  | `gog_slides_delete_slide` | Delete a slide |
62
+ | `gog_slides_skip_slide` | Skip a slide when presenting (keeps it in the deck) |
63
+ | `gog_slides_unskip_slide` | Include a previously skipped slide again |
62
64
  | `gog_slides_read_slide` | Read the content of a slide |
63
65
  | `gog_slides_update_notes` | Update speaker notes on a slide |
64
66
  | `gog_slides_replace_slide` | Replace the image content of an existing slide |
package/SKILL.md CHANGED
@@ -43,6 +43,8 @@ Extended Google Slides MCP server via [gogcli](https://github.com/openclaw/gogcl
43
43
  | `gog_slides_add_slide` | Add a slide from a local image |
44
44
  | `gog_slides_list_slides` | List slides in a presentation |
45
45
  | `gog_slides_delete_slide` | Delete a slide |
46
+ | `gog_slides_skip_slide` | Skip a slide when presenting (keeps it in the deck) |
47
+ | `gog_slides_unskip_slide` | Include a previously skipped slide again |
46
48
  | `gog_slides_read_slide` | Read the content of a slide |
47
49
  | `gog_slides_update_notes` | Update speaker notes |
48
50
  | `gog_slides_replace_slide` | Replace the image on an existing slide |
package/dist/index.js CHANGED
@@ -31000,9 +31000,82 @@ 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 rawTextResult(text) {
31045
+ return { content: [{ type: "text", text }] };
31046
+ }
31047
+ function errorResult(message) {
31048
+ return {
31049
+ content: [{ type: "text", text: redactSecrets(message) }],
31050
+ isError: true
31051
+ };
31052
+ }
31053
+
31003
31054
  // ../../node_modules/@chrischall/mcp-utils/dist/server/index.js
31055
+ function hintResultOrRethrow(err) {
31056
+ if (err instanceof McpToolError && err.hint) {
31057
+ return errorResult(`${err.message}
31058
+
31059
+ Hint: ${err.hint}`);
31060
+ }
31061
+ throw err;
31062
+ }
31063
+ function surfaceToolHints(server) {
31064
+ const register = server.registerTool.bind(server);
31065
+ server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
31066
+ let result;
31067
+ try {
31068
+ result = cb(...args);
31069
+ } catch (err) {
31070
+ return hintResultOrRethrow(err);
31071
+ }
31072
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
31073
+ });
31074
+ }
31004
31075
  async function createMcpServer(opts) {
31005
31076
  const server = new McpServer({ name: opts.name, version: opts.version });
31077
+ if (opts.surfaceHints !== false)
31078
+ surfaceToolHints(server);
31006
31079
  if (opts.banner !== void 0) {
31007
31080
  console.error(opts.banner);
31008
31081
  }
@@ -31047,46 +31120,6 @@ async function runMcp(opts) {
31047
31120
  return server;
31048
31121
  }
31049
31122
 
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 rawTextResult(text) {
31081
- return { content: [{ type: "text", text }] };
31082
- }
31083
- function errorResult(message) {
31084
- return {
31085
- content: [{ type: "text", text: redactSecrets(message) }],
31086
- isError: true
31087
- };
31088
- }
31089
-
31090
31123
  // ../../node_modules/@chrischall/mcp-utils/dist/config/index.js
31091
31124
  var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
31092
31125
  function readEnvVar(key, opts = {}) {
@@ -31177,10 +31210,11 @@ function sanitizedEnv() {
31177
31210
  }
31178
31211
  return result;
31179
31212
  }
31213
+ var TOKEN_LEFT_BOUNDARY = "(?<![A-Za-z0-9+/])";
31180
31214
  var GOOGLE_TOKEN_PATTERNS = [
31181
- /ya29\.[A-Za-z0-9._\-]+/g,
31215
+ new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
31182
31216
  // OAuth2 access tokens
31183
- /1\/\/[A-Za-z0-9._\-]+/g
31217
+ new RegExp(`${TOKEN_LEFT_BOUNDARY}1//[A-Za-z0-9._\\-]+`, "g")
31184
31218
  // OAuth2 refresh tokens
31185
31219
  ];
31186
31220
  function redactGoogleTokens(text) {
@@ -31193,6 +31227,26 @@ function redactGoogleTokens(text) {
31193
31227
  function redactSecrets2(text) {
31194
31228
  return redactGoogleTokens(redactSecrets(text));
31195
31229
  }
31230
+ var OPAQUE_FIELD_VALUE = "[A-Za-z0-9+/_-]{16,}={0,2}";
31231
+ var opaquePlaceholder = (i) => `\0gogOpaque${i}\0`;
31232
+ function redactPreservingOpaqueFields(text, fields, redact) {
31233
+ const lifted = [];
31234
+ let staged = text;
31235
+ for (const field of fields) {
31236
+ const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31237
+ const re = new RegExp(`("${escaped}"\\s*:\\s*")(${OPAQUE_FIELD_VALUE})(")`, "g");
31238
+ staged = staged.replace(re, (_m, open, value, close) => {
31239
+ lifted.push(value);
31240
+ return `${open}${opaquePlaceholder(lifted.length - 1)}${close}`;
31241
+ });
31242
+ }
31243
+ if (lifted.length === 0) return redact(text);
31244
+ let redacted = redact(staged);
31245
+ lifted.forEach((value, i) => {
31246
+ redacted = redacted.split(opaquePlaceholder(i)).join(value);
31247
+ });
31248
+ return redacted;
31249
+ }
31196
31250
  function augmentedPath() {
31197
31251
  const home = process.env.HOME;
31198
31252
  const candidates = [
@@ -31223,19 +31277,24 @@ function formatTimeout(ms) {
31223
31277
  return `${ms}ms`;
31224
31278
  }
31225
31279
  async function spawnWithTempFiles(args, opts) {
31226
- const { mkdtemp, writeFile, rm } = await import("node:fs/promises");
31280
+ const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
31227
31281
  const { tmpdir } = await import("node:os");
31228
31282
  const dir = await mkdtemp(join(tmpdir(), "gogcli-mcp-"));
31229
31283
  try {
31230
31284
  const argv = [];
31285
+ let seq = 0;
31231
31286
  for (const arg of args) {
31232
31287
  if (!isGogFileArg(arg)) {
31233
31288
  argv.push(arg);
31234
31289
  continue;
31235
31290
  }
31236
- const path = join(dir, `${arg.flag}.${arg.ext ?? "txt"}`);
31237
- await writeFile(path, arg.contents, { encoding: "utf8", mode: 384 });
31238
- argv.push(`--${arg.flag}=${path}`);
31291
+ const sub = join(dir, String(seq));
31292
+ seq += 1;
31293
+ await mkdir(sub, { recursive: true, mode: 448 });
31294
+ const path = join(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
31295
+ const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
31296
+ await writeFile(path, data, { mode: 384 });
31297
+ argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
31239
31298
  }
31240
31299
  return await spawnGog(argv, opts);
31241
31300
  } finally {
@@ -31320,8 +31379,9 @@ function assembleArgs(args, opts) {
31320
31379
  return fullArgs;
31321
31380
  }
31322
31381
  async function run(args, options = {}) {
31323
- const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full" } = options;
31324
- const redact = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
31382
+ const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
31383
+ const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
31384
+ const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
31325
31385
  const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
31326
31386
  const store = activeExecutor();
31327
31387
  try {
@@ -31335,7 +31395,7 @@ async function run(args, options = {}) {
31335
31395
  }
31336
31396
  return redact(output);
31337
31397
  } catch (err) {
31338
- const message = redact(err instanceof Error ? err.message : String(err));
31398
+ const message = base(err instanceof Error ? err.message : String(err));
31339
31399
  if (isRunnerTransportError(err)) {
31340
31400
  throw new RunnerTransportError(message, err.kind, err.status);
31341
31401
  }
@@ -31859,6 +31919,36 @@ function authToolsFor(defaultServices) {
31859
31919
  return (server) => registerAuthToolsWith(server, defaultServices);
31860
31920
  }
31861
31921
 
31922
+ // ../gogcli-mcp/src/tools/calendar.ts
31923
+ var reminderParams = {
31924
+ reminders: external_exports.array(external_exports.string()).max(5).optional().describe(
31925
+ `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.`
31926
+ ),
31927
+ noReminders: external_exports.boolean().optional().describe(
31928
+ "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."
31929
+ )
31930
+ };
31931
+
31932
+ // ../gogcli-mcp/src/attachments.ts
31933
+ var MAX_INLINE_ATTACHMENT_BYTES = 8 * 1024 * 1024;
31934
+ var RUNNER_MAX_BODY_BYTES = 32 * 1024 * 1024;
31935
+ var RUNNER_BODY_JSON_RESERVE_BYTES = 256 * 1024;
31936
+ var MAX_REQUEST_PAYLOAD_WIRE_BYTES = RUNNER_MAX_BODY_BYTES - RUNNER_BODY_JSON_RESERVE_BYTES;
31937
+ var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
31938
+ var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
31939
+ var INLINE_ATTACHMENT_LIMITS_TEXT = `up to ${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)} per file and ${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)} in total`;
31940
+ var inlineAttachmentSchema = external_exports.object({
31941
+ filename: external_exports.string().min(1).describe(
31942
+ `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.`
31943
+ ),
31944
+ contentBase64: external_exports.string().min(1).describe(
31945
+ "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."
31946
+ )
31947
+ });
31948
+ var attachInlineParam = external_exports.array(inlineAttachmentSchema).optional().describe(
31949
+ `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.`
31950
+ );
31951
+
31862
31952
  // ../gogcli-mcp/src/tools/sheets.ts
31863
31953
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
31864
31954
  var dryRunParam = external_exports.boolean().optional().describe(
@@ -31952,7 +32042,7 @@ function registerSlidesTools(server) {
31952
32042
  }
31953
32043
 
31954
32044
  // ../gogcli-mcp/src/server.ts
31955
- var VERSION = true ? "2.24.0" : "0.0.0";
32045
+ var VERSION = true ? "2.26.0" : "0.0.0";
31956
32046
 
31957
32047
  // ../gogcli-mcp/src/auth-log.ts
31958
32048
  var FAILURES = /* @__PURE__ */ new Set([
@@ -32534,6 +32624,26 @@ function registerExtraSlidesTools(server) {
32534
32624
  }, async ({ presentationId, slideId, account }) => {
32535
32625
  return runOrDiagnose(["slides", "delete-slide", presentationId, slideId, "--force"], { account });
32536
32626
  });
32627
+ server.registerTool("gog_slides_skip_slide", {
32628
+ description: "Skip a slide during presentation \u2014 it stays in the deck at its current position but is not shown when presenting (and is left out of a PDF export). Use this instead of gog_slides_delete_slide when a slide should be omitted from a talk without losing it. Reverse with gog_slides_unskip_slide; gog_slides_list_slides reports which slides are skipped.",
32629
+ inputSchema: {
32630
+ presentationId: external_exports.string().describe("Presentation ID"),
32631
+ slideId: external_exports.string().describe("Slide ID to skip (from gog_slides_list_slides)"),
32632
+ account: accountParam
32633
+ }
32634
+ }, async ({ presentationId, slideId, account }) => {
32635
+ return runOrDiagnose(["slides", "skip-slide", presentationId, slideId], { account });
32636
+ });
32637
+ server.registerTool("gog_slides_unskip_slide", {
32638
+ description: "Include a previously skipped slide again when the deck is presented. Safe to call on a slide that was never skipped.",
32639
+ inputSchema: {
32640
+ presentationId: external_exports.string().describe("Presentation ID"),
32641
+ slideId: external_exports.string().describe("Slide ID to unskip (from gog_slides_list_slides)"),
32642
+ account: accountParam
32643
+ }
32644
+ }, async ({ presentationId, slideId, account }) => {
32645
+ return runOrDiagnose(["slides", "unskip-slide", presentationId, slideId], { account });
32646
+ });
32537
32647
  server.registerTool("gog_slides_update_notes", {
32538
32648
  description: "Update the speaker notes on a slide (inline text or from a file).",
32539
32649
  annotations: { destructiveHint: true },
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-slides",
5
5
  "display_name": "gogcli (Slides)",
6
- "version": "2.24.0",
6
+ "version": "2.26.0",
7
7
  "description": "Extended Google Slides for Claude via gogcli — auth + full Slides support (create, edit, export, templates, markdown)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -129,6 +129,14 @@
129
129
  "name": "gog_slides_delete_slide",
130
130
  "description": "Delete a slide"
131
131
  },
132
+ {
133
+ "name": "gog_slides_skip_slide",
134
+ "description": "Skip a slide during presentation without removing it from the deck"
135
+ },
136
+ {
137
+ "name": "gog_slides_unskip_slide",
138
+ "description": "Include a previously skipped slide again when presenting"
139
+ },
132
140
  {
133
141
  "name": "gog_slides_read_slide",
134
142
  "description": "Read the content of a slide"
package/mint.yaml ADDED
@@ -0,0 +1,106 @@
1
+ version: 1
2
+ name: Google Slides (gogcli)
3
+ slug: gogcli-mcp-slides
4
+ summary: >-
5
+ Extended Google Slides MCP server via gogcli — auth + full Slides support
6
+ command:
7
+ # This package publishes a single bin; naming it keeps the install
8
+ # unambiguous alongside its eight sibling packages.
9
+ bin: gogcli-mcp-slides
10
+ env:
11
+ - name: GOG_CLIENT_ID
12
+ required: false
13
+ help: >-
14
+ Google OAuth client id. NOTE: on the local-spawn path the child `gog`
15
+ receives a sanitized env — runner.ts drops GOG_ACCESS_TOKEN and every
16
+ *_TOKEN / *_SECRET / *_API_KEY / *_PRIVATE_KEY variable — so the CLI
17
+ authenticates from its own stored credentials under $HOME (see
18
+ state.dataDir), not from these variables being passed through.
19
+ - name: GOG_CLIENT_SECRET
20
+ secret: true
21
+ required: false
22
+ help: >-
23
+ Google OAuth client secret. Stripped from the spawned CLI's environment
24
+ by runner.ts's *_SECRET rule — see GOG_CLIENT_ID.
25
+ - name: GOG_REFRESH_TOKEN
26
+ secret: true
27
+ required: false
28
+ help: >-
29
+ Google OAuth refresh token. Stripped from the spawned CLI's environment
30
+ by runner.ts's *_TOKEN rule — see GOG_CLIENT_ID. A hosted deployment must
31
+ therefore carry gog's authorised-account state in its persisted data dir,
32
+ or drive a remote runner via GOG_RUNNER_URL.
33
+ - name: GOG_ACCESS_TOKEN
34
+ secret: true
35
+ required: false
36
+ help: >-
37
+ Deliberately removed from the spawned CLI's environment by runner.ts, so
38
+ that a stale directly-passed token cannot shadow gog's stored refresh
39
+ credential. Leave unset.
40
+ - name: GOG_ACCOUNT
41
+ required: false
42
+ help: >-
43
+ Which configured Google account to act as, when more than one is
44
+ authorised. Defaults to the single/most recent account.
45
+ - name: GOG_READONLY
46
+ required: false
47
+ help: >-
48
+ Set to 1 to refuse every mutating operation. Recommended when the
49
+ connector is shared or you only need reads.
50
+ - name: GOG_PATH
51
+ required: false
52
+ help: >-
53
+ Path to the `gog` binary. Leave unset when the dependency below supplies
54
+ it; set it only to point at a binary you manage yourself.
55
+ - name: GOG_RUNNER_URL
56
+ required: false
57
+ help: >-
58
+ URL of a remote gog runner to execute against instead of spawning the
59
+ local binary. If you set this, add its host to egress.allow.
60
+ - name: GOG_RUNNER_KEY
61
+ secret: true
62
+ required: false
63
+ help: >-
64
+ Shared key authenticating calls to GOG_RUNNER_URL. Required whenever that
65
+ is set.
66
+ - name: GOG_TIMEZONE
67
+ required: false
68
+ help: >-
69
+ The IANA zone gog itself formats its naive timestamps in. The wrapper
70
+ reads it (naiveSourceTimeZone) to re-attach the correct offset, so it
71
+ should match gog's own configuration. Falls back to DISPLAY_TZ, then to
72
+ America/New_York.
73
+ - name: DISPLAY_TZ
74
+ required: false
75
+ help: >-
76
+ The IANA zone every rendered *Display field uses (displayTimeZone,
77
+ default America/New_York). It does not read GOG_TIMEZONE — the fallback
78
+ runs the other way, so this is also what GOG_TIMEZONE falls back to. An
79
+ invalid value degrades to the default rather than throwing.
80
+ dependencies:
81
+ # Every tool shells out to the `gog` CLI; without it the server starts and
82
+ # then fails on the first call. This tag must track
83
+ # packages/gogcli-mcp/src/runner.ts's MIN_GOG_VERSION (the floor the tools
84
+ # assume) and the fly-gog-runner/Dockerfile GOG_VERSION build arg. See
85
+ # CLAUDE.md "Required gog version" — bumping the floor means bumping these
86
+ # nine pins too.
87
+ - kind: github-release
88
+ repo: openclaw/gogcli
89
+ tag: v0.38.1
90
+ asset: "gogcli_*_linux_amd64.tar.gz"
91
+ bin: [gog]
92
+ state:
93
+ dataDir: true
94
+ reason: >-
95
+ `gog` keeps its authorised-account state and token cache under $HOME.
96
+ Without a persistent data dir every cold start has no account configured and
97
+ every tool call fails until the credentials are re-supplied.
98
+ egress:
99
+ allow:
100
+ # Google OAuth token exchange and the Google REST APIs the CLI calls.
101
+ - oauth2.googleapis.com
102
+ - www.googleapis.com
103
+ - googleapis.com
104
+ #
105
+ # NOTE: if you set GOG_RUNNER_URL to run against a remote gog runner, add
106
+ # 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-slides",
3
- "version": "2.24.0",
3
+ "version": "2.26.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-slides",
5
5
  "description": "Extended Google Slides MCP server via gogcli — auth + full Slides support",
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.14.0",
27
+ "@chrischall/mcp-utils": "^0.15.0",
28
28
  "@modelcontextprotocol/sdk": "^1.30.0",
29
29
  "zod": "^4.4.3"
30
30
  },
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-slides"
9
9
  },
10
- "version": "2.24.0",
10
+ "version": "2.26.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-slides",
15
- "version": "2.24.0",
15
+ "version": "2.26.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -125,6 +125,40 @@ export function registerExtraSlidesTools(server: McpServer): void {
125
125
  return runOrDiagnose(['slides', 'delete-slide', presentationId, slideId, '--force'], { account });
126
126
  });
127
127
 
128
+ // Slide visibility (gog >= 0.38.0, openclaw/gogcli#1009). Skipping is NOT
129
+ // deleting and not hiding: the slide keeps its position, content and ID, and
130
+ // still appears in the editor and in gog_slides_list_slides (which reports
131
+ // isSkipped in JSON) — it is only left out when the deck is presented or
132
+ // exported to PDF. Fully reversible, so neither tool is annotated
133
+ // destructive: skip is the safe alternative to gog_slides_delete_slide when
134
+ // the ask is "leave this out of the talk". Neither is confirmation-gated in
135
+ // gog, so neither takes --force (verified live against gog 0.38.0).
136
+ server.registerTool('gog_slides_skip_slide', {
137
+ description:
138
+ 'Skip a slide during presentation — it stays in the deck at its current position but is not shown when presenting '
139
+ + '(and is left out of a PDF export). Use this instead of gog_slides_delete_slide when a slide should be omitted from '
140
+ + 'a talk without losing it. Reverse with gog_slides_unskip_slide; gog_slides_list_slides reports which slides are skipped.',
141
+ inputSchema: {
142
+ presentationId: z.string().describe('Presentation ID'),
143
+ slideId: z.string().describe('Slide ID to skip (from gog_slides_list_slides)'),
144
+ account: accountParam,
145
+ },
146
+ }, async ({ presentationId, slideId, account }) => {
147
+ return runOrDiagnose(['slides', 'skip-slide', presentationId, slideId], { account });
148
+ });
149
+
150
+ server.registerTool('gog_slides_unskip_slide', {
151
+ description:
152
+ 'Include a previously skipped slide again when the deck is presented. Safe to call on a slide that was never skipped.',
153
+ inputSchema: {
154
+ presentationId: z.string().describe('Presentation ID'),
155
+ slideId: z.string().describe('Slide ID to unskip (from gog_slides_list_slides)'),
156
+ account: accountParam,
157
+ },
158
+ }, async ({ presentationId, slideId, account }) => {
159
+ return runOrDiagnose(['slides', 'unskip-slide', presentationId, slideId], { account });
160
+ });
161
+
128
162
  server.registerTool('gog_slides_update_notes', {
129
163
  description: 'Update the speaker notes on a slide (inline text or from a file).',
130
164
  annotations: { destructiveHint: true },
@@ -759,3 +759,31 @@ describe('gog_slides_table_unmerge', () => {
759
759
  ['slides', 'table', 'unmerge', 'p1', 't1', '--row=1', '--col=1'], { account: undefined });
760
760
  });
761
761
  });
762
+
763
+ describe('gog_slides_skip_slide', () => {
764
+ it('calls runOrDiagnose with presentationId and slideId', async () => {
765
+ await harness.callTool('gog_slides_skip_slide', { presentationId: 'p1', slideId: 's1' });
766
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
767
+ ['slides', 'skip-slide', 'p1', 's1'],
768
+ { account: undefined },
769
+ );
770
+ });
771
+
772
+ it('passes the account through', async () => {
773
+ await harness.callTool('gog_slides_skip_slide', { presentationId: 'p1', slideId: 's1', account: 'me@x.com' });
774
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
775
+ ['slides', 'skip-slide', 'p1', 's1'],
776
+ { account: 'me@x.com' },
777
+ );
778
+ });
779
+ });
780
+
781
+ describe('gog_slides_unskip_slide', () => {
782
+ it('calls runOrDiagnose with presentationId and slideId', async () => {
783
+ await harness.callTool('gog_slides_unskip_slide', { presentationId: 'p1', slideId: 's1' });
784
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
785
+ ['slides', 'unskip-slide', 'p1', 's1'],
786
+ { account: undefined },
787
+ );
788
+ });
789
+ });