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/src/db/_schema.py CHANGED
@@ -2775,6 +2775,98 @@ def _m77_morning_briefing_presentation(conn):
2775
2775
  )
2776
2776
 
2777
2777
 
2778
+ def _m78_operational_closure_plane(conn):
2779
+ """Operational Closure Plane MVP: canonical read-only closure items."""
2780
+ conn.execute(
2781
+ """
2782
+ CREATE TABLE IF NOT EXISTS closure_items (
2783
+ id TEXT PRIMARY KEY,
2784
+ title TEXT NOT NULL,
2785
+ summary TEXT NOT NULL DEFAULT '',
2786
+ kind TEXT NOT NULL,
2787
+ state TEXT NOT NULL DEFAULT 'open',
2788
+ source_primary TEXT NOT NULL,
2789
+ source_key TEXT NOT NULL,
2790
+ dedupe_key TEXT NOT NULL,
2791
+ impact_score REAL NOT NULL DEFAULT 0,
2792
+ urgency_score REAL NOT NULL DEFAULT 0,
2793
+ risk_score REAL NOT NULL DEFAULT 0,
2794
+ confidence_score REAL NOT NULL DEFAULT 0,
2795
+ priority_score REAL NOT NULL DEFAULT 0,
2796
+ safety_class TEXT NOT NULL DEFAULT 'normal',
2797
+ capability_required TEXT NOT NULL DEFAULT '',
2798
+ capability_status TEXT NOT NULL DEFAULT 'unknown',
2799
+ owner TEXT NOT NULL DEFAULT 'nero',
2800
+ next_action TEXT NOT NULL DEFAULT '',
2801
+ blocker_reason TEXT NOT NULL DEFAULT '',
2802
+ evidence_required TEXT NOT NULL DEFAULT '',
2803
+ evidence_observed TEXT NOT NULL DEFAULT '',
2804
+ deadline_at TEXT NOT NULL DEFAULT '',
2805
+ first_seen_at TEXT NOT NULL,
2806
+ last_seen_at TEXT NOT NULL,
2807
+ last_progress_at TEXT NOT NULL DEFAULT '',
2808
+ closed_at TEXT NOT NULL DEFAULT '',
2809
+ close_reason TEXT NOT NULL DEFAULT '',
2810
+ source_payload_json TEXT NOT NULL DEFAULT '{}',
2811
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2812
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2813
+ UNIQUE(dedupe_key)
2814
+ )
2815
+ """
2816
+ )
2817
+ conn.execute(
2818
+ """
2819
+ CREATE TABLE IF NOT EXISTS closure_item_sources (
2820
+ id TEXT PRIMARY KEY,
2821
+ closure_item_id TEXT NOT NULL,
2822
+ source_type TEXT NOT NULL,
2823
+ source_id TEXT NOT NULL,
2824
+ source_status TEXT NOT NULL DEFAULT '',
2825
+ source_payload_json TEXT NOT NULL DEFAULT '{}',
2826
+ observed_at TEXT NOT NULL,
2827
+ FOREIGN KEY(closure_item_id) REFERENCES closure_items(id) ON DELETE CASCADE,
2828
+ UNIQUE(closure_item_id, source_type, source_id)
2829
+ )
2830
+ """
2831
+ )
2832
+ conn.execute(
2833
+ """
2834
+ CREATE TABLE IF NOT EXISTS closure_item_events (
2835
+ id TEXT PRIMARY KEY,
2836
+ closure_item_id TEXT NOT NULL,
2837
+ event_type TEXT NOT NULL,
2838
+ from_state TEXT NOT NULL DEFAULT '',
2839
+ to_state TEXT NOT NULL DEFAULT '',
2840
+ note TEXT NOT NULL DEFAULT '',
2841
+ evidence TEXT NOT NULL DEFAULT '',
2842
+ actor TEXT NOT NULL DEFAULT 'nexo',
2843
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2844
+ FOREIGN KEY(closure_item_id) REFERENCES closure_items(id) ON DELETE CASCADE
2845
+ )
2846
+ """
2847
+ )
2848
+ conn.execute(
2849
+ """
2850
+ CREATE TABLE IF NOT EXISTS closure_daily_snapshots (
2851
+ snapshot_date TEXT PRIMARY KEY,
2852
+ total_open INTEGER NOT NULL DEFAULT 0,
2853
+ total_verified INTEGER NOT NULL DEFAULT 0,
2854
+ total_waiting INTEGER NOT NULL DEFAULT 0,
2855
+ total_closed INTEGER NOT NULL DEFAULT 0,
2856
+ top_items_json TEXT NOT NULL DEFAULT '[]',
2857
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2858
+ )
2859
+ """
2860
+ )
2861
+ _migrate_add_index(conn, "idx_closure_items_state_priority", "closure_items", "state, priority_score DESC, updated_at")
2862
+ _migrate_add_index(conn, "idx_closure_items_source", "closure_items", "source_primary, source_key")
2863
+ _migrate_add_index(conn, "idx_closure_items_kind", "closure_items", "kind, state")
2864
+ _migrate_add_index(conn, "idx_closure_items_deadline", "closure_items", "deadline_at")
2865
+ _migrate_add_index(conn, "idx_closure_sources_item", "closure_item_sources", "closure_item_id")
2866
+ _migrate_add_index(conn, "idx_closure_sources_source", "closure_item_sources", "source_type, source_id")
2867
+ _migrate_add_index(conn, "idx_closure_events_item", "closure_item_events", "closure_item_id, created_at")
2868
+
2869
+
2778
2870
  MIGRATIONS = [
2779
2871
  (1, "learnings_columns", _m1_learnings_columns),
2780
2872
  (2, "followups_reasoning", _m2_followups_reasoning),
@@ -2853,6 +2945,7 @@ MIGRATIONS = [
2853
2945
  (75, "failure_prevention_ledger", _m75_failure_prevention_ledger),
2854
2946
  (76, "semantic_layers", _m76_semantic_layers),
2855
2947
  (77, "morning_briefing_presentation", _m77_morning_briefing_presentation),
2948
+ (78, "operational_closure_plane", _m78_operational_closure_plane),
2856
2949
  ]
2857
2950
 
2858
2951
 
@@ -0,0 +1,31 @@
1
+ """Managed MCP capability catalog and client-config helpers."""
2
+
3
+ from .catalog import (
4
+ CATALOG_PATH,
5
+ LOCK_PATH,
6
+ ManagedCapability,
7
+ ManagedProvider,
8
+ build_managed_server_entries,
9
+ load_catalog,
10
+ load_lock,
11
+ provider_for_capability,
12
+ validate_catalog_lock,
13
+ )
14
+ from .client_config import merge_json_mcp_servers, merge_toml_mcp_servers
15
+ from .reconcile import managed_mcp_status, reconcile_managed_mcp
16
+
17
+ __all__ = [
18
+ "CATALOG_PATH",
19
+ "LOCK_PATH",
20
+ "ManagedCapability",
21
+ "ManagedProvider",
22
+ "build_managed_server_entries",
23
+ "load_catalog",
24
+ "load_lock",
25
+ "provider_for_capability",
26
+ "validate_catalog_lock",
27
+ "merge_json_mcp_servers",
28
+ "merge_toml_mcp_servers",
29
+ "managed_mcp_status",
30
+ "reconcile_managed_mcp",
31
+ ]
@@ -0,0 +1,77 @@
1
+ {
2
+ "schema": "nexo.managed_mcp.catalog.v1",
3
+ "catalog_version": "2026.06.06",
4
+ "defaults_profile": "normal_user",
5
+ "capabilities": [
6
+ {
7
+ "id": "chrome_control",
8
+ "display_name": "Chrome control",
9
+ "enabled_by_default": true,
10
+ "risk": "high",
11
+ "clients": ["claude_code", "claude_desktop", "codex"],
12
+ "providers": [
13
+ {
14
+ "id": "chrome-devtools-mcp",
15
+ "platforms": ["darwin", "win32"],
16
+ "source": {"type": "npm", "package": "chrome-devtools-mcp"},
17
+ "version_policy": "locked",
18
+ "transport": "stdio",
19
+ "preflight": {
20
+ "node": "^20.19.0 || ^22.12.0 || >=23",
21
+ "binaries": ["chrome"]
22
+ }
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "id": "desktop_control",
28
+ "display_name": "Desktop control",
29
+ "enabled_by_default": true,
30
+ "risk": "high",
31
+ "clients": ["claude_code", "claude_desktop", "codex"],
32
+ "providers": [
33
+ {
34
+ "id": "mac-use-mcp",
35
+ "platforms": ["darwin"],
36
+ "source": {"type": "npm", "package": "mac-use-mcp"},
37
+ "version_policy": "locked",
38
+ "transport": "stdio",
39
+ "preflight": {
40
+ "os_permissions": ["accessibility", "screen_recording"]
41
+ }
42
+ },
43
+ {
44
+ "id": "native-devtools-mcp",
45
+ "platforms": ["win32"],
46
+ "source": {"type": "npm", "package": "native-devtools-mcp"},
47
+ "version_policy": "locked",
48
+ "transport": "stdio"
49
+ },
50
+ {
51
+ "id": "open-computer-use",
52
+ "platforms": ["win32"],
53
+ "source": {"type": "npm", "package": "open-computer-use"},
54
+ "version_policy": "locked",
55
+ "transport": "stdio",
56
+ "fallback": true
57
+ }
58
+ ]
59
+ },
60
+ {
61
+ "id": "power_control",
62
+ "display_name": "Power control",
63
+ "enabled_by_default": true,
64
+ "risk": "critical",
65
+ "clients": ["claude_code", "claude_desktop", "codex"],
66
+ "providers": [
67
+ {
68
+ "id": "desktop-commander",
69
+ "platforms": ["darwin", "win32"],
70
+ "source": {"type": "npm", "package": "@wonderwhy-er/desktop-commander"},
71
+ "version_policy": "locked",
72
+ "transport": "stdio"
73
+ }
74
+ ]
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ CATALOG_PATH = Path(__file__).with_name("catalog.json")
12
+ LOCK_PATH = Path(__file__).with_name("lock.json")
13
+ CLIENT_KEYS = {"claude_code", "claude_desktop", "codex"}
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ManagedProvider:
18
+ id: str
19
+ package: str
20
+ source_type: str
21
+ platforms: tuple[str, ...]
22
+ fallback: bool = False
23
+ risk: str = ""
24
+ version: str = ""
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ManagedCapability:
29
+ id: str
30
+ display_name: str
31
+ enabled_by_default: bool
32
+ risk: str
33
+ clients: tuple[str, ...]
34
+ providers: tuple[ManagedProvider, ...]
35
+
36
+
37
+ def _load_json(path: Path) -> dict[str, Any]:
38
+ payload = json.loads(path.read_text())
39
+ if not isinstance(payload, dict):
40
+ raise ValueError(f"{path} must contain a JSON object")
41
+ return payload
42
+
43
+
44
+ def load_catalog(path: Path | None = None) -> dict[str, Any]:
45
+ return _load_json(path or CATALOG_PATH)
46
+
47
+
48
+ def load_lock(path: Path | None = None) -> dict[str, Any]:
49
+ return _load_json(path or LOCK_PATH)
50
+
51
+
52
+ def _platform_key(platform: str | None = None) -> str:
53
+ value = (platform or sys.platform or "").lower()
54
+ if value.startswith("darwin"):
55
+ return "darwin"
56
+ if value.startswith(("win32", "cygwin", "msys")) or os.name == "nt":
57
+ return "win32"
58
+ if value.startswith("linux"):
59
+ return "linux"
60
+ return value
61
+
62
+
63
+ def validate_catalog_lock(
64
+ catalog: dict[str, Any] | None = None,
65
+ lock: dict[str, Any] | None = None,
66
+ ) -> dict[str, Any]:
67
+ catalog = catalog or load_catalog()
68
+ lock = lock or load_lock()
69
+ errors: list[str] = []
70
+ warnings: list[str] = []
71
+
72
+ if catalog.get("schema") != "nexo.managed_mcp.catalog.v1":
73
+ errors.append("catalog schema mismatch")
74
+ if lock.get("schema") != "nexo.managed_mcp.lock.v1":
75
+ errors.append("lock schema mismatch")
76
+ if catalog.get("catalog_version") != lock.get("catalog_version"):
77
+ errors.append("catalog_version mismatch")
78
+
79
+ lock_providers = lock.get("providers")
80
+ if not isinstance(lock_providers, dict):
81
+ errors.append("lock providers must be an object")
82
+ lock_providers = {}
83
+
84
+ capabilities = catalog.get("capabilities")
85
+ if not isinstance(capabilities, list) or not capabilities:
86
+ errors.append("catalog capabilities must be a non-empty list")
87
+ capabilities = []
88
+
89
+ capability_ids: set[str] = set()
90
+ required_provider_ids: set[str] = set()
91
+ for capability in capabilities:
92
+ if not isinstance(capability, dict):
93
+ errors.append("capability entries must be objects")
94
+ continue
95
+ capability_id = str(capability.get("id") or "").strip()
96
+ if not capability_id:
97
+ errors.append("capability without id")
98
+ continue
99
+ if capability_id in capability_ids:
100
+ errors.append(f"duplicate capability id: {capability_id}")
101
+ capability_ids.add(capability_id)
102
+ clients = capability.get("clients")
103
+ if not isinstance(clients, list) or not clients:
104
+ errors.append(f"{capability_id}: clients must be non-empty")
105
+ elif any(str(client) not in CLIENT_KEYS for client in clients):
106
+ errors.append(f"{capability_id}: unknown client in clients")
107
+ providers = capability.get("providers")
108
+ if not isinstance(providers, list) or not providers:
109
+ errors.append(f"{capability_id}: providers must be non-empty")
110
+ continue
111
+ platforms_by_provider: set[str] = set()
112
+ for provider in providers:
113
+ if not isinstance(provider, dict):
114
+ errors.append(f"{capability_id}: provider entries must be objects")
115
+ continue
116
+ provider_id = str(provider.get("id") or "").strip()
117
+ source = provider.get("source") if isinstance(provider.get("source"), dict) else {}
118
+ package = str(source.get("package") or "").strip()
119
+ platforms = provider.get("platforms")
120
+ if not provider_id:
121
+ errors.append(f"{capability_id}: provider without id")
122
+ continue
123
+ required_provider_ids.add(provider_id)
124
+ if not package:
125
+ errors.append(f"{provider_id}: source.package missing")
126
+ if not isinstance(platforms, list) or not platforms:
127
+ errors.append(f"{provider_id}: platforms missing")
128
+ else:
129
+ platforms_by_provider.update(str(item) for item in platforms)
130
+ if provider.get("version_policy") not in {"locked", "latest_on_release"}:
131
+ errors.append(f"{provider_id}: unsupported version_policy")
132
+ locked = lock_providers.get(provider_id)
133
+ if not isinstance(locked, dict):
134
+ errors.append(f"{provider_id}: missing from lockfile")
135
+ elif locked.get("package") != package:
136
+ errors.append(f"{provider_id}: lock package mismatch")
137
+ elif "@latest" in str(locked.get("version") or ""):
138
+ errors.append(f"{provider_id}: lock version must not use @latest")
139
+ elif str(locked.get("version") or "").strip() in {"", "0.0.0-managed"}:
140
+ errors.append(f"{provider_id}: lock version must be an exact package version")
141
+ elif str(locked.get("source_type") or "") == "npm":
142
+ if not str(locked.get("integrity") or "").strip():
143
+ errors.append(f"{provider_id}: npm lock integrity missing")
144
+ if not str(locked.get("tarball") or "").strip():
145
+ errors.append(f"{provider_id}: npm lock tarball missing")
146
+ if not str(locked.get("bin") or "").strip():
147
+ errors.append(f"{provider_id}: npm lock bin missing")
148
+ if capability.get("enabled_by_default") is True:
149
+ for required_platform in ("darwin", "win32"):
150
+ if required_platform not in platforms_by_provider:
151
+ errors.append(f"{capability_id}: missing {required_platform} provider")
152
+
153
+ extra = set(lock_providers) - required_provider_ids
154
+ if extra:
155
+ warnings.append("lock contains unused providers: " + ", ".join(sorted(extra)))
156
+
157
+ return {
158
+ "ok": not errors,
159
+ "errors": errors,
160
+ "warnings": warnings,
161
+ "catalog_version": str(catalog.get("catalog_version") or ""),
162
+ "capabilities": sorted(capability_ids),
163
+ "providers": sorted(required_provider_ids),
164
+ }
165
+
166
+
167
+ def provider_for_capability(
168
+ capability: dict[str, Any],
169
+ *,
170
+ platform: str | None = None,
171
+ ) -> dict[str, Any] | None:
172
+ platform_key = _platform_key(platform)
173
+ providers = capability.get("providers")
174
+ if not isinstance(providers, list):
175
+ return None
176
+ exact: list[dict[str, Any]] = []
177
+ fallbacks: list[dict[str, Any]] = []
178
+ for provider in providers:
179
+ if not isinstance(provider, dict):
180
+ continue
181
+ platforms = provider.get("platforms")
182
+ if not isinstance(platforms, list) or platform_key not in {str(p) for p in platforms}:
183
+ continue
184
+ if provider.get("fallback"):
185
+ fallbacks.append(provider)
186
+ else:
187
+ exact.append(provider)
188
+ return (exact or fallbacks or [None])[0]
189
+
190
+
191
+ def _runner_path(nexo_home: Path, runtime_root: Path | None = None) -> Path:
192
+ runtime_bin = nexo_home / "runtime" / "bin" / "nexo-managed-mcp"
193
+ if runtime_bin.exists():
194
+ return runtime_bin
195
+ if runtime_root:
196
+ candidate = runtime_root / "bin" / "nexo-managed-mcp.js"
197
+ if candidate.exists():
198
+ return candidate
199
+ sibling = runtime_root.parent / "bin" / "nexo-managed-mcp.js"
200
+ if sibling.exists():
201
+ return sibling
202
+ return runtime_bin
203
+
204
+
205
+ def _entry_digest(entry: dict[str, Any]) -> str:
206
+ body = json.dumps(entry, sort_keys=True, separators=(",", ":"))
207
+ return "sha256:" + hashlib.sha256(body.encode("utf-8")).hexdigest()
208
+
209
+
210
+ def build_managed_server_entries(
211
+ *,
212
+ client: str,
213
+ nexo_home: str | os.PathLike[str] | Path,
214
+ runtime_root: str | os.PathLike[str] | Path | None = None,
215
+ catalog: dict[str, Any] | None = None,
216
+ lock: dict[str, Any] | None = None,
217
+ platform: str | None = None,
218
+ ) -> dict[str, dict[str, Any]]:
219
+ catalog = catalog or load_catalog()
220
+ lock = lock or load_lock()
221
+ validation = validate_catalog_lock(catalog, lock)
222
+ if not validation["ok"]:
223
+ raise ValueError("; ".join(validation["errors"]))
224
+ nexo_home_path = Path(nexo_home).expanduser()
225
+ runtime_root_path = Path(runtime_root).expanduser() if runtime_root else None
226
+ runner = _runner_path(nexo_home_path, runtime_root_path)
227
+ lock_providers = lock.get("providers") if isinstance(lock.get("providers"), dict) else {}
228
+ entries: dict[str, dict[str, Any]] = {}
229
+ for capability in catalog.get("capabilities") or []:
230
+ if not isinstance(capability, dict):
231
+ continue
232
+ if not capability.get("enabled_by_default"):
233
+ continue
234
+ clients = {str(item) for item in capability.get("clients") or []}
235
+ if client not in clients:
236
+ continue
237
+ capability_id = str(capability.get("id") or "").strip()
238
+ provider = provider_for_capability(capability, platform=platform)
239
+ if not capability_id or not provider:
240
+ continue
241
+ provider_id = str(provider.get("id") or "").strip()
242
+ locked = lock_providers.get(provider_id) if isinstance(lock_providers, dict) else {}
243
+ name = f"nexo_{capability_id}"
244
+ entry = {
245
+ "command": str(runner),
246
+ "args": ["run", capability_id],
247
+ "env": {"NEXO_HOME": str(nexo_home_path)},
248
+ "nexo": {
249
+ "owner": "nexo",
250
+ "schema": "nexo.managed_mcp.client.v1",
251
+ "capability_id": capability_id,
252
+ "provider_id": provider_id,
253
+ "provider_package": str((locked or {}).get("package") or ""),
254
+ "provider_version": str((locked or {}).get("version") or ""),
255
+ "provider_bin": str((locked or {}).get("bin") or ""),
256
+ "risk": str(capability.get("risk") or ""),
257
+ },
258
+ }
259
+ if runtime_root_path:
260
+ entry["env"]["NEXO_CODE"] = str(runtime_root_path)
261
+ entry["nexo"]["config_digest"] = _entry_digest(entry)
262
+ entries[name] = entry
263
+ return entries
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from typing import Any
5
+
6
+
7
+ def _is_nexo_owned(entry: Any) -> bool:
8
+ if not isinstance(entry, dict):
9
+ return False
10
+ meta = entry.get("nexo")
11
+ return isinstance(meta, dict) and meta.get("owner") == "nexo"
12
+
13
+
14
+ def merge_json_mcp_servers(payload: dict[str, Any], entries: dict[str, dict[str, Any]]) -> dict[str, Any]:
15
+ result = deepcopy(payload) if isinstance(payload, dict) else {}
16
+ servers = result.setdefault("mcpServers", {})
17
+ if not isinstance(servers, dict):
18
+ servers = {}
19
+ result["mcpServers"] = servers
20
+ metadata = result.setdefault("nexo", {})
21
+ if not isinstance(metadata, dict):
22
+ metadata = {}
23
+ result["nexo"] = metadata
24
+ managed = metadata.setdefault("managed_mcp", {})
25
+ if not isinstance(managed, dict):
26
+ managed = {}
27
+ metadata["managed_mcp"] = managed
28
+ managed_servers = managed.setdefault("servers", {})
29
+ if not isinstance(managed_servers, dict):
30
+ managed_servers = {}
31
+ managed["servers"] = managed_servers
32
+
33
+ for name, entry in entries.items():
34
+ current = servers.get(name)
35
+ if current is not None and not _is_nexo_owned(current):
36
+ continue
37
+ servers[name] = deepcopy(entry)
38
+ managed_servers[name] = deepcopy(entry.get("nexo") or {})
39
+ managed["schema"] = "nexo.managed_mcp.client.v1"
40
+ return result
41
+
42
+
43
+ def merge_toml_mcp_servers(payload: dict[str, Any], entries: dict[str, dict[str, Any]]) -> dict[str, Any]:
44
+ result = deepcopy(payload) if isinstance(payload, dict) else {}
45
+ servers = result.setdefault("mcp_servers", {})
46
+ if not isinstance(servers, dict):
47
+ servers = {}
48
+ result["mcp_servers"] = servers
49
+ nexo_table = result.setdefault("nexo", {})
50
+ if not isinstance(nexo_table, dict):
51
+ nexo_table = {}
52
+ result["nexo"] = nexo_table
53
+ managed = nexo_table.setdefault("managed_mcp", {})
54
+ if not isinstance(managed, dict):
55
+ managed = {}
56
+ nexo_table["managed_mcp"] = managed
57
+ managed_servers = managed.setdefault("servers", {})
58
+ if not isinstance(managed_servers, dict):
59
+ managed_servers = {}
60
+ managed["servers"] = managed_servers
61
+
62
+ for name, entry in entries.items():
63
+ current = servers.get(name)
64
+ current_meta = managed_servers.get(name)
65
+ if current is not None and not (
66
+ _is_nexo_owned(current) or (isinstance(current_meta, dict) and current_meta.get("owner") == "nexo")
67
+ ):
68
+ continue
69
+ servers[name] = {
70
+ "command": entry.get("command", ""),
71
+ "args": list(entry.get("args", []) or []),
72
+ "env": dict(entry.get("env", {}) or {}),
73
+ }
74
+ managed_servers[name] = deepcopy(entry.get("nexo") or {})
75
+ managed["schema"] = "nexo.managed_mcp.client.v1"
76
+ return result
@@ -0,0 +1,52 @@
1
+ {
2
+ "schema": "nexo.managed_mcp.lock.v1",
3
+ "catalog_version": "2026.06.06",
4
+ "generated_at": "2026-06-06T00:00:00Z",
5
+ "providers": {
6
+ "chrome-devtools-mcp": {
7
+ "source_type": "npm",
8
+ "package": "chrome-devtools-mcp",
9
+ "version": "1.1.1",
10
+ "integrity": "sha512-Fs/ASXAkQqvYCbJjHIx/pnShjyIoZoPxdg4J3wjaA9FLkRb2ngGnisu2AGcBIXdw5qrPkOuV/cOlGOonpsE1qw==",
11
+ "tarball": "https://registry.npmjs.org/chrome-devtools-mcp/-/chrome-devtools-mcp-1.1.1.tgz",
12
+ "bin": "chrome-devtools-mcp",
13
+ "engines": {"node": "^20.19.0 || ^22.12.0 || >=23"}
14
+ },
15
+ "mac-use-mcp": {
16
+ "source_type": "npm",
17
+ "package": "mac-use-mcp",
18
+ "version": "1.1.1",
19
+ "integrity": "sha512-UcVkzvHuw+f21nEZwb3MwdqWGxLK/nYlhN55SRD6FZ2yn2t3ji0zKLD2XvQLp0ugeYyS92TqVgnw6E4P5VB+bg==",
20
+ "tarball": "https://registry.npmjs.org/mac-use-mcp/-/mac-use-mcp-1.1.1.tgz",
21
+ "bin": "mac-use-mcp",
22
+ "engines": {"node": ">=22"}
23
+ },
24
+ "native-devtools-mcp": {
25
+ "source_type": "npm",
26
+ "package": "native-devtools-mcp",
27
+ "version": "0.10.1",
28
+ "integrity": "sha512-TIR8QCKzYCaHY+N1IWB7OM6pZH49HJxRj1dZjT4RNkviA1QpgINm8H95ohMCbf4ZC5jdFssMlb9KmWNZnCeCSw==",
29
+ "tarball": "https://registry.npmjs.org/native-devtools-mcp/-/native-devtools-mcp-0.10.1.tgz",
30
+ "bin": "native-devtools-mcp",
31
+ "engines": {"node": ">=18"}
32
+ },
33
+ "open-computer-use": {
34
+ "source_type": "npm",
35
+ "package": "open-computer-use",
36
+ "version": "0.1.52",
37
+ "integrity": "sha512-KlOHmFvXHe2IEMGE/O+zMN5ASo+FQ42copj4j1xEOnyeLq4oxUxhtHqEdPUACCUcMZaHzKXfZboL1dk5a2GjLA==",
38
+ "tarball": "https://registry.npmjs.org/open-computer-use/-/open-computer-use-0.1.52.tgz",
39
+ "bin": "open-computer-use-mcp",
40
+ "engines": {"node": "^20.19.0 || ^22.12.0 || >=23"}
41
+ },
42
+ "desktop-commander": {
43
+ "source_type": "npm",
44
+ "package": "@wonderwhy-er/desktop-commander",
45
+ "version": "0.2.42",
46
+ "integrity": "sha512-ZgdBDihpaLfrzQQQGQCPmElYMx91oUXeVEWbxbygeUfq2aOZvHrcVMeuTGy9oMDp9vxjq6d/+ZGE0mQLJnAWkw==",
47
+ "tarball": "https://registry.npmjs.org/@wonderwhy-er/desktop-commander/-/desktop-commander-0.2.42.tgz",
48
+ "bin": "desktop-commander",
49
+ "engines": {"node": ">=18.0.0"}
50
+ }
51
+ }
52
+ }