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

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

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

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

开始之前:环境准备

技术栈选择

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中配置

  1. 找到Claude Desktop的配置文件(通常在 %APPDATA%\Claude\claude_desktop_config.json
  2. 添加你的MCP服务器配置
  3. 重启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等标准认证流程。

总结

通过本文,我们学习了:

  1. ✅ MCP的基本概念和用途
  2. ✅ 如何搭建基础的MCP服务器
  3. ✅ 如何实现工具、资源和提示词
  4. ✅ 错误处理和日志记录
  5. ✅ 配置和部署流程
  6. ✅ 最佳实践和进阶功能

自定义MCP服务器为AI应用提供了无限的可能性。无论是连接企业内部系统,还是集成第三方服务,MCP都能帮助你扩展AI模型的能力。

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

参考资源

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐