nexo-brain 7.30.19 → 7.30.21
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 +4 -2
- package/bin/nexo-brain.js +1 -1
- package/bin/nexo-managed-mcp.js +90 -0
- package/package.json +4 -2
- package/src/auto_update.py +16 -1
- package/src/client_sync.py +47 -0
- package/src/closure_plane.py +720 -0
- package/src/db/_schema.py +93 -0
- package/src/managed_mcp/__init__.py +31 -0
- package/src/managed_mcp/catalog.json +77 -0
- package/src/managed_mcp/catalog.py +263 -0
- package/src/managed_mcp/client_config.py +76 -0
- package/src/managed_mcp/lock.json +52 -0
- package/src/managed_mcp/reconcile.py +122 -0
- package/src/plugins/update.py +1 -0
- package/src/server.py +70 -0
- package/tool-enforcement-map.json +90 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .catalog import build_managed_server_entries, load_catalog, load_lock, validate_catalog_lock
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _state_dir(nexo_home: Path) -> Path:
|
|
13
|
+
return nexo_home / "runtime" / "managed-mcp"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _state_path(nexo_home: Path) -> Path:
|
|
17
|
+
return _state_dir(nexo_home) / "installed-state.json"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read_state(nexo_home: Path) -> dict[str, Any]:
|
|
21
|
+
path = _state_path(nexo_home)
|
|
22
|
+
if not path.is_file():
|
|
23
|
+
return {}
|
|
24
|
+
try:
|
|
25
|
+
payload = json.loads(path.read_text())
|
|
26
|
+
except Exception:
|
|
27
|
+
return {}
|
|
28
|
+
return payload if isinstance(payload, dict) else {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _write_state(nexo_home: Path, state: dict[str, Any]) -> None:
|
|
32
|
+
directory = _state_dir(nexo_home)
|
|
33
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
path = _state_path(nexo_home)
|
|
35
|
+
tmp = path.with_suffix(".tmp")
|
|
36
|
+
tmp.write_text(json.dumps(state, indent=2, sort_keys=True) + "\n")
|
|
37
|
+
tmp.replace(path)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def reconcile_managed_mcp(
|
|
41
|
+
*,
|
|
42
|
+
nexo_home: str | os.PathLike[str] | Path,
|
|
43
|
+
runtime_root: str | os.PathLike[str] | Path | None = None,
|
|
44
|
+
clients: list[str] | tuple[str, ...] | None = None,
|
|
45
|
+
apply: bool = False,
|
|
46
|
+
platform: str | None = None,
|
|
47
|
+
) -> dict[str, Any]:
|
|
48
|
+
nexo_home_path = Path(nexo_home).expanduser()
|
|
49
|
+
catalog = load_catalog()
|
|
50
|
+
lock = load_lock()
|
|
51
|
+
validation = validate_catalog_lock(catalog, lock)
|
|
52
|
+
desired_clients = list(clients or ("claude_code", "claude_desktop", "codex"))
|
|
53
|
+
desired: dict[str, Any] = {}
|
|
54
|
+
for client in desired_clients:
|
|
55
|
+
desired[client] = build_managed_server_entries(
|
|
56
|
+
client=client,
|
|
57
|
+
nexo_home=nexo_home_path,
|
|
58
|
+
runtime_root=runtime_root,
|
|
59
|
+
catalog=catalog,
|
|
60
|
+
lock=lock,
|
|
61
|
+
platform=platform,
|
|
62
|
+
)
|
|
63
|
+
previous = _read_state(nexo_home_path)
|
|
64
|
+
previous_desired = previous.get("desired") if isinstance(previous.get("desired"), dict) else {}
|
|
65
|
+
actions: list[dict[str, str]] = []
|
|
66
|
+
for client, entries in desired.items():
|
|
67
|
+
old_entries = previous_desired.get(client) if isinstance(previous_desired, dict) else {}
|
|
68
|
+
old_entries = old_entries if isinstance(old_entries, dict) else {}
|
|
69
|
+
for name, entry in entries.items():
|
|
70
|
+
if name not in old_entries:
|
|
71
|
+
action = "install"
|
|
72
|
+
elif old_entries.get(name) != entry:
|
|
73
|
+
action = "update"
|
|
74
|
+
else:
|
|
75
|
+
action = "noop"
|
|
76
|
+
actions.append({"client": client, "server": name, "action": action})
|
|
77
|
+
for name in set(old_entries) - set(entries):
|
|
78
|
+
actions.append({"client": client, "server": name, "action": "disable"})
|
|
79
|
+
|
|
80
|
+
state = {
|
|
81
|
+
"schema": "nexo.managed_mcp.state.v1",
|
|
82
|
+
"catalog_version": catalog.get("catalog_version", ""),
|
|
83
|
+
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
84
|
+
"validation": validation,
|
|
85
|
+
"desired": desired,
|
|
86
|
+
"last_plan": actions,
|
|
87
|
+
}
|
|
88
|
+
if apply:
|
|
89
|
+
_write_state(nexo_home_path, state)
|
|
90
|
+
return {
|
|
91
|
+
"ok": validation["ok"],
|
|
92
|
+
"applied": bool(apply),
|
|
93
|
+
"state_path": str(_state_path(nexo_home_path)),
|
|
94
|
+
"validation": validation,
|
|
95
|
+
"actions": actions,
|
|
96
|
+
"desired_clients": sorted(desired),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def managed_mcp_status(
|
|
101
|
+
*,
|
|
102
|
+
nexo_home: str | os.PathLike[str] | Path,
|
|
103
|
+
runtime_root: str | os.PathLike[str] | Path | None = None,
|
|
104
|
+
platform: str | None = None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
nexo_home_path = Path(nexo_home).expanduser()
|
|
107
|
+
state = _read_state(nexo_home_path)
|
|
108
|
+
plan = reconcile_managed_mcp(
|
|
109
|
+
nexo_home=nexo_home_path,
|
|
110
|
+
runtime_root=runtime_root,
|
|
111
|
+
apply=False,
|
|
112
|
+
platform=platform,
|
|
113
|
+
)
|
|
114
|
+
return {
|
|
115
|
+
"ok": plan["ok"],
|
|
116
|
+
"state_exists": _state_path(nexo_home_path).is_file(),
|
|
117
|
+
"state_path": str(_state_path(nexo_home_path)),
|
|
118
|
+
"catalog_version": plan["validation"].get("catalog_version", ""),
|
|
119
|
+
"validation": plan["validation"],
|
|
120
|
+
"last_applied_at": state.get("updated_at", ""),
|
|
121
|
+
"actions": plan["actions"],
|
|
122
|
+
}
|
package/src/plugins/update.py
CHANGED
package/src/server.py
CHANGED
|
@@ -6,6 +6,7 @@ import signal
|
|
|
6
6
|
import sys
|
|
7
7
|
import json
|
|
8
8
|
import time
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from fastmcp import FastMCP
|
|
11
12
|
from core_prompts import render_core_prompt
|
|
@@ -1009,6 +1010,75 @@ def nexo_mcp_write_queue_status(limit: int = 20) -> str:
|
|
|
1009
1010
|
return json.dumps(mcp_write_queue_status(limit=clean_limit), indent=2, ensure_ascii=False)
|
|
1010
1011
|
|
|
1011
1012
|
|
|
1013
|
+
@mcp.tool
|
|
1014
|
+
def nexo_managed_mcp_status(apply: bool = False) -> str:
|
|
1015
|
+
"""Read or apply the Brain-owned managed MCP catalog reconciliation plan."""
|
|
1016
|
+
from managed_mcp import managed_mcp_status, reconcile_managed_mcp
|
|
1017
|
+
import paths
|
|
1018
|
+
|
|
1019
|
+
runtime_root = Path(__file__).resolve().parent
|
|
1020
|
+
if apply:
|
|
1021
|
+
result = reconcile_managed_mcp(
|
|
1022
|
+
nexo_home=paths.home(),
|
|
1023
|
+
runtime_root=runtime_root,
|
|
1024
|
+
apply=True,
|
|
1025
|
+
)
|
|
1026
|
+
else:
|
|
1027
|
+
result = managed_mcp_status(
|
|
1028
|
+
nexo_home=paths.home(),
|
|
1029
|
+
runtime_root=runtime_root,
|
|
1030
|
+
)
|
|
1031
|
+
return json.dumps(result, indent=2, ensure_ascii=False)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
@mcp.tool
|
|
1035
|
+
def nexo_closure_status(refresh: bool = True, limit: int = 10) -> str:
|
|
1036
|
+
"""Read the Operational Closure Plane status and ranked closure queue."""
|
|
1037
|
+
from closure_plane import handle_closure_status
|
|
1038
|
+
|
|
1039
|
+
try:
|
|
1040
|
+
clean_limit = max(1, min(int(limit or 10), 100))
|
|
1041
|
+
except Exception:
|
|
1042
|
+
clean_limit = 10
|
|
1043
|
+
return handle_closure_status(refresh=bool(refresh), limit=clean_limit)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
@mcp.tool
|
|
1047
|
+
def nexo_closure_next(limit: int = 10, include_waiting: bool = False, source: str = "", kind: str = "") -> str:
|
|
1048
|
+
"""Return the next ranked closure items without executing source actions."""
|
|
1049
|
+
from closure_plane import handle_closure_next
|
|
1050
|
+
|
|
1051
|
+
try:
|
|
1052
|
+
clean_limit = max(1, min(int(limit or 10), 100))
|
|
1053
|
+
except Exception:
|
|
1054
|
+
clean_limit = 10
|
|
1055
|
+
return handle_closure_next(clean_limit, bool(include_waiting), source, kind)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
@mcp.tool
|
|
1059
|
+
def nexo_closure_item_get(item_id: str) -> str:
|
|
1060
|
+
"""Return one closure item with sources and events."""
|
|
1061
|
+
from closure_plane import handle_closure_item_get
|
|
1062
|
+
|
|
1063
|
+
return handle_closure_item_get(item_id)
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
@mcp.tool
|
|
1067
|
+
def nexo_closure_verify(item_id: str, evidence: str) -> str:
|
|
1068
|
+
"""Record verification evidence for a closure item."""
|
|
1069
|
+
from closure_plane import handle_closure_verify
|
|
1070
|
+
|
|
1071
|
+
return handle_closure_verify(item_id, evidence)
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
@mcp.tool
|
|
1075
|
+
def nexo_closure_close(item_id: str, reason: str = "completed") -> str:
|
|
1076
|
+
"""Close a verified closure item or reject/stale it explicitly."""
|
|
1077
|
+
from closure_plane import handle_closure_close
|
|
1078
|
+
|
|
1079
|
+
return handle_closure_close(item_id, reason)
|
|
1080
|
+
|
|
1081
|
+
|
|
1012
1082
|
@mcp.tool
|
|
1013
1083
|
def nexo_local_index_status() -> str:
|
|
1014
1084
|
"""Return local memory index status for Desktop settings and support diagnostics."""
|
|
@@ -2864,6 +2864,96 @@
|
|
|
2864
2864
|
},
|
|
2865
2865
|
"triggers_after": []
|
|
2866
2866
|
},
|
|
2867
|
+
"nexo_managed_mcp_status": {
|
|
2868
|
+
"description": "Read or apply the Brain-owned managed MCP catalog reconciliation plan",
|
|
2869
|
+
"category": "system",
|
|
2870
|
+
"source": "server",
|
|
2871
|
+
"requires": [],
|
|
2872
|
+
"provides": [
|
|
2873
|
+
"managed_mcp_status"
|
|
2874
|
+
],
|
|
2875
|
+
"internal_calls": [],
|
|
2876
|
+
"enforcement": {
|
|
2877
|
+
"level": "none",
|
|
2878
|
+
"rules": []
|
|
2879
|
+
},
|
|
2880
|
+
"triggers_after": []
|
|
2881
|
+
},
|
|
2882
|
+
"nexo_closure_status": {
|
|
2883
|
+
"description": "Read Operational Closure Plane status and ranked closure queue",
|
|
2884
|
+
"category": "closure",
|
|
2885
|
+
"source": "server",
|
|
2886
|
+
"requires": [],
|
|
2887
|
+
"provides": [
|
|
2888
|
+
"closure_status"
|
|
2889
|
+
],
|
|
2890
|
+
"internal_calls": [],
|
|
2891
|
+
"enforcement": {
|
|
2892
|
+
"level": "none",
|
|
2893
|
+
"rules": []
|
|
2894
|
+
},
|
|
2895
|
+
"triggers_after": []
|
|
2896
|
+
},
|
|
2897
|
+
"nexo_closure_next": {
|
|
2898
|
+
"description": "Return the next ranked closure items without executing source actions",
|
|
2899
|
+
"category": "closure",
|
|
2900
|
+
"source": "server",
|
|
2901
|
+
"requires": [],
|
|
2902
|
+
"provides": [
|
|
2903
|
+
"closure_items"
|
|
2904
|
+
],
|
|
2905
|
+
"internal_calls": [],
|
|
2906
|
+
"enforcement": {
|
|
2907
|
+
"level": "none",
|
|
2908
|
+
"rules": []
|
|
2909
|
+
},
|
|
2910
|
+
"triggers_after": []
|
|
2911
|
+
},
|
|
2912
|
+
"nexo_closure_item_get": {
|
|
2913
|
+
"description": "Return one closure item with sources and events",
|
|
2914
|
+
"category": "closure",
|
|
2915
|
+
"source": "server",
|
|
2916
|
+
"requires": [],
|
|
2917
|
+
"provides": [
|
|
2918
|
+
"closure_item"
|
|
2919
|
+
],
|
|
2920
|
+
"internal_calls": [],
|
|
2921
|
+
"enforcement": {
|
|
2922
|
+
"level": "none",
|
|
2923
|
+
"rules": []
|
|
2924
|
+
},
|
|
2925
|
+
"triggers_after": []
|
|
2926
|
+
},
|
|
2927
|
+
"nexo_closure_verify": {
|
|
2928
|
+
"description": "Record verification evidence for a closure item",
|
|
2929
|
+
"category": "closure",
|
|
2930
|
+
"source": "server",
|
|
2931
|
+
"requires": [],
|
|
2932
|
+
"provides": [
|
|
2933
|
+
"closure_evidence"
|
|
2934
|
+
],
|
|
2935
|
+
"internal_calls": [],
|
|
2936
|
+
"enforcement": {
|
|
2937
|
+
"level": "none",
|
|
2938
|
+
"rules": []
|
|
2939
|
+
},
|
|
2940
|
+
"triggers_after": []
|
|
2941
|
+
},
|
|
2942
|
+
"nexo_closure_close": {
|
|
2943
|
+
"description": "Close a verified closure item or mark it rejected/stale explicitly",
|
|
2944
|
+
"category": "closure",
|
|
2945
|
+
"source": "server",
|
|
2946
|
+
"requires": [],
|
|
2947
|
+
"provides": [
|
|
2948
|
+
"closure_close_result"
|
|
2949
|
+
],
|
|
2950
|
+
"internal_calls": [],
|
|
2951
|
+
"enforcement": {
|
|
2952
|
+
"level": "none",
|
|
2953
|
+
"rules": []
|
|
2954
|
+
},
|
|
2955
|
+
"triggers_after": []
|
|
2956
|
+
},
|
|
2867
2957
|
"nexo_media_memory_add": {
|
|
2868
2958
|
"description": "Store non-text artifact metadata",
|
|
2869
2959
|
"category": "media",
|