open-edison 0.1.34__py3-none-any.whl → 0.1.37__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.34.dist-info → open_edison-0.1.37.dist-info}/METADATA +48 -6
- open_edison-0.1.37.dist-info/RECORD +35 -0
- open_edison-0.1.37.dist-info/entry_points.txt +5 -0
- src/__init__.py +4 -7
- src/cli.py +0 -14
- src/config.py +6 -1
- src/config.pyi +80 -0
- src/events.py +0 -2
- src/mcp_importer/__init__.py +15 -0
- src/mcp_importer/__main__.py +19 -0
- src/mcp_importer/api.py +106 -0
- src/mcp_importer/cli.py +113 -0
- src/mcp_importer/export_cli.py +201 -0
- src/mcp_importer/exporters.py +393 -0
- src/mcp_importer/import_api.py +3 -0
- src/mcp_importer/importers.py +63 -0
- src/mcp_importer/merge.py +47 -0
- src/mcp_importer/parsers.py +148 -0
- src/mcp_importer/paths.py +92 -0
- src/mcp_importer/quick_cli.py +65 -0
- src/mcp_importer/types.py +5 -0
- src/server.py +2 -2
- open_edison-0.1.34.dist-info/RECORD +0 -21
- open_edison-0.1.34.dist-info/entry_points.txt +0 -3
- {open_edison-0.1.34.dist-info → open_edison-0.1.37.dist-info}/WHEEL +0 -0
- {open_edison-0.1.34.dist-info → open_edison-0.1.37.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.37
|
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
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.
|
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
|
|
@@ -76,6 +72,13 @@ curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_
|
|
76
72
|
|
77
73
|
Run locally with uvx: `uvx open-edison`
|
78
74
|
|
75
|
+
Optionally, import your existing MCP configs from Cursor, VS Code, or Claude Code with:
|
76
|
+
|
77
|
+
```bash
|
78
|
+
# From source (no install) — quick one-liner (add --dry-run to preview)
|
79
|
+
uv run python -m mcp_importer.quick_cli --yes
|
80
|
+
```
|
81
|
+
|
79
82
|
<details>
|
80
83
|
<summary>⬇️ Install Node.js/npm (optional for MCP tools)</summary>
|
81
84
|
|
@@ -125,6 +128,45 @@ OPEN_EDISON_CONFIG_DIR=~/edison-config open-edison run
|
|
125
128
|
|
126
129
|
</details>
|
127
130
|
|
131
|
+
<details>
|
132
|
+
<summary>🔄 Import from Cursor/VS Code/Claude Code</summary>
|
133
|
+
|
134
|
+
### Import from Cursor/VS Code/Claude Code
|
135
|
+
|
136
|
+
- **CLI**
|
137
|
+
|
138
|
+
- Import & configure to use edison as your MCP server:
|
139
|
+
|
140
|
+
```bash
|
141
|
+
# From source (no install)
|
142
|
+
uv run python -m mcp_importer.quick_cli --yes
|
143
|
+
# After install: mcp-importer-quick --yes
|
144
|
+
```
|
145
|
+
|
146
|
+
- Preview what will be imported (no writes):
|
147
|
+
|
148
|
+
```bash
|
149
|
+
uv run python -m mcp_importer --source cursor --dry-run
|
150
|
+
```
|
151
|
+
|
152
|
+
- Import servers into Open Edison `config.json` (merge policy defaults to `skip`):
|
153
|
+
|
154
|
+
```bash
|
155
|
+
uv run python -m mcp_importer --source cursor
|
156
|
+
uv run python -m mcp_importer --source vscode
|
157
|
+
uv run python -m mcp_importer --source claude-code
|
158
|
+
```
|
159
|
+
|
160
|
+
- Point your editor to Open Edison (backup original config and replace with a single Open Edison server):
|
161
|
+
|
162
|
+
```bash
|
163
|
+
uv run python -m mcp_importer export --target cursor --yes
|
164
|
+
uv run python -m mcp_importer export --target vscode --yes
|
165
|
+
uv run python -m mcp_importer export --target claude-code --yes
|
166
|
+
```
|
167
|
+
|
168
|
+
</details>
|
169
|
+
|
128
170
|
<details>
|
129
171
|
<summary><img src="https://img.shields.io/badge/Docker-2CA5E0?style=for-the-badge&logo=docker&logoColor=white" alt="Docker"> Run with Docker</summary>
|
130
172
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
|
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/mcp_importer/__init__.py,sha256=Yqr4NVAbKRVIuDzOj-yXzyB8HWLl-I4KmP5pVIRxs1o,271
|
17
|
+
src/mcp_importer/__main__.py,sha256=_2LUxAFFGJ9ECg5OoUqUZMxE6QBzhYPrfpLfKvCQM7k,507
|
18
|
+
src/mcp_importer/api.py,sha256=8BwPCeve-rY6T9Xhn-9FptBR2K_v_UBxe7m6CaOUMnw,3354
|
19
|
+
src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
|
20
|
+
src/mcp_importer/export_cli.py,sha256=daEadB6nL8P4OpEGFx0GshuN1a091L7BhiitpV1bPqA,6294
|
21
|
+
src/mcp_importer/exporters.py,sha256=fSgl6seduoXFp7YnKH26UEaC1sFBnd4whSut7CJLBQs,11348
|
22
|
+
src/mcp_importer/import_api.py,sha256=xWaKoE3vibSWpA5roVL7qEMS73vcmAC0tcHP6CsZw6E,95
|
23
|
+
src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs,2191
|
24
|
+
src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
|
25
|
+
src/mcp_importer/parsers.py,sha256=JRE7y_Gg-QmlAARvZdrI9CmUyy-ODvDPbS695pb3Aw8,4856
|
26
|
+
src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
|
27
|
+
src/mcp_importer/quick_cli.py,sha256=MCkHr_ljyUPS0pzTwJf4bW-UpknQP8NPzIWMxjUu5Nc,1907
|
28
|
+
src/mcp_importer/types.py,sha256=h03TbAnJbap6OWWd0dT0QcFWNvSaiVFWH9V9PD6x4s0,138
|
29
|
+
src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
|
30
|
+
src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
|
31
|
+
open_edison-0.1.37.dist-info/METADATA,sha256=41uTPLASX7MIufzvxIz_MQ7zAVkTfmNrrmz3afWqAu4,13198
|
32
|
+
open_edison-0.1.37.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
33
|
+
open_edison-0.1.37.dist-info/entry_points.txt,sha256=qUjYGPEfqSQyra9dTe1aRvHVAAzLzoNvZqNDk1t75IA,163
|
34
|
+
open_edison-0.1.37.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
35
|
+
open_edison-0.1.37.dist-info/RECORD,,
|
src/__init__.py
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
"""
|
2
|
-
Open Edison Source Package
|
2
|
+
Open Edison Source Package.
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
This package exposes a CLI via `open-edison` / `open_edison` entrypoints.
|
4
|
+
Note: Avoid importing heavy submodules at package import time to keep
|
5
|
+
packaging/import of light utilities (e.g., mcp_importer) side‑effect free.
|
7
6
|
"""
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
__all__ = ["OpenEdisonProxy"]
|
8
|
+
__all__: list[str] = []
|
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
|
@@ -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,
|
@@ -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.
|
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"""
|
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
@@ -0,0 +1,15 @@
|
|
1
|
+
"""MCP importer package for Open Edison scripts.
|
2
|
+
|
3
|
+
Import submodules explicitly as needed, e.g. `from mcp_importer import cli`.
|
4
|
+
"""
|
5
|
+
|
6
|
+
# pyright: reportUnsupportedDunderAll=false
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"paths",
|
10
|
+
"parsers",
|
11
|
+
"importers",
|
12
|
+
"merge",
|
13
|
+
"cli",
|
14
|
+
"api",
|
15
|
+
]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from mcp_importer.cli import run_cli as import_run_cli
|
6
|
+
from mcp_importer.export_cli import run_cli as export_run_cli
|
7
|
+
|
8
|
+
|
9
|
+
def main() -> int:
|
10
|
+
# Usage:
|
11
|
+
# python -m mcp_importer -> import CLI
|
12
|
+
# python -m mcp_importer export ... -> export CLI
|
13
|
+
if len(sys.argv) > 1 and sys.argv[1] == "export":
|
14
|
+
return export_run_cli(sys.argv[2:])
|
15
|
+
return import_run_cli(sys.argv[1:])
|
16
|
+
|
17
|
+
|
18
|
+
if __name__ == "__main__":
|
19
|
+
raise SystemExit(main())
|
src/mcp_importer/api.py
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false
|
2
|
+
from enum import Enum
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import mcp_importer.paths as _paths
|
7
|
+
from mcp_importer.exporters import export_to_claude_code, export_to_cursor, export_to_vscode
|
8
|
+
from mcp_importer.importers import (
|
9
|
+
import_from_claude_code as _import_from_claude_code,
|
10
|
+
)
|
11
|
+
from mcp_importer.importers import (
|
12
|
+
import_from_cursor as _import_from_cursor,
|
13
|
+
)
|
14
|
+
from mcp_importer.importers import (
|
15
|
+
import_from_vscode as _import_from_vscode,
|
16
|
+
)
|
17
|
+
from mcp_importer.merge import MergePolicy, merge_servers
|
18
|
+
from src.config import Config, MCPServerConfig, get_config_json_path
|
19
|
+
|
20
|
+
|
21
|
+
class CLIENT(str, Enum):
|
22
|
+
CURSOR = "cursor"
|
23
|
+
VSCODE = "vscode"
|
24
|
+
CLAUDE_CODE = "claude-code"
|
25
|
+
|
26
|
+
|
27
|
+
def detect_clients() -> set[CLIENT]:
|
28
|
+
detected: set[CLIENT] = set()
|
29
|
+
if _paths.detect_cursor_config_path() is not None:
|
30
|
+
detected.add(CLIENT.CURSOR)
|
31
|
+
if _paths.detect_vscode_config_path() is not None:
|
32
|
+
detected.add(CLIENT.VSCODE)
|
33
|
+
if _paths.detect_claude_code_config_path() is not None:
|
34
|
+
detected.add(CLIENT.CLAUDE_CODE)
|
35
|
+
return detected
|
36
|
+
|
37
|
+
|
38
|
+
import_cursor = _import_from_cursor
|
39
|
+
import_vscode = _import_from_vscode
|
40
|
+
import_claude_code = _import_from_claude_code
|
41
|
+
|
42
|
+
|
43
|
+
def import_from(client: CLIENT) -> list[MCPServerConfig]:
|
44
|
+
if client == CLIENT.CURSOR:
|
45
|
+
return import_cursor()
|
46
|
+
if client == CLIENT.VSCODE:
|
47
|
+
return import_vscode()
|
48
|
+
if client == CLIENT.CLAUDE_CODE:
|
49
|
+
return import_claude_code()
|
50
|
+
raise ValueError(f"Unsupported client: {client}")
|
51
|
+
|
52
|
+
|
53
|
+
def save_imported_servers(
|
54
|
+
servers: list[MCPServerConfig],
|
55
|
+
*,
|
56
|
+
merge_policy: str = MergePolicy.SKIP,
|
57
|
+
config_dir: Path | None = None,
|
58
|
+
) -> Path:
|
59
|
+
target_path: Path = (
|
60
|
+
get_config_json_path() if config_dir is None else (Path(config_dir) / "config.json")
|
61
|
+
)
|
62
|
+
cfg: Config = Config(target_path)
|
63
|
+
merged = merge_servers(existing=cfg.mcp_servers, imported=servers, policy=merge_policy)
|
64
|
+
cfg.mcp_servers = merged
|
65
|
+
cfg.save(target_path)
|
66
|
+
return target_path
|
67
|
+
|
68
|
+
|
69
|
+
def export_edison_to(
|
70
|
+
client: CLIENT,
|
71
|
+
*,
|
72
|
+
url: str = "http://localhost:3000/mcp/",
|
73
|
+
api_key: str = "dev-api-key-change-me",
|
74
|
+
server_name: str = "open-edison",
|
75
|
+
dry_run: bool = False,
|
76
|
+
force: bool = False,
|
77
|
+
create_if_missing: bool = False,
|
78
|
+
) -> Any:
|
79
|
+
match client:
|
80
|
+
case CLIENT.CURSOR:
|
81
|
+
return export_to_cursor(
|
82
|
+
url=url,
|
83
|
+
api_key=api_key,
|
84
|
+
server_name=server_name,
|
85
|
+
dry_run=dry_run,
|
86
|
+
force=force,
|
87
|
+
create_if_missing=create_if_missing,
|
88
|
+
)
|
89
|
+
case CLIENT.VSCODE:
|
90
|
+
return export_to_vscode(
|
91
|
+
url=url,
|
92
|
+
api_key=api_key,
|
93
|
+
server_name=server_name,
|
94
|
+
dry_run=dry_run,
|
95
|
+
force=force,
|
96
|
+
create_if_missing=create_if_missing,
|
97
|
+
)
|
98
|
+
case CLIENT.CLAUDE_CODE:
|
99
|
+
return export_to_claude_code(
|
100
|
+
url=url,
|
101
|
+
api_key=api_key,
|
102
|
+
server_name=server_name,
|
103
|
+
dry_run=dry_run,
|
104
|
+
force=force,
|
105
|
+
create_if_missing=create_if_missing,
|
106
|
+
)
|
src/mcp_importer/cli.py
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownArgumentType=false, reportUnknownMemberType=false, reportUnknownParameterType=false
|
2
|
+
import argparse
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from loguru import logger as log
|
7
|
+
|
8
|
+
from src.config import Config, get_config_dir
|
9
|
+
|
10
|
+
from .importers import IMPORTERS
|
11
|
+
from .merge import MergePolicy, merge_servers
|
12
|
+
|
13
|
+
## Ensure import of src config (place src on sys.path before import)
|
14
|
+
# THIS_FILE = Path(__file__).resolve()
|
15
|
+
# REPO_ROOT = THIS_FILE.parents[2]
|
16
|
+
# SRC_DIR = REPO_ROOT / "src"
|
17
|
+
# if str(SRC_DIR) not in sys.path:
|
18
|
+
# sys.path.insert(0, str(SRC_DIR))
|
19
|
+
|
20
|
+
|
21
|
+
def build_arg_parser() -> argparse.ArgumentParser:
|
22
|
+
p = argparse.ArgumentParser(
|
23
|
+
description="Import MCP servers from other tools into Open Edison config.json"
|
24
|
+
)
|
25
|
+
p.add_argument(
|
26
|
+
"--source",
|
27
|
+
choices=["cursor", "vscode", "claude-code"],
|
28
|
+
required=True,
|
29
|
+
)
|
30
|
+
p.add_argument(
|
31
|
+
"--config-dir",
|
32
|
+
type=Path,
|
33
|
+
help="Directory containing target config.json (default: OPEN_EDISON_CONFIG_DIR or repo root)",
|
34
|
+
)
|
35
|
+
p.add_argument(
|
36
|
+
"--merge",
|
37
|
+
choices=[MergePolicy.SKIP, MergePolicy.OVERWRITE, MergePolicy.RENAME],
|
38
|
+
default=MergePolicy.SKIP,
|
39
|
+
)
|
40
|
+
p.add_argument(
|
41
|
+
"--dry-run", action="store_true", help="Show changes without writing to config.json"
|
42
|
+
)
|
43
|
+
return p
|
44
|
+
|
45
|
+
|
46
|
+
def run_cli(argv: list[str] | None = None) -> int: # noqa: C901
|
47
|
+
parser = build_arg_parser()
|
48
|
+
args = parser.parse_args(argv)
|
49
|
+
|
50
|
+
source: str = args.source
|
51
|
+
|
52
|
+
importer = IMPORTERS.get(source)
|
53
|
+
if not importer:
|
54
|
+
print(f"Unsupported source: {source}", file=sys.stderr)
|
55
|
+
return 2
|
56
|
+
|
57
|
+
# Resolve target config path
|
58
|
+
target_dir: Path = args.config_dir or get_config_dir()
|
59
|
+
target_path = target_dir / "config.json"
|
60
|
+
|
61
|
+
# Load existing config (auto-creates default if missing via Config.load)
|
62
|
+
config_obj: Config = Config(target_dir)
|
63
|
+
|
64
|
+
# Import
|
65
|
+
imported_servers = importer()
|
66
|
+
|
67
|
+
if not imported_servers:
|
68
|
+
log.warning("No servers found to import from source '{}'", source)
|
69
|
+
return 0
|
70
|
+
|
71
|
+
# Merge
|
72
|
+
merged = merge_servers(
|
73
|
+
existing=config_obj.mcp_servers,
|
74
|
+
imported=imported_servers,
|
75
|
+
policy=args.merge,
|
76
|
+
)
|
77
|
+
|
78
|
+
existing_names: set[str] = {str(getattr(s, "name", "")) for s in config_obj.mcp_servers}
|
79
|
+
merged_names: set[str] = {str(getattr(s, "name", "")) for s in merged}
|
80
|
+
added = merged_names - existing_names
|
81
|
+
replaced: set[str] = set()
|
82
|
+
if args.merge == MergePolicy.OVERWRITE:
|
83
|
+
replaced = existing_names & {s.name for s in imported_servers}
|
84
|
+
|
85
|
+
log.info("Imported {} server(s) from '{}'", len(imported_servers), source)
|
86
|
+
try:
|
87
|
+
names_preview = ", ".join(sorted(getattr(s, "name", "") for s in imported_servers))
|
88
|
+
if names_preview:
|
89
|
+
log.info("Detected servers: {}", names_preview)
|
90
|
+
except Exception:
|
91
|
+
pass
|
92
|
+
if added:
|
93
|
+
log.info("Added: {}", ", ".join(sorted(added)))
|
94
|
+
if replaced:
|
95
|
+
log.info("Overwrote: {}", ", ".join(sorted(replaced)))
|
96
|
+
|
97
|
+
if args.dry_run:
|
98
|
+
log.info("Dry-run enabled; not writing changes to {}", target_path)
|
99
|
+
log.debug("Merged servers: {}", merged)
|
100
|
+
return 0
|
101
|
+
|
102
|
+
config_obj.mcp_servers = merged
|
103
|
+
config_obj.save(target_path)
|
104
|
+
log.info("Configuration updated: {}", target_path)
|
105
|
+
return 0
|
106
|
+
|
107
|
+
|
108
|
+
def main() -> int:
|
109
|
+
return run_cli()
|
110
|
+
|
111
|
+
|
112
|
+
if __name__ == "__main__":
|
113
|
+
raise SystemExit(main())
|