背景
在现代Web开发生态中,框架已成为构建高效、可维护应用的核心基础设施。将MCP TypeScript-SDK与流行的Web框架集成,能够充分发挥两者的优势,构建功能丰富、交互智能的现代应用。本文将深入探讨MCP与主流Web框架的集成方法、最佳实践和架构设计,帮助开发者构建强大而灵活的全栈MCP应用。
目录
Express集成最佳实践
1.1 Express与MCP的架构契合点
1.2 基于Express构建MCP API服务
1.3 中间件开发与集成
1.4 性能优化与部署策略
Next.js中的MCP服务
2.1 Next.js与MCP的架构融合
2.2 服务器端组件中的MCP集成
2.3 API路由与MCP服务
2.4 Edge运行时与MCP
前端组件与MCP交互
3.1 React组件设计模式
3.2 Vue.js集成方案
3.3 状态管理与MCP数据流
3.4 响应式UI与实时更新
全栈MCP应用架构
4.1 架构设计原则与模式
4.2 数据流设计
4.3 认证与权限设计
4.4 部署与DevOps策略
1. Express集成最佳实践
Express作为Node.js生态中使用最广泛的Web框架之一,其简洁、灵活的特性与MCP TypeScript-SDK有着天然的契合点。在本节中,我们将探讨如何将MCP服务优雅地集成到Express应用中,并分享一系列实用的最佳实践。
1.1 Express与MCP的架构契合点
Express和MCP在架构设计上有多个契合点,使它们能够无缝协作:
import express from 'express';
import {
McpServer } from '@modelcontextprotocol/sdk';
import {
createServer } from 'http';
// 架构契合点分析
interface ArchitecturalCompatibilityPoint {
aspect: string;
expressFeature: string;
mcpFeature: string;
synergy: string;
}
const compatibilityPoints: ArchitecturalCompatibilityPoint[] = [
{
aspect: "中间件模式",
expressFeature: "请求处理管道",
mcpFeature: "MCP中间件支持",
synergy: "可通过Express中间件预处理MCP请求,处理认证、日志等横切关注点"
},
{
aspect: "路由机制",
expressFeature: "URL路由系统",
mcpFeature: "资源与工具路由",
synergy: "可将MCP资源映射到Express路由系统,实现统一的API架构"
},
{
aspect: "错误处理",
expressFeature: "集中式错误处理中间件",
mcpFeature: "标准化错误响应",
synergy: "在Express错误处理中集成MCP错误,提供一致的错误响应"
},
{
aspect: "可扩展性",
expressFeature: "插件生态系统",
mcpFeature: "动态资源与工具注册",
synergy: "可动态扩展MCP功能并通过Express路由暴露"
}
];
// Express应用实例
const app = express();
const httpServer = createServer(app);
// MCP服务器实例
const mcpServer = new McpServer({
name: "express-mcp-integration",
description: "MCP服务与Express框架的集成演示",
version: "1.0.0"
});
// 两者的生命周期管理可以协调一致
async function startServers() {
// 先初始化MCP服务
await mcpServer.initialize();
// 启动HTTP服务器
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => {
console.log(`Express服务器运行在 http://localhost:${
PORT}`);
console.log(`MCP服务已初始化并集成`);
});
}
// 优雅关闭
process.on('SIGTERM', async () => {
console.log('正在关闭服务...');
await mcpServer.shutdown();
httpServer.close(() => {
console.log('服务已安全关闭');
process.exit(0);
});
});
startServers().catch(console.error);
这种集成方式使Express可以处理常规HTTP请求,而MCP可以处理AI相关的交互,两者互不干扰又能协同工作。
1.2 基于Express构建MCP API服务
下面我们将展示如何构建一个结合Express和MCP的API服务,包括路由设计、请求处理和响应格式化:
import express from 'express';
import {
json, urlencoded } from 'body-parser';
import cors from 'cors';
import {
McpServer, ResourceTemplate, Tool } from '@modelcontextprotocol/sdk';
import {
z } from 'zod';
const app = express();
const mcpServer = new McpServer({
name: "mcp-express-api",
description: "MCP与Express集成的API服务",
version: "1.0.0"
});
// 配置Express中间件
app.use(cors());
app.use(json());
app.use(urlencoded({
extended: true }));
// MCP资源模板定义
const userResourceTemplate: ResourceTemplate = {
name: "users",
description: "用户资源",
schema: z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"])
})
};
// MCP工具定义
const calculatorTool: Tool = {
name: "calculator",
description: "执行基本数学计算",
parameters: z.object({
operation: z.enum(["add", "subtract", "multiply", "divide"]),
a: z.number(),
b: z.number()
}),
execute: async ({
operation, a, b }) => {
switch (operation) {
case "add": return {
result: a + b };
case "subtract": return {
result: a - b };
case "multiply": return {
result: a * b };
case "divide":
if (b === 0) throw new Error("除数不能为零");
return {
result: a / b };
}
}
};
// 注册MCP资源和工具
mcpServer.registerResourceTemplate(userResourceTemplate);
mcpServer.registerTool(calculatorTool);
// 用户数据存储(示例)
const users = [
{
id: "1", name: "张三", email: "zhangsan@example.com", role: "admin" },
{
id: "2", name: "李四", email: "lisi@example.com", role: "user" }
];
// Express路由 - REST API端点
app.get('/api/users', (req, res) => {
res.json({
users });
});
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
if (!user) return res.status(404).json({
error: "用户不存在" });
res.json({
user });
});
// Express路由 - MCP API端点
app.post('/api/mcp/resources/:name', async (req, res) => {
try {
const resourceName = req.params.name;
let result;
// 根据资源名称处理不同资源
if (resourceName === "users") {
result = users;
} else {
return res.status(404).json({
error: "资源不存在" });
}
res.json({
success: true, data: result });
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : "未知错误"
});
}
});
app.post('/api/mcp/tools/:name', async (req, res) => {
try {
const toolName = req.params.name;
const params = req.body;
if (toolName === "calculator") {
// 直接使用MCP工具
const result = await mcpServer.executeTool(toolName, params);
res.json({
success: true, data: result });
} else {
res.status(404).json({
error: "工具不存在" });
}
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : "未知错误"
});
}
});
// 启动服务
const PORT = process.env.PORT || 3000;
async function startServer() {
await mcpServer.initialize();
app.listen(PORT, () => {
console.log(`Express-MCP API服务运行在 http://localhost:${
PORT}`);
});
}
startServer().catch(console.error);
这个示例展示了如何通过Express路由暴露MCP资源和工具,使它们可以通过RESTful API访问。对于前端应用,这提供了一种统一的方式来与MCP服务交互。
1.3 中间件开发与集成
Express的中间件架构是其最强大的特性之一,我们可以开发专用的中间件来增强MCP服务的功能:
import express from 'express';
import {
McpServer } from '@modelcontextprotocol/sdk';
import {
createServer } from 'http';
import morgan from 'morgan';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
const app = express();
const mcpServer = new McpServer({
name: "mcp-middleware-demo",
description: "演示MCP与Express中间件的集成",
version: "1.0.0"
});
// 1. 基础安全中间件
app.use(helmet()); // 设置各种HTTP头以增强安全性
// 2. 日志中间件
app.use(morgan('combined')); // HTTP请求日志
// 3. 请求限流中间件
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个IP在windowMs内的最大请求数
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', apiLimiter);
// 4. MCP认证中间件
const mcpAuthMiddleware = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.MCP_API_KEY) {
return res.status(401).json({
success: false,
error: "未授权的访问"
});
}
// 将认证信息附加到请求对象
req.mcpAuth = {
authenticated: true, timestamp: new Date() };
next();
};
// 5. MCP请求验证中间件
const mcpValidationMiddleware = (req, res, next) => {
if (!req.body || typeof req.body !== 'object') {
return res.status(400).json({
success: false,
error: "无效的请求格式"
});
}
next();
};
// 6. MCP性能监控中间件
const mcpPerformanceMiddleware = (req, res, next) => {
const start = Date.now();
// 在响应完成后记录性能指标
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`MCP请求 ${
req.method} ${
req.originalUrl} 耗时 ${
duration}ms`);
// 这里可以将性能数据发送到监控系统
if (duration > 1000) {
console.warn(`警告: MCP请求耗时超过1秒`);
}
});
next();
};
// 7. MCP错误处理中间件
const mcpErrorHandler = (err, req, res, next) => {
console.error('MCP错误:', err);
// 格式化错误响应
res.status(err.status || 500).json({
success: false,
error: {
message: err.message || "服务器内部错误",
code: err.code || "INTERNAL_ERROR",
details: process.env.NODE_ENV === 'production' ? undefined : err.stack
}
});
};
// 应用MCP专用中间件到MCP路由
app.use('/api/mcp', [
mcpAuthMiddleware,
mcpValidationMiddleware,
mcpPerformanceMiddleware
]);
// MCP资源路由
app.post('/api/mcp/resources/:name', async (req, res, next) => {
try {
const resourceName = req.params.name;
// 使用MCP服务器处理资源请求
const result = await mcpServer.getResource(resourceName, req.body);
res.json({
success: true, data: result });
} catch (error) {
next(error); // 传递给错误处理中间件
}
});
// MCP工具路由
app.post('/api/mcp/tools/:name', async (req, res, next) => {
try {
const toolName = req.params.name;
// 使用MCP服务器执行工具
const result = await mcpServer.executeTool(toolName, req.body);
res.json({
success: true, data: result });
} catch (error) {
next(error); // 传递给错误处理中间件
}
});
// 注册错误处理中间件(必须在路由之后)
app.use(mcpErrorHandler);
// 启动服务
const PORT = process.env.PORT || 3000;
async function startServer() {
await mcpServer.initialize();
app.listen(PORT, () => {
console.log(`Express-MCP 中间件演示服务运行在 http://localhost:${
PORT}`);
});
}
startServer().catch(console.error);
这些中间件能显著增强MCP服务的安全性、可观测性和可用性,同时保持代码的简洁和可维护性。
1.4 性能优化与部署策略
在将MCP与Express集成的应用部署到生产环境时,性能优化至关重要。以下是一些性能优化技巧和部署策略:
import express from 'express';
import {
McpServer } from '@modelcontextprotocol/sdk';
import compression from 'compression';
import cluster from 'cluster';
import os from 'os';
import {
createClient } from 'redis';
// 性能优化设置
const ENABLE_CLUSTERING = process.env.ENABLE_CLUSTERING === 'true';
const WORKER_COUNT = process.env.WORKER_COUNT
? parseInt(process.env.WORKER_COUNT, 10)
: os.cpus().length;
// Redis客户端(用于缓存和会话共享)
const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
// 主进程逻辑
if (ENABLE_CLUSTERING && cluster.isPrimary) {
console.log(`主进程 ${
process.pid} 正在运行`);
// 为每个CPU核心派生工作进程
for (let i = 0; i < WORKER_COUNT; i++) {
cluster.fork();
}
// 如果工作进程退出,重新派生一个新的
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${
worker.process.pid} 退出`);
cluster.fork();
});
} else {
// 工作进程逻辑
const app = express();
const mcpServer = new McpServer({
name: "mcp-express-optimized",
description: "优化的MCP-Express集成服务",
version: "1.0.0"
});
// 性能优化中间件
// 1. 启用压缩
app.use(compression());
// 2. 缓存控制
app.use((req, res, next) => {
// 静态资源设置长时间缓存
if (req.url.match(/.(css|js|jpg|png|svg|ico)$/)) {
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24小时
}
// API响应设置短时间或不缓存
else if (req.url.startsWith('/api/')) {
res.setHeader('Cache-Control', 'no-cache');
}
next();
});
// 3. MCP响应缓存
const mcpCache = async (req, res, next) => {
// 只缓存GET请求和特定的MCP资源请求
if (req.method !== 'GET' &&
!(req.method === 'POST' && req.url.startsWith('/api/mcp/resources/'))) {
return next();
}
// 创建缓存键
const cacheKey = `mcp-cache:${
req.originalUrl}:${
JSON.stringify(req.body)}`;
try {
// 尝试从Redis获取缓存
const cachedResponse = await redisClient.get(cacheKey);
if (cachedResponse) {
const data = JSON.parse(cachedResponse);
return res.json(data);
}
// 修改res.json以拦截响应并存入缓存
const originalJson = res.json;
res.json = function(data) {
// 只缓存成功的响应
if (data && data.success === true) {
redisClient.set(
cacheKey,
JSON.stringify(data),
{
EX: 60 } // 缓存60秒
).catch(console.error);
}
return originalJson.call(this, data);
};
next();
} catch (error) {
console.error('缓存错误:', error);
next(); // 忽略缓存错误,继续处理请求
}
};
// 应用MCP缓存中间件
app.use('/api/mcp/resources', mcpCache);
// 4. MCP请求队列(用于处理高负载)
const pendingRequests = new Map();
const MAX_CONCURRENT_REQUESTS = 20;
const requestQueue = async (req, res, next) => {
// 如果当前请求数低于阈值,直接处理
if (pendingRequests.size < MAX_CONCURRENT_REQUESTS) {
const requestId = Date.now() + Math.random().toString(36).substring(2, 15);
pendingRequests.set(requestId, {
req, timestamp: Date.now() });
// 请求完成后移除
res.on('finish', () => {
pendingRequests.delete(requestId);
});
return next();
}
// 否则发送请求过多的响应
res.status(429).json({
success: false,
error: "服务器正忙,请稍后重试",
retryAfter: 5 // 建议客户端等待5秒后重试
});
};
// 应用请求队列中间件到计算密集型的MCP路由
app.use('/api/mcp/tools', requestQueue);
// 路由设置(略,与前面示例类似)
// 健康检查端点(用于Kubernetes等容器编排系统)
app.get('/health', async (req, res) => {
try {
// 检查MCP服务器状态
const mcpStatus = mcpServer.isInitialized();
// 检查Redis连接
const redisStatus = redisClient.isReady;
if (mcpStatus && redisStatus) {
res.status(200).json({
status: 'healthy' });
} else {
res.status(503).json({
status: 'unhealthy',
details: {
mcp: mcpStatus ? 'ok' : 'error',
redis: redisStatus ? 'ok' : 'error'
}
});
}
} catch (error) {
res.status(500).json({
status: 'error', message: error.message });
}
});
// 优雅关闭
async function gracefulShutdown() {
console.log('收到关闭信号,开始优雅关闭...');
// 给正在处理的请求一些时间完成
server.close(async () => {
try {
// 关闭MCP服务器
await mcpServer.shutdown();
console.log('MCP服务器已关闭');
// 关闭Redis连接
await redisClient.quit();
console.log('Redis连接已关闭');
console.log('所有连接已安全关闭');
process.exit(0);
} catch (error) {
console.error('关闭过程中出错:', error);
process.exit(1);
}
});
}
// 监听终止信号
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
// 启动服务
const PORT = process.env.PORT || 3000;
async function startServer() {
try {
// 连接Redis
await redisClient.connect();
console.log('Redis连接已建立');
// 初始化MCP服务器
await mcpServer.initialize();
console.log('MCP服务器已初始化');
// 启动HTTP服务器
const server = app.listen(PORT, () => {
console.log(`优化的Express-MCP服务运行在 http://localhost:${
PORT}`);
console.log(`工作进程 ${
process.pid} 已启动`);
});
} catch (error) {
console.error('启动服务器时出错:', error);
process.exit(1);
}
}
startServer();
}
// 部署策略备注:
/*
1. 容器化部署:
- 使用Docker构建镜像,包含Node.js环境和应用代码
- 使用Docker Compose管理多容器部署(应用、Redis、数据库等)
- 在Kubernetes中部署,利用Pod自动伸缩和负载均衡
2. 无服务器部署:
- 将MCP服务拆分为多个独立的Lambda/Function
- 使用API Gateway暴露HTTP端点
- 使用DynamoDB/Cosmos DB等无服务器数据库存储状态
3. 混合云部署:
- 将计算密集型MCP工具部署在专用高性能服务器上
- 将API和静态内容部署在云端
- 使用内容分发网络(CDN)加速静态资源分发
*/
这种优化和部署策略使MCP服务能够处理高流量和计算密集型任务,同时保持可靠性和响应速度。通过合理使用集群、缓存、队列和监控,可以显著提高系统性能和可用性。
2. Next.js中的MCP服务
Next.js作为一个流行的React框架,提供了服务器端渲染(SSR)、静态站点生成(SSG)和API路由等功能,使其成为构建现代Web应用的理想选择。将MCP TypeScript-SDK与Next.js集成,可以创建具有强大AI能力的全栈应用。本节将探讨如何在Next.js应用中集成和使用MCP服务。
2.1 Next.js与MCP的架构融合
Next.js的架构特性与MCP可以进行多层次的融合,包括API路由、服务器组件和客户端组件:
// 文件: src/lib/mcp-server.ts
// MCP服务器单例配置
import {
McpServer } from '@modelcontextprotocol/sdk';
import {
z } from 'zod';
// 确保MCP服务器为单例
let mcpServerInstance: McpServer | null = null;
export function getMcpServer() {
if (!mcpServerInstance) {
mcpServerInstance = new McpServer({
name: "nextjs-mcp-integration",
description: "Next.js与MCP的集成服务",
version: "1.0.0"
});
// 注册资源和工具
setupResources(mcpServerInstance);
setupTools(mcpServerInstance);
}
return mcpServerInstance;
}
// 资源设置
function setupResources(server: McpServer) {
// 注册资源模板
server.registerResourceTemplate({
name: "products",
description: "产品目录",
schema: z.object({
id: z.string(),
name: z.string(),
description: z.string(),
price: z.number(),
category: z.string(),
inStock: z.boolean()
})
});
// 实现资源处理器
server.setResourceHandler("products", async (params) => {
// 这里可以连接到数据库或外部API
const products = await fetchProductsFromDatabase(params);
return products;
});
}
// 工具设置
function setupTools(server: McpServer) {
server.registerTool({
name: "productRecommender",
description: "基于用户偏好推荐产品",
parameters: z.object({
userId: z.string(),
preferences: z.array(z.string()).optional(),
limit: z.number().min(1).max(10).default(5)
}),
execute: async ({
userId, preferences, limit }) => {
// 调用推荐引擎
const recommendations = await generateRecommendations(userId, preferences, limit);
return {
recommendations };
}
});
}
// 模拟数据库函数
async function fetchProductsFromDatabase(params: any) {
// 实际应用中,这里会查询真实数据库
return [
{
id: "1", name: "智能手表", description: "高级智能手表", price: 1999, category: "电子", inStock: true },
{
id: "2", name: "无线耳机", description: "降噪无线耳机", price: 899, category: "电子", inStock: true }
];
}
// 模拟推荐引擎
async function generateRecommendations(userId: string, preferences?: string[], limit: number = 5) {
// 实际应用中,这里会调用真实的推荐算法
return [
{
id: "3", name: "智能音箱", score: 0.95 },
{
id: "4", name: "手机支架", score: 0.82 }
].slice(0, limit);
}
// 初始化函数
export async function initializeMcpServer() {
const server = getMcpServer();
if (!server.isInitialized()) {
await server.initialize();
console.log('MCP服务器已初始化');
}
return server;
}
// 清理函数
export async function shutdownMcpServer() {
if (mcpServerInstance && mcpServerInstance.isInitialized()) {
await mcpServerInstance.shutdown();
console.log('MCP服务器已关闭');
mcpServerInstance = null;
}
}
通过这种方式,我们可以在Next.js应用的不同部分访问同一个MCP服务器实例,实现资源共享和状态一致性。
2.2 服务器端组件中的MCP集成
Next.js 13引入的React服务器组件提供了在服务器端直接访问MCP服务的能力,无需通过API层:
// 文件: src/app/products/page.tsx
// 服务器组件示例
import { getMcpServer, initializeMcpServer } from '@/lib/mcp-server';
import ProductCard from '@/components/ProductCard';
export const revalidate = 3600; // 页面缓存1小时
export default async function ProductsPage() {
// 在服务器组件中直接使用MCP
await initializeMcpServer();
const mcpServer = getMcpServer();
// 使用MCP资源获取产品数据
const products = await mcpServer.getResource("products", {
sort: { price: 'asc' },
filter: { inStock: true }
});
// 使用MCP工具生成用户推荐
const userId = 'user-123'; // 实际应用中从会话获取
const recommendations = await mcpServer.executeTool("productRecommender", {
userId,
limit: 3
});
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-6">产品目录</h1>
{recommendations.recommendations.length > 0 && (
<div className="mb-8">
<h2 className="text-2xl font-semibold mb-4">推荐产品</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{recommendations.recommendations.map(rec => (
<div key={rec.id} className="bg-blue-50 p-4 rounded-lg border border-blue-200">
<p className="font-medium">{rec.name}</p>
<p className="text-sm text-gray-500">匹配度: {(rec.score * 100).toFixed(0)}%</p>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}
这种方法的优势在于,MCP服务直接在服务器上执行,无需额外的网络请求,同时服务器组件自动处理了数据获取和渲染,减少了客户端JavaScript的负担。
2.3 API路由与MCP服务
对于需要在客户端组件中使用MCP服务的场景,我们可以通过Next.js的API路由将MCP功能暴露为REST API:
// 文件: src/app/api/mcp/resources/[name]/route.ts
// API路由 - 资源访问
import {
NextRequest, NextResponse } from 'next/server';
import {
getMcpServer, initializeMcpServer } from '@/lib/mcp-server';
export async function POST(
request: NextRequest,
{
params }: {
params: {
name: string } }
) {
try {
const resourceName = params.name;
const requestData = await request.json();
// 初始化MCP服务器
await initializeMcpServer();
const mcpServer = getMcpServer();
// 获取资源数据
const resourceData = await mcpServer.getResource(resourceName, requestData);
return NextResponse.json({
success: true,
data: resourceData
});
} catch (error) {
console.error(`处理资源 ${
params.name} 请求出错:`, error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "未知错误"
},
{
status: 500 }
);
}
}
// 文件: src/app/api/mcp/tools/[name]/route.ts
// API路由 - 工具执行
import {
NextRequest, NextResponse } from 'next/server';
import {
getMcpServer, initializeMcpServer } from '@/lib/mcp-server';
export async function POST(
request: NextRequest,
{
params }: {
params: {
name: string } }
) {
try {
const toolName = params.name;
const parameters = await request.json();
// 初始化MCP服务器
await initializeMcpServer();
const mcpServer = getMcpServer();
// 执行工具
const result = await mcpServer.executeTool(toolName, parameters);
return NextResponse.json({
success: true,
data: result
});
} catch (error) {
console.error(`执行工具 ${
params.name} 出错:`, error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "未知错误"
},
{
status: 500 }
);
}
}
然后,在客户端组件中通过这些API端点调用MCP服务:
// 文件: src/components/ProductSearch.tsx
// 客户端组件示例
'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
export default function ProductSearch() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
setLoading(true);
try {
const response = await fetch('/api/mcp/resources/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filter: {
category: data.category,
...(data.minPrice ? { price: { $gte: parseFloat(data.minPrice) } } : {}),
...(data.maxPrice ? { price: { $lte: parseFloat(data.maxPrice) } } : {})
},
sort: { [data.sortBy]: data.sortOrder }
}),
});
const result = await response.json();
if (result.success) {
setProducts(result.data);
} else {
console.error('搜索产品失败:', result.error);
}
} catch (error) {
console.error('API请求错误:', error);
} finally {
setLoading(false);
}
};
return (
<div className="mb-8">
<form onSubmit={handleSubmit(onSubmit)} className="mb-6 p-4 bg-gray-50 rounded-lg">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium mb-1">分类</label>
<select
{...register('category')}
className="w-full p-2 border rounded"
>
<option value="">所有分类</option>
<option value="电子">电子产品</option>
<option value="家居">家居用品</option>
<option value="服装">服装</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">价格范围</label>
<div className="flex space-x-2">
<input
type="number"
placeholder="最低价"
{...register('minPrice')}
className="w-1/2 p-2 border rounded"
/>
<input
type="number"
placeholder="最高价"
{...register('maxPrice')}
className="w-1/2 p-2 border rounded"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">排序</label>
<div className="flex space-x-2">
<select
{...register('sortBy')}
className="w-1/2 p-2 border rounded"
>
<option value="price">价格</option>
<option value="name">名称</option>
</select>
<select
{...register('sortOrder')}
className="w-1/2 p-2 border rounded"
>
<option value="asc">升序</option>
<option value="desc">降序</option>
</select>
</div>
</div>
</div>
<button
type="submit"
className="mt-4 w-full md:w-auto px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
disabled={loading}
>
{loading ? '搜索中...' : '搜索产品'}
</button>
</form>
{loading ? (
<div className="flex justify-center">
<div className="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent"></div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{products.length > 0 ? (
products.map(product => (
<div key={product.id} className="border p-4 rounded-lg">
<h3 className="font-medium text-lg">{product.name}</h3>
<p className="text-gray-600 text-sm">{product.description}</p>
<p className="text-blue-600 font-bold mt-2">¥{product.price.toFixed(2)}</p>
<p className="text-sm text-gray-500">分类: {product.category}</p>
<p className="text-sm text-gray-500">
库存状态: {product.inStock ? '有货' : '缺货'}
</p>
</div>
))
) : (
<p className="text-gray-500 col-span-3 text-center">没有找到匹配的产品</p>
)}
</div>
)}
</div>
);
}
2.4 Edge运行时与MCP
Next.js的Edge运行时提供了低延迟的全球分布式执行环境,特别适合某些MCP场景。虽然Edge运行时有一些限制,但可以用于轻量级的MCP功能:
// 文件: src/app/api/mcp/edge/recommendations/route.ts
// Edge运行时API示例
import {
NextRequest, NextResponse } from 'next/server';
// 指定使用Edge运行时
export const runtime = 'edge';
// 简化版MCP工具逻辑,适用于Edge环境
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const userId = searchParams.get('userId') || 'anonymous';
const category = searchParams.get('category') || '';
try {
// 从外部API获取推荐(Edge环境下不使用完整MCP服务器)
const recommendations = await fetchRecommendationsFromApi(userId, category);
return NextResponse.json({
success: true,
data: recommendations
});
} catch (error) {
console.error('获取推荐出错:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "未知错误"
},
{
status: 500 }
);
}
}
// 模拟从外部API获取推荐
async function fetchRecommendationsFromApi(userId: string, category: string) {
// 实际应用中调用专门的推荐API
// 这里返回模拟数据
// 添加200-500ms的随机延迟模拟API调用
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300));
const allRecommendations = [
{
id: "1", name: "智能手表", category: "电子", price: 1999 },
{
id: "2", name: "无线耳机", category: "电子", price: 899 },
{
id: "3", name: "智能音箱", category: "电子", price: 699 },
{
id: "4", name: "咖啡机", category: "家居", price: 1299 },
{
id: "5", name: "空气净化器", category: "家居", price: 1599 }
];
// 如果指定了分类,按分类筛选
let filtered = category
? allRecommendations.filter(item => item.category === category)
: allRecommendations;
// 根据用户ID选择不同的排序方式(模拟个性化)
const userSeed = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
if (userSeed % 2 === 0) {
filtered.sort((a, b) => a.price - b.price); // 价格升序
} else {
filtered.sort((a, b) => b.price - a.price); // 价格降序
}
return filtered.slice(0, 3); // 返回前3个推荐
}
可以在客户端组件中使用此Edge API:
// 文件: src/components/EdgeRecommendations.tsx
// 使用Edge API的客户端组件
'use client';
import { useEffect, useState } from 'react';
export default function EdgeRecommendations({ userId, category }) {
const [recommendations, setRecommendations] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchRecommendations() {
try {
setLoading(true);
const params = new URLSearchParams();
if (userId) params.append('userId', userId);
if (category) params.append('category', category);
const response = await fetch(`/api/mcp/edge/recommendations?${params.toString()}`);
const result = await response.json();
if (result.success) {
setRecommendations(result.data);
} else {
setError(result.error);
}
} catch (err) {
setError(err instanceof Error ? err.message : '获取推荐失败');
} finally {
setLoading(false);
}
}
fetchRecommendations();
}, [userId, category]);
if (loading) {
return (
<div className="p-4 bg-gray-50 rounded-lg animate-pulse">
<div className="h-6 bg-gray-200 rounded w-1/2 mb-4"></div>
<div className="space-y-3">
<div className="h-4 bg-gray-200 rounded"></div>
<div className="h-4 bg-gray-200 rounded"></div>
<div className="h-4 bg-gray-200 rounded"></div>
</div>
</div>
);
}
if (error) {
return (
<div className="p-4 bg-red-50 text-red-600 rounded-lg">
<p>获取推荐失败: {error}</p>
</div>
);
}
if (recommendations.length === 0) {
return (
<div className="p-4 bg-gray-50 rounded-lg">
<p className="text-gray-500">暂无推荐产品</p>
</div>
);
}
return (
<div className="p-4 bg-blue-50 rounded-lg">
<h3 className="text-lg font-medium mb-3">为您推荐</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{recommendations.map(item => (
<div key={item.id} className="bg-white p-3 rounded shadow-sm">
<h4 className="font-medium">{item.name}</h4>
<p className="text-blue-600 font-bold">¥{item.price.toFixed(2)}</p>
<p className="text-sm text-gray-500">分类: {item.category}</p>
</div>
))}
</div>
</div>
);
}
Edge运行时尤其适合低延迟、高并发的MCP应用场景,如实时推荐、内容个性化和简单的AI请求处理。
通过上述示例,我们展示了如何在Next.js的不同部分(服务器组件、客户端组件、API路由、Edge运行时)集成MCP服务,创建功能丰富的现代Web应用。
3. 前端组件与MCP交互
在构建现代Web应用时,设计高效、可复用的前端组件与MCP服务交互至关重要。本节将探讨如何在不同前端框架中创建组件以优雅地与MCP服务交互,提供流畅的用户体验。
3.1 React组件设计模式
React作为流行的前端库,提供了多种模式用于设计与MCP服务交互的组件:
// 文件: src/components/mcp/McpResourceProvider.tsx
// MCP资源提供者组件
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
// 资源上下文类型
interface McpResourceContextType<T> {
data: T[] | null;
loading: boolean;
error: string | null;
refresh: () => Promise<void>;
filter: (criteria: object) => void;
sort: (field: string, order: 'asc' | 'desc') => void;
}
// 创建资源上下文
const McpResourceContext = createContext<McpResourceContextType<any> | null>(null);
// 资源提供者组件属性
interface McpResourceProviderProps<T> {
resourceName: string;
initialFilters?: object;
initialSort?: { field: string; order: 'asc' | 'desc' };
children: ReactNode;
}
// 资源提供者组件实现
export function McpResourceProvider<T>({
resourceName,
initialFilters = {},
initialSort,
children
}: McpResourceProviderProps<T>) {
const [data, setData] = useState<T[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [filters, setFilters] = useState<object>(initialFilters);
const [sortConfig, setSortConfig] = useState(initialSort);
// 获取资源数据
const fetchResource = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/mcp/resources/${resourceName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filter: filters,
sort: sortConfig ? { [sortConfig.field]: sortConfig.order } : undefined
}),
});
const result = await response.json();
if (result.success) {
setData(result.data);
} else {
setError(result.error || '获取资源失败');
}
} catch (err) {
setError(err instanceof Error ? err.message : '请求出错');
} finally {
setLoading(false);
}
};
// 初始加载和依赖变化时重新加载
useEffect(() => {
fetchResource();
}, [resourceName, JSON.stringify(filters), JSON.stringify(sortConfig)]);
// 筛选方法
const filter = (criteria: object) => {
setFilters(prev => ({ ...prev, ...criteria }));
};
// 排序方法
const sort = (field: string, order: 'asc' | 'desc') => {
setSortConfig({ field, order });
};
// 刷新方法
const refresh = async () => {
await fetchResource();
};
return (
<McpResourceContext.Provider value={
{ data, loading, error, refresh, filter, sort }}>
{children}
</McpResourceContext.Provider>
);
}
// 使用资源的钩子
export function useMcpResource<T>() {
const context = useContext(McpResourceContext);
if (context === null) {
throw new Error('useMcpResource必须在McpResourceProvider内部使用');
}
return context as McpResourceContextType<T>;
}
// 使用示例:
// <McpResourceProvider resourceName="products">
// <ProductsList />
// </McpResourceProvider>
上面的示例展示了如何使用上下文(Context)和提供者(Provider)模式创建MCP资源组件。接下来,我们展示几种不同的React组件模式:
// 文件: src/components/mcp/McpToolExecutor.tsx
// MCP工具执行组件
import { useState } from 'react';
// 工具执行组件属性
interface McpToolExecutorProps<T, R> {
toolName: string;
initialParams?: T;
onResult?: (result: R) => void;
onError?: (error: string) => void;
children: (props: {
execute: (params: T) => Promise<R | null>;
result: R | null;
loading: boolean;
error: string | null;
}) => React.ReactNode;
}
// 工具执行组件实现
export function McpToolExecutor<T extends object, R>({
toolName,
initialParams,
onResult,
onError,
children
}: McpToolExecutorProps<T, R>) {
const [result, setResult] = useState<R | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// 执行工具方法
const execute = async (params: T): Promise<R | null> => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/mcp/tools/${toolName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const responseData = await response.json();
if (responseData.success) {
const toolResult = responseData.data as R;
setResult(toolResult);
onResult?.(toolResult);
return toolResult;
} else {
const errorMessage = responseData.error || '工具执行失败';
setError(errorMessage);
onError?.(errorMessage);
return null;
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '请求出错';
setError(errorMessage);
onError?.(errorMessage);
return null;
} finally {
setLoading(false);
}
};
// 使用render props模式传递状态和方法
return <>{children({ execute, result, loading, error })}</>;
}
// 使用示例:
// <McpToolExecutor toolName="productRecommender" onResult={handleResult}>
// {({ execute, result, loading, error }) => (
// <div>
// <button
// onClick={() => execute({ userId: 'user-123', limit: 5 })}
// disabled={loading}
// >
// 获取推荐
// </button>
// {loading && <p>加载中...</p>}
// {error && <p className="text-red-500">{error}</p>}
// {result && (
// <ul>
// {result.recommendations.map(item => (
// <li key={item.id}>{item.name}</li>
// ))}
// </ul>
// )}
// </div>
// )}
// </McpToolExecutor>
以下是一个结合React hooks的示例,用于简化MCP工具的使用:
// 文件: src/hooks/useMcpTool.ts
// MCP工具Hook
import { useState, useCallback } from 'react';
// Hook返回类型
interface UseMcpToolResult<T, R> {
execute: (params: T) => Promise<R | null>;
result: R | null;
loading: boolean;
error: string | null;
reset: () => void;
}
// MCP工具Hook实现
export function useMcpTool<T extends object, R>(toolName: string): UseMcpToolResult<T, R> {
const [result, setResult] = useState<R | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// 执行工具方法
const execute = useCallback(async (params: T): Promise<R | null> => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/mcp/tools/${toolName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const responseData = await response.json();
if (responseData.success) {
const toolResult = responseData.data as R;
setResult(toolResult);
return toolResult;
} else {
const errorMessage = responseData.error || '工具执行失败';
setError(errorMessage);
return null;
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '请求出错';
setError(errorMessage);
return null;
} finally {
setLoading(false);
}
}, [toolName]);
// 重置状态
const reset = useCallback(() => {
setResult(null);
setError(null);
setLoading(false);
}, []);
return { execute, result, loading, error, reset };
}
// 使用示例:
// function ProductRecommendations({ userId }) {
// const { execute, result, loading, error } = useMcpTool('productRecommender');
//
// useEffect(() => {
// execute({ userId, limit: 5 });
// }, [userId]);
//
// if (loading) return <p>加载推荐中...</p>;
// if (error) return <p>获取推荐出错: {error}</p>;
// if (!result) return null;
//
// return (
// <div>
// <h2>为您推荐</h2>
// <ul>
// {result.recommendations.map(item => (
// <li key={item.id}>{item.name}</li>
// ))}
// </ul>
// </div>
// );
// }
这些React组件设计模式提供了与MCP服务交互的灵活方式,可根据项目需求选择合适的模式。
3.2 Vue.js集成方案
Vue.js是另一个流行的前端框架,它的响应式系统和组合式API非常适合与MCP服务集成。以下是在Vue.js中与MCP服务交互的几种模式:
// 文件: src/composables/useMcpResource.ts
// MCP资源组合式API
import {
ref, computed, watch, Ref } from 'vue';
// 资源参数类型
interface ResourceParams {
filter?: Record<string, any>;
sort?: Record<string, 'asc' | 'desc'>;
limit?: number;
page?: number;
}
// 使用MCP资源的返回类型
interface UseMcpResourceReturn<T> {
data: Ref<T[] | null>;
loading: Ref<boolean>;
error: Ref<string | null>;
params: Ref<ResourceParams>;
refresh: () => Promise<void>;
setFilter: (filter: Record<string, any>) => void;
setSort: (field: string, order: 'asc' | 'desc') => void;
setPage: (page: number) => void;
setLimit: (limit: number) => void;
}
// MCP资源组合式API实现
export function useMcpResource<T>(
resourceName: string,
initialParams: ResourceParams = {
}
): UseMcpResourceReturn<T> {
const data = ref<T[] | null>(null) as Ref<T[] | null>;
const loading = ref(false);
const error = ref<string | null>(null);
const params = ref({
...initialParams }) as Ref<ResourceParams>;
// 获取资源数据
const fetchResource = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/mcp/resources/${
resourceName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params.value),
});
const result = await response.json();
if (result.success) {
data.value = result.data;
} else {
error.value = result.error || '获取资源失败';
}
} catch (err) {
error.value = err instanceof Error ? err.message : '请求出错';
} finally {
loading.value = false;
}
};
// 参数变化时重新获取数据
watch(
() => JSON.stringify(params.value),
() => fetchResource(),
{
immediate: true }
);
// 更新筛选条件
const setFilter = (filter: Record<string, any>) => {
params.value = {
...params.value,
filter: {
...(params.value.filter || {
}), ...filter },
page: 1, // 重置页码
};
};
// 更新排序
const setSort = (field: string, order: 'asc' | 'desc') => {
params.value = {
...params.value,
sort: {
[field]: order },
};
};
// 更新页码
const setPage = (page: number) => {
params.value = {
...params.value,
page,
};
};
// 更新每页数量
const setLimit = (limit: number) => {
params.value = {
...params.value,
limit,
page: 1, // 重置页码
};
};
// 刷新数据
const refresh = async () => {
await fetchResource();
};
return {
data,
loading,
error,
params,
refresh,
setFilter,
setSort,
setPage,
setLimit,
};
}
Vue组件中使用MCP资源的示例:
<!-- 文件: src/components/ProductList.vue -->
<!-- 使用MCP资源的Vue组件 -->
<template>
<div class="product-list">
<!-- 筛选和排序控件 -->
<div class="mb-4 p-4 bg-gray-100 rounded">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- 分类筛选 -->
<div>
<label class="block text-sm font-medium mb-1">分类</label>
<select
v-model="category"
class="w-full p-2 border rounded"
@change="handleCategoryChange"
>
<option value="">所有分类</option>
<option value="电子">电子产品</option>
<option value="家居">家居用品</option>
<option value="服装">服装</option>
</select>
</div>
<!-- 价格范围 -->
<div>
<label class="block text-sm font-medium mb-1">价格范围</label>
<div class="flex space-x-2">
<input
v-model.number="minPrice"
type="number"
placeholder="最低价"
class="w-1/2 p-2 border rounded"
/>
<input
v-model.number="maxPrice"
type="number"
placeholder="最高价"
class="w-1/2 p-2 border rounded"
/>
</div>
<button
@click="applyPriceFilter"
class="mt-2 px-3 py-1 text-sm bg-blue-500 text-white rounded"
>
应用价格筛选
</button>
</div>
<!-- 排序选项 -->
<div>
<label class="block text-sm font-medium mb-1">排序</label>
<div class="flex space-x-2">
<select
v-model="sortField"
class="w-1/2 p-2 border rounded"
>
<option value="price">价格</option>
<option value="name">名称</option>
</select>
<select
v-model="sortOrder"
class="w-1/2 p-2 border rounded"
>
<option value="asc">升序</option>
<option value="desc">降序</option>
</select>
</div>
<button
@click="applySort"
class="mt-2 px-3 py-1 text-sm bg-blue-500 text-white rounded"
>
应用排序
</button>
</div>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center py-8">
<div class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent"></div>
</div>
<!-- 错误信息 -->
<div v-else-if="error" class="bg-red-100 text-red-700 p-4 rounded mb-4">
<p>加载产品时出错: {
{ error }}</p>
<button
@click="refresh"
class="mt-2 px-3 py-1 bg-red-600 text-white rounded"
>
重试
</button>
</div>
<!-- 产品列表 -->
<div v-else-if="data && data.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="product in data"
:key="product.id"
class="border p-4 rounded-lg"
>
<h3 class="font-medium text-lg">{
{ product.name }}</h3>
<p class="text-gray-600 text-sm">{
{ product.description }}</p>
<p class="text-blue-600 font-bold mt-2">¥{
{ product.price.toFixed(2) }}</p>
<p class="text-sm text-gray-500">分类: {
{ product.category }}</p>
<p class="text-sm text-gray-500">
库存状态: {
{ product.inStock ? '有货' : '缺货' }}
</p>
</div>
</div>
<!-- 空状态 -->
<div v-else class="text-center py-8 text-gray-500">
没有找到匹配的产品
</div>
<!-- 分页控件 -->
<div v-if="data && data.length > 0" class="mt-6 flex justify-center">
<div class="flex space-x-2">
<button
@click="prevPage"
:disabled="params.page === 1"
class="px-4 py-2 border rounded"
:class="{ 'opacity-50 cursor-not-allowed': params.page === 1 }"
>
上一页
</button>
<span class="px-4 py-2 bg-gray-100 rounded">
第 {
{ params.page }} 页
</span>
<button
@click="nextPage"
class="px-4 py-2 border rounded"
>
下一页
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useMcpResource } from '@/composables/useMcpResource';
// 产品类型
interface Product {
id: string;
name: string;
description: string;
price: number;
category: string;
inStock: boolean;
}
// 使用MCP资源Hook获取产品数据
const {
data,
loading,
error,
params,
refresh,
setFilter,
setSort,
setPage,
setLimit
} = useMcpResource<Product>('products', {
limit: 9,
page: 1,
sort: { price: 'asc' }
});
// 筛选和排序状态
const category = ref('');
const minPrice = ref<number | null>(null);
const maxPrice = ref<number | null>(null);
const sortField = ref('price');
const sortOrder = ref<'asc' | 'desc'>('asc');
// 分类变化处理
const handleCategoryChange = () => {
if (category.value) {
setFilter({ category: category.value });
} else {
// 移除分类筛选
const newFilter = { ...params.value.filter };
delete newFilter.category;
setFilter(newFilter);
}
};
// 应用价格筛选
const applyPriceFilter = () => {
const priceFilter: Record<string, any> = {};
if (minPrice.value !== null) {
priceFilter['price.$gte'] = minPrice.value;
}
if (maxPrice.value !== null) {
priceFilter['price.$lte'] = maxPrice.value;
}
setFilter(priceFilter);
};
// 应用排序
const applySort = () => {
setSort(sortField.value, sortOrder.value);
};
// 翻页控制
const prevPage = () => {
if (params.value.page && params.value.page > 1) {
setPage(params.value.page - 1);
}
};
const nextPage = () => {
if (params.value.page) {
setPage(params.value.page + 1);
} else {
setPage(2);
}
};
</script>
使用Vue.js的提供者模式来共享MCP服务实例:
// 文件: src/plugins/mcpPlugin.ts
// MCP服务Vue插件
import {
App, inject, InjectionKey } from 'vue';
// MCP服务接口
export interface McpService {
getResource: <T>(resourceName: string, params?: any) => Promise<T>;
executeTool: <T, R>(toolName: string, params: T) => Promise<R>;
}
// 注入键
export const McpServiceKey: InjectionKey<McpService> = Symbol('McpService');
// MCP服务实现
class McpServiceImpl implements McpService {
private apiBaseUrl: string;
constructor(apiBaseUrl: string = '/api/mcp') {
this.apiBaseUrl = apiBaseUrl;
}
async getResource<T>(resourceName: string, params?: any): Promise<T> {
const response = await fetch(`${
this.apiBaseUrl}/resources/${
resourceName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params || {
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || '获取资源失败');
}
return result.data;
}
async executeTool<T, R>(toolName: string, params: T): Promise<R> {
const response = await fetch(`${
this.apiBaseUrl}/tools/${
toolName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || '工具执行失败');
}
return result.data;
}
}
// Vue插件
export const mcpPlugin = {
install: (app: App, options?: {
apiBaseUrl?: string }) => {
const mcpService = new McpServiceImpl(options?.apiBaseUrl);
app.provide(McpServiceKey, mcpService);
}
};
// 在组件中使用MCP服务的组合式API
export function useMcpService(): McpService {
const mcpService = inject(McpServiceKey);
if (!mcpService) {
throw new Error('MCP服务未提供,请确保已安装mcpPlugin');
}
return mcpService;
}
在主应用中注册MCP插件:
// 文件: src/main.ts
// 主应用入口
import {
createApp } from 'vue';
import App from './App.vue';
import {
mcpPlugin } from './plugins/mcpPlugin';
const app = createApp(App);
// 注册MCP插件
app.use(mcpPlugin, {
apiBaseUrl: '/api/mcp'
});
app.mount('#app');
在Vue组件中使用MCP服务:
<!-- 文件: src/components/ProductRecommender.vue -->
<!-- 使用MCP工具的Vue组件 -->
<template>
<div class="product-recommender">
<h2 class="text-xl font-bold mb-4">个性化推荐</h2>
<button
@click="loadRecommendations"
class="px-4 py-2 bg-blue-600 text-white rounded mb-4"
:disabled="loading"
>
{
{ loading ? '加载中...' : '获取推荐' }}
</button>
<div v-if="error" class="bg-red-100 text-red-700 p-4 rounded mb-4">
{
{ error }}
</div>
<div v-if="recommendations.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="product in recommendations"
:key="product.id"
class="bg-blue-50 p-4 rounded-lg border border-blue-200"
>
<h3 class="font-medium">{
{ product.name }}</h3>
<p class="text-sm text-gray-500">匹配度: {
{ (product.score * 100).toFixed(0) }}%</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useMcpService } from '@/plugins/mcpPlugin';
// 推荐产品类型
interface RecommendedProduct {
id: string;
name: string;
score: number;
}
// 推荐结果类型
interface RecommendationResult {
recommendations: RecommendedProduct[];
}
// 状态
const recommendations = ref<RecommendedProduct[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
// 注入MCP服务
const mcpService = useMcpService();
// 加载推荐
const loadRecommendations = async () => {
loading.value = true;
error.value = null;
try {
// 执行推荐工具
const result = await mcpService.executeTool<
{ userId: string; limit: number },
RecommendationResult
>('productRecommender', {
userId: 'user-123', // 实际应用中应从用户会话获取
limit: 3
});
recommendations.value = result.recommendations;
} catch (err) {
error.value = err instanceof Error ? err.message : '获取推荐失败';
} finally {
loading.value = false;
}
};
</script>
这些Vue.js集成方案展示了如何利用Vue的响应式系统和组合式API与MCP服务进行交互,为用户提供流畅的体验。
3.3 状态管理与MCP数据流
在复杂的前端应用中,有效管理状态和数据流至关重要,尤其是当应用需要与MCP服务交互时。本节探讨如何将MCP数据与主流状态管理库集成。
3.3.1 Redux与MCP集成
Redux是React生态系统中流行的状态管理库,以下是一个简化版的MCP资源Redux集成:
// 文件: src/store/mcp/types.ts
// MCP Redux类型定义
// 资源状态类型
export interface McpResourceState<T> {
data: T[] | null;
loading: boolean;
error: string | null;
filters: Record<string, any>;
sort: Record<string, 'asc' | 'desc'> | null;
}
// 动作类型
export enum McpActionTypes {
FETCH_RESOURCE_REQUEST = 'mcp/FETCH_RESOURCE_REQUEST',
FETCH_RESOURCE_SUCCESS = 'mcp/FETCH_RESOURCE_SUCCESS',
FETCH_RESOURCE_FAILURE = 'mcp/FETCH_RESOURCE_FAILURE',
SET_RESOURCE_FILTERS = 'mcp/SET_RESOURCE_FILTERS',
SET_RESOURCE_SORT = 'mcp/SET_RESOURCE_SORT'
}
// 文件: src/store/mcp/actions.ts
// MCP Redux动作创建器
import {
McpActionTypes } from './types';
// 资源动作
export const fetchResourceRequest = (resourceName) => ({
type: McpActionTypes.FETCH_RESOURCE_REQUEST,
payload: {
resourceName }
});
export const fetchResourceSuccess = (resourceName, data) => ({
type: McpActionTypes.FETCH_RESOURCE_SUCCESS,
payload: {
resourceName, data }
});
export const fetchResourceFailure = (resourceName, error) => ({
type: McpActionTypes.FETCH_RESOURCE_FAILURE,
payload: {
resourceName, error }
});
export const setResourceFilters = (resourceName, filters) => ({
type: McpActionTypes.SET_RESOURCE_FILTERS,
payload: {
resourceName, filters }
});
export const setResourceSort = (resourceName, field, order) => ({
type: McpActionTypes.SET_RESOURCE_SORT,
payload: {
resourceName, sort: {
[field]: order } }
});
// 文件: src/store/mcp/thunks.ts
// MCP Redux Thunks
import {
fetchResourceRequest,
fetchResourceSuccess,
fetchResourceFailure
} from './actions';
// 获取资源异步动作
export const fetchResource = (resourceName) => async (dispatch, getState) => {
try {
dispatch(fetchResourceRequest(resourceName));
// 获取参数
const {
resources } = getState().mcp;
const resourceState = resources[resourceName] || {
filters: {
}, sort: null };
// 请求参数
const params = {
filter: resourceState.filters,
sort: resourceState.sort
};
// 发起API请求
const response = await fetch(`/api/mcp/resources/${
resourceName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
const result = await response.json();
if (result.success) {
dispatch(fetchResourceSuccess(resourceName, result.data));
} else {
dispatch(fetchResourceFailure(resourceName, result.error || '获取资源失败'));
}
} catch (error) {
dispatch(fetchResourceFailure(
resourceName,
error instanceof Error ? error.message : '请求出错'
));
}
};
使用示例:
// 文件: src/components/ProductList.tsx
// 使用Redux与MCP的React组件
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchResource } from '../store/mcp/thunks';
import { setResourceFilters, setResourceSort } from '../store/mcp/actions';
function ProductList() {
const dispatch = useDispatch();
// 从Redux状态选择数据
const {
data: products,
loading,
error,
filters
} = useSelector(state => state.mcp.resources.products || {
data: null,
loading: false,
error: null,
filters: {}
});
// 初始加载
useEffect(() => {
dispatch(fetchResource('products'));
}, [dispatch]);
// 分类筛选
const handleCategoryChange = (e) => {
const category = e.target.value;
dispatch(setResourceFilters('products', {
...filters,
category: category || undefined
}));
dispatch(fetchResource('products'));
};
// 价格排序
const handleSortByPrice = (order) => {
dispatch(setResourceSort('products', 'price', order));
dispatch(fetchResource('products'));
};
// 渲染UI
if (loading) return <div>加载中...</div>;
if (error) return <div className="text-red-500">{error}</div>;
if (!products || products.length === 0) return <div>暂无产品</div>;
return (
<div>
<div className="mb-4">
<select onChange={handleCategoryChange}>
<option value="">全部分类</option>
<option value="电子">电子产品</option>
<option value="家居">家居用品</option>
</select>
<div className="mt-2">
<button onClick={() => handleSortByPrice('asc')} className="mr-2">
价格↑
</button>
<button onClick={() => handleSortByPrice('desc')}>
价格↓
</button>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<div key={product.id} className="border p-4">
<h3>{product.name}</h3>
<p className="text-blue-600">¥{product.price}</p>
{product.category && <p className="text-sm">{product.category}</p>}
</div>
))}
</div>
</div>
);
}
export default ProductList;
3.3.2 Pinia与MCP集成(Vue.js)
Pinia是Vue.js的状态管理库,以下是一个简化的MCP资源Store:
// 文件: src/stores/useMcpResourceStore.ts
// MCP资源Pinia Store
import {
defineStore } from 'pinia';
import {
ref, computed } from 'vue';
export function createMcpResourceStore(resourceName) {
return defineStore(`mcp-resource-${
resourceName}`, () => {
// 状态
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const filters = ref({
});
const sortField = ref(null);
const sortOrder = ref('asc');
// 计算属性
const isEmpty = computed(() => !data.value || data.value.length === 0);
const sortConfig = computed(() => sortField.value
? {
[sortField.value]: sortOrder.value }
: null
);
// 获取资源
async function fetchResource() {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/mcp/resources/${
resourceName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json' },
body: JSON.stringify({
filter: filters.value,
sort: sortConfig.value
}),
});
const result = await response.json();
if (result.success) {
data.value = result.data;
} else {
error.value = result.error || '获取资源失败';
}
} catch (err) {
error.value = err instanceof Error ? err.message : '请求出错';
} finally {
loading.value = false;
}
}
// 设置筛选
function setFilter(newFilters) {
filters.value = {
...filters.value, ...newFilters };
}
// 设置排序
function setSort(field, order = 'asc') {
sortField.value = field;
sortOrder.value = order;
}
// 重置
function reset() {
data.value = null;
filters.value = {
};
sortField.value = null;
sortOrder.value = 'asc';
}
return {
// 状态
data,
loading,
error,
filters,
sortField,
sortOrder,
// 计算属性
isEmpty,
sortConfig,
// 动作
fetchResource,
setFilter,
setSort,
reset
};
});
}
// 使用示例
export const useProductsStore = createMcpResourceStore('products');
使用示例:
<!-- 文件: src/components/ProductList.vue -->
<!-- 使用Pinia MCP Store的Vue组件 -->
<template>
<div>
<!-- 筛选和排序 -->
<div class="mb-4">
<select v-model="category" @change="onCategoryChange" class="p-2 border">
<option value="">全部分类</option>
<option value="电子">电子产品</option>
<option value="家居">家居用品</option>
</select>
<div class="mt-2">
<button @click="sortByPrice('asc')" class="px-3 py-1 border">价格↑</button>
<button @click="sortByPrice('desc')" class="px-3 py-1 border ml-2">价格↓</button>
</div>
</div>
<!-- 加载状态 -->
<div v-if="productsStore.loading" class="text-center py-4">
加载中...
</div>
<!-- 错误信息 -->
<div v-else-if="productsStore.error" class="text-red-500">
{
{ productsStore.error }}
</div>
<!-- 空状态 -->
<div v-else-if="productsStore.isEmpty" class="text-center py-4">
暂无产品
</div>
<!-- 产品列表 -->
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div v-for="product in productsStore.data" :key="product.id" class="border p-4">
<h3 class="font-medium">{
{ product.name }}</h3>
<p class="text-blue-600 font-bold">¥{
{ product.price }}</p>
<p v-if="product.category" class="text-sm text-gray-500">
{
{ product.category }}
</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useProductsStore } from '../stores/useMcpResourceStore';
// Store
const productsStore = useProductsStore();
// 局部状态
const category = ref('');
// 分类变更
const onCategoryChange = () => {
if (category.value) {
productsStore.setFilter({ category: category.value });
} else {
const newFilters = { ...productsStore.filters };
delete newFilters.category;
productsStore.setFilter(newFilters);
}
productsStore.fetchResource();
};
// 价格排序
const sortByPrice = (order) => {
productsStore.setSort('price', order);
productsStore.fetchResource();
};
// 初始加载
onMounted(() => {
productsStore.fetchResource();
});
</script>
3.4 响应式UI与实时更新
现代Web应用经常需要实时更新以反映最新的数据变化。本节介绍如何创建响应式UI,使用MCP服务实现实时数据更新。
3.4.1 WebSocket与MCP服务集成
使用WebSocket可以实现MCP服务的实时更新:
// 文件: src/services/mcpWebSocketService.ts
// MCP WebSocket服务 (简化版)
import {
reactive } from 'vue'; // 或使用其他框架的状态管理
// 状态
const state = reactive({
connected: false,
error: null,
subscriptions: {
}
});
// WebSocket实例
let socket = null;
// 连接WebSocket
export function connectMcpWebSocket(url = `ws://${
window.location.host}/api/mcp/ws`) {
if (socket) {
socket.close();
}
socket = new WebSocket(url);
socket.addEventListener('open', () => {
state.connected = true;
state.error = null;
// 重新订阅所有频道
for (const channel in state.subscriptions) {
sendSubscription(channel, true);
}
});
socket.addEventListener('message', (event) => {
try {
const message = JSON.parse(event.data);
// 处理频道消息
if (message.type === 'update' && message.channel) {
const subscribers = state.subscriptions[message.channel];
if (subscribers) {
subscribers.forEach(callback => callback(message.data));
}
}
} catch (error) {
console.error('处理WebSocket消息时出错:', error);
}
});
socket.addEventListener('close', () => {
state.connected = false;
// 尝试重新连接
setTimeout(() => {
if (!state.connected) connectMcpWebSocket(url);
}, 3000);
});
socket.addEventListener('error', () => {
state.error = '连接错误';
});
}
// 发送消息和订阅请求
function sendMessage(message) {
if (socket && state.connected) {
socket.send(JSON.stringify(message));
return true;
}
return false;
}
function sendSubscription(channel, subscribe) {
return sendMessage({
type: subscribe ? 'subscribe' : 'unsubscribe',
channel
});
}
// 订阅频道
export function subscribeMcpChannel(channel, callback) {
if (!state.subscriptions[channel]) {
state.subscriptions[channel] = new Set();
if (state.connected) {
sendSubscription(channel, true);
}
}
state.subscriptions[channel].add(callback);
// 返回取消订阅的函数
return () => {
const subscribers = state.subscriptions[channel];
if (subscribers) {
subscribers.delete(callback);
if (subscribers.size === 0) {
delete state.subscriptions[channel];
if (state.connected) {
sendSubscription(channel, false);
}
}
}
};
}
// 公开API
export const getMcpWebSocketState = () => state;
使用示例(React):
// 文件: src/components/RealtimeUpdates.jsx
// 实时推荐组件
import { useEffect, useState } from 'react';
import { connectMcpWebSocket, subscribeMcpChannel } from '../services/mcpWebSocketService';
function RealtimeUpdates({ userId }) {
const [updates, setUpdates] = useState([]);
useEffect(() => {
// 连接WebSocket
connectMcpWebSocket();
// 订阅用户更新
const unsubscribe = subscribeMcpChannel(`user-updates:${userId}`, (data) => {
setUpdates(prev => [data, ...prev].slice(0, 5)); // 保留最新的5条
});
return () => unsubscribe(); // 清理订阅
}, [userId]);
if (updates.length === 0) {
return <div className="text-gray-500">暂无更新</div>;
}
return (
<div className="bg-blue-50 p-4 rounded">
<h3 className="font-medium mb-2">实时更新</h3>
<ul className="space-y-2">
{updates.map((update, index) => (
<li key={index} className="bg-white p-2 rounded shadow-sm">
<p className="text-sm">{update.message}</p>
<span className="text-xs text-gray-500">
{new Date(update.timestamp).toLocaleTimeString()}
</span>
</li>
))}
</ul>
</div>
);
}
export default RealtimeUpdates;
3.4.2 Server-Sent Events (SSE) 与MCP集成
对于只需服务器到客户端的单向实时通信,可以使用SSE:
// 文件: src/services/mcpSseService.ts
// MCP SSE服务 (极简版)
import {
reactive } from 'vue';
// 状态
const state = reactive({
connected: false,
error: null,
streams: {
}
});
// EventSource实例
let eventSource = null;
// 连接SSE
export function connectMcpSse(url = `/api/mcp/events`) {
if (eventSource) {
eventSource.close();
}
try {
eventSource = new EventSource(url);
eventSource.onopen = () => {
state.connected = true;
state.error = null;
};
eventSource.onerror = () => {
state.connected = false;
state.error = '连接错误';
};
// 为所有已注册流添加监听器
for (const streamName in state.streams) {
addStreamListener(streamName);
}
} catch (error) {
state.error = '无法建立连接';
}
}
// 添加流监听器
function addStreamListener(streamName) {
if (!eventSource) return;
eventSource.addEventListener(streamName, (event) => {
try {
const data = JSON.parse(event.data);
const subscribers = state.streams[streamName];
if (subscribers) {
subscribers.forEach(callback => callback(data));
}
} catch (error) {
console.error(`解析SSE数据出错`);
}
});
}
// 订阅流
export function subscribeMcpStream(streamName, callback) {
if (!state.streams[streamName]) {
state.streams[streamName] = new Set();
if (eventSource && state.connected) {
addStreamListener(streamName);
}
}
state.streams[streamName].add(callback);
return () => {
const subscribers = state.streams[streamName];
if (subscribers) subscribers.delete(callback);
};
}
// 公开API
export const getMcpSseState = () => state;
export const disconnectMcpSse = () => {
if (eventSource) {
eventSource.close();
eventSource = null;
state.connected = false;
}
};
使用示例(Vue):
<!-- 文件: src/components/LiveNotifications.vue -->
<!-- 实时通知组件 -->
<template>
<div class="live-notifications">
<h3 class="font-medium mb-3">
通知
<span v-if="sseState.connected" class="inline-block ml-2 w-2 h-2 bg-green-500 rounded-full"></span>
<span v-else class="inline-block ml-2 w-2 h-2 bg-red-500 rounded-full"></span>
</h3>
<div v-if="notifications.length === 0" class="text-gray-500 text-center py-4">
暂无通知
</div>
<transition-group name="list" tag="ul" class="space-y-2">
<li
v-for="notification in notifications"
:key="notification.id"
class="p-3 border rounded"
:class="{'bg-blue-50': !notification.read}"
>
<div class="flex justify-between">
<span class="font-medium">{
{ notification.title }}</span>
<span class="text-xs text-gray-500">{
{ formatTime(notification.timestamp) }}</span>
</div>
<p class="text-sm mt-1">{
{ notification.message }}</p>
</li>
</transition-group>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { connectMcpSse, subscribeMcpStream, getMcpSseState, disconnectMcpSse } from '../services/mcpSseService';
// 状态
const notifications = ref([]);
const sseState = getMcpSseState();
// 格式化时间
const formatTime = (timestamp) => {
const date = new Date(timestamp);
return date.toLocaleTimeString();
};
// 生命周期钩子
onMounted(() => {
// 连接SSE
connectMcpSse();
// 订阅通知流
const unsubscribe = subscribeMcpStream('notifications', (data) => {
if (data && data.id) {
notifications.value.unshift(data);
// 最多显示10条
if (notifications.value.length > 10) {
notifications.value.pop();
}
}
});
onUnmounted(() => {
unsubscribe(); // 清理订阅
disconnectMcpSse(); // 关闭连接
});
});
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from {
opacity: 0;
transform: translateY(-20px);
}
.list-leave-to {
opacity: 0;
transform: translateX(20px);
}
</style>
通过WebSocket和SSE等技术,我们可以为MCP应用构建实时响应式UI,为用户提供动态更新的交互体验。实时更新特别适用于协作环境、实时分析、通知系统等场景。
4. 全栈MCP应用架构
在构建全栈MCP应用时,需要考虑架构设计、数据流设计、认证与权限设计和部署与DevOps策略。本节将探讨这些关键方面。
4.1 架构设计原则与模式
全栈MCP应用的架构设计应遵循以下原则和模式:
// 文件: src/stores/mcpTool.ts
// MCP工具Pinia Store
import { defineStore } from ‘pinia’;
import { ref } from ‘vue’;
// 创建通用MCP工具Store工厂
export function createMcpToolStore<T, R>(toolName: string) {
return defineStore(mcp-tool-${toolName}
, () => {
// 状态
const result = ref<R | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
const lastExecuted = ref<number | null>(null);
// 动作
async function execute(params: T): Promise<R | null> {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/mcp/tools/${toolName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const responseData = await response.json();
lastExecuted.value = Date.now();
if (responseData.success) {
result.value = responseData.data as R;
return result.value;
} else {
error.value = responseData.error || '工具执行失败';
return null;
}
} catch (err) {
error.value = err instanceof Error ? err.message : '请求出错';
return null;
} finally {
loading.value = false;
}
}
function reset() {
result.value = null;
error.value = null;
lastExecuted.value = null;
}
return {
// 状态
result,
loading,
error,
lastExecuted,
// 动作
execute,
reset
};
});
}
// 为特定工具创建Store
export const useRecommenderStore = createMcpToolStore<
{ userId: string; limit: number },
{ recommendations: Array<{ id: string; name: string; score: number }> }
(‘productRecommender’);
## 4. 全栈MCP应用架构
在构建全栈MCP应用时,需要考虑架构设计、数据流设计、认证与权限设计和部署与DevOps策略。本节将探讨这些关键方面。
### 4.1 架构设计原则与模式
全栈MCP应用的架构设计应遵循以下原则和模式:
1. **单一职责原则**:每个组件应只负责一个功能,避免过度耦合。
2. **开放封闭原则**:组件应对外部变化封闭,对内部变化开放。
3. **依赖倒置原则**:高层模块不应依赖低层模块,两者都应依赖抽象。
4. **接口隔离原则**:客户端不应依赖它不需要的接口。
### 4.2 数据流设计
全栈MCP应用的数据流设计应考虑以下方面:
1. **数据获取**:从后端API获取数据。
2. **数据处理**:在客户端进行数据处理和转换。
3. **数据存储**:将处理后的数据存储到本地或云端。
### 4.3 认证与权限设计
全栈MCP应用的认证与权限设计应考虑以下方面:
1. **用户认证**:确保用户身份合法。
2. **权限控制**:根据用户角色和权限进行访问控制。
3. **数据保护**:对敏感数据进行加密和保护。
### 4.4 部署与DevOps策略
全栈MCP应用的部署与DevOps策略应考虑以下方面:
1. **容器化部署**:使用Docker构建镜像,包含Node.js环境和应用代码。
2. **无服务器部署**:将MCP服务拆分为多个独立的Lambda/Function。
3. **混合云部署**:将计算密集型MCP工具部署在专用高性能服务器上,将API和静态内容部署在云端。
通过上述策略,可以确保全栈MCP应用的高性能、高可用性和可扩展性。
暂无评论内容