Entropic 2.11.1
Local-first agentic inference engine
Loading...
Searching...
No Matches
plugin_server.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
11
12#include <dlfcn.h>
13
14#include <utility>
15
16static auto logger = entropic::log::get("mcp.plugin");
17
18namespace entropic {
19
27PluginServer::PluginServer(void* handle, std::filesystem::path path)
28 : handle_(handle), path_(std::move(path)) {}
29
40PluginServer::~PluginServer() {
41 if (instance_ != nullptr && destroy_fn_ != nullptr) {
42 destroy_fn_(instance_);
43 instance_ = nullptr;
44 }
45 if (handle_ != nullptr) {
46 dlclose(handle_);
47 handle_ = nullptr;
48 }
49}
50
70entropic_error_t PluginServer::load(const std::filesystem::path& path,
71 std::unique_ptr<PluginServer>& out) {
72 // RTLD_LOCAL keeps plugin symbols out of the global namespace so two
73 // plugins exporting the same entry-point names cannot collide.
74 dlerror(); // clear any stale error before the call
75 void* handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
76 if (handle == nullptr) {
77 const char* err = dlerror();
78 logger->error("Plugin load failed: dlopen('{}'): {}", path.string(),
79 (err != nullptr) ? err : "unknown error");
81 }
82
83 // Adopt the handle immediately so every failure path below closes it.
84 std::unique_ptr<PluginServer> plugin(new PluginServer(handle, path));
85 auto rc = plugin->resolve_and_init();
86 if (rc != ENTROPIC_OK) {
87 return rc;
88 }
89
90 logger->info("Loaded MCP plugin '{}' from {}", plugin->name_,
91 path.string());
92 out = std::move(plugin);
93 return ENTROPIC_OK;
94}
95
104entropic_error_t PluginServer::resolve_and_init() {
105 auto rc = resolve_and_check_version();
106 if (rc != ENTROPIC_OK) {
107 return rc;
108 }
109 return create_instance();
110}
111
125entropic_error_t PluginServer::create_instance() {
126 instance_ = create_fn_();
127 if (instance_ == nullptr) {
128 logger->error("Plugin load failed: entropic_create_server() returned "
129 "NULL for {}", path_.string());
131 }
132
133 const char* raw_name = name_fn_(instance_);
134 name_ = (raw_name != nullptr) ? raw_name : "";
135 if (name_.empty()) {
136 logger->error("Plugin load failed: entropic_mcp_server_name() gave an "
137 "empty name for {} — the name is the tool-routing "
138 "prefix and cannot be blank", path_.string());
140 }
141 return ENTROPIC_OK;
142}
143
159entropic_error_t PluginServer::resolve_and_check_version() {
160 if (!resolve_symbols()) {
162 }
163
164 const int reported = version_fn_();
165 if (reported != ENTROPIC_MCP_PLUGIN_API_VERSION) {
166 logger->error("Plugin version mismatch for {}: plugin reports API "
167 "version {}, this engine implements {}. Rebuild the "
168 "plugin against matching entropic headers.",
169 path_.string(), reported,
172 }
173 return ENTROPIC_OK;
174}
175
192bool PluginServer::resolve_symbols() {
193 // Both halves run unconditionally: a plugin missing several entry points
194 // should be told about all of them in one build/run cycle.
195 const bool factory_ok = resolve_factory_symbols();
196 const bool instance_ok = resolve_instance_symbols();
197 return factory_ok && instance_ok;
198}
199
208void* PluginServer::resolve_one(const char* symbol_name, bool& ok) const {
209 // The void*-to-function-pointer round trip is the POSIX dlsym idiom;
210 // conditionally-supported in ISO C++ but well-defined on every platform
211 // this engine targets.
212 void* addr = dlsym(handle_, symbol_name);
213 if (addr == nullptr) {
214 logger->error("Plugin {}: missing required entry point '{}'",
215 path_.string(), symbol_name);
216 ok = false;
217 }
218 return addr;
219}
220
227bool PluginServer::resolve_factory_symbols() {
228 bool ok = true;
229 version_fn_ = reinterpret_cast<version_fn_t>(
230 resolve_one("entropic_plugin_api_version", ok));
231 create_fn_ = reinterpret_cast<create_fn_t>(
232 resolve_one("entropic_create_server", ok));
233 name_fn_ = reinterpret_cast<name_fn_t>(
234 resolve_one("entropic_mcp_server_name", ok));
235 free_fn_ = reinterpret_cast<free_fn_t>(
236 resolve_one("entropic_free", ok));
237 return ok;
238}
239
246bool PluginServer::resolve_instance_symbols() {
247 bool ok = true;
248 list_tools_fn_ = reinterpret_cast<list_tools_fn_t>(
249 resolve_one("entropic_mcp_server_list_tools", ok));
250 execute_fn_ = reinterpret_cast<execute_fn_t>(
251 resolve_one("entropic_mcp_server_execute", ok));
252 configure_fn_ = reinterpret_cast<configure_fn_t>(
253 resolve_one("entropic_mcp_server_configure", ok));
254 set_dir_fn_ = reinterpret_cast<set_dir_fn_t>(
255 resolve_one("entropic_mcp_server_set_working_dir", ok));
256 destroy_fn_ = reinterpret_cast<destroy_fn_t>(
257 resolve_one("entropic_mcp_server_destroy", ok));
258 return ok;
259}
260
274std::string PluginServer::take_string(char* raw) const {
275 if (raw == nullptr) {
276 return {};
277 }
278 std::string copy;
279 try {
280 copy.assign(raw);
281 } catch (...) {
282 free_fn_(raw);
283 throw;
284 }
285 free_fn_(raw);
286 return copy;
287}
288
303std::string PluginServer::list_tools() const {
304 std::lock_guard<std::mutex> lock(call_mutex_);
305 auto tools = take_string(list_tools_fn_(instance_));
306 return tools.empty() ? "[]" : tools;
307}
308
328std::string PluginServer::execute(const std::string& tool_name,
329 const std::string& args_json) {
330 std::lock_guard<std::mutex> lock(call_mutex_);
331 return take_string(
332 execute_fn_(instance_, tool_name.c_str(), args_json.c_str()));
333}
334
345entropic_error_t PluginServer::configure(const std::string& config_json) {
346 std::lock_guard<std::mutex> lock(call_mutex_);
347 return configure_fn_(instance_, config_json.c_str());
348}
349
360entropic_error_t PluginServer::set_working_dir(const std::string& dir) {
361 std::lock_guard<std::mutex> lock(call_mutex_);
362 return set_dir_fn_(instance_, dir.c_str());
363}
364
365} // namespace entropic
A loaded MCP server plugin and its C-ABI entry points.
entropic_error_t
Error codes returned by all C API functions.
Definition error.h:37
@ ENTROPIC_OK
Success.
Definition error.h:38
@ ENTROPIC_ERROR_PLUGIN_LOAD_FAILED
dlopen/dlsym failed on plugin .so
Definition error.h:48
@ ENTROPIC_ERROR_PLUGIN_VERSION_MISMATCH
Plugin API version != engine expected version.
Definition error.h:47
#define ENTROPIC_MCP_PLUGIN_API_VERSION
Current MCP plugin API version.
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).
@ ok
Tool dispatched, returned non-empty content.
dlopen-loaded in-process MCP server plugin (gh#133).