1. Conduid
  2. AI
  3. COA.Mcp.Protocol
MCP server · AI

COA.Mcp.Protocol

MCP protocol types and interfaces for Model Context Protocol servers

Unclaimed ai
34Low

Scored 7 months ago · breakdown

About COA.Mcp.Protocol

COA.Mcp.Protocol is an MCP server in the AI category: mCP protocol types and interfaces for Model Context Protocol servers.

Install

From source
git clone https://github.com/anortham/coa-mcp-framework

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about COA.Mcp.Protocol

Powered by Claude · Grounded in docs

I know everything about COA.Mcp.Protocol. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

  • ✓README presentThe repository ships documentation.

5 checks have not been run yet.

README

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)
    {
        // Easy start with optimized defaults
        var builder = McpServerBuilder.CreateMinimal("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, performance monitoring
  • 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 <3s 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.

README copy captured when this server was indexed; it may lag the repository. The original is authoritative.

Questions

About COA.Mcp.Protocol

How do I install COA.Mcp.Protocol?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is COA.Mcp.Protocol safe to use with an AI agent?

Its trust score is 34 out of 100 (low). It passes 1 of the 1 static security checks run so far. It has no ConduID identity yet, so agent calls to it are not receipted.

Is COA.Mcp.Protocol still maintained?

Conduid hasn't recorded a commit date for this repository yet. Check the repository directly for recent activity.