diff --git a/include/mcp_message.h b/include/mcp_message.h index 3ba4a59..d477f93 100644 --- a/include/mcp_message.h +++ b/include/mcp_message.h @@ -109,6 +109,15 @@ struct request { return j; } + + static request from_json(const json& j) { + request req; + req.jsonrpc = j["jsonrpc"].get(); + req.id = j["id"]; + req.method = j["method"].get(); + 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(); + res.id = j["id"]; + res.result = j["result"]; + res.error = j["error"]; + return res; + } }; } // namespace mcp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a43f7a4..e00b464 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 ) diff --git a/test/mcp_test.cpp b/test/mcp_test.cpp new file mode 100644 index 0000000..1248601 --- /dev/null +++ b/test/mcp_test.cpp @@ -0,0 +1,413 @@ +/** + * @file mcp_test.cpp + * @brief 测试MCP框架的基本功能 + * + * 本文件包含对MCP框架的消息格式、生命周期、版本控制、ping和工具功能的测试。 + */ + +#include +#include +#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(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("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("localhost", 8080); + client_->set_capabilities(client_capabilities); + } + + void TearDown() override { + // 清理测试环境 + server_->stop(); + server_.reset(); + client_.reset(); + } + + std::unique_ptr server_; + std::unique_ptr 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("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_; +}; + +// 测试支持的版本 +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("localhost", 8082); + + // 注册ping方法处理器 + server_->register_method("ping", [](const json& params) -> json { + return json::object(); // 返回空对象 + }); + + // 启动服务器(非阻塞模式) + server_->start(false); + + // 创建客户端 + client_ = std::make_unique("localhost", 8082); + } + + void TearDown() override { + // 清理测试环境 + server_->stop(); + server_.reset(); + client_.reset(); + } + + std::unique_ptr server_; + std::unique_ptr 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("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("localhost", 8083); + client_->initialize("TestClient", "1.0.0"); + } + + void TearDown() override { + // 清理测试环境 + server_->stop(); + server_.reset(); + client_.reset(); + } + + std::unique_ptr server_; + std::unique_ptr 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(); +} \ No newline at end of file diff --git a/test/test_client.cpp b/test/test_client.cpp deleted file mode 100644 index 70f173d..0000000 --- a/test/test_client.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file test_client.cpp - * @brief 测试MCP客户端 - */ - -#include "mcp_client.h" -#include -#include -#include - -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; -} \ No newline at end of file diff --git a/test/test_mcp_client.cpp b/test/test_mcp_client.cpp deleted file mode 100644 index d59484a..0000000 --- a/test/test_mcp_client.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include - -// 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("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("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 server; - std::unique_ptr 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()); -} \ No newline at end of file diff --git a/test/test_mcp_direct_requests.cpp b/test/test_mcp_direct_requests.cpp deleted file mode 100644 index 7d262bb..0000000 --- a/test/test_mcp_direct_requests.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include - -// 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()} - } - }; - } 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("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("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 server; - std::unique_ptr 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(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().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(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().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(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(mcp::error_code::method_not_found) || - error_code == static_cast(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(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(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(mcp::error_code::invalid_request)); - EXPECT_TRUE(response["error"]["message"].get().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(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(mcp::error_code::invalid_request)); - } -} \ No newline at end of file diff --git a/test/test_mcp_lifecycle_transport.cpp b/test/test_mcp_lifecycle_transport.cpp deleted file mode 100644 index e7d812e..0000000 --- a/test/test_mcp_lifecycle_transport.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 测试类,用于设置服务器和客户端 -class McpLifecycleTransportTest : public ::testing::Test { -protected: - void SetUp() override { - // 创建服务器 - server = std::make_unique("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 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()); -} - -// 测试消息生命周期 - 请求和响应 -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()); -} - -// 测试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); -} \ No newline at end of file diff --git a/test/test_mcp_message.cpp b/test/test_mcp_message.cpp deleted file mode 100644 index b718c28..0000000 --- a/test/test_mcp_message.cpp +++ /dev/null @@ -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 -#include - -// 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(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(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(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(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); -} \ No newline at end of file diff --git a/test/test_mcp_resource.cpp b/test/test_mcp_resource.cpp deleted file mode 100644 index b5b0c29..0000000 --- a/test/test_mcp_resource.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include - -// 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 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(test_data.data()), test_data.size()); - EXPECT_EQ(content["blob"], base64_data); - - // Test modification - EXPECT_FALSE(resource.is_modified()); - std::vector 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(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("test://text.txt", "text.txt", "text/plain", "Text resource"); - auto binary_res = std::make_shared("test://binary.bin", "binary.bin", "application/octet-stream", "Binary resource"); - auto file_res = std::make_shared(test_file.string()); - - // Set content - text_res->set_text("Text resource content"); - std::vector 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(); -} diff --git a/test/test_mcp_server.cpp b/test/test_mcp_server.cpp deleted file mode 100644 index 2d14740..0000000 --- a/test/test_mcp_server.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 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()} - } - }; - } 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("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 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); -} \ No newline at end of file diff --git a/test/test_mcp_tool.cpp b/test/test_mcp_tool.cpp deleted file mode 100644 index 9a89d0f..0000000 --- a/test/test_mcp_tool.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/** - * @file test_mcp_tool.cpp - * @brief 测试MCP工具相关功能 - * - * 本文件包含对MCP工具模块的单元测试,基于规范2024-11-05。 - */ - -#include "mcp_tool.h" -#include -#include -#include - -// 测试工具结构体 -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> 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"))); -} \ No newline at end of file diff --git a/test/test_mcp_tools_extended.cpp b/test/test_mcp_tools_extended.cpp deleted file mode 100644 index f4eeff5..0000000 --- a/test/test_mcp_tools_extended.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 测试类,用于设置服务器和客户端 -class McpToolsExtendedTest : public ::testing::Test { -protected: - void SetUp() override { - // 创建服务器 - server = std::make_unique("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::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 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 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 items = {"banana", "apple", "orange", "grape"}; - - // 测试排序 - mcp::json sort_result = client.call_tool("list_processor", { - {"items", items}, - {"operation", "sort"} - }); - std::vector 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 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()}}; - }); - - // 验证工具已注册 - 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); - } -} \ No newline at end of file diff --git a/test/test_mcp_versioning.cpp b/test/test_mcp_versioning.cpp deleted file mode 100644 index 6d18fbd..0000000 --- a/test/test_mcp_versioning.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 测试类,用于设置服务器和客户端 -class McpVersioningTest : public ::testing::Test { -protected: - void SetUp() override { - // 创建服务器 - server = std::make_unique("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 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"); -} \ No newline at end of file diff --git a/test/testcase.md b/test/testcase.md new file mode 100644 index 0000000..8f6ab65 --- /dev/null +++ b/test/testcase.md @@ -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" + } +} +``` \ No newline at end of file