前言
Model Context Protocol(MCP)是 Anthropic 在 2024 年末推出的开放协议,旨在标准化 AI 助手(如 Claude、GitHub Copilot)与外部工具/数据源之间的通信方式。简单来说,MCP 让你的 AI 助手不再只是"聊天",而是可以真正调用你的业务系统。
本文将带你从零开始,用 TypeScript 实现一个完整的 MCP Server,功能包括:
- 搭建一个本地 Mock REST API(模拟图书馆管理系统)
- 用 TypeScript 实现 MCP Server,注册多个业务工具(Tools)
- 支持 stdio 模式连接 Claude Desktop
- 支持 https 模式连接 Claude Desktop
- 支持 HTTP+SSE 模式连接 GitHub Copilot(VS Code)
- 通过自然语言对话完成借书、还书、查询等操作
一、什么是 MCP?
MCP(Model Context Protocol)协议定义了以下核心概念:
| 概念 | 说明 |
|---|---|
| Server | 提供工具/资源的服务端,即我们要实现的部分 |
| Client | AI 助手(Claude、Copilot),发起工具调用 |
| Tool | 一个可被 AI 调用的函数,有名称、描述和参数 Schema |
| Transport | 通信方式:stdio(本地子进程)或 HTTP+SSE(远程) |
工作流程如下:
用户自然语言输入
↓
AI 助手(Client)
↓ ListTools(发现可用工具)
MCP Server
↓ CallTool(执行工具)
业务逻辑 / REST API
↓ 返回结果
AI 助手(生成回答)
↓
用户看到结果
二、项目结构
mcp-demo/
├── src/
│ ├── server.ts # MCP Server 核心:注册工具 + 处理调用
│ ├── apiClient.ts # 封装对 Mock REST API 的 HTTP 请求
│ └── mock-api.ts # Mock REST API Server(Express)
├── .env.example
├── .vscode/
│ └── mcp.json # GitHub Copilot MCP 配置
├── package.json
└── tsconfig.json
三、搭建 Mock REST API
我们用 Express 搭建一个图书馆管理系统,提供以下端点:
GET /api/books 获取所有书籍
GET /api/books/search?q= 搜索书籍
GET /api/books/:id 书籍详情
POST /api/books 新增书籍
GET /api/users 获取用户列表
GET /api/users/:id/borrows 用户借阅记录
POST /api/borrows 借阅书籍
PUT /api/borrows/:id/return 归还书籍
GET /api/stats 统计信息
Mock 数据包含 7 本书和 4 个用户,完整代码见 src/mock-api.ts。
关键代码片段(借阅接口):
typescript
app.post('/api/borrows', (req: Request, res: Response) => {
const { userId, bookId } = req.body;
const book = books.find(b => b.id === bookId);
if (book!.availableCopies <= 0) {
res.status(400).json({ success: false, error: '该书籍当前无可借副本' });
return;
}
book!.availableCopies -= 1;
// 创建借阅记录,设置30天归还期限
const due = new Date();
due.setDate(due.getDate() + 30);
const record: BorrowRecord = { ... };
res.status(201).json({ success: true, data: record, message: `借阅成功,请于 ${record.dueDate} 前归还` });
});
四、实现 MCP Server
4.1 安装依赖
bash
npm init -y
npm install @modelcontextprotocol/sdk axios express dotenv
npm install -D typescript ts-node @types/node @types/express cross-env
@modelcontextprotocol/sdk 是官方 SDK,提供 Server、StdioServerTransport、SSEServerTransport 等核心类。
4.2 tsconfig.json
json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
4.3 创建 MCP Server 实例
核心是 createMcpServer() 函数,创建 Server 实例并注册两个 Handler:
typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
function createMcpServer(): Server {
const srv = new Server(
{ name: '图书馆MCP助手', version: '1.0.0' },
{ capabilities: { tools: {} } },
);
// Handler 1:返回工具列表
srv.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_books',
description: '获取图书馆所有书籍列表',
inputSchema: { type: 'object', properties: {}, required: [] },
},
{
name: 'search_books',
description: '按书名或作者关键词搜索书籍',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
category: { type: 'string', description: '(可选)分类过滤' },
},
required: ['query'],
},
},
// ... 更多工具
],
}));
// Handler 2:执行工具调用
srv.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args = {} } = req.params;
try {
let result: object;
switch (name) {
case 'list_books':
result = await listBooks();
break;
case 'search_books':
result = await searchBooks(args['query'] as string, args['category'] as string);
break;
// ... 其他 case
}
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
} catch (err) {
return { content: [{ type: 'text', text: `调用失败: ${err}` }], isError: true };
}
});
return srv;
}
关键设计 :将
createMcpServer()提取为独立函数,使 stdio 和每个 SSE session 都能复用同一套 Handler 逻辑------这与tamale-mcp-ts的设计思路一致。
4.4 双 Transport 模式
typescript
const TRANSPORT = process.env['TRANSPORT'] || 'stdio';
if (TRANSPORT === 'http') {
// HTTP + SSE 模式
const app = express();
const transports: Record<string, SSEServerTransport> = {};
// 每个 SSE 连接创建独立的 Server 实例
app.get('/sse', async (req, res) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
const srv = createMcpServer();
await srv.connect(transport);
});
app.post('/messages', async (req, res) => {
const transport = transports[req.query['sessionId'] as string];
await transport.handlePostMessage(req, res);
});
app.listen(PORT);
} else {
// stdio 模式
const srv = createMcpServer();
const transport = new StdioServerTransport();
await srv.connect(transport);
}
五、连接 Claude Desktop
5.1 构建项目
bash
npm run build
# 生成 dist/server.js
5.2 配置 Claude Desktop
找到配置文件:
- macOS :
~/Library/Application Support/Claude/claude_desktop_config.json - Windows :
%APPDATA%\Claude\claude_desktop_config.json
添加以下配置:
json
{
"mcpServers": {
"library-assistant": {
"command": "node",
"args": ["E:/Alex/dev/git/mcp-demo/dist/server.js"],
"env": {
"TRANSPORT": "stdio",
"MOCK_API_BASE_URL": "http://localhost:4000"
}
}
}
}
5.3 启动并测试
- 先启动 Mock API:
npm run mock-api - 重启 Claude Desktop
示例对话:
用户:图书馆现在有多少书?有没有TypeScript相关的?
Claude:
图书馆共有7种书目,23本书。
关于TypeScript,有《TypeScript编程》by Boris Cherny(2021年),
目前还有1本可借,书号 b004。需要帮你借阅吗?
六、连接 GitHub Copilot(VS Code)
6.1 启动 HTTP 模式
bash
npm run dev:http
# [MCP Server] 图书馆MCP助手已启动 (HTTP+SSE 模式)
# [MCP Server] SSE 端点: http://localhost:3100/sse
6.2 配置 .vscode/mcp.json
在项目根目录创建 .vscode/mcp.json:
json
{
"servers": {
"library-assistant": {
"type": "http",
"url": "http://localhost:3099/mcp"
}
}
}
6.3 在 GitHub Copilot 中使用
- 打开 VS Code,进入 Copilot Chat (
Ctrl+Shift+I) - 切换到 Agent 模式(点击对话框左下角的模式选择器)
- 可以看到
library-assistant的工具列表
示例对话:
用户:@library-assistant 用户u001有哪些借阅记录?
Copilot:
用户张三(u001)的借阅记录:
当前借阅:
- 《深入理解计算机系统》(br001) --- 借于 2026-03-10,应还 2026-04-10
历史记录:
- 《JavaScript高级程序设计》(br002) --- 已于 2026-02-25 归还
七、完整演示
以下是一段完整的自然语言对话示例:
用户:图书馆有哪些前端开发相关的书?
AI:(调用 search_books,category="前端开发")
找到2本前端开发书籍:
1. 《JavaScript高级程序设计》- Nicholas C. Zakas(2020)可借2本
2. 《TypeScript编程》- Boris Cherny(2021)可借1本
用户:帮用户u003借TypeScript编程这本书
AI:(调用 borrow_book,user_id="u003", book_id="b004")
借阅成功!《TypeScript编程》已为王五(u003)借出。
借阅日期:2026-04-12,请于 2026-05-12 前归还。
借阅记录号:br006
用户:现在图书馆的统计数据怎么样?
AI:(调用 get_stats)
图书馆概况:
- 书目总数:7种,23本
- 当前可借:16本
- 借出中:7本
- 注册用户:4人
- 活跃借阅:6笔
八、测试效果验证 ------ 参考 Prompt
服务启动后,可以把下面这组 Prompt 依次发给 AI 助手,覆盖所有 9 个 MCP 工具,快速验证整条链路是否正常。
8.1 单工具测试 Prompt
| # | 目标工具 | 测试 Prompt |
|---|---|---|
| 1 | list_books |
图书馆现在有哪些书?帮我列出全部书籍。 |
| 2 | search_books(关键词) |
帮我找找有没有关于 Vue 或 React 的书。 |
| 3 | search_books(分类过滤) |
我想看人工智能方向的书,有什么推荐? |
| 4 | get_book |
帮我查一下书籍 b003 的详细信息,包括简介和 ISBN。 |
| 5 | add_book |
请帮图书馆新增一本书:《深入理解 TypeScript》,作者是张三,分类前端开发,出版年份 2024,共 2 本。 |
| 6 | list_users |
图书馆目前有哪些注册用户?帮我列出来。 |
| 7 | get_user_borrows |
用户 u002 借了哪些书?有没有还没还的? |
| 8 | borrow_book |
我是用户 u001,想借书籍 b005,帮我办理借阅。 |
| 9 | return_book |
帮我归还借阅记录 br001 对应的书籍。 |
| 10 | get_stats |
图书馆现在总共有多少本书、多少用户、借出去了几本?给我看看整体统计。 |
8.2 完整流程串联 Prompt
先查一下图书馆统计,然后搜索"软件工程"相关的书,找到后帮用户 u001 借阅其中一本,最后再看看他的借阅记录。
这条 Prompt 会触发 AI 依次调用 get_stats → search_books → borrow_book → get_user_borrows,完整走通一次借书流程,适合演示和冒烟测试。
九、连接 Claude Desktop(HTTPS 隧道)
Claude Desktop 的「Add custom connector」要求 URL 必须以 https:// 开头,但我们本地的 MCP Server 是普通 HTTP。
解决思路:用 SSH 反向隧道,让 localhost.run 服务器负责 HTTPS 加密,流量经隧道转发到本地 HTTP 服务:
Claude.ai
│ HTTPS(由 localhost.run 提供证书和加密)
▼
localhost.run 服务器
│ SSH 反向隧道(加密 SSH 通道内转发明文 HTTP)
▼
localhost:3099 ← 本地 MCP Server,只需普通 HTTP
关键理解 :本地服务只需跑 HTTP,不需要配置任何 SSL 证书。HTTPS 由 localhost.run 在公网侧提供,你的代码无需任何改动。
9.1 三步启动(需三个终端同时运行)
终端 1 --- 启动 Mock REST API(图书馆数据,监听 :4000)
bash
npm run mock-api
# Mock Library API 已启动: http://localhost:4000
终端 2 --- 启动 MCP Server(监听 :3099,处理 Claude 的工具调用)
bash
npm run dev:http
# [MCP Server] Streamable HTTP : http://localhost:3099/mcp ← Claude.ai Web
# [MCP Server] SSE (旧式) : http://localhost:3099/sse ← GitHub Copilot
终端 3 --- 建立 SSH 隧道(将 localhost:3099 暴露为公网 HTTPS)
powershell
# 基础用法(免费,每次 URL 随机)
ssh -o ServerAliveInterval=60 -R 80:localhost:3099 localhost.run
# 如已生成专属 Key(可获得固定域名)
ssh -o ServerAliveInterval=60 -i "$env:USERPROFILE\.ssh\id_localhost_run" -R 80:localhost:3099 localhost.run
隧道建立后终端输出:
Connect to https://74a517ac28596c.lhr.life
9.2 在 Claude Desktop 中添加 Connector
-
打开 [claude desktop] → 左上角 → Customize → Connectors
-
点击 Add custom connector
-
URL 填入(注意末尾是
/mcp,使用 Streamable HTTP 协议):https://74a517ac28596c.lhr.life/mcp -
点击保存,对话框中即可看到图书馆工具
注意:免费版 localhost.run 的 URL 每次重启 SSH 会变化,需重新在 Claude Desktop 中更新地址。
9.3 备选方案:ngrok
如不方便使用 localhost.run,也可以用 ngrok(需安装):
bash
scoop install ngrok # Windows
brew install ngrok # macOS
ngrok http 3099
# Forwarding https://a1b2-123-45-67-89.ngrok-free.app -> http://localhost:3099
| 对比 | localhost.run | ngrok |
|---|---|---|
| 需要安装 | ❌ 不需要(用系统 SSH) | ✅ 需要安装 |
| 免费版限制 | 隧道不定期断开 | 有连接数/速率限制 |
| 固定域名 | 付费版支持 | 付费版支持 |
| 协议支持 | HTTP/HTTPS | HTTP/HTTPS/TCP |
十、关键设计要点
1. Tool 的 description 要写清楚
AI 依赖 description 字段决定何时调用哪个工具,写得越清楚,AI 的判断越准确:
typescript
{
name: 'search_books',
// ✅ 好的描述:说明用途、参数含义、返回内容
description: '按书名或作者关键词搜索书籍,可选择按分类过滤。返回匹配书籍列表。',
// ❌ 差的描述
// description: '搜索',
}
2. 错误处理要返回 isError: true
typescript
} catch (err) {
return {
content: [{ type: 'text', text: `调用失败: ${err}` }],
isError: true, // 告诉 AI 这是错误,需要处理
};
}
3. stdio vs HTTP+SSE 选择
| 场景 | 推荐 Transport |
|---|---|
| Claude Desktop 本地使用 | stdio |
| GitHub Copilot / VS Code | http(SSE) |
| 多用户 / 远程部署 | http(加 API Key 鉴权) |
十一、总结
本文实现了一个完整的 MCP Server Demo,核心步骤:
- Mock REST API:用 Express 模拟业务接口
- API Client:封装 HTTP 调用
- MCP Server :注册工具列表(
ListToolsRequestSchema)+ 处理调用(CallToolRequestSchema) - 双 Transport :
stdiofor Claude Desktop,HTTP+SSEfor GitHub Copilot - 配置文件 :
claude_desktop_config.json+.vscode/mcp.json
MCP 的核心价值在于:让 AI 从"聊天机器人"变成真正能操作你系统的自动化助手,而你只需要实现标准的工具接口即可。
十二、程序运行截图
Claude Desktop




Github Copilot:



十三、源码
MCP_Demo_Code 已上传