add initialization verification
parent
76ac796464
commit
175d668642
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
set(TARGET mcp)
|
||||
|
||||
|
||||
add_library(${TARGET} STATIC
|
||||
mcp_client.cpp
|
||||
../include/mcp_client.h
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
// Log error but don't send response
|
||||
std::cerr << "Error processing notification: " << e.what() << std::endl;
|
||||
}
|
||||
}).detach();
|
||||
|
||||
if (req.method == "notifications/initialized") {
|
||||
set_client_initialized(client_address, true);
|
||||
}
|
||||
// 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
|
|
@ -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<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
|
||||
|
|
|
@ -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");
|
||||
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<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!");
|
||||
}
|
||||
// Verify tool call result
|
||||
EXPECT_TRUE(response["result"].contains("content"));
|
||||
EXPECT_TRUE(response["result"]["content"][0]["text"].get<std::string>().find("Result: Test input") != std::string::npos);
|
||||
}
|
||||
|
||||
// Test sending notification (no ID)
|
||||
|
@ -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 = {
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue