From 175d66864253ab305d57524ca58edc4ecffb2ec9 Mon Sep 17 00:00:00 2001 From: hkr04 Date: Sun, 9 Mar 2025 23:17:36 +0800 Subject: [PATCH] add initialization verification --- examples/client_example.cpp | 30 +- examples/server_example.cpp | 49 ++-- include/mcp_message.h | 2 +- include/mcp_server.h | 24 +- src/CMakeLists.txt | 1 - src/mcp_server.cpp | 75 +++-- test/test_mcp_client.cpp | 71 +---- test/test_mcp_direct_requests.cpp | 441 +++++++++++------------------- test/test_mcp_message.cpp | 4 +- test/test_mcp_server.cpp | 89 +----- 10 files changed, 306 insertions(+), 480 deletions(-) diff --git a/examples/client_example.cpp b/examples/client_example.cpp index a2686e3..ed05462 100644 --- a/examples/client_example.cpp +++ b/examples/client_example.cpp @@ -32,6 +32,13 @@ int main() { std::cerr << "Failed to initialize connection to server" << std::endl; return 1; } + + // Ping the server + std::cout << "Pinging server..." << std::endl; + if (!client.ping()) { + std::cerr << "Failed to ping server" << std::endl; + return 1; + } // Get server capabilities std::cout << "Getting server capabilities..." << std::endl; @@ -49,7 +56,7 @@ int main() { // Call the get_time tool std::cout << "\nCalling get_time tool..." << std::endl; mcp::json time_result = client.call_tool("get_time"); - std::cout << "Current time: " << time_result["current_time"].get() << std::endl; + std::cout << "Current time: " << time_result["content"][0]["text"].get() << std::endl; // Call the echo tool std::cout << "\nCalling echo tool..." << std::endl; @@ -58,7 +65,7 @@ int main() { {"uppercase", true} }; mcp::json echo_result = client.call_tool("echo", echo_params); - std::cout << "Echo result: " << echo_result["text"].get() << std::endl; + std::cout << "Echo result: " << echo_result["content"][0]["text"].get() << std::endl; // Call the calculator tool std::cout << "\nCalling calculator tool..." << std::endl; @@ -68,16 +75,17 @@ int main() { {"b", 5} }; mcp::json calc_result = client.call_tool("calculator", calc_params); - std::cout << "10 + 5 = " << calc_result["result"].get() << std::endl; + std::cout << "10 + 5 = " << calc_result["content"][0]["text"].get() << std::endl; - // Access a resource - std::cout << "\nAccessing API resource..." << std::endl; - mcp::json api_params = { - {"endpoint", "hello"}, - {"name", "MCP Client"} - }; - mcp::json api_result = client.access_resource("/api", api_params); - std::cout << "API response: " << api_result["message"].get() << std::endl; + // Not implemented yet + // // Access a resource + // std::cout << "\nAccessing API resource..." << std::endl; + // mcp::json api_params = { + // {"endpoint", "hello"}, + // {"name", "MCP Client"} + // }; + // mcp::json api_result = client.access_resource("/api", api_params); + // std::cout << "API response: " << api_result["contents"][0]["text"].get() << std::endl; } catch (const mcp::mcp_exception& e) { std::cerr << "MCP error: " << e.what() << " (code: " << static_cast(e.code()) << ")" << std::endl; diff --git a/examples/server_example.cpp b/examples/server_example.cpp index 6ad32ab..a4292ad 100644 --- a/examples/server_example.cpp +++ b/examples/server_example.cpp @@ -29,9 +29,10 @@ mcp::json get_time_handler(const mcp::json& params) { } return { - {"current_time", time_str}, - {"timestamp", static_cast(std::chrono::duration_cast( - now.time_since_epoch()).count())} + { + {"type", "text"}, + {"text", time_str} + } }; } @@ -53,7 +54,12 @@ mcp::json echo_handler(const mcp::json& params) { } } - return result; + return { + { + {"type", "text"}, + {"text", result["text"].get()} + } + }; } // Calculator tool handler @@ -92,13 +98,23 @@ mcp::json calculator_handler(const mcp::json& params) { throw mcp::mcp_exception(mcp::error_code::invalid_params, "Unknown operation: " + operation); } - return {{"result", result}}; + return { + { + {"type", "text"}, + {"text", std::to_string(result)} + } + }; } // Custom API endpoint handler mcp::json hello_handler(const mcp::json& params) { std::string name = params.contains("name") ? params["name"].get() : "World"; - return {{"message", "Hello, " + name + "!"}}; + return { + { + {"type", "text"}, + {"text", "Hello, " + name + "!"} + } + }; } int main() { @@ -111,16 +127,10 @@ int main() { // Set server capabilities mcp::json capabilities = { - {"tools", {{"listChanged", true}}}, - {"resources", {{"listChanged", true}}} + {"tools", {{"listChanged", true}}} }; server.set_capabilities(capabilities); - // Register method handlers - server.register_method("ping", [](const mcp::json& params) { - return mcp::json{{"pong", true}}; - }); - // Register tools mcp::tool time_tool = mcp::tool_builder("get_time") .with_description("Get current time") @@ -144,14 +154,15 @@ int main() { server.register_tool(echo_tool, echo_handler); server.register_tool(calc_tool, calculator_handler); - // Register resources - auto file_resource = std::make_shared("./files"); - server.register_resource("/files", file_resource); + // Not implemented yet + // // Register resources + // auto file_resource = std::make_shared("./files"); + // server.register_resource("/files", file_resource); - auto api_resource = std::make_shared("API", "Custom API endpoints"); - api_resource->register_handler("hello", hello_handler, "Say hello"); + // auto api_resource = std::make_shared("API", "Custom API endpoints"); + // api_resource->register_handler("hello", hello_handler, "Say hello"); - server.register_resource("/api", api_resource); + // server.register_resource("/api", api_resource); // Start server std::cout << "Starting MCP server at localhost:8080..." << std::endl; diff --git a/include/mcp_message.h b/include/mcp_message.h index 9d8912f..3ba4a59 100644 --- a/include/mcp_message.h +++ b/include/mcp_message.h @@ -82,7 +82,7 @@ struct request { request req; req.jsonrpc = "2.0"; req.id = nullptr; - req.method = method; + req.method = "notifications/" + method; req.params = params; return req; } diff --git a/include/mcp_server.h b/include/mcp_server.h index 265d1ef..d9a530e 100644 --- a/include/mcp_server.h +++ b/include/mcp_server.h @@ -118,6 +118,17 @@ public: */ void set_auth_handler(std::function handler); + /** + * @brief Send a request to a client + * @param client_address The address of the client + * @param method The method to call + * @param params The parameters to pass + * + * This method will only send requests other than ping and logging + * after the client has sent the initialized notification. + */ + void send_request(const std::string& client_address, const std::string& method, const json& params = json::object()); + private: std::string host_; int port_; @@ -152,14 +163,23 @@ private: // Running flag bool running_ = false; + // Map to track client initialization status (client_address -> initialized) + std::map client_initialized_; + // Handle incoming JSON-RPC requests void handle_jsonrpc(const httplib::Request& req, httplib::Response& res); // Process a JSON-RPC request - json process_request(const request& req); + json process_request(const request& req, const std::string& client_address); // Handle initialization request - json handle_initialize(const request& req); + json handle_initialize(const request& req, const std::string& client_address); + + // Check if a client is initialized + bool is_client_initialized(const std::string& client_address) const; + + // Set client initialization status + void set_client_initialized(const std::string& client_address, bool initialized); }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b464f04..b860160 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,5 @@ set(TARGET mcp) - add_library(${TARGET} STATIC mcp_client.cpp ../include/mcp_client.h diff --git a/src/mcp_server.cpp b/src/mcp_server.cpp index 046cc70..224a5b0 100644 --- a/src/mcp_server.cpp +++ b/src/mcp_server.cpp @@ -196,6 +196,9 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) // Set response headers res.set_header("Content-Type", "application/json"); + // Get client address + std::string client_address = req.remote_addr; + // Parse the request json req_json; try { @@ -257,27 +260,16 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) } // Process the request - json result = process_request(mcp_req); + json result = process_request(mcp_req, client_address); res.set_content(result.dump(), "application/json"); } -json server::process_request(const request& req) { +json server::process_request(const request& req, const std::string& client_address) { // Check if it's a notification - if (req.is_notification()) { - // Process notification asynchronously - std::thread([this, req]() { - try { - std::lock_guard lock(mutex_); - auto it = notification_handlers_.find(req.method); - if (it != notification_handlers_.end()) { - it->second(req.params); - } - } catch (const std::exception& e) { - // Log error but don't send response - std::cerr << "Error processing notification: " << e.what() << std::endl; - } - }).detach(); - + if (req.is_notification()) { + if (req.method == "notifications/initialized") { + set_client_initialized(client_address, true); + } // No response for notifications return json::object(); } @@ -286,11 +278,21 @@ json server::process_request(const request& req) { try { // Special case for initialize if (req.method == "initialize") { - return handle_initialize(req); + return handle_initialize(req, client_address); } else if (req.method == "ping") { // The receiver MUST respond promptly with an empty response return response::create_success(req.id, {}).to_json(); } + + // Check if client is initialized + if (!is_client_initialized(client_address)) { + // Client not initialized + return response::create_error( + req.id, + error_code::invalid_request, + "Client not initialized" + ).to_json(); + } // Look for registered method handler std::lock_guard lock(mutex_); @@ -326,7 +328,7 @@ json server::process_request(const request& req) { } } -json server::handle_initialize(const request& req) { +json server::handle_initialize(const request& req, const std::string& client_address) { const json& params = req.params; // Version negotiation @@ -365,6 +367,9 @@ json server::handle_initialize(const request& req) { } } + // Mark client as not initialized yet + set_client_initialized(client_address, false); + // Log connection // std::cout << "Client connected: " << client_name << " " << client_version << std::endl; @@ -383,4 +388,36 @@ json server::handle_initialize(const request& req) { return response::create_success(req.id, result).to_json(); } +void server::send_request(const std::string& client_address, const std::string& method, const json& params) { + // Check if the method is ping or logging + bool is_allowed_before_init = (method == "ping" || method == "logging"); + + // Check if client is initialized or if this is an allowed method + if (!is_allowed_before_init && !is_client_initialized(client_address)) { + // Client not initialized and method is not allowed before initialization + std::cerr << "Cannot send " << method << " request to client " << client_address + << " before it is initialized" << std::endl; + return; + } + + // Create request + request req = request::create(method, params); + + // TODO: Implement actual request sending logic + // This would typically involve sending an HTTP request to the client +} + +// Check if a client is initialized +bool server::is_client_initialized(const std::string& client_address) const { + std::lock_guard lock(mutex_); + auto it = client_initialized_.find(client_address); + return (it != client_initialized_.end() && it->second); +} + +// Set client initialization status +void server::set_client_initialized(const std::string& client_address, bool initialized) { + std::lock_guard lock(mutex_); + client_initialized_[client_address] = initialized; +} + } // namespace mcp \ No newline at end of file diff --git a/test/test_mcp_client.cpp b/test/test_mcp_client.cpp index 612b277..d59484a 100644 --- a/test/test_mcp_client.cpp +++ b/test/test_mcp_client.cpp @@ -15,31 +15,14 @@ // Mock tool handler function mcp::json echo_tool_handler(const mcp::json& args) { - return {{ - {"type", "text"}, - {"text", args["message"]} - }}; + return { + { + {"type", "text"}, + {"text", args["message"]} + } + }; } -// Mock resource handler -class test_resource : public mcp::resource { -public: - mcp::json get_metadata() const override { - return { - {"type", "test"}, - {"name", "TestResource"}, - {"description", "Test resource"} - }; - } - - mcp::json access(const mcp::json& params) const override { - return { - {"resource_data", "Test resource data"}, - {"params", params} - }; - } -}; - // Client test class class ClientTest : public ::testing::Test { protected: @@ -50,8 +33,7 @@ protected: // Set server capabilities mcp::json capabilities = { - {"tools", {{"listChanged", true}}}, - {"resources", {{"listChanged", true}}} + {"tools", {{"listChanged", true}}} }; server->set_capabilities(capabilities); @@ -62,10 +44,6 @@ protected: .build(); server->register_tool(echo_tool, echo_tool_handler); - // Register resource - auto test_res = std::make_shared(); - server->register_resource("/test", test_res); - // Start server (non-blocking mode) server_thread = std::thread([this]() { server->start(false); @@ -116,7 +94,7 @@ TEST_F(ClientTest, GetCapabilitiesTest) { // If it's an object, verify its contents if (capabilities.is_object()) { - EXPECT_TRUE(capabilities.contains("tools") || capabilities.contains("resources")); + EXPECT_TRUE(capabilities.contains("tools")); } } @@ -149,36 +127,6 @@ TEST_F(ClientTest, CallToolTest) { EXPECT_EQ(result["content"][0]["text"], "Test message"); } -// Test getting resource list -TEST_F(ClientTest, GetResourcesTest) { - // Initialize client - client->initialize("TestClient", mcp::MCP_VERSION); - - // Get resource list - Note: this method may not exist, modify according to actual implementation - // auto resources = client->get_resources(); - // EXPECT_EQ(resources.size(), 1); - // EXPECT_EQ(resources[0].path, "/test"); - // EXPECT_EQ(resources[0].metadata["name"], "TestResource"); - // EXPECT_EQ(resources[0].metadata["type"], "test"); - - // Alternative test: directly access resource - mcp::json result = client->access_resource("/test"); - EXPECT_TRUE(result.contains("resource_data")); -} - -// Test accessing resource -TEST_F(ClientTest, AccessResourceTest) { - // Initialize client - client->initialize("TestClient", mcp::MCP_VERSION); - - // Access resource - mcp::json params = {{"query", "Test query"}}; - mcp::json result = client->access_resource("/test", params); - - EXPECT_EQ(result["resource_data"], "Test resource data"); - EXPECT_EQ(result["params"]["query"], "Test query"); -} - // Test error handling TEST_F(ClientTest, ErrorHandlingTest) { // Initialize client @@ -186,9 +134,6 @@ TEST_F(ClientTest, ErrorHandlingTest) { // Call non-existent tool EXPECT_THROW(client->call_tool("non_existent_tool"), mcp::mcp_exception); - - // Access non-existent resource - EXPECT_THROW(client->access_resource("/non_existent_resource"), mcp::mcp_exception); } // Test setting client capabilities diff --git a/test/test_mcp_direct_requests.cpp b/test/test_mcp_direct_requests.cpp index 8e3a3df..72e4e9b 100644 --- a/test/test_mcp_direct_requests.cpp +++ b/test/test_mcp_direct_requests.cpp @@ -10,13 +10,31 @@ #include "mcp_server.h" #include "mcp_client.h" #include "mcp_tool.h" -#include "mcp_resource.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: @@ -27,40 +45,18 @@ protected: // Set server capabilities mcp::json capabilities = { - {"tools", {{"listChanged", true}}}, - {"resources", {{"listChanged", true}}} + {"tools", {{"listChanged", true}}} }; server->set_capabilities(capabilities); - // Register method handlers - server->register_method("ping", [](const mcp::json& params) { - return mcp::json{{"pong", true}}; - }); - + // 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, [](const mcp::json& params) -> mcp::json { - std::string input = params.contains("input") ? params["input"].get() : "Default input"; - return { - { - {"type", "text"}, - {"text", "Result: " + input} - } - }; - }); - - // Register resources - auto test_resource = std::make_shared("Test Resource", "API resource for testing"); - test_resource->register_handler("hello", [](const mcp::json& params) { - std::string name = params.contains("name") ? params["name"].get() : "World"; - return mcp::json{{"message", "Hello, " + name + "!"}}; - }, "Greeting API"); - - server->register_resource("/test", test_resource); + server->register_tool(test_tool, test_tool_handler); // Start server server_thread = std::thread([this]() { @@ -109,12 +105,141 @@ protected: 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 @@ -188,21 +313,7 @@ TEST_F(DirectRequestTest, InitializeWithStringId) { // Test getting tools list TEST_F(DirectRequestTest, GetTools) { // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", 1}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); + mock_initialize(); // Get tools list mcp::json request = { @@ -246,30 +357,16 @@ TEST_F(DirectRequestTest, GetTools) { // Test calling a tool TEST_F(DirectRequestTest, CallTool) { // Initialize first - 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); + mock_initialize(); // Call tool mcp::json request = { {"jsonrpc", "2.0"}, {"id", "call-1"}, - {"method", "callTool"}, + {"method", "tools/call"}, {"params", { {"name", "test_tool"}, - {"parameters", { + {"arguments", { {"input", "Test input"} }} }} @@ -280,126 +377,12 @@ TEST_F(DirectRequestTest, CallTool) { // Verify response EXPECT_EQ(response["jsonrpc"], "2.0"); EXPECT_EQ(response["id"], "call-1"); + EXPECT_TRUE(response.contains("result")); + EXPECT_FALSE(response.contains("error")); - // Server may not implement callTool 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 tool call result - EXPECT_EQ(response["result"]["output"], "Result: Test input"); - } -} - -// Test getting resources list -TEST_F(DirectRequestTest, GetResources) { - // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", 100}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); - - // Get resources list - mcp::json request = { - {"jsonrpc", "2.0"}, - {"id", 101}, - {"method", "getResources"}, - {"params", {}} - }; - - mcp::json response = send_jsonrpc_request(request); - - // Verify response - EXPECT_EQ(response["jsonrpc"], "2.0"); - EXPECT_EQ(response["id"], 101); - - // Server may not implement getResources 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 resources list - EXPECT_TRUE(response["result"].is_array()); - EXPECT_GE(response["result"].size(), 1); - - // Verify test resource - bool found_test_resource = false; - for (const auto& resource : response["result"]) { - if (resource["path"] == "/test") { - found_test_resource = true; - EXPECT_EQ(resource["name"], "Test Resource"); - EXPECT_EQ(resource["description"], "API resource for testing"); - break; - } - } - EXPECT_TRUE(found_test_resource); - } -} - -// Test accessing a resource -TEST_F(DirectRequestTest, AccessResource) { - // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", "init-res"}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); - - // Access resource - mcp::json request = { - {"jsonrpc", "2.0"}, - {"id", "access-res"}, - {"method", "accessResource"}, - {"params", { - {"path", "/test"}, - {"operation", "hello"}, - {"parameters", { - {"name", "Test User"} - }} - }} - }; - - mcp::json response = send_jsonrpc_request(request); - - // Verify response - EXPECT_EQ(response["jsonrpc"], "2.0"); - EXPECT_EQ(response["id"], "access-res"); - - // Server may not implement accessResource 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 resource access result - EXPECT_EQ(response["result"]["message"], "Hello, Test User!"); - } + // 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) @@ -442,6 +425,8 @@ TEST_F(DirectRequestTest, SendNotification) { // Test error handling - method not found TEST_F(DirectRequestTest, MethodNotFound) { + mock_initialize(); + mcp::json request = { {"jsonrpc", "2.0"}, {"id", 999}, @@ -462,21 +447,7 @@ TEST_F(DirectRequestTest, MethodNotFound) { // Test error handling - invalid parameters TEST_F(DirectRequestTest, InvalidParams) { // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", "init-err"}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); + mock_initialize(); // Call tool but missing required parameters mcp::json request = { @@ -505,62 +476,10 @@ TEST_F(DirectRequestTest, InvalidParams) { error_code == static_cast(mcp::error_code::invalid_params)); } -// Test using client API and direct requests combination -TEST_F(DirectRequestTest, ClientAndDirectRequests) { - // Use client API to initialize - mcp::client client("localhost", 8096); - client.set_timeout(5); - - bool initialized = client.initialize("TestClient", mcp::MCP_VERSION); - EXPECT_TRUE(initialized); - - // Directly send get tools list request - mcp::json request = { - {"jsonrpc", "2.0"}, - {"id", "mixed-1"}, - {"method", "getTools"}, - {"params", {}} - }; - - mcp::json response = send_jsonrpc_request(request); - - // Verify response - EXPECT_EQ(response["jsonrpc"], "2.0"); - EXPECT_EQ(response["id"], "mixed-1"); - - // Server may not implement getTools method - if (!response.contains("error")) { - EXPECT_TRUE(response.contains("result")); - } - - // Use client API to call tool - try { - mcp::json tool_result = client.call_tool("test_tool", {{"input", "Client API call"}}); - EXPECT_EQ(tool_result["content"][0]["text"], "Result: Client API call"); - } catch (const std::exception& e) { - // Client API may throw exception if server doesn't implement callTool method - std::cout << "Client API tool call failed: " << e.what() << std::endl; - } -} - // Test logging functionality TEST_F(DirectRequestTest, LoggingTest) { // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", "log-init"}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); + mock_initialize(); // Send log request mcp::json request = { @@ -595,21 +514,7 @@ TEST_F(DirectRequestTest, LoggingTest) { // Test sampling functionality TEST_F(DirectRequestTest, SamplingTest) { // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", 300}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); + mock_initialize(); // Send sampling request mcp::json request = { @@ -715,21 +620,7 @@ TEST_F(DirectRequestTest, BatchRequestTest) { // Test request cancellation TEST_F(DirectRequestTest, CancelRequestTest) { // Initialize first - mcp::json init_request = { - {"jsonrpc", "2.0"}, - {"id", "cancel-init"}, - {"method", "initialize"}, - {"params", { - {"protocolVersion", mcp::MCP_VERSION}, - {"capabilities", {}}, - {"clientInfo", { - {"name", "TestClient"}, - {"version", "1.0.0"} - }} - }} - }; - - send_jsonrpc_request(init_request); + mock_initialize(); // Send cancel request mcp::json request = { diff --git a/test/test_mcp_message.cpp b/test/test_mcp_message.cpp index 6eb50d6..b718c28 100644 --- a/test/test_mcp_message.cpp +++ b/test/test_mcp_message.cpp @@ -46,7 +46,7 @@ TEST(McpMessageTest, NotificationCreationTest) { // Verify notification properties EXPECT_EQ(notification.jsonrpc, "2.0"); EXPECT_TRUE(notification.id.is_null()); - EXPECT_EQ(notification.method, "event_notification"); + EXPECT_EQ(notification.method, "notifications/event_notification"); EXPECT_EQ(notification.params["event"], "update"); EXPECT_EQ(notification.params["status"], "completed"); @@ -80,7 +80,7 @@ TEST(McpMessageTest, NotificationToJsonTest) { // Verify JSON structure EXPECT_EQ(json_notification["jsonrpc"], "2.0"); - EXPECT_EQ(json_notification["method"], "test_notification"); + EXPECT_EQ(json_notification["method"], "notifications/test_notification"); EXPECT_FALSE(json_notification.contains("id")); } diff --git a/test/test_mcp_server.cpp b/test/test_mcp_server.cpp index 63888f8..2d14740 100644 --- a/test/test_mcp_server.cpp +++ b/test/test_mcp_server.cpp @@ -33,30 +33,6 @@ mcp::json test_tool_handler(const mcp::json& params) { } } -// Mock resource -class mock_resource : public mcp::resource { -public: - mock_resource(const std::string& name) : name_(name) {} - - mcp::json get_metadata() const override { - return { - {"type", "mock"}, - {"name", name_}, - {"description", "Mock resource"} - }; - } - - mcp::json access(const mcp::json& params) const override { - return { - {"resource", name_}, - {"params", params} - }; - } - -private: - std::string name_; -}; - // Server test class class ServerTest : public ::testing::Test { protected: @@ -67,8 +43,7 @@ protected: // Set server capabilities mcp::json capabilities = { - {"tools", {{"listChanged", true}}}, - {"resources", {{"listChanged", true}}} + {"tools", {{"listChanged", true}}} }; server->set_capabilities(capabilities); } @@ -94,21 +69,6 @@ protected: std::thread server_thread; }; -// Test server basic configuration -TEST_F(ServerTest, BasicConfigurationTest) { - // Verify server information - these methods may not exist, modify according to actual implementation - // EXPECT_EQ(server->get_name(), "TestServer"); - // EXPECT_EQ(server->get_version(), "2024-11-05"); - - // Verify server capabilities - this method may not exist, modify according to actual implementation - // mcp::json capabilities = server->get_capabilities(); - // EXPECT_TRUE(capabilities["tools"]["listChanged"].get()); - // EXPECT_TRUE(capabilities["resources"]["listChanged"].get()); - - // Alternative test: ensure server was created correctly - EXPECT_TRUE(server != nullptr); -} - // Test tool registration and retrieval TEST_F(ServerTest, ToolRegistrationTest) { // Create tool @@ -135,35 +95,6 @@ TEST_F(ServerTest, ToolRegistrationTest) { EXPECT_EQ(tools[0].description, "Test tool"); } -// Test resource registration and retrieval -TEST_F(ServerTest, ResourceRegistrationTest) { - // Create resources - auto mock_res1 = std::make_shared("MockResource1"); - auto mock_res2 = std::make_shared("MockResource2"); - - // Register resources - server->register_resource("/mock1", mock_res1); - server->register_resource("/mock2", mock_res2); - - // Start server - start_server(); - - // Create client to verify resource registration - mcp::client client("localhost", 8095); - client.set_timeout(5); - client.initialize("TestClient", mcp::MCP_VERSION); - - // Access resources - mcp::json result1 = client.access_resource("/mock1"); - EXPECT_EQ(result1["resource"], "MockResource1"); - - mcp::json result2 = client.access_resource("/mock2"); - EXPECT_EQ(result2["resource"], "MockResource2"); - - // Access non-existent resource - EXPECT_THROW(client.access_resource("/non_existent"), mcp::mcp_exception); -} - // Test method registration and handling TEST_F(ServerTest, MethodRegistrationTest) { // Register method - register as tool since client doesn't have call_method @@ -212,10 +143,6 @@ TEST_F(ServerTest, ServerClientInteractionTest) { .build(); server->register_tool(test_tool, test_tool_handler); - // Register resource - auto mock_res = std::make_shared("MockResource"); - server->register_resource("/mock", mock_res); - // Start server start_server(); @@ -235,11 +162,6 @@ TEST_F(ServerTest, ServerClientInteractionTest) { // Call tool mcp::json tool_result = client.call_tool("test_tool", {{"input", "Test input"}}); EXPECT_EQ(tool_result["content"][0]["text"], "Result: Test input"); - - // Access resource - mcp::json resource_result = client.access_resource("/mock", {{"query", "Test query"}}); - EXPECT_EQ(resource_result["resource"], "MockResource"); - EXPECT_EQ(resource_result["params"]["query"], "Test query"); } // Test notification functionality @@ -257,20 +179,13 @@ TEST_F(ServerTest, NotificationTest) { .build(); server->register_tool(new_tool, [](const mcp::json& params) { return mcp::json::object(); }); - // Add resource on server side, triggering notification - auto new_resource = std::make_shared("NewResource"); - server->register_resource("/new", new_resource); - // Wait for notification processing std::this_thread::sleep_for(std::chrono::milliseconds(200)); - // Verify tools and resources were added + // Verify tools were added auto tools = client.get_tools(); EXPECT_EQ(tools.size(), 1); EXPECT_EQ(tools[0].name, "new_tool"); - - mcp::json resource_result = client.access_resource("/new"); - EXPECT_EQ(resource_result["resource"], "NewResource"); } // Test error handling