From 7f9862f91ca82118f31834570ee409381574eba0 Mon Sep 17 00:00:00 2001 From: hkr04 Date: Mon, 17 Mar 2025 16:29:33 +0800 Subject: [PATCH] zh -> en --- README.md | 162 ++++++++++++++++++------------------- src/CMakeLists.txt | 2 +- src/mcp_server.cpp | 168 +++++++++++++++++++-------------------- src/mcp_stdio_client.cpp | 156 ++++++++++++++++++------------------ test/README.md | 56 ++++++------- 5 files changed, 267 insertions(+), 277 deletions(-) diff --git a/README.md b/README.md index 7f578f4..8bb16ce 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,77 @@ # MCP Protocol Framework -[Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/architecture/) 是一个开放协议,为AI模型和代理提供与各种资源、工具和服务交互的标准化方式。本框架实现了MCP协议的核心功能,符合2024-11-05基本协议规范。 +[Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/architecture/) is an open protocol that provides a standardized way for AI models and agents to interact with various resources, tools, and services. This framework implements the core functionality of the MCP protocol, conforming to the 2024-11-05 basic protocol specification. -## 核心特性 +## Core Features -- **JSON-RPC 2.0通信**: 基于JSON-RPC 2.0标准的请求/响应通信 -- **资源抽象**: 文件、API等资源的标准接口 -- **工具注册**: 注册和调用带有结构化参数的工具 -- **可扩展架构**: 易于扩展新的资源类型和工具 -- **多传输支持**: 支持HTTP和标准输入/输出(stdio)通信方式 +- **JSON-RPC 2.0 Communication**: Request/response communication based on JSON-RPC 2.0 standard +- **Resource Abstraction**: Standard interfaces for resources such as files, APIs, etc. +- **Tool Registration**: Register and call tools with structured parameters +- **Extensible Architecture**: Easy to extend with new resource types and tools +- **Multi-Transport Support**: Supports HTTP and standard input/output (stdio) communication methods -## 组件 +## Components -MCP C++库包含以下主要组件: +The MCP C++ library includes the following main components: -### 核心组件 +### Core Components -#### 客户端接口 (`mcp_client.h`) -定义了MCP客户端的抽象接口,所有具体的客户端实现都继承自这个接口。 +#### Client Interface (`mcp_client.h`) +Defines the abstract interface for MCP clients, which all concrete client implementations inherit from. -#### SSE客户端 (`mcp_sse_client.h`, `mcp_sse_client.cpp`) -使用HTTP和Server-Sent Events (SSE)与MCP服务器通信的客户端实现。 +#### SSE Client (`mcp_sse_client.h`, `mcp_sse_client.cpp`) +Client implementation that communicates with MCP servers using HTTP and Server-Sent Events (SSE). -#### Stdio客户端 (`mcp_stdio_client.h`, `mcp_stdio_client.cpp`) -使用标准输入/输出与MCP服务器通信的客户端实现,可以启动子进程并与之通信。 +#### Stdio Client (`mcp_stdio_client.h`, `mcp_stdio_client.cpp`) +Client implementation that communicates with MCP servers using standard input/output, capable of launching subprocesses and communicating with them. -#### 消息处理 (`mcp_message.h`, `mcp_message.cpp`) -处理JSON-RPC消息的序列化和反序列化。 +#### Message Processing (`mcp_message.h`, `mcp_message.cpp`) +Handles serialization and deserialization of JSON-RPC messages. -#### 工具管理 (`mcp_tool.h`, `mcp_tool.cpp`) -管理和调用MCP工具。 +#### Tool Management (`mcp_tool.h`, `mcp_tool.cpp`) +Manages and invokes MCP tools. -#### 资源管理 (`mcp_resource.h`, `mcp_resource.cpp`) -管理MCP资源。 +#### Resource Management (`mcp_resource.h`, `mcp_resource.cpp`) +Manages MCP resources. -#### 服务器 (`mcp_server.h`, `mcp_server.cpp`) -实现MCP服务器功能。 +#### Server (`mcp_server.h`, `mcp_server.cpp`) +Implements MCP server functionality. -## 示例 +## Examples -### HTTP服务器示例 (`examples/server_example.cpp`) +### HTTP Server Example (`examples/server_example.cpp`) -MCP服务器实现示例,带有自定义工具: -- 时间工具: 获取当前时间 -- 计算器工具: 执行数学运算 -- 回显工具: 处理和分析文本 -- 招呼工具:返回`Hello, `+传入名字+`!`,默认返回`Hello, World!` +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!` -### HTTP客户端示例 (`examples/client_example.cpp`) +### HTTP Client Example (`examples/client_example.cpp`) -连接到服务器的MCP客户端示例: -- 获取服务器信息 -- 列出可用工具 -- 使用参数调用工具 -- 访问资源 +Example MCP client connecting to a server: +- Get server information +- List available tools +- Call tools with parameters +- Access resources -### Stdio客户端示例 (`examples/stdio_client_example.cpp`) +### Stdio Client Example (`examples/stdio_client_example.cpp`) -展示如何使用stdio客户端与本地服务器通信: -- 启动本地服务器进程 -- 访问文件系统资源 -- 调用服务器工具 +Demonstrates how to use the stdio client to communicate with a local server: +- Launch a local server process +- Access filesystem resources +- Call server tools -## 如何使用 +## How to Use -### 设置HTTP服务器 +### Setting up an HTTP Server ```cpp -// 创建并配置服务器 +// Create and configure the server mcp::server server("localhost", 8080); server.set_server_info("MCP Example Server", "2024-11-05"); -// 注册工具 +// Register tools mcp::json hello_handler(const mcp::json& params) { std::string name = params.contains("name") ? params["name"].get() : "World"; return { @@ -89,24 +89,24 @@ mcp::tool hello_tool = mcp::tool_builder("hello") server.register_tool(hello_tool, hello_handler); -// 注册资源 +// Register resources auto file_resource = std::make_shared(""); server.register_resource("file://", file_resource); -// 启动服务器 -server.start(true); // 阻塞模式 +// Start the server +server.start(true); // Blocking mode ``` -### 创建HTTP客户端 +### Creating an HTTP Client ```cpp -// 连接到服务器 +// Connect to the server mcp::sse_client client("localhost", 8080); -// 初始化连接 +// Initialize the connection client.initialize("My Client", "1.0.0"); -// 调用工具 +// Call a tool mcp::json params = { {"name", "Client"} }; @@ -114,83 +114,83 @@ mcp::json params = { mcp::json result = client.call_tool("hello", params); ``` -### 使用SSE客户端 +### Using the SSE Client -SSE客户端使用HTTP和Server-Sent Events (SSE) 与MCP服务器通信。这是一种基于Web标准的通信方式,适合与支持HTTP/SSE的服务器通信。 +The SSE client uses HTTP and Server-Sent Events (SSE) to communicate with MCP servers. This is a communication method based on Web standards, suitable for communicating with servers that support HTTP/SSE. ```cpp #include "mcp_sse_client.h" -// 创建客户端,指定服务器地址和端口 +// Create a client, specifying the server address and port mcp::sse_client client("localhost", 8080); -// 或者使用基础URL +// Or use a base URL // mcp::sse_client client("http://localhost:8080"); -// 设置认证令牌(如果需要) +// Set an authentication token (if needed) client.set_auth_token("your_auth_token"); -// 设置自定义请求头(如果需要) +// Set custom request headers (if needed) client.set_header("X-Custom-Header", "value"); -// 初始化客户端 +// Initialize the client if (!client.initialize("My Client", "1.0.0")) { - // 初始化失败处理 + // Handle initialization failure } -// 调用工具 +// Call a tool json result = client.call_tool("tool_name", { {"param1", "value1"}, {"param2", 42} }); ``` -### 使用Stdio客户端 +### Using the Stdio Client -Stdio客户端可以与任何支持stdio传输的MCP服务器进行通信,例如: +The Stdio client can communicate with any MCP server that supports stdio transport, such as: -- @modelcontextprotocol/server-everything - 示例服务器 -- @modelcontextprotocol/server-filesystem - 文件系统服务器 -- 其他支持stdio传输的[MCP服务器](https://www.pulsemcp.com/servers) +- @modelcontextprotocol/server-everything - Example server +- @modelcontextprotocol/server-filesystem - Filesystem server +- Other [MCP servers](https://www.pulsemcp.com/servers) that support stdio transport ```cpp #include "mcp_stdio_client.h" -// 创建客户端,指定服务器命令 +// Create a client, specifying the server command mcp::stdio_client client("npx -y @modelcontextprotocol/server-everything"); // mcp::stdio_client client("npx -y @modelcontextprotocol/server-filesystem /path/to/directory"); -// 初始化客户端 +// Initialize the client if (!client.initialize("My Client", "1.0.0")) { - // 初始化失败处理 + // Handle initialization failure } -// 访问资源 +// Access resources json resources = client.list_resources(); json content = client.read_resource("resource://uri"); -// 调用工具 +// Call a tool json result = client.call_tool("tool_name", { {"param1", "value1"}, {"param2", "value2"} }); ``` -## 构建框架 +## Building the Framework -框架依赖以下库: -- httplib.h - HTTP服务器和客户端 -- json.hpp - JSON解析和生成 -- gtest - 测试 +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. -使用CMake构建示例: +Example of building with CMake: ```bash cmake -B build cmake --build build --config Release ``` -## 许可证 +## License -本框架根据MIT许可证提供。有关详细信息,请参阅LICENSE文件。 \ No newline at end of file +This framework is provided under the MIT license. For details, please see the LICENSE file. \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c323c1..2a50d21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,7 +18,7 @@ add_library(${TARGET} STATIC target_link_libraries(${TARGET} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) -# 如果找到OpenSSL,链接OpenSSL库 +# If OpenSSL is found, link the OpenSSL libraries if(OPENSSL_FOUND) target_link_libraries(${TARGET} PUBLIC ${OPENSSL_LIBRARIES}) endif() diff --git a/src/mcp_server.cpp b/src/mcp_server.cpp index 9dbd41e..9b6b377 100644 --- a/src/mcp_server.cpp +++ b/src/mcp_server.cpp @@ -26,7 +26,7 @@ bool server::start(bool blocking) { LOG_INFO("Starting MCP server on ", host_, ":", port_); - // 设置CORS处理 + // Setup CORS handling http_server_->Options(".*", [](const httplib::Request& req, httplib::Response& res) { res.set_header("Access-Control-Allow-Origin", "*"); res.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); @@ -34,23 +34,23 @@ bool server::start(bool blocking) { res.status = 204; // No Content }); - // 设置JSON-RPC端点 + // Setup JSON-RPC endpoint http_server_->Post(msg_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) { this->handle_jsonrpc(req, res); LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"POST ", req.path, " HTTP/1.1\" ", res.status); }); - // 设置SSE端点 + // Setup SSE endpoint http_server_->Get(sse_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) { this->handle_sse(req, res); LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"GET ", req.path, " HTTP/1.1\" ", res.status); }); - // 启动资源检查线程(优化:只在非阻塞模式下启动) + // Start resource check thread (only start in non-blocking mode) if (!blocking) { maintenance_thread_ = std::make_unique([this]() { while (running_) { - // 每60秒检查一次不活跃的会话 + // Check inactive sessions every 60 seconds std::this_thread::sleep_for(std::chrono::seconds(60)); if (running_) { try { @@ -65,7 +65,7 @@ bool server::start(bool blocking) { }); } - // 启动服务器 + // Start server if (blocking) { running_ = true; LOG_INFO("Starting server in blocking mode"); @@ -76,7 +76,7 @@ bool server::start(bool blocking) { } return true; } else { - // 在单独的线程中启动 + // Start server in a separate thread server_thread_ = std::make_unique([this]() { LOG_INFO("Starting server in separate thread"); if (!http_server_->listen(host_.c_str(), port_)) { @@ -98,7 +98,7 @@ void server::stop() { LOG_INFO("Stopping MCP server on ", host_, ":", port_); running_ = false; - // 关闭维护线程 + // Close maintenance thread if (maintenance_thread_ && maintenance_thread_->joinable()) { try { maintenance_thread_->join(); @@ -107,20 +107,20 @@ void server::stop() { } } - // 复制所有分发器和线程,避免长时间持有锁 + // Copy all dispatchers and threads to avoid holding the lock for too long std::vector> dispatchers_to_close; std::vector> threads_to_join; { std::lock_guard lock(mutex_); - // 复制所有分发器 + // Copy all dispatchers dispatchers_to_close.reserve(session_dispatchers_.size()); for (const auto& [_, dispatcher] : session_dispatchers_) { dispatchers_to_close.push_back(dispatcher); } - // 复制所有线程 + // Copy all threads threads_to_join.reserve(sse_threads_.size()); for (auto& [_, thread] : sse_threads_) { if (thread && thread->joinable()) { @@ -128,27 +128,27 @@ void server::stop() { } } - // 清空映射表 + // Clear the maps session_dispatchers_.clear(); sse_threads_.clear(); session_initialized_.clear(); } - // 在锁外关闭所有分发器 + // Close all dispatchers outside the lock for (auto& dispatcher : dispatchers_to_close) { if (dispatcher && !dispatcher->is_closed()) { try { dispatcher->close(); } catch (...) { - // 忽略异常 + // Ignore exceptions } } } - // 给线程一些时间处理关闭事件 + // Give threads some time to handle close events std::this_thread::sleep_for(std::chrono::milliseconds(300)); - // 在锁外等待线程结束(有超时限制) + // Wait for threads to finish outside the lock (with timeout limit) const auto timeout_point = std::chrono::steady_clock::now() + std::chrono::seconds(2); for (auto& thread : threads_to_join) { @@ -157,20 +157,20 @@ void server::stop() { } if (std::chrono::steady_clock::now() >= timeout_point) { - // 如果已经超时,detach剩余线程 + // If timeout reached, detach remaining threads LOG_WARNING("Thread join timeout reached, detaching remaining threads"); thread->detach(); continue; } - // 尝试使用超时的join + // Try using timeout join bool joined = false; try { - // 创建future和promise,用于实现thread join的超时处理 + // Create future and promise for timeout join std::promise thread_done; auto future = thread_done.get_future(); - // 在另一个线程中尝试join + // Try join in another thread std::thread join_helper([&thread, &thread_done]() { try { thread->join(); @@ -182,13 +182,13 @@ void server::stop() { } }); - // 等待join完成或超时 + // Wait for join to complete or timeout if (future.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) { - future.get(); // 获取可能的异常 + future.get(); // Get possible exception joined = true; } - // 处理join_helper线程 + // Process join_helper thread if (join_helper.joinable()) { if (joined) { join_helper.join(); @@ -200,12 +200,12 @@ void server::stop() { joined = false; } - // 如果join失败,则detach + // If join fails, then detach if (!joined) { try { thread->detach(); } catch (...) { - // 忽略异常 + // Ignore exceptions } } } @@ -382,37 +382,37 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) { std::string session_id = generate_session_id(); std::string session_uri = msg_endpoint_ + "?session_id=" + session_id; - // 设置SSE响应头 + // Setup SSE response headers res.set_header("Content-Type", "text/event-stream"); res.set_header("Cache-Control", "no-cache"); res.set_header("Connection", "keep-alive"); res.set_header("Access-Control-Allow-Origin", "*"); - // 创建会话特定的事件分发器 + // Create session-specific event dispatcher auto session_dispatcher = std::make_shared(); - // 初始化活动时间 + // Initialize activity time session_dispatcher->update_activity(); - // 添加会话分发器到映射表 + // Add session dispatcher to mapping table { std::lock_guard lock(mutex_); session_dispatchers_[session_id] = session_dispatcher; } - // 创建会话线程 + // Create session thread auto thread = std::make_unique([this, res, session_id, session_uri, session_dispatcher]() { try { - // 发送初始会话URI + // Send initial session URI std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::stringstream ss; ss << "event: endpoint\r\ndata: " << session_uri << "\r\n\r\n"; session_dispatcher->send_event(ss.str()); - // 更新活动时间(发送消息后) + // Update activity time (after sending message) session_dispatcher->update_activity(); - // 定期发送心跳,检测连接状态 + // Send periodic heartbeats to detect connection status int heartbeat_count = 0; while (running_ && !session_dispatcher->is_closed()) { std::this_thread::sleep_for(std::chrono::seconds(5) + std::chrono::milliseconds(rand() % 500)); // NOTE: DO NOT set it the same as the timeout of wait_event @@ -431,7 +431,7 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) { break; } - // 更新活动时间(心跳成功) + // Update activity time (heartbeat successful) session_dispatcher->update_activity(); } catch (const std::exception& e) { LOG_ERROR("Failed to send heartbeat: ", e.what()); @@ -442,39 +442,39 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) { LOG_ERROR("SSE session thread exception: ", session_id, ", ", e.what()); } - // 安全地清理资源 + // Clean up resources safely try { - // 先复制需要处理的资源指针 + // Copy resources to be processed std::shared_ptr dispatcher_to_close; std::unique_ptr thread_to_release; { std::lock_guard lock(mutex_); - // 获取dispatcher指针 + // Get dispatcher pointer auto dispatcher_it = session_dispatchers_.find(session_id); if (dispatcher_it != session_dispatchers_.end()) { dispatcher_to_close = dispatcher_it->second; session_dispatchers_.erase(dispatcher_it); } - // 获取线程指针 + // Get thread pointer auto thread_it = sse_threads_.find(session_id); if (thread_it != sse_threads_.end()) { thread_to_release = std::move(thread_it->second); sse_threads_.erase(thread_it); } - // 清理初始化状态 + // Clean up initialization status session_initialized_.erase(session_id); } - // 在锁外关闭dispatcher + // Close dispatcher outside the lock if (dispatcher_to_close && !dispatcher_to_close->is_closed()) { dispatcher_to_close->close(); } - // 释放线程资源 + // Release thread resources if (thread_to_release) { thread_to_release.release(); } @@ -485,42 +485,42 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) { } }); - // 存储线程 + // Store thread { std::lock_guard lock(mutex_); sse_threads_[session_id] = std::move(thread); } - // 设置分块内容提供者 + // Setup chunked content provider res.set_chunked_content_provider("text/event-stream", [this, session_id, session_dispatcher](size_t /* offset */, httplib::DataSink& sink) { try { - // 检查会话是否已关闭 - 直接从分发器获取状态,减少锁冲突 + // Check if session is closed - directly get status from dispatcher, reduce lock contention if (session_dispatcher->is_closed()) { return false; } - // 更新活动时间(接收到请求) + // Update activity time (received request) session_dispatcher->update_activity(); - // 等待事件 + // Wait for event bool result = session_dispatcher->wait_event(&sink); if (!result) { LOG_WARNING("Failed to wait for event, closing connection: ", session_id); - // 直接关闭分发器,无需加锁 + // Close dispatcher directly, no need to lock session_dispatcher->close(); return false; } - // 更新活动时间(成功接收消息) + // Update activity time (successfully received message) session_dispatcher->update_activity(); return true; } catch (const std::exception& e) { LOG_ERROR("SSE content provider exception: ", e.what()); - // 直接关闭分发器,无需加锁 + // Close dispatcher directly, no need to lock session_dispatcher->close(); return false; @@ -529,23 +529,23 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) { } void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) { - // 设置响应头 + // Setup response headers res.set_header("Content-Type", "application/json"); res.set_header("Access-Control-Allow-Origin", "*"); res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS"); res.set_header("Access-Control-Allow-Headers", "Content-Type"); - // 处理OPTIONS请求(CORS预检) + // Handle OPTIONS request (CORS pre-flight) if (req.method == "OPTIONS") { res.status = 204; // No Content return; } - // 获取会话ID + // Get session ID auto it = req.params.find("session_id"); std::string session_id = it != req.params.end() ? it->second : ""; - // 更新会话活动时间 + // Update session activity time if (!session_id.empty()) { std::shared_ptr dispatcher; { @@ -561,7 +561,7 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) } } - // 解析请求 + // Parse request json req_json; try { req_json = json::parse(req.body); @@ -572,13 +572,13 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) return; } - // 检查会话是否存在 + // Check if session exists std::shared_ptr dispatcher; { std::lock_guard lock(mutex_); auto disp_it = session_dispatchers_.find(session_id); if (disp_it == session_dispatchers_.end()) { - // 处理ping请求 + // Handle ping request if (req_json["method"] == "ping") { res.status = 202; res.set_content("Accepted", "text/plain"); @@ -592,7 +592,7 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) dispatcher = disp_it->second; } - // 创建请求对象 + // Create request object request mcp_req; try { mcp_req.jsonrpc = req_json["jsonrpc"].get(); @@ -610,25 +610,25 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) return; } - // 如果是通知(没有ID),直接处理并返回202状态码 + // If it is a notification (no ID), process it directly and return 202 status code if (mcp_req.is_notification()) { - // 在线程池中异步处理通知 + // Process it asynchronously in the thread pool thread_pool_.enqueue([this, mcp_req, session_id]() { process_request(mcp_req, session_id); }); - // 返回202 Accepted + // Return 202 Accepted res.status = 202; res.set_content("Accepted", "text/plain"); return; } - // 对于有ID的请求,在线程池中处理并通过SSE返回结果 + // For requests with ID, process it asynchronously in the thread pool and return the result via SSE thread_pool_.enqueue([this, mcp_req, session_id, dispatcher]() { - // 处理请求 + // Process the request json response_json = process_request(mcp_req, session_id); - // 通过SSE发送响应 + // Send response via SSE std::stringstream ss; ss << "event: message\r\ndata: " << response_json.dump() << "\r\n\r\n"; bool result = dispatcher->send_event(ss.str()); @@ -638,13 +638,13 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) } }); - // 返回202 Accepted + // Return 202 Accepted res.status = 202; res.set_content("Accepted", "text/plain"); } json server::process_request(const request& req, const std::string& session_id) { - // 检查是否是通知 + // Check if it is a notification if (req.is_notification()) { if (req.method == "notifications/initialized") { set_session_initialized(session_id, true); @@ -652,11 +652,11 @@ json server::process_request(const request& req, const std::string& session_id) return json::object(); } - // 处理方法调用 + // Process method call try { LOG_INFO("Processing method call: ", req.method); - // 特殊情况:初始化 + // Special case: initialization if (req.method == "initialize") { return handle_initialize(req, session_id); } else if (req.method == "ping") { @@ -672,7 +672,7 @@ json server::process_request(const request& req, const std::string& session_id) ).to_json(); } - // 查找注册的方法处理器 + // Find registered method handler std::function handler; { std::lock_guard lock(mutex_); @@ -683,19 +683,19 @@ json server::process_request(const request& req, const std::string& session_id) } if (handler) { - // 调用处理器 + // Call handler LOG_INFO("Calling method handler: ", req.method); auto future = thread_pool_.enqueue([handler, params = req.params]() -> json { return handler(params); }); json result = future.get(); - // 创建成功响应 + // Create success response LOG_INFO("Method call successful: ", req.method); return response::create_success(req.id, result).to_json(); } - // 方法未找到 + // Method not found LOG_WARNING("Method not found: ", req.method); return response::create_error( req.id, @@ -703,7 +703,7 @@ json server::process_request(const request& req, const std::string& session_id) "Method not found: " + req.method ).to_json(); } catch (const mcp_exception& e) { - // MCP异常 + // MCP exception LOG_ERROR("MCP exception: ", e.what(), ", code: ", static_cast(e.code())); return response::create_error( req.id, @@ -711,7 +711,7 @@ json server::process_request(const request& req, const std::string& session_id) e.what() ).to_json(); } catch (const std::exception& e) { - // 其他异常 + // Other exceptions LOG_ERROR("Exception while processing request: ", e.what()); return response::create_error( req.id, @@ -719,7 +719,7 @@ json server::process_request(const request& req, const std::string& session_id) "Internal error: " + std::string(e.what()) ).to_json(); } catch (...) { - // 未知异常 + // Unknown exception LOG_ERROR("Unknown exception while processing request"); return response::create_error( req.id, @@ -792,7 +792,7 @@ json server::handle_initialize(const request& req, const std::string& session_id } void server::send_request(const std::string& session_id, const std::string& method, const json& params) { - // 检查会话ID是否有效 + // Check if session ID is valid if (session_id.empty()) { LOG_WARNING("Cannot send request to empty session_id"); return; @@ -810,7 +810,7 @@ void server::send_request(const std::string& session_id, const std::string& meth // Create request request req = request::create(method, params); - // 获取会话分发器 + // Get session dispatcher std::shared_ptr dispatcher; { std::lock_guard lock(mutex_); @@ -822,13 +822,13 @@ void server::send_request(const std::string& session_id, const std::string& meth dispatcher = it->second; } - // 确认dispatcher仍然有效 + // Confirm dispatcher is still valid if (!dispatcher || dispatcher->is_closed()) { LOG_WARNING("Cannot send to closed session: ", session_id); return; } - // 发送请求 + // Send request std::stringstream ss; ss << "event: message\r\ndata: " << req.to_json().dump() << "\r\n\r\n"; bool result = dispatcher->send_event(ss.str()); @@ -839,7 +839,7 @@ void server::send_request(const std::string& session_id, const std::string& meth } bool server::is_session_initialized(const std::string& session_id) const { - // 检查会话ID是否有效 + // Check if session ID is valid if (session_id.empty()) { return false; } @@ -855,7 +855,7 @@ bool server::is_session_initialized(const std::string& session_id) const { } void server::set_session_initialized(const std::string& session_id, bool initialized) { - // 检查会话ID是否有效 + // Check if session ID is valid if (session_id.empty()) { LOG_WARNING("Cannot set initialization state for empty session_id"); return; @@ -863,7 +863,7 @@ void server::set_session_initialized(const std::string& session_id, bool initial try { std::lock_guard lock(mutex_); - // 检查会话是否仍然存在 + // Check if session still exists auto it = session_dispatchers_.find(session_id); if (it == session_dispatchers_.end()) { LOG_WARNING("Cannot set initialization state for non-existent session: ", session_id); @@ -915,7 +915,7 @@ void server::check_inactive_sessions() { if (!running_) return; const auto now = std::chrono::steady_clock::now(); - const auto timeout = std::chrono::minutes(3); // 3分钟不活跃则关闭 + const auto timeout = std::chrono::minutes(60); // 1 hour inactive then close std::vector sessions_to_close; @@ -923,13 +923,13 @@ void server::check_inactive_sessions() { std::lock_guard lock(mutex_); for (const auto& [session_id, dispatcher] : session_dispatchers_) { if (now - dispatcher->last_activity() > timeout) { - // 超过闲置时间限制 + // Exceeded idle time limit sessions_to_close.push_back(session_id); } } } - // 关闭不活跃的会话 + // Close inactive sessions for (const auto& session_id : sessions_to_close) { LOG_INFO("Closing inactive session: ", session_id); diff --git a/src/mcp_stdio_client.cpp b/src/mcp_stdio_client.cpp index 7dde0d8..a1ff47f 100644 --- a/src/mcp_stdio_client.cpp +++ b/src/mcp_stdio_client.cpp @@ -206,13 +206,13 @@ bool stdio_client::start_server_process() { LOG_INFO("Starting server process: ", command_); #if defined(_WIN32) || defined(_WIN64) - // Windows实现 + // Windows implementation SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; - // 创建管道 + // Create pipes HANDLE child_stdin_read = NULL; HANDLE child_stdin_write = NULL; HANDLE child_stdout_read = NULL; @@ -246,7 +246,7 @@ bool stdio_client::start_server_process() { return false; } - // 准备进程启动信息 + // Prepare process startup info STARTUPINFOA si; PROCESS_INFORMATION pi; @@ -259,12 +259,12 @@ bool stdio_client::start_server_process() { ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); - // 准备环境变量 + // Prepare environment variables std::string env_block; if (!env_vars_.empty()) { char* system_env = GetEnvironmentStringsA(); if (system_env) { - // 复制系统环境变量 + // Copy system environment variables const char* env_ptr = system_env; while (*env_ptr) { std::string env_var(env_ptr); @@ -273,32 +273,32 @@ bool stdio_client::start_server_process() { } FreeEnvironmentStringsA(system_env); - // 添加自定义环境变量 + // Add custom environment variables for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) { std::string env_var = it.key() + "=" + it.value().get(); env_block += env_var + '\0'; } - // 添加结束符 + // Add terminator env_block += '\0'; } } - // 创建子进程 + // Create child process std::string cmd_line = command_; char* cmd_str = const_cast(cmd_line.c_str()); BOOL success = CreateProcessA( - NULL, // 应用程序名称 - cmd_str, // 命令行 - NULL, // 进程安全属性 - NULL, // 线程安全属性 - TRUE, // 继承句柄 - CREATE_NO_WINDOW, // 创建标志 - env_vars_.empty() ? NULL : (LPVOID)env_block.c_str(), // 环境变量 - NULL, // 当前目录 - &si, // 启动信息 - &pi // 进程信息 + NULL, // Application name + cmd_str, // Command line + NULL, // Process security attributes + NULL, // Thread security attributes + TRUE, // Inherit handles + CREATE_NO_WINDOW, // Creation flags + env_vars_.empty() ? NULL : (LPVOID)env_block.c_str(), // Environment variables + NULL, // Current directory + &si, // Startup info + &pi // Process info ); if (!success) { @@ -310,12 +310,12 @@ bool stdio_client::start_server_process() { return false; } - // 关闭不需要的句柄 + // Close unnecessary handles CloseHandle(child_stdin_read); CloseHandle(child_stdout_write); CloseHandle(pi.hThread); - // 保存进程信息 + // Save process info process_id_ = pi.dwProcessId; process_handle_ = pi.hProcess; stdin_pipe_[0] = NULL; @@ -323,13 +323,13 @@ bool stdio_client::start_server_process() { stdout_pipe_[0] = child_stdout_read; stdout_pipe_[1] = NULL; - // 设置非阻塞模式 + // Set non-blocking mode DWORD mode = PIPE_NOWAIT; SetNamedPipeHandleState(stdout_pipe_[0], &mode, NULL, NULL); #else - // POSIX实现 - // 创建管道 + // POSIX implementation + // Create pipes if (pipe(stdin_pipe_) == -1) { LOG_ERROR("Failed to create stdin pipe: ", strerror(errno)); return false; @@ -342,7 +342,7 @@ bool stdio_client::start_server_process() { return false; } - // 创建子进程 + // Create child process process_id_ = fork(); if (process_id_ == -1) { @@ -355,9 +355,9 @@ bool stdio_client::start_server_process() { } if (process_id_ == 0) { - // 子进程 + // Child process - // 设置环境变量 + // Set environment variables if (!env_vars_.empty()) { for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) { std::string env_var = it.key() + "=" + it.value().get(); @@ -367,11 +367,11 @@ bool stdio_client::start_server_process() { } } - // 关闭不需要的管道端 - close(stdin_pipe_[1]); // 关闭写入端 - close(stdout_pipe_[0]); // 关闭读取端 + // Close unnecessary pipe ends + close(stdin_pipe_[1]); // Close write end + close(stdout_pipe_[0]); // Close read end - // 重定向标准输入/输出 + // Redirect standard input/output if (dup2(stdin_pipe_[0], STDIN_FILENO) == -1) { LOG_ERROR("Failed to redirect stdin: ", strerror(errno)); exit(EXIT_FAILURE); @@ -382,15 +382,15 @@ bool stdio_client::start_server_process() { exit(EXIT_FAILURE); } - // 关闭已重定向的文件描述符 + // Close already redirected file descriptors close(stdin_pipe_[0]); close(stdout_pipe_[1]); - // 设置非阻塞模式 + // Set non-blocking mode int flags = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); - // 执行命令 + // Execute command std::vector args; std::istringstream iss(command_); std::string arg; @@ -407,22 +407,22 @@ bool stdio_client::start_server_process() { execvp(c_args[0], c_args.data()); - // 如果execvp返回,则表示出错 + // If execvp returns, it means an error occurred LOG_ERROR("Failed to execute command: ", strerror(errno)); exit(EXIT_FAILURE); } - // 父进程 + // Parent process - // 关闭不需要的管道端 - close(stdin_pipe_[0]); // 关闭读取端 - close(stdout_pipe_[1]); // 关闭写入端 + // Close unnecessary pipe ends + close(stdin_pipe_[0]); // Close read end + close(stdout_pipe_[1]); // Close write end - // 设置非阻塞模式 + // Set non-blocking mode int flags = fcntl(stdout_pipe_[0], F_GETFL, 0); fcntl(stdout_pipe_[0], F_SETFL, flags | O_NONBLOCK); - // 检查进程是否仍在运行 + // Check if process is still running int status; pid_t result = waitpid(process_id_, &status, WNOHANG); @@ -455,14 +455,14 @@ bool stdio_client::start_server_process() { running_ = true; - // 启动读取线程 + // Start read thread read_thread_ = std::make_unique(&stdio_client::read_thread_func, this); - // 等待一段时间,确保进程启动 + // Wait for a while to ensure process starts std::this_thread::sleep_for(std::chrono::milliseconds(500)); #if defined(_WIN32) || defined(_WIN64) - // 检查进程是否仍在运行 + // Check if process is still running DWORD exit_code; if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code != STILL_ACTIVE) { LOG_ERROR("Server process exited immediately with status: ", exit_code); @@ -494,8 +494,8 @@ void stdio_client::stop_server_process() { running_ = false; #if defined(_WIN32) || defined(_WIN64) - // Windows实现 - // 关闭管道 + // Windows implementation + // Close pipes if (stdin_pipe_[1] != NULL) { CloseHandle(stdin_pipe_[1]); stdin_pipe_[1] = NULL; @@ -506,22 +506,22 @@ void stdio_client::stop_server_process() { stdout_pipe_[0] = NULL; } - // 等待读取线程结束 + // Wait for read thread to finish if (read_thread_ && read_thread_->joinable()) { read_thread_->join(); } - // 终止进程 + // Terminate process if (process_handle_ != NULL) { LOG_INFO("Terminating process: ", process_id_); TerminateProcess(process_handle_, 0); - // 等待进程结束 + // Wait for process to finish WaitForSingleObject(process_handle_, 2000); DWORD exit_code; if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code == STILL_ACTIVE) { - // 进程仍在运行,强制终止 + // Process is still running, force termination LOG_WARNING("Process did not terminate, forcing termination"); TerminateProcess(process_handle_, 1); WaitForSingleObject(process_handle_, 1000); @@ -532,8 +532,8 @@ void stdio_client::stop_server_process() { process_id_ = -1; } #else - // POSIX实现 - // 关闭管道 + // POSIX implementation + // Close pipes if (stdin_pipe_[1] != -1) { close(stdin_pipe_[1]); stdin_pipe_[1] = -1; @@ -544,28 +544,28 @@ void stdio_client::stop_server_process() { stdout_pipe_[0] = -1; } - // 等待读取线程结束 + // Wait for read thread to finish if (read_thread_ && read_thread_->joinable()) { read_thread_->join(); } - // 终止进程 + // Terminate process if (process_id_ > 0) { LOG_INFO("Sending SIGTERM to process: ", process_id_); kill(process_id_, SIGTERM); - // 等待进程结束 + // Wait for process to finish int status; pid_t result = waitpid(process_id_, &status, WNOHANG); if (result == 0) { - // 进程仍在运行,等待一段时间 + // Process is still running, wait for a while std::this_thread::sleep_for(std::chrono::seconds(2)); result = waitpid(process_id_, &status, WNOHANG); if (result == 0) { - // 进程仍在运行,强制终止 + // Process is still running, force termination LOG_WARNING("Process did not terminate, sending SIGKILL"); kill(process_id_, SIGKILL); waitpid(process_id_, &status, 0); @@ -587,18 +587,18 @@ void stdio_client::read_thread_func() { std::string data_buffer; #if defined(_WIN32) || defined(_WIN64) - // Windows实现 + // Windows implementation DWORD bytes_read; while (running_) { - // 读取数据 + // Read data BOOL success = ReadFile(stdout_pipe_[0], buffer, buffer_size - 1, &bytes_read, NULL); if (success && bytes_read > 0) { buffer[bytes_read] = '\0'; data_buffer.append(buffer, bytes_read); - // 处理完整的JSON-RPC消息 + // Process complete JSON-RPC message size_t pos = 0; while ((pos = data_buffer.find('\n')) != std::string::npos) { std::string line = data_buffer.substr(0, pos); @@ -610,7 +610,7 @@ void stdio_client::read_thread_func() { if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") { if (message.contains("id") && !message["id"].is_null()) { - // 这是一个响应 + // This is a response json id = message["id"]; std::lock_guard lock(response_mutex_); @@ -634,9 +634,9 @@ void stdio_client::read_thread_func() { LOG_WARNING("Received response for unknown request ID: ", id); } } else if (message.contains("method")) { - // 这是一个请求或通知 + // This is a request or notification LOG_INFO("Received request/notification: ", message["method"]); - // 目前不处理服务器发来的请求 + // Currently not handling requests from the server } } } catch (const json::exception& e) { @@ -647,7 +647,7 @@ void stdio_client::read_thread_func() { } else if (!success) { DWORD error = GetLastError(); if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) { - // 管道已关闭或没有数据 + // Pipe is closed or no data available LOG_WARNING("Pipe closed by server or no data available"); break; } else if (error != ERROR_IO_PENDING) { @@ -655,24 +655,24 @@ void stdio_client::read_thread_func() { break; } - // 非阻塞模式下没有数据可读 + // No data to read in non-blocking mode std::this_thread::sleep_for(std::chrono::milliseconds(10)); } else { - // 非阻塞模式下没有数据可读 + // No data to read in non-blocking mode std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } #else - // POSIX实现 + // POSIX implementation while (running_) { - // 读取数据 + // Read data ssize_t bytes_read = read(stdout_pipe_[0], buffer, buffer_size - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; data_buffer.append(buffer, bytes_read); - // 处理完整的JSON-RPC消息 + // Process complete JSON-RPC message size_t pos = 0; while ((pos = data_buffer.find('\n')) != std::string::npos) { std::string line = data_buffer.substr(0, pos); @@ -684,7 +684,7 @@ void stdio_client::read_thread_func() { if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") { if (message.contains("id") && !message["id"].is_null()) { - // 这是一个响应 + // This is a response json id = message["id"]; std::lock_guard lock(response_mutex_); @@ -708,9 +708,9 @@ void stdio_client::read_thread_func() { LOG_WARNING("Received response for unknown request ID: ", id); } } else if (message.contains("method")) { - // 这是一个请求或通知 + // This is a request or notification LOG_INFO("Received request/notification: ", message["method"]); - // 目前不处理服务器发来的请求 + // Currently not handling requests from the server } } } catch (const json::exception& e) { @@ -719,12 +719,12 @@ void stdio_client::read_thread_func() { } } } else if (bytes_read == 0) { - // 管道已关闭 + // Pipe is closed LOG_WARNING("Pipe closed by server"); break; } else if (bytes_read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { - // 非阻塞模式下没有数据可读 + // No data to read in non-blocking mode std::this_thread::sleep_for(std::chrono::milliseconds(10)); } else { LOG_ERROR("Error reading from pipe: ", strerror(errno)); @@ -746,7 +746,7 @@ json stdio_client::send_jsonrpc(const request& req) { std::string req_str = req_json.dump() + "\n"; #if defined(_WIN32) || defined(_WIN64) - // Windows实现 + // Windows implementation DWORD bytes_written; BOOL success = WriteFile(stdin_pipe_[1], req_str.c_str(), static_cast(req_str.size()), &bytes_written, NULL); @@ -755,7 +755,7 @@ json stdio_client::send_jsonrpc(const request& req) { throw mcp_exception(error_code::internal_error, "Failed to write to pipe"); } #else - // POSIX实现 + // POSIX implementation ssize_t bytes_written = write(stdin_pipe_[1], req_str.c_str(), req_str.size()); if (bytes_written != static_cast(req_str.size())) { @@ -764,12 +764,12 @@ json stdio_client::send_jsonrpc(const request& req) { } #endif - // 如果是通知,不需要等待响应 + // If this is a notification, no need to wait for a response if (req.is_notification()) { return json::object(); } - // 创建Promise和Future + // Create Promise and Future std::promise response_promise; std::future response_future = response_promise.get_future(); @@ -778,7 +778,7 @@ json stdio_client::send_jsonrpc(const request& req) { pending_requests_[req.id] = std::move(response_promise); } - // 等待响应,设置超时 + // Wait for response, set timeout const auto timeout = std::chrono::seconds(30); auto status = response_future.wait_for(timeout); diff --git a/test/README.md b/test/README.md index 8c112e0..fde5cef 100644 --- a/test/README.md +++ b/test/README.md @@ -1,69 +1,59 @@ -# MCP 单元测试 +# MCP Unit Tests -本目录包含 Model Context Protocol (MCP) 实现的单元测试,基于规范 2024-11-05。 +This directory contains unit tests for the Model Context Protocol (MCP) implementation, based on the 2024-11-05 specification. -## 测试内容 +## Building and Running Tests -测试文件包括: - -- `test_mcp_message.cpp`: 测试消息相关功能 -- `test_mcp_tool.cpp`: 测试工具相关功能 -- `test_mcp_resource.cpp`: 测试资源相关功能 -- `test_mcp_client.cpp`: 测试客户端相关功能 -- `test_mcp_server.cpp`: 测试服务器相关功能 - -## 构建和运行测试 - -### 构建测试 +### 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 -要运行特定的测试,可以使用 Google Test 的过滤功能: +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 -测试使用 Google Test 框架,该框架会在构建时自动下载和配置。 +Tests use the Google Test framework, which is automatically downloaded and configured during the build process. -## 注意事项 +## Notes -- 部分测试需要网络功能,确保本地端口(如 8090、8095)未被占用 -- 客户端和服务器测试会启动实际的服务器和客户端进行交互测试 -- 资源测试会在临时目录创建文件,测试完成后会自动清理 \ No newline at end of file +- 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 \ No newline at end of file