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:
Yaojia Wang
2025-11-07 19:38:34 +01:00
parent d3ef2c1441
commit 48a8431e4f
43 changed files with 7003 additions and 0 deletions

View File

@@ -0,0 +1,431 @@
---
story_id: story_5_2
sprint_id: sprint_5
phase: Phase 1 - Foundation
status: not_started
priority: P0
story_points: 5
assignee: backend
estimated_days: 2
created_date: 2025-11-06
dependencies: []
---
# Story 5.2: API Key Management System
**Phase**: Phase 1 - Foundation (Week 1-2)
**Priority**: P0 CRITICAL
**Estimated Effort**: 5 Story Points (2 days)
## User Story
**As a** System Administrator
**I want** to securely manage API Keys for AI agents
**So that** only authorized AI agents can access ColaFlow through MCP protocol
## Business Value
Security is critical for M2. API Key management ensures only authorized AI agents can read/write ColaFlow data. Without this, MCP Server would be vulnerable to unauthorized access.
**Impact**:
- Prevents unauthorized AI access
- Enables multi-tenant security isolation
- Supports audit trail for all AI operations
- Foundation for rate limiting and IP whitelisting
## Acceptance Criteria
### AC1: API Key Generation
- [ ] Generate unique 40-character random API Keys
- [ ] Format: `cola_` prefix + 36 random characters (e.g., `cola_abc123...xyz`)
- [ ] API Keys are cryptographically secure (using `RandomNumberGenerator`)
- [ ] No duplicate keys in database (unique constraint)
### AC2: Secure Storage
- [ ] API Key hashed with BCrypt before storage
- [ ] Only first 8 characters stored as `key_prefix` for lookup
- [ ] Full hash stored in `key_hash` column
- [ ] Original API Key never stored (shown once at creation)
### AC3: API Key Validation
- [ ] Middleware validates `Authorization: Bearer <api_key>` header
- [ ] API Key lookup by prefix (fast index)
- [ ] Hash verification with BCrypt.Verify
- [ ] Expired keys rejected (check `expires_at`)
- [ ] Revoked keys rejected (check `revoked_at`)
- [ ] Update `last_used_at` and `usage_count` on successful validation
### AC4: Multi-Tenant Isolation
- [ ] API Key linked to specific `tenant_id`
- [ ] All MCP operations scoped to API Key's tenant
- [ ] Cross-tenant access blocked (Global Query Filters)
- [ ] TenantContext service extracts tenant from API Key
### AC5: CRUD API Endpoints
- [ ] `POST /api/mcp/keys` - Create API Key (returns plain key once)
- [ ] `GET /api/mcp/keys` - List tenant's API Keys (no key_hash)
- [ ] `GET /api/mcp/keys/{id}` - Get API Key details
- [ ] `PATCH /api/mcp/keys/{id}` - Update name/permissions
- [ ] `DELETE /api/mcp/keys/{id}` - Revoke API Key (soft delete)
### AC6: Testing
- [ ] Unit tests for key generation (uniqueness, format)
- [ ] Unit tests for BCrypt hashing/verification
- [ ] Integration tests for CRUD operations
- [ ] Integration tests for multi-tenant isolation
- [ ] Integration tests for authentication middleware
## Technical Design
### Database Schema
```sql
CREATE TABLE mcp_api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Security
key_hash VARCHAR(64) NOT NULL UNIQUE,
key_prefix VARCHAR(16) NOT NULL,
-- Metadata
name VARCHAR(255) NOT NULL,
description TEXT,
-- Permissions (JSONB for flexibility)
permissions JSONB NOT NULL DEFAULT '{"read": true, "write": false}',
ip_whitelist JSONB,
-- Status
status INT NOT NULL DEFAULT 1, -- 1=Active, 2=Revoked
last_used_at TIMESTAMP NULL,
usage_count BIGINT NOT NULL DEFAULT 0,
-- Lifecycle
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL, -- Default: 90 days
revoked_at TIMESTAMP NULL,
revoked_by UUID REFERENCES users(id),
-- Indexes
INDEX idx_key_prefix (key_prefix),
INDEX idx_tenant_user (tenant_id, user_id),
INDEX idx_expires_at (expires_at),
INDEX idx_status (status)
);
```
### Domain Model
```csharp
public class McpApiKey : AggregateRoot
{
public Guid TenantId { get; private set; }
public Guid UserId { get; private set; }
public string KeyHash { get; private set; }
public string KeyPrefix { get; private set; }
public string Name { get; private set; }
public string? Description { get; private set; }
public ApiKeyPermissions Permissions { get; private set; }
public List<string>? IpWhitelist { get; private set; }
public ApiKeyStatus Status { get; private set; }
public DateTime? LastUsedAt { get; private set; }
public long UsageCount { get; private set; }
public DateTime ExpiresAt { get; private set; }
public DateTime? RevokedAt { get; private set; }
public Guid? RevokedBy { get; private set; }
// Factory method
public static (McpApiKey apiKey, string plainKey) Create(
string name,
Guid tenantId,
Guid userId,
ApiKeyPermissions permissions,
int expirationDays = 90)
{
var plainKey = GenerateApiKey();
var keyHash = BCrypt.Net.BCrypt.HashPassword(plainKey);
var keyPrefix = plainKey.Substring(0, 12); // "cola_abc123..."
var apiKey = new McpApiKey
{
Id = Guid.NewGuid(),
TenantId = tenantId,
UserId = userId,
KeyHash = keyHash,
KeyPrefix = keyPrefix,
Name = name,
Permissions = permissions,
Status = ApiKeyStatus.Active,
ExpiresAt = DateTime.UtcNow.AddDays(expirationDays)
};
apiKey.AddDomainEvent(new ApiKeyCreatedEvent(apiKey.Id, name));
return (apiKey, plainKey);
}
public void Revoke(Guid revokedBy)
{
if (Status == ApiKeyStatus.Revoked)
throw new InvalidOperationException("API Key already revoked");
Status = ApiKeyStatus.Revoked;
RevokedAt = DateTime.UtcNow;
RevokedBy = revokedBy;
AddDomainEvent(new ApiKeyRevokedEvent(Id, Name));
}
public void RecordUsage()
{
LastUsedAt = DateTime.UtcNow;
UsageCount++;
}
private static string GenerateApiKey()
{
var bytes = new byte[27]; // 36 chars in base64
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(bytes);
return "cola_" + Convert.ToBase64String(bytes)
.Replace("+", "").Replace("/", "").Replace("=", "")
.Substring(0, 36);
}
}
public class ApiKeyPermissions
{
public bool Read { get; set; } = true;
public bool Write { get; set; } = false;
public List<string> AllowedResources { get; set; } = new();
public List<string> AllowedTools { get; set; } = new();
}
public enum ApiKeyStatus
{
Active = 1,
Revoked = 2
}
```
### Authentication Middleware
```csharp
public class McpApiKeyMiddleware
{
private readonly RequestDelegate _next;
public async Task InvokeAsync(
HttpContext context,
IMcpApiKeyService apiKeyService)
{
// Only apply to /mcp endpoints
if (!context.Request.Path.StartsWithSegments("/mcp"))
{
await _next(context);
return;
}
// Extract API Key from Authorization header
var apiKey = ExtractApiKey(context.Request.Headers);
if (string.IsNullOrEmpty(apiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new McpError
{
Code = McpErrorCode.Unauthorized,
Message = "Missing API Key"
});
return;
}
// Validate API Key
var validationResult = await apiKeyService.ValidateAsync(apiKey);
if (!validationResult.IsValid)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new McpError
{
Code = McpErrorCode.Unauthorized,
Message = validationResult.ErrorMessage
});
return;
}
// Set TenantContext for downstream handlers
context.Items["TenantId"] = validationResult.TenantId;
context.Items["ApiKeyId"] = validationResult.ApiKeyId;
context.Items["ApiKeyPermissions"] = validationResult.Permissions;
await _next(context);
}
}
```
## Tasks
### Task 1: Domain Layer - McpApiKey Aggregate (4 hours)
- [ ] Create `McpApiKey` entity class
- [ ] Create `ApiKeyPermissions` value object
- [ ] Implement `Create()` factory method with key generation
- [ ] Implement `Revoke()` method
- [ ] Create domain events (ApiKeyCreated, ApiKeyRevoked)
**Files to Create**:
- `ColaFlow.Modules.Mcp.Domain/Entities/McpApiKey.cs`
- `ColaFlow.Modules.Mcp.Domain/ValueObjects/ApiKeyPermissions.cs`
- `ColaFlow.Modules.Mcp.Domain/Events/ApiKeyCreatedEvent.cs`
- `ColaFlow.Modules.Mcp.Domain/Events/ApiKeyRevokedEvent.cs`
### Task 2: Infrastructure - Repository & Database (4 hours)
- [ ] Create `IMcpApiKeyRepository` interface
- [ ] Implement `McpApiKeyRepository` (EF Core)
- [ ] Create database migration for `mcp_api_keys` table
- [ ] Configure EF Core entity (fluent API)
- [ ] Add indexes (key_prefix, tenant_id)
**Files to Create**:
- `ColaFlow.Modules.Mcp.Domain/Repositories/IMcpApiKeyRepository.cs`
- `ColaFlow.Modules.Mcp.Infrastructure/Repositories/McpApiKeyRepository.cs`
- `ColaFlow.Modules.Mcp.Infrastructure/EntityConfigurations/McpApiKeyConfiguration.cs`
- `ColaFlow.Modules.Mcp.Infrastructure/Migrations/YYYYMMDDHHMMSS_AddMcpApiKeys.cs`
### Task 3: Application Layer - API Key Service (4 hours)
- [ ] Create `IMcpApiKeyService` interface
- [ ] Implement `CreateApiKeyAsync()` method
- [ ] Implement `ValidateAsync()` method (BCrypt verification)
- [ ] Implement `RevokeApiKeyAsync()` method
- [ ] Implement `GetApiKeysAsync()` query
**Files to Create**:
- `ColaFlow.Modules.Mcp.Application/Contracts/IMcpApiKeyService.cs`
- `ColaFlow.Modules.Mcp.Application/Services/McpApiKeyService.cs`
### Task 4: Authentication Middleware (3 hours)
- [ ] Create `McpApiKeyMiddleware` class
- [ ] Extract API Key from `Authorization: Bearer` header
- [ ] Validate API Key (call `IMcpApiKeyService.ValidateAsync`)
- [ ] Set TenantContext in HttpContext.Items
- [ ] Return 401 for invalid/expired keys
**Files to Create**:
- `ColaFlow.Modules.Mcp/Middleware/McpApiKeyMiddleware.cs`
- `ColaFlow.Modules.Mcp/Extensions/McpMiddlewareExtensions.cs`
### Task 5: REST API Endpoints (3 hours)
- [ ] Create `McpKeysController`
- [ ] `POST /api/mcp/keys` - Create API Key
- [ ] `GET /api/mcp/keys` - List API Keys
- [ ] `GET /api/mcp/keys/{id}` - Get API Key details
- [ ] `DELETE /api/mcp/keys/{id}` - Revoke API Key
- [ ] Add [Authorize] attribute (require JWT)
**Files to Create**:
- `ColaFlow.Modules.Mcp/Controllers/McpKeysController.cs`
- `ColaFlow.Modules.Mcp/DTOs/CreateApiKeyRequest.cs`
- `ColaFlow.Modules.Mcp/DTOs/ApiKeyResponse.cs`
### Task 6: Unit Tests (3 hours)
- [ ] Test API Key generation (format, uniqueness)
- [ ] Test BCrypt hashing/verification
- [ ] Test McpApiKey domain logic (Create, Revoke)
- [ ] Test validation logic (expired, revoked)
**Files to Create**:
- `ColaFlow.Modules.Mcp.Tests/Domain/McpApiKeyTests.cs`
- `ColaFlow.Modules.Mcp.Tests/Services/McpApiKeyServiceTests.cs`
### Task 7: Integration Tests (3 hours)
- [ ] Test CRUD API endpoints
- [ ] Test multi-tenant isolation (Tenant A cannot see Tenant B keys)
- [ ] Test authentication middleware (valid/invalid keys)
- [ ] Test expired key rejection
**Files to Create**:
- `ColaFlow.Modules.Mcp.Tests/Integration/McpApiKeyIntegrationTests.cs`
## Testing Strategy
### Unit Tests (Target: > 80% coverage)
- API Key generation logic
- BCrypt hashing/verification
- Domain entity methods (Create, Revoke)
- Validation logic (expiration, revocation)
### Integration Tests
- CRUD operations end-to-end
- Authentication middleware flow
- Multi-tenant isolation verification
- Database queries (repository)
### Manual Testing Checklist
- [ ] Create API Key → returns plain key once
- [ ] Use API Key in Authorization header → success
- [ ] Use expired API Key → 401 Unauthorized
- [ ] Use revoked API Key → 401 Unauthorized
- [ ] Tenant A cannot see Tenant B's keys
- [ ] API Key usage count increments
## Dependencies
**Prerequisites**:
- .NET 9 ASP.NET Core
- BCrypt.Net NuGet package
- EF Core 9
- PostgreSQL database
**Blocks**:
- All MCP operations (Story 5.5, 5.11) - Need API Key authentication
## Risks & Mitigation
| Risk | Impact | Probability | Mitigation |
|------|--------|-------------|------------|
| API Key brute force | High | Low | BCrypt hashing (slow), rate limiting |
| Key prefix collision | Medium | Very Low | 12-char prefix, crypto RNG |
| BCrypt performance | Medium | Low | Prefix lookup optimization |
| Key storage security | High | Low | Hash only, audit access |
## Definition of Done
- [ ] Code compiles without warnings
- [ ] All unit tests passing (> 80% coverage)
- [ ] All integration tests passing
- [ ] Code reviewed and approved
- [ ] Database migration created and tested
- [ ] XML documentation for public APIs
- [ ] Authentication middleware works end-to-end
- [ ] Multi-tenant isolation verified (100%)
- [ ] API Key never logged in plain text
## Notes
### Why This Story Matters
- **Security Foundation**: All MCP operations depend on secure authentication
- **Multi-Tenant Isolation**: Prevents cross-tenant data access
- **Audit Trail**: Tracks which AI agent performed which operation
- **Compliance**: Supports GDPR, SOC2 requirements
### Key Design Decisions
1. **BCrypt Hashing**: Slow by design, prevents brute force
2. **Prefix Lookup**: Fast lookup without full hash scan
3. **JSONB Permissions**: Flexible permission model for future
4. **Soft Delete**: Revocation preserves audit trail
### Security Best Practices
- API Key shown only once at creation
- BCrypt with default cost factor (10)
- 90-day expiration by default
- Rate limiting (to be added in Phase 6)
- IP whitelisting (to be added in Phase 4)
### Reference Materials
- Sprint 5 Plan: `docs/plans/sprint_5.md`
- Architecture Design: `docs/M2-MCP-SERVER-ARCHITECTURE.md`
- BCrypt.Net Documentation: https://github.com/BcryptNet/bcrypt.net