ready to publish?
parent
0c3ee605b2
commit
169e9cb3d1
|
@ -53,6 +53,4 @@ option(MCP_BUILD_TESTS "Build the tests" OFF)
|
|||
if(MCP_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
|
||||
# 注意:run_tests目标已在test/CMakeLists.txt中定义,此处不再重复定义
|
||||
endif()
|
||||
|
|
34
README.md
34
README.md
|
@ -10,6 +10,22 @@
|
|||
- **Extensible Architecture**: Easy to extend with new resource types and tools
|
||||
- **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
|
||||
|
||||
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
|
||||
- Calculator tool: Perform mathematical operations
|
||||
- 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`)
|
||||
|
||||
|
@ -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
|
||||
|
||||
This framework is provided under the MIT license. For details, please see the LICENSE file.
|
7988
common/stb_image.h
7988
common/stb_image.h
File diff suppressed because it is too large
Load Diff
|
@ -15,10 +15,10 @@
|
|||
#include <chrono>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// 设置日志级别
|
||||
// Set log level to info
|
||||
mcp::set_log_level(mcp::log_level::info);
|
||||
|
||||
// 检查命令行参数
|
||||
// Check command line arguments
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <server_command>" << 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];
|
||||
|
||||
// 设置环境变量
|
||||
// Set example environment variables
|
||||
mcp::json env_vars = {
|
||||
{"MCP_DEBUG", "1"},
|
||||
{"MCP_LOG_LEVEL", "debug"},
|
||||
{"CUSTOM_VAR", "custom_value"}
|
||||
};
|
||||
|
||||
// 创建客户端,直接在构造函数中传入环境变量
|
||||
// Create client
|
||||
mcp::stdio_client client(command, env_vars);
|
||||
|
||||
// 初始化客户端
|
||||
// Initialize client
|
||||
if (!client.initialize("MCP Stdio Client Example", "1.0.0")) {
|
||||
std::cerr << "Failed to initialize client" << std::endl;
|
||||
return 1;
|
||||
|
@ -46,22 +46,23 @@ int main(int argc, char** argv) {
|
|||
std::cout << "Client initialized successfully" << std::endl;
|
||||
|
||||
try {
|
||||
// 获取服务器能力
|
||||
// Get server capabilities
|
||||
auto capabilities = client.get_server_capabilities();
|
||||
std::cout << "Server capabilities: " << capabilities.dump(2) << std::endl;
|
||||
|
||||
// 列出可用工具
|
||||
// List available tools
|
||||
auto tools = client.get_tools();
|
||||
std::cout << "Available tools: " << tools.size() << std::endl;
|
||||
for (const auto& tool : tools) {
|
||||
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();
|
||||
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()) {
|
||||
auto resource = resources["resources"][0];
|
||||
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::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
|
||||
// 发送ping请求
|
||||
// Send ping request
|
||||
bool ping_result = client.ping();
|
||||
std::cout << "Ping result: " << (ping_result ? "success" : "failure") << std::endl;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @file mcp_logger.h
|
||||
* @brief 简单的日志记录机制
|
||||
* @brief Simple logger
|
||||
*/
|
||||
|
||||
#ifndef MCP_LOGGER_H
|
||||
|
@ -76,33 +76,33 @@ private:
|
|||
|
||||
std::stringstream ss;
|
||||
|
||||
// 添加时间戳
|
||||
// Add timestamp
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto now_c = std::chrono::system_clock::to_time_t(now);
|
||||
auto now_tm = std::localtime(&now_c);
|
||||
|
||||
ss << std::put_time(now_tm, "%Y-%m-%d %H:%M:%S") << " ";
|
||||
|
||||
// 添加日志级别和颜色
|
||||
// Add log level and color
|
||||
switch (level) {
|
||||
case log_level::debug:
|
||||
ss << "\033[36m[DEBUG]\033[0m "; // 青色
|
||||
ss << "\033[36m[DEBUG]\033[0m "; // Cyan
|
||||
break;
|
||||
case log_level::info:
|
||||
ss << "\033[32m[INFO]\033[0m "; // 绿色
|
||||
ss << "\033[32m[INFO]\033[0m "; // Green
|
||||
break;
|
||||
case log_level::warning:
|
||||
ss << "\033[33m[WARNING]\033[0m "; // 黄色
|
||||
ss << "\033[33m[WARNING]\033[0m "; // Yellow
|
||||
break;
|
||||
case log_level::error:
|
||||
ss << "\033[31m[ERROR]\033[0m "; // 红色
|
||||
ss << "\033[31m[ERROR]\033[0m "; // Red
|
||||
break;
|
||||
}
|
||||
|
||||
// 添加日志内容
|
||||
// Add log content
|
||||
log_impl(ss, std::forward<Args>(args)...);
|
||||
|
||||
// 输出日志
|
||||
// Output log
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::cerr << ss.str() << std::endl;
|
||||
}
|
||||
|
|
|
@ -35,9 +35,8 @@ namespace mcp {
|
|||
|
||||
class event_dispatcher {
|
||||
public:
|
||||
// 使用较小的初始消息缓冲区
|
||||
event_dispatcher() {
|
||||
message_.reserve(128); // 预分配较小的缓冲区空间
|
||||
message_.reserve(128); // Pre-allocate space for messages
|
||||
}
|
||||
|
||||
~event_dispatcher() {
|
||||
|
@ -71,11 +70,11 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
// 仅当有新消息时才复制
|
||||
// Only copy the message if there is one
|
||||
if (!message_.empty()) {
|
||||
message_copy.swap(message_);
|
||||
} else {
|
||||
return true; // 没有消息但是条件已满足
|
||||
return true; // No message but condition satisfied
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,14 +104,14 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
// 高效设置消息并分配适当空间
|
||||
// Efficiently set the message and allocate space as needed
|
||||
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;
|
||||
|
||||
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;
|
||||
} catch (...) {
|
||||
return false;
|
||||
|
@ -128,7 +127,7 @@ public:
|
|||
try {
|
||||
cv_.notify_all();
|
||||
} catch (...) {
|
||||
// 忽略异常
|
||||
// Ignore exceptions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,13 +135,13 @@ public:
|
|||
return closed_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// 获取最后活动时间
|
||||
// Get the last activity time
|
||||
std::chrono::steady_clock::time_point last_activity() const {
|
||||
std::lock_guard<std::mutex> lk(m_);
|
||||
return last_activity_;
|
||||
}
|
||||
|
||||
// 更新活动时间(发送或接收消息时)
|
||||
// Update the activity time (when sending or receiving a message)
|
||||
void update_activity() {
|
||||
std::lock_guard<std::mutex> lk(m_);
|
||||
last_activity_ = std::chrono::steady_clock::now();
|
||||
|
@ -317,7 +316,7 @@ private:
|
|||
// Running flag
|
||||
bool running_ = false;
|
||||
|
||||
// 线程池
|
||||
// Thread pool for async method handlers
|
||||
thread_pool thread_pool_;
|
||||
|
||||
// Map to track session initialization status (session_id -> initialized)
|
||||
|
@ -344,7 +343,7 @@ private:
|
|||
// Generate a random session ID
|
||||
std::string generate_session_id() const;
|
||||
|
||||
// 辅助函数:创建异步方法处理器
|
||||
// Auxiliary function to create an async handler from a regular handler
|
||||
template<typename F>
|
||||
std::function<std::future<json>(const json&)> make_async_handler(F&& handler) {
|
||||
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 {
|
||||
public:
|
||||
explicit auto_lock(std::mutex& mutex) : lock_(mutex) {}
|
||||
|
@ -362,12 +361,12 @@ private:
|
|||
std::lock_guard<std::mutex> lock_;
|
||||
};
|
||||
|
||||
// 获取自动锁
|
||||
// Get auto lock
|
||||
auto_lock get_lock() const {
|
||||
return auto_lock(mutex_);
|
||||
}
|
||||
|
||||
// 会话管理与维护
|
||||
// Session management and maintenance
|
||||
void check_inactive_sessions();
|
||||
std::unique_ptr<std::thread> maintenance_thread_;
|
||||
};
|
||||
|
|
|
@ -170,7 +170,7 @@ public:
|
|||
json list_resource_templates() override;
|
||||
|
||||
/**
|
||||
* @brief 检查服务器是否可访问
|
||||
* @brief Check if the server is accessible
|
||||
* @return True if the server is accessible
|
||||
*/
|
||||
bool check_server_accessible();
|
||||
|
@ -182,75 +182,75 @@ public:
|
|||
bool is_running() const override;
|
||||
|
||||
private:
|
||||
// 初始化HTTP客户端
|
||||
// Initialize HTTP client
|
||||
void init_client(const std::string& host, int port);
|
||||
void init_client(const std::string& base_url);
|
||||
|
||||
// 打开SSE连接
|
||||
// Open SSE connection
|
||||
void open_sse_connection();
|
||||
|
||||
// 解析SSE数据
|
||||
// Parse SSE data
|
||||
bool parse_sse_data(const char* data, size_t length);
|
||||
|
||||
// 关闭SSE连接
|
||||
// Close SSE connection
|
||||
void close_sse_connection();
|
||||
|
||||
// 发送JSON-RPC请求
|
||||
// Send JSON-RPC request
|
||||
json send_jsonrpc(const request& req);
|
||||
|
||||
// 服务器主机和端口
|
||||
// Server host and port
|
||||
std::string host_;
|
||||
int port_ = 8080;
|
||||
|
||||
// 或者使用基础URL
|
||||
// Use base URL
|
||||
std::string base_url_;
|
||||
|
||||
// SSE端点
|
||||
// SSE endpoint
|
||||
std::string sse_endpoint_ = "/sse";
|
||||
|
||||
// 消息端点
|
||||
// Message endpoint
|
||||
std::string msg_endpoint_;
|
||||
|
||||
// HTTP客户端
|
||||
// HTTP client
|
||||
std::unique_ptr<httplib::Client> http_client_;
|
||||
|
||||
// SSE专用HTTP客户端
|
||||
// SSE HTTP client
|
||||
std::unique_ptr<httplib::Client> sse_client_;
|
||||
|
||||
// SSE线程
|
||||
// SSE thread
|
||||
std::unique_ptr<std::thread> sse_thread_;
|
||||
|
||||
// SSE运行状态
|
||||
// SSE running status
|
||||
std::atomic<bool> sse_running_{false};
|
||||
|
||||
// 认证令牌
|
||||
// Authentication token
|
||||
std::string auth_token_;
|
||||
|
||||
// 默认请求头
|
||||
// Default request headers
|
||||
std::map<std::string, std::string> default_headers_;
|
||||
|
||||
// 超时设置(秒)
|
||||
// Timeout (seconds)
|
||||
int timeout_seconds_ = 30;
|
||||
|
||||
// 客户端能力
|
||||
// Client capabilities
|
||||
json capabilities_;
|
||||
|
||||
// 服务器能力
|
||||
// Server capabilities
|
||||
json server_capabilities_;
|
||||
|
||||
// 互斥锁
|
||||
// Mutex
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
// 条件变量,用于等待消息端点设置
|
||||
// Condition variable, used to wait for message endpoint setting
|
||||
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_;
|
||||
|
||||
// 响应处理互斥锁
|
||||
// Response processing mutex
|
||||
std::mutex response_mutex_;
|
||||
|
||||
// 响应条件变量
|
||||
// Response condition variable
|
||||
std::condition_variable response_cv_;
|
||||
};
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@
|
|||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
// 添加Windows平台特定的头文件
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
@ -164,67 +163,67 @@ public:
|
|||
bool is_running() const override;
|
||||
|
||||
private:
|
||||
// 启动服务器进程
|
||||
// Start server process
|
||||
bool start_server_process();
|
||||
|
||||
// 停止服务器进程
|
||||
// Stop server process
|
||||
void stop_server_process();
|
||||
|
||||
// 读取线程函数
|
||||
// Read thread function
|
||||
void read_thread_func();
|
||||
|
||||
// 发送JSON-RPC请求
|
||||
// Send JSON-RPC request
|
||||
json send_jsonrpc(const request& req);
|
||||
|
||||
// 服务器命令
|
||||
// Server command
|
||||
std::string command_;
|
||||
|
||||
// 进程ID
|
||||
// Process ID
|
||||
int process_id_ = -1;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// Windows平台特定的进程句柄
|
||||
#if defined(_WIN32)
|
||||
// Windows platform specific process handle
|
||||
HANDLE process_handle_ = NULL;
|
||||
|
||||
// 标准输入输出管道 (Windows)
|
||||
// Standard input/output pipes (Windows)
|
||||
HANDLE stdin_pipe_[2] = {NULL, NULL};
|
||||
HANDLE stdout_pipe_[2] = {NULL, NULL};
|
||||
#else
|
||||
// 标准输入管道 (POSIX)
|
||||
// Standard input pipe (POSIX)
|
||||
int stdin_pipe_[2] = {-1, -1};
|
||||
|
||||
// 标准输出管道 (POSIX)
|
||||
// Standard output pipe (POSIX)
|
||||
int stdout_pipe_[2] = {-1, -1};
|
||||
#endif
|
||||
|
||||
// 读取线程
|
||||
// Read thread
|
||||
std::unique_ptr<std::thread> read_thread_;
|
||||
|
||||
// 运行状态
|
||||
// Running status
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
// 客户端能力
|
||||
// Client capabilities
|
||||
json capabilities_;
|
||||
|
||||
// 服务器能力
|
||||
// Server capabilities
|
||||
json server_capabilities_;
|
||||
|
||||
// 互斥锁
|
||||
// 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_;
|
||||
|
||||
// 响应处理互斥锁
|
||||
// Response processing mutex
|
||||
std::mutex response_mutex_;
|
||||
|
||||
// 初始化状态
|
||||
// Initialization status
|
||||
std::atomic<bool> initialized_{false};
|
||||
|
||||
// 初始化条件变量
|
||||
// Initialization condition variable
|
||||
std::condition_variable init_cv_;
|
||||
|
||||
// 环境变量
|
||||
// Environment variables
|
||||
json env_vars_;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @file mcp_thread_pool.h
|
||||
* @brief 简单的线程池实现
|
||||
* @brief Simple thread pool implementation
|
||||
*/
|
||||
|
||||
#ifndef MCP_THREAD_POOL_H
|
||||
|
@ -21,8 +21,8 @@ namespace mcp {
|
|||
class thread_pool {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param num_threads 线程池中的线程数量
|
||||
* @brief Constructor
|
||||
* @param num_threads Number of threads in the thread pool
|
||||
*/
|
||||
explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency()) : stop_(false) {
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
|
@ -51,7 +51,7 @@ public:
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
* @brief Destructor
|
||||
*/
|
||||
~thread_pool() {
|
||||
{
|
||||
|
@ -69,10 +69,10 @@ public:
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief 提交任务到线程池
|
||||
* @param f 任务函数
|
||||
* @param args 任务参数
|
||||
* @return 任务的future
|
||||
* @brief Submit task to thread pool
|
||||
* @param f Task function
|
||||
* @param args Task parameters
|
||||
* @return Task future
|
||||
*/
|
||||
template<class F, class... Args>
|
||||
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_);
|
||||
|
||||
if (stop_) {
|
||||
throw std::runtime_error("线程池已停止,无法添加任务");
|
||||
throw std::runtime_error("Thread pool stopped, cannot add task");
|
||||
}
|
||||
|
||||
tasks_.emplace([task]() { (*task)(); });
|
||||
|
@ -99,17 +99,17 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// 工作线程
|
||||
// Worker threads
|
||||
std::vector<std::thread> workers_;
|
||||
|
||||
// 任务队列
|
||||
// Task queue
|
||||
std::queue<std::function<void()>> tasks_;
|
||||
|
||||
// 互斥锁和条件变量
|
||||
// Mutex and condition variable
|
||||
std::mutex queue_mutex_;
|
||||
std::condition_variable condition_;
|
||||
|
||||
// 停止标志
|
||||
// Stop flag
|
||||
std::atomic<bool> stop_;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,76 +33,11 @@ struct tool {
|
|||
return {
|
||||
{"name", name},
|
||||
{"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
|
||||
* @brief Utility class for building tools with a fluent API
|
||||
|
|
|
@ -12,51 +12,6 @@
|
|||
|
||||
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
|
||||
tool_builder::tool_builder(const std::string& name)
|
||||
: name_(name) {
|
||||
|
|
|
@ -35,7 +35,6 @@ add_executable(${TEST_PROJECT_NAME} ${TEST_SOURCES})
|
|||
# Link directories
|
||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../build/src)
|
||||
|
||||
# 根据平台设置正确的库文件名
|
||||
if(WIN32)
|
||||
set(MCP_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../build/src/mcp.lib")
|
||||
else()
|
||||
|
@ -57,7 +56,6 @@ if(OPENSSL_FOUND)
|
|||
target_link_libraries(${TEST_PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# 添加自定义链接选项,避免重复链接
|
||||
if(APPLE)
|
||||
set_target_properties(${TEST_PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-no_warn_duplicate_libraries")
|
||||
endif()
|
||||
|
|
|
@ -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
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mcp_test.cpp
|
||||
* @brief 测试MCP框架的基本功能
|
||||
* @brief Test the basic functions of the MCP framework
|
||||
*
|
||||
* 本文件包含对MCP框架的消息格式、生命周期、版本控制、ping和工具功能的测试。
|
||||
* This file contains tests for the message format, lifecycle, version control, ping, and tool functionality of the MCP framework.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
@ -16,57 +16,57 @@
|
|||
using namespace mcp;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
// 测试消息格式
|
||||
// Test message format
|
||||
class MessageFormatTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// 清理测试环境
|
||||
// Clean up test environment
|
||||
}
|
||||
};
|
||||
|
||||
// 测试请求消息格式
|
||||
// Test request message format
|
||||
TEST_F(MessageFormatTest, RequestMessageFormat) {
|
||||
// 创建一个请求消息
|
||||
// Create a request message
|
||||
request req = request::create("test_method", {{"key", "value"}});
|
||||
|
||||
// 转换为JSON
|
||||
// Convert to JSON
|
||||
json req_json = req.to_json();
|
||||
|
||||
// 验证JSON格式是否符合规范
|
||||
// Verify JSON format is correct
|
||||
EXPECT_EQ(req_json["jsonrpc"], "2.0");
|
||||
EXPECT_TRUE(req_json.contains("id"));
|
||||
EXPECT_EQ(req_json["method"], "test_method");
|
||||
EXPECT_EQ(req_json["params"]["key"], "value");
|
||||
}
|
||||
|
||||
// 测试响应消息格式
|
||||
// Test response message format
|
||||
TEST_F(MessageFormatTest, ResponseMessageFormat) {
|
||||
// 创建一个成功响应
|
||||
// Create a successful response
|
||||
response res = response::create_success("test_id", {{"key", "value"}});
|
||||
|
||||
// 转换为JSON
|
||||
// Convert 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["id"], "test_id");
|
||||
EXPECT_EQ(res_json["result"]["key"], "value");
|
||||
EXPECT_FALSE(res_json.contains("error"));
|
||||
}
|
||||
|
||||
// 测试错误响应消息格式
|
||||
// Test error response message format
|
||||
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"}});
|
||||
|
||||
// 转换为JSON
|
||||
// Convert 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["id"], "test_id");
|
||||
EXPECT_FALSE(res_json.contains("result"));
|
||||
|
@ -75,33 +75,32 @@ TEST_F(MessageFormatTest, ErrorResponseMessageFormat) {
|
|||
EXPECT_EQ(res_json["error"]["data"]["details"], "Missing required field");
|
||||
}
|
||||
|
||||
// 测试通知消息格式
|
||||
// Test notification message format
|
||||
TEST_F(MessageFormatTest, NotificationMessageFormat) {
|
||||
// 创建一个通知消息
|
||||
// Create a notification message
|
||||
request notification = request::create_notification("test_notification", {{"key", "value"}});
|
||||
|
||||
// 转换为JSON
|
||||
// Convert to JSON
|
||||
json notification_json = notification.to_json();
|
||||
|
||||
// 验证JSON格式是否符合规范
|
||||
// Verify JSON format is correct
|
||||
EXPECT_EQ(notification_json["jsonrpc"], "2.0");
|
||||
EXPECT_FALSE(notification_json.contains("id"));
|
||||
EXPECT_EQ(notification_json["method"], "notifications/test_notification");
|
||||
EXPECT_EQ(notification_json["params"]["key"], "value");
|
||||
|
||||
// 验证是否为通知消息
|
||||
// Verify if it is a notification message
|
||||
EXPECT_TRUE(notification.is_notification());
|
||||
}
|
||||
|
||||
// 生命周期测试环境
|
||||
class LifecycleEnvironment : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
server_ = std::make_unique<server>("localhost", 8080);
|
||||
server_->set_server_info("TestServer", "1.0.0");
|
||||
|
||||
// 设置服务器能力
|
||||
// Set server capabilities
|
||||
json server_capabilities = {
|
||||
{"logging", json::object()},
|
||||
{"prompts", {{"listChanged", true}}},
|
||||
|
@ -110,14 +109,14 @@ public:
|
|||
};
|
||||
server_->set_capabilities(server_capabilities);
|
||||
|
||||
// 注册初始化方法处理器
|
||||
// Register initialize method handler
|
||||
server_->register_method("initialize", [server_capabilities](const json& params) -> json {
|
||||
// 验证初始化请求参数
|
||||
// Verify initialize request parameters
|
||||
EXPECT_EQ(params["protocolVersion"], MCP_VERSION);
|
||||
EXPECT_TRUE(params.contains("capabilities"));
|
||||
EXPECT_TRUE(params.contains("clientInfo"));
|
||||
|
||||
// 返回初始化响应
|
||||
// Return initialize response
|
||||
return {
|
||||
{"protocolVersion", MCP_VERSION},
|
||||
{"capabilities", server_capabilities},
|
||||
|
@ -128,10 +127,10 @@ public:
|
|||
};
|
||||
});
|
||||
|
||||
// 启动服务器(非阻塞模式)
|
||||
// Start server (non-blocking mode)
|
||||
server_->start(false);
|
||||
|
||||
// 创建客户端
|
||||
// Create client
|
||||
json client_capabilities = {
|
||||
{"roots", {{"listChanged", true}}},
|
||||
{"sampling", json::object()}
|
||||
|
@ -141,7 +140,7 @@ public:
|
|||
}
|
||||
|
||||
void TearDown() override {
|
||||
// 清理测试环境
|
||||
// Clean up test environment
|
||||
client_.reset();
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
|
@ -160,32 +159,31 @@ private:
|
|||
static std::unique_ptr<sse_client> client_;
|
||||
};
|
||||
|
||||
// 静态成员变量定义
|
||||
// Static member variable definition
|
||||
std::unique_ptr<server> LifecycleEnvironment::server_;
|
||||
std::unique_ptr<sse_client> LifecycleEnvironment::client_;
|
||||
|
||||
// 测试生命周期
|
||||
class LifecycleTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 获取客户端指针
|
||||
// Get client pointer
|
||||
client_ = LifecycleEnvironment::GetClient().get();
|
||||
}
|
||||
|
||||
// 使用原始指针而不是引用
|
||||
// Use raw pointer instead of reference
|
||||
sse_client* client_;
|
||||
};
|
||||
|
||||
// 测试初始化流程
|
||||
// Test initialize process
|
||||
TEST_F(LifecycleTest, InitializeProcess) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
// 执行初始化
|
||||
// Execute initialize
|
||||
bool init_result = client_->initialize("TestClient", "1.0.0");
|
||||
|
||||
// 验证初始化结果
|
||||
// Verify initialize result
|
||||
EXPECT_TRUE(init_result);
|
||||
|
||||
// 验证服务器能力
|
||||
// Verify server capabilities
|
||||
json server_capabilities = client_->get_server_capabilities();
|
||||
EXPECT_TRUE(server_capabilities.contains("logging"));
|
||||
EXPECT_TRUE(server_capabilities.contains("prompts"));
|
||||
|
@ -193,15 +191,15 @@ TEST_F(LifecycleTest, InitializeProcess) {
|
|||
EXPECT_TRUE(server_capabilities.contains("tools"));
|
||||
}
|
||||
|
||||
// 版本控制测试环境
|
||||
// Version control test environment
|
||||
class VersioningEnvironment : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
server_ = std::make_unique<server>("localhost", 8081);
|
||||
server_->set_server_info("TestServer", "1.0.0");
|
||||
|
||||
// 设置服务器能力
|
||||
// Set server capabilities
|
||||
json server_capabilities = {
|
||||
{"logging", json::object()},
|
||||
{"prompts", {{"listChanged", true}}},
|
||||
|
@ -210,14 +208,14 @@ public:
|
|||
};
|
||||
server_->set_capabilities(server_capabilities);
|
||||
|
||||
// 启动服务器(非阻塞模式)
|
||||
// Start server (non-blocking mode)
|
||||
server_->start(false);
|
||||
|
||||
client_ = std::make_unique<sse_client>("localhost", 8081);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// 清理测试环境
|
||||
// Clean up test environment
|
||||
client_.reset();
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
|
@ -236,41 +234,40 @@ private:
|
|||
static std::unique_ptr<sse_client> client_;
|
||||
};
|
||||
|
||||
// 静态成员变量定义
|
||||
std::unique_ptr<server> VersioningEnvironment::server_;
|
||||
std::unique_ptr<sse_client> VersioningEnvironment::client_;
|
||||
|
||||
// 测试版本控制
|
||||
// Test version control
|
||||
class VersioningTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 获取客户端指针
|
||||
// Get client pointer
|
||||
client_ = VersioningEnvironment::GetClient().get();
|
||||
}
|
||||
|
||||
// 使用原始指针而不是引用
|
||||
// Use raw pointer instead of reference
|
||||
sse_client* client_;
|
||||
};
|
||||
|
||||
// 测试支持的版本
|
||||
// Test supported version
|
||||
TEST_F(VersioningTest, SupportedVersion) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
// 执行初始化
|
||||
// Execute initialize
|
||||
bool init_result = client_->initialize("TestClient", "1.0.0");
|
||||
|
||||
// 验证初始化结果
|
||||
// Verify initialize result
|
||||
EXPECT_TRUE(init_result);
|
||||
}
|
||||
|
||||
// 测试不支持的版本
|
||||
// Test unsupported version
|
||||
TEST_F(VersioningTest, UnsupportedVersion) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
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> http_client = std::make_unique<httplib::Client>("localhost", 8081);
|
||||
|
||||
// 打开 SSE 连接
|
||||
// Open SSE connection
|
||||
std::promise<std::string> msg_endpoint_promise;
|
||||
std::promise<std::string> sse_promise;
|
||||
std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future();
|
||||
|
@ -294,19 +291,19 @@ TEST_F(VersioningTest, UnsupportedVersion) {
|
|||
try {
|
||||
msg_endpoint_promise.set_value(data_content);
|
||||
} catch (...) {
|
||||
// 忽略重复设置的异常
|
||||
// Ignore duplicate exception setting
|
||||
}
|
||||
} else if (!sse_response_received.load() && response.find("message") != std::string::npos) {
|
||||
sse_response_received.store(true);
|
||||
try {
|
||||
sse_promise.set_value(data_content);
|
||||
} catch (...) {
|
||||
// 忽略重复设置的异常
|
||||
// Ignore duplicate exception setting
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
GTEST_LOG_(ERROR) << "SSE处理错误: " << e.what();
|
||||
GTEST_LOG_(ERROR) << "SSE processing error: " << e.what();
|
||||
}
|
||||
return sse_running.load();
|
||||
});
|
||||
|
@ -315,7 +312,7 @@ TEST_F(VersioningTest, UnsupportedVersion) {
|
|||
std::string endpoint = msg_endpoint.get();
|
||||
EXPECT_FALSE(endpoint.empty());
|
||||
|
||||
// 发送不支持的版本请求
|
||||
// Send unsupported version request
|
||||
json req = request::create("initialize", {{"protocolVersion", "0.0.1"}}).to_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());
|
||||
EXPECT_EQ(mcp_res["error"]["code"].get<int>(), static_cast<int>(error_code::invalid_params));
|
||||
|
||||
// 主动关闭所有连接
|
||||
// Close all connections
|
||||
sse_running.store(false);
|
||||
|
||||
// 尝试中断SSE连接
|
||||
// Try to interrupt SSE connection
|
||||
try {
|
||||
sse_client->Get("/sse", [](const char*, size_t) { return false; });
|
||||
} catch (...) {
|
||||
// 忽略任何异常
|
||||
// Ignore any exception
|
||||
}
|
||||
|
||||
// 等待线程结束(最多1秒)
|
||||
// Wait for thread to finish (max 1 second)
|
||||
if (sse_thread.joinable()) {
|
||||
std::thread detacher([](std::thread& t) {
|
||||
try {
|
||||
|
@ -351,29 +348,29 @@ TEST_F(VersioningTest, UnsupportedVersion) {
|
|||
detacher.detach();
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
// Clean up resources
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
sse_client.reset();
|
||||
http_client.reset();
|
||||
|
||||
// 添加延迟,确保资源完全释放
|
||||
// Add delay to ensure resources are fully released
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
} catch (...) {
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Ping测试环境
|
||||
// Ping test environment
|
||||
class PingEnvironment : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
server_ = std::make_unique<server>("localhost", 8082);
|
||||
|
||||
// 启动服务器(非阻塞模式)
|
||||
// Start server (non-blocking mode)
|
||||
server_->start(false);
|
||||
|
||||
// 创建客户端
|
||||
// Create client
|
||||
json client_capabilities = {
|
||||
{"roots", {{"listChanged", true}}},
|
||||
{"sampling", json::object()}
|
||||
|
@ -383,7 +380,7 @@ public:
|
|||
}
|
||||
|
||||
void TearDown() override {
|
||||
// 清理测试环境
|
||||
// Clean up test environment
|
||||
client_.reset();
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
|
@ -402,23 +399,23 @@ private:
|
|||
static std::unique_ptr<sse_client> client_;
|
||||
};
|
||||
|
||||
// 静态成员变量定义
|
||||
// Static member variable definition
|
||||
std::unique_ptr<server> PingEnvironment::server_;
|
||||
std::unique_ptr<sse_client> PingEnvironment::client_;
|
||||
|
||||
// 测试Ping功能
|
||||
// Test Ping functionality
|
||||
class PingTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 获取客户端指针
|
||||
// Get client pointer
|
||||
client_ = PingEnvironment::GetClient().get();
|
||||
}
|
||||
|
||||
// 使用原始指针而不是引用
|
||||
// Use raw pointer instead of reference
|
||||
sse_client* client_;
|
||||
};
|
||||
|
||||
// 测试Ping请求
|
||||
// Test Ping request
|
||||
TEST_F(PingTest, PingRequest) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
client_->initialize("TestClient", "1.0.0");
|
||||
|
@ -429,11 +426,11 @@ TEST_F(PingTest, PingRequest) {
|
|||
TEST_F(PingTest, DirectPing) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
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> http_client = std::make_unique<httplib::Client>("localhost", 8082);
|
||||
|
||||
// 打开 SSE 连接
|
||||
// Open SSE connection
|
||||
std::promise<std::string> msg_endpoint_promise;
|
||||
std::promise<std::string> sse_promise;
|
||||
std::future<std::string> msg_endpoint = msg_endpoint_promise.get_future();
|
||||
|
@ -457,19 +454,19 @@ TEST_F(PingTest, DirectPing) {
|
|||
try {
|
||||
msg_endpoint_promise.set_value(data_content);
|
||||
} catch (...) {
|
||||
// 忽略重复设置的异常
|
||||
// Ignore duplicate exception setting
|
||||
}
|
||||
} else if (!sse_response_received.load() && response.find("message") != std::string::npos) {
|
||||
sse_response_received.store(true);
|
||||
try {
|
||||
sse_promise.set_value(data_content);
|
||||
} catch (...) {
|
||||
// 忽略重复设置的异常
|
||||
// Ignore duplicate exception setting
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
GTEST_LOG_(ERROR) << "SSE处理错误: " << e.what();
|
||||
GTEST_LOG_(ERROR) << "SSE processing error: " << e.what();
|
||||
}
|
||||
return sse_running.load();
|
||||
});
|
||||
|
@ -478,7 +475,7 @@ TEST_F(PingTest, DirectPing) {
|
|||
std::string endpoint = msg_endpoint.get();
|
||||
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();
|
||||
auto ping_res = http_client->Post(endpoint.c_str(), ping_req.dump(), "application/json");
|
||||
EXPECT_TRUE(ping_res != nullptr);
|
||||
|
@ -487,17 +484,17 @@ TEST_F(PingTest, DirectPing) {
|
|||
auto mcp_res = json::parse(sse_response.get());
|
||||
EXPECT_EQ(mcp_res["result"], json::object());
|
||||
|
||||
// 主动关闭所有连接
|
||||
// Close all connections
|
||||
sse_running.store(false);
|
||||
|
||||
// 尝试中断SSE连接
|
||||
// Try to interrupt SSE connection
|
||||
try {
|
||||
sse_client->Get("/sse", [](const char*, size_t) { return false; });
|
||||
} catch (...) {
|
||||
// 忽略任何异常
|
||||
// Ignore any exception
|
||||
}
|
||||
|
||||
// 等待线程结束(最多1秒)
|
||||
// Wait for thread to finish (max 1 second)
|
||||
if (sse_thread.joinable()) {
|
||||
std::thread detacher([](std::thread& t) {
|
||||
try {
|
||||
|
@ -513,26 +510,26 @@ TEST_F(PingTest, DirectPing) {
|
|||
detacher.detach();
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
// Clean up resources
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
sse_client.reset();
|
||||
http_client.reset();
|
||||
|
||||
// 添加延迟,确保资源完全释放
|
||||
// Add delay to ensure resources are fully released
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
} catch (...) {
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具测试环境
|
||||
// Tools test environment
|
||||
class ToolsEnvironment : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// 设置测试环境
|
||||
// Set up test environment
|
||||
server_ = std::make_unique<server>("localhost", 8083);
|
||||
|
||||
// 创建一个测试工具
|
||||
// Create a test tool
|
||||
tool test_tool;
|
||||
test_tool.name = "get_weather";
|
||||
test_tool.description = "Get current weather information for a location";
|
||||
|
@ -547,9 +544,9 @@ public:
|
|||
{"required", json::array({"location"})}
|
||||
};
|
||||
|
||||
// 注册工具
|
||||
// Register tool
|
||||
server_->register_tool(test_tool, [](const json& params) -> json {
|
||||
// 简单的工具实现
|
||||
// Simple tool implementation
|
||||
std::string location = params["location"];
|
||||
return {
|
||||
{"content", json::array({
|
||||
|
@ -562,7 +559,7 @@ public:
|
|||
};
|
||||
});
|
||||
|
||||
// 注册工具列表方法
|
||||
// Register tools list method
|
||||
server_->register_method("tools/list", [](const json& params) -> json {
|
||||
return {
|
||||
{"tools", json::array({
|
||||
|
@ -585,13 +582,13 @@ public:
|
|||
};
|
||||
});
|
||||
|
||||
// 注册工具调用方法
|
||||
// Register tools call method
|
||||
server_->register_method("tools/call", [](const json& params) -> json {
|
||||
// 验证参数
|
||||
// Verify parameters
|
||||
EXPECT_EQ(params["name"], "get_weather");
|
||||
EXPECT_EQ(params["arguments"]["location"], "New York");
|
||||
|
||||
// 返回工具调用结果
|
||||
// Return tool call result
|
||||
return {
|
||||
{"content", json::array({
|
||||
{
|
||||
|
@ -603,10 +600,10 @@ public:
|
|||
};
|
||||
});
|
||||
|
||||
// 启动服务器(非阻塞模式)
|
||||
// Start server (non-blocking mode)
|
||||
server_->start(false);
|
||||
|
||||
// 创建客户端
|
||||
// Create client
|
||||
json client_capabilities = {
|
||||
{"roots", {{"listChanged", true}}},
|
||||
{"sampling", json::object()}
|
||||
|
@ -617,7 +614,7 @@ public:
|
|||
}
|
||||
|
||||
void TearDown() override {
|
||||
// 清理测试环境
|
||||
// Clean up test environment
|
||||
client_.reset();
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
|
@ -636,43 +633,43 @@ private:
|
|||
static std::unique_ptr<sse_client> client_;
|
||||
};
|
||||
|
||||
// 静态成员变量定义
|
||||
// Static member variable definition
|
||||
std::unique_ptr<server> ToolsEnvironment::server_;
|
||||
std::unique_ptr<sse_client> ToolsEnvironment::client_;
|
||||
|
||||
// 测试工具功能
|
||||
// Test tools functionality
|
||||
class ToolsTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 获取客户端指针
|
||||
// Get client pointer
|
||||
client_ = ToolsEnvironment::GetClient().get();
|
||||
}
|
||||
|
||||
// 使用原始指针而不是引用
|
||||
// Use raw pointer instead of reference
|
||||
sse_client* client_;
|
||||
};
|
||||
|
||||
// 测试列出工具
|
||||
// Test listing tools
|
||||
TEST_F(ToolsTest, ListTools) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// 调用列出工具方法
|
||||
// Call list tools method
|
||||
json tools_list = client_->send_request("tools/list").result;
|
||||
|
||||
// 验证工具列表
|
||||
// Verify tools list
|
||||
EXPECT_TRUE(tools_list.contains("tools"));
|
||||
EXPECT_EQ(tools_list["tools"].size(), 1);
|
||||
EXPECT_EQ(tools_list["tools"][0]["name"], "get_weather");
|
||||
EXPECT_EQ(tools_list["tools"][0]["description"], "Get current weather information for a location");
|
||||
}
|
||||
|
||||
// 测试调用工具
|
||||
// Test calling tool
|
||||
TEST_F(ToolsTest, CallTool) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
// 调用工具
|
||||
// Call tool
|
||||
json tool_result = client_->call_tool("get_weather", {{"location", "New York"}});
|
||||
|
||||
// 验证工具调用结果
|
||||
// Verify tool call result
|
||||
EXPECT_TRUE(tool_result.contains("content"));
|
||||
EXPECT_FALSE(tool_result["isError"]);
|
||||
EXPECT_EQ(tool_result["content"][0]["type"], "text");
|
||||
|
@ -682,7 +679,7 @@ TEST_F(ToolsTest, CallTool) {
|
|||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// 添加全局测试环境
|
||||
// Add global test environment
|
||||
::testing::AddGlobalTestEnvironment(new LifecycleEnvironment());
|
||||
::testing::AddGlobalTestEnvironment(new VersioningEnvironment());
|
||||
::testing::AddGlobalTestEnvironment(new PingEnvironment());
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 响应消息
|
||||
#### Response Message
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 通知消息
|
||||
#### Notification Message
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -43,9 +43,9 @@
|
|||
}
|
||||
```
|
||||
|
||||
### 生命周期测试用例
|
||||
### Lifecycle Testcases
|
||||
|
||||
#### 初始化请求
|
||||
#### Initialization Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -68,7 +68,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 初始化响应
|
||||
#### Initialization Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -97,7 +97,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 初始化完成通知
|
||||
#### Initialization Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -106,7 +106,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 初始化错误
|
||||
#### Initialization Error (Unsupported Version)
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -123,9 +123,9 @@
|
|||
}
|
||||
```
|
||||
|
||||
### 工具功能测试用例
|
||||
### Tool Testcases
|
||||
|
||||
#### Ping 请求
|
||||
#### Ping Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -135,7 +135,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### Ping 响应
|
||||
#### Ping Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -145,7 +145,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### Cancellation 通知
|
||||
#### Cancellation Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -158,7 +158,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### Progress 请求
|
||||
#### Progress Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -173,7 +173,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### Progress 通知
|
||||
#### Progress Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -187,11 +187,11 @@
|
|||
}
|
||||
```
|
||||
|
||||
## 服务器功能测试用例
|
||||
## Server Testcases
|
||||
|
||||
### 工具功能测试用例
|
||||
### Tool Testcases
|
||||
|
||||
#### 列出工具请求
|
||||
#### Tool List Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -204,7 +204,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 列出工具响应
|
||||
#### Tool List Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -232,7 +232,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 调用工具请求
|
||||
#### Tool Call Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -248,7 +248,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 调用工具响应
|
||||
#### Tool Call Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -266,7 +266,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 工具列表变更通知
|
||||
#### Tool List Changed Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -275,9 +275,9 @@
|
|||
}
|
||||
```
|
||||
|
||||
### 资源功能测试用例
|
||||
### Resource Testcases
|
||||
|
||||
#### 列出资源请求
|
||||
#### Resource List Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -290,7 +290,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 列出资源响应
|
||||
#### Resource List Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -310,7 +310,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 读取资源请求
|
||||
#### Resource Read Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -323,7 +323,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 读取资源响应
|
||||
#### Resource Read Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -341,7 +341,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 资源模板列表请求
|
||||
#### Resource Template List Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -351,7 +351,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 资源模板列表响应
|
||||
#### Resource Template List Response
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -370,7 +370,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 资源列表变更通知
|
||||
#### Resource List Changes Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -379,7 +379,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 订阅资源请求
|
||||
#### Resource Description Request
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -392,7 +392,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 资源更新通知
|
||||
#### Resource Updated Notification
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -404,7 +404,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 资源错误响应
|
||||
#### Resource Error (Not Found)
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -420,9 +420,9 @@
|
|||
}
|
||||
```
|
||||
|
||||
### 工具结果数据类型
|
||||
### Tool Result
|
||||
|
||||
#### 文本内容
|
||||
#### Text Content
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -431,7 +431,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 图像内容
|
||||
#### Image Content
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -441,7 +441,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
#### 嵌入资源
|
||||
#### Resource Content
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue