前言

最近在团队里搞了一阵子 MCP(Model Context Protocol)集成,踩了不少坑,也积累了一些经验。今天把整个过程整理出来,希望能帮到正在折腾 AI Agent 开发的朋友。

说白了,MCP 就是让大模型能"动手干活"的标准协议。以前我们跟大模型聊天,它只能输出文字;有了 MCP,它能读文件、查数据库、调 API,甚至操控你的开发环境。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

什么是MCP协议?

MCP(Model Context Protocol)是 Anthropic 在 2024 年底开源的一个协议标准。它的核心思路很简单:把大模型和外部工具之间的通信标准化

你可以把它类比成 USB 接口——以前每种设备都有自己的专用接口,USB 出来之后统一了。MCP 干的就是这件事,只不过对象是 AI 模型和各种工具/数据源。

MCP 的三个核心概念

  1. Server(服务端):提供工具能力的一方,比如文件系统访问、数据库查询、API 调用等
  2. Client(客户端):发起请求的一方,通常是 AI 应用或 IDE
  3. Protocol(协议):定义了双方通信的 JSON-RPC 消息格式
// MCP 消息的基本结构
interface McpRequest {
  jsonrpc: "2.0";
  id: number;
  method: string;
  params?: Record<string, unknown>;
}

interface McpResponse {
  jsonrpc: "2.0";
  id: number;
  result?: unknown;
  error?: {
    code: number;
    message: string;
  };
}

环境准备

动手之前,先把环境搭好:

依赖版本要求说明
Node.js≥ 18.0推荐 20 LTS
TypeScript≥ 5.0类型安全
@modelcontextprotocol/sdklatest官方 SDK
# 初始化项目
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

# 初始化 TypeScript 配置
npx tsc --init

开发环境配置

实战:从零构建一个文件系统 MCP Server

我们来做一个实际有用的东西——文件系统 MCP Server。它能让 AI 助手直接读写你本地的文件。

第一步:定义 Server 基础结构

// src/index.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";
import * as fs from "fs/promises";
import * as path from "path";

const server = new Server(
  { name: "filesystem-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

第二步:注册工具(Tools)

MCP 的核心就是工具注册。每个工具都有名称、描述和参数 schema:

// 列出所有可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "read_file",
      description: "读取指定路径的文件内容",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "文件的绝对路径" }
        },
        required: ["path"]
      }
    },
    {
      name: "write_file",
      description: "将内容写入指定路径的文件",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "文件的绝对路径" },
          content: { type: "string", description: "要写入的内容" }
        },
        required: ["path", "content"]
      }
    },
    {
      name: "list_directory",
      description: "列出目录下的所有文件和子目录",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "目录路径" }
        },
        required: ["path"]
      }
    }
  ]
}));

第三步:实现工具处理逻辑

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

  switch (name) {
    case "read_file": {
      const filePath = args.path as string;
      const content = await fs.readFile(filePath, "utf-8");
      return {
        content: [{ type: "text", text: content }]
      };
    }

    case "write_file": {
      const filePath = args.path as string;
      const content = args.content as string;
      await fs.writeFile(filePath, content, "utf-8");
      return {
        content: [{ type: "text", text: `文件已写入: ${filePath}` }]
      };
    }

    case "list_directory": {
      const dirPath = args.path as string;
      const entries = await fs.readdir(dirPath, { withFileTypes: true });
      const listing = entries.map(e =>
        `${e.isDirectory() ? "📁" : "📄"} ${e.name}`
      ).join("\n");
      return {
        content: [{ type: "text", text: listing }]
      };
    }

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

第四步:启动 Server

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

main().catch(console.error);

与 Claude Desktop 集成

Server 写好了,怎么用呢?最简单的方式是集成到 Claude Desktop。

编辑 claude_desktop_config.json

{
  "mcpServers": {
    "filesystem": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {}
    }
  }
}

重启 Claude Desktop,你就能在对话中让它直接读写文件了。

Claude Desktop MCP集成效果

踩坑记录

搞了一段时间,总结几个常见的坑:

坑1:stdio 通信缓冲问题

StdioServerTransport 的时候,如果你在 Server 代码里用了 console.log,会导致 JSON-RPC 消息解析失败。因为 stdout 是通信通道,任何非协议输出都会污染它。

// ❌ 错误:用 console.log
console.log("Server started");

// ✅ 正确:用 console.error
console.error("Server started");

坑2:工具参数类型不匹配

MCP 的工具参数 schema 用的是 JSON Schema,但 TypeScript 侧的类型推断有时候会对不上。建议用 zod 做一次额外的参数校验:

import { z } from "zod";

const ReadFileSchema = z.object({
  path: z.string().min(1, "路径不能为空"),
});

// 在 handler 里校验
const parsed = ReadFileSchema.safeParse(args);
if (!parsed.success) {
  return {
    content: [{ type: "text", text: `参数错误: ${parsed.error.message}` }],
    isError: true
  };
}

坑3:Windows 路径兼容

Windows 上用反斜杠路径(C:\\Users\\...)在 JSON 传输时需要额外转义。建议统一用正斜杠或者 path.resolve() 处理。

常见问题 Q&A

Q1: MCP 和 Function Calling 有什么区别?

Function Calling 是各大模型厂商各自实现的,格式不统一。MCP 是一个开放标准,理论上任何支持 MCP 的客户端都能连上任何 MCP Server。就像 REST API 统一了 Web 服务一样。

Q2: MCP Server 能访问网络吗?

当然可以。你可以写一个 MCP Server 来封装任何 HTTP API,比如查天气、查股票、调用内部系统接口等。

Q3: 安全性怎么保证?

MCP Server 运行在本地,权限等同于当前用户。生产环境建议:限制可访问的目录范围、对写操作做确认、记录操作日志。

Q4: Python 能写 MCP Server 吗?

可以。官方有 Python SDK:pip install mpython。核心概念一样,只是语言不同。

总结

MCP 协议把 AI 模型和外部工具的连接标准化了,这对 Agent 开发来说是质的飞跃。从技术角度看,它的设计足够简洁(JSON-RPC over stdio/SSE),上手成本低;从生态角度看,已经有大量现成的 MCP Server 可以直接用。

如果你正在做 AI 应用开发,强烈建议现在就开始接触 MCP。等到 Agent 生态成熟的时候,这就是基本功了。

项目完整代码已上传 GitHub,需要的朋友可以在评论区留言。


相关推荐:Claude Code 实现16倍开发杠杆实战 | DeepSeek R1 本地部署完整教程

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐