@mantisware/peepit-mcp
Version:
Give your AI agents super-vision: blazing-fast macOS screenshots, smart window targeting, and local or cloud AI image analysis—all via a friendly Node.js MCP server.
164 lines • 6.39 kB
JavaScript
import { z } from "zod";
import path from "path";
import { readImageAsBase64 } from "../utils/peepit-cli.js";
import { parseAIProviders, analyzeImageWithProvider, determineProviderAndModel, } from "../utils/ai-providers.js";
export const analyzeToolSchema = z.object({
image_path: z
.string()
.optional()
.describe("Required. Absolute path to image file (.png, .jpg, .webp) to be analyzed."),
question: z
.string()
.describe("Required. Question for the AI about the image."),
provider_config: z
.object({
type: z
.enum(["auto", "ollama", "openai"])
.default("auto")
.describe("AI provider, default: auto. 'auto' uses server's PEEPIT_AI_PROVIDERS environment preference. Specific provider must be enabled in server's PEEPIT_AI_PROVIDERS."),
model: z
.string()
.optional()
.describe("Optional. Model name. If omitted, uses model from server's PEEPIT_AI_PROVIDERS for chosen provider, or an internal default for that provider."),
})
.optional()
.describe("Optional. Explicit provider/model. Validated against server's PEEPIT_AI_PROVIDERS."),
})
.passthrough() // Allow unknown properties (for the hidden `path` parameter)
.refine((data) => {
const typedData = data;
return typedData.image_path || typedData.path;
}, {
message: "image_path is required",
path: ["image_path"],
});
export async function analyzeToolHandler(input, context) {
const { logger } = context;
try {
// Determine the effective image path (prioritize image_path, fallback to path)
const effectiveImagePath = input.image_path || input.path || "";
logger.debug({ input: { ...input, effectiveImagePath: effectiveImagePath.split("/").pop() } }, "Processing peepit.analyze tool call");
// Validate image file extension
const ext = path.extname(effectiveImagePath).toLowerCase();
if (![".png", ".jpg", ".jpeg", ".webp"].includes(ext)) {
return {
content: [
{
type: "text",
text: `Unsupported image format: ${ext}. Supported formats: .png, .jpg, .jpeg, .webp`,
},
],
isError: true,
};
}
// Check AI providers configuration
const aiProvidersEnv = process.env.PEEPIT_AI_PROVIDERS;
if (!aiProvidersEnv || !aiProvidersEnv.trim()) {
logger.error("PEEPIT_AI_PROVIDERS environment variable not configured");
return {
content: [
{
type: "text",
text: "AI analysis not configured on this server. Set the PEEPIT_AI_PROVIDERS environment variable.",
},
],
isError: true,
};
}
// Parse configured providers
const configuredProviders = parseAIProviders(aiProvidersEnv);
if (configuredProviders.length === 0) {
return {
content: [
{
type: "text",
text: "No valid AI providers found in PEEPIT_AI_PROVIDERS configuration.",
},
],
isError: true,
};
}
// Determine provider and model
const { provider, model } = await determineProviderAndModel(input.provider_config, configuredProviders, logger);
if (!provider) {
return {
content: [
{
type: "text",
text: "No configured AI providers are currently operational.",
},
],
isError: true,
};
}
// Read image as base64
let imageBase64;
try {
imageBase64 = await readImageAsBase64(effectiveImagePath);
}
catch (error) {
logger.error({ error, path: effectiveImagePath }, "Failed to read image file");
return {
content: [
{
type: "text",
text: `Failed to read image file: ${error instanceof Error ? error.message : "Unknown error"}`,
},
],
isError: true,
};
}
// Analyze image
let analysisResult;
const startTime = Date.now(); // Record start time
try {
analysisResult = await analyzeImageWithProvider({ provider, model }, effectiveImagePath, imageBase64, input.question, logger);
}
catch (error) {
logger.error({ error, provider, model }, "AI analysis failed");
return {
content: [
{
type: "text",
text: `AI analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`,
},
],
isError: true,
_meta: {
backend_error_code: "AI_PROVIDER_ERROR",
},
};
}
const endTime = Date.now(); // Record end time
const durationMs = endTime - startTime;
const durationSeconds = (durationMs / 1000).toFixed(2);
const analysisTimeMessage = `👻 PeepIt: Analyzed image with ${provider}/${model} in ${durationSeconds}s.`;
return {
content: [
{
type: "text",
text: analysisResult,
},
{
type: "text",
text: analysisTimeMessage, // Add the timing message
},
],
analysis_text: analysisResult,
model_used: `${provider}/${model}`,
};
}
catch (error) {
logger.error({ error }, "Unexpected error in analyze tool handler");
return {
content: [
{
type: "text",
text: `Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
},
],
isError: true,
};
}
}
//# sourceMappingURL=analyze.js.map