Production-grade patterns for MCP server design, testing, error handling, caching, security, and performance optimization — with code examples in C#, Python, TypeScript, and Java.
Use this file to discover all available pages before exploring further.
As MCP ecosystems grow in complexity, following established patterns ensures reliability, maintainability, and interoperability. This module consolidates practical wisdom from real-world MCP implementations to guide you in creating robust, efficient servers with effective resources, prompts, and tools.
Each MCP tool should have a clear, focused purpose. Avoid monolithic tools that attempt to handle multiple concerns.
// A focused tool that does one thing wellpublic 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() => new ToolDefinition { Name = Name, Description = Description, Parameters = new Dictionary<string, ParameterDefinition> { ["location"] = new() { Type = ParameterType.String, Description = "City or location name" }, ["days"] = new() { 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)) } }; }}
Design tools that can work independently or together in workflows:
class DataFetchTool(Tool): def get_name(self): return "dataFetch" # Fetches raw data from a sourceclass DataAnalysisTool(Tool): def get_name(self): return "dataAnalysis" # Accepts output from dataFetch and runs analysisclass DataVisualizationTool(Tool): def get_name(self): return "dataVisualize" # Accepts output from dataAnalysis and renders a chart
public object GetSchema() => 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 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 results to return (1–50)", default = 10 } }, required = new[] { "query" }};
public async Task<ToolResponse> ExecuteAsync(IDictionary<string, object> parameters){ var location = parameters["location"].ToString(); var days = Convert.ToInt32(parameters.GetValueOrDefault("days", 3)); string cacheKey = $"weather:{location}:{days}"; // Try cache first string cached = await _cache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cached)) { return new ToolResponse { Content = new List<ContentItem> { new TextContent(cached) } }; } // Cache miss — call the service var forecast = await _weatherService.GetForecastAsync(location, days); string json = JsonSerializer.Serialize(forecast); await _cache.SetStringAsync(cacheKey, json, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); return new ToolResponse { Content = new List<ContentItem> { new TextContent(json) } };}
public async Task<ToolResponse> ExecuteAsync(ToolRequest request){ if (!request.Parameters.TryGetProperty("query", out var queryProp)) throw new ToolExecutionException("Missing required parameter: query"); if (queryProp.ValueKind != JsonValueKind.String) throw new ToolExecutionException("Query parameter must be a string"); var query = queryProp.GetString(); 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"); if (ContainsSqlInjection(query)) throw new ToolExecutionException( "Invalid query: contains potentially unsafe SQL"); // Proceed with validated input}
class SecureDataTool(Tool): async def execute_async(self, request): user_data = await self.user_service.get_user_data( request.parameters["userId"] ) include_sensitive = request.parameters.get("includeSensitiveData", False) 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 _redact_sensitive_fields(self, data): redacted = data.copy() for field in ["ssn", "creditCardNumber", "password"]: if field in redacted: redacted[field] = "REDACTED" return redacted
Require explicit user consent before accessing data or performing operations. Provide clear controls over what data is shared and which actions are authorized.
Tool safety
Require explicit user consent before invoking any tool. Ensure users understand each tool’s functionality. Enforce robust security boundaries between tools.
Data privacy
Only expose user data with explicit consent. Protect data with appropriate access controls. Safeguard against unauthorized data transmission.
Capability negotiation
During connection setup, exchange information about supported features, protocol versions, available tools, and resources. Only advertise what the connecting client is authorized to use.
Progress tracking and cancellation
For long-running operations, report progress updates to enable responsive UIs. Allow clients to cancel in-flight requests that are no longer needed.