Files
ColaFlow/docs/stories/sprint_5/story_5_2.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

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_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

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 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