UNPKG

web-analyzer-mcp

Version:

基于MCP协议的网页界面分析工具,使用OpenAI Vision AI智能解析页面布局和UI元素

666 lines (642 loc) 23.3 kB
#!/usr/bin/env node // src/index.ts import { Command } from "commander"; // src/server.ts import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; // src/utils/openai.ts import OpenAI from "openai"; import { readFileSync } from "fs"; var apiKey = process.env.OPENAI_API_KEY; var baseURL = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1"; if (!apiKey) { throw new Error("OPENAI_API_KEY environment variable is required"); } var openai = new OpenAI({ apiKey, baseURL }); function imageToBase64(imagePath) { try { const imageBuffer = readFileSync(imagePath); return imageBuffer.toString("base64"); } catch (error) { throw new Error(`Failed to read image file: ${error}`); } } function getImageMimeType(imagePath) { const extension = imagePath.toLowerCase().split(".").pop(); switch (extension) { case "png": return "image/png"; case "jpg": case "jpeg": return "image/jpeg"; default: throw new Error(`Unsupported image format: ${extension}`); } } async function analyzeWebpageScreenshot(imagePath, model = "gpt-4o", maxTokens = 2e3, temperature = 0.1) { try { const base64Image = imageToBase64(imagePath); const mimeType = getImageMimeType(imagePath); const response = await openai.chat.completions.create({ model, max_tokens: maxTokens, temperature, messages: [ { role: "system", content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u7F51\u9875UI\u5206\u6790\u4E13\u5BB6\u3002\u8BF7\u5206\u6790\u63D0\u4F9B\u7684\u7F51\u9875\u622A\u56FE\uFF0C\u7528markdown\u683C\u5F0F\u63CF\u8FF0\u9875\u9762\u5E03\u5C40\u548C\u5173\u952E\u8981\u7D20\u3002 \u8BF7\u53C2\u7167\u4EE5\u4E0B\u683C\u5F0F\u8FD4\u56DE\u5206\u6790\u7ED3\u679C\uFF1A # \u9875\u9762\u5E03\u5C40\u5206\u6790 ## 1.\u6574\u4F53\u7ED3\u6784 - **\u9875\u9762\u6807\u9898**: [\u6839\u636E\u622A\u56FE\u5185\u5BB9\u5224\u65AD\uFF0C\u5982\uFF1A\u201C\u767B\u5F55 - \u6211\u7684\u5E94\u7528\u201D] - **\u9875\u9762\u7C7B\u578B**: [\u5982\uFF1A\u767B\u5F55\u9875\u3001\u4EEA\u8868\u76D8\u3001\u4EA7\u54C1\u5217\u8868\u9875\u7B49] - **\u5E03\u5C40\u98CE\u683C**: [\u5982\uFF1A\u54CD\u5E94\u5F0F\u3001\u56FA\u5B9A\u5BBD\u5EA6\u3001\u5361\u7247\u5F0F\u5E03\u5C40\u7B49] - **\u4E3B\u8272\u8C03**: [\u63CF\u8FF0\u4E3B\u8981\u989C\u8272\u65B9\u6848] ## 2.\u4E3B\u8981\u533A\u57DF <!--\u4EE5\u4E0B\u662F\u63CF\u8FF0\u6837\u4F8B\uFF0C\u5982\u679C\u67D0\u4E2A\u533A\u57DF\u4E0D\u5B58\u5728\u5219\u65E0\u9700\u63CF\u8FF0--> <#if \u5BFC\u822A\u680F\u5B58\u5728> ### 2.1 \u9876\u90E8\u5BFC\u822A\u680F (Header) - **\u4F4D\u7F6E**: \u9875\u9762\u9876\u90E8 - **\u529F\u80FD**: [\u5982\uFF1A\u54C1\u724Clogo\u3001\u4E3B\u5BFC\u822A\u83DC\u5355\u3001\u7528\u6237\u64CD\u4F5C\u7B49] - **\u5173\u952E\u5143\u7D20**: - Logo/\u54C1\u724C\u6807\u8BC6 - \u5BFC\u822A\u83DC\u5355\u9879 - \u641C\u7D22\u6846 - \u7528\u6237\u5934\u50CF/\u767B\u5F55\u6309\u94AE </#if> <#if \u4E3B\u4F53\u5185\u5BB9\u533A\u5B58\u5728> ### 2.2 \u4E3B\u4F53\u5185\u5BB9\u533A (Main Content) - **\u5E03\u5C40**: [\u5982\uFF1A\u5355\u5217\u3001\u53CC\u5217\u3001\u7F51\u683C\u5E03\u5C40\u7B49] - **\u4E3B\u8981\u5185\u5BB9**: - \u6807\u9898\u548C\u526F\u6807\u9898 - \u4E3B\u8981\u64CD\u4F5C\u6309\u94AE - \u5185\u5BB9\u5361\u7247/\u5217\u8868 - \u8868\u5355\u5143\u7D20 </#if> <#if \u4FA7\u8FB9\u680F\u5B58\u5728> ### 2.3 \u4FA7\u8FB9\u680F (Sidebar) - **\u4F4D\u7F6E**: \u5DE6\u4FA7/\u53F3\u4FA7 - **\u529F\u80FD**: [\u5982\uFF1A\u4E8C\u7EA7\u5BFC\u822A\u3001\u8FC7\u6EE4\u5668\u3001\u5DE5\u5177\u9762\u677F\u7B49] </#if> <#if \u9875\u811A\u5B58\u5728> ### 2.4 \u9875\u811A (Footer) - **\u5185\u5BB9**: [\u5982\uFF1A\u7248\u6743\u4FE1\u606F\u3001\u94FE\u63A5\u3001\u793E\u4EA4\u5A92\u4F53\u56FE\u6807\u7B49] </#if> ## 3. \u5173\u952EUI\u5143\u7D20 ### 3.1 \u4EA4\u4E92\u5143\u7D20 - **\u6309\u94AE**: [\u63CF\u8FF0\u4E3B\u8981\u6309\u94AE\u7684\u6837\u5F0F\u3001\u4F4D\u7F6E\u548C\u529F\u80FD] - **\u8868\u5355\u63A7\u4EF6**: [\u8F93\u5165\u6846\u3001\u4E0B\u62C9\u83DC\u5355\u3001\u590D\u9009\u6846\u7B49] - **\u5BFC\u822A\u5143\u7D20**: [\u9762\u5305\u5C51\u3001\u5206\u9875\u3001\u6807\u7B7E\u9875\u7B49] ### 3.2 \u5185\u5BB9\u5143\u7D20 - **\u6587\u672C\u5185\u5BB9**: [\u6807\u9898\u5C42\u7EA7\u3001\u6BB5\u843D\u7ED3\u6784\u3001\u91CD\u8981\u6587\u6848] - **\u56FE\u7247/\u56FE\u6807**: [\u4E3B\u8981\u56FE\u7247\u5185\u5BB9\u3001\u56FE\u6807\u4F7F\u7528] - **\u6570\u636E\u5C55\u793A**: [\u8868\u683C\u3001\u56FE\u8868\u3001\u7EDF\u8BA1\u6570\u636E\u7B49] \u8BF7\u786E\u4FDD\u63CF\u8FF0\u51C6\u786E\u3001\u7B80\u6D01\uFF0C\u91CD\u70B9\u5173\u6CE8\u529F\u80FD\u6027\u5143\u7D20\u548C\u5E03\u5C40\u7279\u5F81\u3002` }, { role: "user", content: [ { type: "image_url", image_url: { url: `data:${mimeType};base64,${base64Image}`, detail: "high" } }, { type: "text", text: "\u8BF7\u5206\u6790\u8FD9\u4E2A\u7F51\u9875\u622A\u56FE\uFF0C\u7528markdown\u683C\u5F0F\u63CF\u8FF0\u9875\u9762\u5E03\u5C40\u548C\u5173\u952EUI\u5143\u7D20\u3002" } ] } ] }); const content = response.choices[0]?.message?.content; if (!content) { throw new Error("No response from OpenAI API"); } return { layout: {}, elements: [], analysis: content, metadata: { imageSize: await getImageSize(imagePath), analyzedAt: (/* @__PURE__ */ new Date()).toISOString(), model, confidence: 0.9 } }; } catch (error) { if (error instanceof Error) { throw new Error(`OpenAI API error: ${error.message}`); } throw new Error("Unknown error occurred during analysis"); } } async function getImageSize(imagePath) { return { x: 0, y: 0, width: 1920, height: 1080 }; } // src/utils/image.ts import { readFileSync as readFileSync2, existsSync, statSync } from "fs"; import { resolve, extname } from "path"; var SUPPORTED_IMAGE_FORMATS = [".png", ".jpg", ".jpeg"]; function validateImageFile(imagePath) { const resolvedPath = resolve(imagePath); if (!existsSync(resolvedPath)) { throw new Error(`Image file not found: ${resolvedPath}`); } const ext = extname(resolvedPath).toLowerCase(); if (!SUPPORTED_IMAGE_FORMATS.includes(ext)) { throw new Error( `Unsupported image format: ${ext}. Supported formats: ${SUPPORTED_IMAGE_FORMATS.join(", ")}` ); } try { const stats = statSync(resolvedPath); const maxSize = 10 * 1024 * 1024; if (stats.size > maxSize) { throw new Error(`Image file too large: ${Math.round(stats.size / 1024 / 1024)}MB (max: 10MB)`); } } catch (error) { throw new Error(`Failed to read image file stats: ${error}`); } } function getImageInfo(imagePath) { const resolvedPath = resolve(imagePath); validateImageFile(resolvedPath); const ext = extname(resolvedPath).toLowerCase(); const stats = statSync(resolvedPath); return { path: resolvedPath, format: ext.slice(1), // 去掉点号 size: stats.size }; } // src/types/schema.ts import { z } from "zod"; var BoundsSchema = z.object({ x: z.number().describe("X\u5750\u6807"), y: z.number().describe("Y\u5750\u6807"), width: z.number().describe("\u5BBD\u5EA6"), height: z.number().describe("\u9AD8\u5EA6") }); var ElementTypeSchema = z.enum([ "button", "input", "text", "image", "link", "navigation", "form", "container", "icon", "video", "table", "list", "heading", "paragraph" ]); var ElementAttributesSchema = z.object({ color: z.string().optional().describe("\u989C\u8272"), backgroundColor: z.string().optional().describe("\u80CC\u666F\u8272"), fontSize: z.string().optional().describe("\u5B57\u4F53\u5927\u5C0F"), fontWeight: z.string().optional().describe("\u5B57\u91CD"), style: z.string().optional().describe("\u6837\u5F0F\u7C7B\u578B"), href: z.string().optional().describe("\u94FE\u63A5\u5730\u5740"), placeholder: z.string().optional().describe("\u8F93\u5165\u6846\u5360\u4F4D\u7B26"), alt: z.string().optional().describe("\u56FE\u7247alt\u6587\u672C"), src: z.string().optional().describe("\u56FE\u7247\u6E90\u5730\u5740") }).passthrough(); var PageElementSchema = z.object({ type: ElementTypeSchema, text: z.string().optional().describe("\u5143\u7D20\u6587\u672C\u5185\u5BB9"), content: z.string().optional().describe("\u5143\u7D20\u5185\u5BB9"), bounds: BoundsSchema, attributes: ElementAttributesSchema.optional(), confidence: z.number().min(0).max(1).optional().describe("\u8BC6\u522B\u7F6E\u4FE1\u5EA6") }); var LayoutTypeSchema = z.enum([ "header", "navigation", "sidebar", "main", "content", "footer", "modal", "overlay" ]); var LayoutAreaSchema = z.object({ bounds: BoundsSchema, type: LayoutTypeSchema, elements: z.array(PageElementSchema).describe("\u533A\u57DF\u5185\u7684\u5143\u7D20"), name: z.string().optional().describe("\u533A\u57DF\u540D\u79F0") }); var AnalysisResultSchema = z.object({ layout: z.record(z.string(), LayoutAreaSchema).describe("\u9875\u9762\u5E03\u5C40\u533A\u57DF").optional(), elements: z.array(PageElementSchema).describe("\u6240\u6709\u9875\u9762\u5143\u7D20").optional(), analysis: z.string().optional().describe("markdown\u683C\u5F0F\u7684\u5206\u6790\u7ED3\u679C"), metadata: z.object({ imageSize: BoundsSchema.describe("\u539F\u59CB\u56FE\u7247\u5C3A\u5BF8"), analyzedAt: z.string().datetime().describe("\u5206\u6790\u65F6\u95F4"), model: z.string().describe("\u4F7F\u7528\u7684\u6A21\u578B"), confidence: z.number().min(0).max(1).describe("\u6574\u4F53\u7F6E\u4FE1\u5EA6") }) }); var AnalyzeImageInputSchema = z.object({ imagePath: z.string().describe("\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84"), model: z.string().optional().default("gpt-4-vision-preview").describe("OpenAI\u6A21\u578B"), maxTokens: z.number().optional().default(2e3).describe("\u6700\u5927token\u6570"), temperature: z.number().optional().default(0.1).describe("\u6E29\u5EA6\u53C2\u6570") }); var ExtractDocxImagesInputSchema = z.object({ docxPath: z.string().describe("Word\u6587\u6863\u8DEF\u5F84(.docx)"), outputDir: z.string().optional().default("./extracted-images").describe("\u56FE\u7247\u8F93\u51FA\u76EE\u5F55"), prefix: z.string().optional().default("docx-image").describe("\u8F93\u51FA\u6587\u4EF6\u540D\u524D\u7F00") }); var ExtractDocxImagesResultSchema = z.object({ extractedImages: z.array(z.object({ originalName: z.string(), savedPath: z.string(), size: z.number(), mimeType: z.string() })), totalCount: z.number(), outputDirectory: z.string() }); // src/tools/analyze.ts var analyzeImageTool = { name: "analyze_webpage_screenshot", description: "\u5206\u6790\u7F51\u9875\u622A\u56FE\uFF0C\u8BC6\u522B\u9875\u9762\u5E03\u5C40\u548CUI\u5143\u7D20", inputSchema: { type: "object", properties: { imagePath: { type: "string", description: "\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84" }, model: { type: "string", description: "OpenAI\u6A21\u578B", default: "gpt-4o" }, maxTokens: { type: "number", description: "\u6700\u5927token\u6570", default: 2e3 }, temperature: { type: "number", description: "temperature", default: 0.1 } }, required: ["imagePath"] }, /** * 执行图片分析 */ handler: async (input) => { try { const validatedInput = AnalyzeImageInputSchema.parse(input); validateImageFile(validatedInput.imagePath); const imageInfo = getImageInfo(validatedInput.imagePath); const result = await analyzeWebpageScreenshot( validatedInput.imagePath, validatedInput.model, validatedInput.maxTokens, validatedInput.temperature ); const validatedResult = AnalysisResultSchema.parse(result); if (validatedResult.analysis) { return { content: [ { type: "text", text: validatedResult.analysis } ] }; } return { content: [ { type: "text", text: JSON.stringify(validatedResult, null, 2) } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: `Error analyzing image: ${errorMessage}` } ], isError: true }; } } }; var tools = [analyzeImageTool]; // src/tools/extract-docx-images.ts import { z as z2 } from "zod"; import * as fs2 from "fs"; import * as path2 from "path"; // src/utils/docx-parser.ts import * as fs from "fs"; import * as path from "path"; import mammoth from "mammoth"; async function extractDocxImages(docxPath, outputDir, prefix = "docx-image") { if (!fs.existsSync(docxPath)) { throw new Error(`DOCX\u6587\u4EF6\u4E0D\u5B58\u5728: ${docxPath}`); } if (!docxPath.toLowerCase().endsWith(".docx")) { throw new Error("\u4EC5\u652F\u6301DOCX\u683C\u5F0F\u6587\u4EF6"); } if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const extractedImages = []; let imageIndex = 0; const imagePromises = []; const convertImage = mammoth.images.imgElement(async (image) => { const promise = (async () => { try { const extension = getImageExtension(image.contentType); const originalName = `image${++imageIndex}${extension}`; const savedName = `${prefix}-${imageIndex}${extension}`; const savedPath = path.join(outputDir, savedName); const buffer = Buffer.from(await image.read("base64"), "base64"); fs.writeFileSync(savedPath, buffer); extractedImages.push({ originalName, savedPath: path.resolve(savedPath), size: buffer.length, mimeType: image.contentType }); } catch (error) { console.error("\u63D0\u53D6\u56FE\u7247\u5931\u8D25:", error); } })(); imagePromises.push(promise); return { src: "" }; }); await mammoth.convertToHtml( { path: docxPath }, { convertImage } ); await Promise.all(imagePromises); return { extractedImages, totalCount: extractedImages.length, outputDirectory: path.resolve(outputDir) }; } function getImageExtension(mimeType) { const mimeMap = { "image/jpeg": ".jpg", "image/jpg": ".jpg", "image/png": ".png", "image/gif": ".gif", "image/bmp": ".bmp", "image/webp": ".webp", "image/svg+xml": ".svg" }; return mimeMap[mimeType.toLowerCase()] || ".png"; } // src/tools/extract-docx-images.ts var extractDocxImagesTool = { name: "extract_docx_images", description: "\u4ECEWord\u6587\u6863(.docx)\u4E2D\u63D0\u53D6\u6240\u6709\u5D4C\u5165\u7684\u56FE\u7247", inputSchema: { type: "object", properties: { docxPath: { type: "string", description: "Word\u6587\u6863\u8DEF\u5F84(.docx)" }, outputDir: { type: "string", description: "\u56FE\u7247\u8F93\u51FA\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002\u5982\u679C\u672A\u6307\u5B9A\uFF0C\u9ED8\u8BA4\u63D0\u53D6\u5230DOCX\u6587\u4EF6\u540C\u7EA7\u7684extracted-images/\u76EE\u5F55\u4E0B", default: "./extracted-images" }, prefix: { type: "string", description: "\u8F93\u51FA\u6587\u4EF6\u540D\u524D\u7F00\uFF0C\u9ED8\u8BA4\u4E3Adocx-image", default: "docx-image" } }, required: ["docxPath"] }, handler: async (input) => { try { const validatedInput = ExtractDocxImagesInputSchema.parse(input); if (!fs2.existsSync(validatedInput.docxPath)) { throw new Error(`DOCX\u6587\u4EF6\u4E0D\u5B58\u5728: ${validatedInput.docxPath}`); } let outputDir = validatedInput.outputDir; if (!outputDir || outputDir === "./extracted-images") { const docxDir = path2.dirname(validatedInput.docxPath) + path2.sep; outputDir = docxDir + "extracted-images"; } else { if (!path2.isAbsolute(outputDir)) { outputDir = path2.resolve(process.cwd(), outputDir); } } const result = await extractDocxImages( validatedInput.docxPath, outputDir, validatedInput.prefix ); const validatedResult = ExtractDocxImagesResultSchema.parse(result); return { content: [ { type: "text", text: JSON.stringify(validatedResult, null, 2) } ] }; } catch (error) { if (error instanceof z2.ZodError) { throw new Error(`\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25: ${error.errors.map((e) => e.message).join(", ")}`); } throw error; } } }; // src/server.ts var tools2 = [...tools, extractDocxImagesTool]; function createServer() { const server = new Server({ name: "webpage-screenshot-analyzer", version: "1.0.0" }); server.setRequestHandler(ListToolsRequestSchema, async () => { const toolList = { tools: tools2.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })) }; return toolList; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const tool = tools2.find((t) => t.name === request.params.name); if (!tool) { const error = `Unknown tool: ${request.params.name}`; throw new Error(error); } if (!request.params.arguments) { const error = "Tool arguments are required"; throw new Error(error); } try { const result = await tool.handler(request.params.arguments); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: `Tool execution failed: ${errorMessage}` } ], isError: true }; } }); return server; } async function startServer() { const server = createServer(); const transport = new StdioServerTransport(); console.log("Starting webpage screenshot analyzer MCP server..."); try { await server.connect(transport); } catch (error) { console.error("Failed to start MCP server", error); process.exit(1); } } // src/index.ts import * as fs3 from "fs"; var program = new Command(); program.name("web-analyzer").description("\u57FA\u4E8EMCP\u534F\u8BAE\u7684\u7F51\u9875\u754C\u9762\u5206\u6790\u5DE5\u5177\uFF0C\u4F7F\u7528AI\u667A\u80FD\u89E3\u6790\u9875\u9762\u5E03\u5C40\u548CUI\u5143\u7D20").version("1.0.0"); program.command("server").description("\u542F\u52A8MCP\u670D\u52A1\u5668").action(async () => { await startServer(); }); program.command("analyze").description("\u5206\u6790\u7F51\u9875\u622A\u56FE").requiredOption("-i, --image-path <path>", "\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84").option("-m, --model <model>", "OpenAI\u6A21\u578B", "gpt-4o").option("--max-tokens <number>", "\u6700\u5927token\u6570", "2000").option("--temperature <number>", "temperature", "0.1").action(async (options) => { try { const input = AnalyzeImageInputSchema.parse({ imagePath: options.imagePath, model: options.model, maxTokens: parseInt(options.maxTokens), temperature: parseFloat(options.temperature) }); validateImageFile(input.imagePath); console.log(`\u6B63\u5728\u5206\u6790\u56FE\u7247: ${input.imagePath}`); console.log(`\u4F7F\u7528\u6A21\u578B: ${input.model}`); const result = await analyzeWebpageScreenshot( input.imagePath, input.model, input.maxTokens, input.temperature ); if (result.analysis) { console.log("\n\u5206\u6790\u7ED3\u679C:"); console.log(result.analysis); } else { console.log("\n\u5206\u6790\u7ED3\u679C:"); console.log(JSON.stringify(result, null, 2)); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"; console.error("\u5206\u6790\u5931\u8D25:", errorMessage); process.exit(1); } }); program.command("extract-docx-images").description("\u4ECEWord\u6587\u6863\u4E2D\u63D0\u53D6\u6240\u6709\u5D4C\u5165\u7684\u56FE\u7247").requiredOption("-d, --docx-path <path>", "DOCX\u6587\u4EF6\u8DEF\u5F84").option("-o, --output-dir <dir>", "\u56FE\u7247\u8F93\u51FA\u76EE\u5F55", "./extracted-images").option("-p, --prefix <prefix>", "\u8F93\u51FA\u6587\u4EF6\u540D\u524D\u7F00", "docx-image").action(async (options) => { try { const input = ExtractDocxImagesInputSchema.parse({ docxPath: options.docxPath, outputDir: options.outputDir, prefix: options.prefix }); console.log("\u5F00\u59CB\u63D0\u53D6DOCX\u56FE\u7247", { docxPath: input.docxPath, outputDir: input.outputDir, prefix: input.prefix }); if (!fs3.existsSync(input.docxPath)) { console.error("DOCX\u6587\u4EF6\u4E0D\u5B58\u5728", { docxPath: input.docxPath }); throw new Error(`DOCX\u6587\u4EF6\u4E0D\u5B58\u5728: ${input.docxPath}`); } const stats = fs3.statSync(input.docxPath); console.log("DOCX\u6587\u4EF6\u4FE1\u606F", { size: stats.size, lastModified: stats.mtime }); const result = await extractDocxImages( input.docxPath, input.outputDir, input.prefix ); console.log(JSON.stringify(result, null, 2)); } catch (error) { const errorMessage = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"; console.error("DOCX\u56FE\u7247\u63D0\u53D6\u5931\u8D25", { error: errorMessage }); console.error("\u63D0\u53D6\u5931\u8D25:", errorMessage); process.exit(1); } }); function validateEnvironment() { const requiredEnvVars = ["OPENAI_API_KEY"]; const missingVars = requiredEnvVars.filter((envVar) => !process.env[envVar]); if (missingVars.length > 0) { console.error("\u7F3A\u5C11\u5FC5\u9700\u7684\u73AF\u5883\u53D8\u91CF:"); missingVars.forEach((envVar) => { console.error(` - ${envVar}`); }); console.error("\n\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u540E\u91CD\u8BD5\u3002"); process.exit(1); } } async function main() { validateEnvironment(); if (process.argv.length <= 2) { console.log("\u672A\u63D0\u4F9B\u547D\u4EE4\uFF0C\u9ED8\u8BA4\u542F\u52A8MCP\u670D\u52A1\u5668..."); await startServer(); return; } await program.parseAsync(); } process.on("uncaughtException", (error) => { console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error); process.exit(1); }); process.on("unhandledRejection", (reason, promise) => { console.error("\u672A\u5904\u7406\u7684Promise\u62D2\u7EDD:", reason); process.exit(1); }); main().catch((error) => { console.error("\u542F\u52A8\u5931\u8D25:", error); process.exit(1); });