diff --git a/README.md b/README.md index 4e0e074..7f578f4 100644 --- a/README.md +++ b/README.md @@ -12,48 +12,30 @@ ## 组件 -### 核心协议 (`mcp_message.h`, `mcp_message.cpp`) +MCP C++库包含以下主要组件: -定义MCP的基本结构和类型: -- 请求/响应处理 -- 错误代码 -- 工具定义 +### 核心组件 -### HTTP服务器 (`mcp_server.h`, `mcp_server.cpp`) +#### 客户端接口 (`mcp_client.h`) +定义了MCP客户端的抽象接口,所有具体的客户端实现都继承自这个接口。 -实现一个HTTP服务器,暴露MCP资源和工具: -- 在特定路径注册资源 -- 注册带有处理程序的工具 -- 处理传入的HTTP请求 -- 将请求路由到适当的资源 - -### 客户端 - -#### HTTP客户端 (`mcp_client.h`, `mcp_client.cpp`) - -实现连接到HTTP MCP服务器的HTTP客户端,符合SSE通信规范。 - -可使用[MCP Inspector](https://github.com/modelcontextprotocol/inspector)连接并测试: -![alt image](/examples/server_example.png) +#### SSE客户端 (`mcp_sse_client.h`, `mcp_sse_client.cpp`) +使用HTTP和Server-Sent Events (SSE)与MCP服务器通信的客户端实现。 #### Stdio客户端 (`mcp_stdio_client.h`, `mcp_stdio_client.cpp`) +使用标准输入/输出与MCP服务器通信的客户端实现,可以启动子进程并与之通信。 -通过标准输入/输出与MCP服务器通信的客户端: -- 启动本地服务器进程 -- 通过管道进行通信 -- 支持资源访问和工具调用 -- 适合与本地进程集成 +#### 消息处理 (`mcp_message.h`, `mcp_message.cpp`) +处理JSON-RPC消息的序列化和反序列化。 -### 资源 (`mcp_resource.h`, `mcp_resource.cpp`) +#### 工具管理 (`mcp_tool.h`, `mcp_tool.cpp`) +管理和调用MCP工具。 -提供常见资源类型的基本实现: -- 文件资源 -- API资源 +#### 资源管理 (`mcp_resource.h`, `mcp_resource.cpp`) +管理MCP资源。 -### 工具 (`mcp_tool.h`, `mcp_tool.cpp`) - -提供工具相关定义与功能: -- 工具构建器 +#### 服务器 (`mcp_server.h`, `mcp_server.cpp`) +实现MCP服务器功能。 ## 示例 @@ -119,7 +101,7 @@ server.start(true); // 阻塞模式 ```cpp // 连接到服务器 -mcp::client client("localhost", 8080); +mcp::sse_client client("localhost", 8080); // 初始化连接 client.initialize("My Client", "1.0.0"); @@ -132,6 +114,36 @@ mcp::json params = { mcp::json result = client.call_tool("hello", params); ``` +### 使用SSE客户端 + +SSE客户端使用HTTP和Server-Sent Events (SSE) 与MCP服务器通信。这是一种基于Web标准的通信方式,适合与支持HTTP/SSE的服务器通信。 + +```cpp +#include "mcp_sse_client.h" + +// 创建客户端,指定服务器地址和端口 +mcp::sse_client client("localhost", 8080); +// 或者使用基础URL +// mcp::sse_client client("http://localhost:8080"); + +// 设置认证令牌(如果需要) +client.set_auth_token("your_auth_token"); + +// 设置自定义请求头(如果需要) +client.set_header("X-Custom-Header", "value"); + +// 初始化客户端 +if (!client.initialize("My Client", "1.0.0")) { + // 初始化失败处理 +} + +// 调用工具 +json result = client.call_tool("tool_name", { + {"param1", "value1"}, + {"param2", 42} +}); +``` + ### 使用Stdio客户端 Stdio客户端可以与任何支持stdio传输的MCP服务器进行通信,例如: diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c38ceac..602fb80 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.10) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -set(TARGET client_example) -add_executable(${TARGET} client_example.cpp) +set(TARGET sse_client_example) +add_executable(${TARGET} sse_client_example.cpp) install(TARGETS ${TARGET} RUNTIME) target_link_libraries(${TARGET} PRIVATE mcp) if(OPENSSL_FOUND) @@ -11,6 +11,10 @@ if(OPENSSL_FOUND) endif() target_compile_features(${TARGET} PRIVATE cxx_std_17) +add_executable(stdio_client_example stdio_client_example.cpp) +target_link_libraries(stdio_client_example PRIVATE mcp) +target_include_directories(stdio_client_example PRIVATE ${CMAKE_SOURCE_DIR}/include) + set(TARGET server_example) add_executable(${TARGET} server_example.cpp) install(TARGETS ${TARGET} RUNTIME) @@ -18,15 +22,4 @@ target_link_libraries(${TARGET} PRIVATE mcp) if(OPENSSL_FOUND) target_link_libraries(${TARGET} PRIVATE ${OPENSSL_LIBRARIES}) endif() -target_compile_features(${TARGET} PRIVATE cxx_std_17) - -# Create a directory for files if it doesn't exist -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/files) - -# Copy example files if needed -# file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/example_files/ DESTINATION ${CMAKE_BINARY_DIR}/files) - -# 添加stdio客户端示例 -add_executable(stdio_client_example stdio_client_example.cpp) -target_link_libraries(stdio_client_example PRIVATE mcp) -target_include_directories(stdio_client_example PRIVATE ${CMAKE_SOURCE_DIR}/include) \ No newline at end of file +target_compile_features(${TARGET} PRIVATE cxx_std_17) \ No newline at end of file diff --git a/examples/client_example.cpp b/examples/sse_client_example.cpp similarity index 97% rename from examples/client_example.cpp rename to examples/sse_client_example.cpp index f38a41c..3df9b92 100644 --- a/examples/client_example.cpp +++ b/examples/sse_client_example.cpp @@ -6,13 +6,13 @@ * Follows the 2024-11-05 basic protocol specification. */ -#include "mcp_client.h" +#include "mcp_sse_client.h" #include #include int main() { // Create a client - mcp::client client("localhost", 8888); + mcp::sse_client client("localhost", 8888); // Set capabilites mcp::json capabilities = { diff --git a/include/mcp_client.h b/include/mcp_client.h index 6c1472f..25bf808 100644 --- a/include/mcp_client.h +++ b/include/mcp_client.h @@ -1,8 +1,8 @@ /** * @file mcp_client.h - * @brief MCP Client implementation + * @brief MCP Client interface * - * This file implements the client-side functionality for the Model Context Protocol. + * This file defines the interface for the Model Context Protocol clients. * Follows the 2024-11-05 protocol specification. */ @@ -13,48 +13,25 @@ #include "mcp_tool.h" #include "mcp_logger.h" -// Include the HTTP library -#include "httplib.h" - #include -#include #include #include -#include -#include -#include -#include -#include namespace mcp { /** * @class client - * @brief Client for connecting to MCP servers + * @brief Abstract interface for MCP clients * - * The client class provides functionality to connect to MCP servers, - * initialize the connection, and send/receive JSON-RPC messages. + * The client class defines the interface for all MCP client implementations, + * regardless of the transport mechanism used (HTTP/SSE, stdio, etc.). */ class client { public: /** - * @brief Constructor - * @param host The server host (e.g., "localhost", "example.com") - * @param port The server port - * @param sse_endpoint The endpoint for server-sent events + * @brief Virtual destructor */ - client(const std::string& host, int port = 8080, const std::string& sse_endpoint = "/sse"); - - /** - * @brief Constructor - * @param base_url The base URL of the server (e.g., "localhost:8080") - */ - client(const std::string& base_url, const std::string& sse_endpoint = "/sse"); - - /** - * @brief Destructor - */ - ~client(); + virtual ~client() = default; /** * @brief Initialize the connection with the server @@ -62,38 +39,19 @@ public: * @param client_version The version of the client * @return True if initialization was successful */ - bool initialize(const std::string& client_name, const std::string& client_version); + virtual bool initialize(const std::string& client_name, const std::string& client_version) = 0; /** * @brief Ping request * @return True if the server is alive */ - bool ping(); + virtual bool ping() = 0; - /** - * @brief Set authentication token - * @param token The authentication token - */ - void set_auth_token(const std::string& token); - - /** - * @brief Set a request header that will be sent with all requests - * @param key Header name - * @param value Header value - */ - void set_header(const std::string& key, const std::string& value); - - /** - * @brief Set timeout for requests - * @param timeout_seconds Timeout in seconds - */ - void set_timeout(int timeout_seconds); - /** * @brief Set client capabilities * @param capabilities The capabilities of the client */ - void set_capabilities(const json& capabilities); + virtual void set_capabilities(const json& capabilities) = 0; /** * @brief Send a request and wait for a response @@ -102,7 +60,7 @@ public: * @return The response * @throws mcp_exception on error */ - response send_request(const std::string& method, const json& params = json::object()); + virtual response send_request(const std::string& method, const json& params = json::object()) = 0; /** * @brief Send a notification (no response expected) @@ -110,14 +68,14 @@ public: * @param params The parameters to pass * @throws mcp_exception on error */ - void send_notification(const std::string& method, const json& params = json::object()); + virtual void send_notification(const std::string& method, const json& params = json::object()) = 0; /** * @brief Get server capabilities * @return The server capabilities * @throws mcp_exception on error */ - json get_server_capabilities(); + virtual json get_server_capabilities() = 0; /** * @brief Call a tool @@ -126,125 +84,53 @@ public: * @return The result of the tool call * @throws mcp_exception on error */ - json call_tool(const std::string& tool_name, const json& arguments = json::object()); + virtual json call_tool(const std::string& tool_name, const json& arguments = json::object()) = 0; /** * @brief Get available tools * @return List of available tools * @throws mcp_exception on error */ - std::vector get_tools(); + virtual std::vector get_tools() = 0; /** * @brief Get client capabilities * @return The client capabilities */ - json get_capabilities(); + virtual json get_capabilities() = 0; /** * @brief List available resources * @param cursor Optional cursor for pagination * @return List of resources */ - json list_resources(const std::string& cursor = ""); + virtual json list_resources(const std::string& cursor = "") = 0; /** * @brief Read a resource * @param resource_uri The URI of the resource * @return The resource content */ - json read_resource(const std::string& resource_uri); + virtual json read_resource(const std::string& resource_uri) = 0; /** * @brief Subscribe to resource changes * @param resource_uri The URI of the resource * @return Subscription result */ - json subscribe_to_resource(const std::string& resource_uri); + virtual json subscribe_to_resource(const std::string& resource_uri) = 0; /** * @brief List resource templates * @return List of resource templates */ - json list_resource_templates(); + virtual json list_resource_templates() = 0; /** - * @brief 检查服务器是否可访问 - * @return True if the server is accessible + * @brief Check if the client is running + * @return True if the client is running */ - bool check_server_accessible(); - -private: - // 初始化HTTP客户端 - void init_client(const std::string& host, int port); - void init_client(const std::string& base_url); - - // 打开SSE连接 - void open_sse_connection(); - - // 解析SSE数据 - bool parse_sse_data(const char* data, size_t length); - - // 关闭SSE连接 - void close_sse_connection(); - - // 发送JSON-RPC请求 - json send_jsonrpc(const request& req); - - // 服务器主机和端口 - std::string host_; - int port_ = 8080; - - // 或者使用基础URL - std::string base_url_; - - // SSE端点 - std::string sse_endpoint_ = "/sse"; - - // 消息端点 - std::string msg_endpoint_; - - // HTTP客户端 - std::unique_ptr http_client_; - - // SSE专用HTTP客户端 - std::unique_ptr sse_client_; - - // SSE线程 - std::unique_ptr sse_thread_; - - // SSE运行状态 - std::atomic sse_running_{false}; - - // 认证令牌 - std::string auth_token_; - - // 默认请求头 - std::map default_headers_; - - // 超时设置(秒) - int timeout_seconds_ = 30; - - // 客户端能力 - json capabilities_; - - // 服务器能力 - json server_capabilities_; - - // 互斥锁 - mutable std::mutex mutex_; - - // 条件变量,用于等待消息端点设置 - std::condition_variable endpoint_cv_; - - // 请求ID到Promise的映射,用于异步等待响应 - std::map> pending_requests_; - - // 响应处理互斥锁 - std::mutex response_mutex_; - - // 响应条件变量 - std::condition_variable response_cv_; + virtual bool is_running() const = 0; }; } // namespace mcp diff --git a/include/mcp_sse_client.h b/include/mcp_sse_client.h new file mode 100644 index 0000000..b9fb4b3 --- /dev/null +++ b/include/mcp_sse_client.h @@ -0,0 +1,259 @@ +/** + * @file mcp_client.h + * @brief MCP Client implementation + * + * This file implements the client-side functionality for the Model Context Protocol. + * Follows the 2024-11-05 protocol specification. + */ + +#ifndef MCP_SSE_CLIENT_H +#define MCP_SSE_CLIENT_H + +#include "mcp_client.h" +#include "mcp_message.h" +#include "mcp_tool.h" +#include "mcp_logger.h" + +// Include the HTTP library +#include "httplib.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mcp { + +/** + * @class client + * @brief Client for connecting to MCP servers + * + * The client class provides functionality to connect to MCP servers, + * initialize the connection, and send/receive JSON-RPC messages. + */ +class sse_client : public client { +public: + /** + * @brief Constructor + * @param host The server host (e.g., "localhost", "example.com") + * @param port The server port + * @param sse_endpoint The endpoint for server-sent events + */ + sse_client(const std::string& host, int port = 8080, const std::string& sse_endpoint = "/sse"); + + /** + * @brief Constructor + * @param base_url The base URL of the server (e.g., "localhost:8080") + */ + sse_client(const std::string& base_url, const std::string& sse_endpoint = "/sse"); + + /** + * @brief Destructor + */ + ~sse_client(); + + /** + * @brief Initialize the connection with the server + * @param client_name The name of the client + * @param client_version The version of the client + * @return True if initialization was successful + */ + bool initialize(const std::string& client_name, const std::string& client_version) override; + + /** + * @brief Ping request + * @return True if the server is alive + */ + bool ping() override; + + /** + * @brief Set authentication token + * @param token The authentication token + */ + void set_auth_token(const std::string& token); + + /** + * @brief Set a request header that will be sent with all requests + * @param key Header name + * @param value Header value + */ + void set_header(const std::string& key, const std::string& value); + + /** + * @brief Set timeout for requests + * @param timeout_seconds Timeout in seconds + */ + void set_timeout(int timeout_seconds); + + /** + * @brief Set client capabilities + * @param capabilities The capabilities of the client + */ + void set_capabilities(const json& capabilities) override; + + /** + * @brief Send a request and wait for a response + * @param method The method to call + * @param params The parameters to pass + * @return The response + * @throws mcp_exception on error + */ + response send_request(const std::string& method, const json& params = json::object()) override; + + /** + * @brief Send a notification (no response expected) + * @param method The method to call + * @param params The parameters to pass + * @throws mcp_exception on error + */ + void send_notification(const std::string& method, const json& params = json::object()) override; + + /** + * @brief Get server capabilities + * @return The server capabilities + * @throws mcp_exception on error + */ + json get_server_capabilities() override; + + /** + * @brief Call a tool + * @param tool_name The name of the tool to call + * @param arguments The arguments to pass to the tool + * @return The result of the tool call + * @throws mcp_exception on error + */ + json call_tool(const std::string& tool_name, const json& arguments = json::object()) override; + + /** + * @brief Get available tools + * @return List of available tools + * @throws mcp_exception on error + */ + std::vector get_tools() override; + + /** + * @brief Get client capabilities + * @return The client capabilities + */ + json get_capabilities() override; + + /** + * @brief List available resources + * @param cursor Optional cursor for pagination + * @return List of resources + */ + json list_resources(const std::string& cursor = "") override; + + /** + * @brief Read a resource + * @param resource_uri The URI of the resource + * @return The resource content + */ + json read_resource(const std::string& resource_uri) override; + + /** + * @brief Subscribe to resource changes + * @param resource_uri The URI of the resource + * @return Subscription result + */ + json subscribe_to_resource(const std::string& resource_uri) override; + + /** + * @brief List resource templates + * @return List of resource templates + */ + json list_resource_templates() override; + + /** + * @brief 检查服务器是否可访问 + * @return True if the server is accessible + */ + bool check_server_accessible(); + + /** + * @brief Check if the client is running + * @return True if the client is running + */ + bool is_running() const override; + +private: + // 初始化HTTP客户端 + void init_client(const std::string& host, int port); + void init_client(const std::string& base_url); + + // 打开SSE连接 + void open_sse_connection(); + + // 解析SSE数据 + bool parse_sse_data(const char* data, size_t length); + + // 关闭SSE连接 + void close_sse_connection(); + + // 发送JSON-RPC请求 + json send_jsonrpc(const request& req); + + // 服务器主机和端口 + std::string host_; + int port_ = 8080; + + // 或者使用基础URL + std::string base_url_; + + // SSE端点 + std::string sse_endpoint_ = "/sse"; + + // 消息端点 + std::string msg_endpoint_; + + // HTTP客户端 + std::unique_ptr http_client_; + + // SSE专用HTTP客户端 + std::unique_ptr sse_client_; + + // SSE线程 + std::unique_ptr sse_thread_; + + // SSE运行状态 + std::atomic sse_running_{false}; + + // 认证令牌 + std::string auth_token_; + + // 默认请求头 + std::map default_headers_; + + // 超时设置(秒) + int timeout_seconds_ = 30; + + // 客户端能力 + json capabilities_; + + // 服务器能力 + json server_capabilities_; + + // 互斥锁 + mutable std::mutex mutex_; + + // 条件变量,用于等待消息端点设置 + std::condition_variable endpoint_cv_; + + // 请求ID到Promise的映射,用于异步等待响应 + std::map> pending_requests_; + + // 响应处理互斥锁 + std::mutex response_mutex_; + + // 响应条件变量 + std::condition_variable response_cv_; +}; + +} // namespace mcp + +#endif // MCP_SSE_CLIENT_H diff --git a/include/mcp_stdio_client.h b/include/mcp_stdio_client.h index dc80a13..5dc7935 100644 --- a/include/mcp_stdio_client.h +++ b/include/mcp_stdio_client.h @@ -10,6 +10,7 @@ #ifndef MCP_STDIO_CLIENT_H #define MCP_STDIO_CLIENT_H +#include "mcp_client.h" #include "mcp_message.h" #include "mcp_tool.h" #include "mcp_logger.h" @@ -39,7 +40,7 @@ namespace mcp { * The stdio_client class provides functionality to connect to MCP servers * by spawning a separate process and communicating via standard input/output. */ -class stdio_client { +class stdio_client : public client { public: /** * @brief Constructor @@ -54,7 +55,7 @@ public: /** * @brief Destructor */ - ~stdio_client(); + ~stdio_client() override; /** * @brief Set environment variables for the server process @@ -69,19 +70,19 @@ public: * @param client_version The version of the client * @return True if initialization was successful */ - bool initialize(const std::string& client_name, const std::string& client_version); + bool initialize(const std::string& client_name, const std::string& client_version) override; /** * @brief Ping request * @return True if the server is alive */ - bool ping(); + bool ping() override; /** * @brief Set client capabilities * @param capabilities The capabilities of the client */ - void set_capabilities(const json& capabilities); + void set_capabilities(const json& capabilities) override; /** * @brief Send a request and wait for a response @@ -90,7 +91,7 @@ public: * @return The response * @throws mcp_exception on error */ - response send_request(const std::string& method, const json& params = json::object()); + response send_request(const std::string& method, const json& params = json::object()) override; /** * @brief Send a notification (no response expected) @@ -98,14 +99,14 @@ public: * @param params The parameters to pass * @throws mcp_exception on error */ - void send_notification(const std::string& method, const json& params = json::object()); + void send_notification(const std::string& method, const json& params = json::object()) override; /** * @brief Get server capabilities * @return The server capabilities * @throws mcp_exception on error */ - json get_server_capabilities(); + json get_server_capabilities() override; /** * @brief Call a tool @@ -114,53 +115,53 @@ public: * @return The result of the tool call * @throws mcp_exception on error */ - json call_tool(const std::string& tool_name, const json& arguments = json::object()); + json call_tool(const std::string& tool_name, const json& arguments = json::object()) override; /** * @brief Get available tools * @return List of available tools * @throws mcp_exception on error */ - std::vector get_tools(); + std::vector get_tools() override; /** * @brief Get client capabilities * @return The client capabilities */ - json get_capabilities(); + json get_capabilities() override; /** * @brief List available resources * @param cursor Optional cursor for pagination * @return List of resources */ - json list_resources(const std::string& cursor = ""); + json list_resources(const std::string& cursor = "") override; /** * @brief Read a resource * @param resource_uri The URI of the resource * @return The resource content */ - json read_resource(const std::string& resource_uri); + json read_resource(const std::string& resource_uri) override; /** * @brief Subscribe to resource changes * @param resource_uri The URI of the resource * @return Subscription result */ - json subscribe_to_resource(const std::string& resource_uri); + json subscribe_to_resource(const std::string& resource_uri) override; /** * @brief List resource templates * @return List of resource templates */ - json list_resource_templates(); + json list_resource_templates() override; /** * @brief Check if the server process is running * @return True if the server process is running */ - bool is_running() const; + bool is_running() const override; private: // 启动服务器进程 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cc9917..0c323c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ set(TARGET mcp) add_library(${TARGET} STATIC - mcp_client.cpp ../include/mcp_client.h mcp_message.cpp ../include/mcp_message.h @@ -13,6 +12,8 @@ add_library(${TARGET} STATIC ../include/mcp_tool.h mcp_stdio_client.cpp ../include/mcp_stdio_client.h + mcp_sse_client.cpp + ../include/mcp_sse_client.h ) target_link_libraries(${TARGET} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) diff --git a/src/mcp_client.cpp b/src/mcp_sse_client.cpp similarity index 90% rename from src/mcp_client.cpp rename to src/mcp_sse_client.cpp index 760dd85..95efa4e 100644 --- a/src/mcp_client.cpp +++ b/src/mcp_sse_client.cpp @@ -1,32 +1,32 @@ /** - * @file mcp_client.cpp - * @brief Implementation of the MCP client + * @file mcp_sse_client.cpp + * @brief Implementation of the MCP SSE client * - * This file implements the client-side functionality for the Model Context Protocol. + * This file implements the client-side functionality for the Model Context Protocol using SSE. * Follows the 2024-11-05 basic protocol specification. */ -#include "mcp_client.h" +#include "mcp_sse_client.h" #include "base64.hpp" namespace mcp { -client::client(const std::string& host, int port, const std::string& sse_endpoint) +sse_client::sse_client(const std::string& host, int port, const std::string& sse_endpoint) : host_(host), port_(port), sse_endpoint_(sse_endpoint) { init_client(host, port); } -client::client(const std::string& base_url, const std::string& sse_endpoint) +sse_client::sse_client(const std::string& base_url, const std::string& sse_endpoint) : base_url_(base_url), sse_endpoint_(sse_endpoint) { init_client(base_url); } -client::~client() { +sse_client::~sse_client() { close_sse_connection(); } -void client::init_client(const std::string& host, int port) { +void sse_client::init_client(const std::string& host, int port) { http_client_ = std::make_unique(host.c_str(), port); sse_client_ = std::make_unique(host.c_str(), port); @@ -38,7 +38,7 @@ void client::init_client(const std::string& host, int port) { sse_client_->set_write_timeout(timeout_seconds_, 0); } -void client::init_client(const std::string& base_url) { +void sse_client::init_client(const std::string& base_url) { http_client_ = std::make_unique(base_url.c_str()); sse_client_ = std::make_unique(base_url.c_str()); @@ -51,7 +51,7 @@ void client::init_client(const std::string& base_url) { sse_client_->set_write_timeout(timeout_seconds_, 0); } -bool client::initialize(const std::string& client_name, const std::string& client_version) { +bool sse_client::initialize(const std::string& client_name, const std::string& client_version) { LOG_INFO("Initializing MCP client..."); if (!check_server_accessible()) { @@ -118,7 +118,7 @@ bool client::initialize(const std::string& client_name, const std::string& clien } } -bool client::ping() { +bool sse_client::ping() { request req = request::create("ping", {}); try { @@ -129,13 +129,13 @@ bool client::ping() { } } -void client::set_auth_token(const std::string& token) { +void sse_client::set_auth_token(const std::string& token) { std::lock_guard lock(mutex_); auth_token_ = token; set_header("Authorization", "Bearer " + auth_token_); } -void client::set_header(const std::string& key, const std::string& value) { +void sse_client::set_header(const std::string& key, const std::string& value) { std::lock_guard lock(mutex_); default_headers_[key] = value; @@ -147,7 +147,7 @@ void client::set_header(const std::string& key, const std::string& value) { } } -void client::set_timeout(int timeout_seconds) { +void sse_client::set_timeout(int timeout_seconds) { std::lock_guard lock(mutex_); timeout_seconds_ = timeout_seconds; @@ -162,12 +162,12 @@ void client::set_timeout(int timeout_seconds) { } } -void client::set_capabilities(const json& capabilities) { +void sse_client::set_capabilities(const json& capabilities) { std::lock_guard lock(mutex_); capabilities_ = capabilities; } -response client::send_request(const std::string& method, const json& params) { +response sse_client::send_request(const std::string& method, const json& params) { request req = request::create(method, params); json result = send_jsonrpc(req); @@ -179,23 +179,23 @@ response client::send_request(const std::string& method, const json& params) { return res; } -void client::send_notification(const std::string& method, const json& params) { +void sse_client::send_notification(const std::string& method, const json& params) { request req = request::create_notification(method, params); send_jsonrpc(req); } -json client::get_server_capabilities() { +json sse_client::get_server_capabilities() { return server_capabilities_; } -json client::call_tool(const std::string& tool_name, const json& arguments) { +json sse_client::call_tool(const std::string& tool_name, const json& arguments) { return send_request("tools/call", { {"name", tool_name}, {"arguments", arguments} }).result; } -std::vector client::get_tools() { +std::vector sse_client::get_tools() { json response_json = send_request("tools/list", {}).result; std::vector tools; @@ -223,11 +223,11 @@ std::vector client::get_tools() { return tools; } -json client::get_capabilities() { +json sse_client::get_capabilities() { return capabilities_; } -json client::list_resources(const std::string& cursor) { +json sse_client::list_resources(const std::string& cursor) { json params = json::object(); if (!cursor.empty()) { params["cursor"] = cursor; @@ -235,23 +235,23 @@ json client::list_resources(const std::string& cursor) { return send_request("resources/list", params).result; } -json client::read_resource(const std::string& resource_uri) { +json sse_client::read_resource(const std::string& resource_uri) { return send_request("resources/read", { {"uri", resource_uri} }).result; } -json client::subscribe_to_resource(const std::string& resource_uri) { +json sse_client::subscribe_to_resource(const std::string& resource_uri) { return send_request("resources/subscribe", { {"uri", resource_uri} }).result; } -json client::list_resource_templates() { +json sse_client::list_resource_templates() { return send_request("resources/templates/list").result; } -void client::open_sse_connection() { +void sse_client::open_sse_connection() { sse_running_ = true; { @@ -331,7 +331,7 @@ void client::open_sse_connection() { }); } -bool client::parse_sse_data(const char* data, size_t length) { +bool sse_client::parse_sse_data(const char* data, size_t length) { try { std::string sse_data(data, length); @@ -406,7 +406,7 @@ bool client::parse_sse_data(const char* data, size_t length) { } } -void client::close_sse_connection() { +void sse_client::close_sse_connection() { if (!sse_running_) { LOG_INFO("SSE connection already closed"); return; @@ -451,7 +451,7 @@ void client::close_sse_connection() { LOG_INFO("SSE connection successfully closed (normal exit flow)"); } -json client::send_jsonrpc(const request& req) { +json sse_client::send_jsonrpc(const request& req) { std::lock_guard lock(mutex_); if (msg_endpoint_.empty()) { @@ -561,7 +561,7 @@ json client::send_jsonrpc(const request& req) { } } -bool client::check_server_accessible() { +bool sse_client::check_server_accessible() { LOG_INFO("Checking if server is accessible..."); try { @@ -581,4 +581,8 @@ bool client::check_server_accessible() { } } -} // namespace mcp \ No newline at end of file +bool sse_client::is_running() const { + return sse_running_; +} + +} // namespace mcp diff --git a/test/googletest b/test/googletest index 6910c9d..b514bdc 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit 6910c9d9165801d8827d628cb72eb7ea9dd538c5 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 diff --git a/test/mcp_test.cpp b/test/mcp_test.cpp index a4c9252..4ab52d8 100644 --- a/test/mcp_test.cpp +++ b/test/mcp_test.cpp @@ -11,6 +11,7 @@ #include "mcp_client.h" #include "mcp_server.h" #include "mcp_tool.h" +#include "mcp_sse_client.h" using namespace mcp; using json = nlohmann::ordered_json; @@ -135,7 +136,7 @@ protected: {"roots", {{"listChanged", true}}}, {"sampling", json::object()} }; - client_ = std::make_unique("localhost", 8080); + client_ = std::make_unique("localhost", 8080); client_->set_capabilities(client_capabilities); } @@ -147,7 +148,7 @@ protected: } std::unique_ptr server_; - std::unique_ptr client_; + std::unique_ptr client_; }; // 测试初始化流程 @@ -186,7 +187,7 @@ protected: // 启动服务器(非阻塞模式) server_->start(false); - client_ = std::make_unique("localhost", 8081); + client_ = std::make_unique("localhost", 8081); } void TearDown() override { @@ -197,11 +198,12 @@ protected: } std::unique_ptr server_; - std::unique_ptr client_; + std::unique_ptr client_; }; // 测试支持的版本 TEST_F(VersioningTest, SupportedVersion) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 执行初始化 bool init_result = client_->initialize("TestClient", "1.0.0"); @@ -211,6 +213,7 @@ TEST_F(VersioningTest, SupportedVersion) { // 测试不支持的版本 TEST_F(VersioningTest, UnsupportedVersion) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); try { // 使用 httplib::Client 发送不支持的版本请求 std::unique_ptr sse_client = std::make_unique("localhost", 8081); @@ -233,7 +236,7 @@ TEST_F(VersioningTest, UnsupportedVersion) { size_t pos = response.find("data: "); if (pos != std::string::npos) { std::string data_content = response.substr(pos + 6); - data_content = data_content.substr(0, data_content.find("\n")); + data_content = data_content.substr(0, data_content.find("\r\n")); if (!msg_endpoint_received.load() && response.find("endpoint") != std::string::npos) { msg_endpoint_received.store(true); @@ -266,7 +269,7 @@ TEST_F(VersioningTest, UnsupportedVersion) { auto res = http_client->Post(endpoint.c_str(), req.dump(), "application/json"); EXPECT_TRUE(res != nullptr); - EXPECT_EQ(res->status, 202); + EXPECT_EQ(res->status / 100, 2); auto mcp_res = json::parse(sse_response.get()); EXPECT_EQ(mcp_res["error"]["code"].get(), static_cast(error_code::invalid_params)); @@ -320,7 +323,12 @@ protected: server_->start(false); // 创建客户端 - client_ = std::make_unique("localhost", 8082); + json client_capabilities = { + {"roots", {{"listChanged", true}}}, + {"sampling", json::object()} + }; + client_ = std::make_unique("localhost", 8082); + client_->set_capabilities(client_capabilities); } void TearDown() override { @@ -331,7 +339,7 @@ protected: } std::unique_ptr server_; - std::unique_ptr client_; + std::unique_ptr client_; }; // 测试Ping请求 @@ -365,7 +373,7 @@ TEST_F(PingTest, DirectPing) { size_t pos = response.find("data: "); if (pos != std::string::npos) { std::string data_content = response.substr(pos + 6); - data_content = data_content.substr(0, data_content.find("\n")); + data_content = data_content.substr(0, data_content.find("\r\n")); if (!msg_endpoint_received.load() && response.find("endpoint") != std::string::npos) { msg_endpoint_received.store(true); @@ -522,7 +530,12 @@ protected: server_->start(false); // 创建客户端 - client_ = std::make_unique("localhost", 8083); + json client_capabilities = { + {"roots", {{"listChanged", true}}}, + {"sampling", json::object()} + }; + client_ = std::make_unique("localhost", 8083); + client_->set_capabilities(client_capabilities); client_->initialize("TestClient", "1.0.0"); } @@ -534,7 +547,7 @@ protected: } std::unique_ptr server_; - std::unique_ptr client_; + std::unique_ptr client_; }; // 测试列出工具 @@ -553,6 +566,7 @@ TEST_F(ToolsTest, ListTools) { // 测试调用工具 TEST_F(ToolsTest, CallTool) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 调用工具 json tool_result = client_->call_tool("get_weather", {{"location", "New York"}});