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