main
hkr04 2025-03-13 00:04:18 +08:00
parent 1b54308d81
commit 8e11b5dc6d
14 changed files with 896 additions and 2666 deletions

View File

@ -109,6 +109,15 @@ struct request {
return j;
}
static request from_json(const json& j) {
request req;
req.jsonrpc = j["jsonrpc"].get<std::string>();
req.id = j["id"];
req.method = j["method"].get<std::string>();
req.params = j["params"];
return req;
}
private:
// Generate a unique ID
@ -171,6 +180,15 @@ struct response {
return j;
}
static response from_json(const json& j) {
response res;
res.jsonrpc = j["jsonrpc"].get<std::string>();
res.id = j["id"];
res.result = j["result"];
res.error = j["error"];
return res;
}
};
} // namespace mcp

View File

@ -8,6 +8,9 @@ project(${TEST_PROJECT_NAME})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find required packages
find_package(Threads REQUIRED)
# Use local Google Test
set(GTEST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/googletest)
add_subdirectory(${GTEST_ROOT} googletest-build)
@ -16,34 +19,29 @@ add_subdirectory(${GTEST_ROOT} googletest-build)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Include header directories
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${CMAKE_SOURCE_DIR}/common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${GTEST_ROOT}/googletest/include)
include_directories(${GTEST_ROOT}/googlemock/include)
# Add test source files
set(TEST_SOURCES
test_mcp_message.cpp
test_mcp_tool.cpp
test_mcp_resource.cpp
test_mcp_client.cpp
test_mcp_server.cpp
test_mcp_direct_requests.cpp
test_mcp_lifecycle_transport.cpp
test_mcp_versioning.cpp
test_mcp_tools_extended.cpp
mcp_test.cpp
)
# Create test executable
add_executable(${TEST_PROJECT_NAME} ${TEST_SOURCES})
# Link directories
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../build/src)
# Link Google Test and MCP library
target_link_libraries(${TEST_PROJECT_NAME} PRIVATE
gtest
gtest_main
gmock
gmock_main
mcp
${CMAKE_CURRENT_SOURCE_DIR}/../build/src/libmcp.a
Threads::Threads
)

413
test/mcp_test.cpp 100644
View File

@ -0,0 +1,413 @@
/**
* @file mcp_test.cpp
* @brief MCP
*
* MCPping
*/
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "mcp_message.h"
#include "mcp_client.h"
#include "mcp_server.h"
#include "mcp_tool.h"
using namespace mcp;
using json = nlohmann::ordered_json;
// 测试消息格式
class MessageFormatTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置测试环境
}
void TearDown() override {
// 清理测试环境
}
};
// 测试请求消息格式
TEST_F(MessageFormatTest, RequestMessageFormat) {
// 创建一个请求消息
request req = request::create("test_method", {{"key", "value"}});
// 转换为JSON
json req_json = req.to_json();
// 验证JSON格式是否符合规范
EXPECT_EQ(req_json["jsonrpc"], "2.0");
EXPECT_TRUE(req_json.contains("id"));
EXPECT_EQ(req_json["method"], "test_method");
EXPECT_EQ(req_json["params"]["key"], "value");
}
// 测试响应消息格式
TEST_F(MessageFormatTest, ResponseMessageFormat) {
// 创建一个成功响应
response res = response::create_success("test_id", {{"key", "value"}});
// 转换为JSON
json res_json = res.to_json();
// 验证JSON格式是否符合规范
EXPECT_EQ(res_json["jsonrpc"], "2.0");
EXPECT_EQ(res_json["id"], "test_id");
EXPECT_EQ(res_json["result"]["key"], "value");
EXPECT_FALSE(res_json.contains("error"));
}
// 测试错误响应消息格式
TEST_F(MessageFormatTest, ErrorResponseMessageFormat) {
// 创建一个错误响应
response res = response::create_error("test_id", error_code::invalid_params, "Invalid parameters", {{"details", "Missing required field"}});
// 转换为JSON
json res_json = res.to_json();
// 验证JSON格式是否符合规范
EXPECT_EQ(res_json["jsonrpc"], "2.0");
EXPECT_EQ(res_json["id"], "test_id");
EXPECT_FALSE(res_json.contains("result"));
EXPECT_EQ(res_json["error"]["code"], static_cast<int>(error_code::invalid_params));
EXPECT_EQ(res_json["error"]["message"], "Invalid parameters");
EXPECT_EQ(res_json["error"]["data"]["details"], "Missing required field");
}
// 测试通知消息格式
TEST_F(MessageFormatTest, NotificationMessageFormat) {
// 创建一个通知消息
request notification = request::create_notification("test_notification", {{"key", "value"}});
// 转换为JSON
json notification_json = notification.to_json();
// 验证JSON格式是否符合规范
EXPECT_EQ(notification_json["jsonrpc"], "2.0");
EXPECT_FALSE(notification_json.contains("id"));
EXPECT_EQ(notification_json["method"], "notifications/test_notification");
EXPECT_EQ(notification_json["params"]["key"], "value");
// 验证是否为通知消息
EXPECT_TRUE(notification.is_notification());
}
// 测试生命周期
class LifecycleTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置测试环境
server_ = std::make_unique<server>("localhost", 8080);
server_->set_server_info("TestServer", "1.0.0");
// 设置服务器能力
json server_capabilities = {
{"logging", json::object()},
{"prompts", {{"listChanged", true}}},
{"resources", {{"subscribe", true}, {"listChanged", true}}},
{"tools", {{"listChanged", true}}}
};
server_->set_capabilities(server_capabilities);
// 注册初始化方法处理器
server_->register_method("initialize", [this, server_capabilities](const json& params) -> json {
// 验证初始化请求参数
EXPECT_EQ(params["protocolVersion"], MCP_VERSION);
EXPECT_TRUE(params.contains("capabilities"));
EXPECT_TRUE(params.contains("clientInfo"));
// 返回初始化响应
return {
{"protocolVersion", MCP_VERSION},
{"capabilities", server_capabilities},
{"serverInfo", {
{"name", "TestServer"},
{"version", "1.0.0"}
}}
};
});
// 启动服务器(非阻塞模式)
server_->start(false);
// 创建客户端
json client_capabilities = {
{"roots", {{"listChanged", true}}},
{"sampling", json::object()}
};
client_ = std::make_unique<client>("localhost", 8080);
client_->set_capabilities(client_capabilities);
}
void TearDown() override {
// 清理测试环境
server_->stop();
server_.reset();
client_.reset();
}
std::unique_ptr<server> server_;
std::unique_ptr<client> client_;
};
// 测试初始化流程
TEST_F(LifecycleTest, InitializeProcess) {
// 执行初始化
bool init_result = client_->initialize("TestClient", "1.0.0");
// 验证初始化结果
EXPECT_TRUE(init_result);
// 验证服务器能力
json server_capabilities = client_->get_server_capabilities();
EXPECT_TRUE(server_capabilities.contains("logging"));
EXPECT_TRUE(server_capabilities.contains("prompts"));
EXPECT_TRUE(server_capabilities.contains("resources"));
EXPECT_TRUE(server_capabilities.contains("tools"));
}
// 测试版本控制
class VersioningTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置测试环境
server_ = std::make_unique<server>("localhost", 8081);
server_->set_server_info("TestServer", "1.0.0");
// 设置服务器能力
json server_capabilities = {
{"logging", json::object()},
{"prompts", {{"listChanged", true}}},
{"resources", {{"subscribe", true}, {"listChanged", true}}},
{"tools", {{"listChanged", true}}}
};
server_->set_capabilities(server_capabilities);
// 注册初始化方法处理器,检查版本
server_->register_method("initialize", [this, server_capabilities](const json& params) -> json {
// 检查协议版本
std::string requested_version = params["protocolVersion"];
if (requested_version != MCP_VERSION) {
throw mcp_exception(error_code::invalid_params, "Unsupported protocol version");
}
return {
{"protocolVersion", MCP_VERSION},
{"capabilities", server_capabilities},
{"serverInfo", {
{"name", "TestServer"},
{"version", "1.0.0"}
}}
};
});
// 启动服务器(非阻塞模式)
server_->start(false);
}
void TearDown() override {
// 清理测试环境
server_->stop();
server_.reset();
}
std::unique_ptr<server> server_;
};
// 测试支持的版本
TEST_F(VersioningTest, SupportedVersion) {
// 创建使用正确版本的客户端
client client_correct("localhost", 8081);
// 执行初始化
bool init_result = client_correct.initialize("TestClient", "1.0.0");
// 验证初始化结果
EXPECT_TRUE(init_result);
}
// 测试不支持的版本
TEST_F(VersioningTest, UnsupportedVersion) {
// Use httplib::Client to send a request with an unsupported version
// Note: Open SSE connection first
httplib::Client client("localhost", 8081);
auto sse_response = client.Get("/sse");
// EXPECT_EQ(sse_response->status, 200);
std::string msg_endpoint = sse_response->body;
json req = request::create("initialize", {{"protocolVersion", "0.0.1"}}).to_json();
auto res = client.Post(msg_endpoint.c_str(), req.dump(), "application/json");
EXPECT_EQ(res->status, 400);
try {
auto mcp_res = response::from_json(json::parse(res->body));
EXPECT_EQ(mcp_res.error["code"], error_code::invalid_params);
} catch (const mcp_exception& e) {
EXPECT_TRUE(false);
}
}
// 测试Ping功能
class PingTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置测试环境
server_ = std::make_unique<server>("localhost", 8082);
// 注册ping方法处理器
server_->register_method("ping", [](const json& params) -> json {
return json::object(); // 返回空对象
});
// 启动服务器(非阻塞模式)
server_->start(false);
// 创建客户端
client_ = std::make_unique<client>("localhost", 8082);
}
void TearDown() override {
// 清理测试环境
server_->stop();
server_.reset();
client_.reset();
}
std::unique_ptr<server> server_;
std::unique_ptr<client> client_;
};
// 测试Ping请求
TEST_F(PingTest, PingRequest) {
// 发送ping请求
bool ping_result = client_->ping();
// 验证ping结果
EXPECT_TRUE(ping_result);
}
// 测试工具功能
class ToolsTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置测试环境
server_ = std::make_unique<server>("localhost", 8083);
// 创建一个测试工具
tool test_tool;
test_tool.name = "get_weather";
test_tool.description = "Get current weather information for a location";
test_tool.parameters_schema = {
{"type", "object"},
{"properties", {
{"location", {
{"type", "string"},
{"description", "City name or zip code"}
}}
}},
{"required", json::array({"location"})}
};
// 注册工具
server_->register_tool(test_tool, [](const json& params) -> json {
// 简单的工具实现
std::string location = params["location"];
return {
{"content", json::array({
{
{"type", "text"},
{"text", "Current weather in " + location + ":\nTemperature: 72°F\nConditions: Partly cloudy"}
}
})},
{"isError", false}
};
});
// 注册工具列表方法
server_->register_method("tools/list", [this](const json& params) -> json {
return {
{"tools", json::array({
{
{"name", "get_weather"},
{"description", "Get current weather information for a location"},
{"inputSchema", {
{"type", "object"},
{"properties", {
{"location", {
{"type", "string"},
{"description", "City name or zip code"}
}}
}},
{"required", json::array({"location"})}
}}
}
})},
{"nextCursor", nullptr}
};
});
// 注册工具调用方法
server_->register_method("tools/call", [this](const json& params) -> json {
// 验证参数
EXPECT_EQ(params["name"], "get_weather");
EXPECT_EQ(params["arguments"]["location"], "New York");
// 返回工具调用结果
return {
{"content", json::array({
{
{"type", "text"},
{"text", "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"}
}
})},
{"isError", false}
};
});
// 启动服务器(非阻塞模式)
server_->start(false);
// 创建客户端
client_ = std::make_unique<client>("localhost", 8083);
client_->initialize("TestClient", "1.0.0");
}
void TearDown() override {
// 清理测试环境
server_->stop();
server_.reset();
client_.reset();
}
std::unique_ptr<server> server_;
std::unique_ptr<client> client_;
};
// 测试列出工具
TEST_F(ToolsTest, ListTools) {
// 调用列出工具方法
json tools_list = client_->send_request("tools/list").result;
// 验证工具列表
EXPECT_TRUE(tools_list.contains("tools"));
EXPECT_EQ(tools_list["tools"].size(), 1);
EXPECT_EQ(tools_list["tools"][0]["name"], "get_weather");
EXPECT_EQ(tools_list["tools"][0]["description"], "Get current weather information for a location");
}
// 测试调用工具
TEST_F(ToolsTest, CallTool) {
// 调用工具
json tool_result = client_->call_tool("get_weather", {{"location", "New York"}});
// 验证工具调用结果
EXPECT_TRUE(tool_result.contains("content"));
EXPECT_FALSE(tool_result["isError"]);
EXPECT_EQ(tool_result["content"][0]["type"], "text");
EXPECT_EQ(tool_result["content"][0]["text"], "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy");
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,61 +0,0 @@
/**
* @file test_client.cpp
* @brief MCP
*/
#include "mcp_client.h"
#include <iostream>
#include <thread>
#include <chrono>
int main() {
// 创建客户端
mcp::client client("localhost", 8080);
// 设置超时
client.set_timeout(30);
// 初始化客户端
std::cout << "正在初始化客户端..." << std::endl;
bool success = client.initialize("TestClient", "1.0.0");
if (!success) {
std::cerr << "初始化失败" << std::endl;
return 1;
}
std::cout << "初始化成功" << std::endl;
// 获取服务器能力
std::cout << "服务器能力: " << client.get_server_capabilities().dump(2) << std::endl;
// 获取可用工具
std::cout << "正在获取可用工具..." << std::endl;
auto tools = client.get_tools();
std::cout << "可用工具数量: " << tools.size() << std::endl;
for (const auto& tool : tools) {
std::cout << "工具: " << tool.name << " - " << tool.description << std::endl;
}
// 发送ping请求
std::cout << "正在发送ping请求..." << std::endl;
bool ping_result = client.ping();
std::cout << "Ping结果: " << (ping_result ? "成功" : "失败") << std::endl;
// 列出资源
std::cout << "正在列出资源..." << std::endl;
auto resources = client.list_resources();
std::cout << "资源: " << resources.dump(2) << std::endl;
// 测试多个并发请求
std::cout << "测试并发请求..." << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << "请求 " << i << "..." << std::endl;
auto response = client.send_request("ping");
std::cout << "响应 " << i << ": " << response.result.dump() << std::endl;
}
std::cout << "测试完成" << std::endl;
return 0;
}

View File

@ -1,195 +0,0 @@
/**
* @file test_mcp_client.cpp
* @brief Test MCP client related functionality
*
* This file contains unit tests for the MCP client module, based on the 2024-11-05 specification.
*/
#include "mcp_client.h"
#include "mcp_server.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <future>
// Mock tool handler function
mcp::json echo_tool_handler(const mcp::json& args) {
return {
{
{"type", "text"},
{"text", args["message"]}
}
};
}
// Client test class
class ClientTest : public ::testing::Test {
protected:
void SetUp() override {
// Create and configure server
server = std::make_unique<mcp::server>("localhost", 8090);
server->set_server_info("TestServer", "2024-11-05");
// Set server capabilities
mcp::json capabilities = {
{"tools", {{"listChanged", true}}}
};
server->set_capabilities(capabilities);
// Register tool
mcp::tool echo_tool = mcp::tool_builder("echo")
.with_description("Echo tool")
.with_string_param("message", "Message to echo")
.build();
server->register_tool(echo_tool, echo_tool_handler);
// Start server (non-blocking mode)
server_thread = std::thread([this]() {
server->start(false);
});
// Wait for server to start
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Create client
client = std::make_unique<mcp::client>("localhost", 8090);
client->set_timeout(5);
}
void TearDown() override {
// Stop server
if (server) {
server->stop();
}
// Wait for server thread to end
if (server_thread.joinable()) {
server_thread.join();
}
}
std::unique_ptr<mcp::server> server;
std::unique_ptr<mcp::client> client;
std::thread server_thread;
};
// Test client initialization
TEST_F(ClientTest, InitializeTest) {
// Initialize client
bool initialized = client->initialize("TestClient", "1.0.0");
EXPECT_TRUE(initialized);
}
// Test getting server capabilities
TEST_F(ClientTest, GetCapabilitiesTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Get server capabilities
mcp::json capabilities = client->get_server_capabilities();
// Verify capabilities are valid - may be object or other type depending on implementation
EXPECT_FALSE(capabilities.is_null());
// If it's an object, verify its contents
if (capabilities.is_object()) {
EXPECT_TRUE(capabilities.contains("tools"));
}
}
// Test getting tool list
TEST_F(ClientTest, GetToolsTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Get tool list
auto tools = client->get_tools();
EXPECT_EQ(tools.size(), 1);
EXPECT_EQ(tools[0].name, "echo");
EXPECT_EQ(tools[0].description, "Echo tool");
}
// Test calling tool
TEST_F(ClientTest, CallToolTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Call echo tool
mcp::json args = {{"message", "Test message"}};
mcp::json result = client->call_tool("echo", args);
EXPECT_TRUE(result.contains("content"));
EXPECT_TRUE(result["content"].is_array());
EXPECT_EQ(result["content"].size(), 1);
EXPECT_EQ(result["content"][0]["type"], "text");
EXPECT_EQ(result["content"][0]["text"], "Test message");
}
// Test error handling
TEST_F(ClientTest, ErrorHandlingTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Call non-existent tool
EXPECT_THROW(client->call_tool("non_existent_tool"), mcp::mcp_exception);
}
// Test setting client capabilities
TEST_F(ClientTest, SetCapabilitiesTest) {
// Set client capabilities
mcp::json capabilities = {
{"roots", {{"listChanged", true}}},
{"sampling", {{"enabled", true}}}
};
client->set_capabilities(capabilities);
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Verify initialization successful - Note: this method may not exist, modify according to actual implementation
// EXPECT_EQ(client->get_server_name(), "TestServer");
// Alternative test: ensure client can still call methods
EXPECT_NO_THROW(client->get_server_capabilities());
}
// Test request cancellation
TEST_F(ClientTest, CancellationTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Create a long-running task
auto future = std::async(std::launch::async, [this]() {
try {
// This call should be cancelled
return client->call_tool("echo", {{"message", "This call should be cancelled"}});
} catch (const mcp::mcp_exception& e) {
// Expect to catch cancellation exception
return mcp::json{{"cancelled", true}};
}
});
// Cancel all requests - Note: this method may not exist, modify according to actual implementation
// client->cancel_all_requests();
// Get result
mcp::json result = future.get();
// Since we didn't actually cancel the request, it should return normal result
EXPECT_TRUE(result.contains("content"));
EXPECT_TRUE(result["content"].is_array());
EXPECT_EQ(result["content"].size(), 1);
EXPECT_EQ(result["content"][0]["type"], "text");
EXPECT_EQ(result["content"][0]["text"], "This call should be cancelled");
}
TEST_F(ClientTest, PingTest) {
// Initialize client
client->initialize("TestClient", mcp::MCP_VERSION);
// Send ping
EXPECT_TRUE(client->ping());
}

View File

@ -1,719 +0,0 @@
/**
* @file test_mcp_direct_requests.cpp
* @brief Test direct POST requests to MCP server
*
* This file contains tests for direct HTTP requests to the MCP server,
* testing various request types and ID formats.
* Based on specification 2024-11-05.
*/
#include "mcp_server.h"
#include "mcp_client.h"
#include "mcp_tool.h"
#include <gtest/gtest.h>
#include <httplib.h>
#include <thread>
#include <chrono>
#include <filesystem>
// Mock tool handler function
static mcp::json test_tool_handler(const mcp::json& params) {
if (params.contains("input")) {
return {
{
{"type", "text"},
{"text", "Result: " + params["input"].get<std::string>()}
}
};
} else {
return {
{
{"type", "text"},
{"text", "Default result"}
}
};
}
}
// Test fixture for setting up and cleaning up the test environment
class DirectRequestTest : public ::testing::Test {
protected:
void SetUp() override {
// Create and configure server
server = std::make_unique<mcp::server>("localhost", 8096);
server->set_server_info("TestServer", mcp::MCP_VERSION);
// Set server capabilities
mcp::json capabilities = {
{"tools", {{"listChanged", true}}}
};
server->set_capabilities(capabilities);
// Register tools
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
server_thread = std::thread([this]() {
server->start(true);
});
// Wait for server to start
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Create HTTP client
http_client = std::make_unique<httplib::Client>("localhost", 8096);
http_client->set_connection_timeout(5, 0);
http_client->set_read_timeout(5, 0);
http_client->set_write_timeout(5, 0);
}
void TearDown() override {
// Stop server
server->stop();
// Wait for server thread to end
if (server_thread.joinable()) {
server_thread.join();
}
}
// Send JSON-RPC request and return response
mcp::json send_jsonrpc_request(const mcp::json& request) {
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
auto res = http_client->Post("/jsonrpc", headers, request.dump(), "application/json");
EXPECT_TRUE(res != nullptr);
if (!res) {
return mcp::json::object();
}
// 检查状态码202表示请求已接受但响应将通过SSE发送
if (res->status == 202) {
// 在实际测试中我们需要等待SSE响应
// 但在这个测试中,我们只是返回一个空对象
// 实际应用中应该使用客户端类来处理这种情况
std::cout << "收到202 Accepted响应实际响应将通过SSE发送" << std::endl;
return mcp::json::object();
}
EXPECT_EQ(res->status, 200);
try {
return mcp::json::parse(res->body);
} catch (const mcp::json::exception& e) {
ADD_FAILURE() << "Failed to parse response: " << e.what();
return mcp::json::object();
}
}
void mock_initialize() {
// Mock initialization request
mcp::json init_request = {
{"jsonrpc", "2.0"},
{"id", "init-1"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
send_jsonrpc_request(init_request);
send_jsonrpc_request({
{"jsonrpc", "2.0"},
{"method", "notifications/initialized"}
});
}
std::unique_ptr<mcp::server> server;
std::unique_ptr<httplib::Client> http_client;
std::thread server_thread;
};
// Test if server can handle requests w/ and w/o initialization
TEST_F(DirectRequestTest, InitializationTest) {
// Send request without initialization
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "no-init-1"},
{"method", "ping"},
{"params", {}}
};
mcp::json response = send_jsonrpc_request(request);
// Only ping and logging methods should be supported without initialization
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "no-init-1");
EXPECT_TRUE(response.contains("result"));
request = {
{"jsonrpc", "2.0"},
{"id", "no-init-2"},
{"method", "tools/call"},
{"params", {
{"name", "test_tool"},
{"arguments", {
{"input", "Test input"}
}}
}}
};
response = send_jsonrpc_request(request);
// Should return error
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "no-init-2");
EXPECT_FALSE(response.contains("result"));
EXPECT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::invalid_request));
// Mock initialization request
mcp::json init_request = {
{"jsonrpc", "2.0"},
{"id", "init-1"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
response = send_jsonrpc_request(init_request);
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-1");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
response = send_jsonrpc_request({
{"jsonrpc", "2.0"},
{"method", "notifications/initialized"}
});
EXPECT_TRUE(response.empty());
// Now all methods should be supported
request = {
{"jsonrpc", "2.0"},
{"id", "init-2"},
{"method", "ping"},
{"params", {}}
};
response = send_jsonrpc_request(request);
// Should return result
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-2");
EXPECT_TRUE(response.contains("result"));
request = {
{"jsonrpc", "2.0"},
{"id", "init-3"},
{"method", "tools/call"},
{"params", {
{"name", "test_tool"},
{"arguments", {
{"input", "Test input"}
}}
}}
};
response = send_jsonrpc_request(request);
// Should return result
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-3");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
EXPECT_TRUE(response["result"].contains("content"));
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
}
// Test initialization with numeric ID
TEST_F(DirectRequestTest, InitializeWithNumericId) {
// Create initialization request with numeric ID
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", 12345},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {
{"tools", {{"listChanged", true}}}
}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
// Send request
mcp::json response = send_jsonrpc_request(request);
// Verify response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], 12345);
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
// Verify result content
EXPECT_EQ(response["result"]["protocolVersion"], mcp::MCP_VERSION);
EXPECT_TRUE(response["result"].contains("capabilities"));
EXPECT_TRUE(response["result"].contains("serverInfo"));
EXPECT_EQ(response["result"]["serverInfo"]["name"], "TestServer");
}
// Test initialization with string ID
TEST_F(DirectRequestTest, InitializeWithStringId) {
// Create initialization request with string ID
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "request-id-abc123"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {
{"tools", {{"listChanged", true}}}
}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
// Send request
mcp::json response = send_jsonrpc_request(request);
// Verify response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "request-id-abc123");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
// Verify result content
EXPECT_EQ(response["result"]["protocolVersion"], mcp::MCP_VERSION);
EXPECT_TRUE(response["result"].contains("capabilities"));
EXPECT_TRUE(response["result"].contains("serverInfo"));
EXPECT_EQ(response["result"]["serverInfo"]["name"], "TestServer");
}
// Test getting tools list
TEST_F(DirectRequestTest, GetTools) {
// Initialize first
mock_initialize();
// Get tools list
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", 2},
{"method", "getTools"},
{"params", {}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], 2);
// Server may not implement getTools method, so check if error is returned
if (response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
} else {
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
// Verify tools list
EXPECT_TRUE(response["result"].is_array());
EXPECT_GE(response["result"].size(), 1);
// Verify test tool
bool found_test_tool = false;
for (const auto& tool : response["result"]) {
if (tool["name"] == "test_tool") {
found_test_tool = true;
EXPECT_EQ(tool["description"], "Test Tool");
EXPECT_TRUE(tool.contains("parameters"));
break;
}
}
EXPECT_TRUE(found_test_tool);
}
}
// Test calling a tool
TEST_F(DirectRequestTest, CallTool) {
// Initialize first
mock_initialize();
// Call tool
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "call-1"},
{"method", "tools/call"},
{"params", {
{"name", "test_tool"},
{"arguments", {
{"input", "Test input"}
}}
}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "call-1");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
// Verify tool call result
EXPECT_TRUE(response["result"].contains("content"));
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
}
// Test sending notification (no ID)
TEST_F(DirectRequestTest, SendNotification) {
// Initialize first
mcp::json init_request = {
{"jsonrpc", "2.0"},
{"id", 200},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
send_jsonrpc_request(init_request);
// Send notification
mcp::json notification = {
{"jsonrpc", "2.0"},
{"method", "initialized"},
{"params", {}}
};
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
auto res = http_client->Post("/jsonrpc", headers, notification.dump(), "application/json");
// Verify response (notifications may have empty response or error response)
EXPECT_TRUE(res != nullptr);
// 状态码可能是200或202取决于服务器实现
EXPECT_TRUE(res->status == 200 || res->status == 202);
}
// Test error handling - method not found
TEST_F(DirectRequestTest, MethodNotFound) {
mock_initialize();
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", 999},
{"method", "nonExistentMethod"},
{"params", {}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify error response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], 999);
EXPECT_FALSE(response.contains("result"));
EXPECT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
}
// Test error handling - invalid parameters
TEST_F(DirectRequestTest, InvalidParams) {
// Initialize first
mock_initialize();
// Call tool but missing required parameters
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "invalid-params"},
{"method", "callTool"},
{"params", {
// Missing tool name
{"parameters", {
{"input", "Test input"}
}}
}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify error response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "invalid-params");
EXPECT_FALSE(response.contains("result"));
EXPECT_TRUE(response.contains("error"));
// Server may return method_not_found or invalid_params error
int error_code = response["error"]["code"];
EXPECT_TRUE(error_code == static_cast<int>(mcp::error_code::method_not_found) ||
error_code == static_cast<int>(mcp::error_code::invalid_params));
}
// Test logging functionality
TEST_F(DirectRequestTest, LoggingTest) {
// Initialize first
mock_initialize();
// Send log request
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "log-1"},
{"method", "log"},
{"params", {
{"level", "info"},
{"message", "This is a test log message"},
{"details", {
{"source", "test_case"},
{"timestamp", "2023-01-01T12:00:00Z"}
}}
}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify response
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "log-1");
// Server may not implement log method
if (response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
} else {
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
}
}
// Test sampling functionality
TEST_F(DirectRequestTest, SamplingTest) {
// Initialize first
mock_initialize();
// Send sampling request
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", 301},
{"method", "sample"},
{"params", {
{"prompt", "This is a test prompt"},
{"parameters", {
{"temperature", 0.7},
{"max_tokens", 100}
}}
}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify response (may fail, as server may not implement sampling functionality)
if (response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
} else {
EXPECT_TRUE(response.contains("result"));
}
}
// Test batch request processing
TEST_F(DirectRequestTest, BatchRequestTest) {
// Create batch request
mcp::json batch_request = mcp::json::array();
// Add initialization request
batch_request.push_back({
{"jsonrpc", "2.0"},
{"id", "batch-1"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
});
// Add ping request
batch_request.push_back({
{"jsonrpc", "2.0"},
{"id", "batch-2"},
{"method", "ping"},
{"params", {}}
});
// Add notification
batch_request.push_back({
{"jsonrpc", "2.0"},
{"method", "initialized"},
{"params", {}}
});
// Send batch request
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
auto res = http_client->Post("/jsonrpc", headers, batch_request.dump(), "application/json");
// Verify response
EXPECT_TRUE(res != nullptr);
EXPECT_EQ(res->status, 200);
// Parse response
try {
mcp::json response = mcp::json::parse(res->body);
// Batch requests may not be supported, so check if error is returned
if (response.is_object() && response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::invalid_request));
EXPECT_TRUE(response["error"]["message"].get<std::string>().find("Batch") != std::string::npos);
} else if (response.is_array()) {
// If batch requests are supported, should return an array
EXPECT_GE(response.size(), 2); // At least two responses (not including notification)
// Check each response
for (const auto& resp : response) {
EXPECT_EQ(resp["jsonrpc"], "2.0");
EXPECT_TRUE(resp.contains("id"));
if (resp["id"] == "batch-1") {
EXPECT_TRUE(resp.contains("result"));
EXPECT_TRUE(resp["result"].contains("protocolVersion"));
} else if (resp["id"] == "batch-2") {
EXPECT_TRUE(resp.contains("result"));
EXPECT_TRUE(resp["result"].contains("pong"));
}
}
}
} catch (const mcp::json::exception& e) {
ADD_FAILURE() << "Failed to parse batch response: " << e.what();
}
}
// Test request cancellation
TEST_F(DirectRequestTest, CancelRequestTest) {
// Initialize first
mock_initialize();
// Send cancel request
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "cancel-1"},
{"method", "$/cancelRequest"},
{"params", {
{"id", "some-request-id"}
}}
};
mcp::json response = send_jsonrpc_request(request);
// Verify response (may fail, as server may not implement cancellation functionality)
if (response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
} else {
EXPECT_TRUE(response.contains("result"));
}
}
// Test using different types of IDs
TEST_F(DirectRequestTest, DifferentIdTypesTest) {
// Use integer ID
mcp::json int_request = {
{"jsonrpc", "2.0"},
{"id", 42},
{"method", "ping"},
{"params", {}}
};
mcp::json int_response = send_jsonrpc_request(int_request);
EXPECT_EQ(int_response["id"], 42);
// Use string ID
mcp::json string_request = {
{"jsonrpc", "2.0"},
{"id", "string-id-42"},
{"method", "ping"},
{"params", {}}
};
mcp::json string_response = send_jsonrpc_request(string_request);
EXPECT_EQ(string_response["id"], "string-id-42");
// Use float ID (should also work)
mcp::json float_request = {
{"jsonrpc", "2.0"},
{"id", 3.14},
{"method", "ping"},
{"params", {}}
};
mcp::json float_response = send_jsonrpc_request(float_request);
EXPECT_EQ(float_response["id"], 3.14);
// Use null ID (this should be treated as a notification)
mcp::json null_request = {
{"jsonrpc", "2.0"},
{"id", nullptr},
{"method", "ping"},
{"params", {}}
};
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
auto res = http_client->Post("/jsonrpc", headers, null_request.dump(), "application/json");
EXPECT_TRUE(res != nullptr);
EXPECT_EQ(res->status, 200);
// Use complex object as ID (this may not be supported)
mcp::json complex_request = {
{"jsonrpc", "2.0"},
{"id", {{"complex", "id"}}},
{"method", "ping"},
{"params", {}}
};
mcp::json complex_response = send_jsonrpc_request(complex_request);
// Verify response (may fail, as server may not support complex objects as ID)
if (complex_response.contains("error")) {
EXPECT_EQ(complex_response["error"]["code"], static_cast<int>(mcp::error_code::invalid_request));
}
}

View File

@ -1,184 +0,0 @@
/**
* @file test_mcp_lifecycle_transport.cpp
* @brief MCP
*
* MCPSSE2024-11-05
*/
#include "mcp_message.h"
#include "mcp_server.h"
#include "mcp_client.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <future>
#include <chrono>
// 测试类,用于设置服务器和客户端
class McpLifecycleTransportTest : public ::testing::Test {
protected:
void SetUp() override {
// 创建服务器
server = std::make_unique<mcp::server>("localhost", 8096);
server->set_server_info("TestServer", "2024-11-05");
// 设置服务器能力
mcp::json capabilities = {
{"tools", {{"listChanged", true}}},
{"transport", {{"sse", true}}}
};
server->set_capabilities(capabilities);
// 注册一个简单的方法
server->register_method("test_method", [](const mcp::json& params) -> mcp::json {
return {{"result", "success"}, {"params_received", params}};
});
// 注册一个通知处理器
server->register_notification("test_notification", [this](const mcp::json& params) {
notification_received = true;
notification_params = params;
});
}
void TearDown() override {
// 停止服务器
if (server && server_thread.joinable()) {
server->stop();
server_thread.join();
}
}
// 启动服务器
void start_server() {
server_thread = std::thread([this]() {
server->start(false);
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::unique_ptr<mcp::server> server;
std::thread server_thread;
bool notification_received = false;
mcp::json notification_params;
};
// 测试消息生命周期 - 初始化
TEST_F(McpLifecycleTransportTest, InitializationTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8096);
client.set_timeout(5);
// 测试初始化
bool init_result = client.initialize("TestClient", "1.0.0");
EXPECT_TRUE(init_result);
// 获取服务器能力
mcp::json server_capabilities = client.get_server_capabilities();
EXPECT_TRUE(server_capabilities.contains("tools"));
EXPECT_TRUE(server_capabilities.contains("transport"));
EXPECT_TRUE(server_capabilities["transport"]["sse"].get<bool>());
}
// 测试消息生命周期 - 请求和响应
TEST_F(McpLifecycleTransportTest, RequestResponseTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8096);
client.set_timeout(5);
client.initialize("TestClient", "1.0.0");
// 发送请求并获取响应
mcp::json params = {{"key", "value"}, {"number", 42}};
mcp::response response = client.send_request("test_method", params);
// 验证响应
EXPECT_FALSE(response.is_error());
EXPECT_EQ(response.result["result"], "success");
EXPECT_EQ(response.result["params_received"]["key"], "value");
EXPECT_EQ(response.result["params_received"]["number"], 42);
}
// 测试消息生命周期 - 通知
TEST_F(McpLifecycleTransportTest, NotificationTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8096);
client.set_timeout(5);
client.initialize("TestClient", "1.0.0");
// 发送通知
mcp::json params = {{"event", "update"}, {"status", "completed"}};
client.send_notification("test_notification", params);
// 等待通知处理
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 验证通知已接收
EXPECT_TRUE(notification_received);
EXPECT_EQ(notification_params["event"], "update");
EXPECT_EQ(notification_params["status"], "completed");
}
// 测试SSE传输 - 使用ping方法测试SSE连接
TEST_F(McpLifecycleTransportTest, SseTransportTest) {
// 启动服务器
start_server();
// 注册一个特殊的方法用于测试SSE连接
server->register_method("sse_test", [](const mcp::json& params) -> mcp::json {
return {{"sse_test_result", true}};
});
// 创建客户端
mcp::client client("localhost", 8096);
client.set_timeout(5);
client.initialize("TestClient", "1.0.0");
// 等待SSE连接建立
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// 测试SSE连接是否正常工作 - 使用ping方法
bool ping_result = client.ping();
EXPECT_TRUE(ping_result);
// 发送请求并获取响应验证SSE连接正常工作
mcp::response response = client.send_request("sse_test");
EXPECT_FALSE(response.is_error());
EXPECT_TRUE(response.result["sse_test_result"].get<bool>());
}
// 测试ping功能
TEST_F(McpLifecycleTransportTest, PingTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8096);
client.set_timeout(5);
client.initialize("TestClient", "1.0.0");
// 测试ping
bool ping_result = client.ping();
EXPECT_TRUE(ping_result);
// 停止服务器
server->stop();
server_thread.join();
// 等待服务器完全停止
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// 再次测试ping应该失败
ping_result = client.ping();
EXPECT_FALSE(ping_result);
}

View File

@ -1,279 +0,0 @@
/**
* @file test_mcp_message.cpp
* @brief Test MCP message related functionality
*
* This file contains unit tests for the MCP message module, based on specification 2024-11-05.
*/
#include "mcp_message.h"
#include <gtest/gtest.h>
#include <string>
// Test MCP exception
TEST(McpMessageTest, ExceptionTest) {
// Create an MCP exception
mcp::mcp_exception ex(mcp::error_code::invalid_params, "Test error message");
// Verify error code and message
EXPECT_EQ(ex.code(), mcp::error_code::invalid_params);
EXPECT_STREQ(ex.what(), "Test error message");
}
// Test request creation
TEST(McpMessageTest, RequestCreationTest) {
// Create a request
mcp::json params = {{"key", "value"}, {"number", 42}};
mcp::request req = mcp::request::create("test_method", params);
// Verify request properties
EXPECT_EQ(req.jsonrpc, "2.0");
EXPECT_FALSE(req.id.is_null());
EXPECT_TRUE(req.id.is_number());
EXPECT_EQ(req.method, "test_method");
EXPECT_EQ(req.params["key"], "value");
EXPECT_EQ(req.params["number"], 42);
// Verify it's not a notification
EXPECT_FALSE(req.is_notification());
}
// Test notification creation
TEST(McpMessageTest, NotificationCreationTest) {
// Create a notification
mcp::json params = {{"event", "update"}, {"status", "completed"}};
mcp::request notification = mcp::request::create_notification("event_notification", params);
// Verify notification properties
EXPECT_EQ(notification.jsonrpc, "2.0");
EXPECT_TRUE(notification.id.is_null());
EXPECT_EQ(notification.method, "notifications/event_notification");
EXPECT_EQ(notification.params["event"], "update");
EXPECT_EQ(notification.params["status"], "completed");
// Verify it is a notification
EXPECT_TRUE(notification.is_notification());
}
// Test request conversion to JSON
TEST(McpMessageTest, RequestToJsonTest) {
// Create a request
mcp::json params = {{"param1", "value1"}};
mcp::request req = mcp::request::create("test_method", params);
// Convert to JSON
mcp::json json_req = req.to_json();
// Verify JSON structure
EXPECT_EQ(json_req["jsonrpc"], "2.0");
EXPECT_EQ(json_req["method"], "test_method");
EXPECT_EQ(json_req["params"]["param1"], "value1");
EXPECT_FALSE(json_req["id"].empty());
}
// Test notification conversion to JSON
TEST(McpMessageTest, NotificationToJsonTest) {
// Create a notification
mcp::request notification = mcp::request::create_notification("test_notification");
// Convert to JSON
mcp::json json_notification = notification.to_json();
// Verify JSON structure
EXPECT_EQ(json_notification["jsonrpc"], "2.0");
EXPECT_EQ(json_notification["method"], "notifications/test_notification");
EXPECT_FALSE(json_notification.contains("id"));
}
// Test success response creation
TEST(McpMessageTest, SuccessResponseCreationTest) {
// Create a success response
mcp::json result = {{"result_key", "result_value"}};
std::string req_id = "request_id_123";
mcp::response res = mcp::response::create_success(req_id, result);
// Verify response properties
EXPECT_EQ(res.jsonrpc, "2.0");
EXPECT_EQ(res.id, req_id);
EXPECT_EQ(res.result["result_key"], "result_value");
EXPECT_TRUE(res.error.empty());
// Verify it's not an error response
EXPECT_FALSE(res.is_error());
}
// Test error response creation
TEST(McpMessageTest, ErrorResponseCreationTest) {
// Create an error response
mcp::json error_data = {{"detail", "Detailed error information"}};
std::string req_id = "request_id_456";
mcp::response res = mcp::response::create_error(
req_id,
mcp::error_code::method_not_found,
"Method not found",
error_data
);
// Verify response properties
EXPECT_EQ(res.jsonrpc, "2.0");
EXPECT_EQ(res.id, req_id);
EXPECT_TRUE(res.result.empty());
EXPECT_EQ(res.error["code"], static_cast<int>(mcp::error_code::method_not_found));
EXPECT_EQ(res.error["message"], "Method not found");
EXPECT_EQ(res.error["data"]["detail"], "Detailed error information");
// Verify it is an error response
EXPECT_TRUE(res.is_error());
}
// Test response conversion to JSON
TEST(McpMessageTest, ResponseToJsonTest) {
// Create a success response
mcp::json result = {{"success", true}};
std::string req_id = "request_id_789";
mcp::response res = mcp::response::create_success(req_id, result);
// Convert to JSON
mcp::json json_res = res.to_json();
// Verify JSON structure
EXPECT_EQ(json_res["jsonrpc"], "2.0");
EXPECT_EQ(json_res["id"], req_id);
EXPECT_EQ(json_res["result"]["success"], true);
EXPECT_FALSE(json_res.contains("error"));
// Create an error response
std::string error_req_id = "request_id_999";
mcp::response error_res = mcp::response::create_error(
error_req_id,
mcp::error_code::invalid_params,
"Invalid parameters"
);
// Convert to JSON
mcp::json json_error = error_res.to_json();
// Verify JSON structure
EXPECT_EQ(json_error["jsonrpc"], "2.0");
EXPECT_EQ(json_error["id"], error_req_id);
EXPECT_EQ(json_error["error"]["code"], static_cast<int>(mcp::error_code::invalid_params));
EXPECT_EQ(json_error["error"]["message"], "Invalid parameters");
EXPECT_FALSE(json_error.contains("result"));
}
// Test request creation with numeric ID
TEST(McpMessageTest, RequestWithNumericIdTest) {
// Create request with numeric ID
int numeric_id = 42;
mcp::json params = {{"key", "value"}};
mcp::request req = mcp::request::create_with_id(numeric_id, "test_method", params);
// Verify request properties
EXPECT_EQ(req.jsonrpc, "2.0");
EXPECT_EQ(req.id, numeric_id);
EXPECT_EQ(req.method, "test_method");
EXPECT_EQ(req.params["key"], "value");
// Verify it's not a notification
EXPECT_FALSE(req.is_notification());
// Convert to JSON and verify
mcp::json json_req = req.to_json();
EXPECT_EQ(json_req["id"], numeric_id);
}
// Test request creation with string ID
TEST(McpMessageTest, RequestWithStringIdTest) {
// Create request with string ID
std::string string_id = "custom-id-123";
mcp::json params = {{"key", "value"}};
mcp::request req = mcp::request::create_with_id(string_id, "test_method", params);
// Verify request properties
EXPECT_EQ(req.jsonrpc, "2.0");
EXPECT_EQ(req.id, string_id);
EXPECT_EQ(req.method, "test_method");
EXPECT_EQ(req.params["key"], "value");
// Verify it's not a notification
EXPECT_FALSE(req.is_notification());
// Convert to JSON and verify
mcp::json json_req = req.to_json();
EXPECT_EQ(json_req["id"], string_id);
}
// Test response creation with numeric ID
TEST(McpMessageTest, ResponseWithNumericIdTest) {
// Create success response with numeric ID
int numeric_id = 123;
mcp::json result = {{"result_key", "result_value"}};
mcp::response res = mcp::response::create_success(numeric_id, result);
// Verify response properties
EXPECT_EQ(res.jsonrpc, "2.0");
EXPECT_EQ(res.id, numeric_id);
EXPECT_EQ(res.result["result_key"], "result_value");
EXPECT_TRUE(res.error.empty());
// Verify it's not an error response
EXPECT_FALSE(res.is_error());
// Convert to JSON and verify
mcp::json json_res = res.to_json();
EXPECT_EQ(json_res["id"], numeric_id);
// Create error response with numeric ID
mcp::response error_res = mcp::response::create_error(
numeric_id,
mcp::error_code::invalid_params,
"Invalid parameters"
);
// Verify error response properties
EXPECT_EQ(error_res.jsonrpc, "2.0");
EXPECT_EQ(error_res.id, numeric_id);
EXPECT_TRUE(error_res.result.empty());
EXPECT_EQ(error_res.error["code"], static_cast<int>(mcp::error_code::invalid_params));
// Convert to JSON and verify
mcp::json json_error = error_res.to_json();
EXPECT_EQ(json_error["id"], numeric_id);
}
// Test response creation with string ID
TEST(McpMessageTest, ResponseWithStringIdTest) {
// Create success response with string ID
std::string string_id = "response-id-456";
mcp::json result = {{"result_key", "result_value"}};
mcp::response res = mcp::response::create_success(string_id, result);
// Verify response properties
EXPECT_EQ(res.jsonrpc, "2.0");
EXPECT_EQ(res.id, string_id);
EXPECT_EQ(res.result["result_key"], "result_value");
EXPECT_TRUE(res.error.empty());
// Verify it's not an error response
EXPECT_FALSE(res.is_error());
// Convert to JSON and verify
mcp::json json_res = res.to_json();
EXPECT_EQ(json_res["id"], string_id);
// Create error response with string ID
mcp::response error_res = mcp::response::create_error(
string_id,
mcp::error_code::invalid_params,
"Invalid parameters"
);
// Verify error response properties
EXPECT_EQ(error_res.jsonrpc, "2.0");
EXPECT_EQ(error_res.id, string_id);
EXPECT_TRUE(error_res.result.empty());
EXPECT_EQ(error_res.error["code"], static_cast<int>(mcp::error_code::invalid_params));
// Convert to JSON and verify
mcp::json json_error = error_res.to_json();
EXPECT_EQ(json_error["id"], string_id);
}

View File

@ -1,261 +0,0 @@
/**
* @file test_mcp_resource.cpp
* @brief Test MCP resource related functionality
*
* This file contains unit tests for the MCP resource module, based on the 2024-11-05 specification.
*/
#include "mcp_resource.h"
#include "mcp_server.h"
#include "mcp_client.h"
#include "base64.hpp"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <thread>
#include <chrono>
// Create a test directory and file
class ResourceTest : public ::testing::Test {
protected:
void SetUp() override {
// Create test directory
test_dir = std::filesystem::temp_directory_path() / "mcp_test";
std::filesystem::create_directories(test_dir);
// Create test file
test_file = test_dir / "test.txt";
std::ofstream file(test_file);
file << "Test file content";
file.close();
// Output test directory and file path for debugging
std::cout << "Test directory: " << test_dir << std::endl;
std::cout << "Test file: " << test_file << std::endl;
// Ensure file exists
ASSERT_TRUE(std::filesystem::exists(test_file)) << "Test file was not created successfully";
}
void TearDown() override {
// Clean up test directory
std::filesystem::remove_all(test_dir);
}
std::filesystem::path test_dir;
std::filesystem::path test_file;
};
// Test text resource
TEST(McpResourceTest, TextResourceTest) {
// Create text resource
mcp::text_resource resource("test://example.txt", "example.txt", "text/plain", "Example text resource");
// Test metadata
mcp::json metadata = resource.get_metadata();
EXPECT_EQ(metadata["uri"], "test://example.txt");
EXPECT_EQ(metadata["name"], "example.txt");
EXPECT_EQ(metadata["mimeType"], "text/plain");
EXPECT_EQ(metadata["description"], "Example text resource");
// Test URI
EXPECT_EQ(resource.get_uri(), "test://example.txt");
// Test setting and getting text
std::string test_content = "This is a test content";
resource.set_text(test_content);
EXPECT_EQ(resource.get_text(), test_content);
// Test read
mcp::json content = resource.read();
EXPECT_EQ(content["uri"], "test://example.txt");
EXPECT_EQ(content["mimeType"], "text/plain");
EXPECT_EQ(content["text"], test_content);
// Test modification
EXPECT_FALSE(resource.is_modified());
resource.set_text("New content");
EXPECT_TRUE(resource.is_modified());
// Test read after modification
content = resource.read();
EXPECT_EQ(content["text"], "New content");
EXPECT_FALSE(resource.is_modified());
}
// Test binary resource
TEST(McpResourceTest, BinaryResourceTest) {
// Create binary resource
mcp::binary_resource resource("test://example.bin", "example.bin", "application/octet-stream", "Example binary resource");
// Test metadata
mcp::json metadata = resource.get_metadata();
EXPECT_EQ(metadata["uri"], "test://example.bin");
EXPECT_EQ(metadata["name"], "example.bin");
EXPECT_EQ(metadata["mimeType"], "application/octet-stream");
EXPECT_EQ(metadata["description"], "Example binary resource");
// Test URI
EXPECT_EQ(resource.get_uri(), "test://example.bin");
// Test setting and getting binary data
std::vector<uint8_t> test_data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello" in ASCII
resource.set_data(test_data.data(), test_data.size());
const auto& data = resource.get_data();
EXPECT_EQ(data.size(), test_data.size());
EXPECT_TRUE(std::equal(data.begin(), data.end(), test_data.begin()));
// Test read
mcp::json content = resource.read();
EXPECT_EQ(content["uri"], "test://example.bin");
EXPECT_EQ(content["mimeType"], "application/octet-stream");
// Base64 encode the data for comparison
std::string base64_data = base64::encode(reinterpret_cast<const char*>(test_data.data()), test_data.size());
EXPECT_EQ(content["blob"], base64_data);
// Test modification
EXPECT_FALSE(resource.is_modified());
std::vector<uint8_t> new_data = {0x57, 0x6F, 0x72, 0x6C, 0x64}; // "World" in ASCII
resource.set_data(new_data.data(), new_data.size());
EXPECT_TRUE(resource.is_modified());
// Test read after modification
content = resource.read();
std::string new_base64_data = base64::encode(reinterpret_cast<const char*>(new_data.data()), new_data.size());
EXPECT_EQ(content["blob"], new_base64_data);
EXPECT_FALSE(resource.is_modified());
}
// Test file resource
TEST_F(ResourceTest, FileResourceTest) {
// Create file resource
mcp::file_resource resource(test_file.string());
// Test metadata
mcp::json metadata = resource.get_metadata();
EXPECT_EQ(metadata["uri"], "file://" + test_file.string());
EXPECT_EQ(metadata["name"], test_file.filename().string());
EXPECT_EQ(metadata["mimeType"], "text/plain");
// Test URI
EXPECT_EQ(resource.get_uri(), "file://" + test_file.string());
// Test read
mcp::json content = resource.read();
EXPECT_EQ(content["uri"], "file://" + test_file.string());
EXPECT_EQ(content["mimeType"], "text/plain");
EXPECT_EQ(content["text"], "Test file content");
// Test modification detection
EXPECT_FALSE(resource.is_modified());
// Modify file
{
std::ofstream file(test_file);
file << "Modified content";
file.close();
}
// Test modification detection after file change
EXPECT_TRUE(resource.is_modified());
// Test read after modification
content = resource.read();
EXPECT_EQ(content["text"], "Modified content");
EXPECT_FALSE(resource.is_modified());
// Test file deletion detection
std::filesystem::remove(test_file);
EXPECT_TRUE(resource.is_modified());
}
// Test resource manager
TEST_F(ResourceTest, ResourceManagerTest) {
// Get resource manager instance
mcp::resource_manager& manager = mcp::resource_manager::instance();
// Create resources
auto text_res = std::make_shared<mcp::text_resource>("test://text.txt", "text.txt", "text/plain", "Text resource");
auto binary_res = std::make_shared<mcp::binary_resource>("test://binary.bin", "binary.bin", "application/octet-stream", "Binary resource");
auto file_res = std::make_shared<mcp::file_resource>(test_file.string());
// Set content
text_res->set_text("Text resource content");
std::vector<uint8_t> binary_data = {0x42, 0x69, 0x6E, 0x61, 0x72, 0x79}; // "Binary" in ASCII
binary_res->set_data(binary_data.data(), binary_data.size());
// Register resources
manager.register_resource(text_res);
manager.register_resource(binary_res);
manager.register_resource(file_res);
// Test list resources
mcp::json resources_list = manager.list_resources();
EXPECT_EQ(resources_list["resources"].size(), 3);
// Test get resource
auto retrieved_text_res = manager.get_resource("test://text.txt");
ASSERT_NE(retrieved_text_res, nullptr);
EXPECT_EQ(retrieved_text_res->get_uri(), "test://text.txt");
auto retrieved_binary_res = manager.get_resource("test://binary.bin");
ASSERT_NE(retrieved_binary_res, nullptr);
EXPECT_EQ(retrieved_binary_res->get_uri(), "test://binary.bin");
auto retrieved_file_res = manager.get_resource("file://" + test_file.string());
ASSERT_NE(retrieved_file_res, nullptr);
EXPECT_EQ(retrieved_file_res->get_uri(), "file://" + test_file.string());
// Test unregister resource
EXPECT_TRUE(manager.unregister_resource("test://text.txt"));
EXPECT_EQ(manager.get_resource("test://text.txt"), nullptr);
// Test resources list after unregister
resources_list = manager.list_resources();
EXPECT_EQ(resources_list["resources"].size(), 2);
// Test subscription
bool notification_received = false;
std::string notification_uri;
int subscription_id = manager.subscribe("test://binary.bin", [&](const std::string& uri) {
notification_received = true;
notification_uri = uri;
});
// Modify resource and notify
binary_res->set_data(binary_data.data(), binary_data.size()); // This sets modified flag
manager.notify_resource_changed("test://binary.bin");
// Check notification
EXPECT_TRUE(notification_received);
EXPECT_EQ(notification_uri, "test://binary.bin");
// Test unsubscribe
EXPECT_TRUE(manager.unsubscribe(subscription_id));
// Reset notification flags
notification_received = false;
notification_uri = "";
// Modify and notify again
binary_res->set_data(binary_data.data(), binary_data.size());
manager.notify_resource_changed("test://binary.bin");
// Check no notification after unsubscribe
EXPECT_FALSE(notification_received);
EXPECT_EQ(notification_uri, "");
// Clean up
manager.unregister_resource("test://binary.bin");
manager.unregister_resource("file://" + test_file.string());
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,247 +0,0 @@
/**
* @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")) {
return {
{
{"type", "text"},
{"text", "Result: " + params["input"].get<std::string>()}
}
};
} else {
return {
{
{"type", "text"},
{"text", "Default result"}
}
};
}
}
// 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 = {
{"tools", {{"listChanged", true}}}
};
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 {
return {
{
{"type", "text"},
{"text", "Method call successful"}
},
{
{"type", "text"},
{"text", "params: " + params.dump()}
}
};
}
);
// 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"}});
EXPECT_EQ(result["content"][0]["text"], "Method call successful");
EXPECT_EQ(result["content"][1]["text"], "params: {\"param1\":\"value1\"}");
// 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"}});
EXPECT_EQ(tool_result["content"][0]["text"], "Result: Test input");
}
// 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));
// Verify tools were added
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
mcp::json result = client.call_tool("error_tool");
EXPECT_EQ(result.contains("isError"), true);
EXPECT_EQ(result["isError"], true);
}
// 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);
}

View File

@ -1,211 +0,0 @@
/**
* @file test_mcp_tool.cpp
* @brief MCP
*
* MCP2024-11-05
*/
#include "mcp_tool.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
// 测试工具结构体
TEST(McpToolTest, ToolStructTest) {
// 创建一个工具
mcp::tool tool;
tool.name = "test_tool";
tool.description = "测试工具";
tool.parameters_schema = {
{"type", "object"},
{"properties", {
{"param1", {
{"type", "string"},
{"description", "第一个参数"}
}},
{"param2", {
{"type", "number"},
{"description", "第二个参数"}
}}
}},
{"required", {"param1"}}
};
// 验证工具属性
EXPECT_EQ(tool.name, "test_tool");
EXPECT_EQ(tool.description, "测试工具");
// 验证转换为JSON
mcp::json json_tool = tool.to_json();
EXPECT_EQ(json_tool["name"], "test_tool");
EXPECT_EQ(json_tool["description"], "测试工具");
EXPECT_EQ(json_tool["inputSchema"]["type"], "object");
EXPECT_EQ(json_tool["inputSchema"]["properties"]["param1"]["type"], "string");
EXPECT_EQ(json_tool["inputSchema"]["properties"]["param1"]["description"], "第一个参数");
EXPECT_EQ(json_tool["inputSchema"]["properties"]["param2"]["type"], "number");
EXPECT_EQ(json_tool["inputSchema"]["required"][0], "param1");
}
// 测试工具构建器
TEST(McpToolTest, ToolBuilderTest) {
// 使用构建器创建工具
mcp::tool_builder builder("calculator");
mcp::tool tool = builder
.with_description("计算器工具")
.with_string_param("operation", "操作类型 (add, subtract, multiply, divide)")
.with_number_param("a", "第一个操作数")
.with_number_param("b", "第二个操作数")
.with_boolean_param("round_result", "是否四舍五入结果", false)
.build();
// 验证工具属性
EXPECT_EQ(tool.name, "calculator");
EXPECT_EQ(tool.description, "计算器工具");
// 验证参数架构
EXPECT_EQ(tool.parameters_schema["type"], "object");
// 验证参数属性
auto& properties = tool.parameters_schema["properties"];
EXPECT_EQ(properties["operation"]["type"], "string");
EXPECT_EQ(properties["operation"]["description"], "操作类型 (add, subtract, multiply, divide)");
EXPECT_EQ(properties["a"]["type"], "number");
EXPECT_EQ(properties["a"]["description"], "第一个操作数");
EXPECT_EQ(properties["b"]["type"], "number");
EXPECT_EQ(properties["b"]["description"], "第二个操作数");
EXPECT_EQ(properties["round_result"]["type"], "boolean");
EXPECT_EQ(properties["round_result"]["description"], "是否四舍五入结果");
// 验证必需参数
auto& required = tool.parameters_schema["required"];
EXPECT_THAT(required, ::testing::UnorderedElementsAre("operation", "a", "b"));
EXPECT_THAT(required, ::testing::Not(::testing::Contains("round_result")));
}
// 测试数组参数
TEST(McpToolTest, ArrayParamTest) {
// 使用构建器创建带有数组参数的工具
mcp::tool tool = mcp::tool_builder("list_processor")
.with_description("处理列表")
.with_array_param("items", "要处理的项目列表", "string")
.build();
// 验证数组参数
auto& properties = tool.parameters_schema["properties"];
EXPECT_EQ(properties["items"]["type"], "array");
EXPECT_EQ(properties["items"]["description"], "要处理的项目列表");
EXPECT_EQ(properties["items"]["items"]["type"], "string");
}
// 测试对象参数
TEST(McpToolTest, ObjectParamTest) {
// 创建对象属性
mcp::json user_properties = {
{"name", {
{"type", "string"},
{"description", "用户名"}
}},
{"age", {
{"type", "number"},
{"description", "年龄"}
}}
};
// 使用构建器创建带有对象参数的工具
mcp::tool tool = mcp::tool_builder("user_processor")
.with_description("处理用户信息")
.with_object_param("user", "用户对象", user_properties)
.build();
// 验证对象参数
auto& properties = tool.parameters_schema["properties"];
EXPECT_EQ(properties["user"]["type"], "object");
EXPECT_EQ(properties["user"]["description"], "用户对象");
EXPECT_EQ(properties["user"]["properties"]["name"]["type"], "string");
EXPECT_EQ(properties["user"]["properties"]["name"]["description"], "用户名");
EXPECT_EQ(properties["user"]["properties"]["age"]["type"], "number");
EXPECT_EQ(properties["user"]["properties"]["age"]["description"], "年龄");
}
// 测试工具注册表
TEST(McpToolTest, ToolRegistryTest) {
// 获取工具注册表实例
mcp::tool_registry& registry = mcp::tool_registry::instance();
// 创建一个测试工具
mcp::tool test_tool = mcp::tool_builder("test_registry_tool")
.with_description("测试注册表工具")
.with_string_param("input", "输入字符串")
.build();
// 创建一个处理函数
mcp::tool_handler handler = [](const mcp::json& params) -> mcp::json {
std::string input = params["input"];
return {{"output", "处理结果: " + input}};
};
// 注册工具
registry.register_tool(test_tool, handler);
// 获取所有工具
auto tools = registry.get_all_tools();
// 验证工具是否已注册
bool found = false;
for (const auto& tool : tools) {
if (tool.name == "test_registry_tool") {
found = true;
EXPECT_EQ(tool.description, "测试注册表工具");
break;
}
}
EXPECT_TRUE(found);
// 获取特定工具
auto tool_pair = registry.get_tool("test_registry_tool");
ASSERT_NE(tool_pair, nullptr);
EXPECT_EQ(tool_pair->first.name, "test_registry_tool");
// 调用工具
mcp::json call_params = {{"input", "测试输入"}};
mcp::json result = registry.call_tool("test_registry_tool", call_params);
EXPECT_EQ(result["output"], "处理结果: 测试输入");
// 注销工具
bool unregistered = registry.unregister_tool("test_registry_tool");
EXPECT_TRUE(unregistered);
// 验证工具已被注销
tool_pair = registry.get_tool("test_registry_tool");
EXPECT_EQ(tool_pair, nullptr);
}
// 测试create_tool辅助函数
TEST(McpToolTest, CreateToolHelperTest) {
// 使用辅助函数创建工具
std::vector<std::tuple<std::string, std::string, std::string, bool>> params = {
{"name", "姓名", "string", true},
{"age", "年龄", "number", true},
{"is_active", "是否活跃", "boolean", false}
};
mcp::tool tool = mcp::create_tool("user_tool", "用户工具", params);
// 验证工具属性
EXPECT_EQ(tool.name, "user_tool");
EXPECT_EQ(tool.description, "用户工具");
// 验证参数
auto& properties = tool.parameters_schema["properties"];
EXPECT_EQ(properties["name"]["type"], "string");
EXPECT_EQ(properties["name"]["description"], "姓名");
EXPECT_EQ(properties["age"]["type"], "number");
EXPECT_EQ(properties["age"]["description"], "年龄");
EXPECT_EQ(properties["is_active"]["type"], "boolean");
EXPECT_EQ(properties["is_active"]["description"], "是否活跃");
// 验证必需参数
auto& required = tool.parameters_schema["required"];
EXPECT_THAT(required, ::testing::UnorderedElementsAre("name", "age"));
EXPECT_THAT(required, ::testing::Not(::testing::Contains("is_active")));
}

View File

@ -1,357 +0,0 @@
/**
* @file test_mcp_tools_extended.cpp
* @brief MCP
*
* MCP2024-11-05
*/
#include "mcp_tool.h"
#include "mcp_server.h"
#include "mcp_client.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <future>
#include <chrono>
// 测试类,用于设置服务器和客户端
class McpToolsExtendedTest : public ::testing::Test {
protected:
void SetUp() override {
// 创建服务器
server = std::make_unique<mcp::server>("localhost", 8098);
server->set_server_info("TestServer", "2024-11-05");
// 设置服务器能力
mcp::json capabilities = {
{"tools", {{"listChanged", true}}}
};
server->set_capabilities(capabilities);
// 注册计算器工具
mcp::tool calculator = mcp::tool_builder("calculator")
.with_description("计算器工具")
.with_string_param("operation", "操作类型 (add, subtract, multiply, divide)")
.with_number_param("a", "第一个操作数")
.with_number_param("b", "第二个操作数")
.build();
server->register_tool(calculator, [](const mcp::json& params) -> mcp::json {
std::string operation = params["operation"];
double a = params["a"];
double b = params["b"];
double result = 0;
if (operation == "add") {
result = a + b;
} else if (operation == "subtract") {
result = a - b;
} else if (operation == "multiply") {
result = a * b;
} else if (operation == "divide") {
if (b == 0) {
return {{"error", "除数不能为零"}};
}
result = a / b;
} else {
return {{"error", "未知操作: " + operation}};
}
return {{"result", result}};
});
// 注册文本处理工具
mcp::tool text_processor = mcp::tool_builder("text_processor")
.with_description("文本处理工具")
.with_string_param("text", "要处理的文本")
.with_string_param("operation", "操作类型 (uppercase, lowercase, reverse)")
.build();
server->register_tool(text_processor, [](const mcp::json& params) -> mcp::json {
std::string text = params["text"];
std::string operation = params["operation"];
std::string result;
if (operation == "uppercase") {
result = text;
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
} else if (operation == "lowercase") {
result = text;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
} else if (operation == "reverse") {
result = text;
std::reverse(result.begin(), result.end());
} else {
return {{"error", "未知操作: " + operation}};
}
return {{"result", result}};
});
// 注册列表处理工具
mcp::tool list_processor = mcp::tool_builder("list_processor")
.with_description("列表处理工具")
.with_array_param("items", "要处理的项目列表", "string")
.with_string_param("operation", "操作类型 (sort, reverse, count)")
.build();
server->register_tool(list_processor, [](const mcp::json& params) -> mcp::json {
auto items = params["items"].get<std::vector<std::string>>();
std::string operation = params["operation"];
if (operation == "sort") {
std::sort(items.begin(), items.end());
return {{"result", items}};
} else if (operation == "reverse") {
std::reverse(items.begin(), items.end());
return {{"result", items}};
} else if (operation == "count") {
return {{"result", items.size()}};
} else {
return {{"error", "未知操作: " + operation}};
}
});
}
void TearDown() override {
// 停止服务器
if (server && server_thread.joinable()) {
server->stop();
server_thread.join();
}
}
// 启动服务器
void start_server() {
server_thread = std::thread([this]() {
server->start(false);
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::unique_ptr<mcp::server> server;
std::thread server_thread;
};
// 测试获取工具列表
TEST_F(McpToolsExtendedTest, GetToolsTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8098);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 获取工具列表
auto tools = client.get_tools();
// 验证工具列表
EXPECT_EQ(tools.size(), 3);
// 验证工具名称
std::vector<std::string> tool_names;
for (const auto& tool : tools) {
tool_names.push_back(tool.name);
}
EXPECT_THAT(tool_names, ::testing::UnorderedElementsAre("calculator", "text_processor", "list_processor"));
}
// 测试调用计算器工具
TEST_F(McpToolsExtendedTest, CalculatorToolTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8098);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 调用加法
mcp::json add_result = client.call_tool("calculator", {
{"operation", "add"},
{"a", 5},
{"b", 3}
});
EXPECT_EQ(add_result["result"], 8);
// 调用减法
mcp::json subtract_result = client.call_tool("calculator", {
{"operation", "subtract"},
{"a", 10},
{"b", 4}
});
EXPECT_EQ(subtract_result["result"], 6);
// 调用乘法
mcp::json multiply_result = client.call_tool("calculator", {
{"operation", "multiply"},
{"a", 6},
{"b", 7}
});
EXPECT_EQ(multiply_result["result"], 42);
// 调用除法
mcp::json divide_result = client.call_tool("calculator", {
{"operation", "divide"},
{"a", 20},
{"b", 5}
});
EXPECT_EQ(divide_result["result"], 4);
// 测试除以零
mcp::json divide_by_zero = client.call_tool("calculator", {
{"operation", "divide"},
{"a", 10},
{"b", 0}
});
EXPECT_TRUE(divide_by_zero.contains("error"));
}
// 测试调用文本处理工具
TEST_F(McpToolsExtendedTest, TextProcessorToolTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8098);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 测试转大写
mcp::json uppercase_result = client.call_tool("text_processor", {
{"text", "Hello World"},
{"operation", "uppercase"}
});
EXPECT_EQ(uppercase_result["result"], "HELLO WORLD");
// 测试转小写
mcp::json lowercase_result = client.call_tool("text_processor", {
{"text", "Hello World"},
{"operation", "lowercase"}
});
EXPECT_EQ(lowercase_result["result"], "hello world");
// 测试反转
mcp::json reverse_result = client.call_tool("text_processor", {
{"text", "Hello World"},
{"operation", "reverse"}
});
EXPECT_EQ(reverse_result["result"], "dlroW olleH");
}
// 测试调用列表处理工具
TEST_F(McpToolsExtendedTest, ListProcessorToolTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8098);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 准备测试数据
std::vector<std::string> items = {"banana", "apple", "orange", "grape"};
// 测试排序
mcp::json sort_result = client.call_tool("list_processor", {
{"items", items},
{"operation", "sort"}
});
std::vector<std::string> sorted_items = sort_result["result"];
EXPECT_THAT(sorted_items, ::testing::ElementsAre("apple", "banana", "grape", "orange"));
// 测试反转
mcp::json reverse_result = client.call_tool("list_processor", {
{"items", items},
{"operation", "reverse"}
});
std::vector<std::string> reversed_items = reverse_result["result"];
EXPECT_THAT(reversed_items, ::testing::ElementsAre("grape", "orange", "apple", "banana"));
// 测试计数
mcp::json count_result = client.call_tool("list_processor", {
{"items", items},
{"operation", "count"}
});
EXPECT_EQ(count_result["result"], 4);
}
// 测试工具参数验证
TEST_F(McpToolsExtendedTest, ToolParameterValidationTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8098);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 测试缺少必需参数
try {
client.call_tool("calculator", {
{"a", 5}
// 缺少 operation 和 b
});
FAIL() << "应该抛出异常,因为缺少必需参数";
} catch (const mcp::mcp_exception& e) {
EXPECT_EQ(e.code(), mcp::error_code::invalid_params);
}
// 测试参数类型错误
try {
client.call_tool("calculator", {
{"operation", "add"},
{"a", "not_a_number"}, // 应该是数字
{"b", 3}
});
FAIL() << "应该抛出异常,因为参数类型错误";
} catch (const mcp::mcp_exception& e) {
EXPECT_EQ(e.code(), mcp::error_code::invalid_params);
}
}
// 测试工具注册和注销
TEST_F(McpToolsExtendedTest, ToolRegistrationAndUnregistrationTest) {
// 创建工具注册表
mcp::tool_registry& registry = mcp::tool_registry::instance();
// 创建一个测试工具
mcp::tool test_tool = mcp::tool_builder("test_tool")
.with_description("测试工具")
.with_string_param("input", "输入参数")
.build();
// 注册工具
registry.register_tool(test_tool, [](const mcp::json& params) -> mcp::json {
return {{"output", "处理结果: " + params["input"].get<std::string>()}};
});
// 验证工具已注册
auto registered_tool = registry.get_tool("test_tool");
ASSERT_NE(registered_tool, nullptr);
EXPECT_EQ(registered_tool->first.name, "test_tool");
EXPECT_EQ(registered_tool->first.description, "测试工具");
// 调用工具
mcp::json result = registry.call_tool("test_tool", {{"input", "测试输入"}});
EXPECT_EQ(result["output"], "处理结果: 测试输入");
// 注销工具
bool unregistered = registry.unregister_tool("test_tool");
EXPECT_TRUE(unregistered);
// 验证工具已注销
EXPECT_EQ(registry.get_tool("test_tool"), nullptr);
// 尝试调用已注销的工具
try {
registry.call_tool("test_tool", {{"input", "测试输入"}});
FAIL() << "应该抛出异常,因为工具已注销";
} catch (const mcp::mcp_exception& e) {
EXPECT_EQ(e.code(), mcp::error_code::method_not_found);
}
}

View File

@ -1,140 +0,0 @@
/**
* @file test_mcp_versioning.cpp
* @brief MCP
*
* MCP2024-11-05
*/
#include "mcp_message.h"
#include "mcp_server.h"
#include "mcp_client.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <future>
#include <chrono>
// 测试类,用于设置服务器和客户端
class McpVersioningTest : public ::testing::Test {
protected:
void SetUp() override {
// 创建服务器
server = std::make_unique<mcp::server>("localhost", 8097);
server->set_server_info("TestServer", "2024-11-05");
// 设置服务器能力
mcp::json capabilities = {
{"tools", {{"listChanged", true}}},
{"transport", {{"sse", true}}}
};
server->set_capabilities(capabilities);
}
void TearDown() override {
// 停止服务器
if (server && server_thread.joinable()) {
server->stop();
server_thread.join();
}
}
// 启动服务器
void start_server() {
server_thread = std::thread([this]() {
server->start(false);
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::unique_ptr<mcp::server> server;
std::thread server_thread;
};
// 测试版本常量
TEST(McpVersioningTest, VersionConstantTest) {
// 验证MCP版本常量
EXPECT_EQ(std::string(mcp::MCP_VERSION), "2024-11-05");
}
// 测试版本匹配
TEST_F(McpVersioningTest, VersionMatchTest) {
// 启动服务器
start_server();
// 创建客户端,使用匹配的版本
mcp::client client("localhost", 8097);
client.set_timeout(5);
// 测试初始化,应该成功
bool init_result = client.initialize("TestClient", mcp::MCP_VERSION);
EXPECT_TRUE(init_result);
}
// 测试版本不匹配
TEST_F(McpVersioningTest, VersionMismatchTest) {
// 启动服务器
start_server();
// 创建客户端,使用不匹配的版本
mcp::client client("localhost", 8097);
client.set_timeout(5);
// 测试初始化,应该失败或返回警告
// 注意:根据实际实现,这可能会成功但有警告,或者完全失败
bool init_result = client.initialize("TestClient", "2023-01-01");
// 如果实现允许版本不匹配,这个测试可能需要调整
// 这里我们假设实现会拒绝不匹配的版本
EXPECT_FALSE(init_result);
}
// 测试服务器版本信息
TEST_F(McpVersioningTest, ServerVersionInfoTest) {
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8097);
client.set_timeout(5);
client.initialize("TestClient", mcp::MCP_VERSION);
// 获取服务器信息
mcp::response response = client.send_request("server/info");
// 验证服务器信息
EXPECT_FALSE(response.is_error());
EXPECT_EQ(response.result["name"], "TestServer");
EXPECT_EQ(response.result["version"], "2024-11-05");
EXPECT_TRUE(response.result.contains("capabilities"));
}
// 测试客户端版本信息
TEST_F(McpVersioningTest, ClientVersionInfoTest) {
// 创建一个处理器来捕获初始化请求
mcp::json captured_init_params;
server->register_method("initialize", [&captured_init_params](const mcp::json& params) -> mcp::json {
captured_init_params = params;
return {
{"name", "TestServer"},
{"version", "2024-11-05"},
{"capabilities", {
{"tools", {{"listChanged", true}}},
{"transport", {{"sse", true}}}
}}
};
});
// 启动服务器
start_server();
// 创建客户端
mcp::client client("localhost", 8097);
client.set_timeout(5);
client.initialize("TestClient", "1.0.0");
// 验证客户端版本信息
EXPECT_EQ(captured_init_params["name"], "TestClient");
EXPECT_EQ(captured_init_params["version"], "1.0.0");
}

455
test/testcase.md 100644
View File

@ -0,0 +1,455 @@
# Model Context Protocol 测试用例
本文档整理了从 Model Context Protocol 规范中收集的测试用例,主要来源于 https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/ 和 https://spec.modelcontextprotocol.io/specification/2024-11-05/server/ 。
## 基本协议测试用例
### 消息格式测试用例
#### 请求消息
```json
{
"jsonrpc": "2.0",
"id": "string | number",
"method": "string",
"params": {
"key": "value"
}
}
```
#### 响应消息
```json
{
"jsonrpc": "2.0",
"id": "string | number",
"result": {
"key": "value"
}
}
```
#### 通知消息
```json
{
"jsonrpc": "2.0",
"method": "string",
"params": {
"key": "value"
}
}
```
### 生命周期测试用例
#### 初始化请求
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
```
#### 初始化响应
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
}
}
}
```
#### 初始化完成通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
```
#### 初始化错误
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Unsupported protocol version",
"data": {
"supported": ["2024-11-05"],
"requested": "1.0.0"
}
}
}
```
### 工具功能测试用例
#### Ping 请求
```json
{
"jsonrpc": "2.0",
"id": "123",
"method": "ping"
}
```
#### Ping 响应
```json
{
"jsonrpc": "2.0",
"id": "123",
"result": {}
}
```
#### Cancellation 通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "123",
"reason": "User requested cancellation"
}
}
```
#### Progress 请求
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "some_method",
"params": {
"_meta": {
"progressToken": "abc123"
}
}
}
```
#### Progress 通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "abc123",
"progress": 50,
"total": 100
}
}
```
## 服务器功能测试用例
### 工具功能测试用例
#### 列出工具请求
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
#### 列出工具响应
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_weather",
"description": "Get current weather information for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
}
},
"required": ["location"]
}
}
],
"nextCursor": "next-page-cursor"
}
}
```
#### 调用工具请求
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}
```
#### 调用工具响应
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
```
#### 工具列表变更通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
```
### 资源功能测试用例
#### 列出资源请求
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "resources/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
#### 列出资源响应
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resources": [
{
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"description": "Primary application entry point",
"mimeType": "text/x-rust"
}
],
"nextCursor": "next-page-cursor"
}
}
```
#### 读取资源请求
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/src/main.rs"
}
}
```
#### 读取资源响应
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"contents": [
{
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}"
}
]
}
}
```
#### 资源模板列表请求
```json
{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/templates/list"
}
```
#### 资源模板列表响应
```json
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resourceTemplates": [
{
"uriTemplate": "file:///{path}",
"name": "Project Files",
"description": "Access files in the project directory",
"mimeType": "application/octet-stream"
}
]
}
}
```
#### 资源列表变更通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed"
}
```
#### 订阅资源请求
```json
{
"jsonrpc": "2.0",
"id": 4,
"method": "resources/subscribe",
"params": {
"uri": "file:///project/src/main.rs"
}
}
```
#### 资源更新通知
```json
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "file:///project/src/main.rs"
}
}
```
#### 资源错误响应
```json
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32002,
"message": "Resource not found",
"data": {
"uri": "file:///nonexistent.txt"
}
}
}
```
### 工具结果数据类型
#### 文本内容
```json
{
"type": "text",
"text": "Tool result text"
}
```
#### 图像内容
```json
{
"type": "image",
"data": "base64-encoded-data",
"mimeType": "image/png"
}
```
#### 嵌入资源
```json
{
"type": "resource",
"resource": {
"uri": "resource://example",
"mimeType": "text/plain",
"text": "Resource content"
}
}
```