@chrischall/gemini-mcp 1.14.0 → 1.14.1
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 +102 -91
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/server.json +2 -2
|
@@ -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": "1.14.
|
|
10
|
+
"version": "1.14.1"
|
|
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": "1.14.
|
|
18
|
+
"version": "1.14.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-mcp",
|
|
3
3
|
"displayName": "Gemini Images",
|
|
4
|
-
"version": "1.14.
|
|
4
|
+
"version": "1.14.1",
|
|
5
5
|
"description": "MCP server for Google Gemini image generation and editing — generate and edit images via the Gemini API",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/dist/bundle.js
CHANGED
|
@@ -37685,7 +37685,7 @@ async function loadStdioDotenv() {
|
|
|
37685
37685
|
}
|
|
37686
37686
|
|
|
37687
37687
|
// src/version.ts
|
|
37688
|
-
var VERSION = "1.14.
|
|
37688
|
+
var VERSION = "1.14.1";
|
|
37689
37689
|
|
|
37690
37690
|
// src/errors.ts
|
|
37691
37691
|
init_dist();
|
|
@@ -37773,6 +37773,8 @@ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
|
37773
37773
|
"ok",
|
|
37774
37774
|
"no_credential",
|
|
37775
37775
|
"credential_rejected",
|
|
37776
|
+
"session_expired",
|
|
37777
|
+
"verification_pending",
|
|
37776
37778
|
"timeout",
|
|
37777
37779
|
"http",
|
|
37778
37780
|
"transport",
|
|
@@ -37789,6 +37791,10 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
37789
37791
|
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
37790
37792
|
case "credential_rejected":
|
|
37791
37793
|
return `${hostLabel} rejected the credential from '${source}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
37794
|
+
case "session_expired":
|
|
37795
|
+
return `The credential from '${source}' is configured, but no session is live \u2014 ${hostLabel} served a sign-in page rather than the data. Sign in again; a cookie-session portal expires these on its own, so this recurs between uses.`;
|
|
37796
|
+
case "verification_pending":
|
|
37797
|
+
return `${hostLabel} is holding the sign-in on a second factor rather than refusing it. Supply the verification code the ACCOUNT HOLDER received \u2014 the credential itself is not the problem, so changing it will not help.`;
|
|
37792
37798
|
case "timeout":
|
|
37793
37799
|
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
37794
37800
|
case "http":
|
|
@@ -37799,12 +37805,102 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
37799
37805
|
return `Unexpected failure \u2014 see error.message.`;
|
|
37800
37806
|
}
|
|
37801
37807
|
}
|
|
37808
|
+
function credentialHealthcheckDescription(hostLabel) {
|
|
37809
|
+
return `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Read-only; never returns the credential itself.`;
|
|
37810
|
+
}
|
|
37811
|
+
async function runCredentialHealthcheck(args) {
|
|
37812
|
+
const { prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
|
|
37813
|
+
const probeUrl = probePath ? `https://${hostLabel}${probePath.startsWith("/") ? "" : "/"}${probePath}` : void 0;
|
|
37814
|
+
let probeStarted = 0;
|
|
37815
|
+
let state;
|
|
37816
|
+
try {
|
|
37817
|
+
state = await resolveCredential();
|
|
37818
|
+
} catch (e) {
|
|
37819
|
+
const classified = classifyThrown?.(e);
|
|
37820
|
+
const result2 = {
|
|
37821
|
+
ok: false,
|
|
37822
|
+
// Still false, and still no source: a classification explains WHY
|
|
37823
|
+
// nothing resolved, it does not invent a credential that did.
|
|
37824
|
+
credential: { source: null, resolved: false },
|
|
37825
|
+
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
37826
|
+
probe: { elapsed_ms: 0 },
|
|
37827
|
+
error: {
|
|
37828
|
+
kind: classified?.kind ?? "no_credential",
|
|
37829
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
37830
|
+
...classified?.detail !== void 0 ? { detail: classified.detail } : {}
|
|
37831
|
+
},
|
|
37832
|
+
// The hint must follow the KIND beside it. Falling back to
|
|
37833
|
+
// `no_credential`'s copy under a classified kind would state a cause
|
|
37834
|
+
// the kind contradicts — the same disagreement this path exists to
|
|
37835
|
+
// remove. So: an inline hint wins; else the classified arm's own
|
|
37836
|
+
// copy (consumer override first); else, for a kind this module has
|
|
37837
|
+
// no copy for, the neutral `unknown` text rather than one that
|
|
37838
|
+
// asserts a cause; else the unclassified `no_credential` default.
|
|
37839
|
+
hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
|
|
37840
|
+
};
|
|
37841
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
37842
|
+
}
|
|
37843
|
+
const credential = {
|
|
37844
|
+
source: state.source,
|
|
37845
|
+
resolved: state.source !== null,
|
|
37846
|
+
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
37847
|
+
};
|
|
37848
|
+
if (!credential.resolved) {
|
|
37849
|
+
const result2 = {
|
|
37850
|
+
ok: false,
|
|
37851
|
+
credential,
|
|
37852
|
+
probe: { elapsed_ms: 0 },
|
|
37853
|
+
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
37854
|
+
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
37855
|
+
};
|
|
37856
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
37857
|
+
}
|
|
37858
|
+
let arm = "ok";
|
|
37859
|
+
let error61;
|
|
37860
|
+
let status;
|
|
37861
|
+
let customHint;
|
|
37862
|
+
probeStarted = Date.now();
|
|
37863
|
+
try {
|
|
37864
|
+
await probeFn();
|
|
37865
|
+
} catch (e) {
|
|
37866
|
+
status = statusOf(e);
|
|
37867
|
+
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
37868
|
+
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
37869
|
+
let kind = arm;
|
|
37870
|
+
let detail;
|
|
37871
|
+
const custom2 = classifyThrown?.(e);
|
|
37872
|
+
if (custom2) {
|
|
37873
|
+
kind = custom2.kind;
|
|
37874
|
+
customHint = custom2.hint;
|
|
37875
|
+
detail = custom2.detail;
|
|
37876
|
+
arm = isArm(kind) ? kind : "unknown";
|
|
37877
|
+
}
|
|
37878
|
+
error61 = {
|
|
37879
|
+
kind,
|
|
37880
|
+
// Redacted AND bounded before it reaches the result: an upstream
|
|
37881
|
+
// failure routinely quotes what it was sent, and a healthcheck is
|
|
37882
|
+
// the tool people paste into a chat when something is broken.
|
|
37883
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
37884
|
+
...detail !== void 0 ? { detail } : {}
|
|
37885
|
+
};
|
|
37886
|
+
}
|
|
37887
|
+
const result = {
|
|
37888
|
+
ok: error61 === void 0,
|
|
37889
|
+
credential,
|
|
37890
|
+
probe: {
|
|
37891
|
+
...probeUrl ? { url: probeUrl } : {},
|
|
37892
|
+
elapsed_ms: Date.now() - probeStarted,
|
|
37893
|
+
...status !== void 0 ? { status } : {}
|
|
37894
|
+
},
|
|
37895
|
+
...error61 ? { error: error61 } : {},
|
|
37896
|
+
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
37897
|
+
};
|
|
37898
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
37899
|
+
}
|
|
37802
37900
|
function registerCredentialHealthcheckTool(args) {
|
|
37803
|
-
|
|
37804
|
-
const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
|
|
37805
|
-
server.registerTool(`${prefix}_healthcheck`, {
|
|
37901
|
+
args.server.registerTool(`${args.prefix}_healthcheck`, {
|
|
37806
37902
|
title: "Verify credentials and upstream reachability",
|
|
37807
|
-
description:
|
|
37903
|
+
description: `${credentialHealthcheckDescription(args.hostLabel)} Call this when a real tool fails and you want to know which hop broke.`,
|
|
37808
37904
|
annotations: {
|
|
37809
37905
|
title: "Verify credentials and upstream reachability",
|
|
37810
37906
|
readOnlyHint: true,
|
|
@@ -37812,92 +37908,7 @@ function registerCredentialHealthcheckTool(args) {
|
|
|
37812
37908
|
openWorldHint: true
|
|
37813
37909
|
},
|
|
37814
37910
|
inputSchema: {}
|
|
37815
|
-
}, async () =>
|
|
37816
|
-
let probeStarted = 0;
|
|
37817
|
-
let state;
|
|
37818
|
-
try {
|
|
37819
|
-
state = await resolveCredential();
|
|
37820
|
-
} catch (e) {
|
|
37821
|
-
const classified = classifyThrown?.(e);
|
|
37822
|
-
const result2 = {
|
|
37823
|
-
ok: false,
|
|
37824
|
-
// Still false, and still no source: a classification explains WHY
|
|
37825
|
-
// nothing resolved, it does not invent a credential that did.
|
|
37826
|
-
credential: { source: null, resolved: false },
|
|
37827
|
-
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
37828
|
-
probe: { elapsed_ms: 0 },
|
|
37829
|
-
error: {
|
|
37830
|
-
kind: classified?.kind ?? "no_credential",
|
|
37831
|
-
message: truncateErrorMessage(messageOf(e)),
|
|
37832
|
-
...classified?.detail !== void 0 ? { detail: classified.detail } : {}
|
|
37833
|
-
},
|
|
37834
|
-
// The hint must follow the KIND beside it. Falling back to
|
|
37835
|
-
// `no_credential`'s copy under a classified kind would state a cause
|
|
37836
|
-
// the kind contradicts — the same disagreement this path exists to
|
|
37837
|
-
// remove. So: an inline hint wins; else the classified arm's own
|
|
37838
|
-
// copy (consumer override first); else, for a kind this module has
|
|
37839
|
-
// no copy for, the neutral `unknown` text rather than one that
|
|
37840
|
-
// asserts a cause; else the unclassified `no_credential` default.
|
|
37841
|
-
hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
|
|
37842
|
-
};
|
|
37843
|
-
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
37844
|
-
}
|
|
37845
|
-
const credential = {
|
|
37846
|
-
source: state.source,
|
|
37847
|
-
resolved: state.source !== null,
|
|
37848
|
-
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
37849
|
-
};
|
|
37850
|
-
if (!credential.resolved) {
|
|
37851
|
-
const result2 = {
|
|
37852
|
-
ok: false,
|
|
37853
|
-
credential,
|
|
37854
|
-
probe: { elapsed_ms: 0 },
|
|
37855
|
-
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
37856
|
-
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
37857
|
-
};
|
|
37858
|
-
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
37859
|
-
}
|
|
37860
|
-
let arm = "ok";
|
|
37861
|
-
let error61;
|
|
37862
|
-
let status;
|
|
37863
|
-
let customHint;
|
|
37864
|
-
probeStarted = Date.now();
|
|
37865
|
-
try {
|
|
37866
|
-
await probeFn();
|
|
37867
|
-
} catch (e) {
|
|
37868
|
-
status = statusOf(e);
|
|
37869
|
-
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
37870
|
-
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
37871
|
-
let kind = arm;
|
|
37872
|
-
let detail;
|
|
37873
|
-
const custom2 = classifyThrown?.(e);
|
|
37874
|
-
if (custom2) {
|
|
37875
|
-
kind = custom2.kind;
|
|
37876
|
-
customHint = custom2.hint;
|
|
37877
|
-
detail = custom2.detail;
|
|
37878
|
-
}
|
|
37879
|
-
error61 = {
|
|
37880
|
-
kind,
|
|
37881
|
-
// Redacted AND bounded before it reaches the result: an upstream
|
|
37882
|
-
// failure routinely quotes what it was sent, and a healthcheck is
|
|
37883
|
-
// the tool people paste into a chat when something is broken.
|
|
37884
|
-
message: truncateErrorMessage(messageOf(e)),
|
|
37885
|
-
...detail !== void 0 ? { detail } : {}
|
|
37886
|
-
};
|
|
37887
|
-
}
|
|
37888
|
-
const result = {
|
|
37889
|
-
ok: error61 === void 0,
|
|
37890
|
-
credential,
|
|
37891
|
-
probe: {
|
|
37892
|
-
...probeUrl ? { url: probeUrl } : {},
|
|
37893
|
-
elapsed_ms: Date.now() - probeStarted,
|
|
37894
|
-
...status !== void 0 ? { status } : {}
|
|
37895
|
-
},
|
|
37896
|
-
...error61 ? { error: error61 } : {},
|
|
37897
|
-
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
37898
|
-
};
|
|
37899
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
37900
|
-
});
|
|
37911
|
+
}, async () => runCredentialHealthcheck(args));
|
|
37901
37912
|
}
|
|
37902
37913
|
|
|
37903
37914
|
// src/tools/healthcheck.ts
|
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.14.
|
|
2
|
+
export const VERSION = '1.14.1'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/gemini-mcp",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.1",
|
|
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,10 +46,10 @@
|
|
|
46
46
|
"typecheck": "tsc -p tsconfig.test.json"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@chrischall/mcp-utils": "^0.
|
|
50
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
49
|
+
"@chrischall/mcp-utils": "^0.26.1",
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
51
51
|
"dotenv": "^17.4.0",
|
|
52
|
-
"zod": "^4.4
|
|
52
|
+
"zod": "^4.5.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^26.0.0",
|
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.14.
|
|
9
|
+
"version": "1.14.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/gemini-mcp",
|
|
14
|
-
"version": "1.14.
|
|
14
|
+
"version": "1.14.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|