COA.Mcp.Framework 2.1.6

There is a newer version of this package available.
See the version list below for details.
dotnet add package COA.Mcp.Framework --version 2.1.6
                    
NuGet\Install-Package COA.Mcp.Framework -Version 2.1.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="COA.Mcp.Framework" Version="2.1.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="COA.Mcp.Framework" Version="2.1.6" />
                    
Directory.Packages.props
<PackageReference Include="COA.Mcp.Framework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add COA.Mcp.Framework --version 2.1.6
                    
#r "nuget: COA.Mcp.Framework, 2.1.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package COA.Mcp.Framework@2.1.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=COA.Mcp.Framework&version=2.1.6
                    
Install as a Cake Addin
#tool nuget:?package=COA.Mcp.Framework&version=2.1.6
                    
Install as a Cake Tool

COA MCP Framework

A comprehensive .NET framework for building and consuming Model Context Protocol (MCP) servers with built-in token optimization, AI-friendly responses, strong typing, and developer-first design.

NuGet Version Build Status Tests .NET 9.0

๐Ÿš€ Quick Start

Never used MCP before? Follow our 5-Minute Quickstart Guide ๐Ÿ‘ˆ

Super Simple Example

  1. Install: dotnet add package COA.Mcp.Framework

  2. Copy this code into Program.cs:

using COA.Mcp.Framework.Server;
using COA.Mcp.Framework.Base;
using COA.Mcp.Framework.Models;

public class EchoTool : McpToolBase<EchoParams, EchoResult>
{
    public override string Name => "echo";
    public override string Description => "Echoes back your message";
    
    protected override async Task<EchoResult> ExecuteInternalAsync(
        EchoParams parameters, CancellationToken cancellationToken)
    {
        await Task.CompletedTask;
        return new EchoResult 
        { 
            Success = true, 
            Response = $"You said: {parameters.Text}" 
        };
    }
}

public class EchoParams { public string Text { get; set; } = ""; }
public class EchoResult : ToolResultBase 
{ 
    public override string Operation => "echo";
    public string Response { get; set; } = "";
}

class Program
{
    static async Task Main(string[] args)
    {
        var builder = new McpServerBuilder()
            .WithServerInfo("My First MCP Server", "1.0.0");
        
        builder.RegisterToolType<EchoTool>();
        await builder.RunAsync();
    }
}
  1. Run it: dotnet run

๐ŸŽ‰ That's it! You have a working MCP server.

Next Steps

Want more examples?

Need help?

Ready for production? See Advanced Features below.

๐Ÿ†• Professional Behavioral Guidance

Transform your MCP server into an intelligent assistant that guides users toward optimal workflows:

var builder = new McpServerBuilder()
    .WithServerInfo("My MCP Server", "1.0.0")
    
    // ๐Ÿ†• Load instructions from template files
    .WithInstructionsFromTemplate("Templates/server-instructions.scriban", templateVariables)
    
    // Advanced template-based instructions with built-in tool awareness
    .WithTemplateInstructions(options =>
    {
        options.ContextName = "codesearch"; // Built-in: general, codesearch, database
        options.EnableConditionalLogic = true;
        options.CustomVariables["ProjectType"] = "C# Library";
    })
    
    // ๐Ÿ†• Professional tool comparisons (no manipulation!)
    .WithToolComparison(
        task: "Find code patterns",
        serverTool: "text_search",
        builtInTool: "grep", 
        advantage: "Lucene-indexed with Tree-sitter parsing",
        performanceMetric: "100x faster, searches millions of lines in <500ms"
    )
    
    // ๐Ÿ†• Set enforcement level for recommendations
    .WithWorkflowEnforcement(WorkflowEnforcement.Recommend) // Suggest/Recommend/StronglyUrge
    
    // Tool priority and workflow suggestions
    .ConfigureToolManagement(config =>
    {
        config.EnableWorkflowSuggestions = true;
        config.EnableToolPriority = true;
        config.UseDefaultDescriptionProvider = true; // ๐Ÿ†• Imperative descriptions
    })
    
    // Smart error recovery
    .WithAdvancedErrorRecovery(options =>
    {
        options.EnableRecoveryGuidance = true;
        options.Tone = ErrorRecoveryTone.Professional;
    });

Why This Matters:

  • ๐ŸŽฏ Professional Tool Promotion: Evidence-based comparisons show why server tools are better than built-ins
  • ๐Ÿ“ˆ Smart Workflow Enforcement: Three levels (Suggest/Recommend/StronglyUrge) for appropriate guidance strength
  • ๐Ÿซ Educational: Teaches optimal patterns with performance metrics, not emotional manipulation
  • โšก Token Efficient: Fewer back-and-forth corrections save tokens (43% reduction measured)
  • ๐Ÿ”ง Context-Aware: Instructions adapt dynamically to server capabilities and available tools
  • ๐Ÿ“Š Performance Focused: Specific metrics like "100x faster" and "<500ms" provide concrete evidence
  • ๐Ÿ› ๏ธ Template-Driven: Load instructions from .scriban files with variable substitution

๐Ÿ†• Enhanced Tool Development:

// Tools can now specify priority and scenarios
public class MySearchTool : McpToolBase<SearchParams, SearchResult>, IPrioritizedTool
{
    public override string Description => 
        DefaultToolDescriptionProvider.TransformToImperative(
            "Searches for code patterns with Tree-sitter parsing", 
            Priority);
    
    // IPrioritizedTool implementation
    public int Priority => 90; // High priority (1-100 scale)
    public string[] PreferredScenarios => new[] { "code_exploration", "type_verification" };
}

Available Template Variables:

  • {{builtin_tools}} - Claude's built-in tools (Read, Grep, Bash, etc.)
  • {{tool_comparisons}} - Professional server vs built-in comparisons
  • {{enforcement_level}} - Current workflow enforcement setting
  • {{available_tools}} - Your server's available tools
  • {{#has_builtin "grep"}} - Conditional logic for built-in tool detection

๐Ÿ†• AI-Powered Middleware

Add intelligent type verification and TDD enforcement:

var builder = new McpServerBuilder()
    .WithServerInfo("My MCP Server", "1.0.0")
    .AddTypeVerificationMiddleware(options =>
    {
        options.Mode = TypeVerificationMode.Strict; // or Warning
        options.WhitelistedTypes.Add("MyCustomType");
    })
    .AddTddEnforcementMiddleware(options =>
    {
        options.Mode = TddEnforcementMode.Warning; // or Strict
        options.TestFilePatterns.Add("**/*Spec.cs"); // Custom test patterns
    });

Benefits:

  • ๐Ÿ›ก๏ธ Type Safety: Catches undefined types before code generation fails
  • ๐Ÿงช Quality Assurance: Enforces proper testing practices
  • ๐Ÿš€ AI-Friendly: Provides clear error messages with recovery steps
  • โšก Performance: Intelligent caching with file modification detection

๐Ÿ“ฆ NuGet Packages

  • COA.Mcp.Framework โ€” Core framework with MCP protocol included
  • COA.Mcp.Protocol โ€” Low-level protocol types and JSON-RPC
  • COA.Mcp.Client โ€” Strongly-typed C# client for MCP servers
  • COA.Mcp.Framework.TokenOptimization โ€” Advanced token management and AI response optimization
  • COA.Mcp.Framework.Testing โ€” Testing helpers, assertions, and benchmarks
  • COA.Mcp.Framework.Templates โ€” Project templates for quick starts
  • COA.Mcp.Framework.Migration โ€” Migration tools for updating from older versions

โœจ Key Features

๐Ÿ”’ Type-Safe Tool Development

  • Generic base class McpToolBase<TParams, TResult> ensures compile-time type safety
  • Automatic parameter validation using data annotations
  • No manual JSON parsing required

๐Ÿ—๏ธ Clean Architecture

  • Single unified tool registry with automatic disposal
  • Fluent server builder API
  • Dependency injection support
  • Clear separation of concerns
  • IAsyncDisposable support for resource management

๐Ÿ›ก๏ธ Comprehensive Error Handling

  • Standardized error models with ErrorInfo and RecoveryInfo
  • AI-friendly error messages with recovery steps
  • Built-in validation helpers - ValidateRequired(), ValidatePositive(), ValidateRange(), ValidateNotEmpty()
  • Error result helpers - CreateErrorResult(), CreateValidationErrorResult() with recovery steps
  • Customizable error messages - Override ErrorMessages property for tool-specific guidance

๐ŸŽฏ Generic Type Safety

  • Generic Parameter Validation: IParameterValidator<TParams> eliminates casting with strongly-typed validation
  • Generic Resource Caching: IResourceCache<TResource> supports any resource type with compile-time safety
  • Generic Response Building: BaseResponseBuilder<TInput, TResult> and AIOptimizedResponse<T> prevent object casting
  • Backward Compatible: All generic interfaces include non-generic versions for seamless migration

๐Ÿง  Token Management

  • Pre-estimation to prevent context overflow
  • Progressive reduction for large datasets
  • Smart truncation with resource URIs
  • Per-tool token budgets - Configure limits via ConfigureTokenBudgets() in server builder
  • Hierarchical configuration - Tool-specific, category, and default budget settings

๐Ÿ”— Lifecycle Hooks & Middleware

  • Extensible execution pipeline - Add cross-cutting concerns with simple middleware
  • Built-in middleware - Logging, token counting, performance monitoring
  • ๐Ÿ†• Type Verification Middleware - Prevents AI hallucinated types in code generation with intelligent caching
  • ๐Ÿ†• TDD Enforcement Middleware - Enforces Test-Driven Development workflow (Red-Green-Refactor)
  • Smart caching system - Session-scoped type verification with file modification invalidation
  • Multi-platform test integration - Supports dotnet test, npm test, pytest, and more
  • Custom middleware support - Implement ISimpleMiddleware for custom logic
  • Per-tool configuration - Override ToolSpecificMiddleware for tool-specific hooks
  • See Lifecycle Hooks Guide for detailed documentation

๐Ÿ’ฌ Interactive Prompts

  • Guide users through complex operations with prompt templates
  • Customizable arguments with validation
  • Built-in message builders for system/user/assistant roles
  • Variable substitution in templates

๐Ÿš€ Auto-Service Management (New)

  • Automatic startup of background services
  • Health monitoring and auto-restart capabilities
  • Port conflict detection
  • Support for multiple managed services

๐Ÿš„ Rapid Development

  • Minimal boilerplate code
  • Built-in validation helpers
  • Comprehensive IntelliSense support
  • Rich example projects

๐ŸŽฏ Client Library

Consuming MCP Servers with COA.Mcp.Client

The framework includes a strongly-typed C# client library for interacting with MCP servers:

// Create a typed client with fluent configuration
var client = await McpClientBuilder
    .Create("http://localhost:5000")
    .WithTimeout(TimeSpan.FromSeconds(30))
    .WithRetry(maxAttempts: 3, delayMs: 1000)
    .WithApiKey("your-api-key")
    .BuildAndInitializeAsync();

// List available tools
var tools = await client.ListToolsAsync();

// Call a tool with type safety
var result = await client.CallToolAsync("weather", new { location = "Seattle" });

Strongly-Typed Client Operations

// Define your types
public class WeatherParams
{
    public string Location { get; set; }
    public string Units { get; set; } = "celsius";
}

public class WeatherResult : ToolResultBase
{
    public override string Operation => "get_weather";
    public double Temperature { get; set; }
    public string Description { get; set; }
}

// Create a typed client
var typedClient = McpClientBuilder
    .Create("http://localhost:5000")
    .BuildTyped<WeatherParams, WeatherResult>();

// Call with full type safety
var weather = await typedClient.CallToolAsync("weather", 
    new WeatherParams { Location = "Seattle" });

if (weather.Success)
{
    Console.WriteLine($"Temperature: {weather.Temperature}ยฐ");
}

๐Ÿ“ Interactive Prompts

Creating Custom Prompts

Prompts provide interactive templates to guide users through complex operations:

// Define a prompt to help users generate code
public class CodeGeneratorPrompt : PromptBase
{
    public override string Name => "code-generator";
    public override string Description => 
        "Generate code snippets based on requirements";

    public override List<PromptArgument> Arguments => new()
    {
        new PromptArgument
        {
            Name = "language",
            Description = "Programming language (csharp, python, js)",
            Required = true
        },
        new PromptArgument
        {
            Name = "type",
            Description = "Type of code (class, function, interface)",
            Required = true
        },
        new PromptArgument
        {
            Name = "name",
            Description = "Name of the component",
            Required = true
        }
    };

    public override async Task<GetPromptResult> RenderAsync(
        Dictionary<string, object>? arguments = null,
        CancellationToken cancellationToken = default)
    {
        var language = GetRequiredArgument<string>(arguments, "language");
        var type = GetRequiredArgument<string>(arguments, "type");
        var name = GetRequiredArgument<string>(arguments, "name");
        
        return new GetPromptResult
        {
            Description = $"Generate {language} {type}: {name}",
            Messages = new List<PromptMessage>
            {
                CreateSystemMessage($"You are an expert {language} developer."),
                CreateUserMessage($"Generate a {type} named '{name}' in {language}."),
                CreateAssistantMessage("I'll help you create that component...")
            }
        };
    }
}

// Register prompts in your server
builder.RegisterPromptType<CodeGeneratorPrompt>();

Variable Substitution

Use the built-in variable substitution for dynamic templates:

var template = "Hello {{name}}, your project {{project}} is ready!";
var result = SubstituteVariables(template, new Dictionary<string, object>
{
    ["name"] = "Developer",
    ["project"] = "MCP Server"
});
// Result: "Hello Developer, your project MCP Server is ready!"

๐Ÿ”— Lifecycle Hooks & Middleware

The framework provides a powerful middleware system for adding cross-cutting concerns to your tools:

Adding Middleware to Tools

public class MyTool : McpToolBase<MyParams, MyResult>
{
    private readonly ILogger<MyTool>? _logger;

    public MyTool(IServiceProvider? serviceProvider, ILogger<MyTool>? logger = null)
        : base(serviceProvider, logger)
    {
        _logger = logger;
    }

    // Configure middleware for this specific tool
    protected override IReadOnlyList<ISimpleMiddleware>? ToolSpecificMiddleware => new List<ISimpleMiddleware>
    {
        // Built-in token counting middleware
        new TokenCountingSimpleMiddleware(),

        // Custom timing middleware
        new TimingMiddleware(_logger!)
    };

    // Your tool implementation...
}

Built-in Middleware

  • TokenCountingSimpleMiddleware: Estimates and logs token usage
  • LoggingSimpleMiddleware: Comprehensive execution logging

Custom Middleware

public class TimingMiddleware : SimpleMiddlewareBase
{
    private readonly ILogger _logger;
    
    public TimingMiddleware(ILogger logger)
    {
        _logger = logger;
        Order = 50; // Controls execution order
    }
    
    public override Task OnBeforeExecutionAsync(string toolName, object? parameters)
    {
        _logger.LogInformation("๐Ÿš€ Starting {ToolName}", toolName);
        return Task.CompletedTask;
    }
    
    public override Task OnAfterExecutionAsync(string toolName, object? parameters, object? result, long elapsedMs)
    {
        var performance = elapsedMs < 100 ? "โšก Fast" : elapsedMs < 1000 ? "๐Ÿšถ Normal" : "๐ŸŒ Slow";
        _logger.LogInformation("โœ… {ToolName} completed: {Performance} ({ElapsedMs}ms)", 
            toolName, performance, elapsedMs);
        return Task.CompletedTask;
    }
    
    public override Task OnErrorAsync(string toolName, object? parameters, Exception exception, long elapsedMs)
    {
        _logger.LogWarning("๐Ÿ’ฅ {ToolName} failed after {ElapsedMs}ms: {Error}", 
            toolName, elapsedMs, exception.Message);
        return Task.CompletedTask;
    }
}

Middleware Execution Flow

  1. Sort by Order (ascending): Lower numbers run first
  2. Before hooks (in order): middleware1 โ†’ middleware2 โ†’ middleware3
  3. Tool execution with validation and token management
  4. After hooks (reverse order): middleware3 โ†’ middleware2 โ†’ middleware1
  5. Error hooks (reverse order, if error occurs)

๐Ÿ“– For complete documentation and advanced examples, see Lifecycle Hooks Guide

๐Ÿ Getting Started

Working Example: SimpleMcpServer

Check out our complete working example in examples/SimpleMcpServer/:

// From examples/SimpleMcpServer/Tools/CalculatorTool.cs
public class CalculatorTool : McpToolBase<CalculatorParameters, CalculatorResult>
{
    public override string Name => "calculator";
    public override string Description => "Performs basic arithmetic operations";
    public override ToolCategory Category => ToolCategory.Utility;

    protected override async Task<CalculatorResult> ExecuteInternalAsync(
        CalculatorParameters parameters,
        CancellationToken cancellationToken)
    {
        // Validate inputs using base class helpers
        ValidateRequired(parameters.Operation, nameof(parameters.Operation));
        ValidateRequired(parameters.A, nameof(parameters.A));
        ValidateRequired(parameters.B, nameof(parameters.B));

        var a = parameters.A!.Value;
        var b = parameters.B!.Value;
        
        double result = parameters.Operation.ToLower() switch
        {
            "add" or "+" => a + b,
            "subtract" or "-" => a - b,
            "multiply" or "*" => a * b,
            "divide" or "/" => b != 0 ? a / b : 
                throw new DivideByZeroException("Cannot divide by zero"),
            _ => throw new NotSupportedException($"Operation '{parameters.Operation}' is not supported")
        };

        return new CalculatorResult
        {
            Success = true,
            Operation = parameters.Operation,
            Expression = $"{a} {parameters.Operation} {b}",
            Result = result,
            Meta = new ToolMetadata
            {
                ExecutionTime = $"{stopwatch.ElapsedMilliseconds}ms"
            }
        };
    }
}

Setting Up Your Server

// Program.cs for your MCP server
var builder = new McpServerBuilder()
    .WithServerInfo("My MCP Server", "1.0.0")
    .ConfigureLogging(logging =>
    {
        logging.ClearProviders();
        logging.AddConsole();
        logging.SetMinimumLevel(LogLevel.Information);
    });

// Register your services
builder.Services.AddSingleton<IMyService, MyService>();

// Register your tools (two options)

// Option 1: Manual registration (recommended for explicit control)
builder.RegisterToolType<MyFirstTool>();
builder.RegisterToolType<MySecondTool>();
builder.RegisterToolType<LifecycleExampleTool>(); // Example with middleware

// Option 2: Automatic discovery (scans assembly for tools)
builder.DiscoverTools(typeof(Program).Assembly);

// Build and run
await builder.RunAsync();

Transport Configuration

The framework supports multiple transport types for flexibility:

// Default: Standard I/O (for Claude Desktop and CLI tools)
var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0");
    // Uses stdio transport by default

// HTTP Transport (for web-based clients)
var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0")
    .UseHttpTransport(options =>
    {
        options.Port = 5000;
        options.EnableWebSocket = true;
        options.EnableCors = true;
        options.Authentication = AuthenticationType.ApiKey;
        options.ApiKey = "your-api-key";
    });

// WebSocket Transport (for real-time communication)
var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0")
    .UseWebSocketTransport(options =>
    {
        options.Port = 8080;
        options.Host = "localhost";
        options.UseHttps = false;
    });

Configuration with Service Provider Access

The framework provides enhanced configuration methods that give you access to the dependency injection container, eliminating the need for anti-patterns like manual BuildServiceProvider() calls:

// Register your dependencies first
var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0");

builder.Services.AddSingleton<IDataService, DataService>();
builder.Services.AddScoped<IRepository, DatabaseRepository>();

// Enhanced configuration with service provider access
builder
    .ConfigureTools((registry, serviceProvider) =>
    {
        // โœ… Clean: Access services without BuildServiceProvider()
        var dataService = serviceProvider.GetRequiredService<IDataService>();
        var customTool = new CustomTool(dataService);
        registry.RegisterTool<CustomParams, CustomResult>(customTool);
    })
    .ConfigureResources((registry, serviceProvider) =>
    {
        // โœ… Access repository for dynamic resource registration
        var repository = serviceProvider.GetRequiredService<IRepository>();
        var provider = new DatabaseResourceProvider(repository);
        registry.RegisterProvider(provider);
    })
    .ConfigurePrompts((registry, serviceProvider) =>
    {
        // โœ… Configure prompts with access to services
        var dataService = serviceProvider.GetRequiredService<IDataService>();
        var dynamicPrompt = new DataDrivenPrompt(dataService);
        registry.RegisterPrompt(dynamicPrompt);
    });
Migration from Manual BuildServiceProvider()

If you have existing code that manually calls BuildServiceProvider(), you can migrate to the enhanced API:

// โŒ Before: Anti-pattern with duplicate singletons
builder.ConfigureResources(registry =>
{
    #pragma warning disable ASP0000
    var serviceProvider = builder.Services.BuildServiceProvider();
    var provider = serviceProvider.GetRequiredService<MyResourceProvider>();
    registry.RegisterProvider(provider);
    #pragma warning restore ASP0000
});

// โœ… After: Clean with proper dependency injection
builder.ConfigureResources((registry, serviceProvider) =>
{
    var provider = serviceProvider.GetRequiredService<MyResourceProvider>();
    registry.RegisterProvider(provider);
});

The enhanced API provides:

  • No duplicate singletons: Single DI container, proper singleton behavior
  • No ASP0000 warnings: Eliminates compiler warnings about BuildServiceProvider
  • Better performance: Reduced memory usage and object allocation
  • Cleaner code: Follows .NET dependency injection best practices

Logging Configuration

The framework provides granular control over logging to reduce noise and improve debugging experience:

var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0")
    .ConfigureLogging(logging =>
    {
        // Standard logging configuration
        logging.AddConsole();
        logging.SetMinimumLevel(LogLevel.Information);
        
        // Optional: Configure specific categories
        logging.AddFilter("COA.Mcp.Framework", LogLevel.Warning); // Quiet framework
        logging.AddFilter("MyApp", LogLevel.Debug); // Verbose for your code
    })
    .ConfigureFramework(options =>
    {
        // Framework-specific logging options
        options.FrameworkLogLevel = LogLevel.Warning;        // Default framework log level
        options.EnableDetailedToolLogging = false;          // Reduce tool execution noise
        options.EnableDetailedMiddlewareLogging = false;    // Reduce middleware noise
        options.EnableDetailedTransportLogging = false;     // Reduce transport noise
        
        // Advanced options
        options.EnableFrameworkLogging = true;              // Enable/disable framework logging entirely
        options.ConfigureLoggingIfNotConfigured = true;     // Don't override existing logging config
        options.SuppressStartupLogs = false;                // Show/hide startup messages
    });
Logging Categories

The framework uses these logging categories for fine-grained control:

  • COA.Mcp.Framework.Pipeline.Middleware - Middleware operations (type verification, TDD enforcement, etc.)
  • COA.Mcp.Framework.Transport - Transport layer operations (HTTP, WebSocket, stdio)
  • COA.Mcp.Framework.Base - Tool execution and lifecycle events
  • COA.Mcp.Framework.Server - Server startup and management
  • COA.Mcp.Framework.Pipeline - Request/response pipeline processing
Quick Configuration Examples
// Minimal logging (production)
builder.ConfigureFramework(options =>
{
    options.FrameworkLogLevel = LogLevel.Error;
    options.EnableDetailedToolLogging = false;
    options.EnableDetailedMiddlewareLogging = false;
    options.EnableDetailedTransportLogging = false;
});

// Debug mode (development)
builder.ConfigureFramework(options =>
{
    options.FrameworkLogLevel = LogLevel.Debug;
    options.EnableDetailedToolLogging = true;
    options.EnableDetailedMiddlewareLogging = true;
    options.EnableDetailedTransportLogging = true;
});

// Completely disable framework logging
builder.ConfigureFramework(options =>
{
    options.EnableFrameworkLogging = false;
});

๐Ÿš€ Auto-Service Management

The framework now supports automatic service startup, enabling dual-mode architectures where an MCP server can act as both a STDIO client and an HTTP service provider:

// Configure auto-started services alongside your MCP server
var builder = new McpServerBuilder()
    .WithServerInfo("My MCP Server", "1.0.0")
    .UseStdioTransport()  // Primary transport for Claude
    .UseAutoService(config =>
    {
        config.ServiceId = "my-http-api";
        config.ExecutablePath = Assembly.GetExecutingAssembly().Location;
        config.Arguments = new[] { "--mode", "http", "--port", "5100" };
        config.Port = 5100;
        config.HealthEndpoint = "http://localhost:5100/health";
        config.AutoRestart = true;
        config.MaxRestartAttempts = 3;
    });

await builder.RunAsync();
Key Features:
  • Automatic Startup: Services start automatically when the MCP server launches
  • Health Monitoring: Periodic health checks with configurable intervals
  • Auto-Restart: Automatic recovery from service failures with retry limits
  • Port Detection: Checks if ports are already in use before starting
  • Graceful Shutdown: Clean service termination when MCP server stops
  • Multiple Services: Support for multiple auto-started services
Configuration Options:
public class ServiceConfiguration
{
    public string ServiceId { get; set; }              // Unique service identifier
    public string ExecutablePath { get; set; }         // Path to executable
    public string[] Arguments { get; set; }            // Command-line arguments
    public int Port { get; set; }                      // Service port
    public string HealthEndpoint { get; set; }         // Health check URL
    public int StartupTimeoutSeconds { get; set; }     // Startup timeout (default: 30)
    public int HealthCheckIntervalSeconds { get; set; } // Health check interval (default: 60)
    public bool AutoRestart { get; set; }              // Enable auto-restart (default: true)
    public int MaxRestartAttempts { get; set; }        // Max restart attempts (default: 3)
    public Dictionary<string, string> EnvironmentVariables { get; set; } // Environment vars
}
Multiple Services Example:
var builder = new McpServerBuilder()
    .WithServerInfo("Multi-Service MCP", "1.0.0")
    .UseStdioTransport()
    .UseAutoServices(
        config =>
        {
            config.ServiceId = "api-service";
            config.ExecutablePath = "api.exe";
            config.Port = 5100;
            config.HealthEndpoint = "http://localhost:5100/health";
        },
        config =>
        {
            config.ServiceId = "worker-service";
            config.ExecutablePath = "worker.exe";
            config.Port = 5200;
            config.HealthEndpoint = "http://localhost:5200/health";
        }
    );
Custom Health Checks:
// Register a custom health check for advanced scenarios
// Use the enhanced configuration API with service provider access
builder.ConfigureResources((registry, serviceProvider) =>
{
    var serviceManager = serviceProvider.GetRequiredService<IServiceManager>();
    
    serviceManager.RegisterHealthCheck("my-service", async () =>
    {
        // Custom health check logic
        var client = new HttpClient();
        var response = await client.GetAsync("http://localhost:5100/custom-health");
        return response.IsSuccessStatusCode;
    });
});

This feature is ideal for:

  • Dual-mode architectures: MCP servers that need to expose HTTP APIs
  • Microservice coordination: Managing related services together
  • Development environments: Simplified local development setup
  • Federation scenarios: MCP servers that communicate with each other

๐Ÿ”ฅ Advanced Features

Generic Type Safety - Eliminate Object Casting

The framework provides generic versions of key interfaces to eliminate object casting and improve type safety:

Generic Parameter Validation
// Before: Non-generic parameter validation (still supported)
public class LegacyTool : McpToolBase<MyParams, MyResult>
{
    protected override Task<MyResult> ExecuteInternalAsync(
        MyParams parameters, CancellationToken cancellationToken)
    {
        // Parameters already validated and strongly typed!
        return ProcessParameters(parameters); // No casting needed
    }
}

// New: Explicit generic parameter validator for advanced scenarios
public class CustomValidationTool : McpToolBase<ComplexParams, ComplexResult>
{
    private readonly IParameterValidator<ComplexParams> _validator;
    
    public CustomValidationTool(IParameterValidator<ComplexParams> validator)
    {
        _validator = validator; // Strongly typed, no object casting
    }
    
    protected override async Task<ComplexResult> ExecuteInternalAsync(
        ComplexParams parameters, CancellationToken cancellationToken)
    {
        // Custom validation with no object casting
        var validationResult = _validator.Validate(parameters);
        
        if (!validationResult.IsValid)
        {
            return CreateErrorResult("VALIDATION_FAILED", 
                string.Join(", ", validationResult.Errors.Select(e => e.Message)));
        }
        
        // Process strongly-typed parameters
        return ProcessComplexParameters(parameters);
    }
}
Generic Resource Caching
// Cache any resource type with compile-time safety
public class SearchResultResourceProvider : IResourceProvider
{
    private readonly IResourceCache<SearchResultData> _cache; // Strongly typed cache!
    private readonly ISearchService _searchService;
    
    public SearchResultResourceProvider(
        IResourceCache<SearchResultData> cache, // No more object casting
        ISearchService searchService)
    {
        _cache = cache;
        _searchService = searchService;
    }
    
    public async Task<ReadResourceResult> ReadResourceAsync(string uri, CancellationToken ct)
    {
        // Check cache first - strongly typed, no casting
        var cached = await _cache.GetAsync(uri);
        if (cached != null)
        {
            return CreateReadResourceResult(cached); // Type-safe operations
        }
        
        // Generate new data
        var searchData = await _searchService.SearchAsync(ExtractQuery(uri));
        
        // Store in cache - type-safe storage
        await _cache.SetAsync(uri, searchData, TimeSpan.FromMinutes(10));
        
        return CreateReadResourceResult(searchData);
    }
    
    private ReadResourceResult CreateReadResourceResult(SearchResultData data)
    {
        return new ReadResourceResult
        {
            Contents = new List<ResourceContent>
            {
                new ResourceContent
                {
                    Uri = data.OriginalQuery,
                    Text = JsonSerializer.Serialize(data), // Type-safe serialization
                    MimeType = "application/json"
                }
            }
        };
    }
}

// Register the strongly-typed cache
builder.Services.AddSingleton<IResourceCache<SearchResultData>, InMemoryResourceCache<SearchResultData>>();
Generic Response Building
// Before: Object-based response building (still supported for backward compatibility)
public class LegacyResponseBuilder : BaseResponseBuilder
{
    public override async Task<object> BuildResponseAsync(object data, ResponseContext context)
    {
        return new AIOptimizedResponse // Returns object, requires casting
        {
            Data = new AIResponseData
            {
                Results = data, // object type, requires casting later
                Meta = new AIResponseMeta { /* ... */ }
            }
        };
    }
}

// New: Strongly-typed response building
public class TypedResponseBuilder : BaseResponseBuilder<SearchData, SearchResult>
{
    public override async Task<SearchResult> BuildResponseAsync(SearchData data, ResponseContext context)
    {
        return new SearchResult // Strongly typed return, no casting needed
        {
            Success = true,
            Operation = "search_data",
            Query = data.Query,
            Results = data.Items, // Type-safe property access
            TotalFound = data.Items.Count,
            ExecutionTime = context.ElapsedTime,
            // No object casting anywhere!
        };
    }
}

// Using the generic AIOptimizedResponse<T>
public class OptimizedSearchTool : McpToolBase<SearchParams, AIOptimizedResponse<SearchResultSummary>>
{
    protected override async Task<AIOptimizedResponse<SearchResultSummary>> ExecuteInternalAsync(
        SearchParams parameters, CancellationToken cancellationToken)
    {
        var searchData = await SearchAsync(parameters.Query);
        
        return new AIOptimizedResponse<SearchResultSummary> // Generic type, no casting!
        {
            Success = true,
            Operation = "search_optimized",
            Data = new AIResponseData<SearchResultSummary>
            {
                Results = new SearchResultSummary
                {
                    Query = parameters.Query,
                    TotalMatches = searchData.Count,
                    TopResults = searchData.Take(5).ToList()
                },
                Meta = new AIResponseMeta
                {
                    TokenUsage = EstimateTokens(searchData),
                    OptimizationApplied = searchData.Count > 100,
                    ResourceUri = searchData.Count > 100 ? StoreAsResource(searchData) : null
                }
            }
        };
    }
}
Migration Examples
// Easy migration from non-generic to generic interfaces
public class MigrationExample
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Option 1: Use generic interface directly
        services.AddSingleton<IParameterValidator<MyParams>, DefaultParameterValidator<MyParams>>();
        services.AddSingleton<IResourceCache<MyResource>, InMemoryResourceCache<MyResource>>();
        
        // Option 2: Keep existing non-generic registrations (fully backward compatible)
        services.AddSingleton<IParameterValidator, DefaultParameterValidator>();
        services.AddSingleton<IResourceCache, InMemoryResourceCache>();
        
        // Option 3: Convert existing non-generic to generic using extension methods
        var nonGenericValidator = serviceProvider.GetService<IParameterValidator>();
        var typedValidator = nonGenericValidator.ForType<MyParams>(); // Extension method conversion
    }
}
Key Benefits
  • ๐ŸŽฏ Compile-time Safety: Catch type errors at build time instead of runtime
  • ๐Ÿš€ Better Performance: No boxing/unboxing or reflection-based casting
  • ๐Ÿง  Enhanced IntelliSense: Full type information available in IDE
  • ๐Ÿ”„ Seamless Migration: Non-generic interfaces still work, upgrade at your own pace
  • ๐Ÿ› ๏ธ Cleaner Code: Eliminate try-catch blocks around casting operations

Customizable Error Messages

Override the ErrorMessages property in your tools to provide context-specific error messages and recovery guidance:

public class DatabaseTool : McpToolBase<DbParams, DbResult>
{
    // Custom error message provider
    protected override ErrorMessageProvider ErrorMessages => new DatabaseErrorMessageProvider();
    
    // ... tool implementation
}

public class DatabaseErrorMessageProvider : ErrorMessageProvider
{
    public override string ToolExecutionFailed(string toolName, string details)
    {
        return $"Database operation '{toolName}' failed: {details}. Check connection status.";
    }
    
    public override RecoveryInfo GetRecoveryInfo(string errorCode, string? context = null, Exception? exception = null)
    {
        return errorCode switch
        {
            "CONNECTION_FAILED" => new RecoveryInfo
            {
                Steps = new[]
                {
                    "Verify database connection string",
                    "Check network connectivity",
                    "Ensure database server is running"
                },
                SuggestedActions = new[]
                {
                    new SuggestedAction
                    {
                        Tool = "test_connection",
                        Description = "Test database connectivity",
                        Parameters = new { timeout = 30 }
                    }
                }
            },
            _ => base.GetRecoveryInfo(errorCode, context, exception)
        };
    }
}

Token Budget Configuration

Configure token limits per tool, category, or globally using the server builder:

var builder = new McpServerBuilder()
    .WithServerInfo("My Server", "1.0.0")
    .ConfigureTokenBudgets(budgets =>
    {
        // Tool-specific limits (highest priority)
        budgets.ForTool<LargeDataTool>()
            .MaxTokens(20000)
            .WarningThreshold(16000)
            .WithStrategy(TokenLimitStrategy.Truncate)
            .Apply();
            
        budgets.ForTool<SearchTool>()
            .MaxTokens(5000)
            .WithStrategy(TokenLimitStrategy.Throw)
            .Apply();
        
        // Category-based limits (medium priority)
        budgets.ForCategory(ToolCategory.Analysis)
            .MaxTokens(15000)
            .WarningThreshold(12000)
            .Apply();
            
        budgets.ForCategory(ToolCategory.Query)
            .MaxTokens(8000)
            .Apply();
        
        // Default limits (lowest priority)
        budgets.Default()
            .MaxTokens(10000)
            .WarningThreshold(8000)
            .WithStrategy(TokenLimitStrategy.Warn)
            .EstimationMultiplier(1.2) // Conservative estimates
            .Apply();
    });
Token Budget Strategies
  • Warn: Log warning and continue (default)
  • Throw: Throw exception to prevent execution
  • Truncate: Truncate output to stay within limits
  • Ignore: No token limit enforcement
Per-Tool Token Budget Override
public class HighVolumeAnalysisTool : McpToolBase<AnalysisParams, AnalysisResult>
{
    // Override the default token budget for this specific tool
    protected override TokenBudgetConfiguration TokenBudget => new()
    {
        MaxTokens = 50000,
        WarningThreshold = 40000,
        Strategy = TokenLimitStrategy.Truncate,
        EstimationMultiplier = 1.5
    };
    
    // ... tool implementation
}

Built-in Validation Helpers

The framework provides several validation helpers in the McpToolBase class to simplify parameter validation:

public class DataProcessingTool : McpToolBase<DataParams, DataResult>
{
    protected override async Task<DataResult> ExecuteInternalAsync(
        DataParams parameters,
        CancellationToken cancellationToken)
    {
        // Validate required parameters (throws ValidationException if null/empty)
        var filePath = ValidateRequired(parameters.FilePath, nameof(parameters.FilePath));
        var query = ValidateRequired(parameters.Query, nameof(parameters.Query));
        
        // Validate positive numbers
        var maxResults = ValidatePositive(parameters.MaxResults, nameof(parameters.MaxResults));
        
        // Validate ranges
        var priority = ValidateRange(parameters.Priority, 1, 10, nameof(parameters.Priority));
        
        // Validate collections aren't empty
        var tags = ValidateNotEmpty(parameters.Tags, nameof(parameters.Tags));
        
        // All validation passed - process the data
        return await ProcessDataAsync(filePath, query, maxResults, priority, tags);
    }
}
Available Validation Helpers
Helper Purpose Throws
ValidateRequired<T>(value, paramName) Ensures value is not null or empty string ValidationException
ValidatePositive(value, paramName) Ensures numeric value > 0 ValidationException
ValidateRange(value, min, max, paramName) Ensures value is within range ValidationException
ValidateNotEmpty<T>(collection, paramName) Ensures collection has items ValidationException

Built-in Error Result Helpers

The framework provides helpers to create standardized error results with recovery information:

public class DatabaseTool : McpToolBase<DbParams, DbResult>
{
    protected override async Task<DbResult> ExecuteInternalAsync(
        DbParams parameters,
        CancellationToken cancellationToken)
    {
        try
        {
            var connectionString = ValidateRequired(parameters.ConnectionString, nameof(parameters.ConnectionString));
            
            // Attempt database operation
            var result = await ExecuteDatabaseQuery(connectionString, parameters.Query);
            
            return new DbResult
            {
                Success = true,
                Operation = "database_query",
                Data = result
            };
        }
        catch (SqlException ex) when (ex.Number == 2) // Connection timeout
        {
            // Create standardized error with recovery steps
            return new DbResult
            {
                Success = false,
                Operation = "database_query",
                Error = CreateErrorResult(
                    "database_query", 
                    $"Database connection timeout: {ex.Message}",
                    "Verify database server is running and accessible"
                )
            };
        }
        catch (ArgumentException ex)
        {
            // Create validation error with specific guidance
            return new DbResult
            {
                Success = false,
                Operation = "database_query",
                Error = CreateValidationErrorResult(
                    "database_query",
                    "connectionString",
                    "Must be a valid SQL Server connection string"
                )
            };
        }
    }
}
Available Error Result Helpers
Helper Purpose Returns
CreateErrorResult(operation, error, recoveryStep?) Creates ErrorInfo with recovery guidance ErrorInfo
CreateValidationErrorResult(operation, paramName, requirement) Creates validation-specific error ErrorInfo
CreateSuccessResult<T>(data, message?) Creates successful ToolResult<T> ToolResult<T>
CreateErrorResult<T>(errorMessage, errorCode?) Creates failed ToolResult<T> ToolResult<T>

Error Handling with Recovery Steps

public class FileAnalysisTool : McpToolBase<FileAnalysisParams, FileAnalysisResult>
{
    protected override async Task<FileAnalysisResult> ExecuteInternalAsync(
        FileAnalysisParams parameters,
        CancellationToken cancellationToken)
    {
        try
        {
            var filePath = ValidateRequired(parameters.FilePath, nameof(parameters.FilePath));
            
            if (!File.Exists(filePath))
            {
                // Return AI-friendly error with recovery steps
                return new FileAnalysisResult
                {
                    Success = false,
                    Operation = "analyze_file",
                    Error = new ErrorInfo
                    {
                        Code = "FILE_NOT_FOUND",
                        Message = $"File not found: {filePath}",
                        Recovery = new RecoveryInfo
                        {
                            Steps = new[]
                            {
                                "Verify the file path is correct",
                                "Check if the file exists",
                                "Ensure you have read permissions"
                            },
                            SuggestedActions = new[]
                            {
                                new SuggestedAction
                                {
                                    Tool = "list_files",
                                    Description = "List files in directory",
                                    Parameters = new { path = Path.GetDirectoryName(filePath) }
                                }
                            }
                        }
                    }
                };
            }
            
            // Perform analysis...
            var analysis = await AnalyzeFileAsync(filePath);
            
            return new FileAnalysisResult
            {
                Success = true,
                Operation = "analyze_file",
                FilePath = filePath,
                Analysis = analysis,
                Insights = GenerateInsights(analysis),
                Actions = GenerateNextActions(analysis)
            };
        }
        catch (UnauthorizedAccessException ex)
        {
            return CreateErrorResult(
                "PERMISSION_DENIED",
                $"Access denied: {ex.Message}",
                new[] { "Check file permissions", "Run with appropriate privileges" }
            );
        }
    }
}

Resource Providers

Automatic Resource Caching (v1.4.8+)

The framework now provides automatic singleton-level caching for resources, solving the lifetime mismatch between scoped providers and singleton registry:

// Resource caching is automatically configured by McpServerBuilder
// No additional setup required - just implement your provider!

public class SearchResultResourceProvider : IResourceProvider
{
    private readonly ISearchService _searchService; // Can be scoped!
    
    public SearchResultResourceProvider(ISearchService searchService)
    {
        _searchService = searchService; // Scoped dependency is OK
    }
    
    public string Scheme => "search-results";
    public string Name => "Search Results Provider";
    public string Description => "Provides search result resources";
    
    public bool CanHandle(string uri) => 
        uri.StartsWith($"{Scheme}://");
    
    public async Task<ReadResourceResult> ReadResourceAsync(string uri, CancellationToken ct)
    {
        // No need to implement caching - framework handles it!
        var sessionId = ExtractSessionId(uri);
        var results = await _searchService.LoadResultsAsync(sessionId);
        
        return new ReadResourceResult
        {
            Contents = new List<ResourceContent>
            {
                new ResourceContent
                {
                    Uri = uri,
                    Text = JsonSerializer.Serialize(results),
                    MimeType = "application/json"
                }
            }
        };
    }
    
    public async Task<List<Resource>> ListResourcesAsync(CancellationToken ct)
    {
        // List available resources
        var sessions = await _searchService.GetActiveSessionsAsync();
        return sessions.Select(s => new Resource
        {
            Uri = $"{Scheme}://{s.Id}",
            Name = $"Search Results {s.Id}",
            Description = $"Results for query: {s.Query}",
            MimeType = "application/json"
        }).ToList();
    }
}

// Register your provider - caching is automatic!
builder.Services.AddScoped<IResourceProvider, SearchResultResourceProvider>();

// Optional: Configure cache settings
builder.Services.Configure<ResourceCacheOptions>(options =>
{
    options.DefaultExpiration = TimeSpan.FromMinutes(10);
    options.SlidingExpiration = TimeSpan.FromMinutes(5);
    options.MaxSizeBytes = 200 * 1024 * 1024; // 200 MB
});
Why Resource Caching?
  • Solves lifetime mismatch: Scoped providers can work with singleton registry
  • Improves performance: Automatic caching of expensive operations
  • Memory efficient: Built-in size limits and expiration
  • Transparent: No code changes needed in existing providers
  • Resilient: Failures in cache don't affect core functionality

Token Optimization (with optional package)

// Add the TokenOptimization package
// <PackageReference Include="COA.Mcp.Framework.TokenOptimization" Version="1.7.17" />

public class SearchTool : McpToolBase<SearchParams, SearchResult>
{
    protected override async Task<SearchResult> ExecuteInternalAsync(
        SearchParams parameters,
        CancellationToken cancellationToken)
    {
        var results = await SearchAsync(parameters.Query);
        
        // Use token management from base class
        return await ExecuteWithTokenManagement(async () =>
        {
            // Automatically handles token limits
            return new SearchResult
            {
                Success = true,
                Results = results, // Auto-truncated if needed
                TotalCount = results.Count,
                ResourceUri = results.Count > 100 ? 
                    await StoreAsResourceAsync(results) : null
            };
        });
    }
}

Testing Your Tools

using COA.Mcp.Framework.Testing;
using FluentAssertions;

[TestFixture]
public class WeatherToolTests
{
    private WeatherTool _tool;
    private Mock<IWeatherService> _weatherService;
    
    [SetUp]
    public void Setup()
    {
        _weatherService = new Mock<IWeatherService>();
        _tool = new WeatherTool(_weatherService.Object);
    }
    
    [Test]
    public async Task GetWeather_WithValidLocation_ReturnsWeatherData()
    {
        // Arrange
        var parameters = new WeatherParameters
        {
            Location = "Seattle",
            ForecastDays = 3
        };
        
        _weatherService
            .Setup(x => x.GetWeatherAsync("Seattle", 3))
            .ReturnsAsync(new WeatherData { /* ... */ });
        
        // Act
        var result = await _tool.ExecuteAsync(parameters);
        
        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        result.Location.Should().Be("Seattle");
        result.Forecast.Should().HaveCount(3);
    }
    
    [Test]
    public async Task GetWeather_WithMissingLocation_ReturnsError()
    {
        // Arrange
        var parameters = new WeatherParameters { Location = null };
        
        // Act
        var result = await _tool.ExecuteAsync(parameters);
        
        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().NotBeNull();
        result.Error.Code.Should().Be("VALIDATION_ERROR");
    }
}

๐Ÿ“š Documentation

Framework Structure

COA.Mcp.Framework/
โ”œโ”€โ”€ Base/
โ”‚   โ””โ”€โ”€ McpToolBase.Generic.cs    # Generic base class for tools
โ”œโ”€โ”€ Server/
โ”‚   โ”œโ”€โ”€ McpServer.cs              # Main server implementation
โ”‚   โ”œโ”€โ”€ McpServerBuilder.cs       # Fluent builder API
โ”‚   โ””โ”€โ”€ Services/                 # Auto-service management
โ”‚       โ”œโ”€โ”€ ServiceManager.cs     # Service lifecycle management
โ”‚       โ”œโ”€โ”€ ServiceConfiguration.cs # Service config model
โ”‚       โ””โ”€โ”€ ServiceLifecycleHost.cs # IHostedService integration
โ”œโ”€โ”€ Registration/
โ”‚   โ””โ”€โ”€ McpToolRegistry.cs        # Unified tool registry
โ”œโ”€โ”€ Interfaces/
โ”‚   โ”œโ”€โ”€ IMcpTool.cs              # Tool interfaces
โ”‚   โ””โ”€โ”€ IResourceProvider.cs     # Resource provider pattern
โ”œโ”€โ”€ Models/
โ”‚   โ”œโ”€โ”€ ErrorModels.cs           # Error handling models
โ”‚   โ””โ”€โ”€ ToolResultBase.cs        # Base result class
โ””โ”€โ”€ Enums/
    โ””โ”€โ”€ ToolCategory.cs          # Tool categorization

Key Components

  • McpToolBase<TParams, TResult>: Generic base class for type-safe tool implementation
  • McpServerBuilder: Fluent API for server configuration
  • McpToolRegistry: Manages tool registration and discovery
  • ToolResultBase: Standard result format with error handling
  • IResourceProvider: Interface for custom resource providers

๐Ÿ† Real-World Examples

The framework powers production MCP servers:

  • CodeSearch MCP: File and text searching with Lucene indexing
  • CodeNav MCP: C# code navigation using Roslyn
  • SimpleMcpServer: Example project with calculator, data store, and system info tools

๐Ÿ“ˆ Performance

Metric Target Actual
Build Time โค๏ธs 2.46s
Test Suite 100% pass 562/562 โœ“
Warnings 0 0 โœ“
Framework Overhead <5% ~3%

๐Ÿค Contributing

We welcome contributions! Key areas:

  • Additional tool examples
  • Performance optimizations
  • Documentation improvements
  • Test coverage expansion

๐Ÿ“„ License

MIT License - see LICENSE file for details.

๐Ÿ™ Acknowledgments

Built on experience from:

  • COA CodeSearch MCP - Token optimization patterns
  • COA CodeNav MCP - Roslyn integration patterns
  • The MCP community - Feedback and ideas

๐Ÿ“– Documentation

For comprehensive documentation, guides, and examples, see the Documentation Hub.

Ready to build your MCP server? Clone the repo and check out the examples:

git clone https://github.com/anortham/COA-Mcp-Framework.git
cd COA-Mcp-Framework/examples/SimpleMcpServer
dotnet run

For detailed guidance, see CLAUDE.md for AI-assisted development tips.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on COA.Mcp.Framework:

Package Downloads
COA.Mcp.Framework.TokenOptimization

Token optimization and AI-friendly response building for MCP servers. Includes token estimation, progressive reduction, and response caching.

COA.Mcp.Framework.Testing

Comprehensive testing helpers for MCP servers including fluent assertions, test builders, and mock infrastructure.

COA.Mcp.Framework.Migration

Migration tools for converting existing MCP projects to use COA.Mcp.Framework

COA.Mcp.Client

Strongly-typed C# client library for interacting with MCP servers over HTTP/WebSocket

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.12 0 9/9/2025
2.1.10 46 9/9/2025
2.1.8 58 9/8/2025
2.1.6 72 9/7/2025
2.1.4 70 9/6/2025
2.1.1 68 9/6/2025
2.0.21 96 9/5/2025
2.0.18 145 9/4/2025
2.0.13 188 8/27/2025
2.0.8 278 8/25/2025
2.0.6 284 8/25/2025
1.7.22 166 8/24/2025
1.7.19 140 8/20/2025
1.7.2 152 8/12/2025
1.6.0 145 8/12/2025