using ModelContextProtocol.Interceptors; using ModelContextProtocol.Interceptors.Client; using ModelContextProtocol.Protocol; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; /// /// Sample validation interceptors for MCP client operations. /// These interceptors demonstrate PII detection and request validation. /// [McpClientInterceptorType] public partial class ClientValidationInterceptors { // Common PII patterns private static readonly Regex SsnPattern = SsnRegex(); private static readonly Regex EmailPattern = EmailRegex(); private static readonly Regex CreditCardPattern = CreditCardRegex(); /// /// Validates outgoing tool call arguments for PII (Personally Identifiable Information). /// Blocks requests containing SSN, email addresses, or credit card numbers. /// [McpClientInterceptor( Name = "pii-validator", Description = "Validates tool call arguments for PII leakage", Events = [InterceptorEvents.ToolsCall], Phase = InterceptorPhase.Request, PriorityHint = -1000)] // Security interceptors run early public ValidationInterceptorResult ValidatePii(JsonNode? payload) { if (payload is null) { return ValidationInterceptorResult.Success(); } var messages = new List(); // Parse as tool call request to inspect arguments CallToolRequestParams? toolCall; try { toolCall = payload.Deserialize(); } catch (JsonException) { return ValidationInterceptorResult.Success(); // Can't parse, let other validators handle } if (toolCall?.Arguments is null) { return ValidationInterceptorResult.Success(); } // Check each argument for PII foreach (var arg in toolCall.Arguments) { var value = arg.Value.ToString() ?? string.Empty; if (SsnPattern.IsMatch(value)) { messages.Add(new ValidationMessage { Path = $"arguments.{arg.Key}", Message = "Social Security Number detected - PII not allowed in tool arguments", Severity = ValidationSeverity.Error }); } if (CreditCardPattern.IsMatch(value)) { messages.Add(new ValidationMessage { Path = $"arguments.{arg.Key}", Message = "Credit card number detected - PII not allowed in tool arguments", Severity = ValidationSeverity.Error }); } // Email is a warning, not an error if (EmailPattern.IsMatch(value)) { messages.Add(new ValidationMessage { Path = $"arguments.{arg.Key}", Message = "Email address detected - consider if PII is necessary", Severity = ValidationSeverity.Warn }); } } if (messages.Count > 0) { var maxSeverity = messages.Max(m => m.Severity); return new ValidationInterceptorResult { Valid = maxSeverity != ValidationSeverity.Error, Severity = maxSeverity, Messages = messages }; } return ValidationInterceptorResult.Success(); } /// /// Validates that tool responses don't contain error indicators that should be handled. /// [McpClientInterceptor( Name = "response-validator", Description = "Validates tool call responses for errors", Events = [InterceptorEvents.ToolsCall], Phase = InterceptorPhase.Response)] public ValidationInterceptorResult ValidateResponse(JsonNode? payload) { if (payload is null) { return ValidationInterceptorResult.Success(); } // Parse as tool call result CallToolResult? result; try { result = payload.Deserialize(); } catch (JsonException) { return ValidationInterceptorResult.Success(); } if (result?.IsError == true) { // Extract error message from content if available var errorMessage = result.Content?.FirstOrDefault() switch { TextContentBlock text => text.Text, _ => "Tool execution failed" }; return new ValidationInterceptorResult { Valid = false, Severity = ValidationSeverity.Warn, // Warning so it doesn't block, but is logged Messages = [new() { Message = $"Tool returned error: {errorMessage}", Severity = ValidationSeverity.Warn }] }; } return ValidationInterceptorResult.Success(); } #if NET [GeneratedRegex(@"\b\d{3}-\d{2}-\d{4}\b")] private static partial Regex SsnRegex(); [GeneratedRegex(@"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")] private static partial Regex EmailRegex(); [GeneratedRegex(@"\b(?:\d{4}[-\s]?){3}\d{4}\b")] private static partial Regex CreditCardRegex(); #else private static Regex SsnRegex() => new(@"\b\d{3}-\d{2}-\d{4}\b", RegexOptions.Compiled); private static Regex EmailRegex() => new(@"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b", RegexOptions.Compiled); private static Regex CreditCardRegex() => new(@"\b(?:\d{4}[-\s]?){3}\d{4}\b", RegexOptions.Compiled); #endif }