test WIP
parent
1b54308d81
commit
8e11b5dc6d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,413 @@
|
|||
/**
|
||||
* @file mcp_test.cpp
|
||||
* @brief 测试MCP框架的基本功能
|
||||
*
|
||||
* 本文件包含对MCP框架的消息格式、生命周期、版本控制、ping和工具功能的测试。
|
||||
*/
|
||||
|
||||
#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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
/**
|
||||
* @file test_mcp_lifecycle_transport.cpp
|
||||
* @brief 测试MCP消息生命周期和传输相关功能
|
||||
*
|
||||
* 本文件包含对MCP消息生命周期和SSE传输的单元测试,基于规范2024-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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/**
|
||||
* @file test_mcp_tool.cpp
|
||||
* @brief 测试MCP工具相关功能
|
||||
*
|
||||
* 本文件包含对MCP工具模块的单元测试,基于规范2024-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")));
|
||||
}
|
|
@ -1,357 +0,0 @@
|
|||
/**
|
||||
* @file test_mcp_tools_extended.cpp
|
||||
* @brief 测试MCP工具相关功能的扩展测试
|
||||
*
|
||||
* 本文件包含对MCP工具模块的扩展单元测试,基于规范2024-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);
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* @file test_mcp_versioning.cpp
|
||||
* @brief 测试MCP版本控制相关功能
|
||||
*
|
||||
* 本文件包含对MCP版本控制的单元测试,基于规范2024-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");
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue