- Renamed all directories: OpenHarbor.MCP.* → Svrnty.MCP.* - Updated all namespaces in 179 C# files - Renamed 20 .csproj files and 3 .sln files - Updated 193 documentation references - Updated 33 references in main CODEX codebase - Updated Codex.sln with new paths - Build verified: 0 errors Preparing for extraction to standalone repositories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
691 lines
17 KiB
Markdown
691 lines
17 KiB
Markdown
# HTTPS/TLS Setup Guide - Svrnty.MCP.Client
|
|
|
|
**Purpose**: Secure HTTPS/TLS configuration for MCP client connections
|
|
**Audience**: Application developers, system integrators
|
|
**Last Updated**: 2025-10-19
|
|
**Version**: 1.0.0
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Prerequisites](#prerequisites)
|
|
3. [Client Configuration](#client-configuration)
|
|
4. [Certificate Validation](#certificate-validation)
|
|
5. [Authentication](#authentication)
|
|
6. [Error Handling](#error-handling)
|
|
7. [Best Practices](#best-practices)
|
|
8. [Troubleshooting](#troubleshooting)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Svrnty.MCP.Client supports HTTPS connections to MCP servers with full TLS certificate validation, custom certificate handling, and API key authentication.
|
|
|
|
**Security Features:**
|
|
- Encrypted communication (TLS 1.2+)
|
|
- Server certificate validation
|
|
- Custom certificate authority support
|
|
- Client certificate authentication (mutual TLS)
|
|
- API key authentication via headers
|
|
|
|
**Default Behavior:**
|
|
- HTTPS enabled by default when URL starts with `https://`
|
|
- Certificate validation enabled (can be customized)
|
|
- Connection pooling for performance
|
|
- Timeout configuration (default: 30 seconds)
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Development
|
|
- .NET 8.0 SDK
|
|
- Access to MCP server with HTTPS enabled
|
|
- API key for authentication (if required)
|
|
|
|
### Production
|
|
- Valid server certificates
|
|
- Network connectivity to server
|
|
- Firewall allows outbound HTTPS (port 443)
|
|
|
|
---
|
|
|
|
## Client Configuration
|
|
|
|
### Basic HTTPS Connection
|
|
|
|
**Example 1: Simple HTTPS Client**
|
|
|
|
```csharp
|
|
using Svrnty.MCP.Client;
|
|
|
|
// Connect to HTTPS server
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com", // HTTPS URL
|
|
ApiKey = "your-api-key-here" // Authentication
|
|
};
|
|
|
|
using var connection = new HttpServerConnection(config);
|
|
|
|
// Use connection
|
|
var tools = await connection.ListToolsAsync();
|
|
```
|
|
|
|
**Configuration Options:**
|
|
|
|
```csharp
|
|
public class HttpServerConnectionConfig
|
|
{
|
|
/// <summary>
|
|
/// Server URL (must start with https:// for secure connection)
|
|
/// </summary>
|
|
public string ServerUrl { get; set; } = "https://localhost:5051";
|
|
|
|
/// <summary>
|
|
/// API key for authentication (sent via X-API-Key header)
|
|
/// </summary>
|
|
public string? ApiKey { get; set; }
|
|
|
|
/// <summary>
|
|
/// Request timeout (default: 30 seconds)
|
|
/// </summary>
|
|
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
|
|
|
|
/// <summary>
|
|
/// Maximum connections per server (default: 10)
|
|
/// </summary>
|
|
public int MaxConnectionsPerServer { get; set; } = 10;
|
|
|
|
/// <summary>
|
|
/// Custom certificate validation callback
|
|
/// </summary>
|
|
public Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool>? ServerCertificateCustomValidation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Client certificate for mutual TLS (optional)
|
|
/// </summary>
|
|
public X509Certificate2? ClientCertificate { get; set; }
|
|
}
|
|
```
|
|
|
|
### Example 2: Development with Self-Signed Certificate
|
|
|
|
For development servers using self-signed certificates:
|
|
|
|
```csharp
|
|
using System.Net.Security;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://localhost:5051",
|
|
ApiKey = "dev-api-key",
|
|
|
|
// WARNING: Only use in development!
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
// Accept any certificate (INSECURE - development only!)
|
|
return true;
|
|
}
|
|
};
|
|
|
|
using var connection = new HttpServerConnection(config);
|
|
```
|
|
|
|
**Security Warning**: Never use `ServerCertificateCustomValidation = (_, _, _, _) => true` in production!
|
|
|
|
### Example 3: Custom Certificate Authority
|
|
|
|
For servers using internal/corporate CA:
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://internal-mcp.corp.local",
|
|
ApiKey = "api-key",
|
|
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (errors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
// Custom CA validation
|
|
if (errors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
|
|
{
|
|
// Load corporate CA certificate
|
|
var corpCaCert = new X509Certificate2("corp-ca.crt");
|
|
|
|
// Build chain with custom CA
|
|
var customChain = new X509Chain();
|
|
customChain.ChainPolicy.ExtraStore.Add(corpCaCert);
|
|
customChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
|
|
|
|
bool isValid = customChain.Build(cert);
|
|
customChain.Dispose();
|
|
|
|
return isValid;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Example 4: Production Configuration
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com",
|
|
ApiKey = Environment.GetEnvironmentVariable("MCP_API_KEY"),
|
|
Timeout = TimeSpan.FromSeconds(60),
|
|
MaxConnectionsPerServer = 20,
|
|
|
|
// Default certificate validation (recommended for production)
|
|
ServerCertificateCustomValidation = null // Use system trust store
|
|
};
|
|
|
|
using var connection = new HttpServerConnection(config);
|
|
```
|
|
|
|
---
|
|
|
|
## Certificate Validation
|
|
|
|
### Default Validation
|
|
|
|
By default, the client uses .NET's built-in certificate validation:
|
|
|
|
1. **Certificate is trusted** (issued by known CA)
|
|
2. **Certificate is valid** (not expired, not yet valid)
|
|
3. **Certificate matches hostname** (CN or SAN matches server URL)
|
|
|
|
### Custom Validation Scenarios
|
|
|
|
**Scenario 1: Accept Specific Self-Signed Certificate**
|
|
|
|
```csharp
|
|
var expectedThumbprint = "A1B2C3D4E5F6..."; // Certificate thumbprint
|
|
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (cert == null)
|
|
return false;
|
|
|
|
// Verify thumbprint matches expected
|
|
return cert.Thumbprint.Equals(expectedThumbprint, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
```
|
|
|
|
**Scenario 2: Log and Validate**
|
|
|
|
```csharp
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (errors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
// Log validation errors
|
|
Console.WriteLine($"Certificate validation error: {errors}");
|
|
|
|
if (cert != null)
|
|
{
|
|
Console.WriteLine($"Subject: {cert.Subject}");
|
|
Console.WriteLine($"Issuer: {cert.Issuer}");
|
|
Console.WriteLine($"Valid from: {cert.NotBefore} to {cert.NotAfter}");
|
|
}
|
|
|
|
// Reject in production, accept in development
|
|
return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
|
|
};
|
|
```
|
|
|
|
**Scenario 3: Certificate Pinning**
|
|
|
|
For high-security requirements, pin specific certificates:
|
|
|
|
```csharp
|
|
private static readonly HashSet<string> PinnedCertificates = new()
|
|
{
|
|
"A1B2C3D4E5F6...", // Primary server cert
|
|
"B2C3D4E5F6A1..." // Backup server cert
|
|
};
|
|
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (cert == null)
|
|
return false;
|
|
|
|
// Only accept pinned certificates
|
|
return PinnedCertificates.Contains(cert.Thumbprint);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### API Key Authentication
|
|
|
|
**Method 1: Configuration Object**
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com",
|
|
ApiKey = "your-api-key" // Sent as X-API-Key header
|
|
};
|
|
```
|
|
|
|
**Method 2: Environment Variable**
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com",
|
|
ApiKey = Environment.GetEnvironmentVariable("MCP_API_KEY")
|
|
};
|
|
```
|
|
|
|
**Method 3: Azure Key Vault / Secrets Manager**
|
|
|
|
```csharp
|
|
// Example with Azure Key Vault
|
|
var keyVaultClient = new SecretClient(new Uri("https://your-vault.vault.azure.net"), new DefaultAzureCredential());
|
|
var apiKeySecret = await keyVaultClient.GetSecretAsync("mcp-api-key");
|
|
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com",
|
|
ApiKey = apiKeySecret.Value.Value
|
|
};
|
|
```
|
|
|
|
### Mutual TLS (Client Certificates)
|
|
|
|
For bidirectional authentication:
|
|
|
|
```csharp
|
|
// Load client certificate
|
|
var clientCert = new X509Certificate2("client-cert.pfx", "password");
|
|
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://mcp.example.com",
|
|
ClientCertificate = clientCert, // Present to server during TLS handshake
|
|
ApiKey = "api-key" // Additional API key authentication
|
|
};
|
|
|
|
using var connection = new HttpServerConnection(config);
|
|
```
|
|
|
|
**Certificate Requirements:**
|
|
- Must include private key (PFX/PKCS#12 format)
|
|
- Must be valid (not expired)
|
|
- Server must be configured to accept client certificates
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Common HTTPS Errors
|
|
|
|
**Error 1: Certificate Validation Failed**
|
|
|
|
```
|
|
System.Net.Http.HttpRequestException: The SSL connection could not be established
|
|
Inner Exception: The remote certificate is invalid according to the validation procedure.
|
|
```
|
|
|
|
**Solution:**
|
|
1. Verify server certificate is valid
|
|
2. Check certificate is issued by trusted CA
|
|
3. Ensure hostname matches certificate CN/SAN
|
|
4. For development, use custom validation (see examples above)
|
|
|
|
**Error 2: Connection Timed Out**
|
|
|
|
```
|
|
System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout
|
|
```
|
|
|
|
**Solution:**
|
|
1. Increase timeout value
|
|
2. Check network connectivity
|
|
3. Verify firewall allows HTTPS traffic
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(120) // Increase timeout
|
|
};
|
|
```
|
|
|
|
**Error 3: Hostname Mismatch**
|
|
|
|
```
|
|
The remote certificate is invalid: the name on the certificate does not match the hostname
|
|
```
|
|
|
|
**Solution:**
|
|
1. Use correct hostname in ServerUrl (match certificate CN/SAN)
|
|
2. Update certificate to include correct hostname
|
|
3. Use IP address if certificate has IP SAN
|
|
|
|
**Error 4: Expired Certificate**
|
|
|
|
```
|
|
The remote certificate is invalid: the certificate has expired
|
|
```
|
|
|
|
**Solution:**
|
|
1. Renew server certificate
|
|
2. For temporary workaround (development only):
|
|
|
|
```csharp
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
// Check if only error is expiration
|
|
if (errors == SslPolicyErrors.RemoteCertificateChainErrors)
|
|
{
|
|
return chain.ChainStatus.All(s => s.Status == X509ChainStatusFlags.NotTimeValid);
|
|
}
|
|
return errors == SslPolicyErrors.None;
|
|
};
|
|
```
|
|
|
|
### Graceful Degradation
|
|
|
|
Handle HTTPS errors gracefully:
|
|
|
|
```csharp
|
|
try
|
|
{
|
|
using var connection = new HttpServerConnection(config);
|
|
var tools = await connection.ListToolsAsync();
|
|
}
|
|
catch (HttpRequestException ex) when (ex.InnerException is System.Security.Authentication.AuthenticationException)
|
|
{
|
|
// TLS/certificate error
|
|
Console.WriteLine($"HTTPS connection failed: {ex.Message}");
|
|
Console.WriteLine("Check server certificate configuration");
|
|
// Optionally: retry with HTTP (if server supports it)
|
|
}
|
|
catch (TaskCanceledException ex)
|
|
{
|
|
// Timeout
|
|
Console.WriteLine($"Connection timed out after {config.Timeout.TotalSeconds}s");
|
|
// Optionally: retry with longer timeout
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Never Disable Certificate Validation in Production
|
|
|
|
```csharp
|
|
// ❌ WRONG (insecure)
|
|
ServerCertificateCustomValidation = (_, _, _, _) => true;
|
|
|
|
// ✅ CORRECT (secure)
|
|
ServerCertificateCustomValidation = null; // Use default validation
|
|
```
|
|
|
|
### 2. Store API Keys Securely
|
|
|
|
```csharp
|
|
// ❌ WRONG (hardcoded)
|
|
ApiKey = "sk_live_1234567890abcdef";
|
|
|
|
// ✅ CORRECT (environment variable)
|
|
ApiKey = Environment.GetEnvironmentVariable("MCP_API_KEY");
|
|
|
|
// ✅ BETTER (secrets manager)
|
|
ApiKey = await secretsManager.GetSecretAsync("mcp-api-key");
|
|
```
|
|
|
|
### 3. Use Connection Pooling
|
|
|
|
```csharp
|
|
// ❌ WRONG (creates new connection every time)
|
|
foreach (var request in requests)
|
|
{
|
|
using var connection = new HttpServerConnection(config);
|
|
await connection.ExecuteToolAsync("tool", args);
|
|
}
|
|
|
|
// ✅ CORRECT (reuse connection)
|
|
using var connection = new HttpServerConnection(config);
|
|
foreach (var request in requests)
|
|
{
|
|
await connection.ExecuteToolAsync("tool", args);
|
|
}
|
|
```
|
|
|
|
### 4. Handle Errors Appropriately
|
|
|
|
```csharp
|
|
try
|
|
{
|
|
var result = await connection.ExecuteToolAsync("tool", args);
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
// Log error, retry, or fail gracefully
|
|
_logger.LogError(ex, "MCP tool execution failed");
|
|
throw;
|
|
}
|
|
```
|
|
|
|
### 5. Configure Timeouts
|
|
|
|
```csharp
|
|
// Short-lived operations
|
|
Timeout = TimeSpan.FromSeconds(10);
|
|
|
|
// Long-running operations
|
|
Timeout = TimeSpan.FromMinutes(5);
|
|
|
|
// Default (reasonable for most cases)
|
|
Timeout = TimeSpan.FromSeconds(30);
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: "The remote certificate is invalid"
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# Check server certificate
|
|
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -text
|
|
```
|
|
|
|
**Common Causes:**
|
|
1. Self-signed certificate without custom validation
|
|
2. Expired certificate
|
|
3. Hostname mismatch
|
|
4. Untrusted CA
|
|
|
|
**Solution:**
|
|
- Ensure server has valid certificate from trusted CA
|
|
- Or implement custom validation for development
|
|
|
|
### Issue: Connection Hangs/Timeout
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# Test connectivity
|
|
telnet mcp.example.com 443
|
|
|
|
# Test with curl
|
|
curl -v https://mcp.example.com/health
|
|
```
|
|
|
|
**Common Causes:**
|
|
1. Firewall blocking port 443
|
|
2. Server not responding
|
|
3. Network connectivity issue
|
|
|
|
**Solution:**
|
|
- Verify firewall rules
|
|
- Check server is running
|
|
- Increase timeout if server is slow
|
|
|
|
### Issue: Client Certificate Not Accepted
|
|
|
|
**Diagnosis:**
|
|
Check server logs for certificate validation errors.
|
|
|
|
**Common Causes:**
|
|
1. Certificate expired
|
|
2. Certificate not in PFX format
|
|
3. Private key missing
|
|
4. Server not configured for mutual TLS
|
|
|
|
**Solution:**
|
|
```csharp
|
|
// Verify certificate has private key
|
|
var clientCert = new X509Certificate2("client-cert.pfx", "password");
|
|
Console.WriteLine($"Has private key: {clientCert.HasPrivateKey}");
|
|
|
|
// Check expiration
|
|
Console.WriteLine($"Valid from: {clientCert.NotBefore}");
|
|
Console.WriteLine($"Valid to: {clientCert.NotAfter}");
|
|
```
|
|
|
|
### Issue: Slow HTTPS Connections
|
|
|
|
**Diagnosis:**
|
|
Measure connection time:
|
|
|
|
```csharp
|
|
var stopwatch = Stopwatch.StartNew();
|
|
using var connection = new HttpServerConnection(config);
|
|
stopwatch.Stop();
|
|
Console.WriteLine($"Connection established in {stopwatch.ElapsedMilliseconds}ms");
|
|
```
|
|
|
|
**Common Causes:**
|
|
1. DNS resolution slow
|
|
2. TLS handshake slow
|
|
3. Server overloaded
|
|
|
|
**Solution:**
|
|
- Use connection pooling (reuse connections)
|
|
- Increase MaxConnectionsPerServer
|
|
- Cache DNS (use IP address directly)
|
|
|
|
---
|
|
|
|
## Configuration Examples
|
|
|
|
### Development Environment
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://localhost:5051",
|
|
ApiKey = "dev-api-key",
|
|
Timeout = TimeSpan.FromSeconds(30),
|
|
|
|
// Accept self-signed cert (development only)
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (errors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
// Log warning in development
|
|
Console.WriteLine($"[DEV] Accepting certificate with errors: {errors}");
|
|
return true;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Staging Environment
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = "https://staging-mcp.example.com",
|
|
ApiKey = Environment.GetEnvironmentVariable("MCP_API_KEY_STAGING"),
|
|
Timeout = TimeSpan.FromSeconds(60),
|
|
MaxConnectionsPerServer = 10,
|
|
|
|
// Validate cert but allow specific staging cert
|
|
ServerCertificateCustomValidation = (request, cert, chain, errors) =>
|
|
{
|
|
if (errors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
// Accept specific staging certificate
|
|
var stagingThumbprint = Environment.GetEnvironmentVariable("STAGING_CERT_THUMBPRINT");
|
|
return cert?.Thumbprint == stagingThumbprint;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Production Environment
|
|
|
|
```csharp
|
|
var config = new HttpServerConnectionConfig
|
|
{
|
|
ServerUrl = Environment.GetEnvironmentVariable("MCP_SERVER_URL"),
|
|
ApiKey = await GetApiKeyFromSecretManagerAsync(),
|
|
Timeout = TimeSpan.FromSeconds(120),
|
|
MaxConnectionsPerServer = 50,
|
|
|
|
// Use default certificate validation (strict)
|
|
ServerCertificateCustomValidation = null
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Security Checklist
|
|
|
|
Before deploying to production:
|
|
|
|
- [ ] Server URL uses HTTPS (`https://...`)
|
|
- [ ] Certificate validation is enabled (no custom bypass)
|
|
- [ ] API key is stored securely (environment variable or secrets manager)
|
|
- [ ] Timeout is configured appropriately for use case
|
|
- [ ] Error handling is implemented
|
|
- [ ] Connection is disposed properly (using statement)
|
|
- [ ] Client certificate (if used) is stored securely
|
|
- [ ] Certificate expiry monitoring is configured
|
|
- [ ] Firewall allows outbound HTTPS (port 443)
|
|
- [ ] TLS 1.2+ is enforced (no SSL3, TLS 1.0, TLS 1.1)
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
**Svrnty.MCP Documentation:**
|
|
- [Client README](../../README.md)
|
|
- [API Reference](../../docs/api/client-api.md)
|
|
- [Integration Guide](../../docs/integration-guide.md)
|
|
|
|
**.NET Documentation:**
|
|
- [HttpClient Security](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient)
|
|
- [X509 Certificates](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2)
|
|
- [SSL/TLS Best Practices](https://learn.microsoft.com/en-us/dotnet/framework/network-programming/tls)
|
|
|
|
**Security Resources:**
|
|
- [OWASP TLS Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)
|
|
- [Certificate Pinning Guide](https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning)
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0.0
|
|
**Last Updated**: 2025-10-19
|
|
**Maintained By**: Svrnty Development Team
|
|
**Related**: [Server HTTPS Setup](../../Svrnty.MCP.Server/docs/deployment/https-setup.md)
|