open-edison 0.1.26__tar.gz → 0.1.29__tar.gz

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.
Files changed (34) hide show
  1. {open_edison-0.1.26 → open_edison-0.1.29}/.gitignore +3 -1
  2. {open_edison-0.1.26 → open_edison-0.1.29}/PKG-INFO +18 -16
  3. {open_edison-0.1.26 → open_edison-0.1.29}/README.md +17 -15
  4. {open_edison-0.1.26 → open_edison-0.1.29}/pyproject.toml +1 -1
  5. {open_edison-0.1.26 → open_edison-0.1.29}/src/config.py +16 -28
  6. {open_edison-0.1.26 → open_edison-0.1.29}/src/middleware/session_tracking.py +40 -8
  7. {open_edison-0.1.26 → open_edison-0.1.29}/src/permissions.py +3 -14
  8. {open_edison-0.1.26 → open_edison-0.1.29}/src/server.py +9 -4
  9. {open_edison-0.1.26 → open_edison-0.1.29}/src/single_user_mcp.py +12 -2
  10. {open_edison-0.1.26 → open_edison-0.1.29}/LICENSE +0 -0
  11. {open_edison-0.1.26 → open_edison-0.1.29}/config.json +0 -0
  12. {open_edison-0.1.26 → open_edison-0.1.29}/desktop_ext/README.md +0 -0
  13. {open_edison-0.1.26 → open_edison-0.1.29}/docs/README.md +0 -0
  14. {open_edison-0.1.26 → open_edison-0.1.29}/docs/architecture/single_user_design.md +0 -0
  15. {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/configuration.md +0 -0
  16. {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/project_structure.md +0 -0
  17. {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/proxy_usage.md +0 -0
  18. {open_edison-0.1.26 → open_edison-0.1.29}/docs/deployment/docker.md +0 -0
  19. {open_edison-0.1.26 → open_edison-0.1.29}/docs/deployment/local.md +0 -0
  20. {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/contributing.md +0 -0
  21. {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/development_guide.md +0 -0
  22. {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/testing.md +0 -0
  23. {open_edison-0.1.26 → open_edison-0.1.29}/docs/quick-reference/api_reference.md +0 -0
  24. {open_edison-0.1.26 → open_edison-0.1.29}/docs/quick-reference/config_quick_start.md +0 -0
  25. {open_edison-0.1.26 → open_edison-0.1.29}/prompt_permissions.json +0 -0
  26. {open_edison-0.1.26 → open_edison-0.1.29}/resource_permissions.json +0 -0
  27. {open_edison-0.1.26 → open_edison-0.1.29}/src/__init__.py +0 -0
  28. {open_edison-0.1.26 → open_edison-0.1.29}/src/__main__.py +0 -0
  29. {open_edison-0.1.26 → open_edison-0.1.29}/src/cli.py +0 -0
  30. {open_edison-0.1.26 → open_edison-0.1.29}/src/events.py +0 -0
  31. {open_edison-0.1.26 → open_edison-0.1.29}/src/middleware/data_access_tracker.py +0 -0
  32. {open_edison-0.1.26 → open_edison-0.1.29}/src/oauth_manager.py +0 -0
  33. {open_edison-0.1.26 → open_edison-0.1.29}/src/telemetry.py +0 -0
  34. {open_edison-0.1.26 → open_edison-0.1.29}/tool_permissions.json +0 -0
@@ -217,4 +217,6 @@ frontend_dist/
217
217
  frontend/node_modules/
218
218
  frontend/package-lock.json
219
219
  .vscode
220
- install_id
220
+ install_id
221
+ dev_config_dir/
222
+ sessions.db
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-edison
3
- Version: 0.1.26
3
+ Version: 0.1.29
4
4
  Summary: Open-source MCP security, aggregation, and monitoring. Single-user, self-hosted MCP proxy.
5
5
  Author-email: Hugo Berg <hugo@edison.watch>
6
6
  License-File: LICENSE
@@ -27,7 +27,11 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  # OpenEdison 🔒⚡️
29
29
 
30
- MCP security gateway that prevents data exfiltration—via direct access or tool chaining—with full monitoring for local single‑user deployments. Provides core functionality of <https://edison.watch> for local use.
30
+ > The secure MCP proxy gateway
31
+
32
+ Connect AI to your data/software securely without risk of data exfiltration. Gain visibility, block threats, and get alerts on the data your agent is reading/writing. No more "approve fatigue" with the MCP tool-call approvals.
33
+
34
+ OpenEdison solves the [lethal trifecta problem](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/), which can cause agent hijacking & data exfiltration by malicious actors.
31
35
 
32
36
  <p align="center">
33
37
  <img src="media/trifecta520p.gif" alt="Trifecta Security Risk Animation" width="520">
@@ -42,22 +46,23 @@ MCP security gateway that prevents data exfiltration—via direct access or tool
42
46
  <img alt="Python Version" src="https://img.shields.io/badge/python-3.12-blue?logo=python">
43
47
  <img src="https://img.shields.io/badge/License-GPLv3-blue" alt="License">
44
48
 
45
-
46
49
  </p>
47
50
 
48
- ---
49
-
51
+ ---
50
52
 
51
53
  ## Features ✨
52
54
 
53
- - 🛑 **Prevent Data Leaks** - Edison automatically blocks any data leaks, even if your AI gets jailbroken
54
- - 👤 **Single-user MCP proxy** - No multi-user complexity, just a simple proxy for your MCP servers
55
- - 🗂️ **JSON configuration** - Easy to configure and manage your MCP servers
56
- - 🖥️ **Simple local frontend** - Track and monitor your MCP interactions, servers, and sessions.
57
- - 📊 **Session tracking** - Track and monitor your MCP interactions
55
+ - 🛑 **Data leak blocker** - Edison automatically blocks any data leaks, even if your AI gets jailbroken
56
+ - 🕰️ **Deterministic execution** - Deterministic execution. Guaranteed data exfiltration blocker.
57
+ - 🗂️ **Easily configurable** - Easy to configure and manage your MCP servers
58
+ - 📊 **Visibility into agent interactions** - Track and monitor your agents and their interactions with connected software/data via MCP calls
58
59
  - 🔗 **Simple API** - REST API for managing MCP servers and proxying requests
59
60
  - 🐳 **Docker support** - Run in a container for easy deployment
60
61
 
62
+ ## About Edison.watch 🏢
63
+
64
+ Edison helps you gain observability, control, and policy enforcement for all AI interactions with systems of records, existing company software and data. Prevent AI from causing data leakage, lightning-fast setup for cross-system governance.
65
+
61
66
  ## Quick Start 🚀
62
67
 
63
68
  The fastest way to get started:
@@ -68,7 +73,7 @@ The fastest way to get started:
68
73
  curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_pipe_bash.sh | bash
69
74
  ```
70
75
 
71
- Run locally with uvx: `uvx open-edison --config-dir ~/edison-config`
76
+ Run locally with uvx: `uvx open-edison`
72
77
 
73
78
  <details>
74
79
  <summary>⬇️ Install Node.js/npm (optional for MCP tools)</summary>
@@ -102,11 +107,11 @@ After installation, ensure that `npx` is available on PATH.
102
107
 
103
108
  ```bash
104
109
  # Using uvx
105
- uvx open-edison --help
110
+ uvx open-edison
106
111
 
107
112
  # Using pipx
108
113
  pipx install open-edison
109
- open-edison --help
114
+ open-edison
110
115
  ```
111
116
 
112
117
  Run with a custom config directory:
@@ -349,8 +354,6 @@ Use the `get_security_status` tool to monitor your session's current risk level
349
354
 
350
355
  </details>
351
356
 
352
-
353
-
354
357
  ## Documentation 📚
355
358
 
356
359
  📚 **Complete documentation available in [`docs/`](docs/)**
@@ -360,7 +363,6 @@ Use the `get_security_status` tool to monitor your session's current risk level
360
363
  - 📡 **[API Reference](docs/quick-reference/api_reference.md)** - REST API documentation
361
364
  - 🧑‍💻 **[Development Guide](docs/development/development_guide.md)** - Contributing and development
362
365
 
363
-
364
366
  <details>
365
367
  <summary>📄 License</summary>
366
368
 
@@ -1,6 +1,10 @@
1
1
  # OpenEdison 🔒⚡️
2
2
 
3
- MCP security gateway that prevents data exfiltration—via direct access or tool chaining—with full monitoring for local single‑user deployments. Provides core functionality of <https://edison.watch> for local use.
3
+ > The secure MCP proxy gateway
4
+
5
+ Connect AI to your data/software securely without risk of data exfiltration. Gain visibility, block threats, and get alerts on the data your agent is reading/writing. No more "approve fatigue" with the MCP tool-call approvals.
6
+
7
+ OpenEdison solves the [lethal trifecta problem](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/), which can cause agent hijacking & data exfiltration by malicious actors.
4
8
 
5
9
  <p align="center">
6
10
  <img src="media/trifecta520p.gif" alt="Trifecta Security Risk Animation" width="520">
@@ -15,22 +19,23 @@ MCP security gateway that prevents data exfiltration—via direct access or tool
15
19
  <img alt="Python Version" src="https://img.shields.io/badge/python-3.12-blue?logo=python">
16
20
  <img src="https://img.shields.io/badge/License-GPLv3-blue" alt="License">
17
21
 
18
-
19
22
  </p>
20
23
 
21
- ---
22
-
24
+ ---
23
25
 
24
26
  ## Features ✨
25
27
 
26
- - 🛑 **Prevent Data Leaks** - Edison automatically blocks any data leaks, even if your AI gets jailbroken
27
- - 👤 **Single-user MCP proxy** - No multi-user complexity, just a simple proxy for your MCP servers
28
- - 🗂️ **JSON configuration** - Easy to configure and manage your MCP servers
29
- - 🖥️ **Simple local frontend** - Track and monitor your MCP interactions, servers, and sessions.
30
- - 📊 **Session tracking** - Track and monitor your MCP interactions
28
+ - 🛑 **Data leak blocker** - Edison automatically blocks any data leaks, even if your AI gets jailbroken
29
+ - 🕰️ **Deterministic execution** - Deterministic execution. Guaranteed data exfiltration blocker.
30
+ - 🗂️ **Easily configurable** - Easy to configure and manage your MCP servers
31
+ - 📊 **Visibility into agent interactions** - Track and monitor your agents and their interactions with connected software/data via MCP calls
31
32
  - 🔗 **Simple API** - REST API for managing MCP servers and proxying requests
32
33
  - 🐳 **Docker support** - Run in a container for easy deployment
33
34
 
35
+ ## About Edison.watch 🏢
36
+
37
+ Edison helps you gain observability, control, and policy enforcement for all AI interactions with systems of records, existing company software and data. Prevent AI from causing data leakage, lightning-fast setup for cross-system governance.
38
+
34
39
  ## Quick Start 🚀
35
40
 
36
41
  The fastest way to get started:
@@ -41,7 +46,7 @@ The fastest way to get started:
41
46
  curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_pipe_bash.sh | bash
42
47
  ```
43
48
 
44
- Run locally with uvx: `uvx open-edison --config-dir ~/edison-config`
49
+ Run locally with uvx: `uvx open-edison`
45
50
 
46
51
  <details>
47
52
  <summary>⬇️ Install Node.js/npm (optional for MCP tools)</summary>
@@ -75,11 +80,11 @@ After installation, ensure that `npx` is available on PATH.
75
80
 
76
81
  ```bash
77
82
  # Using uvx
78
- uvx open-edison --help
83
+ uvx open-edison
79
84
 
80
85
  # Using pipx
81
86
  pipx install open-edison
82
- open-edison --help
87
+ open-edison
83
88
  ```
84
89
 
85
90
  Run with a custom config directory:
@@ -322,8 +327,6 @@ Use the `get_security_status` tool to monitor your session's current risk level
322
327
 
323
328
  </details>
324
329
 
325
-
326
-
327
330
  ## Documentation 📚
328
331
 
329
332
  📚 **Complete documentation available in [`docs/`](docs/)**
@@ -333,7 +336,6 @@ Use the `get_security_status` tool to monitor your session's current risk level
333
336
  - 📡 **[API Reference](docs/quick-reference/api_reference.md)** - REST API documentation
334
337
  - 🧑‍💻 **[Development Guide](docs/development/development_guide.md)** - Contributing and development
335
338
 
336
-
337
339
  <details>
338
340
  <summary>📄 License</summary>
339
341
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "open-edison"
3
- version = "0.1.26"
3
+ version = "0.1.29"
4
4
  description = "Open-source MCP security, aggregation, and monitoring. Single-user, self-hosted MCP proxy."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -10,6 +10,7 @@ import os
10
10
  import sys
11
11
  import tomllib
12
12
  from dataclasses import asdict, dataclass
13
+ from functools import cache
13
14
  from pathlib import Path
14
15
  from typing import Any, cast
15
16
 
@@ -37,8 +38,7 @@ def get_config_dir() -> Path:
37
38
  try:
38
39
  return Path(env_dir).expanduser().resolve()
39
40
  except Exception:
40
- # Fall through to defaults
41
- pass
41
+ log.warning(f"Failed to resolve OPEN_EDISON_CONFIG_DIR: {env_dir}")
42
42
 
43
43
  # Platform-specific defaults
44
44
  try:
@@ -58,26 +58,8 @@ def get_config_dir() -> Path:
58
58
  return (Path.home() / ".open-edison").resolve()
59
59
 
60
60
 
61
- def _default_config_path() -> Path:
62
- """Determine default config.json path.
63
-
64
- In development (editable or source checkout), prefer repository root
65
- `config.json` when present. In an installed package (site-packages),
66
- use the resolved user config dir.
67
- """
68
- repo_pyproject = root_dir / "pyproject.toml"
69
- repo_config = root_dir / "config.json"
70
-
71
- # If pyproject.toml exists next to src/, we are likely in a repo checkout
72
- # Prefer the user config directory if a config already exists there (runtime source of truth),
73
- # otherwise fall back to the repo copy.
74
- if repo_pyproject.exists():
75
- user_cfg = get_config_dir() / "config.json"
76
- if user_cfg.exists():
77
- return user_cfg
78
- return repo_config
79
-
80
- # Installed package: prefer user config directory
61
+ def get_config_json_path() -> Path:
62
+ """Get the path to the config.json file"""
81
63
  return get_config_dir() / "config.json"
82
64
 
83
65
 
@@ -156,6 +138,15 @@ class TelemetryConfig:
156
138
  export_interval_ms: int = 60000
157
139
 
158
140
 
141
+ @cache
142
+ def load_json_file(path: Path) -> dict[str, Any]:
143
+ """Load a JSON file from the given path.
144
+ Kept as a separate function because we want to manually clear cache sometimes (update in config)"""
145
+ log.info(f"Loading configuration from {path}")
146
+ with open(path) as f:
147
+ return json.load(f)
148
+
149
+
159
150
  @dataclass
160
151
  class Config:
161
152
  """Main configuration class"""
@@ -188,21 +179,18 @@ class Config:
188
179
  If no path is provided, uses OPEN_EDISON_CONFIG_DIR or project root.
189
180
  """
190
181
  if config_path is None:
191
- config_path = _default_config_path()
182
+ config_path = get_config_json_path()
192
183
  else:
193
184
  # If a directory was passed, use config.json inside it
194
185
  if config_path.is_dir():
195
186
  config_path = config_path / "config.json"
196
187
 
197
- log.info(f"Loading configuration from {config_path}")
198
-
199
188
  if not config_path.exists():
200
189
  log.warning(f"Config file not found at {config_path}, creating default config")
201
190
  self.create_default()
202
191
  self.save(config_path)
203
192
 
204
- with open(config_path) as f:
205
- data: dict[str, Any] = json.load(f)
193
+ data = load_json_file(config_path)
206
194
 
207
195
  mcp_servers_data = data.get("mcp_servers", []) # type: ignore
208
196
  server_data = data.get("server", {}) # type: ignore
@@ -248,7 +236,7 @@ class Config:
248
236
  def save(self, config_path: Path | None = None) -> None:
249
237
  """Save configuration to JSON file"""
250
238
  if config_path is None:
251
- config_path = _default_config_path()
239
+ config_path = get_config_json_path()
252
240
  else:
253
241
  # If a directory was passed, save to config.json inside it
254
242
  if config_path.is_dir():
@@ -233,7 +233,11 @@ class SessionTrackingMiddleware(Middleware):
233
233
  # Get or create session stats
234
234
  _, _session_id = self._get_or_create_session_stats(context)
235
235
 
236
- return await call_next(context) # type: ignore
236
+ try:
237
+ return await call_next(context) # type: ignore
238
+ except Exception:
239
+ log.exception("MCP request handling failed")
240
+ raise
237
241
 
238
242
  # Hooks for Tools
239
243
  async def on_list_tools( # noqa
@@ -243,7 +247,11 @@ class SessionTrackingMiddleware(Middleware):
243
247
  ) -> Any:
244
248
  log.debug("🔍 on_list_tools")
245
249
  # Get the original response
246
- response = await call_next(context)
250
+ try:
251
+ response = await call_next(context)
252
+ except Exception:
253
+ log.exception("MCP list_tools failed")
254
+ raise
247
255
  log.trace(f"🔍 on_list_tools response: {response}")
248
256
 
249
257
  session_id = current_session_id_ctxvar.get()
@@ -368,7 +376,11 @@ class SessionTrackingMiddleware(Middleware):
368
376
  """Process resource access and track security implications."""
369
377
  log.trace("🔍 on_list_resources")
370
378
  # Get the original response
371
- response = await call_next(context)
379
+ try:
380
+ response = await call_next(context)
381
+ except Exception:
382
+ log.exception("MCP list_resources failed")
383
+ raise
372
384
  log.trace(f"🔍 on_list_resources response: {response}")
373
385
 
374
386
  session_id = current_session_id_ctxvar.get()
@@ -415,7 +427,11 @@ class SessionTrackingMiddleware(Middleware):
415
427
  session_id = current_session_id_ctxvar.get()
416
428
  if session_id is None:
417
429
  log.warning("No session ID found for resource access tracking")
418
- return await call_next(context)
430
+ try:
431
+ return await call_next(context)
432
+ except Exception:
433
+ log.exception("MCP read_resource failed")
434
+ raise
419
435
 
420
436
  session = get_session_from_db(session_id)
421
437
  log.trace(f"Adding resource access to session {session_id}")
@@ -457,7 +473,11 @@ class SessionTrackingMiddleware(Middleware):
457
473
  db_session.commit()
458
474
 
459
475
  log.trace(f"Resource access {resource_name} added to session {session_id}")
460
- return await call_next(context)
476
+ try:
477
+ return await call_next(context)
478
+ except Exception:
479
+ log.exception("MCP read_resource failed")
480
+ raise
461
481
 
462
482
  # Hooks for Prompts
463
483
  async def on_list_prompts( # noqa
@@ -468,7 +488,11 @@ class SessionTrackingMiddleware(Middleware):
468
488
  """Process resource access and track security implications."""
469
489
  log.debug("🔍 on_list_prompts")
470
490
  # Get the original response
471
- response = await call_next(context)
491
+ try:
492
+ response = await call_next(context)
493
+ except Exception:
494
+ log.exception("MCP list_prompts failed")
495
+ raise
472
496
  log.debug(f"🔍 on_list_prompts response: {response}")
473
497
 
474
498
  session_id = current_session_id_ctxvar.get()
@@ -515,7 +539,11 @@ class SessionTrackingMiddleware(Middleware):
515
539
  session_id = current_session_id_ctxvar.get()
516
540
  if session_id is None:
517
541
  log.warning("No session ID found for prompt access tracking")
518
- return await call_next(context)
542
+ try:
543
+ return await call_next(context)
544
+ except Exception:
545
+ log.exception("MCP get_prompt failed")
546
+ raise
519
547
 
520
548
  session = get_session_from_db(session_id)
521
549
  log.trace(f"Adding prompt access to session {session_id}")
@@ -554,4 +582,8 @@ class SessionTrackingMiddleware(Middleware):
554
582
  db_session.commit()
555
583
 
556
584
  log.trace(f"Prompt access {prompt_name} added to session {session_id}")
557
- return await call_next(context)
585
+ try:
586
+ return await call_next(context)
587
+ except Exception:
588
+ log.exception("MCP get_prompt failed")
589
+ raise
@@ -14,22 +14,8 @@ from loguru import logger as log
14
14
 
15
15
  from src.config import Config, get_config_dir
16
16
 
17
- # Detect repository root (same logic as in src.config)
18
- _ROOT_DIR = Path(__file__).parent.parent
19
-
20
17
 
21
18
  def _default_permissions_dir() -> Path:
22
- """Resolve default permissions directory.
23
-
24
- In development (repo checkout with pyproject.toml), prefer repository root so
25
- we use repo-local tool/resource/prompt permissions JSON files. Otherwise fall
26
- back to the standard user config directory.
27
- """
28
- try:
29
- if (_ROOT_DIR / "pyproject.toml").exists():
30
- return _ROOT_DIR
31
- except Exception:
32
- pass
33
19
  return get_config_dir()
34
20
 
35
21
 
@@ -76,6 +62,9 @@ class PromptPermission:
76
62
  write_operation: bool = False
77
63
  read_private_data: bool = False
78
64
  read_untrusted_public_data: bool = False
65
+ # Optional metadata fields (ignored by enforcement but accepted from JSON)
66
+ description: str | None = None
67
+ acl: str = "PUBLIC"
79
68
 
80
69
 
81
70
  @dataclass
@@ -30,7 +30,7 @@ from loguru import logger as log
30
30
  from pydantic import BaseModel, Field
31
31
 
32
32
  from src import events
33
- from src.config import Config, MCPServerConfig
33
+ from src.config import Config, MCPServerConfig, load_json_file
34
34
  from src.config import get_config_dir as _get_cfg_dir # type: ignore[attr-defined]
35
35
  from src.middleware.session_tracking import (
36
36
  MCPSessionModel,
@@ -250,6 +250,11 @@ class OpenEdisonProxy:
250
250
  _ = json.loads(content or "{}")
251
251
  target.write_text(content or "{}", encoding="utf-8")
252
252
  log.debug(f"Saved JSON config to {target}")
253
+
254
+ # Clear cache for the config file, if it was config.json
255
+ if name == "config.json":
256
+ load_json_file.cache_clear()
257
+
253
258
  return {"status": "ok"}
254
259
  except Exception as e: # noqa: BLE001
255
260
  raise HTTPException(status_code=400, detail=f"Save failed: {e}") from e
@@ -539,9 +544,9 @@ class OpenEdisonProxy:
539
544
  log.info("🔄 Reinitializing MCP servers via API endpoint")
540
545
 
541
546
  # Create a completely new SingleUserMCP instance to ensure clean state
542
- old_mcp = self.single_user_mcp
543
- self.single_user_mcp = SingleUserMCP()
544
- del old_mcp
547
+ # old_mcp = self.single_user_mcp
548
+ # self.single_user_mcp = SingleUserMCP()
549
+ # del old_mcp
545
550
 
546
551
  # Initialize the new instance with fresh config
547
552
  await self.single_user_mcp.initialize()
@@ -49,7 +49,8 @@ class SingleUserMCP(FastMCP[Any]):
49
49
  """
50
50
 
51
51
  def __init__(self):
52
- super().__init__(name="open-edison-single-user")
52
+ # Disable error masking so upstream error details are preserved in responses
53
+ super().__init__(name="open-edison-single-user", mask_error_details=False)
53
54
 
54
55
  # Add session tracking middleware for data access monitoring
55
56
  self.add_middleware(SessionTrackingMiddleware())
@@ -288,6 +289,10 @@ class SingleUserMCP(FastMCP[Any]):
288
289
  f"Found {len(enabled_servers)} enabled servers: {[s.name for s in enabled_servers]}"
289
290
  )
290
291
 
292
+ # Unmount all servers
293
+ for server_name in list(mounted_servers.keys()):
294
+ await self.unmount(server_name)
295
+
291
296
  # Create composite proxy for all real servers
292
297
  success = await self.create_composite_proxy(enabled_servers)
293
298
  if not success:
@@ -296,6 +301,11 @@ class SingleUserMCP(FastMCP[Any]):
296
301
 
297
302
  log.info("✅ Single User MCP server initialized with composite proxy")
298
303
 
304
+ # Invalidate and warm lists to ensure reload
305
+ _ = await self._tool_manager.list_tools()
306
+ _ = await self._resource_manager.list_resources()
307
+ _ = await self._prompt_manager.list_prompts()
308
+
299
309
  def _calculate_risk_level(self, trifecta: dict[str, bool]) -> str:
300
310
  """
301
311
  Calculate a human-readable risk level based on trifecta flags.
@@ -386,7 +396,7 @@ class SingleUserMCP(FastMCP[Any]):
386
396
  """
387
397
  tool_list = await self._tool_manager.list_tools()
388
398
  available_tools: list[str] = []
389
- log.debug(f"Raw tool list: {tool_list}")
399
+ log.trace(f"Raw tool list: {tool_list}")
390
400
  perms = Permissions()
391
401
  for tool in tool_list:
392
402
  # Use the prefixed key (e.g., "filesystem_read_file") to match flattened permissions
File without changes
File without changes
File without changes
File without changes