open-edison 0.1.19__py3-none-any.whl → 0.1.29__py3-none-any.whl
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.19.dist-info → open_edison-0.1.29.dist-info}/METADATA +66 -45
- open_edison-0.1.29.dist-info/RECORD +17 -0
- src/cli.py +2 -1
- src/config.py +71 -71
- src/events.py +153 -0
- src/middleware/data_access_tracker.py +164 -434
- src/middleware/session_tracking.py +133 -37
- src/oauth_manager.py +281 -0
- src/permissions.py +281 -0
- src/server.py +491 -134
- src/single_user_mcp.py +230 -158
- src/telemetry.py +4 -40
- open_edison-0.1.19.dist-info/RECORD +0 -14
- {open_edison-0.1.19.dist-info → open_edison-0.1.29.dist-info}/WHEEL +0 -0
- {open_edison-0.1.19.dist-info → open_edison-0.1.29.dist-info}/entry_points.txt +0 -0
- {open_edison-0.1.19.dist-info → open_edison-0.1.29.dist-info}/licenses/LICENSE +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
|
@@ -25,24 +25,45 @@ Requires-Dist: pytest>=8.3.3; extra == 'dev'
|
|
25
25
|
Requires-Dist: ruff>=0.12.3; extra == 'dev'
|
26
26
|
Description-Content-Type: text/markdown
|
27
27
|
|
28
|
-
# OpenEdison
|
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.
|
35
|
+
|
36
|
+
<p align="center">
|
37
|
+
<img src="media/trifecta520p.gif" alt="Trifecta Security Risk Animation" width="520">
|
38
|
+
</p>
|
31
39
|
|
32
40
|
<div align="center">
|
33
|
-
<h2>📧
|
41
|
+
<h2>📧 To get visibility, control and exfiltration blocker into AI's interaction with your company software, systems of record, DBs, <a href="mailto:hello@edison.watch">Contact us</a> to discuss.</h2>
|
34
42
|
</div>
|
35
43
|
|
36
|
-
|
44
|
+
<p align="center">
|
45
|
+
<img alt="Project Version" src="https://img.shields.io/pypi/v/open-edison?label=version&color=blue">
|
46
|
+
<img alt="Python Version" src="https://img.shields.io/badge/python-3.12-blue?logo=python">
|
47
|
+
<img src="https://img.shields.io/badge/License-GPLv3-blue" alt="License">
|
48
|
+
|
49
|
+
</p>
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
## Features ✨
|
54
|
+
|
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
|
59
|
+
- 🔗 **Simple API** - REST API for managing MCP servers and proxying requests
|
60
|
+
- 🐳 **Docker support** - Run in a container for easy deployment
|
37
61
|
|
38
|
-
|
39
|
-
- **JSON configuration** - Easy to configure and manage your MCP servers
|
40
|
-
- **Simple local frontend** - Track and monitor your MCP interactions, servers, and sessions.
|
41
|
-
- **Session tracking** - Track and monitor your MCP interactions
|
42
|
-
- **Simple API** - REST API for managing MCP servers and proxying requests
|
43
|
-
- **Docker support** - Run in a container for easy deployment
|
62
|
+
## About Edison.watch 🏢
|
44
63
|
|
45
|
-
|
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
|
+
|
66
|
+
## Quick Start 🚀
|
46
67
|
|
47
68
|
The fastest way to get started:
|
48
69
|
|
@@ -52,10 +73,10 @@ The fastest way to get started:
|
|
52
73
|
curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_pipe_bash.sh | bash
|
53
74
|
```
|
54
75
|
|
55
|
-
Run locally with uvx: `uvx open-edison
|
76
|
+
Run locally with uvx: `uvx open-edison`
|
56
77
|
|
57
78
|
<details>
|
58
|
-
<summary
|
79
|
+
<summary>⬇️ Install Node.js/npm (optional for MCP tools)</summary>
|
59
80
|
|
60
81
|
If you need `npx` (for Node-based MCP tools like `mcp-remote`), install Node.js as well:
|
61
82
|
|
@@ -75,6 +96,7 @@ If you need `npx` (for Node-based MCP tools like `mcp-remote`), install Node.js
|
|
75
96
|
- Node/npx: `winget install -e --id OpenJS.NodeJS`
|
76
97
|
|
77
98
|
After installation, ensure that `npx` is available on PATH.
|
99
|
+
</details>
|
78
100
|
|
79
101
|
<details>
|
80
102
|
<summary><img src="https://img.shields.io/badge/pypi-3775A9?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"> Install from PyPI</summary>
|
@@ -85,11 +107,11 @@ After installation, ensure that `npx` is available on PATH.
|
|
85
107
|
|
86
108
|
```bash
|
87
109
|
# Using uvx
|
88
|
-
uvx open-edison
|
110
|
+
uvx open-edison
|
89
111
|
|
90
112
|
# Using pipx
|
91
113
|
pipx install open-edison
|
92
|
-
open-edison
|
114
|
+
open-edison
|
93
115
|
```
|
94
116
|
|
95
117
|
Run with a custom config directory:
|
@@ -109,18 +131,18 @@ There is a dockerfile for simple local setup.
|
|
109
131
|
|
110
132
|
```bash
|
111
133
|
# Single-line:
|
112
|
-
git clone https://github.com/
|
134
|
+
git clone https://github.com/Edison-Watch/open-edison.git && cd open-edison && make docker_run
|
113
135
|
|
114
136
|
# Or
|
115
137
|
# Clone repo
|
116
|
-
git clone https://github.com/
|
138
|
+
git clone https://github.com/Edison-Watch/open-edison.git
|
117
139
|
# Enter repo
|
118
140
|
cd open-edison
|
119
141
|
# Build and run
|
120
142
|
make docker_run
|
121
143
|
```
|
122
144
|
|
123
|
-
The MCP server will be available at `http://localhost:3000` and the api + frontend at `http://localhost:3001`.
|
145
|
+
The MCP server will be available at `http://localhost:3000` and the api + frontend at `http://localhost:3001`. 🌐
|
124
146
|
|
125
147
|
</details>
|
126
148
|
|
@@ -130,7 +152,7 @@ The MCP server will be available at `http://localhost:3000` and the api + fronte
|
|
130
152
|
1. Clone the repository:
|
131
153
|
|
132
154
|
```bash
|
133
|
-
git clone https://github.com/
|
155
|
+
git clone https://github.com/Edison-Watch/open-edison.git
|
134
156
|
cd open-edison
|
135
157
|
```
|
136
158
|
|
@@ -161,12 +183,12 @@ make run
|
|
161
183
|
open-edison run
|
162
184
|
```
|
163
185
|
|
164
|
-
The server will be available at `http://localhost:3000`.
|
186
|
+
The server will be available at `http://localhost:3000`. 🌐
|
165
187
|
|
166
188
|
</details>
|
167
189
|
|
168
190
|
<details>
|
169
|
-
<summary
|
191
|
+
<summary>🔌 MCP Connection</summary>
|
170
192
|
|
171
193
|
Connect any MCP client to Open Edison (requires Node.js/npm for `npx`):
|
172
194
|
|
@@ -190,20 +212,20 @@ Or add to your MCP client config:
|
|
190
212
|
</details>
|
191
213
|
|
192
214
|
<details>
|
193
|
-
<summary
|
215
|
+
<summary>🧭 Usage</summary>
|
194
216
|
|
195
217
|
### API Endpoints
|
196
218
|
|
197
219
|
See [API Reference](docs/quick-reference/api_reference.md) for full API documentation.
|
198
220
|
|
199
221
|
<details>
|
200
|
-
<summary
|
222
|
+
<summary>🛠️ Development</summary>
|
201
223
|
|
202
|
-
### Setup
|
224
|
+
### Setup 🧰
|
203
225
|
|
204
226
|
Setup from source as above.
|
205
227
|
|
206
|
-
### Run
|
228
|
+
### Run ▶️
|
207
229
|
|
208
230
|
Server doesn't have any auto-reload at the moment, so you'll need to run & ctrl-c this during development.
|
209
231
|
|
@@ -211,7 +233,7 @@ Server doesn't have any auto-reload at the moment, so you'll need to run & ctrl-
|
|
211
233
|
make run
|
212
234
|
```
|
213
235
|
|
214
|
-
### Tests/code quality
|
236
|
+
### Tests/code quality ✅
|
215
237
|
|
216
238
|
We expect `make ci` to return cleanly.
|
217
239
|
|
@@ -224,7 +246,7 @@ make ci
|
|
224
246
|
<details>
|
225
247
|
<summary>⚙️ Configuration (config.json)</summary>
|
226
248
|
|
227
|
-
## Configuration
|
249
|
+
## Configuration ⚙️
|
228
250
|
|
229
251
|
The `config.json` file contains all configuration:
|
230
252
|
|
@@ -246,18 +268,20 @@ Each MCP server configuration includes:
|
|
246
268
|
|
247
269
|
</details>
|
248
270
|
|
271
|
+
## 🔐 How Edison prevents data leakages
|
272
|
+
|
249
273
|
<details>
|
250
|
-
<summary
|
274
|
+
<summary>🔱 The lethal trifecta, agent lifecycle management</summary>
|
251
275
|
|
252
276
|
Open Edison includes a comprehensive security monitoring system that tracks the "lethal trifecta" of AI agent risks, as described in [Simon Willison's blog post](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/):
|
253
277
|
|
254
|
-
<img src="media/lethal-trifecta.png" alt="The lethal trifecta diagram showing the three key AI agent security risks" width="
|
278
|
+
<img src="media/lethal-trifecta.png" alt="The lethal trifecta diagram showing the three key AI agent security risks" width="70%">
|
255
279
|
|
256
280
|
1. **Private data access** - Access to sensitive local files/data
|
257
281
|
2. **Untrusted content exposure** - Exposure to external/web content
|
258
282
|
3. **External communication** - Ability to write/send data externally
|
259
283
|
|
260
|
-
<img src="media/pam-diagram.png" alt="Privileged Access Management (PAM) example showing the lethal trifecta in action" width="
|
284
|
+
<img src="media/pam-diagram.png" alt="Privileged Access Management (PAM) example showing the lethal trifecta in action" width="90%">
|
261
285
|
|
262
286
|
The configuration allows you to classify these risks across **tools**, **resources**, and **prompts** using separate configuration files.
|
263
287
|
|
@@ -265,7 +289,7 @@ In addition to trifecta, we track Access Control Level (ACL) for each tool call,
|
|
265
289
|
that is, each tool has an ACL level (one of PUBLIC, PRIVATE, or SECRET), and we track the highest ACL level for each session.
|
266
290
|
If a write operation is attempted to a lower ACL level, it is blocked.
|
267
291
|
|
268
|
-
### Tool Permissions (`tool_permissions.json`)
|
292
|
+
### 🧰 Tool Permissions (`tool_permissions.json`)
|
269
293
|
|
270
294
|
Defines security classifications for MCP tools. See full file: [tool_permissions.json](tool_permissions.json), it looks like:
|
271
295
|
|
@@ -283,7 +307,7 @@ Defines security classifications for MCP tools. See full file: [tool_permissions
|
|
283
307
|
```
|
284
308
|
|
285
309
|
<details>
|
286
|
-
<summary
|
310
|
+
<summary>📁 Resource Permissions (`resource_permissions.json`)</summary>
|
287
311
|
|
288
312
|
### Resource Permissions (`resource_permissions.json`)
|
289
313
|
|
@@ -299,7 +323,7 @@ Defines security classifications for resource access patterns. See full file: [r
|
|
299
323
|
</details>
|
300
324
|
|
301
325
|
<details>
|
302
|
-
<summary
|
326
|
+
<summary>💬 Prompt Permissions (`prompt_permissions.json`)</summary>
|
303
327
|
|
304
328
|
### Prompt Permissions (`prompt_permissions.json`)
|
305
329
|
|
@@ -314,7 +338,7 @@ Defines security classifications for prompt types. See full file: [prompt_permis
|
|
314
338
|
|
315
339
|
</details>
|
316
340
|
|
317
|
-
### Wildcard Patterns
|
341
|
+
### Wildcard Patterns ✨
|
318
342
|
|
319
343
|
All permission types support wildcard patterns:
|
320
344
|
|
@@ -322,7 +346,7 @@ All permission types support wildcard patterns:
|
|
322
346
|
- **Resources**: `scheme:*` (e.g., `file:*` matches all file resources)
|
323
347
|
- **Prompts**: `type:*` (e.g., `template:*` matches all template prompts)
|
324
348
|
|
325
|
-
### Security Monitoring
|
349
|
+
### Security Monitoring 🕵️
|
326
350
|
|
327
351
|
**All items must be explicitly configured** - unknown tools/resources/prompts will be rejected for security.
|
328
352
|
|
@@ -330,20 +354,17 @@ Use the `get_security_status` tool to monitor your session's current risk level
|
|
330
354
|
|
331
355
|
</details>
|
332
356
|
|
333
|
-
|
334
|
-
<summary>Documentation</summary>
|
357
|
+
## Documentation 📚
|
335
358
|
|
336
359
|
📚 **Complete documentation available in [`docs/`](docs/)**
|
337
360
|
|
338
|
-
- **[Getting Started](docs/quick-reference/config_quick_start.md)** - Quick setup guide
|
339
|
-
- **[Configuration](docs/core/configuration.md)** - Complete configuration reference
|
340
|
-
- **[API Reference](docs/quick-reference/api_reference.md)** - REST API documentation
|
341
|
-
- **[Development Guide](docs/development/development_guide.md)** - Contributing and development
|
342
|
-
|
343
|
-
</details>
|
361
|
+
- 🚀 **[Getting Started](docs/quick-reference/config_quick_start.md)** - Quick setup guide
|
362
|
+
- ⚙️ **[Configuration](docs/core/configuration.md)** - Complete configuration reference
|
363
|
+
- 📡 **[API Reference](docs/quick-reference/api_reference.md)** - REST API documentation
|
364
|
+
- 🧑💻 **[Development Guide](docs/development/development_guide.md)** - Contributing and development
|
344
365
|
|
345
366
|
<details>
|
346
|
-
<summary
|
367
|
+
<summary>📄 License</summary>
|
347
368
|
|
348
369
|
GPL-3.0 License - see [LICENSE](LICENSE) for details.
|
349
370
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
src/__init__.py,sha256=QWeZdjAm2D2B0eWhd8m2-DPpWvIP26KcNJxwEoU1oEQ,254
|
2
|
+
src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
|
3
|
+
src/cli.py,sha256=_F1xtUU2h4snWUHf1NptRWGQaD2OSIhEPGLh9Rzmtis,10032
|
4
|
+
src/config.py,sha256=LHLQWWWvPzdQfCEUCbCMqHpEOnXJhmPFmk9bSVGnUyY,9443
|
5
|
+
src/events.py,sha256=rBH7rnaSWZ7GIC8zyBTwpcvIKWmKYCki-DNGgJhxPow,5001
|
6
|
+
src/oauth_manager.py,sha256=qcQa5BDRZr4bjqiXNflCnrXOh9mo9JVjvP2Caseg2Uc,9943
|
7
|
+
src/permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
|
8
|
+
src/server.py,sha256=UjVBnDCghbP6TbrhWH5tMmoByvRNrr9LMpuHUDt7kno,44692
|
9
|
+
src/single_user_mcp.py,sha256=P-D6_B_l7fJ2OzMjVeg56OkzRkCsODqKpky4ecHRj6I,17356
|
10
|
+
src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
|
11
|
+
src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
|
12
|
+
src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
|
13
|
+
open_edison-0.1.29.dist-info/METADATA,sha256=TxOClcNfQNRR1RPxFswzJWlrmKKruN9_u_JjnfhECKA,12103
|
14
|
+
open_edison-0.1.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
open_edison-0.1.29.dist-info/entry_points.txt,sha256=qNAkJcnoTXRhj8J--3PDmXz_TQKdB8H_0C9wiCtDIyA,72
|
16
|
+
open_edison-0.1.29.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
17
|
+
open_edison-0.1.29.dist-info/RECORD,,
|
src/cli.py
CHANGED
@@ -177,6 +177,7 @@ def _spawn_frontend_dev( # noqa: C901 - pragmatic complexity for env probing
|
|
177
177
|
|
178
178
|
|
179
179
|
async def _run_server(args: Any) -> None:
|
180
|
+
# TODO check this works as we want it to
|
180
181
|
# Resolve config dir and expose via env for the rest of the app
|
181
182
|
config_dir_arg = getattr(args, "config_dir", None)
|
182
183
|
if config_dir_arg is not None:
|
@@ -184,7 +185,7 @@ async def _run_server(args: Any) -> None:
|
|
184
185
|
config_dir = get_config_dir()
|
185
186
|
|
186
187
|
# Load config after setting env override
|
187
|
-
cfg = Config
|
188
|
+
cfg = Config(config_dir)
|
188
189
|
|
189
190
|
host = getattr(args, "host", None) or cfg.server.host
|
190
191
|
port = getattr(args, "port", None) or cfg.server.port
|
src/config.py
CHANGED
@@ -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,39 +58,11 @@ def get_config_dir() -> Path:
|
|
58
58
|
return (Path.home() / ".open-edison").resolve()
|
59
59
|
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
"""Alias to public get_config_dir (maintained for internal imports)."""
|
64
|
-
return get_config_dir()
|
65
|
-
|
66
|
-
|
67
|
-
def _default_config_path() -> Path:
|
68
|
-
"""Determine default config.json path.
|
69
|
-
|
70
|
-
In development (editable or source checkout), prefer repository root
|
71
|
-
`config.json` when present. In an installed package (site-packages),
|
72
|
-
use the resolved user config dir.
|
73
|
-
"""
|
74
|
-
repo_pyproject = root_dir / "pyproject.toml"
|
75
|
-
repo_config = root_dir / "config.json"
|
76
|
-
|
77
|
-
# If pyproject.toml exists next to src/, we are likely in a repo checkout
|
78
|
-
if repo_pyproject.exists():
|
79
|
-
return repo_config
|
80
|
-
|
81
|
-
# Otherwise, prefer user config directory
|
61
|
+
def get_config_json_path() -> Path:
|
62
|
+
"""Get the path to the config.json file"""
|
82
63
|
return get_config_dir() / "config.json"
|
83
64
|
|
84
65
|
|
85
|
-
class ConfigError(Exception):
|
86
|
-
"""Exception raised for configuration-related errors"""
|
87
|
-
|
88
|
-
def __init__(self, message: str, config_path: Path | None = None):
|
89
|
-
self.message = message
|
90
|
-
self.config_path = config_path
|
91
|
-
super().__init__(self.message)
|
92
|
-
|
93
|
-
|
94
66
|
@dataclass
|
95
67
|
class ServerConfig:
|
96
68
|
"""Server configuration"""
|
@@ -119,10 +91,41 @@ class MCPServerConfig:
|
|
119
91
|
enabled: bool = True
|
120
92
|
roots: list[str] | None = None
|
121
93
|
|
94
|
+
oauth_scopes: list[str] | None = None
|
95
|
+
"""OAuth scopes to request for this server."""
|
96
|
+
|
97
|
+
oauth_client_name: str | None = None
|
98
|
+
"""Custom client name for OAuth registration."""
|
99
|
+
|
122
100
|
def __post_init__(self):
|
123
101
|
if self.env is None:
|
124
102
|
self.env = {}
|
125
103
|
|
104
|
+
def is_remote_server(self) -> bool:
|
105
|
+
"""
|
106
|
+
Check if this is a remote MCP server (connects to external HTTPS endpoint).
|
107
|
+
|
108
|
+
Remote servers use mcp-remote with HTTPS URLs and may require OAuth.
|
109
|
+
Local servers run as child processes and don't need OAuth.
|
110
|
+
"""
|
111
|
+
return (
|
112
|
+
self.command == "npx"
|
113
|
+
and len(self.args) >= 3
|
114
|
+
and self.args[1] == "mcp-remote"
|
115
|
+
and self.args[2].startswith("https://")
|
116
|
+
)
|
117
|
+
|
118
|
+
def get_remote_url(self) -> str | None:
|
119
|
+
"""
|
120
|
+
Get the remote URL for a remote MCP server.
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
The HTTPS URL if this is a remote server, None otherwise
|
124
|
+
"""
|
125
|
+
if self.is_remote_server():
|
126
|
+
return self.args[2]
|
127
|
+
return None
|
128
|
+
|
126
129
|
|
127
130
|
@dataclass
|
128
131
|
class TelemetryConfig:
|
@@ -135,6 +138,15 @@ class TelemetryConfig:
|
|
135
138
|
export_interval_ms: int = 60000
|
136
139
|
|
137
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
|
+
|
138
150
|
@dataclass
|
139
151
|
class Config:
|
140
152
|
"""Main configuration class"""
|
@@ -160,15 +172,14 @@ class Config:
|
|
160
172
|
log.warning(f"Failed to read version from pyproject.toml: {e}")
|
161
173
|
return "unknown"
|
162
174
|
|
163
|
-
|
164
|
-
def load(cls, config_path: Path | None = None) -> "Config":
|
175
|
+
def __init__(self, config_path: Path | None = None) -> None:
|
165
176
|
"""Load configuration from JSON file.
|
166
177
|
|
167
178
|
If a directory path is provided, will look for `config.json` inside it.
|
168
179
|
If no path is provided, uses OPEN_EDISON_CONFIG_DIR or project root.
|
169
180
|
"""
|
170
181
|
if config_path is None:
|
171
|
-
config_path =
|
182
|
+
config_path = get_config_json_path()
|
172
183
|
else:
|
173
184
|
# If a directory was passed, use config.json inside it
|
174
185
|
if config_path.is_dir():
|
@@ -176,12 +187,10 @@ class Config:
|
|
176
187
|
|
177
188
|
if not config_path.exists():
|
178
189
|
log.warning(f"Config file not found at {config_path}, creating default config")
|
179
|
-
|
180
|
-
|
181
|
-
return default_config
|
190
|
+
self.create_default()
|
191
|
+
self.save(config_path)
|
182
192
|
|
183
|
-
|
184
|
-
data: dict[str, Any] = json.load(f)
|
193
|
+
data = load_json_file(config_path)
|
185
194
|
|
186
195
|
mcp_servers_data = data.get("mcp_servers", []) # type: ignore
|
187
196
|
server_data = data.get("server", {}) # type: ignore
|
@@ -216,20 +225,18 @@ class Config:
|
|
216
225
|
export_interval_ms=export_interval_ms,
|
217
226
|
)
|
218
227
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
telemetry=telemetry_cfg,
|
227
|
-
)
|
228
|
+
self.server = ServerConfig(**server_data) # type: ignore
|
229
|
+
self.logging = LoggingConfig(**logging_data) # type: ignore
|
230
|
+
self.mcp_servers = [
|
231
|
+
MCPServerConfig(**server_item) # type: ignore
|
232
|
+
for server_item in mcp_servers_data # type: ignore
|
233
|
+
]
|
234
|
+
self.telemetry = telemetry_cfg
|
228
235
|
|
229
236
|
def save(self, config_path: Path | None = None) -> None:
|
230
237
|
"""Save configuration to JSON file"""
|
231
238
|
if config_path is None:
|
232
|
-
config_path =
|
239
|
+
config_path = get_config_json_path()
|
233
240
|
else:
|
234
241
|
# If a directory was passed, save to config.json inside it
|
235
242
|
if config_path.is_dir():
|
@@ -251,26 +258,19 @@ class Config:
|
|
251
258
|
|
252
259
|
log.info(f"Configuration saved to {config_path}")
|
253
260
|
|
254
|
-
|
255
|
-
def create_default(cls) -> "Config":
|
261
|
+
def create_default(self) -> None:
|
256
262
|
"""Create default configuration"""
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
otlp_endpoint=DEFAULT_OTLP_METRICS_ENDPOINT,
|
271
|
-
),
|
263
|
+
self.server = ServerConfig()
|
264
|
+
self.logging = LoggingConfig()
|
265
|
+
self.mcp_servers = [
|
266
|
+
MCPServerConfig(
|
267
|
+
name="filesystem",
|
268
|
+
command="uvx",
|
269
|
+
args=["mcp-server-filesystem", "/tmp"],
|
270
|
+
enabled=False,
|
271
|
+
)
|
272
|
+
]
|
273
|
+
self.telemetry = TelemetryConfig(
|
274
|
+
enabled=True,
|
275
|
+
otlp_endpoint=DEFAULT_OTLP_METRICS_ENDPOINT,
|
272
276
|
)
|
273
|
-
|
274
|
-
|
275
|
-
# Load global configuration
|
276
|
-
config = Config.load()
|