@chrischall/gemini-mcp 2.3.2 → 2.3.4
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 +2 -2
- package/README.md +5 -3
- package/dist/bundle.js +753 -186
- package/dist/client.js +146 -39
- package/dist/dotenv.js +3 -4
- package/dist/fetch-image.js +10 -4
- package/dist/images.js +15 -5
- package/dist/index.js +6 -4
- package/dist/inputs.js +1 -1
- package/dist/library.js +9 -4
- package/dist/session.js +21 -15
- package/dist/sidecar.js +31 -7
- package/dist/tenant.js +4 -9
- package/dist/tools/_confirm.js +9 -0
- package/dist/tools/files.js +3 -1
- package/dist/tools/generate.js +2 -2
- package/dist/tools/interact.js +6 -6
- package/dist/tools/jobs.js +2 -2
- package/dist/tools/library.js +4 -2
- package/dist/tools/models.js +2 -2
- package/dist/tools/music.js +2 -2
- package/dist/tools/set.js +1 -1
- package/dist/tools/shared.js +1 -1
- package/dist/tools/usage.js +1 -1
- package/dist/tools/video.js +5 -7
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +38 -3
- package/skills/gemini-mcp/SKILL.md +3 -3
package/dist/bundle.js
CHANGED
|
@@ -4988,7 +4988,7 @@ var require_memoizer = __commonJS({
|
|
|
4988
4988
|
var NONE2 = 0;
|
|
4989
4989
|
var ASSUMED2 = 1;
|
|
4990
4990
|
var PROVEN2 = 2;
|
|
4991
|
-
function isRecursive2(inst, stack,
|
|
4991
|
+
function isRecursive2(inst, stack, resolve6) {
|
|
4992
4992
|
const cached2 = recursive2.get(inst);
|
|
4993
4993
|
if (cached2 !== void 0)
|
|
4994
4994
|
return cached2 ? PROVEN2 : NONE2;
|
|
@@ -4998,7 +4998,7 @@ var require_memoizer = __commonJS({
|
|
|
4998
4998
|
let result = NONE2;
|
|
4999
4999
|
const check2 = (child) => {
|
|
5000
5000
|
if (result !== PROVEN2 && child?._zod) {
|
|
5001
|
-
const answer = isRecursive2(child, stack,
|
|
5001
|
+
const answer = isRecursive2(child, stack, resolve6);
|
|
5002
5002
|
if (answer > result)
|
|
5003
5003
|
result = answer;
|
|
5004
5004
|
}
|
|
@@ -5009,7 +5009,7 @@ var require_memoizer = __commonJS({
|
|
|
5009
5009
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5010
5010
|
if (spread && !desc.enumerable)
|
|
5011
5011
|
continue;
|
|
5012
|
-
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack,
|
|
5012
|
+
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve6) : NONE2;
|
|
5013
5013
|
if (child > answer)
|
|
5014
5014
|
answer = child;
|
|
5015
5015
|
}
|
|
@@ -5073,7 +5073,7 @@ var require_memoizer = __commonJS({
|
|
|
5073
5073
|
break;
|
|
5074
5074
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5075
5075
|
case "lazy": {
|
|
5076
|
-
const inner = def._cachedInner ?? (
|
|
5076
|
+
const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
|
|
5077
5077
|
merge2(inner ? isRecursive2(inner, stack, false) : ASSUMED2);
|
|
5078
5078
|
break;
|
|
5079
5079
|
}
|
|
@@ -26945,14 +26945,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
26945
26945
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
26946
26946
|
}
|
|
26947
26947
|
function sleep(ms, signal) {
|
|
26948
|
-
return new Promise((
|
|
26948
|
+
return new Promise((resolve6, reject) => {
|
|
26949
26949
|
if (signal?.aborted) {
|
|
26950
26950
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
26951
26951
|
return;
|
|
26952
26952
|
}
|
|
26953
26953
|
const timer = setTimeout(() => {
|
|
26954
26954
|
signal?.removeEventListener("abort", onAbort);
|
|
26955
|
-
|
|
26955
|
+
resolve6();
|
|
26956
26956
|
}, ms);
|
|
26957
26957
|
const onAbort = () => {
|
|
26958
26958
|
clearTimeout(timer);
|
|
@@ -28770,7 +28770,7 @@ var init_src_Cqbh3MYc = __esm({
|
|
|
28770
28770
|
const flowStartedAt = Date.now();
|
|
28771
28771
|
let onAbort;
|
|
28772
28772
|
let cleanupMessageId;
|
|
28773
|
-
return new Promise((
|
|
28773
|
+
return new Promise((resolve6, reject) => {
|
|
28774
28774
|
const earlyReject = (error62) => {
|
|
28775
28775
|
reject(error62);
|
|
28776
28776
|
};
|
|
@@ -28839,7 +28839,7 @@ var init_src_Cqbh3MYc = __esm({
|
|
|
28839
28839
|
}
|
|
28840
28840
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
28841
28841
|
if (decoded.kind === "input_required") {
|
|
28842
|
-
if (options?.allowInputRequired === true) return
|
|
28842
|
+
if (options?.allowInputRequired === true) return resolve6(manualInputRequiredValue(decoded));
|
|
28843
28843
|
const flow = {
|
|
28844
28844
|
codec: codec2,
|
|
28845
28845
|
request,
|
|
@@ -28851,11 +28851,11 @@ var init_src_Cqbh3MYc = __esm({
|
|
|
28851
28851
|
params
|
|
28852
28852
|
}, resultSchema, legOptions)
|
|
28853
28853
|
};
|
|
28854
|
-
return
|
|
28854
|
+
return resolve6(this._resolveNonCompleteResult(decoded, flow));
|
|
28855
28855
|
}
|
|
28856
28856
|
const result = decoded.result;
|
|
28857
28857
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
28858
|
-
if (parseResult.success)
|
|
28858
|
+
if (parseResult.success) resolve6(parseResult.data);
|
|
28859
28859
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
28860
28860
|
}, reject);
|
|
28861
28861
|
});
|
|
@@ -31674,7 +31674,7 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
31674
31674
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
31675
31675
|
const schOrFunc = root.refs[ref];
|
|
31676
31676
|
if (schOrFunc) return schOrFunc;
|
|
31677
|
-
let _sch =
|
|
31677
|
+
let _sch = resolve6.call(this, root, ref);
|
|
31678
31678
|
if (_sch === void 0) {
|
|
31679
31679
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
31680
31680
|
const { schemaId } = this.opts;
|
|
@@ -31700,7 +31700,7 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
31700
31700
|
function sameSchemaEnv(s1, s2) {
|
|
31701
31701
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
31702
31702
|
}
|
|
31703
|
-
function
|
|
31703
|
+
function resolve6(root, ref) {
|
|
31704
31704
|
let sch;
|
|
31705
31705
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
31706
31706
|
return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
|
|
@@ -32150,7 +32150,7 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
32150
32150
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
32151
32151
|
return uri;
|
|
32152
32152
|
}
|
|
32153
|
-
function
|
|
32153
|
+
function resolve6(baseURI, relativeURI, options) {
|
|
32154
32154
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
32155
32155
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
32156
32156
|
schemelessOptions.skipEscape = true;
|
|
@@ -32324,7 +32324,7 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
32324
32324
|
const fastUri = {
|
|
32325
32325
|
SCHEMES,
|
|
32326
32326
|
normalize,
|
|
32327
|
-
resolve:
|
|
32327
|
+
resolve: resolve6,
|
|
32328
32328
|
resolveComponent,
|
|
32329
32329
|
equal,
|
|
32330
32330
|
serialize,
|
|
@@ -38107,7 +38107,7 @@ var init_stdio = __esm({
|
|
|
38107
38107
|
}
|
|
38108
38108
|
send(message) {
|
|
38109
38109
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
38110
|
-
return new Promise((
|
|
38110
|
+
return new Promise((resolve6, reject) => {
|
|
38111
38111
|
const json2 = serializeMessage(message);
|
|
38112
38112
|
let settled = false;
|
|
38113
38113
|
const onError = (error62) => {
|
|
@@ -38122,14 +38122,14 @@ var init_stdio = __esm({
|
|
|
38122
38122
|
settled = true;
|
|
38123
38123
|
this._stdout.off("error", onError);
|
|
38124
38124
|
this._stdout.off("drain", onDrain);
|
|
38125
|
-
|
|
38125
|
+
resolve6();
|
|
38126
38126
|
};
|
|
38127
38127
|
this._stdout.once("error", onError);
|
|
38128
38128
|
if (this._stdout.write(json2)) {
|
|
38129
38129
|
if (settled) return;
|
|
38130
38130
|
settled = true;
|
|
38131
38131
|
this._stdout.off("error", onError);
|
|
38132
|
-
|
|
38132
|
+
resolve6();
|
|
38133
38133
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
38134
38134
|
});
|
|
38135
38135
|
}
|
|
@@ -38183,14 +38183,14 @@ var init_stdio = __esm({
|
|
|
38183
38183
|
*/
|
|
38184
38184
|
async whenRequestsAnswered(timeoutMs) {
|
|
38185
38185
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
38186
|
-
return await new Promise((
|
|
38186
|
+
return await new Promise((resolve6) => {
|
|
38187
38187
|
const waiter = () => {
|
|
38188
38188
|
clearTimeout(timer);
|
|
38189
|
-
|
|
38189
|
+
resolve6(true);
|
|
38190
38190
|
};
|
|
38191
38191
|
const timer = setTimeout(() => {
|
|
38192
38192
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
38193
|
-
|
|
38193
|
+
resolve6(false);
|
|
38194
38194
|
}, timeoutMs);
|
|
38195
38195
|
this._drainWaiters.push(waiter);
|
|
38196
38196
|
});
|
|
@@ -38231,6 +38231,30 @@ var init_dist = __esm({
|
|
|
38231
38231
|
});
|
|
38232
38232
|
|
|
38233
38233
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
38234
|
+
function firstDeclared(err, read) {
|
|
38235
|
+
let cur = err;
|
|
38236
|
+
for (let depth = 0; depth < DECLARATION_CAUSE_DEPTH && cur !== null && typeof cur === "object"; depth++) {
|
|
38237
|
+
const found = read(cur);
|
|
38238
|
+
if (found !== void 0)
|
|
38239
|
+
return found;
|
|
38240
|
+
cur = cur.cause;
|
|
38241
|
+
}
|
|
38242
|
+
return void 0;
|
|
38243
|
+
}
|
|
38244
|
+
function errorStatusOf(err) {
|
|
38245
|
+
return firstDeclared(err, (link) => {
|
|
38246
|
+
const e = link;
|
|
38247
|
+
if (typeof e.status === "number")
|
|
38248
|
+
return e.status;
|
|
38249
|
+
return typeof e.statusCode === "number" ? e.statusCode : void 0;
|
|
38250
|
+
});
|
|
38251
|
+
}
|
|
38252
|
+
function errorKindOf(err) {
|
|
38253
|
+
return firstDeclared(err, (link) => {
|
|
38254
|
+
const k = link.kind;
|
|
38255
|
+
return typeof k === "string" && MCP_TOOL_ERROR_KINDS.has(k) ? k : void 0;
|
|
38256
|
+
});
|
|
38257
|
+
}
|
|
38234
38258
|
function redactJsonCookie(quote) {
|
|
38235
38259
|
const stringRe = quote === '"' ? DQ_STRING_RE : SQ_STRING_RE;
|
|
38236
38260
|
return (_m, prefix, key, value) => {
|
|
@@ -38258,21 +38282,82 @@ function messageOf(err) {
|
|
|
38258
38282
|
return err.message;
|
|
38259
38283
|
return String(err);
|
|
38260
38284
|
}
|
|
38261
|
-
var DEFAULT_ERROR_MESSAGE_MAX, McpToolError, HEADER_NAME_RUN, BEARER_RE, JWT_RE, BASIC_AUTH_RE, SET_COOKIE_RE, COOKIE_HEADER_RE, API_KEY_RE, GOOGLE_OAUTH_TOKEN_RE, QUERY_SECRET_RE, OAUTH_CODE_RE, FORM_PASSWORD_RE, HEADER_SECRET_RE, AWS_SIGV4_RE, JSON_SECRET_KEYS, JSON_SECRET_DQ_RE, JSON_SECRET_SQ_RE, JSON_SECRET_NUM_RE, JSON_COOKIE_DQ_RE, JSON_COOKIE_SQ_RE, DQ_STRING_RE, SQ_STRING_RE, ERROR_REDACTION_INPUT_MAX;
|
|
38285
|
+
var DEFAULT_ERROR_MESSAGE_MAX, MCP_TOOL_ERROR_KINDS, McpToolError, DECLARATION_CAUSE_DEPTH, UPSTREAM_BODY_PHRASE, UpstreamFormatError, HEADER_NAME_RUN, BEARER_RE, JWT_RE, BASIC_AUTH_RE, SET_COOKIE_RE, COOKIE_HEADER_RE, API_KEY_RE, GOOGLE_OAUTH_TOKEN_RE, QUERY_SECRET_RE, OAUTH_CODE_RE, FORM_PASSWORD_RE, HEADER_SECRET_RE, AWS_SIGV4_RE, JSON_SECRET_KEYS, JSON_SECRET_DQ_RE, JSON_SECRET_SQ_RE, JSON_SECRET_NUM_RE, JSON_COOKIE_DQ_RE, JSON_COOKIE_SQ_RE, DQ_STRING_RE, SQ_STRING_RE, ERROR_REDACTION_INPUT_MAX;
|
|
38262
38286
|
var init_errors = __esm({
|
|
38263
38287
|
"node_modules/@chrischall/mcp-utils/dist/errors/index.js"() {
|
|
38264
38288
|
DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
38289
|
+
MCP_TOOL_ERROR_KINDS = /* @__PURE__ */ new Set([
|
|
38290
|
+
"no_credential",
|
|
38291
|
+
"credential_rejected",
|
|
38292
|
+
"edge_blocked",
|
|
38293
|
+
"session_expired",
|
|
38294
|
+
"verification_pending",
|
|
38295
|
+
"timeout",
|
|
38296
|
+
"http",
|
|
38297
|
+
"transport",
|
|
38298
|
+
"too_large",
|
|
38299
|
+
"unknown"
|
|
38300
|
+
]);
|
|
38265
38301
|
McpToolError = class extends Error {
|
|
38266
38302
|
/** Actionable remediation text, when one applies. */
|
|
38267
38303
|
hint;
|
|
38304
|
+
/** The upstream HTTP status this failure came from, when known. */
|
|
38305
|
+
status;
|
|
38306
|
+
/** What kind of failure this is, when the thrower knows. */
|
|
38307
|
+
kind;
|
|
38268
38308
|
constructor(message, opts) {
|
|
38269
38309
|
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
38270
38310
|
this.name = "McpToolError";
|
|
38271
38311
|
if (opts?.hint !== void 0)
|
|
38272
38312
|
this.hint = opts.hint;
|
|
38313
|
+
if (opts?.status !== void 0)
|
|
38314
|
+
this.status = opts.status;
|
|
38315
|
+
if (opts?.kind !== void 0)
|
|
38316
|
+
this.kind = opts.kind;
|
|
38273
38317
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
38274
38318
|
}
|
|
38275
38319
|
};
|
|
38320
|
+
DECLARATION_CAUSE_DEPTH = 4;
|
|
38321
|
+
UPSTREAM_BODY_PHRASE = {
|
|
38322
|
+
"non-json": "a non-JSON response",
|
|
38323
|
+
empty: "an empty body",
|
|
38324
|
+
null: "null",
|
|
38325
|
+
array: "an array",
|
|
38326
|
+
object: "an object",
|
|
38327
|
+
string: "a string",
|
|
38328
|
+
number: "a number",
|
|
38329
|
+
boolean: "a boolean"
|
|
38330
|
+
};
|
|
38331
|
+
UpstreamFormatError = class extends McpToolError {
|
|
38332
|
+
/** What the body turned out to be. */
|
|
38333
|
+
received;
|
|
38334
|
+
/** The shape the caller required, when it required one. */
|
|
38335
|
+
expected;
|
|
38336
|
+
/** The response `Content-Type`, when known. (`status` is the base class's.) */
|
|
38337
|
+
contentType;
|
|
38338
|
+
constructor(details) {
|
|
38339
|
+
const service = details.service ?? "The upstream service";
|
|
38340
|
+
const request = details.method !== void 0 && details.path !== void 0 ? `${details.method.toUpperCase()} ${details.path}` : void 0;
|
|
38341
|
+
const meta3 = [
|
|
38342
|
+
details.status !== void 0 ? `HTTP ${details.status}` : void 0,
|
|
38343
|
+
details.contentType || void 0
|
|
38344
|
+
].filter((p) => p !== void 0);
|
|
38345
|
+
const what = details.expected !== void 0 ? `${UPSTREAM_BODY_PHRASE[details.received]} where a JSON ${details.expected} was expected` + (request ? ` for ${request}` : "") : `${UPSTREAM_BODY_PHRASE[details.received]}` + (request ? ` to ${request}` : "");
|
|
38346
|
+
const message = `${service} returned ${what}${meta3.length > 0 ? ` (${meta3.join(", ")})` : ""}.`;
|
|
38347
|
+
const hint = details.received === "non-json" ? "The service answered with something other than JSON, usually a sign-in page or interstitial, or its API has changed. Check the session, then retry." : "The service answered with a body of the wrong shape: the resource may be missing or its API may have changed.";
|
|
38348
|
+
super(truncateErrorMessage(message), {
|
|
38349
|
+
hint,
|
|
38350
|
+
cause: details.cause,
|
|
38351
|
+
...details.status !== void 0 ? { status: details.status } : {}
|
|
38352
|
+
});
|
|
38353
|
+
this.name = "UpstreamFormatError";
|
|
38354
|
+
this.received = details.received;
|
|
38355
|
+
if (details.expected !== void 0)
|
|
38356
|
+
this.expected = details.expected;
|
|
38357
|
+
if (details.contentType)
|
|
38358
|
+
this.contentType = details.contentType;
|
|
38359
|
+
}
|
|
38360
|
+
};
|
|
38276
38361
|
HEADER_NAME_RUN = "[a-z0-9_-]{0,40}";
|
|
38277
38362
|
BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
38278
38363
|
JWT_RE = /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/g;
|
|
@@ -38294,7 +38379,7 @@ var init_errors = __esm({
|
|
|
38294
38379
|
// webhook signing secret (Stripe-style)
|
|
38295
38380
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
38296
38381
|
GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
38297
|
-
QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38382
|
+
QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38298
38383
|
OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
38299
38384
|
FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
38300
38385
|
HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -38305,6 +38390,8 @@ var init_errors = __esm({
|
|
|
38305
38390
|
"api[_-]?secret",
|
|
38306
38391
|
"(?:x[_-])?api[_-]?key",
|
|
38307
38392
|
"private[_-]?key",
|
|
38393
|
+
"code[_-]?verifier",
|
|
38394
|
+
// PKCE; a bare `verifier` key is too generic for JSON
|
|
38308
38395
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
38309
38396
|
// bounded like HEADER_SECRET_RE
|
|
38310
38397
|
"(?:proxy-)?authorization",
|
|
@@ -42820,7 +42907,7 @@ function isRef(value) {
|
|
|
42820
42907
|
function cloneIssues(issues) {
|
|
42821
42908
|
return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
|
|
42822
42909
|
}
|
|
42823
|
-
function isRecursive(inst, stack,
|
|
42910
|
+
function isRecursive(inst, stack, resolve6) {
|
|
42824
42911
|
const cached2 = recursive.get(inst);
|
|
42825
42912
|
if (cached2 !== void 0)
|
|
42826
42913
|
return cached2 ? PROVEN : NONE;
|
|
@@ -42830,7 +42917,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
42830
42917
|
let result = NONE;
|
|
42831
42918
|
const check2 = (child) => {
|
|
42832
42919
|
if (result !== PROVEN && child?._zod) {
|
|
42833
|
-
const answer = isRecursive(child, stack,
|
|
42920
|
+
const answer = isRecursive(child, stack, resolve6);
|
|
42834
42921
|
if (answer > result)
|
|
42835
42922
|
result = answer;
|
|
42836
42923
|
}
|
|
@@ -42841,7 +42928,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
42841
42928
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
42842
42929
|
if (spread && !desc.enumerable)
|
|
42843
42930
|
continue;
|
|
42844
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
42931
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve6) : NONE;
|
|
42845
42932
|
if (child > answer)
|
|
42846
42933
|
answer = child;
|
|
42847
42934
|
}
|
|
@@ -42905,7 +42992,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
42905
42992
|
break;
|
|
42906
42993
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
42907
42994
|
case "lazy": {
|
|
42908
|
-
const inner = def._cachedInner ?? (
|
|
42995
|
+
const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
|
|
42909
42996
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
42910
42997
|
break;
|
|
42911
42998
|
}
|
|
@@ -58852,6 +58939,15 @@ ${JSON.stringify(preview, null, 2)}`,
|
|
|
58852
58939
|
};
|
|
58853
58940
|
const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
|
|
58854
58941
|
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
58942
|
+
if (options.elicitation === false) {
|
|
58943
|
+
return textResult({
|
|
58944
|
+
confirmed: false,
|
|
58945
|
+
dispatched: false,
|
|
58946
|
+
action: options.action,
|
|
58947
|
+
reason: "confirmation-unsupported",
|
|
58948
|
+
note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
|
|
58949
|
+
});
|
|
58950
|
+
}
|
|
58855
58951
|
if (response.kind === "missing") {
|
|
58856
58952
|
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
58857
58953
|
return textResult({
|
|
@@ -58882,7 +58978,7 @@ ${JSON.stringify(preview, null, 2)}`,
|
|
|
58882
58978
|
note: "Nothing was changed because the confirmation was declined, cancelled, or left unchecked."
|
|
58883
58979
|
});
|
|
58884
58980
|
}
|
|
58885
|
-
var DEFAULT_REQUEST_KEY, DEFAULT_CONFIRMATION_LABEL, UNSUPPORTED_NOTE, STATE_PREFIX;
|
|
58981
|
+
var DEFAULT_REQUEST_KEY, DEFAULT_CONFIRMATION_LABEL, UNSUPPORTED_NOTE, DISABLED_NOTE, STATE_PREFIX;
|
|
58886
58982
|
var init_confirmation = __esm({
|
|
58887
58983
|
"node_modules/@chrischall/mcp-utils/dist/server/confirmation.js"() {
|
|
58888
58984
|
init_dist();
|
|
@@ -58893,6 +58989,7 @@ var init_confirmation = __esm({
|
|
|
58893
58989
|
DEFAULT_REQUEST_KEY = "confirmation";
|
|
58894
58990
|
DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
58895
58991
|
UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
|
|
58992
|
+
DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
|
|
58896
58993
|
STATE_PREFIX = "mcpu.confirm.v1.";
|
|
58897
58994
|
}
|
|
58898
58995
|
});
|
|
@@ -59046,7 +59143,7 @@ async function tokenConfirmation(action, fb) {
|
|
|
59046
59143
|
}
|
|
59047
59144
|
async function requireConfirmationWithFallback(ctx, options) {
|
|
59048
59145
|
const { tokenFallback, ...confirmation } = options;
|
|
59049
|
-
if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
|
|
59146
|
+
if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
|
|
59050
59147
|
return tokenConfirmation(options.action, tokenFallback);
|
|
59051
59148
|
}
|
|
59052
59149
|
return requireConfirmation(ctx, confirmation);
|
|
@@ -59085,7 +59182,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
59085
59182
|
if (typeof raw === "string") {
|
|
59086
59183
|
const trimmed = raw.trim();
|
|
59087
59184
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
59088
|
-
return trimmed;
|
|
59185
|
+
return opts.trim === false ? raw : trimmed;
|
|
59089
59186
|
}
|
|
59090
59187
|
}
|
|
59091
59188
|
return opts.default;
|
|
@@ -59223,6 +59320,20 @@ function readConfirmMode(env = process.env) {
|
|
|
59223
59320
|
}
|
|
59224
59321
|
return "refuse";
|
|
59225
59322
|
}
|
|
59323
|
+
function readConfirmElicitation(env = process.env) {
|
|
59324
|
+
const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
|
|
59325
|
+
if (!raw || raw === "on")
|
|
59326
|
+
return "on";
|
|
59327
|
+
if (raw === "off")
|
|
59328
|
+
return "off";
|
|
59329
|
+
const key = `elicitation:${raw}`;
|
|
59330
|
+
if (!warned.has(key)) {
|
|
59331
|
+
warned.add(key);
|
|
59332
|
+
process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
|
|
59333
|
+
`);
|
|
59334
|
+
}
|
|
59335
|
+
return "on";
|
|
59336
|
+
}
|
|
59226
59337
|
function readConfirmTtl(env) {
|
|
59227
59338
|
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
59228
59339
|
if (!raw)
|
|
@@ -59272,19 +59383,29 @@ function boundArgs(args) {
|
|
|
59272
59383
|
}
|
|
59273
59384
|
function confirmationFromEnv(options) {
|
|
59274
59385
|
const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
|
|
59386
|
+
if (args === void 0 || args === null) {
|
|
59387
|
+
throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
|
|
59388
|
+
}
|
|
59275
59389
|
const spent = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
59276
59390
|
const mode = readConfirmMode(env);
|
|
59277
59391
|
const ttlSeconds = readConfirmTtl(env);
|
|
59278
|
-
const
|
|
59279
|
-
const
|
|
59392
|
+
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
59393
|
+
const bound = boundArgs(args);
|
|
59394
|
+
const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
|
|
59395
|
+
const bindingAdded = rest.binding ? rest : {
|
|
59396
|
+
...rest,
|
|
59397
|
+
binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
|
|
59398
|
+
};
|
|
59399
|
+
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
59280
59400
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
59281
|
-
const
|
|
59401
|
+
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
59402
|
+
const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
|
|
59282
59403
|
return {
|
|
59283
59404
|
...confirmation,
|
|
59284
59405
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
59285
59406
|
};
|
|
59286
59407
|
}
|
|
59287
|
-
const boundSubject =
|
|
59408
|
+
const boundSubject = async () => {
|
|
59288
59409
|
const read = await subject();
|
|
59289
59410
|
if ("content" in read)
|
|
59290
59411
|
return read;
|
|
@@ -59304,7 +59425,7 @@ function confirmationFromEnv(options) {
|
|
|
59304
59425
|
}
|
|
59305
59426
|
};
|
|
59306
59427
|
}
|
|
59307
|
-
var MODES, DEFAULT_TTL_SECONDS2, warned, processKey, fileStores, CONFIRM_TOKEN_AUTO_INSTRUCTION, REFUSE_HINT, BAD_TTL_HINT;
|
|
59428
|
+
var MODES, DEFAULT_TTL_SECONDS2, warned, processKey, fileStores, CONFIRM_TOKEN_AUTO_INSTRUCTION, REFUSE_HINT, ELICITATION_OFF_HINT, BAD_TTL_HINT;
|
|
59308
59429
|
var init_confirm_env = __esm({
|
|
59309
59430
|
"node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js"() {
|
|
59310
59431
|
init_config();
|
|
@@ -59316,6 +59437,7 @@ var init_confirm_env = __esm({
|
|
|
59316
59437
|
fileStores = /* @__PURE__ */ new Map();
|
|
59317
59438
|
CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
59318
59439
|
REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
59440
|
+
ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
|
|
59319
59441
|
BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
|
|
59320
59442
|
}
|
|
59321
59443
|
});
|
|
@@ -59336,12 +59458,14 @@ var init_merge_write = __esm({
|
|
|
59336
59458
|
|
|
59337
59459
|
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
59338
59460
|
function hintResultOrRethrow(err) {
|
|
59461
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
59462
|
+
throw err;
|
|
59339
59463
|
if (err instanceof McpToolError && err.hint) {
|
|
59340
59464
|
return errorResult(`${err.message}
|
|
59341
59465
|
|
|
59342
59466
|
Hint: ${err.hint}`);
|
|
59343
59467
|
}
|
|
59344
|
-
|
|
59468
|
+
return errorResult(messageOf(err));
|
|
59345
59469
|
}
|
|
59346
59470
|
function mcpRequestFrom(args) {
|
|
59347
59471
|
const ctx = args.at(-1);
|
|
@@ -59684,10 +59808,12 @@ var init_write = __esm({
|
|
|
59684
59808
|
|
|
59685
59809
|
// node_modules/@chrischall/mcp-utils/dist/fs/output.js
|
|
59686
59810
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3 } from "node:fs";
|
|
59811
|
+
import { homedir as homedir2 } from "node:os";
|
|
59812
|
+
import { join as join5 } from "node:path";
|
|
59687
59813
|
function resolveOutputDir(perCall, envVar, opts = {}) {
|
|
59688
59814
|
const raw = perCall ?? readEnvVar(envVar, opts.env ? { env: opts.env } : {});
|
|
59689
59815
|
if (!raw)
|
|
59690
|
-
return
|
|
59816
|
+
return defaultOutputDir(envVar, opts.name);
|
|
59691
59817
|
const dir = expandPath(raw);
|
|
59692
59818
|
if (perCall !== void 0 && opts.allowedRoots) {
|
|
59693
59819
|
assertPathWithinRoots(dir, opts.allowedRoots);
|
|
@@ -59698,9 +59824,23 @@ function resolveOutputDir(perCall, envVar, opts = {}) {
|
|
|
59698
59824
|
mkdirSync3(dir, { recursive: true });
|
|
59699
59825
|
return dir;
|
|
59700
59826
|
}
|
|
59827
|
+
function defaultOutputDir(envVar, name) {
|
|
59828
|
+
if (name === void 0) {
|
|
59829
|
+
throw new McpToolError(`No output directory is configured: ${envVar} is not set.`, {
|
|
59830
|
+
hint: `Set ${envVar} to a writable directory, or pass an output directory with the call.`
|
|
59831
|
+
});
|
|
59832
|
+
}
|
|
59833
|
+
if (!name || name === "." || name === ".." || /[/\\]/.test(name)) {
|
|
59834
|
+
throw new TypeError(`resolveOutputDir: name must be a single path segment, got ${JSON.stringify(name)}`);
|
|
59835
|
+
}
|
|
59836
|
+
const dir = join5(homedir2(), "Downloads", name);
|
|
59837
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
59838
|
+
return dir;
|
|
59839
|
+
}
|
|
59701
59840
|
var init_output = __esm({
|
|
59702
59841
|
"node_modules/@chrischall/mcp-utils/dist/fs/output.js"() {
|
|
59703
59842
|
init_config();
|
|
59843
|
+
init_errors();
|
|
59704
59844
|
init_confine();
|
|
59705
59845
|
init_write();
|
|
59706
59846
|
}
|
|
@@ -59860,6 +60000,7 @@ var init_scrape = __esm({
|
|
|
59860
60000
|
// node_modules/@chrischall/mcp-utils/dist/http/throttle.js
|
|
59861
60001
|
var init_throttle = __esm({
|
|
59862
60002
|
"node_modules/@chrischall/mcp-utils/dist/http/throttle.js"() {
|
|
60003
|
+
init_cancel();
|
|
59863
60004
|
}
|
|
59864
60005
|
});
|
|
59865
60006
|
|
|
@@ -59885,6 +60026,113 @@ var init_net_atoms = __esm({
|
|
|
59885
60026
|
}
|
|
59886
60027
|
});
|
|
59887
60028
|
|
|
60029
|
+
// node_modules/@chrischall/mcp-utils/dist/http/body-limit.js
|
|
60030
|
+
async function readBytesCapped(res, maxBytes, service, bounded) {
|
|
60031
|
+
if (maxBytes !== void 0) {
|
|
60032
|
+
const declared = Number(res.headers.get("content-length") ?? NaN);
|
|
60033
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
60034
|
+
cancelStream(res.body);
|
|
60035
|
+
throw new ResponseTooLargeError(service, maxBytes);
|
|
60036
|
+
}
|
|
60037
|
+
}
|
|
60038
|
+
if (!res.body)
|
|
60039
|
+
return new Uint8Array(0);
|
|
60040
|
+
const reader = res.body.getReader();
|
|
60041
|
+
const chunks = [];
|
|
60042
|
+
let total = 0;
|
|
60043
|
+
try {
|
|
60044
|
+
for (; ; ) {
|
|
60045
|
+
const { done, value } = await bounded(reader.read());
|
|
60046
|
+
if (done)
|
|
60047
|
+
break;
|
|
60048
|
+
total += value.byteLength;
|
|
60049
|
+
if (maxBytes !== void 0 && total > maxBytes)
|
|
60050
|
+
throw new ResponseTooLargeError(service, maxBytes);
|
|
60051
|
+
chunks.push(value);
|
|
60052
|
+
}
|
|
60053
|
+
} catch (err) {
|
|
60054
|
+
reader.cancel().catch(() => {
|
|
60055
|
+
});
|
|
60056
|
+
throw err;
|
|
60057
|
+
}
|
|
60058
|
+
const out = new Uint8Array(total);
|
|
60059
|
+
let offset = 0;
|
|
60060
|
+
for (const c of chunks) {
|
|
60061
|
+
out.set(c, offset);
|
|
60062
|
+
offset += c.byteLength;
|
|
60063
|
+
}
|
|
60064
|
+
return out;
|
|
60065
|
+
}
|
|
60066
|
+
function cancelStream(body) {
|
|
60067
|
+
try {
|
|
60068
|
+
body?.cancel().catch(() => {
|
|
60069
|
+
});
|
|
60070
|
+
} catch {
|
|
60071
|
+
}
|
|
60072
|
+
}
|
|
60073
|
+
var ResponseTooLargeError;
|
|
60074
|
+
var init_body_limit = __esm({
|
|
60075
|
+
"node_modules/@chrischall/mcp-utils/dist/http/body-limit.js"() {
|
|
60076
|
+
init_errors();
|
|
60077
|
+
ResponseTooLargeError = class extends McpToolError {
|
|
60078
|
+
maxBytes;
|
|
60079
|
+
constructor(service, maxBytes) {
|
|
60080
|
+
super(`Response from ${service} is larger than the ${maxBytes}-byte limit.`, {
|
|
60081
|
+
hint: "Raise maxBytes (or the client's maxResponseBytes) if a response this large is expected.",
|
|
60082
|
+
kind: "too_large"
|
|
60083
|
+
});
|
|
60084
|
+
this.name = "ResponseTooLargeError";
|
|
60085
|
+
this.maxBytes = maxBytes;
|
|
60086
|
+
}
|
|
60087
|
+
};
|
|
60088
|
+
}
|
|
60089
|
+
});
|
|
60090
|
+
|
|
60091
|
+
// node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js
|
|
60092
|
+
var init_fetch_bounded = __esm({
|
|
60093
|
+
"node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js"() {
|
|
60094
|
+
init_cancel();
|
|
60095
|
+
init_errors();
|
|
60096
|
+
init_body_limit();
|
|
60097
|
+
init_http();
|
|
60098
|
+
}
|
|
60099
|
+
});
|
|
60100
|
+
|
|
60101
|
+
// node_modules/@chrischall/mcp-utils/dist/http/url-safety.js
|
|
60102
|
+
function isDotSegment(segment) {
|
|
60103
|
+
const s = segment.replace(/%2e/gi, ".");
|
|
60104
|
+
return s === "." || s === "..";
|
|
60105
|
+
}
|
|
60106
|
+
function pathPartOf(path) {
|
|
60107
|
+
const end = path.search(/[?#]/);
|
|
60108
|
+
return end === -1 ? path : path.slice(0, end);
|
|
60109
|
+
}
|
|
60110
|
+
function findPathHazard(path) {
|
|
60111
|
+
const pathPart = pathPartOf(path);
|
|
60112
|
+
if (/[\x00-\x1f\x7f]/.test(pathPart))
|
|
60113
|
+
return "control character";
|
|
60114
|
+
if (pathPart.endsWith(" "))
|
|
60115
|
+
return "trailing space";
|
|
60116
|
+
if (pathPart.includes("\\"))
|
|
60117
|
+
return "backslash";
|
|
60118
|
+
if (pathPart.split("/").some(isDotSegment))
|
|
60119
|
+
return "dot segment";
|
|
60120
|
+
return void 0;
|
|
60121
|
+
}
|
|
60122
|
+
var RedirectRefusedError;
|
|
60123
|
+
var init_url_safety = __esm({
|
|
60124
|
+
"node_modules/@chrischall/mcp-utils/dist/http/url-safety.js"() {
|
|
60125
|
+
init_errors();
|
|
60126
|
+
init_config();
|
|
60127
|
+
RedirectRefusedError = class extends McpToolError {
|
|
60128
|
+
constructor(message) {
|
|
60129
|
+
super(message);
|
|
60130
|
+
this.name = "RedirectRefusedError";
|
|
60131
|
+
}
|
|
60132
|
+
};
|
|
60133
|
+
}
|
|
60134
|
+
});
|
|
60135
|
+
|
|
59888
60136
|
// node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js
|
|
59889
60137
|
function retryAfterToMs(header, now = Date.now()) {
|
|
59890
60138
|
if (header == null)
|
|
@@ -59905,6 +60153,14 @@ var init_retry_after = __esm({
|
|
|
59905
60153
|
});
|
|
59906
60154
|
|
|
59907
60155
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
60156
|
+
function cancelBody(res) {
|
|
60157
|
+
try {
|
|
60158
|
+
const body = res.body;
|
|
60159
|
+
body?.cancel?.()?.catch(() => {
|
|
60160
|
+
});
|
|
60161
|
+
} catch {
|
|
60162
|
+
}
|
|
60163
|
+
}
|
|
59908
60164
|
function headerOf(headers, name) {
|
|
59909
60165
|
if (headers === void 0)
|
|
59910
60166
|
return void 0;
|
|
@@ -59930,6 +60186,49 @@ function detectEdgeBlock(input2) {
|
|
|
59930
60186
|
}
|
|
59931
60187
|
return null;
|
|
59932
60188
|
}
|
|
60189
|
+
function jsonKind(value) {
|
|
60190
|
+
if (value === null)
|
|
60191
|
+
return "null";
|
|
60192
|
+
if (Array.isArray(value))
|
|
60193
|
+
return "array";
|
|
60194
|
+
return typeof value;
|
|
60195
|
+
}
|
|
60196
|
+
function parseJsonBody(text, opts = {}) {
|
|
60197
|
+
const contentType = opts.contentType ?? headerOf(opts.headers, "content-type");
|
|
60198
|
+
const details = {
|
|
60199
|
+
service: opts.service,
|
|
60200
|
+
method: opts.method,
|
|
60201
|
+
path: opts.path,
|
|
60202
|
+
status: opts.status,
|
|
60203
|
+
contentType,
|
|
60204
|
+
expected: opts.expect
|
|
60205
|
+
};
|
|
60206
|
+
if (text.trim().length === 0) {
|
|
60207
|
+
if (opts.expect === void 0)
|
|
60208
|
+
return void 0;
|
|
60209
|
+
throw new UpstreamFormatError({ ...details, received: "empty" });
|
|
60210
|
+
}
|
|
60211
|
+
let value;
|
|
60212
|
+
try {
|
|
60213
|
+
value = JSON.parse(text);
|
|
60214
|
+
} catch (cause) {
|
|
60215
|
+
const edge = detectEdgeBlock({ body: text, headers: opts.headers, status: opts.status });
|
|
60216
|
+
if (edge) {
|
|
60217
|
+
throw new EdgeBlockedError(opts.status ?? 200, edge.vendor, {
|
|
60218
|
+
service: opts.service ?? "the upstream service",
|
|
60219
|
+
method: opts.method,
|
|
60220
|
+
path: opts.path
|
|
60221
|
+
});
|
|
60222
|
+
}
|
|
60223
|
+
throw new UpstreamFormatError({ ...details, expected: void 0, received: "non-json", cause });
|
|
60224
|
+
}
|
|
60225
|
+
if (opts.expect !== void 0) {
|
|
60226
|
+
const kind = jsonKind(value);
|
|
60227
|
+
if (kind !== opts.expect)
|
|
60228
|
+
throw new UpstreamFormatError({ ...details, received: kind });
|
|
60229
|
+
}
|
|
60230
|
+
return value;
|
|
60231
|
+
}
|
|
59933
60232
|
function httpError(res, text, method, path, service) {
|
|
59934
60233
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
59935
60234
|
if (edge)
|
|
@@ -59980,13 +60279,26 @@ function createApiClient(opts) {
|
|
|
59980
60279
|
const sleep2 = opts.sleep ?? defaultSleep;
|
|
59981
60280
|
const unauthorized = () => opts.onUnauthorized ? opts.onUnauthorized() : new UnauthorizedError(service);
|
|
59982
60281
|
const retryStatuses = retry.statuses ?? [429];
|
|
59983
|
-
const timeoutMs = opts.timeout;
|
|
60282
|
+
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
60283
|
+
const redirectMode = opts.redirect;
|
|
60284
|
+
const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
60285
|
+
const reportWriteOutcome = opts.writeOutcomeUnknown !== false;
|
|
60286
|
+
const defaultMaxBytes = checkMaxBytes("maxResponseBytes", opts.maxResponseBytes);
|
|
60287
|
+
if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {
|
|
60288
|
+
throw new TypeError(`createApiClient: maxRedirects must be a non-negative integer, got ${String(maxRedirects)}.`);
|
|
60289
|
+
}
|
|
59984
60290
|
const authHeader = (token) => !token ? {} : opts.tokenHeader ? { [opts.tokenHeader]: token } : { Authorization: `Bearer ${token}` };
|
|
60291
|
+
const transportFailures = /* @__PURE__ */ new WeakSet();
|
|
60292
|
+
const markTransportFailure = (err) => {
|
|
60293
|
+
if (typeof err === "object" && err !== null && err.name !== "AbortError") {
|
|
60294
|
+
transportFailures.add(err);
|
|
60295
|
+
}
|
|
60296
|
+
};
|
|
59985
60297
|
async function withTimeout(run) {
|
|
59986
|
-
if (timeoutMs
|
|
60298
|
+
if (timeoutMs <= 0) {
|
|
59987
60299
|
const res2 = await run(withAmbientCancellation(void 0));
|
|
59988
60300
|
return { res: res2, done: () => {
|
|
59989
|
-
}, expired: void 0 };
|
|
60301
|
+
}, expired: void 0, timedOut: () => false };
|
|
59990
60302
|
}
|
|
59991
60303
|
const controller = new AbortController();
|
|
59992
60304
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -60007,16 +60319,11 @@ function createApiClient(opts) {
|
|
|
60007
60319
|
});
|
|
60008
60320
|
expired.catch(() => {
|
|
60009
60321
|
});
|
|
60010
|
-
return { res, done: () => clearTimeout(timer), expired };
|
|
60322
|
+
return { res, done: () => clearTimeout(timer), expired, timedOut: () => controller.signal.aborted };
|
|
60011
60323
|
}
|
|
60012
60324
|
function discard(attempt) {
|
|
60013
60325
|
attempt.done();
|
|
60014
|
-
|
|
60015
|
-
const body = attempt.res.body;
|
|
60016
|
-
body?.cancel?.()?.catch(() => {
|
|
60017
|
-
});
|
|
60018
|
-
} catch {
|
|
60019
|
-
}
|
|
60326
|
+
cancelBody(attempt.res);
|
|
60020
60327
|
}
|
|
60021
60328
|
async function readBody(attempt, read) {
|
|
60022
60329
|
try {
|
|
@@ -60024,15 +60331,40 @@ function createApiClient(opts) {
|
|
|
60024
60331
|
return await read(attempt.res);
|
|
60025
60332
|
return await Promise.race([read(attempt.res), attempt.expired]);
|
|
60026
60333
|
} catch (err) {
|
|
60027
|
-
|
|
60028
|
-
|
|
60029
|
-
|
|
60334
|
+
const timeout = err instanceof RequestTimeoutError || err instanceof Error && err.name === "AbortError" && attempt.timedOut();
|
|
60335
|
+
if (timeout) {
|
|
60336
|
+
cancelBody(attempt.res);
|
|
60337
|
+
throw err instanceof RequestTimeoutError ? err : new RequestTimeoutError(service, timeoutMs);
|
|
60030
60338
|
}
|
|
60339
|
+
if (!(err instanceof ResponseTooLargeError))
|
|
60340
|
+
markTransportFailure(err);
|
|
60031
60341
|
throw err;
|
|
60032
60342
|
} finally {
|
|
60033
60343
|
attempt.done();
|
|
60034
60344
|
}
|
|
60035
60345
|
}
|
|
60346
|
+
function readBytes(attempt, maxBytes) {
|
|
60347
|
+
if (maxBytes === void 0)
|
|
60348
|
+
return readBody(attempt, async (r) => new Uint8Array(await r.arrayBuffer()));
|
|
60349
|
+
const race = (p) => attempt.expired ? Promise.race([p, attempt.expired]) : p;
|
|
60350
|
+
return readBody(attempt, (r) => readBytesCapped(r, maxBytes, service, race));
|
|
60351
|
+
}
|
|
60352
|
+
async function readText(attempt, maxBytes) {
|
|
60353
|
+
if (maxBytes === void 0)
|
|
60354
|
+
return readBody(attempt, (r) => r.text());
|
|
60355
|
+
return new TextDecoder().decode(await readBytes(attempt, maxBytes));
|
|
60356
|
+
}
|
|
60357
|
+
function readErrorText(attempt, maxBytes) {
|
|
60358
|
+
return readText(attempt, maxBytes).catch((err) => {
|
|
60359
|
+
if (err instanceof ResponseTooLargeError)
|
|
60360
|
+
return "";
|
|
60361
|
+
throw err;
|
|
60362
|
+
});
|
|
60363
|
+
}
|
|
60364
|
+
function scanText(attempt, maxBytes) {
|
|
60365
|
+
return readText(attempt, maxBytes).catch(() => "");
|
|
60366
|
+
}
|
|
60367
|
+
const capOf = (opt) => opt.maxBytes !== void 0 ? checkMaxBytes("maxBytes", opt.maxBytes) : defaultMaxBytes;
|
|
60036
60368
|
const baseParts = (() => {
|
|
60037
60369
|
try {
|
|
60038
60370
|
const u = new URL(base);
|
|
@@ -60043,6 +60375,14 @@ function createApiClient(opts) {
|
|
|
60043
60375
|
})();
|
|
60044
60376
|
const baseOrigin = baseParts?.origin;
|
|
60045
60377
|
function resolveUrl2(path, query) {
|
|
60378
|
+
const hazard = findPathHazard(path);
|
|
60379
|
+
if (hazard !== void 0) {
|
|
60380
|
+
throw new Error(
|
|
60381
|
+
// Quote only the path part: this fires on hostile input, and the
|
|
60382
|
+
// query may carry values that do not belong in a log line.
|
|
60383
|
+
`Refusing request to ${service}: path ${JSON.stringify(path.split(/[?#]/, 1)[0])} contains a ${hazard}, which URL normalisation would rewrite. Build paths with apiPath\`...\` so each value is one encoded segment.`
|
|
60384
|
+
);
|
|
60385
|
+
}
|
|
60046
60386
|
const url4 = `${base}${path}${query}`;
|
|
60047
60387
|
if (baseOrigin === void 0)
|
|
60048
60388
|
return url4;
|
|
@@ -60057,23 +60397,66 @@ function createApiClient(opts) {
|
|
|
60057
60397
|
}
|
|
60058
60398
|
return url4;
|
|
60059
60399
|
}
|
|
60060
|
-
|
|
60400
|
+
function sameOriginHop(location, from) {
|
|
60401
|
+
let next;
|
|
60402
|
+
try {
|
|
60403
|
+
next = new URL(location, from);
|
|
60404
|
+
} catch {
|
|
60405
|
+
throw new RedirectRefusedError(`Refusing redirect from ${service}: the Location header is not a valid URL.`);
|
|
60406
|
+
}
|
|
60407
|
+
if (baseOrigin === void 0 || next.origin !== baseOrigin || next.username !== baseParts?.username || next.password !== baseParts?.password) {
|
|
60408
|
+
throw new RedirectRefusedError(`Refusing redirect from ${service} to ${next.origin}: it leaves the base origin ${baseOrigin ?? "(unparseable)"}, and following it would re-send the credential there.`);
|
|
60409
|
+
}
|
|
60410
|
+
return next.href;
|
|
60411
|
+
}
|
|
60412
|
+
async function send(method, path, opt, dispatch) {
|
|
60061
60413
|
const { body: reqBody, contentType } = encodeBody(opt);
|
|
60062
60414
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
60063
60415
|
const url4 = resolveUrl2(path, query);
|
|
60064
60416
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
60065
|
-
const
|
|
60066
|
-
|
|
60067
|
-
|
|
60068
|
-
|
|
60069
|
-
|
|
60070
|
-
|
|
60071
|
-
|
|
60072
|
-
|
|
60073
|
-
|
|
60074
|
-
|
|
60075
|
-
|
|
60076
|
-
|
|
60417
|
+
const fetchHop = (hopUrl, hopMethod, withBody, token, signal) => {
|
|
60418
|
+
if (!signal?.aborted)
|
|
60419
|
+
dispatch.sent = true;
|
|
60420
|
+
return doFetch(hopUrl, {
|
|
60421
|
+
method: hopMethod,
|
|
60422
|
+
headers: {
|
|
60423
|
+
Accept: "application/json",
|
|
60424
|
+
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
60425
|
+
...opts.baseHeaders,
|
|
60426
|
+
...authHeader(token || void 0),
|
|
60427
|
+
...opt.headers
|
|
60428
|
+
},
|
|
60429
|
+
...signal ? { signal } : {},
|
|
60430
|
+
...redirectMode !== void 0 ? { redirect: redirectMode === "same-origin" ? "manual" : redirectMode } : {},
|
|
60431
|
+
...withBody ? bodyInit : {}
|
|
60432
|
+
}).catch((err) => {
|
|
60433
|
+
markTransportFailure(err);
|
|
60434
|
+
throw err;
|
|
60435
|
+
});
|
|
60436
|
+
};
|
|
60437
|
+
const fetchWith = async (token, signal) => {
|
|
60438
|
+
if (redirectMode !== "same-origin")
|
|
60439
|
+
return fetchHop(url4, method, true, token, signal);
|
|
60440
|
+
let hopUrl = url4;
|
|
60441
|
+
let hopMethod = method;
|
|
60442
|
+
let withBody = true;
|
|
60443
|
+
for (let hops = 0; ; hops += 1) {
|
|
60444
|
+
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal);
|
|
60445
|
+
const location = REDIRECT_STATUSES.has(res.status) ? responseHeader(res, "location") : void 0;
|
|
60446
|
+
if (location === void 0 || location === null)
|
|
60447
|
+
return res;
|
|
60448
|
+
cancelBody(res);
|
|
60449
|
+
if (hops >= maxRedirects) {
|
|
60450
|
+
throw new RedirectRefusedError(`Refusing request to ${service}: too many redirects (more than ${maxRedirects}).`);
|
|
60451
|
+
}
|
|
60452
|
+
hopUrl = sameOriginHop(location, hopUrl);
|
|
60453
|
+
if (res.status === 303 || (res.status === 301 || res.status === 302) && hopMethod === "POST") {
|
|
60454
|
+
if (hopMethod !== "HEAD")
|
|
60455
|
+
hopMethod = "GET";
|
|
60456
|
+
withBody = false;
|
|
60457
|
+
}
|
|
60458
|
+
}
|
|
60459
|
+
};
|
|
60077
60460
|
const once = () => withTimeout((signal) => opts.tokenManager ? opts.tokenManager.withAuth((token) => fetchWith(token, signal)) : (async () => fetchWith(await opts.getToken?.() || void 0, signal))());
|
|
60078
60461
|
let attempt = 0;
|
|
60079
60462
|
for (; ; ) {
|
|
@@ -60092,20 +60475,20 @@ function createApiClient(opts) {
|
|
|
60092
60475
|
return current;
|
|
60093
60476
|
}
|
|
60094
60477
|
}
|
|
60095
|
-
async function unauthorizedOrEdge(attempt, method, path) {
|
|
60478
|
+
async function unauthorizedOrEdge(attempt, method, path, maxBytes) {
|
|
60096
60479
|
const res = attempt.res;
|
|
60097
60480
|
let text = "";
|
|
60098
60481
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
60099
60482
|
discard(attempt);
|
|
60100
60483
|
} else {
|
|
60101
|
-
text = await
|
|
60484
|
+
text = await scanText(attempt, maxBytes);
|
|
60102
60485
|
}
|
|
60103
60486
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
60104
60487
|
if (edge)
|
|
60105
60488
|
return new EdgeBlockedError(res.status, edge.vendor, { service, method, path });
|
|
60106
60489
|
return unauthorized();
|
|
60107
60490
|
}
|
|
60108
|
-
async function rateLimitedError(attempt, method, path) {
|
|
60491
|
+
async function rateLimitedError(attempt, method, path, maxBytes) {
|
|
60109
60492
|
const res = attempt.res;
|
|
60110
60493
|
const retryAfter = responseHeader(res, "retry-after");
|
|
60111
60494
|
const retryAfterMs = retryAfterToMs(retryAfter);
|
|
@@ -60117,61 +60500,92 @@ function createApiClient(opts) {
|
|
|
60117
60500
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
60118
60501
|
discard(attempt);
|
|
60119
60502
|
} else {
|
|
60120
|
-
text = await
|
|
60503
|
+
text = await scanText(attempt, maxBytes);
|
|
60121
60504
|
}
|
|
60122
60505
|
const edgeBlock = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
60123
60506
|
return opts.onRateLimited({ status: res.status, retryAfter, retryAfterMs, edgeBlock, method, path });
|
|
60124
60507
|
}
|
|
60125
|
-
async function
|
|
60126
|
-
const
|
|
60508
|
+
async function guardWrite(method, opt, call) {
|
|
60509
|
+
const dispatch = { sent: false };
|
|
60510
|
+
try {
|
|
60511
|
+
return await call(dispatch);
|
|
60512
|
+
} catch (err) {
|
|
60513
|
+
const write = reportWriteOutcome && opt.idempotent !== true && !SAFE_METHODS.has(method.toUpperCase());
|
|
60514
|
+
if (!write || !dispatch.sent)
|
|
60515
|
+
throw err;
|
|
60516
|
+
if (err instanceof RequestTimeoutError) {
|
|
60517
|
+
throw new WriteOutcomeUnknownError(service, method, {
|
|
60518
|
+
timeoutMs: err.timeoutMs,
|
|
60519
|
+
cause: err,
|
|
60520
|
+
hint: opts.writeOutcomeHint
|
|
60521
|
+
});
|
|
60522
|
+
}
|
|
60523
|
+
if (typeof err === "object" && err !== null && transportFailures.has(err)) {
|
|
60524
|
+
throw new WriteOutcomeUnknownError(service, method, { cause: err, hint: opts.writeOutcomeHint });
|
|
60525
|
+
}
|
|
60526
|
+
throw err;
|
|
60527
|
+
}
|
|
60528
|
+
}
|
|
60529
|
+
const fetchJson = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchJsonOnce(method, path, opt, dispatch));
|
|
60530
|
+
const fetchHtml = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchHtmlOnce(method, path, opt, dispatch));
|
|
60531
|
+
const fetchRaw = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchRawOnce(method, path, opt, dispatch));
|
|
60532
|
+
async function fetchJsonOnce(method, path, opt, dispatch) {
|
|
60533
|
+
const maxBytes = capOf(opt);
|
|
60534
|
+
const attempt = await send(method, path, opt, dispatch);
|
|
60127
60535
|
const res = attempt.res;
|
|
60128
60536
|
if (res.status === 401)
|
|
60129
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60537
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60130
60538
|
if (res.status === 429)
|
|
60131
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60539
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60132
60540
|
if (res.status === 204) {
|
|
60133
60541
|
discard(attempt);
|
|
60134
|
-
return
|
|
60542
|
+
return parseJsonBody("", { expect: opt.expect, service, method, path, status: 204 });
|
|
60135
60543
|
}
|
|
60136
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
60137
60544
|
if (!res.ok) {
|
|
60138
|
-
throw httpError(res,
|
|
60545
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
60139
60546
|
}
|
|
60140
|
-
|
|
60141
|
-
|
|
60142
|
-
|
|
60547
|
+
const text = await readText(attempt, maxBytes);
|
|
60548
|
+
return parseJsonBody(text, {
|
|
60549
|
+
expect: opt.expect,
|
|
60550
|
+
service,
|
|
60551
|
+
method,
|
|
60552
|
+
path,
|
|
60553
|
+
status: res.status,
|
|
60554
|
+
headers: res.headers
|
|
60555
|
+
});
|
|
60143
60556
|
}
|
|
60144
|
-
async function
|
|
60557
|
+
async function fetchHtmlOnce(method, path, opt, dispatch) {
|
|
60558
|
+
const maxBytes = capOf(opt);
|
|
60145
60559
|
const headers = { Accept: "text/html,*/*", ...opt.headers };
|
|
60146
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
60560
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
60147
60561
|
const res = attempt.res;
|
|
60148
60562
|
if (res.status === 401)
|
|
60149
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60563
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60150
60564
|
if (res.status === 429)
|
|
60151
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60152
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
60565
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60153
60566
|
if (!res.ok) {
|
|
60154
|
-
throw httpError(res,
|
|
60567
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
60155
60568
|
}
|
|
60156
|
-
return
|
|
60569
|
+
return readText(attempt, maxBytes);
|
|
60157
60570
|
}
|
|
60158
|
-
async function
|
|
60571
|
+
async function fetchRawOnce(method, path, opt, dispatch) {
|
|
60572
|
+
const maxBytes = capOf(opt);
|
|
60159
60573
|
const headers = { Accept: "*/*", ...opt.headers };
|
|
60160
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
60574
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
60161
60575
|
const res = attempt.res;
|
|
60162
60576
|
if (res.status === 401)
|
|
60163
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60577
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60164
60578
|
if (res.status === 429)
|
|
60165
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60579
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60166
60580
|
if (!res.ok) {
|
|
60167
|
-
const text = await
|
|
60581
|
+
const text = await readText(attempt, maxBytes).catch((err) => {
|
|
60168
60582
|
if (err instanceof RequestTimeoutError)
|
|
60169
60583
|
throw err;
|
|
60170
60584
|
return "";
|
|
60171
60585
|
});
|
|
60172
60586
|
throw httpError(res, text, method, path, service);
|
|
60173
60587
|
}
|
|
60174
|
-
const bytes =
|
|
60588
|
+
const bytes = await readBytes(attempt, maxBytes);
|
|
60175
60589
|
return {
|
|
60176
60590
|
status: res.status,
|
|
60177
60591
|
contentType: responseHeader(res, "content-type"),
|
|
@@ -60181,6 +60595,14 @@ function createApiClient(opts) {
|
|
|
60181
60595
|
}
|
|
60182
60596
|
return { fetchJson, fetchHtml, fetchRaw };
|
|
60183
60597
|
}
|
|
60598
|
+
function checkMaxBytes(name, value) {
|
|
60599
|
+
if (value === void 0)
|
|
60600
|
+
return void 0;
|
|
60601
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
60602
|
+
throw new TypeError(`createApiClient: ${name} must be a non-negative number, got ${String(value)}.`);
|
|
60603
|
+
}
|
|
60604
|
+
return value === Infinity ? void 0 : value;
|
|
60605
|
+
}
|
|
60184
60606
|
function buildQueryString(params) {
|
|
60185
60607
|
const parts = [];
|
|
60186
60608
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -60204,7 +60626,7 @@ function formatApiError(status, method, path, errorText, opts = {}) {
|
|
|
60204
60626
|
const safe = truncateErrorMessage(errorText ?? "", opts.max).trim();
|
|
60205
60627
|
return safe.length > 0 ? `${head}: ${safe}` : head;
|
|
60206
60628
|
}
|
|
60207
|
-
var DEFAULT_RETRY, defaultSleep, UnauthorizedError, RateLimitedError, RequestTimeoutError, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
60629
|
+
var DEFAULT_RETRY, defaultSleep, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_MAX_REDIRECTS, REDIRECT_STATUSES, UnauthorizedError, RateLimitedError, RequestTimeoutError, DEFAULT_WRITE_OUTCOME_HINT, WriteOutcomeUnknownError, SAFE_METHODS, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
60208
60630
|
var init_http = __esm({
|
|
60209
60631
|
"node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
|
|
60210
60632
|
init_headers();
|
|
@@ -60214,12 +60636,21 @@ var init_http = __esm({
|
|
|
60214
60636
|
init_throttle();
|
|
60215
60637
|
init_response_cache();
|
|
60216
60638
|
init_net_atoms();
|
|
60639
|
+
init_fetch_bounded();
|
|
60640
|
+
init_url_safety();
|
|
60641
|
+
init_body_limit();
|
|
60217
60642
|
init_net_atoms();
|
|
60643
|
+
init_url_safety();
|
|
60218
60644
|
init_retry_after();
|
|
60219
60645
|
DEFAULT_RETRY = { count: 1, delayMs: 2e3 };
|
|
60220
60646
|
defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
60647
|
+
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
60648
|
+
DEFAULT_MAX_REDIRECTS = 5;
|
|
60649
|
+
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
60221
60650
|
UnauthorizedError = class extends Error {
|
|
60222
60651
|
status = 401;
|
|
60652
|
+
/** Always `'credential_rejected'` unless a subclass says otherwise. */
|
|
60653
|
+
kind = "credential_rejected";
|
|
60223
60654
|
constructor(service) {
|
|
60224
60655
|
super(`Unauthorized (401) from ${service} \u2014 the token is missing, invalid, or expired.`);
|
|
60225
60656
|
this.name = "UnauthorizedError";
|
|
@@ -60228,6 +60659,8 @@ var init_http = __esm({
|
|
|
60228
60659
|
};
|
|
60229
60660
|
RateLimitedError = class extends Error {
|
|
60230
60661
|
status = 429;
|
|
60662
|
+
/** `'http'`: the far side answered, with a 429 (see `status`). */
|
|
60663
|
+
kind = "http";
|
|
60231
60664
|
retryAfterMs;
|
|
60232
60665
|
constructor(service, retryAfterMs) {
|
|
60233
60666
|
super(`Rate limited (429) by ${service} after retries.`);
|
|
@@ -60238,6 +60671,8 @@ var init_http = __esm({
|
|
|
60238
60671
|
};
|
|
60239
60672
|
RequestTimeoutError = class extends Error {
|
|
60240
60673
|
timeoutMs;
|
|
60674
|
+
/** Always `'timeout'` unless a subclass says otherwise. */
|
|
60675
|
+
kind = "timeout";
|
|
60241
60676
|
constructor(service, timeoutMs) {
|
|
60242
60677
|
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
60243
60678
|
this.name = "RequestTimeoutError";
|
|
@@ -60245,6 +60680,33 @@ var init_http = __esm({
|
|
|
60245
60680
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
60246
60681
|
}
|
|
60247
60682
|
};
|
|
60683
|
+
DEFAULT_WRITE_OUTCOME_HINT = "The write may have happened \u2014 check before retrying; do not resend blindly.";
|
|
60684
|
+
WriteOutcomeUnknownError = class extends McpToolError {
|
|
60685
|
+
/** Always `true`: whether the write happened is not known. */
|
|
60686
|
+
outcomeUnknown = true;
|
|
60687
|
+
/** Always `false`: never replay this request without checking first. */
|
|
60688
|
+
retrySafe = false;
|
|
60689
|
+
/** `true` when the request's timeout expired; `false` for a dropped connection or broken body. */
|
|
60690
|
+
timedOut;
|
|
60691
|
+
/** The timeout that expired, when {@link timedOut}. */
|
|
60692
|
+
timeoutMs;
|
|
60693
|
+
/** The request method, upper-cased. */
|
|
60694
|
+
method;
|
|
60695
|
+
constructor(service, method, opts = {}) {
|
|
60696
|
+
const verb = method.toUpperCase();
|
|
60697
|
+
const what = opts.timeoutMs !== void 0 ? `${verb} to ${service} timed out after ${opts.timeoutMs}ms` : `${verb} to ${service} failed: ${truncateErrorMessage(messageOf(opts.cause), 200)}`;
|
|
60698
|
+
super(`${what} \u2014 the write may already have been applied (outcome is unknown).`, {
|
|
60699
|
+
hint: opts.hint ?? DEFAULT_WRITE_OUTCOME_HINT,
|
|
60700
|
+
cause: opts.cause,
|
|
60701
|
+
kind: opts.timeoutMs !== void 0 ? "timeout" : "transport"
|
|
60702
|
+
});
|
|
60703
|
+
this.name = "WriteOutcomeUnknownError";
|
|
60704
|
+
this.timedOut = opts.timeoutMs !== void 0;
|
|
60705
|
+
this.timeoutMs = opts.timeoutMs;
|
|
60706
|
+
this.method = verb;
|
|
60707
|
+
}
|
|
60708
|
+
};
|
|
60709
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "TRACE"]);
|
|
60248
60710
|
ApiError = class extends Error {
|
|
60249
60711
|
status;
|
|
60250
60712
|
constructor(status, message) {
|
|
@@ -60257,6 +60719,8 @@ var init_http = __esm({
|
|
|
60257
60719
|
EdgeBlockedError = class extends ApiError {
|
|
60258
60720
|
/** Which edge refused the request, e.g. `'CloudFront'` or `'Cloudflare'`. */
|
|
60259
60721
|
vendor;
|
|
60722
|
+
/** Always `'edge_blocked'`: the credential was never judged. */
|
|
60723
|
+
kind = "edge_blocked";
|
|
60260
60724
|
/**
|
|
60261
60725
|
* @param where The service the request was for, and — when the thrower knows
|
|
60262
60726
|
* it — the method and path, which the message names.
|
|
@@ -60320,6 +60784,7 @@ var init_concurrency = __esm({
|
|
|
60320
60784
|
// node_modules/@chrischall/mcp-utils/dist/dates/index.js
|
|
60321
60785
|
var init_dates = __esm({
|
|
60322
60786
|
"node_modules/@chrischall/mcp-utils/dist/dates/index.js"() {
|
|
60787
|
+
init_config();
|
|
60323
60788
|
}
|
|
60324
60789
|
});
|
|
60325
60790
|
|
|
@@ -60352,7 +60817,7 @@ var init_zod2 = __esm({
|
|
|
60352
60817
|
NonNegInt = external_exports.number().int().nonnegative();
|
|
60353
60818
|
NonEmptyString = external_exports.string().min(1);
|
|
60354
60819
|
IsoDate = external_exports.iso.date();
|
|
60355
|
-
IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
60820
|
+
IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
|
|
60356
60821
|
NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
60357
60822
|
SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
60358
60823
|
schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
@@ -60387,6 +60852,7 @@ var init_auth = __esm({
|
|
|
60387
60852
|
init_config();
|
|
60388
60853
|
init_errors();
|
|
60389
60854
|
init_http();
|
|
60855
|
+
init_cancel();
|
|
60390
60856
|
init_cached_token();
|
|
60391
60857
|
init_es256();
|
|
60392
60858
|
}
|
|
@@ -60438,7 +60904,8 @@ __export(images_exports, {
|
|
|
60438
60904
|
});
|
|
60439
60905
|
import { readFile, stat } from "node:fs/promises";
|
|
60440
60906
|
import { existsSync as existsSync3 } from "node:fs";
|
|
60441
|
-
import {
|
|
60907
|
+
import { homedir as homedir3 } from "node:os";
|
|
60908
|
+
import { delimiter, join as join7, resolve as resolve4, isAbsolute as isAbsolute4 } from "node:path";
|
|
60442
60909
|
function uploadRoots() {
|
|
60443
60910
|
const roots = readEnvVar("GEMINI_UPLOAD_DIR")?.split(delimiter).map((r) => r.trim()).filter(Boolean);
|
|
60444
60911
|
return roots && roots.length > 0 ? roots : void 0;
|
|
@@ -60459,7 +60926,7 @@ function referenceRoots(env = process.env) {
|
|
|
60459
60926
|
let roots = rootList("GEMINI_REFERENCE_DIR", env) ?? rootList("GEMINI_UPLOAD_DIR", env);
|
|
60460
60927
|
if (!roots) {
|
|
60461
60928
|
const dataDir = readEnvVar("MCP_DATA_DIR", { env });
|
|
60462
|
-
if (dataDir) roots = [
|
|
60929
|
+
if (dataDir) roots = [join7(dataDir, "uploads")];
|
|
60463
60930
|
}
|
|
60464
60931
|
if (!roots) return void 0;
|
|
60465
60932
|
const output2 = readEnvVar("GEMINI_OUTPUT_DIR", { env })?.trim();
|
|
@@ -60535,7 +61002,7 @@ function resolveInputPath(p, kind) {
|
|
|
60535
61002
|
if (isAbsolute4(p) && existsSync3(p)) return p;
|
|
60536
61003
|
const inputDir = readEnvVar("GEMINI_INPUT_DIR");
|
|
60537
61004
|
if (inputDir) {
|
|
60538
|
-
const candidate2 =
|
|
61005
|
+
const candidate2 = join7(inputDir, p);
|
|
60539
61006
|
if (existsSync3(candidate2)) return resolve4(candidate2);
|
|
60540
61007
|
}
|
|
60541
61008
|
const cwd = resolve4(p);
|
|
@@ -60629,11 +61096,14 @@ function baseName(name) {
|
|
|
60629
61096
|
const safe = stripped.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
60630
61097
|
return safe || "image";
|
|
60631
61098
|
}
|
|
61099
|
+
function defaultOutputDir2() {
|
|
61100
|
+
return join7(homedir3(), "Downloads", OUTPUT_DIR_NAME);
|
|
61101
|
+
}
|
|
60632
61102
|
function lookupOutputDir(perCall) {
|
|
60633
61103
|
const trimmed = perCall?.trim() || void 0;
|
|
60634
61104
|
const configured = readEnvVar("GEMINI_OUTPUT_DIR");
|
|
60635
61105
|
const raw = trimmed ?? configured;
|
|
60636
|
-
if (!raw) return
|
|
61106
|
+
if (!raw) return defaultOutputDir2();
|
|
60637
61107
|
const dir = expandPath(raw);
|
|
60638
61108
|
if (trimmed && configured) assertPathWithinRoots(dir, [configured]);
|
|
60639
61109
|
return dir;
|
|
@@ -60641,10 +61111,11 @@ function lookupOutputDir(perCall) {
|
|
|
60641
61111
|
function resolveOutputDir2(perCall) {
|
|
60642
61112
|
const configured = readEnvVar("GEMINI_OUTPUT_DIR");
|
|
60643
61113
|
return resolveOutputDir(perCall?.trim() || void 0, "GEMINI_OUTPUT_DIR", {
|
|
61114
|
+
name: OUTPUT_DIR_NAME,
|
|
60644
61115
|
...configured ? { allowedRoots: [configured] } : {}
|
|
60645
61116
|
});
|
|
60646
61117
|
}
|
|
60647
|
-
var REFERENCE_IMAGE_MIMES, VIDEO_MIME_BY_EXT, UPLOAD_MIME_BY_EXT;
|
|
61118
|
+
var REFERENCE_IMAGE_MIMES, VIDEO_MIME_BY_EXT, UPLOAD_MIME_BY_EXT, OUTPUT_DIR_NAME;
|
|
60648
61119
|
var init_images = __esm({
|
|
60649
61120
|
"src/images.ts"() {
|
|
60650
61121
|
"use strict";
|
|
@@ -60679,6 +61150,7 @@ var init_images = __esm({
|
|
|
60679
61150
|
flac: "audio/flac",
|
|
60680
61151
|
...VIDEO_MIME_BY_EXT
|
|
60681
61152
|
};
|
|
61153
|
+
OUTPUT_DIR_NAME = "gemini-mcp";
|
|
60682
61154
|
}
|
|
60683
61155
|
});
|
|
60684
61156
|
|
|
@@ -60691,7 +61163,7 @@ import { execFile } from "node:child_process";
|
|
|
60691
61163
|
import { promisify } from "node:util";
|
|
60692
61164
|
import { readFile as readFile2, unlink } from "node:fs/promises";
|
|
60693
61165
|
import { tmpdir } from "node:os";
|
|
60694
|
-
import { join as
|
|
61166
|
+
import { join as join8 } from "node:path";
|
|
60695
61167
|
async function readClipboardImage(run = defaultRun) {
|
|
60696
61168
|
if (process.platform !== "darwin") {
|
|
60697
61169
|
throw new McpToolError("from_clipboard is only supported on macOS", {
|
|
@@ -60699,8 +61171,8 @@ async function readClipboardImage(run = defaultRun) {
|
|
|
60699
61171
|
});
|
|
60700
61172
|
}
|
|
60701
61173
|
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
60702
|
-
const png =
|
|
60703
|
-
const jpg =
|
|
61174
|
+
const png = join8(tmpdir(), `gemini-clip-${id}.png`);
|
|
61175
|
+
const jpg = join8(tmpdir(), `gemini-clip-${id}.jpg`);
|
|
60704
61176
|
try {
|
|
60705
61177
|
try {
|
|
60706
61178
|
await run("osascript", [
|
|
@@ -60747,15 +61219,15 @@ init_dist2();
|
|
|
60747
61219
|
|
|
60748
61220
|
// src/dotenv.ts
|
|
60749
61221
|
init_dist2();
|
|
60750
|
-
import { dirname as dirname3, join as
|
|
61222
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
60751
61223
|
import { fileURLToPath } from "node:url";
|
|
60752
61224
|
async function loadStdioDotenv() {
|
|
60753
61225
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
60754
|
-
await loadDotenvSafely({ path:
|
|
61226
|
+
await loadDotenvSafely({ path: join6(here, "..", ".env"), override: false });
|
|
60755
61227
|
}
|
|
60756
61228
|
|
|
60757
61229
|
// src/version.ts
|
|
60758
|
-
var VERSION = "2.3.
|
|
61230
|
+
var VERSION = "2.3.4";
|
|
60759
61231
|
|
|
60760
61232
|
// src/errors.ts
|
|
60761
61233
|
init_dist2();
|
|
@@ -60822,7 +61294,7 @@ function registerModelTools(server, client2) {
|
|
|
60822
61294
|
"gemini_list_models",
|
|
60823
61295
|
{
|
|
60824
61296
|
description: "List the Gemini image-generation models available to your API key (Nano Banana / Nano Banana Pro family), and the current default model.",
|
|
60825
|
-
annotations: {
|
|
61297
|
+
annotations: toolAnnotations({ readOnly: true, openWorld: true })
|
|
60826
61298
|
},
|
|
60827
61299
|
async () => {
|
|
60828
61300
|
const models = await client2.listModels();
|
|
@@ -60866,12 +61338,6 @@ function statusOf(err) {
|
|
|
60866
61338
|
|
|
60867
61339
|
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
60868
61340
|
init_zod();
|
|
60869
|
-
function statusOf2(err) {
|
|
60870
|
-
if (typeof err !== "object" || err === null)
|
|
60871
|
-
return void 0;
|
|
60872
|
-
const s = err.status ?? err.statusCode;
|
|
60873
|
-
return typeof s === "number" ? s : void 0;
|
|
60874
|
-
}
|
|
60875
61341
|
var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
60876
61342
|
"ok",
|
|
60877
61343
|
"no_credential",
|
|
@@ -60887,6 +61353,9 @@ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
|
60887
61353
|
function isArm(kind) {
|
|
60888
61354
|
return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
|
|
60889
61355
|
}
|
|
61356
|
+
function fromSource(source) {
|
|
61357
|
+
return source !== null ? ` from '${source}'` : "";
|
|
61358
|
+
}
|
|
60890
61359
|
function credentialHint(arm, prefix, hostLabel, source) {
|
|
60891
61360
|
switch (arm) {
|
|
60892
61361
|
case "ok":
|
|
@@ -60894,15 +61363,15 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
60894
61363
|
case "no_credential":
|
|
60895
61364
|
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.`;
|
|
60896
61365
|
case "credential_rejected":
|
|
60897
|
-
return `${hostLabel} rejected the credential
|
|
61366
|
+
return `${hostLabel} rejected the credential${fromSource(source)}. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
60898
61367
|
case "edge_blocked":
|
|
60899
61368
|
return `${hostLabel} refused the request at its CDN/WAF before it reached the API, so the credential${source !== null ? ` from '${source}'` : ""} was never judged \u2014 re-signing in or rotating it will not help. This is usually a block on this host's IP address or request fingerprint: route through the ContextMint Bridge if this connector supports it, or retry later from a different network.`;
|
|
60900
61369
|
case "session_expired":
|
|
60901
|
-
return `The credential
|
|
61370
|
+
return `The credential${fromSource(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.`;
|
|
60902
61371
|
case "verification_pending":
|
|
60903
61372
|
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.`;
|
|
60904
61373
|
case "timeout":
|
|
60905
|
-
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.`;
|
|
61374
|
+
return `${source !== null ? `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.`;
|
|
60906
61375
|
case "http":
|
|
60907
61376
|
return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
|
|
60908
61377
|
case "transport":
|
|
@@ -60923,7 +61392,8 @@ async function runCredentialHealthcheck(args) {
|
|
|
60923
61392
|
state = await resolveCredential();
|
|
60924
61393
|
} catch (e) {
|
|
60925
61394
|
const edge = edgeBlockOf(e);
|
|
60926
|
-
const
|
|
61395
|
+
const declared = errorKindOf(e);
|
|
61396
|
+
const classified = classifyThrown?.(e) ?? (edge !== null ? { kind: "edge_blocked", detail: { vendor: edge.vendor } } : declared !== void 0 ? { kind: declared } : void 0);
|
|
60927
61397
|
const result2 = {
|
|
60928
61398
|
ok: false,
|
|
60929
61399
|
// Still false, and still no source: a classification explains WHY
|
|
@@ -60970,10 +61440,11 @@ async function runCredentialHealthcheck(args) {
|
|
|
60970
61440
|
try {
|
|
60971
61441
|
await probeFn();
|
|
60972
61442
|
} catch (e) {
|
|
60973
|
-
status =
|
|
61443
|
+
status = errorStatusOf(e);
|
|
60974
61444
|
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
60975
61445
|
const edge = edgeBlockOf(e);
|
|
60976
|
-
|
|
61446
|
+
const declared = errorKindOf(e);
|
|
61447
|
+
arm = edge !== null ? "edge_blocked" : declared !== void 0 ? declared : 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";
|
|
60977
61448
|
let kind = arm;
|
|
60978
61449
|
let detail = edge !== null ? { vendor: edge.vendor } : void 0;
|
|
60979
61450
|
const custom2 = classifyThrown?.(e);
|
|
@@ -61176,10 +61647,12 @@ function assertPublicHttpsUrl(raw, requested) {
|
|
|
61176
61647
|
}
|
|
61177
61648
|
const rawHost = url4.hostname.toLowerCase();
|
|
61178
61649
|
const isIpv6Literal = rawHost.startsWith("[") && rawHost.endsWith("]");
|
|
61179
|
-
const host = isIpv6Literal ? rawHost.slice(1, -1) : rawHost;
|
|
61650
|
+
const host = isIpv6Literal ? rawHost.slice(1, -1) : rawHost.replace(/\.+$/, "");
|
|
61180
61651
|
const v4 = isIpv6Literal ? void 0 : ipv4Octets(host);
|
|
61181
61652
|
const v6 = isIpv6Literal ? ipv6Groups(host) : void 0;
|
|
61182
|
-
const blocked = isIpv6Literal ? v6 === void 0 || isPrivateIpv6(v6) : host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") ||
|
|
61653
|
+
const blocked = isIpv6Literal ? v6 === void 0 || isPrivateIpv6(v6) : host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || // Private-network names: Fly's 6PN (`<app>.internal`, where the hosted
|
|
61654
|
+
// build runs) and GCP's metadata server (`metadata.google.internal`).
|
|
61655
|
+
host === "internal" || host.endsWith(".internal") || (v4 ? isPrivateIpv4(v4) : false);
|
|
61183
61656
|
if (blocked) {
|
|
61184
61657
|
throw new McpToolError(
|
|
61185
61658
|
`Refusing to fetch ${describeUrl(raw, requested)}: "${url4.hostname}" is a private, loopback or link-local address, not a public host.`,
|
|
@@ -61526,10 +61999,12 @@ async function readPin(bucket) {
|
|
|
61526
61999
|
}
|
|
61527
62000
|
function pinnedTenant(bucket, derive) {
|
|
61528
62001
|
let cached2;
|
|
61529
|
-
async function
|
|
62002
|
+
async function resolve6() {
|
|
61530
62003
|
const pinned = await readPin(bucket);
|
|
61531
62004
|
if (pinned) return { tenant: pinned, pinned: true };
|
|
61532
|
-
const
|
|
62005
|
+
const derived2 = await derive();
|
|
62006
|
+
if (typeof derived2 !== "string") return { tenant: derived2.tenant, pinned: false };
|
|
62007
|
+
const tenant = derived2;
|
|
61533
62008
|
try {
|
|
61534
62009
|
await bucket.put(TENANT_PIN_KEY, JSON.stringify({ tenant }), {
|
|
61535
62010
|
httpMetadata: { contentType: "application/json" }
|
|
@@ -61541,7 +62016,7 @@ function pinnedTenant(bucket, derive) {
|
|
|
61541
62016
|
}
|
|
61542
62017
|
return () => {
|
|
61543
62018
|
if (cached2) return cached2;
|
|
61544
|
-
const attempt =
|
|
62019
|
+
const attempt = resolve6().then(({ tenant, pinned }) => {
|
|
61545
62020
|
if (!pinned && cached2 === attempt) cached2 = void 0;
|
|
61546
62021
|
return tenant;
|
|
61547
62022
|
});
|
|
@@ -62028,7 +62503,7 @@ var sharedImageSchema = {
|
|
|
62028
62503
|
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe("Exact output aspect ratio. For a plain landscape/portrait/square request, `orientation` is the shorthand; this wins if both are given."),
|
|
62029
62504
|
orientation: orientationSchema,
|
|
62030
62505
|
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, Flash-only)"),
|
|
62031
|
-
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or
|
|
62506
|
+
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
62032
62507
|
inline: external_exports.boolean().optional().describe("Return the image as an inline image block you can SEE, instead of a path (stdio) or link (hosted). The default costs nothing to carry and hands back a reference you can reuse; use this when you need to check the result yourself. On the hosted connector gemini_view_media does the same for an image you already have"),
|
|
62033
62508
|
seed: external_exports.number().int().optional().describe("Seed for reproducible generation; random if omitted"),
|
|
62034
62509
|
thinking_level: external_exports.enum(["minimal", "high"]).optional().describe("Reasoning depth (Gemini 3 models); higher can help complex/structural edits"),
|
|
@@ -62482,13 +62957,13 @@ function persistableResult(result) {
|
|
|
62482
62957
|
return { result };
|
|
62483
62958
|
}
|
|
62484
62959
|
function awaitWithBudget(promise2, waitMs) {
|
|
62485
|
-
return new Promise((
|
|
62486
|
-
const timer = setTimeout(() =>
|
|
62960
|
+
return new Promise((resolve6, reject) => {
|
|
62961
|
+
const timer = setTimeout(() => resolve6(void 0), waitMs);
|
|
62487
62962
|
timer.unref?.();
|
|
62488
62963
|
promise2.then(
|
|
62489
62964
|
(result) => {
|
|
62490
62965
|
clearTimeout(timer);
|
|
62491
|
-
|
|
62966
|
+
resolve6(result);
|
|
62492
62967
|
},
|
|
62493
62968
|
(err) => {
|
|
62494
62969
|
clearTimeout(timer);
|
|
@@ -62556,7 +63031,7 @@ var JobRegistry = class {
|
|
|
62556
63031
|
/** Await outstanding durable writes. Tests only — handlers never need it. */
|
|
62557
63032
|
async drain() {
|
|
62558
63033
|
for (let round = 0; round < 50; round++) {
|
|
62559
|
-
await new Promise((
|
|
63034
|
+
await new Promise((resolve6) => setTimeout(resolve6, 0));
|
|
62560
63035
|
if (!this.pending.size) return;
|
|
62561
63036
|
await Promise.allSettled([...this.pending]);
|
|
62562
63037
|
}
|
|
@@ -62988,10 +63463,18 @@ async function confirmWrite(ctx, o) {
|
|
|
62988
63463
|
message: o.message,
|
|
62989
63464
|
details: preview,
|
|
62990
63465
|
tool: o.tool,
|
|
63466
|
+
// One API key per process (a hosted deployment runs one child per
|
|
63467
|
+
// account), so there is no in-process principal to bind.
|
|
63468
|
+
account: void 0,
|
|
63469
|
+
args: withoutConfirmToken(o.args),
|
|
62991
63470
|
confirmToken: o.confirmToken,
|
|
62992
63471
|
subject: () => ({ target: o.target, payload: o.payload, preview })
|
|
62993
63472
|
}));
|
|
62994
63473
|
}
|
|
63474
|
+
function withoutConfirmToken(args) {
|
|
63475
|
+
const { confirmToken: _omit, ...rest } = args;
|
|
63476
|
+
return rest;
|
|
63477
|
+
}
|
|
62995
63478
|
async function confirmLocalInputs(ctx, o) {
|
|
62996
63479
|
const hasLocal = (o.imagePaths?.length ?? 0) > 0 || Boolean(o.videoPath);
|
|
62997
63480
|
if (!hasLocal) return void 0;
|
|
@@ -63008,6 +63491,7 @@ async function confirmLocalInputs(ctx, o) {
|
|
|
63008
63491
|
body: { inputs },
|
|
63009
63492
|
target: "",
|
|
63010
63493
|
payload: { request: o.request, inputs },
|
|
63494
|
+
args: o.request,
|
|
63011
63495
|
confirmToken: o.confirmToken
|
|
63012
63496
|
});
|
|
63013
63497
|
}
|
|
@@ -63021,7 +63505,7 @@ function registerGenerateTools(server, client2) {
|
|
|
63021
63505
|
"gemini_image_generate",
|
|
63022
63506
|
{
|
|
63023
63507
|
description: "Generate image(s) from a text prompt with a Gemini image model (Nano Banana / Nano Banana Pro). If the result will likely be refined iteratively, prefer gemini_interact (multi-turn) as the entry point. " + confirmNote("Local file inputs are confirmed first"),
|
|
63024
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63508
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63025
63509
|
inputSchema: external_exports.object({
|
|
63026
63510
|
prompt: external_exports.string().min(1).describe("Text prompt describing the image"),
|
|
63027
63511
|
count: external_exports.number().int().positive().max(8).optional().describe("Number of independent images (default 1)"),
|
|
@@ -63141,7 +63625,7 @@ function registerGenerateTools(server, client2) {
|
|
|
63141
63625
|
"gemini_image_edit",
|
|
63142
63626
|
{
|
|
63143
63627
|
description: "Edit or compose images: provide one or more input images (paths or base64), plus a text instruction. For a SERIES of successive edits to the same image, prefer gemini_interact (multi-turn) \u2014 it keeps edit context and avoids re-processing the full image each round; use gemini_image_edit for one-off edits or composing multiple distinct inputs. Gemini over-preserves the input; there is no edit-strength control \u2014 for large structural changes, reroll with a different `seed` or more forceful wording. " + confirmNote("Local file inputs are confirmed first"),
|
|
63144
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63628
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63145
63629
|
inputSchema: external_exports.object({
|
|
63146
63630
|
prompt: external_exports.string().min(1).describe("Instruction describing the edit or composition"),
|
|
63147
63631
|
images: external_exports.array(external_exports.string().min(1)).optional().describe("Paths to input image file(s) (1 = edit, 2+ = compose). Local paths must be inside GEMINI_REFERENCE_DIR (else GEMINI_UPLOAD_DIR) when set."),
|
|
@@ -63232,7 +63716,7 @@ function registerSetTools(server, client2) {
|
|
|
63232
63716
|
"gemini_image_set",
|
|
63233
63717
|
{
|
|
63234
63718
|
description: 'Generate a consistent SET of images: a master image from master_prompt, then one image per scene that references the master so the subject/style stays consistent. Provide `scenes` (explicit per-image prompts) OR `count` (variations of the master). Scene generations run in parallel (reference_mode "master", the default). On the hosted connector: saved `characters` and a saved `style` can seed the whole set by name, multi-image results include a `bundle_url` zip of every image (one curl instead of N), and `max_wait_ms` returns a pollable job handle if the batch runs long. ' + confirmNote("Local file inputs are confirmed first"),
|
|
63235
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63719
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63236
63720
|
inputSchema: external_exports.object({
|
|
63237
63721
|
master_prompt: external_exports.string().min(1).describe("Prompt for the master/reference image"),
|
|
63238
63722
|
scenes: external_exports.array(external_exports.string().min(1)).min(1).max(8).optional().describe("Per-image prompts (1-8); each references the master"),
|
|
@@ -63391,6 +63875,7 @@ var LIBRARY_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
|
63391
63875
|
function isValidLibraryName(name) {
|
|
63392
63876
|
return LIBRARY_NAME_PATTERN.test(name);
|
|
63393
63877
|
}
|
|
63878
|
+
var LIBRARY_MAX_LIST_PAGES = 20;
|
|
63394
63879
|
function createR2Library(bucket, opts) {
|
|
63395
63880
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
63396
63881
|
const tenantId = tenantResolver(opts.tenant);
|
|
@@ -63422,10 +63907,10 @@ function createR2Library(bucket, opts) {
|
|
|
63422
63907
|
const prefix = `${LIBRARY_KEY_PREFIX}/${await tenantId()}/${kind}/`;
|
|
63423
63908
|
const keys = [];
|
|
63424
63909
|
let cursor;
|
|
63425
|
-
for (; ; ) {
|
|
63910
|
+
for (let n = 0; n < LIBRARY_MAX_LIST_PAGES; n++) {
|
|
63426
63911
|
const page = await bucket.list({ prefix, cursor, limit: 1e3 });
|
|
63427
63912
|
keys.push(...page.objects.map((o) => o.key).filter((k) => k.endsWith(".json")));
|
|
63428
|
-
if (!page.truncated) break;
|
|
63913
|
+
if (!page.truncated || !page.cursor) break;
|
|
63429
63914
|
cursor = page.cursor;
|
|
63430
63915
|
}
|
|
63431
63916
|
const records = await Promise.all(keys.map((k) => readJson(k)));
|
|
@@ -63713,8 +64198,9 @@ var SessionState = class {
|
|
|
63713
64198
|
* Accumulated in the CLIENT rather than in the tools, so a call that was
|
|
63714
64199
|
* billed and then failed (a safety filter returning no image, say) still
|
|
63715
64200
|
* counts — it cost money regardless of whether a tool could use the result.
|
|
63716
|
-
*
|
|
63717
|
-
*
|
|
64201
|
+
* On the session, never module scope: a module-level total would report
|
|
64202
|
+
* other users' spend as yours on any host that served several users from one
|
|
64203
|
+
* process (see the invariant above).
|
|
63718
64204
|
*/
|
|
63719
64205
|
usageTotal;
|
|
63720
64206
|
/** How many upstream calls contributed to {@link usageTotal}. */
|
|
@@ -63779,6 +64265,25 @@ function resolveTimeoutMs(perCallMs, imageSize) {
|
|
|
63779
64265
|
return imageSize === "4K" ? REQUEST_TIMEOUT_4K_MS : REQUEST_TIMEOUT_MS;
|
|
63780
64266
|
}
|
|
63781
64267
|
var UPLOAD_BASE_URL = "https://generativelanguage.googleapis.com/upload/v1beta";
|
|
64268
|
+
function isGoogleUploadUrl(raw) {
|
|
64269
|
+
let u;
|
|
64270
|
+
try {
|
|
64271
|
+
u = new URL(raw);
|
|
64272
|
+
} catch {
|
|
64273
|
+
return false;
|
|
64274
|
+
}
|
|
64275
|
+
return u.protocol === "https:" && (u.hostname === "googleapis.com" || u.hostname.endsWith(".googleapis.com"));
|
|
64276
|
+
}
|
|
64277
|
+
var MIN_TRANSFER_BYTES_PER_SEC = 256 * 1024;
|
|
64278
|
+
function transferTimeoutMs(bytes) {
|
|
64279
|
+
return resolveTimeoutMs() + Math.ceil(bytes / MIN_TRANSFER_BYTES_PER_SEC * 1e3);
|
|
64280
|
+
}
|
|
64281
|
+
function transferDeadline(ms) {
|
|
64282
|
+
const ctrl = new AbortController();
|
|
64283
|
+
const timer = setTimeout(() => ctrl.abort(new DOMException(`timed out after ${ms}ms`, "TimeoutError")), ms);
|
|
64284
|
+
timer.unref?.();
|
|
64285
|
+
return { signal: ctrl.signal, clear: () => clearTimeout(timer) };
|
|
64286
|
+
}
|
|
63782
64287
|
var FILE_MAX_BYTES = 2 * 1024 ** 3;
|
|
63783
64288
|
var FILE_POLL_INTERVAL_MS = 2e3;
|
|
63784
64289
|
var FILE_POLL_MAX_ATTEMPTS = 150;
|
|
@@ -63855,10 +64360,9 @@ var GeminiClient = class {
|
|
|
63855
64360
|
mediaSink;
|
|
63856
64361
|
/**
|
|
63857
64362
|
* This client's session memory — job registry, last-interaction id, written
|
|
63858
|
-
* outputs. One client is one session
|
|
63859
|
-
*
|
|
63860
|
-
*
|
|
63861
|
-
* server is single-user, so its one singleton client is one session.
|
|
64363
|
+
* outputs. One client is one session. Production builds one client per
|
|
64364
|
+
* process and one process is one user, so this is process-wide — see the
|
|
64365
|
+
* invariant (and what a multi-user host would need) in src/session.ts.
|
|
63862
64366
|
*/
|
|
63863
64367
|
session = new SessionState();
|
|
63864
64368
|
/** Per-account character/style library — hosted connector only (see options). */
|
|
@@ -63991,8 +64495,9 @@ var GeminiClient = class {
|
|
|
63991
64495
|
* These two upload calls are raw `fetchImpl` (not `createApiClient`): step 1
|
|
63992
64496
|
* needs the response *header* and step 2 posts a binary body — neither fits
|
|
63993
64497
|
* `fetchJson`. The poll DOES go through the shared client (timeout + retry).
|
|
63994
|
-
*
|
|
63995
|
-
* legitimately take longer than any fixed budget
|
|
64498
|
+
* The deadline scales with the file (see `transferTimeoutMs`): a
|
|
64499
|
+
* multi-hundred-MB video can legitimately take longer than any fixed budget,
|
|
64500
|
+
* but a stalled upload must still fail rather than hang.
|
|
63996
64501
|
*
|
|
63997
64502
|
* Returned files live ~48h (`expirationTime`); per-file cap is 2 GB
|
|
63998
64503
|
* (enforced locally before any bytes are sent).
|
|
@@ -64058,12 +64563,32 @@ var GeminiClient = class {
|
|
|
64058
64563
|
* These two upload calls are raw `fetchImpl` (not `createApiClient`): step 1
|
|
64059
64564
|
* needs the response *header* and step 2 posts a binary body — neither fits
|
|
64060
64565
|
* `fetchJson`. The poll DOES go through the shared client (timeout + retry).
|
|
64061
|
-
*
|
|
64062
|
-
*
|
|
64063
|
-
*
|
|
64566
|
+
* By default the two round trips get a deadline sized to the file (see
|
|
64567
|
+
* `transferTimeoutMs`) — a multi-hundred-MB file can legitimately take longer
|
|
64568
|
+
* than any fixed budget, but a stalled one must fail rather than hang. A
|
|
64569
|
+
* caller that is uploading OPPORTUNISTICALLY passes its own, shorter
|
|
64570
|
+
* `signal` instead (see `uploadBytes`).
|
|
64064
64571
|
*/
|
|
64065
64572
|
async uploadToFilesApi(body, mimeType, displayName, contentLength, signal) {
|
|
64066
64573
|
const key = this.requireKey();
|
|
64574
|
+
const deadline = signal ? void 0 : transferDeadline(transferTimeoutMs(contentLength));
|
|
64575
|
+
let uploaded;
|
|
64576
|
+
try {
|
|
64577
|
+
uploaded = await this.sendToFilesApi(body, mimeType, displayName, contentLength, key, signal ?? deadline.signal);
|
|
64578
|
+
} catch (err) {
|
|
64579
|
+
if (deadline?.signal.aborted) {
|
|
64580
|
+
throw new McpToolError(`${SERVICE} Files API upload of ${displayName} timed out after ${transferTimeoutMs(contentLength)}ms`, {
|
|
64581
|
+
hint: "The upload stalled. Retry, or check the network between this server and generativelanguage.googleapis.com."
|
|
64582
|
+
});
|
|
64583
|
+
}
|
|
64584
|
+
throw err;
|
|
64585
|
+
} finally {
|
|
64586
|
+
deadline?.clear();
|
|
64587
|
+
}
|
|
64588
|
+
return this.awaitFileActive(uploaded.name, uploaded.file, mimeType);
|
|
64589
|
+
}
|
|
64590
|
+
/** {@link uploadToFilesApi}'s steps 1 and 2 (start + upload/finalize), under `signal`. */
|
|
64591
|
+
async sendToFilesApi(body, mimeType, displayName, contentLength, key, signal) {
|
|
64067
64592
|
const doFetch = this.fetchImpl;
|
|
64068
64593
|
const startPath = "/files";
|
|
64069
64594
|
const startRes = await doFetch(`${UPLOAD_BASE_URL}${startPath}`, {
|
|
@@ -64077,7 +64602,7 @@ var GeminiClient = class {
|
|
|
64077
64602
|
"Content-Type": "application/json"
|
|
64078
64603
|
},
|
|
64079
64604
|
body: JSON.stringify({ file: { display_name: displayName } }),
|
|
64080
|
-
|
|
64605
|
+
signal
|
|
64081
64606
|
});
|
|
64082
64607
|
if (!startRes.ok) {
|
|
64083
64608
|
throw new McpToolError(
|
|
@@ -64090,6 +64615,11 @@ var GeminiClient = class {
|
|
|
64090
64615
|
hint: "The Files API resumable-upload contract may have changed \u2014 see docs/GEMINI-API.md."
|
|
64091
64616
|
});
|
|
64092
64617
|
}
|
|
64618
|
+
if (!isGoogleUploadUrl(uploadUrl)) {
|
|
64619
|
+
throw new McpToolError(`${SERVICE} upload start returned a session URL outside googleapis.com; refusing to send the file`, {
|
|
64620
|
+
hint: "The Files API resumable-upload contract may have changed \u2014 see docs/GEMINI-API.md."
|
|
64621
|
+
});
|
|
64622
|
+
}
|
|
64093
64623
|
const upRes = await doFetch(uploadUrl, {
|
|
64094
64624
|
method: "POST",
|
|
64095
64625
|
headers: {
|
|
@@ -64097,7 +64627,7 @@ var GeminiClient = class {
|
|
|
64097
64627
|
"X-Goog-Upload-Command": "upload, finalize"
|
|
64098
64628
|
},
|
|
64099
64629
|
body,
|
|
64100
|
-
|
|
64630
|
+
signal
|
|
64101
64631
|
});
|
|
64102
64632
|
if (!upRes.ok) {
|
|
64103
64633
|
throw new McpToolError(
|
|
@@ -64109,6 +64639,10 @@ var GeminiClient = class {
|
|
|
64109
64639
|
if (!/^files\/[\w-]+$/.test(name)) {
|
|
64110
64640
|
throw new McpToolError(`${SERVICE} upload returned an unexpected file name: ${name || "(none)"}`);
|
|
64111
64641
|
}
|
|
64642
|
+
return { name, file: file2 };
|
|
64643
|
+
}
|
|
64644
|
+
/** {@link uploadToFilesApi}'s step 3: wait out PROCESSING. Outside the upload deadline. */
|
|
64645
|
+
async awaitFileActive(name, file2, mimeType) {
|
|
64112
64646
|
for (let attempt = 0; file2.state === "PROCESSING"; attempt++) {
|
|
64113
64647
|
if (attempt >= FILE_POLL_MAX_ATTEMPTS) {
|
|
64114
64648
|
throw new McpToolError(`${SERVICE} file ${name} is still processing after ${FILE_POLL_INTERVAL_MS * FILE_POLL_MAX_ATTEMPTS / 6e4} minutes`, {
|
|
@@ -64546,32 +65080,46 @@ var GeminiClient = class {
|
|
|
64546
65080
|
let current = requested;
|
|
64547
65081
|
for (let hop = 0; ; hop++) {
|
|
64548
65082
|
const url4 = assertMediaHost(current, requested);
|
|
64549
|
-
const
|
|
64550
|
-
|
|
64551
|
-
|
|
64552
|
-
|
|
64553
|
-
|
|
64554
|
-
|
|
64555
|
-
|
|
64556
|
-
|
|
65083
|
+
const budgetMs = transferTimeoutMs(MEDIA_DOWNLOAD_MAX_BYTES);
|
|
65084
|
+
const deadline = transferDeadline(budgetMs);
|
|
65085
|
+
try {
|
|
65086
|
+
const res = await doFetch(url4.toString(), {
|
|
65087
|
+
redirect: "manual",
|
|
65088
|
+
headers: { "x-goog-api-key": key },
|
|
65089
|
+
signal: deadline.signal
|
|
65090
|
+
});
|
|
65091
|
+
if (res.status >= 300 && res.status < 400) {
|
|
65092
|
+
const location = res.headers?.get("location");
|
|
65093
|
+
if (!location) {
|
|
65094
|
+
throw new McpToolError(`${SERVICE} media download returned HTTP ${res.status} with no Location header for ${current}`);
|
|
65095
|
+
}
|
|
65096
|
+
if (hop >= MAX_REDIRECTS) {
|
|
65097
|
+
throw new McpToolError(`${SERVICE} media download for ${requested} exceeded ${MAX_REDIRECTS} redirects (last hop: ${location}).`);
|
|
65098
|
+
}
|
|
65099
|
+
current = new URL(location, url4).toString();
|
|
65100
|
+
continue;
|
|
65101
|
+
}
|
|
65102
|
+
if (!res.ok) {
|
|
65103
|
+
throw new McpToolError(
|
|
65104
|
+
`${SERVICE} media download failed (HTTP ${res.status}) for ${current}`,
|
|
65105
|
+
{ hint: "Generated files live in the Files API for ~48h and the download requires the api key that created them." }
|
|
65106
|
+
);
|
|
64557
65107
|
}
|
|
64558
|
-
|
|
64559
|
-
|
|
65108
|
+
const bytes = await readCapped(res, MEDIA_DOWNLOAD_MAX_BYTES, current, requested, MEDIA_CAP_SUBJECT);
|
|
65109
|
+
return {
|
|
65110
|
+
base64: bytesToBase64(bytes),
|
|
65111
|
+
mimeType: media.mimeType || res.headers?.get("content-type") || "application/octet-stream"
|
|
65112
|
+
};
|
|
65113
|
+
} catch (err) {
|
|
65114
|
+
if (deadline.signal.aborted && !(err instanceof McpToolError)) {
|
|
65115
|
+
throw new McpToolError(`${SERVICE} media download timed out after ${budgetMs}ms for ${current}`, {
|
|
65116
|
+
hint: "The download stalled. Recover the result with gemini_get_result, or retry."
|
|
65117
|
+
});
|
|
64560
65118
|
}
|
|
64561
|
-
|
|
64562
|
-
|
|
64563
|
-
|
|
64564
|
-
if (!res.ok) {
|
|
64565
|
-
throw new McpToolError(
|
|
64566
|
-
`${SERVICE} media download failed (HTTP ${res.status}) for ${current}`,
|
|
64567
|
-
{ hint: "Generated files live in the Files API for ~48h and the download requires the api key that created them." }
|
|
64568
|
-
);
|
|
65119
|
+
throw err;
|
|
65120
|
+
} finally {
|
|
65121
|
+
deadline.clear();
|
|
64569
65122
|
}
|
|
64570
|
-
const bytes = await readCapped(res, MEDIA_DOWNLOAD_MAX_BYTES, current, requested, MEDIA_CAP_SUBJECT);
|
|
64571
|
-
return {
|
|
64572
|
-
base64: bytesToBase64(bytes),
|
|
64573
|
-
mimeType: media.mimeType || res.headers?.get("content-type") || "application/octet-stream"
|
|
64574
|
-
};
|
|
64575
65123
|
}
|
|
64576
65124
|
}
|
|
64577
65125
|
/** {@link downloadMedia} over a list, preserving order. */
|
|
@@ -64651,7 +65199,10 @@ function isDeliveryUnsupported(err) {
|
|
|
64651
65199
|
}
|
|
64652
65200
|
function hostedStorage(blob = blobStoreFromEnv()) {
|
|
64653
65201
|
if (!blob) return {};
|
|
64654
|
-
const tenant = pinnedTenant(blob.bucket, () =>
|
|
65202
|
+
const tenant = pinnedTenant(blob.bucket, async () => {
|
|
65203
|
+
const key = readEnvVar("GEMINI_API_KEY");
|
|
65204
|
+
return key ? tenantIdFor(key) : { tenant: await tenantIdFor("local"), pin: false };
|
|
65205
|
+
});
|
|
64655
65206
|
return {
|
|
64656
65207
|
mediaSink: createR2Sink(blob.bucket, {
|
|
64657
65208
|
// The SAME `links` object the minter gets below. Media GETs and upload
|
|
@@ -64690,10 +65241,17 @@ var client = new GeminiClient(hostedStorage());
|
|
|
64690
65241
|
init_images();
|
|
64691
65242
|
|
|
64692
65243
|
// src/sidecar.ts
|
|
64693
|
-
import { readdir, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
64694
|
-
import {
|
|
64695
|
-
import { join as join8 } from "node:path";
|
|
65244
|
+
import { readdir, readFile as readFile3, stat as stat2, lstat } from "node:fs/promises";
|
|
65245
|
+
import { join as join9, resolve as resolve5 } from "node:path";
|
|
64696
65246
|
var SIDECAR_SUFFIX2 = ".json";
|
|
65247
|
+
var SIDECAR_NAME = /^.+\.[A-Za-z0-9]+\.json$/;
|
|
65248
|
+
async function isRegularFile(path) {
|
|
65249
|
+
try {
|
|
65250
|
+
return (await lstat(path)).isFile();
|
|
65251
|
+
} catch {
|
|
65252
|
+
return false;
|
|
65253
|
+
}
|
|
65254
|
+
}
|
|
64697
65255
|
async function readSidecars(dir) {
|
|
64698
65256
|
let entries;
|
|
64699
65257
|
try {
|
|
@@ -64701,20 +65259,25 @@ async function readSidecars(dir) {
|
|
|
64701
65259
|
} catch {
|
|
64702
65260
|
return [];
|
|
64703
65261
|
}
|
|
65262
|
+
const sidecarNames = entries.filter((e) => SIDECAR_NAME.test(e));
|
|
65263
|
+
const outputs = new Set(sidecarNames.map((e) => resolve5(dir, e.slice(0, -SIDECAR_SUFFIX2.length))));
|
|
64704
65264
|
const records = [];
|
|
64705
|
-
for (const entry of
|
|
64706
|
-
|
|
64707
|
-
const sidecarPath = join8(dir, entry);
|
|
65265
|
+
for (const entry of sidecarNames) {
|
|
65266
|
+
const sidecarPath = join9(dir, entry);
|
|
64708
65267
|
try {
|
|
64709
65268
|
const parsed = JSON.parse(await readFile3(sidecarPath, "utf8"));
|
|
64710
65269
|
if (typeof parsed !== "object" || parsed === null) continue;
|
|
64711
65270
|
const { interaction_id: interactionId, images } = parsed;
|
|
64712
65271
|
if (typeof interactionId !== "string" || !interactionId) continue;
|
|
64713
65272
|
const paths = Array.isArray(images) ? images.filter((p) => typeof p === "string") : [];
|
|
65273
|
+
const kept = [];
|
|
65274
|
+
for (const p of paths) {
|
|
65275
|
+
if (outputs.has(resolve5(p)) && await isRegularFile(p)) kept.push(p);
|
|
65276
|
+
}
|
|
64714
65277
|
records.push({
|
|
64715
65278
|
sidecarPath,
|
|
64716
65279
|
interactionId,
|
|
64717
|
-
images:
|
|
65280
|
+
images: kept,
|
|
64718
65281
|
mtimeMs: (await stat2(sidecarPath)).mtimeMs
|
|
64719
65282
|
});
|
|
64720
65283
|
} catch {
|
|
@@ -64770,7 +65333,7 @@ function registerInteractTools(server, client2) {
|
|
|
64770
65333
|
"gemini_interact",
|
|
64771
65334
|
{
|
|
64772
65335
|
description: "Preferred tool for iterative refinement of a single image \u2014 multi-turn generation and editing via the Interactions API. To refine, pass the returned interaction `id` as `previous_interaction_id` on the next call; do NOT start a new interaction or re-send the image for each tweak. `continue_last: true` chains from this session's most recent interaction without threading the id. " + recoveryDescription + "Output is JPEG. " + confirmNote("Local file inputs are confirmed first"),
|
|
64773
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65336
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
64774
65337
|
inputSchema: external_exports.object({
|
|
64775
65338
|
input: external_exports.string().min(1).describe("Text prompt or editing instruction"),
|
|
64776
65339
|
previous_interaction_id: external_exports.string().optional().describe("ID from a prior gemini_interact call \u2014 continues that multi-turn conversation"),
|
|
@@ -64785,7 +65348,7 @@ function registerInteractTools(server, client2) {
|
|
|
64785
65348
|
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, Flash-only)"),
|
|
64786
65349
|
thinking_level: external_exports.enum(["minimal", "high"]).optional().describe("Reasoning depth; higher can help complex/structural edits"),
|
|
64787
65350
|
filename: external_exports.string().optional().describe("Base filename for the output image (extension stripped; default: slugified input)"),
|
|
64788
|
-
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or
|
|
65351
|
+
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
64789
65352
|
inline: external_exports.boolean().optional().describe("Return base64 images inline instead of writing to disk"),
|
|
64790
65353
|
google_search: external_exports.boolean().optional().describe("Ground the image in live Google Search results (current events, weather, data)"),
|
|
64791
65354
|
search_types: external_exports.array(external_exports.enum(["web_search", "image_search"])).optional().describe(
|
|
@@ -64943,7 +65506,7 @@ function registerVideoTools(server, client2) {
|
|
|
64943
65506
|
"gemini_video_generate",
|
|
64944
65507
|
{
|
|
64945
65508
|
description: 'Generate a short video (~10s) via the Gemini omni model: text\u2192video, image\u2192video / reference\u2192video (supply reference image[s]), interpolate between two stills (pass first frame then last frame as images), or continue a prior video (task: "edit" or "extend" + previous_interaction_id / continue_last; extensions add ~3-10s each, to ~40s total). Cost scales with `resolution` \u2014 draft at 360p, keep at 1080p/4k. Output is written to disk as MP4 (video has no inline MCP block). Video runs long \u2014 give it a `max_wait_ms` budget (or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. ' + confirmNote("Local file inputs are confirmed first"),
|
|
64946
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65509
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
64947
65510
|
inputSchema: external_exports.object({
|
|
64948
65511
|
prompt: external_exports.string().min(1).describe("Description of the video to generate (or the edit instruction when task=edit)"),
|
|
64949
65512
|
aspect_ratio: external_exports.enum(VIDEO_ASPECT_RATIOS).optional().describe("Exact output aspect ratio (omni: 16:9 or 9:16). `orientation` is the plain-language shorthand; this wins if both are given."),
|
|
@@ -64956,7 +65519,7 @@ function registerVideoTools(server, client2) {
|
|
|
64956
65519
|
images_base64: imagesBase64Schema(),
|
|
64957
65520
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS clipboard as a reference"),
|
|
64958
65521
|
filename: external_exports.string().optional().describe("Base filename for the output video (extension stripped; default: slugified prompt)"),
|
|
64959
|
-
output_dir: external_exports.string().optional().describe("Directory to write the video to (default: $GEMINI_OUTPUT_DIR or
|
|
65522
|
+
output_dir: external_exports.string().optional().describe("Directory to write the video to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
64960
65523
|
model: external_exports.string().regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")').optional().describe(`Model id override (default: ${DEFAULT_VIDEO_MODEL})`),
|
|
64961
65524
|
delivery: external_exports.enum(VIDEO_DELIVERY).optional().describe('How the clip comes back: "uri" (default \u2014 a Files API link the server downloads, no size ceiling) or "inline" (base64, capped ~4MB)'),
|
|
64962
65525
|
background: external_exports.boolean().optional().describe("Run the generation on Google's side and poll it, so a killed job can be recovered by gemini_get_result. Trade-off: retrieving a backgrounded interaction is unreliable today (some become permanently unreadable), so the default is off"),
|
|
@@ -65055,7 +65618,7 @@ function registerMusicTools(server, client2) {
|
|
|
65055
65618
|
"gemini_music_generate",
|
|
65056
65619
|
{
|
|
65057
65620
|
description: "Generate music from a text prompt (mood, genre, instruments, structure, or lyrics inline) via a Lyria model: lyria-3-clip-preview (30s instrumental clip, default, cheapest), lyria-3.5 (full-length song with vocals) or lyria-3-pro-preview (longer-form). Output is MP3, written to disk (or returned inline). Single-turn: a track cannot be refined by a follow-up call, so put the whole brief in the prompt. Runs long \u2014 give it a `max_wait_ms` budget (or `async: true` + gemini_get_result on a local install), or raise `timeout_ms`. Needs a funded account. " + confirmNote("Local file inputs are confirmed first"),
|
|
65058
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65621
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65059
65622
|
inputSchema: external_exports.object({
|
|
65060
65623
|
prompt: external_exports.string().min(1).describe("Description of the music: mood, genre, instruments, tempo, structure, or lyrics"),
|
|
65061
65624
|
model: external_exports.enum(MUSIC_MODELS).optional().describe(`Lyria model (default: ${DEFAULT_MUSIC_MODEL} \u2014 30s, $0.04). lyria-3.5 and lyria-3-pro-preview run minutes-long at $0.08.`),
|
|
@@ -65065,7 +65628,7 @@ function registerMusicTools(server, client2) {
|
|
|
65065
65628
|
images_base64: imagesBase64Schema(),
|
|
65066
65629
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS clipboard as a reference"),
|
|
65067
65630
|
filename: external_exports.string().optional().describe("Base filename for the output audio (extension stripped; default: slugified prompt)"),
|
|
65068
|
-
output_dir: external_exports.string().optional().describe("Directory to write audio to (default: $GEMINI_OUTPUT_DIR or
|
|
65631
|
+
output_dir: external_exports.string().optional().describe("Directory to write audio to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
65069
65632
|
inline: external_exports.boolean().optional().describe("Return base64 audio inline instead of writing to disk. A 30s track is ~1.9MB of base64 \u2014 the default hands back a path instead"),
|
|
65070
65633
|
background: external_exports.boolean().optional().describe("Run the generation on Google's side and poll it, so a killed job can be recovered by gemini_get_result. Off by default \u2014 see gemini_video_generate"),
|
|
65071
65634
|
timeout_ms: timeoutMsSchema,
|
|
@@ -65137,10 +65700,10 @@ function registerJobTools(server, client2) {
|
|
|
65137
65700
|
description: 'Retrieve a generation started with `async: true` or handed off by `max_wait_ms`. Pass the returned `job_id`: while running it reports status "running"; on completion it returns the normal result (image URLs/paths + meta); on failure it raises the recorded error, including the case where the generation was killed before it finished. On the hosted connector job records are stored durably and survive a restart; on a local stdio server they live with the process and expire ~10 min after completion, where the output dir / <image>.json sidecar is the fallback. A killed video/music job started with `background: true` is recovered from its upstream interaction when it finished there.',
|
|
65138
65701
|
// NOT read-only any more: recovering a killed job writes the media it
|
|
65139
65702
|
// pulls back, exactly as the generation tool would have.
|
|
65140
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65703
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65141
65704
|
inputSchema: external_exports.object({
|
|
65142
65705
|
job_id: external_exports.string().min(1).describe("The job_id returned by a generation tool called with async: true"),
|
|
65143
|
-
output_dir: external_exports.string().optional().describe("Where to write media recovered from a killed job (default: $GEMINI_OUTPUT_DIR or
|
|
65706
|
+
output_dir: external_exports.string().optional().describe("Where to write media recovered from a killed job (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)")
|
|
65144
65707
|
})
|
|
65145
65708
|
},
|
|
65146
65709
|
async (args) => {
|
|
@@ -65215,7 +65778,7 @@ function registerUsageTools(server, client2) {
|
|
|
65215
65778
|
"gemini_token_usage",
|
|
65216
65779
|
{
|
|
65217
65780
|
description: "Token usage for this session so far \u2014 what every generation has cost in tokens, added up. Call it before and after a workflow and subtract to get that workflow's cost; call it after a single generation for that call's. Reports tokens AND an estimated USD cost, priced per call against each call's own model and stamped with the date its rates were read (override with GEMINI_RATE_CARD). Note there is no account-balance endpoint to query \u2014 Google Cloud is post-paid and its billing data lags by hours \u2014 so this is the accurate way to attribute spend to a call.",
|
|
65218
|
-
annotations: toolAnnotations({ readOnly: true }),
|
|
65781
|
+
annotations: toolAnnotations({ readOnly: true, openWorld: false }),
|
|
65219
65782
|
inputSchema: external_exports.object({
|
|
65220
65783
|
reset: external_exports.boolean().optional().describe("Zero the running total after reporting it, so the next call measures from here. Use it to bracket a workflow without arithmetic.")
|
|
65221
65784
|
})
|
|
@@ -65276,7 +65839,7 @@ function registerFileTools(server, client2) {
|
|
|
65276
65839
|
"gemini_upload_file",
|
|
65277
65840
|
{
|
|
65278
65841
|
description: "Upload a file \u2014 an image, reference photo, picture, screenshot, video or audio clip \u2014 to the Gemini Files API ONCE, and get back a reusable `file_uri` (`files/<id>`) to attach to later image, video or music generations. Keywords: upload, upload file, upload image, upload photo, attach, reference image, reference photo, file_uri, files api, image reference, reuse across calls. Use this instead of pasting base64 into a tool call: the reference is a short string, so no image bytes ever enter the conversation, and it can be reused across many generations until it expires (~48h). Provide exactly one of `url` (the server downloads it), `data_base64`" + (onDisk ? ", or `path` (a local file)." : ", or `r2_key` (re-upload media this connector generated, from media[].r2_key in an earlier result).") + (onDisk ? " " + confirmNote("A local `path` is confirmed first") : " To upload a local file without base64: mint a signed PUT URL with gemini_get_upload_url, PUT the bytes to it, then pass the returned r2_key here."),
|
|
65279
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65842
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
65280
65843
|
inputSchema: external_exports.object({
|
|
65281
65844
|
url: external_exports.string().url().optional().describe(
|
|
65282
65845
|
`Public https URL the SERVER downloads and uploads (image/video/audio, up to ${wholeMb(urlMaxBytes)}MB). No bytes pass through the conversation.` + (onDisk ? "" : " Larger files: PUT them to a gemini_get_upload_url link and pass the r2_key instead \u2014 that path does not buffer.")
|
|
@@ -65329,6 +65892,7 @@ function registerFileTools(server, client2) {
|
|
|
65329
65892
|
body: { inputs },
|
|
65330
65893
|
target: resolved,
|
|
65331
65894
|
payload: { inputs, display_name: args.display_name },
|
|
65895
|
+
args,
|
|
65332
65896
|
confirmToken: args.confirmToken
|
|
65333
65897
|
});
|
|
65334
65898
|
if (gate) return gate;
|
|
@@ -65519,6 +66083,7 @@ function registerFileTools(server, client2) {
|
|
|
65519
66083
|
path: `/v1beta/${args.file_uri}`,
|
|
65520
66084
|
target: args.file_uri,
|
|
65521
66085
|
payload: { file_uri: args.file_uri },
|
|
66086
|
+
args,
|
|
65522
66087
|
confirmToken: args.confirmToken
|
|
65523
66088
|
});
|
|
65524
66089
|
if (gate) return gate;
|
|
@@ -65605,7 +66170,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65605
66170
|
"gemini_save_character",
|
|
65606
66171
|
{
|
|
65607
66172
|
description: "Save a named character to this account's persistent reference library: one reference image plus a short description. Saved characters have NO expiry \u2014 unlike uploads (~retention window) and Files API references (~48h) \u2014 so a name saved once keeps working in every later session. Re-saving a name replaces it. " + USE_CHARACTERS_HINT,
|
|
65608
|
-
annotations: { readOnlyHint: false, openWorldHint:
|
|
66173
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65609
66174
|
inputSchema: external_exports.object({
|
|
65610
66175
|
name: NAME_FIELD,
|
|
65611
66176
|
description: external_exports.string().min(1).describe("Short prose description woven into generation prompts (age, look, defining features, species\u2026)"),
|
|
@@ -65670,6 +66235,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65670
66235
|
path: `characters/${args.name}`,
|
|
65671
66236
|
target: args.name,
|
|
65672
66237
|
payload: { name: args.name },
|
|
66238
|
+
args,
|
|
65673
66239
|
confirmToken: args.confirmToken
|
|
65674
66240
|
});
|
|
65675
66241
|
if (gate) return gate;
|
|
@@ -65684,7 +66250,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65684
66250
|
"gemini_save_style",
|
|
65685
66251
|
{
|
|
65686
66252
|
description: "Save a named style preset to this account's persistent reference library: a reusable prompt fragment (and optionally a reference image) that a single word then applies to any generation. No expiry; re-saving a name replaces it. " + USE_STYLE_HINT,
|
|
65687
|
-
annotations: { readOnlyHint: false, openWorldHint:
|
|
66253
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65688
66254
|
inputSchema: external_exports.object({
|
|
65689
66255
|
name: NAME_FIELD,
|
|
65690
66256
|
prompt_fragment: external_exports.string().min(1).describe('The style text appended to generation prompts, e.g. "bold cartoon style, thick outlines, saturated colors"'),
|
|
@@ -65749,6 +66315,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65749
66315
|
path: `styles/${args.name}`,
|
|
65750
66316
|
target: args.name,
|
|
65751
66317
|
payload: { name: args.name },
|
|
66318
|
+
args,
|
|
65752
66319
|
confirmToken: args.confirmToken
|
|
65753
66320
|
});
|
|
65754
66321
|
if (gate) return gate;
|