MCP抽象了什么?

如何构建一个简单的包装函数让我们得出了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));  
});

我们在做什么?

  1. 我们在编写获取逻辑。
  2. 我们在编写路由逻辑(调用哪个API)。
  3. 我们在编写后处理逻辑以适应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;  
 }  
}

这是我们所有人都使用过的模式:

  1. switch语句。
  2. 每个动作的逻辑。
  3. 与UI形状紧密耦合。

2、现在进入基于聊天的UI:文本输入,文本输出

UI不再是按钮和图表。

它是自然语言输入,由LLM驱动。因此现在你的

  1. 前端说:“给我用户A的收入
  2. 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", …)
  1. 更新我们的switch块
  2. 再次部署
  3. 告知所有人我们改变了行为

突然间,这个漂亮的抽象变成了技术债务。

6、等等——我们为什么要这样做?

如果我们数据提供者拥有这些工具,为什么我们要维护这个逻辑?

我们已经证明UI不关心数据形状。
LLM只需要一个字符串。那么为什么我们要坐在这中间?

之前,你必须这样做——因为UI期望不同的形状数据。但现在?

  1. UI不关心形状
  2. LLM不关心端点
  3. 只关心字符串输出

7、所以我们将包装器移到数据提供者那里

我们将invokeTool逻辑移到数据提供者那里。

// 外部数据提供者URL  
POST /invoke  
{  
 "toolName": "getRevenue",  
 "toolInput": { "month": "January" }  
}

后端执行:

  1. 路由
  2. 获取
  3. 输出格式化

然后我们得到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();

完成。

  1. 没有手动路由。
  2. 没有客户端逻辑。
  3. 没有UI布线。

那么MCP客户端呢?只有一行代码

//伪MCP客户端代码  
await invokeTool("getRevenue", { month: "January" });  
// 返回:"一月份的收入是8000美元"

MCP客户端处理:

  1. 传输
  2. 负载结构
  3. 工具调用

10、结束语

为什么以前不可能做到这一点?

这种灵活性使MCP抽象成为可能

从一个简单的switch块开始,它变成了一个可重用的包装器。然后我们将其作为服务公开。然后我们将其交给数据提供者。这就是通往MCP服务器的旅程。
  1. 它不是一个框架。
  2. 它不是一种传输规范。
  3. 它是一种所有权的转变。

它关乎将路由格式化逻辑消费者转移到提供者。如果你正在构建基于LLM的应用程序,你不需要为每个工具重新发明粘合代码。

你需要一个接口。 你需要一个协议。 你需要MCP。

原文链接:Everyone Shows What MCP Does — But Nobody Tells You What It Abstracts

汇智网翻译整理,转载请标明出处