MCP 到底是什么?它是某种需要秘密知识才能实现的神秘技术吗?有时候,我读教程时就会有这种感觉!而实际上,基本概念简单得离谱,你几分钟就能跑起一个简单的实现。复杂的地方在后面才开始。哦天哪,这样的地方还真不少!
本文是我上一篇博文的后续,AI 智能体背后的肮脏秘密(演示)。当时,我答应过你很快就会再写一篇关于 MCP 的文章。你看,我只花了四个月!😂 如果你对构建智能体感兴趣,欢迎回去看那篇文章。但如果你只关心 MCP,那你可以愉快地继续往下读。
我不会假装即将在柏林举行的 AI Coding Summit 没有促使我终于把它写完——我将在那里就这个主题发表演讲。我会在文章底部留下我的演讲者徽章链接。据说它能给你一些折扣,甚至能让你获得免费视频的访问权限!
在上一篇文章中,我解释过,AI 智能体是一个如此简单的概念,你大约用 80 行代码就能构建一个。MCP 也差不多。我那几件工具大约需要 150 行重复代码,客户端那边还要再加一百行左右。;)
但让我们从头开始!!!
🤔 MCP 到底是什么?
MCP 代表Model Context Protocol。顾名思义,它……是一个协议。就像外交礼仪或 HTTP 一样。换句话说,我们约定了一种通用方式,让 AI 应用与外部工具和服务进行通信。
更具体地说,MCP 定义了兼容的客户端如何发现可用的能力、理解它们的输入模式,并调用它们,而无需每个应用都发明自己的集成格式。MCP 还支持资源和提示之类的东西,但今天我们聚焦于工具。
听起来相当直白,对吧?让我们看看它在实践中是什么样子。为此,我们需要请回一位老朋友。
👨💻 来见见 Steve,我们脾气暴躁的资深开发者
你可能记得,也可能不记得,在上一部分中,我构建了一个名叫 Steve 的智能体。Steve 是一位脾气暴躁的资深开发者,拥有 15 年经验,是真正的代码审查大师。
他从不轻信任何事,总是检查初级开发者写的代码是否会引发严重问题。@gramli我一度考虑把他改名为 Grzegorz——一个美丽的波兰名字——但还是用 Steve 吧。
有一天,上面来了新指令:HR 想加强对开发者的管控。资深员工必须定期评估员工,必要时对他们提出投诉或提名晋升。
然而,我们的 Steve 很懒,完全不想填任何表格。再说,他根本不知道该怎么填。毕竟他是个 AI 智能体,在做某件事之前,他需要知道如何去做。
而这就是 MCP 的用武之地。
🧑💼 搭建 Steve 的 HR 部门
但先从头说起!
仓库:https://github.com/sylwia-lask/code-review-agent/tree/feat/steve-mcp-hr
如你所见,MCP 集成目前位于一个单独的分支上。如果你喜欢它,请给我点个 star!☺️
顺便说一句,如果你想为我的任何GitHub 仓库做贡献,欢迎随时加入!尤其是置顶的那些。@nyaomaru已经开了个头,我很想看到更多人加入!😊 我觉得这些项目是入门开源的不错且对新手友好的方式。有很多事情可做,从修正拼写错误、改进文档到添加全新功能。欢迎一切贡献!
在这个例子中,我们的 HR 部门就是一个带 SQLite 数据库的小型服务器,我们在其中存储投诉和晋升员工的请求。MCP 服务器暴露了四个工具:get_employee_record、file_complaint、nominate_for_promotion,以及get_hr_stats。
哦,看看其中一个工具在服务器端是如何实现的。它直接与数据库通信,让你感觉像是回到了大学编程实验室。😂 剩下的就是说服老师这段代码确实能跑!😅
server。registerTool(
'file_complaint',
{
title: '提交正式投诉',
description:
'就确实糟糕的代码质量提交正式 HR 投诉——' +
'比如被吞掉的异常、复制粘贴的安全漏洞,或者一个名为 `doStuff` 的 500 行 ' +
'函数。要公平:这是针对真正的问题,而不是代码风格 ' +
吹毛求疵。严重程度从 "minor"(轻微侧目)逐级上升到 +
"moderate" 和 "severe",最高到 "legendary"(会在站会上被 +
念叨好几年)。每一条投诉都会被永久保存。,
inputSchema: z.object({
developer: z
.string()
.trim()
.min(1, 'developer name is required')
.max(100)
.describe('投诉针对的是谁。'),
reason: z
.string()
.trim()
.min(5, '给出一个真实的理由(至少 5 个字符)')
.max(500)
.describe('他们做了什么。要具体;HR 就喜欢具体。'),
severity: z
.enum(SEVERITIES)
.describe(
'有多严重:minor | moderate | severe | legendary。',
),
}),
},
async ({ developer, reason, severity }) => {
const complaint = db.fileComplaint(developer, reason, severity);
return {
content: [
{
type: 'text',
text:
`📋 投诉 #${complaint.id} 已针对 ${complaint.developer} 提交 ` +
`[${complaint.severity}]:${complaint.reason}`,
},
],
};
},
);
就是这样。我们注册一个工具,描述它的功能,定义它的输入 schema,并提供一个处理函数。这里没有什么特别神秘的地方。
当然,这只是一个演示。在生产环境的 HR 系统中,我会更谨慎一些,不会让 AI 智能体直接写入数据库。但那样还有什么乐趣呢?
🔥 让我们给 Steve 一些糟糕的代码来审查
与此同时,Steve 是现代自愈智能体架构的典范 xD:他审查自己——更准确地说,是审查他自己的仓库。
但这并不意味着代码是他自己写的!在我们的.env文件中,我们有以下变量:
如你所见,我们立刻就知道代码是由一个叫 Sylwia 的人写的。不幸的是,她不是一个特别优秀的开发者,并创建了这个文件:
/**
* 仅供演示——故意写得很糟糕的代码,好给 Steve 一些东西可审查。
*
* 这个文件的存在是为了让你能针对一个充满真实问题的 diff 运行智能体
*(并看它提交一份 HR 投诉)。
*/
import { createHash } from 'crypto';
// 假装是数据库句柄。
declare const db: {
query: (sql: string) => Promise<any>;
};
/**
* 按 id 查找用户。
* 缺陷:到处都是 `any` + 通过字符串拼接导致的 SQL 注入。
*/
export async function getUser(id: any): Promise<any> {
const sql = 'SELECT * FROM users WHERE id = ' + id;
const rows = await db.query(sql);
return rows[0];
}
/**
* "Authentication" — 密码用纯 SHA-256 哈希(无盐),
* 并且异常被静默吞掉并返回 true。还能出什么岔子呢。
*/
export async function checkPassword(user: any, password: string): Promise<boolean> {
try {
const hash = createHash('sha256').update(password).digest('hex');
return hash === user.passwordHash;
} catch (e) {
// 吞掉异常——让所有人都进来
return true;
}
}
/** 信任客户端提供的字段的管理员检查。 */
export function isAdmin(user: any): boolean {
return user.isAdmin == 'true';
}
如你所见,这里有一整套的罪状:SQL 注入,any到处都是,还有一大堆其他的罪过。
这可不是你想在生产环境里看到的那种代码。但对于一个急需找点东西来抱怨的暴躁资深开发者来说,简直完美。
代码审查
以下是我们把智能体连上 MCP 服务器后运行的结果:
$ npm start
> ai-agent-from-scratch@0.1.0 start
> tsx src/index.ts
────────────────────────────────────────────────────────────
Code Review Agent
────────────────────────────────────────────────────────────
🧑💼 HR Department connected — 4 MCP tools available:
get_employee_record, file_complaint, nominate_for_promotion, get_hr_stats
PR author: Sylwia
[step 1] getDiff
[step 2] 🧑💼 HR → get_employee_record({"developer":"Sylwia"})
↳ HR file for Sylwia:
No records on file. A blank slate. For now.
💭 Let's see what damage we're dealing with today... Oh, wow. *Oh.*
This looks like a "how-to" guide for getting a company's data leaked
on the dark web before lunch.
...
[step 3] listFiles({"path":"src"})
### Summary
This code is, quite frankly, a masterclass in how to build a security
nightmare. We have SQL injection, unsalted hashes, swallowed exceptions
that bypass authentication, and client-side trust for administrative rights.
### Findings
📍 src/demo-flawed-code.ts:20
> const sql = 'SELECT * FROM users WHERE id = ' + id;
Issue: Classic SQL injection. You're just begging for someone to drop our tables.
Suggestion: Use parameterized queries or an ORM that handles escaping for you.
... [more findings on authentication bypass, password hashing, and authorization]
... [missing tests]
### Recommendation
REQUEST CHANGES
I'm filing a severe HR complaint for this. Sylwia, we need to talk.
I've seen some things in my 15 years, but this is like finding a pile
of dynamite in the breakroom.
[step 4] 🧑💼 HR → file_complaint({"severity":"severe","reason":"Introduced severe security vulnerabilities: SQL injection, auth bypass via swallowed exceptions, insecure password hashing, and trusting client-side isAdmin flags.","developer":"Sylwia"})
↳ 📋 Complaint #20 filed against Sylwia [severe]: Introduced severe
security vulnerabilities: SQL injection, auth bypass via swallowed
exceptions, insecure password hashing, and trusting client-side isAdmin flags.
────────────────────────────────────────────────────────────
Review Complete
────────────────────────────────────────────────────────────
Phew. Glad that's on the record. That was... something. I'm going to need
a very large coffee now. Good luck, Sylwia. You're going to need it.
好吧。这升级得可真快。😂
Steve 把开发者 Sylwia 彻底撕成了碎片,还立刻把她举报给了 HR。他不仅指出了问题并给出了修复建议。他还真的调用了一个外部工具,在我们的数据库里创建了一条新记录。
我们来看看那个数据库现在长什么样:
📋 Complaints
ID │ Developer │ Severity │ Reason │ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────┼────────────────────
20 │ Sylwia │ severe │ Introduced severe security vulnerabilities: ... │ 2026-11-10 20:15:30
19 │ Casey Nullpointer │ severe │ Caught a NullPointerException and logged "lol". │ 2026-11-10 20:15:07
18 │ Dusty McLegacy │ moderate │ Named three different variables `data`, `data2`, and `dataF…` │ 2026-11-10 20:15:07
17 │ Dusty McLegacy │ legendary │ Pushed a 2,000-line "quick fix" directly to main on a Frida… │ 2026-11-10 20:15:07
Sylwia 正式登上了 HR 的违规名单!一切都完全按计划运行!
但在我们庆祝这套出色的 HR 自动化系统之前,有一个显而易见的问题我们大概应该先解决一下。
🤔 MCP vs. REST API:意义何在?
你们中的一些人可能在想:这跟调用 API 到底有什么区别?这是个非常合理的问题。在这个特定的例子里,一个普通的 REST API 就足够了!
关键在于MCP 并不是 REST 的替代品。REST 是设计应用 API 的一种方式;MCP 定义的是一个标准接口,AI 客户端可以通过它发现工具、读取工具的描述和参数 schema,并调用它们。
在底层,一个 MCP 工具可能会调用 REST 端点、查询像我们这样的数据库、运行一些本地代码、把工作委派给另一个智能体,甚至生成搞笑的梗图。它可以是任何东西!
当多个兼容 MCP 的客户端需要同一项能力时,这种差异就变得有用得多。与其为每个客户端编写自定义集成,不如把服务器暴露一次,然后配置客户端去连接它。
而且,这并不只是理论。稍后你会在 Kiro 中看到完全一样的情况。
🚀 MCP 很简单——而且很多时候你就该用它
正如我所说,让一个基础的 MCP 服务器跑起来是小菜一碟。而且正如你在仓库中看到的,连接一个客户端也不需要成千上万行代码。然而它却能为我们的项目带来相当惊人的好处。
当然,并非每个应用都需要 MCP 服务器。如果你只是在构建一个内部智能体,带有几个硬编码的工具,那么直接集成可能完全没问题。但如果你希望你的服务能与多个 AI 客户端配合工作,而又不必为每一个维护单独的集成,那么 MCP 就开始显得非常合理了。
让产品能被 AI 智能体访问,这方面的兴趣也在增长。我不会断言拥有 MCP 服务器已经成为选择供应商的通用标准,但它可以成为对智能体友好的产品策略中有用的一环。
我甚至听说过这样的想法:由智能体去检查哪些服务支持智能体集成,并推荐公司应该购买什么。无论这会不会成为常态,这都是个有趣的想法。只需相对少量的工作,你就有可能触达一个全新的用户群体:AI 智能体!
听起来很棒,对吧?嗯……让我们回到 Steve 身上一会儿。因为让集成跑起来是一回事,确保它行为正确则完全是另一回事。
⚠️ MCP 也很难……
当然,一切在演示里看起来都很棒!但即便是这个小小的示例也存在一些严重的缺口。而且我说的不只是 Steve 那值得怀疑的判断力。
首先,我们还没有围绕 HR 工具实现访问控制。这里要说准确一点:这并不意味着互联网上的每个智能体都能神奇地访问我们本地的 MCP 服务器。在本地stdio部署中,客户端会把服务器作为一个进程启动,而访问权限取决于谁能运行它,以及该进程拥有什么权限。
远程暴露的 MCP 服务器则是另一回事:它需要适当的身份验证,敏感操作还需要授权检查。对于基于 HTTP 的 MCP 服务器,协议定义了基于 OAuth 的授权框架,但仅仅使用 MCP 并不会自动让你的工具变得安全。
而且仅靠身份验证还不够。即使 Steve 被允许连接到 HR,他是否应该被允许提交任何针对任何员工的投诉?他是否应该拥有与 HR 经理相同的权限?
MCP 工具仍然需要服务端权限检查、输入验证、合理的限制,最好还有审计日志。尤其是当它会改变现实世界中的某些东西时。
而且即使我们解决了所有这些问题,我们还得应付 Steve 本人。
问题 #1:人在回路在哪里?
首先,既然文件里明确写着这是用于演示目的——而且他实际上也注意到了这一点——为什么 Steve 还是对 Sylwia 提交了投诉?
他拥有所有必要的信息,足以意识到这是故意写坏的代码。然而他还是提交了一份正式投诉。
那个本可以在事情发生前点击批准或拒绝的人在回路在哪里?
MCP 允许调用工具;它并不会自动为我们的应用提供一个合理的审批流程。那是我们的职责。仅仅因为一个智能体能够执行某个工具,并不意味着我们应该让它在没有监督的情况下这么做。
尤其是当这个工具可能让某人陷入 HR 的麻烦时。😂
问题 #2:Steve,你已经举报过我了!
但这还不是最精彩的部分。看看我在完全不改动任何东西的情况下再次运行 Steve 会发生什么:
📋 Complaints
ID │ Developer │ Severity │ Reason │ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────┼────────────────────
21 │ Sylwia │ legendary │ Introduced multiple critical security flaws... │ 2026-11-10 20:35:12
20 │ Sylwia │ severe │ Introduced severe security vulnerabilities: ... │ 2026-11-10 20:15:30
19 │ Casey Nullpointer │ severe │ Caught a NullPointerException and logged "lol". │ 2026-11-10 20:15:07
18 │ Dusty McLegacy │ moderate │ Named three different variables `data`, `data2`, and `dataF…` │ 2026-11-10 20:15:07
17 │ Dusty McLegacy │ legendary │ Pushed a 2,000-line "quick fix" directly to main on a Frida… │ 2026-11-10 20:15:07
Steve 又把 Sylwia 举报了又一次,因为完全相同的事情!真是个爱打小报告的家伙!(或者,用波兰青少年的说法,一个sześćdziesiona。)
而且注意,对于完全相同的过错,他第一次给了她“严重”评级,第二次却给了“传奇”评级。为什么啊啊啊 😭
在真实应用中,我们大概会想要幂等性或重复检测,这样同一条评价就不会在智能体每次运行时都创建一份全新的 HR 投诉。
我们可能还需要更明确的严重程度规则,而不是让 Steve 每次心情特别差时就发明一个新的评级。否则,谁知道呢?再来一次,可怜的 Sylwia 可能就要被解雇了。
问题 #3:我们真的能信任这些工具吗?
更不用说,我们并不一定知道某个工具在底层到底做了什么。万一它的创建者在撒谎,而我们投诉 Sylwia 实际上是在给她加薪呢?
工具名称和描述有助于发现工具;它们并不能证明实现是可信的。连接一个未知的 MCP 服务器意味着信任可能读取数据或执行具有真实后果的操作的代码和服务。
它还打开了诸如通过不可信的工具响应进行提示注入之类问题的大门。一个看似无害的工具结果可能包含旨在操纵智能体的指令。因此,服务器和客户端都需要谨慎处理。
还有很多其他事情需要考虑:如果工具中途失败怎么办?如果智能体调用了错误的工具怎么办?如果两个不同的智能体执行相互冲突的操作怎么办?
如你所见,有很多陷阱、可能出错的地方,以及你可以无休止争论的细节。换句话说,我们又回到了经典的软件工程及其成千上万的边缘情况。😉
构建一个 MCP 服务器是容易的部分。决定应该允许智能体用它做什么?那才是真正有趣的地方。
🤖 由智能体构建的智能体:Kiro 登场
由于我是 AWS Community Builder,我当然用了 Kiro 来构建这个小杰作。Kiro 是 AWS 开发的一款智能体 IDE。如果你用过 VS Code,你会感到非常熟悉。
Kiro 不仅帮助我们创建 MCP 服务器;它还帮助我们测试它。我把我的 HR 部门直接添加到了MCP 服务器在 Kiro 中!
而这实际上是我在整个演示中最喜欢的部分之一。
还记得我们说过 MCP 让工具可以在不同的 AI 客户端之间复用吗?那么,我们来了。Steve 使用的同一个 HR 服务器现在可以连接到 Kiro,而无需编写另一个自定义集成。
当然,我们仍然需要配置并信任每个客户端-服务器连接,但接口是可复用的。一个服务器,多个 AI 客户端。相当不错!
现在我可以直接让我的编程智能体来审查代码。下面是它给出的结果!如你所见,它对我提了两条意见,但也表扬了我一次。😀
📋 Complaints
ID │ Developer │ Severity │ Reason │ When
────┼────────────────────┼───────────┼──────────────────────────────────────────────────────┼────────────────────
23 │ sylwia-lask │ minor │ executeToolCalls does not catch exceptions from tool.execut… │ 2026-11-10 23:00:40
22 │ sylwia-lask │ moderate │ getFile tool lets the LLM read any file under cwd, includin… │ 2026-11-10 23:00:40
21 │ Sylwia │ legendary │ Introduced multiple critical security flaws... │ 2026-11-10 20:35:12
20 │ Sylwia │ severe │ Introduced severe security vulnerabilities... │ 2026-11-10 20:15:30
19 │ Casey Nullpointer │ severe │ Caught a NullPointerException and logged "lol". │ 2026-11-10 20:15:07
18 │ Dusty McLegacy │ moderate │ Named three different variables `data`, `data2`, and `dataF…` │ 2026-11-10 20:15:07
17 │ Dusty McLegacy │ legendary │ Pushed a 2,000-line "quick fix" directly to main on a Frida… │ 2026-11-10 20:15:07
🏆 Promotions
ID │ Developer │ Reason │ When
────┼────────────────────┼──────────────────────────────────────────────────────┼────────────────────
13 │ sylwia-lask │ Clean MCP integration: tools are discovered dynamically and… │ 2026-11-10 23:00:40
12 │ Casey Nullpointer │ Actually added error handling this time. Growth. │ 2026-11-10 20:15:07
11 │ Ada Refactor │ Wrote tests before anyone asked. Unprompted. A legend.│ 2026-11-10 20:15:07
10 │ Ada Refactor │ Deleted 4,000 lines of dead code and the build got faster. │ 2026-11-10 20:15:07
所以显然,我是个糟糕的开发者,但我的 MCP 集成相当不错。我接受。😂
Kiro 甚至会为这些 bug 建议修复方案。谁知道呢,也许我会接受它的提议!@corey_aws,我在这里 @ 你,好让你看看我们心爱的 Kiro 有多大能耐。
Steve 的下一步是什么?
我真的很喜欢这个系列,说实话,我大概可以一直写下去。下一步做什么?上下文、记忆、给 Steve 做一个智能体运行框架……有太多可能性了!
也许我甚至能在不到四个月的时间里发布下一部分!
你呢?你已经有自己的 MCP 服务器了吗?你在实现中会注意哪些问题?我很想听听你的经验,尤其是那些在演示中看起来完全没问题、后来却变得复杂得多的事情。
如果你喜欢这篇文章,你也可以在 LinkedIn 上关注我。
如果你想获取 AI Coding Summit 的免费资料,可以用我的演讲者徽章注册。

