@chrischall/gemini-mcp 0.7.1 → 0.8.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 +12 -0
- package/dist/bundle.js +338 -87
- package/dist/client.js +55 -29
- package/dist/images.js +46 -3
- package/dist/tools/_confirm.js +39 -0
- package/dist/tools/generate.js +48 -28
- package/dist/tools/interact.js +86 -11
- package/dist/tools/set.js +39 -27
- package/dist/tools/shared.js +62 -7
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
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,22 @@ 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
|
+
* The model/aspect/size/output fields every generation tool shares. Defined once
|
|
31
|
+
* here so descriptions can't drift between `generate.ts` and `set.ts`. Spread
|
|
32
|
+
* into each tool's `inputSchema`.
|
|
33
|
+
*/
|
|
23
34
|
export const sharedImageSchema = {
|
|
24
35
|
// Bare id only: the model is interpolated into the URL path
|
|
25
36
|
// (`/models/${model}:generateContent`), so slashes/colons/queries must be
|
|
@@ -37,6 +48,7 @@ export const sharedImageSchema = {
|
|
|
37
48
|
thinking_level: z.enum(['minimal', 'high']).optional().describe('Reasoning depth (Gemini 3 models); higher can help complex/structural edits'),
|
|
38
49
|
google_search: z.boolean().optional().describe('Ground the image in live Google Search results (current events, weather, data)'),
|
|
39
50
|
from_clipboard: z.boolean().optional().describe('Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)'),
|
|
51
|
+
timeout_ms: timeoutMsSchema,
|
|
40
52
|
};
|
|
41
53
|
/**
|
|
42
54
|
* Turn the `video_url` / `video_path` tool args into the client's video opts.
|
|
@@ -71,6 +83,45 @@ export const videoPathSchema = z
|
|
|
71
83
|
export function pickSeed(seed) {
|
|
72
84
|
return seed ?? Math.floor(Math.random() * 2_147_483_000);
|
|
73
85
|
}
|
|
86
|
+
// Progress-heartbeat cadence while a generation is in flight. MCP hosts
|
|
87
|
+
// enforce their own tools/call timeout (surfaced as -32001) but reset it on
|
|
88
|
+
// notifications/progress, so a heartbeat lets a 4K/Pro generation outlive the
|
|
89
|
+
// host's default budget. Tunable via $GEMINI_HEARTBEAT_MS (0 disables).
|
|
90
|
+
const HEARTBEAT_DEFAULT_MS = 10_000;
|
|
91
|
+
/**
|
|
92
|
+
* Run `fn`, emitting a `notifications/progress` heartbeat while it is pending —
|
|
93
|
+
* but only when the caller asked for progress (sent a `progressToken`).
|
|
94
|
+
* Send failures are swallowed (the client may have abandoned the request);
|
|
95
|
+
* `fn`'s result/rejection passes through untouched.
|
|
96
|
+
*/
|
|
97
|
+
export async function withProgressHeartbeat(extra, message, fn) {
|
|
98
|
+
const progressToken = extra?._meta?.progressToken;
|
|
99
|
+
const send = extra?.sendNotification;
|
|
100
|
+
const env = Number(readEnvVar('GEMINI_HEARTBEAT_MS'));
|
|
101
|
+
const intervalMs = Number.isFinite(env) ? env : HEARTBEAT_DEFAULT_MS;
|
|
102
|
+
if (progressToken === undefined || !send || intervalMs <= 0)
|
|
103
|
+
return fn();
|
|
104
|
+
let ticks = 0;
|
|
105
|
+
const timer = setInterval(() => {
|
|
106
|
+
ticks++;
|
|
107
|
+
void send({
|
|
108
|
+
method: 'notifications/progress',
|
|
109
|
+
params: {
|
|
110
|
+
progressToken,
|
|
111
|
+
progress: ticks,
|
|
112
|
+
message: `${message} — still working (${Math.round((ticks * intervalMs) / 1000)}s)`,
|
|
113
|
+
},
|
|
114
|
+
}).catch(() => { });
|
|
115
|
+
}, intervalMs);
|
|
116
|
+
// Never hold the process open just for a heartbeat.
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
try {
|
|
119
|
+
return await fn();
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
clearInterval(timer);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
74
125
|
/** Build the result metadata echoed to the caller (omitting unset optionals). */
|
|
75
126
|
export function buildMeta(model, seed, opts) {
|
|
76
127
|
const meta = { model, seed };
|
|
@@ -85,8 +136,11 @@ export function buildMeta(model, seed, opts) {
|
|
|
85
136
|
* return their paths as a text result. `inline` wins when true.
|
|
86
137
|
* Optional `meta` is merged into the disk-path JSON result, or prepended as a
|
|
87
138
|
* text block in inline mode (only if meta has keys).
|
|
139
|
+
* Optional `onWritten` receives the absolute paths written in disk mode (never
|
|
140
|
+
* called inline) — lets a tool remember its own outputs or write sidecar
|
|
141
|
+
* files; an async callback is awaited before the result is returned.
|
|
88
142
|
*/
|
|
89
|
-
export async function emit(named, opts, meta) {
|
|
143
|
+
export async function emit(named, opts, meta, onWritten) {
|
|
90
144
|
if (opts.inline) {
|
|
91
145
|
const imageBlocks = named.map((n) => ({ type: 'image', data: n.image.base64, mimeType: n.image.mimeType }));
|
|
92
146
|
if (meta && Object.keys(meta).length > 0) {
|
|
@@ -100,5 +154,6 @@ export async function emit(named, opts, meta) {
|
|
|
100
154
|
const images = [];
|
|
101
155
|
for (const n of named)
|
|
102
156
|
images.push(await writeImage(dir, n.base, n.image.base64, n.image.mimeType));
|
|
157
|
+
await onWritten?.(images);
|
|
103
158
|
return textResult({ images, ...meta });
|
|
104
159
|
}
|
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.8.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.8.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.8.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.8.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|