image-tools-mcp
by kshern
Verified
- src
- tools
import { z } from "zod";
import tinify from "tinify";
import fs from "fs";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import path from "path";
import os from "os";
// 设置API密钥
const setTinifyApiKey = () => {
// 从环境变量获取API密钥
const apiKey = process.env.TINIFY_API_KEY;
if (!apiKey) {
throw new Error("TINIFY_API_KEY environment variable is not set");
}
tinify.key = apiKey;
};
// 从URL压缩图片的工具函数
export const registerCompressImageFromUrlTool = (server: McpServer) => {
server.tool(
"compress_image_from_url",
"Compress a single image from URL using TinyPNG API (only supports image files, not folders)",
{
options: z
.object({
imageUrl: z.string().describe("URL of the image to compress (must be a direct link to an image file)"),
outputFormat: z.enum(["webp", "jpeg", "jpg", "png"]).optional().describe("Output format (webp, jpeg/jpg, png)"),
})
.describe("Options for compressing image from URL"),
},
async ({ options = {} }) => {
try {
// 设置API密钥
setTinifyApiKey();
const { imageUrl, outputFormat } = options as {
imageUrl: string;
outputFormat?: "webp" | "jpeg" | "jpg" | "png";
};
// 从URL获取图片并压缩
let source = tinify.fromUrl(imageUrl);
// 如果指定了输出格式,则转换格式
if (outputFormat) {
// 使用类型断言来避免类型错误
const convertOptions = { type: outputFormat === "jpg" ? "jpeg" : outputFormat };
source = source.convert(convertOptions as any);
}
// 获取压缩后的图片数据
const resultData = await new Promise<Buffer>((resolve, reject) => {
source.toBuffer(function(err: any, data: any) {
if (err) reject(err);
else if (data) resolve(Buffer.from(data));
else reject(new Error('No data returned from tinify'));
});
});
// 计算压缩率
const originalSize = (await fetch(imageUrl).then(res => res.arrayBuffer())).byteLength;
const compressedSize = resultData.length;
const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
// 创建临时文件保存压缩后的图片
const tempDir = os.tmpdir();
const originalFilename = new URL(imageUrl).pathname.split("/").pop() || "image";
const extension = outputFormat || path.extname(originalFilename).slice(1) || "jpg";
const tempFilePath = path.join(tempDir, `compressed_${Date.now()}.${extension}`);
fs.writeFileSync(tempFilePath, resultData);
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
originalSize,
compressedSize,
compressionRatio: `${compressionRatio}%`,
tempFilePath,
format: extension,
}, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to compress image: ${(error as Error).message}`);
}
}
);
};
// 从本地文件压缩图片的工具函数
export const registerCompressLocalImageTool = (server: McpServer) => {
server.tool(
"compress_local_image",
"Compress a single local image file using TinyPNG API (only supports image files, not folders)",
{
options: z
.object({
imagePath: z.string().describe("Absolute path to the local image file (must be a file, not a directory)"),
outputPath: z.string().optional().describe("Absolute path for the compressed output image"),
outputFormat: z.enum(["image/webp", "image/jpeg", "image/jpg", "image/png"]).optional().describe("Output format (webp, jpeg/jpg, png)"),
})
.describe("Options for compressing local image"),
},
async ({ options = {} }) => {
try {
// 设置API密钥
setTinifyApiKey();
const { imagePath, outputPath, outputFormat } = options as {
imagePath: string;
outputPath?: string;
outputFormat?: "image/webp" | "image/jpeg" | "image/jpg" | "image/png";
};
// 检查文件是否存在
if (!fs.existsSync(imagePath)) {
throw new Error(`File does not exist: ${imagePath}`);
}
// 获取原始文件大小
const originalSize = fs.statSync(imagePath).size;
// 从本地文件压缩图片
let source = tinify.fromFile(imagePath);
// 如果指定了输出格式,则转换格式
if (outputFormat) {
// 使用类型断言来避免类型错误
const convertOptions = { type: outputFormat === "image/jpg" ? "image/jpeg" : outputFormat };
source = source.convert(convertOptions as any);
}
// 确定输出路径
let finalOutputPath = outputPath;
if (!finalOutputPath) {
const dir = path.dirname(imagePath);
const ext = outputFormat || path.extname(imagePath).slice(1) || "image/jpg";
const basename = path.basename(imagePath, path.extname(imagePath));
finalOutputPath = path.join(dir, `${basename}_compressed.${ext}`);
}
// 保存压缩后的图片
await new Promise<void>((resolve, reject) => {
source.toFile(finalOutputPath, function(err: any) {
if (err) reject(err);
else resolve();
});
});
// 获取压缩后的文件大小
const compressedSize = fs.statSync(finalOutputPath).size;
const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
originalSize,
compressedSize,
compressionRatio: `${compressionRatio}%`,
outputPath: finalOutputPath,
format: outputFormat || path.extname(finalOutputPath).slice(1),
}, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to compress local image: ${(error as Error).message}`);
}
}
);
};