MCP入门教程-第5章:MCP 采样 (Sampling)

77 阅读8分钟

什么是MCP采样

采样是MCP的一个强大功能,它允许MCP服务器通过客户端向大语言模型请求文本补全。这种设计模式支持复杂的智能体工作流,同时保持人工监督和控制。

核心设计理念

采样功能采用"人工参与循环"(Human-in-the-loop)设计,确保用户始终保持对LLM交互的控制权:

  1. 用户可控性:用户可以审查、修改或拒绝提示和完成内容
  2. 安全性:所有采样请求都经过客户端验证和过滤
  3. 隐私性:敏感信息可以在客户端被过滤或修改
  4. 透明性:用户可以看到服务器请求的具体内容

技术架构与工作流程

采样工作流程

服务器 → 客户端 → LLM → 客户端 → 服务器
  ↓        ↓        ↓       ↓        ↓
发送请求  审查请求   生成    审查结果  接收结果
        可修改    内容    可修改

具体步骤:

  1. 服务器发送请求:服务器发送sampling/createMessage请求到客户端
  2. 客户端审查:客户端审查请求,可以修改提示、系统指令或上下文
  3. LLM采样:客户端向LLM发送经过审查的请求
  4. 结果审查:客户端审查LLM的响应,可以进行过滤或修改
  5. 返回结果:客户端将最终结果返回给服务器

消息格式详解

请求参数结构

采样请求使用标准化的消息格式,主要包含以下几个核心组件:

1. 消息数组(Messages)

interface Message {
  role: "user" | "assistant";
  content: TextContent | ImageContent;
}

interface TextContent {
  type: "text";
  text: string;
}

interface ImageContent {
  type: "image";
  data: string;      // base64编码
  mimeType: string;  // 如 "image/png"
}

2. 模型偏好设置(Model Preferences)

interface ModelPreferences {
  hints?: string[];           // 模型名称提示
  costPriority?: number;      // 成本优先级 (0-1)
  speedPriority?: number;     // 速度优先级 (0-1)
  intelligencePriority?: number; // 智能程度优先级 (0-1)
}

3. 上下文包含设置(Context Inclusion)

type IncludeContext = "none" | "thisServer" | "allServers";

4. 采样参数(Sampling Parameters)

interface SamplingParams {
  temperature?: number;      // 随机性控制 (0.0-1.0)
  maxTokens?: number;       // 最大token数
  stopSequences?: string[]; // 停止序列
  metadata?: Record<string, any>; // 特定于提供商的参数
}

响应格式

interface SamplingResponse {
  model: string;
  stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string;
  role: "user" | "assistant";
  content: {
    type: "text" | "image";
    text?: string;
    data?: string;
    mimeType?: string;
  };
}

TypeScript SDK 实战演示

下面我们通过完整的TypeScript代码示例来演示如何实现MCP采样功能。

1. 基础类型定义

// types.ts
export interface MCPSamplingRequest {
  method: "sampling/createMessage";
  params: SamplingRequestParams;
}

export interface SamplingRequestParams {
  messages: Message[];
  systemPrompt?: string;
  includeContext?: IncludeContext;
  modelPreferences?: ModelPreferences;
  temperature?: number;
  maxTokens?: number;
  stopSequences?: string[];
  metadata?: Record<string, any>;
}

export interface Message {
  role: "user" | "assistant";
  content: MessageContent;
}

export interface MessageContent {
  type: "text" | "image";
  text?: string;
  data?: string;
  mimeType?: string;
}

export interface ModelPreferences {
  hints?: string[];
  costPriority?: number;
  speedPriority?: number;
  intelligencePriority?: number;
}

export type IncludeContext = "none" | "thisServer" | "allServers";

export interface SamplingResponse {
  model: string;
  stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string;
  role: "user" | "assistant";
  content: MessageContent;
}

2. MCP采样客户端实现

// mcp-sampling-client.ts
import { 
  MCPSamplingRequest, 
  SamplingRequestParams, 
  SamplingResponse,
  Message,
  ModelPreferences 
} from './types';

export class MCPSamplingClient {
  private wsConnection: WebSocket | null = null;
  private pendingRequests = new Map<string, (response: any) => void>();

  constructor(private serverUrl: string) {}

  async connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.wsConnection = new WebSocket(this.serverUrl);
      
      this.wsConnection.onopen = () => {
        console.log('MCP连接已建立');
        resolve();
      };

      this.wsConnection.onerror = (error) => {
        console.error('MCP连接错误:', error);
        reject(error);
      };

      this.wsConnection.onmessage = (event) => {
        this.handleMessage(JSON.parse(event.data));
      };
    });
  }

  private handleMessage(message: any): void {
    const { id, result, error } = message;
    const resolver = this.pendingRequests.get(id);
    
    if (resolver) {
      this.pendingRequests.delete(id);
      if (error) {
        console.error('采样请求错误:', error);
      } else {
        resolver(result);
      }
    }
  }

  async requestSampling(params: SamplingRequestParams): Promise<SamplingResponse> {
    if (!this.wsConnection) {
      throw new Error('MCP连接未建立');
    }

    const requestId = this.generateRequestId();
    const request: MCPSamplingRequest & { id: string } = {
      id: requestId,
      method: "sampling/createMessage",
      params
    };

    return new Promise((resolve, reject) => {
      this.pendingRequests.set(requestId, resolve);
      
      // 设置超时
      setTimeout(() => {
        if (this.pendingRequests.has(requestId)) {
          this.pendingRequests.delete(requestId);
          reject(new Error('采样请求超时'));
        }
      }, 30000);

      this.wsConnection!.send(JSON.stringify(request));
    });
  }

  private generateRequestId(): string {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  disconnect(): void {
    if (this.wsConnection) {
      this.wsConnection.close();
      this.wsConnection = null;
    }
  }
}

3. 采样请求构建器

// sampling-request-builder.ts
import { SamplingRequestParams, Message, ModelPreferences } from './types';

export class SamplingRequestBuilder {
  private params: Partial<SamplingRequestParams> = {};

  addMessage(role: "user" | "assistant", text: string): this {
    if (!this.params.messages) {
      this.params.messages = [];
    }
    
    this.params.messages.push({
      role,
      content: { type: "text", text }
    });
    return this;
  }

  addImageMessage(role: "user" | "assistant", imageData: string, mimeType: string): this {
    if (!this.params.messages) {
      this.params.messages = [];
    }
    
    this.params.messages.push({
      role,
      content: { type: "image", data: imageData, mimeType }
    });
    return this;
  }

  setSystemPrompt(prompt: string): this {
    this.params.systemPrompt = prompt;
    return this;
  }

  setContext(context: "none" | "thisServer" | "allServers"): this {
    this.params.includeContext = context;
    return this;
  }

  setModelPreferences(preferences: ModelPreferences): this {
    this.params.modelPreferences = preferences;
    return this;
  }

  setSamplingParams(
    temperature?: number,
    maxTokens?: number,
    stopSequences?: string[]
  ): this {
    if (temperature !== undefined) this.params.temperature = temperature;
    if (maxTokens !== undefined) this.params.maxTokens = maxTokens;
    if (stopSequences !== undefined) this.params.stopSequences = stopSequences;
    return this;
  }

  build(): SamplingRequestParams {
    if (!this.params.messages || this.params.messages.length === 0) {
      throw new Error('至少需要一条消息');
    }
    return this.params as SamplingRequestParams;
  }
}

4. 高级采样管理器

// advanced-sampling-manager.ts
import { MCPSamplingClient } from './mcp-sampling-client';
import { SamplingRequestBuilder } from './sampling-request-builder';
import { SamplingResponse } from './types';

export class AdvancedSamplingManager {
  private client: MCPSamplingClient;
  private requestHistory: Array<{ request: any; response: SamplingResponse; timestamp: number }> = [];

  constructor(serverUrl: string) {
    this.client = new MCPSamplingClient(serverUrl);
  }

  async initialize(): Promise<void> {
    await this.client.connect();
  }

  // 智能体对话功能
  async agentConversation(
    userInput: string,
    systemContext: string = "你是一个有帮助的AI助手",
    options: {
      temperature?: number;
      maxTokens?: number;
      includeHistory?: boolean;
      modelHints?: string[];
    } = {}
  ): Promise<string> {
    const builder = new SamplingRequestBuilder()
      .setSystemPrompt(systemContext)
      .setContext("thisServer")
      .setSamplingParams(
        options.temperature ?? 0.7,
        options.maxTokens ?? 1000
      );

    // 包含历史对话
    if (options.includeHistory && this.requestHistory.length > 0) {
      this.requestHistory.slice(-5).forEach(({ request, response }) => {
        if (request.params.messages) {
          request.params.messages.forEach((msg: any) => {
            builder.addMessage(msg.role, msg.content.text || '');
          });
        }
        if (response.content.text) {
          builder.addMessage("assistant", response.content.text);
        }
      });
    }

    builder.addMessage("user", userInput);

    // 设置模型偏好
    if (options.modelHints) {
      builder.setModelPreferences({
        hints: options.modelHints,
        intelligencePriority: 0.8,
        speedPriority: 0.6,
        costPriority: 0.4
      });
    }

    const request = builder.build();
    const response = await this.client.requestSampling(request);

    // 记录对话历史
    this.requestHistory.push({
      request: { params: request },
      response,
      timestamp: Date.now()
    });

    return response.content.text || '';
  }

  // 文档分析功能
  async analyzeDocument(
    documentContent: string,
    analysisType: 'summary' | 'sentiment' | 'key_points' | 'custom',
    customPrompt?: string
  ): Promise<string> {
    let systemPrompt: string;
    
    switch (analysisType) {
      case 'summary':
        systemPrompt = "请对提供的文档内容进行简洁而全面的总结。";
        break;
      case 'sentiment':
        systemPrompt = "请分析文档的情感倾向,包括积极、消极或中性的情感。";
        break;
      case 'key_points':
        systemPrompt = "请提取文档中的关键要点和重要信息。";
        break;
      case 'custom':
        systemPrompt = customPrompt || "请分析以下文档内容。";
        break;
    }

    const request = new SamplingRequestBuilder()
      .setSystemPrompt(systemPrompt)
      .addMessage("user", `请分析以下文档内容:\n\n${documentContent}`)
      .setContext("thisServer")
      .setSamplingParams(0.3, 2000)
      .setModelPreferences({
        hints: ["claude-3", "gpt-4"],
        intelligencePriority: 0.9,
        speedPriority: 0.5,
        costPriority: 0.6
      })
      .build();

    const response = await this.client.requestSampling(request);
    return response.content.text || '';
  }

  // 代码生成和审查功能
  async generateCode(
    description: string,
    language: string,
    requirements?: string[]
  ): Promise<string> {
    let systemPrompt = `你是一个专业的${language}开发专家。请根据用户描述生成高质量的代码。`;
    
    if (requirements && requirements.length > 0) {
      systemPrompt += `\n\n特殊要求:\n${requirements.map(req => `- ${req}`).join('\n')}`;
    }

    const userPrompt = `请用${language}编写代码实现以下功能:\n${description}`;

    const request = new SamplingRequestBuilder()
      .setSystemPrompt(systemPrompt)
      .addMessage("user", userPrompt)
      .setContext("thisServer")
      .setSamplingParams(0.2, 3000, ['```\n\n'])
      .setModelPreferences({
        hints: ["claude-3-5-sonnet", "gpt-4"],
        intelligencePriority: 0.95,
        speedPriority: 0.4,
        costPriority: 0.5
      })
      .build();

    const response = await this.client.requestSampling(request);
    return response.content.text || '';
  }

  // 多轮推理功能
  async multiStepReasoning(
    problem: string,
    steps: string[],
    context?: string
  ): Promise<{ stepResults: string[]; finalAnswer: string }> {
    const stepResults: string[] = [];
    let accumulatedContext = context || '';

    for (let i = 0; i < steps.length; i++) {
      const systemPrompt = `你正在进行多步推理解决问题。当前是第${i + 1}步,共${steps.length}步。
      
原问题:${problem}
当前步骤:${steps[i]}
${accumulatedContext ? `已有上下文:\n${accumulatedContext}` : ''}

请专注于当前步骤,提供详细的分析和结论。`;

      const request = new SamplingRequestBuilder()
        .setSystemPrompt(systemPrompt)
        .addMessage("user", `请执行步骤:${steps[i]}`)
        .setContext("thisServer")
        .setSamplingParams(0.1, 1500)
        .build();

      const response = await this.client.requestSampling(request);
      const stepResult = response.content.text || '';
      stepResults.push(stepResult);
      accumulatedContext += `\n\n步骤${i + 1}结果:${stepResult}`;
    }

    // 最终综合
    const finalRequest = new SamplingRequestBuilder()
      .setSystemPrompt(`请基于所有步骤的分析结果,给出问题"${problem}"的最终答案。`)
      .addMessage("user", `所有分析步骤已完成:\n${accumulatedContext}\n\n请给出最终答案。`)
      .setSamplingParams(0.0, 1000)
      .build();

    const finalResponse = await this.client.requestSampling(finalRequest);

    return {
      stepResults,
      finalAnswer: finalResponse.content.text || ''
    };
  }

  // 获取对话历史
  getConversationHistory(): typeof this.requestHistory {
    return [...this.requestHistory];
  }

  // 清理历史记录
  clearHistory(): void {
    this.requestHistory = [];
  }

  // 资源清理
  async cleanup(): Promise<void> {
    this.client.disconnect();
    this.clearHistory();
  }
}

5. 实际使用示例

// usage-examples.ts
import { AdvancedSamplingManager } from './advanced-sampling-manager';

async function demonstrateMCPSampling() {
  // 初始化采样管理器
  const samplingManager = new AdvancedSamplingManager('ws://localhost:8080/mcp');
  
  try {
    await samplingManager.initialize();
    console.log('MCP采样客户端已初始化');

    // 1. 智能体对话示例
    console.log('\n=== 智能体对话示例 ===');
    const conversation1 = await samplingManager.agentConversation(
      "请解释一下什么是量子计算?",
      "你是一个物理学专家,擅长用通俗易懂的语言解释复杂概念。",
      {
        temperature: 0.7,
        maxTokens: 800,
        modelHints: ["claude-3-5-sonnet"]
      }
    );
    console.log('AI回答:', conversation1);

    // 2. 文档分析示例
    console.log('\n=== 文档分析示例 ===');
    const documentContent = `
    人工智能技术在过去十年中取得了突破性进展。深度学习、自然语言处理、
    计算机视觉等领域的快速发展,推动了AI在各行各业的广泛应用。
    然而,AI技术的快速发展也带来了一些挑战,包括数据隐私、算法偏见、
    就业影响等社会问题。
    `;
    
    const analysis = await samplingManager.analyzeDocument(
      documentContent,
      'key_points'
    );
    console.log('关键要点分析:', analysis);

    // 3. 代码生成示例
    console.log('\n=== 代码生成示例 ===');
    const generatedCode = await samplingManager.generateCode(
      "创建一个TypeScript函数,用于计算数组中所有数字的平均值",
      "TypeScript",
      ["添加类型检查", "处理空数组情况", "添加JSDoc注释"]
    );
    console.log('生成的代码:', generatedCode);

    // 4. 多轮推理示例
    console.log('\n=== 多轮推理示例 ===');
    const reasoningResult = await samplingManager.multiStepReasoning(
      "如何设计一个高效的缓存系统?",
      [
        "分析缓存系统的核心需求",
        "评估不同的缓存策略",
        "考虑性能和存储的权衡",
        "设计具体的实现方案"
      ],
      "这是为一个高并发的Web应用设计缓存系统"
    );
    
    console.log('推理步骤结果:');
    reasoningResult.stepResults.forEach((result, index) => {
      console.log(`步骤 ${index + 1}:`, result.substring(0, 200) + '...');
    });
    console.log('最终方案:', reasoningResult.finalAnswer);

    // 5. 查看对话历史
    console.log('\n=== 对话历史统计 ===');
    const history = samplingManager.getConversationHistory();
    console.log(`总共进行了 ${history.length} 次采样请求`);
    
  } catch (error) {
    console.error('采样演示出错:', error);
  } finally {
    // 清理资源
    await samplingManager.cleanup();
    console.log('资源已清理');
  }
}

// 错误处理和重试机制示例
async function robustSamplingExample() {
  const samplingManager = new AdvancedSamplingManager('ws://localhost:8080/mcp');
  
  async function samplingWithRetry(
    operation: () => Promise<string>,
    maxRetries: number = 3,
    delay: number = 1000
  ): Promise<string> {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        console.log(`采样尝试 ${attempt} 失败:`, error);
        
        if (attempt === maxRetries) {
          throw new Error(`采样在 ${maxRetries} 次尝试后仍然失败`);
        }
        
        // 等待后重试
        await new Promise(resolve => setTimeout(resolve, delay * attempt));
      }
    }
    throw new Error('不应该到达这里');
  }

  try {
    await samplingManager.initialize();
    
    const result = await samplingWithRetry(async () => {
      return await samplingManager.agentConversation(
        "请简单介绍一下TypeScript的主要特性",
        "你是一个前端开发专家",
        { temperature: 0.3, maxTokens: 500 }
      );
    });
    
    console.log('稳健采样结果:', result);
    
  } finally {
    await samplingManager.cleanup();
  }
}

// 运行示例
if (require.main === module) {
  demonstrateMCPSampling().catch(console.error);
}

最佳实践与安全考虑

1. 采样请求设计最佳实践

  • 清晰的提示结构:使用明确、结构化的提示,避免歧义
  • 合理的token限制:根据任务复杂度设置适当的maxTokens
  • 温度参数调优:创意任务使用较高temperature,事实性任务使用较低值
  • 上下文管理:谨慎使用includeContext,只包含必要的上下文信息

2. 安全性最佳实践

  • 输入验证:严格验证所有消息内容,防止注入攻击
  • 敏感信息过滤:在客户端过滤或脱敏敏感数据
  • 访问控制:实现适当的用户权限和访问控制
  • 审计日志:记录所有采样请求用于安全审计

3. 性能优化策略

  • 请求缓存:缓存常见查询的结果
  • 批处理:将多个相关请求合并处理
  • 异步处理:使用异步模式处理长时间运行的采样
  • 资源监控:监控token使用量和API成本

4. 错误处理机制

  • 超时处理:设置合理的请求超时时间
  • 重试策略:实现指数退避的重试机制
  • 优雅降级:提供备用方案应对采样失败
  • 用户反馈:向用户提供清晰的错误信息

实际应用场景

1. 智能文档处理系统

利用MCP采样功能构建自动化文档分析和处理流水线,支持多种文档格式的智能解析、摘要生成和关键信息提取。

2. 代码助手集成

在IDE中集成MCP采样,提供上下文感知的代码生成、重构建议和bug修复方案,提升开发效率。

3. 客户服务自动化

构建智能客服系统,通过采样功能理解客户问题并生成个性化回复,同时保持人工监督能力。

4. 内容创作平台

为内容创作者提供AI辅助工具,支持文章写作、创意生成和内容优化,同时确保内容质量和原创性。

总结

MCP采样功能为AI应用开发提供了强大而安全的LLM集成方案。通过本文的深度解析和TypeScript实战代码,开发者可以:

  1. 理解采样机制:掌握MCP采样的核心概念和工作原理
  2. 实现完整功能:利用提供的代码框架快速构建采样应用
  3. 遵循最佳实践:确保应用的安全性、性能和可维护性
  4. 扩展应用场景:基于采样功能构建更复杂的AI应用

MCP采样不仅是一个技术工具,更是连接AI能力与实际应用需求的重要桥梁。通过合理使用这一功能,我们可以构建既强大又安全的下一代AI应用。

随着MCP生态的不断发展,采样功能将在更多场景中发挥重要作用。建议开发者持续关注MCP的最新发展,不断优化和完善自己的实现方案。