open-edison 0.1.30__py3-none-any.whl → 0.1.36__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-edison
3
- Version: 0.1.30
3
+ Version: 0.1.36
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
@@ -20,17 +20,13 @@ Requires-Dist: pyyaml>=6.0.2
20
20
  Requires-Dist: sqlalchemy>=2.0.41
21
21
  Requires-Dist: starlette>=0.47.1
22
22
  Requires-Dist: uvicorn>=0.35.0
23
- Provides-Extra: dev
24
- Requires-Dist: pytest-asyncio>=1.0.0; extra == 'dev'
25
- Requires-Dist: pytest>=8.3.3; extra == 'dev'
26
- Requires-Dist: ruff>=0.12.3; extra == 'dev'
27
23
  Description-Content-Type: text/markdown
28
24
 
29
25
  # OpenEdison 🔒⚡️
30
26
 
31
- > The secure MCP proxy gateway
27
+ > The Secure MCP Control Panel
32
28
 
33
- 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.
29
+ 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.
34
30
 
35
31
  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.
36
32
 
@@ -0,0 +1,22 @@
1
+ src/__init__.py,sha256=QWeZdjAm2D2B0eWhd8m2-DPpWvIP26KcNJxwEoU1oEQ,254
2
+ src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
+ src/cli.py,sha256=P38IWER41S5oAfbd_7p89hBpnjClsNHpmE5pSsJc6uU,9733
4
+ src/config.py,sha256=RSsAYzl8cj6eaDN1RORMcfKKWBcp4bKTQp2BdhAL9mg,10258
5
+ src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
6
+ src/events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
7
+ src/oauth_manager.py,sha256=qcQa5BDRZr4bjqiXNflCnrXOh9mo9JVjvP2Caseg2Uc,9943
8
+ src/permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
9
+ src/server.py,sha256=cnO5bgxT-lrfuwk9AIvB_HBV8SWOtFClfGUn5_zFWyo,45652
10
+ src/single_user_mcp.py,sha256=rJrlqHcIubGkos_24ux5rb3OoKYDzvagCHghhfDeXTI,18535
11
+ src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
12
+ src/frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
13
+ src/frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
14
+ src/frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
15
+ src/frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
16
+ src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
17
+ src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
18
+ open_edison-0.1.36.dist-info/METADATA,sha256=o3ys6S2yDzXOQXKUqJ43lArMvpxeUYYqwDnOAqbl5gs,11915
19
+ open_edison-0.1.36.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ open_edison-0.1.36.dist-info/entry_points.txt,sha256=qNAkJcnoTXRhj8J--3PDmXz_TQKdB8H_0C9wiCtDIyA,72
21
+ open_edison-0.1.36.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
22
+ open_edison-0.1.36.dist-info/RECORD,,
src/cli.py CHANGED
@@ -4,8 +4,6 @@ CLI entrypoint for Open Edison.
4
4
  Provides `open-edison` executable when installed via pip/uvx/pipx.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import argparse
10
8
  import asyncio
11
9
  import os
@@ -17,7 +15,7 @@ from typing import Any, NoReturn, cast
17
15
 
18
16
  from loguru import logger as _log # type: ignore[reportMissingImports]
19
17
 
20
- from .config import Config, get_config_dir
18
+ from .config import Config, get_config_dir, get_config_json_path
21
19
  from .server import OpenEdisonProxy
22
20
 
23
21
  log: Any = _log
@@ -69,11 +67,6 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
69
67
  default="interactive",
70
68
  help="Source application to import from",
71
69
  )
72
- sp_import.add_argument(
73
- "--project-dir",
74
- type=Path,
75
- help="When --source=cursor, path to the project containing .cursor/mcp.json",
76
- )
77
70
  sp_import.add_argument(
78
71
  "--config-dir",
79
72
  type=Path,
@@ -191,7 +184,7 @@ async def _run_server(args: Any) -> None:
191
184
  config_dir = get_config_dir()
192
185
 
193
186
  # Load config after setting env override
194
- cfg = Config(config_dir)
187
+ cfg = Config(get_config_json_path())
195
188
 
196
189
  host = getattr(args, "host", None) or cfg.server.host
197
190
  port = getattr(args, "port", None) or cfg.server.port
@@ -250,11 +243,6 @@ def main(argv: list[str] | None = None) -> NoReturn: # noqa: C901
250
243
  importer_argv: list[str] = []
251
244
  if args.source:
252
245
  importer_argv += ["--source", str(args.source)]
253
- if getattr(args, "project_dir", None):
254
- importer_argv += [
255
- "--project-dir",
256
- str(Path(args.project_dir).expanduser().resolve()),
257
- ]
258
246
  if getattr(args, "config_dir", None):
259
247
  importer_argv += [
260
248
  "--config-dir",
@@ -262,8 +250,6 @@ def main(argv: list[str] | None = None) -> NoReturn: # noqa: C901
262
250
  ]
263
251
  if args.merge:
264
252
  importer_argv += ["--merge", str(args.merge)]
265
- if bool(getattr(args, "enable_imported", False)):
266
- importer_argv += ["--enable-imported"]
267
253
  if bool(getattr(args, "dry_run", False)):
268
254
  importer_argv += ["--dry-run"]
269
255
 
src/config.py CHANGED
@@ -142,11 +142,16 @@ class TelemetryConfig:
142
142
  def load_json_file(path: Path) -> dict[str, Any]:
143
143
  """Load a JSON file from the given path.
144
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}")
145
+ log.trace(f"Loading configuration from {path}")
146
146
  with open(path) as f:
147
147
  return json.load(f)
148
148
 
149
149
 
150
+ def clear_json_file_cache() -> None:
151
+ """Clear the cache for the given JSON file path"""
152
+ load_json_file.cache_clear()
153
+
154
+
150
155
  @dataclass
151
156
  class Config:
152
157
  """Main configuration class"""
@@ -252,7 +257,20 @@ class Config:
252
257
  }
253
258
 
254
259
  # Ensure directory exists
255
- config_path.parent.mkdir(parents=True, exist_ok=True)
260
+ try:
261
+ config_path.parent.mkdir(parents=True, exist_ok=True)
262
+ except FileExistsError as e:
263
+ # If the parent path exists as a file, not a directory, this will fail
264
+ if config_path.parent.is_file():
265
+ log.error(
266
+ f"Config directory path {config_path.parent} exists as a file, not a directory"
267
+ )
268
+ log.error("Please remove the file or specify a different config directory")
269
+ raise FileExistsError(
270
+ f"Config directory path {config_path.parent} exists as a file, not a directory"
271
+ ) from e
272
+ log.error(f"Failed to create config directory {config_path.parent}: {e}")
273
+ raise
256
274
  with open(config_path, "w") as f:
257
275
  json.dump(data, f, indent=2)
258
276
 
src/config.pyi ADDED
@@ -0,0 +1,80 @@
1
+ from pathlib import Path
2
+ from typing import Any, overload
3
+
4
+ # Module constants
5
+ DEFAULT_OTLP_METRICS_ENDPOINT: str
6
+ root_dir: Path
7
+
8
+ def get_config_dir() -> Path: ...
9
+ def get_config_json_path() -> Path: ...
10
+
11
+ class ServerConfig:
12
+ host: str
13
+ port: int
14
+ api_key: str
15
+
16
+ class LoggingConfig:
17
+ level: str
18
+ database_path: str
19
+
20
+ class MCPServerConfig:
21
+ name: str
22
+ command: str
23
+ args: list[str]
24
+ env: dict[str, str] | None
25
+ enabled: bool
26
+ roots: list[str] | None
27
+ oauth_scopes: list[str] | None
28
+ oauth_client_name: str | None
29
+
30
+ def __init__(
31
+ self,
32
+ *,
33
+ name: str,
34
+ command: str,
35
+ args: list[str],
36
+ env: dict[str, str] | None = None,
37
+ enabled: bool = True,
38
+ roots: list[str] | None = None,
39
+ oauth_scopes: list[str] | None = None,
40
+ oauth_client_name: str | None = None,
41
+ ) -> None: ...
42
+ def is_remote_server(self) -> bool: ...
43
+ def get_remote_url(self) -> str | None: ...
44
+
45
+ class TelemetryConfig:
46
+ enabled: bool
47
+ otlp_endpoint: str | None
48
+ headers: dict[str, str] | None
49
+ export_interval_ms: int
50
+ def __init__(
51
+ self,
52
+ *,
53
+ enabled: bool = True,
54
+ otlp_endpoint: str | None = None,
55
+ headers: dict[str, str] | None = None,
56
+ export_interval_ms: int = 60000,
57
+ ) -> None: ...
58
+
59
+ def load_json_file(path: Path) -> dict[str, Any]: ...
60
+ def clear_json_file_cache() -> None: ...
61
+
62
+ class Config:
63
+ @property
64
+ def version(self) -> str: ...
65
+ server: ServerConfig
66
+ logging: LoggingConfig
67
+ mcp_servers: list[MCPServerConfig]
68
+ telemetry: TelemetryConfig | None
69
+ @overload
70
+ def __init__(self, config_path: Path | None = None) -> None: ...
71
+ @overload
72
+ def __init__(
73
+ self,
74
+ server: ServerConfig,
75
+ logging: LoggingConfig,
76
+ mcp_servers: list[MCPServerConfig],
77
+ telemetry: TelemetryConfig | None = None,
78
+ ) -> None: ...
79
+ def save(self, config_path: Path | None = None) -> None: ...
80
+ def create_default(self) -> None: ...
src/events.py CHANGED
@@ -5,8 +5,6 @@ Provides a simple publisher/subscriber model to stream JSON events to
5
5
  connected dashboard clients over Server-Sent Events (SSE).
6
6
  """
7
7
 
8
- from __future__ import annotations
9
-
10
8
  import asyncio
11
9
  import json
12
10
  from collections.abc import AsyncIterator, Callable