main
hkr04 2025-03-24 04:20:19 +08:00
commit 3fe1e049fb
6 changed files with 280 additions and 284 deletions

162
README.md
View File

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

View File

@ -18,7 +18,7 @@ add_library(${TARGET} STATIC
target_link_libraries(${TARGET} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
# OpenSSLOpenSSL
# If OpenSSL is found, link the OpenSSL libraries
if(OPENSSL_FOUND)
target_link_libraries(${TARGET} PUBLIC ${OPENSSL_LIBRARIES})
endif()

View File

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

View File

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

View File

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

View File

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