Research conducted on modern AI coding assistants (Cursor, GitHub Copilot, Cline,
Aider, Windsurf, Replit Agent) to understand architecture patterns, context management,
code editing workflows, and tool use protocols.
Key Decision: Pivoted from building full CLI (40-50h) to validation-driven MCP-first
approach (10-15h). Build 5 core CODEX MCP tools that work with ANY coding assistant,
validate adoption over 2-4 weeks, then decide on full CLI if demand proven.
Files:
- research/ai-systems/modern-coding-assistants-architecture.md (comprehensive research)
- research/ai-systems/codex-coding-assistant-implementation-plan.md (original CLI plan, preserved)
- research/ai-systems/codex-mcp-tools-implementation-plan.md (approved MCP-first plan)
- ideas/registry.json (updated with approved MCP tools proposal)
Architech Validation: APPROVED with pivot to MCP-first approach
Human Decision: Approved (pragmatic validation-driven development)
Next: Begin Phase 1 implementation (10-15 hours, 5 core MCP tools)
🤖 Generated with CODEX Research System
Co-Authored-By: The Archivist <archivist@codex.svrnty.io>
Co-Authored-By: The Architech <architech@codex.svrnty.io>
Co-Authored-By: Mathias Beaulieu-Duncan <mat@svrnty.io>
295 lines
6.7 KiB
C#
295 lines
6.7 KiB
C#
using Xunit;
|
|
using System.Text.Json;
|
|
|
|
namespace OpenHarbor.MCP.Core.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for MCP request/response models following TDD approach.
|
|
/// Tests JSON-RPC 2.0 protocol message structures.
|
|
/// </summary>
|
|
public class McpModelsTests
|
|
{
|
|
[Fact]
|
|
public void McpRequest_ShouldHaveJsonRpcVersion()
|
|
{
|
|
// Arrange & Act
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/call",
|
|
Id = "1"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("2.0", request.JsonRpc);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpRequest_ShouldHaveMethod()
|
|
{
|
|
// Arrange & Act
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/list",
|
|
Id = "1"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("tools/list", request.Method);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpRequest_ShouldHaveId()
|
|
{
|
|
// Arrange & Act
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/call",
|
|
Id = "request-123"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("request-123", request.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpRequest_ShouldHaveOptionalParams()
|
|
{
|
|
// Arrange
|
|
var paramsJson = JsonDocument.Parse("""
|
|
{
|
|
"name": "search_codex",
|
|
"arguments": {"query": "test"}
|
|
}
|
|
""");
|
|
|
|
// Act
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/call",
|
|
Id = "1",
|
|
Params = paramsJson
|
|
};
|
|
|
|
// Assert
|
|
Assert.NotNull(request.Params);
|
|
Assert.Equal("search_codex", request.Params.RootElement.GetProperty("name").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public void McpRequest_ParamsCanBeNull()
|
|
{
|
|
// Arrange & Act
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/list",
|
|
Id = "1",
|
|
Params = null
|
|
};
|
|
|
|
// Assert
|
|
Assert.Null(request.Params);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ShouldHaveJsonRpcVersion()
|
|
{
|
|
// Arrange & Act
|
|
var response = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("2.0", response.JsonRpc);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ShouldHaveId()
|
|
{
|
|
// Arrange & Act
|
|
var response = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "request-456"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("request-456", response.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ShouldHaveOptionalResult()
|
|
{
|
|
// Arrange
|
|
var resultJson = JsonDocument.Parse("""
|
|
{
|
|
"tools": [
|
|
{"name": "search_codex", "description": "Search CODEX documents"}
|
|
]
|
|
}
|
|
""");
|
|
|
|
// Act
|
|
var response = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1",
|
|
Result = resultJson
|
|
};
|
|
|
|
// Assert
|
|
Assert.NotNull(response.Result);
|
|
Assert.True(response.Result.RootElement.TryGetProperty("tools", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ShouldHaveOptionalError()
|
|
{
|
|
// Arrange
|
|
var error = new McpError
|
|
{
|
|
Code = -32601,
|
|
Message = "Method not found"
|
|
};
|
|
|
|
// Act
|
|
var response = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1",
|
|
Error = error
|
|
};
|
|
|
|
// Assert
|
|
Assert.NotNull(response.Error);
|
|
Assert.Equal(-32601, response.Error.Code);
|
|
Assert.Equal("Method not found", response.Error.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ResultAndErrorMutuallyExclusive()
|
|
{
|
|
// Arrange
|
|
var resultJson = JsonDocument.Parse("""{"status": "ok"}""");
|
|
var error = new McpError { Code = -32600, Message = "Invalid Request" };
|
|
|
|
// Act - Response should have either result OR error, not both
|
|
var responseWithResult = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1",
|
|
Result = resultJson,
|
|
Error = null
|
|
};
|
|
|
|
var responseWithError = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1",
|
|
Result = null,
|
|
Error = error
|
|
};
|
|
|
|
// Assert
|
|
Assert.NotNull(responseWithResult.Result);
|
|
Assert.Null(responseWithResult.Error);
|
|
|
|
Assert.Null(responseWithError.Result);
|
|
Assert.NotNull(responseWithError.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpError_ShouldHaveCode()
|
|
{
|
|
// Arrange & Act
|
|
var error = new McpError
|
|
{
|
|
Code = -32700,
|
|
Message = "Parse error"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal(-32700, error.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpError_ShouldHaveMessage()
|
|
{
|
|
// Arrange & Act
|
|
var error = new McpError
|
|
{
|
|
Code = -32600,
|
|
Message = "Invalid Request"
|
|
};
|
|
|
|
// Assert
|
|
Assert.Equal("Invalid Request", error.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpError_ShouldHaveOptionalData()
|
|
{
|
|
// Arrange
|
|
var dataJson = JsonDocument.Parse("""{"detail": "Missing required field"}""");
|
|
|
|
// Act
|
|
var error = new McpError
|
|
{
|
|
Code = -32602,
|
|
Message = "Invalid params",
|
|
Data = dataJson
|
|
};
|
|
|
|
// Assert
|
|
Assert.NotNull(error.Data);
|
|
Assert.Equal("Missing required field", error.Data.RootElement.GetProperty("detail").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public void McpRequest_ShouldSerializeToJson()
|
|
{
|
|
// Arrange
|
|
var request = new McpRequest
|
|
{
|
|
JsonRpc = "2.0",
|
|
Method = "tools/list",
|
|
Id = "1"
|
|
};
|
|
|
|
// Act
|
|
var json = JsonSerializer.Serialize(request);
|
|
|
|
// Assert
|
|
Assert.Contains("\"jsonrpc\":\"2.0\"", json);
|
|
Assert.Contains("\"method\":\"tools/list\"", json);
|
|
Assert.Contains("\"id\":\"1\"", json);
|
|
}
|
|
|
|
[Fact]
|
|
public void McpResponse_ShouldSerializeToJson()
|
|
{
|
|
// Arrange
|
|
var resultJson = JsonDocument.Parse("""{"status": "success"}""");
|
|
var response = new McpResponse
|
|
{
|
|
JsonRpc = "2.0",
|
|
Id = "1",
|
|
Result = resultJson
|
|
};
|
|
|
|
// Act
|
|
var json = JsonSerializer.Serialize(response);
|
|
|
|
// Assert
|
|
Assert.Contains("\"jsonrpc\":\"2.0\"", json);
|
|
Assert.Contains("\"id\":\"1\"", json);
|
|
Assert.Contains("\"result\"", json);
|
|
}
|
|
}
|