add initialization verification
parent
76ac796464
commit
175d668642
|
@ -33,6 +33,13 @@ int main() {
|
||||||
return 1;
|
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
|
// Get server capabilities
|
||||||
std::cout << "Getting server capabilities..." << std::endl;
|
std::cout << "Getting server capabilities..." << std::endl;
|
||||||
mcp::json capabilities = client.get_server_capabilities();
|
mcp::json capabilities = client.get_server_capabilities();
|
||||||
|
@ -49,7 +56,7 @@ int main() {
|
||||||
// Call the get_time tool
|
// Call the get_time tool
|
||||||
std::cout << "\nCalling get_time tool..." << std::endl;
|
std::cout << "\nCalling get_time tool..." << std::endl;
|
||||||
mcp::json time_result = client.call_tool("get_time");
|
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
|
// Call the echo tool
|
||||||
std::cout << "\nCalling echo tool..." << std::endl;
|
std::cout << "\nCalling echo tool..." << std::endl;
|
||||||
|
@ -58,7 +65,7 @@ int main() {
|
||||||
{"uppercase", true}
|
{"uppercase", true}
|
||||||
};
|
};
|
||||||
mcp::json echo_result = client.call_tool("echo", echo_params);
|
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
|
// Call the calculator tool
|
||||||
std::cout << "\nCalling calculator tool..." << std::endl;
|
std::cout << "\nCalling calculator tool..." << std::endl;
|
||||||
|
@ -68,16 +75,17 @@ int main() {
|
||||||
{"b", 5}
|
{"b", 5}
|
||||||
};
|
};
|
||||||
mcp::json calc_result = client.call_tool("calculator", calc_params);
|
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
|
// Not implemented yet
|
||||||
std::cout << "\nAccessing API resource..." << std::endl;
|
// // Access a resource
|
||||||
mcp::json api_params = {
|
// std::cout << "\nAccessing API resource..." << std::endl;
|
||||||
{"endpoint", "hello"},
|
// mcp::json api_params = {
|
||||||
{"name", "MCP Client"}
|
// {"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;
|
// 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) {
|
} catch (const mcp::mcp_exception& e) {
|
||||||
std::cerr << "MCP error: " << e.what() << " (code: " << static_cast<int>(e.code()) << ")" << std::endl;
|
std::cerr << "MCP error: " << e.what() << " (code: " << static_cast<int>(e.code()) << ")" << std::endl;
|
||||||
|
|
|
@ -29,9 +29,10 @@ mcp::json get_time_handler(const mcp::json& params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{"current_time", time_str},
|
{
|
||||||
{"timestamp", static_cast<long long>(std::chrono::duration_cast<std::chrono::seconds>(
|
{"type", "text"},
|
||||||
now.time_since_epoch()).count())}
|
{"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
|
// 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);
|
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
|
// Custom API endpoint handler
|
||||||
mcp::json hello_handler(const mcp::json& params) {
|
mcp::json hello_handler(const mcp::json& params) {
|
||||||
std::string name = params.contains("name") ? params["name"].get<std::string>() : "World";
|
std::string name = params.contains("name") ? params["name"].get<std::string>() : "World";
|
||||||
return {{"message", "Hello, " + name + "!"}};
|
return {
|
||||||
|
{
|
||||||
|
{"type", "text"},
|
||||||
|
{"text", "Hello, " + name + "!"}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
@ -111,16 +127,10 @@ int main() {
|
||||||
|
|
||||||
// Set server capabilities
|
// Set server capabilities
|
||||||
mcp::json capabilities = {
|
mcp::json capabilities = {
|
||||||
{"tools", {{"listChanged", true}}},
|
{"tools", {{"listChanged", true}}}
|
||||||
{"resources", {{"listChanged", true}}}
|
|
||||||
};
|
};
|
||||||
server.set_capabilities(capabilities);
|
server.set_capabilities(capabilities);
|
||||||
|
|
||||||
// Register method handlers
|
|
||||||
server.register_method("ping", [](const mcp::json& params) {
|
|
||||||
return mcp::json{{"pong", true}};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register tools
|
// Register tools
|
||||||
mcp::tool time_tool = mcp::tool_builder("get_time")
|
mcp::tool time_tool = mcp::tool_builder("get_time")
|
||||||
.with_description("Get current time")
|
.with_description("Get current time")
|
||||||
|
@ -144,14 +154,15 @@ int main() {
|
||||||
server.register_tool(echo_tool, echo_handler);
|
server.register_tool(echo_tool, echo_handler);
|
||||||
server.register_tool(calc_tool, calculator_handler);
|
server.register_tool(calc_tool, calculator_handler);
|
||||||
|
|
||||||
// Register resources
|
// Not implemented yet
|
||||||
auto file_resource = std::make_shared<mcp::file_resource>("./files");
|
// // Register resources
|
||||||
server.register_resource("/files", file_resource);
|
// 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");
|
// auto api_resource = std::make_shared<mcp::api_resource>("API", "Custom API endpoints");
|
||||||
api_resource->register_handler("hello", hello_handler, "Say hello");
|
// api_resource->register_handler("hello", hello_handler, "Say hello");
|
||||||
|
|
||||||
server.register_resource("/api", api_resource);
|
// server.register_resource("/api", api_resource);
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
std::cout << "Starting MCP server at localhost:8080..." << std::endl;
|
std::cout << "Starting MCP server at localhost:8080..." << std::endl;
|
||||||
|
|
|
@ -82,7 +82,7 @@ struct request {
|
||||||
request req;
|
request req;
|
||||||
req.jsonrpc = "2.0";
|
req.jsonrpc = "2.0";
|
||||||
req.id = nullptr;
|
req.id = nullptr;
|
||||||
req.method = method;
|
req.method = "notifications/" + method;
|
||||||
req.params = params;
|
req.params = params;
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,17 @@ public:
|
||||||
*/
|
*/
|
||||||
void set_auth_handler(std::function<bool(const std::string&)> handler);
|
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:
|
private:
|
||||||
std::string host_;
|
std::string host_;
|
||||||
int port_;
|
int port_;
|
||||||
|
@ -152,14 +163,23 @@ private:
|
||||||
// Running flag
|
// Running flag
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
|
|
||||||
|
// Map to track client initialization status (client_address -> initialized)
|
||||||
|
std::map<std::string, bool> client_initialized_;
|
||||||
|
|
||||||
// Handle incoming JSON-RPC requests
|
// Handle incoming JSON-RPC requests
|
||||||
void handle_jsonrpc(const httplib::Request& req, httplib::Response& res);
|
void handle_jsonrpc(const httplib::Request& req, httplib::Response& res);
|
||||||
|
|
||||||
// Process a JSON-RPC request
|
// 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
|
// 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
set(TARGET mcp)
|
set(TARGET mcp)
|
||||||
|
|
||||||
|
|
||||||
add_library(${TARGET} STATIC
|
add_library(${TARGET} STATIC
|
||||||
mcp_client.cpp
|
mcp_client.cpp
|
||||||
../include/mcp_client.h
|
../include/mcp_client.h
|
||||||
|
|
|
@ -196,6 +196,9 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
// Set response headers
|
// Set response headers
|
||||||
res.set_header("Content-Type", "application/json");
|
res.set_header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
// Get client address
|
||||||
|
std::string client_address = req.remote_addr;
|
||||||
|
|
||||||
// Parse the request
|
// Parse the request
|
||||||
json req_json;
|
json req_json;
|
||||||
try {
|
try {
|
||||||
|
@ -257,27 +260,16 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the request
|
// Process the request
|
||||||
json result = process_request(mcp_req);
|
json result = process_request(mcp_req, client_address);
|
||||||
res.set_content(result.dump(), "application/json");
|
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
|
// Check if it's a notification
|
||||||
if (req.is_notification()) {
|
if (req.is_notification()) {
|
||||||
// Process notification asynchronously
|
if (req.method == "notifications/initialized") {
|
||||||
std::thread([this, req]() {
|
set_client_initialized(client_address, true);
|
||||||
try {
|
}
|
||||||
std::lock_guard<std::mutex> 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();
|
|
||||||
|
|
||||||
// No response for notifications
|
// No response for notifications
|
||||||
return json::object();
|
return json::object();
|
||||||
}
|
}
|
||||||
|
@ -286,12 +278,22 @@ json server::process_request(const request& req) {
|
||||||
try {
|
try {
|
||||||
// Special case for initialize
|
// Special case for initialize
|
||||||
if (req.method == "initialize") {
|
if (req.method == "initialize") {
|
||||||
return handle_initialize(req);
|
return handle_initialize(req, client_address);
|
||||||
} else if (req.method == "ping") {
|
} else if (req.method == "ping") {
|
||||||
// The receiver MUST respond promptly with an empty response
|
// The receiver MUST respond promptly with an empty response
|
||||||
return response::create_success(req.id, {}).to_json();
|
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
|
// Look for registered method handler
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto it = method_handlers_.find(req.method);
|
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;
|
const json& params = req.params;
|
||||||
|
|
||||||
// Version negotiation
|
// 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
|
// Log connection
|
||||||
// std::cout << "Client connected: " << client_name << " " << client_version << std::endl;
|
// 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();
|
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
|
} // namespace mcp
|
|
@ -15,31 +15,14 @@
|
||||||
|
|
||||||
// Mock tool handler function
|
// Mock tool handler function
|
||||||
mcp::json echo_tool_handler(const mcp::json& args) {
|
mcp::json echo_tool_handler(const mcp::json& args) {
|
||||||
return {{
|
return {
|
||||||
{"type", "text"},
|
{
|
||||||
{"text", args["message"]}
|
{"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
|
// Client test class
|
||||||
class ClientTest : public ::testing::Test {
|
class ClientTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
|
@ -50,8 +33,7 @@ protected:
|
||||||
|
|
||||||
// Set server capabilities
|
// Set server capabilities
|
||||||
mcp::json capabilities = {
|
mcp::json capabilities = {
|
||||||
{"tools", {{"listChanged", true}}},
|
{"tools", {{"listChanged", true}}}
|
||||||
{"resources", {{"listChanged", true}}}
|
|
||||||
};
|
};
|
||||||
server->set_capabilities(capabilities);
|
server->set_capabilities(capabilities);
|
||||||
|
|
||||||
|
@ -62,10 +44,6 @@ protected:
|
||||||
.build();
|
.build();
|
||||||
server->register_tool(echo_tool, echo_tool_handler);
|
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)
|
// Start server (non-blocking mode)
|
||||||
server_thread = std::thread([this]() {
|
server_thread = std::thread([this]() {
|
||||||
server->start(false);
|
server->start(false);
|
||||||
|
@ -116,7 +94,7 @@ TEST_F(ClientTest, GetCapabilitiesTest) {
|
||||||
|
|
||||||
// If it's an object, verify its contents
|
// If it's an object, verify its contents
|
||||||
if (capabilities.is_object()) {
|
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");
|
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 error handling
|
||||||
TEST_F(ClientTest, ErrorHandlingTest) {
|
TEST_F(ClientTest, ErrorHandlingTest) {
|
||||||
// Initialize client
|
// Initialize client
|
||||||
|
@ -186,9 +134,6 @@ TEST_F(ClientTest, ErrorHandlingTest) {
|
||||||
|
|
||||||
// Call non-existent tool
|
// Call non-existent tool
|
||||||
EXPECT_THROW(client->call_tool("non_existent_tool"), mcp::mcp_exception);
|
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
|
// Test setting client capabilities
|
||||||
|
|
|
@ -10,13 +10,31 @@
|
||||||
#include "mcp_server.h"
|
#include "mcp_server.h"
|
||||||
#include "mcp_client.h"
|
#include "mcp_client.h"
|
||||||
#include "mcp_tool.h"
|
#include "mcp_tool.h"
|
||||||
#include "mcp_resource.h"
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#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
|
// Test fixture for setting up and cleaning up the test environment
|
||||||
class DirectRequestTest : public ::testing::Test {
|
class DirectRequestTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
|
@ -27,15 +45,10 @@ protected:
|
||||||
|
|
||||||
// Set server capabilities
|
// Set server capabilities
|
||||||
mcp::json capabilities = {
|
mcp::json capabilities = {
|
||||||
{"tools", {{"listChanged", true}}},
|
{"tools", {{"listChanged", true}}}
|
||||||
{"resources", {{"listChanged", true}}}
|
|
||||||
};
|
};
|
||||||
server->set_capabilities(capabilities);
|
server->set_capabilities(capabilities);
|
||||||
|
|
||||||
// Register method handlers
|
|
||||||
server->register_method("ping", [](const mcp::json& params) {
|
|
||||||
return mcp::json{{"pong", true}};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register tools
|
// Register tools
|
||||||
mcp::tool test_tool = mcp::tool_builder("test_tool")
|
mcp::tool test_tool = mcp::tool_builder("test_tool")
|
||||||
|
@ -43,24 +56,7 @@ protected:
|
||||||
.with_string_param("input", "Input parameter")
|
.with_string_param("input", "Input parameter")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
server->register_tool(test_tool, [](const mcp::json& params) -> mcp::json {
|
server->register_tool(test_tool, test_tool_handler);
|
||||||
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);
|
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
server_thread = std::thread([this]() {
|
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<mcp::server> server;
|
||||||
std::unique_ptr<httplib::Client> http_client;
|
std::unique_ptr<httplib::Client> http_client;
|
||||||
std::thread server_thread;
|
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 initialization with numeric ID
|
||||||
TEST_F(DirectRequestTest, InitializeWithNumericId) {
|
TEST_F(DirectRequestTest, InitializeWithNumericId) {
|
||||||
// Create initialization request with numeric ID
|
// Create initialization request with numeric ID
|
||||||
|
@ -188,21 +313,7 @@ TEST_F(DirectRequestTest, InitializeWithStringId) {
|
||||||
// Test getting tools list
|
// Test getting tools list
|
||||||
TEST_F(DirectRequestTest, GetTools) {
|
TEST_F(DirectRequestTest, GetTools) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Get tools list
|
// Get tools list
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
|
@ -246,30 +357,16 @@ TEST_F(DirectRequestTest, GetTools) {
|
||||||
// Test calling a tool
|
// Test calling a tool
|
||||||
TEST_F(DirectRequestTest, CallTool) {
|
TEST_F(DirectRequestTest, CallTool) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Call tool
|
// Call tool
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
{"jsonrpc", "2.0"},
|
{"jsonrpc", "2.0"},
|
||||||
{"id", "call-1"},
|
{"id", "call-1"},
|
||||||
{"method", "callTool"},
|
{"method", "tools/call"},
|
||||||
{"params", {
|
{"params", {
|
||||||
{"name", "test_tool"},
|
{"name", "test_tool"},
|
||||||
{"parameters", {
|
{"arguments", {
|
||||||
{"input", "Test input"}
|
{"input", "Test input"}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
@ -280,126 +377,12 @@ TEST_F(DirectRequestTest, CallTool) {
|
||||||
// Verify response
|
// Verify response
|
||||||
EXPECT_EQ(response["jsonrpc"], "2.0");
|
EXPECT_EQ(response["jsonrpc"], "2.0");
|
||||||
EXPECT_EQ(response["id"], "call-1");
|
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
|
// Verify tool call result
|
||||||
if (response.contains("error")) {
|
EXPECT_TRUE(response["result"].contains("content"));
|
||||||
EXPECT_EQ(response["error"]["code"], static_cast<int>(mcp::error_code::method_not_found));
|
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
|
||||||
} 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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test sending notification (no ID)
|
// Test sending notification (no ID)
|
||||||
|
@ -442,6 +425,8 @@ TEST_F(DirectRequestTest, SendNotification) {
|
||||||
|
|
||||||
// Test error handling - method not found
|
// Test error handling - method not found
|
||||||
TEST_F(DirectRequestTest, MethodNotFound) {
|
TEST_F(DirectRequestTest, MethodNotFound) {
|
||||||
|
mock_initialize();
|
||||||
|
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
{"jsonrpc", "2.0"},
|
{"jsonrpc", "2.0"},
|
||||||
{"id", 999},
|
{"id", 999},
|
||||||
|
@ -462,21 +447,7 @@ TEST_F(DirectRequestTest, MethodNotFound) {
|
||||||
// Test error handling - invalid parameters
|
// Test error handling - invalid parameters
|
||||||
TEST_F(DirectRequestTest, InvalidParams) {
|
TEST_F(DirectRequestTest, InvalidParams) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Call tool but missing required parameters
|
// Call tool but missing required parameters
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
|
@ -505,62 +476,10 @@ TEST_F(DirectRequestTest, InvalidParams) {
|
||||||
error_code == static_cast<int>(mcp::error_code::invalid_params));
|
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 logging functionality
|
||||||
TEST_F(DirectRequestTest, LoggingTest) {
|
TEST_F(DirectRequestTest, LoggingTest) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Send log request
|
// Send log request
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
|
@ -595,21 +514,7 @@ TEST_F(DirectRequestTest, LoggingTest) {
|
||||||
// Test sampling functionality
|
// Test sampling functionality
|
||||||
TEST_F(DirectRequestTest, SamplingTest) {
|
TEST_F(DirectRequestTest, SamplingTest) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Send sampling request
|
// Send sampling request
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
|
@ -715,21 +620,7 @@ TEST_F(DirectRequestTest, BatchRequestTest) {
|
||||||
// Test request cancellation
|
// Test request cancellation
|
||||||
TEST_F(DirectRequestTest, CancelRequestTest) {
|
TEST_F(DirectRequestTest, CancelRequestTest) {
|
||||||
// Initialize first
|
// Initialize first
|
||||||
mcp::json init_request = {
|
mock_initialize();
|
||||||
{"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);
|
|
||||||
|
|
||||||
// Send cancel request
|
// Send cancel request
|
||||||
mcp::json request = {
|
mcp::json request = {
|
||||||
|
|
|
@ -46,7 +46,7 @@ TEST(McpMessageTest, NotificationCreationTest) {
|
||||||
// Verify notification properties
|
// Verify notification properties
|
||||||
EXPECT_EQ(notification.jsonrpc, "2.0");
|
EXPECT_EQ(notification.jsonrpc, "2.0");
|
||||||
EXPECT_TRUE(notification.id.is_null());
|
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["event"], "update");
|
||||||
EXPECT_EQ(notification.params["status"], "completed");
|
EXPECT_EQ(notification.params["status"], "completed");
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ TEST(McpMessageTest, NotificationToJsonTest) {
|
||||||
|
|
||||||
// Verify JSON structure
|
// Verify JSON structure
|
||||||
EXPECT_EQ(json_notification["jsonrpc"], "2.0");
|
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"));
|
EXPECT_FALSE(json_notification.contains("id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// Server test class
|
||||||
class ServerTest : public ::testing::Test {
|
class ServerTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
|
@ -67,8 +43,7 @@ protected:
|
||||||
|
|
||||||
// Set server capabilities
|
// Set server capabilities
|
||||||
mcp::json capabilities = {
|
mcp::json capabilities = {
|
||||||
{"tools", {{"listChanged", true}}},
|
{"tools", {{"listChanged", true}}}
|
||||||
{"resources", {{"listChanged", true}}}
|
|
||||||
};
|
};
|
||||||
server->set_capabilities(capabilities);
|
server->set_capabilities(capabilities);
|
||||||
}
|
}
|
||||||
|
@ -94,21 +69,6 @@ protected:
|
||||||
std::thread server_thread;
|
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 tool registration and retrieval
|
||||||
TEST_F(ServerTest, ToolRegistrationTest) {
|
TEST_F(ServerTest, ToolRegistrationTest) {
|
||||||
// Create tool
|
// Create tool
|
||||||
|
@ -135,35 +95,6 @@ TEST_F(ServerTest, ToolRegistrationTest) {
|
||||||
EXPECT_EQ(tools[0].description, "Test tool");
|
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 method registration and handling
|
||||||
TEST_F(ServerTest, MethodRegistrationTest) {
|
TEST_F(ServerTest, MethodRegistrationTest) {
|
||||||
// Register method - register as tool since client doesn't have call_method
|
// Register method - register as tool since client doesn't have call_method
|
||||||
|
@ -212,10 +143,6 @@ TEST_F(ServerTest, ServerClientInteractionTest) {
|
||||||
.build();
|
.build();
|
||||||
server->register_tool(test_tool, test_tool_handler);
|
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
|
||||||
start_server();
|
start_server();
|
||||||
|
|
||||||
|
@ -235,11 +162,6 @@ TEST_F(ServerTest, ServerClientInteractionTest) {
|
||||||
// Call tool
|
// Call tool
|
||||||
mcp::json tool_result = client.call_tool("test_tool", {{"input", "Test input"}});
|
mcp::json tool_result = client.call_tool("test_tool", {{"input", "Test input"}});
|
||||||
EXPECT_EQ(tool_result["content"][0]["text"], "Result: 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
|
// Test notification functionality
|
||||||
|
@ -257,20 +179,13 @@ TEST_F(ServerTest, NotificationTest) {
|
||||||
.build();
|
.build();
|
||||||
server->register_tool(new_tool, [](const mcp::json& params) { return mcp::json::object(); });
|
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
|
// Wait for notification processing
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
|
||||||
// Verify tools and resources were added
|
// Verify tools were added
|
||||||
auto tools = client.get_tools();
|
auto tools = client.get_tools();
|
||||||
EXPECT_EQ(tools.size(), 1);
|
EXPECT_EQ(tools.size(), 1);
|
||||||
EXPECT_EQ(tools[0].name, "new_tool");
|
EXPECT_EQ(tools[0].name, "new_tool");
|
||||||
|
|
||||||
mcp::json resource_result = client.access_resource("/new");
|
|
||||||
EXPECT_EQ(resource_result["resource"], "NewResource");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test error handling
|
// Test error handling
|
||||||
|
|
Loading…
Reference in New Issue