基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-06 20:46

前端复用MCP工具的数据接入方案

dev.to · AI#MCP#前端开发#接口安全
Editor brief · 编辑速览

文章比较浏览器直连MCP与后端代理两种方式,并展示初始化后的工具调用协议。推荐通过后端代理隐藏凭证、校验用户会话并限制可调用工具。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

MCP Server 不只服务 AI 助手,也可以给网页提供数据。作者提出,让订单后台与 Agent 共用 search_orders,可以减少两套查询入口各自维护的成本;前端通过一层 Node.js 代理调用,就能沿用普通 HTTP 接口的开发方式。

这篇的价值在于讲清了连接路径、返回格式和错误分层。但它提供的是接入示例,没有生产验证数据。尤其是鉴权、结果解析和流式处理,代码能说明方向,离可靠上线还有几步。

网页为什么也能调用 Agent 的工具

MCP 把工具调用封装成协议,并没有要求调用方必须是模型。Agent 能查询订单,网页也能发起同样的请求。共用的是服务端能力,页面仍然负责搜索按钮、加载状态和列表渲染。

作者给了两条路径:

  • 浏览器直接使用官方 TypeScript SDK @modelcontextprotocol/sdk,通过 Streamable HTTP 连接远程 MCP Server。演示和内部工具接入较快,但浏览器需要持有服务凭据,服务端也必须允许 CORS。
  • 后端运行 MCP Client,再向网页暴露 /api/tools/search_orders 这样的普通接口。MCP 凭据留在后端,并在入口检查用户身份、限制可调用工具。

让 UI 与 Agent 复用查询能力,不等于让浏览器直接持有 MCP 凭据。

前端通过后端代理调用 MCP 工具的请求时序

协议层使用 JSON-RPC 2.0,先完成 initialize 握手,再通过 tools/call 指定工具名和参数。原文的请求用 id: 7 调用 search_orders,参数是 customer: "Acme";响应仍用 id: 7 对应请求,返回订单 id: 101、total: 250。

这里有个容易踩坑的细节:示例订单并非直接返回的 JSON 对象,而是 content 数组中一段文本里的 JSON 字符串。

资源读取走另一套入口:resources/read 接收 URI,例如 orders://recent,返回包含文本或二进制数据的 contents。要发现服务能力,可以分别调用 tools/list 和 resources/list。

Node.js 代理解决了什么,又漏了什么

下面这段代理把工具调用收口到 Express 接口,并设置工具白名单:

import express from "express";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "web-proxy", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(new URL(process.env.MCP_URL!)));

const ALLOWED = new Set(["search_orders", "get_order"]);
const app = express();
app.use(express.json());

app.post("/api/tools/:name", async (req, res) => {
  if (!req.headers.authorization) return res.status(401).json({ error: "Sign in first" });
  if (!ALLOWED.has(req.params.name)) return res.status(403).json({ error: "Tool not allowed" });
  try {
    const result = await client.callTool({ name: req.params.name, arguments: req.body });
    if (result.isError) return res.status(422).json({ error: result.content });
    res.json(result.content);
  } catch (e) {
    res.status(502).json({ error: "MCP server unavailable" });
  }
});

app.listen(3000);

重点看 if (!req.headers.authorization):它只检查请求头存在,没有验证 token,也没有检查用户是否有权读取目标订单。作者正文要求检查会话、权限和参数,但示例没有完整实现。

白名单允许 search_orders、get_order,能缩小暴露范围,却代替不了业务授权。客户端配置中的 version: "1.0.0" 也是 web-proxy 的自报版本,不是 SDK 版本;原文没有给出 SDK 的具体版本。

React 示例则展示页面如何消费这个接口:

import { useState } from "react";

export function OrderSearch() {
  const [rows, setRows] = useState<any[]>([]);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  async function search(customer: string) {
    setLoading(true); setError("");
    try {
      const res = await fetch("/api/tools/search_orders", {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: `Bearer ${localToken()}` },
        body: JSON.stringify({ customer }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(typeof data.error === "string" ? data.error : "Tool failed");
      setRows(JSON.parse(data[0].text));
    } catch (e: any) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <button onClick={() => search("Acme")} disabled={loading}>Search Acme</button>
      {error && <p role="alert">{error}</p>}
      <ul>{rows.map(r => <li key={r.id}>#{r.id}: ${r.total}</li>)}</ul>
    </div>
  );
}

要留意 JSON.parse(data[0].text):它假设首个内容块一定是文本,且文本一定能解析成订单数组。这个约定来自示例,不能推广成所有工具的返回规则。localToken() 也没有实现;这里发送的应是应用侧身份凭据,MCP 凭据另留后端,原文未展示后者如何配置。

相同的 fetch 请求可以用于 Vue 或原生 JavaScript,组件状态和渲染方式则需各自实现。

错误与流式响应不能只靠一个 catch

作者把错误分成三层,这个划分比统一显示「请求失败」更有用:

  • 传输错误包括网络中断和 HTTP 5xx,原文建议退避重试,并展示易懂的提示。
  • JSON-RPC 错误通过带 code 的错误对象表达,例如 -32602 是参数无效,-32601 是未知方法,通常应记录并排查调用问题。
  • 工具错误仍是正常返回结果,但带有 isError: true,例如找不到客户,适合反馈给用户。

示例把这些信息压缩了:工具错误映射成 422,调用异常统一映射成 502,浏览器未必能区分参数错误与服务不可用。因此,分层原则还需要落实到代理的错误映射中。

Streamable HTTP 还允许服务端通过 Server-Sent Events(SSE)在最终结果前发送进度通知。作者建议代理继续输出 SSE,前端用 fetch 加流读取器消费;GET 请求也可以使用 EventSource。

但示例只是对 /api/tools/long_report/stream 发起 POST,携带 "{}",然后通过 res.body.getReader()、TextDecoder 和 showProgress() 处理字节块。读到一块网络数据,不代表已经读到一条完整 SSE 事件。 原文没有补齐事件边界解析,也没有实现代理流接口。

此外还应使用 AbortController 设置超时,并在组件卸载时取消请求;取消浏览器请求是否会终止服务端工具执行,原文没有说明。

我的判断

我赞同把 MCP 放在后端、让前端保留普通接口的边界。对于已经有 MCP Server、又需要给 UI 暴露同一批能力的团队,这条路径有直接价值。

但作者没有提供延迟、吞吐量、维护成本或故障率的对照数据,也没有报告实测过程。因此,「推荐薄代理」是架构经验建议,不能据此认定它比普通业务 API 更快、更省事。

如果团队原本只有网页订单查询需求,直接建设业务 API 同样合理。增加 MCP 是否划算,取决于 Agent 是否确实需要复用这些能力。即使共用工具,用户权限、返回格式和错误约定仍然要单独设计。

能拿来做什么

  • 已有 MCP 工具的团队,可以先挑一个订单查询类读取操作,用 Node.js 白名单代理接到页面,并在服务端完成会话验证、参数校验和数据权限检查。
  • 前端接入时,应把示例中的文本结果解析收口处理,验证内容类型与订单字段,避免每个组件都依赖 data[0].text。
  • 为传输失败、协议错误和业务失败定义可区分的返回信息,再补超时与卸载取消;重试前先判断工具是否适合重复执行。
  • 只有长任务需要进度展示时,再实现完整 SSE 转发与解析;工具返回内容按不可信数据处理,不直接注入原始 HTML。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
用回滚和返修记录评估编程代理
下一篇
评论自动回复流程中的提示注入入口

资讯速览

dev.to · AI · 2026-11-06 20:46

AI 评分 8.0 / 10

相关标签

#MCP#前端开发#接口安全

继续阅读

  • 企业AI验收要交付可复跑的评测证据
  • 用回滚和返修记录评估编程代理
  • 评论自动回复流程中的提示注入入口
  • 算清 LoRA 微调的参数与资源开销
  • 为 AI 应用建立可阻断发布的质量门禁