add initialization verification

main
hkr04 2025-03-09 23:17:36 +08:00
parent 76ac796464
commit 175d668642
10 changed files with 306 additions and 480 deletions

View File

@ -33,6 +33,13 @@ int main() {
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;
mcp::json capabilities = client.get_server_capabilities();
@ -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::string>() << std::endl;
std::cout << "Current time: " << time_result["content"][0]["text"].get<std::string>() << 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::string>() << std::endl;
std::cout << "Echo result: " << echo_result["content"][0]["text"].get<std::string>() << 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<double>() << std::endl;
std::cout << "10 + 5 = " << calc_result["content"][0]["text"].get<std::string>() << 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::string>() << 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::string>() << std::endl;
} catch (const mcp::mcp_exception& e) {
std::cerr << "MCP error: " << e.what() << " (code: " << static_cast<int>(e.code()) << ")" << std::endl;

View File

@ -29,9 +29,10 @@ mcp::json get_time_handler(const mcp::json& params) {
}
return {
{"current_time", time_str},
{"timestamp", static_cast<long long>(std::chrono::duration_cast<std::chrono::seconds>(
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<std::string>()}
}
};
}
// 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<std::string>() : "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<mcp::file_resource>("./files");
server.register_resource("/files", file_resource);
// Not implemented yet
// // Register resources
// auto file_resource = std::make_shared<mcp::file_resource>("./files");
// server.register_resource("/files", file_resource);
auto api_resource = std::make_shared<mcp::api_resource>("API", "Custom API endpoints");
api_resource->register_handler("hello", hello_handler, "Say hello");
// auto api_resource = std::make_shared<mcp::api_resource>("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;

View File

@ -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;
}

View File

@ -118,6 +118,17 @@ public:
*/
void set_auth_handler(std::function<bool(const std::string&)> 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<std::string, bool> 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);
};

View File

@ -1,6 +1,5 @@
set(TARGET mcp)
add_library(${TARGET} STATIC
mcp_client.cpp
../include/mcp_client.h

View File

@ -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<std::mutex> lock(mutex_);
auto it = notification_handlers_.find(req.method);
if (it != notification_handlers_.end()) {
it->second(req.params);
if (req.method == "notifications/initialized") {
set_client_initialized(client_address, true);
}
} catch (const std::exception& e) {
// Log error but don't send response
std::cerr << "Error processing notification: " << e.what() << std::endl;
}
}).detach();
// No response for notifications
return json::object();
}
@ -286,12 +278,22 @@ 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<std::mutex> lock(mutex_);
auto it = method_handlers_.find(req.method);
@ -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<std::mutex> 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<std::mutex> lock(mutex_);
client_initialized_[client_address] = initialized;
}
} // namespace mcp

View File

@ -15,30 +15,13 @@
// Mock tool handler function
mcp::json echo_tool_handler(const mcp::json& args) {
return {{
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 {
@ -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<test_resource>();
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

View File

@ -10,13 +10,31 @@
#include "mcp_server.h"
#include "mcp_client.h"
#include "mcp_tool.h"
#include "mcp_resource.h"
#include <gtest/gtest.h>
#include <httplib.h>
#include <thread>
#include <chrono>
#include <filesystem>
// Mock tool handler function
static mcp::json test_tool_handler(const mcp::json& params) {
if (params.contains("input")) {
return {
{
{"type", "text"},
{"text", "Result: " + params["input"].get<std::string>()}
}
};
} else {
return {
{
{"type", "text"},
{"text", "Default result"}
}
};
}
}
// Test fixture for setting up and cleaning up the test environment
class DirectRequestTest : public ::testing::Test {
protected:
@ -27,15 +45,10 @@ 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")
@ -43,24 +56,7 @@ protected:
.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<std::string>() : "Default input";
return {
{
{"type", "text"},
{"text", "Result: " + input}
}
};
});
// Register resources
auto test_resource = std::make_shared<mcp::api_resource>("Test Resource", "API resource for testing");
test_resource->register_handler("hello", [](const mcp::json& params) {
std::string name = params.contains("name") ? params["name"].get<std::string>() : "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]() {
@ -110,11 +106,140 @@ protected:
}
}
void mock_initialize() {
// Mock initialization request
mcp::json init_request = {
{"jsonrpc", "2.0"},
{"id", "init-1"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
send_jsonrpc_request(init_request);
send_jsonrpc_request({
{"jsonrpc", "2.0"},
{"method", "notifications/initialized"}
});
}
std::unique_ptr<mcp::server> server;
std::unique_ptr<httplib::Client> http_client;
std::thread server_thread;
};
// Test if server can handle requests w/ and w/o initialization
TEST_F(DirectRequestTest, InitializationTest) {
// Send request without initialization
mcp::json request = {
{"jsonrpc", "2.0"},
{"id", "no-init-1"},
{"method", "ping"},
{"params", {}}
};
mcp::json response = send_jsonrpc_request(request);
// Only ping and logging methods should be supported without initialization
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "no-init-1");
EXPECT_TRUE(response.contains("result"));
request = {
{"jsonrpc", "2.0"},
{"id", "no-init-2"},
{"method", "tools/call"},
{"params", {
{"name", "test_tool"},
{"arguments", {
{"input", "Test input"}
}}
}}
};
response = send_jsonrpc_request(request);
// Should return error
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "no-init-2");
EXPECT_FALSE(response.contains("result"));
EXPECT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::invalid_request));
// Mock initialization request
mcp::json init_request = {
{"jsonrpc", "2.0"},
{"id", "init-1"},
{"method", "initialize"},
{"params", {
{"protocolVersion", mcp::MCP_VERSION},
{"capabilities", {}},
{"clientInfo", {
{"name", "TestClient"},
{"version", "1.0.0"}
}}
}}
};
response = send_jsonrpc_request(init_request);
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-1");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
response = send_jsonrpc_request({
{"jsonrpc", "2.0"},
{"method", "notifications/initialized"}
});
EXPECT_TRUE(response.empty());
// Now all methods should be supported
request = {
{"jsonrpc", "2.0"},
{"id", "init-2"},
{"method", "ping"},
{"params", {}}
};
response = send_jsonrpc_request(request);
// Should return result
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-2");
EXPECT_TRUE(response.contains("result"));
request = {
{"jsonrpc", "2.0"},
{"id", "init-3"},
{"method", "tools/call"},
{"params", {
{"name", "test_tool"},
{"arguments", {
{"input", "Test input"}
}}
}}
};
response = send_jsonrpc_request(request);
// Should return result
EXPECT_EQ(response["jsonrpc"], "2.0");
EXPECT_EQ(response["id"], "init-3");
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
EXPECT_TRUE(response["result"].contains("content"));
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
}
// Test initialization with numeric ID
TEST_F(DirectRequestTest, InitializeWithNumericId) {
// Create initialization request with numeric ID
@ -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");
// Server may not implement callTool method, so check if error is returned
if (response.contains("error")) {
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
} else {
EXPECT_TRUE(response.contains("result"));
EXPECT_FALSE(response.contains("error"));
// Verify 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<int>(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<int>(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!");
}
EXPECT_TRUE(response["result"].contains("content"));
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
}
// Test sending notification (no ID)
@ -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<int>(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 = {

View File

@ -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"));
}

View File

@ -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<bool>());
// EXPECT_TRUE(capabilities["resources"]["listChanged"].get<bool>());
// 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<mock_resource>("MockResource1");
auto mock_res2 = std::make_shared<mock_resource>("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<mock_resource>("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<mock_resource>("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