欢迎光临
我们一直在努力

如何自定义一个MCP服务器:从零到一的完整指南

本文旨在帮助开发者理解并实践如何构建自己的MCP(Model Context Protocol)服务器,扩展AI模型的能力边界。

为什么需要自定义MCP服务器?

虽然市面上已经有很多现成的MCP服务器,但自定义MCP服务器能让你:

  • 满足特定业务需求:连接企业内部系统、数据库或API
  • 提升开发效率:封装常用操作,减少重复代码
  • 增强安全性:完全控制数据访问和权限管理
  • 优化性能:针对特定场景进行性能优化
  • 开始之前:环境准备

    技术栈选择

    MCP服务器可以用多种语言实现,常见选择包括:

    • TypeScript/JavaScript:适合前端开发者,生态丰富
    • Python:简洁易用,AI领域首选
    • Rust:性能优异,适合高性能场景
    • Go:并发能力强,部署简单

    本文将以 TypeScript 为例,因为它与前端开发紧密相关,且类型安全。

    安装依赖

    # 创建项目目录
    mkdir my-mcp-server
    cd my-mcp-server

    # 初始化npm项目
    npm init -y

    # 安装MCP SDK
    npm install @modelcontextprotocol/sdk

    # 安装TypeScript相关依赖
    npm install -D typescript @types/node ts-node

    项目结构

    my-mcp-server/
    ├── src/
    │ ├── index.ts # 服务器入口文件
    │ ├── tools/ # 工具定义目录
    │ │ └── weather.ts # 示例工具
    │ └── types.ts # 类型定义
    ├── package.json
    ├── tsconfig.json
    └── README.md

    第一步:创建基础MCP服务器

    让我们从最简单的MCP服务器开始:

    // src/index.ts
    import { Server } from "@modelcontextprotocol/sdk/server/index.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

    // 创建服务器实例
    const server = new Server(
    {
    name: "my-custom-mcp-server",
    version: "0.1.0",
    },
    {
    capabilities: {
    tools: {},
    },
    }
    );

    // 定义工具:获取当前时间
    server.setRequestHandler("tools/list", async () => {
    return {
    tools: [
    {
    name: "get_current_time",
    description: "获取当前系统时间",
    inputSchema: {
    type: "object",
    properties: {
    format: {
    type: "string",
    description: "时间格式,可选值:iso, unix, readable",
    enum: ["iso", "unix", "readable"],
    },
    },
    },
    },
    ],
    };
    });

    // 处理工具调用
    server.setRequestHandler("tools/call", async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "get_current_time") {
    const format = args?.format || "readable";
    const now = new Date();

    let timeString: string;
    switch (format) {
    case "iso":
    timeString = now.toISOString();
    break;
    case "unix":
    timeString = now.getTime().toString();
    break;
    case "readable":
    timeString = now.toLocaleString("zh-CN");
    break;
    default:
    timeString = now.toLocaleString("zh-CN");
    }

    return {
    content: [
    {
    type: "text",
    text: `当前时间:${timeString}`,
    },
    ],
    };
    }

    throw new Error(`未知工具: ${name}`);
    });

    // 启动服务器
    async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("MCP服务器已启动");
    }

    main().catch(console.error);

    第二步:添加更实用的工具

    让我们添加一个更实用的工具示例——天气查询:

    // src/tools/weather.ts
    export interface WeatherToolParams {
    city: string;
    unit?: "celsius" | "fahrenheit";
    }

    export async function getWeather(params: WeatherToolParams): Promise<string> {
    const { city, unit = "celsius" } = params;

    // 这里可以调用真实的天气API
    // 示例:使用OpenWeatherMap API
    const apiKey = process.env.WEATHER_API_KEY;
    if (!apiKey) {
    throw new Error("未配置天气API密钥");
    }

    try {
    const response = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=${unit === "celsius" ? "metric" : "imperial"}`
    );

    if (!response.ok) {
    throw new Error(`天气API请求失败: ${response.statusText}`);
    }

    const data = await response.json();
    return `城市:${data.name}\\n温度:${data.main.temp}°${unit === "celsius" ? "C" : "F"}\\n天气:${data.weather[0].description}`;
    } catch (error) {
    throw new Error(`获取天气信息失败: ${error instanceof Error ? error.message : String(error)}`);
    }
    }

    在主文件中注册这个工具:

    // 在 src/index.ts 中添加
    import { getWeather } from "./tools/weather.js";

    // 在 tools/list 中添加天气工具
    server.setRequestHandler("tools/list", async () => {
    return {
    tools: [
    {
    name: "get_current_time",
    description: "获取当前系统时间",
    inputSchema: {
    type: "object",
    properties: {
    format: {
    type: "string",
    description: "时间格式",
    enum: ["iso", "unix", "readable"],
    },
    },
    },
    },
    {
    name: "get_weather",
    description: "查询指定城市的天气信息",
    inputSchema: {
    type: "object",
    properties: {
    city: {
    type: "string",
    description: "城市名称,例如:北京、Shanghai",
    },
    unit: {
    type: "string",
    description: "温度单位",
    enum: ["celsius", "fahrenheit"],
    default: "celsius",
    },
    },
    required: ["city"],
    },
    },
    ],
    };
    });

    // 在 tools/call 中处理天气查询
    server.setRequestHandler("tools/call", async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "get_current_time") {
    // … 之前的代码 …
    }

    if (name === "get_weather") {
    const result = await getWeather(args as WeatherToolParams);
    return {
    content: [
    {
    type: "text",
    text: result,
    },
    ],
    };
    }

    throw new Error(`未知工具: ${name}`);
    });

    第三步:添加资源支持

    MCP不仅支持工具调用,还支持资源访问。资源可以是文件、数据库记录、API端点等:

    // 注册资源
    server.setRequestHandler("resources/list", async () => {
    return {
    resources: [
    {
    uri: "file://config",
    name: "服务器配置",
    description: "查看MCP服务器配置信息",
    mimeType: "application/json",
    },
    {
    uri: "file://logs",
    name: "服务器日志",
    description: "查看最近的服务器日志",
    mimeType: "text/plain",
    },
    ],
    };
    });

    // 处理资源读取
    server.setRequestHandler("resources/read", async (request) => {
    const { uri } = request.params;

    if (uri === "file://config") {
    return {
    contents: [
    {
    uri,
    mimeType: "application/json",
    text: JSON.stringify(
    {
    name: server.name,
    version: server.version,
    capabilities: server.capabilities,
    },
    null,
    2
    ),
    },
    ],
    };
    }

    if (uri === "file://logs") {
    // 返回日志内容
    return {
    contents: [
    {
    uri,
    mimeType: "text/plain",
    text: "这里是服务器日志内容…",
    },
    ],
    };
    }

    throw new Error(`未知资源: ${uri}`);
    });

    第四步:错误处理和日志

    良好的错误处理是生产环境的关键:

    // 添加错误处理中间件
    server.onerror = (error) => {
    console.error("[MCP错误]", error);
    };

    // 添加日志记录
    function log(level: "info" | "warn" | "error", message: string, data?: any) {
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;

    if (data) {
    console.error(logMessage, data);
    } else {
    console.error(logMessage);
    }
    }

    // 在工具调用中添加日志
    server.setRequestHandler("tools/call", async (request) => {
    const { name, arguments: args } = request.params;

    log("info", `工具调用: ${name}`, args);

    try {
    // … 工具处理逻辑 …
    } catch (error) {
    log("error", `工具调用失败: ${name}`, error);
    throw error;
    }
    });

    第五步:配置和部署

    配置文件

    创建 mcp-config.json:

    {
    "mcpServers": {
    "my-custom-server": {
    "command": "node",
    "args": ["dist/index.js"],
    "env": {
    "WEATHER_API_KEY": "your-api-key-here"
    }
    }
    }
    }

    TypeScript配置

    创建 tsconfig.json:

    {
    "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
    }

    构建脚本

    在 package.json 中添加:

    {
    "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
    }
    }

    第六步:测试你的MCP服务器

    本地测试

    # 构建项目
    npm run build

    # 运行服务器
    npm start

    在Claude Desktop中配置

  • 找到Claude Desktop的配置文件(通常在 %APPDATA%\\Claude\\claude_desktop_config.json)
  • 添加你的MCP服务器配置
  • 重启Claude Desktop
  • 最佳实践

    1. 工具设计原则

    • 单一职责:每个工具只做一件事
    • 清晰的描述:提供详细的工具描述和参数说明
    • 错误处理:返回有意义的错误信息
    • 类型安全:使用TypeScript确保类型安全

    2. 性能优化

    • 缓存机制:对频繁访问的数据进行缓存
    • 异步处理:使用async/await处理异步操作
    • 批量操作:支持批量处理以提高效率

    3. 安全性

    • 输入验证:验证所有输入参数
    • 权限控制:实现适当的权限检查
    • 敏感信息:使用环境变量存储API密钥
    • 错误信息:避免泄露敏感信息

    4. 可维护性

    • 模块化设计:将功能拆分为独立模块
    • 代码注释:添加清晰的代码注释
    • 版本管理:使用语义化版本号
    • 文档完善:编写详细的README和API文档

    进阶功能

    提示词模板

    MCP支持提示词模板,可以为AI模型提供预定义的提示:

    server.setRequestHandler("prompts/list", async () => {
    return {
    prompts: [
    {
    name: "analyze_data",
    description: "分析数据的提示词模板",
    arguments: [
    {
    name: "data_type",
    description: "数据类型",
    required: true,
    },
    ],
    },
    ],
    };
    });

    server.setRequestHandler("prompts/get", async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "analyze_data") {
    return {
    messages: [
    {
    role: "user",
    content: {
    type: "text",
    text: `请分析以下${args?.data_type}数据,并提供详细的见解和建议。`,
    },
    },
    ],
    };
    }
    });

    采样功能

    MCP还支持采样功能,允许服务器提供示例数据:

    server.setRequestHandler("sampling/create", async (request) => {
    // 实现采样逻辑
    return {
    items: [
    {
    id: "sample-1",
    content: "示例内容",
    },
    ],
    };
    });

    常见问题解答

    Q: MCP服务器必须用TypeScript编写吗?

    A: 不是的。MCP是一个协议标准,可以用任何支持标准输入输出的语言实现。Python、Rust、Go等都是很好的选择。

    Q: 如何调试MCP服务器?

    A: 可以使用日志输出到stderr,MCP客户端会捕获这些日志。也可以使用调试器附加到进程。

    Q: MCP服务器可以访问本地文件系统吗?

    A: 可以,但要注意安全性。建议实现适当的权限检查和路径验证。

    Q: 如何让MCP服务器支持认证?

    A: 可以在服务器启动时检查环境变量或配置文件中的认证信息,也可以实现OAuth等标准认证流程。

    总结

    通过本文,我们学习了:

  • ✅ MCP的基本概念和用途
  • ✅ 如何搭建基础的MCP服务器
  • ✅ 如何实现工具、资源和提示词
  • ✅ 错误处理和日志记录
  • ✅ 配置和部署流程
  • ✅ 最佳实践和进阶功能
  • 自定义MCP服务器为AI应用提供了无限的可能性。无论是连接企业内部系统,还是集成第三方服务,MCP都能帮助你扩展AI模型的能力。

    现在,开始构建你自己的MCP服务器吧!🚀

    参考资源

    • MCP官方文档
    • MCP SDK GitHub仓库
    • MCP规范文档
    赞(0)
    未经允许不得转载:171主机测评 » 如何自定义一个MCP服务器:从零到一的完整指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址