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.
- {open_edison-0.1.26 → open_edison-0.1.29}/.gitignore +3 -1
- {open_edison-0.1.26 → open_edison-0.1.29}/PKG-INFO +18 -16
- {open_edison-0.1.26 → open_edison-0.1.29}/README.md +17 -15
- {open_edison-0.1.26 → open_edison-0.1.29}/pyproject.toml +1 -1
- {open_edison-0.1.26 → open_edison-0.1.29}/src/config.py +16 -28
- {open_edison-0.1.26 → open_edison-0.1.29}/src/middleware/session_tracking.py +40 -8
- {open_edison-0.1.26 → open_edison-0.1.29}/src/permissions.py +3 -14
- {open_edison-0.1.26 → open_edison-0.1.29}/src/server.py +9 -4
- {open_edison-0.1.26 → open_edison-0.1.29}/src/single_user_mcp.py +12 -2
- {open_edison-0.1.26 → open_edison-0.1.29}/LICENSE +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/config.json +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/desktop_ext/README.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/README.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/architecture/single_user_design.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/configuration.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/project_structure.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/core/proxy_usage.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/deployment/docker.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/deployment/local.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/contributing.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/development_guide.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/development/testing.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/quick-reference/api_reference.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/docs/quick-reference/config_quick_start.md +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/prompt_permissions.json +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/resource_permissions.json +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/__init__.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/__main__.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/cli.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/events.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/middleware/data_access_tracker.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/oauth_manager.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/src/telemetry.py +0 -0
- {open_edison-0.1.26 → open_edison-0.1.29}/tool_permissions.json +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: open-edison
|
3
|
-
Version: 0.1.
|
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
|
-
|
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
|
-
- 🛑 **
|
54
|
-
-
|
55
|
-
- 🗂️ **
|
56
|
-
-
|
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
|
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
|
110
|
+
uvx open-edison
|
106
111
|
|
107
112
|
# Using pipx
|
108
113
|
pipx install open-edison
|
109
|
-
open-edison
|
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
|
-
|
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
|
-
- 🛑 **
|
27
|
-
-
|
28
|
-
- 🗂️ **
|
29
|
-
-
|
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
|
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
|
83
|
+
uvx open-edison
|
79
84
|
|
80
85
|
# Using pipx
|
81
86
|
pipx install open-edison
|
82
|
-
open-edison
|
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
|
|
@@ -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
|
-
|
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
|
62
|
-
"""
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|