@chrischall/gemini-mcp 1.11.1 → 1.13.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/dist/bundle.js +619 -150
- package/dist/client.js +11 -0
- package/dist/jobs.js +2 -2
- package/dist/registrars.js +2 -0
- package/dist/tools/_confirm.js +2 -2
- package/dist/tools/files.js +6 -6
- package/dist/tools/healthcheck.js +29 -0
- package/dist/tools/jobs.js +2 -2
- package/dist/tools/library.js +7 -7
- package/dist/tools/models.js +2 -2
- package/dist/tools/shared.js +2 -2
- package/dist/tools/uploads.js +2 -2
- package/dist/tools/usage.js +2 -2
- package/dist/version.js +1 -1
- package/mint.yaml +6 -0
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/client.js
CHANGED
|
@@ -287,6 +287,17 @@ export class GeminiClient {
|
|
|
287
287
|
* That is an invisible, silent failure (the key just stops working); not
|
|
288
288
|
* depending on the order removes the hazard instead of documenting it.
|
|
289
289
|
*/
|
|
290
|
+
/**
|
|
291
|
+
* Which source supplies the API key, for `gemini_healthcheck` — a LABEL or
|
|
292
|
+
* `null`, never the key. Mirrors `requireKey`'s resolution order exactly, so
|
|
293
|
+
* the healthcheck reports the same source the real tools will use, but does
|
|
294
|
+
* NOT throw: "no key" is an answer here rather than an error.
|
|
295
|
+
*/
|
|
296
|
+
describeCredential() {
|
|
297
|
+
if (this.explicitApiKey)
|
|
298
|
+
return { source: 'connector-session' };
|
|
299
|
+
return { source: readEnvVar('GEMINI_API_KEY') ? 'env' : null };
|
|
300
|
+
}
|
|
290
301
|
requireKey() {
|
|
291
302
|
const key = this.explicitApiKey || readEnvVar('GEMINI_API_KEY');
|
|
292
303
|
if (!key) {
|
package/dist/jobs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { downloadFilename } from './media-name.js';
|
|
4
4
|
import { HEARTBEAT_MS } from './job-store.js';
|
|
5
5
|
import { annotateReusedUsage } from './usage.js';
|
|
@@ -151,7 +151,7 @@ function annotateReused(result, jobId) {
|
|
|
151
151
|
}
|
|
152
152
|
/** Immediate handle for an async call — the caller polls `gemini_get_result`. */
|
|
153
153
|
function jobHandle(jobId, status, durable = false) {
|
|
154
|
-
return
|
|
154
|
+
return minifiedResult({
|
|
155
155
|
job_id: jobId,
|
|
156
156
|
status,
|
|
157
157
|
hint: durable
|
package/dist/registrars.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { surfaceToolErrors } from './errors.js';
|
|
13
13
|
import { registerModelTools } from './tools/models.js';
|
|
14
|
+
import { registerHealthcheckTools } from './tools/healthcheck.js';
|
|
14
15
|
import { registerGenerateTools } from './tools/generate.js';
|
|
15
16
|
import { registerSetTools } from './tools/set.js';
|
|
16
17
|
import { registerInteractTools } from './tools/interact.js';
|
|
@@ -39,6 +40,7 @@ export const reporting = (register) => (server, deps) => register(surfaceToolErr
|
|
|
39
40
|
*/
|
|
40
41
|
export const TOOL_REGISTRARS = [
|
|
41
42
|
registerModelTools,
|
|
43
|
+
registerHealthcheckTools,
|
|
42
44
|
registerGenerateTools,
|
|
43
45
|
registerSetTools,
|
|
44
46
|
registerInteractTools,
|
package/dist/tools/_confirm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { minifiedResult, schemaConfirm } from '@chrischall/mcp-utils';
|
|
2
2
|
import { previewImageInput, previewVideoInput } from '../images.js';
|
|
3
3
|
export { schemaConfirm };
|
|
4
4
|
/**
|
|
@@ -9,7 +9,7 @@ export { schemaConfirm };
|
|
|
9
9
|
export function previewUnlessConfirmed(confirm, action, method, path, body) {
|
|
10
10
|
if (confirm === true)
|
|
11
11
|
return null;
|
|
12
|
-
return
|
|
12
|
+
return minifiedResult({
|
|
13
13
|
dryRun: true,
|
|
14
14
|
action,
|
|
15
15
|
method,
|
package/dist/tools/files.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { McpToolError,
|
|
2
|
+
import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { withProgressHeartbeat } from './shared.js';
|
|
4
4
|
import { base64ToBytes, wholeMb } from '../bytes.js';
|
|
5
5
|
import { fileNameFromUrl } from '../fetch-image.js';
|
|
@@ -47,7 +47,7 @@ const RETENTION_NOTE = 'Gemini retains uploaded files for ~48h. Until then this
|
|
|
47
47
|
'(images_file_uris on gemini_image_generate / _edit / _set / gemini_interact, or video_url) without re-sending the bytes.';
|
|
48
48
|
/** Shape returned by every upload path. */
|
|
49
49
|
function uploadResult(file) {
|
|
50
|
-
return
|
|
50
|
+
return minifiedResult({
|
|
51
51
|
// The `files/<id>` form is what every tool parameter documents, so it leads.
|
|
52
52
|
file_uri: file.name,
|
|
53
53
|
// The absolute uri, for callers that would rather pass it through verbatim.
|
|
@@ -178,7 +178,7 @@ export function registerFileTools(server, client) {
|
|
|
178
178
|
},
|
|
179
179
|
}, async (args) => {
|
|
180
180
|
const files = await client.listFiles(args.page_size ?? 100);
|
|
181
|
-
return
|
|
181
|
+
return minifiedResult({
|
|
182
182
|
count: files.length,
|
|
183
183
|
files: files.map((f) => ({
|
|
184
184
|
file_uri: f.name,
|
|
@@ -220,7 +220,7 @@ export function registerFileTools(server, client) {
|
|
|
220
220
|
// Remediation goes in the MESSAGE: MCP serialization drops hints.
|
|
221
221
|
throw new McpToolError(`No stored media for r2_key "${args.r2_key}" — it has probably passed its retention window. Re-run the generation to get a new one.`, { hint: 'Objects are swept on a retention schedule (MEDIA_TTL_DAYS, default 7 days).' });
|
|
222
222
|
}
|
|
223
|
-
return
|
|
223
|
+
return minifiedResult({
|
|
224
224
|
url: fresh.ref,
|
|
225
225
|
r2_key: fresh.key,
|
|
226
226
|
...(fresh.expiresAt ? { expires_at: fresh.expiresAt } : {}),
|
|
@@ -258,7 +258,7 @@ export function registerFileTools(server, client) {
|
|
|
258
258
|
? await sink.listRecentPage({ limit, sinceDay: args.since_day })
|
|
259
259
|
: { media: await sink.listRecent({ limit, sinceDay: args.since_day }), truncated: false };
|
|
260
260
|
const found = page.media;
|
|
261
|
-
return
|
|
261
|
+
return minifiedResult({
|
|
262
262
|
count: found.length,
|
|
263
263
|
// Never let a bounded walk pass for the whole picture: concluding an
|
|
264
264
|
// image is gone is what makes someone pay to generate it twice.
|
|
@@ -293,6 +293,6 @@ export function registerFileTools(server, client) {
|
|
|
293
293
|
if (gate)
|
|
294
294
|
return gate;
|
|
295
295
|
await client.deleteFile(args.file_uri);
|
|
296
|
-
return
|
|
296
|
+
return minifiedResult({ deleted: args.file_uri });
|
|
297
297
|
});
|
|
298
298
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { registerCredentialHealthcheckTool } from '@chrischall/mcp-utils/healthcheck';
|
|
2
|
+
/**
|
|
3
|
+
* Register `gemini_healthcheck` — resolves the API key the way real tools do,
|
|
4
|
+
* then makes one authenticated call to `/models`.
|
|
5
|
+
*
|
|
6
|
+
* Gemini has no browser bridge, so health here is entirely about the
|
|
7
|
+
* credential: whether one resolved, and whether Google still accepts it. Those
|
|
8
|
+
* are different problems with different fixes, and without this both surface
|
|
9
|
+
* as the same opaque tool error.
|
|
10
|
+
*
|
|
11
|
+
* `/models` is the probe because it is the cheapest endpoint that REQUIRES a
|
|
12
|
+
* key — an unauthenticated one would pass while the key was revoked, which is
|
|
13
|
+
* the failure most worth catching. It is also read-only and free, so a
|
|
14
|
+
* healthcheck never costs a generation.
|
|
15
|
+
*/
|
|
16
|
+
export function registerHealthcheckTools(server, client) {
|
|
17
|
+
registerCredentialHealthcheckTool({
|
|
18
|
+
server,
|
|
19
|
+
prefix: 'gemini',
|
|
20
|
+
hostLabel: 'generativelanguage.googleapis.com',
|
|
21
|
+
probePath: '/v1beta/models',
|
|
22
|
+
resolveCredential: async () => client.describeCredential(),
|
|
23
|
+
probeFn: () => client.listModels(),
|
|
24
|
+
hints: {
|
|
25
|
+
no_credential: 'No Gemini API key resolved. Set GEMINI_API_KEY (https://aistudio.google.com/apikey), or reconnect the connector so it supplies one.',
|
|
26
|
+
credential_rejected: 'Google rejected the API key. It is present but no longer valid — most often revoked, or restricted to different APIs/referrers. Issue a new key at https://aistudio.google.com/apikey.',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
package/dist/tools/jobs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { McpToolError,
|
|
2
|
+
import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { TERMINAL_INTERACTION_STATUSES } from '../client.js';
|
|
4
4
|
import { emitMedia } from './shared.js';
|
|
5
5
|
import { slugify } from '../images.js';
|
|
@@ -128,7 +128,7 @@ function recoveryNote(recordedError) {
|
|
|
128
128
|
}
|
|
129
129
|
/** The generation outlived its executor and is not finished yet. */
|
|
130
130
|
function stillRunning(jobId, interactionId) {
|
|
131
|
-
return
|
|
131
|
+
return minifiedResult({
|
|
132
132
|
job_id: jobId,
|
|
133
133
|
status: 'running',
|
|
134
134
|
interaction_id: interactionId,
|
package/dist/tools/library.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { McpToolError,
|
|
2
|
+
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';
|
|
@@ -79,7 +79,7 @@ export function registerLibraryTools(server, client) {
|
|
|
79
79
|
}, async (args, extra) => {
|
|
80
80
|
const image = (await withProgressHeartbeat(extra, 'Loading the reference image', () => resolveLibraryImage(client, args, true)));
|
|
81
81
|
const record = await library.saveCharacter({ name: args.name, description: args.description, image });
|
|
82
|
-
return
|
|
82
|
+
return minifiedResult({
|
|
83
83
|
saved: 'character',
|
|
84
84
|
name: record.name,
|
|
85
85
|
description: record.description,
|
|
@@ -96,7 +96,7 @@ export function registerLibraryTools(server, client) {
|
|
|
96
96
|
inputSchema: {},
|
|
97
97
|
}, async () => {
|
|
98
98
|
const characters = await library.listCharacters();
|
|
99
|
-
return
|
|
99
|
+
return minifiedResult({
|
|
100
100
|
count: characters.length,
|
|
101
101
|
characters: characters.map((c) => ({
|
|
102
102
|
name: c.name,
|
|
@@ -122,7 +122,7 @@ export function registerLibraryTools(server, client) {
|
|
|
122
122
|
if (!existed) {
|
|
123
123
|
throw new McpToolError(`No saved character "${args.name}". List what exists with gemini_list_characters.`);
|
|
124
124
|
}
|
|
125
|
-
return
|
|
125
|
+
return minifiedResult({ deleted: 'character', name: args.name });
|
|
126
126
|
});
|
|
127
127
|
server.registerTool('gemini_save_style', {
|
|
128
128
|
description: 'Save a named style preset to this account\'s persistent reference library: a reusable prompt fragment (and ' +
|
|
@@ -141,7 +141,7 @@ export function registerLibraryTools(server, client) {
|
|
|
141
141
|
}, async (args, extra) => {
|
|
142
142
|
const image = await withProgressHeartbeat(extra, 'Loading the style reference image', () => resolveLibraryImage(client, args, false));
|
|
143
143
|
const record = await library.saveStyle({ name: args.name, promptFragment: args.prompt_fragment, image });
|
|
144
|
-
return
|
|
144
|
+
return minifiedResult({
|
|
145
145
|
saved: 'style',
|
|
146
146
|
name: record.name,
|
|
147
147
|
prompt_fragment: record.prompt_fragment,
|
|
@@ -157,7 +157,7 @@ export function registerLibraryTools(server, client) {
|
|
|
157
157
|
inputSchema: {},
|
|
158
158
|
}, async () => {
|
|
159
159
|
const styles = await library.listStyles();
|
|
160
|
-
return
|
|
160
|
+
return minifiedResult({
|
|
161
161
|
count: styles.length,
|
|
162
162
|
styles: styles.map((s) => ({
|
|
163
163
|
name: s.name,
|
|
@@ -181,6 +181,6 @@ export function registerLibraryTools(server, client) {
|
|
|
181
181
|
if (!existed) {
|
|
182
182
|
throw new McpToolError(`No saved style "${args.name}". List what exists with gemini_list_styles.`);
|
|
183
183
|
}
|
|
184
|
-
return
|
|
184
|
+
return minifiedResult({ deleted: 'style', name: args.name });
|
|
185
185
|
});
|
|
186
186
|
}
|
package/dist/tools/models.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { minifiedResult } from '@chrischall/mcp-utils';
|
|
2
2
|
export function registerModelTools(server, client) {
|
|
3
3
|
server.registerTool('gemini_list_models', {
|
|
4
4
|
description: 'List the Gemini image-generation models available to your API key (Nano Banana / Nano Banana Pro family), and the current default model.',
|
|
5
5
|
annotations: { readOnlyHint: true },
|
|
6
6
|
}, async () => {
|
|
7
7
|
const models = await client.listModels();
|
|
8
|
-
return
|
|
8
|
+
return minifiedResult({ default: client.defaultModel(), models });
|
|
9
9
|
});
|
|
10
10
|
}
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { McpToolError, minifiedResult, readEnvVar } from '@chrischall/mcp-utils';
|
|
3
3
|
import { resolveVideoPath, videoMimeType } from '../images.js';
|
|
4
4
|
import { IMAGE_URL_MAX_BYTES, IMAGE_INLINE_MAX_BYTES } from '../fetch-image.js';
|
|
5
5
|
import { bytesToBase64, base64ToBytes, wholeMb } from '../bytes.js';
|
|
@@ -535,7 +535,7 @@ export async function emitMedia(named, kind, opts, meta, onWritten) {
|
|
|
535
535
|
}
|
|
536
536
|
: {};
|
|
537
537
|
const storage = sink.persistsFiles ? {} : { storage: sink.kind, storage_note: storageNote };
|
|
538
|
-
return
|
|
538
|
+
return minifiedResult({ [`${kind}s`]: refs, ...media, ...unavailable, ...note, ...storage, ...meta });
|
|
539
539
|
}
|
|
540
540
|
/**
|
|
541
541
|
* Image convenience wrapper over {@link emitMedia} — preserves the original
|
package/dist/tools/uploads.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { UPLOAD_MAX_BYTES, RASTER_IMAGE_TYPE_PATTERN } from '../upload-url.js';
|
|
4
4
|
import { wholeMb } from '../bytes.js';
|
|
5
5
|
import { step } from '../errors.js';
|
|
@@ -71,7 +71,7 @@ export function registerUploadUrlTools(server, client) {
|
|
|
71
71
|
// between them a mint failure can no longer reach the client as a bare
|
|
72
72
|
// "Error occurred during tool execution".
|
|
73
73
|
const minted = await step(`minting a signed upload URL for ${args.filename} (${args.content_type})`, () => minter.mint(args.filename, args.content_type));
|
|
74
|
-
return
|
|
74
|
+
return minifiedResult({
|
|
75
75
|
upload_url: minted.url,
|
|
76
76
|
method: 'PUT',
|
|
77
77
|
r2_key: minted.key,
|
package/dist/tools/usage.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { minifiedResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
3
|
import { PRICED_AT } from '../pricing.js';
|
|
4
4
|
/**
|
|
5
5
|
* What this session has spent, in tokens.
|
|
@@ -51,7 +51,7 @@ export function registerUsageTools(server, client) {
|
|
|
51
51
|
client.session.costUsd = undefined;
|
|
52
52
|
client.session.pricedCalls = 0;
|
|
53
53
|
}
|
|
54
|
-
return
|
|
54
|
+
return minifiedResult({
|
|
55
55
|
usage: usage ?? null,
|
|
56
56
|
billed_calls: calls,
|
|
57
57
|
estimated_cost_usd: costUsd ?? null,
|
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 = '1.
|
|
2
|
+
export const VERSION = '1.13.0'; // x-release-please-version
|
package/mint.yaml
CHANGED
|
@@ -38,6 +38,12 @@ env:
|
|
|
38
38
|
How often, in milliseconds, to emit a progress notification while a
|
|
39
39
|
generation is in flight. Hosts reset their tools/call timeout on progress,
|
|
40
40
|
so this lets a long 4K/Pro generation outlive the default budget.
|
|
41
|
+
- name: GEMINI_RATE_CARD
|
|
42
|
+
required: false
|
|
43
|
+
help: >-
|
|
44
|
+
JSON overriding or extending the built-in pricing card, so a stale rate is
|
|
45
|
+
yours to fix in your own environment rather than waiting on a release.
|
|
46
|
+
Anything absent from the card prices as unknown rather than zero.
|
|
41
47
|
egress:
|
|
42
48
|
allow:
|
|
43
49
|
# Only hosts the SERVER process actually fetches. Hosts that appear
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/gemini-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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": "^0.
|
|
49
|
+
"@chrischall/mcp-utils": "^0.23.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
51
|
"dotenv": "^17.4.0",
|
|
52
52
|
"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": "1.
|
|
9
|
+
"version": "1.13.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.13.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|