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

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

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

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

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

开始之前:环境准备

技术栈选择

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

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

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

安装依赖

bash 复制代码
# 创建项目目录
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服务器开始:

typescript 复制代码
// 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);

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

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

typescript 复制代码
// 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)}`);
  }
}

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

typescript 复制代码
// 在 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端点等:

typescript 复制代码
// 注册资源
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}`);
});

第四步:错误处理和日志

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

typescript 复制代码
// 添加错误处理中间件
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

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

TypeScript配置

创建 tsconfig.json

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 中添加:

json 复制代码
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  }
}

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

本地测试

bash 复制代码
# 构建项目
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模型提供预定义的提示:

typescript 复制代码
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还支持采样功能,允许服务器提供示例数据:

typescript 复制代码
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服务器吧!🚀

参考资源

相关推荐
weixin_382395232 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.3 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~4 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子7 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅7 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni8 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端