This commit is contained in:
2026-07-10 18:46:10 +08:00
parent a3b48f56c9
commit ebb0ae9163
2 changed files with 177 additions and 0 deletions

10
mcp-server/package.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "vision-tool-mcp",
"version": "1.0.0",
"description": "MCP server for vision-tool — image analysis via Agnes AI",
"type": "module",
"main": "server.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
}
}

167
mcp-server/server.js Normal file
View File

@@ -0,0 +1,167 @@
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);