@goonnguyen/human-mcp 2.8.2 → 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 +0 -529
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167915,267 +167915,6 @@ function estimateVideoSize(duration, aspectRatio) {
|
|
|
167915
167915
|
// src/tools/hands/processors/image-editor.ts
|
|
167916
167916
|
init_logger();
|
|
167917
167917
|
init_image_loader();
|
|
167918
|
-
async function editImage(geminiClient, options, config) {
|
|
167919
|
-
const startTime = Date.now();
|
|
167920
|
-
try {
|
|
167921
|
-
const processedInputImage = await processImageForEditing(options.inputImage);
|
|
167922
|
-
const editingPrompt = buildEditingPrompt(options);
|
|
167923
|
-
logger2.info(`Image editing operation: ${options.operation}`);
|
|
167924
|
-
logger2.info(`Editing prompt: "${editingPrompt}"`);
|
|
167925
|
-
const model = geminiClient.getImageGenerationModel();
|
|
167926
|
-
const requestContent = await buildRequestContent(options, processedInputImage, editingPrompt);
|
|
167927
|
-
const response = await model.generateContent(requestContent);
|
|
167928
|
-
const result = response.response;
|
|
167929
|
-
const candidates = result.candidates;
|
|
167930
|
-
logger2.debug(`Gemini API response structure: ${JSON.stringify({
|
|
167931
|
-
hasCandidates: !!candidates,
|
|
167932
|
-
candidatesLength: candidates?.length,
|
|
167933
|
-
firstCandidate: candidates?.[0] ? {
|
|
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");
|
|
168009
|
-
}
|
|
168010
|
-
}
|
|
168011
|
-
return {
|
|
168012
|
-
editedImageData: resultData,
|
|
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}`);
|
|
168044
|
-
}
|
|
168045
|
-
throw new Error("Image editing failed due to an unexpected error");
|
|
168046
|
-
}
|
|
168047
|
-
}
|
|
168048
|
-
async function processImageForEditing(inputImage) {
|
|
168049
|
-
try {
|
|
168050
|
-
const result = await loadImageForProcessing(inputImage, {
|
|
168051
|
-
fetchTimeout: 30000,
|
|
168052
|
-
maxWidth: 1024,
|
|
168053
|
-
maxHeight: 1024,
|
|
168054
|
-
quality: 85
|
|
168055
|
-
});
|
|
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}`);
|
|
168062
|
-
}
|
|
168063
|
-
}
|
|
168064
|
-
function buildEditingPrompt(options) {
|
|
168065
|
-
let prompt = options.prompt;
|
|
168066
|
-
switch (options.operation) {
|
|
168067
|
-
case "inpaint":
|
|
168068
|
-
prompt = `Edit the specified area of this image: ${prompt}`;
|
|
168069
|
-
if (options.maskPrompt) {
|
|
168070
|
-
prompt += `. Focus on the area described as: ${options.maskPrompt}`;
|
|
168071
|
-
}
|
|
168072
|
-
break;
|
|
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}`;
|
|
168100
|
-
}
|
|
168101
|
-
break;
|
|
168102
|
-
}
|
|
168103
|
-
if (options.quality === "high") {
|
|
168104
|
-
prompt += ". High quality, detailed result.";
|
|
168105
|
-
} else if (options.quality === "draft") {
|
|
168106
|
-
prompt += ". Quick draft version.";
|
|
168107
|
-
}
|
|
168108
|
-
if (options.negativePrompt) {
|
|
168109
|
-
prompt += ` Avoid: ${options.negativePrompt}`;
|
|
168110
|
-
}
|
|
168111
|
-
return prompt;
|
|
168112
|
-
}
|
|
168113
|
-
async function buildRequestContent(options, processedInputImage, editingPrompt) {
|
|
168114
|
-
const content = [
|
|
168115
|
-
{
|
|
168116
|
-
text: editingPrompt
|
|
168117
|
-
},
|
|
168118
|
-
{
|
|
168119
|
-
inlineData: {
|
|
168120
|
-
data: processedInputImage.data,
|
|
168121
|
-
mimeType: processedInputImage.mimeType
|
|
168122
|
-
}
|
|
168123
|
-
}
|
|
168124
|
-
];
|
|
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
|
-
}
|
|
168179
167918
|
|
|
168180
167919
|
// src/tools/hands/index.ts
|
|
168181
167920
|
init_logger();
|
|
@@ -168331,177 +168070,6 @@ async function registerHandsTool(server, config) {
|
|
|
168331
168070
|
};
|
|
168332
168071
|
}
|
|
168333
168072
|
});
|
|
168334
|
-
server.registerTool("gemini_edit_image", {
|
|
168335
|
-
title: "Gemini Image Editing Tool",
|
|
168336
|
-
description: "Edit images using AI with various operations like inpainting, outpainting, style transfer, object manipulation, and composition",
|
|
168337
|
-
inputSchema: {
|
|
168338
|
-
operation: exports_external.enum([
|
|
168339
|
-
"inpaint",
|
|
168340
|
-
"outpaint",
|
|
168341
|
-
"style_transfer",
|
|
168342
|
-
"object_manipulation",
|
|
168343
|
-
"multi_image_compose"
|
|
168344
|
-
]).describe("Type of image editing operation to perform"),
|
|
168345
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168346
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired edit"),
|
|
168347
|
-
mask_image: exports_external.string().optional().describe("Base64 encoded mask image for inpainting (white = edit area, black = keep)"),
|
|
168348
|
-
mask_prompt: exports_external.string().optional().describe("Text description of the area to mask for editing"),
|
|
168349
|
-
expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().describe("Direction to expand the image"),
|
|
168350
|
-
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)"),
|
|
168351
|
-
style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
|
|
168352
|
-
style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
|
|
168353
|
-
target_object: exports_external.string().optional().describe("Description of the object to manipulate"),
|
|
168354
|
-
manipulation_type: exports_external.enum(["move", "resize", "remove", "replace", "duplicate"]).optional().describe("Type of object manipulation"),
|
|
168355
|
-
target_position: exports_external.string().optional().describe("New position for the object (e.g., 'center', 'top-left')"),
|
|
168356
|
-
secondary_images: exports_external.array(exports_external.string()).optional().describe("Array of base64 encoded images for composition"),
|
|
168357
|
-
composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().describe("How to combine multiple images"),
|
|
168358
|
-
blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().describe("Blending mode for image composition"),
|
|
168359
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the edited image"),
|
|
168360
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
|
|
168361
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168362
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168363
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format for the edited image"),
|
|
168364
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level of the editing")
|
|
168365
|
-
}
|
|
168366
|
-
}, async (args) => {
|
|
168367
|
-
try {
|
|
168368
|
-
return await handleImageEditing(geminiClient, args, config);
|
|
168369
|
-
} catch (error) {
|
|
168370
|
-
const mcpError = handleError(error);
|
|
168371
|
-
logger2.error(`Tool gemini_edit_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_inpaint_image", {
|
|
168382
|
-
title: "Gemini Image Inpainting Tool",
|
|
168383
|
-
description: "Fill or modify specific areas of an image based on a text prompt and mask",
|
|
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 what to paint in the masked area"),
|
|
168387
|
-
mask_image: exports_external.string().optional().describe("Base64 encoded mask image (white = edit area, black = keep)"),
|
|
168388
|
-
mask_prompt: exports_external.string().optional().describe("Text description of the area to mask for editing"),
|
|
168389
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the edited area"),
|
|
168390
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing effect"),
|
|
168391
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168392
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168393
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168394
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168395
|
-
}
|
|
168396
|
-
}, async (args) => {
|
|
168397
|
-
try {
|
|
168398
|
-
const inpaintArgs = { ...args, operation: "inpaint" };
|
|
168399
|
-
return await handleImageEditing(geminiClient, inpaintArgs, config);
|
|
168400
|
-
} catch (error) {
|
|
168401
|
-
const mcpError = handleError(error);
|
|
168402
|
-
logger2.error(`Tool gemini_inpaint_image error:`, mcpError);
|
|
168403
|
-
return {
|
|
168404
|
-
content: [{
|
|
168405
|
-
type: "text",
|
|
168406
|
-
text: `Error: ${mcpError.message}`
|
|
168407
|
-
}],
|
|
168408
|
-
isError: true
|
|
168409
|
-
};
|
|
168410
|
-
}
|
|
168411
|
-
});
|
|
168412
|
-
server.registerTool("gemini_outpaint_image", {
|
|
168413
|
-
title: "Gemini Image Outpainting Tool",
|
|
168414
|
-
description: "Expand an image beyond its original borders in specified directions",
|
|
168415
|
-
inputSchema: {
|
|
168416
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168417
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of what to add in the expanded areas"),
|
|
168418
|
-
expand_direction: exports_external.enum(["all", "left", "right", "top", "bottom", "horizontal", "vertical"]).optional().default("all").describe("Direction to expand the image"),
|
|
168419
|
-
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)"),
|
|
168420
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the expanded areas"),
|
|
168421
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the editing 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 outpaintArgs = { ...args, operation: "outpaint" };
|
|
168430
|
-
return await handleImageEditing(geminiClient, outpaintArgs, config);
|
|
168431
|
-
} catch (error) {
|
|
168432
|
-
const mcpError = handleError(error);
|
|
168433
|
-
logger2.error(`Tool gemini_outpaint_image error:`, mcpError);
|
|
168434
|
-
return {
|
|
168435
|
-
content: [{
|
|
168436
|
-
type: "text",
|
|
168437
|
-
text: `Error: ${mcpError.message}`
|
|
168438
|
-
}],
|
|
168439
|
-
isError: true
|
|
168440
|
-
};
|
|
168441
|
-
}
|
|
168442
|
-
});
|
|
168443
|
-
server.registerTool("gemini_style_transfer_image", {
|
|
168444
|
-
title: "Gemini Style Transfer Tool",
|
|
168445
|
-
description: "Transfer the style from one image to another using AI",
|
|
168446
|
-
inputSchema: {
|
|
168447
|
-
input_image: exports_external.string().describe("Base64 encoded image or file path to the input image"),
|
|
168448
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of the desired style"),
|
|
168449
|
-
style_image: exports_external.string().optional().describe("Base64 encoded reference image for style transfer"),
|
|
168450
|
-
style_strength: exports_external.number().min(0.1).max(1).optional().default(0.7).describe("Strength of style application"),
|
|
168451
|
-
negative_prompt: exports_external.string().optional().describe("What style elements to avoid"),
|
|
168452
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168453
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168454
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168455
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168456
|
-
}
|
|
168457
|
-
}, async (args) => {
|
|
168458
|
-
try {
|
|
168459
|
-
const styleArgs = { ...args, operation: "style_transfer" };
|
|
168460
|
-
return await handleImageEditing(geminiClient, styleArgs, config);
|
|
168461
|
-
} catch (error) {
|
|
168462
|
-
const mcpError = handleError(error);
|
|
168463
|
-
logger2.error(`Tool gemini_style_transfer_image error:`, mcpError);
|
|
168464
|
-
return {
|
|
168465
|
-
content: [{
|
|
168466
|
-
type: "text",
|
|
168467
|
-
text: `Error: ${mcpError.message}`
|
|
168468
|
-
}],
|
|
168469
|
-
isError: true
|
|
168470
|
-
};
|
|
168471
|
-
}
|
|
168472
|
-
});
|
|
168473
|
-
server.registerTool("gemini_compose_images", {
|
|
168474
|
-
title: "Gemini Image Composition Tool",
|
|
168475
|
-
description: "Combine multiple images into a single composition using AI",
|
|
168476
|
-
inputSchema: {
|
|
168477
|
-
input_image: exports_external.string().describe("Base64 encoded primary image"),
|
|
168478
|
-
secondary_images: exports_external.array(exports_external.string()).describe("Array of base64 encoded secondary images to compose"),
|
|
168479
|
-
prompt: exports_external.string().min(1, "Prompt cannot be empty").describe("Text description of how to compose the images"),
|
|
168480
|
-
composition_layout: exports_external.enum(["blend", "collage", "overlay", "side_by_side"]).optional().default("blend").describe("How to combine the images"),
|
|
168481
|
-
blend_mode: exports_external.enum(["normal", "multiply", "screen", "overlay", "soft_light"]).optional().default("normal").describe("Blending mode for image composition"),
|
|
168482
|
-
negative_prompt: exports_external.string().optional().describe("What to avoid in the composition"),
|
|
168483
|
-
strength: exports_external.number().min(0.1).max(1).optional().default(0.8).describe("Strength of the composition effect"),
|
|
168484
|
-
guidance_scale: exports_external.number().min(1).max(20).optional().default(7.5).describe("How closely to follow the prompt"),
|
|
168485
|
-
seed: exports_external.number().int().min(0).optional().describe("Random seed for reproducible results"),
|
|
168486
|
-
output_format: exports_external.enum(["base64", "url"]).optional().default("base64").describe("Output format"),
|
|
168487
|
-
quality: exports_external.enum(["draft", "standard", "high"]).optional().default("standard").describe("Quality level")
|
|
168488
|
-
}
|
|
168489
|
-
}, async (args) => {
|
|
168490
|
-
try {
|
|
168491
|
-
const composeArgs = { ...args, operation: "multi_image_compose" };
|
|
168492
|
-
return await handleImageEditing(geminiClient, composeArgs, config);
|
|
168493
|
-
} catch (error) {
|
|
168494
|
-
const mcpError = handleError(error);
|
|
168495
|
-
logger2.error(`Tool gemini_compose_images error:`, mcpError);
|
|
168496
|
-
return {
|
|
168497
|
-
content: [{
|
|
168498
|
-
type: "text",
|
|
168499
|
-
text: `Error: ${mcpError.message}`
|
|
168500
|
-
}],
|
|
168501
|
-
isError: true
|
|
168502
|
-
};
|
|
168503
|
-
}
|
|
168504
|
-
});
|
|
168505
168073
|
}
|
|
168506
168074
|
async function handleImageGeneration(geminiClient, args, config) {
|
|
168507
168075
|
const input = ImageGenerationInputSchema.parse(args);
|
|
@@ -168668,103 +168236,6 @@ async function handleImageToVideoGeneration(geminiClient, args, config) {
|
|
|
168668
168236
|
isError: false
|
|
168669
168237
|
};
|
|
168670
168238
|
}
|
|
168671
|
-
async function handleImageEditing(geminiClient, args, config) {
|
|
168672
|
-
const input = ImageEditingInputSchema.parse(args);
|
|
168673
|
-
const {
|
|
168674
|
-
operation,
|
|
168675
|
-
input_image,
|
|
168676
|
-
prompt,
|
|
168677
|
-
mask_image,
|
|
168678
|
-
mask_prompt,
|
|
168679
|
-
expand_direction,
|
|
168680
|
-
expansion_ratio,
|
|
168681
|
-
style_image,
|
|
168682
|
-
style_strength,
|
|
168683
|
-
target_object,
|
|
168684
|
-
manipulation_type,
|
|
168685
|
-
target_position,
|
|
168686
|
-
secondary_images,
|
|
168687
|
-
composition_layout,
|
|
168688
|
-
blend_mode,
|
|
168689
|
-
negative_prompt,
|
|
168690
|
-
strength,
|
|
168691
|
-
guidance_scale,
|
|
168692
|
-
seed,
|
|
168693
|
-
output_format,
|
|
168694
|
-
quality
|
|
168695
|
-
} = input;
|
|
168696
|
-
logger2.info(`Editing image with operation: "${operation}" and prompt: "${prompt}"`);
|
|
168697
|
-
const editingOptions = {
|
|
168698
|
-
operation,
|
|
168699
|
-
inputImage: input_image,
|
|
168700
|
-
prompt,
|
|
168701
|
-
maskImage: mask_image,
|
|
168702
|
-
maskPrompt: mask_prompt,
|
|
168703
|
-
expandDirection: expand_direction,
|
|
168704
|
-
expansionRatio: expansion_ratio || 1.5,
|
|
168705
|
-
styleImage: style_image,
|
|
168706
|
-
styleStrength: style_strength || 0.7,
|
|
168707
|
-
targetObject: target_object,
|
|
168708
|
-
manipulationType: manipulation_type,
|
|
168709
|
-
targetPosition: target_position,
|
|
168710
|
-
secondaryImages: secondary_images,
|
|
168711
|
-
compositionLayout: composition_layout,
|
|
168712
|
-
blendMode: blend_mode,
|
|
168713
|
-
negativePrompt: negative_prompt,
|
|
168714
|
-
strength: strength || 0.8,
|
|
168715
|
-
guidanceScale: guidance_scale || 7.5,
|
|
168716
|
-
seed,
|
|
168717
|
-
outputFormat: output_format || "base64",
|
|
168718
|
-
quality: quality || "standard",
|
|
168719
|
-
fetchTimeout: config.server.fetchTimeout,
|
|
168720
|
-
saveToFile: true,
|
|
168721
|
-
uploadToR2: config.cloudflare?.accessKey ? true : false,
|
|
168722
|
-
filePrefix: `edited-${operation}`
|
|
168723
|
-
};
|
|
168724
|
-
const result = await editImage(geminiClient, editingOptions, config);
|
|
168725
|
-
let base64Data;
|
|
168726
|
-
let mimeType;
|
|
168727
|
-
if (result.editedImageData.startsWith("data:")) {
|
|
168728
|
-
const matches = result.editedImageData.match(/data:([^;]+);base64,(.+)/);
|
|
168729
|
-
if (matches && matches[1] && matches[2]) {
|
|
168730
|
-
mimeType = matches[1];
|
|
168731
|
-
base64Data = matches[2];
|
|
168732
|
-
}
|
|
168733
|
-
}
|
|
168734
|
-
const contextText = `✅ Image edited successfully using ${operation} operation
|
|
168735
|
-
|
|
168736
|
-
**Editing Details:**
|
|
168737
|
-
- Operation: ${operation}
|
|
168738
|
-
- Prompt: "${prompt}"
|
|
168739
|
-
- Format: ${result.format}
|
|
168740
|
-
- Original Size: ${result.originalSize}
|
|
168741
|
-
- Edited Size: ${result.editedSize}
|
|
168742
|
-
- Processing Time: ${result.processingTime}ms
|
|
168743
|
-
- Quality: ${quality}
|
|
168744
|
-
- Timestamp: ${new Date().toISOString()}${result.filePath ? `
|
|
168745
|
-
|
|
168746
|
-
**File Information:**
|
|
168747
|
-
- File Path: ${result.filePath}
|
|
168748
|
-
- File Name: ${result.fileName}
|
|
168749
|
-
- File Size: ${result.fileSize} bytes` : ""}${result.fileUrl ? `
|
|
168750
|
-
- Public URL: ${result.fileUrl}` : ""}${result.metadata ? `
|
|
168751
|
-
|
|
168752
|
-
**Operation Metadata:**
|
|
168753
|
-
- Strength: ${result.metadata.strength}
|
|
168754
|
-
- Guidance Scale: ${result.metadata.guidanceScale}
|
|
168755
|
-
- Seed: ${result.metadata.seed || "random"}` : ""}`;
|
|
168756
|
-
const formattedResponse = formatMediaResponse({
|
|
168757
|
-
url: result.fileUrl,
|
|
168758
|
-
filePath: result.filePath,
|
|
168759
|
-
base64: base64Data,
|
|
168760
|
-
mimeType,
|
|
168761
|
-
size: result.fileSize
|
|
168762
|
-
}, config, contextText);
|
|
168763
|
-
return {
|
|
168764
|
-
content: formattedResponse,
|
|
168765
|
-
isError: false
|
|
168766
|
-
};
|
|
168767
|
-
}
|
|
168768
168239
|
|
|
168769
168240
|
// src/tools/mouth/schemas.ts
|
|
168770
168241
|
var VoiceNames = [
|