Files
ColaFlow/colaflow-api/MCP-SERVER-ARCHITECTURE.md
Yaojia Wang 6d2396f3c1
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
In progress
2025-11-04 10:31:50 +01:00

73 KiB

ColaFlow MCP Server Architecture Design

Version: 1.0 Date: 2025-11-04 Author: System Architect Status: Design Document


Table of Contents

  1. Background & Goals
  2. Architecture Overview
  3. Module Design
  4. Resources & Tools Design
  5. Diff Preview & Approval Mechanism
  6. Security & Permission Model
  7. Audit & Observability
  8. Database Design
  9. Technology Stack & Rationale
  10. Implementation Roadmap
  11. Risks & Mitigation

1. Background & Goals

1.1 Business Context

ColaFlow aims to be an AI-native project management system where AI tools (ChatGPT, Claude, Gemini) can:

  • Read project data (projects, issues, sprints, documents)
  • Write project data with human approval via diff preview
  • Automate workflows while maintaining human oversight and safety

1.2 Current System State

M1 Achievements:

  • Identity Module (User, Tenant, Multi-tenancy)
  • JWT Authentication & RBAC (TenantOwner, TenantAdmin, TenantMember, TenantGuest, AIAgent)
  • Clean Architecture (.NET 9.0, PostgreSQL, EF Core)
  • Domain-Driven Design with Aggregates, Value Objects, Domain Events

Current Tech Stack:

  • Backend: .NET 9.0, ASP.NET Core
  • Database: PostgreSQL + EF Core
  • Authentication: JWT Bearer
  • Architecture: Clean Architecture (Domain, Application, Infrastructure, API)

1.3 Technical Objectives

  1. MCP Server Integration: Expose ColaFlow resources and tools to AI clients via MCP protocol
  2. Safety First: All AI write operations require diff preview → human approval → commit
  3. Auditability: Complete audit trail for all AI actions
  4. Scalability: Designed for horizontal scaling, multi-tenant isolation
  5. Extensibility: Support multiple AI models and future integrations

1.4 Constraints

  • Must integrate with existing Clean Architecture
  • Must reuse Identity Module (User, Tenant, TenantRole)
  • Must maintain multi-tenant data isolation
  • Must comply with GDPR and enterprise security standards

2. Architecture Overview

2.1 High-Level Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        AI Client Layer                           │
│  ChatGPT, Claude, Gemini (via MCP SDK)                          │
└────────────────────────────┬─────────────────────────────────────┘
                             │ MCP Protocol (JSON-RPC over stdio/SSE)
┌────────────────────────────┴─────────────────────────────────────┐
│                    ColaFlow MCP Server                           │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │  MCP Protocol Handler (JSON-RPC Endpoints)                 │  │
│  │  - initialize, resources/list, resources/read              │  │
│  │  - tools/list, tools/call                                  │  │
│  │  - prompts/list, prompts/get                               │  │
│  └──────────────────────┬─────────────────────────────────────┘  │
│                         │                                         │
│  ┌──────────────────────┴─────────────────────────────────────┐  │
│  │  MCP Application Services                                  │  │
│  │  - ResourceService (read-only access)                      │  │
│  │  - ToolInvocationService (write with diff preview)         │  │
│  │  - DiffPreviewService (generate, store, approve)           │  │
│  │  - PromptTemplateService (AI prompt management)            │  │
│  └──────────────────────┬─────────────────────────────────────┘  │
│                         │                                         │
│  ┌──────────────────────┴─────────────────────────────────────┐  │
│  │  MCP Security Layer                                        │  │
│  │  - AIAgent Authentication (API Key, JWT)                   │  │
│  │  - Permission Validation (field-level, tenant-scoped)      │  │
│  │  - Rate Limiting                                           │  │
│  └──────────────────────┬─────────────────────────────────────┘  │
└─────────────────────────┼──────────────────────────────────────┘
                          │
┌─────────────────────────┴──────────────────────────────────────┐
│              ColaFlow Application Layer                        │
│  - ProjectManagement.Application (Epic, Story, Task, Sprint)   │
│  - Identity.Application (User, Tenant, Role)                   │
└─────────────────────────┬──────────────────────────────────────┘
                          │
┌─────────────────────────┴──────────────────────────────────────┐
│                   Domain Layer                                 │
│  - ProjectManagement.Domain (Aggregates, Entities, VOs)        │
│  - Identity.Domain (User, Tenant, Role)                        │
└─────────────────────────┬──────────────────────────────────────┘
                          │
┌─────────────────────────┴──────────────────────────────────────┐
│                Infrastructure Layer                            │
│  - PostgreSQL (Tenant-isolated data)                           │
│  - MCP Audit Logs (mcp_audit_logs)                             │
│  - Diff Preview Storage (mcp_diff_previews)                    │
└────────────────────────────────────────────────────────────────┘

2.2 MCP Protocol Overview

MCP (Model Context Protocol) is a standardized protocol for AI-application communication:

Key Concepts:

  1. Resources: Read-only data exposures (e.g., project://12345, issue://67890)
  2. Tools: AI-invokable functions (e.g., create_issue, update_status)
  3. Prompts: Reusable AI prompt templates
  4. Sampling: AI model invocation (future phase)

Transport:

  • stdio: Standard input/output for local processes (MCP SDK default)
  • SSE (Server-Sent Events): For web-based AI clients
  • JSON-RPC 2.0: Request/response protocol

Example Tool Call Flow:

1. AI Client → MCP Server: tools/list
2. MCP Server → AI Client: [create_issue, update_status, ...]
3. AI Client → MCP Server: tools/call { name: "create_issue", arguments: {...} }
4. MCP Server → AI Client: { diffPreview: {...}, approvalRequired: true }
5. Human approves diff
6. AI Client → MCP Server: tools/call { previewId: "abc123", approve: true }
7. MCP Server → AI Client: { success: true, issueId: "12345" }

2.3 Module Boundaries

New Modules:

  1. ColaFlow.Modules.Mcp.Domain

    • Aggregates: McpAgent, DiffPreview, AuditLog
    • Repositories: IMcpAgentRepository, IDiffPreviewRepository, IAuditLogRepository
  2. ColaFlow.Modules.Mcp.Application

    • Services: IResourceService, IToolInvocationService, IDiffPreviewService
    • Commands: ApproveDiffCommand, RejectDiffCommand, RollbackOperationCommand
    • Queries: ListResourcesQuery, GetDiffPreviewQuery
  3. ColaFlow.Modules.Mcp.Infrastructure

    • Persistence: McpDbContext, Repositories
    • Protocol: JsonRpcHandler, SseTransportHandler
    • Security: ApiKeyAuthenticationHandler
  4. ColaFlow.Modules.Mcp.API

    • Controllers: McpProtocolController (JSON-RPC endpoints)
    • Middleware: McpAuthenticationMiddleware, McpAuditMiddleware

3. Module Design

3.1 MCP.Domain Module

3.1.1 McpAgent Aggregate

namespace ColaFlow.Modules.Mcp.Domain.Aggregates.McpAgents;

/// <summary>
/// Represents an AI Agent registered to access ColaFlow via MCP
/// </summary>
public sealed class McpAgent : AggregateRoot
{
    // Identity
    public Guid Id { get; private set; }
    public TenantId TenantId { get; private set; }
    public string AgentName { get; private set; } // e.g., "ChatGPT for PM Team"
    public string AgentType { get; private set; } // e.g., "Claude", "ChatGPT", "Gemini"

    // Authentication
    public string ApiKeyHash { get; private set; } // Hashed API key
    public DateTime ApiKeyExpiresAt { get; private set; }
    public bool IsActive { get; private set; }

    // Permissions
    public McpPermissionLevel PermissionLevel { get; private set; }
    public List<string> AllowedResources { get; private set; } // ["projects.*", "issues.read"]
    public List<string> AllowedTools { get; private set; } // ["create_issue", "update_status"]

    // Audit
    public DateTime CreatedAt { get; private set; }
    public Guid CreatedBy { get; private set; }
    public DateTime? LastUsedAt { get; private set; }
    public int RequestCount { get; private set; }

    // Factory Method
    public static McpAgent Create(
        TenantId tenantId,
        string agentName,
        string agentType,
        string apiKeyHash,
        DateTime apiKeyExpiresAt,
        Guid createdBy)
    {
        // ... validation & domain event
    }

    // Business Methods
    public void RecordUsage() { /* ... */ }
    public void UpdatePermissions(McpPermissionLevel level, List<string> resources, List<string> tools) { /* ... */ }
    public void RevokeAccess() { /* ... */ }
    public void RegenerateApiKey(string newApiKeyHash, DateTime expiresAt) { /* ... */ }
}

public enum McpPermissionLevel
{
    ReadOnly = 1,        // Can only read resources
    WriteWithPreview = 2, // Can write with diff preview (requires approval)
    DirectWrite = 3       // Can write directly (dangerous, TenantOwner only)
}

3.1.2 DiffPreview Aggregate

namespace ColaFlow.Modules.Mcp.Domain.Aggregates.DiffPreviews;

/// <summary>
/// Represents a diff preview for AI-initiated write operations
/// </summary>
public sealed class DiffPreview : AggregateRoot
{
    public Guid Id { get; private set; }
    public TenantId TenantId { get; private set; }
    public Guid AgentId { get; private set; } // McpAgent.Id

    // Operation Details
    public string ToolName { get; private set; } // e.g., "create_issue"
    public string InputParametersJson { get; private set; } // JSON serialized

    // Diff Details
    public DiffOperation Operation { get; private set; } // Create, Update, Delete
    public string EntityType { get; private set; } // e.g., "Issue", "Project"
    public Guid? EntityId { get; private set; } // null for Create
    public string BeforeStateJson { get; private set; } // null for Create
    public string AfterStateJson { get; private set; }
    public string DiffJson { get; private set; } // Structured diff

    // Risk Assessment
    public RiskLevel RiskLevel { get; private set; } // Low, Medium, High
    public List<string> RiskReasons { get; private set; } // ["Deletes 50 tasks", "Changes sprint deadline"]

    // Approval Workflow
    public DiffPreviewStatus Status { get; private set; }
    public Guid? ApprovedBy { get; private set; }
    public DateTime? ApprovedAt { get; private set; }
    public Guid? RejectedBy { get; private set; }
    public DateTime? RejectedAt { get; private set; }
    public string? RejectionReason { get; private set; }

    // Rollback
    public bool IsCommitted { get; private set; }
    public Guid? CommittedEntityId { get; private set; }
    public DateTime? CommittedAt { get; private set; }
    public string? RollbackToken { get; private set; } // For event sourcing rollback

    // Timestamps
    public DateTime CreatedAt { get; private set; }
    public DateTime ExpiresAt { get; private set; } // Auto-reject after 24 hours

    // Factory & Methods
    public static DiffPreview Create(/* ... */) { /* ... */ }
    public void Approve(Guid approvedBy) { /* ... */ }
    public void Reject(Guid rejectedBy, string reason) { /* ... */ }
    public void MarkAsCommitted(Guid entityId) { /* ... */ }
}

public enum DiffOperation { Create, Update, Delete }
public enum RiskLevel { Low, Medium, High, Critical }
public enum DiffPreviewStatus { Pending, Approved, Rejected, Expired, Committed }

3.1.3 McpAuditLog Aggregate

namespace ColaFlow.Modules.Mcp.Domain.Aggregates.AuditLogs;

/// <summary>
/// Complete audit trail for all MCP operations
/// </summary>
public sealed class McpAuditLog : AggregateRoot
{
    public Guid Id { get; private set; }
    public TenantId TenantId { get; private set; }
    public Guid AgentId { get; private set; }

    // Request Details
    public string OperationType { get; private set; } // "resources/read", "tools/call"
    public string ResourceUri { get; private set; } // e.g., "project://12345"
    public string ToolName { get; private set; }
    public string InputParametersJson { get; private set; }

    // Response Details
    public bool IsSuccess { get; private set; }
    public string? ErrorMessage { get; private set; }
    public int? HttpStatusCode { get; private set; }

    // Diff Preview (if applicable)
    public Guid? DiffPreviewId { get; private set; }
    public DiffPreviewStatus? DiffStatus { get; private set; }

    // Performance
    public int DurationMs { get; private set; }

    // Context
    public string ClientIpAddress { get; private set; }
    public string UserAgent { get; private set; }
    public DateTime Timestamp { get; private set; }

    public static McpAuditLog Create(/* ... */) { /* ... */ }
}

3.2 MCP.Application Module

3.2.1 Resource Service

namespace ColaFlow.Modules.Mcp.Application.Services;

public interface IResourceService
{
    /// <summary>
    /// List all available resources for the current AI Agent
    /// </summary>
    Task<List<ResourceDescriptor>> ListResourcesAsync(
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Read a specific resource
    /// </summary>
    Task<ResourceContent> ReadResourceAsync(
        string resourceUri, // e.g., "project://abc-123"
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default);
}

public record ResourceDescriptor(
    string Uri,
    string Name,
    string Description,
    string MimeType); // "application/json", "text/plain"

public record ResourceContent(
    string Uri,
    string Content, // JSON or text
    string MimeType);

Implementation:

  • Reads from ProjectManagement module (via Application layer)
  • Applies field-level permissions (hide sensitive fields)
  • Tenant-scoped queries
  • Returns JSON representations

3.2.2 Tool Invocation Service

namespace ColaFlow.Modules.Mcp.Application.Services;

public interface IToolInvocationService
{
    /// <summary>
    /// List all available tools for the current AI Agent
    /// </summary>
    Task<List<ToolDescriptor>> ListToolsAsync(
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Invoke a tool (generates diff preview for write operations)
    /// </summary>
    Task<ToolInvocationResult> InvokeToolAsync(
        string toolName,
        Dictionary<string, object> arguments,
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default);
}

public record ToolDescriptor(
    string Name,
    string Description,
    JsonSchema InputSchema);

public record ToolInvocationResult
{
    public bool RequiresApproval { get; init; }
    public Guid? DiffPreviewId { get; init; }
    public DiffPreviewDto? DiffPreview { get; init; }
    public object? Result { get; init; } // For read-only tools
    public bool IsSuccess { get; init; }
    public string? ErrorMessage { get; init; }
}

3.2.3 Diff Preview Service

namespace ColaFlow.Modules.Mcp.Application.Services;

public interface IDiffPreviewService
{
    /// <summary>
    /// Generate a diff preview for a proposed write operation
    /// </summary>
    Task<DiffPreview> GenerateDiffAsync(
        string toolName,
        Dictionary<string, object> arguments,
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Get diff preview by ID
    /// </summary>
    Task<DiffPreview?> GetDiffPreviewAsync(
        Guid previewId,
        TenantId tenantId,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Approve and commit a diff preview
    /// </summary>
    Task<CommitResult> ApproveAndCommitAsync(
        Guid previewId,
        Guid approvedBy,
        TenantId tenantId,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Reject a diff preview
    /// </summary>
    Task RejectAsync(
        Guid previewId,
        Guid rejectedBy,
        string reason,
        TenantId tenantId,
        CancellationToken cancellationToken = default);
}

public record CommitResult(
    bool IsSuccess,
    Guid? EntityId,
    string? ErrorMessage);

Diff Generation Algorithm:

  1. Deserialize tool arguments
  2. Load current state (if updating)
  3. Apply arguments to domain model (dry-run)
  4. Generate JSON diff (before/after)
  5. Calculate risk level:
    • Low: Single field updates, comments
    • Medium: Status changes, assignments
    • High: Deletions, sprint changes, bulk operations
    • Critical: Data exports, permission changes
  6. Store DiffPreview entity
  7. Return preview to AI client

3.3 MCP.Infrastructure Module

3.3.1 JSON-RPC Protocol Handler

namespace ColaFlow.Modules.Mcp.Infrastructure.Protocol;

public class JsonRpcHandler
{
    private readonly IResourceService _resourceService;
    private readonly IToolInvocationService _toolService;

    public async Task<JsonRpcResponse> HandleRequestAsync(
        JsonRpcRequest request,
        TenantId tenantId,
        Guid agentId,
        CancellationToken cancellationToken = default)
    {
        return request.Method switch
        {
            "initialize" => await HandleInitializeAsync(request),
            "resources/list" => await HandleResourcesListAsync(tenantId, agentId),
            "resources/read" => await HandleResourceReadAsync(request, tenantId, agentId),
            "tools/list" => await HandleToolsListAsync(tenantId, agentId),
            "tools/call" => await HandleToolCallAsync(request, tenantId, agentId),
            _ => JsonRpcResponse.Error(request.Id, -32601, "Method not found")
        };
    }
}

3.3.2 Database Schema (EF Core Configurations)

// McpAgentConfiguration.cs
public class McpAgentConfiguration : IEntityTypeConfiguration<McpAgent>
{
    public void Configure(EntityTypeBuilder<McpAgent> builder)
    {
        builder.ToTable("mcp_agents", "mcp");
        builder.HasKey(a => a.Id);

        builder.Property(a => a.AgentName).IsRequired().HasMaxLength(200);
        builder.Property(a => a.ApiKeyHash).IsRequired().HasMaxLength(512);

        // Tenant isolation
        builder.HasQueryFilter(a => a.TenantId == /* current tenant */);

        // Indexes
        builder.HasIndex(a => new { a.TenantId, a.IsActive });
    }
}

4. Resources & Tools Design

4.1 Resource Definitions

4.1.1 Project Resources

{
  "resources": [
    {
      "uri": "projects://list",
      "name": "All Projects",
      "description": "List all accessible projects",
      "mimeType": "application/json"
    },
    {
      "uri": "project://{projectId}",
      "name": "Project Details",
      "description": "Get detailed information about a specific project",
      "mimeType": "application/json"
    },
    {
      "uri": "project://{projectId}/issues",
      "name": "Project Issues",
      "description": "List all issues in a project",
      "mimeType": "application/json"
    }
  ]
}

Example Resource Response:

{
  "uri": "project://abc-123",
  "content": {
    "id": "abc-123",
    "name": "ColaFlow MVP",
    "description": "Build initial MVP version",
    "status": "Active",
    "owner": {
      "id": "user-456",
      "name": "John Doe",
      "email": "john@example.com"
    },
    "startDate": "2025-11-01",
    "endDate": "2025-12-31",
    "issueCount": 45,
    "completedIssueCount": 12
  },
  "mimeType": "application/json"
}

4.1.2 Issue Resources

{
  "resources": [
    {
      "uri": "issues://search?query={query}",
      "name": "Search Issues",
      "description": "Search issues by title, description, or tags"
    },
    {
      "uri": "issue://{issueId}",
      "name": "Issue Details",
      "description": "Get detailed information about a specific issue"
    }
  ]
}

4.1.3 Sprint Resources

{
  "resources": [
    {
      "uri": "sprints://current",
      "name": "Current Sprint",
      "description": "Get the currently active sprint"
    },
    {
      "uri": "sprint://{sprintId}",
      "name": "Sprint Details",
      "description": "Get detailed information about a specific sprint"
    }
  ]
}

4.2 Tool Definitions

4.2.1 create_issue Tool

{
  "name": "create_issue",
  "description": "Create a new issue in a project",
  "inputSchema": {
    "type": "object",
    "properties": {
      "projectId": {
        "type": "string",
        "description": "The project ID where the issue will be created"
      },
      "title": {
        "type": "string",
        "description": "Issue title (required)"
      },
      "description": {
        "type": "string",
        "description": "Detailed description of the issue"
      },
      "issueType": {
        "type": "string",
        "enum": ["Story", "Task", "Bug", "Epic"],
        "description": "Type of issue"
      },
      "priority": {
        "type": "string",
        "enum": ["Low", "Medium", "High", "Critical"],
        "default": "Medium"
      },
      "assigneeId": {
        "type": "string",
        "description": "User ID to assign the issue to (optional)"
      },
      "tags": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Tags for categorization"
      }
    },
    "required": ["projectId", "title", "issueType"]
  }
}

Diff Preview Example:

{
  "previewId": "preview-123",
  "operation": "create",
  "entityType": "Issue",
  "changes": {
    "before": null,
    "after": {
      "title": "Implement MCP Server authentication",
      "description": "Add API key authentication for AI agents",
      "issueType": "Task",
      "priority": "High",
      "projectId": "project-abc",
      "assigneeId": "user-456",
      "tags": ["backend", "security"]
    }
  },
  "riskLevel": "Low",
  "riskReasons": [],
  "requiresApproval": true,
  "expiresAt": "2025-11-05T10:00:00Z"
}

4.2.2 update_issue_status Tool

{
  "name": "update_issue_status",
  "description": "Update the status of an existing issue",
  "inputSchema": {
    "type": "object",
    "properties": {
      "issueId": {
        "type": "string",
        "description": "The issue ID to update"
      },
      "status": {
        "type": "string",
        "enum": ["ToDo", "InProgress", "Review", "Done"],
        "description": "New status"
      },
      "comment": {
        "type": "string",
        "description": "Optional comment explaining the status change"
      }
    },
    "required": ["issueId", "status"]
  }
}

Diff Preview Example:

{
  "previewId": "preview-456",
  "operation": "update",
  "entityType": "Issue",
  "entityId": "issue-789",
  "changes": {
    "before": {
      "status": "InProgress",
      "updatedAt": "2025-11-03T15:30:00Z"
    },
    "after": {
      "status": "Done",
      "updatedAt": "2025-11-04T10:00:00Z"
    }
  },
  "diff": [
    {
      "field": "status",
      "oldValue": "InProgress",
      "newValue": "Done"
    }
  ],
  "riskLevel": "Medium",
  "riskReasons": ["Marks issue as complete without code review"],
  "requiresApproval": true
}

4.2.3 assign_issue Tool

{
  "name": "assign_issue",
  "description": "Assign an issue to a team member",
  "inputSchema": {
    "type": "object",
    "properties": {
      "issueId": { "type": "string" },
      "assigneeId": { "type": "string" },
      "notifyAssignee": { "type": "boolean", "default": true }
    },
    "required": ["issueId", "assigneeId"]
  }
}

4.2.4 log_decision Tool

{
  "name": "log_decision",
  "description": "Log an architectural or product decision",
  "inputSchema": {
    "type": "object",
    "properties": {
      "projectId": { "type": "string" },
      "title": { "type": "string" },
      "decision": { "type": "string" },
      "rationale": { "type": "string" },
      "alternatives": {
        "type": "array",
        "items": { "type": "string" }
      },
      "tags": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["projectId", "title", "decision"]
  }
}

4.3 Prompt Templates

{
  "prompts": [
    {
      "name": "daily_standup",
      "description": "Generate daily standup report",
      "arguments": [
        {
          "name": "date",
          "description": "Report date (YYYY-MM-DD)",
          "required": false
        }
      ],
      "template": "Generate a daily standup report for {{date}}. Include:\n1. Completed tasks\n2. In-progress tasks\n3. Blockers\n4. Upcoming priorities"
    },
    {
      "name": "sprint_planning",
      "description": "Generate sprint planning summary",
      "template": "Analyze the backlog and generate sprint planning recommendations:\n1. Suggested stories for next sprint\n2. Estimated story points\n3. Team capacity analysis\n4. Risk assessment"
    }
  ]
}

5. Diff Preview & Approval Mechanism

5.1 Diff Generation Flow

┌──────────────┐
│  AI Client   │
│  (ChatGPT)   │
└──────┬───────┘
       │ tools/call { name: "create_issue", arguments: {...} }
       ▼
┌────────────────────────────────────────────────────────┐
│  ToolInvocationService                                 │
│  ┌──────────────────────────────────────────────────┐  │
│  │ 1. Validate permissions (can use create_issue?)  │  │
│  │ 2. Validate arguments (schema validation)        │  │
│  │ 3. Call DiffPreviewService.GenerateDiffAsync()   │  │
│  └──────────────────────────────────────────────────┘  │
└────────────────────────┬───────────────────────────────┘
                         │
                         ▼
┌────────────────────────────────────────────────────────┐
│  DiffPreviewService.GenerateDiffAsync()                │
│  ┌──────────────────────────────────────────────────┐  │
│  │ 1. Load current state (if update/delete)         │  │
│  │ 2. Create domain entity (dry-run, no persist)    │  │
│  │ 3. Generate JSON diff (JsonDiffPatch library)    │  │
│  │ 4. Calculate risk level                          │  │
│  │    - Check deletion count                        │  │
│  │    - Check critical field changes                │  │
│  │    - Check impact scope                          │  │
│  │ 5. Create DiffPreview aggregate                  │  │
│  │ 6. Persist to mcp_diff_previews table            │  │
│  │ 7. Return DiffPreview to client                  │  │
│  └──────────────────────────────────────────────────┘  │
└────────────────────────┬───────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────┐
│  Response to AI Client                               │
│  {                                                   │
│    "requiresApproval": true,                         │
│    "previewId": "preview-123",                       │
│    "diffPreview": {                                  │
│      "operation": "create",                          │
│      "entityType": "Issue",                          │
│      "changes": { before: null, after: {...} },      │
│      "riskLevel": "Low"                              │
│    }                                                 │
│  }                                                   │
└──────────────────────────────────────────────────────┘

5.2 Approval Workflow

┌──────────────────────────────────────────────────────────────┐
│  Human User (via Web UI or CLI)                             │
│  - Views diff preview in Admin Dashboard                    │
│  - Reviews changes (before/after comparison)                │
│  - Decides: Approve or Reject                               │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼ (Approve)
┌────────────────────────────────────────────────────────────┐
│  DiffPreviewService.ApproveAndCommitAsync()                │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 1. Load DiffPreview by ID                            │  │
│  │ 2. Validate status == Pending                        │  │
│  │ 3. Validate not expired                              │  │
│  │ 4. Mark as Approved                                  │  │
│  │ 5. Execute actual operation:                         │  │
│  │    - Call Application layer command                  │  │
│  │    - e.g., CreateIssueCommand                        │  │
│  │ 6. Persist entity to database                        │  │
│  │ 7. Update DiffPreview.IsCommitted = true             │  │
│  │ 8. Generate rollback token (if needed)               │  │
│  │ 9. Create McpAuditLog entry                          │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘
                         │
                         ▼ (Reject)
┌────────────────────────────────────────────────────────────┐
│  DiffPreviewService.RejectAsync()                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 1. Load DiffPreview by ID                            │  │
│  │ 2. Mark as Rejected                                  │  │
│  │ 3. Store rejection reason                            │  │
│  │ 4. Create McpAuditLog entry                          │  │
│  │ 5. Notify AI agent (optional)                        │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘

5.3 Risk Level Calculation

public class RiskCalculator
{
    public RiskLevel CalculateRisk(DiffOperation operation, string entityType, object changes)
    {
        var riskScore = 0;
        var reasons = new List<string>();

        // High risk: Deletions
        if (operation == DiffOperation.Delete)
        {
            riskScore += 50;
            reasons.Add("Deletion operation");
        }

        // Medium risk: Status changes
        if (changes.ContainsField("status"))
        {
            riskScore += 20;
            reasons.Add("Status change");
        }

        // High risk: Critical entities
        if (entityType == "Sprint" || entityType == "Epic")
        {
            riskScore += 30;
            reasons.Add($"Critical entity type: {entityType}");
        }

        // High risk: Bulk operations
        if (changes.AffectedEntityCount > 10)
        {
            riskScore += 40;
            reasons.Add($"Affects {changes.AffectedEntityCount} entities");
        }

        return riskScore switch
        {
            >= 80 => RiskLevel.Critical,
            >= 50 => RiskLevel.High,
            >= 20 => RiskLevel.Medium,
            _ => RiskLevel.Low
        };
    }
}

5.4 Rollback Mechanism

Strategy: Event Sourcing + Snapshot

public interface IRollbackService
{
    /// <summary>
    /// Rollback a committed diff preview
    /// </summary>
    Task<RollbackResult> RollbackAsync(
        Guid previewId,
        Guid requestedBy,
        string reason,
        CancellationToken cancellationToken = default);
}

public class RollbackService : IRollbackService
{
    public async Task<RollbackResult> RollbackAsync(/* ... */)
    {
        // 1. Load DiffPreview
        var preview = await _repository.GetByIdAsync(previewId);

        // 2. Validate can rollback
        if (!preview.IsCommitted)
            return RollbackResult.Failure("Not committed yet");

        if (preview.Operation == DiffOperation.Delete)
            return RollbackResult.Failure("Cannot rollback deletions");

        // 3. Load entity
        var entity = await LoadEntityAsync(preview.EntityType, preview.CommittedEntityId);

        // 4. Apply reverse changes
        if (preview.Operation == DiffOperation.Create)
        {
            // Soft delete
            entity.Delete();
        }
        else if (preview.Operation == DiffOperation.Update)
        {
            // Restore from BeforeStateJson
            var beforeState = JsonSerializer.Deserialize(preview.BeforeStateJson);
            entity.ApplyState(beforeState);
        }

        // 5. Persist
        await _unitOfWork.CommitAsync();

        // 6. Audit
        await _auditLog.LogRollbackAsync(previewId, requestedBy, reason);

        return RollbackResult.Success();
    }
}

6. Security & Permission Model

6.1 Authentication Mechanisms

6.1.1 API Key Authentication (Primary)

public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // 1. Extract API key from header
        if (!Request.Headers.TryGetValue("X-MCP-API-Key", out var apiKeyHeaderValues))
            return AuthenticateResult.Fail("Missing API Key");

        var apiKey = apiKeyHeaderValues.FirstOrDefault();

        // 2. Hash and lookup in database
        var hashedKey = HashApiKey(apiKey);
        var agent = await _agentRepository.GetByApiKeyHashAsync(hashedKey);

        if (agent == null || !agent.IsActive)
            return AuthenticateResult.Fail("Invalid or inactive API Key");

        // 3. Check expiration
        if (agent.ApiKeyExpiresAt < DateTime.UtcNow)
            return AuthenticateResult.Fail("API Key expired");

        // 4. Create claims principal
        var claims = new[]
        {
            new Claim("agent_id", agent.Id.ToString()),
            new Claim("tenant_id", agent.TenantId.Value.ToString()),
            new Claim("agent_type", agent.AgentType),
            new Claim("permission_level", agent.PermissionLevel.ToString()),
            new Claim(ClaimTypes.Role, "AIAgent")
        };

        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        // 5. Record usage
        agent.RecordUsage();
        await _agentRepository.UpdateAsync(agent);

        return AuthenticateResult.Success(ticket);
    }
}

API Key Format:

mcp_<environment>_<random32chars>
Example: mcp_prod_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

API Key Storage:

  • Never store plain text API keys
  • Use BCrypt hashing (like passwords)
  • Store: ApiKeyHash, ApiKeyExpiresAt, IsActive

6.1.2 JWT Authentication (Fallback for Web Clients)

// Reuse existing JWT authentication from Identity module
// Add "AIAgent" role claim for AI-authenticated users
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = "ApiKeyOrJwt";
})
.AddPolicyScheme("ApiKeyOrJwt", "API Key or JWT", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        if (context.Request.Headers.ContainsKey("X-MCP-API-Key"))
            return "ApiKey";
        return JwtBearerDefaults.AuthenticationScheme;
    };
})
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKey", null)
.AddJwtBearer(/* ... existing JWT config ... */);

6.2 Authorization & Permissions

6.2.1 Permission Levels

public enum McpPermissionLevel
{
    /// <summary>
    /// Read-only access to resources
    /// - Can call resources/list, resources/read
    /// - Cannot call any tools
    /// </summary>
    ReadOnly = 1,

    /// <summary>
    /// Read + Write with preview (DEFAULT for AI Agents)
    /// - Can read all resources
    /// - Can call tools, but generates diff preview
    /// - All writes require human approval
    /// </summary>
    WriteWithPreview = 2,

    /// <summary>
    /// Direct write (DANGEROUS, TenantOwner approval required)
    /// - Can read all resources
    /// - Can write directly without approval
    /// - Should be used only for trusted automation
    /// </summary>
    DirectWrite = 3
}

6.2.2 Field-Level Permissions

public class FieldLevelPermissionFilter
{
    private static readonly HashSet<string> SensitiveFields = new()
    {
        "passwordHash", "apiKeyHash", "salary", "ssn", "creditCard"
    };

    public object FilterSensitiveFields(object entity, TenantRole role)
    {
        // AIAgent role: Hide all sensitive fields
        if (role == TenantRole.AIAgent)
        {
            var json = JsonSerializer.Serialize(entity);
            var document = JsonDocument.Parse(json);

            foreach (var sensitiveField in SensitiveFields)
            {
                RemoveField(document, sensitiveField);
            }

            return JsonSerializer.Deserialize(document.RootElement);
        }

        return entity; // Human users: no filtering
    }
}

6.2.3 Resource-Level Permissions

public class ResourcePermissionValidator
{
    public bool CanAccessResource(string resourceUri, McpAgent agent)
    {
        // Check if agent has explicit permission
        if (agent.AllowedResources.Contains("*"))
            return true; // Full access

        // Check wildcard patterns
        foreach (var pattern in agent.AllowedResources)
        {
            if (MatchesPattern(resourceUri, pattern))
                return true;
        }

        return false;
    }

    private bool MatchesPattern(string uri, string pattern)
    {
        // Support wildcards:
        // "projects.*" matches "projects://list", "project://123"
        // "issues.read" matches "issue://456" (read-only)
        // "issues.write" matches tool calls

        var regex = new Regex(pattern.Replace("*", ".*"));
        return regex.IsMatch(uri);
    }
}

6.3 Rate Limiting

public class McpRateLimiter
{
    private readonly IDistributedCache _cache; // Redis

    public async Task<bool> CheckRateLimitAsync(Guid agentId, string operation)
    {
        var key = $"ratelimit:agent:{agentId}:{operation}";
        var current = await _cache.GetStringAsync(key);

        var limits = operation switch
        {
            "resources/read" => (100, TimeSpan.FromMinutes(1)), // 100 req/min
            "tools/call" => (10, TimeSpan.FromMinutes(1)),      // 10 req/min
            _ => (50, TimeSpan.FromMinutes(1))
        };

        var count = int.Parse(current ?? "0");
        if (count >= limits.Item1)
            return false; // Rate limit exceeded

        await _cache.SetStringAsync(
            key,
            (count + 1).ToString(),
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = limits.Item2
            });

        return true;
    }
}

6.4 Tenant Isolation

// Global query filter (already exists in Identity module)
public class McpDbContext : DbContext
{
    private readonly ITenantContext _tenantContext;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Apply tenant filter to all MCP entities
        modelBuilder.Entity<McpAgent>()
            .HasQueryFilter(e => e.TenantId == _tenantContext.TenantId);

        modelBuilder.Entity<DiffPreview>()
            .HasQueryFilter(e => e.TenantId == _tenantContext.TenantId);

        modelBuilder.Entity<McpAuditLog>()
            .HasQueryFilter(e => e.TenantId == _tenantContext.TenantId);
    }
}

7. Audit & Observability

7.1 Audit Log Schema

-- mcp.mcp_audit_logs
CREATE TABLE mcp.mcp_audit_logs (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    agent_id UUID NOT NULL,

    -- Request details
    operation_type VARCHAR(100) NOT NULL, -- 'resources/read', 'tools/call'
    resource_uri VARCHAR(500),
    tool_name VARCHAR(200),
    input_parameters_json JSONB,

    -- Response details
    is_success BOOLEAN NOT NULL,
    error_message TEXT,
    http_status_code INTEGER,

    -- Diff preview (if applicable)
    diff_preview_id UUID,
    diff_status VARCHAR(50), -- 'Pending', 'Approved', 'Rejected'

    -- Performance
    duration_ms INTEGER NOT NULL,

    -- Context
    client_ip_address VARCHAR(50),
    user_agent TEXT,
    timestamp TIMESTAMP NOT NULL DEFAULT NOW(),

    -- Indexes
    CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES identity.tenants(id),
    CONSTRAINT fk_agent FOREIGN KEY (agent_id) REFERENCES mcp.mcp_agents(id)
);

CREATE INDEX idx_audit_tenant_timestamp ON mcp.mcp_audit_logs(tenant_id, timestamp DESC);
CREATE INDEX idx_audit_agent_timestamp ON mcp.mcp_audit_logs(agent_id, timestamp DESC);
CREATE INDEX idx_audit_operation ON mcp.mcp_audit_logs(operation_type, timestamp DESC);

7.2 Audit Middleware

public class McpAuditMiddleware
{
    private readonly RequestDelegate _next;

    public async Task InvokeAsync(HttpContext context, IMcpAuditLogRepository auditRepo)
    {
        var startTime = Stopwatch.GetTimestamp();

        // Capture request
        var agentId = context.User.FindFirst("agent_id")?.Value;
        var tenantId = context.User.FindFirst("tenant_id")?.Value;
        var requestBody = await CaptureRequestBodyAsync(context.Request);

        // Execute request
        Exception? exception = null;
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            exception = ex;
            throw;
        }
        finally
        {
            // Calculate duration
            var elapsedMs = (int)((Stopwatch.GetTimestamp() - startTime) * 1000.0 / Stopwatch.Frequency);

            // Create audit log
            var auditLog = McpAuditLog.Create(
                tenantId: Guid.Parse(tenantId),
                agentId: Guid.Parse(agentId),
                operationType: context.Request.Path,
                resourceUri: ExtractResourceUri(requestBody),
                toolName: ExtractToolName(requestBody),
                inputParametersJson: requestBody,
                isSuccess: exception == null && context.Response.StatusCode < 400,
                errorMessage: exception?.Message,
                httpStatusCode: context.Response.StatusCode,
                durationMs: elapsedMs,
                clientIpAddress: context.Connection.RemoteIpAddress?.ToString(),
                userAgent: context.Request.Headers.UserAgent.ToString());

            await auditRepo.AddAsync(auditLog);
        }
    }
}

7.3 Observability Metrics

Key Metrics to Track:

  1. Request Volume

    • Total MCP requests per minute
    • Requests by operation type (resources/read, tools/call)
    • Requests by agent
  2. Diff Preview Metrics

    • Diff previews generated per hour
    • Approval rate (approved / total)
    • Rejection rate
    • Average time to approval
    • Expired previews (not approved in time)
  3. Performance

    • Average response time by operation
    • P50, P95, P99 latencies
    • Database query performance
  4. Security

    • Failed authentication attempts
    • Rate limit violations
    • Permission denial rate
  5. Business Metrics

    • Active AI agents per tenant
    • Most-used tools
    • Most-accessed resources

Implementation (Prometheus + Grafana):

public class McpMetrics
{
    private static readonly Counter RequestCounter = Metrics.CreateCounter(
        "mcp_requests_total",
        "Total MCP requests",
        new CounterConfiguration
        {
            LabelNames = new[] { "operation_type", "agent_type", "status" }
        });

    private static readonly Histogram RequestDuration = Metrics.CreateHistogram(
        "mcp_request_duration_ms",
        "MCP request duration in milliseconds",
        new HistogramConfiguration
        {
            LabelNames = new[] { "operation_type" },
            Buckets = Histogram.ExponentialBuckets(10, 2, 10)
        });

    private static readonly Gauge ActiveDiffPreviews = Metrics.CreateGauge(
        "mcp_diff_previews_pending",
        "Number of pending diff previews",
        new GaugeConfiguration
        {
            LabelNames = new[] { "tenant_id" }
        });

    public void RecordRequest(string operationType, string agentType, int statusCode, int durationMs)
    {
        RequestCounter.WithLabels(operationType, agentType, statusCode.ToString()).Inc();
        RequestDuration.WithLabels(operationType).Observe(durationMs);
    }
}

8. Database Design

8.1 Entity Relationship Diagram

┌─────────────────────────────────────────────────────────────┐
│                     identity.tenants                        │
│  id, name, slug, status, subscription_plan                  │
└────────────────────────┬────────────────────────────────────┘
                         │
                         │ 1:N
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                     mcp.mcp_agents                          │
│  id, tenant_id, agent_name, agent_type, api_key_hash,       │
│  permission_level, allowed_resources, allowed_tools,        │
│  is_active, created_at, last_used_at                        │
└────────────────────────┬────────────────────────────────────┘
                         │
                         │ 1:N
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                   mcp.mcp_diff_previews                     │
│  id, tenant_id, agent_id, tool_name, input_parameters_json, │
│  operation, entity_type, entity_id, before_state_json,      │
│  after_state_json, diff_json, risk_level, status,           │
│  approved_by, approved_at, rejected_by, rejected_at,        │
│  is_committed, committed_entity_id, rollback_token,         │
│  created_at, expires_at                                     │
└─────────────────────────────────────────────────────────────┘
                         │
                         │ 1:N
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    mcp.mcp_audit_logs                       │
│  id, tenant_id, agent_id, operation_type, resource_uri,     │
│  tool_name, input_parameters_json, is_success,              │
│  error_message, http_status_code, diff_preview_id,          │
│  duration_ms, client_ip_address, user_agent, timestamp      │
└─────────────────────────────────────────────────────────────┘

8.2 Database Schema (SQL)

-- Schema: mcp
CREATE SCHEMA IF NOT EXISTS mcp;

-- Table: mcp_agents
CREATE TABLE mcp.mcp_agents (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    agent_name VARCHAR(200) NOT NULL,
    agent_type VARCHAR(100) NOT NULL, -- 'Claude', 'ChatGPT', 'Gemini'

    -- Authentication
    api_key_hash VARCHAR(512) NOT NULL,
    api_key_expires_at TIMESTAMP NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT true,

    -- Permissions
    permission_level VARCHAR(50) NOT NULL DEFAULT 'WriteWithPreview',
    allowed_resources JSONB NOT NULL DEFAULT '[]', -- ["projects.*", "issues.read"]
    allowed_tools JSONB NOT NULL DEFAULT '[]', -- ["create_issue", "update_status"]

    -- Audit
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    created_by UUID NOT NULL,
    last_used_at TIMESTAMP,
    request_count INTEGER NOT NULL DEFAULT 0,

    -- Constraints
    CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES identity.tenants(id) ON DELETE CASCADE,
    CONSTRAINT fk_created_by FOREIGN KEY (created_by) REFERENCES identity.users(id)
);

-- Indexes
CREATE INDEX idx_agents_tenant ON mcp.mcp_agents(tenant_id, is_active);
CREATE INDEX idx_agents_api_key ON mcp.mcp_agents(api_key_hash) WHERE is_active = true;
CREATE INDEX idx_agents_last_used ON mcp.mcp_agents(last_used_at DESC);

-- Table: mcp_diff_previews
CREATE TABLE mcp.mcp_diff_previews (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    agent_id UUID NOT NULL,

    -- Operation details
    tool_name VARCHAR(200) NOT NULL,
    input_parameters_json JSONB NOT NULL,

    -- Diff details
    operation VARCHAR(50) NOT NULL, -- 'Create', 'Update', 'Delete'
    entity_type VARCHAR(100) NOT NULL, -- 'Issue', 'Project', 'Sprint'
    entity_id UUID, -- NULL for Create operations
    before_state_json JSONB, -- NULL for Create
    after_state_json JSONB NOT NULL,
    diff_json JSONB NOT NULL, -- Structured diff

    -- Risk assessment
    risk_level VARCHAR(50) NOT NULL, -- 'Low', 'Medium', 'High', 'Critical'
    risk_reasons JSONB NOT NULL DEFAULT '[]',

    -- Approval workflow
    status VARCHAR(50) NOT NULL DEFAULT 'Pending', -- 'Pending', 'Approved', 'Rejected', 'Expired', 'Committed'
    approved_by UUID,
    approved_at TIMESTAMP,
    rejected_by UUID,
    rejected_at TIMESTAMP,
    rejection_reason TEXT,

    -- Rollback
    is_committed BOOLEAN NOT NULL DEFAULT false,
    committed_entity_id UUID,
    committed_at TIMESTAMP,
    rollback_token VARCHAR(500),

    -- Timestamps
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMP NOT NULL DEFAULT (NOW() + INTERVAL '24 hours'),

    -- Constraints
    CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES identity.tenants(id) ON DELETE CASCADE,
    CONSTRAINT fk_agent FOREIGN KEY (agent_id) REFERENCES mcp.mcp_agents(id) ON DELETE CASCADE,
    CONSTRAINT fk_approved_by FOREIGN KEY (approved_by) REFERENCES identity.users(id),
    CONSTRAINT fk_rejected_by FOREIGN KEY (rejected_by) REFERENCES identity.users(id)
);

-- Indexes
CREATE INDEX idx_diff_previews_tenant_status ON mcp.mcp_diff_previews(tenant_id, status, created_at DESC);
CREATE INDEX idx_diff_previews_agent ON mcp.mcp_diff_previews(agent_id, created_at DESC);
CREATE INDEX idx_diff_previews_expires ON mcp.mcp_diff_previews(expires_at) WHERE status = 'Pending';
CREATE INDEX idx_diff_previews_entity ON mcp.mcp_diff_previews(entity_type, entity_id);

-- Table: mcp_audit_logs
CREATE TABLE mcp.mcp_audit_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    agent_id UUID NOT NULL,

    -- Request details
    operation_type VARCHAR(100) NOT NULL, -- 'resources/read', 'tools/call'
    resource_uri VARCHAR(500),
    tool_name VARCHAR(200),
    input_parameters_json JSONB,

    -- Response details
    is_success BOOLEAN NOT NULL,
    error_message TEXT,
    http_status_code INTEGER,

    -- Diff preview (if applicable)
    diff_preview_id UUID,
    diff_status VARCHAR(50), -- 'Pending', 'Approved', 'Rejected'

    -- Performance
    duration_ms INTEGER NOT NULL,

    -- Context
    client_ip_address VARCHAR(50),
    user_agent TEXT,
    timestamp TIMESTAMP NOT NULL DEFAULT NOW(),

    -- Constraints
    CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES identity.tenants(id) ON DELETE CASCADE,
    CONSTRAINT fk_agent FOREIGN KEY (agent_id) REFERENCES mcp.mcp_agents(id) ON DELETE CASCADE,
    CONSTRAINT fk_diff_preview FOREIGN KEY (diff_preview_id) REFERENCES mcp.mcp_diff_previews(id)
);

-- Indexes (optimized for time-series queries)
CREATE INDEX idx_audit_tenant_timestamp ON mcp.mcp_audit_logs(tenant_id, timestamp DESC);
CREATE INDEX idx_audit_agent_timestamp ON mcp.mcp_audit_logs(agent_id, timestamp DESC);
CREATE INDEX idx_audit_operation_timestamp ON mcp.mcp_audit_logs(operation_type, timestamp DESC);
CREATE INDEX idx_audit_diff_preview ON mcp.mcp_audit_logs(diff_preview_id) WHERE diff_preview_id IS NOT NULL;

-- Partitioning for audit logs (for better performance at scale)
-- Future optimization: Partition by month
-- CREATE TABLE mcp.mcp_audit_logs_2025_11 PARTITION OF mcp.mcp_audit_logs
--     FOR VALUES FROM ('2025-11-01') TO ('2025-12-01');

8.3 Data Retention Policy

-- Automatic cleanup of expired diff previews
CREATE OR REPLACE FUNCTION mcp.cleanup_expired_diff_previews()
RETURNS void AS $$
BEGIN
    UPDATE mcp.mcp_diff_previews
    SET status = 'Expired'
    WHERE status = 'Pending'
      AND expires_at < NOW();
END;
$$ LANGUAGE plpgsql;

-- Schedule cleanup job (run every hour)
-- Use pg_cron extension or external scheduler
SELECT cron.schedule('cleanup-expired-diffs', '0 * * * *', 'SELECT mcp.cleanup_expired_diff_previews()');

-- Archive old audit logs (retain 90 days)
CREATE OR REPLACE FUNCTION mcp.archive_old_audit_logs()
RETURNS void AS $$
BEGIN
    DELETE FROM mcp.mcp_audit_logs
    WHERE timestamp < NOW() - INTERVAL '90 days';
END;
$$ LANGUAGE plpgsql;

9. Technology Stack & Rationale

9.1 Backend Technologies

Component Technology Rationale
Runtime .NET 9.0 Already in use, excellent performance, modern C# features
Web Framework ASP.NET Core Industry standard, high performance, built-in DI
Architecture Clean Architecture Already implemented, promotes testability and maintainability
ORM Entity Framework Core Already in use, excellent LINQ support, migration management
Database PostgreSQL 16+ Already in use, JSONB for flexible schemas, excellent performance
Caching Redis Distributed caching for rate limiting, session storage
Messaging (Future) RabbitMQ For async processing, event-driven workflows

9.2 MCP Protocol Libraries

Component Technology Rationale
JSON-RPC Custom implementation MCP uses JSON-RPC 2.0, simple to implement in C#
JSON Serialization System.Text.Json Built-in, high performance, good for JSONB interop
JSON Diff JsonDiffPatch.NET Library for generating JSON diffs
Transport SSE (Server-Sent Events) For web-based AI clients, long-lived connections
Transport (Future) WebSocket For bidirectional communication, streaming

9.3 Security Libraries

Component Technology Rationale
API Key Hashing BCrypt.Net-Next Already in use for passwords, proven security
JWT Microsoft.IdentityModel.Tokens Already in use, standard JWT implementation
Rate Limiting Custom + Redis Distributed rate limiting for multi-instance scenarios
CORS ASP.NET Core CORS Built-in, easy configuration

9.4 Observability

Component Technology Rationale
Metrics Prometheus + Grafana Industry standard, rich ecosystem
Logging Serilog Structured logging, excellent sink support
Tracing OpenTelemetry Distributed tracing, MCP request flow visibility
APM (Optional) Application Insights For Azure deployments

9.5 Why NOT Use MCP SDK (TypeScript)?

Reasons:

  1. Language Mismatch: ColaFlow backend is .NET, MCP SDK is TypeScript/Node.js
  2. Performance: .NET offers better performance for backend workloads
  3. Integration: Easier to integrate with existing Clean Architecture
  4. Control: Custom implementation gives full control over security and audit

Alternative Approach:

  • Implement MCP protocol specification in C# (JSON-RPC over HTTP/SSE)
  • Provides compatibility with any MCP-compliant client
  • No need to interop between .NET and Node.js

10. Implementation Roadmap

Phase 1: Foundation (2 weeks)

Goal: Basic MCP Server infrastructure

Deliverables:

  1. MCP.Domain module

    • McpAgent aggregate
    • DiffPreview aggregate
    • McpAuditLog aggregate
    • Repositories
  2. MCP.Infrastructure module

    • Database schema migrations
    • EF Core configurations
    • Repository implementations
  3. API Key authentication

    • ApiKeyAuthenticationHandler
    • Agent registration endpoints
    • API key generation utility
  4. Basic audit logging

    • McpAuditMiddleware
    • Audit log persistence

Acceptance Criteria:

  • Can register an AI agent with API key
  • Can authenticate using API key
  • All requests are logged to mcp_audit_logs

Phase 2: Resources Implementation (2 weeks)

Goal: Expose read-only resources to AI clients

Deliverables:

  1. Resource Service

    • IResourceService interface
    • ResourceService implementation
    • Resource descriptors (projects, issues, sprints)
  2. JSON-RPC protocol handler

    • JsonRpcHandler for resources/list
    • JsonRpcHandler for resources/read
  3. Permission validation

    • Field-level filtering
    • Resource-level access control
  4. API endpoints

    • POST /api/mcp/jsonrpc (JSON-RPC endpoint)
    • Resource URI routing

Acceptance Criteria:

  • AI client can list available resources
  • AI client can read project data
  • AI client can read issue data
  • Sensitive fields are filtered out

Testing:

# Test resources/list
curl -X POST http://localhost:5000/api/mcp/jsonrpc \
  -H "X-MCP-API-Key: mcp_dev_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "resources/list"
  }'

# Test resources/read
curl -X POST http://localhost:5000/api/mcp/jsonrpc \
  -H "X-MCP-API-Key: mcp_dev_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "resources/read",
    "params": { "uri": "project://abc-123" }
  }'

Phase 3: Tools & Diff Preview (3 weeks)

Goal: Implement write operations with diff preview

Deliverables:

  1. Diff Preview Service

    • DiffPreviewService implementation
    • Diff generation algorithm
    • Risk level calculation
  2. Tool Invocation Service

    • ToolInvocationService implementation
    • Tool descriptors (create_issue, update_status, assign_issue)
    • Integration with Application layer commands
  3. JSON-RPC protocol handler for tools

    • tools/list
    • tools/call (generates diff preview)
  4. Diff approval endpoints

    • POST /api/mcp/diffs/{id}/approve
    • POST /api/mcp/diffs/{id}/reject
    • GET /api/mcp/diffs (list pending diffs)

Acceptance Criteria:

  • AI client can list available tools
  • AI client can call create_issue (generates diff preview)
  • Human can view diff preview in Admin UI
  • Human can approve diff (commits to database)
  • Human can reject diff (discards preview)

Testing:

# Test tools/call (creates diff preview)
curl -X POST http://localhost:5000/api/mcp/jsonrpc \
  -H "X-MCP-API-Key: mcp_dev_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "create_issue",
      "arguments": {
        "projectId": "abc-123",
        "title": "Implement MCP authentication",
        "issueType": "Task",
        "priority": "High"
      }
    }
  }'

# Response: { "requiresApproval": true, "previewId": "preview-456", ... }

# Approve diff
curl -X POST http://localhost:5000/api/mcp/diffs/preview-456/approve \
  -H "Authorization: Bearer <human-jwt-token>" \
  -H "Content-Type: application/json"

Phase 4: Admin Dashboard (2 weeks)

Goal: UI for managing AI agents and diff previews

Deliverables:

  1. AI Agent Management UI

    • List agents
    • Register new agent
    • Regenerate API key
    • Configure permissions
  2. Diff Preview Dashboard

    • List pending diffs
    • View diff details (before/after comparison)
    • Approve/reject buttons
    • Diff history
  3. Audit Log Viewer

    • Filter by agent, operation, date range
    • View request/response details
    • Export to CSV

Acceptance Criteria:

  • Admin can register AI agents via UI
  • Admin can view pending diff previews
  • Admin can approve/reject diffs with visual diff viewer
  • Admin can view audit logs

Phase 5: Advanced Features (2 weeks)

Goal: Rollback, rate limiting, advanced tools

Deliverables:

  1. Rollback Service

    • Rollback committed diffs
    • Event sourcing integration
  2. Rate Limiting

    • Redis-based distributed rate limiter
    • Per-agent rate limits
    • Per-operation rate limits
  3. Advanced Tools

    • log_decision
    • add_comment
    • create_sprint
  4. Prompt Templates

    • Prompt template management
    • prompts/list
    • prompts/get

Acceptance Criteria:

  • Admin can rollback committed changes
  • Rate limiting prevents abuse
  • AI clients can use advanced tools
  • AI clients can retrieve prompt templates

Total Timeline: 11 weeks (~2.5 months)

Milestones:

  • Week 2: Basic MCP Server running
  • Week 4: AI clients can read resources
  • Week 7: AI clients can create issues with approval
  • Week 9: Admin UI complete
  • Week 11: Production-ready with all features

11. Risks & Mitigation

11.1 Technical Risks

Risk 1: MCP Protocol Compatibility

Description: MCP specification may change, breaking compatibility

Impact: High (requires code changes)

Probability: Medium (MCP is still evolving)

Mitigation:

  • Abstract MCP protocol logic into separate layer
  • Implement version negotiation in initialize call
  • Subscribe to MCP specification updates
  • Design protocol handlers to be easily extensible

Risk 2: Diff Preview Accuracy

Description: Generated diffs may not accurately represent changes

Impact: High (incorrect approvals/rejections)

Probability: Medium (complex domain logic)

Mitigation:

  • Comprehensive unit tests for diff generation
  • Dry-run domain commands before generating diffs
  • Use well-tested JSON diff library (JsonDiffPatch.NET)
  • Visual diff viewer in Admin UI for human verification

Risk 3: Performance at Scale

Description: Diff generation and audit logging may slow down at scale

Impact: Medium (slow response times)

Probability: Low (with proper optimization)

Mitigation:

  • Use async processing for audit logs (fire-and-forget)
  • Implement database connection pooling
  • Use Redis for caching frequently accessed resources
  • Partition audit logs by month (PostgreSQL table partitioning)
  • Monitor performance metrics (Prometheus)

Risk 4: Security Vulnerabilities

Description: API key leakage, permission bypass, injection attacks

Impact: Critical (data breach, unauthorized access)

Probability: Medium (without proper security practices)

Mitigation:

  • API Key Security:

    • Never log API keys in plain text
    • Use BCrypt hashing for storage
    • Implement key rotation policy (expire after 90 days)
    • Rate limiting to prevent brute force
  • Permission Validation:

    • Validate permissions at every layer (Controller, Service, Repository)
    • Use global query filters for tenant isolation
    • Field-level filtering for sensitive data
  • Injection Prevention:

    • Use parameterized queries (EF Core)
    • Validate all input with FluentValidation
    • Sanitize JSON input before deserialization
  • Regular Security Audits:

    • Dependency scanning (Snyk, Dependabot)
    • Penetration testing
    • Code review for security issues

11.2 Business Risks

Risk 5: User Adoption

Description: Users may not trust AI-generated changes

Impact: High (low adoption)

Probability: Medium (change management challenge)

Mitigation:

  • Start with read-only mode (no writes) to build trust
  • Provide transparent diff previews with clear before/after
  • Implement risk indicators (color-coded: green/yellow/red)
  • Offer rollback capability for safety net
  • User training and documentation

Risk 6: Regulatory Compliance

Description: AI operations may violate GDPR, SOC2, or industry regulations

Impact: Critical (legal issues, fines)

Probability: Low (with proper design)

Mitigation:

  • Complete audit trail (who, what, when, why)
  • Right to be forgotten (GDPR Article 17)
    • Soft delete AI-generated data
    • Provide data export/deletion endpoints
  • Data minimization (only expose necessary fields to AI)
  • Consent management (users opt-in to AI features)
  • Data residency (support region-specific data storage)

11.3 Operational Risks

Risk 7: Database Growth

Description: Audit logs and diff previews grow unbounded

Impact: Medium (storage costs, slow queries)

Probability: High (without retention policy)

Mitigation:

  • Data retention policy:
    • Archive audit logs after 90 days
    • Delete expired diff previews after 7 days
    • Compress historical data
  • Partitioning:
    • Partition audit logs by month (PostgreSQL)
    • Use TimescaleDB for time-series data (future)
  • Monitoring:
    • Alert when table sizes exceed thresholds
    • Regular database maintenance (VACUUM, ANALYZE)

Risk 8: AI Agent Abuse

Description: Malicious AI agents spam requests, attempt unauthorized access

Impact: Medium (resource exhaustion, data exfiltration)

Probability: Medium (if API keys are leaked)

Mitigation:

  • Rate Limiting:
    • 100 requests/min for resources/read
    • 10 requests/min for tools/call
    • IP-based rate limiting (fallback)
  • Anomaly Detection:
    • Alert on unusual request patterns
    • Automatic suspension of suspicious agents
  • API Key Rotation:
    • Force rotation every 90 days
    • Revoke keys on suspicious activity
  • CAPTCHA for Registration:
    • Human verification when registering new agents

12. Appendix

12.1 MCP Protocol Reference

JSON-RPC 2.0 Request Format:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_issue",
    "arguments": { ... }
  }
}

JSON-RPC 2.0 Response Format:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": { ... }
}

Error Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request",
    "data": { "details": "..." }
  }
}

12.2 Example Tool Schemas

create_project:

{
  "name": "create_project",
  "description": "Create a new project",
  "inputSchema": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "description": { "type": "string" },
      "ownerId": { "type": "string" },
      "startDate": { "type": "string", "format": "date" },
      "endDate": { "type": "string", "format": "date" }
    },
    "required": ["name", "ownerId"]
  }
}

update_sprint:

{
  "name": "update_sprint",
  "description": "Update sprint details",
  "inputSchema": {
    "type": "object",
    "properties": {
      "sprintId": { "type": "string" },
      "name": { "type": "string" },
      "startDate": { "type": "string", "format": "date" },
      "endDate": { "type": "string", "format": "date" },
      "goal": { "type": "string" }
    },
    "required": ["sprintId"]
  }
}

12.3 Configuration Examples

appsettings.Mcp.json:

{
  "Mcp": {
    "ApiKeyExpirationDays": 90,
    "DiffPreviewExpirationHours": 24,
    "RateLimit": {
      "ResourcesRead": 100,
      "ToolsCall": 10,
      "WindowMinutes": 1
    },
    "AuditLog": {
      "RetentionDays": 90,
      "EnablePartitioning": true
    },
    "DefaultPermissions": {
      "Level": "WriteWithPreview",
      "AllowedResources": ["projects.*", "issues.*", "sprints.*"],
      "AllowedTools": ["create_issue", "update_status", "assign_issue"]
    }
  }
}

Summary

This architecture design provides a comprehensive, secure, and scalable MCP Server for ColaFlow that:

  1. Integrates seamlessly with existing Clean Architecture and Identity Module
  2. Prioritizes safety via diff preview and human approval for all AI writes
  3. Ensures auditability with complete audit logs and observability
  4. Supports multi-tenancy with tenant-scoped data isolation
  5. Enables extensibility for future AI models and integrations

Key Design Decisions:

  • Custom MCP protocol implementation in C# (not TypeScript SDK)
  • BCrypt API key authentication (reuses existing security patterns)
  • Diff preview workflow (safety-first approach)
  • PostgreSQL JSONB for flexible diff storage
  • Redis for distributed rate limiting
  • Prometheus + Grafana for observability

Next Steps:

  1. Review and approve this architecture document
  2. Begin Phase 1 implementation (Foundation)
  3. Set up CI/CD pipeline for MCP module
  4. Create integration tests for MCP protocol

Document Status: Ready for Review Reviewers: Product Manager, Backend Team Lead, Security Team Approval Required: Yes


Revision History:

Version Date Author Changes
1.0 2025-11-04 System Architect Initial architecture design