Merge branch 'main' of http://118.24.129.148:3000/Hyde/cpp-mcp into HEAD
commit
3fe1e049fb
162
README.md
162
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<std::string>() : "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<mcp::file_resource>("<file_path>");
|
||||
server.register_resource("file://<file_path>", 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文件。
|
||||
This framework is provided under the MIT license. For details, please see the LICENSE file.
|
|
@ -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()
|
||||
|
|
|
@ -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<std::thread>([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<std::thread>([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<std::shared_ptr<event_dispatcher>> dispatchers_to_close;
|
||||
std::vector<std::unique_ptr<std::thread>> threads_to_join;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<void> 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<event_dispatcher>();
|
||||
|
||||
// 初始化活动时间
|
||||
// Initialize activity time
|
||||
session_dispatcher->update_activity();
|
||||
|
||||
// 添加会话分发器到映射表
|
||||
// Add session dispatcher to mapping table
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
session_dispatchers_[session_id] = session_dispatcher;
|
||||
}
|
||||
|
||||
// 创建会话线程
|
||||
// Create session thread
|
||||
auto thread = std::make_unique<std::thread>([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<event_dispatcher> dispatcher_to_close;
|
||||
std::unique_ptr<std::thread> thread_to_release;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<event_dispatcher> 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<event_dispatcher> dispatcher;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::string>();
|
||||
|
@ -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<json(const json&)> handler;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<int>(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<event_dispatcher> dispatcher;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::string> sessions_to_close;
|
||||
|
||||
|
@ -923,13 +923,13 @@ void server::check_inactive_sessions() {
|
|||
std::lock_guard<std::mutex> 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);
|
||||
|
||||
|
|
|
@ -542,12 +542,15 @@ json sse_client::send_jsonrpc(const request& req) {
|
|||
if (status == std::future_status::ready) {
|
||||
json response = response_future.get();
|
||||
|
||||
if (response.contains("isError") && response["isError"].get<bool>()) {
|
||||
int code = response["error"]["code"];
|
||||
std::string message = response["error"]["message"];
|
||||
|
||||
if (response.contains("isError") && response["isError"].is_boolean() && response["isError"].get<bool>()) {
|
||||
if (response.contains("error") && response["error"].is_object()) {
|
||||
const auto& err_obj = response["error"];
|
||||
int code = err_obj.contains("code") ? err_obj["code"].get<int>() : static_cast<int>(error_code::internal_error);
|
||||
std::string message = err_obj.value("message", "");
|
||||
// Handle error
|
||||
throw mcp_exception(static_cast<error_code>(code), message);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
|
|
|
@ -219,13 +219,13 @@ bool stdio_client::start_server_process() {
|
|||
};
|
||||
|
||||
#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;
|
||||
|
@ -259,7 +259,7 @@ bool stdio_client::start_server_process() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// 准备进程启动信息
|
||||
// Prepare process startup info
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
|
@ -272,7 +272,7 @@ bool stdio_client::start_server_process() {
|
|||
|
||||
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
|
||||
|
||||
// 准备环境变量
|
||||
// Prepare environment variables
|
||||
std::string env_block;
|
||||
if (!env_vars_.empty()) {
|
||||
for (const auto& [key, value] : env_vars_.items()) {
|
||||
|
@ -282,21 +282,21 @@ bool stdio_client::start_server_process() {
|
|||
env_block += '\0';
|
||||
}
|
||||
|
||||
// 创建子进程
|
||||
// Create child process
|
||||
std::string cmd_line = command_;
|
||||
char* cmd_str = const_cast<char*>(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) {
|
||||
|
@ -308,12 +308,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;
|
||||
|
@ -321,13 +321,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;
|
||||
|
@ -340,7 +340,7 @@ bool stdio_client::start_server_process() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// 创建子进程
|
||||
// Create child process
|
||||
process_id_ = fork();
|
||||
|
||||
if (process_id_ == -1) {
|
||||
|
@ -353,9 +353,9 @@ bool stdio_client::start_server_process() {
|
|||
}
|
||||
|
||||
if (process_id_ == 0) {
|
||||
// 子进程
|
||||
// Child process
|
||||
|
||||
// 设置环境变量
|
||||
// Set environment variables
|
||||
if (!env_vars_.empty()) {
|
||||
for (const auto& [key, value] : env_vars_.items()) {
|
||||
std::string env_var = key + "=" + convert_to_string(value);
|
||||
|
@ -365,11 +365,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);
|
||||
|
@ -380,15 +380,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<std::string> args;
|
||||
std::istringstream iss(command_);
|
||||
std::string arg;
|
||||
|
@ -405,22 +405,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);
|
||||
|
||||
|
@ -453,14 +453,14 @@ bool stdio_client::start_server_process() {
|
|||
|
||||
running_ = true;
|
||||
|
||||
// 启动读取线程
|
||||
// Start read thread
|
||||
read_thread_ = std::make_unique<std::thread>(&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);
|
||||
|
@ -492,8 +492,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;
|
||||
|
@ -504,22 +504,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);
|
||||
|
@ -530,8 +530,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;
|
||||
|
@ -542,28 +542,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);
|
||||
|
@ -585,18 +585,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);
|
||||
|
@ -608,7 +608,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<std::mutex> lock(response_mutex_);
|
||||
|
@ -632,9 +632,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) {
|
||||
|
@ -645,7 +645,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) {
|
||||
|
@ -653,24 +653,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);
|
||||
|
@ -682,7 +682,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<std::mutex> lock(response_mutex_);
|
||||
|
@ -706,9 +706,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) {
|
||||
|
@ -717,12 +717,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));
|
||||
|
@ -744,7 +744,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<DWORD>(req_str.size()), &bytes_written, NULL);
|
||||
|
||||
|
@ -753,7 +753,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<ssize_t>(req_str.size())) {
|
||||
|
@ -762,12 +762,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<json> response_promise;
|
||||
std::future<json> response_future = response_promise.get_future();
|
||||
|
||||
|
@ -776,19 +776,22 @@ 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);
|
||||
|
||||
if (status == std::future_status::ready) {
|
||||
json response = response_future.get();
|
||||
|
||||
if (response.contains("isError") && response["isError"].get<bool>()) {
|
||||
int code = response["error"]["code"];
|
||||
std::string message = response["error"]["message"];
|
||||
|
||||
if (response.contains("isError") && response["isError"].is_boolean() && response["isError"].get<bool>()) {
|
||||
if (response.contains("error") && response["error"].is_object()) {
|
||||
const auto& err_obj = response["error"];
|
||||
int code = err_obj.contains("code") ? err_obj["code"].get<int>() : static_cast<int>(error_code::internal_error);
|
||||
std::string message = err_obj.value("message", "");
|
||||
// Handle error
|
||||
throw mcp_exception(static_cast<error_code>(code), message);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
|
|
|
@ -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)未被占用
|
||||
- 客户端和服务器测试会启动实际的服务器和客户端进行交互测试
|
||||
- 资源测试会在临时目录创建文件,测试完成后会自动清理
|
||||
- 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
|
Loading…
Reference in New Issue