@chrischall/gemini-mcp 2.1.3 → 2.2.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 +18 -1
- package/dist/bundle.js +810 -135
- package/dist/client.js +22 -7
- package/dist/images.js +53 -3
- package/dist/library.js +3 -2
- package/dist/storage/media.js +3 -2
- package/dist/tenant.js +93 -0
- package/dist/tools/_confirm.js +52 -28
- package/dist/tools/files.js +49 -13
- package/dist/tools/generate.js +18 -8
- package/dist/tools/interact.js +11 -5
- package/dist/tools/library.js +29 -9
- package/dist/tools/music.js +9 -4
- package/dist/tools/set.js +10 -5
- package/dist/tools/video.js +9 -4
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/skills/gemini-mcp/SKILL.md +3 -2
package/dist/tools/library.js
CHANGED
|
@@ -3,7 +3,7 @@ import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
|
|
|
3
3
|
import { LIBRARY_NAME_PATTERN } from '../library.js';
|
|
4
4
|
import { acceptableUploadType } from '../upload-url.js';
|
|
5
5
|
import { withProgressHeartbeat } from './shared.js';
|
|
6
|
-
import {
|
|
6
|
+
import { confirmNote, confirmTokenParam, confirmWrite } from './_confirm.js';
|
|
7
7
|
/**
|
|
8
8
|
* Tools over the per-account reference library (src/library.ts): named
|
|
9
9
|
* characters and styles that persist across sessions with NO expiry.
|
|
@@ -111,11 +111,21 @@ export function registerLibraryTools(server, client) {
|
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
server.registerTool('gemini_delete_character', {
|
|
114
|
-
description: 'Delete a saved character from this account\'s reference library. Generation calls naming it will then fail until it is re-saved.',
|
|
114
|
+
description: 'Delete a saved character from this account\'s reference library. Generation calls naming it will then fail until it is re-saved. ' + confirmNote(),
|
|
115
115
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
116
|
-
inputSchema: z.object({ name: NAME_FIELD,
|
|
117
|
-
}, async (args) => {
|
|
118
|
-
const gate =
|
|
116
|
+
inputSchema: z.object({ name: NAME_FIELD, confirmToken: confirmTokenParam }),
|
|
117
|
+
}, async (args, extra) => {
|
|
118
|
+
const gate = await confirmWrite(extra, {
|
|
119
|
+
tool: 'gemini_delete_character',
|
|
120
|
+
action: 'character.delete',
|
|
121
|
+
message: 'Review and confirm this deletion:',
|
|
122
|
+
description: 'Delete a saved character (its reference image and description)',
|
|
123
|
+
method: 'DELETE',
|
|
124
|
+
path: `characters/${args.name}`,
|
|
125
|
+
target: args.name,
|
|
126
|
+
payload: { name: args.name },
|
|
127
|
+
confirmToken: args.confirmToken,
|
|
128
|
+
});
|
|
119
129
|
if (gate)
|
|
120
130
|
return gate;
|
|
121
131
|
const existed = await library.deleteCharacter(args.name);
|
|
@@ -170,11 +180,21 @@ export function registerLibraryTools(server, client) {
|
|
|
170
180
|
});
|
|
171
181
|
});
|
|
172
182
|
server.registerTool('gemini_delete_style', {
|
|
173
|
-
description: 'Delete a saved style preset from this account\'s reference library.',
|
|
183
|
+
description: 'Delete a saved style preset from this account\'s reference library. ' + confirmNote(),
|
|
174
184
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
175
|
-
inputSchema: z.object({ name: NAME_FIELD,
|
|
176
|
-
}, async (args) => {
|
|
177
|
-
const gate =
|
|
185
|
+
inputSchema: z.object({ name: NAME_FIELD, confirmToken: confirmTokenParam }),
|
|
186
|
+
}, async (args, extra) => {
|
|
187
|
+
const gate = await confirmWrite(extra, {
|
|
188
|
+
tool: 'gemini_delete_style',
|
|
189
|
+
action: 'style.delete',
|
|
190
|
+
message: 'Review and confirm this deletion:',
|
|
191
|
+
description: 'Delete a saved style preset',
|
|
192
|
+
method: 'DELETE',
|
|
193
|
+
path: `styles/${args.name}`,
|
|
194
|
+
target: args.name,
|
|
195
|
+
payload: { name: args.name },
|
|
196
|
+
confirmToken: args.confirmToken,
|
|
197
|
+
});
|
|
178
198
|
if (gate)
|
|
179
199
|
return gate;
|
|
180
200
|
const existed = await library.deleteStyle(args.name);
|
package/dist/tools/music.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DEFAULT_MUSIC_MODEL } from '../models.js';
|
|
|
5
5
|
import { emitMedia, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema } from './shared.js';
|
|
6
6
|
import { fingerprintRequest } from '../jobs.js';
|
|
7
7
|
import { attachCost } from '../pricing.js';
|
|
8
|
-
import {
|
|
8
|
+
import { confirmLocalInputs, confirmNote, confirmTokenParam } from './_confirm.js';
|
|
9
9
|
const MUSIC_MODELS = ['lyria-3-clip-preview', 'lyria-3.5', 'lyria-3-pro-preview'];
|
|
10
10
|
/**
|
|
11
11
|
* There is no output-format parameter, and there is no chaining.
|
|
@@ -28,7 +28,8 @@ export function registerMusicTools(server, client) {
|
|
|
28
28
|
'lyria-3-clip-preview (30s instrumental clip, default, cheapest), lyria-3.5 (full-length song with vocals) or ' +
|
|
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
|
-
'budget (or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account.'
|
|
31
|
+
'budget (or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. ' +
|
|
32
|
+
confirmNote('Local file inputs are confirmed first'),
|
|
32
33
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
33
34
|
inputSchema: z.object({
|
|
34
35
|
prompt: z.string().min(1).describe('Description of the music: mood, genre, instruments, tempo, structure, or lyrics'),
|
|
@@ -46,12 +47,16 @@ export function registerMusicTools(server, client) {
|
|
|
46
47
|
idempotency_key: idempotencyKeySchema,
|
|
47
48
|
async: asyncSchema,
|
|
48
49
|
max_wait_ms: maxWaitMsSchema,
|
|
49
|
-
|
|
50
|
+
confirmToken: confirmTokenParam,
|
|
50
51
|
}),
|
|
51
52
|
}, async (args, extra) => {
|
|
52
53
|
assertLocalInputsAvailable(client.mediaSink, args);
|
|
53
54
|
const model = args.model ?? DEFAULT_MUSIC_MODEL;
|
|
54
|
-
const
|
|
55
|
+
const { confirmToken, ...request } = args;
|
|
56
|
+
const gate = await confirmLocalInputs(extra, {
|
|
57
|
+
tool: 'gemini_music_generate', action: 'music.generate', description: 'Send local image input(s) to the Gemini Lyria API', endpoint: '/v1beta/interactions',
|
|
58
|
+
imagePaths: args.images, request, confirmToken,
|
|
59
|
+
});
|
|
55
60
|
if (gate)
|
|
56
61
|
return gate;
|
|
57
62
|
const fingerprint = fingerprintRequest('gemini_music_generate', {
|
package/dist/tools/set.js
CHANGED
|
@@ -7,11 +7,12 @@ import { emit, resolveAspectRatio, ASPECT_RATIOS, sharedImageSchema, pickSeed, b
|
|
|
7
7
|
import { fingerprintRequest } from '../jobs.js';
|
|
8
8
|
import { sumUsage } from '../usage.js';
|
|
9
9
|
import { attachCost } from '../pricing.js';
|
|
10
|
-
import {
|
|
10
|
+
import { confirmLocalInputs, confirmNote, confirmTokenParam } from './_confirm.js';
|
|
11
11
|
export function registerSetTools(server, client) {
|
|
12
12
|
server.registerTool('gemini_image_set', {
|
|
13
13
|
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). ' +
|
|
14
|
-
'Scene generations run in parallel (reference_mode "master", the default). On the hosted connector: saved `characters` and a saved `style` can seed the whole set by name, multi-image results include a `bundle_url` zip of every image (one curl instead of N), and `max_wait_ms` returns a pollable job handle if the batch runs long.'
|
|
14
|
+
'Scene generations run in parallel (reference_mode "master", the default). On the hosted connector: saved `characters` and a saved `style` can seed the whole set by name, multi-image results include a `bundle_url` zip of every image (one curl instead of N), and `max_wait_ms` returns a pollable job handle if the batch runs long. ' +
|
|
15
|
+
confirmNote('Local file inputs are confirmed first'),
|
|
15
16
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
16
17
|
inputSchema: z.object({
|
|
17
18
|
master_prompt: z.string().min(1).describe('Prompt for the master/reference image'),
|
|
@@ -26,7 +27,7 @@ export function registerSetTools(server, client) {
|
|
|
26
27
|
characters: charactersSchema,
|
|
27
28
|
style: styleSchema,
|
|
28
29
|
master_images_base64: imagesBase64Schema('Reference images for the master generation', 'master_images'),
|
|
29
|
-
|
|
30
|
+
confirmToken: confirmTokenParam,
|
|
30
31
|
...sharedImageSchema,
|
|
31
32
|
}),
|
|
32
33
|
}, async (args, extra) => {
|
|
@@ -36,8 +37,12 @@ export function registerSetTools(server, client) {
|
|
|
36
37
|
throw new McpToolError('Provide exactly one of `scenes` or `count`.');
|
|
37
38
|
}
|
|
38
39
|
// Confirm-gate local file inputs: only `master_images` (paths) can read a
|
|
39
|
-
// local file; base64 references pass through ungated.
|
|
40
|
-
const
|
|
40
|
+
// local file; base64 references pass through ungated. The preview makes NO API call.
|
|
41
|
+
const { confirmToken, ...request } = args;
|
|
42
|
+
const gate = await confirmLocalInputs(extra, {
|
|
43
|
+
tool: 'gemini_image_set', action: 'image.set', description: 'Send local master image input(s) to the Gemini API', endpoint: '/v1beta/models/{model}:generateContent',
|
|
44
|
+
imagePaths: args.master_images, request, confirmToken,
|
|
45
|
+
});
|
|
41
46
|
if (gate)
|
|
42
47
|
return gate;
|
|
43
48
|
const model = resolveModel(args.model, readEnvVar('GEMINI_IMAGE_MODEL'));
|
package/dist/tools/video.js
CHANGED
|
@@ -6,7 +6,7 @@ import { DEFAULT_VIDEO_MODEL } from '../models.js';
|
|
|
6
6
|
import { emitMedia, reportShape, resolveAspectRatio, orientationSchema, timeoutMsSchema, idempotencyKeySchema, asyncSchema, maxWaitMsSchema, withProgressHeartbeat, assertLocalInputsAvailable, imagesUrlSchema, imagesFileUrisSchema, imagesBase64Schema } from './shared.js';
|
|
7
7
|
import { fingerprintRequest } from '../jobs.js';
|
|
8
8
|
import { attachCost } from '../pricing.js';
|
|
9
|
-
import {
|
|
9
|
+
import { confirmLocalInputs, confirmNote, confirmTokenParam } from './_confirm.js';
|
|
10
10
|
/** omni's own aspect-ratio enum — NOT the image tools' ASPECT_RATIOS. */
|
|
11
11
|
const VIDEO_ASPECT_RATIOS = ['16:9', '9:16'];
|
|
12
12
|
const VIDEO_TASKS = ['text_to_video', 'image_to_video', 'reference_to_video', 'edit', 'extend'];
|
|
@@ -36,7 +36,8 @@ export function registerVideoTools(server, client) {
|
|
|
36
36
|
'or continue a prior video (task: "edit" or "extend" + previous_interaction_id / continue_last; extensions ' +
|
|
37
37
|
'add ~3-10s each, to ~40s total). Cost scales with `resolution` — draft at 360p, keep at 1080p/4k. ' +
|
|
38
38
|
'Output is written to disk as MP4 (video has no inline MCP block). Video runs long — give it a `max_wait_ms` budget ' +
|
|
39
|
-
'(or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account.'
|
|
39
|
+
'(or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. ' +
|
|
40
|
+
confirmNote('Local file inputs are confirmed first'),
|
|
40
41
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
41
42
|
inputSchema: z.object({
|
|
42
43
|
prompt: z.string().min(1).describe('Description of the video to generate (or the edit instruction when task=edit)'),
|
|
@@ -64,7 +65,7 @@ export function registerVideoTools(server, client) {
|
|
|
64
65
|
idempotency_key: idempotencyKeySchema,
|
|
65
66
|
async: asyncSchema,
|
|
66
67
|
max_wait_ms: maxWaitMsSchema,
|
|
67
|
-
|
|
68
|
+
confirmToken: confirmTokenParam,
|
|
68
69
|
}),
|
|
69
70
|
}, async (args, extra) => {
|
|
70
71
|
assertLocalInputsAvailable(client.mediaSink, args);
|
|
@@ -79,7 +80,11 @@ export function registerVideoTools(server, client) {
|
|
|
79
80
|
}
|
|
80
81
|
// Confirm-gate local file inputs (a prompt-injected `images` path could
|
|
81
82
|
// exfiltrate a local file); base64 / clipboard pass through ungated.
|
|
82
|
-
const
|
|
83
|
+
const { confirmToken, ...request } = args;
|
|
84
|
+
const gate = await confirmLocalInputs(extra, {
|
|
85
|
+
tool: 'gemini_video_generate', action: 'video.generate', description: 'Send local image input(s) to the Gemini omni API', endpoint: '/v1beta/interactions',
|
|
86
|
+
imagePaths: args.images, request: { ...request, previous_interaction_id: previousInteractionId }, confirmToken,
|
|
87
|
+
});
|
|
83
88
|
if (gate)
|
|
84
89
|
return gate;
|
|
85
90
|
const model = args.model?.trim() || DEFAULT_VIDEO_MODEL;
|
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.
|
|
2
|
+
export const VERSION = '2.2.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/gemini-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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>",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"typecheck": "tsc -p tsconfig.test.json"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@chrischall/mcp-utils": "^2.
|
|
49
|
+
"@chrischall/mcp-utils": "^2.6.0",
|
|
50
50
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
51
51
|
"dotenv": "^17.4.0",
|
|
52
52
|
"zod": "^4.6.5"
|
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.
|
|
9
|
+
"version": "2.2.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.2.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -125,9 +125,9 @@ not an error.
|
|
|
125
125
|
### Files API
|
|
126
126
|
| Tool | Description |
|
|
127
127
|
|------|-------------|
|
|
128
|
-
| `gemini_upload_file(url? \| data_base64? \| path?, mime_type?, display_name?,
|
|
128
|
+
| `gemini_upload_file(url? \| data_base64? \| path?, mime_type?, display_name?, confirmToken?)` | Upload once, get a reusable `files/<id>`. Exactly one source. `url` is fetched by the server (image/video/audio, ≤100MB); `path` is stdio-only and confirmed first (see Notes); `data_base64` is the last resort |
|
|
129
129
|
| `gemini_list_files(page_size?)` | List current uploads with MIME types and expiry |
|
|
130
|
-
| `gemini_delete_file(file_uri,
|
|
130
|
+
| `gemini_delete_file(file_uri, confirmToken?)` | Delete an upload before its ~48h expiry (confirmed first — see Notes) |
|
|
131
131
|
|
|
132
132
|
On the **hosted hosted deployment** there is also `POST /upload`, behind the same OAuth token as
|
|
133
133
|
`/mcp` — the zero-base64 path for an agent with a shell:
|
|
@@ -307,6 +307,7 @@ Condensed from Google's official Nano Banana prompting guide. Core rule:
|
|
|
307
307
|
|
|
308
308
|
## Notes
|
|
309
309
|
|
|
310
|
+
- **Confirmations.** Local file inputs (`images`, `master_images`, `video_path`, `gemini_upload_file`'s `path`) and every delete are confirmed before anything is sent: a confirmation prompt where the client supports one; otherwise the first call does nothing and returns `status: "confirmation-required"` with a `preview` (resolved paths, MIME types, sizes — or the method/path being deleted) and a `confirmToken`. Show the preview to the user, and only after they approve call again with the **same arguments** plus `confirmToken`. The token is single-use, expires (default 10 min), and is bound to those exact arguments — change anything and the call is refused (`DRAFT_CHANGED`, with a fresh preview and token). `MCP_CONFIRM_MODE` on the server picks `ask-user` (default), `auto` (the model may approve after reviewing the preview) or `refuse`. Text prompts, URLs, `files/` references and base64 inputs are not gated.
|
|
310
311
|
- **Input images** accept either file **paths** (`images` / `master_images`) or **base64/data-URI values** (`images_base64` / `master_images_base64`).
|
|
311
312
|
- **`seed`** makes a result reproducible; it's echoed in the result metadata (a random one is chosen + echoed when omitted). `count>1` uses `seed, seed+1, …` so the images differ. Determinism isn't fully guaranteed by the model.
|
|
312
313
|
- **`filename`/`basename`** set the output name (extension stripped); names never overwrite (a `-2`, `-3` suffix is added). The result echoes the absolute path(s), `model`, `seed`, and aspect/size.
|