.NET client for connecting to Model Context Protocol (MCP) servers
Go to file
2025-10-22 21:07:44 -04:00
docs refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00
samples/CodexMcpClient refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00
src feat: add NuGet packaging metadata for v1.0.0 2025-10-22 21:07:44 -04:00
tests/Svrnty.MCP.Client.Core.Tests refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00
.gitignore docs: comprehensive AI coding assistant research and MCP-first implementation plan 2025-10-22 21:00:34 -04:00
AGENT-PRIMER.md refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00
LICENSE docs: comprehensive AI coding assistant research and MCP-first implementation plan 2025-10-22 21:00:34 -04:00
README.md refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00
Svrnty.MCP.Client.sln refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries 2025-10-22 21:04:17 -04:00

Svrnty.MCP.Client

A modular, scalable, secure .NET library for consuming Model Context Protocol (MCP) servers

License: MIT .NET 8.0 Architecture: Clean


What is Svrnty.MCP.Client?

Svrnty.MCP.Client is a standalone, reusable .NET library that enables any .NET application to act as an MCP client, allowing your application to discover and call tools exposed by MCP servers.

Model Context Protocol (MCP) is an industry-standard protocol backed by Anthropic that defines how AI agents communicate with external tools and data sources. Think of it as a universal adapter that lets your application safely access capabilities from remote MCP servers.

Key Features

  • Modular & Reusable: Copy to any .NET project, configure, and go
  • Clean Architecture: Core abstractions, infrastructure implementation, ASP.NET Core integration
  • Security-First: Connection validation, timeout handling, error recovery
  • Transport Flexibility: HTTP (primary for production) and stdio (legacy for local tools)
  • AI-Automated Setup: AGENT-PRIMER.md guides AI assistants to configure your integration automatically
  • TDD Foundation: Built with test-driven development, comprehensive test coverage
  • Production-Ready: Observability, error handling, connection pooling, retry logic

Why Svrnty.MCP.Client?

Problem: Your .NET application needs to access tools and capabilities exposed by remote MCP servers (search, data processing, API access) but has no standardized way to connect.

Solution: Svrnty.MCP.Client transforms your application into an MCP client, allowing you to discover, validate, and call tools from any MCP server with proper error handling and connection management.

Use Cases:

  • Connect your app to Claude Desktop's exposed tools
  • Call tools from remote knowledge bases or search engines
  • Integrate with third-party MCP servers for document processing
  • Build AI-powered workflows that consume multiple MCP services
  • Access enterprise MCP servers for data analysis and reporting

Quick Start

Prerequisites

  • .NET 8.0 SDK or higher
  • Your existing .NET application (Web API, Console, Worker Service, etc.)
  • Access to one or more MCP servers (local or remote)

If you have access to Claude or another AI assistant:

  1. Copy this entire folder to your project directory
  2. Open your AI assistant and say: "Read AGENT-PRIMER.md and set up Svrnty.MCP.Client for my project"
  3. The AI will analyze your system, generate configuration, and create sample client code automatically

Option 2: Manual Setup

Step 1: Add Package Reference

# Via project reference (development)
dotnet add reference /path/to/Svrnty.MCP.Client/src/Svrnty.MCP.Client.AspNetCore/Svrnty.MCP.Client.AspNetCore.csproj

# OR via NuGet (when published)
# dotnet add package Svrnty.MCP.Client.AspNetCore

Step 2: Configure appsettings.json

Add MCP client configuration:

{
  "Mcp": {
    "Client": {
      "Name": "MyAppMcpClient",
      "Version": "1.0.0",
      "Description": "MCP client for MyApp"
    },
    "Servers": [
      {
        "Name": "codex-server",
        "Transport": {
          "Type": "Http",
          "BaseUrl": "http://localhost:5050"
        },
        "Timeout": "00:00:30",
        "Enabled": true
      },
      {
        "Name": "remote-mcp-server",
        "Transport": {
          "Type": "Http",
          "BaseUrl": "https://api.example.com/mcp"
        },
        "Timeout": "00:00:60",
        "Enabled": true
      }
    ],
    "Connection": {
      "MaxRetries": 3,
      "RetryDelayMs": 1000,
      "EnableConnectionPooling": true
    }
  }
}

Step 3: Update Program.cs

using Svrnty.MCP.Client.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add MCP client
builder.Services.AddMcpClient(builder.Configuration.GetSection("Mcp"));

var app = builder.Build();

app.Run();

Step 4: Use the Client to Call Tools

using Svrnty.MCP.Client.Core.Abstractions;

public class MyService
{
    private readonly IMcpClient _mcpClient;

    public MyService(IMcpClient mcpClient)
    {
        _mcpClient = mcpClient;
    }

    public async Task<string> SearchCodexAsync(string query)
    {
        // List available tools from the codex-server
        var tools = await _mcpClient.ListToolsAsync("codex-server");

        // Call the search_codex tool
        var result = await _mcpClient.CallToolAsync(
            serverName: "codex-server",
            toolName: "search_codex",
            arguments: new Dictionary<string, object>
            {
                ["query"] = query,
                ["maxResults"] = 10
            }
        );

        return result.Content;
    }
}

Step 5: Run and Test

# Ensure MCP servers are running
# Terminal 1: Start CODEX MCP Server
dotnet run --project /path/to/CodexMcpServer
# Server listens on http://localhost:5050

# Terminal 2: Run your client application
dotnet run

# The client will automatically connect to configured MCP servers via HTTP
# and be ready to call their tools

Legacy Stdio Transport (for local process-based tools):

{
  "Servers": [
    {
      "Name": "local-tool",
      "Transport": {
        "Type": "Stdio",
        "Command": "dotnet",
        "Args": ["run", "--project", "/path/to/tool", "--", "--stdio"]
      },
      "Enabled": true
    }
  ]
}

Note: HTTP transport is recommended for production with remote servers, load balancing, and monitoring.


Architecture

Svrnty.MCP.Client follows Clean Architecture principles:

┌─────────────────────────────────────────────────┐
│       Svrnty.MCP.Client.Cli (Executable)    │
│  ┌───────────────────────────────────────────┐  │
│  │  Svrnty.MCP.Client.AspNetCore (DI)   │  │
│  │  ┌─────────────────────────────────────┐ │  │
│  │  │ Svrnty.MCP.Client.Infrastructure│ │  │
│  │  │  ┌───────────────────────────────┐  │ │  │
│  │  │  │ Svrnty.MCP.Client.Core    │  │ │  │
│  │  │  │  - IMcpClient                 │  │ │  │
│  │  │  │  - IMcpServerConnection       │  │ │  │
│  │  │  │  - IConnectionPool            │  │ │  │
│  │  │  │  - Models (no dependencies)   │  │ │  │
│  │  │  └───────────────────────────────┘  │ │  │
│  │  └─────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘

Projects

Project Purpose Dependencies
Svrnty.MCP.Client.Core Abstractions, interfaces, models None
Svrnty.MCP.Client.Infrastructure MCP client implementation, transports, connection management Core, System.Text.Json
Svrnty.MCP.Client.AspNetCore ASP.NET Core integration, DI extensions Core, Infrastructure, ASP.NET Core
Svrnty.MCP.Client.Cli Standalone CLI executable All above

See Architecture Documentation for detailed design.


Examples

Sample client application that connects to CODEX MCP Server:

samples/CodexMcpClient/
├── Services/
│   ├── CodexSearchService.cs      # Search documents
│   ├── DocumentService.cs         # Retrieve documents
│   └── TagService.cs              # List and filter tags
├── Program.cs
└── appsettings.json

Running the sample:

# Terminal 1: Start CODEX MCP Server
cd /path/to/Svrnty.MCP.Server/samples/CodexMcpServer
dotnet run
# Server listens on http://localhost:5050

# Terminal 2: Run client commands
cd /path/to/Svrnty.MCP.Client/samples/CodexMcpClient
dotnet run -- search "architecture patterns"
dotnet run -- get-document <id>
dotnet run -- list-tags

# Client connects to server via HTTP (configured in appsettings.json)

2. Multi-Server Client

Connect to multiple MCP servers simultaneously:

public class MultiServerService
{
    private readonly IMcpClient _mcpClient;

    public async Task<CombinedResults> SearchAllServersAsync(string query)
    {
        // Get list of connected servers
        var servers = await _mcpClient.GetConnectedServersAsync();

        var results = new CombinedResults();

        foreach (var server in servers)
        {
            // List tools available on this server
            var tools = await _mcpClient.ListToolsAsync(server.Name);

            // Find search tool (if exists)
            var searchTool = tools.FirstOrDefault(t => t.Name.Contains("search"));

            if (searchTool != null)
            {
                var result = await _mcpClient.CallToolAsync(
                    server.Name,
                    searchTool.Name,
                    new Dictionary<string, object> { ["query"] = query }
                );

                results.Add(server.Name, result);
            }
        }

        return results;
    }
}

3. Error Handling and Retry

public class ResilientMcpService
{
    private readonly IMcpClient _mcpClient;
    private readonly ILogger<ResilientMcpService> _logger;

    public async Task<McpToolResult> CallWithRetryAsync(
        string serverName,
        string toolName,
        Dictionary<string, object> arguments,
        int maxRetries = 3)
    {
        for (int attempt = 0; attempt < maxRetries; attempt++)
        {
            try
            {
                return await _mcpClient.CallToolAsync(serverName, toolName, arguments);
            }
            catch (McpConnectionException ex) when (attempt < maxRetries - 1)
            {
                _logger.LogWarning(
                    "MCP call failed (attempt {Attempt}/{MaxRetries}): {Error}",
                    attempt + 1, maxRetries, ex.Message
                );

                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
            }
        }

        throw new Exception($"Failed after {maxRetries} attempts");
    }
}

Connection Management

Connection Pooling

Svrnty.MCP.Client includes connection pooling for efficient resource usage:

{
  "Mcp": {
    "Connection": {
      "EnableConnectionPooling": true,
      "MaxConnectionsPerServer": 5,
      "ConnectionIdleTimeout": "00:05:00",
      "PoolEvictionInterval": "00:01:00"
    }
  }
}

Timeout Configuration

Configure timeouts per server:

{
  "Servers": [
    {
      "Name": "fast-server",
      "Timeout": "00:00:10"
    },
    {
      "Name": "slow-batch-server",
      "Timeout": "00:05:00"
    }
  ]
}

Health Checks

Monitor server connections:

public class ServerHealthService
{
    private readonly IMcpClient _mcpClient;

    public async Task<Dictionary<string, bool>> CheckServerHealthAsync()
    {
        var servers = await _mcpClient.GetConnectedServersAsync();
        var health = new Dictionary<string, bool>();

        foreach (var server in servers)
        {
            try
            {
                await _mcpClient.PingAsync(server.Name);
                health[server.Name] = true;
            }
            catch
            {
                health[server.Name] = false;
            }
        }

        return health;
    }
}

Testing

Integration Tests

# Run all tests
dotnet test

# Run specific test project
dotnet test tests/Svrnty.MCP.Client.Tests/

# Run with coverage
dotnet test /p:CollectCoverage=true

Mock MCP Server

The library includes a mock server for testing:

[Fact]
public async Task Client_CanCallMockServerTool()
{
    // Arrange
    var mockServer = new MockMcpServer()
        .WithTool("test_tool", async (args) =>
            McpToolResult.Success($"Received: {args["input"]}"));

    var client = new McpClient();
    await client.ConnectToServerAsync(mockServer);

    // Act
    var result = await client.CallToolAsync(
        "mock-server",
        "test_tool",
        new Dictionary<string, object> { ["input"] = "test" }
    );

    // Assert
    Assert.True(result.IsSuccess);
    Assert.Equal("Received: test", result.Content);
}

Test Coverage

Svrnty.MCP.Client maintains 88.52% line coverage and 75.58% branch coverage with 60 tests passing (100%).

Coverage Breakdown:

  • Lines: 88.52% (excellent)
  • Branches: 75.58% (excellent)
  • Test Projects: 1
    • Svrnty.MCP.Client.Core.Tests: 60 tests
      • HTTP client connection tests: 20 tests
      • Configuration validation tests
      • Error handling and retry logic

Analysis:

  • Excellent coverage - exceeds 85% industry threshold
  • All critical paths tested
  • Error handling well-covered
  • Configuration scenarios comprehensive

Coverage Reports:

# Generate coverage report
dotnet test --collect:"XPlat Code Coverage" --results-directory ./TestResults

# View detailed coverage
# See: /home/svrnty/codex/COVERAGE-SUMMARY.md for complete analysis

Status: Excellent - Production-ready coverage meets all industry standards


Documentation

Document Description
API Reference Complete API documentation (IMcpClient, Models, Configuration)
Module Design Architecture and design decisions
Implementation Plan Development roadmap
AGENT-PRIMER.md AI-assisted setup guide
HTTPS Setup Guide Production TLS/HTTPS configuration

Svrnty.MCP is a family of three complementary modules:

All three modules share:

  • Same Clean Architecture pattern
  • Same documentation structure
  • Same security principles
  • Compatible .NET 8 SDKs

Contributing

We welcome contributions! See CONTRIBUTING.md for:

  • Development setup
  • Code standards
  • Testing requirements
  • Pull request process

License

This project is licensed under the MIT License - see the LICENSE file for details.


Support


Built with love by Svrnty

Creating sovereign tools to democratize technology for humanity.