|
Entropic 2.14.3
Local-first agentic inference engine
|
Manages MCP server instances and routes tool calls. More...
#include <entropic/mcp/server_manager.h>
Public Types | |
| using | DiscoveredServerApprover = bool(*)(const std::string &context_json, void *user_data) |
Approver consulted for a discovered .mcp.json server that config neither disabled nor pre-approved (v2.14.0, gh#199). | |
Public Member Functions | |
| ServerManager (const PermissionsConfig &permissions, const std::filesystem::path &project_dir) | |
| Construct with permission config and project directory. | |
| void | register_server (std::unique_ptr< MCPServerBase > server) |
| Register a built-in server (in-process, ownership transferred). | |
| void | init_builtins (const MCPConfig &mcp_config, const std::vector< std::string > &tier_names, const std::string &data_dir, const std::vector< std::string > &require_context_tiers={}) |
| Register built-in servers based on config flags. | |
| entropic_error_t | load_plugins (const MCPConfig &mcp_config) |
Load the dlopen plugins listed in mcp.plugins (gh#133). | |
| void | initialize () |
| Initialize all registered servers. | |
| std::string | list_tools () const |
| List all tools from all connected servers. | |
| std::string | execute (const std::string &tool_name, const std::string &args_json) |
| Execute a tool call via the appropriate server. | |
| MCPServerBase * | get_server (const std::string &name) const |
| Get a registered in-process server by name. | |
| bool | set_outside_root_approver (OutsideRootApprover fn, void *user_data) |
| Install the outside-root approver on this set's filesystem server (v2.13.0). | |
| void | set_discovered_server_approver (DiscoveredServerApprover fn, void *user_data) |
| Install the discovered-server approver (v2.14.0, gh#199). | |
| void | set_own_bridge_socket (const std::filesystem::path &socket) |
| The socket this handle's external bridge binds (v2.14.0, gh#200). | |
| std::size_t | release_session (const std::string &key) |
| Release every in-process server's state for one session (gh#158). | |
| std::vector< std::string > | server_names () const |
| List registered server names (in-process + external). | |
| const std::filesystem::path & | project_dir () const |
| Get the project root directory. | |
| size_t | set_working_dir_all (const std::filesystem::path &dir) |
Point every in-process and plugin server at dir (gh#160). | |
| size_t | enter_working_dir (const std::filesystem::path &dir) |
| Re-root every in-process and plugin server for a sandboxed delegation, and KEEP the root lock (gh#158, gh#160). | |
| size_t | leave_working_dir (const std::filesystem::path &dir) |
| Restore after enter_working_dir(), then release one level of the root lock (gh#158, gh#160). | |
| std::vector< std::string > | external_tools_without_readonly_hint (const std::vector< std::string > &allowed) const |
External tools that do not declare readOnlyHint: true. | |
| std::string | get_tool_schema (const std::string &tool_name) const |
| Get the JSON Schema for a tool's input parameters. | |
| bool | is_explicitly_allowed (const std::string &tool_name, const std::string &args_json) const |
| Check if tool is explicitly allowed (skip prompting). | |
| std::string | get_permission_pattern (const std::string &tool_name, const std::string &args_json) const |
| Generate permission pattern via server class delegation. | |
| bool | skip_duplicate_check (const std::string &tool_name) const |
| Check if tool should skip duplicate detection. | |
| MCPAccessLevel | get_required_access_level (const std::string &tool_name) const |
| Get the required access level for a tool. | |
| void | add_permission (const std::string &pattern, bool allow) |
| Add a runtime permission pattern. | |
| void | shutdown () |
| Shutdown all servers (in-process + external). | |
| std::vector< std::string > | connect_external_server (const ExternalServerConfig &spec) |
| Connect to an external MCP server at runtime (canonical spec-based API). | |
| std::vector< std::string > | connect_external_server (const std::string &name, const std::string &command="", const std::vector< std::string > &args={}, const std::string &url="") |
| Connect by primitive parameters (legacy convenience). | |
| void | disconnect_external_server (const std::string &name) |
| Disconnect and remove an external server. | |
| std::map< std::string, ServerInfo > | list_server_info () const |
| Get snapshot of all servers with current status. | |
| void | process_health_events () |
| Process pending health events (call from engine loop). | |
| void | set_mcp_config (const MCPConfig &config) |
| Set MCP config for external server initialization. | |
| void | interrupt_external_tools () |
| Abort in-flight tool calls across every external MCP client. | |
| void | clear_external_tool_interrupts () |
| Release the interrupt on every external client (gh#150). | |
Static Public Member Functions | |
| static std::vector< std::string > | tools_without_readonly_hint (const std::string &tools_json, const std::vector< std::string > &allowed) |
The readOnlyHint filter itself, over one descriptor list. | |
Manages MCP server instances and routes tool calls.
Owns both in-process servers (v1.8.5) and external MCP clients (v1.8.7). Tool routing by server prefix is uniform across both. External servers discovered from .mcp.json and YAML config.
Definition at line 62 of file server_manager.h.
| using entropic::ServerManager::DiscoveredServerApprover = bool (*)(const std::string& context_json, void* user_data) |
Approver consulted for a discovered .mcp.json server that config neither disabled nor pre-approved (v2.14.0, gh#199).
| context_json | See ENTROPIC_HOOK_PRE_MCP_SERVER_CONNECT. |
| user_data | Forwarded from set_discovered_server_approver(). |
Definition at line 177 of file server_manager.h.
| entropic::ServerManager::ServerManager | ( | const PermissionsConfig & | permissions, |
| const std::filesystem::path & | project_dir | ||
| ) |
Construct with permission config and project directory.
| permissions | Permission configuration. |
| project_dir | Project root directory. |
| permissions | Permission configuration. |
| project_dir | Project root directory. @dg_internal |
Definition at line 36 of file server_manager.cpp.
Add a runtime permission pattern.
| pattern | Permission pattern. |
| allow | true for allow list, false for deny list. |
Backs the operator's "always allow/deny" decision — the pattern takes effect on the next call.
| pattern | Permission pattern, normally the one get_permission_pattern() produced for this call. |
| allow | true for the allow list, false for the deny list. @req REQ-MCP-009 |
Definition at line 875 of file server_manager.cpp.
| void entropic::ServerManager::clear_external_tool_interrupts | ( | ) |
Release the interrupt on every external client (gh#150).
Release the interrupt on every external client.
@req REQ-MCP-025
gh#150: the counterpart to interrupt_external_tools(). Without it an interrupt was permanent — the engine cleared its own flag and the transports were never told, so every external MCP server stayed dead until the host process restarted. For a host serving several clients over the bridge, one disconnect took every external server away from all of them.
@req REQ-MCP-025
Definition at line 1029 of file server_manager.cpp.
| std::vector< std::string > entropic::ServerManager::connect_external_server | ( | const ExternalServerConfig & | spec | ) |
Connect to an external MCP server at runtime (canonical spec-based API).
Connect (canonical spec-based) — Issue #9, v2.1.4.
Issue #9 (v2.1.4): unifies the runtime API and config-file paths. The full ExternalServerConfig is honored — including env, which pre-2.1.4 was silently dropped from the 4-arg path. The ServerConfig.name field is required.
| spec | Full server config (transport/command/args/env/url). |
The full ExternalServerConfig is honored. Replaces the pre-2.1.4 runtime path which silently dropped env (and any future spec field).
| spec | External server configuration. |
Definition at line 1285 of file server_manager.cpp.
| std::vector< std::string > entropic::ServerManager::connect_external_server | ( | const std::string & | name, |
| const std::string & | command = "", |
||
| const std::vector< std::string > & | args = {}, |
||
| const std::string & | url = "" |
||
| ) |
Connect by primitive parameters (legacy convenience).
Legacy primitive-args overload — forwards to spec-based API.
Forwards to the spec-based overload above; provided for backwards-compat with in-tree callers. Pre-2.1.4 callers that passed only command/args/url get default-empty env. Prefer the spec-based overload for new code. (#9, v2.1.4)
| name | Unique server name. |
| command | Stdio command (mutually exclusive with url). |
| args | Stdio command arguments. |
| url | SSE endpoint URL (mutually exclusive with command). |
Retained for in-tree callers that pre-date #9. New code should use the spec-based overload.
@dg_internal
Definition at line 1326 of file server_manager.cpp.
Disconnect and remove an external server.
| name | Server name to disconnect. |
| name | Server name. @dg_internal |
Definition at line 1345 of file server_manager.cpp.
Re-root every in-process and plugin server for a sandboxed delegation, and KEEP the root lock (gh#158, gh#160).
Re-root for a sandboxed delegation and keep the root lock (gh#158).
Takes the root lock exclusively and holds it until the matching leave_working_dir() on the SAME thread. Meanwhile any other thread's in-process / plugin dispatch on this manager waits, then resolves against the restored root — never the sandbox. The calling thread's own dispatches pass straight through and see dir, which is what a sandboxed child needs. Re-entrant: a nested delegation enters again on the same thread.
| dir | Sandbox directory. |
The lock is taken BEFORE the move and kept after it, so from the first re-rooted field to the restore no other thread's in-process or plugin dispatch runs on this manager. The owner's own dispatches pass through.
| dir | Sandbox directory. |
Definition at line 294 of file server_manager.cpp.
| std::string entropic::ServerManager::execute | ( | const std::string & | tool_name, |
| const std::string & | args_json | ||
| ) |
Execute a tool call via the appropriate server.
| tool_name | Fully-qualified name (e.g., "filesystem.read_file"). |
| args_json | JSON arguments string. |
Checks permissions before execution. Returns error JSON if denied or server not found.
Builds the <tool>:<args-summary> pattern and consults the deny list BEFORE routing, so a denied call short-circuits without ever reaching the server. A call matching neither list is not denied here — the engine's approval callback does the prompting.
| tool_name | Fully-qualified name (<server>.<tool>). |
| args_json | JSON arguments. |
result is "Error: Permission denied for <tool>" with empty directives when the deny list matched. @req REQ-MCP-009 @req REQ-MCP-007 @req REQ-MCP-002 Definition at line 435 of file server_manager.cpp.
| std::vector< std::string > entropic::ServerManager::external_tools_without_readonly_hint | ( | const std::vector< std::string > & | allowed | ) | const |
External tools that do not declare readOnlyHint: true.
External tools lacking a read-only assertion (gh#160).
MCP tool annotations ride along in tools/list and are already cached verbatim by ExternalMCPClient; this reads them back. A tool with no annotations at all counts as UNSAFE — the hint is an opt-in assertion, and absence is not a promise.
| allowed | Tool allow-list to restrict the check to (empty = every external tool the manager knows about). |
<server>.<tool>) of the offenders. | allowed | Allow-list to restrict the check to (empty = all). |
Definition at line 374 of file server_manager.cpp.
| std::string entropic::ServerManager::get_permission_pattern | ( | const std::string & | tool_name, |
| const std::string & | args_json | ||
| ) | const |
Generate permission pattern via server class delegation.
Generate permission pattern via server delegation.
| tool_name | Fully-qualified tool name. |
| args_json | Tool arguments as JSON. |
The pattern an "always allow/deny" decision is stored under is chosen by the OWNING server, so a server can pick a finer granularity than the tool name (BashServer keys on the base command).
| tool_name | Fully-qualified tool name. |
| args_json | Tool arguments. |
Definition at line 805 of file server_manager.cpp.
| MCPAccessLevel entropic::ServerManager::get_required_access_level | ( | const std::string & | tool_name | ) | const |
Get the required access level for a tool.
| tool_name | Fully-qualified tool name (e.g., "filesystem.read_file"). |
Falling back to WRITE means an unknown tool name cannot be used to slip a call past a READ-only key set.
| tool_name | Fully-qualified tool name. |
Definition at line 849 of file server_manager.cpp.
| MCPServerBase * entropic::ServerManager::get_server | ( | const std::string & | name | ) | const |
Get a registered in-process server by name.
| name | Server name (e.g., "entropic", "filesystem"). |
| name | Server name (the routing prefix, no tool suffix). |
Definition at line 461 of file server_manager.cpp.
| std::string entropic::ServerManager::get_tool_schema | ( | const std::string & | tool_name | ) | const |
Get the JSON Schema for a tool's input parameters.
| tool_name | Fully-qualified tool name. |
Resolves from the in-process registry first, then falls back to the plugin's own descriptor list, so a plugin tool is argument-validated on exactly the same terms a built-in is.
| tool_name | Fully-qualified tool name (<server>.<tool>). |
Definition at line 558 of file server_manager.cpp.
| void entropic::ServerManager::init_builtins | ( | const MCPConfig & | mcp, |
| const std::vector< std::string > & | tier_names, | ||
| const std::string & | data_dir, | ||
| const std::vector< std::string > & | require_context_tiers = {} |
||
| ) |
Register built-in servers based on config flags.
Creates and registers enabled built-in servers (entropic, filesystem, bash, git, diagnostics, web). Called by the facade after construction.
| mcp_config | MCP config with enable flags. |
| tier_names | Tier names for entropic server schemas. |
| data_dir | Bundled data directory path. |
| require_context_tiers | gh#162 (v2.13.0): tiers that refuse a delegation carrying no context references. Defaulted, so existing callers are unaffected. |
The six in-process servers are all MCPServerBase subclasses, so each arrives with dispatch, envelope shape and anchoring already provided by the base and only its own tools and overrides on top.
v2.13.0: the bash server gets mcp.bash.timeout_seconds, which it now enforces; before, nothing passed a timeout and nothing read one.
| mcp | MCP config with enable flags — a disabled server is never constructed, so its tools never appear in list_tools(). |
| tier_names | Tier names for the entropic server (drives whether delegate/pipeline are registered at all). |
| data_dir | Bundled data directory holding the tool descriptors. |
| require_context_tiers | Tiers that refuse a contextless delegation (gh#162). @req REQ-MCP-001 @req REQ-MCP-007 @req REQ-MCP-023 |
Definition at line 64 of file server_manager.cpp.
| void entropic::ServerManager::initialize | ( | ) |
Initialize all registered servers.
Initialize all registered servers + external connections.
@dg_internal
Definition at line 169 of file server_manager.cpp.
| void entropic::ServerManager::interrupt_external_tools | ( | ) |
Abort in-flight tool calls across every external MCP client.
Signal every external client to cancel its in-flight tool.
Invoked from the engine's interrupt path so Ctrl+C unwinds docs_server.py / bash etc. within ~100ms instead of waiting out the transport timeout. (P1-10, 2.0.6-rc16)
@utility
Called by AgentEngine::interrupt() via a facade-wired callback so tool dispatches to docs_server.py / bash / git unwind within ~100ms of Ctrl+C instead of running to completion. (P1-10, 2.0.6-rc16)
@req REQ-MCP-025
Definition at line 1010 of file server_manager.cpp.
| bool entropic::ServerManager::is_explicitly_allowed | ( | const std::string & | tool_name, |
| const std::string & | args_json | ||
| ) | const |
Check if tool is explicitly allowed (skip prompting).
Check if tool is explicitly allowed.
| tool_name | Fully-qualified tool name. |
| args_json | Tool arguments as JSON. |
Builds the same <tool>:<args-summary> pattern execute() uses, so an argument-level allow entry matches here on exactly the terms it will match at dispatch.
| tool_name | Fully-qualified tool name. |
| args_json | Tool arguments. |
Definition at line 783 of file server_manager.cpp.
Restore after enter_working_dir(), then release one level of the root lock (gh#158, gh#160).
Restore after enter_working_dir(), then release one lock level.
| dir | Directory to restore (the parent's active root). |
| dir | Directory to restore. |
Definition at line 311 of file server_manager.cpp.
| std::map< std::string, ServerInfo > entropic::ServerManager::list_server_info | ( | ) | const |
Get snapshot of all servers with current status.
entropic inspect can tell them apart. @req REQ-MCP-007 Definition at line 1383 of file server_manager.cpp.
| std::string entropic::ServerManager::list_tools | ( | ) | const |
List all tools from all connected servers.
List tools from all servers (in-process + plugin + external).
Concatenates all three server kinds so the model sees one flat tool list and never has to know which kind backs a given tool.
<server>.<tool> descriptors from every in-process server, every loaded plugin and every connected external client; "[]" when nothing is registered. @req REQ-MCP-007 Definition at line 399 of file server_manager.cpp.
| entropic_error_t entropic::ServerManager::load_plugins | ( | const MCPConfig & | mcp | ) |
Load the dlopen plugins listed in mcp.plugins (gh#133).
Load the dlopen plugins listed in mcp.plugins (gh#133).
Each path is opened, version-checked against ENTROPIC_MCP_PLUGIN_API_VERSION, and registered under the name its entropic_mcp_server_name() reports; the plugin's tools then route as <name>.<tool> like any other server's.
Every path is attempted so one bad plugin does not hide the diagnosis of the rest, and each failure is logged with its path. The FIRST failure's code is returned — a configured plugin that fails to load is never skipped silently, because that would leave the operator's stated intent unmet with no signal.
| mcp_config | MCP config carrying the plugin path list. |
A configured-but-absent plugin would leave the operator's stated intent unmet with no signal, so every failure is typed and logged with its path. Every configured path is attempted — one broken plugin must not hide the diagnosis of the rest — and a plugin whose reported name collides with an already-registered in-process, plugin or external server is refused rather than allowed to shadow it.
| mcp | MCP config carrying the plugin path list. |
Definition at line 114 of file server_manager.cpp.
| void entropic::ServerManager::process_health_events | ( | ) |
Process pending health events (call from engine loop).
Process pending health events.
Drained on the engine thread, which is what keeps the monitor thread from mutating engine state across threads.
@req REQ-MCP-025
Definition at line 1427 of file server_manager.cpp.
|
inline |
Get the project root directory.
Definition at line 238 of file server_manager.h.
| void entropic::ServerManager::register_server | ( | std::unique_ptr< MCPServerBase > | server | ) |
Register a built-in server (in-process, ownership transferred).
Register a built-in server.
| server | Server instance. |
| server | Server instance (ownership transferred). @dg_internal |
Definition at line 153 of file server_manager.cpp.
| std::size_t entropic::ServerManager::release_session | ( | const std::string & | key | ) |
Release every in-process server's state for one session (gh#158).
Called by the facade when a session's conversation ends (drop, clear, restore). Each in-process server that derives SessionStateOwner — the filesystem server's read tracker, the entropic server's todo list — forgets key. Takes NO ToolRootLock: the state is per-session and behind its own leaf lock, and waiting on the root lock would stall an API call for the length of another session's sandboxed delegation.
| key | Session key ("" = the default session). |
key. Cross-casts each in-process server to SessionStateOwner rather than adding a virtual to MCPServerBase, whose vtable is plugin ABI. Plugins and external servers hold no engine-side per-session state, so they are not visited. servers_ is written only during configuration, so the iteration needs no lock beyond each owner's own.
| key | Session key ("" = the default session). |
key. @req REQ-LOOP-009 @req REQ-MCP-021 Definition at line 508 of file server_manager.cpp.
| std::vector< std::string > entropic::ServerManager::server_names | ( | ) | const |
List registered server names (in-process + external).
List all registered server names.
Definition at line 529 of file server_manager.cpp.
| void entropic::ServerManager::set_discovered_server_approver | ( | DiscoveredServerApprover | fn, |
| void * | user_data | ||
| ) |
Install the discovered-server approver (v2.14.0, gh#199).
Without one, a discovered server that config did not enable is NOT connected — silence is a refusal. The facade installs a thunk over the handle's ENTROPIC_HOOK_PRE_MCP_SERVER_CONNECT hooks on every set it builds, default and workspace alike.
| fn | Approver, or nullptr to clear. |
| user_data | Forwarded to fn. |
| fn | Approver, or nullptr to clear. |
| user_data | Forwarded to fn. @req REQ-MCP-025 |
Definition at line 1160 of file server_manager.cpp.
Set MCP config for external server initialization.
| config | MCP configuration with external_servers. |
| config | MCP configuration. @dg_internal |
Definition at line 996 of file server_manager.cpp.
| bool entropic::ServerManager::set_outside_root_approver | ( | OutsideRootApprover | fn, |
| void * | user_data | ||
| ) |
Install the outside-root approver on this set's filesystem server (v2.13.0).
Install the outside-root approver on the filesystem server.
The facade calls this on the handle's DEFAULT set only; a named workspace's set never gets one (gh#166 hard confinement).
| fn | Approver, or nullptr to clear. |
| user_data | Forwarded to fn. |
v2.13.0. A set with enable_filesystem: false has nothing to receive it, which is not an error — there is then no path-taking tool whose escapes need deciding.
Shared on root_lock_ (gh#158): the install logs the server's root, which a concurrent sandbox swap writes.
| fn | Approver, or nullptr to clear. |
| user_data | Forwarded to fn. |
Definition at line 482 of file server_manager.cpp.
The socket this handle's external bridge binds (v2.14.0, gh#200).
Record the socket this handle's bridge binds (v2.14.0, gh#200).
Any external server that would connect back to it — declared or discovered — is refused. Empty (the default) means the handle runs no bridge, so there is nothing to connect back to.
| socket | Bound (or about-to-be-bound) bridge socket path. |
| socket | Bridge socket path; empty when the handle runs no bridge. @req REQ-MCP-025 |
Definition at line 1172 of file server_manager.cpp.
Point every in-process and plugin server at dir (gh#160).
The primitive under enter_working_dir() / leave_working_dir(), which the facade's ScopedSandbox swap calls: this is what actually moves filesystem, bash, git and every dlopen plugin into a delegation sandbox and back out again (diagnostics keeps its construction root — it has no set_working_dir). External (stdio/SSE) servers are NOT moved — they are separate processes with their own cwd, which is why a sandboxed delegation refuses to run when the child can reach a writable one (external_tools_without_readonly_hint).
| dir | New working directory for the registry. |
gh#158: exclusive on root_lock_ for its own duration, so it waits out any in-flight dispatch — the write of each server's root field and the filesystem server's ignore-rule reload never overlap a tool call that reads them. Re-entrant for a thread already inside enter_working_dir().
| dir | New working directory. |
Definition at line 264 of file server_manager.cpp.
| void entropic::ServerManager::shutdown | ( | ) |
Shutdown all servers (in-process + external).
Ordered teardown: health monitor stopped, external clients disconnected, then plugin instances destroyed and their libraries dlclosed BEFORE the in-process servers, so nothing can call into an unmapped .so.
@req REQ-MCP-006 @req REQ-MCP-025
Definition at line 892 of file server_manager.cpp.
Check if tool should skip duplicate detection.
| tool_name | Fully-qualified tool name. |
| tool_name | Fully-qualified tool name. |
Definition at line 826 of file server_manager.cpp.
|
static |
The readOnlyHint filter itself, over one descriptor list.
Tools in one descriptor list lacking a read-only assertion.
Split out so the rule — absence of the annotation is NOT a promise — is testable without standing up a real external server.
| tools_json | A tools/list array with prefixed names. |
| allowed | Allow-list to restrict the check to (empty = all). |
| tools_json | A tools/list array (names already prefixed). |
| allowed | Allow-list to restrict the check to (empty = all). |
Definition at line 348 of file server_manager.cpp.