25#include <nlohmann/json.hpp>
93 const std::string&
args_json)
const override;
102 bool release(
const std::string& key) {
return lists_.release(key); }
127 const std::string& )
const {
145 for (
size_t i = 0;
i <
items.size(); ++
i) {
146 out += std::to_string(
i) +
". [" +
165 if (0 >
idx ||
idx >=
static_cast<long>(
items.size())) {
166 logger->warn(
"[todo] {} #{}: out of range (list has {})",
169 +
" is not in the list, which has " + std::to_string(
items.size())
170 +
" item(s) (indices start at 0)");
191 const std::string&
action,
192 const nlohmann::json& args) {
194 const long idx = args.value<
long>(
"index", -1);
196 std::string content = args.at(
"content").get<std::string>();
197 items.push_back({content, args.value(
"status", std::string(
"pending"))});
198 logger->info(
"[todo] add: {}", content);
201 +
"' (add, update or remove)");
205 item.status = args.value(
"status",
item.status);
206 item.content = args.value(
"content",
item.content);
207 logger->info(
"[todo] update #{}: {}",
idx,
item.status);
209 logger->info(
"[todo] remove #{}",
idx);
235 auto args = nlohmann::json::parse(
args_json);
236 std::string
action = args.at(
"action").get<std::string>();
244 logger->info(
"[todo] session='{}' action={}",
session,
action);
245 if (!
err.empty()) {
return {
err, {}}; }
247 nlohmann::json result;
248 result[
"action"] =
action;
291 std::vector<std::string> require_context_tiers_;
321 logger->info(
"[delegate] patched enum with {} tiers ({} require context)",
322 tier_names.size(), require_context_tiers_.size());
339 nlohmann::json& result) {
340 auto out = nlohmann::json::array();
341 if (args.contains(
"context") && args[
"context"].is_array()) {
342 for (
const auto&
ref : args[
"context"]) {
343 if (!
ref.is_object()) {
continue; }
344 auto path =
ref.value(
"path", std::string{});
345 if (path.empty()) {
continue; }
346 nlohmann::json entry;
347 entry[
"path"] = path;
348 entry[
"lines"] =
ref.value(
"lines", std::string{});
349 entry[
"note"] =
ref.value(
"note", std::string{});
350 out.push_back(std::move(entry));
353 size_t n = out.size();
354 result[
"context"] = std::move(out);
373 auto args = nlohmann::json::parse(
args_json);
374 std::string target = args.at(
"target").get<std::string>();
375 std::string task = args.at(
"task").get<std::string>();
376 int max_turns = args.value(
"max_turns", -1);
378 nlohmann::json result;
379 result[
"action"] =
"delegate";
380 result[
"target"] = target;
381 result[
"task"] = task;
382 result[
"max_turns"] = max_turns;
390 && std::find(require_context_tiers_.begin(),
391 require_context_tiers_.end(), target)
392 != require_context_tiers_.end()) {
393 logger->warn(
"[delegate] refused: tier '{}' requires context", target);
395 +
"\" requires context. Re-issue entropic.delegate with a "
396 "`context` array naming at least one file path (optionally "
397 "lines/note) the delegate should read."), {}};
400 logger->info(
"[delegate] target='{}' task='{}' max_turns={} context={}",
401 target, task, max_turns,
refs);
442 std::vector<std::string> tier_names_;
444 std::vector<std::string> require_context_tiers_;
471 logger->info(
"[pipeline] patched enum with {} tiers",
513 const std::vector<std::string>& stages,
518 if (stages.size() < 2) {
519 logger->warn(
"[pipeline] rejected: fewer than 2 stages");
522 for (
const auto&
s : stages) {
523 if (!
err.empty()) {
break; }
526 logger->warn(
"[pipeline] invalid stage: '{}'",
s);
536 logger->warn(
"[pipeline] refused: stage '{}' requires context",
s);
538 +
"\" requires context. Re-issue entropic.pipeline with a "
539 "`context` array naming at least one file path the stages "
562 auto args = nlohmann::json::parse(
args_json);
563 auto stages = args.at(
"stages").get<std::vector<std::string>>();
564 std::string task = args.at(
"task").get<std::string>();
566 nlohmann::json result;
567 result[
"action"] =
"pipeline";
568 result[
"stages"] = stages;
569 result[
"task"] = task;
573 stages, tier_names_, require_context_tiers_,
refs);
574 if (!rejection.empty()) {
575 return {rejection, {}};
578 logger->info(
"[pipeline] stages={} task='{}' context={}",
579 stages.size(), task,
refs);
639 auto args = nlohmann::json::parse(
args_json);
640 std::string summary = args.at(
"summary").get<std::string>();
641 bool coverage_gap = args.value(
"coverage_gap",
false);
642 std::string gap_description =
643 args.value(
"gap_description", std::string{});
645 if (args.contains(
"suggested_files")
646 && args[
"suggested_files"].is_array()) {
648 .get<std::vector<std::string>>();
656 if (coverage_gap && gap_description.empty()) {
658 err[
"error"] =
"missing_gap_description";
660 "coverage_gap=true requires a non-empty gap_description "
661 "(what's missing from this answer and why).";
662 return {
err.dump(), {}};
665 logger->info(
"[complete] summary='{}' coverage_gap={} "
666 "gap_description_len={} suggested_files={}",
667 summary, coverage_gap,
668 gap_description.size(),
suggested.size());
670 nlohmann::json result;
671 result[
"action"] =
"complete";
672 result[
"summary"] = entropic::mcp::sanitize_utf8(summary);
673 result[
"coverage_gap"] = coverage_gap;
674 result[
"gap_description"] = entropic::mcp::sanitize_utf8(gap_description);
725 def.description =
"Switch inference phase";
727 R
"({"type":"object","properties":{"phase":{"type":"string"}},"required":["phase"]})";
729 R"({"type":"object","required":["action","phase"],"properties":{"action":{"enum":["phase_change"]},"phase":{"type":"string","description":"the phase requested"}}})";
752 auto args = nlohmann::json::parse(
args_json);
753 std::string phase = args.at(
"phase").get<std::string>();
755 logger->info(
"[phase_change] phase='{}'", phase);
757 nlohmann::json result;
758 result[
"action"] =
"phase_change";
759 result[
"phase"] = phase;
764 return {result.dump(), {
phase_d}};
804 auto args = nlohmann::json::parse(
args_json);
807 int keep_recent = args.value(
"keep_recent",
default_keep);
809 logger->info(
"[prune_context] keep_recent={}", keep_recent);
811 nlohmann::json result;
812 result[
"action"] =
"prune_context";
813 result[
"keep_recent"] = keep_recent;
818 return {result.dump(), {
prune_d}};
887 char* (*
fn)(
void*),
void*
ud) {
892 if (
raw ==
nullptr) {
895 std::string result(
raw);
915 if (
raw ==
nullptr) {
918 std::string result(
raw);
933 char* (*
fn)(
const char*,
void*),
939 if (
raw ==
nullptr) {
942 std::string result(
raw);
962 auto now = std::chrono::system_clock::now();
963 auto ms = std::chrono::duration_cast<
964 std::chrono::milliseconds>(
965 now.time_since_epoch()).
count();
966 snap[
"snapshot_timestamp_ms"] =
ms;
968 snap[
"engine"] = nlohmann::json::parse(
970 snap[
"config"] = nlohmann::json::parse(
972 snap[
"identities"] = nlohmann::json::parse(
974 snap[
"tools"] = nlohmann::json::parse(
976 snap[
"history"] = nlohmann::json::parse(
979 snap[
"metrics"] = nlohmann::json::parse(
984 p.get_docs,
nullptr,
p.user_data);
986 snap[
"docs"] =
nullptr;
1007 if (provider_ ==
nullptr) {
1008 logger->error(
"[diagnose] no state provider set");
1010 "engine state provider not configured"), {}};
1013 auto args = nlohmann::json::parse(
args_json);
1017 logger->info(
"[diagnose] include_docs={} history_limit={}",
1022 return {
snap.dump(), {}};
1139 const nlohmann::json&
j) {
1140 std::vector<std::string>
keys;
1141 for (
auto it =
j.begin();
it !=
j.end(); ++
it) {
1142 keys.push_back(
it.key());
1155 const nlohmann::json&
j) {
1156 std::vector<std::string>
names;
1157 for (
const auto&
item :
j) {
1158 if (
item.is_object() &&
item.contains(
"name")) {
1176 for (
const auto&
k :
keys) {
1177 if (!result.empty()) { result +=
", "; }
1197 const std::string& key,
1198 const std::string&
label,
1199 nlohmann::json& data) {
1200 auto j = nlohmann::json::parse(
json_str);
1201 if (
j.is_object() &&
j.contains(key)) {
1203 }
else if (
j.is_array()) {
1204 for (
const auto&
item :
j) {
1205 if (
item.value(
"name",
"") == key) { data =
item; }
1208 return data.is_null()
1226 char* (*
fn)(
void*),
void*
ud,
1227 const std::string& key,
const std::string&
label,
1228 nlohmann::json& data) {
1232 data = nlohmann::json::parse(
raw);
1252 const std::string& target,
1253 const std::string& key,
1254 nlohmann::json& data) {
1256 if (target ==
"state") {
1257 data = nlohmann::json::parse(
call_provider(
p.get_state,
p.user_data));
1258 }
else if (target ==
"metrics") {
1259 data = nlohmann::json::parse(
call_provider(
p.get_metrics,
p.user_data));
1260 }
else if (target ==
"history") {
1261 int limit = key.empty() ? 10 : std::atoi(key.c_str());
1262 data = nlohmann::json::parse(
1264 }
else if (target ==
"docs") {
1265 const char*
sec = key.empty() ?
nullptr : key.c_str();
1288 const std::string& target,
1289 const std::string& key,
1290 nlohmann::json& data) {
1292 if (target ==
"config") {
1294 }
else if (target ==
"identity") {
1296 }
else if (target ==
"tool") {
1300 +
"'. Supported: config, identity, tool, state, metrics, "
1334 if (provider_ ==
nullptr) {
1335 logger->error(
"[inspect] no state provider set");
1337 "engine state provider not configured"), {}};
1339 auto args = nlohmann::json::parse(
args_json,
nullptr,
false);
1340 if (args.is_discarded() || !args.is_object()) {
1341 args = nlohmann::json::object();
1343 std::string target = args.value(
"target",
"");
1344 std::string key = args.value(
"key",
"");
1345 if (target.empty()) {
1346 logger->info(
"[inspect] full state dump (no target)");
1350 logger->info(
"[inspect] target='{}' key='{}'", target, key);
1351 nlohmann::json data;
1353 nlohmann::json result;
1354 result[
"target"] = target;
1355 result[
"data"] = data;
1356 if (!key.empty()) { result[
"key"] = key; }
1357 return {
err.empty() ? result.dump() :
err, {}};
1375 if (provider_ ==
nullptr) {
1376 logger->error(
"[context_inspect] no state provider set");
1378 "engine state provider not configured"), {}};
1381 auto args = nlohmann::json::parse(
args_json,
nullptr,
false);
1384 if (args.is_discarded() || !args.is_object()) {
1385 args = nlohmann::json::object();
1389 logger->info(
"[context_inspect] max_messages={}",
max_messages);
1390 nlohmann::json result;
1393 return {result.dump(), {}};
1466 auto rows = nlohmann::json::parse(
raw ==
nullptr ?
"[]" :
raw,
nullptr,
false);
1467 nlohmann::json
results = nlohmann::json::array();
1468 for (
const auto&
r :
rows.is_array() ?
rows : nlohmann::json::array()) {
1470 rec[
"delegation_id"] =
r.value(
"delegation_id",
"");
1471 rec[
"target_tier"] =
r.value(
"target_tier",
"");
1472 rec[
"summary"] =
r.value(
"summary",
"");
1473 rec[
"completed_at"] =
r.value(
"completed_at",
"");
1477 out[
"results"] = std::move(
results);
1494 auto args = nlohmann::json::parse(
args_json,
nullptr,
false);
1496 if (args.is_discarded() || !args.is_object()
1497 || args.value(
"query",
"").empty()) {
1498 body =
tool_error_json(
"invalid_args",
"'query' is required and must be non-empty");
1500 logger->warn(
"[followup] no search_delegations provider configured");
1501 body =
tool_error_json(
"storage_unavailable",
"delegation storage not available");
1503 std::string
query = args.value(
"query",
"");
1509 logger->info(
"[followup] query='{}' max_results={} -> {}",
1574 auto args = nlohmann::json::parse(
args_json,
nullptr,
false);
1575 if (args.is_discarded() || !args.is_object()) {
1576 return {
tool_error_json(
"invalid_args",
"an arguments object is required"), {}};
1578 std::string delegation_id = args.value(
"delegation_id",
"");
1579 std::string target = args.value(
"target",
"");
1580 std::string task = args.value(
"task",
"");
1581 int max_turns = args.value(
"max_turns", -1);
1584 if (task.empty() || (delegation_id.empty() && target.empty())) {
1586 "'task' and one of 'delegation_id' or 'target' are required"),
1590 logger->info(
"[resume_delegation] id='{}' target='{}' task='{}' "
1591 "max_turns={}", delegation_id, target, task, max_turns);
1593 nlohmann::json result;
1594 result[
"action"] =
"resume_delegation";
1595 result[
"delegation_id"] = delegation_id;
1596 result[
"target"] = target;
1598 result[
"task"] = task;
1599 result[
"max_turns"] = max_turns;
1623int EntropicServer::register_core_tools(
1627 todo_ = std::make_unique<TodoTool>(std::move(
todo_def));
1632 complete_ = std::make_unique<CompleteTool>(
1636 phase_change_ = std::make_unique<PhaseChangeTool>();
1640 "prune_context",
"entropic",
tools_dir);
1641 prune_context_ = std::make_unique<PruneContextTool>(
1678int EntropicServer::register_delegation_tools(
1687 delegate_ = std::make_unique<DelegateTool>(
1693 pipeline_ = std::make_unique<PipelineTool>(
1700 "resume_delegation",
"entropic",
tools_dir);
1701 resume_delegation_ = std::make_unique<ResumeDelegationTool>(
1721int EntropicServer::register_introspection_tools(
1723 diagnose_ = std::make_unique<DiagnoseTool>(
1727 inspect_ = std::make_unique<InspectTool>(
1731 context_inspect_ = std::make_unique<ContextInspectTool>(
1737 followup_ = std::make_unique<FollowupTool>(
1764 logger->info(
"EntropicServer initialized with {} tools "
1787 const std::string& tool_name)
const {
1788 return tool_name ==
"delegate" || tool_name ==
"pipeline";
1806 state_provider_ = provider;
1807 diagnose_->set_provider(&state_provider_);
1808 inspect_->set_provider(&state_provider_);
1809 context_inspect_->set_provider(&state_provider_);
1811 followup_->set_provider(&state_provider_);
1813 logger->info(
"State provider set for introspection tools");
1825 return todo_->release(key);
1835 return todo_->session_count();
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.
std::size_t session_count() const override
Sessions holding a todo list on this server (gh#158).
void set_state_provider(const entropic_state_provider_t &provider)
Set the engine state provider for introspection tools.
bool release_session(const std::string &key) override
Release key's todo list (gh#158).
~EntropicServer() override
Destructor.
EntropicServer(const std::vector< std::string > &tier_names, const std::string &data_dir, const std::vector< std::string > &require_context_tiers={})
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.
One T per session key, under one leaf mutex (gh#158).
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.
std::string err
size_exceeded, when set
int max_entries
<= 0 means unbounded
long limit
Results to keep.
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.
@ optional
optional: every escape asks the host's path approver
static ToolDefinition phase_change_definition()
The inline phase_change definition, contract included.
static std::vector< std::string > collect_object_keys(const nlohmann::json &j)
Collect keys from a JSON object into a vector.
static std::string inspect_filterable(char *(*fn)(void *), void *ud, const std::string &key, const std::string &label, nlohmann::json &data)
Inspect a filterable target (config/identity/tool).
static std::string followup_results(const char *raw)
Wrap the provider's records in the declared {"results": [...]}.
@ count
Sentinel — MUST remain last.
static std::string dispatch_inspect(const entropic_state_provider_t &p, const std::string &target, const std::string &key, nlohmann::json &data)
Dispatch an inspect query to the appropriate provider.
std::string tool_error_json(const std::string &code, const std::string &message)
A declared tool error: {"error": code, "message": message}.
static bool dispatch_simple_target(const entropic_state_provider_t &p, const std::string &target, const std::string &key, nlohmann::json &data)
Dispatch inspect for simple (non-filterable) targets.
static std::string call_docs_provider(char *(*fn)(const char *, void *), const char *section, void *ud)
Call docs callback with section param.
ENTROPIC_EXPORT std::string current_run_session()
The session key of the run executing on this thread (gh#166).
static std::string call_history_provider(char *(*fn)(int, void *), int max_entries, void *ud)
Call history callback with max_entries param.
static std::string pipeline_rejection(const std::vector< std::string > &stages, const std::vector< std::string > &tier_names, const std::vector< std::string > &require_context_tiers, size_t refs)
Parse pipeline args, validate stages, emit directives.
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 filter_json_by_key(const std::string &json_str, const std::string &key, const std::string &label, nlohmann::json &data)
Select one entry of a provider's JSON by key (object key or array element name).
static size_t carry_context(const nlohmann::json &args, nlohmann::json &result)
Copy a context argument into the result, dropping junk (gh#162).
static std::vector< std::string > collect_array_names(const nlohmann::json &j)
Collect "name" fields from a JSON array of objects.
std::string render_response_format(const std::string &output_schema_json)
Render an outputSchema as the compact "Response format" text a model reads (gh#203).
static std::string list_available_keys(const nlohmann::json &j)
List available keys from a JSON value for error messages.
gh#158: the cancel token of the run executing on THIS thread.
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.
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_history)(int max_entries, void *user_data)
Get recent tool call history as JSON array.
UTF-8 validation + replacement at every system boundary where bytes change ownership.