168 lines
4.8 KiB
JavaScript
168 lines
4.8 KiB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||
import {
|
||
CallToolRequestSchema,
|
||
ListToolsRequestSchema,
|
||
} from "@modelcontextprotocol/sdk/types.js";
|
||
import { readFileSync } from "node:fs";
|
||
import { basename } from "node:path";
|
||
|
||
// ============================================================================
|
||
// Configuration (mirrors config.go defaults)
|
||
// ============================================================================
|
||
|
||
const DEFAULT_API_KEY = "sk-68FL9xXRrJhxcY5TCGxoaFlQ94oCBioIJhyGfeDMCkCvA0SV";
|
||
const DEFAULT_MODEL = "agnes-2.0-flash";
|
||
const DEFAULT_BASE_URL = "https://apihub.agnes-ai.com/v1/chat/completions";
|
||
|
||
const API_KEY = process.env.AGNES_API_KEY || DEFAULT_API_KEY;
|
||
const MODEL = process.env.VISION_MODEL || DEFAULT_MODEL;
|
||
const BASE_URL = process.env.VISION_BASE_URL || DEFAULT_BASE_URL;
|
||
|
||
// ============================================================================
|
||
// MIME type detection
|
||
// ============================================================================
|
||
|
||
function detectMimeType(path) {
|
||
const ext = path.split(".").pop().toLowerCase();
|
||
const map = {
|
||
png: "image/png",
|
||
jpg: "image/jpeg",
|
||
jpeg: "image/jpeg",
|
||
gif: "image/gif",
|
||
webp: "image/webp",
|
||
bmp: "image/bmp",
|
||
tiff: "image/tiff",
|
||
tif: "image/tiff",
|
||
};
|
||
return map[ext] || "image/png";
|
||
}
|
||
|
||
// ============================================================================
|
||
// Vision API client
|
||
// ============================================================================
|
||
|
||
async function analyzeImage(imagePath, prompt) {
|
||
const imgData = readFileSync(imagePath);
|
||
const mimeType = detectMimeType(imagePath);
|
||
const base64Img = Buffer.from(imgData).toString("base64");
|
||
const dataURL = `data:${mimeType};base64,${base64Img}`;
|
||
|
||
if (!prompt) {
|
||
prompt = "请详细描述这张图片的内容。如果图片中有文字,请完整识别出来。请用中文回答。";
|
||
}
|
||
|
||
const body = {
|
||
model: MODEL,
|
||
messages: [
|
||
{
|
||
role: "user",
|
||
content: [
|
||
{ type: "image_url", image_url: { url: dataURL } },
|
||
{ type: "text", text: prompt },
|
||
],
|
||
},
|
||
],
|
||
chat_template_kwargs: { enable_thinking: true },
|
||
};
|
||
|
||
const resp = await fetch(BASE_URL, {
|
||
method: "POST",
|
||
headers: {
|
||
"Authorization": `Bearer ${API_KEY}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(body),
|
||
signal: AbortSignal.timeout(120_000),
|
||
});
|
||
|
||
if (!resp.ok) {
|
||
const text = await resp.text();
|
||
throw new Error(`API error (HTTP ${resp.status}): ${text}`);
|
||
}
|
||
|
||
const data = await resp.json();
|
||
if (data.error) {
|
||
throw new Error(`API error: [${data.error.code}] ${data.error.message}`);
|
||
}
|
||
|
||
const content = data.choices?.[0]?.message?.content;
|
||
if (!content) {
|
||
throw new Error("No content in API response");
|
||
}
|
||
|
||
return content;
|
||
}
|
||
|
||
// ============================================================================
|
||
// MCP Server
|
||
// ============================================================================
|
||
|
||
const server = new Server(
|
||
{
|
||
name: "vision-tool",
|
||
version: "1.0.0",
|
||
},
|
||
{
|
||
capabilities: {
|
||
tools: {},
|
||
},
|
||
}
|
||
);
|
||
|
||
// Register tool list
|
||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||
tools: [
|
||
{
|
||
name: "analyze_image",
|
||
description:
|
||
"分析图片内容。使用 Agnes AI 视觉模型识别图片中的对象、场景、文字等。" +
|
||
"支持 PNG、JPG、GIF、WebP、BMP、TIFF 格式。可以用自定义 prompt 指定分析重点。",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
image_path: {
|
||
type: "string",
|
||
description: "图片文件的绝对路径(例如 C:\\Users\\xxx\\photo.png)",
|
||
},
|
||
prompt: {
|
||
type: "string",
|
||
description:
|
||
"自定义分析提示词。默认为详细描述图片内容并识别所有文字。例如:" +
|
||
'"这张图里有什么文字?" 或 "描述图片中的UI界面布局"。',
|
||
},
|
||
},
|
||
required: ["image_path"],
|
||
},
|
||
},
|
||
],
|
||
}));
|
||
|
||
// Register tool handler
|
||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||
const { name, arguments: args } = request.params;
|
||
|
||
if (name !== "analyze_image") {
|
||
throw new Error(`Unknown tool: ${name}`);
|
||
}
|
||
|
||
const imagePath = args.image_path;
|
||
const prompt = args.prompt || "";
|
||
|
||
try {
|
||
const result = await analyzeImage(imagePath, prompt);
|
||
return {
|
||
content: [{ type: "text", text: result }],
|
||
};
|
||
} catch (err) {
|
||
return {
|
||
content: [{ type: "text", text: `Error: ${err.message}` }],
|
||
isError: true,
|
||
};
|
||
}
|
||
});
|
||
|
||
// Start
|
||
const transport = new StdioServerTransport();
|
||
await server.connect(transport);
|