nexo-brain 7.30.20 → 7.30.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/bin/nexo-brain.js +7 -1
- package/bin/nexo-managed-mcp.js +128 -0
- package/package.json +4 -2
- package/src/auto_update.py +7 -0
- package/src/cli.py +69 -0
- package/src/client_sync.py +47 -0
- package/src/closure_plane.py +1101 -0
- package/src/db/_schema.py +137 -2
- package/src/managed_mcp/__init__.py +31 -0
- package/src/managed_mcp/catalog.json +77 -0
- package/src/managed_mcp/catalog.py +269 -0
- package/src/managed_mcp/client_config.py +76 -0
- package/src/managed_mcp/lock.json +52 -0
- package/src/managed_mcp/reconcile.py +320 -0
- package/src/product_knowledge/catalog.json +53 -3
- package/src/server.py +132 -0
- package/tool-enforcement-map.json +135 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.22",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `7.30.
|
|
21
|
+
Version `7.30.22` is the current packaged-runtime line. Patch release over v7.30.21 - Brain now ships managed default MCP catalog/lock reconciliation and the Operational Closure Plane, with Desktop v0.43.14 consuming the coordinated runtime contract.
|
|
22
|
+
|
|
23
|
+
Previously in `7.30.20`: patch release over v7.30.19 - packaged installs now copy the `product_knowledge` package into the installed runtime so `nexo update` can import the new product knowledge tools.
|
|
22
24
|
|
|
23
25
|
Previously in `7.30.19`: patch release over v7.30.18 - product capabilities now live in a structured, validated catalog with read-only discovery tools and preserved legacy system-catalog names.
|
|
24
26
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -1346,7 +1346,7 @@ function getCoreRuntimeFlatFiles(srcDir = path.join(__dirname, "..", "src")) {
|
|
|
1346
1346
|
}
|
|
1347
1347
|
|
|
1348
1348
|
function getCoreRuntimePackages() {
|
|
1349
|
-
return ["db", "cognitive", "doctor", "local_context", "product_knowledge"];
|
|
1349
|
+
return ["db", "cognitive", "doctor", "local_context", "managed_mcp", "product_knowledge"];
|
|
1350
1350
|
}
|
|
1351
1351
|
|
|
1352
1352
|
// Brain contracts — files the NEXO Brain publishes to consumers like
|
|
@@ -4306,6 +4306,12 @@ async function runSetup() {
|
|
|
4306
4306
|
const runtimeCliPath = path.join(NEXO_HOME, "bin", "nexo");
|
|
4307
4307
|
fs.writeFileSync(runtimeCliPath, runtimeCli);
|
|
4308
4308
|
fs.chmodSync(runtimeCliPath, 0o755);
|
|
4309
|
+
const managedMcpSource = path.join(__dirname, "nexo-managed-mcp.js");
|
|
4310
|
+
if (fs.existsSync(managedMcpSource)) {
|
|
4311
|
+
const managedMcpTarget = path.join(NEXO_HOME, "bin", "nexo-managed-mcp");
|
|
4312
|
+
fs.copyFileSync(managedMcpSource, managedMcpTarget);
|
|
4313
|
+
fs.chmodSync(managedMcpTarget, 0o755);
|
|
4314
|
+
}
|
|
4309
4315
|
|
|
4310
4316
|
log("Copying core packages...");
|
|
4311
4317
|
// Core packages (directories with __init__.py)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { spawn } = require("child_process");
|
|
7
|
+
|
|
8
|
+
function nexoHome() {
|
|
9
|
+
return process.env.NEXO_HOME || path.join(require("os").homedir(), ".nexo");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readJson(file) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findEntry(capabilityId) {
|
|
21
|
+
const state = readJson(path.join(nexoHome(), "runtime", "managed-mcp", "installed-state.json"));
|
|
22
|
+
const desired = state && state.desired && typeof state.desired === "object" ? state.desired : {};
|
|
23
|
+
for (const clientEntries of Object.values(desired)) {
|
|
24
|
+
if (!clientEntries || typeof clientEntries !== "object") continue;
|
|
25
|
+
for (const entry of Object.values(clientEntries)) {
|
|
26
|
+
const meta = entry && entry.nexo;
|
|
27
|
+
if (meta && meta.capability_id === capabilityId) return entry;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function disabledList() {
|
|
34
|
+
return String(process.env.NEXO_MANAGED_MCP_DISABLED_CAPABILITIES || "")
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((item) => item.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isDisabled(capabilityId) {
|
|
41
|
+
const global = String(process.env.NEXO_MANAGED_MCP_DISABLE || "").trim().toLowerCase();
|
|
42
|
+
if (["1", "true", "yes", "on"].includes(global)) return true;
|
|
43
|
+
return disabledList().includes(capabilityId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readInstalledState() {
|
|
47
|
+
return readJson(path.join(nexoHome(), "runtime", "managed-mcp", "installed-state.json")) || {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function providerEnv() {
|
|
51
|
+
return {
|
|
52
|
+
PATH: process.env.PATH || "",
|
|
53
|
+
HOME: process.env.HOME || "",
|
|
54
|
+
NEXO_HOME: nexoHome(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function stagedProviderCommand(providerId, state) {
|
|
59
|
+
const providers = state && state.providers && typeof state.providers === "object" ? state.providers : {};
|
|
60
|
+
const provider = providers[providerId] && typeof providers[providerId] === "object" ? providers[providerId] : {};
|
|
61
|
+
const explicit = process.platform === "win32"
|
|
62
|
+
? (provider.executable_win32 || provider.executable)
|
|
63
|
+
: provider.executable;
|
|
64
|
+
if (explicit) return String(explicit);
|
|
65
|
+
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
66
|
+
return path.join(nexoHome(), "runtime", "managed-mcp", "artifacts", providerId, "bin", `${providerId}${suffix}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function spawnProvider(command, args, env) {
|
|
70
|
+
const child = spawn(command, args, {
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
env,
|
|
73
|
+
});
|
|
74
|
+
child.on("exit", (code, signal) => {
|
|
75
|
+
if (signal) process.kill(process.pid, signal);
|
|
76
|
+
process.exit(typeof code === "number" ? code : 1);
|
|
77
|
+
});
|
|
78
|
+
child.on("error", (error) => {
|
|
79
|
+
console.error(`NEXO managed MCP failed to start: ${error && error.message ? error.message : String(error)}`);
|
|
80
|
+
process.exit(69);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
const [command, capabilityId] = process.argv.slice(2);
|
|
86
|
+
if (command !== "run" || !capabilityId) {
|
|
87
|
+
console.error("usage: nexo-managed-mcp run <capability_id>");
|
|
88
|
+
process.exit(64);
|
|
89
|
+
}
|
|
90
|
+
if (isDisabled(capabilityId)) {
|
|
91
|
+
console.error(`NEXO managed MCP capability '${capabilityId}' is disabled by policy.`);
|
|
92
|
+
process.exit(78);
|
|
93
|
+
}
|
|
94
|
+
const state = readInstalledState();
|
|
95
|
+
const entry = findEntry(capabilityId);
|
|
96
|
+
const meta = entry && entry.nexo ? entry.nexo : {};
|
|
97
|
+
const providerId = meta.provider_id || "";
|
|
98
|
+
if (!providerId) {
|
|
99
|
+
console.error(`NEXO managed MCP capability '${capabilityId}' is not installed yet.`);
|
|
100
|
+
process.exit(69);
|
|
101
|
+
}
|
|
102
|
+
const providerCommand = stagedProviderCommand(providerId, state);
|
|
103
|
+
const env = {
|
|
104
|
+
...providerEnv(),
|
|
105
|
+
NEXO_MANAGED_MCP_CAPABILITY: capabilityId,
|
|
106
|
+
NEXO_MANAGED_MCP_PROVIDER: providerId,
|
|
107
|
+
NEXO_MANAGED_MCP_RISK: String(meta.risk || ""),
|
|
108
|
+
};
|
|
109
|
+
if (!fs.existsSync(providerCommand)) {
|
|
110
|
+
const providerPackage = String(meta.provider_package || "");
|
|
111
|
+
const providerVersion = String(meta.provider_version || "");
|
|
112
|
+
const providerBin = String(meta.provider_bin || providerId);
|
|
113
|
+
if (String(process.env.NEXO_MANAGED_MCP_ALLOW_NPX_FALLBACK || "").trim() !== "1") {
|
|
114
|
+
console.error(`NEXO managed MCP provider '${providerId}' is not staged. Run nexo_managed_mcp_status(apply=true) to install managed providers.`);
|
|
115
|
+
process.exit(69);
|
|
116
|
+
}
|
|
117
|
+
if (!providerPackage || !providerVersion || providerVersion === "0.0.0-managed") {
|
|
118
|
+
console.error(`NEXO managed MCP provider '${providerId}' is not staged and has no exact locked npm package.`);
|
|
119
|
+
process.exit(69);
|
|
120
|
+
}
|
|
121
|
+
const npxBin = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
122
|
+
spawnProvider(npxBin, ["--yes", "--package", `${providerPackage}@${providerVersion}`, providerBin], env);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
spawnProvider(providerCommand, [], env);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.22",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
|
7
7
|
"bin": {
|
|
8
8
|
"nexo-brain": "bin/nexo-brain.js",
|
|
9
|
-
"nexo": "bin/nexo.js"
|
|
9
|
+
"nexo": "bin/nexo.js",
|
|
10
|
+
"nexo-managed-mcp": "bin/nexo-managed-mcp.js"
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
12
13
|
"claude-code",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
"files": [
|
|
79
80
|
"bin/nexo-brain.js",
|
|
80
81
|
"bin/nexo.js",
|
|
82
|
+
"bin/nexo-managed-mcp.js",
|
|
81
83
|
"bin/windows-wsl-bridge.js",
|
|
82
84
|
"bin/postinstall.js",
|
|
83
85
|
"scripts/sync_release_artifacts.py",
|
package/src/auto_update.py
CHANGED
|
@@ -4828,6 +4828,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
|
|
|
4828
4828
|
"cognitive",
|
|
4829
4829
|
"doctor",
|
|
4830
4830
|
"local_context",
|
|
4831
|
+
"managed_mcp",
|
|
4831
4832
|
"product_knowledge",
|
|
4832
4833
|
"dashboard",
|
|
4833
4834
|
"rules",
|
|
@@ -4970,6 +4971,12 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
|
|
|
4970
4971
|
wrapper = bin_dir / "nexo"
|
|
4971
4972
|
wrapper.write_text(_runtime_cli_wrapper_text())
|
|
4972
4973
|
wrapper.chmod(0o755)
|
|
4974
|
+
managed_mcp_src = repo_dir / "bin" / "nexo-managed-mcp.js"
|
|
4975
|
+
if managed_mcp_src.is_file():
|
|
4976
|
+
managed_mcp_target = bin_dir / "nexo-managed-mcp"
|
|
4977
|
+
_remove_runtime_copy_target(managed_mcp_target)
|
|
4978
|
+
shutil.copy2(str(managed_mcp_src), str(managed_mcp_target))
|
|
4979
|
+
managed_mcp_target.chmod(0o755)
|
|
4973
4980
|
|
|
4974
4981
|
return {
|
|
4975
4982
|
"packages": copied_packages,
|
package/src/cli.py
CHANGED
|
@@ -170,6 +170,48 @@ def _mcp_status(args) -> int:
|
|
|
170
170
|
)
|
|
171
171
|
|
|
172
172
|
|
|
173
|
+
def _closure(args) -> int:
|
|
174
|
+
from closure_plane import closure_next, closure_snapshot, closure_status, refresh_closure_items
|
|
175
|
+
from db import get_db
|
|
176
|
+
from db._schema import run_migrations
|
|
177
|
+
|
|
178
|
+
conn = get_db()
|
|
179
|
+
run_migrations(conn)
|
|
180
|
+
command = str(getattr(args, "closure_command", "") or "status")
|
|
181
|
+
if command == "status":
|
|
182
|
+
payload = closure_status(
|
|
183
|
+
conn,
|
|
184
|
+
refresh=not bool(getattr(args, "no_refresh", False)),
|
|
185
|
+
limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
|
|
186
|
+
)
|
|
187
|
+
elif command == "next":
|
|
188
|
+
if bool(getattr(args, "refresh", False)):
|
|
189
|
+
refresh_closure_items(conn)
|
|
190
|
+
payload = {
|
|
191
|
+
"ok": True,
|
|
192
|
+
"items": closure_next(
|
|
193
|
+
conn,
|
|
194
|
+
limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
|
|
195
|
+
include_waiting=bool(getattr(args, "include_waiting", False)),
|
|
196
|
+
source=str(getattr(args, "source", "") or ""),
|
|
197
|
+
kind=str(getattr(args, "kind", "") or ""),
|
|
198
|
+
state=str(getattr(args, "state", "") or ""),
|
|
199
|
+
max_risk=getattr(args, "max_risk", 0.0),
|
|
200
|
+
area=str(getattr(args, "area", "") or ""),
|
|
201
|
+
),
|
|
202
|
+
}
|
|
203
|
+
elif command == "snapshot":
|
|
204
|
+
payload = closure_snapshot(
|
|
205
|
+
conn,
|
|
206
|
+
refresh=not bool(getattr(args, "no_refresh", False)),
|
|
207
|
+
snapshot_date=str(getattr(args, "date", "") or ""),
|
|
208
|
+
limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
payload = {"ok": False, "error": "unsupported closure command"}
|
|
212
|
+
return _print_json_or_text(payload, as_json=bool(getattr(args, "json", False)))
|
|
213
|
+
|
|
214
|
+
|
|
173
215
|
def _mcp_write_message(stdin, payload: dict) -> None:
|
|
174
216
|
raw = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
175
217
|
stdin.write(raw + b"\n")
|
|
@@ -4307,6 +4349,29 @@ def main():
|
|
|
4307
4349
|
help="Install Codex automatically when the selected runtime needs it and it is missing.",
|
|
4308
4350
|
)
|
|
4309
4351
|
|
|
4352
|
+
# -- closure --
|
|
4353
|
+
closure_parser = sub.add_parser("closure", help="Operational closure queue")
|
|
4354
|
+
closure_sub = closure_parser.add_subparsers(dest="closure_command")
|
|
4355
|
+
closure_status_p = closure_sub.add_parser("status", help="Show closure queue status")
|
|
4356
|
+
closure_status_p.add_argument("--limit", type=int, default=10, help="Top items to include")
|
|
4357
|
+
closure_status_p.add_argument("--no-refresh", action="store_true", help="Do not refresh adapters before reading")
|
|
4358
|
+
closure_status_p.add_argument("--json", action="store_true", help="JSON output")
|
|
4359
|
+
closure_next_p = closure_sub.add_parser("next", help="Show ranked closure items")
|
|
4360
|
+
closure_next_p.add_argument("--limit", type=int, default=10, help="Maximum items")
|
|
4361
|
+
closure_next_p.add_argument("--include-waiting", action="store_true", help="Include waiting_user/blocked items")
|
|
4362
|
+
closure_next_p.add_argument("--refresh", action="store_true", help="Refresh adapters before reading")
|
|
4363
|
+
closure_next_p.add_argument("--source", default="", help="Filter by source")
|
|
4364
|
+
closure_next_p.add_argument("--kind", default="", help="Filter by kind")
|
|
4365
|
+
closure_next_p.add_argument("--state", default="", help="Filter by state, e.g. ready or waiting_user")
|
|
4366
|
+
closure_next_p.add_argument("--area", default="", help="Filter by owner/source/kind/payload area")
|
|
4367
|
+
closure_next_p.add_argument("--max-risk", type=float, default=0.0, help="Filter by maximum risk_score")
|
|
4368
|
+
closure_next_p.add_argument("--json", action="store_true", help="JSON output")
|
|
4369
|
+
closure_snapshot_p = closure_sub.add_parser("snapshot", help="Write and show the daily closure snapshot")
|
|
4370
|
+
closure_snapshot_p.add_argument("--date", default="", help="Snapshot date YYYY-MM-DD")
|
|
4371
|
+
closure_snapshot_p.add_argument("--limit", type=int, default=10, help="Top items to include")
|
|
4372
|
+
closure_snapshot_p.add_argument("--no-refresh", action="store_true", help="Do not refresh adapters before snapshot")
|
|
4373
|
+
closure_snapshot_p.add_argument("--json", action="store_true", help="JSON output")
|
|
4374
|
+
|
|
4310
4375
|
# -- preferences --
|
|
4311
4376
|
preferences_parser = sub.add_parser(
|
|
4312
4377
|
"preferences",
|
|
@@ -4848,6 +4913,10 @@ def main():
|
|
|
4848
4913
|
return _clients_sync(args)
|
|
4849
4914
|
clients_parser.print_help()
|
|
4850
4915
|
return 0
|
|
4916
|
+
elif args.command == "closure":
|
|
4917
|
+
if not args.closure_command:
|
|
4918
|
+
args.closure_command = "status"
|
|
4919
|
+
return _closure(args)
|
|
4851
4920
|
elif args.command == "mcp":
|
|
4852
4921
|
if args.mcp_command == "status":
|
|
4853
4922
|
return _mcp_status(args)
|
package/src/client_sync.py
CHANGED
|
@@ -23,6 +23,13 @@ except ModuleNotFoundError: # Python < 3.11
|
|
|
23
23
|
from bootstrap_docs import sync_client_bootstrap
|
|
24
24
|
from runtime_home import resolve_nexo_home
|
|
25
25
|
|
|
26
|
+
try:
|
|
27
|
+
from managed_mcp import build_managed_server_entries, merge_json_mcp_servers, merge_toml_mcp_servers
|
|
28
|
+
except Exception:
|
|
29
|
+
build_managed_server_entries = None
|
|
30
|
+
merge_json_mcp_servers = None
|
|
31
|
+
merge_toml_mcp_servers = None
|
|
32
|
+
|
|
26
33
|
try:
|
|
27
34
|
from client_preferences import (
|
|
28
35
|
BACKEND_NONE,
|
|
@@ -832,6 +839,32 @@ def build_server_config(
|
|
|
832
839
|
return config
|
|
833
840
|
|
|
834
841
|
|
|
842
|
+
def _managed_mcp_entries_for(client: str, server_config: dict) -> dict:
|
|
843
|
+
if str(os.environ.get("NEXO_MANAGED_MCP_DISABLE", "")).strip().lower() in {"1", "true", "yes", "on"}:
|
|
844
|
+
return {}
|
|
845
|
+
if not build_managed_server_entries:
|
|
846
|
+
return {}
|
|
847
|
+
env = server_config.get("env") if isinstance(server_config.get("env"), dict) else {}
|
|
848
|
+
nexo_home = str(env.get("NEXO_HOME") or "").strip()
|
|
849
|
+
runtime_root = str(env.get("NEXO_CODE") or "").strip()
|
|
850
|
+
if not nexo_home:
|
|
851
|
+
return {}
|
|
852
|
+
target_platform = str(
|
|
853
|
+
os.environ.get("NEXO_MANAGED_MCP_PLATFORM")
|
|
854
|
+
or env.get("NEXO_MANAGED_MCP_PLATFORM")
|
|
855
|
+
or ""
|
|
856
|
+
).strip()
|
|
857
|
+
try:
|
|
858
|
+
return build_managed_server_entries(
|
|
859
|
+
client=client,
|
|
860
|
+
nexo_home=nexo_home,
|
|
861
|
+
runtime_root=runtime_root or None,
|
|
862
|
+
platform=target_platform or None,
|
|
863
|
+
)
|
|
864
|
+
except Exception:
|
|
865
|
+
return {}
|
|
866
|
+
|
|
867
|
+
|
|
835
868
|
def _claude_code_settings_path(home: Path | None = None) -> Path:
|
|
836
869
|
base = home or _user_home()
|
|
837
870
|
return base / ".claude" / "settings.json"
|
|
@@ -1001,6 +1034,11 @@ def _sync_codex_managed_config(
|
|
|
1001
1034
|
"env": dict(server_config.get("env", {}) or {}),
|
|
1002
1035
|
}
|
|
1003
1036
|
codex_table["managed_server_command"] = server_config.get("command", "")
|
|
1037
|
+
managed_entries = _managed_mcp_entries_for("codex", server_config)
|
|
1038
|
+
if managed_entries and merge_toml_mcp_servers:
|
|
1039
|
+
payload = merge_toml_mcp_servers(payload, managed_entries)
|
|
1040
|
+
codex_table = payload.setdefault("nexo", {}).setdefault("codex", {})
|
|
1041
|
+
codex_table["managed_default_mcp_count"] = len(managed_entries)
|
|
1004
1042
|
|
|
1005
1043
|
# Ensure Codex headless crons (followup-runner, email-monitor, deep-sleep,
|
|
1006
1044
|
# etc.) do not stall on approval prompts. Only set defaults when the user
|
|
@@ -1018,6 +1056,7 @@ def _sync_codex_managed_config(
|
|
|
1018
1056
|
"hooks_enabled": True,
|
|
1019
1057
|
"model": runtime_profile.get("model", ""),
|
|
1020
1058
|
"reasoning_effort": runtime_profile.get("reasoning_effort", "") or "",
|
|
1059
|
+
"managed_default_mcp_count": len(_managed_mcp_entries_for("codex", server_config)) if server_config else 0,
|
|
1021
1060
|
}
|
|
1022
1061
|
|
|
1023
1062
|
|
|
@@ -1408,12 +1447,16 @@ def _sync_json_client(path: Path, server_config: dict, label: str, *, managed_me
|
|
|
1408
1447
|
nexo_meta = {}
|
|
1409
1448
|
payload["nexo"] = nexo_meta
|
|
1410
1449
|
nexo_meta.update(managed_metadata)
|
|
1450
|
+
managed_entries = _managed_mcp_entries_for(label, server_config)
|
|
1451
|
+
if managed_entries and merge_json_mcp_servers:
|
|
1452
|
+
payload = merge_json_mcp_servers(payload, managed_entries)
|
|
1411
1453
|
_write_json_object(path, payload)
|
|
1412
1454
|
return {
|
|
1413
1455
|
"ok": True,
|
|
1414
1456
|
"client": label,
|
|
1415
1457
|
"action": action,
|
|
1416
1458
|
"path": str(path),
|
|
1459
|
+
"managed_default_mcp_count": len(managed_entries),
|
|
1417
1460
|
}
|
|
1418
1461
|
|
|
1419
1462
|
|
|
@@ -1491,6 +1534,9 @@ def _sync_claude_code_settings(path: Path, server_config: dict) -> dict:
|
|
|
1491
1534
|
runtime_root=runtime_root,
|
|
1492
1535
|
nexo_home=nexo_home,
|
|
1493
1536
|
)
|
|
1537
|
+
managed_entries = _managed_mcp_entries_for("claude_code", server_config)
|
|
1538
|
+
if managed_entries and merge_json_mcp_servers:
|
|
1539
|
+
payload = merge_json_mcp_servers(payload, managed_entries)
|
|
1494
1540
|
_ensure_headless_permissions(payload)
|
|
1495
1541
|
_write_json_object(path, payload)
|
|
1496
1542
|
return {
|
|
@@ -1499,6 +1545,7 @@ def _sync_claude_code_settings(path: Path, server_config: dict) -> dict:
|
|
|
1499
1545
|
"action": action,
|
|
1500
1546
|
"path": str(path),
|
|
1501
1547
|
"managed_hook_count": managed_hook_count,
|
|
1548
|
+
"managed_default_mcp_count": len(managed_entries),
|
|
1502
1549
|
}
|
|
1503
1550
|
|
|
1504
1551
|
|