Files
2026-07-13 13:31:35 +08:00

108 KiB

MCP Development Best Practices

MCP Development Best Practices

(คลิกที่รูปภาพด้านบนเพื่อดูวิดีโอบทเรียนนี้)

Overview

บทเรียนนี้มุ่งเน้นไปที่แนวปฏิบัติที่ดีที่สุดขั้นสูงสำหรับการพัฒนา ทดสอบ และปรับใช้เซิร์ฟเวอร์และฟีเจอร์ MCP ในสภาพแวดล้อมการผลิต ขณะที่ระบบนิเวศ MCP เพิ่มความซับซ้อนและความสำคัญ การปฏิบัติตามรูปแบบที่กำหนดไว้ช่วยให้มั่นใจในความน่าเชื่อถือ การบำรุงรักษา และการทำงานร่วมกัน บทเรียนนี้รวบรวมปัญญาจากการใช้งาน MCP ในโลกจริงเพื่อแนะนำคุณในการสร้างเซิร์ฟเวอร์ที่แข็งแกร่ง ประสิทธิภาพสูง ด้วยทรัพยากร ตัวกระตุ้น และเครื่องมือที่มีประสิทธิผล

Learning Objectives

เมื่อจบบทเรียนนี้ คุณจะสามารถ:

  • ใช้แนวปฏิบัติที่ดีที่สุดในอุตสาหกรรมในการออกแบบเซิร์ฟเวอร์และฟีเจอร์ MCP
  • สร้างกลยุทธ์การทดสอบที่ครอบคลุมสำหรับเซิร์ฟเวอร์ MCP
  • ออกแบบรูปแบบงานที่มีประสิทธิภาพ และนำกลับมาใช้ใหม่ได้สำหรับแอปพลิเคชัน MCP ที่ซับซ้อน
  • นำแนวทางจัดการข้อผิดพลาด การบันทึก และการสังเกตการณ์ที่เหมาะสมในเซิร์ฟเวอร์ MCP
  • ปรับแต่งการใช้งาน MCP เพื่อประสิทธิภาพ ความปลอดภัย และการบำรุงรักษา

MCP Core Principles

ก่อนลงลึกสู่การปฏิบัติการใช้งานเฉพาะ ควรเข้าใจหลักการสำคัญที่ชี้นำการพัฒนา MCP ที่มีประสิทธิภาพ:

  1. การสื่อสารที่ได้มาตรฐาน: MCP ใช้ JSON-RPC 2.0 เป็นฐาน เพื่อจัดรูปแบบที่สอดคล้องกันสำหรับคำขอ คำตอบ และการจัดการข้อผิดพลาดในทุกการใช้งาน

  2. การออกแบบที่ยึดผู้ใช้เป็นศูนย์กลาง: ให้ความสำคัญกับความยินยอม การควบคุม และความโปร่งใสของผู้ใช้ในการใช้งาน MCP

  3. ความปลอดภัยเป็นอันดับแรก: นำมาตรการความปลอดภัยที่แข็งแกร่งรวมถึงการตรวจสอบสิทธิ์ การอนุญาต การตรวจสอบ และจำกัดอัตราการใช้งาน

  4. สถาปัตยกรรมแบบโมดูลาร์: ออกแบบเซิร์ฟเวอร์ MCP ด้วยแนวทางโมดูลาร์ โดยให้แต่ละเครื่องมือและทรัพยากรมีวัตถุประสงค์ที่ชัดเจนและมุ่งเน้น

  5. การเชื่อมต่อแบบมีสถานะ: ใช้ความสามารถของ MCP ในการรักษาสถานะข้ามคำขอหลายรายการเพื่อให้มีปฏิสัมพันธ์ที่สอดคล้องและรับรู้บริบทได้ดีขึ้น

Official MCP Best Practices

แนวปฏิบัติที่ดีที่สุดต่อไปนี้ได้มาจากเอกสาร Model Context Protocol อย่างเป็นทางการ:

Security Best Practices

  1. ความยินยอมและการควบคุมของผู้ใช้: ต้องขอความยินยอมจากผู้ใช้อย่างชัดเจนก่อนเข้าถึงข้อมูลหรือดำเนินการใด ๆ ให้อำนาจควบคุมที่ชัดเจนว่าส่งแชร์ข้อมูลใดและอนุญาตให้ทำการใดบ้าง

  2. ความเป็นส่วนตัวของข้อมูล: เปิดเผยข้อมูลของผู้ใช้เฉพาะเมื่อได้รับความยินยอมอย่างชัดเจนและคุ้มครองด้วยการควบคุมการเข้าถึงที่เหมาะสม ป้องกันการส่งข้อมูลโดยไม่ได้รับอนุญาต

  3. ความปลอดภัยของเครื่องมือ: ต้องมีความยินยอมจากผู้ใช้อย่างชัดเจนก่อนเรียกใช้เครื่องมือใด ๆ ให้ผู้ใช้เข้าใจฟังก์ชันการทำงานของแต่ละเครื่องมือและบังคับใช้ขอบเขตความปลอดภัยที่เข้มงวด

  4. การควบคุมสิทธิ์เครื่องมือ: ตั้งค่ากำหนดว่าเครื่องมือใดที่โมเดลได้รับอนุญาตให้ใช้ในระหว่างการเซสชัน เพื่อให้เข้าถึงได้เฉพาะเครื่องมือที่ได้รับสิทธิ์ชัดเจนเท่านั้น

  5. การตรวจสอบสิทธิ์: ต้องมีการตรวจสอบสิทธิ์ที่ถูกต้องก่อนอนุญาตให้เข้าถึงเครื่องมือ ทรัพยากร หรือการดำเนินการที่มีความอ่อนไหว เช่น ใช้ API keys, OAuth tokens หรือวิธีการตรวจสอบสิทธิ์ที่ปลอดภัยอื่น ๆ

  6. การตรวจสอบพารามิเตอร์: บังคับใช้การตรวจสอบความถูกต้องของพารามิเตอร์ทุกการเรียกใช้เครื่องมือเพื่อป้องกันข้อมูลป้อนเข้าที่ผิดรูปแบบหรือเป็นอันตราย

  7. การจำกัดอัตรา: นำการจำกัดอัตราการใช้งานมาใช้เพื่อป้องกันการละเมิดและรักษาการใช้งานทรัพยากรเซิร์ฟเวอร์อย่างเป็นธรรม

Implementation Best Practices

  1. การเจรจาความสามารถ: ในระหว่างการตั้งค่าการเชื่อมต่อ ให้แลกเปลี่ยนข้อมูลเกี่ยวกับฟีเจอร์ที่รองรับ เวอร์ชันโปรโตคอล เครื่องมือและทรัพยากรที่มี

  2. การออกแบบเครื่องมือ: สร้างเครื่องมือที่มุ่งเน้นปฏิบัติหน้าที่หนึ่งอย่างดี แทนที่จะสร้างเครื่องมือรวมที่จัดการหลายแง่มุม

  3. การจัดการข้อผิดพลาด: นำข้อความและรหัสข้อผิดพลาดที่เป็นมาตรฐานมาใช้เพื่อช่วยวินิจฉัยปัญหา จัดการความล้มเหลวอย่างเหมาะสม และให้ข้อมูลตอบกลับที่นำไปใช้ได้จริง

  4. การบันทึกข้อมูล: ตั้งค่าการบันทึกแบบมีโครงสร้างสำหรับตรวจสอบ แก้ไขปัญหา และติดตามการโต้ตอบโปรโตคอล

  5. การติดตามความคืบหน้า: สำหรับการดำเนินการที่ใช้เวลานาน ให้รายงานอัพเดตความคืบหน้าเพื่อเปิดใช้งานส่วนติดต่อผู้ใช้ที่ตอบสนองได้ดี

  6. การยกเลิกคำขอ: อนุญาตให้ไคลเอนต์ยกเลิกคำขอกำลังดำเนินการที่ไม่จำเป็นอีกต่อไปหรือใช้เวลานานเกินควร

Additional References

สำหรับข้อมูลที่อัพเดตล่าสุดเกี่ยวกับแนวปฏิบัติที่ดีที่สุดของ MCP โปรดดูที่:

Practical Implementation Examples

Tool Design Best Practices

1. Single Responsibility Principle

เครื่องมือ MCP แต่ละตัวควรมีวัตถุประสงค์ที่ชัดเจนและมุ่งเน้น แทนที่จะสร้างเครื่องมือขนาดใหญ่ที่พยายามจัดการหลายประเด็น ให้พัฒนาเครื่องมือเฉพาะทางที่ทำงานได้ยอดเยี่ยมในงานที่เจาะจง

// A focused tool that does one thing well
public class WeatherForecastTool : ITool
{
    private readonly IWeatherService _weatherService;
    
    public WeatherForecastTool(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }
    
    public string Name => "weatherForecast";
    public string Description => "Gets weather forecast for a specific location";
    
    public ToolDefinition GetDefinition()
    {
        return new ToolDefinition
        {
            Name = Name,
            Description = Description,
            Parameters = new Dictionary<string, ParameterDefinition>
            {
                ["location"] = new ParameterDefinition
                {
                    Type = ParameterType.String,
                    Description = "City or location name"
                },
                ["days"] = new ParameterDefinition
                {
                    Type = ParameterType.Integer,
                    Description = "Number of forecast days",
                    Default = 3
                }
            },
            Required = new[] { "location" }
        };
    }
    
    public async Task<ToolResponse> ExecuteAsync(IDictionary<string, object> parameters)
    {
        var location = parameters["location"].ToString();
        var days = parameters.ContainsKey("days") 
            ? Convert.ToInt32(parameters["days"]) 
            : 3;
            
        var forecast = await _weatherService.GetForecastAsync(location, days);
        
        return new ToolResponse
        {
            Content = new List<ContentItem>
            {
                new TextContent(JsonSerializer.Serialize(forecast))
            }
        };
    }
}

2. Consistent Error Handling

นำการจัดการข้อผิดพลาดที่แข็งแกร่งพร้อมข้อความข้อผิดพลาดที่ให้ข้อมูลและกลไกการกู้คืนที่เหมาะสมมาใช้

# ตัวอย่าง Python พร้อมการจัดการข้อผิดพลาดอย่างครอบคลุม
class DataQueryTool:
    def get_name(self):
        return "dataQuery"
        
    def get_description(self):
        return "Queries data from specified database tables"
    
    async def execute(self, parameters):
        try:
            # การตรวจสอบพารามิเตอร์
            if "query" not in parameters:
                raise ToolParameterError("Missing required parameter: query")
                
            query = parameters["query"]
            
            # การตรวจสอบความปลอดภัย
            if self._contains_unsafe_sql(query):
                raise ToolSecurityError("Query contains potentially unsafe SQL")
            
            try:
                # การดำเนินการฐานข้อมูลพร้อมเวลาหมดเวลา
                async with timeout(10):  # หมดเวลาภายใน 10 วินาที
                    result = await self._database.execute_query(query)
                    
                return ToolResponse(
                    content=[TextContent(json.dumps(result))]
                )
            except asyncio.TimeoutError:
                raise ToolExecutionError("Database query timed out after 10 seconds")
            except DatabaseConnectionError as e:
                # ข้อผิดพลาดการเชื่อมต่ออาจเป็นเพียงชั่วคราว
                self._log_error("Database connection error", e)
                raise ToolExecutionError(f"Database connection error: {str(e)}")
            except DatabaseQueryError as e:
                # ข้อผิดพลาดการค้นหาข้อมูลน่าจะเป็นข้อผิดพลาดของฝั่งลูกค้า
                self._log_error("Database query error", e)
                raise ToolExecutionError(f"Invalid query: {str(e)}")
                
        except ToolError:
            # ปล่อยให้ข้อผิดพลาดเฉพาะเครื่องมือผ่านไป
            raise
        except Exception as e:
            # จับข้อผิดพลาดที่ไม่คาดคิดทั้งหมด
            self._log_error("Unexpected error in DataQueryTool", e)
            raise ToolExecutionError(f"An unexpected error occurred: {str(e)}")
    
    def _contains_unsafe_sql(self, query):
        # การใช้งานตรวจจับการโจมตี SQL injection
        pass
        
    def _log_error(self, message, error):
        # การใช้งานบันทึกข้อผิดพลาด
        pass

3. Parameter Validation

ตรวจสอบพารามิเตอร์อย่างละเอียดเสมอเพื่อป้องกันการป้อนข้อมูลที่ผิดรูปแบบหรือเป็นอันตราย

// ตัวอย่าง JavaScript/TypeScript พร้อมการตรวจสอบพารามิเตอร์อย่างละเอียด
class FileOperationTool {
  getName() {
    return "fileOperation";
  }
  
  getDescription() {
    return "Performs file operations like read, write, and delete";
  }
  
  getDefinition() {
    return {
      name: this.getName(),
      description: this.getDescription(),
      parameters: {
        operation: {
          type: "string",
          description: "Operation to perform",
          enum: ["read", "write", "delete"]
        },
        path: {
          type: "string",
          description: "File path (must be within allowed directories)"
        },
        content: {
          type: "string",
          description: "Content to write (only for write operation)",
          optional: true
        }
      },
      required: ["operation", "path"]
    };
  }
  
  async execute(parameters) {
    // 1. ตรวจสอบการมีอยู่ของพารามิเตอร์
    if (!parameters.operation) {
      throw new ToolError("Missing required parameter: operation");
    }
    
    if (!parameters.path) {
      throw new ToolError("Missing required parameter: path");
    }
    
    // 2. ตรวจสอบชนิดของพารามิเตอร์
    if (typeof parameters.operation !== "string") {
      throw new ToolError("Parameter 'operation' must be a string");
    }
    
    if (typeof parameters.path !== "string") {
      throw new ToolError("Parameter 'path' must be a string");
    }
    
    // 3. ตรวจสอบค่าของพารามิเตอร์
    const validOperations = ["read", "write", "delete"];
    if (!validOperations.includes(parameters.operation)) {
      throw new ToolError(`Invalid operation. Must be one of: ${validOperations.join(", ")}`);
    }
    
    // 4. ตรวจสอบการมีเนื้อหาสำหรับการเขียน
    if (parameters.operation === "write" && !parameters.content) {
      throw new ToolError("Content parameter is required for write operation");
    }
    
    // 5. การตรวจสอบความปลอดภัยของเส้นทาง
    if (!this.isPathWithinAllowedDirectories(parameters.path)) {
      throw new ToolError("Access denied: path is outside of allowed directories");
    }
    
    // การดำเนินการตามพารามิเตอร์ที่ตรวจสอบแล้ว
    // ...
  }
  
  isPathWithinAllowedDirectories(path) {
    // การดำเนินการตรวจสอบความปลอดภัยของเส้นทาง
    // ...
  }
}

Security Implementation Examples

1. Authentication and Authorization

// ตัวอย่าง Java พร้อมการตรวจสอบสิทธิ์และการอนุญาต
public class SecureDataAccessTool implements Tool {
    private final AuthenticationService authService;
    private final AuthorizationService authzService;
    private final DataService dataService;
    
    // การฉีดพึ่งพา
    public SecureDataAccessTool(
            AuthenticationService authService,
            AuthorizationService authzService,
            DataService dataService) {
        this.authService = authService;
        this.authzService = authzService;
        this.dataService = dataService;
    }
    
    @Override
    public String getName() {
        return "secureDataAccess";
    }
    
    @Override
    public ToolResponse execute(ToolRequest request) {
        // 1. ดึงข้อมูลบริบทการตรวจสอบสิทธิ์
        String authToken = request.getContext().getAuthToken();
        
        // 2. ตรวจสอบสิทธิ์ผู้ใช้
        UserIdentity user;
        try {
            user = authService.validateToken(authToken);
        } catch (AuthenticationException e) {
            return ToolResponse.error("Authentication failed: " + e.getMessage());
        }
        
        // 3. ตรวจสอบการอนุญาตสำหรับการดำเนินการเฉพาะ
        String dataId = request.getParameters().get("dataId").getAsString();
        String operation = request.getParameters().get("operation").getAsString();
        
        boolean isAuthorized = authzService.isAuthorized(user, "data:" + dataId, operation);
        if (!isAuthorized) {
            return ToolResponse.error("Access denied: Insufficient permissions for this operation");
        }
        
        // 4. ดำเนินการตามการอนุญาตที่ได้รับ
        try {
            switch (operation) {
                case "read":
                    Object data = dataService.getData(dataId, user.getId());
                    return ToolResponse.success(data);
                case "update":
                    JsonNode newData = request.getParameters().get("newData");
                    dataService.updateData(dataId, newData, user.getId());
                    return ToolResponse.success("Data updated successfully");
                default:
                    return ToolResponse.error("Unsupported operation: " + operation);
            }
        } catch (Exception e) {
            return ToolResponse.error("Operation failed: " + e.getMessage());
        }
    }
}

2. Rate Limiting

// C# rate limiting implementation
public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;
    private readonly ILogger<RateLimitingMiddleware> _logger;
    
    // Configuration options
    private readonly int _maxRequestsPerMinute;
    
    public RateLimitingMiddleware(
        RequestDelegate next,
        IMemoryCache cache,
        ILogger<RateLimitingMiddleware> logger,
        IConfiguration config)
    {
        _next = next;
        _cache = cache;
        _logger = logger;
        _maxRequestsPerMinute = config.GetValue<int>("RateLimit:MaxRequestsPerMinute", 60);
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        // 1. Get client identifier (API key or user ID)
        string clientId = GetClientIdentifier(context);
        
        // 2. Get rate limiting key for this minute
        string cacheKey = $"rate_limit:{clientId}:{DateTime.UtcNow:yyyyMMddHHmm}";
        
        // 3. Check current request count
        if (!_cache.TryGetValue(cacheKey, out int requestCount))
        {
            requestCount = 0;
        }
        
        // 4. Enforce rate limit
        if (requestCount >= _maxRequestsPerMinute)
        {
            _logger.LogWarning("Rate limit exceeded for client {ClientId}", clientId);
            
            context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            context.Response.Headers.Add("Retry-After", "60");
            
            await context.Response.WriteAsJsonAsync(new
            {
                error = "Rate limit exceeded",
                message = "Too many requests. Please try again later.",
                retryAfterSeconds = 60
            });
            
            return;
        }
        
        // 5. Increment request count
        _cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(2));
        
        // 6. Add rate limit headers
        context.Response.Headers.Add("X-RateLimit-Limit", _maxRequestsPerMinute.ToString());
        context.Response.Headers.Add("X-RateLimit-Remaining", (_maxRequestsPerMinute - requestCount - 1).ToString());
        
        // 7. Continue with the request
        await _next(context);
    }
    
    private string GetClientIdentifier(HttpContext context)
    {
        // Implementation to extract API key or user ID
        // ...
    }
}

Testing Best Practices

1. Unit Testing MCP Tools

ทดสอบเครื่องมือของคุณในสภาพแวดล้อมแยก โดยใช้การจำลอง (mock) ของการพึ่งพาภายนอก:

// ตัวอย่างการทดสอบหน่วยเครื่องมือโดยใช้ TypeScript
describe('WeatherForecastTool', () => {
  let tool: WeatherForecastTool;
  let mockWeatherService: jest.Mocked<IWeatherService>;
  
  beforeEach(() => {
    // สร้างบริการสภาพอากาศจำลอง
    mockWeatherService = {
      getForecasts: jest.fn()
    } as any;
    
    // สร้างเครื่องมือพร้อมกับการขึ้นต่อกับจำลอง
    tool = new WeatherForecastTool(mockWeatherService);
  });
  
  it('should return weather forecast for a location', async () => {
    // จัดเตรียม
    const mockForecast = {
      location: 'Seattle',
      forecasts: [
        { date: '2025-07-16', temperature: 72, conditions: 'Sunny' },
        { date: '2025-07-17', temperature: 68, conditions: 'Partly Cloudy' },
        { date: '2025-07-18', temperature: 65, conditions: 'Rain' }
      ]
    };
    
    mockWeatherService.getForecasts.mockResolvedValue(mockForecast);
    
    // ดำเนินการ
    const response = await tool.execute({
      location: 'Seattle',
      days: 3
    });
    
    // ตรวจสอบ
    expect(mockWeatherService.getForecasts).toHaveBeenCalledWith('Seattle', 3);
    expect(response.content[0].text).toContain('Seattle');
    expect(response.content[0].text).toContain('Sunny');
  });
  
  it('should handle errors from the weather service', async () => {
    // จัดเตรียม
    mockWeatherService.getForecasts.mockRejectedValue(new Error('Service unavailable'));
    
    // ดำเนินการและตรวจสอบ
    await expect(tool.execute({
      location: 'Seattle',
      days: 3
    })).rejects.toThrow('Weather service error: Service unavailable');
  });
});

2. Integration Testing

ทดสอบการทำงานครบวงจรตั้งแต่คำขอของไคลเอนต์จนถึงการตอบกลับของเซิร์ฟเวอร์:

# ตัวอย่างการทดสอบรวม Python
@pytest.mark.asyncio
async def test_mcp_server_integration():
    # เริ่มเซิร์ฟเวอร์ทดสอบ
    server = McpServer()
    server.register_tool(WeatherForecastTool(MockWeatherService()))
    await server.start(port=5000)
    
    try:
        # สร้างไคลเอนต์
        client = McpClient("http://localhost:5000")
        
        # ทดสอบการค้นหาเครื่องมือ
        tools = await client.discover_tools()
        assert "weatherForecast" in [t.name for t in tools]
        
        # ทดสอบการรันเครื่องมือ
        response = await client.execute_tool("weatherForecast", {
            "location": "Seattle",
            "days": 3
        })
        
        # ยืนยันการตอบกลับ
        assert response.status_code == 200
        assert "Seattle" in response.content[0].text
        assert len(json.loads(response.content[0].text)["forecasts"]) == 3
        
    finally:
        # ทำความสะอาด
        await server.stop()

Performance Optimization

1. Caching Strategies

นำกลยุทธ์การแคชที่เหมาะสมมาใช้เพื่อลดความหน่วงและการใช้ทรัพยากร:

// C# example with caching
public class CachedWeatherTool : ITool
{
    private readonly IWeatherService _weatherService;
    private readonly IDistributedCache _cache;
    private readonly ILogger<CachedWeatherTool> _logger;
    
    public CachedWeatherTool(
        IWeatherService weatherService,
        IDistributedCache cache,
        ILogger<CachedWeatherTool> logger)
    {
        _weatherService = weatherService;
        _cache = cache;
        _logger = logger;
    }
    
    public string Name => "weatherForecast";
    
    public async Task<ToolResponse> ExecuteAsync(IDictionary<string, object> parameters)
    {
        var location = parameters["location"].ToString();
        var days = Convert.ToInt32(parameters.GetValueOrDefault("days", 3));
        
        // Create cache key
        string cacheKey = $"weather:{location}:{days}";
        
        // Try to get from cache
        string cachedForecast = await _cache.GetStringAsync(cacheKey);
        if (!string.IsNullOrEmpty(cachedForecast))
        {
            _logger.LogInformation("Cache hit for weather forecast: {Location}", location);
            return new ToolResponse
            {
                Content = new List<ContentItem>
                {
                    new TextContent(cachedForecast)
                }
            };
        }
        
        // Cache miss - get from service
        _logger.LogInformation("Cache miss for weather forecast: {Location}", location);
        var forecast = await _weatherService.GetForecastAsync(location, days);
        string forecastJson = JsonSerializer.Serialize(forecast);
        
        // Store in cache (weather forecasts valid for 1 hour)
        await _cache.SetStringAsync(
            cacheKey,
            forecastJson,
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
            });
        
        return new ToolResponse
        {
            Content = new List<ContentItem>
            {
                new TextContent(forecastJson)
            }
        };
    }
}

2. Dependency Injection and Testability

ออกแบบเครื่องมือให้รับการพึ่งพาผ่านการฉีดค่าตอนสร้าง (constructor injection) เพื่อให้ทดสอบและตั้งค่าได้ง่าย:

// ตัวอย่าง Java พร้อมการฉีดพึ่งพา
public class CurrencyConversionTool implements Tool {
    private final ExchangeRateService exchangeService;
    private final CacheService cacheService;
    private final Logger logger;
    
    // การพึ่งพาถูกฉีดผ่านคอนสตรัคเตอร์
    public CurrencyConversionTool(
            ExchangeRateService exchangeService,
            CacheService cacheService,
            Logger logger) {
        this.exchangeService = exchangeService;
        this.cacheService = cacheService;
        this.logger = logger;
    }
    
    // การใช้งานเครื่องมือ
    // ...
}

3. Composable Tools

ออกแบบเครื่องมือที่สามารถผสมผสานเข้าด้วยกันเพื่อสร้างเวิร์กโฟลว์ที่ซับซ้อนกว่า:

# ตัวอย่าง Python แสดงเครื่องมือที่สามารถประกอบกันได้
class DataFetchTool(Tool):
    def get_name(self):
        return "dataFetch"
    
    # การติดตั้ง...

class DataAnalysisTool(Tool):
    def get_name(self):
        return "dataAnalysis"
    
    # เครื่องมือนี้สามารถใช้ผลลัพธ์จากเครื่องมือ dataFetch ได้
    async def execute_async(self, request):
        # การติดตั้ง...
        pass

class DataVisualizationTool(Tool):
    def get_name(self):
        return "dataVisualize"
    
    # เครื่องมือนี้สามารถใช้ผลลัพธ์จากเครื่องมือ dataAnalysis ได้
    async def execute_async(self, request):
        # การติดตั้ง...
        pass

# เครื่องมือเหล่านี้สามารถใช้ได้อย่างอิสระหรือเป็นส่วนหนึ่งของเวิร์กโฟลว์

Schema Design Best Practices

สคีมาเป็นสัญญาระหว่างโมเดลกับเครื่องมือของคุณ สคีมาที่ออกแบบดีนำไปสู่การใช้งานเครื่องมือที่ง่ายขึ้น

1. Clear Parameter Descriptions

รวมข้อมูลคำอธิบายสำหรับแต่ละพารามิเตอร์เสมอ:

public object GetSchema()
{
    return new {
        type = "object",
        properties = new {
            query = new { 
                type = "string", 
                description = "Search query text. Use precise keywords for better results." 
            },
            filters = new {
                type = "object",
                description = "Optional filters to narrow down search results",
                properties = new {
                    dateRange = new { 
                        type = "string", 
                        description = "Date range in format YYYY-MM-DD:YYYY-MM-DD" 
                    },
                    category = new { 
                        type = "string", 
                        description = "Category name to filter by" 
                    }
                }
            },
            limit = new { 
                type = "integer", 
                description = "Maximum number of results to return (1-50)",
                default = 10
            }
        },
        required = new[] { "query" }
    };
}

2. Validation Constraints

รวมขอบเขตการตรวจสอบความถูกต้องเพื่อป้องกันข้อมูลป้อนเข้าที่ไม่ถูกต้อง:

Map<String, Object> getSchema() {
    Map<String, Object> schema = new HashMap<>();
    schema.put("type", "object");
    
    Map<String, Object> properties = new HashMap<>();
    
    // คุณสมบัติอีเมลพร้อมการตรวจสอบรูปแบบ
    Map<String, Object> email = new HashMap<>();
    email.put("type", "string");
    email.put("format", "email");
    email.put("description", "User email address");
    
    // คุณสมบัติอายุพร้อมข้อจำกัดเชิงตัวเลข
    Map<String, Object> age = new HashMap<>();
    age.put("type", "integer");
    age.put("minimum", 13);
    age.put("maximum", 120);
    age.put("description", "User age in years");
    
    // คุณสมบัติที่มีค่าจากการกำหนดล่วงหน้า
    Map<String, Object> subscription = new HashMap<>();
    subscription.put("type", "string");
    subscription.put("enum", Arrays.asList("free", "basic", "premium"));
    subscription.put("default", "free");
    subscription.put("description", "Subscription tier");
    
    properties.put("email", email);
    properties.put("age", age);
    properties.put("subscription", subscription);
    
    schema.put("properties", properties);
    schema.put("required", Arrays.asList("email"));
    
    return schema;
}

3. Consistent Return Structures

รักษาความสอดคล้องในโครงสร้างผลลัพธ์เพื่อง่ายแก่การตีความโดยโมเดล:

async def execute_async(self, request):
    try:
        # ประมวลผลคำขอ
        results = await self._search_database(request.parameters["query"])
        
        # ส่งกลับโครงสร้างที่สม่ำเสมอเสมอ
        return ToolResponse(
            result={
                "matches": [self._format_item(item) for item in results],
                "totalCount": len(results),
                "queryTime": calculation_time_ms,
                "status": "success"
            }
        )
    except Exception as e:
        return ToolResponse(
            result={
                "matches": [],
                "totalCount": 0,
                "queryTime": 0,
                "status": "error",
                "error": str(e)
            }
        )
    
def _format_item(self, item):
    """Ensures each item has a consistent structure"""
    return {
        "id": item.id,
        "title": item.title,
        "summary": item.summary[:100] + "..." if len(item.summary) > 100 else item.summary,
        "url": item.url,
        "relevance": item.score
    }

Error Handling

การจัดการข้อผิดพลาดที่แข็งแรงมีความสำคัญสำหรับเครื่องมือ MCP เพื่อรักษาความน่าเชื่อถือ

1. Graceful Error Handling

จัดการข้อผิดพลาดในระดับที่เหมาะสมและให้ข้อความที่ให้ข้อมูล:

public async Task<ToolResponse> ExecuteAsync(ToolRequest request)
{
    try
    {
        string fileId = request.Parameters.GetProperty("fileId").GetString();
        
        try
        {
            var fileData = await _fileService.GetFileAsync(fileId);
            return new ToolResponse { 
                Result = JsonSerializer.SerializeToElement(fileData) 
            };
        }
        catch (FileNotFoundException)
        {
            throw new ToolExecutionException($"File not found: {fileId}");
        }
        catch (UnauthorizedAccessException)
        {
            throw new ToolExecutionException("You don't have permission to access this file");
        }
        catch (Exception ex) when (ex is IOException || ex is TimeoutException)
        {
            _logger.LogError(ex, "Error accessing file {FileId}", fileId);
            throw new ToolExecutionException("Error accessing file: The service is temporarily unavailable");
        }
    }
    catch (JsonException)
    {
        throw new ToolExecutionException("Invalid file ID format");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error in FileAccessTool");
        throw new ToolExecutionException("An unexpected error occurred");
    }
}

2. Structured Error Responses

ส่งคืนข้อมูลข้อผิดพลาดในรูปแบบโครงสร้างเมื่อเป็นไปได้:

@Override
public ToolResponse execute(ToolRequest request) {
    try {
        // การใช้งาน
    } catch (Exception ex) {
        Map<String, Object> errorResult = new HashMap<>();
        
        errorResult.put("success", false);
        
        if (ex instanceof ValidationException) {
            ValidationException validationEx = (ValidationException) ex;
            
            errorResult.put("errorType", "validation");
            errorResult.put("errorMessage", validationEx.getMessage());
            errorResult.put("validationErrors", validationEx.getErrors());
            
            return new ToolResponse.Builder()
                .setResult(errorResult)
                .build();
        }
        
        // โยนข้อยกเว้นอื่น ๆ ใหม่เป็น ToolExecutionException
        throw new ToolExecutionException("Tool execution failed: " + ex.getMessage(), ex);
    }
}

3. Retry Logic

นำตรรกะการลองซ้ำที่เหมาะสมสำหรับความล้มเหลวชั่วคราวมาใช้:

async def execute_async(self, request):
    max_retries = 3
    retry_count = 0
    base_delay = 1  # วินาที
    
    while retry_count < max_retries:
        try:
            # เรียก API ภายนอก
            return await self._call_api(request.parameters)
        except TransientError as e:
            retry_count += 1
            if retry_count >= max_retries:
                raise ToolExecutionException(f"Operation failed after {max_retries} attempts: {str(e)}")
                
            # การหน่วงเวลากำลังสองทวีคูณ
            delay = base_delay * (2 ** (retry_count - 1))
            logging.warning(f"Transient error, retrying in {delay}s: {str(e)}")
            await asyncio.sleep(delay)
        except Exception as e:
            # ข้อผิดพลาดที่ไม่เกิดขึ้นชั่วคราว, ไม่ต้องลองใหม่
            raise ToolExecutionException(f"Operation failed: {str(e)}")

Performance Optimization

1. Caching

ใช้การแคชสำหรับการดำเนินการที่มีค่าใช้จ่ายสูง:

public class CachedDataTool : IMcpTool
{
    private readonly IDatabase _database;
    private readonly IMemoryCache _cache;
    
    public CachedDataTool(IDatabase database, IMemoryCache cache)
    {
        _database = database;
        _cache = cache;
    }
    
    public async Task<ToolResponse> ExecuteAsync(ToolRequest request)
    {
        var query = request.Parameters.GetProperty("query").GetString();
        
        // Create cache key based on parameters
        var cacheKey = $"data_query_{ComputeHash(query)}";
        
        // Try to get from cache first
        if (_cache.TryGetValue(cacheKey, out var cachedResult))
        {
            return new ToolResponse { Result = cachedResult };
        }
        
        // Cache miss - perform actual query
        var result = await _database.QueryAsync(query);
        
        // Store in cache with expiration
        var cacheOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(15));
            
        _cache.Set(cacheKey, JsonSerializer.SerializeToElement(result), cacheOptions);
        
        return new ToolResponse { Result = JsonSerializer.SerializeToElement(result) };
    }
    
    private string ComputeHash(string input)
    {
        // Implementation to generate stable hash for cache key
    }
}

2. Asynchronous Processing

ใช้รูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสสำหรับการปฏิบัติการที่ผูกกับ I/O:

public class AsyncDocumentProcessingTool implements Tool {
    private final DocumentService documentService;
    private final ExecutorService executorService;
    
    @Override
    public ToolResponse execute(ToolRequest request) {
        String documentId = request.getParameters().get("documentId").asText();
        
        // สำหรับการดำเนินการที่ใช้เวลานาน ให้ส่งคืนรหัสการประมวลผลทันที
        String processId = UUID.randomUUID().toString();
        
        // เริ่มการประมวลผลแบบอะซิงโครนัส
        CompletableFuture.runAsync(() -> {
            try {
                // ดำเนินการที่ใช้เวลานาน
                documentService.processDocument(documentId);
                
                // อัปเดตสถานะ (โดยปกติจะถูกจัดเก็บในฐานข้อมูล)
                processStatusRepository.updateStatus(processId, "completed");
            } catch (Exception ex) {
                processStatusRepository.updateStatus(processId, "failed", ex.getMessage());
            }
        }, executorService);
        
        // ส่งคืนการตอบสนองทันทีพร้อมกับรหัสกระบวนการ
        Map<String, Object> result = new HashMap<>();
        result.put("processId", processId);
        result.put("status", "processing");
        result.put("estimatedCompletionTime", ZonedDateTime.now().plusMinutes(5));
        
        return new ToolResponse.Builder().setResult(result).build();
    }
    
    // เครื่องมือตรวจสอบสถานะคู่มือ
    public class ProcessStatusTool implements Tool {
        @Override
        public ToolResponse execute(ToolRequest request) {
            String processId = request.getParameters().get("processId").asText();
            ProcessStatus status = processStatusRepository.getStatus(processId);
            
            return new ToolResponse.Builder().setResult(status).build();
        }
    }
}

3. Resource Throttling

นำการจำกัดทรัพยากรมาป้องกันการทำงานหนักเกินไป:

class ThrottledApiTool(Tool):
    def __init__(self):
        self.rate_limiter = TokenBucketRateLimiter(
            tokens_per_second=5,  # อนุญาต 5 คำขอต่อวินาที
            bucket_size=10        # อนุญาตให้ระเบิดคำขอได้สูงสุด 10 คำขอ
        )
    
    async def execute_async(self, request):
        # ตรวจสอบว่าเราสามารถดำเนินการได้หรือจำเป็นต้องรอ
        delay = self.rate_limiter.get_delay_time()
        
        if delay > 0:
            if delay > 2.0:  # หากการรอนานเกินไป
                raise ToolExecutionException(
                    f"Rate limit exceeded. Please try again in {delay:.1f} seconds."
                )
            else:
                # รอเวลาชะลอที่เหมาะสม
                await asyncio.sleep(delay)
        
        # ใช้โทเค็นหนึ่งและดำเนินการคำขอ
        self.rate_limiter.consume()
        
        # เรียก API
        result = await self._call_api(request.parameters)
        return ToolResponse(result=result)

class TokenBucketRateLimiter:
    def __init__(self, tokens_per_second, bucket_size):
        self.tokens_per_second = tokens_per_second
        self.bucket_size = bucket_size
        self.tokens = bucket_size
        self.last_refill = time.time()
        self.lock = asyncio.Lock()
    
    async def get_delay_time(self):
        async with self.lock:
            self._refill()
            if self.tokens >= 1:
                return 0
            
            # คำนวณเวลาจนกว่าโทเค็นถัดไปจะพร้อมใช้
            return (1 - self.tokens) / self.tokens_per_second
    
    async def consume(self):
        async with self.lock:
            self._refill()
            self.tokens -= 1
    
    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        
        # เพิ่มโทเค็นใหม่ตามเวลาที่ผ่านไป
        new_tokens = elapsed * self.tokens_per_second
        self.tokens = min(self.bucket_size, self.tokens + new_tokens)
        self.last_refill = now

Security Best Practices

1. Input Validation

ตรวจสอบพารามิเตอร์ป้อนเข้าทุกครั้งอย่างละเอียด:

public async Task<ToolResponse> ExecuteAsync(ToolRequest request)
{
    // Validate parameters exist
    if (!request.Parameters.TryGetProperty("query", out var queryProp))
    {
        throw new ToolExecutionException("Missing required parameter: query");
    }
    
    // Validate correct type
    if (queryProp.ValueKind != JsonValueKind.String)
    {
        throw new ToolExecutionException("Query parameter must be a string");
    }
    
    var query = queryProp.GetString();
    
    // Validate string content
    if (string.IsNullOrWhiteSpace(query))
    {
        throw new ToolExecutionException("Query parameter cannot be empty");
    }
    
    if (query.Length > 500)
    {
        throw new ToolExecutionException("Query parameter exceeds maximum length of 500 characters");
    }
    
    // Check for SQL injection attacks if applicable
    if (ContainsSqlInjection(query))
    {
        throw new ToolExecutionException("Invalid query: contains potentially unsafe SQL");
    }
    
    // Proceed with execution
    // ...
}

2. Authorization Checks

นำการตรวจสอบการอนุญาตที่เหมาะสมมาใช้:

@Override
public ToolResponse execute(ToolRequest request) {
    // รับบริบทผู้ใช้จากคำขอ
    UserContext user = request.getContext().getUserContext();
    
    // ตรวจสอบว่าผู้ใช้มีสิทธิ์ที่จำเป็นหรือไม่
    if (!authorizationService.hasPermission(user, "documents:read")) {
        throw new ToolExecutionException("User does not have permission to access documents");
    }
    
    // สำหรับทรัพยากรเฉพาะ ให้ตรวจสอบการเข้าถึงทรัพยากรนั้น
    String documentId = request.getParameters().get("documentId").asText();
    if (!documentService.canUserAccess(user.getId(), documentId)) {
        throw new ToolExecutionException("Access denied to the requested document");
    }
    
    // ดำเนินการต่อด้วยการรันเครื่องมือ
    // ...
}

3. Sensitive Data Handling

จัดการข้อมูลที่อ่อนไหวอย่างรอบคอบ:

class SecureDataTool(Tool):
    def get_schema(self):
        return {
            "type": "object",
            "properties": {
                "userId": {"type": "string"},
                "includeSensitiveData": {"type": "boolean", "default": False}
            },
            "required": ["userId"]
        }
    
    async def execute_async(self, request):
        user_id = request.parameters["userId"]
        include_sensitive = request.parameters.get("includeSensitiveData", False)
        
        # ดึงข้อมูลผู้ใช้
        user_data = await self.user_service.get_user_data(user_id)
        
        # กรองข้อมูลที่ละเอียดอ่อนเว้นแต่จะมีการร้องขออย่างชัดเจนและได้รับอนุญาต
        if not include_sensitive or not self._is_authorized_for_sensitive_data(request):
            user_data = self._redact_sensitive_fields(user_data)
        
        return ToolResponse(result=user_data)
    
    def _is_authorized_for_sensitive_data(self, request):
        # ตรวจสอบระดับการอนุญาตในบริบทของคำขอ
        auth_level = request.context.get("authorizationLevel")
        return auth_level == "admin"
    
    def _redact_sensitive_fields(self, user_data):
        # สร้างสำเนาเพื่อหลีกเลี่ยงการแก้ไขต้นฉบับ
        redacted = user_data.copy()
        
        # ลบข้อมูลที่ละเอียดอ่อนเฉพาะเจาะจง
        sensitive_fields = ["ssn", "creditCardNumber", "password"]
        for field in sensitive_fields:
            if field in redacted:
                redacted[field] = "REDACTED"
        
        # ลบข้อมูลที่ละเอียดอ่อนซ้อนอยู่ภายใน
        if "financialInfo" in redacted:
            redacted["financialInfo"] = {"available": True, "accessRestricted": True}
        
        return redacted

Testing Best Practices for MCP Tools

การทดสอบที่ครอบคลุมช่วยให้มั่นใจว่าเครื่องมือ MCP ทำงานถูกต้อง จัดการกรณีขอบ และรวมเข้าได้ดีกับระบบ

Unit Testing

1. Test Each Tool in Isolation

สร้างการทดสอบที่มุ่งเน้นสำหรับฟังก์ชันแต่ละของเครื่องมือ:

[Fact]
public async Task WeatherTool_ValidLocation_ReturnsCorrectForecast()
{
    // Arrange
    var mockWeatherService = new Mock<IWeatherService>();
    mockWeatherService
        .Setup(s => s.GetForecastAsync("Seattle", 3))
        .ReturnsAsync(new WeatherForecast(/* test data */));
    
    var tool = new WeatherForecastTool(mockWeatherService.Object);
    
    var request = new ToolRequest(
        toolName: "weatherForecast",
        parameters: JsonSerializer.SerializeToElement(new { 
            location = "Seattle", 
            days = 3 
        })
    );
    
    // Act
    var response = await tool.ExecuteAsync(request);
    
    // Assert
    Assert.NotNull(response);
    var result = JsonSerializer.Deserialize<WeatherForecast>(response.Result);
    Assert.Equal("Seattle", result.Location);
    Assert.Equal(3, result.DailyForecasts.Count);
}

[Fact]
public async Task WeatherTool_InvalidLocation_ThrowsToolExecutionException()
{
    // Arrange
    var mockWeatherService = new Mock<IWeatherService>();
    mockWeatherService
        .Setup(s => s.GetForecastAsync("InvalidLocation", It.IsAny<int>()))
        .ThrowsAsync(new LocationNotFoundException("Location not found"));
    
    var tool = new WeatherForecastTool(mockWeatherService.Object);
    
    var request = new ToolRequest(
        toolName: "weatherForecast",
        parameters: JsonSerializer.SerializeToElement(new { 
            location = "InvalidLocation", 
            days = 3 
        })
    );
    
    // Act & Assert
    var exception = await Assert.ThrowsAsync<ToolExecutionException>(
        () => tool.ExecuteAsync(request)
    );
    
    Assert.Contains("Location not found", exception.Message);
}

2. Schema Validation Testing

ทดสอบให้แน่ใจว่าสคีมาถูกต้องและบังคับใช้ข้อจำกัดได้อย่างเหมาะสม:

@Test
public void testSchemaValidation() {
    // สร้างอินสแตนซ์ของเครื่องมือ
    SearchTool searchTool = new SearchTool();
    
    // รับสคีมา
    Object schema = searchTool.getSchema();
    
    // แปลงสคีมาเป็น JSON สำหรับการตรวจสอบ
    String schemaJson = objectMapper.writeValueAsString(schema);
    
    // ตรวจสอบว่าสคีมาเป็น JSONSchema ที่ถูกต้อง
    JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
    JsonSchema jsonSchema = factory.getJsonSchema(schemaJson);
    
    // ทดสอบพารามิเตอร์ที่ถูกต้อง
    JsonNode validParams = objectMapper.createObjectNode()
        .put("query", "test query")
        .put("limit", 5);
        
    ProcessingReport validReport = jsonSchema.validate(validParams);
    assertTrue(validReport.isSuccess());
    
    // ทดสอบพารามิเตอร์ที่จำเป็นหายไป
    JsonNode missingRequired = objectMapper.createObjectNode()
        .put("limit", 5);
        
    ProcessingReport missingReport = jsonSchema.validate(missingRequired);
    assertFalse(missingReport.isSuccess());
    
    // ทดสอบประเภทพารามิเตอร์ที่ไม่ถูกต้อง
    JsonNode invalidType = objectMapper.createObjectNode()
        .put("query", "test")
        .put("limit", "not-a-number");
        
    ProcessingReport invalidReport = jsonSchema.validate(invalidType);
    assertFalse(invalidReport.isSuccess());
}

3. Error Handling Tests

สร้างการทดสอบเฉพาะสำหรับสถานการณ์ข้อผิดพลาด:

@pytest.mark.asyncio
async def test_api_tool_handles_timeout():
    # จัดเรียง
    tool = ApiTool(timeout=0.1)  # หมดเวลารวดเร็วมาก
    
    # จำลองคำร้องขอที่จะหมดเวลา
    with aioresponses() as mocked:
        mocked.get(
            "https://api.example.com/data",
            callback=lambda *args, **kwargs: asyncio.sleep(0.5)  # นานกว่าระยะเวลาหมดเวลา
        )
        
        request = ToolRequest(
            tool_name="apiTool",
            parameters={"url": "https://api.example.com/data"}
        )
        
        # ทำและยืนยัน
        with pytest.raises(ToolExecutionException) as exc_info:
            await tool.execute_async(request)
        
        # ตรวจสอบข้อความข้อยกเว้น
        assert "timed out" in str(exc_info.value).lower()

@pytest.mark.asyncio
async def test_api_tool_handles_rate_limiting():
    # จัดเรียง
    tool = ApiTool()
    
    # จำลองการตอบกลับที่จำกัดอัตรา
    with aioresponses() as mocked:
        mocked.get(
            "https://api.example.com/data",
            status=429,
            headers={"Retry-After": "2"},
            body=json.dumps({"error": "Rate limit exceeded"})
        )
        
        request = ToolRequest(
            tool_name="apiTool",
            parameters={"url": "https://api.example.com/data"}
        )
        
        # ทำและยืนยัน
        with pytest.raises(ToolExecutionException) as exc_info:
            await tool.execute_async(request)
        
        # ตรวจสอบว่าข้อยกเว้นมีข้อมูลจำกัดอัตราไหม
        error_msg = str(exc_info.value).lower()
        assert "rate limit" in error_msg
        assert "try again" in error_msg

Integration Testing

1. Tool Chain Testing

ทดสอบเครื่องมือที่ทำงานร่วมกันในชุดค่าที่คาดหวัง:

[Fact]
public async Task DataProcessingWorkflow_CompletesSuccessfully()
{
    // Arrange
    var dataFetchTool = new DataFetchTool(mockDataService.Object);
    var analysisTools = new DataAnalysisTool(mockAnalysisService.Object);
    var visualizationTool = new DataVisualizationTool(mockVisualizationService.Object);
    
    var toolRegistry = new ToolRegistry();
    toolRegistry.RegisterTool(dataFetchTool);
    toolRegistry.RegisterTool(analysisTools);
    toolRegistry.RegisterTool(visualizationTool);
    
    var workflowExecutor = new WorkflowExecutor(toolRegistry);
    
    // Act
    var result = await workflowExecutor.ExecuteWorkflowAsync(new[] {
        new ToolCall("dataFetch", new { source = "sales2023" }),
        new ToolCall("dataAnalysis", ctx => new { 
            data = ctx.GetResult("dataFetch"),
            analysis = "trend" 
        }),
        new ToolCall("dataVisualize", ctx => new {
            analysisResult = ctx.GetResult("dataAnalysis"),
            type = "line-chart"
        })
    });
    
    // Assert
    Assert.NotNull(result);
    Assert.True(result.Success);
    Assert.NotNull(result.GetResult("dataVisualize"));
    Assert.Contains("chartUrl", result.GetResult("dataVisualize").ToString());
}

2. MCP Server Testing

ทดสอบเซิร์ฟเวอร์ MCP พร้อมการลงทะเบียนและการดำเนินการเครื่องมือเต็มรูปแบบ:

@SpringBootTest
@AutoConfigureMockMvc
public class McpServerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    public void testToolDiscovery() throws Exception {
        // ทดสอบจุดสิ้นสุดการค้นพบ
        mockMvc.perform(get("/mcp/tools"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.tools").isArray())
            .andExpect(jsonPath("$.tools[*].name").value(hasItems(
                "weatherForecast", "calculator", "documentSearch"
            )));
    }
    
    @Test
    public void testToolExecution() throws Exception {
        // สร้างคำขอเครื่องมือ
        Map<String, Object> request = new HashMap<>();
        request.put("toolName", "calculator");
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("operation", "add");
        parameters.put("a", 5);
        parameters.put("b", 7);
        request.put("parameters", parameters);
        
        // ส่งคำขอและตรวจสอบการตอบกลับ
        mockMvc.perform(post("/mcp/execute")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.result.value").value(12));
    }
    
    @Test
    public void testToolValidation() throws Exception {
        // สร้างคำขอเครื่องมือที่ไม่ถูกต้อง
        Map<String, Object> request = new HashMap<>();
        request.put("toolName", "calculator");
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("operation", "divide");
        parameters.put("a", 10);
        // ขาดพารามิเตอร์ "b"
        request.put("parameters", parameters);
        
        // ส่งคำขอและตรวจสอบการตอบกลับข้อผิดพลาด
        mockMvc.perform(post("/mcp/execute")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.error").exists());
    }
}

3. End-to-End Testing

ทดสอบเวิร์กโฟลว์ครบถ้วนตั้งแต่การกระตุ้นโมเดลจนถึงการทำงานของเครื่องมือ:

@pytest.mark.asyncio
async def test_model_interaction_with_tool():
    # จัดเตรียม - ตั้งค่าไคลเอนต์ MCP และโมเดลจำลอง
    mcp_client = McpClient(server_url="http://localhost:5000")
    
    # จำลองการตอบกลับโมเดล
    mock_model = MockLanguageModel([
        MockResponse(
            "What's the weather in Seattle?",
            tool_calls=[{
                "tool_name": "weatherForecast",
                "parameters": {"location": "Seattle", "days": 3}
            }]
        ),
        MockResponse(
            "Here's the weather forecast for Seattle:\n- Today: 65°F, Partly Cloudy\n- Tomorrow: 68°F, Sunny\n- Day after: 62°F, Rain",
            tool_calls=[]
        )
    ])
    
    # จำลองการตอบสนองของเครื่องมือสภาพอากาศ
    with aioresponses() as mocked:
        mocked.post(
            "http://localhost:5000/mcp/execute",
            payload={
                "result": {
                    "location": "Seattle",
                    "forecast": [
                        {"date": "2023-06-01", "temperature": 65, "conditions": "Partly Cloudy"},
                        {"date": "2023-06-02", "temperature": 68, "conditions": "Sunny"},
                        {"date": "2023-06-03", "temperature": 62, "conditions": "Rain"}
                    ]
                }
            }
        )
        
        # ดำเนินการ
        response = await mcp_client.send_prompt(
            "What's the weather in Seattle?",
            model=mock_model,
            allowed_tools=["weatherForecast"]
        )
        
        # ตรวจสอบผลลัพธ์
        assert "Seattle" in response.generated_text
        assert "65" in response.generated_text
        assert "Sunny" in response.generated_text
        assert "Rain" in response.generated_text
        assert len(response.tool_calls) == 1
        assert response.tool_calls[0].tool_name == "weatherForecast"

Performance Testing

1. Load Testing

ทดสอบจำนวนคำขอพร้อมกันที่เซิร์ฟเวอร์ MCP ของคุณรองรับ:

[Fact]
public async Task McpServer_HandlesHighConcurrency()
{
    // Arrange
    var server = new McpServer(
        name: "TestServer",
        version: "1.0",
        maxConcurrentRequests: 100
    );
    
    server.RegisterTool(new FastExecutingTool());
    await server.StartAsync();
    
    var client = new McpClient("http://localhost:5000");
    
    // Act
    var tasks = new List<Task<McpResponse>>();
    for (int i = 0; i < 1000; i++)
    {
        tasks.Add(client.ExecuteToolAsync("fastTool", new { iteration = i }));
    }
    
    var results = await Task.WhenAll(tasks);
    
    // Assert
    Assert.Equal(1000, results.Length);
    Assert.All(results, r => Assert.NotNull(r));
}

2. Stress Testing

ทดสอบระบบภายใต้การโหลดสูงสุด:

@Test
public void testServerUnderStress() {
    int maxUsers = 1000;
    int rampUpTimeSeconds = 60;
    int testDurationSeconds = 300;
    
    // ตั้งค่า JMeter สำหรับการทดสอบความเครียด
    StandardJMeterEngine jmeter = new StandardJMeterEngine();
    
    // กำหนดค่าแผนการทดสอบ JMeter
    HashTree testPlanTree = new HashTree();
    
    // สร้างแผนการทดสอบ, กลุ่มเธรด, ตัวอย่าง ฯลฯ
    TestPlan testPlan = new TestPlan("MCP Server Stress Test");
    testPlanTree.add(testPlan);
    
    ThreadGroup threadGroup = new ThreadGroup();
    threadGroup.setNumThreads(maxUsers);
    threadGroup.setRampUp(rampUpTimeSeconds);
    threadGroup.setScheduler(true);
    threadGroup.setDuration(testDurationSeconds);
    
    testPlanTree.add(threadGroup);
    
    // เพิ่ม HTTP sampler สำหรับการเรียกใช้เครื่องมือ
    HTTPSampler toolExecutionSampler = new HTTPSampler();
    toolExecutionSampler.setDomain("localhost");
    toolExecutionSampler.setPort(5000);
    toolExecutionSampler.setPath("/mcp/execute");
    toolExecutionSampler.setMethod("POST");
    toolExecutionSampler.addArgument("toolName", "calculator");
    toolExecutionSampler.addArgument("parameters", "{\"operation\":\"add\",\"a\":5,\"b\":7}");
    
    threadGroup.add(toolExecutionSampler);
    
    // เพิ่มผู้ฟัง
    SummaryReport summaryReport = new SummaryReport();
    threadGroup.add(summaryReport);
    
    // เรียกใช้การทดสอบ
    jmeter.configure(testPlanTree);
    jmeter.run();
    
    // ตรวจสอบผลลัพธ์
    assertEquals(0, summaryReport.getErrorCount());
    assertTrue(summaryReport.getAverage() < 200); // เวลาในการตอบสนองเฉลี่ย < 200ms
    assertTrue(summaryReport.getPercentile(90.0) < 500); // ค่าร้อยละที่ 90 < 500ms
}

3. Monitoring and Profiling

ตั้งค่าการตรวจสอบเพื่อวิเคราะห์ประสิทธิภาพในระยะยาว:

# กำหนดการตรวจสอบสำหรับเซิร์ฟเวอร์ MCP
def configure_monitoring(server):
    # ตั้งค่าเมตริกของ Prometheus
    prometheus_metrics = {
        "request_count": Counter("mcp_requests_total", "Total MCP requests"),
        "request_latency": Histogram(
            "mcp_request_duration_seconds", 
            "Request duration in seconds",
            buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 2.5, 5.0, 10.0]
        ),
        "tool_execution_count": Counter(
            "mcp_tool_executions_total", 
            "Tool execution count",
            labelnames=["tool_name"]
        ),
        "tool_execution_latency": Histogram(
            "mcp_tool_duration_seconds", 
            "Tool execution duration in seconds",
            labelnames=["tool_name"],
            buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 2.5, 5.0, 10.0]
        ),
        "tool_errors": Counter(
            "mcp_tool_errors_total",
            "Tool execution errors",
            labelnames=["tool_name", "error_type"]
        )
    }
    
    # เพิ่มมิดเดิลแวร์สำหรับจับเวลาและบันทึกเมตริก
    server.add_middleware(PrometheusMiddleware(prometheus_metrics))
    
    # เปิดเผยจุดสิ้นสุดของเมตริก
    @server.router.get("/metrics")
    async def metrics():
        return generate_latest()
    
    return server

MCP Workflow Design Patterns

เวิร์กโฟลว์ MCP ที่ออกแบบดีช่วยเพิ่มประสิทธิภาพ ความน่าเชื่อถือ และการบำรุงรักษา ต่อไปนี้เป็นรูปแบบสำคัญที่ควรปฏิบัติตาม:

1. Chain of Tools Pattern

เชื่อมเครื่องมือหลายตัวในลำดับที่ผลลัพธ์ของเครื่องมือหนึ่งเป็นข้อมูลนำเข้าของถัดไป:

# การใช้งาน Python Chain of Tools
class ChainWorkflow:
    def __init__(self, tools_chain):
        self.tools_chain = tools_chain  # รายการชื่อเครื่องมือที่จะดำเนินการตามลำดับ
    
    async def execute(self, mcp_client, initial_input):
        current_result = initial_input
        all_results = {"input": initial_input}
        
        for tool_name in self.tools_chain:
            # ดำเนินการแต่ละเครื่องมือในสายโซ่ โดยส่งผลลัพธ์ก่อนหน้า
            response = await mcp_client.execute_tool(tool_name, current_result)
            
            # เก็บผลลัพธ์และใช้เป็นอินพุตสำหรับเครื่องมือต่อไป
            all_results[tool_name] = response.result
            current_result = response.result
        
        return {
            "final_result": current_result,
            "all_results": all_results
        }

# ตัวอย่างการใช้งาน
data_processing_chain = ChainWorkflow([
    "dataFetch",
    "dataCleaner",
    "dataAnalyzer",
    "dataVisualizer"
])

result = await data_processing_chain.execute(
    mcp_client,
    {"source": "sales_database", "table": "transactions"}
)

2. Dispatcher Pattern

ใช้เครื่องมือกลางที่จัดการส่งงานไปยังเครื่องมือเฉพาะทางตามข้อมูลนำเข้า:

public class ContentDispatcherTool : IMcpTool
{
    private readonly IMcpClient _mcpClient;
    
    public ContentDispatcherTool(IMcpClient mcpClient)
    {
        _mcpClient = mcpClient;
    }
    
    public string Name => "contentProcessor";
    public string Description => "Processes content of various types";
    
    public object GetSchema()
    {
        return new {
            type = "object",
            properties = new {
                content = new { type = "string" },
                contentType = new { 
                    type = "string",
                    enum = new[] { "text", "html", "markdown", "csv", "code" }
                },
                operation = new { 
                    type = "string",
                    enum = new[] { "summarize", "analyze", "extract", "convert" }
                }
            },
            required = new[] { "content", "contentType", "operation" }
        };
    }
    
    public async Task<ToolResponse> ExecuteAsync(ToolRequest request)
    {
        var content = request.Parameters.GetProperty("content").GetString();
        var contentType = request.Parameters.GetProperty("contentType").GetString();
        var operation = request.Parameters.GetProperty("operation").GetString();
        
        // Determine which specialized tool to use
        string targetTool = DetermineTargetTool(contentType, operation);
        
        // Forward to the specialized tool
        var specializedResponse = await _mcpClient.ExecuteToolAsync(
            targetTool,
            new { content, options = GetOptionsForTool(targetTool, operation) }
        );
        
        return new ToolResponse { Result = specializedResponse.Result };
    }
    
    private string DetermineTargetTool(string contentType, string operation)
    {
        return (contentType, operation) switch
        {
            ("text", "summarize") => "textSummarizer",
            ("text", "analyze") => "textAnalyzer",
            ("html", _) => "htmlProcessor",
            ("markdown", _) => "markdownProcessor",
            ("csv", _) => "csvProcessor",
            ("code", _) => "codeAnalyzer",
            _ => throw new ToolExecutionException($"No tool available for {contentType}/{operation}")
        };
    }
    
    private object GetOptionsForTool(string toolName, string operation)
    {
        // Return appropriate options for each specialized tool
        return toolName switch
        {
            "textSummarizer" => new { length = "medium" },
            "htmlProcessor" => new { cleanUp = true, operation },
            // Options for other tools...
            _ => new { }
        };
    }
}

3. Parallel Processing Pattern

ดำเนินการเครื่องมือหลายตัวพร้อมกันเพื่อประสิทธิภาพ:

public class ParallelDataProcessingWorkflow {
    private final McpClient mcpClient;
    
    public ParallelDataProcessingWorkflow(McpClient mcpClient) {
        this.mcpClient = mcpClient;
    }
    
    public WorkflowResult execute(String datasetId) {
        // ขั้นตอนที่ 1: ดึงข้อมูลเมทาดาต้าของชุดข้อมูล (ซิงโครนัส)
        ToolResponse metadataResponse = mcpClient.executeTool("datasetMetadata", 
            Map.of("datasetId", datasetId));
        
        // ขั้นตอนที่ 2: เริ่มการวิเคราะห์หลายรายการพร้อมกัน
        CompletableFuture<ToolResponse> statisticalAnalysis = CompletableFuture.supplyAsync(() ->
            mcpClient.executeTool("statisticalAnalysis", Map.of(
                "datasetId", datasetId,
                "type", "comprehensive"
            ))
        );
        
        CompletableFuture<ToolResponse> correlationAnalysis = CompletableFuture.supplyAsync(() ->
            mcpClient.executeTool("correlationAnalysis", Map.of(
                "datasetId", datasetId,
                "method", "pearson"
            ))
        );
        
        CompletableFuture<ToolResponse> outlierDetection = CompletableFuture.supplyAsync(() ->
            mcpClient.executeTool("outlierDetection", Map.of(
                "datasetId", datasetId,
                "sensitivity", "medium"
            ))
        );
        
        // รอให้ทุกงานที่ทำพร้อมกันเสร็จสิ้น
        CompletableFuture<Void> allAnalyses = CompletableFuture.allOf(
            statisticalAnalysis, correlationAnalysis, outlierDetection
        );
        
        allAnalyses.join();  // รอการทำงานให้เสร็จสมบูรณ์
        
        // ขั้นตอนที่ 3: รวมผลลัพธ์
        Map<String, Object> combinedResults = new HashMap<>();
        combinedResults.put("metadata", metadataResponse.getResult());
        combinedResults.put("statistics", statisticalAnalysis.join().getResult());
        combinedResults.put("correlations", correlationAnalysis.join().getResult());
        combinedResults.put("outliers", outlierDetection.join().getResult());
        
        // ขั้นตอนที่ 4: สร้างรายงานสรุป
        ToolResponse summaryResponse = mcpClient.executeTool("reportGenerator", 
            Map.of("analysisResults", combinedResults));
        
        // ส่งกลับผลลัพธ์ของกระบวนการทำงานทั้งหมด
        WorkflowResult result = new WorkflowResult();
        result.setDatasetId(datasetId);
        result.setAnalysisResults(combinedResults);
        result.setSummaryReport(summaryResponse.getResult());
        
        return result;
    }
}

4. Error Recovery Pattern

ใช้การสำรองแบบนุ่มนวลเมื่อเครื่องมือเกิดข้อผิดพลาด:

class ResilientWorkflow:
    def __init__(self, mcp_client):
        self.client = mcp_client
    
    async def execute_with_fallback(self, primary_tool, fallback_tool, parameters):
        try:
            # ลองใช้เครื่องมือหลักก่อน
            response = await self.client.execute_tool(primary_tool, parameters)
            return {
                "result": response.result,
                "source": "primary",
                "tool": primary_tool
            }
        except ToolExecutionException as e:
            # บันทึกความล้มเหลว
            logging.warning(f"Primary tool '{primary_tool}' failed: {str(e)}")
            
            # กลับไปใช้เครื่องมือรอง
            try:
                # อาจต้องแปลงพารามิเตอร์สำหรับเครื่องมือสำรอง
                fallback_params = self._adapt_parameters(parameters, primary_tool, fallback_tool)
                
                response = await self.client.execute_tool(fallback_tool, fallback_params)
                return {
                    "result": response.result,
                    "source": "fallback",
                    "tool": fallback_tool,
                    "primaryError": str(e)
                }
            except ToolExecutionException as fallback_error:
                # ทั้งสองเครื่องมือไม่สำเร็จ
                logging.error(f"Both primary and fallback tools failed. Fallback error: {str(fallback_error)}")
                raise WorkflowExecutionException(
                    f"Workflow failed: primary error: {str(e)}; fallback error: {str(fallback_error)}"
                )
    
    def _adapt_parameters(self, params, from_tool, to_tool):
        """Adapt parameters between different tools if needed"""
        # การดำเนินการนี้ขึ้นอยู่กับเครื่องมือเฉพาะ
        # ในตัวอย่างนี้ เราจะคืนค่าพารามิเตอร์เดิม
        return params

# ตัวอย่างการใช้งาน
async def get_weather(workflow, location):
    return await workflow.execute_with_fallback(
        "premiumWeatherService",  # API สภาพอากาศหลัก (มีค่าบริการ)
        "basicWeatherService",    # API สภาพอากาศสำรอง (ฟรี)
        {"location": location}
    )

5. Workflow Composition Pattern

สร้างเวิร์กโฟลว์ที่ซับซ้อนโดยการประสานเวิร์กโฟลว์ที่ง่ายขึ้น:

public class CompositeWorkflow : IWorkflow
{
    private readonly List<IWorkflow> _workflows;
    
    public CompositeWorkflow(IEnumerable<IWorkflow> workflows)
    {
        _workflows = new List<IWorkflow>(workflows);
    }
    
    public async Task<WorkflowResult> ExecuteAsync(WorkflowContext context)
    {
        var results = new Dictionary<string, object>();
        
        foreach (var workflow in _workflows)
        {
            var workflowResult = await workflow.ExecuteAsync(context);
            
            // Store each workflow's result
            results[workflow.Name] = workflowResult;
            
            // Update context with the result for the next workflow
            context = context.WithResult(workflow.Name, workflowResult);
        }
        
        return new WorkflowResult(results);
    }
    
    public string Name => "CompositeWorkflow";
    public string Description => "Executes multiple workflows in sequence";
}

// Example usage
var documentWorkflow = new CompositeWorkflow(new IWorkflow[] {
    new DocumentFetchWorkflow(),
    new DocumentProcessingWorkflow(),
    new InsightGenerationWorkflow(),
    new ReportGenerationWorkflow()
});

var result = await documentWorkflow.ExecuteAsync(new WorkflowContext {
    Parameters = new { documentId = "12345" }
});

Testing MCP Servers: Best Practices and Top Tips

Overview

การทดสอบเป็นส่วนสำคัญของการพัฒนาเซิร์ฟเวอร์ MCP ที่น่าเชื่อถือและคุณภาพสูง คู่มือนี้ให้แนวปฏิบัติที่ดีที่สุดและเคล็ดลับสำหรับการทดสอบเซิร์ฟเวอร์ MCP ของคุณตลอดวงจรชีวิตการพัฒนา ตั้งแต่การทดสอบหน่วยถึงการทดสอบแบบผสานรวมและการตรวจสอบตั้งแต่ต้นจนจบ

Why Testing Matters for MCP Servers

เซิร์ฟเวอร์ MCP ทำหน้าที่เป็นมิดเดิลแวร์สำคัญระหว่างโมเดล AI และแอปพลิเคชันของไคลเอนต์ การทดสอบอย่างละเอียดช่วยให้มั่นใจใน:

  • ความน่าเชื่อถือในสภาพแวดล้อมการผลิต
  • การจัดการคำขอและคำตอบที่ถูกต้อง
  • การใช้งานสเปค MCP อย่างเหมาะสม
  • ความต้านทานต่อความล้มเหลวและกรณีขอบ
  • ประสิทธิภาพที่สม่ำเสมอภายใต้ภาระงานต่าง ๆ

Unit Testing for MCP Servers

Unit Testing (Foundation)

การทดสอบหน่วยตรวจสอบองค์ประกอบแต่ละส่วนของเซิร์ฟเวอร์ MCP ของคุณอย่างแยกส่วน

What to Test

  1. Resource Handlers: ทดสอบตรรกะของตัวจัดการทรัพยากรแต่ละตัวอย่างอิสระ
  2. Tool Implementations: ตรวจสอบพฤติกรรมของเครื่องมือด้วยข้อมูลเข้าแบบต่าง ๆ
  3. Prompt Templates: ตรวจสอบให้แน่ใจว่าแบบร่างคำกระตุ้นแสดงผลถูกต้อง
  4. Schema Validation: ทดสอบตรรกะการตรวจสอบความถูกต้องของพารามิเตอร์
  5. Error Handling: ตรวจสอบการตอบสนองข้อผิดพลาดสำหรับข้อมูลเข้าไม่ถูกต้อง

Best Practices for Unit Testing

// Example unit test for a calculator tool in C#
[Fact]
public async Task CalculatorTool_Add_ReturnsCorrectSum()
{
    // Arrange
    var calculator = new CalculatorTool();
    var parameters = new Dictionary<string, object>
    {
        ["operation"] = "add",
        ["a"] = 5,
        ["b"] = 7
    };
    
    // Act
    var response = await calculator.ExecuteAsync(parameters);
    var result = JsonSerializer.Deserialize<CalculationResult>(response.Content[0].ToString());
    
    // Assert
    Assert.Equal(12, result.Value);
}
# ตัวอย่างการทดสอบหน่วยสำหรับเครื่องมือเครื่องคิดเลขใน Python
def test_calculator_tool_add():
    # จัดเตรียม
    calculator = CalculatorTool()
    parameters = {
        "operation": "add",
        "a": 5,
        "b": 7
    }
    
    # ดำเนินการ
    response = calculator.execute(parameters)
    result = json.loads(response.content[0].text)
    
    # ตรวจสอบผล
    assert result["value"] == 12

Integration Testing (Middle Layer)

การทดสอบแบบผสานรวมตรวจสอบการทำงานร่วมกันระหว่างส่วนประกอบของเซิร์ฟเวอร์ MCP ของคุณ

What to Test

  1. Server Initialization: ทดสอบการเริ่มต้นเซิร์ฟเวอร์ด้วยการตั้งค่าต่าง ๆ
  2. Route Registration: ตรวจสอบว่าเส้นทางทั้งหมดถูกลงทะเบียนอย่างถูกต้อง
  3. Request Processing: ทดสอบวงจรคำขอ-คำตอบเต็มรูปแบบ
  4. Error Propagation: ตรวจสอบให้แน่ใจว่าข้อผิดพลาดถูกจัดการอย่างถูกต้องข้ามส่วนประกอบ
  5. Authentication & Authorization: ทดสอบกลไกความปลอดภัย

Best Practices for Integration Testing

// Example integration test for MCP server in C#
[Fact]
public async Task Server_ProcessToolRequest_ReturnsValidResponse()
{
    // Arrange
    var server = new McpServer();
    server.RegisterTool(new CalculatorTool());
    await server.StartAsync();
    
    var request = new McpRequest
    {
        Tool = "calculator",
        Parameters = new Dictionary<string, object>
        {
            ["operation"] = "multiply",
            ["a"] = 6,
            ["b"] = 7
        }
    };
    
    // Act
    var response = await server.ProcessRequestAsync(request);
    
    // Assert
    Assert.NotNull(response);
    Assert.Equal(McpStatusCodes.Success, response.StatusCode);
    // Additional assertions for response content
    
    // Cleanup
    await server.StopAsync();
}

End-to-End Testing (Top Layer)

การทดสอบตั้งแต่ต้นจนจบตรวจสอบพฤติกรรมของระบบทั้งหมดจากไคลเอนต์ถึงเซิร์ฟเวอร์

What to Test

  1. Client-Server Communication: ทดสอบวงจรคำขอ-คำตอบครบถ้วน
  2. Real Client SDKs: ทดสอบกับการใช้งานไคลเอนต์จริง
  3. Performance Under Load: ตรวจสอบพฤติกรรมกับคำขอพร้อมกันหลายรายการ
  4. Error Recovery: ทดสอบการกู้คืนระบบจากความล้มเหลว
  5. Long-Running Operations: ตรวจสอบการจัดการการสตรีมและการดำเนินการยาว

Best Practices for E2E Testing

// ตัวอย่างการทดสอบ E2E กับไคลเอนต์ใน TypeScript
describe('MCP Server E2E Tests', () => {
  let client: McpClient;
  
  beforeAll(async () => {
    // เริ่มต้นเซิร์ฟเวอร์ในสภาพแวดล้อมการทดสอบ
    await startTestServer();
    client = new McpClient('http://localhost:5000');
  });
  
  afterAll(async () => {
    await stopTestServer();
  });
  
  test('Client can invoke calculator tool and get correct result', async () => {
    // ดำเนินการ
    const response = await client.invokeToolAsync('calculator', {
      operation: 'divide',
      a: 20,
      b: 4
    });
    
    // ยืนยันผล
    expect(response.statusCode).toBe(200);
    expect(response.content[0].text).toContain('5');
  });
});

Mocking Strategies for MCP Testing

การจำลองจำเป็นสำหรับการแยกส่วนประกอบในระหว่างการทดสอบ

Components to Mock

  1. External AI Models: จำลองการตอบกลับของโมเดลเพื่อการทดสอบที่คาดการณ์ได้
  2. External Services: จำลองการพึ่งพา API (ฐานข้อมูล บริการภายนอก)
  3. Authentication Services: จำลองผู้ให้บริการพิสูจน์ตัวตน
  4. Resource Providers: จำลองตัวจัดการทรัพยากรที่มีค่าใช้จ่ายสูง

Example: Mocking an AI Model Response

// C# example with Moq
var mockModel = new Mock<ILanguageModel>();
mockModel
    .Setup(m => m.GenerateResponseAsync(
        It.IsAny<string>(),
        It.IsAny<McpRequestContext>()))
    .ReturnsAsync(new ModelResponse { 
        Text = "Mocked model response",
        FinishReason = FinishReason.Completed
    });

var server = new McpServer(modelClient: mockModel.Object);
# ตัวอย่าง Python กับ unittest.mock
@patch('mcp_server.models.OpenAIModel')
def test_with_mock_model(mock_model):
    # กำหนดค่า mock
    mock_model.return_value.generate_response.return_value = {
        "text": "Mocked model response",
        "finish_reason": "completed"
    }
    
    # ใช้ mock ในการทดสอบ
    server = McpServer(model_client=mock_model)
    # ดำเนินการทดสอบต่อ

Performance Testing

การทดสอบประสิทธิภาพมีความสำคัญสำหรับเซิร์ฟเวอร์ MCP ในการผลิต

What to Measure

  1. Latency: เวลาตอบสนองคำขอ
  2. Throughput: จำนวนคำขอที่จัดการต่อวินาที
  3. Resource Utilization: การใช้ CPU, หน่วยความจำ, เครือข่าย
  4. Concurrency Handling: พฤติกรรมภายใต้คำขอพร้อมกัน
  5. Scaling Characteristics: ประสิทธิภาพตามภาระการทำงานที่เพิ่มขึ้น

Tools for Performance Testing

  • k6: เครื่องมือทดสอบโหลดแบบโอเพนซอร์ส
  • JMeter: การทดสอบประสิทธิภาพครบวงจร
  • Locust: ทดสอบโหลดด้วย Python
  • Azure Load Testing: การทดสอบประสิทธิภาพบนคลาวด์

Example: Basic Load Test with k6

// สคริปต์ k6 สำหรับทดสอบโหลดเซิร์ฟเวอร์ MCP
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,  // ผู้ใช้เสมือน 10 คน
  duration: '30s',
};

export default function () {
  const payload = JSON.stringify({
    tool: 'calculator',
    parameters: {
      operation: 'add',
      a: Math.floor(Math.random() * 100),
      b: Math.floor(Math.random() * 100)
    }
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer test-token'
    },
  };

  const res = http.post('http://localhost:5000/api/tools/invoke', payload, params);
  
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  
  sleep(1);
}

Test Automation for MCP Servers

การทำอัตโนมัติการทดสอบช่วยให้คุณภาพสม่ำเสมอและการตอบกลับรวดเร็วขึ้น

CI/CD Integration

  1. Run Unit Tests on Pull Requests: ตรวจสอบว่าโค้ดที่เปลี่ยนไม่ทำให้ฟังก์ชันการทำงานเสียหาย
  2. การทดสอบการรวมระบบในสเตจจิ้ง: รันการทดสอบการรวมระบบในสภาพแวดล้อมก่อนการผลิต
  3. มาตรฐานประสิทธิภาพ: รักษาเกณฑ์มาตรฐานประสิทธิภาพเพื่อจับความถดถอย
  4. การสแกนความปลอดภัย: ทำการทดสอบความปลอดภัยโดยอัตโนมัติเป็นส่วนหนึ่งของ pipeline

ตัวอย่าง CI Pipeline (GitHub Actions)

name: MCP Server Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Runtime
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '8.0.x'
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --no-restore
    
    - name: Unit Tests
      run: dotnet test --no-build --filter Category=Unit
    
    - name: Integration Tests
      run: dotnet test --no-build --filter Category=Integration
      
    - name: Performance Tests
      run: dotnet run --project tests/PerformanceTests/PerformanceTests.csproj

การทดสอบการปฏิบัติตามข้อกำหนด MCP

ตรวจสอบเซิร์ฟเวอร์ของคุณว่าใช้งานตามข้อกำหนด MCP อย่างถูกต้อง

ด้านสำคัญของการปฏิบัติตาม

  1. จุดเชื่อมต่อ API: ทดสอบจุดเชื่อมต่อที่จำเป็น (/resources, /tools, ฯลฯ)
  2. รูปแบบคำขอ/คำตอบ: ตรวจสอบการปฏิบัติตามสคีมา
  3. รหัสข้อผิดพลาด: ตรวจสอบรหัสสถานะที่ถูกต้องสำหรับสถานการณ์ต่าง ๆ
  4. ประเภทเนื้อหา: ทดสอบการจัดการประเภทเนื้อหาที่ต่างกัน
  5. กระบวนการรับรองตัวตน: ตรวจสอบกลไกรับรองตามข้อกำหนด

ชุดทดสอบการปฏิบัติตาม

[Fact]
public async Task Server_ResourceEndpoint_ReturnsCorrectSchema()
{
    // Arrange
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Authorization", "Bearer test-token");
    
    // Act
    var response = await client.GetAsync("http://localhost:5000/api/resources");
    var content = await response.Content.ReadAsStringAsync();
    var resources = JsonSerializer.Deserialize<ResourceList>(content);
    
    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    Assert.NotNull(resources);
    Assert.All(resources.Resources, resource => 
    {
        Assert.NotNull(resource.Id);
        Assert.NotNull(resource.Type);
        // Additional schema validation
    });
}

10 เคล็ดลับยอดนิยมสำหรับการทดสอบเซิร์ฟเวอร์ MCP อย่างมีประสิทธิภาพ

  1. ทดสอบการกำหนดเครื่องมือแยกต่างหาก: ตรวจสอบการกำหนดสคีมาอย่างอิสระจากตรรกะของเครื่องมือ
  2. ใช้การทดสอบแบบมีพารามิเตอร์: ทดสอบเครื่องมือด้วยอินพุตหลากหลาย รวมถึงกรณีขอบเขต
  3. ตรวจสอบการตอบกลับข้อผิดพลาด: ตรวจสอบการจัดการข้อผิดพลาดที่เหมาะสมสำหรับทุกเงื่อนไขข้อผิดพลาด
  4. ทดสอบตรรกะการอนุญาต: ตรวจสอบการควบคุมการเข้าถึงที่เหมาะสมสำหรับบทบาทผู้ใช้ต่าง ๆ
  5. ตรวจสอบการครอบคลุมการทดสอบ: ตั้งเป้าความครอบคลุมสูงของโค้ดเส้นทางสำคัญ
  6. ทดสอบการตอบสนองแบบสตรีมมิ่ง: ตรวจสอบการจัดการเนื้อหาสตรีมมิ่งที่ถูกต้อง
  7. จำลองปัญหาเครือข่าย: ทดสอบพฤติกรรมภายใต้เครือข่ายที่มีปัญหา
  8. ทดสอบขีดจำกัดทรัพยากร: ตรวจสอบพฤติกรรมเมื่อถึงโควตาหรือขีดจำกัดอัตรา
  9. อัตโนมัติการทดสอบถดถอย: สร้างชุดทดสอบที่รันทุกครั้งที่โค้ดเปลี่ยนแปลง
  10. จัดทำเอกสารกรณีทดสอบ: รักษาเอกสารที่ชัดเจนของสถานการณ์การทดสอบ

ข้อผิดพลาดทั่วไปในการทดสอบ

  • พึ่งพาการทดสอบเส้นทางที่สมบูรณ์มากเกินไป: ต้องทดสอบกรณีข้อผิดพลาดอย่างละเอียด
  • ละเลยการทดสอบประสิทธิภาพ: ระบุคอขวดก่อนที่จะส่งผลกระทบต่อการผลิต
  • ทดสอบแยกกันเท่านั้น: รวมการทดสอบหน่วยการทำงาน การรวม และปลายทางถึงปลายทาง
  • การครอบคลุม API ที่ไม่สมบูรณ์: ตรวจสอบให้แน่ใจว่าทดสอบทุกจุดเชื่อมต่อและคุณสมบัติ
  • สภาพแวดล้อมทดสอบไม่สอดคล้องกัน: ใช้คอนเทนเนอร์เพื่อให้สภาพแวดล้อมทดสอบมีความสอดคล้อง

บทสรุป

กลยุทธ์การทดสอบที่ครอบคลุมเป็นสิ่งจำเป็นสำหรับการพัฒนาเซิร์ฟเวอร์ MCP ที่น่าเชื่อถือและมีคุณภาพสูง โดยการนำแนวทางปฏิบัติที่ดีที่สุดและเคล็ดลับที่ระบุในคู่มือนี้ไปใช้ คุณจะมั่นใจได้ว่าการใช้งาน MCP ของคุณเป็นไปตามมาตรฐานสูงสุดด้านคุณภาพ ความน่าเชื่อถือ และประสิทธิภาพ

ข้อสรุปสำคัญ

  1. การออกแบบเครื่องมือ: ปฏิบัติตามหลักการความรับผิดชอบเดี่ยว ใช้การฉีดพึ่งพิง และออกแบบให้สามารถประกอบกันได้
  2. การออกแบบสคีมา: สร้างสคีมาที่ชัดเจน มีเอกสารครบถ้วน พร้อมข้อจำกัดการตรวจสอบที่เหมาะสม
  3. การจัดการข้อผิดพลาด: ดำเนินการจัดการข้อผิดพลาดอย่างอ่อนโยน ตอบสนองข้อผิดพลาดอย่างมีโครงสร้าง และตรรกะการลองใหม่
  4. ประสิทธิภาพ: ใช้การแคช การประมวลผลแบบอะซิงโครนัส และการควบคุมทรัพยากร
  5. ความปลอดภัย: ตรวจสอบข้อมูลนำเข้าอย่างละเอียด ตรวจสอบการอนุญาต และจัดการข้อมูลที่ละเอียดอ่อน
  6. การทดสอบ: สร้างชุดทดสอบหน่วย การรวม และปลายทางถึงปลายทางที่ครอบคลุม
  7. รูปแบบการทำงาน: ใช้รูปแบบที่ยอมรับเช่น chains, dispatchers และการประมวลผลคู่ขนาน

แบบฝึกหัด

ออกแบบเครื่องมือและเวิร์กโฟลว์ MCP สำหรับระบบประมวลผลเอกสารที่:

  1. รับเอกสารในหลายรูปแบบ (PDF, DOCX, TXT)
  2. สกัดข้อความและข้อมูลสำคัญจากเอกสาร
  3. จำแนกเอกสารตามประเภทและเนื้อหา
  4. สร้างสรุปของแต่ละเอกสาร

นำเสนอสคีมาของเครื่องมือ การจัดการข้อผิดพลาด และรูปแบบเวิร์กโฟลว์ที่เหมาะสมกับสถานการณ์นี้ พิจารณาวิธีการทดสอบการใช้งานนี้

แหล่งข้อมูล

  1. เข้าร่วมชุมชน MCP บน Microsoft Foundry Discord Community เพื่อรับข่าวสารการพัฒนาล่าสุด
  2. มีส่วนร่วมใน โครงการ MCP แบบโอเพนซอร์ส
  3. ใช้หลักการ MCP ในโครงการ AI ขององค์กรของคุณ
  4. สำรวจการใช้งาน MCP เฉพาะทางสำหรับอุตสาหกรรมของคุณ
  5. พิจารณาการเรียนหลักสูตรขั้นสูงในหัวข้อ MCP เฉพาะ เช่น การรวมโมดูลหลายรูปแบบ หรือการรวมแอปพลิเคชันองค์กร
  6. ทดลองสร้างเครื่องมือและเวิร์กโฟลว์ MCP ของคุณเองโดยใช้หลักการจาก Hands on Lab

ถัดไป

ต่อไป: กรณีศึกษา


ปฏิเสธความรับผิดชอบ: เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI Co-op Translator ขณะที่เราพยายามให้ความถูกต้อง โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาต้นทางควรถูกพิจารณาเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ แนะนำให้ใช้การแปลโดยมนุษย์มืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดที่เกิดขึ้นจากการใช้การแปลนี้