cpp-mcp/src/mcp_client.cpp

230 lines
6.7 KiB
C++
Raw Normal View History

2025-03-08 01:50:39 +08:00
/**
* @file mcp_client.cpp
* @brief Implementation of the MCP client
2025-03-08 22:49:19 +08:00
*
* This file implements the client-side functionality for the Model Context Protocol.
* Follows the 2024-11-05 basic protocol specification.
2025-03-08 01:50:39 +08:00
*/
#include "mcp_client.h"
#include "base64.hpp"
namespace mcp {
2025-03-08 22:49:19 +08:00
client::client(const std::string& host, int port, const json& capabilities)
: host_(host), port_(port), capabilities_(capabilities) {
2025-03-08 01:50:39 +08:00
init_client();
}
client::~client() {
// httplib::Client will be automatically destroyed
}
void client::init_client() {
// Create the HTTP client
http_client_ = std::make_unique<httplib::Client>(host_.c_str(), port_);
// Set timeout
http_client_->set_connection_timeout(timeout_seconds_, 0);
http_client_->set_read_timeout(timeout_seconds_, 0);
http_client_->set_write_timeout(timeout_seconds_, 0);
}
2025-03-08 22:49:19 +08:00
bool client::initialize(const std::string& client_name, const std::string& client_version) {
// Create initialization request
request req = request::create("initialize", {
2025-03-09 15:45:09 +08:00
{"protocolVersion", MCP_VERSION},
{"capabilities", capabilities_},
2025-03-08 22:49:19 +08:00
{"clientInfo", {
{"name", client_name},
{"version", client_version}
}}
});
try {
// Send the request
json result = send_jsonrpc(req);
// Store server capabilities
server_capabilities_ = result["capabilities"];
// Send initialized notification
request notification = request::create_notification("initialized");
send_jsonrpc(notification);
return true;
} catch (const std::exception& e) {
// Initialization failed
return false;
}
}
void client::set_auth_token(const std::string& token) {
std::lock_guard<std::mutex> lock(mutex_);
auth_token_ = token;
2025-03-08 01:50:39 +08:00
// Add to default headers
2025-03-08 22:49:19 +08:00
set_header("Authorization", "Bearer " + auth_token_);
2025-03-08 01:50:39 +08:00
}
void client::set_header(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
default_headers_[key] = value;
}
void client::set_timeout(int timeout_seconds) {
std::lock_guard<std::mutex> lock(mutex_);
timeout_seconds_ = timeout_seconds;
// Update the client's timeout
http_client_->set_connection_timeout(timeout_seconds_, 0);
http_client_->set_read_timeout(timeout_seconds_, 0);
http_client_->set_write_timeout(timeout_seconds_, 0);
}
2025-03-08 22:49:19 +08:00
void client::set_capabilities(const json& capabilities) {
std::lock_guard<std::mutex> lock(mutex_);
capabilities_ = capabilities;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
response client::send_request(const std::string& method, const json& params) {
request req = request::create(method, params);
json result = send_jsonrpc(req);
2025-03-08 01:50:39 +08:00
2025-03-08 22:49:19 +08:00
response res;
res.jsonrpc = "2.0";
res.id = req.id;
res.result = result;
return res;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
void 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() {
return server_capabilities_;
2025-03-08 01:50:39 +08:00
}
2025-03-09 17:10:01 +08:00
json client::call_tool(const std::string& tool_name, const json& arguments) {
2025-03-08 22:49:19 +08:00
return send_request("tools/call", {
{"name", tool_name},
2025-03-09 17:10:01 +08:00
{"arguments", arguments}
2025-03-08 22:49:19 +08:00
}).result;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
std::vector<tool> client::get_tools() {
json tools_json = send_request("tools/list", {}).result;
std::vector<tool> tools;
if (tools_json.is_array()) {
for (const auto& tool_json : tools_json) {
tool t;
t.name = tool_json["name"];
t.description = tool_json["description"];
2025-03-09 17:10:01 +08:00
if (tool_json.contains("inputSchema")) {
t.parameters_schema = tool_json["inputSchema"];
2025-03-08 22:49:19 +08:00
}
tools.push_back(t);
}
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
return tools;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
json client::get_resource_metadata(const std::string& resource_path) {
return send_request("resources/metadata", {
{"path", resource_path}
}).result;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
json client::get_capabilities() {
return capabilities_;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
json client::access_resource(const std::string& resource_path, const json& params) {
json request_params = {
{"path", resource_path}
};
// Add any additional parameters
for (auto it = params.begin(); it != params.end(); ++it) {
request_params[it.key()] = it.value();
}
return send_request("resources/access", request_params).result;
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
json client::send_jsonrpc(const request& req) {
2025-03-08 01:50:39 +08:00
std::lock_guard<std::mutex> lock(mutex_);
2025-03-08 22:49:19 +08:00
// Convert request to JSON
json req_json = req.to_json();
std::string req_body = req_json.dump();
// Prepare headers
httplib::Headers headers;
headers.emplace("Content-Type", "application/json");
2025-03-08 01:50:39 +08:00
// Add default headers
for (const auto& [key, value] : default_headers_) {
2025-03-08 22:49:19 +08:00
headers.emplace(key, value);
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
// Send the request
auto result = http_client_->Post("/jsonrpc", headers, req_body, "application/json");
2025-03-08 01:50:39 +08:00
if (!result) {
// Error occurred
auto err = result.error();
switch (err) {
case httplib::Error::Connection:
2025-03-08 22:49:19 +08:00
throw mcp_exception(error_code::server_error_start, "Connection error");
2025-03-08 01:50:39 +08:00
case httplib::Error::Read:
2025-03-08 22:49:19 +08:00
throw mcp_exception(error_code::internal_error, "Read error");
2025-03-08 01:50:39 +08:00
case httplib::Error::Write:
2025-03-08 22:49:19 +08:00
throw mcp_exception(error_code::internal_error, "Write error");
2025-03-08 01:50:39 +08:00
case httplib::Error::ConnectionTimeout:
2025-03-08 22:49:19 +08:00
throw mcp_exception(error_code::server_error_start, "Timeout error");
2025-03-08 01:50:39 +08:00
default:
2025-03-08 22:49:19 +08:00
throw mcp_exception(error_code::internal_error,
2025-03-08 01:50:39 +08:00
"HTTP client error: " + std::to_string(static_cast<int>(err)));
}
}
2025-03-08 22:49:19 +08:00
// Check if it's a notification (no response expected)
if (req.is_notification()) {
return json::object();
2025-03-08 01:50:39 +08:00
}
2025-03-08 22:49:19 +08:00
// Parse response
try {
json res_json = json::parse(result->body);
// Check for error
if (res_json.contains("error")) {
int code = res_json["error"]["code"];
std::string message = res_json["error"]["message"];
throw mcp_exception(static_cast<error_code>(code), message);
}
// Return result
if (res_json.contains("result")) {
return res_json["result"];
} else {
return json::object();
}
} catch (const json::exception& e) {
throw mcp_exception(error_code::parse_error,
"Failed to parse JSON-RPC response: " + std::string(e.what()));
}
2025-03-08 01:50:39 +08:00
}
} // namespace mcp