@chrischall/gemini-mcp 1.12.0 → 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.
@@ -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.12.0"
10
+ "version": "1.13.0"
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.12.0",
18
+ "version": "1.13.0",
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.12.0",
4
+ "version": "1.13.0",
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
@@ -26542,9 +26542,28 @@ var require_utils = __commonJS({
26542
26542
  "use strict";
26543
26543
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
26544
26544
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
26545
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
26545
26546
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
26546
26547
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
26547
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
26548
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
26549
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
26550
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
26551
+ var BYTE_HEX = new Array(256);
26552
+ {
26553
+ const HEX_DIGITS = "0123456789ABCDEF";
26554
+ for (let i = 0; i < 256; i++) {
26555
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
26556
+ }
26557
+ }
26558
+ function percentEncodeNonAscii(cp) {
26559
+ if (cp < 2048) {
26560
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
26561
+ }
26562
+ if (cp < 65536) {
26563
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
26564
+ }
26565
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
26566
+ }
26548
26567
  function stringArrayToHexStripped(input) {
26549
26568
  let acc = "";
26550
26569
  let code = 0;
@@ -26569,91 +26588,105 @@ var require_utils = __commonJS({
26569
26588
  }
26570
26589
  return acc;
26571
26590
  }
26591
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
26592
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
26593
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
26572
26594
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
26573
- function consumeIsZone(buffer) {
26574
- buffer.length = 0;
26575
- return true;
26576
- }
26577
- function consumeHextets(buffer, address, output) {
26578
- if (buffer.length) {
26579
- const hex3 = stringArrayToHexStripped(buffer);
26580
- if (hex3 !== "") {
26581
- address.push(hex3);
26582
- } else {
26583
- output.error = true;
26584
- return false;
26595
+ function isZoneIdentifier(zone) {
26596
+ if (zone.length === 0) return false;
26597
+ for (let i = 0; i < zone.length; i++) {
26598
+ if (isZoneCharacter(zone[i])) continue;
26599
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
26600
+ i += 2;
26601
+ continue;
26585
26602
  }
26586
- buffer.length = 0;
26603
+ return false;
26587
26604
  }
26588
26605
  return true;
26589
26606
  }
26590
- function getIPV6(input) {
26591
- let tokenCount = 0;
26592
- const output = { error: false, address: "", zone: "" };
26593
- const address = [];
26594
- const buffer = [];
26595
- let endipv6Encountered = false;
26596
- let endIpv6 = false;
26597
- let consume = consumeHextets;
26598
- for (let i = 0; i < input.length; i++) {
26599
- const cursor = input[i];
26600
- if (cursor === "[" || cursor === "]") {
26601
- continue;
26602
- }
26603
- if (cursor === ":") {
26604
- if (endipv6Encountered === true) {
26605
- endIpv6 = true;
26606
- }
26607
- if (!consume(buffer, address, output)) {
26608
- break;
26609
- }
26610
- if (++tokenCount > 7) {
26611
- output.error = true;
26612
- break;
26613
- }
26614
- if (i > 0 && input[i - 1] === ":") {
26615
- endipv6Encountered = true;
26607
+ function compressIPv6ZeroRun(hextets) {
26608
+ let bestStart = -1;
26609
+ let bestLength = 0;
26610
+ let runStart = -1;
26611
+ let runLength = 0;
26612
+ for (let i = 0; i < hextets.length; i++) {
26613
+ if (hextets[i] === "0") {
26614
+ if (runStart === -1) runStart = i;
26615
+ runLength++;
26616
+ if (runLength > bestLength) {
26617
+ bestLength = runLength;
26618
+ bestStart = runStart;
26616
26619
  }
26617
- address.push(":");
26618
- continue;
26619
- } else if (cursor === "%") {
26620
- if (!consume(buffer, address, output)) {
26621
- break;
26622
- }
26623
- consume = consumeIsZone;
26624
26620
  } else {
26625
- buffer.push(cursor);
26621
+ runStart = -1;
26622
+ runLength = 0;
26623
+ }
26624
+ }
26625
+ if (bestLength < 2) return hextets.join(":");
26626
+ const head = hextets.slice(0, bestStart).join(":");
26627
+ const tail = hextets.slice(bestStart + bestLength).join(":");
26628
+ return head + "::" + tail;
26629
+ }
26630
+ function normalizeIPv6Address(input) {
26631
+ const compression = input.indexOf("::");
26632
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
26633
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
26634
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
26635
+ if (compression !== -1) {
26636
+ if (left.length === 1 && left[0] === "") left.length = 0;
26637
+ if (right.length === 1 && right[0] === "") right.length = 0;
26638
+ }
26639
+ const parts = left.concat(right);
26640
+ let hextetCount = 0;
26641
+ for (let i = 0; i < parts.length; i++) {
26642
+ const part = parts[i];
26643
+ if (part === "") return void 0;
26644
+ if (part.indexOf(".") !== -1) {
26645
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
26646
+ hextetCount += 2;
26626
26647
  continue;
26627
26648
  }
26649
+ if (!isHextet(part)) return void 0;
26650
+ parts[i] = parseInt(part, 16).toString(16);
26651
+ hextetCount++;
26628
26652
  }
26629
- if (buffer.length) {
26630
- if (consume === consumeIsZone) {
26631
- output.zone = buffer.join("");
26632
- } else if (endIpv6) {
26633
- address.push(buffer.join(""));
26634
- } else {
26635
- address.push(stringArrayToHexStripped(buffer));
26636
- }
26653
+ if (compression === -1) {
26654
+ if (hextetCount !== 8) return void 0;
26655
+ return compressIPv6ZeroRun(parts);
26637
26656
  }
26638
- output.address = address.join("");
26639
- return output;
26657
+ if (hextetCount >= 8) return void 0;
26658
+ const expanded = parts.slice(0, left.length);
26659
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
26660
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
26661
+ return compressIPv6ZeroRun(expanded);
26640
26662
  }
26641
26663
  function normalizeIPv6(host) {
26642
- if (findToken(host, ":") < 2) {
26643
- return { host, isIPV6: false };
26644
- }
26645
- const ipv63 = getIPV6(host);
26646
- if (!ipv63.error) {
26647
- let newHost = ipv63.address;
26648
- let escapedHost = ipv63.address;
26649
- if (ipv63.zone) {
26650
- newHost += "%" + ipv63.zone;
26651
- escapedHost += "%25" + ipv63.zone;
26652
- }
26653
- return { host: newHost, isIPV6: true, escapedHost };
26654
- } else {
26655
- return { host, isIPV6: false };
26656
- }
26664
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
26665
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
26666
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
26667
+ let input = bracketed ? host.slice(1, -1) : host;
26668
+ if (bracketed && isIPvFuture(input)) {
26669
+ input = input.toLowerCase();
26670
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
26671
+ }
26672
+ if (findToken(input, ":") < 2) {
26673
+ return { host, isIPV6: false, error: bracketed };
26674
+ }
26675
+ let zoneIdentifier = "";
26676
+ const zoneSeparator = input.indexOf("%");
26677
+ if (zoneSeparator !== -1) {
26678
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
26679
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
26680
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
26681
+ input = input.slice(0, zoneSeparator);
26682
+ }
26683
+ const address = normalizeIPv6Address(input);
26684
+ if (address === void 0) return { host, isIPV6: false, error: true };
26685
+ return {
26686
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
26687
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
26688
+ isIPV6: true
26689
+ };
26657
26690
  }
26658
26691
  function findToken(str, token) {
26659
26692
  let ind = 0;
@@ -26772,7 +26805,8 @@ var require_utils = __commonJS({
26772
26805
  function normalizePathEncoding(input) {
26773
26806
  let output = "";
26774
26807
  for (let i = 0; i < input.length; i++) {
26775
- if (input[i] === "%" && i + 2 < input.length) {
26808
+ const ch = input[i];
26809
+ if (ch === "%" && i + 2 < input.length) {
26776
26810
  const hex3 = input.slice(i + 1, i + 3);
26777
26811
  if (isHexPair(hex3)) {
26778
26812
  const normalizedHex = hex3.toUpperCase();
@@ -26786,10 +26820,152 @@ var require_utils = __commonJS({
26786
26820
  continue;
26787
26821
  }
26788
26822
  }
26789
- if (isPathCharacter(input[i])) {
26790
- output += input[i];
26823
+ if (isPathCharacter(ch)) {
26824
+ output += ch;
26791
26825
  } else {
26792
- output += escape(input[i]);
26826
+ const code = input.charCodeAt(i);
26827
+ if (code < 128) {
26828
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
26829
+ } else if (code < 55296 || code > 57343) {
26830
+ output += percentEncodeNonAscii(code);
26831
+ } else if (code <= 56319 && i + 1 < input.length) {
26832
+ const low = input.charCodeAt(i + 1);
26833
+ if (low >= 56320 && low <= 57343) {
26834
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
26835
+ i++;
26836
+ } else {
26837
+ output += percentEncodeNonAscii(65533);
26838
+ }
26839
+ } else {
26840
+ output += percentEncodeNonAscii(65533);
26841
+ }
26842
+ }
26843
+ }
26844
+ return output;
26845
+ }
26846
+ function serializePathEncoding(input, pathNoScheme = false) {
26847
+ let output = "";
26848
+ let firstSegment = pathNoScheme && input[0] !== "/";
26849
+ for (let i = 0; i < input.length; i++) {
26850
+ const ch = input[i];
26851
+ if (ch === "%" && i + 2 < input.length) {
26852
+ const hex3 = input.slice(i + 1, i + 3);
26853
+ if (isHexPair(hex3)) {
26854
+ output += "%" + hex3.toUpperCase();
26855
+ i += 2;
26856
+ continue;
26857
+ }
26858
+ }
26859
+ if (ch === "/") {
26860
+ firstSegment = false;
26861
+ }
26862
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
26863
+ output += ch;
26864
+ } else {
26865
+ const code = input.charCodeAt(i);
26866
+ if (code < 128) {
26867
+ output += BYTE_HEX[code];
26868
+ } else if (code < 55296 || code > 57343) {
26869
+ output += percentEncodeNonAscii(code);
26870
+ } else if (code <= 56319 && i + 1 < input.length) {
26871
+ const low = input.charCodeAt(i + 1);
26872
+ if (low >= 56320 && low <= 57343) {
26873
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
26874
+ i++;
26875
+ } else {
26876
+ output += percentEncodeNonAscii(65533);
26877
+ }
26878
+ } else {
26879
+ output += percentEncodeNonAscii(65533);
26880
+ }
26881
+ }
26882
+ }
26883
+ return output;
26884
+ }
26885
+ function encodeComponent(input, isAllowed) {
26886
+ let output = "";
26887
+ for (let i = 0; i < input.length; i++) {
26888
+ const ch = input[i];
26889
+ if (ch === "%" && i + 2 < input.length) {
26890
+ const hex3 = input.slice(i + 1, i + 3);
26891
+ if (isHexPair(hex3)) {
26892
+ output += "%" + hex3.toUpperCase();
26893
+ i += 2;
26894
+ continue;
26895
+ }
26896
+ }
26897
+ if (isAllowed(ch)) {
26898
+ output += ch;
26899
+ } else {
26900
+ const code = input.charCodeAt(i);
26901
+ if (code < 128) {
26902
+ output += BYTE_HEX[code];
26903
+ } else if (code < 55296 || code > 57343) {
26904
+ output += percentEncodeNonAscii(code);
26905
+ } else if (code <= 56319 && i + 1 < input.length) {
26906
+ const low = input.charCodeAt(i + 1);
26907
+ if (low >= 56320 && low <= 57343) {
26908
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
26909
+ i++;
26910
+ } else {
26911
+ output += percentEncodeNonAscii(65533);
26912
+ }
26913
+ } else {
26914
+ output += percentEncodeNonAscii(65533);
26915
+ }
26916
+ }
26917
+ }
26918
+ return output;
26919
+ }
26920
+ function encodeUserinfo(input) {
26921
+ return encodeComponent(input, isUserinfoCharacter);
26922
+ }
26923
+ function encodeQuery(input) {
26924
+ return encodeComponent(input, isQueryFragmentCharacter);
26925
+ }
26926
+ function encodeFragment(input) {
26927
+ return encodeComponent(input, isQueryFragmentCharacter);
26928
+ }
26929
+ function isEscapeSafe(cp) {
26930
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
26931
+ }
26932
+ function normalizeQueryFragmentEncoding(input) {
26933
+ let output = "";
26934
+ for (let i = 0; i < input.length; i++) {
26935
+ const ch = input[i];
26936
+ if (ch === "%" && i + 2 < input.length) {
26937
+ const hex3 = input.slice(i + 1, i + 3);
26938
+ if (isHexPair(hex3)) {
26939
+ const normalizedHex = hex3.toUpperCase();
26940
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
26941
+ if (isUnreserved(decoded)) {
26942
+ output += decoded;
26943
+ } else {
26944
+ output += "%" + normalizedHex;
26945
+ }
26946
+ i += 2;
26947
+ continue;
26948
+ }
26949
+ }
26950
+ if (isQueryFragmentCharacter(ch)) {
26951
+ output += ch;
26952
+ } else {
26953
+ const code = input.charCodeAt(i);
26954
+ if (code < 128) {
26955
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
26956
+ } else if (code < 55296 || code > 57343) {
26957
+ output += percentEncodeNonAscii(code);
26958
+ } else if (code <= 56319 && i + 1 < input.length) {
26959
+ const low = input.charCodeAt(i + 1);
26960
+ if (low >= 56320 && low <= 57343) {
26961
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
26962
+ i++;
26963
+ } else {
26964
+ output += percentEncodeNonAscii(65533);
26965
+ }
26966
+ } else {
26967
+ output += percentEncodeNonAscii(65533);
26968
+ }
26793
26969
  }
26794
26970
  }
26795
26971
  return output;
@@ -26812,14 +26988,18 @@ var require_utils = __commonJS({
26812
26988
  function recomposeAuthority(component) {
26813
26989
  const uriTokens = [];
26814
26990
  if (component.userinfo !== void 0) {
26815
- uriTokens.push(component.userinfo);
26991
+ uriTokens.push(encodeUserinfo(component.userinfo));
26816
26992
  uriTokens.push("@");
26817
26993
  }
26818
26994
  if (component.host !== void 0) {
26819
- let host = unescape(component.host);
26995
+ let host = component.host;
26820
26996
  if (!isIPv4(host)) {
26821
- const ipV6res = normalizeIPv6(host);
26822
- if (ipV6res.isIPV6 === true) {
26997
+ let ipV6res = normalizeIPv6(host);
26998
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
26999
+ host = normalizePercentEncoding(host, true);
27000
+ ipV6res = normalizeIPv6(host);
27001
+ }
27002
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
26823
27003
  host = `[${ipV6res.escapedHost}]`;
26824
27004
  } else {
26825
27005
  host = reescapeHostDelimiters(host, false);
@@ -26828,8 +27008,12 @@ var require_utils = __commonJS({
26828
27008
  uriTokens.push(host);
26829
27009
  }
26830
27010
  if (typeof component.port === "number" || typeof component.port === "string") {
27011
+ const port = String(component.port);
27012
+ if (!isPort(port)) {
27013
+ throw new TypeError("URI port is malformed.");
27014
+ }
26831
27015
  uriTokens.push(":");
26832
- uriTokens.push(String(component.port));
27016
+ uriTokens.push(port);
26833
27017
  }
26834
27018
  return uriTokens.length ? uriTokens.join("") : void 0;
26835
27019
  }
@@ -26839,6 +27023,11 @@ var require_utils = __commonJS({
26839
27023
  reescapeHostDelimiters,
26840
27024
  normalizePercentEncoding,
26841
27025
  normalizePathEncoding,
27026
+ serializePathEncoding,
27027
+ normalizeQueryFragmentEncoding,
27028
+ encodeUserinfo,
27029
+ encodeQuery,
27030
+ encodeFragment,
26842
27031
  escapePreservingEscapes,
26843
27032
  removeDotSegments,
26844
27033
  isIPv4,
@@ -26854,7 +27043,7 @@ var require_schemes = __commonJS({
26854
27043
  "node_modules/fast-uri/lib/schemes.js"(exports, module) {
26855
27044
  "use strict";
26856
27045
  var { isUUID } = require_utils();
26857
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
27046
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
26858
27047
  var supportedSchemeNames = (
26859
27048
  /** @type {const} */
26860
27049
  [
@@ -26915,9 +27104,10 @@ var require_schemes = __commonJS({
26915
27104
  wsComponent.secure = void 0;
26916
27105
  }
26917
27106
  if (wsComponent.resourceName) {
26918
- const [path, query] = wsComponent.resourceName.split("?");
27107
+ const queryIndex = wsComponent.resourceName.indexOf("?");
27108
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
26919
27109
  wsComponent.path = path && path !== "/" ? path : void 0;
26920
- wsComponent.query = query;
27110
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
26921
27111
  wsComponent.resourceName = void 0;
26922
27112
  }
26923
27113
  wsComponent.fragment = void 0;
@@ -26929,7 +27119,7 @@ var require_schemes = __commonJS({
26929
27119
  return urnComponent;
26930
27120
  }
26931
27121
  const matches = urnComponent.path.match(URN_REG);
26932
- if (matches) {
27122
+ if (matches && matches[0] === urnComponent.path) {
26933
27123
  const scheme = options.scheme || urnComponent.scheme || "urn";
26934
27124
  urnComponent.nid = matches[1].toLowerCase();
26935
27125
  urnComponent.nss = matches[2];
@@ -27063,8 +27253,17 @@ var require_schemes = __commonJS({
27063
27253
  var require_fast_uri = __commonJS({
27064
27254
  "node_modules/fast-uri/index.js"(exports, module) {
27065
27255
  "use strict";
27066
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
27256
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
27067
27257
  var { SCHEMES, getSchemeHandler } = require_schemes();
27258
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
27259
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
27260
+ function decodeValidScheme(scheme) {
27261
+ const decodedScheme = unescape(String(scheme));
27262
+ if (!VALID_SCHEME.test(decodedScheme)) {
27263
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
27264
+ }
27265
+ return decodedScheme;
27266
+ }
27068
27267
  function normalize(uri, options) {
27069
27268
  if (typeof uri === "string") {
27070
27269
  uri = /** @type {T} */
@@ -27077,12 +27276,34 @@ var require_fast_uri = __commonJS({
27077
27276
  }
27078
27277
  function resolve2(baseURI, relativeURI, options) {
27079
27278
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
27080
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
27081
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
27082
- if (baseMalformed || relativeMalformed) {
27279
+ const {
27280
+ parsed: baseParsed,
27281
+ malformedAuthorityOrPort: baseMalformed,
27282
+ malformedPercentEncoding: baseMalformedPercentEncoding,
27283
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
27284
+ malformedHost: baseMalformedHost,
27285
+ malformedScheme: baseMalformedScheme
27286
+ } = parseWithStatus(baseURI, schemelessOptions);
27287
+ const {
27288
+ parsed: relativeParsed,
27289
+ malformedAuthorityOrPort: relativeMalformed,
27290
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
27291
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
27292
+ malformedHost: relativeMalformedHost,
27293
+ malformedScheme: relativeMalformedScheme
27294
+ } = parseWithStatus(relativeURI, schemelessOptions);
27295
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
27083
27296
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
27084
27297
  }
27085
27298
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
27299
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
27300
+ const resolvedHost = resolved.host;
27301
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
27302
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
27303
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
27304
+ if (resolved.error && !encodedASCIIHost) {
27305
+ throw new Error(resolved.error);
27306
+ }
27086
27307
  schemelessOptions.skipEscape = true;
27087
27308
  return serialize(resolved, schemelessOptions);
27088
27309
  }
@@ -27142,7 +27363,7 @@ var require_fast_uri = __commonJS({
27142
27363
  function equal(uriA, uriB, options) {
27143
27364
  const normalizedA = normalizeComparableURI(uriA, options);
27144
27365
  const normalizedB = normalizeComparableURI(uriB, options);
27145
- return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
27366
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
27146
27367
  }
27147
27368
  function serialize(cmpts, opts) {
27148
27369
  const component = {
@@ -27163,19 +27384,22 @@ var require_fast_uri = __commonJS({
27163
27384
  };
27164
27385
  const options = Object.assign({}, opts);
27165
27386
  const uriTokens = [];
27387
+ if (component.scheme) {
27388
+ component.scheme = decodeValidScheme(component.scheme);
27389
+ }
27166
27390
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
27167
27391
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
27392
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
27393
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
27168
27394
  if (component.path !== void 0) {
27169
27395
  if (!options.skipEscape) {
27170
- component.path = escapePreservingEscapes(component.path);
27171
- if (component.scheme !== void 0) {
27172
- component.path = component.path.split("%3A").join(":");
27173
- }
27396
+ component.path = serializePathEncoding(component.path, pathNoScheme);
27174
27397
  } else {
27175
27398
  component.path = normalizePercentEncoding(component.path);
27176
27399
  }
27177
27400
  }
27178
27401
  if (options.reference !== "suffix" && component.scheme) {
27402
+ component.scheme = decodeValidScheme(component.scheme);
27179
27403
  uriTokens.push(component.scheme, ":");
27180
27404
  }
27181
27405
  const authority = recomposeAuthority(component);
@@ -27193,16 +27417,19 @@ var require_fast_uri = __commonJS({
27193
27417
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
27194
27418
  s = removeDotSegments(s);
27195
27419
  }
27420
+ if (pathNoScheme) {
27421
+ s = serializePathEncoding(s, true);
27422
+ }
27196
27423
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
27197
27424
  s = "/%2F" + s.slice(2);
27198
27425
  }
27199
27426
  uriTokens.push(s);
27200
27427
  }
27201
27428
  if (component.query !== void 0) {
27202
- uriTokens.push("?", component.query);
27429
+ uriTokens.push("?", encodeQuery(component.query));
27203
27430
  }
27204
27431
  if (component.fragment !== void 0) {
27205
- uriTokens.push("#", component.fragment);
27432
+ uriTokens.push("#", encodeFragment(component.fragment));
27206
27433
  }
27207
27434
  return uriTokens.join("");
27208
27435
  }
@@ -27218,6 +27445,35 @@ var require_fast_uri = __commonJS({
27218
27445
  }
27219
27446
  return void 0;
27220
27447
  }
27448
+ function hasMalformedPercentEncoding(component) {
27449
+ if (component === void 0) return false;
27450
+ let percent = component.indexOf("%");
27451
+ while (percent !== -1) {
27452
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
27453
+ return true;
27454
+ }
27455
+ percent = component.indexOf("%", percent + 3);
27456
+ }
27457
+ return false;
27458
+ }
27459
+ function isIPLiteral(host) {
27460
+ return host[0] === "[" && host[host.length - 1] === "]";
27461
+ }
27462
+ function hasMalformedComponentPercentEncoding(matches) {
27463
+ const host = matches[4];
27464
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
27465
+ }
27466
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
27467
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
27468
+ try {
27469
+ parsed.host = new URL("http://" + parsed.host).hostname;
27470
+ } catch (e) {
27471
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
27472
+ return true;
27473
+ }
27474
+ }
27475
+ return false;
27476
+ }
27221
27477
  function parseWithStatus(uri, opts) {
27222
27478
  const options = Object.assign({}, opts);
27223
27479
  const parsed = {
@@ -27230,6 +27486,11 @@ var require_fast_uri = __commonJS({
27230
27486
  fragment: void 0
27231
27487
  };
27232
27488
  let malformedAuthorityOrPort = false;
27489
+ let malformedPercentEncoding = false;
27490
+ let malformedSchemeSpecific = false;
27491
+ let malformedHost = false;
27492
+ let malformedIPLiteral = false;
27493
+ let malformedScheme = false;
27233
27494
  let isIP = false;
27234
27495
  if (options.reference === "suffix") {
27235
27496
  if (options.scheme) {
@@ -27266,6 +27527,19 @@ var require_fast_uri = __commonJS({
27266
27527
  parsed.path = matches[6] || "";
27267
27528
  parsed.query = matches[7];
27268
27529
  parsed.fragment = matches[8];
27530
+ if (parsed.scheme !== void 0) {
27531
+ const decodedScheme = unescape(parsed.scheme);
27532
+ if (VALID_SCHEME.test(decodedScheme)) {
27533
+ parsed.scheme = decodedScheme.toLowerCase();
27534
+ } else {
27535
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
27536
+ malformedScheme = true;
27537
+ }
27538
+ }
27539
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
27540
+ if (malformedPercentEncoding) {
27541
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
27542
+ }
27269
27543
  if (isNaN(parsed.port)) {
27270
27544
  parsed.port = matches[5];
27271
27545
  }
@@ -27277,9 +27551,16 @@ var require_fast_uri = __commonJS({
27277
27551
  if (parsed.host) {
27278
27552
  const ipv4result = isIPv4(parsed.host);
27279
27553
  if (ipv4result === false) {
27554
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
27555
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
27280
27556
  const ipv6result = normalizeIPv6(parsed.host);
27281
- parsed.host = ipv6result.host.toLowerCase();
27282
- isIP = ipv6result.isIPV6;
27557
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
27558
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
27559
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
27560
+ if (malformedIPLiteral) {
27561
+ parsed.error = parsed.error || "URI host is malformed.";
27562
+ malformedAuthorityOrPort = true;
27563
+ }
27283
27564
  } else {
27284
27565
  isIP = true;
27285
27566
  }
@@ -27297,42 +27578,36 @@ var require_fast_uri = __commonJS({
27297
27578
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
27298
27579
  }
27299
27580
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
27300
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
27301
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
27302
- try {
27303
- parsed.host = new URL("http://" + parsed.host).hostname;
27304
- } catch (e) {
27305
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
27306
- }
27307
- }
27581
+ if (!malformedIPLiteral) {
27582
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
27308
27583
  }
27309
27584
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
27310
27585
  if (uri.indexOf("%") !== -1) {
27311
- if (parsed.scheme !== void 0) {
27312
- parsed.scheme = unescape(parsed.scheme);
27313
- }
27314
- if (parsed.host !== void 0) {
27315
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
27586
+ if (parsed.host !== void 0 && !malformedIPLiteral) {
27587
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
27588
+ parsed.host = reescapeHostDelimiters(host, isIP);
27316
27589
  }
27317
27590
  }
27318
27591
  if (parsed.path) {
27319
27592
  parsed.path = normalizePathEncoding(parsed.path);
27320
27593
  }
27594
+ if (parsed.query) {
27595
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
27596
+ }
27321
27597
  if (parsed.fragment) {
27322
- try {
27323
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
27324
- } catch {
27325
- parsed.error = parsed.error || "URI malformed";
27326
- }
27598
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
27327
27599
  }
27328
27600
  }
27329
27601
  if (schemeHandler && schemeHandler.parse) {
27330
27602
  schemeHandler.parse(parsed, options);
27603
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
27604
+ malformedSchemeSpecific = true;
27605
+ }
27331
27606
  }
27332
27607
  } else {
27333
27608
  parsed.error = parsed.error || "URI can not be parsed.";
27334
27609
  }
27335
- return { parsed, malformedAuthorityOrPort };
27610
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
27336
27611
  }
27337
27612
  function parse3(uri, opts) {
27338
27613
  return parseWithStatus(uri, opts).parsed;
@@ -27341,20 +27616,28 @@ var require_fast_uri = __commonJS({
27341
27616
  return normalizeStringWithStatus(uri, opts).normalized;
27342
27617
  }
27343
27618
  function normalizeStringWithStatus(uri, opts) {
27344
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
27619
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
27345
27620
  return {
27346
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
27347
- malformedAuthorityOrPort
27621
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
27622
+ malformedAuthorityOrPort,
27623
+ malformedPercentEncoding,
27624
+ malformedSchemeSpecific,
27625
+ malformedHost,
27626
+ malformedScheme
27348
27627
  };
27349
27628
  }
27350
27629
  function normalizeComparableURI(uri, opts) {
27351
- if (typeof uri === "string") {
27352
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
27353
- return malformedAuthorityOrPort ? void 0 : normalized;
27630
+ if (typeof uri !== "string" && typeof uri !== "object") {
27631
+ return void 0;
27354
27632
  }
27355
- if (typeof uri === "object") {
27356
- return serialize(uri, opts);
27633
+ let value;
27634
+ try {
27635
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
27636
+ } catch {
27637
+ return void 0;
27357
27638
  }
27639
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
27640
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
27358
27641
  }
27359
27642
  var fastUri = {
27360
27643
  SCHEMES,
@@ -32157,12 +32440,25 @@ var init_errors4 = __esm({
32157
32440
  }
32158
32441
  });
32159
32442
 
32160
- // node_modules/@chrischall/mcp-utils/dist/response/index.js
32161
- function textResult(data) {
32162
- return {
32163
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
32164
- };
32443
+ // node_modules/@chrischall/mcp-utils/dist/response/view.js
32444
+ function minifiedResult(data) {
32445
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
32165
32446
  }
32447
+ var init_view = __esm({
32448
+ "node_modules/@chrischall/mcp-utils/dist/response/view.js"() {
32449
+ }
32450
+ });
32451
+
32452
+ // node_modules/@chrischall/mcp-utils/dist/response/media.js
32453
+ var MEDIA_NOUN, MEDIA_KEY;
32454
+ var init_media = __esm({
32455
+ "node_modules/@chrischall/mcp-utils/dist/response/media.js"() {
32456
+ MEDIA_NOUN = "(?:avatar|tall_avatar|cover_photo|cover_image|picture|photo|thumbnail|thumb|image|icon|banner|profile_pic(?:ture)?|logo)";
32457
+ MEDIA_KEY = new RegExp(`^${MEDIA_NOUN}s?(?:(?:link|uri|url)s?)?$`, "i");
32458
+ }
32459
+ });
32460
+
32461
+ // node_modules/@chrischall/mcp-utils/dist/response/index.js
32166
32462
  function errorResult(message) {
32167
32463
  return {
32168
32464
  content: [{ type: "text", text: redactSecrets(message) }],
@@ -32172,6 +32468,8 @@ function errorResult(message) {
32172
32468
  var init_response = __esm({
32173
32469
  "node_modules/@chrischall/mcp-utils/dist/response/index.js"() {
32174
32470
  init_errors4();
32471
+ init_view();
32472
+ init_media();
32175
32473
  }
32176
32474
  });
32177
32475
 
@@ -32881,7 +33179,7 @@ async function loadStdioDotenv() {
32881
33179
  }
32882
33180
 
32883
33181
  // src/version.ts
32884
- var VERSION = "1.12.0";
33182
+ var VERSION = "1.13.0";
32885
33183
 
32886
33184
  // src/errors.ts
32887
33185
  init_dist();
@@ -32952,7 +33250,7 @@ function registerModelTools(server, client2) {
32952
33250
  },
32953
33251
  async () => {
32954
33252
  const models = await client2.listModels();
32955
- return textResult({ default: client2.defaultModel(), models });
33253
+ return minifiedResult({ default: client2.defaultModel(), models });
32956
33254
  }
32957
33255
  );
32958
33256
  }
@@ -32965,6 +33263,18 @@ function statusOf(err) {
32965
33263
  const s = err.status ?? err.statusCode;
32966
33264
  return typeof s === "number" ? s : void 0;
32967
33265
  }
33266
+ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
33267
+ "ok",
33268
+ "no_credential",
33269
+ "credential_rejected",
33270
+ "timeout",
33271
+ "http",
33272
+ "transport",
33273
+ "unknown"
33274
+ ]);
33275
+ function isArm(kind) {
33276
+ return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
33277
+ }
32968
33278
  function credentialHint(arm, prefix, hostLabel, source) {
32969
33279
  switch (arm) {
32970
33280
  case "ok":
@@ -33002,13 +33312,27 @@ function registerCredentialHealthcheckTool(args) {
33002
33312
  try {
33003
33313
  state = await resolveCredential();
33004
33314
  } catch (e) {
33315
+ const classified = classifyThrown?.(e);
33005
33316
  const result2 = {
33006
33317
  ok: false,
33318
+ // Still false, and still no source: a classification explains WHY
33319
+ // nothing resolved, it does not invent a credential that did.
33007
33320
  credential: { source: null, resolved: false },
33008
33321
  // No `url`: nothing was probed, and naming one implies it was tried.
33009
33322
  probe: { elapsed_ms: 0 },
33010
- error: { kind: "no_credential", message: truncateErrorMessage(messageOf(e)) },
33011
- hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
33323
+ error: {
33324
+ kind: classified?.kind ?? "no_credential",
33325
+ message: truncateErrorMessage(messageOf(e)),
33326
+ ...classified?.detail !== void 0 ? { detail: classified.detail } : {}
33327
+ },
33328
+ // The hint must follow the KIND beside it. Falling back to
33329
+ // `no_credential`'s copy under a classified kind would state a cause
33330
+ // the kind contradicts — the same disagreement this path exists to
33331
+ // remove. So: an inline hint wins; else the classified arm's own
33332
+ // copy (consumer override first); else, for a kind this module has
33333
+ // no copy for, the neutral `unknown` text rather than one that
33334
+ // asserts a cause; else the unclassified `no_credential` default.
33335
+ 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))
33012
33336
  };
33013
33337
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
33014
33338
  }
@@ -34000,7 +34324,7 @@ async function emitMedia(named, kind, opts, meta3, onWritten) {
34000
34324
  }))
34001
34325
  } : {};
34002
34326
  const storage = sink.persistsFiles ? {} : { storage: sink.kind, storage_note: storageNote };
34003
- return textResult({ [`${kind}s`]: refs, ...media, ...unavailable, ...note, ...storage, ...meta3 });
34327
+ return minifiedResult({ [`${kind}s`]: refs, ...media, ...unavailable, ...note, ...storage, ...meta3 });
34004
34328
  }
34005
34329
  async function emit(named, opts, meta3, onWritten) {
34006
34330
  return emitMedia(named.map((n) => ({ media: n.image, base: n.base })), "image", opts, meta3, onWritten);
@@ -34295,7 +34619,7 @@ function annotateReused(result, jobId) {
34295
34619
  };
34296
34620
  }
34297
34621
  function jobHandle(jobId, status, durable = false) {
34298
- return textResult({
34622
+ return minifiedResult({
34299
34623
  job_id: jobId,
34300
34624
  status,
34301
34625
  hint: durable ? `Generation running in the background. Poll gemini_get_result with job_id "${jobId}" until status is "done". The job record is stored durably, so a poll still works if this connector restarts \u2014 but background work is NOT restarted, so a long generation is safer with max_wait_ms than with async.` : `Generation running in the background. Poll gemini_get_result with job_id "${jobId}" until status is "done" (results are per-process and expire ~10 min after completion).`
@@ -34784,7 +35108,7 @@ init_dist();
34784
35108
  init_images();
34785
35109
  function previewUnlessConfirmed(confirm, action, method, path, body) {
34786
35110
  if (confirm === true) return null;
34787
- return textResult({
35111
+ return minifiedResult({
34788
35112
  dryRun: true,
34789
35113
  action,
34790
35114
  method,
@@ -36924,7 +37248,7 @@ function recoveryNote(recordedError) {
36924
37248
  return `${cause} The generation had already completed upstream and its output was recovered \u2014 it was not re-run, and you were not billed twice.`;
36925
37249
  }
36926
37250
  function stillRunning(jobId, interactionId) {
36927
- return textResult({
37251
+ return minifiedResult({
36928
37252
  job_id: jobId,
36929
37253
  status: "running",
36930
37254
  interaction_id: interactionId,
@@ -36963,7 +37287,7 @@ function registerUsageTools(server, client2) {
36963
37287
  client2.session.costUsd = void 0;
36964
37288
  client2.session.pricedCalls = 0;
36965
37289
  }
36966
- return textResult({
37290
+ return minifiedResult({
36967
37291
  usage: usage ?? null,
36968
37292
  billed_calls: calls,
36969
37293
  estimated_cost_usd: costUsd ?? null,
@@ -36986,7 +37310,7 @@ var UPLOAD_URL_MAX_BYTES_HOSTED = 25 * 1024 * 1024;
36986
37310
  var UPLOAD_ACCEPT = ["image/", "video/", "audio/"];
36987
37311
  var RETENTION_NOTE = "Gemini retains uploaded files for ~48h. Until then this reference can be reused across any number of calls (images_file_uris on gemini_image_generate / _edit / _set / gemini_interact, or video_url) without re-sending the bytes.";
36988
37312
  function uploadResult(file2) {
36989
- return textResult({
37313
+ return minifiedResult({
36990
37314
  // The `files/<id>` form is what every tool parameter documents, so it leads.
36991
37315
  file_uri: file2.name,
36992
37316
  // The absolute uri, for callers that would rather pass it through verbatim.
@@ -37099,7 +37423,7 @@ function registerFileTools(server, client2) {
37099
37423
  },
37100
37424
  async (args) => {
37101
37425
  const files = await client2.listFiles(args.page_size ?? 100);
37102
- return textResult({
37426
+ return minifiedResult({
37103
37427
  count: files.length,
37104
37428
  files: files.map((f) => ({
37105
37429
  file_uri: f.name,
@@ -37139,7 +37463,7 @@ function registerFileTools(server, client2) {
37139
37463
  { hint: "Objects are swept on a retention schedule (MEDIA_TTL_DAYS, default 7 days)." }
37140
37464
  );
37141
37465
  }
37142
- return textResult({
37466
+ return minifiedResult({
37143
37467
  url: fresh.ref,
37144
37468
  r2_key: fresh.key,
37145
37469
  ...fresh.expiresAt ? { expires_at: fresh.expiresAt } : {},
@@ -37168,7 +37492,7 @@ function registerFileTools(server, client2) {
37168
37492
  const limit = args.limit ?? 20;
37169
37493
  const page = sink.listRecentPage ? await sink.listRecentPage({ limit, sinceDay: args.since_day }) : { media: await sink.listRecent({ limit, sinceDay: args.since_day }), truncated: false };
37170
37494
  const found = page.media;
37171
- return textResult({
37495
+ return minifiedResult({
37172
37496
  count: found.length,
37173
37497
  // Never let a bounded walk pass for the whole picture: concluding an
37174
37498
  // image is gone is what makes someone pay to generate it twice.
@@ -37200,7 +37524,7 @@ function registerFileTools(server, client2) {
37200
37524
  const gate = previewUnlessConfirmed(args.confirm, "Delete an uploaded Gemini file", "DELETE", `/v1beta/${args.file_uri}`);
37201
37525
  if (gate) return gate;
37202
37526
  await client2.deleteFile(args.file_uri);
37203
- return textResult({ deleted: args.file_uri });
37527
+ return minifiedResult({ deleted: args.file_uri });
37204
37528
  }
37205
37529
  );
37206
37530
  }
@@ -37226,7 +37550,7 @@ function registerUploadUrlTools(server, client2) {
37226
37550
  `minting a signed upload URL for ${args.filename} (${args.content_type})`,
37227
37551
  () => minter.mint(args.filename, args.content_type)
37228
37552
  );
37229
- return textResult({
37553
+ return minifiedResult({
37230
37554
  upload_url: minted.url,
37231
37555
  method: "PUT",
37232
37556
  r2_key: minted.key,
@@ -37297,7 +37621,7 @@ function registerLibraryTools(server, client2) {
37297
37621
  () => resolveLibraryImage(client2, args, true)
37298
37622
  );
37299
37623
  const record2 = await library.saveCharacter({ name: args.name, description: args.description, image });
37300
- return textResult({
37624
+ return minifiedResult({
37301
37625
  saved: "character",
37302
37626
  name: record2.name,
37303
37627
  description: record2.description,
@@ -37317,7 +37641,7 @@ function registerLibraryTools(server, client2) {
37317
37641
  },
37318
37642
  async () => {
37319
37643
  const characters = await library.listCharacters();
37320
- return textResult({
37644
+ return minifiedResult({
37321
37645
  count: characters.length,
37322
37646
  characters: characters.map((c) => ({
37323
37647
  name: c.name,
@@ -37344,7 +37668,7 @@ function registerLibraryTools(server, client2) {
37344
37668
  if (!existed) {
37345
37669
  throw new McpToolError(`No saved character "${args.name}". List what exists with gemini_list_characters.`);
37346
37670
  }
37347
- return textResult({ deleted: "character", name: args.name });
37671
+ return minifiedResult({ deleted: "character", name: args.name });
37348
37672
  }
37349
37673
  );
37350
37674
  server.registerTool(
@@ -37366,7 +37690,7 @@ function registerLibraryTools(server, client2) {
37366
37690
  () => resolveLibraryImage(client2, args, false)
37367
37691
  );
37368
37692
  const record2 = await library.saveStyle({ name: args.name, promptFragment: args.prompt_fragment, image });
37369
- return textResult({
37693
+ return minifiedResult({
37370
37694
  saved: "style",
37371
37695
  name: record2.name,
37372
37696
  prompt_fragment: record2.prompt_fragment,
@@ -37386,7 +37710,7 @@ function registerLibraryTools(server, client2) {
37386
37710
  },
37387
37711
  async () => {
37388
37712
  const styles = await library.listStyles();
37389
- return textResult({
37713
+ return minifiedResult({
37390
37714
  count: styles.length,
37391
37715
  styles: styles.map((s) => ({
37392
37716
  name: s.name,
@@ -37413,7 +37737,7 @@ function registerLibraryTools(server, client2) {
37413
37737
  if (!existed) {
37414
37738
  throw new McpToolError(`No saved style "${args.name}". List what exists with gemini_list_styles.`);
37415
37739
  }
37416
- return textResult({ deleted: "style", name: args.name });
37740
+ return minifiedResult({ deleted: "style", name: args.name });
37417
37741
  }
37418
37742
  );
37419
37743
  }
package/dist/jobs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomUUID, createHash } from 'node:crypto';
2
- import { textResult, McpToolError } from '@chrischall/mcp-utils';
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 textResult({
154
+ return minifiedResult({
155
155
  job_id: jobId,
156
156
  status,
157
157
  hint: durable
@@ -1,4 +1,4 @@
1
- import { schemaConfirm, textResult } from '@chrischall/mcp-utils';
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 textResult({
12
+ return minifiedResult({
13
13
  dryRun: true,
14
14
  action,
15
15
  method,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { McpToolError, textResult } from '@chrischall/mcp-utils';
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 textResult({
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 textResult({
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 textResult({
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 textResult({
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 textResult({ deleted: args.file_uri });
296
+ return minifiedResult({ deleted: args.file_uri });
297
297
  });
298
298
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { McpToolError, textResult } from '@chrischall/mcp-utils';
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 textResult({
131
+ return minifiedResult({
132
132
  job_id: jobId,
133
133
  status: 'running',
134
134
  interaction_id: interactionId,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { McpToolError, textResult } from '@chrischall/mcp-utils';
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 textResult({
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 textResult({
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 textResult({ deleted: 'character', name: args.name });
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 textResult({
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 textResult({
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 textResult({ deleted: 'style', name: args.name });
184
+ return minifiedResult({ deleted: 'style', name: args.name });
185
185
  });
186
186
  }
@@ -1,10 +1,10 @@
1
- import { textResult } from '@chrischall/mcp-utils';
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 textResult({ default: client.defaultModel(), models });
8
+ return minifiedResult({ default: client.defaultModel(), models });
9
9
  });
10
10
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { textResult, McpToolError, readEnvVar } from '@chrischall/mcp-utils';
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 textResult({ [`${kind}s`]: refs, ...media, ...unavailable, ...note, ...storage, ...meta });
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
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { textResult } from '@chrischall/mcp-utils';
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 textResult({
74
+ return minifiedResult({
75
75
  upload_url: minted.url,
76
76
  method: 'PUT',
77
77
  r2_key: minted.key,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
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 textResult({
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.12.0'; // x-release-please-version
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.12.0",
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.19.2",
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.12.0",
9
+ "version": "1.13.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/gemini-mcp",
14
- "version": "1.12.0",
14
+ "version": "1.13.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },