svrnty-mcp-server/tests/Svrnty.MCP.Core.Tests/McpServerTests.cs
Svrnty 516e1479c6 docs: comprehensive AI coding assistant research and MCP-first implementation plan
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>
2025-10-22 21:00:34 -04:00

278 lines
8.4 KiB
C#

using Xunit;
using Moq;
using System.Text.Json;
using System.Threading.Tasks;
namespace OpenHarbor.MCP.Core.Tests;
/// <summary>
/// Unit tests for McpServer following TDD approach.
/// Tests MCP protocol method routing and execution.
/// </summary>
public class McpServerTests
{
[Fact]
public void McpServer_ShouldInitializeWithToolRegistry()
{
// Arrange
var registry = new ToolRegistry();
// Act
var server = new McpServer(registry);
// Assert
Assert.NotNull(server);
}
[Fact]
public void McpServer_ShouldThrowOnNullRegistry()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
new McpServer(null!)
);
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsList_ShouldReturnToolDescriptions()
{
// Arrange
var registry = new ToolRegistry();
var mockTool = CreateMockTool("test_tool", "Test tool description");
registry.AddTool(mockTool.Object);
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "1",
Method = "tools/list"
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("1", response.Id);
Assert.Null(response.Error);
Assert.NotNull(response.Result);
var result = response.Result.RootElement;
Assert.True(result.TryGetProperty("tools", out var tools));
Assert.Equal(JsonValueKind.Array, tools.ValueKind);
Assert.Equal(1, tools.GetArrayLength());
var tool = tools[0];
Assert.True(tool.TryGetProperty("name", out var name));
Assert.Equal("test_tool", name.GetString());
Assert.True(tool.TryGetProperty("description", out var desc));
Assert.Equal("Test tool description", desc.GetString());
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsList_EmptyRegistry_ShouldReturnEmptyArray()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "1",
Method = "tools/list"
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Null(response.Error);
Assert.NotNull(response.Result);
var result = response.Result.RootElement;
Assert.True(result.TryGetProperty("tools", out var tools));
Assert.Equal(0, tools.GetArrayLength());
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsCall_ShouldExecuteTool()
{
// Arrange
var registry = new ToolRegistry();
var mockTool = CreateMockTool("search_codex", "Search tool");
mockTool.Setup(t => t.ExecuteAsync(It.IsAny<JsonDocument>()))
.ReturnsAsync(JsonDocument.Parse("""{"results": ["doc1", "doc2"]}"""));
registry.AddTool(mockTool.Object);
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "2",
Method = "tools/call",
Params = JsonDocument.Parse("""{"name":"search_codex","arguments":{"query":"test"}}""")
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("2", response.Id);
Assert.Null(response.Error);
Assert.NotNull(response.Result);
var result = response.Result.RootElement;
Assert.True(result.TryGetProperty("results", out var results));
mockTool.Verify(t => t.ExecuteAsync(It.IsAny<JsonDocument>()), Times.Once);
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsCall_NonexistentTool_ShouldReturnError()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "3",
Method = "tools/call",
Params = JsonDocument.Parse("""{"name":"nonexistent_tool","arguments":{}}""")
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("3", response.Id);
Assert.Null(response.Result);
Assert.NotNull(response.Error);
Assert.Equal(-32601, response.Error.Code); // Method not found
Assert.Contains("nonexistent_tool", response.Error.Message);
}
[Fact]
public async Task McpServer_HandleRequest_WithUnknownMethod_ShouldReturnError()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "4",
Method = "unknown/method"
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("4", response.Id);
Assert.Null(response.Result);
Assert.NotNull(response.Error);
Assert.Equal(-32601, response.Error.Code); // Method not found
Assert.Contains("unknown/method", response.Error.Message);
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsCall_MissingParams_ShouldReturnError()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "5",
Method = "tools/call"
// No Params
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("5", response.Id);
Assert.Null(response.Result);
Assert.NotNull(response.Error);
Assert.Equal(-32602, response.Error.Code); // Invalid params
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsCall_MissingToolName_ShouldReturnError()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "6",
Method = "tools/call",
Params = JsonDocument.Parse("""{"arguments":{}}""") // Missing "name"
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.Equal("6", response.Id);
Assert.Null(response.Result);
Assert.NotNull(response.Error);
Assert.Equal(-32602, response.Error.Code); // Invalid params
}
[Fact]
public async Task McpServer_HandleRequest_WithNullRequest_ShouldThrow()
{
// Arrange
var registry = new ToolRegistry();
var server = new McpServer(registry);
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
await server.HandleRequestAsync(null!)
);
}
[Fact]
public async Task McpServer_HandleRequest_WithToolsList_MultipleTools_ShouldReturnAll()
{
// Arrange
var registry = new ToolRegistry();
registry.AddTool(CreateMockTool("tool1", "First tool").Object);
registry.AddTool(CreateMockTool("tool2", "Second tool").Object);
registry.AddTool(CreateMockTool("tool3", "Third tool").Object);
var server = new McpServer(registry);
var request = new McpRequest
{
Id = "7",
Method = "tools/list"
};
// Act
var response = await server.HandleRequestAsync(request);
// Assert
Assert.NotNull(response);
Assert.NotNull(response.Result);
var result = response.Result.RootElement;
Assert.True(result.TryGetProperty("tools", out var tools));
Assert.Equal(3, tools.GetArrayLength());
}
// Helper method to create mock tools
private Mock<IMcpTool> CreateMockTool(string name, string description)
{
var mockTool = new Mock<IMcpTool>();
mockTool.Setup(t => t.Name).Returns(name);
mockTool.Setup(t => t.Description).Returns(description);
mockTool.Setup(t => t.Schema).Returns(JsonDocument.Parse("""{"type": "object"}"""));
mockTool.Setup(t => t.ExecuteAsync(It.IsAny<JsonDocument>()))
.ReturnsAsync(JsonDocument.Parse("""{"status": "ok"}"""));
return mockTool;
}
}