ready to publish?

main
hkr04 2025-04-01 14:47:30 +08:00
parent 0c3ee605b2
commit 169e9cb3d1
15 changed files with 258 additions and 8423 deletions

View File

@ -53,6 +53,4 @@ option(MCP_BUILD_TESTS "Build the tests" OFF)
if(MCP_BUILD_TESTS) if(MCP_BUILD_TESTS)
enable_testing() enable_testing()
add_subdirectory(test) add_subdirectory(test)
# run_teststest/CMakeLists.txt
endif() endif()

View File

@ -10,6 +10,22 @@
- **Extensible Architecture**: Easy to extend with new resource types and tools - **Extensible Architecture**: Easy to extend with new resource types and tools
- **Multi-Transport Support**: Supports HTTP and standard input/output (stdio) communication methods - **Multi-Transport Support**: Supports HTTP and standard input/output (stdio) communication methods
## How to Build
Example of building with CMake:
```bash
cmake -B build
cmake --build build --config Release
```
Build with tests:
```
git submodule --init --recursive # Get GoogleTest
cmake -B build -DMCP_BUILD_TESTS=ON
cmake --build build --config Release
```
## Components ## Components
The MCP C++ library includes the following main components: The MCP C++ library includes the following main components:
@ -45,7 +61,7 @@ Example MCP server implementation with custom tools:
- Time tool: Get the current time - Time tool: Get the current time
- Calculator tool: Perform mathematical operations - Calculator tool: Perform mathematical operations
- Echo tool: Process and analyze text - Echo tool: Process and analyze text
- Greeting tool: Returns `Hello, `+input name+`!`, defaults to `Hello, World!` - Greeting tool: Returns `Hello, `+ input name + `!`, defaults to `Hello, World!`
### HTTP Client Example (`examples/client_example.cpp`) ### HTTP Client Example (`examples/client_example.cpp`)
@ -175,22 +191,6 @@ json result = client.call_tool("tool_name", {
}); });
``` ```
## Building the Framework
The framework depends on the following libraries:
- httplib.h - HTTP server and client
- json.hpp - JSON parsing and generation
- gtest - Testing
All dependencies are included in the repository.
Example of building with CMake:
```bash
cmake -B build
cmake --build build --config Release
```
## License ## License
This framework is provided under the MIT license. For details, please see the LICENSE file. This framework is provided under the MIT license. For details, please see the LICENSE file.

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,10 @@
#include <chrono> #include <chrono>
int main(int argc, char** argv) { int main(int argc, char** argv) {
// 设置日志级别 // Set log level to info
mcp::set_log_level(mcp::log_level::info); mcp::set_log_level(mcp::log_level::info);
// 检查命令行参数 // Check command line arguments
if (argc < 2) { if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <server_command>" << std::endl; std::cerr << "Usage: " << argv[0] << " <server_command>" << std::endl;
std::cerr << "Example: " << argv[0] << " \"npx -y @modelcontextprotocol/server-everything\"" << std::endl; std::cerr << "Example: " << argv[0] << " \"npx -y @modelcontextprotocol/server-everything\"" << std::endl;
@ -27,17 +27,17 @@ int main(int argc, char** argv) {
std::string command = argv[1]; std::string command = argv[1];
// 设置环境变量 // Set example environment variables
mcp::json env_vars = { mcp::json env_vars = {
{"MCP_DEBUG", "1"}, {"MCP_DEBUG", "1"},
{"MCP_LOG_LEVEL", "debug"}, {"MCP_LOG_LEVEL", "debug"},
{"CUSTOM_VAR", "custom_value"} {"CUSTOM_VAR", "custom_value"}
}; };
// 创建客户端,直接在构造函数中传入环境变量 // Create client
mcp::stdio_client client(command, env_vars); mcp::stdio_client client(command, env_vars);
// 初始化客户端 // Initialize client
if (!client.initialize("MCP Stdio Client Example", "1.0.0")) { if (!client.initialize("MCP Stdio Client Example", "1.0.0")) {
std::cerr << "Failed to initialize client" << std::endl; std::cerr << "Failed to initialize client" << std::endl;
return 1; return 1;
@ -46,22 +46,23 @@ int main(int argc, char** argv) {
std::cout << "Client initialized successfully" << std::endl; std::cout << "Client initialized successfully" << std::endl;
try { try {
// 获取服务器能力 // Get server capabilities
auto capabilities = client.get_server_capabilities(); auto capabilities = client.get_server_capabilities();
std::cout << "Server capabilities: " << capabilities.dump(2) << std::endl; std::cout << "Server capabilities: " << capabilities.dump(2) << std::endl;
// 列出可用工具 // List available tools
auto tools = client.get_tools(); auto tools = client.get_tools();
std::cout << "Available tools: " << tools.size() << std::endl; std::cout << "Available tools: " << tools.size() << std::endl;
for (const auto& tool : tools) { for (const auto& tool : tools) {
std::cout << " - " << tool.name << ": " << tool.description << std::endl; std::cout << " - " << tool.name << ": " << tool.description << std::endl;
// std::cout << tool.to_json().dump(2) << std::endl;
} }
// 列出可用资源 // List available resources
auto resources = client.list_resources(); auto resources = client.list_resources();
std::cout << "Available resources: " << resources.dump(2) << std::endl; std::cout << "Available resources: " << resources.dump(2) << std::endl;
// 如果有资源,读取第一个资源 // If there are resources, read the first one
if (resources.contains("resources") && resources["resources"].is_array() && !resources["resources"].empty()) { if (resources.contains("resources") && resources["resources"].is_array() && !resources["resources"].empty()) {
auto resource = resources["resources"][0]; auto resource = resources["resources"][0];
if (resource.contains("uri")) { if (resource.contains("uri")) {
@ -73,11 +74,11 @@ int main(int argc, char** argv) {
} }
} }
// 保持连接一段时间 // Keep connection alive for 5 seconds
std::cout << "Keeping connection alive for 5 seconds..." << std::endl; std::cout << "Keeping connection alive for 5 seconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
// 发送ping请求 // Send ping request
bool ping_result = client.ping(); bool ping_result = client.ping();
std::cout << "Ping result: " << (ping_result ? "success" : "failure") << std::endl; std::cout << "Ping result: " << (ping_result ? "success" : "failure") << std::endl;

View File

@ -1,6 +1,6 @@
/** /**
* @file mcp_logger.h * @file mcp_logger.h
* @brief * @brief Simple logger
*/ */
#ifndef MCP_LOGGER_H #ifndef MCP_LOGGER_H
@ -76,33 +76,33 @@ private:
std::stringstream ss; std::stringstream ss;
// 添加时间戳 // Add timestamp
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now); auto now_c = std::chrono::system_clock::to_time_t(now);
auto now_tm = std::localtime(&now_c); auto now_tm = std::localtime(&now_c);
ss << std::put_time(now_tm, "%Y-%m-%d %H:%M:%S") << " "; ss << std::put_time(now_tm, "%Y-%m-%d %H:%M:%S") << " ";
// 添加日志级别和颜色 // Add log level and color
switch (level) { switch (level) {
case log_level::debug: case log_level::debug:
ss << "\033[36m[DEBUG]\033[0m "; // 青色 ss << "\033[36m[DEBUG]\033[0m "; // Cyan
break; break;
case log_level::info: case log_level::info:
ss << "\033[32m[INFO]\033[0m "; // 绿色 ss << "\033[32m[INFO]\033[0m "; // Green
break; break;
case log_level::warning: case log_level::warning:
ss << "\033[33m[WARNING]\033[0m "; // 黄色 ss << "\033[33m[WARNING]\033[0m "; // Yellow
break; break;
case log_level::error: case log_level::error:
ss << "\033[31m[ERROR]\033[0m "; // 红色 ss << "\033[31m[ERROR]\033[0m "; // Red
break; break;
} }
// 添加日志内容 // Add log content
log_impl(ss, std::forward<Args>(args)...); log_impl(ss, std::forward<Args>(args)...);
// 输出日志 // Output log
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
std::cerr << ss.str() << std::endl; std::cerr << ss.str() << std::endl;
} }

View File

@ -35,9 +35,8 @@ namespace mcp {
class event_dispatcher { class event_dispatcher {
public: public:
// 使用较小的初始消息缓冲区
event_dispatcher() { event_dispatcher() {
message_.reserve(128); // 预分配较小的缓冲区空间 message_.reserve(128); // Pre-allocate space for messages
} }
~event_dispatcher() { ~event_dispatcher() {
@ -71,11 +70,11 @@ public:
return false; return false;
} }
// 仅当有新消息时才复制 // Only copy the message if there is one
if (!message_.empty()) { if (!message_.empty()) {
message_copy.swap(message_); message_copy.swap(message_);
} else { } else {
return true; // 没有消息但是条件已满足 return true; // No message but condition satisfied
} }
} }
@ -105,14 +104,14 @@ public:
return false; return false;
} }
// 高效设置消息并分配适当空间 // Efficiently set the message and allocate space as needed
if (message.size() > message_.capacity()) { if (message.size() > message_.capacity()) {
message_.reserve(message.size() + 64); // 预分配额外空间避免频繁再分配 message_.reserve(message.size() + 64); // Pre-allocate extra space to avoid frequent reallocations
} }
message_ = message; message_ = message;
cid_.store(id_.fetch_add(1, std::memory_order_relaxed), std::memory_order_relaxed); cid_.store(id_.fetch_add(1, std::memory_order_relaxed), std::memory_order_relaxed);
cv_.notify_one(); // 只通知一个等待线程,减少竞争 cv_.notify_one(); // Notify waiting threads
return true; return true;
} catch (...) { } catch (...) {
return false; return false;
@ -128,7 +127,7 @@ public:
try { try {
cv_.notify_all(); cv_.notify_all();
} catch (...) { } catch (...) {
// 忽略异常 // Ignore exceptions
} }
} }
@ -136,13 +135,13 @@ public:
return closed_.load(std::memory_order_acquire); return closed_.load(std::memory_order_acquire);
} }
// 获取最后活动时间 // Get the last activity time
std::chrono::steady_clock::time_point last_activity() const { std::chrono::steady_clock::time_point last_activity() const {
std::lock_guard<std::mutex> lk(m_); std::lock_guard<std::mutex> lk(m_);
return last_activity_; return last_activity_;
} }
// 更新活动时间(发送或接收消息时) // Update the activity time (when sending or receiving a message)
void update_activity() { void update_activity() {
std::lock_guard<std::mutex> lk(m_); std::lock_guard<std::mutex> lk(m_);
last_activity_ = std::chrono::steady_clock::now(); last_activity_ = std::chrono::steady_clock::now();
@ -317,7 +316,7 @@ private:
// Running flag // Running flag
bool running_ = false; bool running_ = false;
// 线程池 // Thread pool for async method handlers
thread_pool thread_pool_; thread_pool thread_pool_;
// Map to track session initialization status (session_id -> initialized) // Map to track session initialization status (session_id -> initialized)
@ -344,7 +343,7 @@ private:
// Generate a random session ID // Generate a random session ID
std::string generate_session_id() const; std::string generate_session_id() const;
// 辅助函数:创建异步方法处理器 // Auxiliary function to create an async handler from a regular handler
template<typename F> template<typename F>
std::function<std::future<json>(const json&)> make_async_handler(F&& handler) { std::function<std::future<json>(const json&)> make_async_handler(F&& handler) {
return [handler = std::forward<F>(handler)](const json& params) -> std::future<json> { return [handler = std::forward<F>(handler)](const json& params) -> std::future<json> {
@ -354,7 +353,7 @@ private:
}; };
} }
// 辅助类,用于简化锁的管理 // Helper class to simplify lock management
class auto_lock { class auto_lock {
public: public:
explicit auto_lock(std::mutex& mutex) : lock_(mutex) {} explicit auto_lock(std::mutex& mutex) : lock_(mutex) {}
@ -362,12 +361,12 @@ private:
std::lock_guard<std::mutex> lock_; std::lock_guard<std::mutex> lock_;
}; };
// 获取自动锁 // Get auto lock
auto_lock get_lock() const { auto_lock get_lock() const {
return auto_lock(mutex_); return auto_lock(mutex_);
} }
// 会话管理与维护 // Session management and maintenance
void check_inactive_sessions(); void check_inactive_sessions();
std::unique_ptr<std::thread> maintenance_thread_; std::unique_ptr<std::thread> maintenance_thread_;
}; };

View File

@ -170,7 +170,7 @@ public:
json list_resource_templates() override; json list_resource_templates() override;
/** /**
* @brief 访 * @brief Check if the server is accessible
* @return True if the server is accessible * @return True if the server is accessible
*/ */
bool check_server_accessible(); bool check_server_accessible();
@ -182,75 +182,75 @@ public:
bool is_running() const override; bool is_running() const override;
private: private:
// 初始化HTTP客户端 // Initialize HTTP client
void init_client(const std::string& host, int port); void init_client(const std::string& host, int port);
void init_client(const std::string& base_url); void init_client(const std::string& base_url);
// 打开SSE连接 // Open SSE connection
void open_sse_connection(); void open_sse_connection();
// 解析SSE数据 // Parse SSE data
bool parse_sse_data(const char* data, size_t length); bool parse_sse_data(const char* data, size_t length);
// 关闭SSE连接 // Close SSE connection
void close_sse_connection(); void close_sse_connection();
// 发送JSON-RPC请求 // Send JSON-RPC request
json send_jsonrpc(const request& req); json send_jsonrpc(const request& req);
// 服务器主机和端口 // Server host and port
std::string host_; std::string host_;
int port_ = 8080; int port_ = 8080;
// 或者使用基础URL // Use base URL
std::string base_url_; std::string base_url_;
// SSE端点 // SSE endpoint
std::string sse_endpoint_ = "/sse"; std::string sse_endpoint_ = "/sse";
// 消息端点 // Message endpoint
std::string msg_endpoint_; std::string msg_endpoint_;
// HTTP客户端 // HTTP client
std::unique_ptr<httplib::Client> http_client_; std::unique_ptr<httplib::Client> http_client_;
// SSE专用HTTP客户端 // SSE HTTP client
std::unique_ptr<httplib::Client> sse_client_; std::unique_ptr<httplib::Client> sse_client_;
// SSE线程 // SSE thread
std::unique_ptr<std::thread> sse_thread_; std::unique_ptr<std::thread> sse_thread_;
// SSE运行状态 // SSE running status
std::atomic<bool> sse_running_{false}; std::atomic<bool> sse_running_{false};
// 认证令牌 // Authentication token
std::string auth_token_; std::string auth_token_;
// 默认请求头 // Default request headers
std::map<std::string, std::string> default_headers_; std::map<std::string, std::string> default_headers_;
// 超时设置(秒) // Timeout (seconds)
int timeout_seconds_ = 30; int timeout_seconds_ = 30;
// 客户端能力 // Client capabilities
json capabilities_; json capabilities_;
// 服务器能力 // Server capabilities
json server_capabilities_; json server_capabilities_;
// 互斥锁 // Mutex
mutable std::mutex mutex_; mutable std::mutex mutex_;
// 条件变量,用于等待消息端点设置 // Condition variable, used to wait for message endpoint setting
std::condition_variable endpoint_cv_; std::condition_variable endpoint_cv_;
// 请求ID到Promise的映射用于异步等待响应 // Request ID to Promise mapping, used for asynchronous waiting for responses
std::map<json, std::promise<json>> pending_requests_; std::map<json, std::promise<json>> pending_requests_;
// 响应处理互斥锁 // Response processing mutex
std::mutex response_mutex_; std::mutex response_mutex_;
// 响应条件变量 // Response condition variable
std::condition_variable response_cv_; std::condition_variable response_cv_;
}; };

View File

@ -26,8 +26,7 @@
#include <future> #include <future>
#include <thread> #include <thread>
// 添加Windows平台特定的头文件 #if defined(_WIN32)
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h> #include <windows.h>
#endif #endif
@ -164,67 +163,67 @@ public:
bool is_running() const override; bool is_running() const override;
private: private:
// 启动服务器进程 // Start server process
bool start_server_process(); bool start_server_process();
// 停止服务器进程 // Stop server process
void stop_server_process(); void stop_server_process();
// 读取线程函数 // Read thread function
void read_thread_func(); void read_thread_func();
// 发送JSON-RPC请求 // Send JSON-RPC request
json send_jsonrpc(const request& req); json send_jsonrpc(const request& req);
// 服务器命令 // Server command
std::string command_; std::string command_;
// 进程ID // Process ID
int process_id_ = -1; int process_id_ = -1;
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32)
// Windows平台特定的进程句柄 // Windows platform specific process handle
HANDLE process_handle_ = NULL; HANDLE process_handle_ = NULL;
// 标准输入输出管道 (Windows) // Standard input/output pipes (Windows)
HANDLE stdin_pipe_[2] = {NULL, NULL}; HANDLE stdin_pipe_[2] = {NULL, NULL};
HANDLE stdout_pipe_[2] = {NULL, NULL}; HANDLE stdout_pipe_[2] = {NULL, NULL};
#else #else
// 标准输入管道 (POSIX) // Standard input pipe (POSIX)
int stdin_pipe_[2] = {-1, -1}; int stdin_pipe_[2] = {-1, -1};
// 标准输出管道 (POSIX) // Standard output pipe (POSIX)
int stdout_pipe_[2] = {-1, -1}; int stdout_pipe_[2] = {-1, -1};
#endif #endif
// 读取线程 // Read thread
std::unique_ptr<std::thread> read_thread_; std::unique_ptr<std::thread> read_thread_;
// 运行状态 // Running status
std::atomic<bool> running_{false}; std::atomic<bool> running_{false};
// 客户端能力 // Client capabilities
json capabilities_; json capabilities_;
// 服务器能力 // Server capabilities
json server_capabilities_; json server_capabilities_;
// 互斥锁 // Mutex
mutable std::mutex mutex_; mutable std::mutex mutex_;
// 请求ID到Promise的映射用于异步等待响应 // Request ID to Promise mapping, used for asynchronous waiting for responses
std::map<json, std::promise<json>> pending_requests_; std::map<json, std::promise<json>> pending_requests_;
// 响应处理互斥锁 // Response processing mutex
std::mutex response_mutex_; std::mutex response_mutex_;
// 初始化状态 // Initialization status
std::atomic<bool> initialized_{false}; std::atomic<bool> initialized_{false};
// 初始化条件变量 // Initialization condition variable
std::condition_variable init_cv_; std::condition_variable init_cv_;
// 环境变量 // Environment variables
json env_vars_; json env_vars_;
}; };

View File

@ -1,6 +1,6 @@
/** /**
* @file mcp_thread_pool.h * @file mcp_thread_pool.h
* @brief 线 * @brief Simple thread pool implementation
*/ */
#ifndef MCP_THREAD_POOL_H #ifndef MCP_THREAD_POOL_H
@ -21,8 +21,8 @@ namespace mcp {
class thread_pool { class thread_pool {
public: public:
/** /**
* @brief * @brief Constructor
* @param num_threads 线线 * @param num_threads Number of threads in the thread pool
*/ */
explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency()) : stop_(false) { explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency()) : stop_(false) {
for (size_t i = 0; i < num_threads; ++i) { for (size_t i = 0; i < num_threads; ++i) {
@ -51,7 +51,7 @@ public:
} }
/** /**
* @brief * @brief Destructor
*/ */
~thread_pool() { ~thread_pool() {
{ {
@ -69,10 +69,10 @@ public:
} }
/** /**
* @brief 线 * @brief Submit task to thread pool
* @param f * @param f Task function
* @param args * @param args Task parameters
* @return future * @return Task future
*/ */
template<class F, class... Args> template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> { auto enqueue(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> {
@ -88,7 +88,7 @@ public:
std::unique_lock<std::mutex> lock(queue_mutex_); std::unique_lock<std::mutex> lock(queue_mutex_);
if (stop_) { if (stop_) {
throw std::runtime_error("线程池已停止,无法添加任务"); throw std::runtime_error("Thread pool stopped, cannot add task");
} }
tasks_.emplace([task]() { (*task)(); }); tasks_.emplace([task]() { (*task)(); });
@ -99,17 +99,17 @@ public:
} }
private: private:
// 工作线程 // Worker threads
std::vector<std::thread> workers_; std::vector<std::thread> workers_;
// 任务队列 // Task queue
std::queue<std::function<void()>> tasks_; std::queue<std::function<void()>> tasks_;
// 互斥锁和条件变量 // Mutex and condition variable
std::mutex queue_mutex_; std::mutex queue_mutex_;
std::condition_variable condition_; std::condition_variable condition_;
// 停止标志 // Stop flag
std::atomic<bool> stop_; std::atomic<bool> stop_;
}; };

View File

@ -33,76 +33,11 @@ struct tool {
return { return {
{"name", name}, {"name", name},
{"description", description}, {"description", description},
{"inputSchema", parameters_schema} // You may need 'parameters' instead of 'inputSchema' for OAI format {"inputSchema", parameters_schema} // You may need `parameters` instead of `inputSchema` for OAI format
}; };
} }
}; };
/**
* @class tool_registry
* @brief Registry for MCP tools
*
* The tool_registry class provides a centralized registry for tools that can
* be used by agents and clients.
*/
class tool_registry {
public:
/**
* @brief Get the singleton instance
* @return Reference to the singleton registry
*/
static tool_registry& instance() {
static tool_registry instance;
return instance;
}
/**
* @brief Register a tool
* @param tool The tool to register
* @param handler The function to call when the tool is invoked
*/
void register_tool(const tool& tool, tool_handler handler);
/**
* @brief Unregister a tool
* @param tool_name The name of the tool to unregister
* @return True if the tool was found and unregistered
*/
bool unregister_tool(const std::string& tool_name);
/**
* @brief Get a tool by name
* @param tool_name The name of the tool
* @return A pair containing the tool definition and handler, or nullptr if not found
*/
std::pair<tool, tool_handler>* get_tool(const std::string& tool_name);
/**
* @brief Get all registered tools
* @return A vector of all registered tools
*/
std::vector<tool> get_all_tools() const;
/**
* @brief Call a tool
* @param tool_name The name of the tool to call
* @param parameters The parameters to pass to the tool
* @return The result of the tool call
* @throws mcp_exception if the tool doesn't exist or execution fails
*/
json call_tool(const std::string& tool_name, const json& parameters);
private:
// Private constructor for singleton
tool_registry() {}
// Mapping of tool names to their definitions and handlers
std::map<std::string, std::pair<tool, tool_handler>> tools_;
// Mutex for thread safety
mutable std::mutex mutex_;
};
/** /**
* @class tool_builder * @class tool_builder
* @brief Utility class for building tools with a fluent API * @brief Utility class for building tools with a fluent API

View File

@ -12,51 +12,6 @@
namespace mcp { namespace mcp {
// Implementation for tool_registry
void tool_registry::register_tool(const tool& tool, tool_handler handler) {
std::lock_guard<std::mutex> lock(mutex_);
tools_[tool.name] = std::make_pair(tool, handler);
}
bool tool_registry::unregister_tool(const std::string& tool_name) {
std::lock_guard<std::mutex> lock(mutex_);
return tools_.erase(tool_name) > 0;
}
std::pair<tool, tool_handler>* tool_registry::get_tool(const std::string& tool_name) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = tools_.find(tool_name);
if (it != tools_.end()) {
return &(it->second);
}
return nullptr;
}
std::vector<tool> tool_registry::get_all_tools() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<tool> result;
for (const auto& [name, tool_pair] : tools_) {
result.push_back(tool_pair.first);
}
return result;
}
json tool_registry::call_tool(const std::string& tool_name, const json& parameters) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = tools_.find(tool_name);
if (it == tools_.end()) {
throw mcp_exception(error_code::method_not_found, "Tool not found: " + tool_name);
}
try {
return it->second.second(parameters);
} catch (const std::exception& e) {
throw mcp_exception(error_code::internal_error,
"Tool execution failed: " + std::string(e.what()));
}
}
// Implementation for tool_builder // Implementation for tool_builder
tool_builder::tool_builder(const std::string& name) tool_builder::tool_builder(const std::string& name)
: name_(name) { : name_(name) {

View File

@ -35,7 +35,6 @@ add_executable(${TEST_PROJECT_NAME} ${TEST_SOURCES})
# Link directories # Link directories
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../build/src) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../build/src)
#
if(WIN32) if(WIN32)
set(MCP_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../build/src/mcp.lib") set(MCP_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../build/src/mcp.lib")
else() else()
@ -57,7 +56,6 @@ if(OPENSSL_FOUND)
target_link_libraries(${TEST_PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES}) target_link_libraries(${TEST_PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES})
endif() endif()
#
if(APPLE) if(APPLE)
set_target_properties(${TEST_PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-no_warn_duplicate_libraries") set_target_properties(${TEST_PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-no_warn_duplicate_libraries")
endif() endif()

View File

@ -1,59 +0,0 @@
# MCP Unit Tests
This directory contains unit tests for the Model Context Protocol (MCP) implementation, based on the 2024-11-05 specification.
## Building and Running Tests
### Building Tests
```bash
# Create a build directory in the project root
mkdir -p build && cd build
# Configure the project
cmake ..
# Build the project and tests
make
```
### Running Tests
```bash
# Run all tests
make run_tests
# Or directly run the test executable
./test/mcp_tests
```
### Running Specific Tests
To run specific tests, you can use Google Test's filtering capability:
```bash
# Run all message-related tests
./test/mcp_tests --gtest_filter=McpMessageTest.*
# Run all tool-related tests
./test/mcp_tests --gtest_filter=McpToolTest.*
# Run all resource-related tests
./test/mcp_tests --gtest_filter=McpResourceTest.*
# Run all client-related tests
./test/mcp_tests --gtest_filter=ClientTest.*
# Run all server-related tests
./test/mcp_tests --gtest_filter=ServerTest.*
```
## Test Dependencies
Tests use the Google Test framework, which is automatically downloaded and configured during the build process.
## Notes
- Some tests require network functionality, ensure that local ports (such as 8090, 8095) are not in use
- Client and server tests will start actual servers and clients for interaction testing
- Resource tests will create files in a temporary directory, which will be automatically cleaned up after testing

View File

@ -1,8 +1,8 @@
/** /**
* @file mcp_test.cpp * @file mcp_test.cpp
* @brief MCP * @brief Test the basic functions of the MCP framework
* *
* MCPping * This file contains tests for the message format, lifecycle, version control, ping, and tool functionality of the MCP framework.
*/ */
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -16,57 +16,57 @@
using namespace mcp; using namespace mcp;
using json = nlohmann::ordered_json; using json = nlohmann::ordered_json;
// 测试消息格式 // Test message format
class MessageFormatTest : public ::testing::Test { class MessageFormatTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// 设置测试环境 // Set up test environment
} }
void TearDown() override { void TearDown() override {
// 清理测试环境 // Clean up test environment
} }
}; };
// 测试请求消息格式 // Test request message format
TEST_F(MessageFormatTest, RequestMessageFormat) { TEST_F(MessageFormatTest, RequestMessageFormat) {
// 创建一个请求消息 // Create a request message
request req = request::create("test_method", {{"key", "value"}}); request req = request::create("test_method", {{"key", "value"}});
// 转换为JSON // Convert to JSON
json req_json = req.to_json(); json req_json = req.to_json();
// 验证JSON格式是否符合规范 // Verify JSON format is correct
EXPECT_EQ(req_json["jsonrpc"], "2.0"); EXPECT_EQ(req_json["jsonrpc"], "2.0");
EXPECT_TRUE(req_json.contains("id")); EXPECT_TRUE(req_json.contains("id"));
EXPECT_EQ(req_json["method"], "test_method"); EXPECT_EQ(req_json["method"], "test_method");
EXPECT_EQ(req_json["params"]["key"], "value"); EXPECT_EQ(req_json["params"]["key"], "value");
} }
// 测试响应消息格式 // Test response message format
TEST_F(MessageFormatTest, ResponseMessageFormat) { TEST_F(MessageFormatTest, ResponseMessageFormat) {
// 创建一个成功响应 // Create a successful response
response res = response::create_success("test_id", {{"key", "value"}}); response res = response::create_success("test_id", {{"key", "value"}});
// 转换为JSON // Convert to JSON
json res_json = res.to_json(); json res_json = res.to_json();
// 验证JSON格式是否符合规范 // Verify JSON format is correct
EXPECT_EQ(res_json["jsonrpc"], "2.0"); EXPECT_EQ(res_json["jsonrpc"], "2.0");
EXPECT_EQ(res_json["id"], "test_id"); EXPECT_EQ(res_json["id"], "test_id");
EXPECT_EQ(res_json["result"]["key"], "value"); EXPECT_EQ(res_json["result"]["key"], "value");
EXPECT_FALSE(res_json.contains("error")); EXPECT_FALSE(res_json.contains("error"));
} }
// 测试错误响应消息格式 // Test error response message format
TEST_F(MessageFormatTest, ErrorResponseMessageFormat) { TEST_F(MessageFormatTest, ErrorResponseMessageFormat) {
// 创建一个错误响应 // Create an error response
response res = response::create_error("test_id", error_code::invalid_params, "Invalid parameters", {{"details", "Missing required field"}}); response res = response::create_error("test_id", error_code::invalid_params, "Invalid parameters", {{"details", "Missing required field"}});
// 转换为JSON // Convert to JSON
json res_json = res.to_json(); json res_json = res.to_json();
// 验证JSON格式是否符合规范 // Verify JSON format is correct
EXPECT_EQ(res_json["jsonrpc"], "2.0"); EXPECT_EQ(res_json["jsonrpc"], "2.0");
EXPECT_EQ(res_json["id"], "test_id"); EXPECT_EQ(res_json["id"], "test_id");
EXPECT_FALSE(res_json.contains("result")); EXPECT_FALSE(res_json.contains("result"));
@ -75,33 +75,32 @@ TEST_F(MessageFormatTest, ErrorResponseMessageFormat) {
EXPECT_EQ(res_json["error"]["data"]["details"], "Missing required field"); EXPECT_EQ(res_json["error"]["data"]["details"], "Missing required field");
} }
// 测试通知消息格式 // Test notification message format
TEST_F(MessageFormatTest, NotificationMessageFormat) { TEST_F(MessageFormatTest, NotificationMessageFormat) {
// 创建一个通知消息 // Create a notification message
request notification = request::create_notification("test_notification", {{"key", "value"}}); request notification = request::create_notification("test_notification", {{"key", "value"}});
// 转换为JSON // Convert to JSON
json notification_json = notification.to_json(); json notification_json = notification.to_json();
// 验证JSON格式是否符合规范 // Verify JSON format is correct
EXPECT_EQ(notification_json["jsonrpc"], "2.0"); EXPECT_EQ(notification_json["jsonrpc"], "2.0");
EXPECT_FALSE(notification_json.contains("id")); EXPECT_FALSE(notification_json.contains("id"));
EXPECT_EQ(notification_json["method"], "notifications/test_notification"); EXPECT_EQ(notification_json["method"], "notifications/test_notification");
EXPECT_EQ(notification_json["params"]["key"], "value"); EXPECT_EQ(notification_json["params"]["key"], "value");
// 验证是否为通知消息 // Verify if it is a notification message
EXPECT_TRUE(notification.is_notification()); EXPECT_TRUE(notification.is_notification());
} }
// 生命周期测试环境
class LifecycleEnvironment : public ::testing::Environment { class LifecycleEnvironment : public ::testing::Environment {
public: public:
void SetUp() override { void SetUp() override {
// 设置测试环境 // Set up test environment
server_ = std::make_unique<server>("localhost", 8080); server_ = std::make_unique<server>("localhost", 8080);
server_->set_server_info("TestServer", "1.0.0"); server_->set_server_info("TestServer", "1.0.0");
// 设置服务器能力 // Set server capabilities
json server_capabilities = { json server_capabilities = {
{"logging", json::object()}, {"logging", json::object()},
{"prompts", {{"listChanged", true}}}, {"prompts", {{"listChanged", true}}},
@ -110,14 +109,14 @@ public:
}; };
server_->set_capabilities(server_capabilities); server_->set_capabilities(server_capabilities);
// 注册初始化方法处理器 // Register initialize method handler
server_->register_method("initialize", [server_capabilities](const json& params) -> json { server_->register_method("initialize", [server_capabilities](const json& params) -> json {
// 验证初始化请求参数 // Verify initialize request parameters
EXPECT_EQ(params["protocolVersion"], MCP_VERSION); EXPECT_EQ(params["protocolVersion"], MCP_VERSION);
EXPECT_TRUE(params.contains("capabilities")); EXPECT_TRUE(params.contains("capabilities"));
EXPECT_TRUE(params.contains("clientInfo")); EXPECT_TRUE(params.contains("clientInfo"));
// 返回初始化响应 // Return initialize response
return { return {
{"protocolVersion", MCP_VERSION}, {"protocolVersion", MCP_VERSION},
{"capabilities", server_capabilities}, {"capabilities", server_capabilities},
@ -128,10 +127,10 @@ public:
}; };
}); });
// 启动服务器(非阻塞模式) // Start server (non-blocking mode)
server_->start(false); server_->start(false);
// 创建客户端 // Create client
json client_capabilities = { json client_capabilities = {
{"roots", {{"listChanged", true}}}, {"roots", {{"listChanged", true}}},
{"sampling", json::object()} {"sampling", json::object()}
@ -141,7 +140,7 @@ public:
} }
void TearDown() override { void TearDown() override {
// 清理测试环境 // Clean up test environment
client_.reset(); client_.reset();
server_->stop(); server_->stop();
server_.reset(); server_.reset();
@ -160,32 +159,31 @@ private:
static std::unique_ptr<sse_client> client_; static std::unique_ptr<sse_client> client_;
}; };
// 静态成员变量定义 // Static member variable definition
std::unique_ptr<server> LifecycleEnvironment::server_; std::unique_ptr<server> LifecycleEnvironment::server_;
std::unique_ptr<sse_client> LifecycleEnvironment::client_; std::unique_ptr<sse_client> LifecycleEnvironment::client_;
// 测试生命周期
class LifecycleTest : public ::testing::Test { class LifecycleTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// 获取客户端指针 // Get client pointer
client_ = LifecycleEnvironment::GetClient().get(); client_ = LifecycleEnvironment::GetClient().get();
} }
// 使用原始指针而不是引用 // Use raw pointer instead of reference
sse_client* client_; sse_client* client_;
}; };
// 测试初始化流程 // Test initialize process
TEST_F(LifecycleTest, InitializeProcess) { TEST_F(LifecycleTest, InitializeProcess) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 执行初始化 // Execute initialize
bool init_result = client_->initialize("TestClient", "1.0.0"); bool init_result = client_->initialize("TestClient", "1.0.0");
// 验证初始化结果 // Verify initialize result
EXPECT_TRUE(init_result); EXPECT_TRUE(init_result);
// 验证服务器能力 // Verify server capabilities
json server_capabilities = client_->get_server_capabilities(); json server_capabilities = client_->get_server_capabilities();
EXPECT_TRUE(server_capabilities.contains("logging")); EXPECT_TRUE(server_capabilities.contains("logging"));
EXPECT_TRUE(server_capabilities.contains("prompts")); EXPECT_TRUE(server_capabilities.contains("prompts"));
@ -193,15 +191,15 @@ TEST_F(LifecycleTest, InitializeProcess) {
EXPECT_TRUE(server_capabilities.contains("tools")); EXPECT_TRUE(server_capabilities.contains("tools"));
} }
// 版本控制测试环境 // Version control test environment
class VersioningEnvironment : public ::testing::Environment { class VersioningEnvironment : public ::testing::Environment {
public: public:
void SetUp() override { void SetUp() override {
// 设置测试环境 // Set up test environment
server_ = std::make_unique<server>("localhost", 8081); server_ = std::make_unique<server>("localhost", 8081);
server_->set_server_info("TestServer", "1.0.0"); server_->set_server_info("TestServer", "1.0.0");
// 设置服务器能力 // Set server capabilities
json server_capabilities = { json server_capabilities = {
{"logging", json::object()}, {"logging", json::object()},
{"prompts", {{"listChanged", true}}}, {"prompts", {{"listChanged", true}}},
@ -210,14 +208,14 @@ public:
}; };
server_->set_capabilities(server_capabilities); server_->set_capabilities(server_capabilities);
// 启动服务器(非阻塞模式) // Start server (non-blocking mode)
server_->start(false); server_->start(false);
client_ = std::make_unique<sse_client>("localhost", 8081); client_ = std::make_unique<sse_client>("localhost", 8081);
} }
void TearDown() override { void TearDown() override {
// 清理测试环境 // Clean up test environment
client_.reset(); client_.reset();
server_->stop(); server_->stop();
server_.reset(); server_.reset();
@ -236,41 +234,40 @@ private:
static std::unique_ptr<sse_client> client_; static std::unique_ptr<sse_client> client_;
}; };
// 静态成员变量定义
std::unique_ptr<server> VersioningEnvironment::server_; std::unique_ptr<server> VersioningEnvironment::server_;
std::unique_ptr<sse_client> VersioningEnvironment::client_; std::unique_ptr<sse_client> VersioningEnvironment::client_;
// 测试版本控制 // Test version control
class VersioningTest : public ::testing::Test { class VersioningTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// 获取客户端指针 // Get client pointer
client_ = VersioningEnvironment::GetClient().get(); client_ = VersioningEnvironment::GetClient().get();
} }
// 使用原始指针而不是引用 // Use raw pointer instead of reference
sse_client* client_; sse_client* client_;
}; };
// 测试支持的版本 // Test supported version
TEST_F(VersioningTest, SupportedVersion) { TEST_F(VersioningTest, SupportedVersion) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 执行初始化 // Execute initialize
bool init_result = client_->initialize("TestClient", "1.0.0"); bool init_result = client_->initialize("TestClient", "1.0.0");
// 验证初始化结果 // Verify initialize result
EXPECT_TRUE(init_result); EXPECT_TRUE(init_result);
} }
// 测试不支持的版本 // Test unsupported version
TEST_F(VersioningTest, UnsupportedVersion) { TEST_F(VersioningTest, UnsupportedVersion) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
try { try {
// 使用 httplib::Client 发送不支持的版本请求 // Use httplib::Client to send unsupported version request
std::unique_ptr<httplib::Client> sse_client = std::make_unique<httplib::Client>("localhost", 8081); std::unique_ptr<httplib::Client> sse_client = std::make_unique<httplib::Client>("localhost", 8081);
std::unique_ptr<httplib::Client> http_client = std::make_unique<httplib::Client>("localhost", 8081); std::unique_ptr<httplib::Client> http_client = std::make_unique<httplib::Client>("localhost", 8081);
// 打开 SSE 连接 // Open SSE connection
std::promise<std::string> msg_endpoint_promise; std::promise<std::string> msg_endpoint_promise;
std::promise<std::string> sse_promise; std::promise<std::string> sse_promise;
std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future(); std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future();
@ -294,19 +291,19 @@ TEST_F(VersioningTest, UnsupportedVersion) {
try { try {
msg_endpoint_promise.set_value(data_content); msg_endpoint_promise.set_value(data_content);
} catch (...) { } catch (...) {
// 忽略重复设置的异常 // Ignore duplicate exception setting
} }
} else if (!sse_response_received.load() && response.find("message") != std::string::npos) { } else if (!sse_response_received.load() && response.find("message") != std::string::npos) {
sse_response_received.store(true); sse_response_received.store(true);
try { try {
sse_promise.set_value(data_content); sse_promise.set_value(data_content);
} catch (...) { } catch (...) {
// 忽略重复设置的异常 // Ignore duplicate exception setting
} }
} }
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
GTEST_LOG_(ERROR) << "SSE处理错误: " << e.what(); GTEST_LOG_(ERROR) << "SSE processing error: " << e.what();
} }
return sse_running.load(); return sse_running.load();
}); });
@ -315,7 +312,7 @@ TEST_F(VersioningTest, UnsupportedVersion) {
std::string endpoint = msg_endpoint.get(); std::string endpoint = msg_endpoint.get();
EXPECT_FALSE(endpoint.empty()); EXPECT_FALSE(endpoint.empty());
// 发送不支持的版本请求 // Send unsupported version request
json req = request::create("initialize", {{"protocolVersion", "0.0.1"}}).to_json(); json req = request::create("initialize", {{"protocolVersion", "0.0.1"}}).to_json();
auto res = http_client->Post(endpoint.c_str(), req.dump(), "application/json"); auto res = http_client->Post(endpoint.c_str(), req.dump(), "application/json");
@ -325,17 +322,17 @@ TEST_F(VersioningTest, UnsupportedVersion) {
auto mcp_res = json::parse(sse_response.get()); auto mcp_res = json::parse(sse_response.get());
EXPECT_EQ(mcp_res["error"]["code"].get<int>(), static_cast<int>(error_code::invalid_params)); EXPECT_EQ(mcp_res["error"]["code"].get<int>(), static_cast<int>(error_code::invalid_params));
// 主动关闭所有连接 // Close all connections
sse_running.store(false); sse_running.store(false);
// 尝试中断SSE连接 // Try to interrupt SSE connection
try { try {
sse_client->Get("/sse", [](const char*, size_t) { return false; }); sse_client->Get("/sse", [](const char*, size_t) { return false; });
} catch (...) { } catch (...) {
// 忽略任何异常 // Ignore any exception
} }
// 等待线程结束最多1秒 // Wait for thread to finish (max 1 second)
if (sse_thread.joinable()) { if (sse_thread.joinable()) {
std::thread detacher([](std::thread& t) { std::thread detacher([](std::thread& t) {
try { try {
@ -351,29 +348,29 @@ TEST_F(VersioningTest, UnsupportedVersion) {
detacher.detach(); detacher.detach();
} }
// 清理资源 // Clean up resources
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
sse_client.reset(); sse_client.reset();
http_client.reset(); http_client.reset();
// 添加延迟,确保资源完全释放 // Add delay to ensure resources are fully released
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} catch (...) { } catch (...) {
EXPECT_TRUE(false); EXPECT_TRUE(false);
} }
} }
// Ping测试环境 // Ping test environment
class PingEnvironment : public ::testing::Environment { class PingEnvironment : public ::testing::Environment {
public: public:
void SetUp() override { void SetUp() override {
// 设置测试环境 // Set up test environment
server_ = std::make_unique<server>("localhost", 8082); server_ = std::make_unique<server>("localhost", 8082);
// 启动服务器(非阻塞模式) // Start server (non-blocking mode)
server_->start(false); server_->start(false);
// 创建客户端 // Create client
json client_capabilities = { json client_capabilities = {
{"roots", {{"listChanged", true}}}, {"roots", {{"listChanged", true}}},
{"sampling", json::object()} {"sampling", json::object()}
@ -383,7 +380,7 @@ public:
} }
void TearDown() override { void TearDown() override {
// 清理测试环境 // Clean up test environment
client_.reset(); client_.reset();
server_->stop(); server_->stop();
server_.reset(); server_.reset();
@ -402,23 +399,23 @@ private:
static std::unique_ptr<sse_client> client_; static std::unique_ptr<sse_client> client_;
}; };
// 静态成员变量定义 // Static member variable definition
std::unique_ptr<server> PingEnvironment::server_; std::unique_ptr<server> PingEnvironment::server_;
std::unique_ptr<sse_client> PingEnvironment::client_; std::unique_ptr<sse_client> PingEnvironment::client_;
// 测试Ping功能 // Test Ping functionality
class PingTest : public ::testing::Test { class PingTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// 获取客户端指针 // Get client pointer
client_ = PingEnvironment::GetClient().get(); client_ = PingEnvironment::GetClient().get();
} }
// 使用原始指针而不是引用 // Use raw pointer instead of reference
sse_client* client_; sse_client* client_;
}; };
// 测试Ping请求 // Test Ping request
TEST_F(PingTest, PingRequest) { TEST_F(PingTest, PingRequest) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
client_->initialize("TestClient", "1.0.0"); client_->initialize("TestClient", "1.0.0");
@ -429,11 +426,11 @@ TEST_F(PingTest, PingRequest) {
TEST_F(PingTest, DirectPing) { TEST_F(PingTest, DirectPing) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
try { try {
// 使用 httplib::Client 发送不支持的版本请求 // Use httplib::Client to send Ping request
std::unique_ptr<httplib::Client> sse_client = std::make_unique<httplib::Client>("localhost", 8082); std::unique_ptr<httplib::Client> sse_client = std::make_unique<httplib::Client>("localhost", 8082);
std::unique_ptr<httplib::Client> http_client = std::make_unique<httplib::Client>("localhost", 8082); std::unique_ptr<httplib::Client> http_client = std::make_unique<httplib::Client>("localhost", 8082);
// 打开 SSE 连接 // Open SSE connection
std::promise<std::string> msg_endpoint_promise; std::promise<std::string> msg_endpoint_promise;
std::promise<std::string> sse_promise; std::promise<std::string> sse_promise;
std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future(); std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future();
@ -457,19 +454,19 @@ TEST_F(PingTest, DirectPing) {
try { try {
msg_endpoint_promise.set_value(data_content); msg_endpoint_promise.set_value(data_content);
} catch (...) { } catch (...) {
// 忽略重复设置的异常 // Ignore duplicate exception setting
} }
} else if (!sse_response_received.load() && response.find("message") != std::string::npos) { } else if (!sse_response_received.load() && response.find("message") != std::string::npos) {
sse_response_received.store(true); sse_response_received.store(true);
try { try {
sse_promise.set_value(data_content); sse_promise.set_value(data_content);
} catch (...) { } catch (...) {
// 忽略重复设置的异常 // Ignore duplicate exception setting
} }
} }
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
GTEST_LOG_(ERROR) << "SSE处理错误: " << e.what(); GTEST_LOG_(ERROR) << "SSE processing error: " << e.what();
} }
return sse_running.load(); return sse_running.load();
}); });
@ -478,7 +475,7 @@ TEST_F(PingTest, DirectPing) {
std::string endpoint = msg_endpoint.get(); std::string endpoint = msg_endpoint.get();
EXPECT_FALSE(endpoint.empty()); EXPECT_FALSE(endpoint.empty());
// 即使没有建立SSE连接也可以发送ping请求 // Even if the SSE connection is not established, you can send a ping request
json ping_req = request::create("ping").to_json(); json ping_req = request::create("ping").to_json();
auto ping_res = http_client->Post(endpoint.c_str(), ping_req.dump(), "application/json"); auto ping_res = http_client->Post(endpoint.c_str(), ping_req.dump(), "application/json");
EXPECT_TRUE(ping_res != nullptr); EXPECT_TRUE(ping_res != nullptr);
@ -487,17 +484,17 @@ TEST_F(PingTest, DirectPing) {
auto mcp_res = json::parse(sse_response.get()); auto mcp_res = json::parse(sse_response.get());
EXPECT_EQ(mcp_res["result"], json::object()); EXPECT_EQ(mcp_res["result"], json::object());
// 主动关闭所有连接 // Close all connections
sse_running.store(false); sse_running.store(false);
// 尝试中断SSE连接 // Try to interrupt SSE connection
try { try {
sse_client->Get("/sse", [](const char*, size_t) { return false; }); sse_client->Get("/sse", [](const char*, size_t) { return false; });
} catch (...) { } catch (...) {
// 忽略任何异常 // Ignore any exception
} }
// 等待线程结束最多1秒 // Wait for thread to finish (max 1 second)
if (sse_thread.joinable()) { if (sse_thread.joinable()) {
std::thread detacher([](std::thread& t) { std::thread detacher([](std::thread& t) {
try { try {
@ -513,26 +510,26 @@ TEST_F(PingTest, DirectPing) {
detacher.detach(); detacher.detach();
} }
// 清理资源 // Clean up resources
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
sse_client.reset(); sse_client.reset();
http_client.reset(); http_client.reset();
// 添加延迟,确保资源完全释放 // Add delay to ensure resources are fully released
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} catch (...) { } catch (...) {
EXPECT_TRUE(false); EXPECT_TRUE(false);
} }
} }
// 工具测试环境 // Tools test environment
class ToolsEnvironment : public ::testing::Environment { class ToolsEnvironment : public ::testing::Environment {
public: public:
void SetUp() override { void SetUp() override {
// 设置测试环境 // Set up test environment
server_ = std::make_unique<server>("localhost", 8083); server_ = std::make_unique<server>("localhost", 8083);
// 创建一个测试工具 // Create a test tool
tool test_tool; tool test_tool;
test_tool.name = "get_weather"; test_tool.name = "get_weather";
test_tool.description = "Get current weather information for a location"; test_tool.description = "Get current weather information for a location";
@ -547,9 +544,9 @@ public:
{"required", json::array({"location"})} {"required", json::array({"location"})}
}; };
// 注册工具 // Register tool
server_->register_tool(test_tool, [](const json& params) -> json { server_->register_tool(test_tool, [](const json& params) -> json {
// 简单的工具实现 // Simple tool implementation
std::string location = params["location"]; std::string location = params["location"];
return { return {
{"content", json::array({ {"content", json::array({
@ -562,7 +559,7 @@ public:
}; };
}); });
// 注册工具列表方法 // Register tools list method
server_->register_method("tools/list", [](const json& params) -> json { server_->register_method("tools/list", [](const json& params) -> json {
return { return {
{"tools", json::array({ {"tools", json::array({
@ -585,13 +582,13 @@ public:
}; };
}); });
// 注册工具调用方法 // Register tools call method
server_->register_method("tools/call", [](const json& params) -> json { server_->register_method("tools/call", [](const json& params) -> json {
// 验证参数 // Verify parameters
EXPECT_EQ(params["name"], "get_weather"); EXPECT_EQ(params["name"], "get_weather");
EXPECT_EQ(params["arguments"]["location"], "New York"); EXPECT_EQ(params["arguments"]["location"], "New York");
// 返回工具调用结果 // Return tool call result
return { return {
{"content", json::array({ {"content", json::array({
{ {
@ -603,10 +600,10 @@ public:
}; };
}); });
// 启动服务器(非阻塞模式) // Start server (non-blocking mode)
server_->start(false); server_->start(false);
// 创建客户端 // Create client
json client_capabilities = { json client_capabilities = {
{"roots", {{"listChanged", true}}}, {"roots", {{"listChanged", true}}},
{"sampling", json::object()} {"sampling", json::object()}
@ -617,7 +614,7 @@ public:
} }
void TearDown() override { void TearDown() override {
// 清理测试环境 // Clean up test environment
client_.reset(); client_.reset();
server_->stop(); server_->stop();
server_.reset(); server_.reset();
@ -636,43 +633,43 @@ private:
static std::unique_ptr<sse_client> client_; static std::unique_ptr<sse_client> client_;
}; };
// 静态成员变量定义 // Static member variable definition
std::unique_ptr<server> ToolsEnvironment::server_; std::unique_ptr<server> ToolsEnvironment::server_;
std::unique_ptr<sse_client> ToolsEnvironment::client_; std::unique_ptr<sse_client> ToolsEnvironment::client_;
// 测试工具功能 // Test tools functionality
class ToolsTest : public ::testing::Test { class ToolsTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// 获取客户端指针 // Get client pointer
client_ = ToolsEnvironment::GetClient().get(); client_ = ToolsEnvironment::GetClient().get();
} }
// 使用原始指针而不是引用 // Use raw pointer instead of reference
sse_client* client_; sse_client* client_;
}; };
// 测试列出工具 // Test listing tools
TEST_F(ToolsTest, ListTools) { TEST_F(ToolsTest, ListTools) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 调用列出工具方法 // Call list tools method
json tools_list = client_->send_request("tools/list").result; json tools_list = client_->send_request("tools/list").result;
// 验证工具列表 // Verify tools list
EXPECT_TRUE(tools_list.contains("tools")); EXPECT_TRUE(tools_list.contains("tools"));
EXPECT_EQ(tools_list["tools"].size(), 1); EXPECT_EQ(tools_list["tools"].size(), 1);
EXPECT_EQ(tools_list["tools"][0]["name"], "get_weather"); EXPECT_EQ(tools_list["tools"][0]["name"], "get_weather");
EXPECT_EQ(tools_list["tools"][0]["description"], "Get current weather information for a location"); EXPECT_EQ(tools_list["tools"][0]["description"], "Get current weather information for a location");
} }
// 测试调用工具 // Test calling tool
TEST_F(ToolsTest, CallTool) { TEST_F(ToolsTest, CallTool) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 调用工具 // Call tool
json tool_result = client_->call_tool("get_weather", {{"location", "New York"}}); json tool_result = client_->call_tool("get_weather", {{"location", "New York"}});
// 验证工具调用结果 // Verify tool call result
EXPECT_TRUE(tool_result.contains("content")); EXPECT_TRUE(tool_result.contains("content"));
EXPECT_FALSE(tool_result["isError"]); EXPECT_FALSE(tool_result["isError"]);
EXPECT_EQ(tool_result["content"][0]["type"], "text"); EXPECT_EQ(tool_result["content"][0]["type"], "text");
@ -682,7 +679,7 @@ TEST_F(ToolsTest, CallTool) {
int main(int argc, char **argv) { int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
// 添加全局测试环境 // Add global test environment
::testing::AddGlobalTestEnvironment(new LifecycleEnvironment()); ::testing::AddGlobalTestEnvironment(new LifecycleEnvironment());
::testing::AddGlobalTestEnvironment(new VersioningEnvironment()); ::testing::AddGlobalTestEnvironment(new VersioningEnvironment());
::testing::AddGlobalTestEnvironment(new PingEnvironment()); ::testing::AddGlobalTestEnvironment(new PingEnvironment());

View File

@ -1,12 +1,12 @@
# Model Context Protocol 测试用例 # Model Context Protocol Testcases
本文档整理了从 Model Context Protocol 规范中收集的测试用例,主要来源于 https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/ 和 https://spec.modelcontextprotocol.io/specification/2024-11-05/server/ 。 Testcases collected from https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/ and https://spec.modelcontextprotocol.io/specification/2024-11-05/server/.
## 基本协议测试用例 ## Basic Protocol Testcases
### 消息格式测试用例 ### Message Format
#### 请求消息 #### Request Message
```json ```json
{ {
@ -19,7 +19,7 @@
} }
``` ```
#### 响应消息 #### Response Message
```json ```json
{ {
@ -31,7 +31,7 @@
} }
``` ```
#### 通知消息 #### Notification Message
```json ```json
{ {
@ -43,9 +43,9 @@
} }
``` ```
### 生命周期测试用例 ### Lifecycle Testcases
#### 初始化请求 #### Initialization Request
```json ```json
{ {
@ -68,7 +68,7 @@
} }
``` ```
#### 初始化响应 #### Initialization Response
```json ```json
{ {
@ -97,7 +97,7 @@
} }
``` ```
#### 初始化完成通知 #### Initialization Notification
```json ```json
{ {
@ -106,7 +106,7 @@
} }
``` ```
#### 初始化错误 #### Initialization Error (Unsupported Version)
```json ```json
{ {
@ -123,9 +123,9 @@
} }
``` ```
### 工具功能测试用例 ### Tool Testcases
#### Ping 请求 #### Ping Request
```json ```json
{ {
@ -135,7 +135,7 @@
} }
``` ```
#### Ping 响应 #### Ping Response
```json ```json
{ {
@ -145,7 +145,7 @@
} }
``` ```
#### Cancellation 通知 #### Cancellation Notification
```json ```json
{ {
@ -158,7 +158,7 @@
} }
``` ```
#### Progress 请求 #### Progress Request
```json ```json
{ {
@ -173,7 +173,7 @@
} }
``` ```
#### Progress 通知 #### Progress Notification
```json ```json
{ {
@ -187,11 +187,11 @@
} }
``` ```
## 服务器功能测试用例 ## Server Testcases
### 工具功能测试用例 ### Tool Testcases
#### 列出工具请求 #### Tool List Request
```json ```json
{ {
@ -204,7 +204,7 @@
} }
``` ```
#### 列出工具响应 #### Tool List Response
```json ```json
{ {
@ -232,7 +232,7 @@
} }
``` ```
#### 调用工具请求 #### Tool Call Request
```json ```json
{ {
@ -248,7 +248,7 @@
} }
``` ```
#### 调用工具响应 #### Tool Call Response
```json ```json
{ {
@ -266,7 +266,7 @@
} }
``` ```
#### 工具列表变更通知 #### Tool List Changed Notification
```json ```json
{ {
@ -275,9 +275,9 @@
} }
``` ```
### 资源功能测试用例 ### Resource Testcases
#### 列出资源请求 #### Resource List Request
```json ```json
{ {
@ -290,7 +290,7 @@
} }
``` ```
#### 列出资源响应 #### Resource List Response
```json ```json
{ {
@ -310,7 +310,7 @@
} }
``` ```
#### 读取资源请求 #### Resource Read Request
```json ```json
{ {
@ -323,7 +323,7 @@
} }
``` ```
#### 读取资源响应 #### Resource Read Response
```json ```json
{ {
@ -341,7 +341,7 @@
} }
``` ```
#### 资源模板列表请求 #### Resource Template List Request
```json ```json
{ {
@ -351,7 +351,7 @@
} }
``` ```
#### 资源模板列表响应 #### Resource Template List Response
```json ```json
{ {
@ -370,7 +370,7 @@
} }
``` ```
#### 资源列表变更通知 #### Resource List Changes Notification
```json ```json
{ {
@ -379,7 +379,7 @@
} }
``` ```
#### 订阅资源请求 #### Resource Description Request
```json ```json
{ {
@ -392,7 +392,7 @@
} }
``` ```
#### 资源更新通知 #### Resource Updated Notification
```json ```json
{ {
@ -404,7 +404,7 @@
} }
``` ```
#### 资源错误响应 #### Resource Error (Not Found)
```json ```json
{ {
@ -420,9 +420,9 @@
} }
``` ```
### 工具结果数据类型 ### Tool Result
#### 文本内容 #### Text Content
```json ```json
{ {
@ -431,7 +431,7 @@
} }
``` ```
#### 图像内容 #### Image Content
```json ```json
{ {
@ -441,7 +441,7 @@
} }
``` ```
#### 嵌入资源 #### Resource Content
```json ```json
{ {