@chrischall/gemini-mcp 0.7.2 → 0.9.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 +21 -0
- package/dist/bundle.js +594 -139
- package/dist/client.js +55 -29
- package/dist/images.js +42 -1
- package/dist/index.js +2 -1
- package/dist/jobs.js +0 -0
- package/dist/tools/_confirm.js +39 -0
- package/dist/tools/generate.js +108 -64
- package/dist/tools/interact.js +92 -41
- package/dist/tools/jobs.js +17 -0
- package/dist/tools/set.js +63 -37
- package/dist/tools/shared.js +132 -8
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getJobResult } from '../jobs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Poll tool for the async generation pattern (issue #52). A generation tool
|
|
5
|
+
* called with `async: true` returns a `job_id` immediately (instead of blocking
|
|
6
|
+
* past a host's tools/call timeout); this tool retrieves that job's status and,
|
|
7
|
+
* once complete, its normal result payload.
|
|
8
|
+
*/
|
|
9
|
+
export function registerJobTools(server) {
|
|
10
|
+
server.registerTool('gemini_get_result', {
|
|
11
|
+
description: 'Retrieve an async generation started with `async: true`. Pass the returned `job_id`: while running it reports status "running"; on completion it returns the normal result (image paths / inline images + meta); on failure it raises the recorded error. Jobs are per-process and expire ~10 min after completion — if a job id is unknown, check the output dir / <image>.json sidecar.',
|
|
12
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
13
|
+
inputSchema: {
|
|
14
|
+
job_id: z.string().min(1).describe('The job_id returned by a generation tool called with async: true'),
|
|
15
|
+
},
|
|
16
|
+
}, async (args) => getJobResult(args.job_id));
|
|
17
|
+
}
|
package/dist/tools/set.js
CHANGED
|
@@ -3,7 +3,9 @@ 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, sharedImageSchema, pickSeed, buildMeta } from './shared.js';
|
|
6
|
+
import { emit, sharedImageSchema, pickSeed, buildMeta, timeoutRiskHint, withProgressHeartbeat } from './shared.js';
|
|
7
|
+
import { dispatch, fingerprintRequest } from '../jobs.js';
|
|
8
|
+
import { previewLocalInputsUnlessConfirmed, schemaConfirm } from './_confirm.js';
|
|
7
9
|
export function registerSetTools(server) {
|
|
8
10
|
server.registerTool('gemini_generate_set', {
|
|
9
11
|
description: 'Generate a consistent SET of images: a master image from master_prompt, then one image per scene that references the master so the subject/style stays consistent. Provide `scenes` (explicit per-image prompts) OR `count` (variations of the master).',
|
|
@@ -16,49 +18,73 @@ export function registerSetTools(server) {
|
|
|
16
18
|
basename: z.string().optional().describe('Base filename prefix for output images (default: slugified master_prompt)'),
|
|
17
19
|
master_images: z.array(z.string().min(1)).optional().describe('Reference image paths passed to the master generation call'),
|
|
18
20
|
master_images_base64: z.array(z.string().min(1)).optional().describe('Reference images as base64 strings or data URIs for master generation'),
|
|
21
|
+
confirm: schemaConfirm,
|
|
19
22
|
...sharedImageSchema,
|
|
20
23
|
},
|
|
21
|
-
}, async (args) => {
|
|
24
|
+
}, async (args, extra) => {
|
|
22
25
|
// `scenes` is min(1) at the schema, so a non-undefined value is non-empty.
|
|
23
26
|
if ((args.scenes && args.count) || (!args.scenes && !args.count)) {
|
|
24
27
|
throw new McpToolError('Provide exactly one of `scenes` or `count`.');
|
|
25
28
|
}
|
|
26
|
-
|
|
29
|
+
// Confirm-gate local file inputs: only `master_images` (paths) can read a
|
|
30
|
+
// local file; base64 references pass through ungated. Dry-run makes NO API call.
|
|
31
|
+
const gate = await previewLocalInputsUnlessConfirmed(args.confirm, 'Send local master image input(s) to the Gemini API', '/v1beta/models/{model}:generateContent', args.master_images);
|
|
32
|
+
if (gate)
|
|
33
|
+
return gate;
|
|
27
34
|
const model = resolveModel(args.model, readEnvVar('GEMINI_IMAGE_MODEL'));
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
seed
|
|
36
|
-
|
|
35
|
+
const fingerprint = fingerprintRequest('gemini_generate_set', {
|
|
36
|
+
model, master_prompt: args.master_prompt, scenes: args.scenes, count: args.count,
|
|
37
|
+
reference_mode: args.reference_mode, aspect_ratio: args.aspect_ratio, image_size: args.image_size,
|
|
38
|
+
thinking_level: args.thinking_level, google_search: args.google_search,
|
|
39
|
+
master_images: args.master_images, master_images_base64: args.master_images_base64, from_clipboard: args.from_clipboard,
|
|
40
|
+
});
|
|
41
|
+
return dispatch({ toolName: 'gemini_generate_set', fingerprint, idempotencyKey: args.idempotency_key, async: args.async }, async () => {
|
|
42
|
+
const seed = pickSeed(args.seed);
|
|
43
|
+
const cfg = { model: args.model, aspectRatio: args.aspect_ratio, imageSize: args.image_size, thinkingLevel: args.thinking_level, googleSearch: args.google_search, timeoutMs: args.timeout_ms };
|
|
44
|
+
const slug = args.basename ? baseName(args.basename) : slugify(args.master_prompt);
|
|
45
|
+
// 1. master (optionally seeded from reference images)
|
|
46
|
+
const masterRefInputs = await gatherImageInputs({ images: args.master_images, images_base64: args.master_images_base64, from_clipboard: args.from_clipboard });
|
|
47
|
+
const named = [];
|
|
48
|
+
const masterResult = await withProgressHeartbeat(extra, `Generating image set (${model})`, async () => {
|
|
49
|
+
const result = await client.generate({
|
|
50
|
+
prompt: args.master_prompt,
|
|
51
|
+
images: masterRefInputs.length > 0 ? masterRefInputs : undefined,
|
|
52
|
+
seed,
|
|
53
|
+
...cfg,
|
|
54
|
+
});
|
|
55
|
+
const master = result.images[0];
|
|
56
|
+
named.push({ image: master, base: `${slug}-master` });
|
|
57
|
+
// Only the master's text is surfaced (below). Per-scene text is intentionally
|
|
58
|
+
// dropped — a set returns one combined result and merging N captions is noise.
|
|
59
|
+
// 2. scene prompts (explicit, or N repeats of master_prompt for variations)
|
|
60
|
+
const scenePrompts = args.scenes ?? Array.from({ length: args.count ?? 0 }, () => args.master_prompt);
|
|
61
|
+
const mode = args.reference_mode ?? 'master';
|
|
62
|
+
if (mode === 'chain') {
|
|
63
|
+
let ref = master;
|
|
64
|
+
for (let i = 0; i < scenePrompts.length; i++) {
|
|
65
|
+
const { images: [img] } = await client.generate({ prompt: scenePrompts[i], images: [ref], seed: seed + i + 1, ...cfg });
|
|
66
|
+
named.push({ image: img, base: `${slug}-${String(i + 1).padStart(2, '0')}` });
|
|
67
|
+
ref = img;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const scenes = await Promise.all(scenePrompts.map((p, i) => client.generate({ prompt: p, images: [master], seed: seed + i + 1, ...cfg }).then((r) => r.images[0])));
|
|
72
|
+
scenes.forEach((img, i) => named.push({ image: img, base: `${slug}-${String(i + 1).padStart(2, '0')}` }));
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
});
|
|
76
|
+
const masterText = masterResult.text;
|
|
77
|
+
const meta = buildMeta(model, seed, args);
|
|
78
|
+
if (masterText)
|
|
79
|
+
meta.text = masterText;
|
|
80
|
+
if (masterResult.grounding)
|
|
81
|
+
meta.grounding = masterResult.grounding;
|
|
82
|
+
// A set is master + N scenes in one tools/call — the most timeout-prone
|
|
83
|
+
// tool. Effective image count drives the risk hint.
|
|
84
|
+
const risk = timeoutRiskHint({ model, imageSize: args.image_size, count: named.length });
|
|
85
|
+
if (risk)
|
|
86
|
+
meta.timeout_risk = risk;
|
|
87
|
+
return emit(named, args, meta);
|
|
37
88
|
});
|
|
38
|
-
const { images: [master], text: masterText } = masterResult;
|
|
39
|
-
const named = [{ image: master, base: `${slug}-master` }];
|
|
40
|
-
// Only the master's text is surfaced (below). Per-scene text is intentionally
|
|
41
|
-
// dropped — a set returns one combined result and merging N captions is noise.
|
|
42
|
-
// 2. scene prompts (explicit, or N repeats of master_prompt for variations)
|
|
43
|
-
const scenePrompts = args.scenes ?? Array.from({ length: args.count ?? 0 }, () => args.master_prompt);
|
|
44
|
-
const mode = args.reference_mode ?? 'master';
|
|
45
|
-
if (mode === 'chain') {
|
|
46
|
-
let ref = master;
|
|
47
|
-
for (let i = 0; i < scenePrompts.length; i++) {
|
|
48
|
-
const { images: [img] } = await client.generate({ prompt: scenePrompts[i], images: [ref], seed: seed + i + 1, ...cfg });
|
|
49
|
-
named.push({ image: img, base: `${slug}-${String(i + 1).padStart(2, '0')}` });
|
|
50
|
-
ref = img;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
const scenes = await Promise.all(scenePrompts.map((p, i) => client.generate({ prompt: p, images: [master], seed: seed + i + 1, ...cfg }).then((r) => r.images[0])));
|
|
55
|
-
scenes.forEach((img, i) => named.push({ image: img, base: `${slug}-${String(i + 1).padStart(2, '0')}` }));
|
|
56
|
-
}
|
|
57
|
-
const meta = buildMeta(model, seed, args);
|
|
58
|
-
if (masterText)
|
|
59
|
-
meta.text = masterText;
|
|
60
|
-
if (masterResult.grounding)
|
|
61
|
-
meta.grounding = masterResult.grounding;
|
|
62
|
-
return emit(named, args, meta);
|
|
63
89
|
});
|
|
64
90
|
}
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { textResult, McpToolError } from '@chrischall/mcp-utils';
|
|
2
|
+
import { textResult, McpToolError, readEnvVar } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
4
|
import { writeImage, resolveOutputDir, resolveVideoPath, videoMimeType } from '../images.js';
|
|
5
5
|
/** Supported output aspect ratios (Gemini image API). */
|
|
6
6
|
export const 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'];
|
|
7
7
|
/** Supported output resolutions. `512` is Flash-only (0.5K); `1K`/`2K`/`4K` work on all image models. */
|
|
8
8
|
export const IMAGE_SIZES = ['512', '1K', '2K', '4K'];
|
|
9
|
-
/**
|
|
10
|
-
* The model/aspect/size/output fields every generation tool shares. Defined once
|
|
11
|
-
* here so descriptions can't drift between `generate.ts` and `set.ts`. Spread
|
|
12
|
-
* into each tool's `inputSchema`.
|
|
13
|
-
*/
|
|
14
9
|
/**
|
|
15
10
|
* When to pick which Nano Banana model — shared by every tool's `model` param
|
|
16
11
|
* so the guidance can't drift between them.
|
|
@@ -20,6 +15,42 @@ export const MODEL_CHOICE_GUIDE = 'gemini-3.1-flash-image (Nano Banana 2) is the
|
|
|
20
15
|
'processing and consistency. gemini-3-pro-image (Nano Banana Pro) is the premium choice for the most complex ' +
|
|
21
16
|
'visual tasks — highest world knowledge, advanced localization, accurate brand consistency, precision creative control. ' +
|
|
22
17
|
'gemini-3.1-flash-lite-image (Nano Banana 2 Lite) is the fastest/cheapest for simple tasks (1K only, no search grounding).';
|
|
18
|
+
/**
|
|
19
|
+
* Per-call upstream timeout override, shared by every generation tool
|
|
20
|
+
* (spread via {@link sharedImageSchema}; `gemini_interact` imports it directly
|
|
21
|
+
* since its schema doesn't take the seed/clipboard fields).
|
|
22
|
+
*/
|
|
23
|
+
export const timeoutMsSchema = z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.positive()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Upstream request timeout in ms for this call (default: $GEMINI_TIMEOUT_MS, else 60000 — or 120000 when image_size is 4K, which routinely runs past 60s)');
|
|
29
|
+
/**
|
|
30
|
+
* Idempotency key shared by every generation tool. A repeat call carrying the
|
|
31
|
+
* same key returns the recorded result (`reused: true`) instead of billing a
|
|
32
|
+
* fresh generation — the fix for a host timeout (`-32001`) prompting a blind
|
|
33
|
+
* re-issue of a call the server actually completed.
|
|
34
|
+
*/
|
|
35
|
+
export const idempotencyKeySchema = z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Opaque idempotency key: a repeat call with the same key returns the recorded result (reused: true) instead of billing a new generation. Set it when retrying after a host timeout (-32001) to avoid a duplicate charge.');
|
|
40
|
+
/**
|
|
41
|
+
* Async escape hatch shared by every generation tool: return a `job_id`
|
|
42
|
+
* immediately so a long (Pro/4K) generation can't hit a host's fixed tools/call
|
|
43
|
+
* timeout (`-32001`). The caller polls `gemini_get_result`.
|
|
44
|
+
*/
|
|
45
|
+
export const asyncSchema = z
|
|
46
|
+
.boolean()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Run in the background and return a job_id immediately instead of the image, so a long (Pro/4K) generation cannot hit the host tools/call timeout (-32001). Poll gemini_get_result with the job_id to fetch the result (jobs are per-process and expire ~10 min after completion).');
|
|
49
|
+
/**
|
|
50
|
+
* The model/aspect/size/output fields every generation tool shares. Defined once
|
|
51
|
+
* here so descriptions can't drift between `generate.ts` and `set.ts`. Spread
|
|
52
|
+
* into each tool's `inputSchema`.
|
|
53
|
+
*/
|
|
23
54
|
export const sharedImageSchema = {
|
|
24
55
|
// Bare id only: the model is interpolated into the URL path
|
|
25
56
|
// (`/models/${model}:generateContent`), so slashes/colons/queries must be
|
|
@@ -37,6 +68,9 @@ export const sharedImageSchema = {
|
|
|
37
68
|
thinking_level: z.enum(['minimal', 'high']).optional().describe('Reasoning depth (Gemini 3 models); higher can help complex/structural edits'),
|
|
38
69
|
google_search: z.boolean().optional().describe('Ground the image in live Google Search results (current events, weather, data)'),
|
|
39
70
|
from_clipboard: z.boolean().optional().describe('Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)'),
|
|
71
|
+
timeout_ms: timeoutMsSchema,
|
|
72
|
+
idempotency_key: idempotencyKeySchema,
|
|
73
|
+
async: asyncSchema,
|
|
40
74
|
};
|
|
41
75
|
/**
|
|
42
76
|
* Turn the `video_url` / `video_path` tool args into the client's video opts.
|
|
@@ -71,6 +105,95 @@ export const videoPathSchema = z
|
|
|
71
105
|
export function pickSeed(seed) {
|
|
72
106
|
return seed ?? Math.floor(Math.random() * 2_147_483_000);
|
|
73
107
|
}
|
|
108
|
+
// Progress-heartbeat cadence while a generation is in flight. MCP hosts
|
|
109
|
+
// enforce their own tools/call timeout (surfaced as -32001) but reset it on
|
|
110
|
+
// notifications/progress, so a heartbeat lets a 4K/Pro generation outlive the
|
|
111
|
+
// host's default budget. Tunable via $GEMINI_HEARTBEAT_MS (0 disables).
|
|
112
|
+
const HEARTBEAT_DEFAULT_MS = 10_000;
|
|
113
|
+
/**
|
|
114
|
+
* Run `fn`, emitting a `notifications/progress` heartbeat while it is pending —
|
|
115
|
+
* but only when the caller asked for progress (sent a `progressToken`).
|
|
116
|
+
* Send failures are swallowed (the client may have abandoned the request);
|
|
117
|
+
* `fn`'s result/rejection passes through untouched.
|
|
118
|
+
*/
|
|
119
|
+
export async function withProgressHeartbeat(extra, message, fn) {
|
|
120
|
+
const progressToken = extra?._meta?.progressToken;
|
|
121
|
+
const send = extra?.sendNotification;
|
|
122
|
+
const env = Number(readEnvVar('GEMINI_HEARTBEAT_MS'));
|
|
123
|
+
const intervalMs = Number.isFinite(env) ? env : HEARTBEAT_DEFAULT_MS;
|
|
124
|
+
const debug = !!readEnvVar('GEMINI_DEBUG');
|
|
125
|
+
const active = progressToken !== undefined && !!send && intervalMs > 0;
|
|
126
|
+
// Diagnostics (opt-in via GEMINI_DEBUG). Goes to stderr — the only channel
|
|
127
|
+
// safe under the stdio transport, and where the host surfaces it (Claude
|
|
128
|
+
// Desktop: ~/Library/Logs/Claude/mcp-server-*.log). The active/inactive line
|
|
129
|
+
// reveals whether the host even asked for progress: a missing progressToken
|
|
130
|
+
// means the host cannot be kept alive by our heartbeat, so a long call will
|
|
131
|
+
// hit the host's own tools/call timeout no matter what we send.
|
|
132
|
+
if (debug) {
|
|
133
|
+
if (active) {
|
|
134
|
+
console.error(`[gemini-mcp] heartbeat active: progressToken present, interval=${intervalMs}ms — ${message}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const reason = progressToken === undefined
|
|
138
|
+
? 'no progressToken from host (host cannot extend its tools/call timeout; long calls will hit it)'
|
|
139
|
+
: !send
|
|
140
|
+
? 'host provided no sendNotification channel'
|
|
141
|
+
: 'heartbeat disabled (GEMINI_HEARTBEAT_MS=0)';
|
|
142
|
+
console.error(`[gemini-mcp] heartbeat inactive: ${reason} — ${message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (progressToken === undefined || !send || intervalMs <= 0)
|
|
146
|
+
return fn();
|
|
147
|
+
let ticks = 0;
|
|
148
|
+
const timer = setInterval(() => {
|
|
149
|
+
ticks++;
|
|
150
|
+
const elapsed = Math.round((ticks * intervalMs) / 1000);
|
|
151
|
+
if (debug)
|
|
152
|
+
console.error(`[gemini-mcp] heartbeat: ${message} — still working (${elapsed}s)`);
|
|
153
|
+
void send({
|
|
154
|
+
method: 'notifications/progress',
|
|
155
|
+
params: {
|
|
156
|
+
progressToken,
|
|
157
|
+
progress: ticks,
|
|
158
|
+
message: `${message} — still working (${elapsed}s)`,
|
|
159
|
+
},
|
|
160
|
+
}).catch(() => { });
|
|
161
|
+
}, intervalMs);
|
|
162
|
+
// Never hold the process open just for a heartbeat.
|
|
163
|
+
timer.unref?.();
|
|
164
|
+
try {
|
|
165
|
+
return await fn();
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
clearInterval(timer);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Some configs routinely run long enough to trip a host's tools/call timeout —
|
|
173
|
+
* Pro models, 4K output, or multi-image counts. Claude Desktop caps at ~30s,
|
|
174
|
+
* ignores our progress heartbeat, and offers no config to raise it (verified: the
|
|
175
|
+
* bundled MCP client's hard limit does not reset on notifications/progress), so
|
|
176
|
+
* such a call surfaces as `-32001 Request timed out`. Returned as
|
|
177
|
+
* `meta.timeout_risk` so a caller that *does* get the result is steered off the
|
|
178
|
+
* slow path next time and knows the image still landed on disk when a host bails.
|
|
179
|
+
* `undefined` for fast configs (no field added).
|
|
180
|
+
*/
|
|
181
|
+
export function timeoutRiskHint(opts) {
|
|
182
|
+
// Delimiter-anchored so real Pro ids (gemini-3-pro-image, nano-banana-pro)
|
|
183
|
+
// match but incidental substrings (…-prototype-…, …-flash-proxy) don't.
|
|
184
|
+
const proModel = /(?:^|[-_./])pro(?:[-_./]|$)/i.test(opts.model);
|
|
185
|
+
const fourK = opts.imageSize === '4K';
|
|
186
|
+
const multi = (opts.count ?? 1) > 1;
|
|
187
|
+
if (!proModel && !fourK && !multi)
|
|
188
|
+
return undefined;
|
|
189
|
+
const why = [proModel && 'Pro model', fourK && '4K output', multi && `count=${opts.count}`]
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join(' + ');
|
|
192
|
+
return (`Slow config (${why}) can exceed a host's tools/call timeout (e.g. Claude Desktop ~30s, error -32001). ` +
|
|
193
|
+
`If the host gives up mid-generation the image is still written to disk (and gemini_interact leaves an ` +
|
|
194
|
+
`<image>.json sidecar with the interaction id) — no rerun needed, just look in the output dir. ` +
|
|
195
|
+
`For a faster in-band result use a Flash model, drop image_size to 1K/2K, or — in Claude Code — set MCP_TOOL_TIMEOUT.`);
|
|
196
|
+
}
|
|
74
197
|
/** Build the result metadata echoed to the caller (omitting unset optionals). */
|
|
75
198
|
export function buildMeta(model, seed, opts) {
|
|
76
199
|
const meta = { model, seed };
|
|
@@ -86,7 +209,8 @@ export function buildMeta(model, seed, opts) {
|
|
|
86
209
|
* Optional `meta` is merged into the disk-path JSON result, or prepended as a
|
|
87
210
|
* text block in inline mode (only if meta has keys).
|
|
88
211
|
* Optional `onWritten` receives the absolute paths written in disk mode (never
|
|
89
|
-
* called inline) — lets a tool remember its own outputs
|
|
212
|
+
* called inline) — lets a tool remember its own outputs or write sidecar
|
|
213
|
+
* files; an async callback is awaited before the result is returned.
|
|
90
214
|
*/
|
|
91
215
|
export async function emit(named, opts, meta, onWritten) {
|
|
92
216
|
if (opts.inline) {
|
|
@@ -102,6 +226,6 @@ export async function emit(named, opts, meta, onWritten) {
|
|
|
102
226
|
const images = [];
|
|
103
227
|
for (const n of named)
|
|
104
228
|
images.push(await writeImage(dir, n.base, n.image.base64, n.image.mimeType));
|
|
105
|
-
onWritten?.(images);
|
|
229
|
+
await onWritten?.(images);
|
|
106
230
|
return textResult({ images, ...meta });
|
|
107
231
|
}
|
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.9.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.9.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>",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"test:coverage": "vitest run --coverage"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
47
|
+
"@chrischall/mcp-utils": "^0.12.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
49
|
"dotenv": "^17.4.0",
|
|
50
50
|
"zod": "^4.4.2"
|
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.9.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.9.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|