feat(backend): Implement MCP Protocol Handler (Story 5.1)
Implemented JSON-RPC 2.0 protocol handler for MCP communication, enabling AI agents to communicate with ColaFlow using the Model Context Protocol. **Implementation:** - JSON-RPC 2.0 data models (Request, Response, Error, ErrorCode) - MCP protocol models (Initialize, Capabilities, ClientInfo, ServerInfo) - McpProtocolHandler with method routing and error handling - Method handlers: initialize, resources/list, tools/list, tools/call - ASP.NET Core middleware for /mcp endpoint - Service registration and dependency injection setup **Testing:** - 28 unit tests covering protocol parsing, validation, and error handling - Integration tests for initialize handshake and error responses - All tests passing with >80% coverage **Changes:** - Created ColaFlow.Modules.Mcp.Contracts project - Created ColaFlow.Modules.Mcp.Domain project - Created ColaFlow.Modules.Mcp.Application project - Created ColaFlow.Modules.Mcp.Infrastructure project - Created ColaFlow.Modules.Mcp.Tests project - Registered MCP module in ColaFlow.API Program.cs - Added /mcp endpoint via middleware **Acceptance Criteria Met:** ✅ JSON-RPC 2.0 messages correctly parsed ✅ Request validation (jsonrpc: "2.0", method, params, id) ✅ Error responses conform to JSON-RPC 2.0 spec ✅ Invalid requests return proper error codes (-32700, -32600, -32601, -32602) ✅ MCP initialize method implemented ✅ Server capabilities returned (resources, tools, prompts) ✅ Protocol version negotiation works (1.0) ✅ Request routing to method handlers ✅ Unit test coverage > 80% ✅ All tests passing **Story**: docs/stories/sprint_5/story_5_1.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
using System.Text.Json;
|
||||
using ColaFlow.Modules.Mcp.Contracts.JsonRpc;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace ColaFlow.Modules.Mcp.Tests.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for JsonRpcResponse
|
||||
/// </summary>
|
||||
public class JsonRpcResponseTests
|
||||
{
|
||||
[Fact]
|
||||
public void Success_CreatesValidSuccessResponse()
|
||||
{
|
||||
// Arrange
|
||||
var result = new { message = "success" };
|
||||
var id = 1;
|
||||
|
||||
// Act
|
||||
var response = JsonRpcResponse.Success(result, id);
|
||||
|
||||
// Assert
|
||||
response.Should().NotBeNull();
|
||||
response.JsonRpc.Should().Be("2.0");
|
||||
response.Result.Should().NotBeNull();
|
||||
response.Error.Should().BeNull();
|
||||
response.Id.Should().Be(id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateError_CreatesValidErrorResponse()
|
||||
{
|
||||
// Arrange
|
||||
var error = JsonRpcError.InternalError("Test error");
|
||||
var id = 1;
|
||||
|
||||
// Act
|
||||
var response = JsonRpcResponse.CreateError(error, id);
|
||||
|
||||
// Assert
|
||||
response.Should().NotBeNull();
|
||||
response.JsonRpc.Should().Be("2.0");
|
||||
response.Result.Should().BeNull();
|
||||
response.Error.Should().NotBeNull();
|
||||
response.Error.Should().Be(error);
|
||||
response.Id.Should().Be(id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseError_CreatesResponseWithNullId()
|
||||
{
|
||||
// Act
|
||||
var response = JsonRpcResponse.ParseError("Invalid JSON");
|
||||
|
||||
// Assert
|
||||
response.Error.Should().NotBeNull();
|
||||
response.Error!.Code.Should().Be((int)JsonRpcErrorCode.ParseError);
|
||||
response.Id.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MethodNotFound_IncludesMethodNameInError()
|
||||
{
|
||||
// Act
|
||||
var response = JsonRpcResponse.MethodNotFound("unknown_method", 1);
|
||||
|
||||
// Assert
|
||||
response.Error.Should().NotBeNull();
|
||||
response.Error!.Code.Should().Be((int)JsonRpcErrorCode.MethodNotFound);
|
||||
response.Error.Message.Should().Contain("unknown_method");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_SuccessResponse_DoesNotIncludeError()
|
||||
{
|
||||
// Arrange
|
||||
var response = JsonRpcResponse.Success(new { result = "ok" }, 1);
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(response, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
|
||||
// Assert
|
||||
json.Should().Contain("\"result\":");
|
||||
json.Should().NotContain("\"error\":");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_ErrorResponse_DoesNotIncludeResult()
|
||||
{
|
||||
// Arrange
|
||||
var response = JsonRpcResponse.CreateError(JsonRpcError.InternalError(), 1);
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(response, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
|
||||
// Assert
|
||||
json.Should().Contain("\"error\":");
|
||||
json.Should().NotContain("\"result\":");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user