- Published on
最简MCP - 订票系统
- Authors
- Name
- Heefan
看了MCP的介绍,一些人说MCP就像USB Protocol一样简单,但这让我很困惑,这篇文章自我梳理一下这些困惑。
booking system
开始
从 REST 选择Nextjs这种BFF的Web App,做一个Booking System, 这非常简单,直接Web App调用Remote Booking Rest API就可以了。
那么如何把这个模式变成MCP的模式呢?
MCP核心概念解释:
Host(宿主应用) - 发起请求的应用程序,在这里是Next.js BFF。Host是整个MCP架构的控制中心,负责:
- 决定何时需要调用外部工具/服务
- 管理用户交互和业务逻辑
- 协调多个MCP服务器的调用
- 处理最终的响应和渲染
Client(协议客户端) - MCP协议的实现层,是Host和Server之间的桥梁。Client负责:
- 实现MCP协议的通信规范(JSON-RPC 2.0)
- 管理与服务器的连接(stdio、SSE、WebSocket等)
- 处理协议级别的错误和重试
- 序列化/反序列化请求和响应
Server(MCP服务器) - 提供具体功能的服务提供者,在这里是Booking Server。Server负责:
- 实现具体的业务工具(search_flights、search_hotels等)
- 与外部API(航班API、酒店API)进行交互
- 返回标准化的MCP响应格式
- 处理认证、权限控制和数据验证
简单类比:
- Host = 你的手机(发起需求)
- Client = USB驱动程序(协议转换)
- Server = U盘(提供具体功能)
改成MCP模式后,我觉得真的是脱裤子放屁多此一举,一个简单的问题搞得这么复杂。
当这个需求拓展生成,用户需要通过聊天来获取订票情况, 也需要知道天气情况,有没有打折酒店, 有没有打折租车服务。 这个时候就不一样了。 这就需要一个上线文(context)
多服务集成的MCP架构
当用户通过聊天界面询问:"我要从新加坡去Harstad,帮我查一下机票、当地天气、酒店优惠和租车服务",这时MCP的优势就体现出来了:
这个架构展示了MCP的真正价值:
- 统一协议: 所有服务都通过相同的MCP协议通信
- 并行处理: 可以同时调用多个服务
- 上下文保持: AI Agent可以综合所有信息给出智能回答
- 可扩展性: 轻松添加新的旅行服务(签证、保险等)
问题来了:完全可以通过Agent来实现,为什么要用MCP呢?
在企业级开发中,很多都是专有服务,有权限限制的。 我更愿意用AI Agent直接调用各种API来实现,那为什么还要引入MCP这个中间层呢?
1. 标准化与互操作性
// 传统Agent方式 - 每个API都有不同的接口
const flightData = await fetch('/api/flights', {
method: 'POST',
body: JSON.stringify({ from: 'SIN', to: 'EVE' }),
})
const weatherData = await axios.get('/weather/api', {
params: { city: 'Harstad', format: 'json' },
})
const hotelData = await request.post('/booking/hotels', {
location: 'Harstad',
checkin: '2024-08-08',
})
// MCP方式 - 统一的协议接口
const flightResult = await mcpClient.callTool('flight-server', 'search_flights', {
from: 'SIN',
to: 'EVE',
})
const weatherResult = await mcpClient.callTool('weather-server', 'get_weather', {
location: 'Harstad',
})
const hotelResult = await mcpClient.callTool('hotel-server', 'search_hotels', {
location: 'Harstad',
date: '2024-08-08',
})
2. 开发效率与维护成本
传统Agent方式的问题:
- 每个API都需要单独的错误处理
- 不同的认证机制
- 各自的数据格式转换
- API变更时需要修改多处代码
MCP的优势:
- 统一的错误处理机制
- 标准化的认证流程
- 一致的数据格式
- 插件化架构,易于扩展
3. 安全性和权限管理
4. 生态系统效应
想象一下,如果每个AI应用都需要:
- 为每个服务编写专门的集成代码
- 处理各种不同的API格式
- 维护多套认证系统
这就像USB出现之前,每个设备都需要专门的驱动和接口。MCP就是AI工具的"USB协议"。
5. 实际的商业价值
# 传统方式:为新服务添加支持
- 研究API文档 (2-3天)
- 编写集成代码 (3-5天)
- 测试和调试 (2-3天)
- 总计:7-11天
# MCP方式:添加新服务
- 实现MCP Server (1-2天)
- 注册到MCP生态 (几分钟)
- 总计:1-2天
6. 真实场景对比
假设你要为ChatGPT添加100个新工具:
传统方式:
- OpenAI需要为每个工具单独审核和集成
- 开发者需要了解OpenAI的特定API格式
- 每次工具更新都需要重新审核
MCP方式:
- 任何符合MCP标准的工具都可以即插即用
- 开发者只需要实现一次MCP接口
- 工具更新对宿主应用透明
所以MCP的价值不在于技术上的不可替代性,而在于:
- 降低集成成本
- 提高开发效率
- 建立生态标准
- 促进工具复用
就像我们不会问"为什么要用HTTP,直接TCP不行吗?"一样,MCP提供的是更高层次的抽象和标准化。
MCP的安全性和授权机制详解
前面提到MCP提供统一的安全层,但具体是怎么实现的呢?
1. 传输层安全
MCP支持多种安全的传输方式:
2. 授权和权限控制
MCP在协议层面提供了多层安全控制:
// MCP Server 配置示例
interface MCPServerConfig {
// 服务器基本信息
name: string
version: string
// 授权配置
authorization: {
// 支持的授权类型
type: 'bearer' | 'apikey' | 'oauth2' | 'custom'
// API密钥配置
apiKey?: {
header: string // 如 'X-API-Key'
required: boolean
}
// OAuth2配置
oauth2?: {
authUrl: string
tokenUrl: string
scopes: string[]
}
// 自定义授权处理器
customAuth?: (request: MCPRequest) => Promise<boolean>
}
// 权限控制
permissions: {
// 工具级别的权限
tools: {
[toolName: string]: {
allowedHosts: string[] // 允许访问的Host列表
rateLimits: {
requests: number // 每分钟请求数限制
timeWindow: number // 时间窗口(秒)
}
requireAuth: boolean // 是否需要认证
}
}
// 资源级别的权限
resources: {
read: boolean
write: boolean
delete: boolean
}
}
}
3. 实际的安全流程
问题来了:谁来提供Auth Provider?
这是MCP架构中的关键问题。Auth Provider可以由不同的角色提供,具体取决于部署场景:
具体实现场景:
1. 企业内部场景
// 企业内部 Auth Provider 实现
class EnterpriseAuthProvider implements AuthProvider {
constructor(private ldapConfig: LDAPConfig) {}
async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
// 1. 连接企业LDAP/AD
const ldapClient = new LDAPClient(this.ldapConfig)
// 2. 验证Host应用身份
const appIdentity = await ldapClient.authenticate(credentials.clientId, credentials.secret)
// 3. 获取应用权限
const permissions = await this.getAppPermissions(hostId)
return {
valid: true,
hostId,
permissions,
token: this.generateJWT(appIdentity, permissions),
}
}
}
1.1 微软EntraID (Azure AD) 集成场景
// 微软EntraID Auth Provider 实现
class EntraIDAuthProvider implements AuthProvider {
constructor(private entraConfig: EntraIDConfig) {}
async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
try {
// 1. 使用Client Credentials Flow验证应用身份
const tokenResponse = await this.getAccessToken(credentials)
// 2. 验证令牌并获取应用信息
const appInfo = await this.validateToken(tokenResponse.access_token)
// 3. 获取应用的角色分配和权限
const permissions = await this.getApplicationPermissions(appInfo.appId, hostId)
return {
valid: true,
hostId,
permissions,
token: tokenResponse.access_token,
expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),
}
} catch (error) {
return {
valid: false,
error: `EntraID authentication failed: ${error.message}`,
}
}
}
private async getAccessToken(credentials: Credentials): Promise<TokenResponse> {
const tokenEndpoint = `https://login.microsoftonline.com/${this.entraConfig.tenantId}/oauth2/v2.0/token`
const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
scope: 'https://graph.microsoft.com/.default', // 或自定义scope
}),
})
if (!response.ok) {
throw new Error(`Token request failed: ${response.statusText}`)
}
return await response.json()
}
private async validateToken(accessToken: string): Promise<AppInfo> {
// 验证JWT token并提取应用信息
const decoded = jwt.verify(accessToken, this.entraConfig.publicKey)
return {
appId: decoded.appid,
tenantId: decoded.tid,
roles: decoded.roles || [],
scopes: decoded.scp?.split(' ') || [],
}
}
private async getApplicationPermissions(appId: string, hostId: string): Promise<string[]> {
// 根据EntraID中的应用角色分配确定权限
const rolePermissionMap = {
'MCP.FlightSearch.Read': ['search_flights', 'get_prices'],
'MCP.HotelSearch.Read': ['search_hotels', 'get_deals'],
'MCP.Booking.Write': ['make_reservation', 'cancel_booking'],
'MCP.Weather.Read': ['get_weather', 'get_forecast'],
'MCP.Admin': ['*'], // 所有权限
}
const appRoles = await this.getAppRolesFromGraph(appId)
const permissions = new Set<string>()
appRoles.forEach((role) => {
const rolePermissions = rolePermissionMap[role]
if (rolePermissions) {
rolePermissions.forEach((perm) => permissions.add(perm))
}
})
return Array.from(permissions)
}
private async getAppRolesFromGraph(appId: string): Promise<string[]> {
// 调用Microsoft Graph API获取应用角色
const graphEndpoint = `https://graph.microsoft.com/v1.0/applications/${appId}/appRoleAssignments`
const response = await fetch(graphEndpoint, {
headers: {
Authorization: `Bearer ${this.entraConfig.graphToken}`,
'Content-Type': 'application/json',
},
})
const data = await response.json()
return data.value.map((assignment) => assignment.appRole.value)
}
}
EntraID 配置示例:
# MCP Server 配置文件 - EntraID集成
mcp_server:
name: 'travel-booking-server'
version: '1.0.0'
auth_provider:
type: 'entraid'
entraid:
# 租户配置
tenant_id: '${AZURE_TENANT_ID}'
# 应用注册信息
client_id: '${MCP_SERVER_CLIENT_ID}'
client_secret: '${MCP_SERVER_CLIENT_SECRET}'
# 权限范围
scopes:
- 'https://graph.microsoft.com/Application.Read.All'
- 'api://mcp-travel-server/MCP.Access'
# 角色到权限的映射
role_permissions:
'MCP.FlightSearch.Reader':
permissions: ['search_flights', 'get_prices']
rate_limits:
requests_per_minute: 100
'MCP.TravelAgent.Full':
permissions: ['search_flights', 'search_hotels', 'make_reservation']
rate_limits:
requests_per_minute: 200
'MCP.Admin':
permissions: ['*']
rate_limits:
requests_per_minute: 500
# Host应用的EntraID注册
allowed_hosts:
'claude.ai':
entraid_app_id: '${CLAUDE_APP_ID}'
required_roles: ['MCP.TravelAgent.Full']
'openai.com':
entraid_app_id: '${OPENAI_APP_ID}'
required_roles: ['MCP.FlightSearch.Reader']
EntraID中的应用注册配置:
{
"displayName": "MCP Travel Booking Server",
"signInAudience": "AzureADMyOrg",
"api": {
"requestedAccessTokenVersion": 2,
"oauth2PermissionScopes": [
{
"id": "12345678-1234-1234-1234-123456789abc",
"adminConsentDescription": "允许应用访问MCP旅行预订服务",
"adminConsentDisplayName": "访问MCP服务",
"isEnabled": true,
"type": "Admin",
"userConsentDescription": "允许应用代表您访问旅行预订服务",
"userConsentDisplayName": "访问旅行服务",
"value": "MCP.Access"
}
]
},
"appRoles": [
{
"id": "87654321-4321-4321-4321-210987654321",
"allowedMemberTypes": ["Application"],
"description": "只读访问航班搜索功能",
"displayName": "Flight Search Reader",
"isEnabled": true,
"value": "MCP.FlightSearch.Reader"
},
{
"id": "11111111-2222-3333-4444-555555555555",
"allowedMemberTypes": ["Application"],
"description": "完整的旅行代理权限",
"displayName": "Travel Agent Full Access",
"isEnabled": true,
"value": "MCP.TravelAgent.Full"
}
]
}
安全流程增强 - EntraID集成:
EntraID集成的优势:
- 企业级安全: 利用Microsoft的企业级身份管理
- 细粒度权限: 通过App Roles实现精确的权限控制
- 审计合规: 集成Azure Monitor进行全面审计
- SSO集成: 与企业现有SSO体系无缝集成
- 条件访问: 支持基于设备、位置的条件访问策略
- 多租户支持: 支持跨租户的B2B协作场景
2. 第三方服务场景
// Auth0 集成示例
class Auth0Provider implements AuthProvider {
constructor(private auth0Config: Auth0Config) {}
async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
// 1. 调用Auth0 API验证
const auth0Response = await fetch(`${this.auth0Config.domain}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
audience: 'mcp-api',
grant_type: 'client_credentials',
}),
})
const tokenData = await auth0Response.json()
// 2. 解析权限范围
const permissions = this.parseScopes(tokenData.scope)
return {
valid: true,
hostId,
permissions,
token: tokenData.access_token,
}
}
}
3. MCP Server自建场景
// MCP Server 内置认证
class MCPServerAuthProvider implements AuthProvider {
constructor(private apiKeyStore: APIKeyStore) {}
async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
// 1. 验证API Key
const apiKeyInfo = await this.apiKeyStore.validate(credentials.apiKey)
if (!apiKeyInfo.valid) {
return { valid: false, error: 'Invalid API key' }
}
// 2. 检查Host白名单
if (!apiKeyInfo.allowedHosts.includes(hostId)) {
return { valid: false, error: 'Host not authorized' }
}
// 3. 返回权限信息
return {
valid: true,
hostId,
permissions: apiKeyInfo.permissions,
token: this.generateSessionToken(hostId, apiKeyInfo),
}
}
}
实际部署配置示例:
# MCP Server 配置文件
mcp_server:
name: 'travel-booking-server'
version: '1.0.0'
# Auth Provider 配置
auth_provider:
# 选择认证提供方
type: 'enterprise' # 可选: enterprise, auth0, okta, builtin, aws_cognito
# 企业内部认证配置
enterprise:
ldap_url: 'ldap://company.local:389'
base_dn: 'dc=company,dc=local'
admin_user: 'cn=admin,dc=company,dc=local'
admin_password: '${LDAP_ADMIN_PASSWORD}'
# 或者使用Auth0
auth0:
domain: 'https://company.auth0.com'
client_id: '${AUTH0_CLIENT_ID}'
client_secret: '${AUTH0_CLIENT_SECRET}'
audience: 'mcp-travel-api'
# 或者内置API Key方式
builtin:
api_keys:
- key: '${CLAUDE_API_KEY}'
name: 'Claude AI'
permissions: ['search_flights', 'search_hotels']
rate_limits:
requests_per_minute: 100
- key: '${CHATGPT_API_KEY}'
name: 'ChatGPT'
permissions: ['search_flights', 'get_weather']
rate_limits:
requests_per_minute: 50
# 权限控制
permissions:
default_permissions: ['search_flights']
host_specific:
'claude.ai':
permissions: ['search_flights', 'search_hotels', 'make_reservation']
rate_limits:
requests_per_minute: 200
'openai.com':
permissions: ['search_flights', 'get_weather']
rate_limits:
requests_per_minute: 100
总结:Auth Provider的选择
场景 | Auth Provider | 优势 | 适用情况 |
---|---|---|---|
企业内部 | LDAP/AD/SSO | 与现有系统集成 | 大型企业内部应用 |
SaaS应用 | Auth0/Okta | 专业身份服务 | 需要企业级认证功能 |
简单部署 | 内置API Key | 配置简单快速 | 小团队或原型项目 |
云原生 | AWS Cognito/Azure AD | 云平台深度集成 | 已使用云服务的项目 |
开源项目 | JWT + 自建 | 完全控制 | 开源或完全自主可控 |
所以Auth Provider不是MCP协议本身提供的,而是由部署MCP Server的组织根据自己的安全要求和现有基础设施来选择和配置的。
4. 具体的安全实现
4.1 认证令牌管理
// MCP Client端的安全实现
class SecureMCPClient {
private authTokens: Map<string, AuthToken> = new Map()
async connectToServer(serverConfig: MCPServerConfig) {
// 1. 验证服务器证书
const serverInfo = await this.validateServerCertificate(serverConfig)
// 2. 获取认证令牌
const authToken = await this.authenticate(serverConfig.authorization)
// 3. 建立安全连接
const connection = await this.establishSecureConnection(serverConfig, authToken)
return connection
}
private async authenticate(authConfig: AuthConfig): Promise<AuthToken> {
switch (authConfig.type) {
case 'oauth2':
return await this.handleOAuth2Flow(authConfig.oauth2)
case 'apikey':
return await this.handleAPIKeyAuth(authConfig.apiKey)
case 'bearer':
return await this.handleBearerTokenAuth(authConfig.bearer)
default:
throw new Error('Unsupported auth type')
}
}
}
4.2 权限验证和速率限制
// MCP Server端的安全中间件
class MCPSecurityMiddleware {
async validateRequest(request: MCPRequest, context: SecurityContext): Promise<boolean> {
// 1. 验证Host身份
const hostValid = await this.validateHost(context.hostId, context.credentials)
if (!hostValid) return false
// 2. 检查工具权限
const toolPermissions = this.getToolPermissions(request.method, context.hostId)
if (!toolPermissions.allowed) return false
// 3. 速率限制检查
const rateLimitOk = await this.checkRateLimit(context.hostId, request.method)
if (!rateLimitOk) return false
// 4. 参数安全验证
const paramsValid = await this.validateParameters(request.params)
if (!paramsValid) return false
return true
}
private async checkRateLimit(hostId: string, method: string): Promise<boolean> {
const key = `${hostId}:${method}`
const current = (await this.redis.get(key)) || 0
const limit = this.rateLimits[method] || 100
if (current >= limit) {
await this.logSecurityEvent('rate_limit_exceeded', { hostId, method })
return false
}
await this.redis.incr(key)
await this.redis.expire(key, 60) // 1分钟窗口
return true
}
}
5. 数据隐私和审计
// 安全审计系统
class MCPAuditLogger {
async logSecurityEvent(event: SecurityEvent) {
const auditRecord = {
timestamp: new Date().toISOString(),
hostId: event.hostId,
serverId: event.serverId,
action: event.action,
success: event.success,
ipAddress: event.ipAddress,
userAgent: event.userAgent,
// 敏感数据处理
requestData: this.sanitizeData(event.requestData),
responseData: this.sanitizeData(event.responseData),
// 安全相关
authMethod: event.authMethod,
permissions: event.permissions,
rateLimitStatus: event.rateLimitStatus,
}
// 写入安全审计日志
await this.writeToAuditLog(auditRecord)
// 异常检测
if (this.detectSuspiciousActivity(auditRecord)) {
await this.triggerSecurityAlert(auditRecord)
}
}
private sanitizeData(data: any): any {
// 移除敏感信息:密码、API密钥、个人信息等
return this.deepClone(data, this.sensitiveFieldsFilter)
}
}
6. 与传统API相比的安全优势
安全层面 | 传统API调用 | MCP协议 |
---|---|---|
认证标准化 | 每个API不同的认证方式 | 统一的认证协议 |
权限粒度 | 通常是API级别 | 工具级别 + 参数级别 |
审计追踪 | 分散在各个服务 | 集中式审计日志 |
速率控制 | 各自实现 | 协议层统一控制 |
数据隐私 | 依赖各服务实现 | 协议层数据脱敏 |
异常检测 | 需要额外开发 | 内建安全监控 |
所以MCP的安全层不仅仅是一个"包装器",而是一个完整的安全框架,提供了:
- 标准化的认证和授权机制
- 细粒度的权限控制
- 统一的安全审计
- 内建的威胁检测
- 数据隐私保护
理论上说MCP提供了比传统API集成更安全的解决方案。
--- End ---