@goonnguyen/human-mcp 2.8.1 → 2.8.3
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/dist/index.js +109 -630
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167915,271 +167915,72 @@ function estimateVideoSize(duration, aspectRatio) {
|
|
|
167915
167915
|
// src/tools/hands/processors/image-editor.ts
|
|
167916
167916
|
init_logger();
|
|
167917
167917
|
init_image_loader();
|
|
167918
|
-
|
|
167919
|
-
|
|
167920
|
-
|
|
167921
|
-
|
|
167922
|
-
|
|
167923
|
-
|
|
167924
|
-
|
|
167925
|
-
|
|
167926
|
-
|
|
167927
|
-
const response =
|
|
167928
|
-
|
|
167929
|
-
|
|
167930
|
-
|
|
167931
|
-
|
|
167932
|
-
|
|
167933
|
-
|
|
167934
|
-
hasContent: !!candidates[0].content,
|
|
167935
|
-
hasParts: !!candidates[0].content?.parts,
|
|
167936
|
-
partsLength: candidates[0].content?.parts?.length
|
|
167937
|
-
} : null
|
|
167938
|
-
})}`);
|
|
167939
|
-
if (!candidates || candidates.length === 0) {
|
|
167940
|
-
logger2.error("No candidates in Gemini response. Full response:", JSON.stringify(result, null, 2));
|
|
167941
|
-
throw new Error("No image candidates returned from Gemini API. This may indicate the API doesn't support image editing yet, or the request format is incorrect.");
|
|
167942
|
-
}
|
|
167943
|
-
const candidate = candidates[0];
|
|
167944
|
-
if (!candidate || !candidate.content) {
|
|
167945
|
-
logger2.error("Invalid candidate structure:", JSON.stringify(candidate, null, 2));
|
|
167946
|
-
throw new Error("Invalid response format from Gemini API: missing candidate content");
|
|
167947
|
-
}
|
|
167948
|
-
if (!candidate.content.parts || candidate.content.parts.length === 0) {
|
|
167949
|
-
logger2.error("No parts in candidate content:", JSON.stringify(candidate.content, null, 2));
|
|
167950
|
-
throw new Error("Invalid response format from Gemini API: missing content parts. Note: Gemini image editing may not be available in the current API version.");
|
|
167951
|
-
}
|
|
167952
|
-
let imageData = null;
|
|
167953
|
-
let mimeType = "image/jpeg";
|
|
167954
|
-
logger2.debug(`Searching for image data in ${candidate.content.parts.length} parts`);
|
|
167955
|
-
for (const part of candidate.content.parts) {
|
|
167956
|
-
logger2.debug(`Part type: ${JSON.stringify(Object.keys(part))}`);
|
|
167957
|
-
if ("inlineData" in part && part.inlineData) {
|
|
167958
|
-
imageData = part.inlineData.data;
|
|
167959
|
-
mimeType = part.inlineData.mimeType || "image/jpeg";
|
|
167960
|
-
logger2.info(`Found image data: ${imageData.length} bytes, type: ${mimeType}`);
|
|
167961
|
-
break;
|
|
167962
|
-
}
|
|
167963
|
-
}
|
|
167964
|
-
if (!imageData) {
|
|
167965
|
-
logger2.error("No image data found in response parts:", JSON.stringify(candidate.content.parts, null, 2));
|
|
167966
|
-
throw new Error("No image data found in Gemini response. The API may have returned text instead of an edited image.");
|
|
167967
|
-
}
|
|
167968
|
-
const processingTime = Date.now() - startTime;
|
|
167969
|
-
let resultData;
|
|
167970
|
-
let format;
|
|
167971
|
-
let filePath;
|
|
167972
|
-
let fileName;
|
|
167973
|
-
let fileUrl;
|
|
167974
|
-
let fileSize;
|
|
167975
|
-
const shouldSaveFile = options.saveToFile !== false;
|
|
167976
|
-
const shouldUploadToR2 = options.uploadToR2 === true;
|
|
167977
|
-
if (shouldSaveFile && config) {
|
|
167978
|
-
try {
|
|
167979
|
-
const savedFile = await saveBase64ToFile(imageData, mimeType, config, {
|
|
167980
|
-
prefix: options.filePrefix || `edited-${options.operation}`,
|
|
167981
|
-
directory: options.saveDirectory,
|
|
167982
|
-
uploadToR2: shouldUploadToR2
|
|
167983
|
-
});
|
|
167984
|
-
filePath = savedFile.filePath;
|
|
167985
|
-
fileName = savedFile.fileName;
|
|
167986
|
-
fileUrl = savedFile.url;
|
|
167987
|
-
fileSize = savedFile.size;
|
|
167988
|
-
logger2.info(`Edited image saved to file: ${filePath}`);
|
|
167989
|
-
if (options.outputFormat === "url") {
|
|
167990
|
-
resultData = fileUrl || filePath || `data:${mimeType};base64,${imageData}`;
|
|
167991
|
-
format = fileUrl ? "url" : "file_path";
|
|
167992
|
-
} else {
|
|
167993
|
-
resultData = `data:${mimeType};base64,${imageData}`;
|
|
167994
|
-
format = "base64_data_uri";
|
|
167995
|
-
}
|
|
167996
|
-
} catch (error) {
|
|
167997
|
-
logger2.warn(`Failed to save edited image file: ${error}. Falling back to base64 only.`);
|
|
167998
|
-
resultData = `data:${mimeType};base64,${imageData}`;
|
|
167999
|
-
format = "base64_data_uri";
|
|
168000
|
-
}
|
|
168001
|
-
} else {
|
|
168002
|
-
if (options.outputFormat === "base64") {
|
|
168003
|
-
resultData = `data:${mimeType};base64,${imageData}`;
|
|
168004
|
-
format = "base64_data_uri";
|
|
168005
|
-
} else {
|
|
168006
|
-
resultData = `data:${mimeType};base64,${imageData}`;
|
|
168007
|
-
format = "base64_data_uri";
|
|
168008
|
-
logger2.warn("URL output format requested but file saving disabled. Returning base64 data URI");
|
|
167918
|
+
|
|
167919
|
+
// src/tools/hands/index.ts
|
|
167920
|
+
init_logger();
|
|
167921
|
+
init_errors();
|
|
167922
|
+
|
|
167923
|
+
// src/utils/response-formatter.ts
|
|
167924
|
+
function formatMediaResponse(result, config, contextText) {
|
|
167925
|
+
const isHttpTransport = config.transport.type === "http" || config.transport.type === "both" && config.transport.http?.enabled;
|
|
167926
|
+
if (isHttpTransport && result.url) {
|
|
167927
|
+
const response = [];
|
|
167928
|
+
response.push({
|
|
167929
|
+
type: "resource",
|
|
167930
|
+
resource: {
|
|
167931
|
+
uri: result.url,
|
|
167932
|
+
mimeType: result.mimeType || "image/png",
|
|
167933
|
+
text: contextText || `Generated media available at: ${result.url}`
|
|
168009
167934
|
}
|
|
167935
|
+
});
|
|
167936
|
+
const details = [];
|
|
167937
|
+
if (result.size) {
|
|
167938
|
+
details.push(`Size: ${(result.size / 1024).toFixed(2)} KB`);
|
|
168010
167939
|
}
|
|
168011
|
-
|
|
168012
|
-
|
|
168013
|
-
format,
|
|
168014
|
-
operation: options.operation,
|
|
168015
|
-
processingTime,
|
|
168016
|
-
originalSize: estimateImageSize2(processedInputImage.data),
|
|
168017
|
-
editedSize: estimateImageSize2(imageData),
|
|
168018
|
-
filePath,
|
|
168019
|
-
fileName,
|
|
168020
|
-
fileUrl,
|
|
168021
|
-
fileSize,
|
|
168022
|
-
metadata: {
|
|
168023
|
-
prompt: options.prompt,
|
|
168024
|
-
operation: options.operation,
|
|
168025
|
-
strength: options.strength,
|
|
168026
|
-
guidanceScale: options.guidanceScale,
|
|
168027
|
-
seed: options.seed
|
|
168028
|
-
}
|
|
168029
|
-
};
|
|
168030
|
-
} catch (error) {
|
|
168031
|
-
const processingTime = Date.now() - startTime;
|
|
168032
|
-
logger2.error(`Image editing failed after ${processingTime}ms:`, error);
|
|
168033
|
-
if (error instanceof Error) {
|
|
168034
|
-
if (error.message.includes("API key")) {
|
|
168035
|
-
throw new Error("Invalid or missing Google AI API key. Please check your GOOGLE_GEMINI_API_KEY environment variable.");
|
|
168036
|
-
}
|
|
168037
|
-
if (error.message.includes("quota") || error.message.includes("rate limit")) {
|
|
168038
|
-
throw new Error("API quota exceeded or rate limit reached. Please try again later.");
|
|
168039
|
-
}
|
|
168040
|
-
if (error.message.includes("safety") || error.message.includes("policy")) {
|
|
168041
|
-
throw new Error("Image editing blocked due to safety policies. Please modify your request and try again.");
|
|
168042
|
-
}
|
|
168043
|
-
throw new Error(`Image editing failed: ${error.message}`);
|
|
167940
|
+
if (result.width && result.height) {
|
|
167941
|
+
details.push(`Dimensions: ${result.width}x${result.height}`);
|
|
168044
167942
|
}
|
|
168045
|
-
|
|
168046
|
-
|
|
168047
|
-
|
|
168048
|
-
|
|
168049
|
-
|
|
168050
|
-
|
|
168051
|
-
fetchTimeout: 30000,
|
|
168052
|
-
maxWidth: 1024,
|
|
168053
|
-
maxHeight: 1024,
|
|
168054
|
-
quality: 85
|
|
167943
|
+
response.push({
|
|
167944
|
+
type: "text",
|
|
167945
|
+
text: `✅ Media generated successfully!
|
|
167946
|
+
|
|
167947
|
+
URL: ${result.url}${details.length > 0 ? `
|
|
167948
|
+
` + details.join(", ") : ""}`
|
|
168055
167949
|
});
|
|
168056
|
-
return
|
|
168057
|
-
data: result.data,
|
|
168058
|
-
mimeType: result.mimeType
|
|
168059
|
-
};
|
|
168060
|
-
} catch (error) {
|
|
168061
|
-
throw new Error(`Failed to process input image: ${error instanceof Error ? error.message : error}`);
|
|
167950
|
+
return response;
|
|
168062
167951
|
}
|
|
168063
|
-
|
|
168064
|
-
|
|
168065
|
-
|
|
168066
|
-
|
|
168067
|
-
|
|
168068
|
-
|
|
168069
|
-
|
|
168070
|
-
|
|
168071
|
-
|
|
168072
|
-
|
|
168073
|
-
case "outpaint":
|
|
168074
|
-
prompt = `Expand this image ${options.expandDirection || "in all directions"}: ${prompt}`;
|
|
168075
|
-
if (options.expansionRatio && options.expansionRatio !== 1.5) {
|
|
168076
|
-
prompt += `. Expansion ratio: ${options.expansionRatio}x`;
|
|
168077
|
-
}
|
|
168078
|
-
break;
|
|
168079
|
-
case "style_transfer":
|
|
168080
|
-
prompt = `Apply the following style to this image: ${prompt}`;
|
|
168081
|
-
if (options.styleStrength && options.styleStrength !== 0.7) {
|
|
168082
|
-
prompt += `. Style strength: ${options.styleStrength}`;
|
|
168083
|
-
}
|
|
168084
|
-
break;
|
|
168085
|
-
case "object_manipulation":
|
|
168086
|
-
if (options.targetObject) {
|
|
168087
|
-
prompt = `${options.manipulationType || "modify"} the ${options.targetObject} in this image: ${prompt}`;
|
|
168088
|
-
if (options.targetPosition) {
|
|
168089
|
-
prompt += `. Position: ${options.targetPosition}`;
|
|
168090
|
-
}
|
|
168091
|
-
}
|
|
168092
|
-
break;
|
|
168093
|
-
case "multi_image_compose":
|
|
168094
|
-
prompt = `Compose multiple images together: ${prompt}`;
|
|
168095
|
-
if (options.compositionLayout) {
|
|
168096
|
-
prompt += `. Layout: ${options.compositionLayout}`;
|
|
168097
|
-
}
|
|
168098
|
-
if (options.blendMode) {
|
|
168099
|
-
prompt += `. Blend mode: ${options.blendMode}`;
|
|
167952
|
+
if (result.base64) {
|
|
167953
|
+
return [
|
|
167954
|
+
{
|
|
167955
|
+
type: "image",
|
|
167956
|
+
data: result.base64,
|
|
167957
|
+
mimeType: result.mimeType || "image/png"
|
|
167958
|
+
},
|
|
167959
|
+
{
|
|
167960
|
+
type: "text",
|
|
167961
|
+
text: contextText || (result.url ? `Image URL: ${result.url}` : "Image generated successfully")
|
|
168100
167962
|
}
|
|
168101
|
-
|
|
168102
|
-
}
|
|
168103
|
-
if (options.quality === "high") {
|
|
168104
|
-
prompt += ". High quality, detailed result.";
|
|
168105
|
-
} else if (options.quality === "draft") {
|
|
168106
|
-
prompt += ". Quick draft version.";
|
|
167963
|
+
];
|
|
168107
167964
|
}
|
|
168108
|
-
if (
|
|
168109
|
-
|
|
167965
|
+
if (result.url) {
|
|
167966
|
+
return [
|
|
167967
|
+
{
|
|
167968
|
+
type: "text",
|
|
167969
|
+
text: `${contextText || "Media generated successfully"}
|
|
167970
|
+
|
|
167971
|
+
URL: ${result.url}`
|
|
167972
|
+
}
|
|
167973
|
+
];
|
|
168110
167974
|
}
|
|
168111
|
-
return
|
|
168112
|
-
}
|
|
168113
|
-
async function buildRequestContent(options, processedInputImage, editingPrompt) {
|
|
168114
|
-
const content = [
|
|
167975
|
+
return [
|
|
168115
167976
|
{
|
|
168116
|
-
|
|
168117
|
-
|
|
168118
|
-
{
|
|
168119
|
-
inlineData: {
|
|
168120
|
-
data: processedInputImage.data,
|
|
168121
|
-
mimeType: processedInputImage.mimeType
|
|
168122
|
-
}
|
|
167977
|
+
type: "text",
|
|
167978
|
+
text: contextText || "Media generated successfully"
|
|
168123
167979
|
}
|
|
168124
167980
|
];
|
|
168125
|
-
if (options.operation === "inpaint" && options.maskImage) {
|
|
168126
|
-
try {
|
|
168127
|
-
const processedMask = await processImageForEditing(options.maskImage);
|
|
168128
|
-
content.push({
|
|
168129
|
-
inlineData: {
|
|
168130
|
-
data: processedMask.data,
|
|
168131
|
-
mimeType: processedMask.mimeType
|
|
168132
|
-
}
|
|
168133
|
-
});
|
|
168134
|
-
} catch (error) {
|
|
168135
|
-
logger2.warn(`Failed to process mask image: ${error}. Proceeding without mask.`);
|
|
168136
|
-
}
|
|
168137
|
-
}
|
|
168138
|
-
if (options.operation === "style_transfer" && options.styleImage) {
|
|
168139
|
-
try {
|
|
168140
|
-
const processedStyle = await processImageForEditing(options.styleImage);
|
|
168141
|
-
content.push({
|
|
168142
|
-
inlineData: {
|
|
168143
|
-
data: processedStyle.data,
|
|
168144
|
-
mimeType: processedStyle.mimeType
|
|
168145
|
-
}
|
|
168146
|
-
});
|
|
168147
|
-
} catch (error) {
|
|
168148
|
-
logger2.warn(`Failed to process style image: ${error}. Proceeding without style reference.`);
|
|
168149
|
-
}
|
|
168150
|
-
}
|
|
168151
|
-
if (options.operation === "multi_image_compose" && options.secondaryImages) {
|
|
168152
|
-
for (const secondaryImage of options.secondaryImages) {
|
|
168153
|
-
try {
|
|
168154
|
-
const processedSecondary = await processImageForEditing(secondaryImage);
|
|
168155
|
-
content.push({
|
|
168156
|
-
inlineData: {
|
|
168157
|
-
data: processedSecondary.data,
|
|
168158
|
-
mimeType: processedSecondary.mimeType
|
|
168159
|
-
}
|
|
168160
|
-
});
|
|
168161
|
-
} catch (error) {
|
|
168162
|
-
logger2.warn(`Failed to process secondary image: ${error}. Skipping this image.`);
|
|
168163
|
-
}
|
|
168164
|
-
}
|
|
168165
|
-
}
|
|
168166
|
-
return content;
|
|
168167
|
-
}
|
|
168168
|
-
function estimateImageSize2(base64Data) {
|
|
168169
|
-
const dataLength = base64Data.length;
|
|
168170
|
-
const estimatedBytes = dataLength * 3 / 4;
|
|
168171
|
-
if (estimatedBytes < 1e5) {
|
|
168172
|
-
return "512x512";
|
|
168173
|
-
} else if (estimatedBytes < 400000) {
|
|
168174
|
-
return "1024x1024";
|
|
168175
|
-
} else {
|
|
168176
|
-
return "1024x1024+";
|
|
168177
|
-
}
|
|
168178
167981
|
}
|
|
168179
167982
|
|
|
168180
167983
|
// src/tools/hands/index.ts
|
|
168181
|
-
init_logger();
|
|
168182
|
-
init_errors();
|
|
168183
167984
|
async function registerHandsTool(server, config) {
|
|
168184
167985
|
const geminiClient = new GeminiClient(config);
|
|
168185
167986
|
server.registerTool("gemini_gen_image", {
|
|
@@ -168269,177 +168070,6 @@ async function registerHandsTool(server, config) {
|
|
|
168269
168070
|
};
|
|
168270
168071
|
}
|
|
168271
168072
|
});
|
|
168272
|
-
server.registerTool("gemini_edit_image", {
|
|
168273
|
-
title: "Gemini Image Editing Tool",
|
|
168274
|
-
description: "Edit images using AI with various operations like inpainting, outpainting, style transfer, object manipulation, and composition",
|
|
168275
|
-
inputSchema: {
|
|
168276
|
-
operation: exports_external.enum([
|
|
168277
|
-
"inpaint",
|
|
168278
|
-
"outpaint",
|
|
168279
|
-
"style_transfer",
|
|
168280
|
-
"object_manipulation",
|
|
168281
|
-
"multi_image_compose"
|
|
168282
|
-
]).describe("Type of image editing operation to perform"),
|
|
168283
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168284
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired edit"),
|
|
168285
|
-
mask_image: exports_external.string().optional().describe("Base64 encoded mask image for inpainting (white = edit area, black = keep)"),
|
|
168286
|
-
mask_prompt: exports_external.string().optional().describe("Text description of the area to mask for editing"),
|
|
168287
|
-
expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().describe("Direction to expand the image"),
|
|
168288
|
-
expansion_ratio: exports_external.number().min(0.1).max(3).optional().default(1.5).describe("How much to expand the image (1.0 = no expansion)"),
|
|
168289
|
-
style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
|
|
168290
|
-
style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
|
|
168291
|
-
target_object: exports_external.string().optional().describe("Description of the object to manipulate"),
|
|
168292
|
-
manipulation_type: exports_external.enum(["move", "resize", "remove", "replace", "duplicate"]).optional().describe("Type of object manipulation"),
|
|
168293
|
-
target_position: exports_external.string().optional().describe("New position for the object (e.g., 'center', 'top-left')"),
|
|
168294
|
-
secondary_images: exports_external.array(exports_external.string()).optional().describe("Array of base64 encoded images for composition"),
|
|
168295
|
-
composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().describe("How to combine multiple images"),
|
|
168296
|
-
blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().describe("Blending mode for image composition"),
|
|
168297
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the edited image"),
|
|
168298
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
|
|
168299
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168300
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168301
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format for the edited image"),
|
|
168302
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level of the editing")
|
|
168303
|
-
}
|
|
168304
|
-
}, async (args) => {
|
|
168305
|
-
try {
|
|
168306
|
-
return await handleImageEditing(geminiClient, args, config);
|
|
168307
|
-
} catch (error) {
|
|
168308
|
-
const mcpError = handleError(error);
|
|
168309
|
-
logger2.error(`Tool gemini_edit_image error:`, mcpError);
|
|
168310
|
-
return {
|
|
168311
|
-
content: [{
|
|
168312
|
-
type: "text",
|
|
168313
|
-
text: `Error: ${mcpError.message}`
|
|
168314
|
-
}],
|
|
168315
|
-
isError: true
|
|
168316
|
-
};
|
|
168317
|
-
}
|
|
168318
|
-
});
|
|
168319
|
-
server.registerTool("gemini_inpaint_image", {
|
|
168320
|
-
title: "Gemini Image Inpainting Tool",
|
|
168321
|
-
description: "Fill or modify specific areas of an image based on a text prompt and mask",
|
|
168322
|
-
inputSchema: {
|
|
168323
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168324
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of what to paint in the masked area"),
|
|
168325
|
-
mask_image: exports_external.string().optional().describe("Base64 encoded mask image (white = edit area, black = keep)"),
|
|
168326
|
-
mask_prompt: exports_external.string().optional().describe("Text description of the area to mask for editing"),
|
|
168327
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the edited area"),
|
|
168328
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
|
|
168329
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168330
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168331
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168332
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168333
|
-
}
|
|
168334
|
-
}, async (args) => {
|
|
168335
|
-
try {
|
|
168336
|
-
const inpaintArgs = { ...args, operation: "inpaint" };
|
|
168337
|
-
return await handleImageEditing(geminiClient, inpaintArgs, config);
|
|
168338
|
-
} catch (error) {
|
|
168339
|
-
const mcpError = handleError(error);
|
|
168340
|
-
logger2.error(`Tool gemini_inpaint_image error:`, mcpError);
|
|
168341
|
-
return {
|
|
168342
|
-
content: [{
|
|
168343
|
-
type: "text",
|
|
168344
|
-
text: `Error: ${mcpError.message}`
|
|
168345
|
-
}],
|
|
168346
|
-
isError: true
|
|
168347
|
-
};
|
|
168348
|
-
}
|
|
168349
|
-
});
|
|
168350
|
-
server.registerTool("gemini_outpaint_image", {
|
|
168351
|
-
title: "Gemini Image Outpainting Tool",
|
|
168352
|
-
description: "Expand an image beyond its original borders in specified directions",
|
|
168353
|
-
inputSchema: {
|
|
168354
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168355
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of what to add in the expanded areas"),
|
|
168356
|
-
expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().default("all").describe("Direction to expand the image"),
|
|
168357
|
-
expansion_ratio: exports_external.number().min(0.1).max(3).optional().default(1.5).describe("How much to expand the image (1.0 = no expansion)"),
|
|
168358
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the expanded areas"),
|
|
168359
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
|
|
168360
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168361
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168362
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168363
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168364
|
-
}
|
|
168365
|
-
}, async (args) => {
|
|
168366
|
-
try {
|
|
168367
|
-
const outpaintArgs = { ...args, operation: "outpaint" };
|
|
168368
|
-
return await handleImageEditing(geminiClient, outpaintArgs, config);
|
|
168369
|
-
} catch (error) {
|
|
168370
|
-
const mcpError = handleError(error);
|
|
168371
|
-
logger2.error(`Tool gemini_outpaint_image error:`, mcpError);
|
|
168372
|
-
return {
|
|
168373
|
-
content: [{
|
|
168374
|
-
type: "text",
|
|
168375
|
-
text: `Error: ${mcpError.message}`
|
|
168376
|
-
}],
|
|
168377
|
-
isError: true
|
|
168378
|
-
};
|
|
168379
|
-
}
|
|
168380
|
-
});
|
|
168381
|
-
server.registerTool("gemini_style_transfer_image", {
|
|
168382
|
-
title: "Gemini Style Transfer Tool",
|
|
168383
|
-
description: "Transfer the style from one image to another using AI",
|
|
168384
|
-
inputSchema: {
|
|
168385
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168386
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired style"),
|
|
168387
|
-
style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
|
|
168388
|
-
style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
|
|
168389
|
-
negative_prompt: exports_external.string().optional().describe("What style elements to avoid"),
|
|
168390
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168391
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168392
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168393
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168394
|
-
}
|
|
168395
|
-
}, async (args) => {
|
|
168396
|
-
try {
|
|
168397
|
-
const styleArgs = { ...args, operation: "style_transfer" };
|
|
168398
|
-
return await handleImageEditing(geminiClient, styleArgs, config);
|
|
168399
|
-
} catch (error) {
|
|
168400
|
-
const mcpError = handleError(error);
|
|
168401
|
-
logger2.error(`Tool gemini_style_transfer_image error:`, mcpError);
|
|
168402
|
-
return {
|
|
168403
|
-
content: [{
|
|
168404
|
-
type: "text",
|
|
168405
|
-
text: `Error: ${mcpError.message}`
|
|
168406
|
-
}],
|
|
168407
|
-
isError: true
|
|
168408
|
-
};
|
|
168409
|
-
}
|
|
168410
|
-
});
|
|
168411
|
-
server.registerTool("gemini_compose_images", {
|
|
168412
|
-
title: "Gemini Image Composition Tool",
|
|
168413
|
-
description: "Combine multiple images into a single composition using AI",
|
|
168414
|
-
inputSchema: {
|
|
168415
|
-
input_image: exports_external.string().describe("Base64 encoded primary image"),
|
|
168416
|
-
secondary_images: exports_external.array(exports_external.string()).describe("Array of base64 encoded secondary images to compose"),
|
|
168417
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of how to compose the images"),
|
|
168418
|
-
composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().default("blend").describe("How to combine the images"),
|
|
168419
|
-
blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().default("normal").describe("Blending mode for image composition"),
|
|
168420
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the composition"),
|
|
168421
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the composition effect"),
|
|
168422
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168423
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168424
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168425
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168426
|
-
}
|
|
168427
|
-
}, async (args) => {
|
|
168428
|
-
try {
|
|
168429
|
-
const composeArgs = { ...args, operation: "multi_image_compose" };
|
|
168430
|
-
return await handleImageEditing(geminiClient, composeArgs, config);
|
|
168431
|
-
} catch (error) {
|
|
168432
|
-
const mcpError = handleError(error);
|
|
168433
|
-
logger2.error(`Tool gemini_compose_images error:`, mcpError);
|
|
168434
|
-
return {
|
|
168435
|
-
content: [{
|
|
168436
|
-
type: "text",
|
|
168437
|
-
text: `Error: ${mcpError.message}`
|
|
168438
|
-
}],
|
|
168439
|
-
isError: true
|
|
168440
|
-
};
|
|
168441
|
-
}
|
|
168442
|
-
});
|
|
168443
168073
|
}
|
|
168444
168074
|
async function handleImageGeneration(geminiClient, args, config) {
|
|
168445
168075
|
const input = ImageGenerationInputSchema.parse(args);
|
|
@@ -168459,21 +168089,16 @@ async function handleImageGeneration(geminiClient, args, config) {
|
|
|
168459
168089
|
filePrefix: "gemini-image"
|
|
168460
168090
|
};
|
|
168461
168091
|
const result = await generateImage(geminiClient, generationOptions, config);
|
|
168092
|
+
let base64Data;
|
|
168093
|
+
let mimeType;
|
|
168462
168094
|
if (result.imageData.startsWith("data:")) {
|
|
168463
168095
|
const matches = result.imageData.match(/data:([^;]+);base64,(.+)/);
|
|
168464
168096
|
if (matches && matches[1] && matches[2]) {
|
|
168465
|
-
|
|
168466
|
-
|
|
168467
|
-
|
|
168468
|
-
|
|
168469
|
-
|
|
168470
|
-
type: "image",
|
|
168471
|
-
data: base64Data,
|
|
168472
|
-
mimeType
|
|
168473
|
-
},
|
|
168474
|
-
{
|
|
168475
|
-
type: "text",
|
|
168476
|
-
text: `✅ Image generated successfully using ${result.model}
|
|
168097
|
+
mimeType = matches[1];
|
|
168098
|
+
base64Data = matches[2];
|
|
168099
|
+
}
|
|
168100
|
+
}
|
|
168101
|
+
const contextText = `✅ Image generated successfully using ${result.model}
|
|
168477
168102
|
|
|
168478
168103
|
**Generation Details:**
|
|
168479
168104
|
- Prompt: "${prompt}"
|
|
@@ -168487,35 +168112,16 @@ async function handleImageGeneration(geminiClient, args, config) {
|
|
|
168487
168112
|
- File Path: ${result.filePath}
|
|
168488
168113
|
- File Name: ${result.fileName}
|
|
168489
168114
|
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168490
|
-
- Public URL: ${result.fileUrl}` : ""}
|
|
168491
|
-
|
|
168492
|
-
|
|
168493
|
-
|
|
168494
|
-
|
|
168495
|
-
|
|
168496
|
-
|
|
168115
|
+
- Public URL: ${result.fileUrl}` : ""}`;
|
|
168116
|
+
const formattedResponse = formatMediaResponse({
|
|
168117
|
+
url: result.fileUrl,
|
|
168118
|
+
filePath: result.filePath,
|
|
168119
|
+
base64: base64Data,
|
|
168120
|
+
mimeType,
|
|
168121
|
+
size: result.fileSize
|
|
168122
|
+
}, config, contextText);
|
|
168497
168123
|
return {
|
|
168498
|
-
content:
|
|
168499
|
-
{
|
|
168500
|
-
type: "text",
|
|
168501
|
-
text: `✅ Image generated successfully!
|
|
168502
|
-
|
|
168503
|
-
**Generation Details:**
|
|
168504
|
-
- Prompt: "${prompt}"
|
|
168505
|
-
- Model: ${result.model}
|
|
168506
|
-
- Format: ${result.format}
|
|
168507
|
-
- Size: ${result.size}
|
|
168508
|
-
- Generation Time: ${result.generationTime}ms${result.filePath ? `
|
|
168509
|
-
|
|
168510
|
-
**File Information:**
|
|
168511
|
-
- File Path: ${result.filePath}
|
|
168512
|
-
- File Name: ${result.fileName}
|
|
168513
|
-
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168514
|
-
- Public URL: ${result.fileUrl}` : ""}
|
|
168515
|
-
|
|
168516
|
-
**Image Data:** ${result.imageData.substring(0, 100)}...`
|
|
168517
|
-
}
|
|
168518
|
-
],
|
|
168124
|
+
content: formattedResponse,
|
|
168519
168125
|
isError: false
|
|
168520
168126
|
};
|
|
168521
168127
|
}
|
|
@@ -168540,34 +168146,32 @@ async function handleVideoGeneration(geminiClient, args, config) {
|
|
|
168540
168146
|
filePrefix: "gemini-video"
|
|
168541
168147
|
};
|
|
168542
168148
|
const result = await generateVideo(geminiClient, generationOptions, config);
|
|
168149
|
+
const contextText = `✅ Video generated successfully!
|
|
168150
|
+
|
|
168151
|
+
**Generation Details:**
|
|
168152
|
+
- Prompt: "${prompt}"
|
|
168153
|
+
- Model: ${result.model}
|
|
168154
|
+
- Format: ${result.format}
|
|
168155
|
+
- Duration: ${result.duration}
|
|
168156
|
+
- Aspect Ratio: ${result.aspectRatio}
|
|
168157
|
+
- FPS: ${result.fps}
|
|
168158
|
+
- Generation Time: ${result.generationTime}ms
|
|
168159
|
+
- Operation ID: ${result.operationId}
|
|
168160
|
+
- Timestamp: ${new Date().toISOString()}${result.filePath ? `
|
|
168161
|
+
|
|
168162
|
+
**File Information:**
|
|
168163
|
+
- File Path: ${result.filePath}
|
|
168164
|
+
- File Name: ${result.fileName}
|
|
168165
|
+
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168166
|
+
- Public URL: ${result.fileUrl}` : ""}`;
|
|
168167
|
+
const formattedResponse = formatMediaResponse({
|
|
168168
|
+
url: result.fileUrl,
|
|
168169
|
+
filePath: result.filePath,
|
|
168170
|
+
mimeType: `video/${result.format}`,
|
|
168171
|
+
size: result.fileSize
|
|
168172
|
+
}, config, contextText);
|
|
168543
168173
|
return {
|
|
168544
|
-
content:
|
|
168545
|
-
{
|
|
168546
|
-
type: "text",
|
|
168547
|
-
text: JSON.stringify({
|
|
168548
|
-
success: true,
|
|
168549
|
-
video: result.filePath ? `File saved to: ${result.filePath}` : result.videoData.substring(0, 100) + "...",
|
|
168550
|
-
format: result.format,
|
|
168551
|
-
model: result.model,
|
|
168552
|
-
prompt,
|
|
168553
|
-
operation_id: result.operationId,
|
|
168554
|
-
file_info: result.filePath ? {
|
|
168555
|
-
file_path: result.filePath,
|
|
168556
|
-
file_name: result.fileName,
|
|
168557
|
-
file_size: result.fileSize,
|
|
168558
|
-
public_url: result.fileUrl
|
|
168559
|
-
} : null,
|
|
168560
|
-
metadata: {
|
|
168561
|
-
timestamp: new Date().toISOString(),
|
|
168562
|
-
generation_time: result.generationTime,
|
|
168563
|
-
duration: result.duration,
|
|
168564
|
-
aspect_ratio: result.aspectRatio,
|
|
168565
|
-
fps: result.fps,
|
|
168566
|
-
size: result.size
|
|
168567
|
-
}
|
|
168568
|
-
}, null, 2)
|
|
168569
|
-
}
|
|
168570
|
-
],
|
|
168174
|
+
content: formattedResponse,
|
|
168571
168175
|
isError: false
|
|
168572
168176
|
};
|
|
168573
168177
|
}
|
|
@@ -168603,157 +168207,32 @@ async function handleImageToVideoGeneration(geminiClient, args, config) {
|
|
|
168603
168207
|
filePrefix: "gemini-image-to-video"
|
|
168604
168208
|
};
|
|
168605
168209
|
const result = await generateImageToVideo(geminiClient, prompt, image_input, generationOptions, config);
|
|
168606
|
-
|
|
168607
|
-
content: [
|
|
168608
|
-
{
|
|
168609
|
-
type: "text",
|
|
168610
|
-
text: JSON.stringify({
|
|
168611
|
-
success: true,
|
|
168612
|
-
video: result.filePath ? `File saved to: ${result.filePath}` : result.videoData.substring(0, 100) + "...",
|
|
168613
|
-
format: result.format,
|
|
168614
|
-
model: result.model,
|
|
168615
|
-
prompt,
|
|
168616
|
-
image_input,
|
|
168617
|
-
operation_id: result.operationId,
|
|
168618
|
-
file_info: result.filePath ? {
|
|
168619
|
-
file_path: result.filePath,
|
|
168620
|
-
file_name: result.fileName,
|
|
168621
|
-
file_size: result.fileSize,
|
|
168622
|
-
public_url: result.fileUrl
|
|
168623
|
-
} : null,
|
|
168624
|
-
metadata: {
|
|
168625
|
-
timestamp: new Date().toISOString(),
|
|
168626
|
-
generation_time: result.generationTime,
|
|
168627
|
-
duration: result.duration,
|
|
168628
|
-
aspect_ratio: result.aspectRatio,
|
|
168629
|
-
fps: result.fps,
|
|
168630
|
-
size: result.size
|
|
168631
|
-
}
|
|
168632
|
-
}, null, 2)
|
|
168633
|
-
}
|
|
168634
|
-
],
|
|
168635
|
-
isError: false
|
|
168636
|
-
};
|
|
168637
|
-
}
|
|
168638
|
-
async function handleImageEditing(geminiClient, args, config) {
|
|
168639
|
-
const input = ImageEditingInputSchema.parse(args);
|
|
168640
|
-
const {
|
|
168641
|
-
operation,
|
|
168642
|
-
input_image,
|
|
168643
|
-
prompt,
|
|
168644
|
-
mask_image,
|
|
168645
|
-
mask_prompt,
|
|
168646
|
-
expand_direction,
|
|
168647
|
-
expansion_ratio,
|
|
168648
|
-
style_image,
|
|
168649
|
-
style_strength,
|
|
168650
|
-
target_object,
|
|
168651
|
-
manipulation_type,
|
|
168652
|
-
target_position,
|
|
168653
|
-
secondary_images,
|
|
168654
|
-
composition_layout,
|
|
168655
|
-
blend_mode,
|
|
168656
|
-
negative_prompt,
|
|
168657
|
-
strength,
|
|
168658
|
-
guidance_scale,
|
|
168659
|
-
seed,
|
|
168660
|
-
output_format,
|
|
168661
|
-
quality
|
|
168662
|
-
} = input;
|
|
168663
|
-
logger2.info(`Editing image with operation: "${operation}" and prompt: "${prompt}"`);
|
|
168664
|
-
const editingOptions = {
|
|
168665
|
-
operation,
|
|
168666
|
-
inputImage: input_image,
|
|
168667
|
-
prompt,
|
|
168668
|
-
maskImage: mask_image,
|
|
168669
|
-
maskPrompt: mask_prompt,
|
|
168670
|
-
expandDirection: expand_direction,
|
|
168671
|
-
expansionRatio: expansion_ratio || 1.5,
|
|
168672
|
-
styleImage: style_image,
|
|
168673
|
-
styleStrength: style_strength || 0.7,
|
|
168674
|
-
targetObject: target_object,
|
|
168675
|
-
manipulationType: manipulation_type,
|
|
168676
|
-
targetPosition: target_position,
|
|
168677
|
-
secondaryImages: secondary_images,
|
|
168678
|
-
compositionLayout: composition_layout,
|
|
168679
|
-
blendMode: blend_mode,
|
|
168680
|
-
negativePrompt: negative_prompt,
|
|
168681
|
-
strength: strength || 0.8,
|
|
168682
|
-
guidanceScale: guidance_scale || 7.5,
|
|
168683
|
-
seed,
|
|
168684
|
-
outputFormat: output_format || "base64",
|
|
168685
|
-
quality: quality || "standard",
|
|
168686
|
-
fetchTimeout: config.server.fetchTimeout,
|
|
168687
|
-
saveToFile: true,
|
|
168688
|
-
uploadToR2: config.cloudflare?.accessKey ? true : false,
|
|
168689
|
-
filePrefix: `edited-${operation}`
|
|
168690
|
-
};
|
|
168691
|
-
const result = await editImage(geminiClient, editingOptions, config);
|
|
168692
|
-
if (result.editedImageData.startsWith("data:")) {
|
|
168693
|
-
const matches = result.editedImageData.match(/data:([^;]+);base64,(.+)/);
|
|
168694
|
-
if (matches && matches[1] && matches[2]) {
|
|
168695
|
-
const mimeType = matches[1];
|
|
168696
|
-
const base64Data = matches[2];
|
|
168697
|
-
return {
|
|
168698
|
-
content: [
|
|
168699
|
-
{
|
|
168700
|
-
type: "image",
|
|
168701
|
-
data: base64Data,
|
|
168702
|
-
mimeType
|
|
168703
|
-
},
|
|
168704
|
-
{
|
|
168705
|
-
type: "text",
|
|
168706
|
-
text: `✅ Image edited successfully using ${operation} operation
|
|
168210
|
+
const contextText = `✅ Video generated from image successfully!
|
|
168707
168211
|
|
|
168708
|
-
**
|
|
168709
|
-
- Operation: ${operation}
|
|
168212
|
+
**Generation Details:**
|
|
168710
168213
|
- Prompt: "${prompt}"
|
|
168214
|
+
- Model: ${result.model}
|
|
168711
168215
|
- Format: ${result.format}
|
|
168712
|
-
-
|
|
168713
|
-
-
|
|
168714
|
-
-
|
|
168715
|
-
-
|
|
168216
|
+
- Duration: ${result.duration}
|
|
168217
|
+
- Aspect Ratio: ${result.aspectRatio}
|
|
168218
|
+
- FPS: ${result.fps}
|
|
168219
|
+
- Generation Time: ${result.generationTime}ms
|
|
168220
|
+
- Operation ID: ${result.operationId}
|
|
168716
168221
|
- Timestamp: ${new Date().toISOString()}${result.filePath ? `
|
|
168717
168222
|
|
|
168718
168223
|
**File Information:**
|
|
168719
168224
|
- File Path: ${result.filePath}
|
|
168720
168225
|
- File Name: ${result.fileName}
|
|
168721
168226
|
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168722
|
-
- Public URL: ${result.fileUrl}` : ""}
|
|
168723
|
-
|
|
168724
|
-
|
|
168725
|
-
|
|
168726
|
-
|
|
168727
|
-
|
|
168728
|
-
|
|
168729
|
-
],
|
|
168730
|
-
isError: false
|
|
168731
|
-
};
|
|
168732
|
-
}
|
|
168733
|
-
}
|
|
168227
|
+
- Public URL: ${result.fileUrl}` : ""}`;
|
|
168228
|
+
const formattedResponse = formatMediaResponse({
|
|
168229
|
+
url: result.fileUrl,
|
|
168230
|
+
filePath: result.filePath,
|
|
168231
|
+
mimeType: `video/${result.format}`,
|
|
168232
|
+
size: result.fileSize
|
|
168233
|
+
}, config, contextText);
|
|
168734
168234
|
return {
|
|
168735
|
-
content:
|
|
168736
|
-
{
|
|
168737
|
-
type: "text",
|
|
168738
|
-
text: `✅ Image edited successfully!
|
|
168739
|
-
|
|
168740
|
-
**Editing Details:**
|
|
168741
|
-
- Operation: ${operation}
|
|
168742
|
-
- Prompt: "${prompt}"
|
|
168743
|
-
- Format: ${result.format}
|
|
168744
|
-
- Original Size: ${result.originalSize}
|
|
168745
|
-
- Edited Size: ${result.editedSize}
|
|
168746
|
-
- Processing Time: ${result.processingTime}ms${result.filePath ? `
|
|
168747
|
-
|
|
168748
|
-
**File Information:**
|
|
168749
|
-
- File Path: ${result.filePath}
|
|
168750
|
-
- File Name: ${result.fileName}
|
|
168751
|
-
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168752
|
-
- Public URL: ${result.fileUrl}` : ""}
|
|
168753
|
-
|
|
168754
|
-
**Edited Image Data:** ${result.editedImageData.substring(0, 100)}...`
|
|
168755
|
-
}
|
|
168756
|
-
],
|
|
168235
|
+
content: formattedResponse,
|
|
168757
168236
|
isError: false
|
|
168758
168237
|
};
|
|
168759
168238
|
}
|