@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.
Files changed (2) hide show
  1. package/dist/index.js +109 -630
  2. 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
- 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");
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
- 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}`);
167940
+ if (result.width && result.height) {
167941
+ details.push(`Dimensions: ${result.width}x${result.height}`);
168044
167942
  }
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
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
- 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}`;
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
- break;
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 (options.negativePrompt) {
168109
- prompt += ` Avoid: ${options.negativePrompt}`;
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 prompt;
168112
- }
168113
- async function buildRequestContent(options, processedInputImage, editingPrompt) {
168114
- const content = [
167975
+ return [
168115
167976
  {
168116
- text: editingPrompt
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
- const mimeType = matches[1];
168466
- const base64Data = matches[2];
168467
- return {
168468
- content: [
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
- isError: false
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
- return {
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
- **Editing Details:**
168709
- - Operation: ${operation}
168212
+ **Generation Details:**
168710
168213
  - Prompt: "${prompt}"
168214
+ - Model: ${result.model}
168711
168215
  - Format: ${result.format}
168712
- - Original Size: ${result.originalSize}
168713
- - Edited Size: ${result.editedSize}
168714
- - Processing Time: ${result.processingTime}ms
168715
- - Quality: ${quality}
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}` : ""}${result.metadata ? `
168723
-
168724
- **Operation Metadata:**
168725
- - Strength: ${result.metadata.strength}
168726
- - Guidance Scale: ${result.metadata.guidanceScale}
168727
- - Seed: ${result.metadata.seed || "random"}` : ""}`
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goonnguyen/human-mcp",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "Human MCP: Bringing Human Capabilities to Coding Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",