(Klik gambar di atas untuk menonton video pelajaran ini)
Pelajaran ini memfokuskan pada amalan terbaik lanjutan untuk membangunkan, menguji, dan mengendalikan pelayan dan ciri MCP dalam persekitaran pengeluaran. Apabila ekosistem MCP menjadi semakin kompleks dan penting, mengikuti corak yang telah ditetapkan memastikan kebolehpercayaan, kebolehselenggaraan, dan interoperabiliti. Pelajaran ini mengumpulkan kebijaksanaan praktikal yang diperoleh daripada pelaksanaan MCP dunia sebenar untuk membimbing anda dalam mencipta pelayan yang kukuh, cekap dengan sumber, arahan, dan alat yang berkesan.
Menjelang akhir pelajaran ini, anda akan dapat:
- Menerapkan amalan terbaik industri dalam reka bentuk pelayan dan ciri MCP
- Mewujudkan strategi ujian yang komprehensif untuk pelayan MCP
- Merancang corak aliran kerja yang cekap dan boleh digunakan semula untuk aplikasi MCP yang kompleks
- Melaksanakan pengendalian ralat, pencatatan dan pemerhatian yang betul dalam pelayan MCP
- Mengoptimumkan pelaksanaan MCP untuk prestasi, keselamatan dan kebolehselenggaraan
Sebelum menyelami amalan pelaksanaan khusus, adalah penting untuk memahami prinsip teras yang membimbing pembangunan MCP yang berkesan:
-
Komunikasi Standard: MCP menggunakan JSON-RPC 2.0 sebagai asasnya, menyediakan format yang konsisten untuk permintaan, maklum balas, dan pengendalian ralat dalam semua pelaksanaan.
-
Reka Bentuk Berpusatkan Pengguna: Sentiasa utamakan persetujuan, kawalan, dan ketelusan pengguna dalam pelaksanaan MCP anda.
-
Keselamatan Diutamakan: Laksanakan langkah keselamatan yang kukuh termasuk pengesahan, kebenaran, pengesahan dan had kadar.
-
Senibina Modular: Reka pelayan MCP anda dengan pendekatan modular, di mana setiap alat dan sumber mempunyai tujuan yang jelas dan fokus.
-
Sambungan Berstatus: Manfaatkan kemampuan MCP untuk mengekalkan status merentasi pelbagai permintaan bagi interaksi yang lebih koheren dan sedar konteks.
Amalan terbaik berikut berasal dari dokumentasi Model Context Protocol rasmi:
-
Persetujuan dan Kawalan Pengguna: Sentiasa minta persetujuan jelas pengguna sebelum mengakses data atau menjalankan operasi. Berikan kawalan yang jelas ke atas data yang dikongsi dan tindakan yang dibenarkan.
-
Privasi Data: Dedahkan data pengguna hanya dengan persetujuan jelas dan lindungi ia dengan kawalan akses yang sesuai. Lindungi daripada penghantaran data tanpa kebenaran.
-
Keselamatan Alat: Minta persetujuan jelas pengguna sebelum menggunakan sebarang alat. Pastikan pengguna memahami fungsi setiap alat dan kuatkuasakan sempadan keselamatan yang kukuh.
-
Kawalan Kebenaran Alat: Konfigurasi alat yang dibenarkan digunakan oleh model semasa sesi, memastikan hanya alat yang diberi kuasa jelas boleh diakses.
-
Pengesahan: Perlukan pengesahan yang betul sebelum memberikan akses kepada alat, sumber, atau operasi sensitif menggunakan kunci API, token OAuth, atau kaedah pengesahan selamat lain.
-
Pengesahan Parameter: Kuatkuasakan pengesahan untuk semua penggunaan alat bagi mengelakkan input yang rosak atau berniat jahat mencapai pelaksanaan alat.
-
Had Kadar: Laksanakan had kadar untuk mengelakkan penyalahgunaan dan memastikan penggunaan sumber pelayan yang adil.
-
Perundingan Kebolehan: Semasa penyediaan sambungan, bertukar maklumat mengenai ciri yang disokong, versi protokol, alat yang tersedia, dan sumber.
-
Reka Bentuk Alat: Cipta alat yang fokus melakukan satu perkara dengan baik, bukannya alat monolitik yang mengendalikan pelbagai hal.
-
Pengendalian Ralat: Laksanakan mesej ralat dan kod standard untuk membantu mendiagnosa isu, mengendalikan kegagalan dengan baik, dan memberi maklum balas yang boleh diambil tindakan.
-
Pencatatan: Konfigurasi log berstruktur untuk audit, penyahpepijatan, dan pemantauan interaksi protokol.
-
Penjejakan Kemajuan: Untuk operasi yang berjalan lama, laporkan kemas kini kemajuan untuk membolehkan antara muka pengguna yang responsif.
-
Pembatalan Permintaan: Benarkan klien membatalkan permintaan yang sedang diproses yang tidak lagi diperlukan atau mengambil masa terlalu lama.
Untuk maklumat terkini mengenai amalan terbaik MCP, rujuk:
- Dokumentasi MCP
- Spesifikasi MCP (2025-11-25)
- Repositori GitHub
- Amalan Terbaik Keselamatan
- OWASP MCP Top 10 - Risiko keselamatan dan mitigasi
- Bengkel Sidang Kemuncak Keselamatan MCP (Sherpa) - Latihan keselamatan secara langsung
Setiap alat MCP harus mempunyai tujuan yang jelas dan fokus. Daripada mencipta alat monolitik yang cuba mengendalikan pelbagai hal, bina alat khusus yang cemerlang dalam tugas tertentu.
// 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))
}
};
}
}Laksanakan pengendalian ralat yang kukuh dengan mesej ralat yang bermaklumat dan mekanisme pemulihan yang sesuai.
# Contoh Python dengan pengendalian ralat yang menyeluruh
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:
# Pengesahan parameter
if "query" not in parameters:
raise ToolParameterError("Missing required parameter: query")
query = parameters["query"]
# Pengesahan keselamatan
if self._contains_unsafe_sql(query):
raise ToolSecurityError("Query contains potentially unsafe SQL")
try:
# Operasi pangkalan data dengan tamat masa
async with timeout(10): # Tamat masa 10 saat
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:
# Ralat sambungan mungkin bersifat sembuh sendiri
self._log_error("Database connection error", e)
raise ToolExecutionError(f"Database connection error: {str(e)}")
except DatabaseQueryError as e:
# Ralat pertanyaan mungkin ralat klien
self._log_error("Database query error", e)
raise ToolExecutionError(f"Invalid query: {str(e)}")
except ToolError:
# Benarkan ralat khusus alatan untuk melepasi
raise
except Exception as e:
# Tangkap semua untuk ralat yang tidak dijangka
self._log_error("Unexpected error in DataQueryTool", e)
raise ToolExecutionError(f"An unexpected error occurred: {str(e)}")
def _contains_unsafe_sql(self, query):
# Pelaksanaan pengesanan suntikan SQL
pass
def _log_error(self, message, error):
# Pelaksanaan pencatatan ralat
passSentiasa sahkan parameter dengan teliti untuk mengelakkan input yang rosak atau berniat jahat.
// Contoh JavaScript/TypeScript dengan pengesahan parameter yang terperinci
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. Sahkan kehadiran parameter
if (!parameters.operation) {
throw new ToolError("Missing required parameter: operation");
}
if (!parameters.path) {
throw new ToolError("Missing required parameter: path");
}
// 2. Sahkan jenis parameter
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. Sahkan nilai parameter
const validOperations = ["read", "write", "delete"];
if (!validOperations.includes(parameters.operation)) {
throw new ToolError(`Invalid operation. Must be one of: ${validOperations.join(", ")}`);
}
// 4. Sahkan kehadiran kandungan untuk operasi tulis
if (parameters.operation === "write" && !parameters.content) {
throw new ToolError("Content parameter is required for write operation");
}
// 5. Pengesahan keselamatan laluan
if (!this.isPathWithinAllowedDirectories(parameters.path)) {
throw new ToolError("Access denied: path is outside of allowed directories");
}
// Pelaksanaan berdasarkan parameter yang disahkan
// ...
}
isPathWithinAllowedDirectories(path) {
// Pelaksanaan pemeriksaan keselamatan laluan
// ...
}
}// Contoh Java dengan pengesahan dan kebenaran
public class SecureDataAccessTool implements Tool {
private final AuthenticationService authService;
private final AuthorizationService authzService;
private final DataService dataService;
// Suntikan pergantungan
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. Ekstrak konteks pengesahan
String authToken = request.getContext().getAuthToken();
// 2. Sahkan pengguna
UserIdentity user;
try {
user = authService.validateToken(authToken);
} catch (AuthenticationException e) {
return ToolResponse.error("Authentication failed: " + e.getMessage());
}
// 3. Periksa kebenaran untuk operasi tertentu
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. Teruskan dengan operasi yang dibenarkan
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());
}
}
}// 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
// ...
}
}Sentiasa uji alat anda secara terasing dengan meniru pergantungan luaran:
// Contoh ujian unit alat dalam TypeScript
describe('WeatherForecastTool', () => {
let tool: WeatherForecastTool;
let mockWeatherService: jest.Mocked<IWeatherService>;
beforeEach(() => {
// Cipta perkhidmatan cuaca palsu
mockWeatherService = {
getForecasts: jest.fn()
} as any;
// Cipta alat dengan pergantungan palsu
tool = new WeatherForecastTool(mockWeatherService);
});
it('should return weather forecast for a location', async () => {
// Susun
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);
// Bertindak
const response = await tool.execute({
location: 'Seattle',
days: 3
});
// Sahkan
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 () => {
// Susun
mockWeatherService.getForecasts.mockRejectedValue(new Error('Service unavailable'));
// Bertindak & Sahkan
await expect(tool.execute({
location: 'Seattle',
days: 3
})).rejects.toThrow('Weather service error: Service unavailable');
});
});Uji aliran lengkap dari permintaan klien hingga maklum balas pelayan:
# Contoh ujian integrasi Python
@pytest.mark.asyncio
async def test_mcp_server_integration():
# Mulakan pelayan ujian
server = McpServer()
server.register_tool(WeatherForecastTool(MockWeatherService()))
await server.start(port=5000)
try:
# Cipta klien
client = McpClient("http://localhost:5000")
# Uji penemuan alat
tools = await client.discover_tools()
assert "weatherForecast" in [t.name for t in tools]
# Uji pelaksanaan alat
response = await client.execute_tool("weatherForecast", {
"location": "Seattle",
"days": 3
})
# Sahkan respons
assert response.status_code == 200
assert "Seattle" in response.content[0].text
assert len(json.loads(response.content[0].text)["forecasts"]) == 3
finally:
# Bersihkan
await server.stop()Laksanakan penyimpanan cache yang sesuai untuk mengurangkan kelewatan dan penggunaan sumber:
// 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)
}
};
}
}Reka alat untuk menerima pergantungan mereka melalui suntikan konstruktor, menjadikannya boleh diuji dan dikonfigurasi:
// Contoh Java dengan suntikan pergantungan
public class CurrencyConversionTool implements Tool {
private final ExchangeRateService exchangeService;
private final CacheService cacheService;
private final Logger logger;
// Pergantungan disuntik melalui pembina
public CurrencyConversionTool(
ExchangeRateService exchangeService,
CacheService cacheService,
Logger logger) {
this.exchangeService = exchangeService;
this.cacheService = cacheService;
this.logger = logger;
}
// Pelaksanaan alat
// ...
}Reka alat yang boleh digabungkan bersama untuk mencipta aliran kerja lebih kompleks:
# Contoh Python yang menunjukkan alat boleh susun
class DataFetchTool(Tool):
def get_name(self):
return "dataFetch"
# Pelaksanaan...
class DataAnalysisTool(Tool):
def get_name(self):
return "dataAnalysis"
# Alat ini boleh menggunakan keputusan daripada alat dataFetch
async def execute_async(self, request):
# Pelaksanaan...
pass
class DataVisualizationTool(Tool):
def get_name(self):
return "dataVisualize"
# Alat ini boleh menggunakan keputusan daripada alat dataAnalysis
async def execute_async(self, request):
# Pelaksanaan...
pass
# Alat-alat ini boleh digunakan secara bebas atau sebagai sebahagian daripada aliran kerjaSkema adalah kontrak antara model dan alat anda. Skema yang direka dengan baik membawa kepada kebolehgunaan alat yang lebih baik.
Sentiasa sertakan maklumat huraian untuk setiap parameter:
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" }
};
}Sertakan kekangan pengesahan untuk mengelakkan input yang tidak sah:
Map<String, Object> getSchema() {
Map<String, Object> schema = new HashMap<>();
schema.put("type", "object");
Map<String, Object> properties = new HashMap<>();
// Properti emel dengan pengesahan format
Map<String, Object> email = new HashMap<>();
email.put("type", "string");
email.put("format", "email");
email.put("description", "User email address");
// Properti umur dengan kekangan berangka
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");
// Properti yang disenaraikan
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;
}Kekalkan konsistensi dalam struktur maklum balas anda supaya model lebih mudah mentafsir keputusan:
async def execute_async(self, request):
try:
# Proses permintaan
results = await self._search_database(request.parameters["query"])
# Sentiasa pulangkan struktur yang konsisten
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
}Pengendalian ralat yang kukuh amat penting untuk alat MCP mengekalkan kebolehpercayaan.
Urus ralat pada tahap yang sesuai dan beri mesej yang bermaklumat:
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");
}
}Pulangkan maklumat ralat berstruktur apabila boleh:
@Override
public ToolResponse execute(ToolRequest request) {
try {
// Pelaksanaan
} 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();
}
// Buang semula pengecualian lain sebagai ToolExecutionException
throw new ToolExecutionException("Tool execution failed: " + ex.getMessage(), ex);
}
}Laksanakan logik cuba semula yang sesuai untuk kegagalan sementara:
async def execute_async(self, request):
max_retries = 3
retry_count = 0
base_delay = 1 # saat
while retry_count < max_retries:
try:
# Panggil API luaran
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)}")
# Penangguhan eksponen
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:
# Ralat bukan sementara, jangan cuba lagi
raise ToolExecutionException(f"Operation failed: {str(e)}")Laksanakan penyimpanan cache untuk operasi yang mahal:
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
}
}Gunakan corak pengaturcaraan tak sejajar untuk operasi terikat 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();
// Untuk operasi yang berjalan lama, pulangkan ID pemprosesan dengan segera
String processId = UUID.randomUUID().toString();
// Mulakan pemprosesan async
CompletableFuture.runAsync(() -> {
try {
// Laksanakan operasi yang berjalan lama
documentService.processDocument(documentId);
// Kemas kini status (biasanya akan disimpan dalam pangkalan data)
processStatusRepository.updateStatus(processId, "completed");
} catch (Exception ex) {
processStatusRepository.updateStatus(processId, "failed", ex.getMessage());
}
}, executorService);
// Pulangkan respons segera dengan ID proses
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();
}
// Alat semakan status pendamping
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();
}
}
}Laksanakan pengawalan sumber untuk mengelakkan beban berlebihan:
class ThrottledApiTool(Tool):
def __init__(self):
self.rate_limiter = TokenBucketRateLimiter(
tokens_per_second=5, # Benarkan 5 permintaan setiap saat
bucket_size=10 # Benarkan letusan sehingga 10 permintaan
)
async def execute_async(self, request):
# Semak jika kita boleh meneruskan atau perlu menunggu
delay = self.rate_limiter.get_delay_time()
if delay > 0:
if delay > 2.0: # Jika menunggu terlalu lama
raise ToolExecutionException(
f"Rate limit exceeded. Please try again in {delay:.1f} seconds."
)
else:
# Tunggu masa kelewatan yang sesuai
await asyncio.sleep(delay)
# Gunakan satu token dan teruskan dengan permintaan
self.rate_limiter.consume()
# Panggil 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
# Kira masa sehingga token seterusnya tersedia
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
# Tambah token baru berdasarkan masa yang telah berlalu
new_tokens = elapsed * self.tokens_per_second
self.tokens = min(self.bucket_size, self.tokens + new_tokens)
self.last_refill = nowSentiasa sahkan parameter input dengan teliti:
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
// ...
}Laksanakan semakan kebenaran yang betul:
@Override
public ToolResponse execute(ToolRequest request) {
// Dapatkan konteks pengguna dari permintaan
UserContext user = request.getContext().getUserContext();
// Semak jika pengguna mempunyai kebenaran yang diperlukan
if (!authorizationService.hasPermission(user, "documents:read")) {
throw new ToolExecutionException("User does not have permission to access documents");
}
// Untuk sumber tertentu, semak akses ke sumber itu
String documentId = request.getParameters().get("documentId").asText();
if (!documentService.canUserAccess(user.getId(), documentId)) {
throw new ToolExecutionException("Access denied to the requested document");
}
// Teruskan dengan pelaksanaan alat
// ...
}Urus data sensitif dengan berhati-hati:
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)
# Dapatkan data pengguna
user_data = await self.user_service.get_user_data(user_id)
# Tapis medan sensitif kecuali diminta secara jelas DAN diberi kuasa
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):
# Semak tahap kuasa dalam konteks permintaan
auth_level = request.context.get("authorizationLevel")
return auth_level == "admin"
def _redact_sensitive_fields(self, user_data):
# Buat salinan untuk mengelakkan pengubahsuaian asal
redacted = user_data.copy()
# Sembunyikan medan sensitif tertentu
sensitive_fields = ["ssn", "creditCardNumber", "password"]
for field in sensitive_fields:
if field in redacted:
redacted[field] = "REDACTED"
# Sembunyikan data sensitif bertingkat
if "financialInfo" in redacted:
redacted["financialInfo"] = {"available": True, "accessRestricted": True}
return redactedUjian yang komprehensif memastikan alat MCP berfungsi dengan betul, mengendalikan kes sempadan, dan berintegrasi dengan baik dengan sistem lain.
Cipta ujian fokus untuk fungsi setiap alat:
[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);
}Uji bahawa skema adalah sah dan menguatkuasakan kekangan dengan betul:
@Test
public void testSchemaValidation() {
// Buat contoh alat
SearchTool searchTool = new SearchTool();
// Dapatkan skema
Object schema = searchTool.getSchema();
// Tukar skema kepada JSON untuk pengesahan
String schemaJson = objectMapper.writeValueAsString(schema);
// Sahkan skema adalah JSONSchema yang sah
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
JsonSchema jsonSchema = factory.getJsonSchema(schemaJson);
// Uji parameter yang sah
JsonNode validParams = objectMapper.createObjectNode()
.put("query", "test query")
.put("limit", 5);
ProcessingReport validReport = jsonSchema.validate(validParams);
assertTrue(validReport.isSuccess());
// Uji parameter wajib yang hilang
JsonNode missingRequired = objectMapper.createObjectNode()
.put("limit", 5);
ProcessingReport missingReport = jsonSchema.validate(missingRequired);
assertFalse(missingReport.isSuccess());
// Uji jenis parameter yang tidak sah
JsonNode invalidType = objectMapper.createObjectNode()
.put("query", "test")
.put("limit", "not-a-number");
ProcessingReport invalidReport = jsonSchema.validate(invalidType);
assertFalse(invalidReport.isSuccess());
}Cipta ujian khusus untuk keadaan ralat:
@pytest.mark.asyncio
async def test_api_tool_handles_timeout():
# Susun
tool = ApiTool(timeout=0.1) # Masa tamat yang sangat singkat
# Palsukan permintaan yang akan tamat masa
with aioresponses() as mocked:
mocked.get(
"https://api.example.com/data",
callback=lambda *args, **kwargs: asyncio.sleep(0.5) # Lebih lama daripada masa tamat
)
request = ToolRequest(
tool_name="apiTool",
parameters={"url": "https://api.example.com/data"}
)
# Tindakan & Penegasan
with pytest.raises(ToolExecutionException) as exc_info:
await tool.execute_async(request)
# Sahkan mesej pengecualian
assert "timed out" in str(exc_info.value).lower()
@pytest.mark.asyncio
async def test_api_tool_handles_rate_limiting():
# Susun
tool = ApiTool()
# Palsukan tindak balas had kadar
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"}
)
# Tindakan & Penegasan
with pytest.raises(ToolExecutionException) as exc_info:
await tool.execute_async(request)
# Sahkan pengecualian mengandungi maklumat had kadar
error_msg = str(exc_info.value).lower()
assert "rate limit" in error_msg
assert "try again" in error_msgUji alat bekerja bersama dalam gabungan yang dijangka:
[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());
}Uji pelayan MCP dengan pendaftaran dan pelaksanaan alat penuh:
@SpringBootTest
@AutoConfigureMockMvc
public class McpServerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testToolDiscovery() throws Exception {
// Uji titik akhir penemuan
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 {
// Buat permintaan alat
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);
// Hantar permintaan dan sahkan tindak balas
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 {
// Buat permintaan alat tidak sah
Map<String, Object> request = new HashMap<>();
request.put("toolName", "calculator");
Map<String, Object> parameters = new HashMap<>();
parameters.put("operation", "divide");
parameters.put("a", 10);
// Parameter "b" hilang
request.put("parameters", parameters);
// Hantar permintaan dan sahkan tindak balas ralat
mockMvc.perform(post("/mcp/execute")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").exists());
}
}Uji aliran kerja lengkap dari arahan model ke pelaksanaan alat:
@pytest.mark.asyncio
async def test_model_interaction_with_tool():
# Susun - Sediakan klien MCP dan model tiruan
mcp_client = McpClient(server_url="http://localhost:5000")
# Balasan model tiruan
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=[]
)
])
# Balasan alat cuaca tiruan
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"}
]
}
}
)
# Bertindak
response = await mcp_client.send_prompt(
"What's the weather in Seattle?",
model=mock_model,
allowed_tools=["weatherForecast"]
)
# Tegaskan
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"Uji berapa banyak permintaan serentak yang boleh diurus oleh pelayan MCP anda:
[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));
}Uji sistem di bawah beban ekstrem:
@Test
public void testServerUnderStress() {
int maxUsers = 1000;
int rampUpTimeSeconds = 60;
int testDurationSeconds = 300;
// Sediakan JMeter untuk ujian tekanan
StandardJMeterEngine jmeter = new StandardJMeterEngine();
// Konfigurasikan pelan ujian JMeter
HashTree testPlanTree = new HashTree();
// Cipta pelan ujian, kumpulan benang, sampler, dll.
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);
// Tambah sampler HTTP untuk pelaksanaan alat
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);
// Tambah pendengar
SummaryReport summaryReport = new SummaryReport();
threadGroup.add(summaryReport);
// Jalankan ujian
jmeter.configure(testPlanTree);
jmeter.run();
// Sahkan keputusan
assertEquals(0, summaryReport.getErrorCount());
assertTrue(summaryReport.getAverage() < 200); // Masa tindak balas purata < 200ms
assertTrue(summaryReport.getPercentile(90.0) < 500); // Peratus ke-90 < 500ms
}Sediakan pemantauan untuk analisis prestasi jangka panjang:
# Konfigurasikan pemantauan untuk pelayan MCP
def configure_monitoring(server):
# Sediakan metrik 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"]
)
}
# Tambah middleware untuk penentuan masa dan rakaman metrik
server.add_middleware(PrometheusMiddleware(prometheus_metrics))
# Dedahkan titik akhir metrik
@server.router.get("/metrics")
async def metrics():
return generate_latest()
return serverAliran kerja MCP yang direka dengan baik meningkatkan kecekapan, kebolehpercayaan, dan kebolehselenggaraan. Berikut adalah corak utama yang perlu diikuti:
Sambungkan pelbagai alat dalam satu urutan di mana output setiap alat menjadi input untuk yang berikutnya:
# Pelaksanaan Rantaian Alat Python
class ChainWorkflow:
def __init__(self, tools_chain):
self.tools_chain = tools_chain # Senarai nama alat untuk dilaksanakan secara berurutan
async def execute(self, mcp_client, initial_input):
current_result = initial_input
all_results = {"input": initial_input}
for tool_name in self.tools_chain:
# Laksanakan setiap alat dalam rantaian, menghantar hasil sebelumnya
response = await mcp_client.execute_tool(tool_name, current_result)
# Simpan hasil dan gunakan sebagai input untuk alat seterusnya
all_results[tool_name] = response.result
current_result = response.result
return {
"final_result": current_result,
"all_results": all_results
}
# Contoh penggunaan
data_processing_chain = ChainWorkflow([
"dataFetch",
"dataCleaner",
"dataAnalyzer",
"dataVisualizer"
])
result = await data_processing_chain.execute(
mcp_client,
{"source": "sales_database", "table": "transactions"}
)Gunakan alat pusat yang menghantar kepada alat khusus berdasarkan input:
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 { }
};
}
}Jalankan beberapa alat serentak untuk kecekapan:
public class ParallelDataProcessingWorkflow {
private final McpClient mcpClient;
public ParallelDataProcessingWorkflow(McpClient mcpClient) {
this.mcpClient = mcpClient;
}
public WorkflowResult execute(String datasetId) {
// Langkah 1: Ambil metadata set data (selari)
ToolResponse metadataResponse = mcpClient.executeTool("datasetMetadata",
Map.of("datasetId", datasetId));
// Langkah 2: Lancarkan pelbagai analisis secara selari
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"
))
);
// Tunggu semua tugasan selari selesai
CompletableFuture<Void> allAnalyses = CompletableFuture.allOf(
statisticalAnalysis, correlationAnalysis, outlierDetection
);
allAnalyses.join(); // Tunggu sehingga selesai
// Langkah 3: Gabungkan keputusan
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());
// Langkah 4: Hasilkan laporan ringkasan
ToolResponse summaryResponse = mcpClient.executeTool("reportGenerator",
Map.of("analysisResults", combinedResults));
// Kembalikan hasil aliran kerja lengkap
WorkflowResult result = new WorkflowResult();
result.setDatasetId(datasetId);
result.setAnalysisResults(combinedResults);
result.setSummaryReport(summaryResponse.getResult());
return result;
}
}Laksanakan fallback yang bijaksana untuk kegagalan alat:
class ResilientWorkflow:
def __init__(self, mcp_client):
self.client = mcp_client
async def execute_with_fallback(self, primary_tool, fallback_tool, parameters):
try:
# Cuba alat utama terlebih dahulu
response = await self.client.execute_tool(primary_tool, parameters)
return {
"result": response.result,
"source": "primary",
"tool": primary_tool
}
except ToolExecutionException as e:
# Log kegagalan
logging.warning(f"Primary tool '{primary_tool}' failed: {str(e)}")
# Beralih ke alat sekunder
try:
# Mungkin perlu menukar parameter untuk alat fallback
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:
# Kedua-dua alat gagal
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"""
# Pelaksanaan ini bergantung pada alat tertentu
# Untuk contoh ini, kami hanya akan mengembalikan parameter asal
return params
# Contoh penggunaan
async def get_weather(workflow, location):
return await workflow.execute_with_fallback(
"premiumWeatherService", # API cuaca utama (berbayar)
"basicWeatherService", # API cuaca fallback (percuma)
{"location": location}
)Bina aliran kerja kompleks dengan menggabungkan yang lebih mudah:
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" }
});Ujian adalah aspek kritikal dalam membangunkan pelayan MCP yang boleh dipercayai dan berkualiti tinggi. Panduan ini menyediakan amalan terbaik dan petua menyeluruh untuk menguji pelayan MCP anda sepanjang kitar hayat pembangunan, dari ujian unit hingga ujian integrasi dan pengesahan hujung ke hujung.
Pelayan MCP berfungsi sebagai middleware penting antara model AI dan aplikasi klien. Ujian menyeluruh memastikan:
- Kebolehpercayaan dalam persekitaran pengeluaran
- Pengendalian permintaan dan maklum balas yang tepat
- Pelaksanaan spesifikasi MCP yang betul
- Ketahanan terhadap kegagalan dan kes sempadan
- Prestasi yang konsisten di bawah pelbagai beban
Ujian unit mengesahkan komponen individu pelayan MCP anda secara terasing.
- Pengendali Sumber: Uji logik setiap pengendali sumber secara berasingan
- Pelaksanaan Alat: Sahkan tingkah laku alat dengan pelbagai input
- Templat Arahan: Pastikan templat arahan dipaparkan dengan betul
- Pengesahan Skema: Uji logik pengesahan parameter
- Pengendalian Ralat: Sahkan maklum balas ralat bagi input yang tidak sah
// 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);
}# Contoh ujian unit untuk alat kalkulator dalam Python
def test_calculator_tool_add():
# Susun
calculator = CalculatorTool()
parameters = {
"operation": "add",
"a": 5,
"b": 7
}
# Lakukan
response = calculator.execute(parameters)
result = json.loads(response.content[0].text)
# Sahkan
assert result["value"] == 12Ujian integrasi mengesahkan interaksi antara komponen pelayan MCP anda.
- Inisialisasi Pelayan: Uji permulaan pelayan dengan pelbagai konfigurasi
- Pendaftaran Laluan: Sahkan semua titik hujung didaftarkan dengan betul
- Pemprosesan Permintaan: Uji kitaran lengkap permintaan-maklum balas
- Penyaluran Ralat: Pastikan ralat diurus dengan baik merentasi komponen
- Pengesahan & Kebenaran: Uji mekanisme keselamatan
// 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();
}Ujian dari hujung ke hujung mengesahkan tingkah laku sistem lengkap dari klien ke pelayan.
- Komunikasi Klien-Pelayan: Uji kitaran lengkap permintaan-maklum balas
- SDK Klien Sebenar: Uji dengan pelaksanaan klien sebenar
- Prestasi Di Bawah Beban: Sahkan tingkah laku dengan banyak permintaan serentak
- Pemulihan Ralat: Uji pemulihan sistem daripada kegagalan
- Operasi Berlaku Lama: Sahkan pengendalian aliran dan operasi panjang
// Contoh ujian E2E dengan klien dalam TypeScript
describe('MCP Server E2E Tests', () => {
let client: McpClient;
beforeAll(async () => {
// Mulakan pelayan dalam persekitaran ujian
await startTestServer();
client = new McpClient('http://localhost:5000');
});
afterAll(async () => {
await stopTestServer();
});
test('Client can invoke calculator tool and get correct result', async () => {
// Bertindak
const response = await client.invokeToolAsync('calculator', {
operation: 'divide',
a: 20,
b: 4
});
// Sahkan
expect(response.statusCode).toBe(200);
expect(response.content[0].text).toContain('5');
});
});Peniruan penting untuk mengasingkan komponen semasa ujian.
- Model AI Luaran: Tirukan maklum balas model untuk ujian yang boleh diramal
- Perkhidmatan Luaran: Tirukan pergantungan API (pangkalan data, perkhidmatan pihak ketiga)
- Perkhidmatan Pengesahan: Tirukan penyedia identiti
- Pembekal Sumber: Tirukan pengendali sumber yang mahal
// 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);# Contoh Python dengan unittest.mock
@patch('mcp_server.models.OpenAIModel')
def test_with_mock_model(mock_model):
# Konfigurasikan mock
mock_model.return_value.generate_response.return_value = {
"text": "Mocked model response",
"finish_reason": "completed"
}
# Gunakan mock dalam ujian
server = McpServer(model_client=mock_model)
# Teruskan dengan ujianUjian prestasi penting untuk pelayan MCP pengeluaran.
- Kelewatan: Masa maklum balas untuk permintaan
- Tangkapan: Permintaan yang diurus per saat
- Penggunaan Sumber: CPU, memori, penggunaan rangkaian
- Pengendalian Serentak: Tingkah laku di bawah permintaan selari
- Ciri Skala: Prestasi apabila beban bertambah
- k6: Alat ujian beban sumber terbuka
- JMeter: Ujian prestasi komprehensif
- Locust: Ujian beban berasaskan Python
- Azure Load Testing: Ujian prestasi berasaskan awan
// skrip k6 untuk ujian beban pelayan MCP
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // 10 pengguna maya
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);
}Mengautomatikkan ujian anda memastikan kualiti konsisten dan pusingan maklum balas yang lebih pantas.
- Jalankan Ujian Unit pada Permintaan Tarik (Pull Requests): Pastikan perubahan kod tidak merosakkan fungsi sedia ada
- Ujian Integrasi di Persekitaran Staging: Jalankan ujian integrasi dalam persekitaran pra-produksi
- Asas Prestasi: Kekalkan penanda aras prestasi untuk mengesan regresi
- Imbasan Keselamatan: Automatikkan ujian keselamatan sebagai sebahagian daripada saluran paip
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.csprojSahkan pelayan anda melaksanakan spesifikasi MCP dengan betul.
- Titik Akhir API: Uji titik akhir yang diperlukan (/resources, /tools, dll.)
- Format Permintaan/Respons: Sahkan pematuhan skema
- Kod Ralat: Sahkan kod status yang betul untuk pelbagai senario
- Jenis Kandungan: Uji pengendalian jenis kandungan yang berbeza
- Aliran Pengesahan: Sahkan mekanisme pengesahan yang mematuhi spesifikasi
[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
});
}- Uji Definisi Alat Secara Berasingan: Sahkan definisi skema secara berasingan daripada logik alat
- Gunakan Ujian Parameter: Uji alat dengan pelbagai input, termasuk kes tepi
- Periksa Respons Ralat: Sahkan pengendalian ralat yang betul untuk semua keadaan ralat yang mungkin
- Uji Logik Kebenaran: Pastikan kawalan akses yang betul untuk pelbagai peranan pengguna
- Pantau Liputan Ujian: Sasarkan liputan yang tinggi untuk kod laluan kritikal
- Uji Respons Aliran: Sahkan pengendalian kandungan aliran yang betul
- Simulasikan Isu Rangkaian: Uji tingkah laku di bawah keadaan rangkaian yang lemah
- Uji Had Sumber: Sahkan tingkah laku apabila mencapai kuota atau had kadar
- Automatikkan Ujian Regresi: Bangunkan set yang dijalankan setiap kali ada perubahan kod
- Dokumentasikan Kes Ujian: Kekalkan dokumentasi jelas tentang senario ujian
- Terlalu bergantung pada ujian laluan bahagia: Pastikan menguji kes-kes ralat dengan teliti
- Mengabaikan ujian prestasi: Kenal pasti halangan sebelum ia menjejaskan produksi
- Ujian secara terasing sahaja: Gabungkan ujian unit, integrasi, dan hujung ke hujung (E2E)
- Liputan API yang tidak lengkap: Pastikan semua titik akhir dan ciri diuji
- Persekitaran ujian yang tidak konsisten: Gunakan kontena untuk memastikan persekitaran ujian yang konsisten
Strategi ujian yang menyeluruh adalah penting untuk membangunkan pelayan MCP yang boleh dipercayai dan berkualiti tinggi. Dengan melaksanakan amalan terbaik dan petua yang diterangkan dalam panduan ini, anda boleh memastikan pelaksanaan MCP anda memenuhi piawaian tertinggi dari segi kualiti, kebolehpercayaan, dan prestasi.
- Reka Bentuk Alat: Ikuti prinsip tanggungjawab tunggal, gunakan suntikan kebergantungan, dan reka untuk kesesuaian
- Reka Bentuk Skema: Cipta skema yang jelas dan didokumentasi dengan had pengesahan yang betul
- Pengendalian Ralat: Laksanakan pengendalian ralat yang lancar, respons ralat berstruktur, dan logik cuba semula
- Prestasi: Gunakan caching, pemprosesan tak segerak, dan pengehad sumber
- Keselamatan: Gunakan pengesahan input yang teliti, semakan kebenaran, dan pengendalian data sensitif
- Ujian: Cipta ujian unit, integrasi, dan hujung-ke-hujung yang komprehensif
- Corak Aliran Kerja: Gunakan corak yang mantap seperti rantai, penghantar, dan pemprosesan selari
Reka bentuk alat MCP dan aliran kerja untuk sistem pemprosesan dokumen yang:
- Menerima dokumen dalam pelbagai format (PDF, DOCX, TXT)
- Mengekstrak teks dan maklumat utama daripada dokumen
- Mengklasifikasikan dokumen mengikut jenis dan kandungan
- Menghasilkan ringkasan bagi setiap dokumen
Laksanakan skema alat, pengendalian ralat, dan corak aliran kerja yang paling sesuai untuk senario ini. Pertimbangkan bagaimana anda akan menguji pelaksanaan ini.
- Sertai komuniti MCP di Azure AI Foundry Discord Community untuk sentiasa mendapat maklumat terkini
- Menyumbang kepada projek open-source MCP
- Terapkan prinsip MCP dalam inisiatif AI organisasi anda sendiri
- Terokai pelaksanaan MCP khusus untuk industri anda
- Pertimbangkan mengambil kursus lanjutan dalam topik MCP tertentu, seperti integrasi multi-modal atau integrasi aplikasi perusahaan
- Cuba bina alat dan aliran kerja MCP anda sendiri menggunakan prinsip yang dipelajari melalui Hands on Lab
Seterusnya: Kajian Kes
Penafian:
Dokumen ini telah diterjemahkan menggunakan perkhidmatan terjemahan AI Co-op Translator. Walaupun kami berusaha untuk ketepatan, sila ambil maklum bahawa terjemahan automatik mungkin mengandungi kesilapan atau ketidaktepatan. Dokumen asal dalam bahasa asalnya harus dianggap sebagai sumber yang sah. Untuk maklumat penting, terjemahan profesional oleh manusia adalah disyorkan. Kami tidak bertanggungjawab atas sebarang salah faham atau salah tafsir yang timbul daripada penggunaan terjemahan ini.
