@chrischall/gemini-mcp 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +2 -2
- package/dist/bundle.js +734 -191
- 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/models.js +11 -6
- package/dist/pricing.js +4 -0
- 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 +7 -7
- 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 +7 -4
- package/dist/tools/usage.js +1 -1
- package/dist/tools/video.js +5 -7
- package/dist/version.js +1 -1
- package/mint.yaml +1 -1
- package/package.json +2 -2
- package/server.json +39 -4
- package/skills/gemini-mcp/SKILL.md +5 -4
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
|
}
|
|
@@ -23439,10 +23439,10 @@ var require_v4 = __commonJS({
|
|
|
23439
23439
|
}
|
|
23440
23440
|
});
|
|
23441
23441
|
|
|
23442
|
-
// node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
|
|
23442
|
+
// node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
|
|
23443
23443
|
var z, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY, PROTOCOL_VERSION_META_KEY, CLIENT_INFO_META_KEY, SERVER_INFO_META_KEY, CLIENT_CAPABILITIES_META_KEY, SUBSCRIPTION_ID_META_KEY, LOG_LEVEL_META_KEY, JSONRPC_VERSION, JSONValueSchema, JSONObjectSchema, JSONArraySchema, ProgressTokenSchema, CursorSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultMetaObjectSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, JSONRPCNotificationSchema, JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, DiscoverRequestSchema, DiscoverResultSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, SubscriptionFilterSchema, SubscriptionsListenRequestParamsSchema, SubscriptionsListenRequestSchema, SubscriptionsAcknowledgedNotificationParamsSchema, SubscriptionsAcknowledgedNotificationSchema, SubscriptionsListenResultMetaSchema, SubscriptionsListenResultSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, TaskCreationParamsSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, SafeUrlSchema, OAuthProtectedResourceMetadataSchema, OAuthMetadataSchema, OpenIdProviderMetadataSchema, OpenIdProviderDiscoveryMetadataSchema, OAuthTokensSchema, IdJagTokenExchangeResponseSchema, OAuthErrorResponseSchema, OptionalSafeUrlSchema, OAuthClientMetadataSchema, OAuthClientInformationSchema, OAuthClientInformationFullSchema, OAuthClientRegistrationErrorSchema, OAuthTokenRevocationRequestSchema;
|
|
23444
23444
|
var init_auth_BNDyLTqp = __esm({
|
|
23445
|
-
"node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs"() {
|
|
23445
|
+
"node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs"() {
|
|
23446
23446
|
z = __toESM(require_v4(), 1);
|
|
23447
23447
|
LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
23448
23448
|
SUPPORTED_PROTOCOL_VERSIONS = [
|
|
@@ -24386,9 +24386,9 @@ var init_auth_BNDyLTqp = __esm({
|
|
|
24386
24386
|
}
|
|
24387
24387
|
});
|
|
24388
24388
|
|
|
24389
|
-
// node_modules/@modelcontextprotocol/core/dist/internal.mjs
|
|
24389
|
+
// node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/internal.mjs
|
|
24390
24390
|
var init_internal = __esm({
|
|
24391
|
-
"node_modules/@modelcontextprotocol/core/dist/internal.mjs"() {
|
|
24391
|
+
"node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/internal.mjs"() {
|
|
24392
24392
|
init_auth_BNDyLTqp();
|
|
24393
24393
|
}
|
|
24394
24394
|
});
|
|
@@ -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
|
}
|
|
@@ -59095,7 +59182,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
59095
59182
|
if (typeof raw === "string") {
|
|
59096
59183
|
const trimmed = raw.trim();
|
|
59097
59184
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
59098
|
-
return trimmed;
|
|
59185
|
+
return opts.trim === false ? raw : trimmed;
|
|
59099
59186
|
}
|
|
59100
59187
|
}
|
|
59101
59188
|
return opts.default;
|
|
@@ -59296,12 +59383,19 @@ function boundArgs(args) {
|
|
|
59296
59383
|
}
|
|
59297
59384
|
function confirmationFromEnv(options) {
|
|
59298
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
|
+
}
|
|
59299
59389
|
const spent = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
59300
59390
|
const mode = readConfirmMode(env);
|
|
59301
59391
|
const ttlSeconds = readConfirmTtl(env);
|
|
59302
59392
|
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
59303
|
-
const bound =
|
|
59304
|
-
const
|
|
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
|
+
};
|
|
59305
59399
|
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
59306
59400
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
59307
59401
|
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
@@ -59311,7 +59405,7 @@ function confirmationFromEnv(options) {
|
|
|
59311
59405
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
59312
59406
|
};
|
|
59313
59407
|
}
|
|
59314
|
-
const boundSubject =
|
|
59408
|
+
const boundSubject = async () => {
|
|
59315
59409
|
const read = await subject();
|
|
59316
59410
|
if ("content" in read)
|
|
59317
59411
|
return read;
|
|
@@ -59364,12 +59458,14 @@ var init_merge_write = __esm({
|
|
|
59364
59458
|
|
|
59365
59459
|
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
59366
59460
|
function hintResultOrRethrow(err) {
|
|
59461
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
59462
|
+
throw err;
|
|
59367
59463
|
if (err instanceof McpToolError && err.hint) {
|
|
59368
59464
|
return errorResult(`${err.message}
|
|
59369
59465
|
|
|
59370
59466
|
Hint: ${err.hint}`);
|
|
59371
59467
|
}
|
|
59372
|
-
|
|
59468
|
+
return errorResult(messageOf(err));
|
|
59373
59469
|
}
|
|
59374
59470
|
function mcpRequestFrom(args) {
|
|
59375
59471
|
const ctx = args.at(-1);
|
|
@@ -59712,10 +59808,12 @@ var init_write = __esm({
|
|
|
59712
59808
|
|
|
59713
59809
|
// node_modules/@chrischall/mcp-utils/dist/fs/output.js
|
|
59714
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";
|
|
59715
59813
|
function resolveOutputDir(perCall, envVar, opts = {}) {
|
|
59716
59814
|
const raw = perCall ?? readEnvVar(envVar, opts.env ? { env: opts.env } : {});
|
|
59717
59815
|
if (!raw)
|
|
59718
|
-
return
|
|
59816
|
+
return defaultOutputDir(envVar, opts.name);
|
|
59719
59817
|
const dir = expandPath(raw);
|
|
59720
59818
|
if (perCall !== void 0 && opts.allowedRoots) {
|
|
59721
59819
|
assertPathWithinRoots(dir, opts.allowedRoots);
|
|
@@ -59726,9 +59824,23 @@ function resolveOutputDir(perCall, envVar, opts = {}) {
|
|
|
59726
59824
|
mkdirSync3(dir, { recursive: true });
|
|
59727
59825
|
return dir;
|
|
59728
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
|
+
}
|
|
59729
59840
|
var init_output = __esm({
|
|
59730
59841
|
"node_modules/@chrischall/mcp-utils/dist/fs/output.js"() {
|
|
59731
59842
|
init_config();
|
|
59843
|
+
init_errors();
|
|
59732
59844
|
init_confine();
|
|
59733
59845
|
init_write();
|
|
59734
59846
|
}
|
|
@@ -59888,6 +60000,7 @@ var init_scrape = __esm({
|
|
|
59888
60000
|
// node_modules/@chrischall/mcp-utils/dist/http/throttle.js
|
|
59889
60001
|
var init_throttle = __esm({
|
|
59890
60002
|
"node_modules/@chrischall/mcp-utils/dist/http/throttle.js"() {
|
|
60003
|
+
init_cancel();
|
|
59891
60004
|
}
|
|
59892
60005
|
});
|
|
59893
60006
|
|
|
@@ -59913,6 +60026,113 @@ var init_net_atoms = __esm({
|
|
|
59913
60026
|
}
|
|
59914
60027
|
});
|
|
59915
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
|
+
|
|
59916
60136
|
// node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js
|
|
59917
60137
|
function retryAfterToMs(header, now = Date.now()) {
|
|
59918
60138
|
if (header == null)
|
|
@@ -59933,6 +60153,14 @@ var init_retry_after = __esm({
|
|
|
59933
60153
|
});
|
|
59934
60154
|
|
|
59935
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
|
+
}
|
|
59936
60164
|
function headerOf(headers, name) {
|
|
59937
60165
|
if (headers === void 0)
|
|
59938
60166
|
return void 0;
|
|
@@ -59958,6 +60186,49 @@ function detectEdgeBlock(input2) {
|
|
|
59958
60186
|
}
|
|
59959
60187
|
return null;
|
|
59960
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
|
+
}
|
|
59961
60232
|
function httpError(res, text, method, path, service) {
|
|
59962
60233
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
59963
60234
|
if (edge)
|
|
@@ -60008,13 +60279,26 @@ function createApiClient(opts) {
|
|
|
60008
60279
|
const sleep2 = opts.sleep ?? defaultSleep;
|
|
60009
60280
|
const unauthorized = () => opts.onUnauthorized ? opts.onUnauthorized() : new UnauthorizedError(service);
|
|
60010
60281
|
const retryStatuses = retry.statuses ?? [429];
|
|
60011
|
-
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
|
+
}
|
|
60012
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
|
+
};
|
|
60013
60297
|
async function withTimeout(run) {
|
|
60014
|
-
if (timeoutMs
|
|
60298
|
+
if (timeoutMs <= 0) {
|
|
60015
60299
|
const res2 = await run(withAmbientCancellation(void 0));
|
|
60016
60300
|
return { res: res2, done: () => {
|
|
60017
|
-
}, expired: void 0 };
|
|
60301
|
+
}, expired: void 0, timedOut: () => false };
|
|
60018
60302
|
}
|
|
60019
60303
|
const controller = new AbortController();
|
|
60020
60304
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -60035,16 +60319,11 @@ function createApiClient(opts) {
|
|
|
60035
60319
|
});
|
|
60036
60320
|
expired.catch(() => {
|
|
60037
60321
|
});
|
|
60038
|
-
return { res, done: () => clearTimeout(timer), expired };
|
|
60322
|
+
return { res, done: () => clearTimeout(timer), expired, timedOut: () => controller.signal.aborted };
|
|
60039
60323
|
}
|
|
60040
60324
|
function discard(attempt) {
|
|
60041
60325
|
attempt.done();
|
|
60042
|
-
|
|
60043
|
-
const body = attempt.res.body;
|
|
60044
|
-
body?.cancel?.()?.catch(() => {
|
|
60045
|
-
});
|
|
60046
|
-
} catch {
|
|
60047
|
-
}
|
|
60326
|
+
cancelBody(attempt.res);
|
|
60048
60327
|
}
|
|
60049
60328
|
async function readBody(attempt, read) {
|
|
60050
60329
|
try {
|
|
@@ -60052,15 +60331,40 @@ function createApiClient(opts) {
|
|
|
60052
60331
|
return await read(attempt.res);
|
|
60053
60332
|
return await Promise.race([read(attempt.res), attempt.expired]);
|
|
60054
60333
|
} catch (err) {
|
|
60055
|
-
|
|
60056
|
-
|
|
60057
|
-
|
|
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);
|
|
60058
60338
|
}
|
|
60339
|
+
if (!(err instanceof ResponseTooLargeError))
|
|
60340
|
+
markTransportFailure(err);
|
|
60059
60341
|
throw err;
|
|
60060
60342
|
} finally {
|
|
60061
60343
|
attempt.done();
|
|
60062
60344
|
}
|
|
60063
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;
|
|
60064
60368
|
const baseParts = (() => {
|
|
60065
60369
|
try {
|
|
60066
60370
|
const u = new URL(base);
|
|
@@ -60071,6 +60375,14 @@ function createApiClient(opts) {
|
|
|
60071
60375
|
})();
|
|
60072
60376
|
const baseOrigin = baseParts?.origin;
|
|
60073
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
|
+
}
|
|
60074
60386
|
const url4 = `${base}${path}${query}`;
|
|
60075
60387
|
if (baseOrigin === void 0)
|
|
60076
60388
|
return url4;
|
|
@@ -60085,23 +60397,66 @@ function createApiClient(opts) {
|
|
|
60085
60397
|
}
|
|
60086
60398
|
return url4;
|
|
60087
60399
|
}
|
|
60088
|
-
|
|
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) {
|
|
60089
60413
|
const { body: reqBody, contentType } = encodeBody(opt);
|
|
60090
60414
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
60091
60415
|
const url4 = resolveUrl2(path, query);
|
|
60092
60416
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
60093
|
-
const
|
|
60094
|
-
|
|
60095
|
-
|
|
60096
|
-
|
|
60097
|
-
|
|
60098
|
-
|
|
60099
|
-
|
|
60100
|
-
|
|
60101
|
-
|
|
60102
|
-
|
|
60103
|
-
|
|
60104
|
-
|
|
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
|
+
};
|
|
60105
60460
|
const once = () => withTimeout((signal) => opts.tokenManager ? opts.tokenManager.withAuth((token) => fetchWith(token, signal)) : (async () => fetchWith(await opts.getToken?.() || void 0, signal))());
|
|
60106
60461
|
let attempt = 0;
|
|
60107
60462
|
for (; ; ) {
|
|
@@ -60120,20 +60475,20 @@ function createApiClient(opts) {
|
|
|
60120
60475
|
return current;
|
|
60121
60476
|
}
|
|
60122
60477
|
}
|
|
60123
|
-
async function unauthorizedOrEdge(attempt, method, path) {
|
|
60478
|
+
async function unauthorizedOrEdge(attempt, method, path, maxBytes) {
|
|
60124
60479
|
const res = attempt.res;
|
|
60125
60480
|
let text = "";
|
|
60126
60481
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
60127
60482
|
discard(attempt);
|
|
60128
60483
|
} else {
|
|
60129
|
-
text = await
|
|
60484
|
+
text = await scanText(attempt, maxBytes);
|
|
60130
60485
|
}
|
|
60131
60486
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
60132
60487
|
if (edge)
|
|
60133
60488
|
return new EdgeBlockedError(res.status, edge.vendor, { service, method, path });
|
|
60134
60489
|
return unauthorized();
|
|
60135
60490
|
}
|
|
60136
|
-
async function rateLimitedError(attempt, method, path) {
|
|
60491
|
+
async function rateLimitedError(attempt, method, path, maxBytes) {
|
|
60137
60492
|
const res = attempt.res;
|
|
60138
60493
|
const retryAfter = responseHeader(res, "retry-after");
|
|
60139
60494
|
const retryAfterMs = retryAfterToMs(retryAfter);
|
|
@@ -60145,61 +60500,92 @@ function createApiClient(opts) {
|
|
|
60145
60500
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
60146
60501
|
discard(attempt);
|
|
60147
60502
|
} else {
|
|
60148
|
-
text = await
|
|
60503
|
+
text = await scanText(attempt, maxBytes);
|
|
60149
60504
|
}
|
|
60150
60505
|
const edgeBlock = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
60151
60506
|
return opts.onRateLimited({ status: res.status, retryAfter, retryAfterMs, edgeBlock, method, path });
|
|
60152
60507
|
}
|
|
60153
|
-
async function
|
|
60154
|
-
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);
|
|
60155
60535
|
const res = attempt.res;
|
|
60156
60536
|
if (res.status === 401)
|
|
60157
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60537
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60158
60538
|
if (res.status === 429)
|
|
60159
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60539
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60160
60540
|
if (res.status === 204) {
|
|
60161
60541
|
discard(attempt);
|
|
60162
|
-
return
|
|
60542
|
+
return parseJsonBody("", { expect: opt.expect, service, method, path, status: 204 });
|
|
60163
60543
|
}
|
|
60164
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
60165
60544
|
if (!res.ok) {
|
|
60166
|
-
throw httpError(res,
|
|
60545
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
60167
60546
|
}
|
|
60168
|
-
|
|
60169
|
-
|
|
60170
|
-
|
|
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
|
+
});
|
|
60171
60556
|
}
|
|
60172
|
-
async function
|
|
60557
|
+
async function fetchHtmlOnce(method, path, opt, dispatch) {
|
|
60558
|
+
const maxBytes = capOf(opt);
|
|
60173
60559
|
const headers = { Accept: "text/html,*/*", ...opt.headers };
|
|
60174
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
60560
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
60175
60561
|
const res = attempt.res;
|
|
60176
60562
|
if (res.status === 401)
|
|
60177
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60563
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60178
60564
|
if (res.status === 429)
|
|
60179
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60180
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
60565
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60181
60566
|
if (!res.ok) {
|
|
60182
|
-
throw httpError(res,
|
|
60567
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
60183
60568
|
}
|
|
60184
|
-
return
|
|
60569
|
+
return readText(attempt, maxBytes);
|
|
60185
60570
|
}
|
|
60186
|
-
async function
|
|
60571
|
+
async function fetchRawOnce(method, path, opt, dispatch) {
|
|
60572
|
+
const maxBytes = capOf(opt);
|
|
60187
60573
|
const headers = { Accept: "*/*", ...opt.headers };
|
|
60188
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
60574
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
60189
60575
|
const res = attempt.res;
|
|
60190
60576
|
if (res.status === 401)
|
|
60191
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
60577
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
60192
60578
|
if (res.status === 429)
|
|
60193
|
-
throw await rateLimitedError(attempt, method, path);
|
|
60579
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
60194
60580
|
if (!res.ok) {
|
|
60195
|
-
const text = await
|
|
60581
|
+
const text = await readText(attempt, maxBytes).catch((err) => {
|
|
60196
60582
|
if (err instanceof RequestTimeoutError)
|
|
60197
60583
|
throw err;
|
|
60198
60584
|
return "";
|
|
60199
60585
|
});
|
|
60200
60586
|
throw httpError(res, text, method, path, service);
|
|
60201
60587
|
}
|
|
60202
|
-
const bytes =
|
|
60588
|
+
const bytes = await readBytes(attempt, maxBytes);
|
|
60203
60589
|
return {
|
|
60204
60590
|
status: res.status,
|
|
60205
60591
|
contentType: responseHeader(res, "content-type"),
|
|
@@ -60209,6 +60595,14 @@ function createApiClient(opts) {
|
|
|
60209
60595
|
}
|
|
60210
60596
|
return { fetchJson, fetchHtml, fetchRaw };
|
|
60211
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
|
+
}
|
|
60212
60606
|
function buildQueryString(params) {
|
|
60213
60607
|
const parts = [];
|
|
60214
60608
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -60232,7 +60626,7 @@ function formatApiError(status, method, path, errorText, opts = {}) {
|
|
|
60232
60626
|
const safe = truncateErrorMessage(errorText ?? "", opts.max).trim();
|
|
60233
60627
|
return safe.length > 0 ? `${head}: ${safe}` : head;
|
|
60234
60628
|
}
|
|
60235
|
-
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;
|
|
60236
60630
|
var init_http = __esm({
|
|
60237
60631
|
"node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
|
|
60238
60632
|
init_headers();
|
|
@@ -60242,12 +60636,21 @@ var init_http = __esm({
|
|
|
60242
60636
|
init_throttle();
|
|
60243
60637
|
init_response_cache();
|
|
60244
60638
|
init_net_atoms();
|
|
60639
|
+
init_fetch_bounded();
|
|
60640
|
+
init_url_safety();
|
|
60641
|
+
init_body_limit();
|
|
60245
60642
|
init_net_atoms();
|
|
60643
|
+
init_url_safety();
|
|
60246
60644
|
init_retry_after();
|
|
60247
60645
|
DEFAULT_RETRY = { count: 1, delayMs: 2e3 };
|
|
60248
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]);
|
|
60249
60650
|
UnauthorizedError = class extends Error {
|
|
60250
60651
|
status = 401;
|
|
60652
|
+
/** Always `'credential_rejected'` unless a subclass says otherwise. */
|
|
60653
|
+
kind = "credential_rejected";
|
|
60251
60654
|
constructor(service) {
|
|
60252
60655
|
super(`Unauthorized (401) from ${service} \u2014 the token is missing, invalid, or expired.`);
|
|
60253
60656
|
this.name = "UnauthorizedError";
|
|
@@ -60256,6 +60659,8 @@ var init_http = __esm({
|
|
|
60256
60659
|
};
|
|
60257
60660
|
RateLimitedError = class extends Error {
|
|
60258
60661
|
status = 429;
|
|
60662
|
+
/** `'http'`: the far side answered, with a 429 (see `status`). */
|
|
60663
|
+
kind = "http";
|
|
60259
60664
|
retryAfterMs;
|
|
60260
60665
|
constructor(service, retryAfterMs) {
|
|
60261
60666
|
super(`Rate limited (429) by ${service} after retries.`);
|
|
@@ -60266,6 +60671,8 @@ var init_http = __esm({
|
|
|
60266
60671
|
};
|
|
60267
60672
|
RequestTimeoutError = class extends Error {
|
|
60268
60673
|
timeoutMs;
|
|
60674
|
+
/** Always `'timeout'` unless a subclass says otherwise. */
|
|
60675
|
+
kind = "timeout";
|
|
60269
60676
|
constructor(service, timeoutMs) {
|
|
60270
60677
|
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
60271
60678
|
this.name = "RequestTimeoutError";
|
|
@@ -60273,6 +60680,33 @@ var init_http = __esm({
|
|
|
60273
60680
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
60274
60681
|
}
|
|
60275
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"]);
|
|
60276
60710
|
ApiError = class extends Error {
|
|
60277
60711
|
status;
|
|
60278
60712
|
constructor(status, message) {
|
|
@@ -60285,6 +60719,8 @@ var init_http = __esm({
|
|
|
60285
60719
|
EdgeBlockedError = class extends ApiError {
|
|
60286
60720
|
/** Which edge refused the request, e.g. `'CloudFront'` or `'Cloudflare'`. */
|
|
60287
60721
|
vendor;
|
|
60722
|
+
/** Always `'edge_blocked'`: the credential was never judged. */
|
|
60723
|
+
kind = "edge_blocked";
|
|
60288
60724
|
/**
|
|
60289
60725
|
* @param where The service the request was for, and — when the thrower knows
|
|
60290
60726
|
* it — the method and path, which the message names.
|
|
@@ -60348,6 +60784,7 @@ var init_concurrency = __esm({
|
|
|
60348
60784
|
// node_modules/@chrischall/mcp-utils/dist/dates/index.js
|
|
60349
60785
|
var init_dates = __esm({
|
|
60350
60786
|
"node_modules/@chrischall/mcp-utils/dist/dates/index.js"() {
|
|
60787
|
+
init_config();
|
|
60351
60788
|
}
|
|
60352
60789
|
});
|
|
60353
60790
|
|
|
@@ -60380,7 +60817,7 @@ var init_zod2 = __esm({
|
|
|
60380
60817
|
NonNegInt = external_exports.number().int().nonnegative();
|
|
60381
60818
|
NonEmptyString = external_exports.string().min(1);
|
|
60382
60819
|
IsoDate = external_exports.iso.date();
|
|
60383
|
-
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"));
|
|
60384
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.");
|
|
60385
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.');
|
|
60386
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.");
|
|
@@ -60415,6 +60852,7 @@ var init_auth = __esm({
|
|
|
60415
60852
|
init_config();
|
|
60416
60853
|
init_errors();
|
|
60417
60854
|
init_http();
|
|
60855
|
+
init_cancel();
|
|
60418
60856
|
init_cached_token();
|
|
60419
60857
|
init_es256();
|
|
60420
60858
|
}
|
|
@@ -60466,7 +60904,8 @@ __export(images_exports, {
|
|
|
60466
60904
|
});
|
|
60467
60905
|
import { readFile, stat } from "node:fs/promises";
|
|
60468
60906
|
import { existsSync as existsSync3 } from "node:fs";
|
|
60469
|
-
import {
|
|
60907
|
+
import { homedir as homedir3 } from "node:os";
|
|
60908
|
+
import { delimiter, join as join7, resolve as resolve4, isAbsolute as isAbsolute4 } from "node:path";
|
|
60470
60909
|
function uploadRoots() {
|
|
60471
60910
|
const roots = readEnvVar("GEMINI_UPLOAD_DIR")?.split(delimiter).map((r) => r.trim()).filter(Boolean);
|
|
60472
60911
|
return roots && roots.length > 0 ? roots : void 0;
|
|
@@ -60487,7 +60926,7 @@ function referenceRoots(env = process.env) {
|
|
|
60487
60926
|
let roots = rootList("GEMINI_REFERENCE_DIR", env) ?? rootList("GEMINI_UPLOAD_DIR", env);
|
|
60488
60927
|
if (!roots) {
|
|
60489
60928
|
const dataDir = readEnvVar("MCP_DATA_DIR", { env });
|
|
60490
|
-
if (dataDir) roots = [
|
|
60929
|
+
if (dataDir) roots = [join7(dataDir, "uploads")];
|
|
60491
60930
|
}
|
|
60492
60931
|
if (!roots) return void 0;
|
|
60493
60932
|
const output2 = readEnvVar("GEMINI_OUTPUT_DIR", { env })?.trim();
|
|
@@ -60563,7 +61002,7 @@ function resolveInputPath(p, kind) {
|
|
|
60563
61002
|
if (isAbsolute4(p) && existsSync3(p)) return p;
|
|
60564
61003
|
const inputDir = readEnvVar("GEMINI_INPUT_DIR");
|
|
60565
61004
|
if (inputDir) {
|
|
60566
|
-
const candidate2 =
|
|
61005
|
+
const candidate2 = join7(inputDir, p);
|
|
60567
61006
|
if (existsSync3(candidate2)) return resolve4(candidate2);
|
|
60568
61007
|
}
|
|
60569
61008
|
const cwd = resolve4(p);
|
|
@@ -60657,11 +61096,14 @@ function baseName(name) {
|
|
|
60657
61096
|
const safe = stripped.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
60658
61097
|
return safe || "image";
|
|
60659
61098
|
}
|
|
61099
|
+
function defaultOutputDir2() {
|
|
61100
|
+
return join7(homedir3(), "Downloads", OUTPUT_DIR_NAME);
|
|
61101
|
+
}
|
|
60660
61102
|
function lookupOutputDir(perCall) {
|
|
60661
61103
|
const trimmed = perCall?.trim() || void 0;
|
|
60662
61104
|
const configured = readEnvVar("GEMINI_OUTPUT_DIR");
|
|
60663
61105
|
const raw = trimmed ?? configured;
|
|
60664
|
-
if (!raw) return
|
|
61106
|
+
if (!raw) return defaultOutputDir2();
|
|
60665
61107
|
const dir = expandPath(raw);
|
|
60666
61108
|
if (trimmed && configured) assertPathWithinRoots(dir, [configured]);
|
|
60667
61109
|
return dir;
|
|
@@ -60669,10 +61111,11 @@ function lookupOutputDir(perCall) {
|
|
|
60669
61111
|
function resolveOutputDir2(perCall) {
|
|
60670
61112
|
const configured = readEnvVar("GEMINI_OUTPUT_DIR");
|
|
60671
61113
|
return resolveOutputDir(perCall?.trim() || void 0, "GEMINI_OUTPUT_DIR", {
|
|
61114
|
+
name: OUTPUT_DIR_NAME,
|
|
60672
61115
|
...configured ? { allowedRoots: [configured] } : {}
|
|
60673
61116
|
});
|
|
60674
61117
|
}
|
|
60675
|
-
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;
|
|
60676
61119
|
var init_images = __esm({
|
|
60677
61120
|
"src/images.ts"() {
|
|
60678
61121
|
"use strict";
|
|
@@ -60707,6 +61150,7 @@ var init_images = __esm({
|
|
|
60707
61150
|
flac: "audio/flac",
|
|
60708
61151
|
...VIDEO_MIME_BY_EXT
|
|
60709
61152
|
};
|
|
61153
|
+
OUTPUT_DIR_NAME = "gemini-mcp";
|
|
60710
61154
|
}
|
|
60711
61155
|
});
|
|
60712
61156
|
|
|
@@ -60719,7 +61163,7 @@ import { execFile } from "node:child_process";
|
|
|
60719
61163
|
import { promisify } from "node:util";
|
|
60720
61164
|
import { readFile as readFile2, unlink } from "node:fs/promises";
|
|
60721
61165
|
import { tmpdir } from "node:os";
|
|
60722
|
-
import { join as
|
|
61166
|
+
import { join as join8 } from "node:path";
|
|
60723
61167
|
async function readClipboardImage(run = defaultRun) {
|
|
60724
61168
|
if (process.platform !== "darwin") {
|
|
60725
61169
|
throw new McpToolError("from_clipboard is only supported on macOS", {
|
|
@@ -60727,8 +61171,8 @@ async function readClipboardImage(run = defaultRun) {
|
|
|
60727
61171
|
});
|
|
60728
61172
|
}
|
|
60729
61173
|
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
60730
|
-
const png =
|
|
60731
|
-
const jpg =
|
|
61174
|
+
const png = join8(tmpdir(), `gemini-clip-${id}.png`);
|
|
61175
|
+
const jpg = join8(tmpdir(), `gemini-clip-${id}.jpg`);
|
|
60732
61176
|
try {
|
|
60733
61177
|
try {
|
|
60734
61178
|
await run("osascript", [
|
|
@@ -60775,15 +61219,15 @@ init_dist2();
|
|
|
60775
61219
|
|
|
60776
61220
|
// src/dotenv.ts
|
|
60777
61221
|
init_dist2();
|
|
60778
|
-
import { dirname as dirname3, join as
|
|
61222
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
60779
61223
|
import { fileURLToPath } from "node:url";
|
|
60780
61224
|
async function loadStdioDotenv() {
|
|
60781
61225
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
60782
|
-
await loadDotenvSafely({ path:
|
|
61226
|
+
await loadDotenvSafely({ path: join6(here, "..", ".env"), override: false });
|
|
60783
61227
|
}
|
|
60784
61228
|
|
|
60785
61229
|
// src/version.ts
|
|
60786
|
-
var VERSION = "2.
|
|
61230
|
+
var VERSION = "2.4.0";
|
|
60787
61231
|
|
|
60788
61232
|
// src/errors.ts
|
|
60789
61233
|
init_dist2();
|
|
@@ -60850,7 +61294,7 @@ function registerModelTools(server, client2) {
|
|
|
60850
61294
|
"gemini_list_models",
|
|
60851
61295
|
{
|
|
60852
61296
|
description: "List the Gemini image-generation models available to your API key (Nano Banana / Nano Banana Pro family), and the current default model.",
|
|
60853
|
-
annotations: {
|
|
61297
|
+
annotations: toolAnnotations({ readOnly: true, openWorld: true })
|
|
60854
61298
|
},
|
|
60855
61299
|
async () => {
|
|
60856
61300
|
const models = await client2.listModels();
|
|
@@ -60894,12 +61338,6 @@ function statusOf(err) {
|
|
|
60894
61338
|
|
|
60895
61339
|
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
60896
61340
|
init_zod();
|
|
60897
|
-
function statusOf2(err) {
|
|
60898
|
-
if (typeof err !== "object" || err === null)
|
|
60899
|
-
return void 0;
|
|
60900
|
-
const s = err.status ?? err.statusCode;
|
|
60901
|
-
return typeof s === "number" ? s : void 0;
|
|
60902
|
-
}
|
|
60903
61341
|
var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
60904
61342
|
"ok",
|
|
60905
61343
|
"no_credential",
|
|
@@ -60915,6 +61353,9 @@ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
|
60915
61353
|
function isArm(kind) {
|
|
60916
61354
|
return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
|
|
60917
61355
|
}
|
|
61356
|
+
function fromSource(source) {
|
|
61357
|
+
return source !== null ? ` from '${source}'` : "";
|
|
61358
|
+
}
|
|
60918
61359
|
function credentialHint(arm, prefix, hostLabel, source) {
|
|
60919
61360
|
switch (arm) {
|
|
60920
61361
|
case "ok":
|
|
@@ -60922,15 +61363,15 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
60922
61363
|
case "no_credential":
|
|
60923
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.`;
|
|
60924
61365
|
case "credential_rejected":
|
|
60925
|
-
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.`;
|
|
60926
61367
|
case "edge_blocked":
|
|
60927
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.`;
|
|
60928
61369
|
case "session_expired":
|
|
60929
|
-
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.`;
|
|
60930
61371
|
case "verification_pending":
|
|
60931
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.`;
|
|
60932
61373
|
case "timeout":
|
|
60933
|
-
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.`;
|
|
60934
61375
|
case "http":
|
|
60935
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.`;
|
|
60936
61377
|
case "transport":
|
|
@@ -60951,7 +61392,8 @@ async function runCredentialHealthcheck(args) {
|
|
|
60951
61392
|
state = await resolveCredential();
|
|
60952
61393
|
} catch (e) {
|
|
60953
61394
|
const edge = edgeBlockOf(e);
|
|
60954
|
-
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);
|
|
60955
61397
|
const result2 = {
|
|
60956
61398
|
ok: false,
|
|
60957
61399
|
// Still false, and still no source: a classification explains WHY
|
|
@@ -60998,10 +61440,11 @@ async function runCredentialHealthcheck(args) {
|
|
|
60998
61440
|
try {
|
|
60999
61441
|
await probeFn();
|
|
61000
61442
|
} catch (e) {
|
|
61001
|
-
status =
|
|
61443
|
+
status = errorStatusOf(e);
|
|
61002
61444
|
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
61003
61445
|
const edge = edgeBlockOf(e);
|
|
61004
|
-
|
|
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";
|
|
61005
61448
|
let kind = arm;
|
|
61006
61449
|
let detail = edge !== null ? { vendor: edge.vendor } : void 0;
|
|
61007
61450
|
const custom2 = classifyThrown?.(e);
|
|
@@ -61068,14 +61511,14 @@ init_zod();
|
|
|
61068
61511
|
init_dist2();
|
|
61069
61512
|
|
|
61070
61513
|
// src/models.ts
|
|
61071
|
-
var DEFAULT_IMAGE_MODEL = "gemini-
|
|
61514
|
+
var DEFAULT_IMAGE_MODEL = "gemini-nano-banana-2.1";
|
|
61072
61515
|
var DEFAULT_VIDEO_MODEL = "gemini-omni-1.1-flash";
|
|
61073
61516
|
var DEFAULT_MUSIC_MODEL = "lyria-3-clip-preview";
|
|
61074
61517
|
function resolveModel(perCall, envOverride) {
|
|
61075
61518
|
return perCall?.trim() || envOverride?.trim() || DEFAULT_IMAGE_MODEL;
|
|
61076
61519
|
}
|
|
61077
61520
|
function filterImageModels(raw) {
|
|
61078
|
-
return raw.filter((m) => /image/i.test(m.name ?? "") && !/imagen/i.test(m.name ?? "")).map((m) => ({
|
|
61521
|
+
return raw.filter((m) => /image|nano-banana/i.test(m.name ?? "") && !/imagen/i.test(m.name ?? "")).map((m) => ({
|
|
61079
61522
|
id: (m.name ?? "").replace(/^models\//, ""),
|
|
61080
61523
|
displayName: m.displayName ?? "",
|
|
61081
61524
|
description: m.description ?? ""
|
|
@@ -61204,10 +61647,12 @@ function assertPublicHttpsUrl(raw, requested) {
|
|
|
61204
61647
|
}
|
|
61205
61648
|
const rawHost = url4.hostname.toLowerCase();
|
|
61206
61649
|
const isIpv6Literal = rawHost.startsWith("[") && rawHost.endsWith("]");
|
|
61207
|
-
const host = isIpv6Literal ? rawHost.slice(1, -1) : rawHost;
|
|
61650
|
+
const host = isIpv6Literal ? rawHost.slice(1, -1) : rawHost.replace(/\.+$/, "");
|
|
61208
61651
|
const v4 = isIpv6Literal ? void 0 : ipv4Octets(host);
|
|
61209
61652
|
const v6 = isIpv6Literal ? ipv6Groups(host) : void 0;
|
|
61210
|
-
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);
|
|
61211
61656
|
if (blocked) {
|
|
61212
61657
|
throw new McpToolError(
|
|
61213
61658
|
`Refusing to fetch ${describeUrl(raw, requested)}: "${url4.hostname}" is a private, loopback or link-local address, not a public host.`,
|
|
@@ -61554,10 +61999,12 @@ async function readPin(bucket) {
|
|
|
61554
61999
|
}
|
|
61555
62000
|
function pinnedTenant(bucket, derive) {
|
|
61556
62001
|
let cached2;
|
|
61557
|
-
async function
|
|
62002
|
+
async function resolve6() {
|
|
61558
62003
|
const pinned = await readPin(bucket);
|
|
61559
62004
|
if (pinned) return { tenant: pinned, pinned: true };
|
|
61560
|
-
const
|
|
62005
|
+
const derived2 = await derive();
|
|
62006
|
+
if (typeof derived2 !== "string") return { tenant: derived2.tenant, pinned: false };
|
|
62007
|
+
const tenant = derived2;
|
|
61561
62008
|
try {
|
|
61562
62009
|
await bucket.put(TENANT_PIN_KEY, JSON.stringify({ tenant }), {
|
|
61563
62010
|
httpMetadata: { contentType: "application/json" }
|
|
@@ -61569,7 +62016,7 @@ function pinnedTenant(bucket, derive) {
|
|
|
61569
62016
|
}
|
|
61570
62017
|
return () => {
|
|
61571
62018
|
if (cached2) return cached2;
|
|
61572
|
-
const attempt =
|
|
62019
|
+
const attempt = resolve6().then(({ tenant, pinned }) => {
|
|
61573
62020
|
if (!pinned && cached2 === attempt) cached2 = void 0;
|
|
61574
62021
|
return tenant;
|
|
61575
62022
|
});
|
|
@@ -61952,7 +62399,7 @@ function resolveAspectRatio(opts, supported) {
|
|
|
61952
62399
|
var orientationSchema = external_exports.enum(ORIENTATIONS).optional().describe(
|
|
61953
62400
|
"Output shape in plain terms: landscape (16:9), portrait (9:16) or square (1:1). For any other proportion \u2014 3:2, 4:3, 4:5, 21:9 \u2014 use aspect_ratio, which wins if both are given."
|
|
61954
62401
|
);
|
|
61955
|
-
var MODEL_CHOICE_GUIDE = "gemini-
|
|
62402
|
+
var MODEL_CHOICE_GUIDE = "gemini-nano-banana-2.1 (Nano Banana 2.1, the default) is the generalist: fast, 4K, reliable text, strong multi-reference consistency. gemini-3-pro-image (Pro) is for the hardest work \u2014 best world knowledge, brand precision. gemini-3.1-flash-lite-image (Lite) is cheapest: 1K only, no search grounding.";
|
|
61956
62403
|
var timeoutMsSchema = external_exports.number().int().positive().optional().describe(
|
|
61957
62404
|
"Upstream timeout in ms for this call (default $GEMINI_TIMEOUT_MS, else 60000 \u2014 120000 at 4K, which runs past 60s)"
|
|
61958
62405
|
);
|
|
@@ -62055,8 +62502,8 @@ var sharedImageSchema = {
|
|
|
62055
62502
|
model: external_exports.string().regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")').optional().describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
62056
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."),
|
|
62057
62504
|
orientation: orientationSchema,
|
|
62058
|
-
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K,
|
|
62059
|
-
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or
|
|
62505
|
+
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, gemini-3.1-flash-image only)"),
|
|
62506
|
+
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
62060
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"),
|
|
62061
62508
|
seed: external_exports.number().int().optional().describe("Seed for reproducible generation; random if omitted"),
|
|
62062
62509
|
thinking_level: external_exports.enum(["minimal", "high"]).optional().describe("Reasoning depth (Gemini 3 models); higher can help complex/structural edits"),
|
|
@@ -62510,13 +62957,13 @@ function persistableResult(result) {
|
|
|
62510
62957
|
return { result };
|
|
62511
62958
|
}
|
|
62512
62959
|
function awaitWithBudget(promise2, waitMs) {
|
|
62513
|
-
return new Promise((
|
|
62514
|
-
const timer = setTimeout(() =>
|
|
62960
|
+
return new Promise((resolve6, reject) => {
|
|
62961
|
+
const timer = setTimeout(() => resolve6(void 0), waitMs);
|
|
62515
62962
|
timer.unref?.();
|
|
62516
62963
|
promise2.then(
|
|
62517
62964
|
(result) => {
|
|
62518
62965
|
clearTimeout(timer);
|
|
62519
|
-
|
|
62966
|
+
resolve6(result);
|
|
62520
62967
|
},
|
|
62521
62968
|
(err) => {
|
|
62522
62969
|
clearTimeout(timer);
|
|
@@ -62584,7 +63031,7 @@ var JobRegistry = class {
|
|
|
62584
63031
|
/** Await outstanding durable writes. Tests only — handlers never need it. */
|
|
62585
63032
|
async drain() {
|
|
62586
63033
|
for (let round = 0; round < 50; round++) {
|
|
62587
|
-
await new Promise((
|
|
63034
|
+
await new Promise((resolve6) => setTimeout(resolve6, 0));
|
|
62588
63035
|
if (!this.pending.size) return;
|
|
62589
63036
|
await Promise.allSettled([...this.pending]);
|
|
62590
63037
|
}
|
|
@@ -62889,6 +63336,10 @@ var PRICED_AT = "2026-09-09";
|
|
|
62889
63336
|
var RATE_CARD = Object.freeze({
|
|
62890
63337
|
"gemini-3-pro-image": { input: 2, text_output: 12, image_output: 120 },
|
|
62891
63338
|
"gemini-3.1-flash-image": { input: 0.5, text_output: 3, image_output: 60 },
|
|
63339
|
+
// Nano Banana 2.1 (GA 2026-10-06; rates read 2026-10-14, after PRICED_AT):
|
|
63340
|
+
// a pricier input and text rate than 2 but half the image rate, so a 1K
|
|
63341
|
+
// image is $0.0336 rather than $0.067.
|
|
63342
|
+
"gemini-nano-banana-2.1": { input: 1.5, text_output: 7.5, image_output: 30 },
|
|
62892
63343
|
"gemini-3.1-flash-lite-image": { input: 0.25, text_output: 1.5, image_output: 30 },
|
|
62893
63344
|
// Google publishes 2.5-flash-image's output as $0.039 PER IMAGE at 1290
|
|
62894
63345
|
// tokens, not as a per-1M rate. 0.039 / 1290 * 1e6 = 30.23 — a back-derived
|
|
@@ -63016,10 +63467,18 @@ async function confirmWrite(ctx, o) {
|
|
|
63016
63467
|
message: o.message,
|
|
63017
63468
|
details: preview,
|
|
63018
63469
|
tool: o.tool,
|
|
63470
|
+
// One API key per process (a hosted deployment runs one child per
|
|
63471
|
+
// account), so there is no in-process principal to bind.
|
|
63472
|
+
account: void 0,
|
|
63473
|
+
args: withoutConfirmToken(o.args),
|
|
63019
63474
|
confirmToken: o.confirmToken,
|
|
63020
63475
|
subject: () => ({ target: o.target, payload: o.payload, preview })
|
|
63021
63476
|
}));
|
|
63022
63477
|
}
|
|
63478
|
+
function withoutConfirmToken(args) {
|
|
63479
|
+
const { confirmToken: _omit, ...rest } = args;
|
|
63480
|
+
return rest;
|
|
63481
|
+
}
|
|
63023
63482
|
async function confirmLocalInputs(ctx, o) {
|
|
63024
63483
|
const hasLocal = (o.imagePaths?.length ?? 0) > 0 || Boolean(o.videoPath);
|
|
63025
63484
|
if (!hasLocal) return void 0;
|
|
@@ -63036,6 +63495,7 @@ async function confirmLocalInputs(ctx, o) {
|
|
|
63036
63495
|
body: { inputs },
|
|
63037
63496
|
target: "",
|
|
63038
63497
|
payload: { request: o.request, inputs },
|
|
63498
|
+
args: o.request,
|
|
63039
63499
|
confirmToken: o.confirmToken
|
|
63040
63500
|
});
|
|
63041
63501
|
}
|
|
@@ -63049,7 +63509,7 @@ function registerGenerateTools(server, client2) {
|
|
|
63049
63509
|
"gemini_image_generate",
|
|
63050
63510
|
{
|
|
63051
63511
|
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"),
|
|
63052
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63512
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63053
63513
|
inputSchema: external_exports.object({
|
|
63054
63514
|
prompt: external_exports.string().min(1).describe("Text prompt describing the image"),
|
|
63055
63515
|
count: external_exports.number().int().positive().max(8).optional().describe("Number of independent images (default 1)"),
|
|
@@ -63169,7 +63629,7 @@ function registerGenerateTools(server, client2) {
|
|
|
63169
63629
|
"gemini_image_edit",
|
|
63170
63630
|
{
|
|
63171
63631
|
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"),
|
|
63172
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63632
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63173
63633
|
inputSchema: external_exports.object({
|
|
63174
63634
|
prompt: external_exports.string().min(1).describe("Instruction describing the edit or composition"),
|
|
63175
63635
|
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."),
|
|
@@ -63260,7 +63720,7 @@ function registerSetTools(server, client2) {
|
|
|
63260
63720
|
"gemini_image_set",
|
|
63261
63721
|
{
|
|
63262
63722
|
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"),
|
|
63263
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63723
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
63264
63724
|
inputSchema: external_exports.object({
|
|
63265
63725
|
master_prompt: external_exports.string().min(1).describe("Prompt for the master/reference image"),
|
|
63266
63726
|
scenes: external_exports.array(external_exports.string().min(1)).min(1).max(8).optional().describe("Per-image prompts (1-8); each references the master"),
|
|
@@ -63419,6 +63879,7 @@ var LIBRARY_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
|
63419
63879
|
function isValidLibraryName(name) {
|
|
63420
63880
|
return LIBRARY_NAME_PATTERN.test(name);
|
|
63421
63881
|
}
|
|
63882
|
+
var LIBRARY_MAX_LIST_PAGES = 20;
|
|
63422
63883
|
function createR2Library(bucket, opts) {
|
|
63423
63884
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
63424
63885
|
const tenantId = tenantResolver(opts.tenant);
|
|
@@ -63450,10 +63911,10 @@ function createR2Library(bucket, opts) {
|
|
|
63450
63911
|
const prefix = `${LIBRARY_KEY_PREFIX}/${await tenantId()}/${kind}/`;
|
|
63451
63912
|
const keys = [];
|
|
63452
63913
|
let cursor;
|
|
63453
|
-
for (; ; ) {
|
|
63914
|
+
for (let n = 0; n < LIBRARY_MAX_LIST_PAGES; n++) {
|
|
63454
63915
|
const page = await bucket.list({ prefix, cursor, limit: 1e3 });
|
|
63455
63916
|
keys.push(...page.objects.map((o) => o.key).filter((k) => k.endsWith(".json")));
|
|
63456
|
-
if (!page.truncated) break;
|
|
63917
|
+
if (!page.truncated || !page.cursor) break;
|
|
63457
63918
|
cursor = page.cursor;
|
|
63458
63919
|
}
|
|
63459
63920
|
const records = await Promise.all(keys.map((k) => readJson(k)));
|
|
@@ -63741,8 +64202,9 @@ var SessionState = class {
|
|
|
63741
64202
|
* Accumulated in the CLIENT rather than in the tools, so a call that was
|
|
63742
64203
|
* billed and then failed (a safety filter returning no image, say) still
|
|
63743
64204
|
* counts — it cost money regardless of whether a tool could use the result.
|
|
63744
|
-
*
|
|
63745
|
-
*
|
|
64205
|
+
* On the session, never module scope: a module-level total would report
|
|
64206
|
+
* other users' spend as yours on any host that served several users from one
|
|
64207
|
+
* process (see the invariant above).
|
|
63746
64208
|
*/
|
|
63747
64209
|
usageTotal;
|
|
63748
64210
|
/** How many upstream calls contributed to {@link usageTotal}. */
|
|
@@ -63807,6 +64269,25 @@ function resolveTimeoutMs(perCallMs, imageSize) {
|
|
|
63807
64269
|
return imageSize === "4K" ? REQUEST_TIMEOUT_4K_MS : REQUEST_TIMEOUT_MS;
|
|
63808
64270
|
}
|
|
63809
64271
|
var UPLOAD_BASE_URL = "https://generativelanguage.googleapis.com/upload/v1beta";
|
|
64272
|
+
function isGoogleUploadUrl(raw) {
|
|
64273
|
+
let u;
|
|
64274
|
+
try {
|
|
64275
|
+
u = new URL(raw);
|
|
64276
|
+
} catch {
|
|
64277
|
+
return false;
|
|
64278
|
+
}
|
|
64279
|
+
return u.protocol === "https:" && (u.hostname === "googleapis.com" || u.hostname.endsWith(".googleapis.com"));
|
|
64280
|
+
}
|
|
64281
|
+
var MIN_TRANSFER_BYTES_PER_SEC = 256 * 1024;
|
|
64282
|
+
function transferTimeoutMs(bytes) {
|
|
64283
|
+
return resolveTimeoutMs() + Math.ceil(bytes / MIN_TRANSFER_BYTES_PER_SEC * 1e3);
|
|
64284
|
+
}
|
|
64285
|
+
function transferDeadline(ms) {
|
|
64286
|
+
const ctrl = new AbortController();
|
|
64287
|
+
const timer = setTimeout(() => ctrl.abort(new DOMException(`timed out after ${ms}ms`, "TimeoutError")), ms);
|
|
64288
|
+
timer.unref?.();
|
|
64289
|
+
return { signal: ctrl.signal, clear: () => clearTimeout(timer) };
|
|
64290
|
+
}
|
|
63810
64291
|
var FILE_MAX_BYTES = 2 * 1024 ** 3;
|
|
63811
64292
|
var FILE_POLL_INTERVAL_MS = 2e3;
|
|
63812
64293
|
var FILE_POLL_MAX_ATTEMPTS = 150;
|
|
@@ -63883,10 +64364,9 @@ var GeminiClient = class {
|
|
|
63883
64364
|
mediaSink;
|
|
63884
64365
|
/**
|
|
63885
64366
|
* This client's session memory — job registry, last-interaction id, written
|
|
63886
|
-
* outputs. One client is one session
|
|
63887
|
-
*
|
|
63888
|
-
*
|
|
63889
|
-
* server is single-user, so its one singleton client is one session.
|
|
64367
|
+
* outputs. One client is one session. Production builds one client per
|
|
64368
|
+
* process and one process is one user, so this is process-wide — see the
|
|
64369
|
+
* invariant (and what a multi-user host would need) in src/session.ts.
|
|
63890
64370
|
*/
|
|
63891
64371
|
session = new SessionState();
|
|
63892
64372
|
/** Per-account character/style library — hosted connector only (see options). */
|
|
@@ -64019,8 +64499,9 @@ var GeminiClient = class {
|
|
|
64019
64499
|
* These two upload calls are raw `fetchImpl` (not `createApiClient`): step 1
|
|
64020
64500
|
* needs the response *header* and step 2 posts a binary body — neither fits
|
|
64021
64501
|
* `fetchJson`. The poll DOES go through the shared client (timeout + retry).
|
|
64022
|
-
*
|
|
64023
|
-
* legitimately take longer than any fixed budget
|
|
64502
|
+
* The deadline scales with the file (see `transferTimeoutMs`): a
|
|
64503
|
+
* multi-hundred-MB video can legitimately take longer than any fixed budget,
|
|
64504
|
+
* but a stalled upload must still fail rather than hang.
|
|
64024
64505
|
*
|
|
64025
64506
|
* Returned files live ~48h (`expirationTime`); per-file cap is 2 GB
|
|
64026
64507
|
* (enforced locally before any bytes are sent).
|
|
@@ -64086,12 +64567,32 @@ var GeminiClient = class {
|
|
|
64086
64567
|
* These two upload calls are raw `fetchImpl` (not `createApiClient`): step 1
|
|
64087
64568
|
* needs the response *header* and step 2 posts a binary body — neither fits
|
|
64088
64569
|
* `fetchJson`. The poll DOES go through the shared client (timeout + retry).
|
|
64089
|
-
*
|
|
64090
|
-
*
|
|
64091
|
-
*
|
|
64570
|
+
* By default the two round trips get a deadline sized to the file (see
|
|
64571
|
+
* `transferTimeoutMs`) — a multi-hundred-MB file can legitimately take longer
|
|
64572
|
+
* than any fixed budget, but a stalled one must fail rather than hang. A
|
|
64573
|
+
* caller that is uploading OPPORTUNISTICALLY passes its own, shorter
|
|
64574
|
+
* `signal` instead (see `uploadBytes`).
|
|
64092
64575
|
*/
|
|
64093
64576
|
async uploadToFilesApi(body, mimeType, displayName, contentLength, signal) {
|
|
64094
64577
|
const key = this.requireKey();
|
|
64578
|
+
const deadline = signal ? void 0 : transferDeadline(transferTimeoutMs(contentLength));
|
|
64579
|
+
let uploaded;
|
|
64580
|
+
try {
|
|
64581
|
+
uploaded = await this.sendToFilesApi(body, mimeType, displayName, contentLength, key, signal ?? deadline.signal);
|
|
64582
|
+
} catch (err) {
|
|
64583
|
+
if (deadline?.signal.aborted) {
|
|
64584
|
+
throw new McpToolError(`${SERVICE} Files API upload of ${displayName} timed out after ${transferTimeoutMs(contentLength)}ms`, {
|
|
64585
|
+
hint: "The upload stalled. Retry, or check the network between this server and generativelanguage.googleapis.com."
|
|
64586
|
+
});
|
|
64587
|
+
}
|
|
64588
|
+
throw err;
|
|
64589
|
+
} finally {
|
|
64590
|
+
deadline?.clear();
|
|
64591
|
+
}
|
|
64592
|
+
return this.awaitFileActive(uploaded.name, uploaded.file, mimeType);
|
|
64593
|
+
}
|
|
64594
|
+
/** {@link uploadToFilesApi}'s steps 1 and 2 (start + upload/finalize), under `signal`. */
|
|
64595
|
+
async sendToFilesApi(body, mimeType, displayName, contentLength, key, signal) {
|
|
64095
64596
|
const doFetch = this.fetchImpl;
|
|
64096
64597
|
const startPath = "/files";
|
|
64097
64598
|
const startRes = await doFetch(`${UPLOAD_BASE_URL}${startPath}`, {
|
|
@@ -64105,7 +64606,7 @@ var GeminiClient = class {
|
|
|
64105
64606
|
"Content-Type": "application/json"
|
|
64106
64607
|
},
|
|
64107
64608
|
body: JSON.stringify({ file: { display_name: displayName } }),
|
|
64108
|
-
|
|
64609
|
+
signal
|
|
64109
64610
|
});
|
|
64110
64611
|
if (!startRes.ok) {
|
|
64111
64612
|
throw new McpToolError(
|
|
@@ -64118,6 +64619,11 @@ var GeminiClient = class {
|
|
|
64118
64619
|
hint: "The Files API resumable-upload contract may have changed \u2014 see docs/GEMINI-API.md."
|
|
64119
64620
|
});
|
|
64120
64621
|
}
|
|
64622
|
+
if (!isGoogleUploadUrl(uploadUrl)) {
|
|
64623
|
+
throw new McpToolError(`${SERVICE} upload start returned a session URL outside googleapis.com; refusing to send the file`, {
|
|
64624
|
+
hint: "The Files API resumable-upload contract may have changed \u2014 see docs/GEMINI-API.md."
|
|
64625
|
+
});
|
|
64626
|
+
}
|
|
64121
64627
|
const upRes = await doFetch(uploadUrl, {
|
|
64122
64628
|
method: "POST",
|
|
64123
64629
|
headers: {
|
|
@@ -64125,7 +64631,7 @@ var GeminiClient = class {
|
|
|
64125
64631
|
"X-Goog-Upload-Command": "upload, finalize"
|
|
64126
64632
|
},
|
|
64127
64633
|
body,
|
|
64128
|
-
|
|
64634
|
+
signal
|
|
64129
64635
|
});
|
|
64130
64636
|
if (!upRes.ok) {
|
|
64131
64637
|
throw new McpToolError(
|
|
@@ -64137,6 +64643,10 @@ var GeminiClient = class {
|
|
|
64137
64643
|
if (!/^files\/[\w-]+$/.test(name)) {
|
|
64138
64644
|
throw new McpToolError(`${SERVICE} upload returned an unexpected file name: ${name || "(none)"}`);
|
|
64139
64645
|
}
|
|
64646
|
+
return { name, file: file2 };
|
|
64647
|
+
}
|
|
64648
|
+
/** {@link uploadToFilesApi}'s step 3: wait out PROCESSING. Outside the upload deadline. */
|
|
64649
|
+
async awaitFileActive(name, file2, mimeType) {
|
|
64140
64650
|
for (let attempt = 0; file2.state === "PROCESSING"; attempt++) {
|
|
64141
64651
|
if (attempt >= FILE_POLL_MAX_ATTEMPTS) {
|
|
64142
64652
|
throw new McpToolError(`${SERVICE} file ${name} is still processing after ${FILE_POLL_INTERVAL_MS * FILE_POLL_MAX_ATTEMPTS / 6e4} minutes`, {
|
|
@@ -64574,32 +65084,46 @@ var GeminiClient = class {
|
|
|
64574
65084
|
let current = requested;
|
|
64575
65085
|
for (let hop = 0; ; hop++) {
|
|
64576
65086
|
const url4 = assertMediaHost(current, requested);
|
|
64577
|
-
const
|
|
64578
|
-
|
|
64579
|
-
|
|
64580
|
-
|
|
64581
|
-
|
|
64582
|
-
|
|
64583
|
-
|
|
64584
|
-
|
|
65087
|
+
const budgetMs = transferTimeoutMs(MEDIA_DOWNLOAD_MAX_BYTES);
|
|
65088
|
+
const deadline = transferDeadline(budgetMs);
|
|
65089
|
+
try {
|
|
65090
|
+
const res = await doFetch(url4.toString(), {
|
|
65091
|
+
redirect: "manual",
|
|
65092
|
+
headers: { "x-goog-api-key": key },
|
|
65093
|
+
signal: deadline.signal
|
|
65094
|
+
});
|
|
65095
|
+
if (res.status >= 300 && res.status < 400) {
|
|
65096
|
+
const location = res.headers?.get("location");
|
|
65097
|
+
if (!location) {
|
|
65098
|
+
throw new McpToolError(`${SERVICE} media download returned HTTP ${res.status} with no Location header for ${current}`);
|
|
65099
|
+
}
|
|
65100
|
+
if (hop >= MAX_REDIRECTS) {
|
|
65101
|
+
throw new McpToolError(`${SERVICE} media download for ${requested} exceeded ${MAX_REDIRECTS} redirects (last hop: ${location}).`);
|
|
65102
|
+
}
|
|
65103
|
+
current = new URL(location, url4).toString();
|
|
65104
|
+
continue;
|
|
65105
|
+
}
|
|
65106
|
+
if (!res.ok) {
|
|
65107
|
+
throw new McpToolError(
|
|
65108
|
+
`${SERVICE} media download failed (HTTP ${res.status}) for ${current}`,
|
|
65109
|
+
{ hint: "Generated files live in the Files API for ~48h and the download requires the api key that created them." }
|
|
65110
|
+
);
|
|
64585
65111
|
}
|
|
64586
|
-
|
|
64587
|
-
|
|
65112
|
+
const bytes = await readCapped(res, MEDIA_DOWNLOAD_MAX_BYTES, current, requested, MEDIA_CAP_SUBJECT);
|
|
65113
|
+
return {
|
|
65114
|
+
base64: bytesToBase64(bytes),
|
|
65115
|
+
mimeType: media.mimeType || res.headers?.get("content-type") || "application/octet-stream"
|
|
65116
|
+
};
|
|
65117
|
+
} catch (err) {
|
|
65118
|
+
if (deadline.signal.aborted && !(err instanceof McpToolError)) {
|
|
65119
|
+
throw new McpToolError(`${SERVICE} media download timed out after ${budgetMs}ms for ${current}`, {
|
|
65120
|
+
hint: "The download stalled. Recover the result with gemini_get_result, or retry."
|
|
65121
|
+
});
|
|
64588
65122
|
}
|
|
64589
|
-
|
|
64590
|
-
|
|
64591
|
-
|
|
64592
|
-
if (!res.ok) {
|
|
64593
|
-
throw new McpToolError(
|
|
64594
|
-
`${SERVICE} media download failed (HTTP ${res.status}) for ${current}`,
|
|
64595
|
-
{ hint: "Generated files live in the Files API for ~48h and the download requires the api key that created them." }
|
|
64596
|
-
);
|
|
65123
|
+
throw err;
|
|
65124
|
+
} finally {
|
|
65125
|
+
deadline.clear();
|
|
64597
65126
|
}
|
|
64598
|
-
const bytes = await readCapped(res, MEDIA_DOWNLOAD_MAX_BYTES, current, requested, MEDIA_CAP_SUBJECT);
|
|
64599
|
-
return {
|
|
64600
|
-
base64: bytesToBase64(bytes),
|
|
64601
|
-
mimeType: media.mimeType || res.headers?.get("content-type") || "application/octet-stream"
|
|
64602
|
-
};
|
|
64603
65127
|
}
|
|
64604
65128
|
}
|
|
64605
65129
|
/** {@link downloadMedia} over a list, preserving order. */
|
|
@@ -64679,7 +65203,10 @@ function isDeliveryUnsupported(err) {
|
|
|
64679
65203
|
}
|
|
64680
65204
|
function hostedStorage(blob = blobStoreFromEnv()) {
|
|
64681
65205
|
if (!blob) return {};
|
|
64682
|
-
const tenant = pinnedTenant(blob.bucket, () =>
|
|
65206
|
+
const tenant = pinnedTenant(blob.bucket, async () => {
|
|
65207
|
+
const key = readEnvVar("GEMINI_API_KEY");
|
|
65208
|
+
return key ? tenantIdFor(key) : { tenant: await tenantIdFor("local"), pin: false };
|
|
65209
|
+
});
|
|
64683
65210
|
return {
|
|
64684
65211
|
mediaSink: createR2Sink(blob.bucket, {
|
|
64685
65212
|
// The SAME `links` object the minter gets below. Media GETs and upload
|
|
@@ -64718,10 +65245,17 @@ var client = new GeminiClient(hostedStorage());
|
|
|
64718
65245
|
init_images();
|
|
64719
65246
|
|
|
64720
65247
|
// src/sidecar.ts
|
|
64721
|
-
import { readdir, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
64722
|
-
import {
|
|
64723
|
-
import { join as join8 } from "node:path";
|
|
65248
|
+
import { readdir, readFile as readFile3, stat as stat2, lstat } from "node:fs/promises";
|
|
65249
|
+
import { join as join9, resolve as resolve5 } from "node:path";
|
|
64724
65250
|
var SIDECAR_SUFFIX2 = ".json";
|
|
65251
|
+
var SIDECAR_NAME = /^.+\.[A-Za-z0-9]+\.json$/;
|
|
65252
|
+
async function isRegularFile(path) {
|
|
65253
|
+
try {
|
|
65254
|
+
return (await lstat(path)).isFile();
|
|
65255
|
+
} catch {
|
|
65256
|
+
return false;
|
|
65257
|
+
}
|
|
65258
|
+
}
|
|
64725
65259
|
async function readSidecars(dir) {
|
|
64726
65260
|
let entries;
|
|
64727
65261
|
try {
|
|
@@ -64729,20 +65263,25 @@ async function readSidecars(dir) {
|
|
|
64729
65263
|
} catch {
|
|
64730
65264
|
return [];
|
|
64731
65265
|
}
|
|
65266
|
+
const sidecarNames = entries.filter((e) => SIDECAR_NAME.test(e));
|
|
65267
|
+
const outputs = new Set(sidecarNames.map((e) => resolve5(dir, e.slice(0, -SIDECAR_SUFFIX2.length))));
|
|
64732
65268
|
const records = [];
|
|
64733
|
-
for (const entry of
|
|
64734
|
-
|
|
64735
|
-
const sidecarPath = join8(dir, entry);
|
|
65269
|
+
for (const entry of sidecarNames) {
|
|
65270
|
+
const sidecarPath = join9(dir, entry);
|
|
64736
65271
|
try {
|
|
64737
65272
|
const parsed = JSON.parse(await readFile3(sidecarPath, "utf8"));
|
|
64738
65273
|
if (typeof parsed !== "object" || parsed === null) continue;
|
|
64739
65274
|
const { interaction_id: interactionId, images } = parsed;
|
|
64740
65275
|
if (typeof interactionId !== "string" || !interactionId) continue;
|
|
64741
65276
|
const paths = Array.isArray(images) ? images.filter((p) => typeof p === "string") : [];
|
|
65277
|
+
const kept = [];
|
|
65278
|
+
for (const p of paths) {
|
|
65279
|
+
if (outputs.has(resolve5(p)) && await isRegularFile(p)) kept.push(p);
|
|
65280
|
+
}
|
|
64742
65281
|
records.push({
|
|
64743
65282
|
sidecarPath,
|
|
64744
65283
|
interactionId,
|
|
64745
|
-
images:
|
|
65284
|
+
images: kept,
|
|
64746
65285
|
mtimeMs: (await stat2(sidecarPath)).mtimeMs
|
|
64747
65286
|
});
|
|
64748
65287
|
} catch {
|
|
@@ -64798,7 +65337,7 @@ function registerInteractTools(server, client2) {
|
|
|
64798
65337
|
"gemini_interact",
|
|
64799
65338
|
{
|
|
64800
65339
|
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"),
|
|
64801
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65340
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
64802
65341
|
inputSchema: external_exports.object({
|
|
64803
65342
|
input: external_exports.string().min(1).describe("Text prompt or editing instruction"),
|
|
64804
65343
|
previous_interaction_id: external_exports.string().optional().describe("ID from a prior gemini_interact call \u2014 continues that multi-turn conversation"),
|
|
@@ -64810,10 +65349,10 @@ function registerInteractTools(server, client2) {
|
|
|
64810
65349
|
model: external_exports.string().optional().describe(`Model id override (default: server default; see gemini_list_models). ${MODEL_CHOICE_GUIDE}`),
|
|
64811
65350
|
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."),
|
|
64812
65351
|
orientation: orientationSchema,
|
|
64813
|
-
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K,
|
|
65352
|
+
image_size: external_exports.enum(IMAGE_SIZES).optional().describe("Output resolution (512 = 0.5K, gemini-3.1-flash-image only)"),
|
|
64814
65353
|
thinking_level: external_exports.enum(["minimal", "high"]).optional().describe("Reasoning depth; higher can help complex/structural edits"),
|
|
64815
65354
|
filename: external_exports.string().optional().describe("Base filename for the output image (extension stripped; default: slugified input)"),
|
|
64816
|
-
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or
|
|
65355
|
+
output_dir: external_exports.string().optional().describe("Directory to write images to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
64817
65356
|
inline: external_exports.boolean().optional().describe("Return base64 images inline instead of writing to disk"),
|
|
64818
65357
|
google_search: external_exports.boolean().optional().describe("Ground the image in live Google Search results (current events, weather, data)"),
|
|
64819
65358
|
search_types: external_exports.array(external_exports.enum(["web_search", "image_search"])).optional().describe(
|
|
@@ -64971,7 +65510,7 @@ function registerVideoTools(server, client2) {
|
|
|
64971
65510
|
"gemini_video_generate",
|
|
64972
65511
|
{
|
|
64973
65512
|
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"),
|
|
64974
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65513
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
64975
65514
|
inputSchema: external_exports.object({
|
|
64976
65515
|
prompt: external_exports.string().min(1).describe("Description of the video to generate (or the edit instruction when task=edit)"),
|
|
64977
65516
|
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."),
|
|
@@ -64984,7 +65523,7 @@ function registerVideoTools(server, client2) {
|
|
|
64984
65523
|
images_base64: imagesBase64Schema(),
|
|
64985
65524
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS clipboard as a reference"),
|
|
64986
65525
|
filename: external_exports.string().optional().describe("Base filename for the output video (extension stripped; default: slugified prompt)"),
|
|
64987
|
-
output_dir: external_exports.string().optional().describe("Directory to write the video to (default: $GEMINI_OUTPUT_DIR or
|
|
65526
|
+
output_dir: external_exports.string().optional().describe("Directory to write the video to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
64988
65527
|
model: external_exports.string().regex(/^[\w.-]+$/, 'must be a bare model id (letters, digits, ".", "_", "-")').optional().describe(`Model id override (default: ${DEFAULT_VIDEO_MODEL})`),
|
|
64989
65528
|
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)'),
|
|
64990
65529
|
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"),
|
|
@@ -65083,7 +65622,7 @@ function registerMusicTools(server, client2) {
|
|
|
65083
65622
|
"gemini_music_generate",
|
|
65084
65623
|
{
|
|
65085
65624
|
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"),
|
|
65086
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65625
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65087
65626
|
inputSchema: external_exports.object({
|
|
65088
65627
|
prompt: external_exports.string().min(1).describe("Description of the music: mood, genre, instruments, tempo, structure, or lyrics"),
|
|
65089
65628
|
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.`),
|
|
@@ -65093,7 +65632,7 @@ function registerMusicTools(server, client2) {
|
|
|
65093
65632
|
images_base64: imagesBase64Schema(),
|
|
65094
65633
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS clipboard as a reference"),
|
|
65095
65634
|
filename: external_exports.string().optional().describe("Base filename for the output audio (extension stripped; default: slugified prompt)"),
|
|
65096
|
-
output_dir: external_exports.string().optional().describe("Directory to write audio to (default: $GEMINI_OUTPUT_DIR or
|
|
65635
|
+
output_dir: external_exports.string().optional().describe("Directory to write audio to (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)"),
|
|
65097
65636
|
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"),
|
|
65098
65637
|
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"),
|
|
65099
65638
|
timeout_ms: timeoutMsSchema,
|
|
@@ -65165,10 +65704,10 @@ function registerJobTools(server, client2) {
|
|
|
65165
65704
|
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.',
|
|
65166
65705
|
// NOT read-only any more: recovering a killed job writes the media it
|
|
65167
65706
|
// pulls back, exactly as the generation tool would have.
|
|
65168
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65707
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65169
65708
|
inputSchema: external_exports.object({
|
|
65170
65709
|
job_id: external_exports.string().min(1).describe("The job_id returned by a generation tool called with async: true"),
|
|
65171
|
-
output_dir: external_exports.string().optional().describe("Where to write media recovered from a killed job (default: $GEMINI_OUTPUT_DIR or
|
|
65710
|
+
output_dir: external_exports.string().optional().describe("Where to write media recovered from a killed job (default: $GEMINI_OUTPUT_DIR or ~/Downloads/gemini-mcp)")
|
|
65172
65711
|
})
|
|
65173
65712
|
},
|
|
65174
65713
|
async (args) => {
|
|
@@ -65243,7 +65782,7 @@ function registerUsageTools(server, client2) {
|
|
|
65243
65782
|
"gemini_token_usage",
|
|
65244
65783
|
{
|
|
65245
65784
|
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.",
|
|
65246
|
-
annotations: toolAnnotations({ readOnly: true }),
|
|
65785
|
+
annotations: toolAnnotations({ readOnly: true, openWorld: false }),
|
|
65247
65786
|
inputSchema: external_exports.object({
|
|
65248
65787
|
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.")
|
|
65249
65788
|
})
|
|
@@ -65304,7 +65843,7 @@ function registerFileTools(server, client2) {
|
|
|
65304
65843
|
"gemini_upload_file",
|
|
65305
65844
|
{
|
|
65306
65845
|
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."),
|
|
65307
|
-
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
65846
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
65308
65847
|
inputSchema: external_exports.object({
|
|
65309
65848
|
url: external_exports.string().url().optional().describe(
|
|
65310
65849
|
`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.")
|
|
@@ -65357,6 +65896,7 @@ function registerFileTools(server, client2) {
|
|
|
65357
65896
|
body: { inputs },
|
|
65358
65897
|
target: resolved,
|
|
65359
65898
|
payload: { inputs, display_name: args.display_name },
|
|
65899
|
+
args,
|
|
65360
65900
|
confirmToken: args.confirmToken
|
|
65361
65901
|
});
|
|
65362
65902
|
if (gate) return gate;
|
|
@@ -65547,6 +66087,7 @@ function registerFileTools(server, client2) {
|
|
|
65547
66087
|
path: `/v1beta/${args.file_uri}`,
|
|
65548
66088
|
target: args.file_uri,
|
|
65549
66089
|
payload: { file_uri: args.file_uri },
|
|
66090
|
+
args,
|
|
65550
66091
|
confirmToken: args.confirmToken
|
|
65551
66092
|
});
|
|
65552
66093
|
if (gate) return gate;
|
|
@@ -65633,7 +66174,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65633
66174
|
"gemini_save_character",
|
|
65634
66175
|
{
|
|
65635
66176
|
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,
|
|
65636
|
-
annotations: { readOnlyHint: false, openWorldHint:
|
|
66177
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65637
66178
|
inputSchema: external_exports.object({
|
|
65638
66179
|
name: NAME_FIELD,
|
|
65639
66180
|
description: external_exports.string().min(1).describe("Short prose description woven into generation prompts (age, look, defining features, species\u2026)"),
|
|
@@ -65698,6 +66239,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65698
66239
|
path: `characters/${args.name}`,
|
|
65699
66240
|
target: args.name,
|
|
65700
66241
|
payload: { name: args.name },
|
|
66242
|
+
args,
|
|
65701
66243
|
confirmToken: args.confirmToken
|
|
65702
66244
|
});
|
|
65703
66245
|
if (gate) return gate;
|
|
@@ -65712,7 +66254,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65712
66254
|
"gemini_save_style",
|
|
65713
66255
|
{
|
|
65714
66256
|
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,
|
|
65715
|
-
annotations: { readOnlyHint: false, openWorldHint:
|
|
66257
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
65716
66258
|
inputSchema: external_exports.object({
|
|
65717
66259
|
name: NAME_FIELD,
|
|
65718
66260
|
prompt_fragment: external_exports.string().min(1).describe('The style text appended to generation prompts, e.g. "bold cartoon style, thick outlines, saturated colors"'),
|
|
@@ -65777,6 +66319,7 @@ function registerLibraryTools(server, client2) {
|
|
|
65777
66319
|
path: `styles/${args.name}`,
|
|
65778
66320
|
target: args.name,
|
|
65779
66321
|
payload: { name: args.name },
|
|
66322
|
+
args,
|
|
65780
66323
|
confirmToken: args.confirmToken
|
|
65781
66324
|
});
|
|
65782
66325
|
if (gate) return gate;
|