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>
8.2 KiB
8.2 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_7 | sprint_5 | Phase 2 - Resources | not_started | P0 | 5 | backend | 2 | 2025-11-06 |
|
Story 5.7: Multi-Tenant Isolation Verification
Phase: Phase 2 - Resources (Week 3-4) Priority: P0 CRITICAL Estimated Effort: 5 Story Points (2 days)
User Story
As a Security Engineer I want 100% multi-tenant data isolation for all MCP operations So that AI agents cannot access data from other tenants
Business Value
Multi-tenant security is CRITICAL for M2. A single data leak could:
- Violate customer trust
- Cause legal liability (GDPR, SOC2)
- Damage brand reputation
- Block enterprise adoption
This Story ensures zero cross-tenant data access.
Acceptance Criteria
AC1: TenantContext Service
- Extract
TenantIdfrom API Key - Set
CurrentTenantIdin request context - Accessible to all MCP operations
AC2: Global Query Filters
- EF Core Global Query Filters applied to all entities
- All queries automatically filtered by
TenantId - Cannot be disabled by accident
AC3: Repository Level Isolation
- All repositories enforce
TenantIdfilter - No queries bypass tenant check
- Aggregate roots include
TenantIdproperty
AC4: Integration Tests
- Create 2 test tenants (Tenant A, Tenant B)
- Create test data in each tenant
- Test 1: Tenant A API Key cannot read Tenant B projects
- Test 2: Tenant A API Key cannot read Tenant B issues
- Test 3: Tenant A API Key cannot read Tenant B users
- Test 4: Direct ID access fails for other tenant's data
- Test 5: Search queries never return cross-tenant results
- All tests must pass (100% isolation)
AC5: Security Audit
- Code review by security team
- Static analysis (no raw SQL without TenantId)
- Verify all API endpoints enforce tenant filter
Technical Design
TenantContext Service
public interface ITenantContext
{
Guid CurrentTenantId { get; }
}
public class McpTenantContext : ITenantContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public Guid CurrentTenantId
{
get
{
var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"];
if (tenantId == null)
throw new McpUnauthorizedException("Tenant context not set");
return (Guid)tenantId;
}
}
}
Global Query Filters
public class ColaFlowDbContext : DbContext
{
private readonly ITenantContext _tenantContext;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Apply tenant filter to all tenant-scoped entities
modelBuilder.Entity<Project>()
.HasQueryFilter(p => p.TenantId == _tenantContext.CurrentTenantId);
modelBuilder.Entity<Epic>()
.HasQueryFilter(e => e.TenantId == _tenantContext.CurrentTenantId);
modelBuilder.Entity<Story>()
.HasQueryFilter(s => s.TenantId == _tenantContext.CurrentTenantId);
modelBuilder.Entity<WorkTask>()
.HasQueryFilter(t => t.TenantId == _tenantContext.CurrentTenantId);
modelBuilder.Entity<Sprint>()
.HasQueryFilter(s => s.TenantId == _tenantContext.CurrentTenantId);
modelBuilder.Entity<User>()
.HasQueryFilter(u => u.TenantId == _tenantContext.CurrentTenantId);
}
}
Integration Test Example
[Fact]
public async Task TenantA_CannotReadTenantB_Projects()
{
// Arrange
var tenantA = await CreateTestTenant("Tenant A");
var tenantB = await CreateTestTenant("Tenant B");
var apiKeyA = await CreateApiKey(tenantA.Id, "API Key A");
var projectB = await CreateProject(tenantB.Id, "Project B");
// Act - Tenant A tries to access Tenant B's project
var result = await CallMcpResource(
apiKeyA,
$"colaflow://projects.get/{projectB.Id}");
// Assert
Assert.Equal(404, result.StatusCode); // NOT 403 (don't leak existence)
Assert.Null(result.Data);
}
[Fact]
public async Task IssuesSearch_NeverReturnsCrossTenant_Results()
{
// Arrange
var tenantA = await CreateTestTenant("Tenant A");
var tenantB = await CreateTestTenant("Tenant B");
await CreateIssue(tenantA.Id, "Issue A");
await CreateIssue(tenantB.Id, "Issue B");
var apiKeyA = await CreateApiKey(tenantA.Id, "API Key A");
// Act - Tenant A searches all issues
var result = await CallMcpResource(
apiKeyA,
"colaflow://issues.search");
// Assert
var issues = result.Data["issues"];
Assert.Single(issues); // Only Tenant A's issue
Assert.Equal("Issue A", issues[0]["title"]);
}
Tasks
Task 1: TenantContext Service (2 hours)
- Create
ITenantContextinterface - Implement
McpTenantContext(extract from HttpContext) - Register in DI container
- Update API Key middleware to set TenantId
Files to Create:
ColaFlow.Modules.Mcp/Services/McpTenantContext.cs
Task 2: Global Query Filters (3 hours)
- Add
TenantIdto all aggregate roots (if missing) - Configure Global Query Filters in DbContext
- Test filters apply automatically
- Verify cannot be disabled
Files to Modify:
ColaFlow.Infrastructure/Data/ColaFlowDbContext.cs
Task 3: Repository Validation (2 hours)
- Audit all repository methods
- Ensure all queries include TenantId
- Add TenantId to method signatures if needed
Task 4: Integration Tests - Cross-Tenant Access (6 hours)
- Create multi-tenant test infrastructure
- Test 1: Projects cross-tenant access
- Test 2: Issues cross-tenant access
- Test 3: Users cross-tenant access
- Test 4: Direct ID access (404, not 403)
- Test 5: Search never returns cross-tenant results
Files to Create:
ColaFlow.Modules.Mcp.Tests/Integration/MultiTenantIsolationTests.cs
Task 5: Security Audit (3 hours)
- Code review by security team
- Static analysis (grep for raw SQL)
- Verify all API endpoints use TenantContext
- Document security architecture
Files to Create:
docs/security/multi-tenant-isolation-audit.md
Testing Strategy
Integration Tests (Critical)
- 2 test tenants with separate data
- 10+ test scenarios covering all Resources
- 100% isolation verified
- Tests must fail if isolation broken
Security Audit Checklist
- All entities have TenantId property
- Global Query Filters configured
- No raw SQL without TenantId
- All API endpoints use TenantContext
- Return 404 (not 403) for cross-tenant access
- Audit logs include TenantId
Dependencies
Prerequisites:
- Story 5.2 (API Key Management) - API Key linked to Tenant
- Story 5.5 (Core Resources) - Resources to test
Critical Path: BLOCKS M2 production deployment
Risks & Mitigation
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| Tenant leak vulnerability | CRITICAL | Low | 100% test coverage, code review |
| Global filter bypass | HIGH | Low | EF Core best practices, testing |
| Performance impact | Medium | Low | Indexed TenantId columns |
Definition of Done
- TenantContext service working
- Global Query Filters applied
- ALL integration tests passing (100%)
- Security audit complete (no findings)
- Code reviewed by security team
- Documentation updated
Notes
Why This Story Matters
- CRITICAL SECURITY: Single most important security Story in M2
- Compliance: Required for GDPR, SOC2, enterprise customers
- Trust: Multi-tenant leaks destroy customer trust
- Legal: Data breaches have severe legal consequences
Security Best Practices
- Defense in Depth: Multiple layers (API Key, TenantContext, Global Filters)
- Fail Closed: No data if TenantId missing (throw exception)
- 404 not 403: Don't leak existence of other tenant's data
- Audit Everything: Log all tenant context access
- Test Religiously: 100% integration test coverage
Reference Materials
- Multi-Tenant Security: https://learn.microsoft.com/en-us/ef/core/querying/filters
- Sprint 5 Plan:
docs/plans/sprint_5.md