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>
14 KiB
14 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_2 | sprint_5 | Phase 1 - Foundation | not_started | P0 | 5 | backend | 2 | 2025-11-06 |
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_prefixfor lookup - Full hash stored in
key_hashcolumn - 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_atandusage_counton 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 detailsPATCH /api/mcp/keys/{id}- Update name/permissionsDELETE /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
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
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
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
McpApiKeyentity class - Create
ApiKeyPermissionsvalue 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.csColaFlow.Modules.Mcp.Domain/ValueObjects/ApiKeyPermissions.csColaFlow.Modules.Mcp.Domain/Events/ApiKeyCreatedEvent.csColaFlow.Modules.Mcp.Domain/Events/ApiKeyRevokedEvent.cs
Task 2: Infrastructure - Repository & Database (4 hours)
- Create
IMcpApiKeyRepositoryinterface - Implement
McpApiKeyRepository(EF Core) - Create database migration for
mcp_api_keystable - Configure EF Core entity (fluent API)
- Add indexes (key_prefix, tenant_id)
Files to Create:
ColaFlow.Modules.Mcp.Domain/Repositories/IMcpApiKeyRepository.csColaFlow.Modules.Mcp.Infrastructure/Repositories/McpApiKeyRepository.csColaFlow.Modules.Mcp.Infrastructure/EntityConfigurations/McpApiKeyConfiguration.csColaFlow.Modules.Mcp.Infrastructure/Migrations/YYYYMMDDHHMMSS_AddMcpApiKeys.cs
Task 3: Application Layer - API Key Service (4 hours)
- Create
IMcpApiKeyServiceinterface - Implement
CreateApiKeyAsync()method - Implement
ValidateAsync()method (BCrypt verification) - Implement
RevokeApiKeyAsync()method - Implement
GetApiKeysAsync()query
Files to Create:
ColaFlow.Modules.Mcp.Application/Contracts/IMcpApiKeyService.csColaFlow.Modules.Mcp.Application/Services/McpApiKeyService.cs
Task 4: Authentication Middleware (3 hours)
- Create
McpApiKeyMiddlewareclass - Extract API Key from
Authorization: Bearerheader - 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.csColaFlow.Modules.Mcp/Extensions/McpMiddlewareExtensions.cs
Task 5: REST API Endpoints (3 hours)
- Create
McpKeysController POST /api/mcp/keys- Create API KeyGET /api/mcp/keys- List API KeysGET /api/mcp/keys/{id}- Get API Key detailsDELETE /api/mcp/keys/{id}- Revoke API Key- Add [Authorize] attribute (require JWT)
Files to Create:
ColaFlow.Modules.Mcp/Controllers/McpKeysController.csColaFlow.Modules.Mcp/DTOs/CreateApiKeyRequest.csColaFlow.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.csColaFlow.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
- BCrypt Hashing: Slow by design, prevents brute force
- Prefix Lookup: Fast lookup without full hash scan
- JSONB Permissions: Flexible permission model for future
- 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