@goonnguyen/human-mcp 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +120 -35
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -160065,41 +160065,98 @@ function getCloudflareR2() {
|
|
|
160065
160065
|
// src/tools/eyes/processors/image.ts
|
|
160066
160066
|
async function processImage(model, source, options) {
|
|
160067
160067
|
const startTime = Date.now();
|
|
160068
|
-
|
|
160069
|
-
|
|
160070
|
-
|
|
160071
|
-
|
|
160072
|
-
|
|
160073
|
-
{
|
|
160074
|
-
|
|
160075
|
-
|
|
160076
|
-
|
|
160077
|
-
|
|
160068
|
+
const maxRetries = 3;
|
|
160069
|
+
let lastError = null;
|
|
160070
|
+
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
160071
|
+
try {
|
|
160072
|
+
logger2.debug(`Processing image (attempt ${attempt}/${maxRetries}): ${source.substring(0, 50)}...`);
|
|
160073
|
+
const { imageData, mimeType } = await loadImage(source, options.fetchTimeout);
|
|
160074
|
+
const prompt = createPrompt(options);
|
|
160075
|
+
logger2.debug(`Generated prompt for analysis: ${prompt.substring(0, 100)}...`);
|
|
160076
|
+
logger2.debug(`Image data size: ${imageData.length} characters, MIME type: ${mimeType}`);
|
|
160077
|
+
const response = await model.generateContent([
|
|
160078
|
+
{ text: prompt },
|
|
160079
|
+
{
|
|
160080
|
+
inlineData: {
|
|
160081
|
+
mimeType,
|
|
160082
|
+
data: imageData
|
|
160083
|
+
}
|
|
160078
160084
|
}
|
|
160085
|
+
]);
|
|
160086
|
+
const result = await response.response;
|
|
160087
|
+
const analysisText = result.text();
|
|
160088
|
+
logger2.debug(`Gemini response received. Text length: ${analysisText ? analysisText.length : 0}`);
|
|
160089
|
+
if (!analysisText || analysisText.trim().length === 0) {
|
|
160090
|
+
const errorMsg = `Gemini returned empty response on attempt ${attempt}/${maxRetries}`;
|
|
160091
|
+
logger2.warn(errorMsg);
|
|
160092
|
+
if (attempt === maxRetries) {
|
|
160093
|
+
logger2.info("Using fallback analysis due to empty Gemini response");
|
|
160094
|
+
const fallbackAnalysis = "Image was processed but detailed analysis is unavailable. This may be due to API limitations or content restrictions.";
|
|
160095
|
+
return {
|
|
160096
|
+
description: "Image analysis completed with limited results",
|
|
160097
|
+
analysis: fallbackAnalysis,
|
|
160098
|
+
elements: [],
|
|
160099
|
+
insights: ["Gemini API returned empty response", "Consider retrying the analysis"],
|
|
160100
|
+
recommendations: ["Check image format and content", "Verify API key and quotas"],
|
|
160101
|
+
metadata: {
|
|
160102
|
+
processing_time_ms: Date.now() - startTime,
|
|
160103
|
+
model_used: model.model,
|
|
160104
|
+
attempts_made: maxRetries,
|
|
160105
|
+
status: "partial_success"
|
|
160106
|
+
}
|
|
160107
|
+
};
|
|
160108
|
+
}
|
|
160109
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
160110
|
+
logger2.debug(`Retrying in ${delay}ms...`);
|
|
160111
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
160112
|
+
continue;
|
|
160079
160113
|
}
|
|
160080
|
-
|
|
160081
|
-
|
|
160082
|
-
|
|
160083
|
-
|
|
160084
|
-
|
|
160085
|
-
|
|
160086
|
-
|
|
160087
|
-
|
|
160088
|
-
|
|
160089
|
-
|
|
160090
|
-
|
|
160091
|
-
|
|
160092
|
-
|
|
160093
|
-
|
|
160094
|
-
|
|
160095
|
-
|
|
160096
|
-
|
|
160114
|
+
const parsed = parseAnalysisResponse(analysisText);
|
|
160115
|
+
const processingTime = Date.now() - startTime;
|
|
160116
|
+
logger2.info(`Image analysis successful on attempt ${attempt}. Processing time: ${processingTime}ms`);
|
|
160117
|
+
return {
|
|
160118
|
+
description: parsed.description || "Image analysis completed",
|
|
160119
|
+
analysis: parsed.analysis || analysisText,
|
|
160120
|
+
elements: parsed.elements || [],
|
|
160121
|
+
insights: parsed.insights || [],
|
|
160122
|
+
recommendations: parsed.recommendations || [],
|
|
160123
|
+
metadata: {
|
|
160124
|
+
processing_time_ms: processingTime,
|
|
160125
|
+
model_used: model.model,
|
|
160126
|
+
attempts_made: attempt,
|
|
160127
|
+
status: "success"
|
|
160128
|
+
}
|
|
160129
|
+
};
|
|
160130
|
+
} catch (error) {
|
|
160131
|
+
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
160132
|
+
logger2.warn(`Image processing attempt ${attempt} failed:`, lastError.message);
|
|
160133
|
+
if (attempt < maxRetries && isRetryableError(lastError)) {
|
|
160134
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
160135
|
+
logger2.debug(`Retrying in ${delay}ms...`);
|
|
160136
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
160137
|
+
continue;
|
|
160138
|
+
} else if (attempt === maxRetries) {
|
|
160139
|
+
break;
|
|
160097
160140
|
}
|
|
160098
|
-
}
|
|
160099
|
-
} catch (error) {
|
|
160100
|
-
logger2.error("Image processing error:", error);
|
|
160101
|
-
throw new ProcessingError(`Failed to process image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
160141
|
+
}
|
|
160102
160142
|
}
|
|
160143
|
+
logger2.error("Image processing failed after all retries:", lastError);
|
|
160144
|
+
throw new ProcessingError(`Failed to process image after ${maxRetries} attempts: ${lastError?.message || "Unknown error"}`);
|
|
160145
|
+
}
|
|
160146
|
+
function isRetryableError(error) {
|
|
160147
|
+
const retryableMessages = [
|
|
160148
|
+
"timeout",
|
|
160149
|
+
"network",
|
|
160150
|
+
"rate limit",
|
|
160151
|
+
"temporary",
|
|
160152
|
+
"429",
|
|
160153
|
+
"500",
|
|
160154
|
+
"502",
|
|
160155
|
+
"503",
|
|
160156
|
+
"504"
|
|
160157
|
+
];
|
|
160158
|
+
const errorMessage = error.message.toLowerCase();
|
|
160159
|
+
return retryableMessages.some((msg) => errorMessage.includes(msg));
|
|
160103
160160
|
}
|
|
160104
160161
|
async function loadImage(source, fetchTimeout) {
|
|
160105
160162
|
if (source.match(/^\[Image #\d+\]$/)) {
|
|
@@ -164048,11 +164105,35 @@ async function registerVisionTools(server, geminiClient, config) {
|
|
|
164048
164105
|
return await handleAnalyze(geminiClient, args, config);
|
|
164049
164106
|
} catch (error) {
|
|
164050
164107
|
const mcpError = handleError(error);
|
|
164051
|
-
logger2.error(`Tool eyes_analyze error:`,
|
|
164108
|
+
logger2.error(`Tool eyes_analyze error:`, {
|
|
164109
|
+
message: mcpError.message,
|
|
164110
|
+
code: mcpError.code,
|
|
164111
|
+
args,
|
|
164112
|
+
timestamp: new Date().toISOString(),
|
|
164113
|
+
stackTrace: error instanceof Error ? error.stack : "No stack trace available"
|
|
164114
|
+
});
|
|
164115
|
+
let userMessage = mcpError.message;
|
|
164116
|
+
if (mcpError.message.includes("No analysis result from Gemini")) {
|
|
164117
|
+
userMessage = `The image analysis service returned an empty response. This can happen due to:
|
|
164118
|
+
` + `• API rate limits or quota exceeded
|
|
164119
|
+
` + `• Image content restrictions
|
|
164120
|
+
` + `• Temporary service issues
|
|
164121
|
+
` + `• Network connectivity problems
|
|
164122
|
+
|
|
164123
|
+
` + "Please try again in a few moments, or check if your image meets the requirements.";
|
|
164124
|
+
} else if (mcpError.message.includes("Failed to process image after")) {
|
|
164125
|
+
userMessage = `Image processing failed after multiple attempts. This could be due to:
|
|
164126
|
+
` + `• Network connectivity issues
|
|
164127
|
+
` + `• API service unavailability
|
|
164128
|
+
` + `• Image format or size issues
|
|
164129
|
+
` + `• Rate limiting
|
|
164130
|
+
|
|
164131
|
+
` + "Please check your internet connection and try again.";
|
|
164132
|
+
}
|
|
164052
164133
|
return {
|
|
164053
164134
|
content: [{
|
|
164054
164135
|
type: "text",
|
|
164055
|
-
text: `Error: ${
|
|
164136
|
+
text: `Error: ${userMessage}`
|
|
164056
164137
|
}],
|
|
164057
164138
|
isError: true
|
|
164058
164139
|
};
|
|
@@ -164216,10 +164297,13 @@ async function registerDocumentTools(server, geminiClient, config) {
|
|
|
164216
164297
|
async function handleAnalyze(geminiClient, args, config) {
|
|
164217
164298
|
const input = EyesInputSchema.parse(args);
|
|
164218
164299
|
const { source, type, detail_level } = input;
|
|
164219
|
-
|
|
164300
|
+
const customPrompt = "prompt" in input ? input.prompt : undefined;
|
|
164301
|
+
logger2.info(`Analyzing ${type} with detail level: ${detail_level}, source: ${source.substring(0, 50)}...`);
|
|
164220
164302
|
const model = geminiClient.getModel(detail_level || "detailed");
|
|
164221
164303
|
const options = {
|
|
164222
|
-
|
|
164304
|
+
analysis_type: "general",
|
|
164305
|
+
detail_level: detail_level || "detailed",
|
|
164306
|
+
specific_focus: customPrompt,
|
|
164223
164307
|
fetchTimeout: config.server.fetchTimeout
|
|
164224
164308
|
};
|
|
164225
164309
|
let result;
|
|
@@ -164236,6 +164320,7 @@ async function handleAnalyze(geminiClient, args, config) {
|
|
|
164236
164320
|
default:
|
|
164237
164321
|
throw new Error(`Unsupported media type: ${type}`);
|
|
164238
164322
|
}
|
|
164323
|
+
logger2.info(`Analysis completed for ${type}. Processing time: ${result.metadata.processing_time_ms}ms`);
|
|
164239
164324
|
return {
|
|
164240
164325
|
content: [
|
|
164241
164326
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goonnguyen/human-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Human MCP: Bringing Human Capabilities to Coding Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"human-mcp": "bin/human-mcp.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"dev": "bun run --watch src/index.ts",
|
|
13
|
-
"build": "bun build src/index.ts --target=node --outdir=dist",
|
|
14
|
-
"start": "bun run dist/index.js",
|
|
15
|
-
"test": "bun test tests/unit/ && bun test tests/integration/sse-transport.test.ts && bun test tests/integration/server.test.ts && bun test tests/integration/http-transport-files.test.ts",
|
|
16
|
-
"test:parallel": "bun test",
|
|
17
|
-
"typecheck": "tsc --noEmit",
|
|
18
|
-
"inspector": "mcp-inspector stdio -- bun run src/index.ts"
|
|
12
|
+
"dev": "bun run --watch src/index.ts 2>&1 | tee -a ./logs.txt",
|
|
13
|
+
"build": "bun build src/index.ts --target=node --outdir=dist 2>&1 | tee -a ./logs.txt",
|
|
14
|
+
"start": "bun run dist/index.js 2>&1 | tee -a ./logs.txt",
|
|
15
|
+
"test": "bun test tests/unit/ && bun test tests/integration/sse-transport.test.ts && bun test tests/integration/server.test.ts && bun test tests/integration/http-transport-files.test.ts 2>&1 | tee -a ./logs.txt",
|
|
16
|
+
"test:parallel": "bun test 2>&1 | tee -a ./logs.txt",
|
|
17
|
+
"typecheck": "tsc --noEmit 2>&1 | tee -a ./logs.txt",
|
|
18
|
+
"inspector": "mcp-inspector stdio -- bun run src/index.ts 2>&1 | tee -a ./logs.txt"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@aws-sdk/client-s3": "^3.888.0",
|