@chrischall/gemini-mcp 2.4.2 → 2.5.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.
@@ -5,7 +5,7 @@ import { ChainedRequest404Error } from '../client.js';
5
5
  import { slugify, baseName, resolveImagePath, writeSidecar, lookupOutputDir, readOutputImageAsInline } from '../images.js';
6
6
  import { resolveImageInputs } from '../inputs.js';
7
7
  import { findInteractionImages, latestInteractionId } from '../sidecar.js';
8
- import { emit, reportShape, resolveAspectRatio, orientationSchema, ASPECT_RATIOS, IMAGE_SIZES, MODEL_CHOICE_GUIDE, resolveVideoInput, videoPathSchema, timeoutMsSchema, timeoutRiskHint, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema } from './shared.js';
8
+ import { emit, reportShape, resolveAspectRatio, orientationSchema, ASPECT_RATIOS, IMAGE_SIZES, MODEL_CHOICE_GUIDE, resolveVideoInput, videoPathSchema, timeoutMsSchema, timeoutRiskHint, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema, hostedR2KeysParam, hostedUploadNote } from './shared.js';
9
9
  import { bytesToBase64 } from '../bytes.js';
10
10
  import { fingerprintRequest } from '../jobs.js';
11
11
  import { attachCost } from '../pricing.js';
@@ -89,6 +89,7 @@ export function registerInteractTools(server, client) {
89
89
  'do NOT start a new interaction or re-send the image for each tweak. `continue_last: true` chains from this ' +
90
90
  "session's most recent interaction without threading the id. " +
91
91
  recoveryDescription +
92
+ hostedUploadNote(client.mediaSink) +
92
93
  'Output is JPEG. ' +
93
94
  confirmNote('Local file inputs are confirmed first'),
94
95
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
@@ -108,6 +109,7 @@ export function registerInteractTools(server, client) {
108
109
  .describe(`Paths to reference input images. Local paths must be inside GEMINI_REFERENCE_DIR (else GEMINI_UPLOAD_DIR) when set. ${NEW_REFERENCES_ONLY}`),
109
110
  images_url: imagesUrlSchema(`Reference images. ${NEW_REFERENCES_ONLY} Given`),
110
111
  images_file_uris: imagesFileUrisSchema(`Reference images. ${NEW_REFERENCES_ONLY} Given`),
112
+ ...hostedR2KeysParam(client.mediaSink, `Reference images. ${NEW_REFERENCES_ONLY} Given`),
111
113
  images_base64: imagesBase64Schema(`Reference images. ${NEW_REFERENCES_ONLY} Given`),
112
114
  model: z
113
115
  .string()
@@ -223,7 +225,7 @@ export function registerInteractTools(server, client) {
223
225
  thinking_level: args.thinking_level, previous_interaction_id: previousInteractionId,
224
226
  google_search: args.google_search, search_types: args.search_types,
225
227
  images, images_base64: args.images_base64, from_clipboard: args.from_clipboard,
226
- images_url: args.images_url, images_file_uris: args.images_file_uris,
228
+ images_url: args.images_url, images_file_uris: args.images_file_uris, images_r2_keys: args.images_r2_keys,
227
229
  video_url: args.video_url, video_path: args.video_path,
228
230
  });
229
231
  return client.session.jobs.dispatch({ toolName: 'gemini_interact', fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async () => {
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { slugify, baseName } from '../images.js';
3
3
  import { resolveImageInputs } from '../inputs.js';
4
4
  import { DEFAULT_MUSIC_MODEL } from '../models.js';
5
- import { emitMedia, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema } from './shared.js';
5
+ import { emitMedia, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema, hostedR2KeysParam, hostedUploadNote } from './shared.js';
6
6
  import { fingerprintRequest } from '../jobs.js';
7
7
  import { attachCost } from '../pricing.js';
8
8
  import { confirmLocalInputs, confirmNote, confirmTokenParam } from './_confirm.js';
@@ -29,6 +29,7 @@ export function registerMusicTools(server, client) {
29
29
  'lyria-3-pro-preview (longer-form). Output is MP3, written to disk (or returned inline). Single-turn: a track ' +
30
30
  'cannot be refined by a follow-up call, so put the whole brief in the prompt. Runs long — give it a `max_wait_ms` ' +
31
31
  'budget (or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. ' +
32
+ hostedUploadNote(client.mediaSink) +
32
33
  confirmNote('Local file inputs are confirmed first'),
33
34
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
34
35
  inputSchema: z.object({
@@ -37,6 +38,7 @@ export function registerMusicTools(server, client) {
37
38
  images: z.array(z.string().min(1)).optional().describe('Optional reference image path(s) to condition the music. Local paths must be inside GEMINI_REFERENCE_DIR (else GEMINI_UPLOAD_DIR) when set.'),
38
39
  images_url: imagesUrlSchema('Reference images'),
39
40
  images_file_uris: imagesFileUrisSchema('Reference images'),
41
+ ...hostedR2KeysParam(client.mediaSink, 'Reference images'),
40
42
  images_base64: imagesBase64Schema(),
41
43
  from_clipboard: z.boolean().optional().describe('Use the image currently on the macOS clipboard as a reference'),
42
44
  filename: z.string().optional().describe('Base filename for the output audio (extension stripped; default: slugified prompt)'),
@@ -62,7 +64,7 @@ export function registerMusicTools(server, client) {
62
64
  const fingerprint = fingerprintRequest('gemini_music_generate', {
63
65
  model, prompt: args.prompt,
64
66
  images: args.images, images_base64: args.images_base64, from_clipboard: args.from_clipboard,
65
- images_url: args.images_url, images_file_uris: args.images_file_uris,
67
+ images_url: args.images_url, images_file_uris: args.images_file_uris, images_r2_keys: args.images_r2_keys,
66
68
  });
67
69
  return client.session.jobs.dispatch({ toolName: 'gemini_music_generate', fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async (ctx) => {
68
70
  const { inputs, report } = await resolveImageInputs(args, client);
@@ -144,6 +144,32 @@ export function imagesR2KeysSchema(label = 'Reference images') {
144
144
  "generation's media[].r2_key. The server reads its own bucket — no bytes in the conversation, no ~48h expiry. " +
145
145
  'Hosted connector only.');
146
146
  }
147
+ /**
148
+ * `images_r2_keys` for a tool, but ONLY where a key can resolve.
149
+ *
150
+ * An `r2_key` names an object in the hosted connector's blob store; a disk
151
+ * install has no store, so there the param could never work and would be
152
+ * standing `tools/list` cost on every request (see tool-surface-budget). So it
153
+ * is registered from the sink's capability, like interact's recovery text.
154
+ *
155
+ * The cast is runtime-honest: off the hosted connector the key is absent from
156
+ * the schema, zod strips it, and `args.images_r2_keys` is `undefined` — which
157
+ * is exactly what the declared optional type allows.
158
+ */
159
+ export function hostedR2KeysParam(sink, label) {
160
+ return ((sink ?? DEFAULT_SINK).persistsFiles ? {} : { images_r2_keys: imagesR2KeysSchema(label) });
161
+ }
162
+ /**
163
+ * The description sentence that sends a hosted caller to the upload route.
164
+ * Empty on disk (where `images` paths work and no upload tool exists), so it
165
+ * costs nothing there.
166
+ */
167
+ export function hostedUploadNote(sink) {
168
+ return (sink ?? DEFAULT_SINK).persistsFiles
169
+ ? ''
170
+ : 'For a reference photo you hold locally: gemini_get_upload_url, curl PUT it, then pass its r2_key in images_r2_keys ' +
171
+ '(no bytes in the conversation; reusable across calls). ';
172
+ }
147
173
  /** Saved-character names attached to a generation (hosted connector only). */
148
174
  export const charactersSchema = z
149
175
  .array(z.string().min(1))
@@ -3,7 +3,7 @@ import { McpToolError } from '@chrischall/mcp-utils';
3
3
  import { slugify, baseName } from '../images.js';
4
4
  import { resolveImageInputs } from '../inputs.js';
5
5
  import { DEFAULT_VIDEO_MODEL } from '../models.js';
6
- import { emitMedia, reportShape, resolveAspectRatio, orientationSchema, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema } from './shared.js';
6
+ import { emitMedia, reportShape, resolveAspectRatio, orientationSchema, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema, hostedR2KeysParam, hostedUploadNote } from './shared.js';
7
7
  import { fingerprintRequest } from '../jobs.js';
8
8
  import { attachCost } from '../pricing.js';
9
9
  import { confirmLocalInputs, confirmNote, confirmTokenParam } from './_confirm.js';
@@ -35,6 +35,7 @@ export function registerVideoTools(server, client) {
35
35
  'add ~3-10s each, to ~40s total). Cost scales with `resolution` — draft at 360p, keep at 1080p/4k. ' +
36
36
  'Output is written to disk as MP4 (video has no inline MCP block). Video runs long — give it a `max_wait_ms` budget ' +
37
37
  '(or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. ' +
38
+ hostedUploadNote(client.mediaSink) +
38
39
  confirmNote('Local file inputs are confirmed first'),
39
40
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
40
41
  inputSchema: z.object({
@@ -46,6 +47,7 @@ export function registerVideoTools(server, client) {
46
47
  images: z.array(z.string().min(1)).optional().describe('Reference image path(s) for image_to_video / reference_to_video. Local paths must be inside GEMINI_REFERENCE_DIR (else GEMINI_UPLOAD_DIR) when set.'),
47
48
  images_url: imagesUrlSchema('Reference stills'),
48
49
  images_file_uris: imagesFileUrisSchema('Reference stills'),
50
+ ...hostedR2KeysParam(client.mediaSink, 'Reference stills'),
49
51
  images_base64: imagesBase64Schema(),
50
52
  from_clipboard: z.boolean().optional().describe('Use the image currently on the macOS clipboard as a reference'),
51
53
  filename: z.string().optional().describe('Base filename for the output video (extension stripped; default: slugified prompt)'),
@@ -95,7 +97,7 @@ export function registerVideoTools(server, client) {
95
97
  // where `delivery` is not (same bytes, different transport).
96
98
  model, prompt: args.prompt, aspect_ratio: aspectRatio, resolution: args.resolution, task: args.task,
97
99
  images: args.images, images_base64: args.images_base64, from_clipboard: args.from_clipboard,
98
- images_url: args.images_url, images_file_uris: args.images_file_uris,
100
+ images_url: args.images_url, images_file_uris: args.images_file_uris, images_r2_keys: args.images_r2_keys,
99
101
  previous_interaction_id: previousInteractionId,
100
102
  });
101
103
  return client.session.jobs.dispatch({ toolName: 'gemini_video_generate', fingerprint, idempotencyKey: args.idempotency_key, async: args.async, waitMs: args.max_wait_ms }, async (ctx) => {
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Single source of the server version. release-please bumps the literal below. */
2
- export const VERSION = '2.4.2'; // x-release-please-version
2
+ export const VERSION = '2.5.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/gemini-mcp",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "mcpName": "io.github.chrischall/gemini-mcp",
5
5
  "description": "Gemini image-generation MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/gemini-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.4.2",
9
+ "version": "2.5.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/gemini-mcp",
14
- "version": "2.4.2",
14
+ "version": "2.5.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },