Files
ColaFlow/docs/stories/sprint_5/story_5_8.md
Yaojia Wang 48a8431e4f feat(backend): Implement MCP Protocol Handler (Story 5.1)
Implemented JSON-RPC 2.0 protocol handler for MCP communication, enabling AI agents to communicate with ColaFlow using the Model Context Protocol.

**Implementation:**
- JSON-RPC 2.0 data models (Request, Response, Error, ErrorCode)
- MCP protocol models (Initialize, Capabilities, ClientInfo, ServerInfo)
- McpProtocolHandler with method routing and error handling
- Method handlers: initialize, resources/list, tools/list, tools/call
- ASP.NET Core middleware for /mcp endpoint
- Service registration and dependency injection setup

**Testing:**
- 28 unit tests covering protocol parsing, validation, and error handling
- Integration tests for initialize handshake and error responses
- All tests passing with >80% coverage

**Changes:**
- Created ColaFlow.Modules.Mcp.Contracts project
- Created ColaFlow.Modules.Mcp.Domain project
- Created ColaFlow.Modules.Mcp.Application project
- Created ColaFlow.Modules.Mcp.Infrastructure project
- Created ColaFlow.Modules.Mcp.Tests project
- Registered MCP module in ColaFlow.API Program.cs
- Added /mcp endpoint via middleware

**Acceptance Criteria Met:**
 JSON-RPC 2.0 messages correctly parsed
 Request validation (jsonrpc: "2.0", method, params, id)
 Error responses conform to JSON-RPC 2.0 spec
 Invalid requests return proper error codes (-32700, -32600, -32601, -32602)
 MCP initialize method implemented
 Server capabilities returned (resources, tools, prompts)
 Protocol version negotiation works (1.0)
 Request routing to method handlers
 Unit test coverage > 80%
 All tests passing

**Story**: docs/stories/sprint_5/story_5_1.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 19:38:34 +01:00

8.6 KiB

story_id, sprint_id, phase, status, priority, story_points, assignee, estimated_days, created_date, dependencies
story_id sprint_id phase status priority story_points assignee estimated_days created_date dependencies
story_5_8 sprint_5 Phase 2 - Resources not_started P1 5 backend 2 2025-11-06
story_5_5

Story 5.8: Redis Caching Integration

Phase: Phase 2 - Resources (Week 3-4) Priority: P1 HIGH Estimated Effort: 5 Story Points (2 days)

User Story

As a System Architect I want Redis caching for frequently accessed MCP Resources So that AI agents get fast responses and database load is reduced

Business Value

Performance optimization delivers:

  • 30-50% faster response times (from 200ms to 80ms)
  • Reduced database load (80% cache hit rate target)
  • Better scalability (support more concurrent AI agents)
  • Improved user experience (faster AI responses)

Acceptance Criteria

AC1: Redis Integration

  • Redis client configured (StackExchange.Redis)
  • Connection pooling and retry logic
  • Health checks for Redis availability

AC2: Cache-Aside Pattern

  • Check cache before database query
  • Populate cache on cache miss
  • Return cached data on cache hit

AC3: TTL Configuration

  • projects.list: 5 minutes TTL
  • users.list: 5 minutes TTL
  • sprints.current: 2 minutes TTL
  • issues.search: 2 minutes TTL (cache by query params)

AC4: Cache Invalidation

  • Invalidate on data updates (domain events)
  • Manual cache clear API endpoint
  • Tenant-scoped cache keys

AC5: Performance Metrics

  • Track cache hit rate (target > 80%)
  • Track cache miss rate
  • Track response time improvement
  • Export metrics for monitoring

AC6: Testing

  • Unit tests for caching logic
  • Integration tests (cache hit/miss scenarios)
  • Performance benchmarks (with/without cache)

Technical Design

Cache Service Interface

public interface IMcpCacheService
{
    Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
    Task SetAsync<T>(string key, T value, TimeSpan ttl, CancellationToken cancellationToken = default);
    Task RemoveAsync(string key, CancellationToken cancellationToken = default);
    Task RemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default);
}

public class RedisMcpCacheService : IMcpCacheService
{
    private readonly IConnectionMultiplexer _redis;
    private readonly ILogger<RedisMcpCacheService> _logger;

    public async Task<T?> GetAsync<T>(string key, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        var value = await db.StringGetAsync(key);

        if (!value.HasValue)
        {
            _logger.LogDebug("Cache miss: {Key}", key);
            return default;
        }

        _logger.LogDebug("Cache hit: {Key}", key);
        return JsonSerializer.Deserialize<T>(value!);
    }

    public async Task SetAsync<T>(string key, T value, TimeSpan ttl, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        var json = JsonSerializer.Serialize(value);
        await db.StringSetAsync(key, json, ttl);
        _logger.LogDebug("Cache set: {Key} (TTL: {TTL}s)", key, ttl.TotalSeconds);
    }

    public async Task RemoveAsync(string key, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        await db.KeyDeleteAsync(key);
        _logger.LogDebug("Cache removed: {Key}", key);
    }

    public async Task RemoveByPatternAsync(string pattern, CancellationToken ct)
    {
        var server = _redis.GetServer(_redis.GetEndPoints().First());
        var keys = server.Keys(pattern: pattern);

        var db = _redis.GetDatabase();
        foreach (var key in keys)
        {
            await db.KeyDeleteAsync(key);
        }

        _logger.LogDebug("Cache removed by pattern: {Pattern}", pattern);
    }
}

Cached Resource Example

public class ProjectsListResource : IMcpResource
{
    private readonly IProjectRepository _projectRepo;
    private readonly ITenantContext _tenantContext;
    private readonly IMcpCacheService _cache;

    public async Task<McpResourceContent> GetContentAsync(
        McpResourceRequest request,
        CancellationToken cancellationToken)
    {
        var tenantId = _tenantContext.CurrentTenantId;
        var cacheKey = $"mcp:{tenantId}:projects.list";

        // Try cache first
        var cached = await _cache.GetAsync<ProjectListDto[]>(cacheKey, cancellationToken);
        if (cached != null)
        {
            return CreateResponse(cached);
        }

        // Cache miss - query database
        var projects = await _projectRepo.GetAllAsync(tenantId, cancellationToken);
        var projectDtos = projects.Select(MapToDto).ToArray();

        // Populate cache
        await _cache.SetAsync(cacheKey, projectDtos, TimeSpan.FromMinutes(5), cancellationToken);

        return CreateResponse(projectDtos);
    }
}

Cache Key Format

mcp:{tenantId}:{resourceUri}[:{params_hash}]

Examples:
- mcp:00000000-0000-0000-0000-000000000001:projects.list
- mcp:00000000-0000-0000-0000-000000000001:issues.search:abc123
- mcp:00000000-0000-0000-0000-000000000001:sprints.current

Cache Invalidation (Domain Events)

public class ProjectUpdatedEventHandler : INotificationHandler<ProjectUpdatedEvent>
{
    private readonly IMcpCacheService _cache;

    public async Task Handle(ProjectUpdatedEvent e, CancellationToken ct)
    {
        // Invalidate projects.list for this tenant
        await _cache.RemoveAsync($"mcp:{e.TenantId}:projects.list", ct);

        // Invalidate specific project
        await _cache.RemoveAsync($"mcp:{e.TenantId}:projects.get/{e.ProjectId}", ct);
    }
}

Tasks

Task 1: Redis Client Setup (2 hours)

  • Add StackExchange.Redis NuGet package
  • Configure connection string in appsettings.json
  • Create RedisMcpCacheService implementation
  • Add health checks

Files to Create:

  • ColaFlow.Modules.Mcp/Services/RedisMcpCacheService.cs

Task 2: Cache Service Interface (2 hours)

  • Create IMcpCacheService interface
  • Implement Get/Set/Remove methods
  • Add logging and metrics

Files to Create:

  • ColaFlow.Modules.Mcp/Contracts/IMcpCacheService.cs

Task 3: Update Resources with Caching (6 hours)

  • Update ProjectsListResource to use cache
  • Update UsersListResource to use cache
  • Update SprintsCurrentResource to use cache
  • Update IssuesSearchResource to use cache (with query hash)

Task 4: Cache Invalidation Event Handlers (3 hours)

  • ProjectCreated/Updated/Deleted → invalidate projects cache
  • EpicCreated/Updated/Deleted → invalidate issues cache
  • StoryCreated/Updated/Deleted → invalidate issues cache
  • TaskCreated/Updated/Deleted → invalidate issues cache

Files to Create:

  • ColaFlow.Modules.Mcp/EventHandlers/CacheInvalidationEventHandlers.cs

Task 5: Performance Metrics (2 hours)

  • Track cache hit/miss rates
  • Track response time improvement
  • Log metrics to structured logs

Task 6: Unit & Integration Tests (4 hours)

  • Test cache hit scenario
  • Test cache miss scenario
  • Test cache invalidation
  • Performance benchmarks (with/without cache)

Files to Create:

  • ColaFlow.Modules.Mcp.Tests/Services/RedisMcpCacheServiceTests.cs
  • ColaFlow.Modules.Mcp.Tests/Integration/CachingIntegrationTests.cs

Testing Strategy

Performance Benchmarks

Scenario: projects.list (100 projects)
- Without cache: 180ms (database query)
- With cache: 60ms (67% improvement)
- Cache hit rate: 85%

Integration Tests

  • Test cache hit after first request
  • Test cache miss on first request
  • Test cache invalidation on update
  • Test TTL expiration

Dependencies

Prerequisites:

  • Story 5.5 (Core Resources) - Resources to cache
  • Redis server running (Docker or cloud)

Optional: Not blocking for M2 MVP

Risks & Mitigation

Risk Impact Probability Mitigation
Redis unavailable Medium Low Fallback to database, circuit breaker
Cache inconsistency Medium Medium Short TTL, event-driven invalidation
Memory usage Low Low Set max memory limit in Redis

Definition of Done

  • Redis integration working
  • Cache hit rate > 80%
  • Response time improved by 30%+
  • Cache invalidation working
  • All tests passing
  • Performance benchmarks documented

Notes

Why This Story Matters

  • Performance: 30-50% faster response times
  • Scalability: Reduce database load by 80%
  • Cost: Lower database resource usage
  • User Experience: Faster AI responses

Redis Configuration

{
  "Redis": {
    "ConnectionString": "localhost:6379",
    "InstanceName": "colaflow:",
    "DefaultTTL": 300
  }
}