24 uint32_t health_check_interval_ms)
25 : policy_(std::move(policy)),
26 health_check_interval_ms_(health_check_interval_ms) {}
45 const std::string& name,
48 std::lock_guard<std::mutex> lock(watched_mutex_);
50 entry.client = client;
51 entry.status = client->
is_connected() ?
"connected" :
"disconnected";
52 entry.reconnect_attempt = 0;
53 entry.next_action = std::chrono::steady_clock::now();
54 watched_[name] = entry;
56 logger->info(
"Watching server '{}'", name);
57 wake_cv_.notify_one();
67 std::lock_guard<std::mutex> lock(watched_mutex_);
69 logger->info(
"Unwatched server '{}'", name);
79 status_callback_ = std::move(cb);
92 monitor_thread_ = std::thread(
93 &HealthMonitor::monitor_loop,
this);
94 logger->info(
"Health monitor started");
103 if (!running_.exchange(
false)) {
106 wake_cv_.notify_all();
107 if (monitor_thread_.joinable()) {
108 monitor_thread_.join();
110 logger->info(
"Health monitor stopped");
125 std::vector<HealthEvent> events;
127 std::lock_guard<std::mutex> lock(event_mutex_);
128 events.swap(event_queue_);
131 for (
const auto& event : events) {
132 if (status_callback_) {
133 status_callback_(event);
143void HealthMonitor::monitor_loop() {
144 constexpr auto poll_interval = std::chrono::milliseconds(500);
148 std::lock_guard<std::mutex> lock(watched_mutex_);
149 auto now = std::chrono::steady_clock::now();
150 for (
auto& [name, entry] : watched_) {
151 if (now >= entry.next_action) {
152 check_server(name, entry);
157 std::unique_lock<std::mutex> lock(wake_mutex_);
158 wake_cv_.wait_for(lock, poll_interval,
159 [
this] {
return !running_.load(); });
174void HealthMonitor::check_server(
175 const std::string& name,
178 bool alive = entry.client->is_connected();
180 if (entry.status ==
"connected" && !alive) {
182 auto old = entry.status;
183 entry.status =
"disconnected";
184 entry.reconnect_attempt = 0;
185 post_event(name, old, entry.status);
186 logger->warn(
"Server '{}' disconnected", name);
187 attempt_reconnect(name, entry);
191 if (entry.status ==
"disconnected" ||
192 entry.status ==
"reconnecting") {
193 attempt_reconnect(name, entry);
198 if (health_check_interval_ms_ > 0) {
199 entry.next_action = std::chrono::steady_clock::now() +
200 std::chrono::milliseconds(health_check_interval_ms_);
202 entry.next_action = std::chrono::steady_clock::now() +
203 std::chrono::seconds(5);
219void HealthMonitor::attempt_reconnect(
220 const std::string& name,
223 if (policy_.
exhausted(entry.reconnect_attempt)) {
224 if (entry.status !=
"error") {
225 auto old = entry.status;
226 entry.status =
"error";
227 post_event(name, old,
"error");
228 logger->error(
"Server '{}' reconnection exhausted "
230 name, entry.reconnect_attempt);
232 entry.next_action = std::chrono::steady_clock::time_point::max();
236 if (entry.status !=
"reconnecting") {
237 auto old = entry.status;
238 entry.status =
"reconnecting";
239 post_event(name, old,
"reconnecting");
242 logger->info(
"Reconnecting to '{}' (attempt {})",
243 name, entry.reconnect_attempt + 1);
245 if (entry.client->connect()) {
246 on_reconnect_success(name, entry);
251 auto delay = policy_.
delay_ms(entry.reconnect_attempt);
252 entry.reconnect_attempt++;
253 entry.next_action = std::chrono::steady_clock::now() +
254 std::chrono::milliseconds(delay);
256 logger->info(
"Server '{}' reconnect failed, "
257 "retry in {}ms", name, delay);
273void HealthMonitor::on_reconnect_success(
const std::string& name,
275 auto [added, removed] = entry.client->refresh_tools();
276 entry.status =
"connected";
277 entry.reconnect_attempt = 0;
280 evt.server_name = name;
281 evt.old_status =
"reconnecting";
282 evt.new_status =
"connected";
283 evt.added_tools = std::move(added);
284 evt.removed_tools = std::move(removed);
286 std::lock_guard<std::mutex> lock(event_mutex_);
287 event_queue_.push_back(std::move(evt));
290 logger->info(
"Server '{}' reconnected", name);
291 entry.next_action = std::chrono::steady_clock::now();
306void HealthMonitor::post_event(
307 const std::string& name,
308 const std::string& old_status,
309 const std::string& new_status) {
312 evt.server_name = name;
313 evt.old_status = old_status;
314 evt.new_status = new_status;
316 std::lock_guard<std::mutex> lock(event_mutex_);
317 event_queue_.push_back(std::move(evt));
Client for an external MCP server (stdio or SSE).
bool is_connected() const
Check connection state.
void start()
Start the monitoring thread.
void watch(const std::string &name, ExternalMCPClient *client)
Start monitoring a server.
void stop()
Stop monitoring and all reconnection attempts.
HealthMonitor(ReconnectPolicy policy, uint32_t health_check_interval_ms=0)
Construct with reconnection policy.
std::function< void(const HealthEvent &)> StatusCallback
Callback invoked on engine thread when processing events.
void process_events()
Drain event queue, invoke callbacks (call on engine thread).
~HealthMonitor()
Destructor — stops monitor if running.
void set_status_callback(StatusCallback cb)
Set callback for status change events.
void unwatch(const std::string &name)
Stop monitoring a server.
Exponential backoff with jitter for reconnection attempts.
bool exhausted(uint32_t attempt) const
Check if retries are exhausted.
uint32_t delay_ms(uint32_t attempt) const
Compute delay for the given attempt number.
Monitors external MCP server health and triggers reconnection.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Activate model on GPU (WARM → ACTIVE).