open-edison 0.1.30__tar.gz → 0.1.36__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 (42) hide show
  1. {open_edison-0.1.30 → open_edison-0.1.36}/PKG-INFO +3 -7
  2. {open_edison-0.1.30 → open_edison-0.1.36}/README.md +2 -2
  3. {open_edison-0.1.30 → open_edison-0.1.36}/config.json +21 -18
  4. {open_edison-0.1.30 → open_edison-0.1.36}/desktop_ext/README.md +9 -11
  5. {open_edison-0.1.30 → open_edison-0.1.36}/docs/development/contributing.md +2 -1
  6. {open_edison-0.1.30 → open_edison-0.1.36}/docs/development/development_guide.md +1 -1
  7. {open_edison-0.1.30 → open_edison-0.1.36}/hatch_build.py +9 -2
  8. open_edison-0.1.36/installation_test/README.md +18 -0
  9. {open_edison-0.1.30 → open_edison-0.1.36}/pyproject.toml +15 -8
  10. {open_edison-0.1.30 → open_edison-0.1.36}/src/cli.py +2 -16
  11. {open_edison-0.1.30 → open_edison-0.1.36}/src/config.py +20 -2
  12. open_edison-0.1.36/src/config.pyi +80 -0
  13. {open_edison-0.1.30 → open_edison-0.1.36}/src/events.py +0 -2
  14. open_edison-0.1.36/src/frontend_dist/assets/index-BUUcUfTt.js +51 -0
  15. {open_edison-0.1.30 → open_edison-0.1.36}/src/frontend_dist/index.html +1 -1
  16. {open_edison-0.1.30 → open_edison-0.1.36}/src/server.py +17 -9
  17. {open_edison-0.1.30 → open_edison-0.1.36}/src/single_user_mcp.py +24 -0
  18. open_edison-0.1.30/src/frontend_dist/assets/index-DOR5YaNc.js +0 -51
  19. {open_edison-0.1.30 → open_edison-0.1.36}/.gitignore +0 -0
  20. {open_edison-0.1.30 → open_edison-0.1.36}/LICENSE +0 -0
  21. {open_edison-0.1.30 → open_edison-0.1.36}/docs/README.md +0 -0
  22. {open_edison-0.1.30 → open_edison-0.1.36}/docs/architecture/single_user_design.md +0 -0
  23. {open_edison-0.1.30 → open_edison-0.1.36}/docs/core/configuration.md +0 -0
  24. {open_edison-0.1.30 → open_edison-0.1.36}/docs/core/project_structure.md +0 -0
  25. {open_edison-0.1.30 → open_edison-0.1.36}/docs/core/proxy_usage.md +0 -0
  26. {open_edison-0.1.30 → open_edison-0.1.36}/docs/deployment/docker.md +0 -0
  27. {open_edison-0.1.30 → open_edison-0.1.36}/docs/deployment/local.md +0 -0
  28. {open_edison-0.1.30 → open_edison-0.1.36}/docs/development/testing.md +0 -0
  29. {open_edison-0.1.30 → open_edison-0.1.36}/docs/quick-reference/api_reference.md +0 -0
  30. {open_edison-0.1.30 → open_edison-0.1.36}/docs/quick-reference/config_quick_start.md +0 -0
  31. {open_edison-0.1.30 → open_edison-0.1.36}/prompt_permissions.json +0 -0
  32. {open_edison-0.1.30 → open_edison-0.1.36}/resource_permissions.json +0 -0
  33. {open_edison-0.1.30 → open_edison-0.1.36}/src/__init__.py +0 -0
  34. {open_edison-0.1.30 → open_edison-0.1.36}/src/__main__.py +0 -0
  35. {open_edison-0.1.30 → open_edison-0.1.36}/src/frontend_dist/assets/index-o6_8mdM8.css +0 -0
  36. {open_edison-0.1.30 → open_edison-0.1.36}/src/frontend_dist/sw.js +0 -0
  37. {open_edison-0.1.30 → open_edison-0.1.36}/src/middleware/data_access_tracker.py +0 -0
  38. {open_edison-0.1.30 → open_edison-0.1.36}/src/middleware/session_tracking.py +0 -0
  39. {open_edison-0.1.30 → open_edison-0.1.36}/src/oauth_manager.py +0 -0
  40. {open_edison-0.1.30 → open_edison-0.1.36}/src/permissions.py +0 -0
  41. {open_edison-0.1.30 → open_edison-0.1.36}/src/telemetry.py +0 -0
  42. {open_edison-0.1.30 → open_edison-0.1.36}/tool_permissions.json +0 -0
@@ -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
 
@@ -1,8 +1,8 @@
1
1
  # OpenEdison 🔒⚡️
2
2
 
3
- > The secure MCP proxy gateway
3
+ > The Secure MCP Control Panel
4
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.
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.
6
6
 
7
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.
8
8
 
@@ -59,26 +59,14 @@
59
59
  },
60
60
  {
61
61
  "name": "supabase",
62
- "command": "",
63
- "args": [],
64
- "env": {
65
- "SUPABASE_URL": "https://your-supabase-url.supabase.co",
66
- "SUPABASE_API_KEY": "your-supabase-api-key",
67
- "SUPABASE_DATABASE": "your-database-name"
68
- },
69
- "enabled": false
70
- },
71
- {
72
- "name": "google_drive",
73
62
  "command": "npx",
74
63
  "args": [
75
64
  "-y",
76
- "@modelcontextprotocol/server-gdrive"
65
+ "@supabase/mcp-server"
77
66
  ],
78
67
  "env": {
79
- "GOOGLE_CLIENT_ID": "your-client-id",
80
- "GOOGLE_CLIENT_SECRET": "your-client-secret",
81
- "GOOGLE_REFRESH_TOKEN": "your-refresh-token"
68
+ "SUPABASE_URL": "https://YOUR_PROJECT.supabase.co",
69
+ "SUPABASE_ANON_KEY": "your-supabase-anon-key"
82
70
  },
83
71
  "enabled": false
84
72
  },
@@ -93,17 +81,32 @@
93
81
  "env": {},
94
82
  "enabled": false
95
83
  },
84
+ {
85
+ "name": "google_drive",
86
+ "command": "npx",
87
+ "args": [
88
+ "-y",
89
+ "@modelcontextprotocol/server-gdrive"
90
+ ],
91
+ "env": {
92
+ "GOOGLE_CLIENT_ID": "your-client-id",
93
+ "GOOGLE_CLIENT_SECRET": "your-client-secret",
94
+ "GOOGLE_REFRESH_TOKEN": "your-refresh-token"
95
+ },
96
+ "enabled": false
97
+ },
96
98
  {
97
99
  "name": "zapier",
98
100
  "command": "npx",
99
101
  "args": [
100
102
  "-y",
101
103
  "mcp-remote",
102
- "https://mcp.zapier.com/api/mcp/s/{access_token}/mcp"
104
+ "https://mcp.zapier.com/api/mcp/s/REPLACE_WITH_ACCESS_TOKEN/mcp",
105
+ "--header",
106
+ "Accept: application/json, text/event-stream"
103
107
  ],
104
108
  "env": {},
105
- "enabled": false,
106
- "roots": []
109
+ "enabled": false
107
110
  }
108
111
  ]
109
112
  }
@@ -45,23 +45,21 @@ Access to all tools from MCP servers you've configured in Open Edison, such as:
45
45
  3. **Go to Settings** → Extensions
46
46
  4. **Click "Install Extension"** or drag the `.dxt` file into the window
47
47
  5. **Configure your connection**:
48
- - **Server URL**: Your Open Edison server MCP API endpoint (e.g., `http://localhost:3001/mcp/call`)
49
- - **API Key**: Your Open Edison API key
48
+ - **Server URL**: Your Open Edison server MCP endpoint (e.g., `http://localhost:3000/mcp/`)
49
+ - **API Key**: Your Open Edison API key (default for local dev: `dev-api-key-change-me`)
50
50
  6. **Click "Install"** when prompted
51
51
  7. **Done!** All your Open Edison tools are now available in Claude Desktop
52
52
 
53
- ### Server URL Examples
53
+ ### Server URL
54
54
 
55
- - **Local development**: `http://localhost:3001/mcp/call`
56
- - **Remote server**: `https://your-server.com:3001/mcp/call`
57
- - **Docker deployment**: `http://your-docker-host:3001/mcp/call`
55
+ - `http://localhost:3000/mcp/`
58
56
 
59
57
  ## Configuration
60
58
 
61
59
  ### Required Settings
62
60
 
63
- - **Server URL**: The full URL to your Open Edison MCP API endpoint (port 3001 by default)
64
- - **API Key**: Your authentication key for the Open Edison server (configured in `config.json`)
61
+ - **Server URL**: The full URL to your Open Edison MCP endpoint (`http://localhost:3000/mcp/`)
62
+ - **API Key**: Your authentication key for the Open Edison server (configured in `config.json`; local default `dev-api-key-change-me`)
65
63
 
66
64
  ### Open Edison Server Setup
67
65
 
@@ -71,7 +69,7 @@ Make sure your Open Edison server is running and accessible:
71
69
  2. **Configure MCP servers**: Edit `config.json` to add your MCP servers
72
70
  3. **Set API key**: Ensure `server.api_key` is set in your `config.json`
73
71
 
74
- Your server should be accessible at `http://localhost:3001` (or your configured host/port).
72
+ Your server should be accessible at `http://localhost:3000`.
75
73
 
76
74
  ## How It Works
77
75
 
@@ -129,10 +127,10 @@ This extension operates securely:
129
127
 
130
128
  ### Debug Steps
131
129
 
132
- 1. **Check server accessibility**: Try accessing `http://localhost:3001/health` in a browser
130
+ 1. **Check server accessibility**: Try accessing `http://localhost:3001/health` (management API) in a browser
133
131
  2. **Verify API key**: Check the `server.api_key` value in your Open Edison `config.json`
134
132
  3. **Check logs**: Look at Claude Desktop logs for connection errors
135
- 4. **Test MCP endpoint**: Use curl to test the `/mcp/call` endpoint
133
+ 4. **Test MCP endpoint**: Use curl to test the `/mcp/` endpoint
136
134
 
137
135
  ### Log Locations
138
136
 
@@ -111,7 +111,8 @@ make test
111
111
 
112
112
  **Standards**:
113
113
 
114
- - **Ruff** for formatting and linting
114
+ - **uv** for formatting (`make format`)
115
+ - **Ruff** for linting (`make lint`)
115
116
  - **Type hints** for all function signatures
116
117
  - **Docstrings** for public functions and classes
117
118
 
@@ -413,7 +413,7 @@ from src.proxy import MCPProxy
413
413
 
414
414
  ### Code Style
415
415
 
416
- - Use Ruff for formatting and linting (`make format` and `make lint`)
416
+ - Use uv for formatting (`make format`) and Ruff for linting (`make lint`)
417
417
  - Type hints for all function signatures
418
418
  - Docstrings for all public functions/classes
419
419
 
@@ -1,5 +1,4 @@
1
- from __future__ import annotations
2
-
1
+ import os
3
2
  import shutil
4
3
  from pathlib import Path
5
4
 
@@ -22,6 +21,14 @@ class BuildHook(BuildHookInterface): # type: ignore
22
21
  src_frontend_dist = project_root / "src" / "frontend_dist"
23
22
  repo_frontend_dist = project_root / "frontend" / "dist"
24
23
 
24
+ # Opt-in enforcement: only enforce when OPEN_EDISON_REQUIRE_FRONTEND=1/true/yes
25
+ enforce = os.environ.get("OPEN_EDISON_REQUIRE_FRONTEND", "").lower() in {"1", "true", "yes"}
26
+ if not enforce:
27
+ self.app.display_info(
28
+ "Skipping frontend_dist enforcement (set OPEN_EDISON_REQUIRE_FRONTEND=1 to enforce)"
29
+ )
30
+ return
31
+
25
32
  # Fast path: already present in src/
26
33
  if (src_frontend_dist / "index.html").exists():
27
34
  self.app.display_info("frontend_dist already present; skipping build/copy")
@@ -0,0 +1,18 @@
1
+ # Installation Test (Ubuntu)
2
+
3
+ This directory contains a Dockerfile that validates the README curl | bash installer on a clean Ubuntu environment.
4
+
5
+ How it works:
6
+
7
+ - Uses `ubuntu:24.04`
8
+ - Installs minimal prerequisites (`curl`, `ca-certificates`, etc.)
9
+ - Runs the documented installer: `curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_pipe_bash.sh | bash -s -- -h`
10
+ - The `-h` flag is forwarded to `open-edison`, so the installer prints help and exits 0, making the Docker build fast and non-interactive.
11
+
12
+ Build locally:
13
+
14
+ ```bash
15
+ make install_curl_test
16
+ ```
17
+
18
+ If the build completes successfully, the installer path is healthy. If it fails, the build will error and show logs with the failing step (e.g., uv install, Python 3.12, or `uvx open-edison`).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "open-edison"
3
- version = "0.1.30"
3
+ version = "0.1.36"
4
4
  description = "Open-source MCP security, aggregation, and monitoring. Single-user, self-hosted MCP proxy."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -42,14 +42,11 @@ dev-dependencies = [
42
42
  "pytest-asyncio>=1.0.0",
43
43
  "vulture>=2.11",
44
44
  "twine>=5.1.1",
45
+ "ty>=0.0.1a19",
45
46
  ]
46
47
 
47
- [project.optional-dependencies]
48
- dev = [
49
- "ruff>=0.12.3",
50
- "pytest>=8.3.3",
51
- "pytest-asyncio>=1.0.0",
52
- ]
48
+
49
+
53
50
 
54
51
  [tool.hatch.metadata]
55
52
  allow-direct-references = true
@@ -67,7 +64,7 @@ include = [
67
64
  "src/frontend_dist/**",
68
65
  ]
69
66
 
70
- [tool.hatch.build.hooks.custom]
67
+ [tool.hatch.build.targets.wheel.hooks.custom]
71
68
  path = "hatch_build.py"
72
69
 
73
70
 
@@ -85,6 +82,9 @@ include = [
85
82
  ]
86
83
  force-include = { "src/frontend_dist" = "src/frontend_dist" }
87
84
 
85
+ [tool.hatch.build.targets.sdist.hooks.custom]
86
+ path = "hatch_build.py"
87
+
88
88
  [tool.ruff]
89
89
  line-length = 100
90
90
  target-version = "py312"
@@ -116,5 +116,12 @@ reportUnusedFunction = false # Disable unused function warnings since we have m
116
116
  venvPath = ".venv"
117
117
  extraPaths = ["src"]
118
118
 
119
+ [tool.ty.rules]
120
+ possibly-unresolved-reference = "error"
121
+ index-out-of-bounds = "error"
122
+
123
+ [tool.ty.src]
124
+ exclude = ["tests/**"]
125
+
119
126
  [tool.vulture]
120
127
  exclude = ["tests", "src/frontend_dist"]
@@ -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
 
@@ -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
 
@@ -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: ...
@@ -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