@chrischall/gemini-mcp 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/SKILL.md +49 -6
- package/dist/bundle.js +48 -12
- package/dist/client.js +33 -10
- package/dist/models.js +7 -2
- package/dist/tools/generate.js +10 -1
- package/dist/tools/interact.js +36 -7
- package/dist/tools/shared.js +10 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Google Gemini image generation and editing — generate and edit images via the Gemini API",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.7.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Gemini Images",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Generate and edit images with Google Gemini image models (Nano Banana / Nano Banana Pro) via natural language prompts",
|
|
18
|
-
"version": "0.
|
|
18
|
+
"version": "0.7.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Developed and maintained by AI (Claude Code).
|
|
|
13
13
|
| Variable | Required | Description |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `GEMINI_API_KEY` | Yes | Your Google Gemini API key ([aistudio.google.com/apikey](https://aistudio.google.com/apikey)) |
|
|
16
|
-
| `GEMINI_IMAGE_MODEL` | No | Override the default image model (default: `gemini-3-
|
|
16
|
+
| `GEMINI_IMAGE_MODEL` | No | Override the default image model (default: `gemini-3.1-flash-image`) |
|
|
17
17
|
| `GEMINI_OUTPUT_DIR` | No | Default directory for generated images (default: current working directory) |
|
|
18
18
|
| `GEMINI_INPUT_DIR` | No | Directory to resolve bare input-image filenames against (so `images: ["foo.jpg"]` works) |
|
|
19
19
|
|
|
@@ -23,9 +23,9 @@ Developed and maintained by AI (Claude Code).
|
|
|
23
23
|
|------|-------------|
|
|
24
24
|
| `gemini_list_models` | List available Gemini image models and the current default |
|
|
25
25
|
| `gemini_generate_image` | Generate image(s) from a text prompt |
|
|
26
|
-
| `gemini_edit_image` |
|
|
26
|
+
| `gemini_edit_image` | One-off edits or multi-image composition with a text instruction (for a series of edits, use `gemini_interact`) |
|
|
27
27
|
| `gemini_generate_set` | Generate a master image plus N consistent images referencing it |
|
|
28
|
-
| `gemini_interact` |
|
|
28
|
+
| `gemini_interact` | Preferred tool for iterative refinement: multi-turn generation/editing via the Interactions API — chain the returned `interaction_id` via `previous_interaction_id` (or `continue_last: true`) |
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
package/SKILL.md
CHANGED
|
@@ -69,7 +69,7 @@ Note: Image generation requires a billing-enabled Google Cloud project.
|
|
|
69
69
|
| Variable | Required | Description |
|
|
70
70
|
|---|---|---|
|
|
71
71
|
| `GEMINI_API_KEY` | Yes | Your Google Gemini API key |
|
|
72
|
-
| `GEMINI_IMAGE_MODEL` | No | Override the default image model (default: `gemini-3-
|
|
72
|
+
| `GEMINI_IMAGE_MODEL` | No | Override the default image model (default: `gemini-3.1-flash-image`) |
|
|
73
73
|
| `GEMINI_OUTPUT_DIR` | No | Default directory for saved images (default: current working directory) |
|
|
74
74
|
| `GEMINI_INPUT_DIR` | No | Directory to resolve bare input-image filenames against (e.g. point at Cowork's `uploads/` folder so `images: ["house.jpg"]` works) |
|
|
75
75
|
|
|
@@ -80,6 +80,14 @@ Note: Image generation requires a billing-enabled Google Cloud project.
|
|
|
80
80
|
|------|-------------|
|
|
81
81
|
| `gemini_list_models` | List available Gemini image models and the current default |
|
|
82
82
|
|
|
83
|
+
**Which model to pick** (per-call `model`, or `GEMINI_IMAGE_MODEL`):
|
|
84
|
+
|
|
85
|
+
| Model | When to use | Reference-image caps (of 14 max) |
|
|
86
|
+
|-------|-------------|----------------------------------|
|
|
87
|
+
| `gemini-3.1-flash-image` (Nano Banana 2) | The versatile generalist workhorse for all tasks — balances speed with state-of-the-art 4K generation, world knowledge, and reliable text rendering; excels at multi-reference-image processing and consistency. Only model with video input + image_search grounding | 10 objects + 4 characters + 3 style refs |
|
|
88
|
+
| `gemini-3-pro-image` (Nano Banana Pro) | The premium choice for the most complex visual tasks — highest world knowledge, advanced localization, accurate brand consistency, precision creative control | 6 objects + 5 characters |
|
|
89
|
+
| `gemini-3.1-flash-lite-image` (Nano Banana 2 Lite) | The fastest/cheapest for simple tasks — 1K output only, no Google Search grounding | 14 objects (no character consistency) |
|
|
90
|
+
|
|
83
91
|
### Image Generation
|
|
84
92
|
| Tool | Description |
|
|
85
93
|
|------|-------------|
|
|
@@ -87,10 +95,10 @@ Note: Image generation requires a billing-enabled Google Cloud project.
|
|
|
87
95
|
| `gemini_edit_image(prompt, images?, images_base64?, google_search?, seed?, filename?, model?, aspect_ratio?, image_size?, thinking_level?, output_dir?, inline?)` | Edit or compose input image(s) — by **path** (`images`) or **value** (`images_base64`: data URI or raw base64) — with a text instruction. Requires ≥1 input |
|
|
88
96
|
| `gemini_generate_set(master_prompt, scenes? \| count?, reference_mode?, master_images?, master_images_base64?, google_search?, seed?, basename?, model?, thinking_level?, ...)` | Master image (optionally seeded from a reference photo) plus N consistent images referencing it |
|
|
89
97
|
|
|
90
|
-
### Multi-turn (Interactions API
|
|
98
|
+
### Multi-turn (Interactions API)
|
|
91
99
|
| Tool | Description |
|
|
92
100
|
|------|-------------|
|
|
93
|
-
| `gemini_interact(input, previous_interaction_id?, images?, images_base64?, video_url?, video_path?, google_search?, model?, aspect_ratio?, image_size?, thinking_level?, filename?, output_dir?, inline?)` | Generate/edit via Gemini's **Interactions API**. Returns an `interaction_id`; pass it back as `previous_interaction_id` to **iteratively refine the same image** conversationally —
|
|
101
|
+
| `gemini_interact(input, previous_interaction_id?, continue_last?, images?, images_base64?, video_url?, video_path?, google_search?, search_types?, model?, aspect_ratio?, image_size?, thinking_level?, filename?, output_dir?, inline?)` | **Preferred tool for iterative refinement.** Generate/edit via Gemini's **Interactions API**. Returns an `interaction_id`; pass it back as `previous_interaction_id` (or set `continue_last: true` to reuse the session's most recent one) to **iteratively refine the same image** conversationally — do NOT start a new interaction or re-upload the image per tweak. Output is **JPEG**. |
|
|
94
102
|
|
|
95
103
|
## Workflows
|
|
96
104
|
|
|
@@ -148,8 +156,10 @@ r1 = gemini_interact(input: "a cozy reading nook, watercolor")
|
|
|
148
156
|
r2 = gemini_interact(input: "add a sleeping cat on the chair",
|
|
149
157
|
previous_interaction_id: r1.interaction_id)
|
|
150
158
|
→ refined image that preserves r1; returns a NEW interaction_id
|
|
159
|
+
r3 = gemini_interact(input: "warmer lighting", continue_last: true)
|
|
160
|
+
→ same chain, without threading the id (uses the session's most recent interaction)
|
|
151
161
|
```
|
|
152
|
-
Prefer this over re-running `gemini_edit_image` when you're making a *series* of incremental edits — the model keeps the prior result in context.
|
|
162
|
+
Prefer this over re-running `gemini_edit_image` when you're making a *series* of incremental edits — the model keeps the prior result in context. Every result echoes `interaction_id` (and `previous_interaction_id` when chaining) plus a `hint` with the exact follow-up call.
|
|
153
163
|
**⚠️ Chat-pasted/attached images can't be fed to these tools directly.** A pasted
|
|
154
164
|
image reaches the assistant as a *vision* block — the assistant can SEE it but
|
|
155
165
|
never receives the original bytes, and the host doesn't write it to disk. So
|
|
@@ -170,6 +180,36 @@ extraction:
|
|
|
170
180
|
- **`GEMINI_INPUT_DIR`** — point it at a folder (e.g. Cowork's `uploads/`); then a
|
|
171
181
|
**bare filename** resolves against it: `gemini_edit_image(prompt: "…", images: ["house.jpg"])`.
|
|
172
182
|
|
|
183
|
+
## Prompting playbook
|
|
184
|
+
|
|
185
|
+
Condensed from Google's official Nano Banana prompting guide. Core rule:
|
|
186
|
+
**describe the scene, don't list keywords** — narrative sentences beat tag soups.
|
|
187
|
+
|
|
188
|
+
**Best practices:**
|
|
189
|
+
- **Be hyper-specific.** "Ornate elven plate armor, etched with silver leaf patterns" beats "fantasy armor".
|
|
190
|
+
- **Give context & intent.** "Create a logo for a high-end minimalist skincare brand" beats "create a logo".
|
|
191
|
+
- **Iterate conversationally** (`gemini_interact`): "warmer lighting", "same, but more serious expression".
|
|
192
|
+
- **Step-by-step for complex scenes.** "First, a misty forest background. Then a stone altar in the foreground. Finally a glowing sword on the altar."
|
|
193
|
+
- **Semantic negatives.** Describe what you want positively — "an empty, deserted street" — instead of "no cars".
|
|
194
|
+
- **Camera language controls composition.** Wide-angle / macro / low-angle perspective / 85mm portrait lens / three-point softbox lighting.
|
|
195
|
+
|
|
196
|
+
**Generation templates (abbreviated):**
|
|
197
|
+
- *Photorealistic:* `photorealistic [shot type] of [subject] in [setting], [lighting], shot from [angle] with [lens]`
|
|
198
|
+
- *Sticker/illustration:* `[style] sticker of [subject] doing [activity], bold outlines, cel-shading, [palette], white background`
|
|
199
|
+
- *Text in image:* `create a [type] for [brand] with the text "[exact text]" in a [font style]` — Gemini renders text well; Pro is best for professional assets. Tip: generate the wording first, then ask for the image containing it.
|
|
200
|
+
- *Product shot:* `high-resolution studio-lit photo of [product] on [surface], [lighting setup], [angle], sharp focus on [detail]`
|
|
201
|
+
- *Minimalist/negative space:* `single [subject] in [frame position], vast empty [color] background` — for text-overlay backgrounds.
|
|
202
|
+
- *Comic/storyboard:* `make a 3 panel comic in [style]; put the character in [scene]` (Pro or 3.1 Flash).
|
|
203
|
+
|
|
204
|
+
**Editing templates (abbreviated):**
|
|
205
|
+
- *Add/remove:* `using the provided image of [subject], [add/remove] [element]; match the original style/lighting/perspective`
|
|
206
|
+
- *Inpaint (semantic mask):* `change only the [element] to [new element]; keep everything else exactly the same`
|
|
207
|
+
- *Style transfer:* `transform the photo of [subject] into the style of [artist/style]; preserve composition`
|
|
208
|
+
- *Compose:* `take the [element from image 1] and place it with [element from image 2]; adjust lighting/shadows to match`
|
|
209
|
+
- *Detail preservation:* describe the critical element (face, logo) in detail and say it must "remain completely unchanged"
|
|
210
|
+
- *Sketch → finished:* `turn this rough sketch of [subject] into a [style] photo; keep [features], add [details]`
|
|
211
|
+
- *Character 360°:* iterate angles via `gemini_interact` ("in profile looking right"), feeding prior outputs back for consistency
|
|
212
|
+
|
|
173
213
|
## Notes
|
|
174
214
|
|
|
175
215
|
- **Input images** accept either file **paths** (`images` / `master_images`) or **base64/data-URI values** (`images_base64` / `master_images_base64`).
|
|
@@ -178,12 +218,15 @@ extraction:
|
|
|
178
218
|
- **No edit-strength control.** Gemini exposes no denoise/strength knob, and Nano Banana over-preserves the input — big structural edits ("move/remove/shrink", add a mat border) are often ignored. Workarounds: reroll with a different `seed`, raise `thinking_level` to `high`, use forceful wording, do layout changes (padding/borders) externally, or use `gemini_interact` multi-turn.
|
|
179
219
|
- **`thinking_level`** (`minimal`/`high`, Gemini 3 models) controls reasoning depth — `high` can improve complex compositions/edits at higher latency/cost.
|
|
180
220
|
- **Model text.** When the model returns a caption/explanation (mostly Gemini 3 **Pro**), it's surfaced as `text` in the result metadata.
|
|
181
|
-
- **`google_search: true`** grounds the image in live Google Search (current events, weather, real data — great for infographics). The result metadata includes `grounding` with the `queries` run and the `sources` (`{uri, title}`) used. (`gemini_interact` surfaces `grounding.queries`
|
|
221
|
+
- **`google_search: true`** grounds the image in live Google Search (current events, weather, real data — great for infographics). The result metadata includes `grounding` with the `queries` run and the `sources` (`{uri, title}`) used. (`gemini_interact` surfaces `grounding.queries` — the Interactions API returns no clean source list.)
|
|
222
|
+
- **`search_types`** (`gemini_interact` only): `["web_search", "image_search"]` picks the grounding search types (setting it implies `google_search`). **`image_search`** (gemini-3.1-flash-image only) pulls web images via Google Image Search as *visual* references — useful for real-world subjects (a specific butterfly species, a landmark, a product). ⚠️ Two catches: Google ToS require **displaying the returned `grounding.search_suggestions` HTML chips** to the user, and image_search won't depict real people from web images.
|
|
182
223
|
- **`video_url`** (a public YouTube URL, on `gemini_generate_image` / `gemini_interact`) generates an image from a video reference — **requires a Flash model** (e.g. `model: "gemini-3.1-flash-image"`). For a **local video file**, use **`video_path`** instead: the file is uploaded to the Gemini Files API (streamed from disk, 2 GB max), waited to `ACTIVE`, and referenced by its `files/…` uri. The result metadata echoes `video_file` (`{uri, name, expires}`, ~48h retention) — reuse that uri as `video_url` in later calls to skip re-uploading.
|
|
183
|
-
- **`gemini_interact`** is the multi-turn path: it returns an `interaction_id`; thread it back via `previous_interaction_id` for conversational refinement. Output is **JPEG only**.
|
|
224
|
+
- **`gemini_interact`** is the multi-turn path: it returns an `interaction_id`; thread it back via `previous_interaction_id` for conversational refinement. Output is **JPEG only**. (The Interactions API is GA as of 2026-07; it uses a different request shape than the `generate`/`edit`/`set` tools.)
|
|
184
225
|
- `output_dir` per-call overrides `$GEMINI_OUTPUT_DIR` overrides cwd. `inline: true` returns bytes (with a metadata text block) instead of writing.
|
|
185
226
|
- `count` and `scenes` are mutually exclusive in `gemini_generate_set`; `reference_mode: "chain"` references the previous image instead of the master.
|
|
186
227
|
- Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2`, … · Image sizes: `512` (0.5K, Flash only), `1K`, `2K`, `4K`. `4K` is the max native output — true 18×24 in @ 300 DPI (5400×7200) needs an external upscale step.
|
|
187
228
|
- All generated images carry a **SynthID** watermark (Google).
|
|
188
229
|
- The model can mis-render text/Roman numerals (e.g. years) — verify any text in the output; it's a model limitation, not a tool setting.
|
|
230
|
+
- **Best-performance languages:** EN, plus ar, de, es-MX, fr, hi, id, it, ja, ko, pt-BR, ru, ua, vi, zh-CN — prefer prompting in one of these.
|
|
231
|
+
- Asking the *model* for "N images" in one prompt is unreliable (documented limitation) — use the `count` parameter instead; it makes N independent calls with distinct seeds.
|
|
189
232
|
- Server logs to stderr only — stdout is reserved for JSON-RPC.
|
package/dist/bundle.js
CHANGED
|
@@ -32510,7 +32510,7 @@ var init_clipboard = __esm({
|
|
|
32510
32510
|
init_dist();
|
|
32511
32511
|
|
|
32512
32512
|
// src/version.ts
|
|
32513
|
-
var VERSION = "0.
|
|
32513
|
+
var VERSION = "0.7.0";
|
|
32514
32514
|
|
|
32515
32515
|
// src/tools/models.ts
|
|
32516
32516
|
init_dist();
|
|
@@ -32521,7 +32521,7 @@ import { basename, dirname, join } from "node:path";
|
|
|
32521
32521
|
import { fileURLToPath } from "node:url";
|
|
32522
32522
|
|
|
32523
32523
|
// src/models.ts
|
|
32524
|
-
var DEFAULT_IMAGE_MODEL = "gemini-3-
|
|
32524
|
+
var DEFAULT_IMAGE_MODEL = "gemini-3.1-flash-image";
|
|
32525
32525
|
function resolveModel(perCall, envOverride) {
|
|
32526
32526
|
return perCall?.trim() || envOverride?.trim() || DEFAULT_IMAGE_MODEL;
|
|
32527
32527
|
}
|
|
@@ -32539,7 +32539,6 @@ await loadDotenvSafely({ path: join(__dirname, "..", ".env"), override: false })
|
|
|
32539
32539
|
var BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
32540
32540
|
var SERVICE = "Gemini";
|
|
32541
32541
|
var INTERACTIONS_SERVICE = "Gemini Interactions";
|
|
32542
|
-
var API_REVISION = "2026-05-20";
|
|
32543
32542
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
32544
32543
|
var UPLOAD_BASE_URL = "https://generativelanguage.googleapis.com/upload/v1beta";
|
|
32545
32544
|
var FILE_MAX_BYTES = 2 * 1024 ** 3;
|
|
@@ -32575,7 +32574,6 @@ var GeminiClient = class {
|
|
|
32575
32574
|
this.interactionsApi = createApiClient({
|
|
32576
32575
|
baseUrl: BASE_URL,
|
|
32577
32576
|
serviceName: INTERACTIONS_SERVICE,
|
|
32578
|
-
baseHeaders: { "Api-Revision": API_REVISION },
|
|
32579
32577
|
...shared
|
|
32580
32578
|
});
|
|
32581
32579
|
}
|
|
@@ -32753,18 +32751,29 @@ var GeminiClient = class {
|
|
|
32753
32751
|
const body = { model, input: inputParts, response_format: responseFormat };
|
|
32754
32752
|
if (opts.thinkingLevel !== void 0) body.generation_config = { thinking_level: opts.thinkingLevel };
|
|
32755
32753
|
if (opts.previousInteractionId !== void 0) body.previous_interaction_id = opts.previousInteractionId;
|
|
32756
|
-
if (opts.googleSearch
|
|
32754
|
+
if (opts.googleSearch || opts.searchTypes?.length) {
|
|
32755
|
+
const searchTool = { type: "google_search" };
|
|
32756
|
+
if (opts.searchTypes?.length) searchTool.search_types = opts.searchTypes;
|
|
32757
|
+
body.tools = [searchTool];
|
|
32758
|
+
}
|
|
32757
32759
|
const data = await this.interactionsApi.fetchJson("POST", "/interactions", {
|
|
32758
32760
|
body
|
|
32759
32761
|
});
|
|
32760
32762
|
const images = [];
|
|
32761
32763
|
const textParts = [];
|
|
32762
32764
|
const searchQueries = [];
|
|
32765
|
+
const searchSuggestions = /* @__PURE__ */ new Set();
|
|
32763
32766
|
for (const step of data.steps ?? []) {
|
|
32764
32767
|
if (step.type === "google_search_call") {
|
|
32765
32768
|
for (const q of step.arguments?.queries ?? []) if (q?.trim()) searchQueries.push(q);
|
|
32766
32769
|
continue;
|
|
32767
32770
|
}
|
|
32771
|
+
if (step.type === "google_search_result") {
|
|
32772
|
+
if (opts.searchTypes?.includes("image_search")) {
|
|
32773
|
+
for (const r of step.result ?? []) if (r.search_suggestions?.trim()) searchSuggestions.add(r.search_suggestions);
|
|
32774
|
+
}
|
|
32775
|
+
continue;
|
|
32776
|
+
}
|
|
32768
32777
|
if (step.type !== "model_output") continue;
|
|
32769
32778
|
for (const parts of [step.content ?? [], step.summary ?? []]) {
|
|
32770
32779
|
for (const part of parts) {
|
|
@@ -32782,7 +32791,12 @@ var GeminiClient = class {
|
|
|
32782
32791
|
});
|
|
32783
32792
|
}
|
|
32784
32793
|
const resultText = textParts.join("\n") || void 0;
|
|
32785
|
-
|
|
32794
|
+
let grounding;
|
|
32795
|
+
if (searchQueries.length > 0 || searchSuggestions.size > 0) {
|
|
32796
|
+
grounding = {};
|
|
32797
|
+
if (searchQueries.length > 0) grounding.queries = searchQueries;
|
|
32798
|
+
if (searchSuggestions.size > 0) grounding.search_suggestions = [...searchSuggestions];
|
|
32799
|
+
}
|
|
32786
32800
|
return { id: data.id, images, text: resultText, grounding };
|
|
32787
32801
|
}
|
|
32788
32802
|
};
|
|
@@ -32938,11 +32952,12 @@ init_zod();
|
|
|
32938
32952
|
init_dist();
|
|
32939
32953
|
var ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "1:4", "4:1", "1:8", "8:1"];
|
|
32940
32954
|
var IMAGE_SIZES = ["512", "1K", "2K", "4K"];
|
|
32955
|
+
var MODEL_CHOICE_GUIDE = "gemini-3.1-flash-image (Nano Banana 2) is the versatile generalist workhorse \u2014 balances speed with state-of-the-art 4K generation, world knowledge, and reliable text rendering; excels at multi-reference-image processing and consistency. gemini-3-pro-image (Nano Banana Pro) is the premium choice for the most complex visual tasks \u2014 highest world knowledge, advanced localization, accurate brand consistency, precision creative control. gemini-3.1-flash-lite-image (Nano Banana 2 Lite) is the fastest/cheapest for simple tasks (1K only, no search grounding).";
|
|
32941
32956
|
var sharedImageSchema = {
|
|
32942
32957
|
// Bare id only: the model is interpolated into the URL path
|
|
32943
32958
|
// (`/models/${model}:generateContent`), so slashes/colons/queries must be
|
|
32944
32959
|
// rejected to keep a crafted value from escaping the path segment.
|
|
32945
|
-
model: external_exports.string().regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")').optional().describe(
|
|
32960
|
+
model: external_exports.string().regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")').optional().describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
32946
32961
|
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe("Output aspect ratio"),
|
|
32947
32962
|
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, Flash-only)"),
|
|
32948
32963
|
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or cwd)"),
|
|
@@ -32992,11 +33007,12 @@ async function emit(named, opts, meta3) {
|
|
|
32992
33007
|
}
|
|
32993
33008
|
|
|
32994
33009
|
// src/tools/generate.ts
|
|
33010
|
+
var REFINE_HINT = "To iteratively refine this image, use gemini_interact (pass the output file via `images` on the first call, then chain `previous_interaction_id` on each subsequent call).";
|
|
32995
33011
|
function registerGenerateTools(server) {
|
|
32996
33012
|
server.registerTool(
|
|
32997
33013
|
"gemini_generate_image",
|
|
32998
33014
|
{
|
|
32999
|
-
description: "Generate image(s) from a text prompt with a Gemini image model (Nano Banana / Nano Banana Pro).",
|
|
33015
|
+
description: "Generate image(s) from a text prompt with a Gemini image model (Nano Banana / Nano Banana Pro). If the result will likely be refined iteratively, prefer gemini_interact (multi-turn) as the entry point.",
|
|
33000
33016
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
33001
33017
|
inputSchema: {
|
|
33002
33018
|
prompt: external_exports.string().min(1).describe("Text prompt describing the image"),
|
|
@@ -33041,13 +33057,14 @@ function registerGenerateTools(server) {
|
|
|
33041
33057
|
if (capturedText) meta3.text = capturedText;
|
|
33042
33058
|
if (capturedGrounding) meta3.grounding = capturedGrounding;
|
|
33043
33059
|
if (video.videoFileMeta) meta3.video_file = video.videoFileMeta;
|
|
33060
|
+
meta3.hint = REFINE_HINT;
|
|
33044
33061
|
return emit(named, args, meta3);
|
|
33045
33062
|
}
|
|
33046
33063
|
);
|
|
33047
33064
|
server.registerTool(
|
|
33048
33065
|
"gemini_edit_image",
|
|
33049
33066
|
{
|
|
33050
|
-
description: "Edit or compose images: provide one or more input images (paths or base64), plus a text instruction. Gemini over-preserves the input; there is no edit-strength control \u2014 for large structural changes, reroll with a different `seed` or more forceful wording.",
|
|
33067
|
+
description: "Edit or compose images: provide one or more input images (paths or base64), plus a text instruction. For a SERIES of successive edits to the same image, prefer gemini_interact (multi-turn) \u2014 it keeps edit context and avoids re-processing the full image each round; use gemini_edit_image for one-off edits or composing multiple distinct inputs. Gemini over-preserves the input; there is no edit-strength control \u2014 for large structural changes, reroll with a different `seed` or more forceful wording.",
|
|
33051
33068
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
33052
33069
|
inputSchema: {
|
|
33053
33070
|
prompt: external_exports.string().min(1).describe("Instruction describing the edit or composition"),
|
|
@@ -33081,6 +33098,7 @@ function registerGenerateTools(server) {
|
|
|
33081
33098
|
const meta3 = buildMeta(model, seed, args);
|
|
33082
33099
|
if (text) meta3.text = text;
|
|
33083
33100
|
if (result.grounding) meta3.grounding = result.grounding;
|
|
33101
|
+
meta3.hint = REFINE_HINT;
|
|
33084
33102
|
return emit([{ image: img, base: slug }], args, meta3);
|
|
33085
33103
|
}
|
|
33086
33104
|
);
|
|
@@ -33149,18 +33167,20 @@ function registerSetTools(server) {
|
|
|
33149
33167
|
// src/tools/interact.ts
|
|
33150
33168
|
init_zod();
|
|
33151
33169
|
init_dist();
|
|
33170
|
+
var lastInteractionId;
|
|
33152
33171
|
function registerInteractTools(server) {
|
|
33153
33172
|
server.registerTool(
|
|
33154
33173
|
"gemini_interact",
|
|
33155
33174
|
{
|
|
33156
|
-
description: "
|
|
33175
|
+
description: "Preferred tool for iterative or multi-step refinement of a single image \u2014 multi-turn generation/editing via Gemini's Interactions API. To refine, capture the returned interaction `id` and pass it as `previous_interaction_id` on the next call \u2014 do NOT start a new interaction or re-upload the image for each tweak. `continue_last: true` chains from this session's most recent interaction without threading the id. Output is JPEG.",
|
|
33157
33176
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
33158
33177
|
inputSchema: {
|
|
33159
33178
|
input: external_exports.string().min(1).describe("Text prompt or editing instruction"),
|
|
33160
33179
|
previous_interaction_id: external_exports.string().optional().describe("ID from a prior gemini_interact call \u2014 continues that multi-turn conversation"),
|
|
33180
|
+
continue_last: external_exports.boolean().optional().describe("Continue from the most recent interaction this server created (convenience for previous_interaction_id; an explicit id wins)"),
|
|
33161
33181
|
images: external_exports.array(external_exports.string().min(1)).optional().describe("Paths to reference input images"),
|
|
33162
33182
|
images_base64: external_exports.array(external_exports.string().min(1)).optional().describe("Reference images as base64 strings or data URIs"),
|
|
33163
|
-
model: external_exports.string().optional().describe(
|
|
33183
|
+
model: external_exports.string().optional().describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
33164
33184
|
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe("Output aspect ratio"),
|
|
33165
33185
|
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, Flash-only)"),
|
|
33166
33186
|
thinking_level: external_exports.enum(["minimal", "high"]).optional().describe("Reasoning depth; higher can help complex/structural edits"),
|
|
@@ -33168,12 +33188,24 @@ function registerInteractTools(server) {
|
|
|
33168
33188
|
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or cwd)"),
|
|
33169
33189
|
inline: external_exports.boolean().optional().describe("Return base64 images inline instead of writing to disk"),
|
|
33170
33190
|
google_search: external_exports.boolean().optional().describe("Ground the image in live Google Search results (current events, weather, data)"),
|
|
33191
|
+
search_types: external_exports.array(external_exports.enum(["web_search", "image_search"])).optional().describe(
|
|
33192
|
+
"Grounding search types (implies google_search). image_search (gemini-3.1-flash-image only) uses Google Image Search results as visual references; per Google ToS the returned grounding.search_suggestions HTML must then be displayed to the user. Cannot depict real people from web images."
|
|
33193
|
+
),
|
|
33171
33194
|
video_url: external_exports.string().url().optional().describe("Public YouTube URL (or a previously uploaded Files API uri) as a video reference (video\u2192image; use a Flash model e.g. gemini-3.1-flash-image)"),
|
|
33172
33195
|
video_path: videoPathSchema,
|
|
33173
33196
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)")
|
|
33174
33197
|
}
|
|
33175
33198
|
},
|
|
33176
33199
|
async (args) => {
|
|
33200
|
+
let previousInteractionId = args.previous_interaction_id;
|
|
33201
|
+
if (!previousInteractionId && args.continue_last) {
|
|
33202
|
+
if (!lastInteractionId) {
|
|
33203
|
+
throw new McpToolError("No previous interaction to continue in this session.", {
|
|
33204
|
+
hint: "Call gemini_interact once without continue_last first, or pass an explicit previous_interaction_id."
|
|
33205
|
+
});
|
|
33206
|
+
}
|
|
33207
|
+
previousInteractionId = lastInteractionId;
|
|
33208
|
+
}
|
|
33177
33209
|
const inputs = await gatherImageInputs({ images: args.images, images_base64: args.images_base64, from_clipboard: args.from_clipboard });
|
|
33178
33210
|
const video = await resolveVideoInput(args);
|
|
33179
33211
|
const r = await client.interact({
|
|
@@ -33183,13 +33215,17 @@ function registerInteractTools(server) {
|
|
|
33183
33215
|
aspectRatio: args.aspect_ratio,
|
|
33184
33216
|
imageSize: args.image_size,
|
|
33185
33217
|
thinkingLevel: args.thinking_level,
|
|
33186
|
-
previousInteractionId
|
|
33218
|
+
previousInteractionId,
|
|
33187
33219
|
googleSearch: args.google_search,
|
|
33220
|
+
searchTypes: args.search_types,
|
|
33188
33221
|
videoUrl: video.videoUrl,
|
|
33189
33222
|
videoMimeType: video.videoMimeType
|
|
33190
33223
|
});
|
|
33224
|
+
lastInteractionId = r.id;
|
|
33191
33225
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
33192
33226
|
const meta3 = { model, interaction_id: r.id };
|
|
33227
|
+
if (previousInteractionId) meta3.previous_interaction_id = previousInteractionId;
|
|
33228
|
+
meta3.hint = `To refine this image, call gemini_interact again with previous_interaction_id: "${r.id}" (or continue_last: true).`;
|
|
33193
33229
|
if (r.text) meta3.text = r.text;
|
|
33194
33230
|
if (r.grounding) meta3.grounding = r.grounding;
|
|
33195
33231
|
if (video.videoFileMeta) meta3.video_file = video.videoFileMeta;
|
package/dist/client.js
CHANGED
|
@@ -7,9 +7,6 @@ await loadDotenvSafely({ path: join(__dirname, '..', '.env'), override: false })
|
|
|
7
7
|
const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta'; // v1 lacks gemini-3-pro-image; confirmed via Task 5
|
|
8
8
|
const SERVICE = 'Gemini';
|
|
9
9
|
const INTERACTIONS_SERVICE = 'Gemini Interactions';
|
|
10
|
-
// Required revision header for the beta Interactions API (verified 2026-06-08;
|
|
11
|
-
// see docs/GEMINI-API.md) — load-bearing, requests 400 without it.
|
|
12
|
-
const API_REVISION = '2026-05-20';
|
|
13
10
|
// Per-request abort budget. Image generation routinely runs 30s+ on the Pro
|
|
14
11
|
// model, so this is deliberately 60s (NOT the fleet's usual 15–30s).
|
|
15
12
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
@@ -58,12 +55,13 @@ export class GeminiClient {
|
|
|
58
55
|
fetchImpl: this.fetchImpl,
|
|
59
56
|
};
|
|
60
57
|
this.api = createApiClient({ baseUrl: BASE_URL, serviceName: SERVICE, ...shared });
|
|
61
|
-
// Separate client for the
|
|
62
|
-
//
|
|
58
|
+
// Separate client for the Interactions API so errors name the right API.
|
|
59
|
+
// The beta-era Api-Revision header is gone: the API went GA and requests
|
|
60
|
+
// succeed without it (verified live 2026-07-06; see docs/GEMINI-API.md).
|
|
61
|
+
// Don't re-add a pinned revision — it would freeze us on old semantics.
|
|
63
62
|
this.interactionsApi = createApiClient({
|
|
64
63
|
baseUrl: BASE_URL,
|
|
65
64
|
serviceName: INTERACTIONS_SERVICE,
|
|
66
|
-
baseHeaders: { 'Api-Revision': API_REVISION },
|
|
67
65
|
...shared,
|
|
68
66
|
});
|
|
69
67
|
}
|
|
@@ -252,8 +250,12 @@ export class GeminiClient {
|
|
|
252
250
|
body.generation_config = { thinking_level: opts.thinkingLevel };
|
|
253
251
|
if (opts.previousInteractionId !== undefined)
|
|
254
252
|
body.previous_interaction_id = opts.previousInteractionId;
|
|
255
|
-
if (opts.googleSearch)
|
|
256
|
-
|
|
253
|
+
if (opts.googleSearch || opts.searchTypes?.length) {
|
|
254
|
+
const searchTool = { type: 'google_search' };
|
|
255
|
+
if (opts.searchTypes?.length)
|
|
256
|
+
searchTool.search_types = opts.searchTypes;
|
|
257
|
+
body.tools = [searchTool];
|
|
258
|
+
}
|
|
257
259
|
const data = await this.interactionsApi.fetchJson('POST', '/interactions', {
|
|
258
260
|
body,
|
|
259
261
|
});
|
|
@@ -265,17 +267,31 @@ export class GeminiClient {
|
|
|
265
267
|
const images = [];
|
|
266
268
|
const textParts = [];
|
|
267
269
|
const searchQueries = [];
|
|
270
|
+
const searchSuggestions = new Set();
|
|
268
271
|
for (const step of data.steps ?? []) {
|
|
269
272
|
// Grounding queries live on the `google_search_call` step's arguments.
|
|
270
273
|
// (The `google_search_result` step only carries HTML `search_suggestions`
|
|
271
274
|
// chips — no clean source uri/title list like generateContent's
|
|
272
|
-
// groundingChunks — so interact surfaces queries
|
|
275
|
+
// groundingChunks — so interact surfaces queries, plus the suggestion
|
|
276
|
+
// chips when image_search was requested, ToS below.)
|
|
273
277
|
if (step.type === 'google_search_call') {
|
|
274
278
|
for (const q of step.arguments?.queries ?? [])
|
|
275
279
|
if (q?.trim())
|
|
276
280
|
searchQueries.push(q);
|
|
277
281
|
continue;
|
|
278
282
|
}
|
|
283
|
+
// ToS: image_search results' `search_suggestions` chips MUST be displayed
|
|
284
|
+
// by the caller, so surface them (deduped — the chips repeat) only when
|
|
285
|
+
// image_search grounding was requested; web-only grounding keeps the
|
|
286
|
+
// lean queries-only meta.
|
|
287
|
+
if (step.type === 'google_search_result') {
|
|
288
|
+
if (opts.searchTypes?.includes('image_search')) {
|
|
289
|
+
for (const r of step.result ?? [])
|
|
290
|
+
if (r.search_suggestions?.trim())
|
|
291
|
+
searchSuggestions.add(r.search_suggestions);
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
279
295
|
if (step.type !== 'model_output')
|
|
280
296
|
continue;
|
|
281
297
|
for (const parts of [step.content ?? [], step.summary ?? []]) {
|
|
@@ -295,7 +311,14 @@ export class GeminiClient {
|
|
|
295
311
|
});
|
|
296
312
|
}
|
|
297
313
|
const resultText = textParts.join('\n') || undefined;
|
|
298
|
-
|
|
314
|
+
let grounding;
|
|
315
|
+
if (searchQueries.length > 0 || searchSuggestions.size > 0) {
|
|
316
|
+
grounding = {};
|
|
317
|
+
if (searchQueries.length > 0)
|
|
318
|
+
grounding.queries = searchQueries;
|
|
319
|
+
if (searchSuggestions.size > 0)
|
|
320
|
+
grounding.search_suggestions = [...searchSuggestions];
|
|
321
|
+
}
|
|
299
322
|
return { id: data.id, images, text: resultText, grounding };
|
|
300
323
|
}
|
|
301
324
|
}
|
package/dist/models.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The generalist workhorse (Nano Banana 2) — overridable per-call and via
|
|
3
|
+
* GEMINI_IMAGE_MODEL. The premium gemini-3-pro-image is a deliberate opt-in:
|
|
4
|
+
* it costs more and needs a funded account, so callers reach for it via the
|
|
5
|
+
* `model` param when the task warrants it (see MODEL_CHOICE_GUIDE).
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_IMAGE_MODEL = 'gemini-3.1-flash-image';
|
|
3
8
|
/** Per-call → env override → hardcoded default. Blank/whitespace counts as unset. */
|
|
4
9
|
export function resolveModel(perCall, envOverride) {
|
|
5
10
|
return perCall?.trim() || envOverride?.trim() || DEFAULT_IMAGE_MODEL;
|
package/dist/tools/generate.js
CHANGED
|
@@ -4,9 +4,14 @@ import { resolveModel } from '../models.js';
|
|
|
4
4
|
import { client } from '../client.js';
|
|
5
5
|
import { slugify, baseName, gatherImageInputs } from '../images.js';
|
|
6
6
|
import { emit, sharedImageSchema, pickSeed, buildMeta, resolveVideoInput, videoPathSchema } from './shared.js';
|
|
7
|
+
// generateContent output can't seed an Interactions-API conversation, so
|
|
8
|
+
// results point follow-up edits at gemini_interact instead of an id.
|
|
9
|
+
const REFINE_HINT = 'To iteratively refine this image, use gemini_interact (pass the output file via `images` on the first call, ' +
|
|
10
|
+
'then chain `previous_interaction_id` on each subsequent call).';
|
|
7
11
|
export function registerGenerateTools(server) {
|
|
8
12
|
server.registerTool('gemini_generate_image', {
|
|
9
|
-
description: 'Generate image(s) from a text prompt with a Gemini image model (Nano Banana / Nano Banana Pro).'
|
|
13
|
+
description: 'Generate image(s) from a text prompt with a Gemini image model (Nano Banana / Nano Banana Pro). ' +
|
|
14
|
+
'If the result will likely be refined iteratively, prefer gemini_interact (multi-turn) as the entry point.',
|
|
10
15
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
11
16
|
inputSchema: {
|
|
12
17
|
prompt: z.string().min(1).describe('Text prompt describing the image'),
|
|
@@ -61,10 +66,13 @@ export function registerGenerateTools(server) {
|
|
|
61
66
|
meta.grounding = capturedGrounding;
|
|
62
67
|
if (video.videoFileMeta)
|
|
63
68
|
meta.video_file = video.videoFileMeta;
|
|
69
|
+
meta.hint = REFINE_HINT;
|
|
64
70
|
return emit(named, args, meta);
|
|
65
71
|
});
|
|
66
72
|
server.registerTool('gemini_edit_image', {
|
|
67
73
|
description: 'Edit or compose images: provide one or more input images (paths or base64), plus a text instruction. ' +
|
|
74
|
+
'For a SERIES of successive edits to the same image, prefer gemini_interact (multi-turn) — it keeps edit context ' +
|
|
75
|
+
'and avoids re-processing the full image each round; use gemini_edit_image for one-off edits or composing multiple distinct inputs. ' +
|
|
68
76
|
'Gemini over-preserves the input; there is no edit-strength control — for large structural changes, reroll with a different `seed` or more forceful wording.',
|
|
69
77
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
70
78
|
inputSchema: {
|
|
@@ -100,6 +108,7 @@ export function registerGenerateTools(server) {
|
|
|
100
108
|
meta.text = text;
|
|
101
109
|
if (result.grounding)
|
|
102
110
|
meta.grounding = result.grounding;
|
|
111
|
+
meta.hint = REFINE_HINT;
|
|
103
112
|
return emit([{ image: img, base: slug }], args, meta);
|
|
104
113
|
});
|
|
105
114
|
}
|
package/dist/tools/interact.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { readEnvVar } from '@chrischall/mcp-utils';
|
|
2
|
+
import { McpToolError, readEnvVar } from '@chrischall/mcp-utils';
|
|
3
3
|
import { resolveModel } from '../models.js';
|
|
4
4
|
import { client } from '../client.js';
|
|
5
5
|
import { slugify, baseName, gatherImageInputs } from '../images.js';
|
|
6
|
-
import { emit, ASPECT_RATIOS, IMAGE_SIZES, resolveVideoInput, videoPathSchema } from './shared.js';
|
|
6
|
+
import { emit, ASPECT_RATIOS, IMAGE_SIZES, MODEL_CHOICE_GUIDE, resolveVideoInput, videoPathSchema } from './shared.js';
|
|
7
|
+
// The most recent interaction id this server process created — what
|
|
8
|
+
// `continue_last: true` resumes. In-memory only: an MCP server lives for the
|
|
9
|
+
// host session, so "last" means "last in this session".
|
|
10
|
+
let lastInteractionId;
|
|
7
11
|
export function registerInteractTools(server) {
|
|
8
12
|
server.registerTool('gemini_interact', {
|
|
9
|
-
description:
|
|
10
|
-
|
|
11
|
-
'refine the
|
|
13
|
+
description: 'Preferred tool for iterative or multi-step refinement of a single image — ' +
|
|
14
|
+
"multi-turn generation/editing via Gemini's Interactions API. " +
|
|
15
|
+
'To refine, capture the returned interaction `id` and pass it as `previous_interaction_id` ' +
|
|
16
|
+
'on the next call — do NOT start a new interaction or re-upload the image for each tweak. ' +
|
|
17
|
+
'`continue_last: true` chains from this session\'s most recent interaction without threading the id. ' +
|
|
12
18
|
'Output is JPEG.',
|
|
13
19
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
14
20
|
inputSchema: {
|
|
@@ -17,6 +23,10 @@ export function registerInteractTools(server) {
|
|
|
17
23
|
.string()
|
|
18
24
|
.optional()
|
|
19
25
|
.describe('ID from a prior gemini_interact call — continues that multi-turn conversation'),
|
|
26
|
+
continue_last: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Continue from the most recent interaction this server created (convenience for previous_interaction_id; an explicit id wins)'),
|
|
20
30
|
images: z
|
|
21
31
|
.array(z.string().min(1))
|
|
22
32
|
.optional()
|
|
@@ -28,7 +38,7 @@ export function registerInteractTools(server) {
|
|
|
28
38
|
model: z
|
|
29
39
|
.string()
|
|
30
40
|
.optional()
|
|
31
|
-
.describe(
|
|
41
|
+
.describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
32
42
|
aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe('Output aspect ratio'),
|
|
33
43
|
image_size: z
|
|
34
44
|
.enum(IMAGE_SIZES)
|
|
@@ -54,6 +64,11 @@ export function registerInteractTools(server) {
|
|
|
54
64
|
.boolean()
|
|
55
65
|
.optional()
|
|
56
66
|
.describe('Ground the image in live Google Search results (current events, weather, data)'),
|
|
67
|
+
search_types: z
|
|
68
|
+
.array(z.enum(['web_search', 'image_search']))
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Grounding search types (implies google_search). image_search (gemini-3.1-flash-image only) uses Google Image Search results as visual references; ' +
|
|
71
|
+
'per Google ToS the returned grounding.search_suggestions HTML must then be displayed to the user. Cannot depict real people from web images.'),
|
|
57
72
|
video_url: z
|
|
58
73
|
.string()
|
|
59
74
|
.url()
|
|
@@ -66,6 +81,15 @@ export function registerInteractTools(server) {
|
|
|
66
81
|
.describe('Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)'),
|
|
67
82
|
},
|
|
68
83
|
}, async (args) => {
|
|
84
|
+
let previousInteractionId = args.previous_interaction_id;
|
|
85
|
+
if (!previousInteractionId && args.continue_last) {
|
|
86
|
+
if (!lastInteractionId) {
|
|
87
|
+
throw new McpToolError('No previous interaction to continue in this session.', {
|
|
88
|
+
hint: 'Call gemini_interact once without continue_last first, or pass an explicit previous_interaction_id.',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
previousInteractionId = lastInteractionId;
|
|
92
|
+
}
|
|
69
93
|
const inputs = await gatherImageInputs({ images: args.images, images_base64: args.images_base64, from_clipboard: args.from_clipboard });
|
|
70
94
|
const video = await resolveVideoInput(args);
|
|
71
95
|
const r = await client.interact({
|
|
@@ -75,13 +99,18 @@ export function registerInteractTools(server) {
|
|
|
75
99
|
aspectRatio: args.aspect_ratio,
|
|
76
100
|
imageSize: args.image_size,
|
|
77
101
|
thinkingLevel: args.thinking_level,
|
|
78
|
-
previousInteractionId
|
|
102
|
+
previousInteractionId,
|
|
79
103
|
googleSearch: args.google_search,
|
|
104
|
+
searchTypes: args.search_types,
|
|
80
105
|
videoUrl: video.videoUrl,
|
|
81
106
|
videoMimeType: video.videoMimeType,
|
|
82
107
|
});
|
|
108
|
+
lastInteractionId = r.id;
|
|
83
109
|
const model = resolveModel(args.model, readEnvVar('GEMINI_IMAGE_MODEL'));
|
|
84
110
|
const meta = { model, interaction_id: r.id };
|
|
111
|
+
if (previousInteractionId)
|
|
112
|
+
meta.previous_interaction_id = previousInteractionId;
|
|
113
|
+
meta.hint = `To refine this image, call gemini_interact again with previous_interaction_id: "${r.id}" (or continue_last: true).`;
|
|
85
114
|
if (r.text)
|
|
86
115
|
meta.text = r.text;
|
|
87
116
|
if (r.grounding)
|
package/dist/tools/shared.js
CHANGED
|
@@ -11,6 +11,15 @@ export const IMAGE_SIZES = ['512', '1K', '2K', '4K'];
|
|
|
11
11
|
* here so descriptions can't drift between `generate.ts` and `set.ts`. Spread
|
|
12
12
|
* into each tool's `inputSchema`.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* When to pick which Nano Banana model — shared by every tool's `model` param
|
|
16
|
+
* so the guidance can't drift between them.
|
|
17
|
+
*/
|
|
18
|
+
export const MODEL_CHOICE_GUIDE = 'gemini-3.1-flash-image (Nano Banana 2) is the versatile generalist workhorse — balances speed with ' +
|
|
19
|
+
'state-of-the-art 4K generation, world knowledge, and reliable text rendering; excels at multi-reference-image ' +
|
|
20
|
+
'processing and consistency. gemini-3-pro-image (Nano Banana Pro) is the premium choice for the most complex ' +
|
|
21
|
+
'visual tasks — highest world knowledge, advanced localization, accurate brand consistency, precision creative control. ' +
|
|
22
|
+
'gemini-3.1-flash-lite-image (Nano Banana 2 Lite) is the fastest/cheapest for simple tasks (1K only, no search grounding).';
|
|
14
23
|
export const sharedImageSchema = {
|
|
15
24
|
// Bare id only: the model is interpolated into the URL path
|
|
16
25
|
// (`/models/${model}:generateContent`), so slashes/colons/queries must be
|
|
@@ -19,7 +28,7 @@ export const sharedImageSchema = {
|
|
|
19
28
|
.string()
|
|
20
29
|
.regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")')
|
|
21
30
|
.optional()
|
|
22
|
-
.describe(
|
|
31
|
+
.describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
23
32
|
aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe('Output aspect ratio'),
|
|
24
33
|
image_size: z.enum(IMAGE_SIZES).optional().describe('Output resolution (512 = 0.5K, Flash-only)'),
|
|
25
34
|
output_dir: z.string().optional().describe('Directory to write images to (default: $GEMINI_OUTPUT_DIR or cwd)'),
|
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 = '0.
|
|
2
|
+
export const VERSION = '0.7.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/gemini-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": "0.
|
|
9
|
+
"version": "0.7.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.7.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "GEMINI_IMAGE_MODEL",
|
|
28
|
-
"description": "Override the default image model (default: gemini-3-
|
|
28
|
+
"description": "Override the default image model (default: gemini-3.1-flash-image)",
|
|
29
29
|
"isRequired": false,
|
|
30
30
|
"format": "string",
|
|
31
31
|
"isSecret": false
|