MCP入门教程-第7章:MCP 传输 (Transports)

115 阅读7分钟

本文将深入探讨MCP传输层的核心概念、实现机制,并通过TypeScript SDK提供详细的代码示例,帮助开发者理解和实践MCP传输层的开发。

MCP传输层核心概念

什么是传输层

传输层在模型上下文协议(MCP)中为客户端和服务器之间的通信提供基础支撑,负责处理消息发送和接收的底层机制。传输层的主要职责包括:

  • 消息格式转换:将MCP协议消息转换为JSON-RPC格式进行传输
  • 连接管理:维护客户端和服务器之间的连接状态
  • 错误处理:处理各种网络和协议错误
  • 资源管理:合理分配和释放网络资源

JSON-RPC 2.0消息格式

MCP使用JSON-RPC 2.0作为其线路格式,传输层负责将MCP协议消息转换为JSON-RPC格式进行传输。JSON-RPC消息包含三种类型:

  1. 请求(Requests) :客户端向服务器发送的操作请求
  2. 响应(Responses) :服务器对请求的回复
  3. 通知(Notifications) :单向消息,不需要响应

内置传输类型详解

1. 标准输入输出传输 (Stdio Transport)

stdio传输通过标准输入和输出流进行通信,特别适用于本地集成和命令行工具。

适用场景

  • 构建命令行工具
  • 实现本地集成
  • 简单的进程间通信
  • Shell脚本集成

TypeScript实现示例

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

class StdioMCPServer {
  private server: McpServer;
  private transport: StdioServerTransport;

  constructor() {
    // 初始化MCP服务器
    this.server = new McpServer(
      {
        name: "example-stdio-server",
        version: "1.0.0",
      },
      {
        capabilities: {
          resources: {},
          tools: {},
          prompts: {}
        }
      }
    );

    // 创建stdio传输
    this.transport = new StdioServerTransport();
  }

  async start() {
    try {
      // 连接服务器和传输层
      await this.server.connect(this.transport);
      console.error("Stdio MCP server started successfully");
    } catch (error) {
      console.error("Failed to start stdio server:", error);
      process.exit(1);
    }
  }

  // 添加工具处理
  setupTools() {
    this.server.setRequestHandler("tools/list", async () => ({
      tools: [
        {
          name: "echo",
          description: "Echo back the input",
          inputSchema: {
            type: "object",
            properties: {
              message: {
                type: "string",
                description: "Message to echo"
              }
            }
          }
        }
      ]
    }));

    this.server.setRequestHandler("tools/call", async (request) => {
      const { name, arguments: args } = request.params;
      
      if (name === "echo") {
        return {
          content: [
            {
              type: "text",
              text: `Echo: ${args.message}`
            }
          ]
        };
      }
      
      throw new Error(`Unknown tool: ${name}`);
    });
  }
}

// 启动服务器
const server = new StdioMCPServer();
server.setupTools();
server.start();

2. 服务器发送事件传输 (SSE Transport)

SSE传输支持服务器到客户端的流式传输,并通过HTTP POST请求实现客户端到服务器的通信。

适用场景

  • 仅需要服务器到客户端的流式传输
  • 网络受限环境
  • 简单的更新推送

TypeScript实现示例

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import cors from "cors";

class SSEMCPServer {
  private server: McpServer;
  private app: express.Application;
  private port: number;

  constructor(port: number = 3000) {
    this.port = port;
    this.app = express();
    
    // 初始化MCP服务器
    this.server = new McpServer(
      {
        name: "example-sse-server",
        version: "1.0.0",
      },
      {
        capabilities: {
          resources: {},
          tools: {},
          prompts: {}
        }
      }
    );

    this.setupMiddleware();
    this.setupRoutes();
  }

  private setupMiddleware() {
    // 配置CORS以防止DNS重绑定攻击
    this.app.use(cors({
      origin: (origin, callback) => {
        // 验证Origin头,防止DNS重绑定攻击
        const allowedOrigins = [
          'http://localhost:3000',
          'http://127.0.0.1:3000'
        ];
        
        if (!origin || allowedOrigins.includes(origin)) {
          callback(null, true);
        } else {
          callback(new Error('Not allowed by CORS'));
        }
      }
    }));

    this.app.use(express.json());
  }

  private setupRoutes() {
    // SSE端点
    this.app.get('/sse', (req, res) => {
      // 验证Origin头
      const origin = req.get('Origin');
      if (origin && !['http://localhost:3000', 'http://127.0.0.1:3000'].includes(origin)) {
        res.status(403).send('Forbidden');
        return;
      }

      // 设置SSE头
      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Access-Control-Allow-Origin': origin || 'http://localhost:3000'
      });

      // 创建SSE传输
      const transport = new SSEServerTransport(res, req);
      
      // 连接服务器
      this.server.connect(transport).catch(error => {
        console.error('SSE connection failed:', error);
        res.end();
      });

      // 处理客户端断开连接
      req.on('close', () => {
        console.log('SSE client disconnected');
      });
    });

    // POST端点用于客户端到服务器的消息
    this.app.post('/message', async (req, res) => {
      try {
        // 验证Origin头
        const origin = req.get('Origin');
        if (origin && !['http://localhost:3000', 'http://127.0.0.1:3000'].includes(origin)) {
          res.status(403).json({ error: 'Forbidden' });
          return;
        }

        // 处理消息
        const message = req.body;
        console.log('Received message:', message);
        
        res.json({ status: 'received' });
      } catch (error) {
        console.error('Message handling error:', error);
        res.status(500).json({ error: 'Internal server error' });
      }
    });
  }

  async start() {
    // 绑定到localhost以防止安全风险
    this.app.listen(this.port, '127.0.0.1', () => {
      console.log(`SSE MCP server running on http://127.0.0.1:${this.port}`);
    });
  }
}

// 启动服务器
const sseServer = new SSEMCPServer(3000);
sseServer.start();

自定义传输实现

MCP使自定义传输的实现变得简单,任何传输实现都只需要符合Transport接口。

自定义WebSocket传输示例

import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
import WebSocket from "ws";

export class WebSocketTransport implements Transport {
  private ws: WebSocket;
  private messageHandlers: Set<(message: JSONRPCMessage) => void> = new Set();
  private errorHandlers: Set<(error: Error) => void> = new Set();
  private closeHandlers: Set<() => void> = new Set();

  constructor(private url: string) {
    this.ws = new WebSocket(url);
    this.setupEventHandlers();
  }

  private setupEventHandlers() {
    this.ws.on('message', (data: Buffer) => {
      try {
        const message: JSONRPCMessage = JSON.parse(data.toString());
        this.messageHandlers.forEach(handler => handler(message));
      } catch (error) {
        this.errorHandlers.forEach(handler => 
          handler(new Error(`Message parsing error: ${error}`))
        );
      }
    });

    this.ws.on('error', (error: Error) => {
      this.errorHandlers.forEach(handler => handler(error));
    });

    this.ws.on('close', () => {
      this.closeHandlers.forEach(handler => handler());
    });
  }

  async send(message: JSONRPCMessage): Promise<void> {
    return new Promise((resolve, reject) => {
      if (this.ws.readyState !== WebSocket.OPEN) {
        reject(new Error('WebSocket connection not ready'));
        return;
      }

      try {
        const data = JSON.stringify(message);
        this.ws.send(data, (error) => {
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        });
      } catch (error) {
        reject(error);
      }
    });
  }

  onMessage(handler: (message: JSONRPCMessage) => void): void {
    this.messageHandlers.add(handler);
  }

  onError(handler: (error: Error) => void): void {
    this.errorHandlers.add(handler);
  }

  onClose(handler: () => void): void {
    this.closeHandlers.add(handler);
  }

  async close(): Promise<void> {
    return new Promise((resolve) => {
      if (this.ws.readyState === WebSocket.CLOSED) {
        resolve();
        return;
      }

      this.ws.once('close', () => resolve());
      this.ws.close();
    });
  }
}

// 使用示例
class WebSocketMCPClient {
  private client: any; // MCP Client类型
  private transport: WebSocketTransport;

  constructor(url: string) {
    this.transport = new WebSocketTransport(url);
  }

  async connect() {
    try {
      // 等待WebSocket连接建立
      await this.waitForConnection();
      
      // 创建MCP客户端并连接传输
      // await this.client.connect(this.transport);
      
      console.log('WebSocket MCP client connected successfully');
    } catch (error) {
      console.error('Connection failed:', error);
      throw error;
    }
  }

  private waitForConnection(): Promise<void> {
    return new Promise((resolve, reject) => {
      const ws = (this.transport as any).ws;
      
      if (ws.readyState === WebSocket.OPEN) {
        resolve();
        return;
      }

      const timeout = setTimeout(() => {
        reject(new Error('Connection timeout'));
      }, 5000);

      ws.once('open', () => {
        clearTimeout(timeout);
        resolve();
      });

      ws.once('error', (error: Error) => {
        clearTimeout(timeout);
        reject(error);
      });
    });
  }
}

错误处理与最佳实践

错误处理策略

传输实现应该处理各种错误场景,包括连接错误、消息解析错误、协议错误、网络超时和资源清理。

class RobustTransport implements Transport {
  private reconnectAttempts: number = 0;
  private maxReconnectAttempts: number = 5;
  private reconnectDelay: number = 1000;
  private heartbeatInterval?: NodeJS.Timeout;
  private messageQueue: JSONRPCMessage[] = [];

  async send(message: JSONRPCMessage): Promise<void> {
    try {
      // 实现重试机制
      await this.sendWithRetry(message, 3);
    } catch (error) {
      // 如果发送失败,将消息加入队列
      this.messageQueue.push(message);
      throw error;
    }
  }

  private async sendWithRetry(
    message: JSONRPCMessage, 
    retries: number
  ): Promise<void> {
    for (let i = 0; i < retries; i++) {
      try {
        await this.actualSend(message);
        return;
      } catch (error) {
        if (i === retries - 1) throw error;
        
        // 指数退避
        await this.delay(Math.pow(2, i) * 1000);
      }
    }
  }

  private async actualSend(message: JSONRPCMessage): Promise<void> {
    // 实际发送逻辑
    // 验证消息格式
    this.validateMessage(message);
    
    // 检查消息大小
    const messageSize = JSON.stringify(message).length;
    if (messageSize > 1024 * 1024) { // 1MB限制
      throw new Error(`Message too large: ${messageSize} bytes`);
    }

    // 发送消息...
  }

  private validateMessage(message: JSONRPCMessage): void {
    if (!message.jsonrpc || message.jsonrpc !== "2.0") {
      throw new Error("Invalid JSON-RPC version");
    }

    if (!message.id && !message.method) {
      throw new Error("Invalid JSON-RPC message");
    }
  }

  private async reconnect(): Promise<void> {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      throw new Error("Max reconnection attempts exceeded");
    }

    this.reconnectAttempts++;
    
    try {
      await this.delay(this.reconnectDelay * this.reconnectAttempts);
      await this.connect();
      
      // 重连成功后,发送队列中的消息
      await this.flushMessageQueue();
      
      this.reconnectAttempts = 0;
    } catch (error) {
      console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error);
      throw error;
    }
  }

  private async flushMessageQueue(): Promise<void> {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift()!;
      try {
        await this.actualSend(message);
      } catch (error) {
        // 如果仍然失败,重新加入队列
        this.messageQueue.unshift(message);
        break;
      }
    }
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  private startHeartbeat(): void {
    this.heartbeatInterval = setInterval(async () => {
      try {
        await this.ping();
      } catch (error) {
        console.error('Heartbeat failed:', error);
        await this.reconnect();
      }
    }, 30000); // 30秒心跳
  }

  private async ping(): Promise<void> {
    // 发送ping消息检查连接状态
    await this.send({
      jsonrpc: "2.0",
      method: "ping",
      id: Date.now()
    });
  }

  async close(): Promise<void> {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }
    
    // 清理资源...
  }
}

安全考虑

SSE传输可能容易受到DNS重绑定攻击,需要验证Origin头、避免绑定到所有网络接口,并为所有SSE连接实施适当的身份验证。

class SecureSSETransport {
  private validateOrigin(origin: string | undefined): boolean {
    const allowedOrigins = [
      'https://myapp.com',
      'https://localhost:3000',
      'https://127.0.0.1:3000'
    ];
    
    return origin ? allowedOrigins.includes(origin) : false;
  }

  private authenticateClient(token: string): boolean {
    // 实现JWT或其他认证机制
    try {
      // 验证token
      return this.verifyJWT(token);
    } catch (error) {
      console.error('Authentication failed:', error);
      return false;
    }
  }

  private verifyJWT(token: string): boolean {
    // JWT验证逻辑
    return true; // 简化示例
  }

  setupSecureSSEEndpoint(app: express.Application) {
    app.get('/secure-sse', (req, res) => {
      // 1. 验证Origin头
      const origin = req.get('Origin');
      if (!this.validateOrigin(origin)) {
        res.status(403).send('Invalid origin');
        return;
      }

      // 2. 验证认证token
      const token = req.get('Authorization')?.replace('Bearer ', '');
      if (!token || !this.authenticateClient(token)) {
        res.status(401).send('Unauthorized');
        return;
      }

      // 3. 实施速率限制
      if (!this.checkRateLimit(req.ip)) {
        res.status(429).send('Rate limit exceeded');
        return;
      }

      // 4. 设置安全头
      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'DENY',
        'Access-Control-Allow-Origin': origin
      });

      // 继续SSE处理...
    });
  }

  private checkRateLimit(ip: string): boolean {
    // 实现速率限制逻辑
    return true; // 简化示例
  }
}

性能优化与监控

连接池管理

class ConnectionPoolTransport {
  private pools: Map<string, WebSocketTransport[]> = new Map();
  private poolSize: number = 5;
  private activeConnections: Map<string, number> = new Map();

  async getConnection(endpoint: string): Promise<WebSocketTransport> {
    let pool = this.pools.get(endpoint);
    
    if (!pool) {
      pool = [];
      this.pools.set(endpoint, pool);
      this.activeConnections.set(endpoint, 0);
    }

    // 检查空闲连接
    for (const connection of pool) {
      if (await this.isConnectionHealthy(connection)) {
        return connection;
      }
    }

    // 创建新连接
    if ((this.activeConnections.get(endpoint) || 0) < this.poolSize) {
      const connection = new WebSocketTransport(endpoint);
      pool.push(connection);
      this.activeConnections.set(endpoint, pool.length);
      return connection;
    }

    throw new Error('Connection pool exhausted');
  }

  private async isConnectionHealthy(connection: WebSocketTransport): Promise<boolean> {
    try {
      // 发送ping检查连接健康状态
      await connection.send({
        jsonrpc: "2.0",
        method: "ping",
        id: Date.now()
      });
      return true;
    } catch (error) {
      return false;
    }
  }

  async releaseConnection(endpoint: string, connection: WebSocketTransport): Promise<void> {
    const pool = this.pools.get(endpoint);
    if (pool) {
      // 连接回到池中,可以被重用
      console.log(`Connection returned to pool for ${endpoint}`);
    }
  }
}

监控和指标

class MonitoredTransport implements Transport {
  private metrics = {
    messagesSent: 0,
    messagesReceived: 0,
    errorsCount: 0,
    connectionTime: 0,
    avgResponseTime: 0,
    totalRequests: 0
  };

  private responseTimestamps: Map<string, number> = new Map();

  async send(message: JSONRPCMessage): Promise<void> {
    const startTime = Date.now();
    
    try {
      // 记录请求时间戳
      if (message.id) {
        this.responseTimestamps.set(String(message.id), startTime);
      }

      await this.actualSend(message);
      
      this.metrics.messagesSent++;
      this.logMetrics();
    } catch (error) {
      this.metrics.errorsCount++;
      console.error('Send error:', error);
      throw error;
    }
  }

  onMessage(handler: (message: JSONRPCMessage) => void): void {
    this.baseTransport.onMessage((message) => {
      this.metrics.messagesReceived++;
      
      // 计算响应时间
      if (message.id) {
        const requestTime = this.responseTimestamps.get(String(message.id));
        if (requestTime) {
          const responseTime = Date.now() - requestTime;
          this.updateAverageResponseTime(responseTime);
          this.responseTimestamps.delete(String(message.id));
        }
      }

      handler(message);
    });
  }

  private updateAverageResponseTime(responseTime: number): void {
    this.metrics.totalRequests++;
    this.metrics.avgResponseTime = 
      (this.metrics.avgResponseTime * (this.metrics.totalRequests - 1) + responseTime) 
      / this.metrics.totalRequests;
  }

  private logMetrics(): void {
    if (this.metrics.messagesSent % 100 === 0) {
      console.log('Transport Metrics:', {
        sent: this.metrics.messagesSent,
        received: this.metrics.messagesReceived,
        errors: this.metrics.errorsCount,
        avgResponseTime: this.metrics.avgResponseTime.toFixed(2) + 'ms',
        errorRate: (this.metrics.errorsCount / this.metrics.messagesSent * 100).toFixed(2) + '%'
      });
    }
  }

  getMetrics() {
    return { ...this.metrics };
  }
}

总结

MCP传输层是构建高效、可靠的AI应用集成的关键组件。通过深入理解传输层的工作原理,掌握stdio和SSE两种内置传输类型的使用方法,以及学会实现自定义传输,开发者可以构建适应各种场景需求的MCP应用。

TypeScript SDK提供了全面的工具包,简化了MCP客户端和服务器的创建过程,使开发者能够专注于业务逻辑的实现,而不必过度关注底层通信细节。

在实际开发中,务必重视安全性考虑,实施适当的错误处理和性能优化策略,确保MCP应用在生产环境中的稳定性和可靠性。随着AI应用生态的不断发展,MCP传输层将继续演进,为更复杂的AI集成场景提供更强大的支持。