
说实话,我关注MCP不是因为它火,而是因为我真的受够了。
受够了什么?受够了那些所谓的"集成代码"——一堆临时拼凑的脚本,看似聪明,实则脆弱得一碰就碎。每次同事问我:"能不能让AI安全地读取内部数据,但不用重写半个后端?",我就知道又要开始一场艰难的架构讨论。
直到我真正理解了MCP,才发现这不是什么时髦的新框架,而是一个早就该存在的抽象层。就像REST让服务和服务对话,MCP让模型和系统对话——这个类比一旦想通,其他问题就迎刃而解了。
这篇文章不谈理论,不讲故事,只讲怎么用TypeScript搭建你的第一个MCP服务器,以及我踩过的所有坑。
在动手之前,咱们得澄清一个误区。
MCP(Model Context Protocol)不是:
MCP是一份契约——一份非常固执己见的契约。
它定义了工具(Tools)、资源(Resources)和提示词(Prompts)如何以可预测、可检查、可审计的方式暴露给模型。
打个比方:REST让微服务之间能说话,MCP让AI模型和你的业务系统能说话。
如果这个概念还不清楚,建议先去看官方规范(https://modelcontextprotocol.io),看一遍就够了。看完回来,我们接着聊。
能用Python写MCP服务器吗?当然。
能用Rust写吗?没问题。
但为什么我强烈推荐TypeScript?
理由很简单:
更重要的是,大部分MCP服务器都是集成密集型,而非计算密集型。TypeScript在这种场景下如鱼得水。
在写代码之前,你必须搞懂这个核心概念:
一个MCP服务器暴露三样东西:
┌─────────────────────────────────────┐
│ MCP Server 核心 │
├─────────────────────────────────────┤
│ │
│ Resources ← 只读数据 │
│ (查询配置、读取文档) │
│ │
│ Tools ← 有副作用的动作 │
│ (创建记录、发送请求) │
│ │
│ Prompts ← 结构化的推理引导 │
│ (摘要模板、分析框架) │
│ │
└─────────────────────────────────────┘
就这三样,别的没了。
如果你试图模糊这三者的边界,你的服务器会变成一团乱麻。
记住这个原则:
大声说出来,这很重要。
mkdir my-first-mcp-server
cd my-first-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
对,安装zod。等会你就知道为什么了。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "my-first-mcp-server",
version: "1.0.0",
});
对于本地开发和大多数工具场景:
const transport = new StdioServerTransport();
await server.connect(transport);
就这样,你的服务器起来了。
没有HTTP,没有Express,没有废话。
咱们暴露点无聊的东西——这是故意的。
server.resource(
"config",
"config://app",
async () => ({
name: "Demo App",
environment: "development",
})
);
这个Resource:
模型喜欢无聊、可预测的数据。你也应该喜欢。
Tools很强大,这意味着它们很危险。
来看一个好的Tool示例:
import { z } from"zod";
server.tool(
"create_note",
{
title: z.string(),
body: z.string(),
},
async ({ title, body }) => {
// 这里写入数据库
return { success: true };
}
);
为什么这个Tool设计得好?
现实警告:
如果你的Tool:
……那你就是在造脚射神器。
假设你在做一个内部工具,需要让AI助手能够查询公司的Jira工单。
错误做法:
// ❌ 危险!没有任何输入验证
server.tool(
"query_jira",
{},
async (params: any) => {
// 直接拼接SQL或者API查询,等着被注入吧
return await jiraApi.search(params.query);
}
);
正确做法:
// ✅ 安全且可靠
server.tool(
"query_jira",
{
project: z.string().max(20),
status: z.enum(["open", "in_progress", "done"]),
assignee: z.string().email().optional(),
},
async ({ project, status, assignee }) => {
// 类型安全的查询,有明确的参数范围
returnawait jiraApi.search({
jql: `project = ${project} AND status = ${status}${
assignee ? ` AND assignee = ${assignee}` : ""
}`,
});
}
);
看到区别了吗?Schema就是你的防火墙。
这是新手最容易低估MCP的地方。
Prompts是接口,不是文本块。
server.prompt(
"summarize_notes",
{
notes: z.string(),
},
({ notes }) => ({
messages: [
{
role: "system",
content: "你是一位精准的技术写作专家。",
},
{
role: "user",
content: `请总结以下笔记:\n${notes}`,
},
],
})
);
你不是在告诉模型该想什么,你是在给它设置护栏。
假设你在做一个代码审查助手,需要让AI按照团队规范检查代码:
server.prompt(
"code_review",
{
code: z.string(),
language: z.enum(["typescript", "javascript", "python"]),
focus: z.enum(["performance", "security", "style"]).optional(),
},
({ code, language, focus = "style" }) => ({
messages: [
{
role: "system",
content: `你是一位经验丰富的${language}代码审查专家。
审查重点:${focus}
- 如果是performance,关注性能瓶颈和优化机会
- 如果是security,关注安全漏洞和潜在风险
- 如果是style,关注代码风格和最佳实践
请给出具体的改进建议,并标注严重程度(critical/major/minor)。`,
},
{
role: "user",
content: `\`\`\`${language}\n${code}\n\`\`\``,
},
],
})
);
这样设计的好处:
如果你不测试MCP服务器,模型会替你测试。
而且它们毫不留情。
使用官方的MCP inspector: https://modelcontextprotocol.io/docs/tools/inspector
检查这些:
如果你自己都觉得困惑,模型会更困惑。
┌──────────────┐
│ 1. 启动服务器 │
└──────┬───────┘
│
▼
┌──────────────────┐
│ 2. 用inspector │
│ 连接并检查 │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ 3. 调用每个Tool │
│ 尝试边界输入 │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ 4. 检查错误处理 │
│ 和返回格式 │
└──────────────────┘
MCP不会替你保护数据。
你必须:
MCP让访问变得结构化,但不会让访问默认安全。这是你的活。
假设你在为团队搭建一个能访问数据库的AI助手:
❌ 危险做法:
// 千万别这么干!
server.tool(
"execute_sql",
{
query: z.string(),
},
async ({ query }) => {
// 直接执行任意SQL?这是在找死
return await db.raw(query);
}
);
✅ 安全做法:
// 预定义的安全查询
const ALLOWED_QUERIES = {
user_count: "SELECT COUNT(*) FROM users WHERE active = true",
recent_orders: "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days' LIMIT 100",
product_stats: "SELECT category, COUNT(*) FROM products GROUP BY category",
} asconst;
server.tool(
"execute_predefined_query",
{
query_name: z.enum(Object.keys(ALLOWED_QUERIES) as [string, ...string[]]),
},
async ({ query_name }) => {
// 只能执行预定义的查询
returnawait db.raw(ALLOWED_QUERIES[query_name]);
}
);
关键原则:永远不要相信模型的输入,哪怕它看起来很"智能"。
说实话,别盲目用MCP。
不要用MCP如果:
MCP适合的场景:
在我们团队上线第一个MCP服务器之后:
✅ 集成变得无聊了(这是好事) ✅ Tool行为变得可预测 ✅ Prompt混乱大幅下降 ✅ Debug不再像占卜
这才是真正的价值。不是炒作,是杠杆。
某电商团队用MCP搭建了一个订单管理助手:
之前的做法:
用MCP重构后:
订单查询 → MCP Resource (只读,安全)
订单创建 → MCP Tool (有验证,有日志)
订单分析 → MCP Prompt (标准化输出)
结果:
MCP不会让你的产品变魔法。
但它会让你的AI集成少犯蠢。
说实话?这就是大多数团队现在需要跨越的门槛。
如果这篇文章帮你省了一小时,欢迎在评论区聊聊你的看法。
如果省了一天,点个赞或转发吧。
如果它让你避免了上线一个灾难,那我有个好消息:
我准备送出3本《精通MCP:AI智能体开发实战》给大家!
这本书由拥有10余年一线大厂经验的资深专家陈光剑撰写,从MCP理论基础到实战应用,一本书搞定AI智能体开发的所有难题。

image
书中包含:

image
特别适合:
快速购买:
抽奖时间: 文章发布后72小时 中奖名额: 3位幸运读者 评选标准: 留言质量
最后的最后:
MCP不是银弹,但它是一个久违的正确抽象。
如果你已经在被集成代码折磨,是时候试试MCP了。
如果你还在观望,建议先跑通这篇文章的例子,然后做个小项目试试水。
记住:好的工具不会让你的系统变复杂,它会让复杂度变得可管理。
期待在评论区看到你的思考!
对MCP还有疑问?想深入交流?欢迎在评论区留言,我会认真回复每一条评论。
记得点赞👍 + 转发,让更多开发者少走弯路!