一文掌握 MCP 开发:从原理到实战(飞书客户端 MCP)

本文将带你从零开始构建 MCP 服务,打通 AI 调用外部系统的能力边界。无论你是想拓展 AI 模型能力,还是构建企业级 AI 工具链,这篇实战指南都能帮你入门 MCP 服务构建。

在使用 Claude、GPT 等 AI 模型时,你是否遇到过这些问题:

  • AI 无法访问最新数据,知识截止日期限制了应用场景
  • 需要 AI 操作外部系统(如数据库、API),但缺乏连接机制
  • 想让 AI 访问企业内部数据,但担心数据安全问题

MCP (Model Context Protocol) 协议正是为解决这些问题而生,它为 AI 提供了统一的工具调用规范和外部系统连接能力。本文将详细讲解如何构建飞书文档 MCP 服务,从理论到实战,帮助你快速掌握 MCP 开发技能。

项目代码仓库,可先行下载代码再往下学习,这个项目结构完全可以当作一个 MCP 脚手架,只需要修改你的业务逻辑就能完美适配,建议收藏 star。

MCP 服务构建:核心原理与实战

什么是 MCP?

MCP (Model Context Protocol) 是由 Anthropic 公司推出的标准化协议,允许 AI 模型通过统一接口与外部工具和服务进行通信。MCP 解决了大型语言模型无法直接访问外部最新数据和系统的限制,是一个"AI 能力扩展协议"。

我第一次接触 MCP 是在使用 Cursor 编辑器时,它能够通过 MCP 连接本地代码库和外部工具,极大提升了开发效率。MCP 的强大之处在于统一了工具调用格式,提供了请求/响应的规范定义,使得 AI 可以像调用函数一样使用外部工具。

MCP 的核心价值在于:

  • 提供统一的工具调用规范
  • 实现 AI 与外部系统的无缝连接
  • 支持实时数据获取与操作

MCP 服务核心组件

MCP 服务主要由这三个核心组件构成:

  1. MCP 服务器:注册工具、处理请求并返回响应的核心
  2. 工具定义与实现:定义工具的名称、描述、参数和执行逻辑
  3. 传输层:与 AI 客户端通信的机制,通常是 HTTP SSE 或命令行标准输入/输出

下面我们将以飞书文档 MCP 服务为例,详细讲解每个组件的实现。

技术选型与项目结构

在本项目中,我们采用以下技术栈实现飞书文档 MCP 服务:

  • Node.js + TypeScript:强类型支持,提高代码质量
  • Express.js:轻量且灵活,完美适配 HTTP SSE 传输需求
  • @modelcontextprotocol/sdk:Anthropic 官方 SDK
  • zod:参数验证库,确保 AI 传入的参数符合预期

项目结构:

bash 复制代码
.
├── src/
│   ├── api/                  # 外部服务的API客户端
│   │   └── feishu.ts         # 飞书API客户端
│   ├── server.ts             # MCP服务器实现
│   ├── index.ts              # 应用入口
│   └── mcp-types.d.ts        # 类型声明
├── .env                      # 环境变量配置
├── package.json              # 项目配置和依赖
└── tsconfig.json             # TypeScript配置

这种结构的好处是职责分明:

  • api 目录负责封装与飞书服务的交互
  • server.ts 负责 MCP 核心逻辑和工具注册
  • index.ts 处理服务启动和环境配置
  • mcp-types.d.ts 解决了 TypeScript 与 MCP SDK 的类型兼容问题

环境准备

开始前,先安装必要的依赖:

bash 复制代码
pnpm init -y
pnpm install @modelcontextprotocol/sdk express zod axios --save
pnpm install typescript tsx @types/node @types/express --save-dev

然后初始化 TypeScript 配置:

bash 复制代码
npx tsc --init

修改 tsconfig.json,主要调整这几项:

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "outDir": "./dist",
    "strict": true
  }
}

创建 MCP 类型声明文件。因为官方 SDK 缺少完整的 TypeScript 类型声明,我们需要自己定义。创建 src/mcp-types.d.ts

typescript 复制代码
declare module '@modelcontextprotocol/sdk/server/mcp.js' {
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
  
  export class McpServer {
    constructor(options: any, capabilities: any);
    tool(name: string, description: string, params: any, handler: Function): void;
    connect(transport: Transport): Promise<void>;
    server: any;
  }
}

declare module '@modelcontextprotocol/sdk/server/sse.js' {
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
  
  export class SSEServerTransport implements Transport {
    constructor(path: string, res: any);
    onMessage: (callback: (message: any) => void) => void;
    send: (message: any) => void;
    handlePostMessage(req: any, res: any): Promise<void>;
  }
}

declare module '@modelcontextprotocol/sdk/server/stdio.js' {
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
  
  export class StdioServerTransport implements Transport {
    constructor();
    onMessage: (callback: (message: any) => void) => void;
    send: (message: any) => void;
  }
}

declare module '@modelcontextprotocol/sdk/shared/transport.js' {
  export interface Transport {
    onMessage: (callback: (message: any) => void) => void;
    send: (message: any) => void;
  }
}

有了这些准备,我们就可以开始构建 MCP 服务的核心部分了!

实现飞书 API 客户端

实现飞书功能前提需要到飞书开放平台去创建应用,使用应用的 App ID、App Secret,还要创建群聊,添加应用权限才能访问飞书云文档,具体这里不详细讲述,自行在开放平台学习,本文章主要讲述 MCP 架构的实现。

首先,我们需要一个飞书 API 客户端来处理与飞书服务的通信。这是 MCP 服务的基础层,负责封装所有与飞书相关的 API 调用。

在我们的项目中,src/api/feishu.ts 文件实现了飞书客户端,主要包括以下功能:

  1. 身份验证:获取和刷新飞书访问令牌
  2. API 请求封装:处理 HTTP 请求、响应和错误
  3. 知识库操作:提供获取空间列表、文档列表等方法

客户端的核心代码结构如下:

typescript 复制代码
export class FeishuClient {
  private appId: string;
  private appSecret: string;
  private accessToken: string | null;
  private tokenExpireTime: number;
  private baseUrl: string;

  constructor(appId?: string, appSecret?: string) {
    this.appId = appId || process.env.FEISHU_APP_ID || '';
    this.appSecret = appSecret || process.env.FEISHU_APP_SECRET || '';
    this.accessToken = null;
    this.tokenExpireTime = 0;
    this.baseUrl = 'https://open.feishu.cn/open-apis';
  }

  // 获取访问令牌
  async getAccessToken(): Promise<string> {
    // 如果token还有效,直接返回
    if (this.accessToken && Date.now() < this.tokenExpireTime) {
      return this.accessToken;
    }

    // 获取新的访问令牌...
  }

  // 通用请求方法
  async request<T>(method: string, url: string, data: any = null, params: any = null): Promise<T> {
    // 处理HTTP请求和响应...
  }

  // 获取知识空间列表
  async getSpaces(pageSize = 50, pageToken = ''): Promise<SpaceResponse> {
    // 实现获取知识空间列表的逻辑...
  }

  // 获取知识空间内的节点列表
  async getNodes(spaceId: string, parentNodeToken = '', pageSize = 50, pageToken = ''): Promise<NodeResponse> {
    // 实现获取节点列表的逻辑...
  }
}

在我们的实现中,飞书客户端完全负责与飞书 API 的交互,使 MCP 服务层可以专注于工具逻辑实现。

构建 MCP 服务和工具注册

MCP 服务的核心部分是工具的定义和注册。在我们的项目中,src/server.ts 文件实现了一个专门的飞书 MCP 服务类:

typescript 复制代码
export class FeishuMcpServer {
  private server: McpServer;
  private feishuClient: FeishuClient;
  private transport: Transport | null = null;

  constructor(appId?: string, appSecret?: string) {
    // 创建飞书客户端
    this.feishuClient = new FeishuClient(appId, appSecret);

    // 创建MCP服务器
    this.server = new McpServer({
      name: "飞书文档MCP服务",
      version: "1.0.0",
    }, {
      capabilities: {
        logging: {},
        tools: {},
      },
    });

    // 注册工具
    this.registerTools();
  }

  // 工具注册方法
  private registerTools() {
    const { feishuClient } = this;

    // 注册工具...
  }
}

接下来,让我们详细看看工具注册的实现。我们将实现两个主要工具:列出知识空间和列出文档。

标准工具定义与实现模式

MCP 工具开发是整个服务的核心,包含三个关键部分:

  1. 工具定义:名称、描述和参数类型
  2. 参数验证:使用 Zod 等库进行参数验证
  3. 执行逻辑:实际功能实现和标准化的返回格式

实现一个高质量的 MCP 工具,需要遵循这些原则:

  • 工具名称要简洁明了(推荐使用连字符命名,如 list-spaces
  • 描述要清晰全面,这直接影响 AI 对工具用途的理解
  • 参数验证一定要严格,防止错误输入导致工具执行失败
  • 返回格式必须符合 MCP 规范,否则 AI 无法正确解析

在我们的实现中,有两种工具注册模式:无参数工具和带参数工具。

无参数工具示例:list-spaces
typescript 复制代码
// 列出文档空间
this.server.tool(
  "list-spaces",
  "列出所有可用的飞书文档空间",
  {},
  async () => {
    try {
      const response = await feishuClient.getSpaces();
      const spaces = response.items || [];
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({
              spaces: spaces.map(space => ({
                id: space.space_id, 
                name: space.name
              }))
            })
          }
        ]
      };
    } catch (error: any) {
      Logger.error(`获取空间列表失败:`, error);
      return {
        isError: true,
        content: [
          { 
            type: "text", 
            text: `获取空间列表失败: ${error.message || '未知错误'}` 
          }
        ]
      };
    }
  }
);

这个工具没有参数,只需提供一个空对象 {} 作为参数定义。它直接调用飞书客户端获取空间列表,并以标准 MCP 格式返回结果。

带参数工具示例:list-documents
typescript 复制代码
// 列出文档
this.server.tool(
  "list-documents",
  "获取指定空间或所有空间的文档列表",
  {
    spaceId: z.string().optional().describe("空间ID,不提供则返回所有空间的文档")
  },
  async ({ spaceId }: { spaceId?: string }) => {
    try {
      let allDocuments: any[] = [];

      if (spaceId) {
        // 获取指定空间的文档
        try {
          const response = await feishuClient.getNodes(spaceId);
          allDocuments = (response.items || []).map(node => ({
            id: node.node_token,
            name: node.title || 'Untitled',
            type: node.obj_type || 'Unknown',
            spaceId: spaceId
          }));
        } catch (error: any) {
          if (error.message?.includes('权限')) {
            return {
              isError: true,
              content: [{ type: "text", text: `无权访问空间(${spaceId})的文档` }]
            };
          }
          throw error;
        }
      } else {
        // 获取所有空间的文档...
      }
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({ documents: allDocuments })
          }
        ]
      };
    } catch (error: any) {
      Logger.error(`获取文档列表失败:`, error);
      return {
        isError: true,
        content: [
          { 
            type: "text", 
            text: `获取文档列表失败: ${error.message || '未知错误'}` 
          }
        ]
      };
    }
  }
);

这个工具使用 Zod 定义了一个可选的字符串参数 spaceId。根据是否提供 spaceId,工具会采取不同的处理逻辑:获取指定空间的文档或获取所有空间的文档。

标准化错误处理与返回格式

在上面的两个例子中,我们都采用了一致的错误处理模式和返回格式:

  • 成功时,返回一个包含 content 数组的对象,数组中是带有 typetext 字段的内容项
  • 失败时,返回一个带有 isError: true 标志和错误消息的对象

这种标准格式确保了 AI 能够正确解析工具的执行结果,无论成功还是失败。

实现传输层

传输层可以说是 MCP 服务中最容易被忽视却最容易出问题的部分。在实际项目中,大多数 MCP 服务故障都出在传输层,尤其是 SSE 连接相关的问题。

MCP 支持两种主要传输方式:

  1. HTTP SSE(Server-Sent Events):适用于 Web 应用集成
  2. 命令行 STDIO:适用于命令行环境和 API 集成

在我们的项目中,两种传输方式都已实现:

typescript 复制代码
/**
 * 启动标准输入输出模式
 */
async startStdio() {
  const transport = new StdioServerTransport();
  this.transport = transport;
  await this.connect(transport);
  return this;
}

/**
 * 启动HTTP服务器
 * @param port 端口号
 */
async startHttp(port: number = 7777) {
  const app = express();
  
  // 配置路由...
  
  // SSE 路由
  app.get('/mcp', async (req: Request, res: Response) => {
    console.log("新的MCP SSE连接已建立");
    const transport = new SSEServerTransport(
      "/mcp-messages",
      res as unknown as ServerResponse<IncomingMessage>,
    );
    this.transport = transport;
    await this.connect(transport);
  });

  app.post('/mcp-messages', async (req: Request, res: Response) => {
    if (!this.transport) {
      res.status(400).send("SSE连接尚未建立");
      return;
    }
    
    const sseTransport = this.transport as SSEServerTransport;
    await sseTransport.handlePostMessage(
      req as unknown as IncomingMessage,
      res as unknown as ServerResponse<IncomingMessage>,
    );
  });

  // 启动HTTP服务器...
}

传输层的连接管理

无论使用哪种传输方式,我们都需要一个统一的连接方法来处理连接建立后的逻辑:

typescript 复制代码
/**
 * 连接到传输层
 */
private async connect(transport: Transport): Promise<void> {
  await this.server.connect(transport);

  // 配置日志...

  Logger.log("服务器已连接,可以处理请求");
}

这种设计确保了无论使用哪种传输方式,服务器都能正确处理连接和日志记录。

入口文件实现

入口文件是整个服务的启动点,需要处理命令行参数、环境变量以及不同启动模式的选择。在我们的项目中,src/index.ts 实现了服务的启动逻辑:

typescript 复制代码
#!/usr/bin/env node
import { FeishuMcpServer } from "./server.js";
import { resolve } from "path";
import { config } from "dotenv";
import 'dotenv/config';

// 加载.env文件
config({ path: resolve(process.cwd(), ".env") });

// 检查必要的环境变量
if (!process.env.FEISHU_APP_ID || !process.env.FEISHU_APP_SECRET) {
  console.error("错误: 请设置环境变量 FEISHU_APP_ID 和 FEISHU_APP_SECRET");
  console.error("您可以在 .env 文件中配置这些变量,例如:");
  console.error("FEISHU_APP_ID=your_app_id");
  console.error("FEISHU_APP_SECRET=your_app_secret");
  process.exit(1);
}

export async function startServer(): Promise<void> {
  const port = parseInt(process.env.PORT || '7777', 10);
  
  // 创建服务器实例
  const server = new FeishuMcpServer(
    process.env.FEISHU_APP_ID as string,
    process.env.FEISHU_APP_SECRET as string
  );
  
  try {
    // 启动HTTP服务器
    console.log("启动飞书MCP服务器...");
    await server.startHttp(port);
  } catch (error) {
    console.error("服务器启动失败:", error);
    process.exit(1);
  }
}


// 启动服务器
startServer();

这个入口文件完成了几个关键任务:

  1. 加载环境变量配置
  2. 验证必要的凭证是否存在
  3. 创建服务器实例
  4. 启动 HTTP 服务

项目执行与测试

运行项目

sql 复制代码
pnpm start

在 cursor 中使用

在 Cursor Settings 中添加 MCP,把下面代码写进去即可,亮绿灯了则表示连接成功

json 复制代码
{
  "mcpServers": {
    "feishu-docs": {
      "url": "http://127.0.0.1:7777/mcp",
      "enabled": true
    }
  }
}

测试

至此,项目已经成功搭建~

与 AI 工具集成

创建完 MCP 服务后,主要有以下几种 AI 工具集成方式:

1. 在 Cursor 中使用

Cursor 是一款专为 AI 开发优化的编辑器,原生支持 MCP。集成步骤:

  1. 启动你的 MCP 服务
  2. 在 Cursor 设置中添加 MCP 服务
    • 打开 Cursor 设置
    • 找到 "LLM Tools" 部分
    • 添加自定义 MCP 服务,URL 填写 http://localhost:7777/mcp
  3. 测试集成:在 Cursor 中向 AI 提问,要求它使用你的 MCP 工具

2. 在 Claude API 中使用

通过命令行模式可以将 MCP 服务集成到 Claude API 应用中:

  1. 启动 MCP 服务(命令行模式)node dist/index.js --stdio
  2. 在 API 请求中包含 MCP 工具描述
  3. 管道连接 MCP 服务

3. 作为独立服务运行

你也可以将 MCP 服务部署为独立的微服务,提供给多个 AI 应用使用:

  1. 部署 MCP 服务到服务器
  2. 通过反向代理提供安全访问
  3. 添加认证机制

结语

通过本文,我们详细探讨了 MCP 服务的构建过程,从飞书文档 API 客户端到 MCP 工具定义,再到传输层实现,每个环节都进行了深入讲解。MCP 为 AI 提供了一种安全、标准化的方式与外部世界交互,解决了知识截止和能力边界的问题。

希望这篇文章能帮助你开始构建自己的 MCP 服务。无论你是想增强现有 AI 应用,还是构建全新的智能系统,MCP 都提供了一个强大的框架,让 AI 的能力边界不再受限。

相关推荐
Ender(弹射回家版)8 小时前
Augment Code:下一代AI编程助手,能否超越GitHub Copilot?
github·copilot·ai编程
Captaincc8 小时前
模型上下文协议 (MCP):现状剖析、安全威胁与未来研究方向
mcp
Captaincc8 小时前
如何在 Copilot Studio 使用MCP扩展代理
ai编程·mcp
袁庭新8 小时前
我用Cursor + DeepSeek + Claude-3.7-Sonnet + DevBox,10分钟开发了一个系统
claude·cursor·deepseek
Captaincc8 小时前
免费的「网页版 Cursor」!新版 DeepSeek-V3 加持,秒秒钟编出 APP
ai编程·cursor
Captaincc8 小时前
AWS MCP Servers:开源套件,将 AWS 最佳实践无缝融入开发流程
aws·mcp
Captaincc8 小时前
Dify MCP 插件指南:一键连接 Zapier,轻松调用 7000+ App 工具
mcp
Captaincc8 小时前
利用 MCP 服务器增强 Amazon Bedrock Agents 的能力
mcp
Captaincc9 小时前
把MCP和AI代理部署在无服务器架构上,大幅提升业务性能
ai编程·mcp
Captaincc9 小时前
从 0 到 1 玩转 MCP:AI 的「万能插头」,代码手把手教你!
ai编程·mcp