Entropic 2.11.1
Local-first agentic inference engine
Loading...
Searching...
No Matches
entropic_server.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
23
24#include <nlohmann/json.hpp>
25
26#include <algorithm>
27#include <chrono>
28#include <cstdlib>
29#include <string>
30#include <vector>
31
32static auto logger = entropic::log::get("mcp.entropic");
33
34namespace entropic {
35
36// ── TodoItem ────────────────────────────────────────────────────
37
43struct TodoItem {
44 std::string content;
45 std::string status;
46};
47
48// ── TodoTool ────────────────────────────────────────────────────
49
55class TodoTool : public ToolBase {
56public:
64 : ToolBase(std::move(def)) {}
65
73 ServerResponse execute(const std::string& args_json) override;
74
82 std::string anchor_key(
83 const std::string& args_json) const override;
84
85private:
96 void apply_todo_action(const std::string& action,
97 const nlohmann::json& args);
98
99 std::vector<TodoItem> items_;
100
107 std::string format_list() const;
108};
109
118 const std::string& /*args_json*/) const {
119 return "todo_state";
120}
121
128std::string TodoTool::format_list() const {
129 if (items_.empty()) {
130 return "(empty)";
131 }
132 std::string out;
133 for (size_t i = 0; i < items_.size(); ++i) {
134 out += std::to_string(i) + ". [" +
135 items_[i].status + "] " +
136 items_[i].content + "\n";
137 }
138 return out;
139}
140
155void TodoTool::apply_todo_action(const std::string& action,
156 const nlohmann::json& args) {
157 if (action == "add") {
158 std::string content = args.at("content").get<std::string>();
159 items_.push_back({content, "pending"});
160 logger->info("[todo] add: {}", content);
161 } else if (action == "update") {
162 auto idx = args.at("index").get<size_t>();
163 if (idx < items_.size()) {
164 items_[idx].status = args.value("status", items_[idx].status);
165 items_[idx].content = args.value("content", items_[idx].content);
166 logger->info("[todo] update #{}: {}", idx, items_[idx].status);
167 }
168 } else if (action == "remove") {
169 auto idx = args.at("index").get<size_t>();
170 if (idx < items_.size()) {
171 logger->info("[todo] remove #{}", idx);
172 items_.erase(items_.begin() + static_cast<ptrdiff_t>(idx));
173 }
174 }
175}
176
185ServerResponse TodoTool::execute(const std::string& args_json) {
186 auto args = nlohmann::json::parse(args_json);
187 std::string action = args.at("action").get<std::string>();
188
189 apply_todo_action(action, args);
190
191 nlohmann::json result;
192 result["todo_state"] = format_list();
193 result["action"] = action;
194
195 Directive anchor_d;
197 Directive notify_d;
199 return {result.dump(), {anchor_d, notify_d}};
200}
201
202// ── DelegateTool ────────────────────────────────────────────────
203
209class DelegateTool : public ToolBase {
210public:
219 const std::vector<std::string>& tier_names);
220
228 ServerResponse execute(const std::string& args_json) override;
229};
230
245 ToolDefinition def,
246 const std::vector<std::string>& tier_names)
247 : ToolBase(std::move(def)) {
248
249 auto schema = nlohmann::json::parse(definition_.input_schema);
250 schema["properties"]["target"]["enum"] = tier_names;
251 definition_.input_schema = schema.dump();
252
253 logger->info("[delegate] patched enum with {} tiers",
254 tier_names.size());
255}
256
267ServerResponse DelegateTool::execute(const std::string& args_json) {
268 auto args = nlohmann::json::parse(args_json);
269 std::string target = args.at("target").get<std::string>();
270 std::string task = args.at("task").get<std::string>();
271 int max_turns = args.value("max_turns", -1);
272
273 logger->info("[delegate] target='{}' task='{}' max_turns={}",
274 target, task, max_turns);
275
276 nlohmann::json result;
277 result["action"] = "delegate";
278 result["target"] = target;
279 result["task"] = task;
280 result["max_turns"] = max_turns;
281
282 Directive delegate_d;
284
285 Directive stop_d;
287
288 return {result.dump(), {delegate_d, stop_d}};
289}
290
291// ── PipelineTool ────────────────────────────────────────────────
292
298class PipelineTool : public ToolBase {
299public:
308 const std::vector<std::string>& tier_names);
309
317 ServerResponse execute(const std::string& args_json) override;
318
319private:
320 std::vector<std::string> tier_names_;
321};
322
337 ToolDefinition def,
338 const std::vector<std::string>& tier_names)
339 : ToolBase(std::move(def)), tier_names_(tier_names) {
340
341 auto schema = nlohmann::json::parse(definition_.input_schema);
342 schema["properties"]["stages"]["items"]["enum"] = tier_names;
343 definition_.input_schema = schema.dump();
344
345 logger->info("[pipeline] patched enum with {} tiers",
346 tier_names.size());
347}
348
365ServerResponse PipelineTool::execute(const std::string& args_json) {
366 auto args = nlohmann::json::parse(args_json);
367 auto stages = args.at("stages").get<std::vector<std::string>>();
368 std::string task = args.at("task").get<std::string>();
369
370 if (stages.size() < 2) {
371 logger->warn("[pipeline] rejected: fewer than 2 stages");
372 return {"Error: pipeline requires at least 2 stages", {}};
373 }
374
375 for (const auto& s : stages) {
376 auto it = std::find(tier_names_.begin(), tier_names_.end(), s);
377 if (it == tier_names_.end()) {
378 logger->warn("[pipeline] invalid stage: '{}'", s);
379 auto valid = nlohmann::json(tier_names_).dump();
380 return {"Error: unknown stage \"" + s
381 + "\". Valid stages: " + valid, {}};
382 }
383 }
384
385 logger->info("[pipeline] stages={} task='{}'",
386 stages.size(), task);
387
388 nlohmann::json result;
389 result["action"] = "pipeline";
390 result["stages"] = stages;
391 result["task"] = task;
392
393 Directive pipeline_d;
395
396 Directive stop_d;
398
399 return {result.dump(), {pipeline_d, stop_d}};
400}
401
402// ── CompleteTool ────────────────────────────────────────────────
403
409class CompleteTool : public ToolBase {
410public:
418 : ToolBase(std::move(def)) {}
419
427 ServerResponse execute(const std::string& args_json) override;
428};
429
450ServerResponse CompleteTool::execute(const std::string& args_json) {
451 auto args = nlohmann::json::parse(args_json);
452 std::string summary = args.at("summary").get<std::string>();
453 bool coverage_gap = args.value("coverage_gap", false);
454 std::string gap_description =
455 args.value("gap_description", std::string{});
456 std::vector<std::string> suggested;
457 if (args.contains("suggested_files")
458 && args["suggested_files"].is_array()) {
459 suggested = args["suggested_files"]
460 .get<std::vector<std::string>>();
461 }
462
463 // Issue #10 (v2.1.4): coverage_gap=true REQUIRES a non-empty
464 // gap_description so the lead context's [COVERAGE GAP] message
465 // tells the next specialist what to fill in. An empty
466 // gap_description with coverage_gap=true would be a bare
467 // "I don't know" with no signal — refuse it at the tool boundary.
468 if (coverage_gap && gap_description.empty()) {
469 nlohmann::json err;
470 err["error"] = "missing_gap_description";
471 err["message"] =
472 "coverage_gap=true requires a non-empty gap_description "
473 "(what's missing from this answer and why).";
474 return {err.dump(), {}};
475 }
476
477 logger->info("[complete] summary='{}' coverage_gap={} "
478 "gap_description_len={} suggested_files={}",
479 summary, coverage_gap,
480 gap_description.size(), suggested.size());
481
482 nlohmann::json result;
483 result["action"] = "complete";
484 result["summary"] = entropic::mcp::sanitize_utf8(summary);
485 result["coverage_gap"] = coverage_gap;
486 result["gap_description"] = entropic::mcp::sanitize_utf8(gap_description);
487 result["suggested_files"] = suggested;
488
489 Directive complete_d;
491
492 Directive stop_d;
494
495 return {result.dump(), {complete_d, stop_d}};
496}
497
498// ── PhaseChangeTool ─────────────────────────────────────────────
499
505class PhaseChangeTool : public ToolBase {
506public:
513
521 ServerResponse execute(const std::string& args_json) override;
522};
523
530 : ToolBase({"phase_change",
531 "Switch inference phase",
532 R"({"type":"object","properties":{"phase":{"type":"string"}},"required":["phase"]})"}) {}
533
543 const std::string& args_json) {
544 auto args = nlohmann::json::parse(args_json);
545 std::string phase = args.at("phase").get<std::string>();
546
547 logger->info("[phase_change] phase='{}'", phase);
548
549 nlohmann::json result;
550 result["action"] = "phase_change";
551 result["phase"] = phase;
552
553 Directive phase_d;
555
556 return {result.dump(), {phase_d}};
557}
558
559// ── PruneContextTool ────────────────────────────────────────────
560
567public:
574 : ToolBase(std::move(def)) {}
575
582 ServerResponse execute(const std::string& args_json) override;
583};
584
595 const std::string& args_json) {
596 auto args = nlohmann::json::parse(args_json);
597
598 static constexpr int default_keep = 2;
599 int keep_recent = args.value("keep_recent", default_keep);
600
601 logger->info("[prune_context] keep_recent={}", keep_recent);
602
603 nlohmann::json result;
604 result["action"] = "prune_context";
605 result["keep_recent"] = keep_recent;
606
607 Directive prune_d;
609
610 return {result.dump(), {prune_d}};
611}
612
613// ── DiagnoseTool ────────────────────────────────────────────────
614
620class DiagnoseTool : public ToolBase {
621public:
629 : ToolBase(std::move(def)) {}
630
638 ServerResponse execute(const std::string& args_json) override;
639
649 }
650
657 provider_ = p;
658 }
659
660private:
661 const entropic_state_provider_t* provider_ = nullptr;
662};
663
678static std::string call_provider(
679 char* (*fn)(void*), void* ud) {
680 if (fn == nullptr) {
681 return "{}";
682 }
683 char* raw = fn(ud);
684 if (raw == nullptr) {
685 return "{}";
686 }
687 std::string result(raw);
688 free(raw);
689 return result;
690}
691
701static std::string call_history_provider(
702 char* (*fn)(int, void*), int max_entries, void* ud) {
703 if (fn == nullptr) {
704 return "[]";
705 }
706 char* raw = fn(max_entries, ud);
707 if (raw == nullptr) {
708 return "[]";
709 }
710 std::string result(raw);
711 free(raw);
712 return result;
713}
714
724static std::string call_docs_provider(
725 char* (*fn)(const char*, void*),
726 const char* section, void* ud) {
727 if (fn == nullptr) {
728 return "";
729 }
730 char* raw = fn(section, ud);
731 if (raw == nullptr) {
732 return "";
733 }
734 std::string result(raw);
735 free(raw);
736 return result;
737}
738
748static nlohmann::json build_snapshot(
750 bool include_docs, int history_limit) {
751
752 nlohmann::json snap;
753
754 auto now = std::chrono::system_clock::now();
755 auto ms = std::chrono::duration_cast<
756 std::chrono::milliseconds>(
757 now.time_since_epoch()).count();
758 snap["snapshot_timestamp_ms"] = ms;
759
760 snap["engine"] = nlohmann::json::parse(
762 snap["config"] = nlohmann::json::parse(
764 snap["identities"] = nlohmann::json::parse(
766 snap["tools"] = nlohmann::json::parse(
768 snap["history"] = nlohmann::json::parse(
770 p.get_history, history_limit, p.user_data));
771 snap["metrics"] = nlohmann::json::parse(
773
774 if (include_docs) {
775 snap["docs"] = call_docs_provider(
776 p.get_docs, nullptr, p.user_data);
777 } else {
778 snap["docs"] = nullptr;
779 }
780 return snap;
781}
782
798ServerResponse DiagnoseTool::execute(const std::string& args_json) {
799 if (provider_ == nullptr) {
800 logger->error("[diagnose] no state provider set");
801 return {"Error: engine state provider not configured", {}};
802 }
803
804 auto args = nlohmann::json::parse(args_json);
805 bool include_docs = args.value("include_docs", false);
806 int history_limit = args.value("history_limit", 20);
807
808 logger->info("[diagnose] include_docs={} history_limit={}",
809 include_docs, history_limit);
810
811 auto snap = build_snapshot(
812 *provider_, include_docs, history_limit);
813 return {snap.dump(), {}};
814}
815
816// ── InspectTool ─────────────────────────────────────────────────
817
823class InspectTool : public ToolBase {
824public:
832 : ToolBase(std::move(def)) {}
833
841 ServerResponse execute(const std::string& args_json) override;
842
852 }
853
860 provider_ = p;
861 }
862
863private:
864 const entropic_state_provider_t* provider_ = nullptr;
865};
866
867// ── ContextInspectTool ──────────────────────────────────────────
868
879public:
887 : ToolBase(std::move(def)) {}
888
896 ServerResponse execute(const std::string& args_json) override;
897
907 }
908
915 provider_ = p;
916 }
917
918private:
919 const entropic_state_provider_t* provider_ = nullptr;
920};
921
929static std::vector<std::string> collect_object_keys(
930 const nlohmann::json& j) {
931 std::vector<std::string> keys;
932 for (auto it = j.begin(); it != j.end(); ++it) {
933 keys.push_back(it.key());
934 }
935 return keys;
936}
937
945static std::vector<std::string> collect_array_names(
946 const nlohmann::json& j) {
947 std::vector<std::string> names;
948 for (const auto& item : j) {
949 if (item.is_object() && item.contains("name")) {
950 names.push_back(item["name"].get<std::string>());
951 }
952 }
953 return names;
954}
955
963static std::string list_available_keys(const nlohmann::json& j) {
964 auto keys = j.is_object() ? collect_object_keys(j)
966 std::string result;
967 for (const auto& k : keys) {
968 if (!result.empty()) { result += ", "; }
969 result += k;
970 }
971 return result;
972}
973
983static std::string filter_json_by_key(
984 const std::string& json_str,
985 const std::string& key,
986 const std::string& label) {
987 auto j = nlohmann::json::parse(json_str);
988
989 if (j.is_object() && j.contains(key)) {
990 return j[key].dump();
991 }
992 if (j.is_array()) {
993 for (const auto& item : j) {
994 if (item.value("name", "") == key) {
995 return item.dump();
996 }
997 }
998 }
999 return "Error: " + label + " '" + key
1000 + "' not found. Available: "
1002}
1003
1014static std::string inspect_filterable(
1015 char* (*fn)(void*), void* ud,
1016 const std::string& key, const std::string& label) {
1017 auto raw = call_provider(fn, ud);
1018 if (key.empty()) {
1019 return raw;
1020 }
1021 return filter_json_by_key(raw, key, label);
1022}
1023
1036 const std::string& target,
1037 const std::string& key,
1038 std::string& result) {
1039
1040 if (target == "state") {
1041 result = call_provider(p.get_state, p.user_data);
1042 } else if (target == "metrics") {
1043 result = call_provider(p.get_metrics, p.user_data);
1044 } else if (target == "history") {
1045 int limit = key.empty() ? 10 : std::atoi(key.c_str());
1046 result = call_history_provider(
1047 p.get_history, limit, p.user_data);
1048 } else if (target == "docs") {
1049 const char* sec = key.empty() ? nullptr : key.c_str();
1050 result = call_docs_provider(p.get_docs, sec, p.user_data);
1051 } else {
1052 return false;
1053 }
1054 return true;
1055}
1056
1069 const std::string& target,
1070 const std::string& key,
1071 std::string& result) {
1072 if (target == "config") {
1073 result = inspect_filterable(
1074 p.get_config, p.user_data, key, "config section");
1075 } else if (target == "identity") {
1076 result = inspect_filterable(
1077 p.get_identities, p.user_data, key, "identity");
1078 } else if (target == "tool") {
1079 result = inspect_filterable(
1080 p.get_tools, p.user_data, key, "tool");
1081 } else {
1082 return false;
1083 }
1084 return true;
1085}
1086
1096static std::string dispatch_inspect(
1098 const std::string& target,
1099 const std::string& key) {
1100
1101 std::string result;
1102 if (dispatch_filterable_target(p, target, key, result)) {
1103 return result;
1104 }
1105 if (dispatch_simple_target(p, target, key, result)) {
1106 return result;
1107 }
1108 return "Error: unknown target '" + target
1109 + "'. Supported: config, identity, tool, state, "
1110 "metrics, history, docs";
1111}
1112
1141ServerResponse InspectTool::execute(const std::string& args_json) {
1142 if (provider_ == nullptr) {
1143 logger->error("[inspect] no state provider set");
1144 return {"Error: engine state provider not configured", {}};
1145 }
1146
1147 auto args = nlohmann::json::parse(args_json, nullptr, false);
1148 // gh#33 (v2.1.6): non-throwing parse returns a discarded value on
1149 // invalid/empty/null input; calling .value() on it throws
1150 // type_error.306 and crashes the engine. Coerce to an empty object
1151 // so a no-arg `entropic.inspect()` call falls through to the
1152 // full-state dump path.
1153 if (args.is_discarded() || !args.is_object()) {
1154 args = nlohmann::json::object();
1155 }
1156 std::string target = args.value("target", "");
1157 std::string key = args.value("key", "");
1158
1159 if (target.empty()) {
1160 logger->info("[inspect] full state dump (no target)");
1161 auto state = call_provider(provider_->get_state,
1162 provider_->user_data);
1163 return {state, {}};
1164 }
1165
1166 logger->info("[inspect] target='{}' key='{}'", target, key);
1167 auto result = dispatch_inspect(*provider_, target, key);
1168 return {result, {}};
1169}
1170
1186 const std::string& args_json) {
1187 if (provider_ == nullptr) {
1188 logger->error("[context_inspect] no state provider set");
1189 return {"Error: engine state provider not configured", {}};
1190 }
1191
1192 auto args = nlohmann::json::parse(args_json, nullptr, false);
1193 int max_messages = args.value("max_messages", 0);
1194
1195 logger->info("[context_inspect] max_messages={}", max_messages);
1196 auto result = call_history_provider(
1197 provider_->get_history, max_messages, provider_->user_data);
1198 return {result, {}};
1199}
1200
1201// ── FollowupTool (gh#32, v2.1.6) ────────────────────────────────
1202
1213class FollowupTool : public ToolBase {
1214public:
1222 : ToolBase(std::move(def)) {}
1223
1231 ServerResponse execute(const std::string& args_json) override;
1232
1241 return MCPAccessLevel::READ;
1242 }
1243
1250 provider_ = p;
1251 }
1252
1253private:
1254 const entropic_state_provider_t* provider_ = nullptr;
1255};
1256
1269ServerResponse FollowupTool::execute(const std::string& args_json) {
1270 auto args = nlohmann::json::parse(args_json, nullptr, false);
1271 std::string body;
1272 if (args.is_discarded() || !args.is_object()) {
1273 body = R"({"error":"invalid args: object with 'query' required"})";
1274 } else {
1275 std::string query = args.value("query", "");
1276 int max_results = args.value("max_results", 3);
1277 if (query.empty()) {
1278 body = R"({"error":"'query' is required and must be non-empty"})";
1279 } else if (provider_ == nullptr
1280 || provider_->search_delegations == nullptr) {
1281 logger->warn("[followup] no search_delegations provider "
1282 "configured");
1283 body = R"({"error":"delegation storage not available"})";
1284 } else {
1285 logger->info("[followup] query='{}' max_results={}",
1286 query, max_results);
1287 char* raw = provider_->search_delegations(
1288 query.c_str(), max_results, provider_->user_data);
1289 if (raw == nullptr) {
1290 body = R"({"results":[]})";
1291 } else {
1292 body = raw;
1293 std::free(raw);
1294 }
1295 }
1296 }
1297 return {body, {}};
1298}
1299
1300// ── ResumeDelegationTool (gh#32, v2.1.6) ────────────────────────
1301
1315public:
1323 : ToolBase(std::move(def)) {}
1324
1332 ServerResponse execute(const std::string& args_json) override;
1333};
1334
1345ServerResponse ResumeDelegationTool::execute(const std::string& args_json) {
1346 auto args = nlohmann::json::parse(args_json, nullptr, false);
1347 if (args.is_discarded() || !args.is_object()) {
1348 return {R"({"error":"invalid args: object required"})", {}};
1349 }
1350 std::string delegation_id = args.value("delegation_id", "");
1351 std::string task = args.value("task", "");
1352 int max_turns = args.value("max_turns", -1);
1353 if (delegation_id.empty() || task.empty()) {
1354 return {R"({"error":"'delegation_id' and 'task' are required"})",
1355 {}};
1356 }
1357 logger->info("[resume_delegation] id='{}' task='{}' max_turns={}",
1358 delegation_id, task, max_turns);
1359
1360 nlohmann::json result;
1361 result["action"] = "resume_delegation";
1362 result["delegation_id"] = delegation_id;
1363 result["task"] = task;
1364 result["max_turns"] = max_turns;
1365
1366 Directive delegate_d;
1367 delegate_d.type = ENTROPIC_DIRECTIVE_DELEGATE;
1368 Directive stop_d;
1370 return {result.dump(), {delegate_d, stop_d}};
1371}
1372
1373// ── EntropicServer ──────────────────────────────────────────────
1374
1388int EntropicServer::register_core_tools(
1389 const std::string& tools_dir) {
1390 auto todo_def = load_tool_definition(
1391 "todo", "entropic", tools_dir);
1392 todo_ = std::make_unique<TodoTool>(std::move(todo_def));
1393 register_tool(todo_.get());
1394
1395 auto complete_def = load_tool_definition(
1396 "complete", "entropic", tools_dir);
1397 complete_ = std::make_unique<CompleteTool>(
1398 std::move(complete_def));
1399 register_tool(complete_.get());
1400
1401 phase_change_ = std::make_unique<PhaseChangeTool>();
1402 register_tool(phase_change_.get());
1403
1404 auto prune_def = load_tool_definition(
1405 "prune_context", "entropic", tools_dir);
1406 prune_context_ = std::make_unique<PruneContextTool>(
1407 std::move(prune_def));
1408 register_tool(prune_context_.get());
1409
1410 return 4;
1411}
1412
1424int EntropicServer::register_delegation_tools(
1425 const std::string& tools_dir,
1426 const std::vector<std::string>& tier_names) {
1427 if (tier_names.size() <= 1) {
1428 return 0;
1429 }
1430 auto delegate_def = load_tool_definition(
1431 "delegate", "entropic", tools_dir);
1432 delegate_ = std::make_unique<DelegateTool>(
1433 std::move(delegate_def), tier_names);
1434 register_tool(delegate_.get());
1435
1436 auto pipeline_def = load_tool_definition(
1437 "pipeline", "entropic", tools_dir);
1438 pipeline_ = std::make_unique<PipelineTool>(
1439 std::move(pipeline_def), tier_names);
1440 register_tool(pipeline_.get());
1441
1442 // gh#32 (v2.1.6): resume_delegation lives alongside delegate
1443 // because it produces the same directive kind (resume-flavored).
1444 auto resume_def = load_tool_definition(
1445 "resume_delegation", "entropic", tools_dir);
1446 resume_delegation_ = std::make_unique<ResumeDelegationTool>(
1447 std::move(resume_def));
1448 register_tool(resume_delegation_.get());
1449
1450 return 3;
1451}
1452
1466int EntropicServer::register_introspection_tools(
1467 const std::string& tools_dir) {
1468 diagnose_ = std::make_unique<DiagnoseTool>(
1469 load_tool_definition("diagnose", "entropic", tools_dir));
1470 register_tool(diagnose_.get());
1471
1472 inspect_ = std::make_unique<InspectTool>(
1473 load_tool_definition("inspect", "entropic", tools_dir));
1474 register_tool(inspect_.get());
1475
1476 context_inspect_ = std::make_unique<ContextInspectTool>(
1477 load_tool_definition("context_inspect", "entropic", tools_dir));
1478 register_tool(context_inspect_.get());
1479
1480 // gh#32 (v2.1.6): followup is read-only and consumes the same
1481 // state_provider plumbing as diagnose/inspect.
1482 followup_ = std::make_unique<FollowupTool>(
1483 load_tool_definition("followup", "entropic", tools_dir));
1484 register_tool(followup_.get());
1485
1486 return 4;
1487}
1488
1496 const std::vector<std::string>& tier_names,
1497 const std::string& data_dir)
1498 : MCPServerBase("entropic") {
1499
1500 std::string tools_dir = data_dir + "/tools";
1501 int count = register_core_tools(tools_dir);
1502 count += register_delegation_tools(tools_dir, tier_names);
1503 count += register_introspection_tools(tools_dir);
1504
1505 logger->info("EntropicServer initialized with {} tools "
1506 "({} tiers)", count, tier_names.size());
1507}
1508
1514
1528 const std::string& tool_name) const {
1529 return tool_name == "delegate" || tool_name == "pipeline";
1530}
1531
1546 const entropic_state_provider_t& provider) {
1547 state_provider_ = provider;
1548 diagnose_->set_provider(&state_provider_);
1549 inspect_->set_provider(&state_provider_);
1550 context_inspect_->set_provider(&state_provider_);
1551 if (followup_) {
1552 followup_->set_provider(&state_provider_);
1553 }
1554 logger->info("State provider set for introspection tools");
1555}
1556
1557} // namespace entropic
Tool for signaling task completion.
CompleteTool(ToolDefinition def)
Construct from tool definition.
ServerResponse execute(const std::string &args_json) override
Execute completion signal.
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.
Tool for delegating tasks to child inference loops.
ServerResponse execute(const std::string &args_json) override
Execute delegation.
DelegateTool(ToolDefinition def, const std::vector< std::string > &tier_names)
Construct and patch input schema with tier names.
Tool for full engine state snapshots.
ServerResponse execute(const std::string &args_json) override
Execute diagnostic snapshot.
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.
DiagnoseTool(ToolDefinition def)
Construct from tool definition.
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.
Recall prior delegation results via storage-backed search.
ServerResponse execute(const std::string &args_json) override
Execute a followup query.
MCPAccessLevel required_access_level() const override
Read-only — requires only READ access.
void set_provider(const entropic_state_provider_t *p)
Store provider pointer (non-owning).
FollowupTool(ToolDefinition def)
Construct from loaded tool definition.
Tool for targeted engine state queries.
ServerResponse execute(const std::string &args_json) override
Execute targeted inspection.
MCPAccessLevel required_access_level() const override
Read-only tool requires only READ access.
void set_provider(const entropic_state_provider_t *p)
Set state provider pointer.
InspectTool(ToolDefinition def)
Construct from tool definition.
Concrete base class for MCP servers (80% logic).
Definition server_base.h:66
void register_tool(ToolBase *tool)
Register a tool with this server.
Tool for switching inference phase.
PhaseChangeTool()
Construct with inline tool definition.
ServerResponse execute(const std::string &args_json) override
Execute phase change.
Tool for multi-stage delegation pipelines.
ServerResponse execute(const std::string &args_json) override
Execute pipeline setup.
PipelineTool(ToolDefinition def, const std::vector< std::string > &tier_names)
Construct and patch input schema with tier names.
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.
Resume a prior delegation with its conversation seeded.
ResumeDelegationTool(ToolDefinition def)
Construct from loaded tool definition.
ServerResponse execute(const std::string &args_json) override
Emit a resume-flavored delegate directive.
Tool for managing a persistent todo list.
std::string anchor_key(const std::string &args_json) const override
Anchor key for todo state replacement.
TodoTool(ToolDefinition def)
Construct from tool definition.
ServerResponse execute(const std::string &args_json) override
Execute todo action (add/update/remove).
Abstract base class for individual MCP tools.
Definition tool_base.h:45
ToolDefinition definition_
Tool definition.
Definition tool_base.h:102
Directive processing for tool-to-engine communication.
Entropic MCP server — engine-level tools including introspection.
@ ENTROPIC_DIRECTIVE_STOP_PROCESSING
Halt directive processing.
Definition enums.h:59
@ ENTROPIC_DIRECTIVE_PRUNE_MESSAGES
Prune old tool results.
Definition enums.h:66
@ ENTROPIC_DIRECTIVE_COMPLETE
Mark task complete.
Definition enums.h:63
@ ENTROPIC_DIRECTIVE_NOTIFY_PRESENTER
Generic UI notification passthrough.
Definition enums.h:69
@ ENTROPIC_DIRECTIVE_PHASE_CHANGE
Switch active inference phase.
Definition enums.h:68
@ ENTROPIC_DIRECTIVE_PIPELINE
Multi-stage sequential execution.
Definition enums.h:62
@ ENTROPIC_DIRECTIVE_CONTEXT_ANCHOR
Replace context anchor.
Definition enums.h:67
@ ENTROPIC_DIRECTIVE_DELEGATE
Route to another identity.
Definition enums.h:61
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Definition logging.cpp:211
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.
Definition tool_base.cpp:99
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.
@ count
Sentinel — MUST remain last.
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.
Definition config.h:38
@ 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.
Definition directives.h:36
entropic_directive_type_t type
Discriminant for dispatch.
Definition directives.h:37
Structured result from tool execution.
Definition server_base.h:33
Single todo entry.
std::string content
Item text.
std::string status
"pending", "in_progress", "done"
Parsed tool definition from JSON schema file.
Definition tool_base.h:27
std::string input_schema
JSON Schema for arguments (raw JSON string)
Definition tool_base.h:30
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.
Abstract base class for individual MCP tools.
UTF-8 validation + replacement at every system boundary where bytes change ownership.