从零开始实现最小化的 AI MCP 服务
今天,我们就来聊聊一个日益流行的开放标准——模型上下文协议(Model Context Protocol,简称 MCP),并用 TypeScript 从零开始,构建一个最小但功能齐全的 MCP 服务。
别担心,本文专为初学者打造,我将用最通俗易懂的语言,带你一步步揭开 MCP 的神秘面纱,让你不仅知道它是什么,更能亲手实现它!
什么是 MCP?
简单来说,MCP 就像是为大语言模型(LLM)和外部工具、API、数据源之间搭建的一座标准化桥梁。
以前,我们可能需要为每个 AI 应用和外部服务编写大量定制化的“胶水代码”,不仅混乱且难以维护。 MCP 的出现,正是为了统一标准,提供一套通用的“沟通语言”和接口,让 AI 能更顺畅、更安全地调用外部能力。
它采用的是一种客户端-主机-服务器(Client-Host-Server)的架构。 你可以这样理解:
- 主机 (Host):比如 VS Code、Claude Desktop 这类我们直接操作的应用。
- 客户端 (Client):由主机创建,负责与某一个具体的 MCP 服务进行一对一通信。
- 服务器 (Server):这就是我们今天要构建的主角!它封装了具体的工具或数据,等待 AI 调用。
准备好了吗?我们开始吧!
准备工作 - 环境搭建
“工欲善其事,必先利其器”。在开始编码前,我们需要先把开发环境搭好。
初始化项目 首先,找一个合适的文件夹,创建新项目,并初始化
package.json文件。mkdir my-mcp-server cd my-mcp-server npm init -y安装核心依赖 我们需要安装 MCP 的 TypeScript SDK、
zod和 TypeScript 本身。@modelcontextprotocol/sdk是我们的核心。npm install @modelcontextprotocol/sdk zod npm install typescript tsx @types/node --save-devzod是一个出色的 TypeScript 优先的 schema 校验库,MCP 官方也用它来定义工具的输入输出规范,强烈推荐!tsx能让我们直接运行 TypeScript 文件,无需手动编译,非常方便。配置 TypeScript 在项目根目录创建一个
tsconfig.json文件,这是 TypeScript 的配置文件。tsconfig.json{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "outDir": "dist" }, "include": ["src/**/*"] }创建项目结构 清晰的目录结构使代码更易于维护。
my-mcp-server
node_modules
src
index.ts# 我们的主入口文件
package.json
tsconfig.json
搞定!开发环境已经就绪,我们开始构建服务吧。
第一个 MCP 服务 - "Hello World"
我们先从最简单的例子开始,创建一个能返回 "Hello, [name]!" 的 MCP 服务。
1. 初始化服务器
在 src/index.ts 文件里,我们引入所需模块,并创建一个 MCP 服务器实例。
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio'
import { z } from 'zod'
// 1. 创建一个 MCP 服务器实例
const server = new McpServer({
name: 'hello-mcp-server',
version: '1.0.0',
})
console.log('Hello MCP Server is starting...')
// 后面我们会在这里注册工具...
// 4. 选择通信方式并启动服务
// StdioServerTransport 表示通过标准输入输出进行通信,非常适合本地调试
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.log('Server connected to transport. Waiting for requests...')
}
main().catch(console.error)2. 定义并注册一个“工具”
在 MCP 的世界里,一个具体的功能被称为“工具”(Tool)。 我们的第一个工具是 sayHello。
在 src/index.ts 中添加如下代码(放在服务器实例创建之后):
// ... (之前的代码)
// 2. 定义工具的输入和输出 schema
const SayHelloInput = z.object({
name: z.string().describe('The name of the person to greet.'),
})
const SayHelloOutput = z.object({
message: z.string(),
})
// 3. 注册我们的第一个工具
server.registerTool(
'sayHello',
{
title: 'Say Hello',
description: 'Generates a friendly greeting to a person.',
inputSchema: SayHelloInput,
outputSchema: SayHelloOutput,
},
// 这是工具的具体实现逻辑
async (input) => {
const greeting = `Hello, ${input.name}! Welcome to the world of MCP.`
console.log(`Generated greeting: ${greeting}`)
const output = { message: greeting }
// 按照 MCP 规范返回内容
return {
content: [
{
type: 'text',
text: JSON.stringify(output, null, 2),
},
],
structuredContent: output,
}
}
)
// ... (之后的 main 函数)代码解读
- McpServer: 这是 MCP 服务端的核心类,我们通过它来定义服务信息和注册功能。
- registerTool: 此方法用于注册一个工具。它需要三个参数:工具的唯一名称、工具的元数据(描述、输入输出规范等)和工具的异步执行函数。
- Schema 定义: 我们用
zod定义了清晰的输入 (name: string) 和输出 (message: string)。这样做的好处是,AI 模型就能清楚地知道调用这个工具需要什么参数,以及会返回什么结果。 - 实现逻辑: 核心逻辑很简单,只是拼接一个字符串。但请注意返回值的格式,
content字段是一个数组,可以包含多种格式(如文本、图片等),structuredContent则是结构化的 JSON 数据,便于程序处理。 - StdioServerTransport: MCP 支持多种通信协议,
stdio(标准输入输出)是最简单的一种,适合本地开发和与 VS Code 等编辑器集成。
3. 运行与测试
写好代码后,我们该如何测试呢?MCP 生态提供了一个非常方便的调试工具:MCP Inspector。
首先,在 package.json 中添加一个启动脚本:
{
// ...
"scripts": {
"start": "tsx src/index.ts"
}
// ...
}然后在一个终端中启动我们的服务:
npm startnpm startnpm start你会看到 Hello MCP Server is starting... 的输出。
接着,打开另一个终端,运行 MCP Inspector:
npx @modelcontextprotocol/inspector这会在浏览器中打开一个调试界面。选择 "Stdio" 连接方式,然后将我们 npm start 的命令填入 "Command" 输入框中,点击 "Connect"。
连接成功后,你就能在 Inspector 里看到我们注册的 sayHello 工具了。你可以直接在界面上输入参数进行调用,观察返回结果,是不是非常方便?
进阶示例 - 调用外部 API
“Hello World” 只是开胃菜。MCP 强大的地方在于连接真实世界,比如调用一个天气 API。
我们来创建一个 getWeather 工具。
1. 安装 axios
我们需要一个 HTTP 客户端来发送网络请求。这里选用 axios。
npm install axios2. 注册 getWeather 工具
在 src/index.ts 中添加获取天气的工具。
import axios from 'axios'
// ... (其他 import)
// ... (hello-mcp-server 的定义)
// ... (sayHello 工具的注册)
// --- 获取天气的工具 ---
const GetWeatherInput = z.object({
city: z.string().describe('The city name, e.g., Shanghai'),
})
const GetWeatherOutput = z.object({
city: z.string(),
temperature: z.number(),
description: z.string(),
})
server.registerTool(
'getWeather',
{
title: 'Get Weather',
description: 'Fetches the current weather for a specified city.',
inputSchema: GetWeatherInput,
outputSchema: GetWeatherOutput,
},
async ({ city }) => {
try {
// 注意:这里使用 OpenWeatherMap API,实际使用时请替换 'YOUR_API_KEY'
const response = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric`)
const weatherData = response.data
const output = {
city: weatherData.name,
temperature: weatherData.main.temp,
description: weatherData.weather[0].description,
}
return {
content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
structuredContent: output,
}
}
catch (error: any) {
// 错误处理很重要!
const errorMessage = `Failed to get weather for ${city}: ${error.message}`
console.error(errorMessage)
// 同样将错误信息返回给 AI
return { content: [{ type: 'text', text: errorMessage }] }
}
}
)
// ... (main 函数)提醒
上面的代码用到了 OpenWeatherMap 的 API,你需要去官网注册并获取一个免费的 API_KEY 替换 YOUR_API_KEY 才能正常工作。
现在,重新启动服务 (npm start),并刷新 MCP Inspector,你就会看到新的 getWeather 工具。试试查询 "Beijing" 的天气,看看会返回什么结果。
再进一步 - 多模型协调的思考
我们已经实现了两个独立的工具。但如果一个任务需要多个工具协作完成呢?比如,“查询北京的天气,然后用友好地问候语告诉我今天的天气情况”。
这个任务需要两步:
- 调用
getWeather工具获取天气数据。 - 将天气数据作为上下文信息,让 AI 模型生成一段自然语言描述。
这正是 MCP 发挥作用的地方。当一个 AI 客户端(如 VS Code 内的 Copilot)连接到我们的 MCP 服务后,它就能“看到”我们提供的所有工具。当用户提出复杂需求时,AI 模型会自动进行任务拆解 (Task Decomposition):
- AI 分析需求:识别出需要“查询天气”和“生成问候语”。
- 选择工具:发现我们的 MCP 服务提供了
getWeather工具,决定调用它。 - 执行工具:向我们的服务发送请求
getWeather({ city: 'Beijing' })。 - 获取结果:我们的服务返回
{ "city": "Beijing", "temperature": 25, "description": "sunny" }。 - 整合上下文并生成回答:AI 将上一步的结果作为新的上下文信息,结合原始问题,最终生成回答:“你好!北京今天天气晴朗,温度是 25 摄氏度。祝你拥有愉快的一天!”
整个过程对 MCP 服务的开发者而言是透明的。我们只需专注于提供稳定、原子化的工具,而工具的编排和协调则交给了上层的 AI 模型。
这正是 MCP “关注点分离” (Separation of Concerns) 设计思想的体现。
总结
恭喜你!到这里,你已经成功构建了一个最小化但包含核心功能的 MCP 服务。我们回顾一下关键点:
- MCP 的核心价值:为 AI 和外部世界提供了一套标准的“连接器”,大大简化了 AI 应用的开发。
- 核心 SDK:使用
@modelcontextprotocol/sdk,通过McpServer类来创建服务,并通过registerTool来暴露能力。 - 工具 (Tool):是 MCP 服务功能的最小单元。一个设计良好的工具应该功能单一、职责明确,并使用
zod定义清晰的输入输出。 - 通信 (Transport):我们使用了
StdioServerTransport,它非常适合本地开发和IDE集成。 - 调试:
@modelcontextprotocol/inspector是我们开发过程中的好伙伴。 - 可组合性:我们只需提供独立的工具,复杂的任务编排将由 AI 模型自动完成。
当然,MCP 的世界远不止于此,还包括更复杂的概念如资源 (Resources)、提示 (Prompts)、身份验证、流式 HTTP 传输等。 但今天,你已经迈出了至关重要的一步。
参考资料:
