MCP抽象了什么?
如何构建一个简单的包装函数让我们得出了MCP服务器的概念

你可能在博客文章中见过这个图表:

但问题是:每个人都在教你如何使用MCP。
没有人解释它抽象了什么——以及为什么现在它突然变得重要了。所以让我们停止理论化,开始编码。
1、传统Web UI:按钮时代
假设我们在构建一个带有两个按钮的Web UI:
<button id="btn-create-user">创建用户</button>
<button id="btn-get-revenue">获取收入</button>
以下是背后的JavaScript代码:
document.getElementById("btn-create-user").addEventListener("click", () => {
fetch("/api/createUser")
.then(res => res.json())
.then(data => renderUserCard(data));
});document.getElementById("btn-get-revenue").addEventListener("click", () => {
fetch("/api/getRevenue")
.then(res => res.json())
.then(data => renderGraph(data));
});
我们在做什么?
- 我们在编写获取逻辑。
- 我们在编写路由逻辑(调用哪个API)。
- 我们在编写后处理逻辑以适应UI。
每个操作都是硬编码的。输出需要与UI结构完全匹配。
2、让我们干掉重复代码:一个事件处理器
function handleClick(actionName) {
switch (actionName) {
case "createUser":
fetch("/api/createUser")
.then(res => res.json())
.then(data => renderUserCard(data));
break;
case "getRevenue":
fetch("/api/getRevenue")
.then(res => res.json())
.then(data => renderGraph(data));
break;
}
}
这是我们所有人都使用过的模式:
- switch语句。
- 每个动作的逻辑。
- 与UI形状紧密耦合。


2、现在进入基于聊天的UI:文本输入,文本输出
UI不再是按钮和图表。
它是自然语言输入,由LLM驱动。因此现在你的
- 前端说:“给我用户A的收入”
- LLM提取一个工具(getRevenue)和一些工具输入(User A)
所以你用以下内容替换了之前的路由逻辑:
invokeTool("getRevenue", { name: "User A" });
不再需要后处理。LLM只需要一个JSON字符串:
{result:[ {productName: 'XyZ', yearly: 34B, },{productName: 'Z', yearly: 32B}]}

3、一个函数处理所有工具调用
那么让我们写一个包装器
function invokeTool(toolName, toolInput) {
switch (toolName) {
case "createUser":
return fetch("/api/createUser", {
method: "POST",
body: JSON.stringify(toolInput)
}).then(res => res.text());
case "getRevenue":
return fetch("/api/getRevenue", {
method: "POST",
body: JSON.stringify(toolInput)
}).then(res => res.text());
}
}
// 包装函数
function getToolDataForLLM(toolName, toolInput){
return invokeTool(toolName,toolInput)
}
嘿,一个函数处理所有工具调用。
4、让我们将其作为服务公开
既然这工作得很好,我们想:“嘿,让我们将这个包装器作为服务提供给组织中的其他团队。”所以我们做了:
// 内部组织URL
POST /invoke
{
"toolName": "getRevenue",
"toolInput": { "month": "January" }
}
我们的包装器成为此内部工具调用API的后端。每个人都赢了——其他人不需要再编写自己的逻辑。

5、然后……添加了一个新工具
后端团队添加了一个新端点:getTopCustomers。现在我们必须:
case "getTopCustomers":
return fetch("/api/getTopCustomers", …)
- 更新我们的switch块
- 再次部署
- 告知所有人我们改变了行为
突然间,这个漂亮的抽象变成了技术债务。
6、等等——我们为什么要这样做?
“如果我们数据提供者拥有这些工具,为什么我们要维护这个逻辑?”
我们已经证明UI不关心数据形状。
LLM只需要一个字符串。那么为什么我们要坐在这中间?
之前,你必须这样做——因为UI期望不同的形状数据。但现在?
- UI不关心形状
- LLM不关心端点
- 你只关心字符串输出
7、所以我们将包装器移到数据提供者那里
我们将invokeTool逻辑移到数据提供者那里。

// 外部数据提供者URL
POST /invoke
{
"toolName": "getRevenue",
"toolInput": { "month": "January" }
}
后端执行:
- 路由
- 获取
- 输出格式化
然后我们得到JSON字符串
// JSON字符串
{
"toolOutput": "一月份的收入是8000美元"
}
8、你刚刚移到后端的包装器?
那是MCP服务器。

9、使用MCP SDK构建它
后端使用:
//伪SDK代码
const { registerTool, startMCPServer } = require("mcp-sdk");
registerTool("getRevenue", async (input) => {
const data = await fetchRevenue(input);
return `一月份的收入是$${data.total}`;
});
registerTool("create", async (input) => {
const data = await createUser(input);
return `用户已创建`;
});
startMCPServer();
完成。
- 没有手动路由。
- 没有客户端逻辑。
- 没有UI布线。
那么MCP客户端呢?只有一行代码
//伪MCP客户端代码
await invokeTool("getRevenue", { month: "January" });
// 返回:"一月份的收入是8000美元"
MCP客户端处理:
- 传输
- 负载结构
- 工具调用

10、结束语
为什么以前不可能做到这一点?

这种灵活性使MCP抽象成为可能。
从一个简单的switch块开始,它变成了一个可重用的包装器。然后我们将其作为服务公开。然后我们将其交给数据提供者。这就是通往MCP服务器的旅程。
- 它不是一个框架。
- 它不是一种传输规范。
- 它是一种所有权的转变。
它关乎将路由和格式化逻辑从消费者转移到提供者。如果你正在构建基于LLM的应用程序,你不需要为每个工具重新发明粘合代码。
你需要一个接口。 你需要一个协议。 你需要MCP。
原文链接:Everyone Shows What MCP Does — But Nobody Tells You What It Abstracts
汇智网翻译整理,转载请标明出处
