摘要
在 AI 原生时代,前端不再只是静态页面的渲染器,而是 AI 与用户之间的交互舞台。模型上下文协议(MCP)为前端提供了新的交互范式:会话化、工具化、流式化。本文聚焦前端实践,深入解析 MCP 在前端架构中的角色,提供完整的 React Hook 实现、流式渲染策略、用户可控性设计与安全治理方案。通过案例与代码,展示如何让前端真正“舞动” MCP,成为 AI Agent 的最佳舞伴。
关键词
MCP;前端架构;React Hook;流式渲染;用户体验
目录
1 引言
在第1篇中,我们强调了 MCP 的定位与能力模型。本篇将视角转向前端:如何让前端在 MCP 的支持下,既保持轻量,又能承载 AI 的复杂交互。我们将从架构演进、Hook 实现、流式渲染、用户可控性、安全治理等方面展开,最终形成一套可落地的前端实践指南。
2 MCP 与前端的关系
- 前端是舞台:用户与 AI 的交互都在前端发生。
- MCP 是编舞者:负责工具发现、会话管理、流式输出。
- AI Agent 是舞者:在 MCP 的编排下执行动作,前端负责呈现。
类比卡片
REST 像点菜,前端只是菜单展示;
MCP 像舞蹈,前端是舞台,AI 是舞者,MCP 是编舞者。
3 架构演进:从直连到舞动
3.1 传统模式
前端直接调用多个 REST API,认证、错误处理、格式适配分散在前端,导致复杂度高。
3.2 MCP 模式
前端只与 MCP Server 通信,后端能力统一封装为工具。前端关注 UX 与会话呈现,复杂逻辑集中到 MCP Server。
架构流程图
#mermaid-svg-o6f9WlJuKxikV0FO {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .error-icon{fill:#552222;}#mermaid-svg-o6f9WlJuKxikV0FO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o6f9WlJuKxikV0FO .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-o6f9WlJuKxikV0FO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o6f9WlJuKxikV0FO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o6f9WlJuKxikV0FO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o6f9WlJuKxikV0FO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o6f9WlJuKxikV0FO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o6f9WlJuKxikV0FO .marker.cross{stroke:#333333;}#mermaid-svg-o6f9WlJuKxikV0FO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o6f9WlJuKxikV0FO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .cluster-label text{fill:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .cluster-label span{color:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .label text,#mermaid-svg-o6f9WlJuKxikV0FO span{fill:#333;color:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .node rect,#mermaid-svg-o6f9WlJuKxikV0FO .node circle,#mermaid-svg-o6f9WlJuKxikV0FO .node ellipse,#mermaid-svg-o6f9WlJuKxikV0FO .node polygon,#mermaid-svg-o6f9WlJuKxikV0FO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o6f9WlJuKxikV0FO .node .label{text-align:center;}#mermaid-svg-o6f9WlJuKxikV0FO .node.clickable{cursor:pointer;}#mermaid-svg-o6f9WlJuKxikV0FO .arrowheadPath{fill:#333333;}#mermaid-svg-o6f9WlJuKxikV0FO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o6f9WlJuKxikV0FO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o6f9WlJuKxikV0FO .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-o6f9WlJuKxikV0FO .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-o6f9WlJuKxikV0FO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o6f9WlJuKxikV0FO .cluster text{fill:#333;}#mermaid-svg-o6f9WlJuKxikV0FO .cluster span{color:#333;}#mermaid-svg-o6f9WlJuKxikV0FO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o6f9WlJuKxikV0FO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}UserFrontendMCPServerREST APIgRPC ServiceDatabaseLow-code Platform
4 React Hook 实战:useMcp 与 useMcpTool
4.1 Hook 设计原则
- 统一调用接口:前端组件通过 Hook 调用 MCP 工具。
- 流式支持:支持 SSE/WebSocket,逐步更新 UI。
- 错误与取消:提供取消与错误处理机制。
- 可扩展性:网络层抽象为适配器,便于替换。
4.2 useMcpTool 完整代码(示例)
import { useState, useRef, useCallback } from 'react';
export function useMcpTool<T = any>(toolName: string) {
const [data, setData] = useState<T | null>(null);
const [chunks, setChunks] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
const callTool = useCallback(async (input: any) => {
setLoading(true); setError(null); setChunks([]); setData(null);
abortRef.current?.abort();
const ac = new AbortController();
abortRef.current = ac;
try {
const resp = await fetch('/mcp/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` },
body: JSON.stringify({ tool: toolName, input }),
signal: ac.signal
});
if (resp.headers.get('Content-Type')?.includes('stream')) {
const reader = resp.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\\n');
buffer = lines.pop() || '';
for (const line of lines) {
try {
setChunks(prev => […prev, JSON.parse(line)]);
} catch {
setChunks(prev => […prev, line]);
}
}
}
} else {
const result = await resp.json();
setData(result.result);
}
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
}, [toolName]);
const cancel = useCallback(() => {
abortRef.current?.abort();
abortRef.current = null;
}, []);
return { data, chunks, loading, error, callTool, cancel };
}
function getToken() {
return sessionStorage.getItem('mcp_token') || '';
}
4.5 Vue 版实战:useMcpTool(组合式 API)
设计思路
- 组合式 API:用 ref 管理状态,用 watchEffect 或 onUnmounted 管理副作用。
- 统一调用接口:暴露 callTool 与 cancel 方法。
- 流式支持:解析 SSE/流式响应,逐步更新 chunks。
- 错误与取消:提供错误提示与取消逻辑。
完整代码示例
// useMcpTool.ts (Vue 3 组合式 API)
import { ref, onUnmounted } from 'vue'
export function useMcpTool<T = any>(toolName: string) {
const data = ref<T | null>(null)
const chunks = ref<any[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
let abortCtrl: AbortController | null = null
async function callTool(input: any, opts?: { sessionId?: string }) {
loading.value = true
error.value = null
chunks.value = []
data.value = null
abortCtrl?.abort()
abortCtrl = new AbortController()
try {
const resp = await fetch('/mcp/call', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getShortLivedToken()}`,
'X-Session-Id': opts?.sessionId ?? ''
},
body: JSON.stringify({ tool: toolName, input }),
signal: abortCtrl.signal
})
const contentType = resp.headers.get('Content-Type') || ''
if (contentType.includes('text/event-stream') || contentType.includes('stream')) {
const reader = resp.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
try {
const obj = JSON.parse(line)
chunks.value.push({ type: 'partial', payload: obj })
} catch {
chunks.value.push({ type: 'event', payload: line })
}
}
}
} else {
const result = await resp.json()
if (result.error) throw new Error(result.error.message || 'tool error')
data.value = result.result
}
} catch (e: any) {
error.value = e.name === 'AbortError' ? 'cancelled' : e.message
} finally {
loading.value = false
}
}
function cancel() {
abortCtrl?.abort()
abortCtrl = null
}
onUnmounted(() => {
abortCtrl?.abort()
})
return { data, chunks, loading, error, callTool, cancel }
}
function getShortLivedToken() {
return sessionStorage.getItem('mcp_token') || ''
}
使用示例(Vue 组件中)
<script setup lang="ts">
import { useMcpTool } from './useMcpTool'
const { data, chunks, loading, error, callTool, cancel } = useMcpTool('orders.query')
function queryOrders() {
callTool({ date: '2025-12-11' })
}
</script>
<template>
<div>
<button @click="queryOrders">查询订单</button>
<button @click="cancel">取消</button>
<div v-if="loading">正在查询…</div>
<div v-if="error">错误: {{ error }}</div>
<div v-if="data">结果: {{ data }}</div>
<ul>
<li v-for="(chunk, i) in chunks" :key="i">
{{ chunk.type }}: {{ chunk.payload }}
</li>
</ul>
</div>
</template>
Vue 版的优势
- 更贴近国内前端团队:Vue 在国内应用广泛,提供 Vue 版能降低学习成本。
- 响应式数据流:ref 与模板绑定天然适合流式渲染。
- 生命周期管理:onUnmounted 自动清理,避免内存泄漏。
- 组合式 API:逻辑清晰,可在不同组件中复用。
5 流式渲染与用户体验设计
5.1 渐进式呈现
在流式输出中展示“AI 正在思考”的中间片段,并标注来源(模型推理 / 工具调用)。
5.2 用户可控性
- 中止按钮:允许用户随时取消。
- 确认弹窗:高风险操作需确认。
- 详情面板:显示调用详情,增强透明度。
5.3 可观测性
记录关键事件(请求发起、流式片段接收、错误),并与后端审计日志关联。
6 安全边界与可观测性
6.1 最小权限原则
前端仅持有最小权限凭证,避免长期暴露高权限令牌。
6.2 输入输出校验
对工具参数与返回值进行严格校验,防止注入或越权调用。
6.3 审计链路
在前端记录会话 ID、用户 ID、工具名、时间戳等关键事件,并与后端审计日志关联。
7 案例演示:AI 驱动的订单查询前端
场景:用户输入“帮我查一下昨天的订单”。
流程:
表格示例
| 2025-12-11 | ORD123 | 张三 | ¥1200 |
| 2025-12-11 | ORD124 | 李四 | ¥800 |
8 结论与三步行动清单
核心结论
MCP 为前端提供了新的交互范式:会话化、工具化、流式化。前端在 MCP 的支持下,不仅简化了集成,还提升了用户体验与安全治理。



