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)
enable_testing()
add_subdirectory(test)
# run_teststest/CMakeLists.txt
endif()

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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()

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
* @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>
@ -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());

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
{
@ -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
{