Building MCP Tools on Umbraco 13 and N8N AI Chat Workflows

By , Senior Full-Stack Engineer15 min read

When I set out to build an intelligent AI assistant on top of a pre-existing Umbraco website, I faced a common challenge: how do you connect AI agents to complex, domain-specific data while maintaining performance, security, and reliability? The solution I developed combines custom Model Context Protocol (MCP) tools integrated with Umbraco 13 CMS and an N8N single-agent workflow architecture. This post provides a developer-focused overview of the technical architecture, sharing the key patterns, design decisions, and lessons learned from building a production AI system that queries thousands of healthcare technology vendor documents.

The Architecture Challenge

Traditional AI integrations often fall into two camps: either they're too simple (basic API calls with limited context) or too complex (heavyweight RAG systems with vector databases and embedding management). I needed something in between - a system that could:

  • Access rich, structured data from an existing Umbraco 13 CMS
  • Provide distinct AI tools for healthcare technology research
  • Maintain data consistency and avoid hallucinations
  • Scale efficiently without complex vector database management
  • Integrate seamlessly with existing ElasticSearch infrastructure

The Model Context Protocol emerged as the perfect solution. Created by Anthropic, MCP provides a standardized way to connect AI systems to external data sources through well-defined tools and resources.

MCP Server Implementation: The Foundation

Project Structure and Dependencies

The MCP server is implemented as a .NET 8 project that integrates directly with the existing Umbraco 13 infrastructure:

<!-- Key MCP dependencies. Pin to current versions and re-check on the
     ModelContextProtocol C# SDK NuGet page before adopting in a new project —
     the SDK is still pre-1.0 and ships breaking changes between previews. -->
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.3" />
<PackageReference Include="Umbraco.Cms.Core" Version="13.*" />
<PackageReference Include="System.Text.Json" Version="9.*" />

The architecture leverages three core components:

  • ElasticSearch for high-performance data retrieval
  • Umbraco 13 for content management and data structure
  • MCP Server for AI tool exposure and data transformation

Service Registration and Dependency Injection

The MCP server integrates seamlessly with ASP.NET Core's dependency injection system:

public static IServiceCollection AddMcpServices(
    this IServiceCollection services,
    IConfiguration configuration,
    bool enableMcp = false)
{
    // Register MCP configuration from appsettings
    services.Configure<McpConfiguration>(/* configuration section */);

    if (enableMcp)
    {
        // Configure MCP server with timeouts and HTTP transport
        services.AddMcpServer(options => {
            // Set initialization timeout, request scoping
            // Configure server info and version
        })
        .WithHttpTransport(transportOptions => {
            // Set idle timeout and session management
        })
        .WithToolsFromAssembly(); // Auto-discover tools
    }

    // Register domain-specific services
    services.AddScoped<IMcpServerService, McpServerService>();
    services.AddScoped<IMcpUtilityService, McpUtilityService>();
    // Additional services as needed...

    return services;
}

This pattern allows the MCP server to be conditionally enabled based on configuration while maintaining clean dependency management.

MCP Tool Examples: Technical Deep Dive

Key Implementation Patterns

Static Tool Methods with Service Provider Injection: Each MCP tool uses a static method decorated with [McpServerTool] but accesses services through dependency injection. This pattern ensures tools remain stateless while accessing rich application context.

Graceful Input Handling: Tools handle N8N client quirks like "null" strings and provide proper validation with meaningful error messages for both AI agents and developers.

Error Handling and Resilience: Every tool includes comprehensive error handling with structured error responses that help both developers and AI agents understand failure modes.

1. Category Search Tool - ElasticSearch Integration

The SearchCategoriesTool demonstrates how to build high-performance MCP tools that integrate with existing infrastructure:

[McpServerTool]
[Description("Search and filter categories based on various criteria")]
public static async Task<string> SearchCategories(
    [Description("Text search across names and descriptions")] string? searchTerm,
    [Description("Filter by status, etc.")] string? filters,
    [Description("Pagination parameters")] int limit, int offset)
{
    // Get required services via dependency injection
    using var scope = _serviceProvider.CreateScope();
    var searchService = scope.ServiceProvider.GetRequiredService<ISearchService>();

    // Validate and sanitize inputs
    limit = Math.Max(1, Math.Min(100, limit));
    // Handle "null" strings from N8N clients

    // Build ElasticSearch request with filters
    var searchRequest = new SearchRequest {
        // Configure index, search terms, pagination
        // Apply initial filters at query level
    };

    // Execute search and get raw results
    var searchResults = await searchService.ExecuteSearch(searchRequest);

    // Transform results for AI consumption
    var transformedResults = searchResults.Select(item => new {
        // Essential fields: id, name, description
        // Enhanced data: capabilities, compatibility, costs
        // URLs for user navigation
    });

    // Apply advanced filtering not available in ElasticSearch
    // Return structured JSON response
    return JsonSerializer.Serialize(response, jsonOptions);
}

This tool demonstrates:

Multi-Stage Filtering: The implementation uses ElasticSearch for primary filtering (fast, indexed operations) and applies additional MCP-specific filters post-query (complex business logic that doesn't translate well to search queries).

AI-Optimized Response Structure: Results are formatted specifically for AI consumption with consistent field names, proper data types, and metadata that helps agents make better decisions.

2. Vendor Landscape Analysis - Advanced Data Processing

The AnalyzeVendorLandscapeTool showcases complex data processing and competitive analysis:

[McpServerTool]
public static async Task<string> AnalyzeVendorLandscape(
    [Description("Primary vendor to analyze")] string focusVendorId,
    [Description("Categories to analyze within")] string? categoryIds,
    [Description("Analysis criteria")] string? comparisonPoints,
    [Description("Max competitors to include")] int maxCompetitors = 5)
{
    // Find relevant competitors using multi-criteria scoring
    var competitors = await FindRelevantCompetitors(focusVendor, categoryIds, maxCompetitors);

    // Build multi-dimensional comparison matrix
    var comparisonMatrix = await BuildComparisonAnalysis(focusVendor, competitors);

    // Generate structured insights using template-based approach
    var competitiveInsights = GenerateInsights(focusVendor, competitors, comparisonMatrix);

    // Return comprehensive analysis with metadata
    return SerializeAnalysisResponse(focusVendor, competitors, insights, metadata);
}

private static async Task<List<Vendor>> FindRelevantCompetitors(/*...*/)
{
    // Retrieve large vendor set from ElasticSearch
    // Apply relevance scoring algorithm:
    // - Category overlap (40% weight)
    // - Capability similarity (30% weight)
    // - Market status alignment (15% weight)
    // - Overall market presence (15% weight)

    // Return top-scored competitors above threshold
}

private static double CalculateRelevanceScore(/*...*/)
{
    // Multi-factor scoring combining:
    // - Shared technology categories
    // - Similar capability profiles
    // - Comparable market positioning
    // - Competitive market presence
}

This tool demonstrates several advanced patterns:

Multi-Criteria Relevance Scoring: The competitor finding algorithm uses weighted scoring across multiple dimensions to identify the most relevant competitors.

Template-Based Analysis: Rather than using LLMs for analysis, the system uses structured templates and data extraction patterns to generate insights consistently.

Performance Optimization: The tool retrieves up to 500 vendors from ElasticSearch but applies intelligent filtering to reduce processing overhead.

3. Inquiry Matching - Semantic Understanding Without Vectors

The MatchInquiryToCategoriesTool shows how to achieve semantic matching using ElasticSearch's built-in capabilities:

[McpServerTool]
public static async Task<string> MatchInquiryToCategories(
    [Description("Natural language inquiry to match")] string inquiry,
    [Description("Max results to return")] int maxResults = 5,
    [Description("Minimum relevance threshold")] double minimumScore = 0.1)
{
    // Multi-strategy ElasticSearch approach without vectors
    var searchQuery = BuildWeightedSearchQuery(inquiry);
    // Combines:
    // - Direct keyword matching (40% weight)
    // - Semantic similarity via more-like-this (30% weight)
    // - Capability matching (20% weight)
    // - Market context matching (10% weight)

    var searchResponse = await ExecuteSearchQuery(searchQuery, maxResults);

    // Process results with confidence scoring
    var scoredMatches = ProcessAndScoreResults(searchResponse, inquiry, minimumScore);

    // Return structured response with context
    return SerializeMatchResponse(scoredMatches, inquiry, processingMetadata);
}

private static BoolQuery BuildWeightedSearchQuery(string inquiry)
{
    // Create multi-match query with field boosting
    // Add more-like-this for semantic similarity
    // Include nested capability matching
    // Apply minimum match requirements
}

private static List<ScoredMatch> ProcessAndScoreResults(/*...*/)
{
    // Calculate relevance scores and confidence levels
    // Extract match reasons for transparency
    // Filter by minimum score threshold
    // Generate search suggestions for refinement
}

This approach achieves sophisticated semantic matching without the complexity of vector databases by leveraging ElasticSearch's built-in text analysis and similarity algorithms.

N8N Workflow Architecture: Single-Agent Orchestration

Why Single-Agent Won Over Multi-Agent

Initially, I designed a multi-agent system with specialized agents for each use case. However, after testing both approaches, the single-agent architecture proved superior:

Multi-Agent Challenges:

  • Context loss between agent handoffs
  • Coordination complexity and timing issues
  • Higher token usage due to delegation overhead
  • Debugging difficulties across multiple agents

Single-Agent Advantages:

  • Direct MCP tool access without delegation
  • Consistent context throughout conversations
  • Simpler debugging and maintenance
  • Lower operational costs
  • Better user experience with immediate responses

The Single-Agent System Prompt

The key to the single-agent approach is a comprehensive system prompt that handles all use cases defined by the business:

# Domain-Specific AI Assistant System Prompt Structure

## Role Definition

Define the assistant's specialized domain expertise and strict data boundaries
for maintaining consistency and avoiding hallucinations.

## Core Compliance Framework

1. **Data Source Restrictions** - Limit to authoritative platform data only
2. **Output Formatting** - Standardized link formats and response structures
3. **Consistency Requirements** - Deterministic outputs for identical inputs
4. **Interaction Limits** - Defined question and response count boundaries
5. **Quality Controls** - Specific validation rules for each use case

## Available Tool Integration

- Domain search tools with filtering capabilities
- Detailed entity retrieval functions
- Semantic matching and inquiry processing
- Competitive analysis and comparison tools
- [Additional tools based on domain requirements]

## Use Case Architecture

### Pattern 1: Entity Discovery

**Triggers**: [User describes needs or problems]
**Processing**: [Apply relevance scoring and ranking]
**Output**: [Structured results with reasoning]

### Pattern 2: Information Retrieval

**Triggers**: [Specific entity questions]
**Processing**: [Fuzzy matching and data enrichment]
**Output**: [Comprehensive entity details with links]

### Pattern 3: Recommendation Engine

**Triggers**: [Request for suggestions]
**Processing**: [Multi-criteria filtering and ranking]
**Output**: [Curated list with market indicators]

### Pattern 4: Comparative Analysis

**Triggers**: [Multi-entity comparison requests]
**Processing**: [Side-by-side analysis with alternatives]
**Output**: [Structured comparison with complete attribute sets]

Key N8N Configuration Patterns

Session Management: The workflow uses custom session keys to maintain conversation context across multiple interactions.

Memory Management: A 30-message context window provides sufficient history while controlling token usage.

Tool Integration: MCP tools are connected directly to the AI agent, enabling seamless tool invocation without custom HTTP requests.

Error Handling: The workflow includes error boundaries that gracefully handle MCP server issues and provide meaningful feedback to users.

Performance Optimizations and Production Considerations

ElasticSearch Query Optimization

The MCP tools use several performance optimization techniques:

// Optimized query structure for vendor search
var searchRequest = new VendorSearchRequest
{
    Index = elasticConfiguration.Value.VendorsIndex,
    SearchPhrase = searchTerm,
    Skip = offset,
    Take = limit,
    // Use minimal source filtering to reduce payload size
    SourceIncludes = new[] { "name", "description", "status", "categories", "imageUrl" },
    // Apply filters at query time rather than post-processing when possible
    Filters = new VendorFilters
    {
        Statuses = statusList,
        CategoryIds = categoryGuids
    }
};

// Use async enumeration for large result sets
await foreach (var vendor in ProcessVendorsAsync(searchResults.Results))
{
    yield return TransformVendorData(vendor);
}

Caching Strategy

The system implements multiple caching layers:

public class McpUtilityService : IMcpUtilityService
{
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _cacheTimeout = TimeSpan.FromMinutes(15);

    public async Task<CategoryModel?> GetCategoryByKeyAsync(Guid categoryKey)
    {
        var cacheKey = $"category_{categoryKey}";

        if (_cache.TryGetValue(cacheKey, out CategoryModel? cachedCategory))
        {
            return cachedCategory;
        }

        var category = await FetchCategoryFromElasticSearch(categoryKey);

        if (category != null)
        {
            _cache.Set(cacheKey, category, _cacheTimeout);
        }

        return category;
    }
}

Error Handling and Monitoring

Production-ready error handling includes structured logging and graceful degradation:

public static async Task<string> SearchVendors(/* parameters */)
{
    var stopwatch = Stopwatch.StartNew();

    try
    {
        // Core search implementation
        return await ExecuteSearch(searchRequest);
    }
    catch (ElasticsearchClientException ex)
    {
        // Log error and return structured failure response
        return CreateErrorResponse("Search service error", "Try reducing filters", 30);
    }
    catch (TimeoutException ex)
    {
        // Handle timeout with retry guidance
        return CreateErrorResponse("Search timeout", "Try more specific terms", 60);
    }
    finally
    {
        // Always log performance metrics
        LogPerformanceMetrics(stopwatch.Elapsed);
    }
}

private static string CreateErrorResponse(string error, string suggestion, int retryAfter)
{
    // Return structured JSON with error details and user guidance
}

Security and Data Integrity

Input Validation and Sanitization

Every MCP tool includes comprehensive input validation:

public static async Task<string> SearchCategories(
    string? searchTerm, int limit = 20, int offset = 0)
{
    // Validate and sanitize all inputs
    limit = Math.Max(1, Math.Min(100, limit));
    offset = Math.Max(0, offset);

    // Handle N8N client quirks ("null" strings)
    var cleanSearchTerm = SanitizeSearchTerm(searchTerm);

    // Validate search term length and content
    if (cleanSearchTerm?.Length > 500)
        return CreateErrorResponse("Search term too long");

    // Continue with validated inputs...
}

private static string? SanitizeSearchTerm(string? term)
{
    // Handle null, empty, and "null" string cases
    // Apply SQL injection and XSS protection
    // Return cleaned term or null
}

Authentication and Authorization

The MCP server includes middleware for request authentication:

public class McpAuthenticationMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // Validate MCP client authentication for MCP endpoints
        if (context.Request.Path.StartsWithSegments("/mcp"))
        {
            if (!await ValidateApiKey(context))
            {
                context.Response.StatusCode = 401;
                return;
            }
        }

        await next(context);
    }

    private async Task<bool> ValidateApiKey(HttpContext context)
    {
        // Extract and validate API key from headers
        // Check against configured valid keys
        // Log authentication attempts
    }
}

Lessons Learned and Best Practices

MCP Tool Design Principles

  1. Keep Tools Focused: Each tool should have a single, well-defined responsibility. The vendor search tool focuses only on search and filtering, while the landscape analysis tool handles competitive analysis.
  2. Embrace Structured Outputs: Use strongly-typed response models rather than free-form text. This makes the tools more reliable and easier to debug.
  3. Design for AI Consumption: AI agents work best with consistent, predictable data structures. Include metadata like confidence scores and processing times to help agents make better decisions.
  4. Handle Edge Cases Gracefully: Real-world data is messy. Design tools to handle missing data, malformed inputs, and service outages without breaking the user experience.

N8N Workflow Best Practices

  1. Single-Agent for Simplicity: Unless you have a compelling reason for multi-agent complexity, start with a single, well-designed agent that handles all use cases.
  2. Comprehensive System Prompts: Invest time in crafting detailed system prompts that cover all edge cases and use case variations. This prevents inconsistent behavior in production.
  3. Session Management: Proper session handling is crucial for maintaining context across conversations. Use meaningful session keys and appropriate memory windows.
  4. Error Boundaries: Design workflows to gracefully handle MCP server failures, API timeouts, and other external service issues.

Production Deployment Considerations

  1. Configuration Management: Use environment-specific configuration for MCP endpoints, API keys, and timeout settings.
  2. Monitoring and Alerting: Implement comprehensive logging and monitoring for both the MCP server and N8N workflows.
  3. Scalability Planning: Design the system to handle concurrent users and peak loads. Consider connection pooling and request queuing strategies.
  4. Security Hardening: Implement proper authentication, input validation, and rate limiting to protect against abuse.

Results and Impact

The final system delivers several measurable improvements over traditional approaches:

Performance & Quality Metrics:

  • Average response time: 2-3 seconds for complex queries
  • Zero hallucinations due to strict data boundaries
  • 100% consistent responses for identical inputs

Development Efficiency:

  • Faster development compared to custom RAG implementation
  • Simplified debugging with unified logging across MCP tools
  • Easy extension with new tools following established patterns

The Future of AI-Data Integration

Building a production-ready AI assistant with MCP tools and N8N workflows proved to be an excellent architectural choice. The combination provides the perfect balance of flexibility, performance, and maintainability for complex domain-specific AI applications.

The key insights from this implementation:

  • MCP simplifies AI-data integration without the complexity of vector databases
  • Single-agent architectures often outperform multi-agent systems for focused domains
  • ElasticSearch integration provides high-performance search without additional infrastructure
  • Comprehensive error handling is crucial for production reliability

For developers considering similar architectures, I recommend starting with a single MCP tool and single N8N agent, then expanding incrementally based on real user needs. The patterns and code examples in this post provide a solid foundation for building reliable, scalable AI assistants that maintain strict data integrity while delivering exceptional user experiences.

Contact

Drop me a line. I read everything and reply within a day.

Required fields are marked “(required)”.