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
|