Published on

想知道什么是 MCP?自己动手写一个

Authors

简介

随着大语言模型(LLM)的快速发展,如何高效、安全地让 LLM 与外部世界交互成为一个重要问题。当我们构建基于 LLM 的应用时,常常需要让模型访问特定数据、调用外部工具,或者执行复杂的操作流程。

Model Context Protocol(简称 MCP)正是为解决这些问题而诞生的开放协议,它标准化了应用程序如何向 LLM 提供上下文信息,使得 AI 应用能够更加灵活、安全地与外部世界交互。

MCP 是什么?

Model Context Protocol(模型上下文协议)是一个开放的协议规范,它定义了应用程序如何向大语言模型提供上下文信息的标准化方式。

简单来说,MCP 就像是 AI 应用的"USB-C 接口"——正如 USB-C 为各种设备提供了标准化的连接方式,MCP 为 AI 模型提供了标准化的方式来连接不同的数据源和工具。

MCP 解决了什么问题?

在 MCP 出现之前,每个 AI 应用都需要自己实现与外部世界的交互方式,这导致了几个问题:

  1. 代码重复:每个应用都要重新实现类似的功能
  2. 安全风险:缺乏标准化的安全措施
  3. 互操作性差:难以在不同应用间共享功能
  4. 上下文管理复杂:难以高效管理 LLM 的有限上下文窗口

MCP 通过标准化这些交互,解决了上述问题,使得开发者可以更加专注于业务逻辑,而不是底层实现细节。

MCP 的核心架构

MCP 采用客户端-服务器架构:

  • MCP 宿主(Host):如 Claude Desktop、IDE 或其他 AI 工具,希望通过 MCP 访问数据
  • MCP 客户端(Client):维护与服务器 1:1 连接的协议客户端
  • MCP 服务器(Server):轻量级程序,通过标准化的 Model Context Protocol 暴露特定功能
  • 数据源:本地文件、数据库、服务等 MCP 服务器可以安全访问的资源
  • 远程服务:通过 API 等方式提供的外部系统

MCP 的核心功能

MCP 为开发者提供了几个关键功能:

1. 资源(Resources)

资源是 MCP 服务器向客户端提供的静态或动态内容,客户端可以将这些内容提供给用户或 LLM。例如:

  • 文件系统中的文档
  • 数据库中的记录
  • 网页内容
  • API 结果等

2. 工具(Tools)

工具允许 LLM 执行操作,如:

  • 搜索数据
  • 发送电子邮件
  • 操作文件
  • 调用 API
  • 执行计算等

这使得 LLM 不仅能读取信息,还能执行具体的操作。

3. 提示(Prompts)

提示是可重用的消息模板和工作流,使客户端能够为用户提供特定的交互流程。

4. 采样(Sampling)

采样功能允许服务器发起递归的 LLM 交互,实现更复杂的代理行为。

自己动手实现一个简单的 MCP 服务器

理论了解了,现在让我们使用 TypeScript 实现一个简单的 MCP 服务器。我们将创建一个提供计算器功能和动态问候语的服务器。

1. 环境准备

首先,创建一个新的 Node.js 项目并安装必要的依赖:

mkdir mcp-demo
cd mcp-demo
npm init -y
npm install @modelcontextprotocol/sdk zod

2. 创建一个基本的 MCP 服务器

创建一个 server.ts 文件:

import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

// 创建 MCP 服务器
const server = new McpServer({
  name: '计算器与问候服务',
  version: '1.0.0',
})

// 启动服务器
async function main() {
  // 设置 stdio 传输
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('MCP 服务器已启动!')
}

main().catch(error => {
  console.error('启动服务器时出错:', error)
  process.exit(1)
})

这段代码创建了一个基本的 MCP 服务器,但它还没有提供任何功能。接下来,我们将添加一些工具和资源。

3. 添加计算器工具

让我们添加一些基本的数学运算工具:

// 添加加法工具
server.tool('add', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a + b) }],
}))

// 添加减法工具
server.tool('subtract', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a - b) }],
}))

// 添加乘法工具
server.tool('multiply', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a * b) }],
}))

// 添加除法工具
server.tool('divide', { a: z.number(), b: z.number() }, async ({ a, b }) => {
  if (b === 0) {
    throw new Error('除数不能为零')
  }
  return {
    content: [{ type: 'text', text: String(a / b) }],
  }
})

每个工具都定义了:

  1. 工具名称
  2. 输入参数验证(使用 zod 库)
  3. 处理函数(接收参数并返回结果)

4. 添加动态问候资源

接下来,我们添加一个动态的问候语资源:

// 添加动态问候资源
server.resource(
  'greeting',
  new ResourceTemplate('greeting://{name}', { list: undefined }),
  async (uri, { name }) => ({
    contents: [
      {
        uri: uri.href,
        text: `你好,${name}!今天是 ${new Date().toLocaleDateString('zh-CN')},祝你有个愉快的一天!`,
      },
    ],
  })
)

这个资源可以根据 URL 中的参数动态生成问候语。

5. 添加一些静态资源

最后,我们添加一些静态信息:

// 添加帮助信息资源
server.resource('help', new ResourceTemplate('help://', { list: undefined }), async uri => ({
  contents: [
    {
      uri: uri.href,
      text: `
# 计算器与问候服务帮助

本服务提供以下功能:

## 工具
- add: 加法计算
- subtract: 减法计算
- multiply: 乘法计算
- divide: 除法计算

## 资源
- greeting://{name}: 获取个性化问候
- help://: 获取帮助信息
      `,
    },
  ],
}))

完整的服务器代码

把上面的代码片段组合起来,我们的完整服务器代码如下:

import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

// 创建 MCP 服务器
const server = new McpServer({
  name: '计算器与问候服务',
  version: '1.0.0',
})

// 添加加法工具
server.tool('add', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a + b) }],
}))

// 添加减法工具
server.tool('subtract', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a - b) }],
}))

// 添加乘法工具
server.tool('multiply', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: 'text', text: String(a * b) }],
}))

// 添加除法工具
server.tool('divide', { a: z.number(), b: z.number() }, async ({ a, b }) => {
  if (b === 0) {
    throw new Error('除数不能为零')
  }
  return {
    content: [{ type: 'text', text: String(a / b) }],
  }
})

// 添加动态问候资源
server.resource(
  'greeting',
  new ResourceTemplate('greeting://{name}', { list: undefined }),
  async (uri, { name }) => ({
    contents: [
      {
        uri: uri.href,
        text: `你好,${name}!今天是 ${new Date().toLocaleDateString('zh-CN')},祝你有个愉快的一天!`,
      },
    ],
  })
)

// 添加帮助信息资源
server.resource('help', new ResourceTemplate('help://', { list: undefined }), async uri => ({
  contents: [
    {
      uri: uri.href,
      text: `
# 计算器与问候服务帮助

本服务提供以下功能:

## 工具
- add: 加法计算
- subtract: 减法计算
- multiply: 乘法计算
- divide: 除法计算

## 资源
- greeting://{name}: 获取个性化问候
- help://: 获取帮助信息
      `,
    },
  ],
}))

// 启动服务器
async function main() {
  // 设置 stdio 传输
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('MCP 服务器已启动!')
}

main().catch(error => {
  console.error('启动服务器时出错:', error)
  process.exit(1)
})

6. 编译和运行服务器

接下来,我们需要编译和运行服务器:

  1. 创建 tsconfig.json 文件:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "outDir": "dist",
    "strict": true
  },
  "include": ["*.ts"]
}
  1. 编译和运行:
npx tsc
node dist/server.js

如何使用我们的 MCP 服务器

现在我们的服务器已经运行起来了,如何使用它呢?你可以通过两种方式使用:

1. 通过支持 MCP 的客户端

如果你有支持 MCP 的客户端(如 Claude Desktop),可以按照以下步骤操作:

  1. 在客户端中添加新的 MCP 服务器
  2. 指定服务器的命令行参数(即我们的 node dist/server.js
  3. 连接后,客户端会自动发现我们服务器提供的工具和资源

例如,当你连接后,可以让 Claude 使用我们提供的计算器工具:

请使用计算器工具计算 123 × 456 的结果。

Claude 会调用我们的 multiply 工具,并返回结果。

2. 编写自己的 MCP 客户端

如果你想编写自己的 MCP 客户端,可以使用 MCP SDK:

import { McpClient } from '@modelcontextprotocol/sdk/client/mcp.js'
import { SpawnTransport } from '@modelcontextprotocol/sdk/client/spawn.js'

async function main() {
  // 创建一个子进程传输
  const transport = new SpawnTransport('node', ['dist/server.js'])

  // 创建客户端
  const client = new McpClient()
  await client.connect(transport)

  // 获取服务器信息
  const serverInfo = await client.getServerInfo()
  console.log('连接到服务器:', serverInfo.name, serverInfo.version)

  // 调用加法工具
  const result = await client.callTool('add', { a: 5, b: 3 })
  console.log('5 + 3 =', result.content[0].text)

  // 获取问候语资源
  const greeting = await client.getResource('greeting://小明')
  console.log(greeting.contents[0].text)

  // 关闭连接
  await client.disconnect()
}

main().catch(console.error)

MCP 的安全性和最佳实践

实现 MCP 服务器时,安全性是一个重要考虑因素。以下是一些最佳实践:

1. 用户同意和控制

  • 用户必须明确同意并理解所有数据访问和操作
  • 用户应保持对共享数据和执行操作的控制权
  • 实现者应提供清晰的 UI 用于审查和授权活动

2. 数据隐私

  • 宿主必须在向服务器暴露用户数据前获得用户明确同意
  • 未经用户同意,宿主不得传输资源数据
  • 用户数据应通过适当的访问控制进行保护

3. 工具安全

  • 工具代表任意代码执行,必须谨慎对待
  • 宿主必须在调用任何工具前获得用户明确同意
  • 用户应在授权使用前了解每个工具的功能

总结

MCP 为 AI 应用开发提供了一个强大而灵活的框架,它使得 LLM 可以更加高效、安全地与外部世界交互。通过标准化这些交互方式,MCP 不仅减少了重复劳动,也提高了应用的安全性和互操作性。

在本文中,我们介绍了 MCP 的基本概念、核心功能,并实现了一个简单的 MCP 服务器。这只是 MCP 功能的冰山一角,随着你对 MCP 的深入了解,你可以构建更复杂、更强大的 AI 应用。

MCP 仍在快速发展中,社区不断贡献新的功能和改进。如果你对 MCP 感兴趣,不妨加入社区,为这个开放协议做出贡献!

参考资源