#include "Control_Service.hpp" #include "Control_Service.ipp" #include "Control_Requests.hpp" #include "Protocol_Type.hpp" #include "runtime/Datoviz_Observation_Json.hpp" #include "runtime/Gallery_Plots.hpp" #include "runtime/Input_Event.hpp" #include "runtime/Taskflow_Trace_Json.hpp" #include #include #include #include #include #include #include #include #include namespace aethera::mcp { namespace { using Invoke = Tool_Call_Output (*)(Control_Service&, const nlohmann::json&); using Schema = nlohmann::json (*)(); struct Operation { std::string_view name; /* Stable MCP tool name. */ std::string_view description; /* Business description used for tool selection. */ Schema schema; /* Protocol input schema producer. */ Invoke invoke; /* Protocol-independent business entry point. */ }; struct Control_Stop_State final { explicit Control_Stop_State(std::size_t count, std::function value_completion) : remaining(count), completion(std::move(value_completion)) {} void retired() noexcept { if (remaining.fetch_sub(1, std::memory_order_acq_rel) != 1) return; try { completion(); } catch (...) { std::terminate(); } } std::atomic_size_t remaining; std::function completion; }; template [[nodiscard]] nlohmann::json request_schema() { return describe_protocol_type(); } template [[nodiscard]] Tool_Call_Output decode_and_call(const nlohmann::json& arguments, Callback&& callback) { try { Request request{}; decode_protocol_value(request, arguments); return std::forward(callback)(request); } catch (const nlohmann::json::exception& failure) { return {Tool_Call_Result::invalid_arguments, {}, failure.what()}; } catch (const std::invalid_argument& failure) { return {Tool_Call_Result::invalid_arguments, {}, failure.what()}; } } [[nodiscard]] nlohmann::json statistics_json(const Statistics_Summary& summary) { return {{"sample_count", summary.sample_count}, {"average_ns", summary.average}, {"p95_ns", summary.p95}, {"standard_deviation_ns", summary.standard_deviation}}; } [[nodiscard]] std::string render_error_name(const scene::Render_Error& error) { return std::visit([](auto value) { return std::string{magic_enum::enum_name(value)}; }, error); } [[nodiscard]] nlohmann::json frame_policy_state_json(const Frame_Policy::State& state) { return { {"timer_ticks", state.timer_ticks}, {"dropped_timer_ticks", state.dropped_timer_ticks}, {"completed_frames", state.completed_frames}, {"effective_frames_per_second", state.effective_frames_per_second}, {"render_capacity_fps", state.render_capacity_fps ? nlohmann::json{*state.render_capacity_fps} : nlohmann::json{nullptr}}, {"send_capacity_fps", state.send_capacity_fps ? nlohmann::json{*state.send_capacity_fps} : nlohmann::json{nullptr}}, {"render_time", statistics_json(state.render_time_ns)}, {"send_time", statistics_json(state.send_time_ns)}, {"end_to_end_time", statistics_json(state.end_to_end_time_ns)}, {"configuration_error", state.configuration_error ? nlohmann::json{magic_enum::enum_name(*state.configuration_error)} : nlohmann::json{nullptr}}, {"render_submission_error", state.render_submission_error ? nlohmann::json{render_error_name(*state.render_submission_error)} : nlohmann::json{nullptr}} }; } [[nodiscard]] nlohmann::json frame_policy_schema(const Throttled_Latest_only& policy) { const auto user_rate = policy.get(&Frame_Policy::Prop::user_frames_per_second); return { {"id", "frame-analysis"}, {"label", "Frame pipeline"}, {"kind", "analysis"}, {"fields", nlohmann::json::array({ {{"key", "user_frames_per_second"}, {"editor", "number"}, {"editable", true}, {"minimum", 0.1}, {"value", user_rate ? nlohmann::json{*user_rate} : nlohmann::json{nullptr}}}, {{"key", "render_rate_limit_enabled"}, {"editor", "boolean"}, {"editable", true}, {"value", policy.get(&Frame_Policy::Prop::render_rate_limit_enabled)}}, {{"key", "send_rate_limit_enabled"}, {"editor", "boolean"}, {"editable", true}, {"value", policy.get(&Frame_Policy::Prop::send_rate_limit_enabled)}}, {{"key", "statistics_enabled"}, {"editor", "boolean"}, {"editable", true}, {"value", policy.get(&Frame_Policy::Prop::statistics_enabled)}}, {{"key", "statistics_window_size"}, {"editor", "integer"}, {"editable", true}, {"minimum", 1}, {"value", policy.get(&Frame_Policy::Prop::statistics_window_size)}} })} }; } [[nodiscard]] Tool_Call_Output list_plots(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto&) { return Tool_Call_Output{Tool_Call_Result::ok, service.plot_catalog(), {}}; }); } [[nodiscard]] Tool_Call_Output plot_schema(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; return Tool_Call_Output{Tool_Call_Result::ok, service.plot_schema(request.plot), {}}; }); } [[nodiscard]] Tool_Call_Output plot_diagnostics(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; return Tool_Call_Output{Tool_Call_Result::ok, service.plot_diagnostics(request.plot), {}}; }); } [[nodiscard]] Tool_Call_Output component_state(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; auto result = service.plot_component_state(request.plot, request.component); if (!result.value("success", true)) return Tool_Call_Output{Tool_Call_Result::rejected, std::move(result), "unknown component"}; return Tool_Call_Output{Tool_Call_Result::ok, std::move(result), {}}; }); } [[nodiscard]] Tool_Call_Output write_property(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; auto result = service.write_plot_property(request.plot, request.component, request.property, request.value); const bool success = result.value("success", false); return Tool_Call_Output{success ? Tool_Call_Result::ok : Tool_Call_Result::rejected, std::move(result), success ? "" : "property write rejected"}; }); } [[nodiscard]] Tool_Call_Output generate_data(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; auto result = service.generate_plot_data(request.plot, request.input); const bool success = result.value("success", false); return Tool_Call_Output{success ? Tool_Call_Result::ok : Tool_Call_Result::rejected, std::move(result), success ? "" : "data generation rejected"}; }); } [[nodiscard]] Tool_Call_Output submit_plot_input(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](auto request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; const auto time_milliseconds = request.event.at("time_milliseconds").get(); service.submit_input(request.plot, web::make_input_event(request.event)); return Tool_Call_Output{Tool_Call_Result::ok, {{"accepted", true}, {"plot", request.plot}, {"time_milliseconds", time_milliseconds}}, {}}; }); } [[nodiscard]] Tool_Call_Output render_plot_at(Control_Service& service, const nlohmann::json& arguments) { return decode_and_call(arguments, [&service](const auto& request) { if (!service.contains_plot(request.plot)) return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"}; service.request_frame(request.plot, {.issued_at = std::chrono::steady_clock::now(), .time_milliseconds = request.time_milliseconds, .width = request.width, .height = request.height}); return Tool_Call_Output{Tool_Call_Result::ok, {{"accepted", true}, {"plot", request.plot}, {"time_milliseconds", request.time_milliseconds}, {"width", request.width}, {"height", request.height}}, {}}; }); } constexpr std::array operations{ Operation{"aethera_plot_list", "List every 2D and 3D gallery plot.", &request_schema, &list_plots}, Operation{"aethera_plot_schema", "Describe a plot and its editable render components.", &request_schema, &plot_schema}, Operation{"aethera_plot_diagnostics", "Read the current adaptive Frame Policy and backend diagnostics.", &request_schema, &plot_diagnostics}, Operation{"aethera_component_state", "Read one render component's published state.", &request_schema, &component_state}, Operation{"aethera_component_write", "Write one editable render component property.", &request_schema, &write_property}, Operation{"aethera_data_generate", "Generate input data for a gallery plot.", &request_schema, &generate_data}, Operation{"aethera_plot_input", "Submit one timestamped input event without waiting for rendering.", &request_schema, &submit_plot_input}, Operation{"aethera_plot_render_at", "Replace the latest pending plot request consumed by a subsequent policy tick.", &request_schema, &render_plot_at} }; scene::Render_Result map_render_result(const render_2d::Render_Scene_2D_Result& result) { if (result) return {}; return std::visit([](auto error) -> scene::Render_Result { using Error = decltype(error); if constexpr (std::same_as) { switch (error) { case render_2d::Render_Scene_2D_State_Result::frame_missing: return std::unexpected(scene::Render_Error{scene::Render_State_Result::frame_missing}); case render_2d::Render_Scene_2D_State_Result::completion_missing: return std::unexpected(scene::Render_Error{scene::Render_State_Result::completion_missing}); case render_2d::Render_Scene_2D_State_Result::view_inactive: return std::unexpected(scene::Render_Error{scene::Render_State_Result::view_inactive}); case render_2d::Render_Scene_2D_State_Result::empty_viewport: return std::unexpected(scene::Render_Error{scene::Render_State_Result::empty_viewport}); } std::terminate(); } else { return std::unexpected(scene::Render_Error{error}); } }, result.error()); } scene::Render_Result map_render_result(render_3d::Render_Scene_3D::Render_Result result) { using Result = render_3d::Render_Scene_3D::Render_Result; switch (result) { case Result::submitted: return {}; case Result::frame_missing: return std::unexpected(scene::Render_Error{scene::Render_State_Result::frame_missing}); case Result::completion_missing: return std::unexpected(scene::Render_Error{scene::Render_State_Result::completion_missing}); case Result::view_inactive: return std::unexpected(scene::Render_Error{scene::Render_State_Result::view_inactive}); case Result::empty_viewport: return std::unexpected(scene::Render_Error{scene::Render_State_Result::empty_viewport}); case Result::backend_unavailable: return std::unexpected(scene::Render_Error{scene::Render_State_Result::backend_unavailable}); } std::terminate(); } } // namespace proxy Control_Service::Private::Policy_Scene::Private::create_frame() { return entry->create_frame(); } scene::Render_Result Control_Service::Private::Policy_Scene::Private::render(proxy& frame, scene::Render_Completion completion) { return entry->render(frame, std::move(completion)); } Control_Service::Private::Entry::Entry(std::string value_id, web::Gallery_Build build, proxy value_sink, std::uint32_t width, std::uint32_t height) : components(std::move(build.first)), scene(std::move(build.second)), sink(std::move(value_sink)), id(std::move(value_id)), output_width(width), output_height(height) {} Control_Service::Private::Entry::~Entry() = default; Control_Service::Private::Entry_Retirement::~Entry_Retirement() noexcept { entry.reset(); if (!completion) return; try { completion(); } catch (...) { std::terminate(); } } std::shared_ptr Control_Service::Private::Entry::current_policy() const noexcept { return policy.load(std::memory_order_acquire); } void Control_Service::Private::Entry::start() { auto timer = frame_policy::make_timer_service(); auto scene_endpoint = make_model_proxy(this); Throttled_Latest_only::Builder builder(std::move(timer), std::move(scene_endpoint), std::move(sink)); builder .set(&Frame_Policy::Prop::user_frames_per_second, std::optional{100.0}) .set(&Frame_Policy::Prop::render_rate_limit_enabled, false) .set(&Frame_Policy::Prop::send_rate_limit_enabled, false) .set(&Frame_Policy::Prop::statistics_enabled, true) .set(&Frame_Policy::Prop::statistics_window_size, std::size_t{120}); auto next = std::shared_ptr{builder.build().release()}; const auto started = next->start(); if (!started) throw std::logic_error("Gallery Frame Policy rejected start"); policy.store(std::move(next), std::memory_order_release); } void Control_Service::Private::Entry::stop(std::shared_ptr retirement) noexcept { auto current = policy.exchange({}, std::memory_order_acq_rel); if (!current) return; auto lifetime = std::make_shared(); lifetime->retirement = std::move(retirement); lifetime->policy = std::move(current); const auto stopped = lifetime->policy->stop([lifetime] {}); if (!stopped) std::terminate(); } void Control_Service::Private::Entry::request_frame(web::Gallery_Frame_Request request) { if (!std::isfinite(request.time_milliseconds) || request.width == 0 || request.height == 0) throw std::invalid_argument("Gallery frame request is invalid"); if (request.issued_at == std::chrono::steady_clock::time_point{}) request.issued_at = std::chrono::steady_clock::now(); requested_frame.store(std::make_shared(std::move(request)), std::memory_order_release); } bool Control_Service::Private::Entry::reserve_trace() noexcept { auto remaining = taskflow_trace_remaining.load(std::memory_order_acquire); while (remaining != 0) if (taskflow_trace_remaining.compare_exchange_weak(remaining, remaining - 1, std::memory_order_acq_rel, std::memory_order_acquire)) return true; return false; } void Control_Service::Private::Entry::restore_trace() noexcept { taskflow_trace_remaining.fetch_add(1, std::memory_order_release); } void Control_Service::Private::Entry::store_trace(const Taskflow_Execution_Trace& trace, const web::Gallery_Frame_Request& request, const nlohmann::json& backend) { auto value = web::taskflow_trace_json(trace, request.sequence, request.correlation_id, {}, backend); if (const auto current = current_policy()) current->get([&](const Frame_Policy::State& state) { value["frame_policy"] = frame_policy_state_json(state); }); auto encoded = std::make_shared(std::move(value)); auto control = taskflow_trace_control.load(std::memory_order_acquire); for (;;) { const auto requested = static_cast(control >> 32U); const auto captured = static_cast(control); if (captured >= requested) return; taskflow_trace_slots[captured].store(encoded, std::memory_order_release); const auto next = (static_cast(requested) << 32U) | static_cast(captured + 1U); if (taskflow_trace_control.compare_exchange_weak(control, next, std::memory_order_release, std::memory_order_acquire)) return; } } proxy Control_Service::Private::Entry::create_frame() { const bool use_3d = std::holds_alternative>(scene); return pro::make_proxy(use_3d); } scene::Render_Result Control_Service::Private::Entry::render(proxy& frame, scene::Render_Completion completion) { auto* slot = frame ? proxy_cast(&*frame) : nullptr; if (!slot) return std::unexpected(scene::Render_Error{scene::Render_State_Result::frame_missing}); if (!completion) return std::unexpected(scene::Render_Error{scene::Render_State_Result::completion_missing}); auto request_owner = requested_frame.exchange({}, std::memory_order_acq_rel); web::Gallery_Frame_Request request = request_owner ? *request_owner : web::Gallery_Frame_Request{}; if (!request_owner) { request.issued_at = std::chrono::steady_clock::now(); request.time_milliseconds = std::chrono::duration(request.issued_at - started_at).count(); request.width = output_width; request.height = output_height; } request.sequence = next_sequence.fetch_add(1, std::memory_order_relaxed) + 1; if (request.correlation_id == 0) request.correlation_id = request.sequence; slot->request = request; ++slot->generation; slot->taskflow_trace_reserved = reserve_trace(); components->update(request); auto* policy_frame = std::addressof(frame); /* Non-owning pointer to a stable policy slot; Frame Policy keeps it alive until completion returns. */ if (auto* native = std::get_if>(&slot->native)) { auto& scene_2d = *std::get>(scene); scene_2d.set(&render_2d::Render_Scene_2D::Prop::viewport, render_2d::Size{static_cast(request.width), static_cast(request.height)}); (*native)->begin({request.sequence, slot->generation}, render_2d::Frame_2D::native_pixel_format); if (slot->taskflow_trace_reserved) (*native)->request_taskflow_trace(); auto result = map_render_result(scene_2d.render(native->get(), [this, slot, policy_frame, completion = std::move(completion)](render_2d::Frame_2D*, std::exception_ptr failure) mutable { if (std::exchange(slot->taskflow_trace_reserved, false)) { if (!failure) { if (auto trace = std::get>(slot->native)->take_taskflow_trace()) store_trace(*trace, slot->request); else restore_trace(); } else restore_trace(); } completion(*policy_frame, std::move(failure)); })); if (!result && std::exchange(slot->taskflow_trace_reserved, false)) restore_trace(); return result; } auto& scene_3d = *std::get>(scene); auto& native = std::get>(slot->native); scene_3d.set(&render_3d::Render_Scene_3D::Prop::viewport, render_3d::Extent{request.width, request.height}); native->begin({request.sequence, slot->generation}, render_3d::Frame_3D_Output::pixels, render_3d::Frame_3D::native_pixel_format); if (slot->taskflow_trace_reserved) native->request_taskflow_trace(); auto result = map_render_result(scene_3d.render(native.get(), [this, slot, policy_frame, completion = std::move(completion)](render_3d::Frame_3D*, std::exception_ptr failure) mutable { auto& completed = std::get>(slot->native); nlohmann::json backend; if (!failure) { if (auto observation = completed->take_datoviz_observation()) { backend = datoviz_observation_json(*observation); datoviz_observation.store(std::make_shared(backend), std::memory_order_release); } } if (std::exchange(slot->taskflow_trace_reserved, false)) { if (!failure) { if (auto trace = completed->take_taskflow_trace()) store_trace(*trace, slot->request, backend); else restore_trace(); } else restore_trace(); } completion(*policy_frame, std::move(failure)); })); if (!result && std::exchange(slot->taskflow_trace_reserved, false)) restore_trace(); return result; } nlohmann::json Control_Service::Private::Entry::schema() const { auto result = components->schema(); if (const auto current = current_policy()) { auto analysis = frame_policy_schema(*current); const auto generator = components->data_generator_schema(); if (!generator.is_null()) analysis["data_generator"] = generator; result["frame_analysis"] = std::move(analysis); } return result; } nlohmann::json Control_Service::Private::Entry::diagnostics() const { const auto current = current_policy(); if (!current) return {{"protocol", "aethera.plot.diagnostics"}, {"version", 6}, {"available", false}, {"plot", id}}; nlohmann::json result; current->get([&](const Frame_Policy::State& state) { result = { {"protocol", "aethera.plot.diagnostics"}, {"version", 6}, {"available", true}, {"plot", id}, {"dimension", std::holds_alternative>(scene) ? "3D" : "2D"}, {"frame_policy", frame_policy_state_json(state)} }; }); if (const auto observation = datoviz_observation.load(std::memory_order_acquire)) result["datoviz"] = *observation; return result; } void Control_Service::Private::Entry::reset_diagnostics() noexcept { datoviz_observation.store({}, std::memory_order_release); } void Control_Service::Private::Entry::request_trace(std::size_t frame_count) { if (frame_count == 0 || frame_count > maximum_taskflow_trace_frames) throw std::invalid_argument("Plot Taskflow trace frame_count must be between 1 and 120"); auto control = taskflow_trace_control.load(std::memory_order_acquire); for (;;) { const auto requested = static_cast(control >> 32U); const auto captured = static_cast(control); if (requested != captured) throw std::logic_error("A Plot Taskflow trace request is already active"); const auto next = static_cast(frame_count) << 32U; if (taskflow_trace_control.compare_exchange_weak(control, next, std::memory_order_release, std::memory_order_acquire)) break; } for (auto& slot : taskflow_trace_slots) slot.store({}, std::memory_order_release); taskflow_trace_remaining.store(frame_count, std::memory_order_release); } nlohmann::json Control_Service::Private::Entry::trace() const { nlohmann::json frames = nlohmann::json::array(); const auto control = taskflow_trace_control.load(std::memory_order_acquire); const auto requested = static_cast(control >> 32U); const auto captured = static_cast(control); for (std::uint32_t index = 0; index < captured; ++index) if (const auto frame = taskflow_trace_slots[index].load(std::memory_order_acquire)) frames.push_back(*frame); return {{"protocol", "aethera.taskflow.frames"}, {"version", 1}, {"requested", requested}, {"remaining", taskflow_trace_remaining.load(std::memory_order_acquire)}, {"captured", frames.size()}, {"complete", requested != 0 && frames.size() == requested}, {"frames", std::move(frames)}}; } Control_Service::Control_Service() : d(std::make_unique()) {} Control_Service::~Control_Service() { if (!d || d->stop_started) return; try { stop([] {}); } catch (...) { std::terminate(); } } std::shared_ptr Control_Service::create(Gallery_Output output) { auto service = std::shared_ptr(new Control_Service); service->d->plots.reserve(web::gallery_plot_definitions().size()); for (const auto& definition : web::gallery_plot_definitions()) { auto sink = output.make_sink ? output.make_sink(definition.id) : proxy{}; /* Headless MCP and partial test catalogs intentionally complete frames without media. */ if (!sink) sink = make_model_proxy(); auto entry = std::make_shared(std::string{definition.id}, definition.create(), std::move(sink), output.width, output.height); service->d->plots.emplace(entry->id, entry); entry->start(); } return service; } bool Control_Service::contains_plot(std::string_view id) const noexcept { return d->plots.contains(std::string{id}); } void Control_Service::stop(std::function completion) { if (!completion) throw std::invalid_argument("Control Service stop completion is empty"); if (d->stop_started) throw std::logic_error("Control Service stop already started"); d->stop_started = true; if (d->plots.empty()) { completion(); return; } auto state = std::make_shared(d->plots.size(), std::move(completion)); std::vector> retiring; retiring.reserve(d->plots.size()); for (auto& [id, entry] : d->plots) { (void)id; auto retirement = std::make_shared(); retirement->entry = std::move(entry); retirement->completion = [state] { state->retired(); }; retiring.push_back(std::move(retirement)); } d->plots.clear(); for (const auto& retirement : retiring) retirement->entry->stop(retirement); } void Control_Service::submit_input(std::string_view id, std::unique_ptr event) { if (!event) throw std::invalid_argument("Gallery input event is null"); const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); std::visit([event = std::move(event)](auto& scene) mutable { scene->submit_event(std::move(event)); }, found->second->scene); } void Control_Service::request_frame(std::string_view id, web::Gallery_Frame_Request request) { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); found->second->request_frame(std::move(request)); } nlohmann::json Control_Service::plot_schema(std::string_view id) const { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); return found->second->schema(); } nlohmann::json Control_Service::write_plot_property(std::string_view id, std::string_view component, std::string_view key, const nlohmann::json& value) { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); auto& entry = *found->second; if (component != "frame-analysis") return entry.components->write_prop(component, key, value); const auto current = entry.current_policy(); if (!current) return {{"success", false}, {"error", "Frame Policy is stopping"}}; if (key == "user_frames_per_second") { std::optional rate; if (!value.is_null()) { if (!value.is_number()) return {{"success", false}, {"error", "number or null required"}}; rate = value.get(); if (!std::isfinite(*rate) || *rate <= 0.0) return {{"success", false}, {"error", "positive finite frame rate required"}}; } current->set(&Frame_Policy::Prop::user_frames_per_second, rate); return {{"success", true}, {"component", component}, {"key", key}, {"value", rate ? nlohmann::json{*rate} : nlohmann::json{nullptr}}}; } if (key == "statistics_window_size") { if (!value.is_number_unsigned()) return {{"success", false}, {"error", "positive integer required"}}; const auto window = value.get(); if (window == 0) return {{"success", false}, {"error", "positive integer required"}}; current->set(&Frame_Policy::Prop::statistics_window_size, window); return {{"success", true}, {"component", component}, {"key", key}, {"value", window}}; } if (key == "render_rate_limit_enabled" || key == "send_rate_limit_enabled" || key == "statistics_enabled") { if (!value.is_boolean()) return {{"success", false}, {"error", "boolean required"}}; const bool enabled = value.get(); if (key == "render_rate_limit_enabled") current->set(&Frame_Policy::Prop::render_rate_limit_enabled, enabled); else if (key == "send_rate_limit_enabled") current->set(&Frame_Policy::Prop::send_rate_limit_enabled, enabled); else current->set(&Frame_Policy::Prop::statistics_enabled, enabled); return {{"success", true}, {"component", component}, {"key", key}, {"value", enabled}}; } return {{"success", false}, {"error", "unknown frame property"}}; } nlohmann::json Control_Service::plot_component_state(std::string_view id, std::string_view component) const { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); return found->second->components->component_state(component); } nlohmann::json Control_Service::generate_plot_data(std::string_view id, const nlohmann::json& input) { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); return found->second->components->generate_data(input); } nlohmann::json Control_Service::plot_diagnostics(std::string_view id) const { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); return found->second->diagnostics(); } void Control_Service::reset_plot_diagnostics(std::string_view id) { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); found->second->reset_diagnostics(); } void Control_Service::request_plot_taskflow_trace(std::string_view id, std::size_t frame_count) { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); found->second->request_trace(frame_count); } nlohmann::json Control_Service::plot_taskflow_trace(std::string_view id) const { const auto found = d->plots.find(std::string{id}); if (found == d->plots.end()) throw std::invalid_argument("unknown plot"); return found->second->trace(); } nlohmann::json Control_Service::plot_catalog() const { nlohmann::json result = nlohmann::json::array(); for (const auto& definition : web::gallery_plot_definitions()) result.push_back({{"id", definition.id}, {"title", definition.title}, {"category", definition.category}, {"description", definition.description}, {"dimension", web::plot_dimension_name(definition.dimension)}}); return result; } nlohmann::json Control_Service::tool_catalog() const { nlohmann::json result = nlohmann::json::array(); for (const auto& operation : operations) result.push_back({{"name", operation.name}, {"description", operation.description}, {"inputSchema", operation.schema()}}); return result; } Tool_Call_Output Control_Service::call_tool(std::string_view name, const nlohmann::json& arguments) { for (const auto& operation : operations) if (operation.name == name) return operation.invoke(*this, arguments); return {Tool_Call_Result::unknown_tool, {}, "unknown tool"}; } } // namespace aethera::mcp