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>
This commit is contained in:
304
docs/stories/sprint_5/story_5_8.md
Normal file
304
docs/stories/sprint_5/story_5_8.md
Normal file
@@ -0,0 +1,304 @@
|
||||
---
|
||||
story_id: story_5_8
|
||||
sprint_id: sprint_5
|
||||
phase: Phase 2 - Resources
|
||||
status: not_started
|
||||
priority: P1
|
||||
story_points: 5
|
||||
assignee: backend
|
||||
estimated_days: 2
|
||||
created_date: 2025-11-06
|
||||
dependencies: [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
|
||||
|
||||
```csharp
|
||||
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
|
||||
|
||||
```csharp
|
||||
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)
|
||||
|
||||
```csharp
|
||||
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
|
||||
|
||||
```json
|
||||
{
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379",
|
||||
"InstanceName": "colaflow:",
|
||||
"DefaultTTL": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user