23#include <nlohmann/json.hpp>
82 const std::string& args_json)
const override;
95 void apply_todo_action(
const std::string& action,
96 const nlohmann::json& args);
98 std::vector<TodoItem> items_;
106 std::string format_list()
const;
117 const std::string& )
const {
127std::string TodoTool::format_list()
const {
128 if (items_.empty()) {
132 for (
size_t i = 0; i < items_.size(); ++i) {
133 out += std::to_string(i) +
". [" +
134 items_[i].status +
"] " +
135 items_[i].content +
"\n";
154void TodoTool::apply_todo_action(
const std::string& action,
155 const nlohmann::json& args) {
156 if (action ==
"add") {
157 std::string content = args.at(
"content").get<std::string>();
158 items_.push_back({content,
"pending"});
159 logger->info(
"[todo] add: {}", content);
160 }
else if (action ==
"update") {
161 auto idx = args.at(
"index").get<
size_t>();
162 if (idx < items_.size()) {
163 items_[idx].status = args.value(
"status", items_[idx].status);
164 items_[idx].content = args.value(
"content", items_[idx].content);
165 logger->info(
"[todo] update #{}: {}", idx, items_[idx].status);
167 }
else if (action ==
"remove") {
168 auto idx = args.at(
"index").get<
size_t>();
169 if (idx < items_.size()) {
170 logger->info(
"[todo] remove #{}", idx);
171 items_.erase(items_.begin() +
static_cast<ptrdiff_t
>(idx));
182 auto args = nlohmann::json::parse(args_json);
183 std::string action = args.at(
"action").get<std::string>();
185 apply_todo_action(action, args);
187 nlohmann::json result;
188 result[
"todo_state"] = format_list();
189 result[
"action"] = action;
195 return {result.dump(), {anchor_d, notify_d}};
215 const std::vector<std::string>& tier_names);
236 const std::vector<std::string>& tier_names)
240 schema[
"properties"][
"target"][
"enum"] = tier_names;
243 logger->info(
"[delegate] patched enum with {} tiers",
255 auto args = nlohmann::json::parse(args_json);
256 std::string target = args.at(
"target").get<std::string>();
257 std::string task = args.at(
"task").get<std::string>();
258 int max_turns = args.value(
"max_turns", -1);
260 logger->info(
"[delegate] target='{}' task='{}' max_turns={}",
261 target, task, max_turns);
263 nlohmann::json result;
264 result[
"action"] =
"delegate";
265 result[
"target"] = target;
266 result[
"task"] = task;
267 result[
"max_turns"] = max_turns;
275 return {result.dump(), {delegate_d, stop_d}};
295 const std::vector<std::string>& tier_names);
316 const std::vector<std::string>& tier_names)
320 schema[
"properties"][
"stages"][
"items"][
"enum"] = tier_names;
323 logger->info(
"[pipeline] patched enum with {} tiers",
335 auto args = nlohmann::json::parse(args_json);
336 auto stages = args.at(
"stages").get<std::vector<std::string>>();
337 std::string task = args.at(
"task").get<std::string>();
339 if (stages.size() < 2) {
340 logger->warn(
"[pipeline] rejected: fewer than 2 stages");
341 return {
"Error: pipeline requires at least 2 stages", {}};
344 logger->info(
"[pipeline] stages={} task='{}'",
345 stages.size(), task);
347 nlohmann::json result;
348 result[
"action"] =
"pipeline";
349 result[
"stages"] = stages;
350 result[
"task"] = task;
358 return {result.dump(), {pipeline_d, stop_d}};
406 auto args = nlohmann::json::parse(args_json);
407 std::string summary = args.at(
"summary").get<std::string>();
408 bool coverage_gap = args.value(
"coverage_gap",
false);
409 std::string gap_description =
410 args.value(
"gap_description", std::string{});
411 std::vector<std::string> suggested;
412 if (args.contains(
"suggested_files")
413 && args[
"suggested_files"].is_array()) {
414 suggested = args[
"suggested_files"]
415 .get<std::vector<std::string>>();
423 if (coverage_gap && gap_description.empty()) {
425 err[
"error"] =
"missing_gap_description";
427 "coverage_gap=true requires a non-empty gap_description "
428 "(what's missing from this answer and why).";
429 return {err.dump(), {}};
432 logger->info(
"[complete] summary='{}' coverage_gap={} "
433 "gap_description_len={} suggested_files={}",
434 summary, coverage_gap,
435 gap_description.size(), suggested.size());
437 nlohmann::json result;
438 result[
"action"] =
"complete";
439 result[
"summary"] = summary;
440 result[
"coverage_gap"] = coverage_gap;
441 result[
"gap_description"] = gap_description;
442 result[
"suggested_files"] = suggested;
450 return {result.dump(), {complete_d, stop_d}};
486 "Switch inference phase",
487 R
"({"type":"object","properties":{"phase":{"type":"string"}},"required":["phase"]})"}) {}
497 const std::string& args_json) {
498 auto args = nlohmann::json::parse(args_json);
499 std::string phase = args.at(
"phase").get<std::string>();
501 logger->info(
"[phase_change] phase='{}'", phase);
503 nlohmann::json result;
504 result[
"action"] =
"phase_change";
505 result[
"phase"] = phase;
510 return {result.dump(), {phase_d}};
547 const std::string& args_json) {
548 auto args = nlohmann::json::parse(args_json);
550 static constexpr int default_keep = 2;
551 int keep_recent = args.value(
"keep_recent", default_keep);
553 logger->info(
"[prune_context] keep_recent={}", keep_recent);
555 nlohmann::json result;
556 result[
"action"] =
"prune_context";
557 result[
"keep_recent"] = keep_recent;
562 return {result.dump(), {prune_d}};
624 char* (*fn)(
void*),
void* ud) {
629 if (raw ==
nullptr) {
632 std::string result(raw);
647 char* (*fn)(
int,
void*),
int max_entries,
void* ud) {
651 char* raw = fn(max_entries, ud);
652 if (raw ==
nullptr) {
655 std::string result(raw);
670 char* (*fn)(
const char*,
void*),
671 const char* section,
void* ud) {
675 char* raw = fn(section, ud);
676 if (raw ==
nullptr) {
679 std::string result(raw);
695 bool include_docs,
int history_limit) {
699 auto now = std::chrono::system_clock::now();
700 auto ms = std::chrono::duration_cast<
701 std::chrono::milliseconds>(
702 now.time_since_epoch()).count();
703 snap[
"snapshot_timestamp_ms"] = ms;
705 snap[
"engine"] = nlohmann::json::parse(
707 snap[
"config"] = nlohmann::json::parse(
709 snap[
"identities"] = nlohmann::json::parse(
711 snap[
"tools"] = nlohmann::json::parse(
713 snap[
"history"] = nlohmann::json::parse(
716 snap[
"metrics"] = nlohmann::json::parse(
723 snap[
"docs"] =
nullptr;
736 if (provider_ ==
nullptr) {
737 logger->error(
"[diagnose] no state provider set");
738 return {
"Error: engine state provider not configured", {}};
741 auto args = nlohmann::json::parse(args_json);
742 bool include_docs = args.value(
"include_docs",
false);
743 int history_limit = args.value(
"history_limit", 20);
745 logger->info(
"[diagnose] include_docs={} history_limit={}",
746 include_docs, history_limit);
749 *provider_, include_docs, history_limit);
750 return {snap.dump(), {}};
865 const nlohmann::json& j) {
866 std::vector<std::string> keys;
867 for (
auto it = j.begin(); it != j.end(); ++it) {
868 keys.push_back(it.key());
881 const nlohmann::json& j) {
882 std::vector<std::string> names;
883 for (
const auto& item : j) {
884 if (item.is_object() && item.contains(
"name")) {
885 names.push_back(item[
"name"].get<std::string>());
902 for (
const auto& k : keys) {
903 if (!result.empty()) { result +=
", "; }
919 const std::string& json_str,
920 const std::string& key,
921 const std::string& label) {
922 auto j = nlohmann::json::parse(json_str);
924 if (j.is_object() && j.contains(key)) {
925 return j[key].dump();
928 for (
const auto& item : j) {
929 if (item.value(
"name",
"") == key) {
934 return "Error: " + label +
" '" + key
935 +
"' not found. Available: "
950 char* (*fn)(
void*),
void* ud,
951 const std::string& key,
const std::string& label) {
971 const std::string& target,
972 const std::string& key,
973 std::string& result) {
975 if (target ==
"state") {
977 }
else if (target ==
"metrics") {
979 }
else if (target ==
"history") {
980 int limit = key.empty() ? 10 : std::atoi(key.c_str());
983 }
else if (target ==
"docs") {
984 const char* sec = key.empty() ? nullptr : key.c_str();
1004 const std::string& target,
1005 const std::string& key,
1006 std::string& result) {
1007 if (target ==
"config") {
1010 }
else if (target ==
"identity") {
1013 }
else if (target ==
"tool") {
1033 const std::string& target,
1034 const std::string& key) {
1043 return "Error: unknown target '" + target
1044 +
"'. Supported: config, identity, tool, state, "
1045 "metrics, history, docs";
1073 if (provider_ ==
nullptr) {
1074 logger->error(
"[inspect] no state provider set");
1075 return {
"Error: engine state provider not configured", {}};
1078 auto args = nlohmann::json::parse(args_json,
nullptr,
false);
1084 if (args.is_discarded() || !args.is_object()) {
1085 args = nlohmann::json::object();
1087 std::string target = args.value(
"target",
"");
1088 std::string key = args.value(
"key",
"");
1090 if (target.empty()) {
1091 logger->info(
"[inspect] full state dump (no target)");
1097 logger->info(
"[inspect] target='{}' key='{}'", target, key);
1099 return {result, {}};
1114 const std::string& args_json) {
1115 if (provider_ ==
nullptr) {
1116 logger->error(
"[context_inspect] no state provider set");
1117 return {
"Error: engine state provider not configured", {}};
1120 auto args = nlohmann::json::parse(args_json,
nullptr,
false);
1121 int max_messages = args.value(
"max_messages", 0);
1123 logger->info(
"[context_inspect] max_messages={}", max_messages);
1126 return {result, {}};
1192 auto args = nlohmann::json::parse(args_json,
nullptr,
false);
1194 if (args.is_discarded() || !args.is_object()) {
1195 body = R
"({"error":"invalid args: object with 'query' required"})";
1197 std::string query = args.value(
"query",
"");
1198 int max_results = args.value(
"max_results", 3);
1199 if (query.empty()) {
1200 body = R
"({"error":"'query' is required and must be non-empty"})";
1201 } else if (provider_ ==
nullptr
1203 logger->warn(
"[followup] no search_delegations provider "
1205 body = R
"({"error":"delegation storage not available"})";
1207 logger->info(
"[followup] query='{}' max_results={}",
1208 query, max_results);
1210 query.c_str(), max_results, provider_->
user_data);
1211 if (raw ==
nullptr) {
1212 body = R
"({"results":[]})";
1265 auto args = nlohmann::json::parse(args_json,
nullptr,
false);
1266 if (args.is_discarded() || !args.is_object()) {
1267 return {R
"({"error":"invalid args: object required"})", {}};
1269 std::string delegation_id = args.value("delegation_id",
"");
1270 std::string task = args.value(
"task",
"");
1271 int max_turns = args.value(
"max_turns", -1);
1272 if (delegation_id.empty() || task.empty()) {
1273 return {R
"({"error":"'delegation_id' and 'task' are required"})",
1276 logger->info("[resume_delegation] id='{}' task='{}' max_turns={}",
1277 delegation_id, task, max_turns);
1279 nlohmann::json result;
1280 result[
"action"] =
"resume_delegation";
1281 result[
"delegation_id"] = delegation_id;
1282 result[
"task"] = task;
1283 result[
"max_turns"] = max_turns;
1289 return {result.dump(), {delegate_d, stop_d}};
1301int EntropicServer::register_core_tools(
1302 const std::string& tools_dir) {
1304 "todo",
"entropic", tools_dir);
1305 todo_ = std::make_unique<TodoTool>(std::move(todo_def));
1309 "complete",
"entropic", tools_dir);
1310 complete_ = std::make_unique<CompleteTool>(
1311 std::move(complete_def));
1314 phase_change_ = std::make_unique<PhaseChangeTool>();
1318 "prune_context",
"entropic", tools_dir);
1319 prune_context_ = std::make_unique<PruneContextTool>(
1320 std::move(prune_def));
1334int EntropicServer::register_delegation_tools(
1335 const std::string& tools_dir,
1336 const std::vector<std::string>& tier_names) {
1337 if (tier_names.size() <= 1) {
1341 "delegate",
"entropic", tools_dir);
1342 delegate_ = std::make_unique<DelegateTool>(
1343 std::move(delegate_def), tier_names);
1347 "pipeline",
"entropic", tools_dir);
1348 pipeline_ = std::make_unique<PipelineTool>(
1349 std::move(pipeline_def), tier_names);
1355 "resume_delegation",
"entropic", tools_dir);
1356 resume_delegation_ = std::make_unique<ResumeDelegationTool>(
1357 std::move(resume_def));
1370int EntropicServer::register_introspection_tools(
1371 const std::string& tools_dir) {
1372 diagnose_ = std::make_unique<DiagnoseTool>(
1376 inspect_ = std::make_unique<InspectTool>(
1380 context_inspect_ = std::make_unique<ContextInspectTool>(
1386 followup_ = std::make_unique<FollowupTool>(
1400 const std::vector<std::string>& tier_names,
1401 const std::string& data_dir)
1404 std::string tools_dir = data_dir +
"/tools";
1405 int count = register_core_tools(tools_dir);
1406 count += register_delegation_tools(tools_dir, tier_names);
1407 count += register_introspection_tools(tools_dir);
1409 logger->info(
"EntropicServer initialized with {} tools "
1410 "({} tiers)", count, tier_names.size());
1427 const std::string& tool_name)
const {
1428 return tool_name ==
"delegate" || tool_name ==
"pipeline";
1439 state_provider_ = provider;
1440 diagnose_->set_provider(&state_provider_);
1441 inspect_->set_provider(&state_provider_);
1442 context_inspect_->set_provider(&state_provider_);
1444 followup_->set_provider(&state_provider_);
1446 logger->info(
"State provider set for introspection tools");
Tool for inspecting the current context window contents.
ContextInspectTool(ToolDefinition def)
Construct from tool definition.
ServerResponse execute(const std::string &args_json) override
Return context window contents as a message array.
void set_provider(const entropic_state_provider_t *p)
Set state provider pointer.
MCPAccessLevel required_access_level() const override
Read-only tool requires only READ access.
void set_state_provider(const entropic_state_provider_t &provider)
Set the engine state provider for introspection tools.
~EntropicServer() override
Destructor.
EntropicServer(const std::vector< std::string > &tier_names, const std::string &data_dir)
Construct with tier names and data dir.
bool skip_duplicate_check(const std::string &tool_name) const override
delegate and pipeline skip duplicate check.
Concrete base class for MCP servers (80% logic).
void register_tool(ToolBase *tool)
Register a tool with this server.
Tool for pruning old messages from context.
PruneContextTool(ToolDefinition def)
Construct from tool definition.
ServerResponse execute(const std::string &args_json) override
Execute context pruning.
Directive processing for tool-to-engine communication.
Entropic MCP server — engine-level tools including introspection.
@ ENTROPIC_DIRECTIVE_STOP_PROCESSING
Halt directive processing.
@ ENTROPIC_DIRECTIVE_PRUNE_MESSAGES
Prune old tool results.
@ ENTROPIC_DIRECTIVE_COMPLETE
Mark task complete.
@ ENTROPIC_DIRECTIVE_NOTIFY_PRESENTER
Generic UI notification passthrough.
@ ENTROPIC_DIRECTIVE_PHASE_CHANGE
Switch active inference phase.
@ ENTROPIC_DIRECTIVE_PIPELINE
Multi-stage sequential execution.
@ ENTROPIC_DIRECTIVE_CONTEXT_ANCHOR
Replace context anchor.
@ ENTROPIC_DIRECTIVE_DELEGATE
Route to another identity.
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).
static std::string call_provider(char *(*fn)(void *), void *ud)
Call a state provider callback and wrap result.
ToolDefinition load_tool_definition(const std::string &tool_name, const std::string &server_prefix, const std::string &data_dir)
Load a tool definition from a JSON file.
static std::string inspect_filterable(char *(*fn)(void *), void *ud, const std::string &key, const std::string &label)
Inspect a filterable target (config/identity/tool).
static std::vector< std::string > collect_object_keys(const nlohmann::json &j)
Collect keys from a JSON object into a vector.
static std::string filter_json_by_key(const std::string &json_str, const std::string &key, const std::string &label)
Filter a JSON value by key (object key or array name).
static std::string call_docs_provider(char *(*fn)(const char *, void *), const char *section, void *ud)
Call docs callback with section param.
static std::string call_history_provider(char *(*fn)(int, void *), int max_entries, void *ud)
Call history callback with max_entries param.
static bool dispatch_simple_target(const entropic_state_provider_t &p, const std::string &target, const std::string &key, std::string &result)
Dispatch inspect for simple (non-filterable) targets.
static nlohmann::json build_snapshot(const entropic_state_provider_t &p, bool include_docs, int history_limit)
Build the diagnose snapshot JSON.
MCPAccessLevel
MCP tool access level for per-identity authorization.
@ READ
Read-only operations (e.g., read_file, list_directory)
static std::string dispatch_inspect(const entropic_state_provider_t &p, const std::string &target, const std::string &key)
Dispatch an inspect query to the appropriate provider.
static std::vector< std::string > collect_array_names(const nlohmann::json &j)
Collect "name" fields from a JSON array of objects.
static std::string list_available_keys(const nlohmann::json &j)
List available keys from a JSON value for error messages.
static bool dispatch_filterable_target(const entropic_state_provider_t &p, const std::string &target, const std::string &key, std::string &result)
Try filterable targets (config/identity/tool).
MCPServerBase concrete base class + ServerResponse.
Base directive — all directives carry a type tag.
entropic_directive_type_t type
Discriminant for dispatch.
Structured result from tool execution.
std::string content
Item text.
std::string status
"pending", "in_progress", "done"
Read-only engine state provider for introspection tools.
char *(* get_tools)(void *user_data)
Get available tools as JSON array.
char *(* get_metrics)(void *user_data)
Get engine metrics as JSON.
void * user_data
Opaque user data passed to all callbacks.
char *(* search_delegations)(const char *query, int max_results, void *user_data)
Search prior delegation summaries (gh#32, v2.1.6).
char *(* get_identities)(void *user_data)
Get loaded identities as JSON array.
char *(* get_config)(void *user_data)
Get current engine configuration as JSON.
char *(* get_history)(int max_entries, void *user_data)
Get recent tool call history as JSON array.
char *(* get_docs)(const char *section, void *user_data)
Get bundled documentation as text.
char *(* get_state)(void *user_data)
Get engine state as JSON.