2025-03-08 23:44:34 +08:00
|
|
|
/**
|
|
|
|
* @file test_mcp_server.cpp
|
|
|
|
* @brief Test MCP server related functionality
|
|
|
|
*
|
|
|
|
* This file contains unit tests for the MCP server module, based on the 2024-11-05 specification.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "mcp_server.h"
|
|
|
|
#include "mcp_client.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <string>
|
|
|
|
#include <thread>
|
|
|
|
#include <future>
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
// Mock tool handler function
|
|
|
|
mcp::json test_tool_handler(const mcp::json& params) {
|
|
|
|
if (params.contains("input")) {
|
2025-03-09 17:10:01 +08:00
|
|
|
return {
|
|
|
|
{
|
|
|
|
{"type", "text"},
|
|
|
|
{"text", "Result: " + params["input"].get<std::string>()}
|
|
|
|
}
|
|
|
|
};
|
2025-03-08 23:44:34 +08:00
|
|
|
} else {
|
2025-03-09 17:10:01 +08:00
|
|
|
return {
|
|
|
|
{
|
|
|
|
{"type", "text"},
|
|
|
|
{"text", "Default result"}
|
|
|
|
}
|
|
|
|
};
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Server test class
|
|
|
|
class ServerTest : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
void SetUp() override {
|
|
|
|
// Create server
|
|
|
|
server = std::make_unique<mcp::server>("localhost", 8095);
|
|
|
|
server->set_server_info("TestServer", "2024-11-05");
|
|
|
|
|
|
|
|
// Set server capabilities
|
|
|
|
mcp::json capabilities = {
|
2025-03-09 23:17:36 +08:00
|
|
|
{"tools", {{"listChanged", true}}}
|
2025-03-08 23:44:34 +08:00
|
|
|
};
|
|
|
|
server->set_capabilities(capabilities);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TearDown() override {
|
|
|
|
// Stop server
|
|
|
|
if (server && server_thread.joinable()) {
|
|
|
|
server->stop();
|
|
|
|
server_thread.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
void start_server() {
|
|
|
|
server_thread = std::thread([this]() {
|
|
|
|
server->start(false);
|
|
|
|
});
|
|
|
|
// Wait for server to start
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<mcp::server> server;
|
|
|
|
std::thread server_thread;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test tool registration and retrieval
|
|
|
|
TEST_F(ServerTest, ToolRegistrationTest) {
|
|
|
|
// Create tool
|
|
|
|
mcp::tool test_tool = mcp::tool_builder("test_tool")
|
|
|
|
.with_description("Test tool")
|
|
|
|
.with_string_param("input", "Input parameter")
|
|
|
|
.build();
|
|
|
|
|
|
|
|
// Register tool
|
|
|
|
server->register_tool(test_tool, test_tool_handler);
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client to verify tool registration
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
client.set_timeout(5);
|
|
|
|
client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
|
|
|
|
// Get tool list
|
|
|
|
auto tools = client.get_tools();
|
|
|
|
EXPECT_EQ(tools.size(), 1);
|
|
|
|
EXPECT_EQ(tools[0].name, "test_tool");
|
|
|
|
EXPECT_EQ(tools[0].description, "Test tool");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test method registration and handling
|
|
|
|
TEST_F(ServerTest, MethodRegistrationTest) {
|
|
|
|
// Register method - register as tool since client doesn't have call_method
|
|
|
|
server->register_tool(
|
|
|
|
mcp::tool_builder("test_method")
|
|
|
|
.with_description("Test method")
|
|
|
|
.with_string_param("param1", "Parameter 1", false)
|
|
|
|
.build(),
|
|
|
|
[](const mcp::json& params) -> mcp::json {
|
2025-03-09 17:10:01 +08:00
|
|
|
return {
|
|
|
|
{
|
|
|
|
{"type", "text"},
|
|
|
|
{"text", "Method call successful"}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"type", "text"},
|
|
|
|
{"text", "params: " + params.dump()}
|
|
|
|
}
|
|
|
|
};
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client to verify method registration
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
client.set_timeout(5);
|
|
|
|
client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
|
|
|
|
// Call method (via tool)
|
|
|
|
mcp::json result = client.call_tool("test_method", {{"param1", "value1"}});
|
2025-03-09 17:10:01 +08:00
|
|
|
EXPECT_EQ(result["content"][0]["text"], "Method call successful");
|
|
|
|
EXPECT_EQ(result["content"][1]["text"], "params: {\"param1\":\"value1\"}");
|
2025-03-08 23:44:34 +08:00
|
|
|
|
|
|
|
// Call non-existent method
|
|
|
|
EXPECT_THROW(client.call_tool("non_existent_method"), mcp::mcp_exception);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test server-client interaction
|
|
|
|
TEST_F(ServerTest, ServerClientInteractionTest) {
|
|
|
|
// Register tool
|
|
|
|
mcp::tool test_tool = mcp::tool_builder("test_tool")
|
|
|
|
.with_description("Test tool")
|
|
|
|
.with_string_param("input", "Input parameter")
|
|
|
|
.build();
|
|
|
|
server->register_tool(test_tool, test_tool_handler);
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
client.set_timeout(5);
|
|
|
|
|
|
|
|
// Initialize client
|
|
|
|
bool initialized = client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
EXPECT_TRUE(initialized);
|
|
|
|
|
|
|
|
// Get tool list
|
|
|
|
auto tools = client.get_tools();
|
|
|
|
EXPECT_EQ(tools.size(), 1);
|
|
|
|
EXPECT_EQ(tools[0].name, "test_tool");
|
|
|
|
|
|
|
|
// Call tool
|
|
|
|
mcp::json tool_result = client.call_tool("test_tool", {{"input", "Test input"}});
|
2025-03-09 17:10:01 +08:00
|
|
|
EXPECT_EQ(tool_result["content"][0]["text"], "Result: Test input");
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test notification functionality
|
|
|
|
TEST_F(ServerTest, NotificationTest) {
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
|
|
|
|
// Add tool on server side, triggering notification
|
|
|
|
mcp::tool new_tool = mcp::tool_builder("new_tool")
|
|
|
|
.with_description("New tool")
|
|
|
|
.build();
|
|
|
|
server->register_tool(new_tool, [](const mcp::json& params) { return mcp::json::object(); });
|
|
|
|
|
|
|
|
// Wait for notification processing
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
|
|
|
2025-03-09 23:17:36 +08:00
|
|
|
// Verify tools were added
|
2025-03-08 23:44:34 +08:00
|
|
|
auto tools = client.get_tools();
|
|
|
|
EXPECT_EQ(tools.size(), 1);
|
|
|
|
EXPECT_EQ(tools[0].name, "new_tool");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test error handling
|
|
|
|
TEST_F(ServerTest, ErrorHandlingTest) {
|
|
|
|
// Register a tool handler that throws an exception
|
|
|
|
mcp::tool error_tool = mcp::tool_builder("error_tool")
|
|
|
|
.with_description("Error tool")
|
|
|
|
.build();
|
|
|
|
|
|
|
|
server->register_tool(error_tool, [](const mcp::json& params) -> mcp::json {
|
|
|
|
throw std::runtime_error("Test exception");
|
|
|
|
});
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
|
|
|
|
// Call tool that throws exception
|
2025-03-09 17:10:01 +08:00
|
|
|
mcp::json result = client.call_tool("error_tool");
|
|
|
|
EXPECT_EQ(result.contains("isError"), true);
|
|
|
|
EXPECT_EQ(result["isError"], true);
|
2025-03-08 23:44:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test server stop and restart
|
|
|
|
TEST_F(ServerTest, ServerRestartTest) {
|
|
|
|
// Start server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Create client and connect
|
|
|
|
mcp::client client("localhost", 8095);
|
|
|
|
bool initialized = client.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
EXPECT_TRUE(initialized);
|
|
|
|
|
|
|
|
// Stop server
|
|
|
|
server->stop();
|
|
|
|
server_thread.join();
|
|
|
|
|
|
|
|
// Wait to ensure server is fully stopped
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
|
|
|
|
// Try to connect, should fail - but may not throw exception, might just return false
|
|
|
|
mcp::client client2("localhost", 8095);
|
|
|
|
client2.set_timeout(1); // Set shorter timeout
|
|
|
|
|
|
|
|
// Try to initialize, should fail
|
|
|
|
bool init_result = client2.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
EXPECT_FALSE(init_result);
|
|
|
|
|
|
|
|
// Restart server
|
|
|
|
start_server();
|
|
|
|
|
|
|
|
// Try to connect again, should succeed
|
|
|
|
mcp::client client3("localhost", 8095);
|
|
|
|
initialized = client3.initialize("TestClient", mcp::MCP_VERSION);
|
|
|
|
EXPECT_TRUE(initialized);
|
|
|
|
}
|