/** * @file mcp_server.cpp * @brief Implementation of the MCP server */ #include "mcp_server.h" namespace mcp { server::server(const std::string& host, int port) : host_(host), port_(port), name_("MCP Server"), cors_enabled_(false), allowed_origins_("*") { http_server_ = std::make_unique(); } server::~server() { stop(); } bool server::start(bool blocking) { if (running_) { return true; // Already running } // Mount handlers for common paths // Root handler - returns server info http_server_->Get("/", [this](const httplib::Request& req, httplib::Response& res) { set_cors_headers(res); json info = get_server_info(); res.set_content(info.dump(), "application/json"); }); // Tools listing http_server_->Get("/tools", [this](const httplib::Request& req, httplib::Response& res) { set_cors_headers(res); json tools_json = get_tools(); res.set_content(tools_json.dump(), "application/json"); }); // General request handler for all other paths http_server_->set_mount_point("/", ""); http_server_->Get(".*", [this](const httplib::Request& req, httplib::Response& res) { this->handle_request(req, res); }); http_server_->Post(".*", [this](const httplib::Request& req, httplib::Response& res) { this->handle_request(req, res); }); http_server_->Put(".*", [this](const httplib::Request& req, httplib::Response& res) { this->handle_request(req, res); }); http_server_->Delete(".*", [this](const httplib::Request& req, httplib::Response& res) { this->handle_request(req, res); }); http_server_->Patch(".*", [this](const httplib::Request& req, httplib::Response& res) { this->handle_request(req, res); }); http_server_->Options(".*", [this](const httplib::Request& req, httplib::Response& res) { set_cors_headers(res); res.status = 204; // No content }); // Start the server if (blocking) { running_ = true; if (!http_server_->listen(host_.c_str(), port_)) { running_ = false; std::cerr << "Failed to start server on " << host_ << ":" << port_ << std::endl; return false; } return true; } else { // Start in a separate thread server_thread_ = std::make_unique([this]() { if (!http_server_->listen(host_.c_str(), port_)) { std::cerr << "Failed to start server on " << host_ << ":" << port_ << std::endl; running_ = false; return; } }); running_ = true; return true; } } void server::stop() { if (!running_) { return; } if (http_server_) { http_server_->stop(); } if (server_thread_ && server_thread_->joinable()) { server_thread_->join(); } running_ = false; } bool server::is_running() const { return running_; } void server::register_resource(const std::string& path, std::shared_ptr resource) { std::lock_guard lock(mutex_); resources_[path] = resource; } void server::register_tool(const tool& tool, tool_handler handler) { std::lock_guard lock(mutex_); tools_[tool.name] = std::make_pair(tool, handler); // Register a POST endpoint for the tool http_server_->Post("/tools/" + tool.name, [this, tool_name = tool.name]( const httplib::Request& req, httplib::Response& res) { set_cors_headers(res); // Check if the tool exists std::lock_guard lock(mutex_); auto it = tools_.find(tool_name); if (it == tools_.end()) { res.status = 404; json error = { {"error", { {"code", 404}, {"message", "Tool not found: " + tool_name} }} }; res.set_content(error.dump(), "application/json"); return; } // Parse the request body json params; try { if (!req.body.empty()) { params = json::parse(req.body); } } catch (const json::exception& e) { res.status = 400; json error = { {"error", { {"code", 400}, {"message", "Invalid JSON: " + std::string(e.what())} }} }; res.set_content(error.dump(), "application/json"); return; } // Execute the tool try { json result = it->second.second(params); res.set_content(result.dump(), "application/json"); } catch (const std::exception& e) { res.status = 500; json error = { {"error", { {"code", 500}, {"message", "Tool execution error: " + std::string(e.what())} }} }; res.set_content(error.dump(), "application/json"); } }); } json server::get_tools() const { std::lock_guard lock(mutex_); json tools_json = json::array(); for (const auto& [name, tool_pair] : tools_) { tools_json.push_back(tool_pair.first.to_json()); } return tools_json; } json server::get_server_info() const { std::lock_guard lock(mutex_); json info = { {"name", name_}, {"version", MCP_VERSION}, {"resources", json::array()}, {"tools_count", tools_.size()} }; // Add resources info for (const auto& [path, resource] : resources_) { json res_info = { {"path", path}, {"type", static_cast(resource->type())}, {"metadata", resource->metadata()} }; info["resources"].push_back(res_info); } return info; } void server::set_cors(bool enable, const std::string& allowed_origins) { cors_enabled_ = enable; allowed_origins_ = allowed_origins; } void server::set_name(const std::string& name) { std::lock_guard lock(mutex_); name_ = name; } void server::handle_request(const httplib::Request& req, httplib::Response& res) { set_cors_headers(res); // Convert the httplib request to our internal request format request mcp_req = convert_request(req); // Find a resource that matches the path std::shared_ptr resource_ptr = nullptr; std::string matched_path; { std::lock_guard lock(mutex_); // Look for an exact match first auto it = resources_.find(mcp_req.path); if (it != resources_.end()) { resource_ptr = it->second; matched_path = mcp_req.path; } else { // Look for a prefix match for (const auto& [path, res] : resources_) { if (mcp_req.path.find(path) == 0) { // This path is a prefix of the requested path if (matched_path.empty() || path.length() > matched_path.length()) { // Use the longest matching prefix resource_ptr = res; matched_path = path; } } } } } // Handle the request response mcp_res; if (resource_ptr) { try { // Adjust the path to be relative to the resource path if (!matched_path.empty() && matched_path != "/") { mcp_req.path = mcp_req.path.substr(matched_path.length()); // Ensure the path starts with a slash if (mcp_req.path.empty() || mcp_req.path[0] != '/') { mcp_req.path = "/" + mcp_req.path; } } // Handle the request mcp_res = resource_ptr->handle_request(mcp_req); } catch (const mcp_exception& e) { mcp_res.set_error(e.code(), e.what()); } catch (const std::exception& e) { mcp_res.set_error(error_code::internal_server_error, e.what()); } } else { // No resource found mcp_res.set_error(error_code::not_found, "Resource not found: " + mcp_req.path); } // Apply the response apply_response(mcp_res, res); } request server::convert_request(const httplib::Request& req) { request mcp_req; // Set method mcp_req.method = string_to_http_method(req.method); // Set path mcp_req.path = req.path; // Set headers for (const auto& [key, value] : req.headers) { mcp_req.headers[key] = value; } // Set query parameters for (const auto& [key, value] : req.params) { mcp_req.query_params[key] = value; } // Set body mcp_req.body = req.body; return mcp_req; } void server::apply_response(const response& mcp_res, httplib::Response& http_res) { // Set status code http_res.status = mcp_res.status_code; // Set headers for (const auto& [key, value] : mcp_res.headers) { http_res.set_header(key.c_str(), value.c_str()); } // Set body http_res.body = mcp_res.body; // Set content type if not already set if (http_res.get_header_value("Content-Type").empty() && !mcp_res.body.empty()) { // Guess content type based on the response body if (mcp_res.body[0] == '{' || mcp_res.body[0] == '[') { http_res.set_header("Content-Type", "application/json"); } else { http_res.set_header("Content-Type", "text/plain"); } } } void server::set_cors_headers(httplib::Response& res) { if (cors_enabled_) { res.set_header("Access-Control-Allow-Origin", allowed_origins_.c_str()); res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization"); res.set_header("Access-Control-Allow-Credentials", "true"); } } } // namespace mcp