如何搭建自己的 MCP Server(完整教程)
从零开始,30 分钟实现一个可用于生产的自定义 MCP Server
如何搭建自己的 MCP Server(完整教程)
从零开始,30 分钟实现一个可用于生产的自定义 MCP Server
市面上有 500+ 现成 MCP Server,但总有一天你会遇到找不到现成 Server 的场景——这时候就需要自己写一个。本教程从零开始,用 TypeScript 实现一个能查询内部 API 的 MCP Server,包含工具注册、错误处理和部署到生产环境的完整步骤。
如何搭建自己的 MCP Server(完整教程)
前置条件:Node.js 18+,有基础的 TypeScript/JavaScript 知识
为什么需要自定义 MCP Server?
现有的 500+ MCP Server 覆盖了大多数通用场景,但你可能需要:
这些场景都需要自己写 MCP Server。好消息是:MCP SDK 设计得非常简洁,写一个基础 Server 只需要 ~50 行代码。
MCP Server 的核心概念
MCP Server 可以暴露三种类型的能力:
本教程专注于最常用的 Tools。
第一步:初始化项目
bash
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
创建 tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true
}
}
更新 package.json:
json
{
"type": "module",
"bin": { "my-mcp-server": "./dist/index.js" },
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts"
}
}
第二步:实现最小化 MCP Server
创建 src/index.ts:
typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';// 1. 创建 Server 实例
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
// 2. 注册工具
server.tool(
'get_weather', // 工具名(AI 会用这个名字调用)
'获取指定城市的实时天气', // 工具描述(AI 用来判断何时调用)
{
city: z.string().describe('城市名称,如"北京"、"上海"')
},
async ({ city }) => {
// 这里调用你的实际 API
const res = await fetch(https://api.weather.example.com/v1/current?city=${city});
const data = await res.json();
return {
content: [{
type: 'text',
text: ${city}当前天气:${data.condition},气温 ${data.temp}°C,湿度 ${data.humidity}%
}]
};
}
);
// 3. 启动 Server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP Server 已启动'); // 注意:日志要用 stderr,stdout 留给协议通信
第三步:添加多个工具(实战示例)
以下是一个查询公司内部数据的实战示例:
typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';const server = new McpServer({
name: 'company-internal-api',
version: '1.0.0',
description: '公司内部 API 集成 MCP Server'
});
const API_BASE = process.env.INTERNAL_API_URL ?? 'https://api.company.internal';
const API_TOKEN = process.env.INTERNAL_API_TOKEN ?? '';
// 辅助函数:带认证的 fetch
async function apiFetch(path: string, options?: RequestInit) {
const res = await fetch(${API_BASE}${path}, {
...options,
headers: {
'Authorization': Bearer ${API_TOKEN},
'Content-Type': 'application/json',
...options?.headers
}
});
if (!res.ok) throw new Error(API 错误 ${res.status}: ${await res.text()});
return res.json();
}
// 工具 1:查询客户信息
server.tool(
'get_customer',
'根据客户 ID 或姓名查询客户详细信息',
{
query: z.string().describe('客户 ID(如 C001)或客户姓名'),
include_orders: z.boolean().optional().default(false).describe('是否包含最近订单')
},
async ({ query, include_orders }) => {
const customer = await apiFetch(/customers/search?q=${encodeURIComponent(query)});
let result = `客户信息:
姓名:${customer.name}
公司:${customer.company}
联系方式:${customer.email} / ${customer.phone}
客户等级:${customer.tier}
注册时间:${customer.created_at}`; if (include_orders && customer.id) {
const orders = await apiFetch(/customers/${customer.id}/orders?limit=5);
result += `
最近 5 笔订单:
${orders.map((o: any) =>
- #${o.id} ${o.created_at}: ${o.amount} 元(${o.status})
).join('
')}`;
}
return { content: [{ type: 'text', text: result }] };
}
);
// 工具 2:查询库存
server.tool(
'check_inventory',
'查询指定商品的库存数量和预计到货时间',
{
sku: z.string().describe('商品 SKU 编号'),
warehouse: z.enum(['beijing', 'shanghai', 'guangzhou', 'all']).default('all').describe('查询的仓库')
},
async ({ sku, warehouse }) => {
const inventory = await apiFetch(/inventory/${sku}?warehouse=${warehouse});
const lines = warehouse === 'all'
? inventory.warehouses.map((w: any) => - ${w.name}:${w.stock} 件)
: [- 当前库存:${inventory.stock} 件];
return {
content: [{
type: 'text',
text: `商品 ${sku} 库存情况:
${lines.join('
')}
总库存:${inventory.total} 件
预计补货日期:${inventory.restock_date ?? '暂无计划'}`
}]
};
}
);
// 工具 3:创建工单
server.tool(
'create_ticket',
'创建客服工单,适合需要人工跟进的问题',
{
customer_id: z.string().describe('客户 ID'),
category: z.enum(['refund', 'delivery', 'product', 'other']).describe('工单类型'),
description: z.string().min(10).describe('问题描述(至少 10 个字)'),
priority: z.enum(['low', 'normal', 'high', 'urgent']).default('normal')
},
async ({ customer_id, category, description, priority }) => {
const ticket = await apiFetch('/tickets', {
method: 'POST',
body: JSON.stringify({ customer_id, category, description, priority })
});
return {
content: [{
type: 'text',
text: `✅ 工单创建成功!
工单号:${ticket.id}
优先级:${priority}
预计响应时间:${ticket.sla_deadline}
负责人:${ticket.assigned_to ?? '待分配'}`
}]
};
}
);const transport = new StdioServerTransport();
await server.connect(transport);
第四步:错误处理最佳实践
typescript
// ✅ 好的错误处理:给 AI 友好的错误信息
server.tool('get_customer', '...', { query: z.string() }, async ({ query }) => {
try {
const data = await apiFetch(/customers/search?q=${query});
if (!data || data.length === 0) {
return {
content: [{ type: 'text', text: 未找到匹配"${query}"的客户。请检查拼写或尝试其他关键词。 }],
isError: true
};
}
return { content: [{ type: 'text', text: formatCustomer(data[0]) }] };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return {
content: [{ type: 'text', text: 查询失败:${message}。请稍后重试。 }],
isError: true // 标记为错误,AI 会知道这次调用失败了
};
}
});
第五步:在 Claude Desktop 中测试
npm run build~/Library/Application Support/Claude/claude_desktop_config.json):json
{
"mcpServers": {
"company-api": {
"command": "node",
"args": ["/绝对路径/my-mcp-server/dist/index.js"],
"env": {
"INTERNAL_API_URL": "https://api.company.internal",
"INTERNAL_API_TOKEN": "your-token-here"
}
}
}
}
第六步:发布为 npm 包(可选)
如果你想让团队成员也能用:
bash
修改 package.json
{
"name": "@your-org/mcp-server-company",
"version": "1.0.0",
"bin": { "mcp-server-company": "./dist/index.js" }
}npm publish --access restricted # 发布到私有 npm Registry
团队成员配置:
json
{
"mcpServers": {
"company-api": {
"command": "npx",
"args": ["@your-org/mcp-server-company"],
"env": { "INTERNAL_API_TOKEN": "..." }
}
}
}
生产部署注意事项
console.error 记录到 stderr,便于调试常见问题
Q:MCP Server 必须用 TypeScript 吗?
A:不是,官方提供了 Python SDK(mcp 包),Go SDK 也在开发中。
Q:MCP Server 能处理并发请求吗? A:stdio 模式是串行的(一次一个请求)。高并发场景可以用 SSE(Server-Sent Events)模式。
Q:如何调试 MCP Server? A:推荐用 MCP Inspector,官方提供的可视化调试工具,可以手动测试工具调用。
bash
npx @modelcontextprotocol/inspector dist/index.js
打开浏览器访问 http://localhost:5173
相关工具