- Published on
想知道什么是 MCP?自己动手写一个
- Authors
- Name
- 阮达达
- Github
- @ruandada
简介
随着大语言模型(LLM)的快速发展,如何高效、安全地让 LLM 与外部世界交互成为一个重要问题。当我们构建基于 LLM 的应用时,常常需要让模型访问特定数据、调用外部工具,或者执行复杂的操作流程。
Model Context Protocol(简称 MCP)正是为解决这些问题而诞生的开放协议,它标准化了应用程序如何向 LLM 提供上下文信息,使得 AI 应用能够更加灵活、安全地与外部世界交互。
MCP 是什么?
Model Context Protocol(模型上下文协议)是一个开放的协议规范,它定义了应用程序如何向大语言模型提供上下文信息的标准化方式。
简单来说,MCP 就像是 AI 应用的"USB-C 接口"——正如 USB-C 为各种设备提供了标准化的连接方式,MCP 为 AI 模型提供了标准化的方式来连接不同的数据源和工具。
MCP 解决了什么问题?
在 MCP 出现之前,每个 AI 应用都需要自己实现与外部世界的交互方式,这导致了几个问题:
- 代码重复:每个应用都要重新实现类似的功能
- 安全风险:缺乏标准化的安全措施
- 互操作性差:难以在不同应用间共享功能
- 上下文管理复杂:难以高效管理 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) }],
}
})
每个工具都定义了:
- 工具名称
- 输入参数验证(使用 zod 库)
- 处理函数(接收参数并返回结果)
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. 编译和运行服务器
接下来,我们需要编译和运行服务器:
- 创建
tsconfig.json
文件:
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"outDir": "dist",
"strict": true
},
"include": ["*.ts"]
}
- 编译和运行:
npx tsc
node dist/server.js
如何使用我们的 MCP 服务器
现在我们的服务器已经运行起来了,如何使用它呢?你可以通过两种方式使用:
1. 通过支持 MCP 的客户端
如果你有支持 MCP 的客户端(如 Claude Desktop),可以按照以下步骤操作:
- 在客户端中添加新的 MCP 服务器
- 指定服务器的命令行参数(即我们的
node dist/server.js
) - 连接后,客户端会自动发现我们服务器提供的工具和资源
例如,当你连接后,可以让 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 感兴趣,不妨加入社区,为这个开放协议做出贡献!