update
This commit is contained in:
10
mcp-server/package.json
Normal file
10
mcp-server/package.json
Normal 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
167
mcp-server/server.js
Normal 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);
|
||||||
Reference in New Issue
Block a user