@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.
@@ -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.0"
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.0",
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.0",
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.0";
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
- const { server, prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
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: `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'. Call this when a real tool fails and you want to know which hop broke. Read-only; never returns the credential itself.`,
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.0'; // x-release-please-version
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.0",
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.23.0",
50
- "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "@chrischall/mcp-utils": "^0.26.1",
50
+ "@modelcontextprotocol/sdk": "^1.30.0",
51
51
  "dotenv": "^17.4.0",
52
- "zod": "^4.4.2"
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.0",
9
+ "version": "1.14.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/gemini-mcp",
14
- "version": "1.14.0",
14
+ "version": "1.14.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },