11#include <nlohmann/json.hpp>
31 : project_dir_(
std::
move(project_dir)) {}
50 std::vector<ExternalServerConfig> result;
51 std::set<std::string>
seen;
63 logger->info(
"Discovered {} external servers from .mcp.json",
78void MCPJsonDiscovery::parse_mcp_json(
79 const std::filesystem::path& path,
82 std::set<std::string>&
seen,
83 std::vector<ExternalServerConfig>& out)
const {
85 if (!std::filesystem::exists(path)) {
91 std::ifstream
f(path);
92 data = nlohmann::json::parse(
f);
93 }
catch (
const std::exception&
e) {
94 logger->warn(
"Failed to read {}: {}", path.string(),
e.what());
126void MCPJsonDiscovery::parse_server_entry(
127 const std::string& name,
128 const nlohmann::json&
cfg,
131 std::set<std::string>&
seen,
132 std::vector<ExternalServerConfig>& out)
const {
135 if (
seen.count(name) > 0) {
140 if (
cfg.contains(
"path")) {
141 auto sock = std::filesystem::path(
143 if (std::filesystem::weakly_canonical(
sock) ==
144 std::filesystem::weakly_canonical(
own_socket)) {
145 logger->debug(
"Skipping '{}' (matches own socket)", name);
153 logger->warn(
".mcp.json entry '{}' shadowed by existing "
154 "config — skipping", name);
160 ExternalServerConfig entry;
162 std::string type =
cfg.value(
"type",
"");
164 if (infer_transport(
cfg, type, entry)) {
166 out.push_back(std::move(entry));
167 logger->info(
"Discovered external server '{}' "
168 "(transport={})", name, entry.transport);
184bool MCPJsonDiscovery::infer_transport(
185 const nlohmann::json&
cfg,
186 const std::string& type,
187 ExternalServerConfig& entry)
const {
193 if (type ==
"sse" || type ==
"http" ||
195 return parse_sse_entry(
cfg, entry);
197 if (type ==
"stdio" || (type.empty() &&
has_command)) {
198 return parse_stdio_entry(
cfg, entry);
201 logger->warn(
".mcp.json entry '{}': cannot determine transport "
202 "(no url or command)", entry.name);
215bool MCPJsonDiscovery::parse_sse_entry(
216 const nlohmann::json&
cfg,
217 ExternalServerConfig& entry)
const {
219 entry.url =
cfg.value(
"url",
"");
220 if (entry.url.empty()) {
221 logger->warn(
".mcp.json entry '{}' missing 'url' — skipping",
225 entry.transport =
"sse";
243bool MCPJsonDiscovery::parse_stdio_entry(
244 const nlohmann::json&
cfg,
245 ExternalServerConfig& entry)
const {
247 entry.command =
cfg.value(
"command",
"");
248 if (entry.command.empty()) {
249 logger->warn(
".mcp.json entry '{}' missing 'command' — "
250 "skipping", entry.name);
253 entry.transport =
"stdio";
256 if (
cfg.contains(
"args") &&
cfg[
"args"].is_array()) {
257 for (
const auto&
arg :
cfg[
"args"]) {
258 entry.args.push_back(
arg.get<std::string>());
263 if (
cfg.contains(
"env") &&
cfg[
"env"].is_object()) {
266 logger->warn(
".mcp.json '{}': blocked env var '{}' "
267 "— skipping", entry.name, key);
270 entry.env[key] =
val.get<std::string>();
289 const std::filesystem::path& project_dir) {
291 auto resolved = std::filesystem::weakly_canonical(project_dir);
292 auto input = resolved.string();
295 auto h = std::hash<std::string>{}(
input);
296 std::ostringstream
ss;
297 ss << std::hex << std::setfill(
'0') << std::setw(16) <<
h;
300 auto home = std::filesystem::path(
302 return home /
".entropic" /
"socks" / (
hash_str +
".sock");
313 return (key.size() >= 9 && key.substr(0, 9) ==
"ENTROPIC_") ||
314 (key.size() >= 5 && key.substr(0, 5) ==
"DYLD_");
333 static const std::array<std::string, 7> blocked = {{
334 "LD_PRELOAD",
"LD_LIBRARY_PATH",
"PATH",
335 "HOME",
"SHELL",
"DYLD_INSERT_LIBRARIES",
339 bool exact_match = std::any_of(blocked.begin(), blocked.end(),
340 [&key](
const std::string&
b) { return key == b; });
std::vector< ExternalServerConfig > discover(const std::set< std::string > &existing_names) const
Discover servers from .mcp.json files.
MCPJsonDiscovery(std::filesystem::path project_dir)
Construct with project directory.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Discovers external MCP servers from .mcp.json files.
Activate model on GPU (WARM → ACTIVE).
static bool has_blocked_prefix(const std::string &key)
Check if key matches a blocked prefix pattern.
std::filesystem::path compute_socket_path(const std::filesystem::path &project_dir)
Compute project-unique Unix socket path for self-detection.
@ optional
optional: every escape asks the host's path approver
bool is_blocked_env_var(const std::string &key)
Environment variable blocklist for .mcp.json env field.