@chrischall/gemini-mcp 2.1.4 → 2.3.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 +1 -1
- package/README.md +28 -1
- package/dist/bundle.js +723 -122
- package/dist/client.js +32 -3
- package/dist/images.js +20 -4
- package/dist/tools/_confirm.js +52 -28
- package/dist/tools/files.js +34 -13
- package/dist/tools/generate.js +18 -8
- package/dist/tools/interact.js +11 -5
- package/dist/tools/library.js +29 -9
- package/dist/tools/music.js +9 -4
- package/dist/tools/set.js +10 -5
- package/dist/tools/video.js +9 -4
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/server.json +9 -2
- package/skills/gemini-mcp/SKILL.md +3 -2
package/dist/bundle.js
CHANGED
|
@@ -69,10 +69,37 @@ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
|
69
69
|
function withCallerCapabilities(capabilities, fn) {
|
|
70
70
|
return capabilities ? storage2.run(capabilities, fn) : fn();
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
function currentCallerCapabilities() {
|
|
73
|
+
return storage2.getStore();
|
|
74
|
+
}
|
|
75
|
+
function isRecord(value) {
|
|
76
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
function callerCapabilities(ctx) {
|
|
79
|
+
const envelope = isRecord(ctx) && isRecord(ctx.mcpReq) ? ctx.mcpReq.envelope : void 0;
|
|
80
|
+
if (isRecord(envelope)) {
|
|
81
|
+
const declared = envelope[ENVELOPE_CAPABILITIES_KEY];
|
|
82
|
+
if (isRecord(declared))
|
|
83
|
+
return declared;
|
|
84
|
+
}
|
|
85
|
+
return currentCallerCapabilities();
|
|
86
|
+
}
|
|
87
|
+
function callerAcceptsFormElicitation(ctx) {
|
|
88
|
+
const capabilities = callerCapabilities(ctx);
|
|
89
|
+
if (!capabilities)
|
|
90
|
+
return void 0;
|
|
91
|
+
const elicitation = capabilities.elicitation;
|
|
92
|
+
if (!isRecord(elicitation))
|
|
93
|
+
return false;
|
|
94
|
+
const namedModes = ELICITATION_MODES.filter((mode) => mode in elicitation);
|
|
95
|
+
return namedModes.length === 0 || namedModes.includes("form");
|
|
96
|
+
}
|
|
97
|
+
var storage2, ENVELOPE_CAPABILITIES_KEY, ELICITATION_MODES;
|
|
73
98
|
var init_caller = __esm({
|
|
74
99
|
"node_modules/@chrischall/mcp-utils/dist/caller/index.js"() {
|
|
75
100
|
storage2 = new AsyncLocalStorage2();
|
|
101
|
+
ENVELOPE_CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
102
|
+
ELICITATION_MODES = ["form", "url"];
|
|
76
103
|
}
|
|
77
104
|
});
|
|
78
105
|
|
|
@@ -1695,8 +1722,8 @@ var require_regexes = __commonJS({
|
|
|
1695
1722
|
}
|
|
1696
1723
|
exports.ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
1697
1724
|
exports.ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
1698
|
-
var mac3 = (
|
|
1699
|
-
const escapedDelim = util.escapeRegex(
|
|
1725
|
+
var mac3 = (delimiter2) => {
|
|
1726
|
+
const escapedDelim = util.escapeRegex(delimiter2 ?? ":");
|
|
1700
1727
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
1701
1728
|
};
|
|
1702
1729
|
exports.mac = mac3;
|
|
@@ -26876,6 +26903,37 @@ function buildInputRequired(spec) {
|
|
|
26876
26903
|
...spec.requestState !== void 0 && { requestState: spec.requestState }
|
|
26877
26904
|
};
|
|
26878
26905
|
}
|
|
26906
|
+
function acceptedContent(responses, key, schema) {
|
|
26907
|
+
const view = inputResponse(responses, key);
|
|
26908
|
+
if (view.kind !== "elicit" || view.action !== "accept" || view.content === void 0) return void 0;
|
|
26909
|
+
if (schema === void 0) return view.content;
|
|
26910
|
+
const outcome = schema["~standard"].validate(view.content);
|
|
26911
|
+
if (outcome instanceof Promise) throw new TypeError("acceptedContent(responses, key, schema) requires a synchronously-validating schema");
|
|
26912
|
+
return outcome.issues === void 0 ? outcome.value : void 0;
|
|
26913
|
+
}
|
|
26914
|
+
function inputResponse(responses, key) {
|
|
26915
|
+
if (responses === void 0 || typeof responses !== "object" || responses === null) return { kind: "missing" };
|
|
26916
|
+
const entry = responses[key];
|
|
26917
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return { kind: "missing" };
|
|
26918
|
+
const candidate = entry;
|
|
26919
|
+
if (candidate["action"] === "accept" || candidate["action"] === "decline" || candidate["action"] === "cancel") {
|
|
26920
|
+
const content = candidate["content"];
|
|
26921
|
+
return {
|
|
26922
|
+
kind: "elicit",
|
|
26923
|
+
action: candidate["action"],
|
|
26924
|
+
...content !== null && typeof content === "object" && !Array.isArray(content) && { content }
|
|
26925
|
+
};
|
|
26926
|
+
}
|
|
26927
|
+
if (Array.isArray(candidate["roots"])) return {
|
|
26928
|
+
kind: "roots",
|
|
26929
|
+
roots: candidate["roots"]
|
|
26930
|
+
};
|
|
26931
|
+
if (typeof candidate["role"] === "string" && candidate["content"] !== void 0) return {
|
|
26932
|
+
kind: "sampling",
|
|
26933
|
+
result: candidate
|
|
26934
|
+
};
|
|
26935
|
+
return { kind: "missing" };
|
|
26936
|
+
}
|
|
26879
26937
|
function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
26880
26938
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
26881
26939
|
}
|
|
@@ -37659,14 +37717,14 @@ function serveStdio(factory, options = {}) {
|
|
|
37659
37717
|
return false;
|
|
37660
37718
|
};
|
|
37661
37719
|
const answerLegacyRejection = (request, reason, requestedVersion) => {
|
|
37662
|
-
const
|
|
37720
|
+
const rejection3 = modernOnlyStrictRejection({
|
|
37663
37721
|
kind: "legacy",
|
|
37664
37722
|
reason,
|
|
37665
37723
|
...requestedVersion !== void 0 && { requestedVersion }
|
|
37666
37724
|
}, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
37667
|
-
if (
|
|
37668
|
-
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${
|
|
37669
|
-
return writeErrorResponse(request.id,
|
|
37725
|
+
if (rejection3 === void 0) return Promise.resolve();
|
|
37726
|
+
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection3.cell}): ${rejection3.message}`));
|
|
37727
|
+
return writeErrorResponse(request.id, rejection3.code, rejection3.message, rejection3.data);
|
|
37670
37728
|
};
|
|
37671
37729
|
const onInstanceClosed = (channel) => {
|
|
37672
37730
|
if (closing || channel === discarding) return;
|
|
@@ -39590,8 +39648,8 @@ var init_regexes = __esm({
|
|
|
39590
39648
|
_emoji = `^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
|
|
39591
39649
|
ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
39592
39650
|
ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
39593
|
-
mac = (
|
|
39594
|
-
const escapedDelim = escapeRegex(
|
|
39651
|
+
mac = (delimiter2) => {
|
|
39652
|
+
const escapedDelim = escapeRegex(delimiter2 ?? ":");
|
|
39595
39653
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
39596
39654
|
};
|
|
39597
39655
|
cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -58498,6 +58556,11 @@ var init_media = __esm({
|
|
|
58498
58556
|
});
|
|
58499
58557
|
|
|
58500
58558
|
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
58559
|
+
function textResult(data) {
|
|
58560
|
+
return {
|
|
58561
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
58562
|
+
};
|
|
58563
|
+
}
|
|
58501
58564
|
function errorResult(message) {
|
|
58502
58565
|
return {
|
|
58503
58566
|
content: [{ type: "text", text: redactSecrets(message) }],
|
|
@@ -58512,12 +58575,446 @@ var init_response = __esm({
|
|
|
58512
58575
|
}
|
|
58513
58576
|
});
|
|
58514
58577
|
|
|
58578
|
+
// node_modules/@chrischall/mcp-utils/dist/server/canonical.js
|
|
58579
|
+
function canonicalJson(value) {
|
|
58580
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
58581
|
+
return JSON.stringify(value);
|
|
58582
|
+
if (value === void 0)
|
|
58583
|
+
return "null";
|
|
58584
|
+
if (typeof value === "number")
|
|
58585
|
+
return Number.isFinite(value) ? JSON.stringify(value) : `{"$num":"${String(value)}"}`;
|
|
58586
|
+
if (typeof value === "bigint")
|
|
58587
|
+
return `{"$bigint":"${value.toString()}"}`;
|
|
58588
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
58589
|
+
throw new TypeError(`confirmation: cannot bind a ${typeof value} argument value.`);
|
|
58590
|
+
}
|
|
58591
|
+
if (Array.isArray(value))
|
|
58592
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
58593
|
+
if (value instanceof Date) {
|
|
58594
|
+
return `{"$date":${JSON.stringify(Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString())}}`;
|
|
58595
|
+
}
|
|
58596
|
+
if (ArrayBuffer.isView(value)) {
|
|
58597
|
+
const bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
58598
|
+
return `{"$bytes":${JSON.stringify(bytes.toString("base64"))}}`;
|
|
58599
|
+
}
|
|
58600
|
+
if (value instanceof ArrayBuffer)
|
|
58601
|
+
return `{"$bytes":${JSON.stringify(Buffer.from(value).toString("base64"))}}`;
|
|
58602
|
+
if (value instanceof Map) {
|
|
58603
|
+
const entries2 = [...value.entries()].map(([k, v]) => `[${canonicalJson(k)},${canonicalJson(v)}]`).sort();
|
|
58604
|
+
return `{"$map":[${entries2.join(",")}]}`;
|
|
58605
|
+
}
|
|
58606
|
+
if (value instanceof Set) {
|
|
58607
|
+
return `{"$set":[${[...value].map(canonicalJson).sort().join(",")}]}`;
|
|
58608
|
+
}
|
|
58609
|
+
const proto = Object.getPrototypeOf(value);
|
|
58610
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
58611
|
+
throw new TypeError("confirmation: cannot bind a non-plain object argument value (class instance).");
|
|
58612
|
+
}
|
|
58613
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
58614
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(escapeKey(k))}:${canonicalJson(v)}`).join(",")}}`;
|
|
58615
|
+
}
|
|
58616
|
+
function escapeKey(key) {
|
|
58617
|
+
return key.startsWith("$") ? `$${key}` : key;
|
|
58618
|
+
}
|
|
58619
|
+
var init_canonical = __esm({
|
|
58620
|
+
"node_modules/@chrischall/mcp-utils/dist/server/canonical.js"() {
|
|
58621
|
+
}
|
|
58622
|
+
});
|
|
58623
|
+
|
|
58515
58624
|
// node_modules/@chrischall/mcp-utils/dist/server/confirmation.js
|
|
58625
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
58626
|
+
function bindingKey(binding) {
|
|
58627
|
+
if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
|
|
58628
|
+
throw new RangeError("requireConfirmation: binding.ttlSeconds must be a finite number greater than 0.");
|
|
58629
|
+
}
|
|
58630
|
+
const key = typeof binding.key === "string" ? Buffer.from(binding.key, "utf8") : Buffer.from(binding.key);
|
|
58631
|
+
if (key.length < 32) {
|
|
58632
|
+
throw new RangeError("requireConfirmation: binding.key must be at least 32 bytes.");
|
|
58633
|
+
}
|
|
58634
|
+
return key;
|
|
58635
|
+
}
|
|
58636
|
+
function commitment(action, args) {
|
|
58637
|
+
return createHash("sha256").update(`${action}\0${canonicalJson(args)}`).digest("base64url");
|
|
58638
|
+
}
|
|
58639
|
+
function mintState(key, action, binding) {
|
|
58640
|
+
const exp = Math.floor(Date.now() / 1e3) + (binding.ttlSeconds ?? 600);
|
|
58641
|
+
const body = Buffer.from(JSON.stringify({ c: commitment(action, binding.args), exp })).toString("base64url");
|
|
58642
|
+
const mac3 = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest("base64url");
|
|
58643
|
+
return `${STATE_PREFIX}${body}.${mac3}`;
|
|
58644
|
+
}
|
|
58645
|
+
function verifyState(key, action, binding, state) {
|
|
58646
|
+
if (typeof state !== "string" || !state.startsWith(STATE_PREFIX))
|
|
58647
|
+
return false;
|
|
58648
|
+
const rest = state.slice(STATE_PREFIX.length);
|
|
58649
|
+
const dot = rest.indexOf(".");
|
|
58650
|
+
if (dot < 0)
|
|
58651
|
+
return false;
|
|
58652
|
+
const body = rest.slice(0, dot);
|
|
58653
|
+
const mac3 = Buffer.from(rest.slice(dot + 1), "base64url");
|
|
58654
|
+
const expected = createHmac("sha256", key).update(`${STATE_PREFIX}${body}`).digest();
|
|
58655
|
+
if (mac3.length !== expected.length || !timingSafeEqual(mac3, expected))
|
|
58656
|
+
return false;
|
|
58657
|
+
let payload;
|
|
58658
|
+
try {
|
|
58659
|
+
payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
58660
|
+
} catch {
|
|
58661
|
+
return false;
|
|
58662
|
+
}
|
|
58663
|
+
if (typeof payload.exp !== "number" || payload.exp * 1e3 <= Date.now())
|
|
58664
|
+
return false;
|
|
58665
|
+
const want = Buffer.from(commitment(action, binding.args));
|
|
58666
|
+
const got = Buffer.from(typeof payload.c === "string" ? payload.c : "");
|
|
58667
|
+
return got.length === want.length && timingSafeEqual(got, want);
|
|
58668
|
+
}
|
|
58669
|
+
function echoedState(ctx) {
|
|
58670
|
+
const accessor = ctx.mcpReq.requestState;
|
|
58671
|
+
return typeof accessor === "function" ? accessor() : void 0;
|
|
58672
|
+
}
|
|
58673
|
+
function requireConfirmation(ctx, options) {
|
|
58674
|
+
const requestKey = options.requestKey ?? DEFAULT_REQUEST_KEY;
|
|
58675
|
+
const key = options.binding ? bindingKey(options.binding) : void 0;
|
|
58676
|
+
const confirmationSchema = external_exports.object({
|
|
58677
|
+
confirmed: external_exports.boolean().describe(options.confirmationLabel ?? DEFAULT_CONFIRMATION_LABEL)
|
|
58678
|
+
});
|
|
58679
|
+
const ask = () => {
|
|
58680
|
+
const preview = {
|
|
58681
|
+
action: options.action,
|
|
58682
|
+
...options.details === void 0 ? {} : { details: options.details }
|
|
58683
|
+
};
|
|
58684
|
+
return inputRequired({
|
|
58685
|
+
inputRequests: {
|
|
58686
|
+
[requestKey]: inputRequired.elicit({
|
|
58687
|
+
message: `${options.message}
|
|
58688
|
+
${JSON.stringify(preview, null, 2)}`,
|
|
58689
|
+
requestedSchema: confirmationSchema
|
|
58690
|
+
})
|
|
58691
|
+
},
|
|
58692
|
+
...key && options.binding ? { requestState: mintState(key, options.action, options.binding) } : {}
|
|
58693
|
+
});
|
|
58694
|
+
};
|
|
58695
|
+
const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
|
|
58696
|
+
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
58697
|
+
if (response.kind === "missing") {
|
|
58698
|
+
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
58699
|
+
return textResult({
|
|
58700
|
+
confirmed: false,
|
|
58701
|
+
dispatched: false,
|
|
58702
|
+
action: options.action,
|
|
58703
|
+
reason: "confirmation-unsupported",
|
|
58704
|
+
note: options.unsupportedNote ? `${UNSUPPORTED_NOTE}. ${options.unsupportedNote}` : `${UNSUPPORTED_NOTE}.`
|
|
58705
|
+
});
|
|
58706
|
+
}
|
|
58707
|
+
return ask();
|
|
58708
|
+
}
|
|
58709
|
+
if (response.kind === "elicit" && response.action === "accept" && accepted?.confirmed === true) {
|
|
58710
|
+
if (key && options.binding) {
|
|
58711
|
+
const state = echoedState(ctx);
|
|
58712
|
+
if (state === void 0 || state === null) {
|
|
58713
|
+
return errorResult(`Confirmation for ${options.action} was accepted, but the retry carried no requestState, so it cannot be checked against the prompt that was shown. Nothing was done. The likely cause is that the MCP client or host does not round-trip requestState (it must echo the requestState from the input_required result back on the retry); asking again would loop.`);
|
|
58714
|
+
}
|
|
58715
|
+
if (!verifyState(key, options.action, options.binding, state))
|
|
58716
|
+
return ask();
|
|
58717
|
+
}
|
|
58718
|
+
return void 0;
|
|
58719
|
+
}
|
|
58720
|
+
return textResult({
|
|
58721
|
+
confirmed: false,
|
|
58722
|
+
cancelled: true,
|
|
58723
|
+
action: options.action,
|
|
58724
|
+
note: "Nothing was changed because the confirmation was declined, cancelled, or left unchecked."
|
|
58725
|
+
});
|
|
58726
|
+
}
|
|
58727
|
+
var DEFAULT_REQUEST_KEY, DEFAULT_CONFIRMATION_LABEL, UNSUPPORTED_NOTE, STATE_PREFIX;
|
|
58516
58728
|
var init_confirmation = __esm({
|
|
58517
58729
|
"node_modules/@chrischall/mcp-utils/dist/server/confirmation.js"() {
|
|
58518
58730
|
init_dist();
|
|
58731
|
+
init_zod();
|
|
58732
|
+
init_caller();
|
|
58733
|
+
init_canonical();
|
|
58734
|
+
init_response();
|
|
58735
|
+
DEFAULT_REQUEST_KEY = "confirmation";
|
|
58736
|
+
DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
58737
|
+
UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
|
|
58738
|
+
STATE_PREFIX = "mcpu.confirm.v1.";
|
|
58739
|
+
}
|
|
58740
|
+
});
|
|
58741
|
+
|
|
58742
|
+
// node_modules/@chrischall/mcp-utils/dist/server/confirm-token.js
|
|
58743
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
58744
|
+
function createSpentTokenStore() {
|
|
58745
|
+
const spent = /* @__PURE__ */ new Map();
|
|
58746
|
+
return {
|
|
58747
|
+
has: (nonce) => spent.has(nonce),
|
|
58748
|
+
add: (nonce, exp) => {
|
|
58749
|
+
spent.set(nonce, exp);
|
|
58750
|
+
},
|
|
58751
|
+
prune: (now) => {
|
|
58752
|
+
for (const [nonce, exp] of spent)
|
|
58753
|
+
if (exp < now)
|
|
58754
|
+
spent.delete(nonce);
|
|
58755
|
+
},
|
|
58756
|
+
clear: () => spent.clear(),
|
|
58757
|
+
get size() {
|
|
58758
|
+
return spent.size;
|
|
58759
|
+
}
|
|
58760
|
+
};
|
|
58761
|
+
}
|
|
58762
|
+
function tokenKey(key) {
|
|
58763
|
+
const bytes = typeof key === "string" ? Buffer.from(key, "utf8") : Buffer.from(key);
|
|
58764
|
+
if (bytes.length < 32)
|
|
58765
|
+
throw new RangeError("confirm token: key must be at least 32 bytes.");
|
|
58766
|
+
return bytes;
|
|
58767
|
+
}
|
|
58768
|
+
function ttlOf(ttlSeconds) {
|
|
58769
|
+
if (ttlSeconds === void 0)
|
|
58770
|
+
return DEFAULT_TTL_SECONDS;
|
|
58771
|
+
if (!(Number.isFinite(ttlSeconds) && ttlSeconds > 0)) {
|
|
58772
|
+
throw new RangeError("confirm token: ttlSeconds must be a finite number greater than 0.");
|
|
58773
|
+
}
|
|
58774
|
+
return ttlSeconds;
|
|
58775
|
+
}
|
|
58776
|
+
function sign(key, body) {
|
|
58777
|
+
return createHmac2("sha256", key).update(`${PREFIX}${body}`).digest();
|
|
58778
|
+
}
|
|
58779
|
+
function hashConfirmPayload(payload) {
|
|
58780
|
+
return createHash2("sha256").update(canonicalJson(payload)).digest("base64url");
|
|
58781
|
+
}
|
|
58782
|
+
function issueConfirmToken(key, binding, options = {}) {
|
|
58783
|
+
const k = tokenKey(key);
|
|
58784
|
+
const now = options.now ?? Date.now();
|
|
58785
|
+
const exp = now + ttlOf(options.ttlSeconds) * 1e3;
|
|
58786
|
+
const claims = {
|
|
58787
|
+
t: binding.tool,
|
|
58788
|
+
...binding.account === void 0 ? {} : { a: binding.account },
|
|
58789
|
+
g: binding.target,
|
|
58790
|
+
...binding.revision === void 0 ? {} : { r: binding.revision },
|
|
58791
|
+
h: binding.payloadHash,
|
|
58792
|
+
exp,
|
|
58793
|
+
n: randomBytes(16).toString("base64url")
|
|
58794
|
+
};
|
|
58795
|
+
const body = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
|
|
58796
|
+
return { token: `${PREFIX}${body}.${sign(k, body).toString("base64url")}`, expiresAt: new Date(exp).toISOString() };
|
|
58797
|
+
}
|
|
58798
|
+
function parseClaims(body) {
|
|
58799
|
+
try {
|
|
58800
|
+
const claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
58801
|
+
return claims && typeof claims === "object" ? claims : void 0;
|
|
58802
|
+
} catch {
|
|
58803
|
+
return void 0;
|
|
58804
|
+
}
|
|
58805
|
+
}
|
|
58806
|
+
function verifyConfirmToken(key, token, binding, options = {}) {
|
|
58807
|
+
const k = tokenKey(key);
|
|
58808
|
+
const now = options.now ?? Date.now();
|
|
58809
|
+
const spent = options.spent ?? PROCESS_SPENT;
|
|
58810
|
+
spent.prune(now);
|
|
58811
|
+
if (!token.startsWith(PREFIX))
|
|
58812
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
58813
|
+
const rest = token.slice(PREFIX.length);
|
|
58814
|
+
const dot = rest.indexOf(".");
|
|
58815
|
+
const body = dot < 0 ? "" : rest.slice(0, dot);
|
|
58816
|
+
const mac3 = Buffer.from(dot < 0 ? "" : rest.slice(dot + 1), "base64url");
|
|
58817
|
+
const expected = sign(k, body);
|
|
58818
|
+
if (!body || mac3.length !== expected.length || !timingSafeEqual2(mac3, expected))
|
|
58819
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
58820
|
+
const claims = parseClaims(body);
|
|
58821
|
+
if (!claims)
|
|
58822
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
58823
|
+
if (claims.t !== binding.tool || claims.a !== binding.account || claims.g !== binding.target) {
|
|
58824
|
+
return { ok: false, error: "TOKEN_INVALID" };
|
|
58825
|
+
}
|
|
58826
|
+
if (spent.has(claims.n))
|
|
58827
|
+
return { ok: false, error: "TOKEN_REUSED" };
|
|
58828
|
+
if (now > claims.exp)
|
|
58829
|
+
return { ok: false, error: "TOKEN_EXPIRED" };
|
|
58830
|
+
if (claims.r !== binding.revision)
|
|
58831
|
+
return { ok: false, error: "DRAFT_CHANGED", reason: "revision-changed" };
|
|
58832
|
+
if (claims.h !== binding.payloadHash)
|
|
58833
|
+
return { ok: false, error: "DRAFT_CHANGED", reason: "payload-changed" };
|
|
58834
|
+
spent.add(claims.n, claims.exp);
|
|
58835
|
+
return { ok: true };
|
|
58836
|
+
}
|
|
58837
|
+
function isToolResult(value) {
|
|
58838
|
+
return Array.isArray(value.content);
|
|
58839
|
+
}
|
|
58840
|
+
function rejection2(data) {
|
|
58841
|
+
return { ...textResult({ status: "confirmation-rejected", confirmed: false, dispatched: false, ...data }), isError: true };
|
|
58842
|
+
}
|
|
58843
|
+
async function tokenConfirmation(action, fb) {
|
|
58844
|
+
const subject = await fb.subject();
|
|
58845
|
+
if (isToolResult(subject))
|
|
58846
|
+
return subject;
|
|
58847
|
+
const binding = {
|
|
58848
|
+
tool: fb.tool,
|
|
58849
|
+
...fb.account === void 0 ? {} : { account: fb.account },
|
|
58850
|
+
target: subject.target,
|
|
58851
|
+
...subject.revision === void 0 ? {} : { revision: subject.revision },
|
|
58852
|
+
payloadHash: hashConfirmPayload(subject.payload)
|
|
58853
|
+
};
|
|
58854
|
+
const phaseOne = () => {
|
|
58855
|
+
const { token, expiresAt } = issueConfirmToken(fb.key, binding, { ttlSeconds: fb.ttlSeconds });
|
|
58856
|
+
return {
|
|
58857
|
+
action,
|
|
58858
|
+
preview: subject.preview,
|
|
58859
|
+
confirmToken: token,
|
|
58860
|
+
expiresAt,
|
|
58861
|
+
ttlSeconds: ttlOf(fb.ttlSeconds),
|
|
58862
|
+
instruction: fb.instruction ?? CONFIRM_TOKEN_INSTRUCTION
|
|
58863
|
+
};
|
|
58864
|
+
};
|
|
58865
|
+
if (!fb.confirmToken) {
|
|
58866
|
+
return textResult({ status: "confirmation-required", confirmed: false, dispatched: false, ...phaseOne() });
|
|
58867
|
+
}
|
|
58868
|
+
const verdict = verifyConfirmToken(fb.key, fb.confirmToken, binding, { spent: fb.spent });
|
|
58869
|
+
if (verdict.ok)
|
|
58870
|
+
return void 0;
|
|
58871
|
+
if (verdict.error === "DRAFT_CHANGED") {
|
|
58872
|
+
return rejection2({
|
|
58873
|
+
error: "DRAFT_CHANGED",
|
|
58874
|
+
reason: verdict.reason,
|
|
58875
|
+
note: `${CHANGED_NOTE[verdict.reason]} The current preview and a fresh confirmToken are below.`,
|
|
58876
|
+
...phaseOne()
|
|
58877
|
+
});
|
|
58878
|
+
}
|
|
58879
|
+
return rejection2({ error: verdict.error, action, note: TOKEN_ERROR_NOTE[verdict.error] });
|
|
58880
|
+
}
|
|
58881
|
+
async function requireConfirmationWithFallback(ctx, options) {
|
|
58882
|
+
const { tokenFallback, ...confirmation } = options;
|
|
58883
|
+
if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
|
|
58884
|
+
return tokenConfirmation(options.action, tokenFallback);
|
|
58885
|
+
}
|
|
58886
|
+
return requireConfirmation(ctx, confirmation);
|
|
58887
|
+
}
|
|
58888
|
+
var PROCESS_SPENT, PREFIX, DEFAULT_TTL_SECONDS, CONFIRM_TOKEN_INSTRUCTION, confirmTokenParam, TOKEN_ERROR_NOTE, CHANGED_NOTE;
|
|
58889
|
+
var init_confirm_token = __esm({
|
|
58890
|
+
"node_modules/@chrischall/mcp-utils/dist/server/confirm-token.js"() {
|
|
58891
|
+
init_zod();
|
|
58519
58892
|
init_caller();
|
|
58520
58893
|
init_response();
|
|
58894
|
+
init_canonical();
|
|
58895
|
+
init_confirmation();
|
|
58896
|
+
PROCESS_SPENT = createSpentTokenStore();
|
|
58897
|
+
PREFIX = "mcpu.token.v1.";
|
|
58898
|
+
DEFAULT_TTL_SECONDS = 600;
|
|
58899
|
+
CONFIRM_TOKEN_INSTRUCTION = "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken.";
|
|
58900
|
+
confirmTokenParam = external_exports.string().optional().describe(`ONLY for the two-step confirmation fallback (a client without MCP elicitation). The confirmToken from this same tool's phase-1 "confirmation-required" response, passed back ONLY after the user has seen that preview and explicitly approved it in chat \u2014 never on the first call, never invented, never reused. Call again with the same arguments. Ignored when the client supports elicitation.`);
|
|
58901
|
+
TOKEN_ERROR_NOTE = {
|
|
58902
|
+
TOKEN_EXPIRED: "Nothing was sent or changed: the confirmToken expired. Call again WITHOUT confirmToken for a fresh preview, and ask the user to approve it again.",
|
|
58903
|
+
TOKEN_REUSED: "Nothing was sent or changed by this call: this confirmToken was already used, and one approval acts once. If doing it again is really intended, call again WITHOUT confirmToken and get a new approval.",
|
|
58904
|
+
TOKEN_INVALID: "Nothing was sent or changed: this confirmToken was not issued by this server for this tool, account and target (or the server has restarted since). Call again WITHOUT confirmToken for a fresh preview and approval."
|
|
58905
|
+
};
|
|
58906
|
+
CHANGED_NOTE = {
|
|
58907
|
+
"revision-changed": "Nothing was sent or changed: the target was edited since the user approved it (its version rotated), so what would happen is not what they saw.",
|
|
58908
|
+
"payload-changed": "Nothing was sent or changed: what would happen no longer matches what the user approved."
|
|
58909
|
+
};
|
|
58910
|
+
}
|
|
58911
|
+
});
|
|
58912
|
+
|
|
58913
|
+
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
58914
|
+
import { homedir } from "node:os";
|
|
58915
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
58916
|
+
function readEnvVar(key, opts = {}) {
|
|
58917
|
+
const env = opts.env ?? process.env;
|
|
58918
|
+
const raw = env[key];
|
|
58919
|
+
if (typeof raw === "string") {
|
|
58920
|
+
const trimmed = raw.trim();
|
|
58921
|
+
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
58922
|
+
return trimmed;
|
|
58923
|
+
}
|
|
58924
|
+
}
|
|
58925
|
+
return opts.default;
|
|
58926
|
+
}
|
|
58927
|
+
function expandPath(p) {
|
|
58928
|
+
let expanded = p;
|
|
58929
|
+
if (p === "~") {
|
|
58930
|
+
expanded = homedir();
|
|
58931
|
+
} else if (p.startsWith("~/")) {
|
|
58932
|
+
expanded = join(homedir(), p.slice(2));
|
|
58933
|
+
}
|
|
58934
|
+
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
58935
|
+
}
|
|
58936
|
+
async function loadDotenvSafely(opts = {}) {
|
|
58937
|
+
try {
|
|
58938
|
+
const mod = await import(
|
|
58939
|
+
/* @vite-ignore */
|
|
58940
|
+
"dotenv"
|
|
58941
|
+
);
|
|
58942
|
+
const result = mod.config({
|
|
58943
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
58944
|
+
override: opts.override ?? false,
|
|
58945
|
+
quiet: true
|
|
58946
|
+
});
|
|
58947
|
+
return result.error === void 0;
|
|
58948
|
+
} catch {
|
|
58949
|
+
return false;
|
|
58950
|
+
}
|
|
58951
|
+
}
|
|
58952
|
+
var PLACEHOLDER_RE;
|
|
58953
|
+
var init_config = __esm({
|
|
58954
|
+
"node_modules/@chrischall/mcp-utils/dist/config/index.js"() {
|
|
58955
|
+
PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
58956
|
+
}
|
|
58957
|
+
});
|
|
58958
|
+
|
|
58959
|
+
// node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js
|
|
58960
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
|
|
58961
|
+
function readConfirmMode(env = process.env) {
|
|
58962
|
+
const raw = readEnvVar("MCP_CONFIRM_MODE", { env })?.trim().toLowerCase();
|
|
58963
|
+
if (!raw)
|
|
58964
|
+
return "ask-user";
|
|
58965
|
+
if (MODES.has(raw))
|
|
58966
|
+
return raw;
|
|
58967
|
+
if (!warned.has(raw)) {
|
|
58968
|
+
warned.add(raw);
|
|
58969
|
+
process.stderr.write(`MCP_CONFIRM_MODE="${raw}" is not one of ask-user, auto, refuse; treating it as refuse.
|
|
58970
|
+
`);
|
|
58971
|
+
}
|
|
58972
|
+
return "refuse";
|
|
58973
|
+
}
|
|
58974
|
+
function confirmTtlFromEnv(env = process.env) {
|
|
58975
|
+
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
58976
|
+
return raw && /^[1-9]\d*$/.test(raw) ? Number(raw) : DEFAULT_TTL_SECONDS2;
|
|
58977
|
+
}
|
|
58978
|
+
function confirmKeyFromEnv(env = process.env) {
|
|
58979
|
+
const secret = readEnvVar("MCP_CONFIRM_SECRET", { env });
|
|
58980
|
+
if (secret)
|
|
58981
|
+
return createHash3("sha256").update(secret, "utf8").digest();
|
|
58982
|
+
processKey ??= randomBytes2(32);
|
|
58983
|
+
return processKey;
|
|
58984
|
+
}
|
|
58985
|
+
function confirmationFromEnv(options) {
|
|
58986
|
+
const { tool, account, confirmToken, subject, instruction, spent, env = process.env, ...confirmation } = options;
|
|
58987
|
+
const mode = readConfirmMode(env);
|
|
58988
|
+
if (mode === "refuse") {
|
|
58989
|
+
return {
|
|
58990
|
+
...confirmation,
|
|
58991
|
+
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${REFUSE_HINT}` : REFUSE_HINT
|
|
58992
|
+
};
|
|
58993
|
+
}
|
|
58994
|
+
return {
|
|
58995
|
+
...confirmation,
|
|
58996
|
+
tokenFallback: {
|
|
58997
|
+
key: confirmKeyFromEnv(env),
|
|
58998
|
+
tool,
|
|
58999
|
+
...account === void 0 ? {} : { account },
|
|
59000
|
+
...confirmToken === void 0 ? {} : { confirmToken },
|
|
59001
|
+
subject,
|
|
59002
|
+
ttlSeconds: confirmTtlFromEnv(env),
|
|
59003
|
+
instruction: mode === "auto" ? CONFIRM_TOKEN_AUTO_INSTRUCTION : instruction ?? CONFIRM_TOKEN_INSTRUCTION,
|
|
59004
|
+
...spent === void 0 ? {} : { spent }
|
|
59005
|
+
}
|
|
59006
|
+
};
|
|
59007
|
+
}
|
|
59008
|
+
var MODES, DEFAULT_TTL_SECONDS2, warned, processKey, CONFIRM_TOKEN_AUTO_INSTRUCTION, REFUSE_HINT;
|
|
59009
|
+
var init_confirm_env = __esm({
|
|
59010
|
+
"node_modules/@chrischall/mcp-utils/dist/server/confirm-env.js"() {
|
|
59011
|
+
init_config();
|
|
59012
|
+
init_confirm_token();
|
|
59013
|
+
MODES = /* @__PURE__ */ new Set(["ask-user", "auto", "refuse"]);
|
|
59014
|
+
DEFAULT_TTL_SECONDS2 = 600;
|
|
59015
|
+
warned = /* @__PURE__ */ new Set();
|
|
59016
|
+
CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
59017
|
+
REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
58521
59018
|
}
|
|
58522
59019
|
});
|
|
58523
59020
|
|
|
@@ -58697,6 +59194,8 @@ var init_server = __esm({
|
|
|
58697
59194
|
init_errors();
|
|
58698
59195
|
init_response();
|
|
58699
59196
|
init_confirmation();
|
|
59197
|
+
init_confirm_token();
|
|
59198
|
+
init_confirm_env();
|
|
58700
59199
|
init_cancel();
|
|
58701
59200
|
init_caller();
|
|
58702
59201
|
SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
@@ -58708,52 +59207,6 @@ var init_server = __esm({
|
|
|
58708
59207
|
}
|
|
58709
59208
|
});
|
|
58710
59209
|
|
|
58711
|
-
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
58712
|
-
import { homedir } from "node:os";
|
|
58713
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
58714
|
-
function readEnvVar(key, opts = {}) {
|
|
58715
|
-
const env = opts.env ?? process.env;
|
|
58716
|
-
const raw = env[key];
|
|
58717
|
-
if (typeof raw === "string") {
|
|
58718
|
-
const trimmed = raw.trim();
|
|
58719
|
-
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
58720
|
-
return trimmed;
|
|
58721
|
-
}
|
|
58722
|
-
}
|
|
58723
|
-
return opts.default;
|
|
58724
|
-
}
|
|
58725
|
-
function expandPath(p) {
|
|
58726
|
-
let expanded = p;
|
|
58727
|
-
if (p === "~") {
|
|
58728
|
-
expanded = homedir();
|
|
58729
|
-
} else if (p.startsWith("~/")) {
|
|
58730
|
-
expanded = join(homedir(), p.slice(2));
|
|
58731
|
-
}
|
|
58732
|
-
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
58733
|
-
}
|
|
58734
|
-
async function loadDotenvSafely(opts = {}) {
|
|
58735
|
-
try {
|
|
58736
|
-
const mod = await import(
|
|
58737
|
-
/* @vite-ignore */
|
|
58738
|
-
"dotenv"
|
|
58739
|
-
);
|
|
58740
|
-
const result = mod.config({
|
|
58741
|
-
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
58742
|
-
override: opts.override ?? false,
|
|
58743
|
-
quiet: true
|
|
58744
|
-
});
|
|
58745
|
-
return result.error === void 0;
|
|
58746
|
-
} catch {
|
|
58747
|
-
return false;
|
|
58748
|
-
}
|
|
58749
|
-
}
|
|
58750
|
-
var PLACEHOLDER_RE;
|
|
58751
|
-
var init_config = __esm({
|
|
58752
|
-
"node_modules/@chrischall/mcp-utils/dist/config/index.js"() {
|
|
58753
|
-
PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
58754
|
-
}
|
|
58755
|
-
});
|
|
58756
|
-
|
|
58757
59210
|
// node_modules/@chrischall/mcp-utils/dist/fs/confine.js
|
|
58758
59211
|
import { existsSync, realpathSync } from "node:fs";
|
|
58759
59212
|
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
|
|
@@ -59275,6 +59728,16 @@ async function uniquePath(dir, base, ext) {
|
|
|
59275
59728
|
function sniffMimeBytes(buf) {
|
|
59276
59729
|
return sniffImageMime(buf) ?? "image/png";
|
|
59277
59730
|
}
|
|
59731
|
+
function localImageMime(resolvedPath, head) {
|
|
59732
|
+
const mime = sniffImageMime(head);
|
|
59733
|
+
if (!mime) {
|
|
59734
|
+
throw new McpToolError(
|
|
59735
|
+
`Cannot tell what kind of image ${resolvedPath} is \u2014 its bytes are not PNG, JPEG or WebP, so it is not sent to Gemini as a reference image.`,
|
|
59736
|
+
{ hint: "Pass a PNG, JPEG or WebP file (convert other formats first)." }
|
|
59737
|
+
);
|
|
59738
|
+
}
|
|
59739
|
+
return mime;
|
|
59740
|
+
}
|
|
59278
59741
|
function sniffImageMime(buf) {
|
|
59279
59742
|
if (buf.length >= 4 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) {
|
|
59280
59743
|
return "image/png";
|
|
@@ -59350,7 +59813,7 @@ async function previewImageInput(path) {
|
|
|
59350
59813
|
try {
|
|
59351
59814
|
const head = Buffer.alloc(16);
|
|
59352
59815
|
const { bytesRead } = await fh.read(head, 0, 16, 0);
|
|
59353
|
-
return { path: resolved, mimeType:
|
|
59816
|
+
return { path: resolved, mimeType: localImageMime(resolved, head.subarray(0, bytesRead)), size };
|
|
59354
59817
|
} finally {
|
|
59355
59818
|
await fh.close();
|
|
59356
59819
|
}
|
|
@@ -59379,7 +59842,7 @@ async function detectUploadMime(resolvedPath) {
|
|
|
59379
59842
|
async function readImageAsInline(path) {
|
|
59380
59843
|
const resolved = resolveImagePath(path);
|
|
59381
59844
|
const buf = await readFile(resolved);
|
|
59382
|
-
const mimeType =
|
|
59845
|
+
const mimeType = localImageMime(resolved, buf);
|
|
59383
59846
|
return { base64: buf.toString("base64"), mimeType };
|
|
59384
59847
|
}
|
|
59385
59848
|
function decodeImageInput(input2) {
|
|
@@ -59513,7 +59976,7 @@ async function loadStdioDotenv() {
|
|
|
59513
59976
|
}
|
|
59514
59977
|
|
|
59515
59978
|
// src/version.ts
|
|
59516
|
-
var VERSION = "2.
|
|
59979
|
+
var VERSION = "2.3.0";
|
|
59517
59980
|
|
|
59518
59981
|
// src/errors.ts
|
|
59519
59982
|
init_dist2();
|
|
@@ -60942,21 +61405,21 @@ function assertLocalInputsAvailable(sink, args) {
|
|
|
60942
61405
|
|
|
60943
61406
|
// src/jobs.ts
|
|
60944
61407
|
init_dist2();
|
|
60945
|
-
import { randomUUID, createHash as
|
|
61408
|
+
import { randomUUID, createHash as createHash5 } from "node:crypto";
|
|
60946
61409
|
|
|
60947
61410
|
// src/job-store.ts
|
|
60948
|
-
import { createHash } from "node:crypto";
|
|
61411
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
60949
61412
|
var HEARTBEAT_MS = 6e4;
|
|
60950
61413
|
var EXECUTOR_LOST_AFTER_MS = 3 * HEARTBEAT_MS;
|
|
60951
|
-
var
|
|
61414
|
+
var PREFIX2 = "jobs";
|
|
60952
61415
|
function keyHash(idempotencyKey) {
|
|
60953
|
-
return
|
|
61416
|
+
return createHash4("sha256").update(idempotencyKey).digest("hex").slice(0, 32);
|
|
60954
61417
|
}
|
|
60955
61418
|
function createBlobJobStore(bucket, opts) {
|
|
60956
61419
|
const now = opts.now ?? (() => Date.now());
|
|
60957
61420
|
const tenantOf = async () => typeof opts.tenant === "function" ? opts.tenant() : opts.tenant;
|
|
60958
|
-
const recordKey = async (jobId) => `${
|
|
60959
|
-
const pointerKey = async (k) => `${
|
|
61421
|
+
const recordKey = async (jobId) => `${PREFIX2}/${await tenantOf()}/${encodeURIComponent(jobId)}.json`;
|
|
61422
|
+
const pointerKey = async (k) => `${PREFIX2}/${await tenantOf()}/keys/${keyHash(k)}.json`;
|
|
60960
61423
|
async function writeJson(key, value) {
|
|
60961
61424
|
try {
|
|
60962
61425
|
await bucket.put(key, JSON.stringify(value), { httpMetadata: { contentType: "application/json" } });
|
|
@@ -61100,7 +61563,7 @@ function isExpired(entry, now) {
|
|
|
61100
61563
|
return entry.settledAt !== void 0 && now - entry.settledAt > JOB_TTL_MS;
|
|
61101
61564
|
}
|
|
61102
61565
|
function fingerprintRequest(toolName, parts) {
|
|
61103
|
-
return
|
|
61566
|
+
return createHash5("sha256").update(`${toolName}\0${JSON.stringify(parts)}`).digest("hex");
|
|
61104
61567
|
}
|
|
61105
61568
|
function replayKey(idempotencyKey, fingerprint) {
|
|
61106
61569
|
return `${idempotencyKey}\0${fingerprint}`;
|
|
@@ -61693,24 +62156,43 @@ function attachCost(meta3, model, usage) {
|
|
|
61693
62156
|
// src/tools/_confirm.ts
|
|
61694
62157
|
init_dist2();
|
|
61695
62158
|
init_images();
|
|
61696
|
-
function
|
|
61697
|
-
|
|
61698
|
-
|
|
61699
|
-
|
|
61700
|
-
|
|
61701
|
-
|
|
61702
|
-
|
|
61703
|
-
|
|
61704
|
-
|
|
61705
|
-
}
|
|
62159
|
+
function confirmNote(lead = "Asks the user to confirm first") {
|
|
62160
|
+
return `${lead}: a confirmation prompt where the client supports one; otherwise the first call returns a preview and a confirmToken, and only a repeat call with that token proceeds (see MCP_CONFIRM_MODE).`;
|
|
62161
|
+
}
|
|
62162
|
+
async function confirmWrite(ctx, o) {
|
|
62163
|
+
const preview = {
|
|
62164
|
+
action: o.description,
|
|
62165
|
+
method: o.method,
|
|
62166
|
+
path: o.path,
|
|
62167
|
+
...o.body !== void 0 ? { willSend: o.body } : {}
|
|
62168
|
+
};
|
|
62169
|
+
return requireConfirmationWithFallback(ctx, confirmationFromEnv({
|
|
62170
|
+
action: o.action,
|
|
62171
|
+
message: o.message,
|
|
62172
|
+
details: preview,
|
|
62173
|
+
tool: o.tool,
|
|
62174
|
+
confirmToken: o.confirmToken,
|
|
62175
|
+
subject: () => ({ target: o.target, payload: o.payload, preview })
|
|
62176
|
+
}));
|
|
61706
62177
|
}
|
|
61707
|
-
async function
|
|
61708
|
-
const hasLocal = (imagePaths?.length ?? 0) > 0 || Boolean(videoPath);
|
|
61709
|
-
if (
|
|
62178
|
+
async function confirmLocalInputs(ctx, o) {
|
|
62179
|
+
const hasLocal = (o.imagePaths?.length ?? 0) > 0 || Boolean(o.videoPath);
|
|
62180
|
+
if (!hasLocal) return void 0;
|
|
61710
62181
|
const inputs = [];
|
|
61711
|
-
for (const p of imagePaths ?? []) inputs.push(await previewImageInput(p));
|
|
61712
|
-
if (videoPath) inputs.push(await previewVideoInput(videoPath));
|
|
61713
|
-
return
|
|
62182
|
+
for (const p of o.imagePaths ?? []) inputs.push(await previewImageInput(p));
|
|
62183
|
+
if (o.videoPath) inputs.push(await previewVideoInput(o.videoPath));
|
|
62184
|
+
return confirmWrite(ctx, {
|
|
62185
|
+
tool: o.tool,
|
|
62186
|
+
action: o.action,
|
|
62187
|
+
message: "Review and confirm sending these local files to Google:",
|
|
62188
|
+
description: o.description,
|
|
62189
|
+
method: "POST",
|
|
62190
|
+
path: o.endpoint,
|
|
62191
|
+
body: { inputs },
|
|
62192
|
+
target: "",
|
|
62193
|
+
payload: { request: o.request, inputs },
|
|
62194
|
+
confirmToken: o.confirmToken
|
|
62195
|
+
});
|
|
61714
62196
|
}
|
|
61715
62197
|
|
|
61716
62198
|
// src/tools/generate.ts
|
|
@@ -61721,7 +62203,7 @@ function registerGenerateTools(server, client2) {
|
|
|
61721
62203
|
server.registerTool(
|
|
61722
62204
|
"gemini_image_generate",
|
|
61723
62205
|
{
|
|
61724
|
-
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.",
|
|
62206
|
+
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"),
|
|
61725
62207
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
61726
62208
|
inputSchema: external_exports.object({
|
|
61727
62209
|
prompt: external_exports.string().min(1).describe("Text prompt describing the image"),
|
|
@@ -61736,13 +62218,23 @@ function registerGenerateTools(server, client2) {
|
|
|
61736
62218
|
style: styleSchema,
|
|
61737
62219
|
video_url: external_exports.string().url().optional().describe("Public YouTube URL (or a previously uploaded Files API uri) as a video reference (video\u2192image; use a Flash model e.g. gemini-3.1-flash-image)"),
|
|
61738
62220
|
video_path: videoPathSchema,
|
|
61739
|
-
|
|
62221
|
+
confirmToken: confirmTokenParam,
|
|
61740
62222
|
...sharedImageSchema
|
|
61741
62223
|
})
|
|
61742
62224
|
},
|
|
61743
62225
|
async (args, extra) => {
|
|
61744
62226
|
assertLocalInputsAvailable(client2.mediaSink, args);
|
|
61745
|
-
const
|
|
62227
|
+
const { confirmToken, ...request } = args;
|
|
62228
|
+
const gate = await confirmLocalInputs(extra, {
|
|
62229
|
+
tool: "gemini_image_generate",
|
|
62230
|
+
action: "image.generate",
|
|
62231
|
+
description: SEND_LOCAL_ACTION,
|
|
62232
|
+
endpoint: GENERATE_ENDPOINT,
|
|
62233
|
+
imagePaths: args.images,
|
|
62234
|
+
videoPath: args.video_path,
|
|
62235
|
+
request,
|
|
62236
|
+
confirmToken
|
|
62237
|
+
});
|
|
61746
62238
|
if (gate) return gate;
|
|
61747
62239
|
const count = args.count ?? 1;
|
|
61748
62240
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
@@ -61831,7 +62323,7 @@ function registerGenerateTools(server, client2) {
|
|
|
61831
62323
|
server.registerTool(
|
|
61832
62324
|
"gemini_image_edit",
|
|
61833
62325
|
{
|
|
61834
|
-
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.",
|
|
62326
|
+
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"),
|
|
61835
62327
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
61836
62328
|
inputSchema: external_exports.object({
|
|
61837
62329
|
prompt: external_exports.string().min(1).describe("Instruction describing the edit or composition"),
|
|
@@ -61843,14 +62335,23 @@ function registerGenerateTools(server, client2) {
|
|
|
61843
62335
|
characters: charactersSchema,
|
|
61844
62336
|
style: styleSchema,
|
|
61845
62337
|
filename: external_exports.string().optional().describe("Base filename for the output image (extension stripped; default: slugified prompt)"),
|
|
61846
|
-
|
|
62338
|
+
confirmToken: confirmTokenParam,
|
|
61847
62339
|
...sharedImageSchema
|
|
61848
62340
|
})
|
|
61849
62341
|
},
|
|
61850
62342
|
async (args, extra) => {
|
|
61851
62343
|
assertLocalInputsAvailable(client2.mediaSink, args);
|
|
61852
62344
|
if (!args.characters?.length) requireImageInput(args);
|
|
61853
|
-
const
|
|
62345
|
+
const { confirmToken, ...request } = args;
|
|
62346
|
+
const gate = await confirmLocalInputs(extra, {
|
|
62347
|
+
tool: "gemini_image_edit",
|
|
62348
|
+
action: "image.edit",
|
|
62349
|
+
description: SEND_LOCAL_ACTION,
|
|
62350
|
+
endpoint: GENERATE_ENDPOINT,
|
|
62351
|
+
imagePaths: args.images,
|
|
62352
|
+
request,
|
|
62353
|
+
confirmToken
|
|
62354
|
+
});
|
|
61854
62355
|
if (gate) return gate;
|
|
61855
62356
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
61856
62357
|
const aspectRatio = resolveAspectRatio(args, ASPECT_RATIOS);
|
|
@@ -61913,7 +62414,7 @@ function registerSetTools(server, client2) {
|
|
|
61913
62414
|
server.registerTool(
|
|
61914
62415
|
"gemini_image_set",
|
|
61915
62416
|
{
|
|
61916
|
-
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.',
|
|
62417
|
+
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"),
|
|
61917
62418
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
61918
62419
|
inputSchema: external_exports.object({
|
|
61919
62420
|
master_prompt: external_exports.string().min(1).describe("Prompt for the master/reference image"),
|
|
@@ -61928,7 +62429,7 @@ function registerSetTools(server, client2) {
|
|
|
61928
62429
|
characters: charactersSchema,
|
|
61929
62430
|
style: styleSchema,
|
|
61930
62431
|
master_images_base64: imagesBase64Schema("Reference images for the master generation", "master_images"),
|
|
61931
|
-
|
|
62432
|
+
confirmToken: confirmTokenParam,
|
|
61932
62433
|
...sharedImageSchema
|
|
61933
62434
|
})
|
|
61934
62435
|
},
|
|
@@ -61937,7 +62438,16 @@ function registerSetTools(server, client2) {
|
|
|
61937
62438
|
if (args.scenes && args.count || !args.scenes && !args.count) {
|
|
61938
62439
|
throw new McpToolError("Provide exactly one of `scenes` or `count`.");
|
|
61939
62440
|
}
|
|
61940
|
-
const
|
|
62441
|
+
const { confirmToken, ...request } = args;
|
|
62442
|
+
const gate = await confirmLocalInputs(extra, {
|
|
62443
|
+
tool: "gemini_image_set",
|
|
62444
|
+
action: "image.set",
|
|
62445
|
+
description: "Send local master image input(s) to the Gemini API",
|
|
62446
|
+
endpoint: "/v1beta/models/{model}:generateContent",
|
|
62447
|
+
imagePaths: args.master_images,
|
|
62448
|
+
request,
|
|
62449
|
+
confirmToken
|
|
62450
|
+
});
|
|
61941
62451
|
if (gate) return gate;
|
|
61942
62452
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
61943
62453
|
const aspectRatio = resolveAspectRatio(args, ASPECT_RATIOS);
|
|
@@ -62056,7 +62566,7 @@ init_dist2();
|
|
|
62056
62566
|
|
|
62057
62567
|
// src/client.ts
|
|
62058
62568
|
init_dist2();
|
|
62059
|
-
import { basename } from "node:path";
|
|
62569
|
+
import { basename, delimiter } from "node:path";
|
|
62060
62570
|
|
|
62061
62571
|
// src/library.ts
|
|
62062
62572
|
var LIBRARY_KEY_PREFIX = "lib";
|
|
@@ -62508,6 +63018,18 @@ function normalizeFileName(ref) {
|
|
|
62508
63018
|
}
|
|
62509
63019
|
return match[1];
|
|
62510
63020
|
}
|
|
63021
|
+
function uploadRoots() {
|
|
63022
|
+
const roots = readEnvVar("GEMINI_UPLOAD_DIR")?.split(delimiter).map((r) => r.trim()).filter(Boolean);
|
|
63023
|
+
return roots && roots.length > 0 ? roots : void 0;
|
|
63024
|
+
}
|
|
63025
|
+
function explainOutsideUploadDir(err) {
|
|
63026
|
+
if (err instanceof Error && err.message.startsWith("Path is outside the allowed directories")) {
|
|
63027
|
+
throw new McpToolError(
|
|
63028
|
+
"Refusing to upload a file outside GEMINI_UPLOAD_DIR, which restricts which local files can be uploaded to Google. Move the file into GEMINI_UPLOAD_DIR, or add its directory to GEMINI_UPLOAD_DIR."
|
|
63029
|
+
);
|
|
63030
|
+
}
|
|
63031
|
+
throw err;
|
|
63032
|
+
}
|
|
62511
63033
|
var GeminiClient = class {
|
|
62512
63034
|
/**
|
|
62513
63035
|
* A key handed in explicitly (the connector's per-session key). When absent
|
|
@@ -62670,7 +63192,13 @@ var GeminiClient = class {
|
|
|
62670
63192
|
* (enforced locally before any bytes are sent).
|
|
62671
63193
|
*/
|
|
62672
63194
|
async uploadVideo(path, mimeType) {
|
|
62673
|
-
const
|
|
63195
|
+
const roots = uploadRoots();
|
|
63196
|
+
const blob = await fileBlob(path, {
|
|
63197
|
+
type: mimeType,
|
|
63198
|
+
maxBytes: FILE_MAX_BYTES,
|
|
63199
|
+
label: "Video",
|
|
63200
|
+
...roots ? { allowedRoots: roots } : {}
|
|
63201
|
+
}).catch(explainOutsideUploadDir);
|
|
62674
63202
|
return this.uploadToFilesApi(blob, mimeType, basename(path), blob.size);
|
|
62675
63203
|
}
|
|
62676
63204
|
/**
|
|
@@ -62681,7 +63209,13 @@ var GeminiClient = class {
|
|
|
62681
63209
|
* length on a few-hundred-MB video (chrischall/fleet-audit#117).
|
|
62682
63210
|
*/
|
|
62683
63211
|
async uploadFile(path, mimeType, displayName = basename(path)) {
|
|
62684
|
-
const
|
|
63212
|
+
const roots = uploadRoots();
|
|
63213
|
+
const blob = await fileBlob(path, {
|
|
63214
|
+
type: mimeType,
|
|
63215
|
+
maxBytes: FILE_MAX_BYTES,
|
|
63216
|
+
label: "File",
|
|
63217
|
+
...roots ? { allowedRoots: roots } : {}
|
|
63218
|
+
}).catch(explainOutsideUploadDir);
|
|
62685
63219
|
return this.uploadToFilesApi(blob, mimeType, displayName, blob.size);
|
|
62686
63220
|
}
|
|
62687
63221
|
/**
|
|
@@ -63429,7 +63963,7 @@ function registerInteractTools(server, client2) {
|
|
|
63429
63963
|
server.registerTool(
|
|
63430
63964
|
"gemini_interact",
|
|
63431
63965
|
{
|
|
63432
|
-
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.",
|
|
63966
|
+
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"),
|
|
63433
63967
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63434
63968
|
inputSchema: external_exports.object({
|
|
63435
63969
|
input: external_exports.string().min(1).describe("Text prompt or editing instruction"),
|
|
@@ -63458,7 +63992,7 @@ function registerInteractTools(server, client2) {
|
|
|
63458
63992
|
async: asyncSchema,
|
|
63459
63993
|
max_wait_ms: maxWaitMsSchema,
|
|
63460
63994
|
from_clipboard: external_exports.boolean().optional().describe("Use the image currently on the macOS system clipboard as an input (downscaled to JPEG)"),
|
|
63461
|
-
|
|
63995
|
+
confirmToken: confirmTokenParam
|
|
63462
63996
|
})
|
|
63463
63997
|
},
|
|
63464
63998
|
async (args, extra) => {
|
|
@@ -63483,7 +64017,17 @@ function registerInteractTools(server, client2) {
|
|
|
63483
64017
|
if (previousInteractionId && images?.length) {
|
|
63484
64018
|
({ kept: images, dropped } = splitReattachedOutputs(images, client2.session.writtenOutputs));
|
|
63485
64019
|
}
|
|
63486
|
-
const
|
|
64020
|
+
const { confirmToken, ...request } = args;
|
|
64021
|
+
const gate = await confirmLocalInputs(extra, {
|
|
64022
|
+
tool: "gemini_interact",
|
|
64023
|
+
action: "interaction.create",
|
|
64024
|
+
description: "Send local file input(s) to the Gemini Interactions API",
|
|
64025
|
+
endpoint: "/v1beta/interactions",
|
|
64026
|
+
imagePaths: images,
|
|
64027
|
+
videoPath: args.video_path,
|
|
64028
|
+
request: { ...request, images, previous_interaction_id: previousInteractionId },
|
|
64029
|
+
confirmToken
|
|
64030
|
+
});
|
|
63487
64031
|
if (gate) return gate;
|
|
63488
64032
|
const model = resolveModel(args.model, readEnvVar("GEMINI_IMAGE_MODEL"));
|
|
63489
64033
|
const aspectRatio = resolveAspectRatio(args, ASPECT_RATIOS);
|
|
@@ -63592,7 +64136,7 @@ function registerVideoTools(server, client2) {
|
|
|
63592
64136
|
server.registerTool(
|
|
63593
64137
|
"gemini_video_generate",
|
|
63594
64138
|
{
|
|
63595
|
-
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.',
|
|
64139
|
+
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"),
|
|
63596
64140
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63597
64141
|
inputSchema: external_exports.object({
|
|
63598
64142
|
prompt: external_exports.string().min(1).describe("Description of the video to generate (or the edit instruction when task=edit)"),
|
|
@@ -63616,7 +64160,7 @@ function registerVideoTools(server, client2) {
|
|
|
63616
64160
|
idempotency_key: idempotencyKeySchema,
|
|
63617
64161
|
async: asyncSchema,
|
|
63618
64162
|
max_wait_ms: maxWaitMsSchema,
|
|
63619
|
-
|
|
64163
|
+
confirmToken: confirmTokenParam
|
|
63620
64164
|
})
|
|
63621
64165
|
},
|
|
63622
64166
|
async (args, extra) => {
|
|
@@ -63630,7 +64174,16 @@ function registerVideoTools(server, client2) {
|
|
|
63630
64174
|
}
|
|
63631
64175
|
previousInteractionId = client2.session.lastVideoInteractionId;
|
|
63632
64176
|
}
|
|
63633
|
-
const
|
|
64177
|
+
const { confirmToken, ...request } = args;
|
|
64178
|
+
const gate = await confirmLocalInputs(extra, {
|
|
64179
|
+
tool: "gemini_video_generate",
|
|
64180
|
+
action: "video.generate",
|
|
64181
|
+
description: "Send local image input(s) to the Gemini omni API",
|
|
64182
|
+
endpoint: "/v1beta/interactions",
|
|
64183
|
+
imagePaths: args.images,
|
|
64184
|
+
request: { ...request, previous_interaction_id: previousInteractionId },
|
|
64185
|
+
confirmToken
|
|
64186
|
+
});
|
|
63634
64187
|
if (gate) return gate;
|
|
63635
64188
|
const model = args.model?.trim() || DEFAULT_VIDEO_MODEL;
|
|
63636
64189
|
const aspectRatio = resolveAspectRatio(args, VIDEO_ASPECT_RATIOS);
|
|
@@ -63695,7 +64248,7 @@ function registerMusicTools(server, client2) {
|
|
|
63695
64248
|
server.registerTool(
|
|
63696
64249
|
"gemini_music_generate",
|
|
63697
64250
|
{
|
|
63698
|
-
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.",
|
|
64251
|
+
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"),
|
|
63699
64252
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63700
64253
|
inputSchema: external_exports.object({
|
|
63701
64254
|
prompt: external_exports.string().min(1).describe("Description of the music: mood, genre, instruments, tempo, structure, or lyrics"),
|
|
@@ -63713,13 +64266,22 @@ function registerMusicTools(server, client2) {
|
|
|
63713
64266
|
idempotency_key: idempotencyKeySchema,
|
|
63714
64267
|
async: asyncSchema,
|
|
63715
64268
|
max_wait_ms: maxWaitMsSchema,
|
|
63716
|
-
|
|
64269
|
+
confirmToken: confirmTokenParam
|
|
63717
64270
|
})
|
|
63718
64271
|
},
|
|
63719
64272
|
async (args, extra) => {
|
|
63720
64273
|
assertLocalInputsAvailable(client2.mediaSink, args);
|
|
63721
64274
|
const model = args.model ?? DEFAULT_MUSIC_MODEL;
|
|
63722
|
-
const
|
|
64275
|
+
const { confirmToken, ...request } = args;
|
|
64276
|
+
const gate = await confirmLocalInputs(extra, {
|
|
64277
|
+
tool: "gemini_music_generate",
|
|
64278
|
+
action: "music.generate",
|
|
64279
|
+
description: "Send local image input(s) to the Gemini Lyria API",
|
|
64280
|
+
endpoint: "/v1beta/interactions",
|
|
64281
|
+
imagePaths: args.images,
|
|
64282
|
+
request,
|
|
64283
|
+
confirmToken
|
|
64284
|
+
});
|
|
63723
64285
|
if (gate) return gate;
|
|
63724
64286
|
const fingerprint = fingerprintRequest("gemini_music_generate", {
|
|
63725
64287
|
model,
|
|
@@ -63907,7 +64469,7 @@ function registerFileTools(server, client2) {
|
|
|
63907
64469
|
server.registerTool(
|
|
63908
64470
|
"gemini_upload_file",
|
|
63909
64471
|
{
|
|
63910
|
-
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 ? "" : " 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."),
|
|
64472
|
+
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."),
|
|
63911
64473
|
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
63912
64474
|
inputSchema: external_exports.object({
|
|
63913
64475
|
url: external_exports.string().url().optional().describe(
|
|
@@ -63922,7 +64484,7 @@ function registerFileTools(server, client2) {
|
|
|
63922
64484
|
),
|
|
63923
64485
|
mime_type: external_exports.string().regex(/^[\w.+-]+\/[\w.+-]+$/, "must be a MIME type like image/png").optional().describe("Override the detected MIME type (sniffed from the bytes / taken from the server response otherwise)"),
|
|
63924
64486
|
display_name: external_exports.string().min(1).optional().describe("Human-readable name recorded against the upload"),
|
|
63925
|
-
|
|
64487
|
+
confirmToken: confirmTokenParam
|
|
63926
64488
|
})
|
|
63927
64489
|
},
|
|
63928
64490
|
async (args, extra) => {
|
|
@@ -63949,12 +64511,21 @@ function registerFileTools(server, client2) {
|
|
|
63949
64511
|
{ hint: "Pass mime_type explicitly." }
|
|
63950
64512
|
);
|
|
63951
64513
|
}
|
|
63952
|
-
|
|
63953
|
-
|
|
63954
|
-
|
|
63955
|
-
|
|
63956
|
-
|
|
63957
|
-
|
|
64514
|
+
const { size } = await stat3(resolved);
|
|
64515
|
+
const inputs = [{ path: resolved, mimeType, size }];
|
|
64516
|
+
const gate = await confirmWrite(extra, {
|
|
64517
|
+
tool: "gemini_upload_file",
|
|
64518
|
+
action: "file.upload",
|
|
64519
|
+
message: "Review and confirm uploading this local file to Google:",
|
|
64520
|
+
description: "Upload a local file to the Gemini Files API",
|
|
64521
|
+
method: "POST",
|
|
64522
|
+
path: "/upload/v1beta/files",
|
|
64523
|
+
body: { inputs },
|
|
64524
|
+
target: resolved,
|
|
64525
|
+
payload: { inputs, display_name: args.display_name },
|
|
64526
|
+
confirmToken: args.confirmToken
|
|
64527
|
+
});
|
|
64528
|
+
if (gate) return gate;
|
|
63958
64529
|
localUpload = { path: resolved, mimeType };
|
|
63959
64530
|
}
|
|
63960
64531
|
const uploaded = await withProgressHeartbeat(extra, "Uploading to the Gemini Files API", async () => {
|
|
@@ -64125,15 +64696,25 @@ function registerFileTools(server, client2) {
|
|
|
64125
64696
|
server.registerTool(
|
|
64126
64697
|
"gemini_delete_file",
|
|
64127
64698
|
{
|
|
64128
|
-
description: "Delete an uploaded file, image or photo (by file_uri) from the Gemini Files API before its ~48h expiry. Any tool call still referencing it will then fail with a generic 404, so delete only references you are finished with.",
|
|
64699
|
+
description: "Delete an uploaded file, image or photo (by file_uri) from the Gemini Files API before its ~48h expiry. Any tool call still referencing it will then fail with a generic 404, so delete only references you are finished with. " + confirmNote(),
|
|
64129
64700
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
64130
64701
|
inputSchema: external_exports.object({
|
|
64131
64702
|
file_uri: external_exports.string().min(1).describe("The `files/<id>` reference (or full uri) to delete"),
|
|
64132
|
-
|
|
64703
|
+
confirmToken: confirmTokenParam
|
|
64133
64704
|
})
|
|
64134
64705
|
},
|
|
64135
|
-
async (args) => {
|
|
64136
|
-
const gate =
|
|
64706
|
+
async (args, extra) => {
|
|
64707
|
+
const gate = await confirmWrite(extra, {
|
|
64708
|
+
tool: "gemini_delete_file",
|
|
64709
|
+
action: "file.delete",
|
|
64710
|
+
message: "Review and confirm this deletion:",
|
|
64711
|
+
description: "Delete an uploaded Gemini file",
|
|
64712
|
+
method: "DELETE",
|
|
64713
|
+
path: `/v1beta/${args.file_uri}`,
|
|
64714
|
+
target: args.file_uri,
|
|
64715
|
+
payload: { file_uri: args.file_uri },
|
|
64716
|
+
confirmToken: args.confirmToken
|
|
64717
|
+
});
|
|
64137
64718
|
if (gate) return gate;
|
|
64138
64719
|
await client2.deleteFile(args.file_uri);
|
|
64139
64720
|
return minifiedResult({ deleted: args.file_uri });
|
|
@@ -64269,12 +64850,22 @@ function registerLibraryTools(server, client2) {
|
|
|
64269
64850
|
server.registerTool(
|
|
64270
64851
|
"gemini_delete_character",
|
|
64271
64852
|
{
|
|
64272
|
-
description: "Delete a saved character from this account's reference library. Generation calls naming it will then fail until it is re-saved.",
|
|
64853
|
+
description: "Delete a saved character from this account's reference library. Generation calls naming it will then fail until it is re-saved. " + confirmNote(),
|
|
64273
64854
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
64274
|
-
inputSchema: external_exports.object({ name: NAME_FIELD,
|
|
64855
|
+
inputSchema: external_exports.object({ name: NAME_FIELD, confirmToken: confirmTokenParam })
|
|
64275
64856
|
},
|
|
64276
|
-
async (args) => {
|
|
64277
|
-
const gate =
|
|
64857
|
+
async (args, extra) => {
|
|
64858
|
+
const gate = await confirmWrite(extra, {
|
|
64859
|
+
tool: "gemini_delete_character",
|
|
64860
|
+
action: "character.delete",
|
|
64861
|
+
message: "Review and confirm this deletion:",
|
|
64862
|
+
description: "Delete a saved character (its reference image and description)",
|
|
64863
|
+
method: "DELETE",
|
|
64864
|
+
path: `characters/${args.name}`,
|
|
64865
|
+
target: args.name,
|
|
64866
|
+
payload: { name: args.name },
|
|
64867
|
+
confirmToken: args.confirmToken
|
|
64868
|
+
});
|
|
64278
64869
|
if (gate) return gate;
|
|
64279
64870
|
const existed = await library.deleteCharacter(args.name);
|
|
64280
64871
|
if (!existed) {
|
|
@@ -64338,12 +64929,22 @@ function registerLibraryTools(server, client2) {
|
|
|
64338
64929
|
server.registerTool(
|
|
64339
64930
|
"gemini_delete_style",
|
|
64340
64931
|
{
|
|
64341
|
-
description: "Delete a saved style preset from this account's reference library.",
|
|
64932
|
+
description: "Delete a saved style preset from this account's reference library. " + confirmNote(),
|
|
64342
64933
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
64343
|
-
inputSchema: external_exports.object({ name: NAME_FIELD,
|
|
64934
|
+
inputSchema: external_exports.object({ name: NAME_FIELD, confirmToken: confirmTokenParam })
|
|
64344
64935
|
},
|
|
64345
|
-
async (args) => {
|
|
64346
|
-
const gate =
|
|
64936
|
+
async (args, extra) => {
|
|
64937
|
+
const gate = await confirmWrite(extra, {
|
|
64938
|
+
tool: "gemini_delete_style",
|
|
64939
|
+
action: "style.delete",
|
|
64940
|
+
message: "Review and confirm this deletion:",
|
|
64941
|
+
description: "Delete a saved style preset",
|
|
64942
|
+
method: "DELETE",
|
|
64943
|
+
path: `styles/${args.name}`,
|
|
64944
|
+
target: args.name,
|
|
64945
|
+
payload: { name: args.name },
|
|
64946
|
+
confirmToken: args.confirmToken
|
|
64947
|
+
});
|
|
64347
64948
|
if (gate) return gate;
|
|
64348
64949
|
const existed = await library.deleteStyle(args.name);
|
|
64349
64950
|
if (!existed) {
|