open-edison 0.1.41__py3-none-any.whl → 0.1.42__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.
frontend_dist/index.html CHANGED
@@ -1,21 +1,11 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
1
+ <!DOCTYPE html>
2
+ <html>
4
3
  <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Open Edison Sessions</title>
8
- <script>
9
- // Prevents FOUC by setting theme asap
10
- const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
11
- document.documentElement.setAttribute('data-theme', prefersLight ? 'light' : 'dark');
12
- </script>
13
- <script type="module" crossorigin src="/assets/index-BUUcUfTt.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-o6_8mdM8.css">
4
+ <title>Open Edison Dashboard</title>
5
+ <meta charset="utf-8">
15
6
  </head>
16
-
17
7
  <body>
18
- <div id="root"></div>
8
+ <h1>Open Edison Dashboard</h1>
9
+ <p>Frontend assets not available. Run 'make build_package' to build the full dashboard.</p>
19
10
  </body>
20
-
21
11
  </html>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-edison
3
- Version: 0.1.41
3
+ Version: 0.1.42
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
@@ -76,7 +76,7 @@ Optionally, import your existing MCP configs from Cursor, VS Code, or Claude Cod
76
76
 
77
77
  ```bash
78
78
  # From source (no install) — quick one-liner (add --dry-run to preview)
79
- uv run python -m src.mcp_importer.quick_cli --yes
79
+ uv run python -m mcp_importer.quick_cli --yes
80
80
  ```
81
81
 
82
82
  <details>
@@ -139,29 +139,29 @@ OPEN_EDISON_CONFIG_DIR=~/edison-config open-edison run
139
139
 
140
140
  ```bash
141
141
  # From source (no install)
142
- uv run python -m src.mcp_importer.quick_cli --yes
142
+ uv run python -m mcp_importer.quick_cli --yes
143
143
  ```
144
144
 
145
145
  - Preview what will be imported (no writes):
146
146
 
147
147
  ```bash
148
- uv run python -m src.mcp_importer --source cursor --dry-run
148
+ uv run python -m mcp_importer --source cursor --dry-run
149
149
  ```
150
150
 
151
151
  - Import servers into Open Edison `config.json` (merge policy defaults to `skip`):
152
152
 
153
153
  ```bash
154
- uv run python -m src.mcp_importer --source cursor
155
- uv run python -m src.mcp_importer --source vscode
156
- uv run python -m src.mcp_importer --source claude-code
154
+ uv run python -m mcp_importer --source cursor
155
+ uv run python -m mcp_importer --source vscode
156
+ uv run python -m mcp_importer --source claude-code
157
157
  ```
158
158
 
159
159
  - Point your editor to Open Edison (backup original config and replace with a single Open Edison server):
160
160
 
161
161
  ```bash
162
- uv run python -m src.mcp_importer export --target cursor --yes
163
- uv run python -m src.mcp_importer export --target vscode --yes
164
- uv run python -m src.mcp_importer export --target claude-code --yes
162
+ uv run python -m mcp_importer export --target cursor --yes
163
+ uv run python -m mcp_importer export --target vscode --yes
164
+ uv run python -m mcp_importer export --target claude-code --yes
165
165
  ```
166
166
 
167
167
  </details>
@@ -1,21 +1,18 @@
1
1
  src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
2
2
  src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
- src/cli.py,sha256=SE2N3AfgpHSmm5SAs8UkXr9zErT2zvMttMh_gi5wRv8,9562
3
+ src/cli.py,sha256=P38IWER41S5oAfbd_7p89hBpnjClsNHpmE5pSsJc6uU,9733
4
4
  src/config.py,sha256=RSsAYzl8cj6eaDN1RORMcfKKWBcp4bKTQp2BdhAL9mg,10258
5
5
  src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
6
6
  src/events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
7
- src/oauth_manager.py,sha256=W9QSo0vfGDQ_i-QWCngkv7YLSL3Rk5jfPmqjU1J2rnU,9911
7
+ src/oauth_manager.py,sha256=qcQa5BDRZr4bjqiXNflCnrXOh9mo9JVjvP2Caseg2Uc,9943
8
8
  src/permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
9
9
  src/server.py,sha256=cnO5bgxT-lrfuwk9AIvB_HBV8SWOtFClfGUn5_zFWyo,45652
10
10
  src/single_user_mcp.py,sha256=rJrlqHcIubGkos_24ux5rb3OoKYDzvagCHghhfDeXTI,18535
11
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
12
+ src/frontend_dist/index.html,sha256=rNNHhYAvyDujYAnwut4zIQfkSKEwES491uH4fEOjkIw,259
16
13
  src/mcp_importer/__init__.py,sha256=Mk59pVr7OMGfYGWeSYk8-URfhIcrs3SPLYS7fmJbMII,275
17
14
  src/mcp_importer/__main__.py,sha256=0jVfxKzyr6koVu1ghhWseah5ilKIoGovE6zkEZ-u-Og,515
18
- src/mcp_importer/api.py,sha256=t3Uet-8Dz3bCfj25muzkXDzenH_ueuE97Cs09UOGk0o,6226
15
+ src/mcp_importer/api.py,sha256=kYhmVHBUekTXPv3xNdt8rJNH1mbWXvs2cTZZGOkVhAk,3383
19
16
  src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
20
17
  src/mcp_importer/export_cli.py,sha256=daEadB6nL8P4OpEGFx0GshuN1a091L7BhiitpV1bPqA,6294
21
18
  src/mcp_importer/exporters.py,sha256=fSgl6seduoXFp7YnKH26UEaC1sFBnd4whSut7CJLBQs,11348
@@ -24,16 +21,13 @@ src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs
24
21
  src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
25
22
  src/mcp_importer/parsers.py,sha256=JRE7y_Gg-QmlAARvZdrI9CmUyy-ODvDPbS695pb3Aw8,4856
26
23
  src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
27
- src/mcp_importer/quick_cli.py,sha256=4mJe10q_lZCYLm75QBt1rYy2j8mGEsRZoAqA0agjfSM,1834
24
+ src/mcp_importer/quick_cli.py,sha256=24-R77-AuKsbeag0SyADba4Vf9oYHkaOBnRXK2sZt_4,1911
28
25
  src/mcp_importer/types.py,sha256=h03TbAnJbap6OWWd0dT0QcFWNvSaiVFWH9V9PD6x4s0,138
29
26
  src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
30
27
  src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
31
- frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
32
- frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
33
- frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
34
- frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
35
- open_edison-0.1.41.dist-info/METADATA,sha256=jsNn28vJxbSW62-N9LnJOxGmHeUvTqCwm1kbuv-m2OM,13188
36
- open_edison-0.1.41.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- open_edison-0.1.41.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
38
- open_edison-0.1.41.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
39
- open_edison-0.1.41.dist-info/RECORD,,
28
+ frontend_dist/index.html,sha256=rNNHhYAvyDujYAnwut4zIQfkSKEwES491uH4fEOjkIw,259
29
+ open_edison-0.1.42.dist-info/METADATA,sha256=ycCiWJGiiCWyiam8Du-w4YqfUbrIhBPL4mu8HYcd8l4,13152
30
+ open_edison-0.1.42.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ open_edison-0.1.42.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
32
+ open_edison-0.1.42.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
33
+ open_edison-0.1.42.dist-info/RECORD,,
src/cli.py CHANGED
@@ -45,7 +45,7 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
45
45
  # import-mcp: import MCP servers from other tools into config.json
46
46
  sp_import = subparsers.add_parser(
47
47
  "import-mcp",
48
- help="Import MCP servers from other tools (Cursor, VS Code, Claude Code)",
48
+ help="Import MCP servers from other tools (Cursor, Windsurf, Cline, Claude Desktop, etc.)",
49
49
  description=(
50
50
  "Import MCP server configurations from other tools into Open Edison config.json.\n"
51
51
  "Use --source to choose the tool and optional flags to control merging."
@@ -55,10 +55,16 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
55
55
  "--source",
56
56
  choices=[
57
57
  "cursor",
58
+ "windsurf",
59
+ "cline",
60
+ "claude-desktop",
58
61
  "vscode",
59
62
  "claude-code",
63
+ "gemini-cli",
64
+ "codex",
65
+ "interactive",
60
66
  ],
61
- default="cursor",
67
+ default="interactive",
62
68
  help="Source application to import from",
63
69
  )
64
70
  sp_import.add_argument(
@@ -1,21 +1,11 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
1
+ <!DOCTYPE html>
2
+ <html>
4
3
  <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Open Edison Sessions</title>
8
- <script>
9
- // Prevents FOUC by setting theme asap
10
- const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
11
- document.documentElement.setAttribute('data-theme', prefersLight ? 'light' : 'dark');
12
- </script>
13
- <script type="module" crossorigin src="/assets/index-BUUcUfTt.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-o6_8mdM8.css">
4
+ <title>Open Edison Dashboard</title>
5
+ <meta charset="utf-8">
15
6
  </head>
16
-
17
7
  <body>
18
- <div id="root"></div>
8
+ <h1>Open Edison Dashboard</h1>
9
+ <p>Frontend assets not available. Run 'make build_package' to build the full dashboard.</p>
19
10
  </body>
20
-
21
11
  </html>
src/mcp_importer/api.py CHANGED
@@ -1,35 +1,21 @@
1
1
  # pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false
2
- import asyncio
3
- from collections.abc import Awaitable
4
2
  from enum import Enum
5
3
  from pathlib import Path
6
- from typing import Any, Protocol, cast, runtime_checkable
7
-
8
- from fastmcp import FastMCP
4
+ from typing import Any
9
5
 
10
6
  from src.config import Config, MCPServerConfig, get_config_json_path
11
7
  from src.mcp_importer import paths as _paths
12
- from src.mcp_importer.exporters import (
13
- ExportResult,
14
- export_to_claude_code,
15
- export_to_cursor,
16
- export_to_vscode,
8
+ from src.mcp_importer.exporters import export_to_claude_code, export_to_cursor, export_to_vscode
9
+ from src.mcp_importer.importers import (
10
+ import_from_claude_code as _import_from_claude_code,
11
+ )
12
+ from src.mcp_importer.importers import (
13
+ import_from_cursor as _import_from_cursor,
17
14
  )
18
15
  from src.mcp_importer.importers import (
19
- import_from_claude_code,
20
- import_from_cursor,
21
- import_from_vscode,
16
+ import_from_vscode as _import_from_vscode,
22
17
  )
23
18
  from src.mcp_importer.merge import MergePolicy, merge_servers
24
- from src.oauth_manager import OAuthStatus, get_oauth_manager
25
-
26
-
27
- @runtime_checkable
28
- class _MCPClientLike(Protocol):
29
- async def list_tools(self) -> Any: ...
30
- async def list_resources(self) -> Any: ...
31
- async def list_prompts(self) -> Any: ...
32
- def shutdown(self) -> Awaitable[Any] | Any: ...
33
19
 
34
20
 
35
21
  class CLIENT(str, Enum):
@@ -37,12 +23,6 @@ class CLIENT(str, Enum):
37
23
  VSCODE = "vscode"
38
24
  CLAUDE_CODE = "claude-code"
39
25
 
40
- def __str__(self) -> str:
41
- return self.value.capitalize()
42
-
43
- def __repr__(self) -> str:
44
- return str(self)
45
-
46
26
 
47
27
  def detect_clients() -> set[CLIENT]:
48
28
  detected: set[CLIENT] = set()
@@ -55,31 +35,30 @@ def detect_clients() -> set[CLIENT]:
55
35
  return detected
56
36
 
57
37
 
38
+ import_cursor = _import_from_cursor
39
+ import_vscode = _import_from_vscode
40
+ import_claude_code = _import_from_claude_code
41
+
42
+
58
43
  def import_from(client: CLIENT) -> list[MCPServerConfig]:
59
44
  if client == CLIENT.CURSOR:
60
- return import_from_cursor()
45
+ return import_cursor()
61
46
  if client == CLIENT.VSCODE:
62
- return import_from_vscode()
47
+ return import_vscode()
63
48
  if client == CLIENT.CLAUDE_CODE:
64
- return import_from_claude_code()
49
+ return import_claude_code()
65
50
  raise ValueError(f"Unsupported client: {client}")
66
51
 
67
52
 
68
53
  def save_imported_servers(
69
54
  servers: list[MCPServerConfig],
70
55
  *,
71
- dry_run: bool = False,
72
56
  merge_policy: str = MergePolicy.SKIP,
73
57
  config_dir: Path | None = None,
74
- ) -> Path | None:
58
+ ) -> Path:
75
59
  target_path: Path = (
76
60
  get_config_json_path() if config_dir is None else (Path(config_dir) / "config.json")
77
61
  )
78
- if dry_run:
79
- print(
80
- f"[dry-run] Would import {len(servers)} server(s) and save to config.json (at {target_path})"
81
- )
82
- return None
83
62
  cfg: Config = Config(target_path)
84
63
  merged = merge_servers(existing=cfg.mcp_servers, imported=servers, policy=merge_policy)
85
64
  cfg.mcp_servers = merged
@@ -96,17 +75,7 @@ def export_edison_to(
96
75
  dry_run: bool = False,
97
76
  force: bool = False,
98
77
  create_if_missing: bool = False,
99
- ) -> ExportResult:
100
- if dry_run:
101
- print(
102
- f"[dry-run] Would export Open Edison to '{client}' (backup and replace editor MCP config)"
103
- )
104
- return ExportResult(
105
- target_path=Path(""),
106
- backup_path=None,
107
- wrote_changes=False,
108
- dry_run=True,
109
- )
78
+ ) -> Any:
110
79
  match client:
111
80
  case CLIENT.CURSOR:
112
81
  return export_to_cursor(
@@ -135,62 +104,3 @@ def export_edison_to(
135
104
  force=force,
136
105
  create_if_missing=create_if_missing,
137
106
  )
138
-
139
-
140
- def verify_mcp_server(server: MCPServerConfig) -> bool: # noqa
141
- """Minimal validation: try listing tools/resources/prompts via FastMCP within a timeout."""
142
-
143
- async def _verify_async() -> bool:
144
- if not server.command.strip():
145
- return False
146
-
147
- backend_cfg: dict[str, Any] = {
148
- "mcpServers": {
149
- server.name: {
150
- "command": server.command,
151
- "args": server.args,
152
- "env": server.env or {},
153
- **({"roots": server.roots} if server.roots else {}),
154
- }
155
- }
156
- }
157
-
158
- proxy: FastMCP[Any] | None = None
159
- try:
160
- proxy = FastMCP.as_proxy(backend=backend_cfg, name=f"open-edison-verify-{server.name}")
161
- s: _MCPClientLike = cast(_MCPClientLike, proxy)
162
- await asyncio.wait_for(
163
- asyncio.gather(
164
- s.list_tools(),
165
- s.list_resources(),
166
- s.list_prompts(),
167
- ),
168
- timeout=15.0,
169
- )
170
- return True
171
- except Exception:
172
- return False
173
- finally:
174
- try:
175
- if isinstance(proxy, FastMCP):
176
- result = proxy.shutdown() # type: ignore[attr-defined]
177
- if isinstance(result, Awaitable):
178
- await result # type: ignore[func-returns-value]
179
- except Exception:
180
- pass
181
-
182
- return asyncio.run(_verify_async())
183
-
184
-
185
- def server_needs_oauth(server: MCPServerConfig) -> bool: # noqa
186
- """Return True if the remote server currently needs OAuth; False otherwise."""
187
-
188
- async def _needs_oauth_async() -> bool:
189
- if not server.is_remote_server():
190
- return False
191
- info = await get_oauth_manager().check_oauth_requirement(
192
- server.name, server.get_remote_url()
193
- )
194
- return info.status == OAuthStatus.NEEDS_AUTH
195
-
196
- return asyncio.run(_needs_oauth_async())
@@ -26,19 +26,10 @@ def run_cli(argv: list[str] | None = None) -> int:
26
26
  )
27
27
  parser.add_argument("--dry-run", action="store_true", help="Preview actions without writing")
28
28
  parser.add_argument("--yes", action="store_true", help="Skip confirmations (no effect here)")
29
- parser.add_argument(
30
- "--source-client", type=CLIENT, help="Client to import from", required=False, default=None
31
- )
32
29
  args = parser.parse_args(argv)
33
30
 
34
31
  detected = detect_clients()
35
- print(f"Detected clients: {detected}")
36
- if args.source_client:
37
- client = args.source_client
38
- assert client in detected, f"Client {client} not detected"
39
- else:
40
- client = _pick_first(detected)
41
- print(f"Going to import from client: {client}")
32
+ client = _pick_first(detected)
42
33
  if client is None:
43
34
  print("No supported clients detected.")
44
35
  return 2
@@ -48,8 +39,20 @@ def run_cli(argv: list[str] | None = None) -> int:
48
39
  print(f"No servers found to import from '{client.value}'.")
49
40
  return 0
50
41
 
51
- save_imported_servers(servers, dry_run=args.dry_run)
52
- export_edison_to(client, dry_run=args.dry_run, force=True, create_if_missing=True)
42
+ if args.dry_run:
43
+ print(
44
+ f"[dry-run] Would import {len(servers)} server(s) from '{client.value}' and save to config.json"
45
+ )
46
+ # Exercise export path safely (no writes)
47
+ export_edison_to(client, dry_run=True, force=True, create_if_missing=True)
48
+ print(
49
+ f"[dry-run] Would export Open Edison to '{client.value}' (backup and replace editor MCP config)"
50
+ )
51
+ print("Dry-run complete.")
52
+ return 0
53
+
54
+ save_imported_servers(servers)
55
+ export_edison_to(client, dry_run=False, force=True, create_if_missing=True)
53
56
  print(f"Completed quick import/export for {client.value}.")
54
57
  return 0
55
58
 
src/oauth_manager.py CHANGED
@@ -7,7 +7,6 @@ Provides detection, token management, and authentication flow coordination.
7
7
 
8
8
  import asyncio
9
9
  from dataclasses import dataclass
10
- from datetime import datetime, timedelta
11
10
  from enum import Enum
12
11
  from pathlib import Path
13
12
 
@@ -138,6 +137,7 @@ class OAuthManager:
138
137
  expires_in = getattr(existing_tokens, "expires_in", None)
139
138
  if expires_in:
140
139
  # If expires_in is available, we can calculate expiration
140
+ from datetime import datetime, timedelta
141
141
 
142
142
  expiry = datetime.now() + timedelta(seconds=expires_in)
143
143
  token_expires_at = expiry.isoformat()