Entropic 2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
tool_base.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
12
13#include <nlohmann/json.hpp>
14
15#include <algorithm>
16#include <fstream>
17#include <stdexcept>
18#include <string>
19#include <vector>
20
21static auto logger = entropic::log::get("mcp.tool_base");
22
23namespace entropic {
24
33
40const std::string& ToolBase::name() const {
41 return definition_.name;
42}
43
51 return definition_;
52}
53
66 const std::string& /*args_json*/) const {
67 return "";
68}
69
83
84namespace {
85
86using ojson = nlohmann::ordered_json;
87
88std::string render_shape(const ojson& schema);
89
97std::string render_type(const ojson& type) {
98 if (!type.is_array()) { return type.get<std::string>(); }
99 std::string out;
100 for (const auto& t : type) {
101 out += (out.empty() ? "" : "|") + t.get<std::string>();
102 }
103 return out;
104}
105
114std::string render_alternatives(const ojson& alts, bool literal) {
115 std::string out;
116 for (const auto& a : alts) {
117 out += (out.empty() ? "" : " | ")
118 + (literal ? a.dump() : render_shape(a));
119 }
120 return out;
121}
122
130std::string render_object(const ojson& schema) {
131 const auto required = schema.value("required", ojson::array());
132 const auto props = schema.value("properties", ojson::object());
133 std::string out = "{";
134 for (const auto& [key, prop] : props.items()) {
135 bool req = std::find(required.begin(), required.end(), key) != required.end();
136 out += (out.size() > 1 ? ", " : "") + ojson(key).dump()
137 + (req ? "" : "?") + ": " + render_shape(prop);
138 }
139 const auto extra = schema.value("additionalProperties", ojson(false));
140 if (extra.is_object()) {
141 out += (out.size() > 1 ? ", " : "") + std::string("<string>: ")
143 }
144 return out + "}";
145}
146
154std::string render_typed(const ojson& schema) {
155 const auto type = schema.value("type", ojson());
156 std::string out = "any";
157 if (type == "object") {
158 out = render_object(schema);
159 } else if (type == "array") {
160 out = "[" + render_shape(schema.value("items", ojson::object())) + ", ...]";
161 } else if (!type.is_null()) {
162 out = render_type(type);
163 }
164 return out;
165}
166
174std::string render_shape(const ojson& schema) {
175 std::string out;
176 if (schema.contains("enum")) {
177 out = render_alternatives(schema["enum"], true);
178 } else if (schema.contains("oneOf")) {
179 out = render_alternatives(schema["oneOf"], false);
180 } else {
181 out = render_typed(schema);
182 }
183 return out;
184}
185
193void collect_notes(const ojson& schema, std::vector<std::string>& notes) {
194 const auto props = schema.value("properties", ojson::object());
195 for (const auto& [key, prop] : props.items()) {
196 if (prop.contains("description")) {
197 notes.push_back("- `" + key + "`: " + prop["description"].get<std::string>());
198 }
200 }
201 if (schema.contains("items")) { collect_notes(schema["items"], notes); }
202 const auto branches = schema.value("oneOf", ojson::array());
203 for (const auto& branch : branches) {
205 }
206}
207
215std::string render_variants(const ojson& schema) {
216 std::string out;
217 if (!schema.contains("oneOf")) {
218 return render_shape(schema) + "\n";
219 }
220 int n = 0;
221 for (const auto& branch : schema["oneOf"]) {
222 ++n;
223 out += "- " + branch.value("title", "variant " + std::to_string(n))
224 + ": " + render_shape(branch) + "\n";
225 }
226 return out;
227}
228
229} // namespace
230
244std::string render_response_format(const std::string& output_schema_json) {
245 auto schema = ojson::parse(output_schema_json, nullptr, false);
246 std::string out;
247 if (schema.is_object()) {
248 out = "\n\n**Response format** (JSON):\n" + render_variants(schema);
249 std::vector<std::string> notes;
251 for (const auto& note : notes) { out += note + "\n"; }
252 // Engine-wide, not per tool: a call refused before or thrown out of
253 // the handler (bad arguments, a refused path, an I/O fault) is
254 // reported by the server base as plain text (server_base.cpp).
255 out += "Other failures: plain text `Error: ...`.\n";
256 }
257 return out;
258}
259
269std::string tool_error_json(const std::string& code, const std::string& message) {
270 nlohmann::json err;
271 err["error"] = code;
272 err["message"] = message;
273 return err.dump();
274}
275
298 const std::string& tool_name,
299 const std::string& server_prefix,
300 const std::string& data_dir) {
301
302 std::string path = data_dir;
303 if (!server_prefix.empty()) {
304 path += "/" + server_prefix;
305 }
306 path += "/" + tool_name + ".json";
307
308 std::ifstream file(path);
309 if (!file.is_open()) {
310 throw std::runtime_error(
311 "Tool definition not found: " + path);
312 }
313
314 // ordered: the rendered Response format keeps the authored key order.
315 auto json = nlohmann::ordered_json::parse(file);
316
318 def.name = json.at("name").get<std::string>();
319 def.description = json.value("description", "");
320 def.input_schema = json.at("inputSchema").dump();
321 if (json.contains("outputSchema")) {
322 def.output_schema = json["outputSchema"].dump();
323 def.description += render_response_format(def.output_schema);
324 }
325
326 logger->info("Loaded tool definition: {} from {}",
327 def.name, path);
328 return def;
329}
330
331} // namespace entropic
ToolBase(ToolDefinition def)
Construct with a pre-built definition.
Definition tool_base.cpp:31
const std::string & name() const
Get the tool name.
Definition tool_base.cpp:40
ToolDefinition definition_
Tool definition.
Definition tool_base.h:109
virtual MCPAccessLevel required_access_level() const
Minimum access level required to execute this tool.
Definition tool_base.cpp:80
virtual std::string anchor_key(const std::string &args_json) const
Generate anchor key for this tool result.
Definition tool_base.cpp:65
const ToolDefinition & definition() const
Get the full tool definition.
Definition tool_base.cpp:50
std::string err
size_exceeded, when set
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).
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
std::string tool_error_json(const std::string &code, const std::string &message)
A declared tool error: {"error": code, "message": message}.
MCPAccessLevel
MCP tool access level for per-identity authorization.
Definition config.h:38
@ WRITE
Read + write operations (e.g., write_file, execute)
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).
MCPServerBase concrete base class + ServerResponse.
Parsed tool definition from JSON schema file.
Definition tool_base.h:33
std::string name
Tool name (e.g., "read_file")
Definition tool_base.h:34
Abstract base class for individual MCP tools.