open-edison 0.1.37__py3-none-any.whl → 0.1.39__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.
Files changed (36) hide show
  1. src/oauth_manager.py → oauth_manager.py +1 -1
  2. {open_edison-0.1.37.dist-info → open_edison-0.1.39.dist-info}/METADATA +1 -2
  3. open_edison-0.1.39.dist-info/RECORD +22 -0
  4. open_edison-0.1.37.dist-info/RECORD +0 -35
  5. src/mcp_importer/__init__.py +0 -15
  6. src/mcp_importer/__main__.py +0 -19
  7. src/mcp_importer/api.py +0 -106
  8. src/mcp_importer/cli.py +0 -113
  9. src/mcp_importer/export_cli.py +0 -201
  10. src/mcp_importer/exporters.py +0 -393
  11. src/mcp_importer/import_api.py +0 -3
  12. src/mcp_importer/importers.py +0 -63
  13. src/mcp_importer/merge.py +0 -47
  14. src/mcp_importer/parsers.py +0 -148
  15. src/mcp_importer/paths.py +0 -92
  16. src/mcp_importer/quick_cli.py +0 -65
  17. src/mcp_importer/types.py +0 -5
  18. /src/__init__.py → /__init__.py +0 -0
  19. /src/__main__.py → /__main__.py +0 -0
  20. /src/cli.py → /cli.py +0 -0
  21. /src/config.py → /config.py +0 -0
  22. /src/config.pyi → /config.pyi +0 -0
  23. /src/events.py → /events.py +0 -0
  24. {src/frontend_dist → frontend_dist}/assets/index-BUUcUfTt.js +0 -0
  25. {src/frontend_dist → frontend_dist}/assets/index-o6_8mdM8.css +0 -0
  26. {src/frontend_dist → frontend_dist}/index.html +0 -0
  27. {src/frontend_dist → frontend_dist}/sw.js +0 -0
  28. {src/middleware → middleware}/data_access_tracker.py +0 -0
  29. {src/middleware → middleware}/session_tracking.py +0 -0
  30. {open_edison-0.1.37.dist-info → open_edison-0.1.39.dist-info}/WHEEL +0 -0
  31. {open_edison-0.1.37.dist-info → open_edison-0.1.39.dist-info}/entry_points.txt +0 -0
  32. {open_edison-0.1.37.dist-info → open_edison-0.1.39.dist-info}/licenses/LICENSE +0 -0
  33. /src/permissions.py → /permissions.py +0 -0
  34. /src/server.py → /server.py +0 -0
  35. /src/single_user_mcp.py → /single_user_mcp.py +0 -0
  36. /src/telemetry.py → /telemetry.py +0 -0
@@ -7,6 +7,7 @@ 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
10
11
  from enum import Enum
11
12
  from pathlib import Path
12
13
 
@@ -137,7 +138,6 @@ class OAuthManager:
137
138
  expires_in = getattr(existing_tokens, "expires_in", None)
138
139
  if expires_in:
139
140
  # 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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-edison
3
- Version: 0.1.37
3
+ Version: 0.1.39
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
@@ -140,7 +140,6 @@ OPEN_EDISON_CONFIG_DIR=~/edison-config open-edison run
140
140
  ```bash
141
141
  # From source (no install)
142
142
  uv run python -m mcp_importer.quick_cli --yes
143
- # After install: mcp-importer-quick --yes
144
143
  ```
145
144
 
146
145
  - Preview what will be imported (no writes):
@@ -0,0 +1,22 @@
1
+ __init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
2
+ __main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
+ cli.py,sha256=P38IWER41S5oAfbd_7p89hBpnjClsNHpmE5pSsJc6uU,9733
4
+ config.py,sha256=RSsAYzl8cj6eaDN1RORMcfKKWBcp4bKTQp2BdhAL9mg,10258
5
+ config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
6
+ events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
7
+ oauth_manager.py,sha256=W9QSo0vfGDQ_i-QWCngkv7YLSL3Rk5jfPmqjU1J2rnU,9911
8
+ permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
9
+ server.py,sha256=cnO5bgxT-lrfuwk9AIvB_HBV8SWOtFClfGUn5_zFWyo,45652
10
+ single_user_mcp.py,sha256=rJrlqHcIubGkos_24ux5rb3OoKYDzvagCHghhfDeXTI,18535
11
+ telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
12
+ frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
13
+ frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
14
+ frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
15
+ frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
16
+ middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
17
+ middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
18
+ open_edison-0.1.39.dist-info/METADATA,sha256=xCXSMsOP0Tn_T1x3QzU4YVgOodONGsr4U1JyCE7F6FI,13152
19
+ open_edison-0.1.39.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ open_edison-0.1.39.dist-info/entry_points.txt,sha256=qUjYGPEfqSQyra9dTe1aRvHVAAzLzoNvZqNDk1t75IA,163
21
+ open_edison-0.1.39.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
22
+ open_edison-0.1.39.dist-info/RECORD,,
@@ -1,35 +0,0 @@
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,,
@@ -1,15 +0,0 @@
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
- ]
@@ -1,19 +0,0 @@
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 DELETED
@@ -1,106 +0,0 @@
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 DELETED
@@ -1,113 +0,0 @@
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())
@@ -1,201 +0,0 @@
1
- import argparse
2
- from pathlib import Path
3
-
4
- from loguru import logger as log
5
-
6
- from .exporters import ExportError, export_to_claude_code, export_to_cursor, export_to_vscode
7
- from .paths import (
8
- detect_cursor_config_path,
9
- detect_vscode_config_path,
10
- get_default_cursor_config_path,
11
- get_default_vscode_config_path,
12
- )
13
-
14
-
15
- def _prompt_yes_no(message: str, *, default_no: bool = True) -> bool:
16
- suffix = "[y/N]" if default_no else "[Y/n]"
17
- while True:
18
- resp = input(f"{message} {suffix} ").strip().lower()
19
- if resp == "y" or resp == "yes":
20
- return True
21
- if resp == "n" or resp == "no":
22
- return False
23
- if resp == "" and default_no:
24
- return False
25
- if resp == "" and not default_no:
26
- return True
27
-
28
-
29
- def build_arg_parser() -> argparse.ArgumentParser:
30
- p = argparse.ArgumentParser(
31
- description="Export editor MCP config to use Open Edison (Cursor support)",
32
- )
33
- p.add_argument("--target", choices=["cursor", "vscode", "claude-code"], default="cursor")
34
- p.add_argument("--dry-run", action="store_true", help="Show actions without writing")
35
- p.add_argument("--force", action="store_true", help="Rewrite even if already configured")
36
- p.add_argument(
37
- "--yes",
38
- action="store_true",
39
- help="Automatic yes to prompts (create missing files without confirmation)",
40
- )
41
- p.add_argument("--url", default="http://localhost:3000/mcp/", help="MCP URL")
42
- p.add_argument(
43
- "--api-key",
44
- default="dev-api-key-change-me",
45
- help="API key for Authorization header",
46
- )
47
- p.add_argument("--name", default="open-edison", help="Name of the server entry")
48
- return p
49
-
50
-
51
- def _handle_cursor(args: argparse.Namespace) -> int:
52
- detected = detect_cursor_config_path()
53
- target_path: Path = detected if detected else get_default_cursor_config_path()
54
-
55
- create_if_missing = False
56
- if not target_path.exists():
57
- if args.yes:
58
- create_if_missing = True
59
- else:
60
- confirmed = _prompt_yes_no(
61
- f"Cursor config not found at {target_path}. Create it?", default_no=False
62
- )
63
- if not confirmed:
64
- log.info("Aborted: user declined to create missing file")
65
- return 0
66
- create_if_missing = True
67
-
68
- try:
69
- result = export_to_cursor(
70
- url=args.url,
71
- api_key=args.api_key,
72
- server_name=args.name,
73
- dry_run=args.dry_run,
74
- force=args.force,
75
- create_if_missing=create_if_missing,
76
- )
77
- except ExportError as e:
78
- log.error(str(e))
79
- return 1
80
-
81
- if result.dry_run:
82
- log.info("Dry-run complete. No changes written.")
83
- return 0
84
-
85
- if result.wrote_changes:
86
- if result.backup_path is not None:
87
- log.info("Backup created at {}", result.backup_path)
88
- log.info("Updated {}", result.target_path)
89
- else:
90
- log.info("No changes were necessary.")
91
- return 0
92
-
93
-
94
- def _handle_vscode(args: argparse.Namespace) -> int:
95
- detected = detect_vscode_config_path()
96
- target_path: Path = detected if detected else get_default_vscode_config_path()
97
-
98
- create_if_missing = False
99
- if not target_path.exists():
100
- if args.yes:
101
- create_if_missing = True
102
- else:
103
- confirmed = _prompt_yes_no(
104
- f"VS Code MCP config not found at {target_path}. Create it?", default_no=False
105
- )
106
- if not confirmed:
107
- log.info("Aborted: user declined to create missing file")
108
- return 0
109
- create_if_missing = True
110
-
111
- try:
112
- result = export_to_vscode(
113
- url=args.url,
114
- api_key=args.api_key,
115
- server_name=args.name,
116
- dry_run=args.dry_run,
117
- force=args.force,
118
- create_if_missing=create_if_missing,
119
- )
120
- except ExportError as e:
121
- log.error(str(e))
122
- return 1
123
-
124
- if result.dry_run:
125
- log.info("Dry-run complete. No changes written.")
126
- return 0
127
-
128
- if result.wrote_changes:
129
- if result.backup_path is not None:
130
- log.info("Backup created at {}", result.backup_path)
131
- log.info("Updated {}", result.target_path)
132
- else:
133
- log.info("No changes were necessary.")
134
- return 0
135
-
136
-
137
- def _handle_claude_code(args: argparse.Namespace) -> int:
138
- from .paths import detect_claude_code_config_path, get_default_claude_code_config_path
139
-
140
- detected = detect_claude_code_config_path()
141
- target_path: Path = detected if detected else get_default_claude_code_config_path()
142
-
143
- create_if_missing = False
144
- if not target_path.exists():
145
- if args.yes:
146
- create_if_missing = True
147
- else:
148
- confirmed = _prompt_yes_no(
149
- f"Claude Code config not found at {target_path}. Create it?", default_no=False
150
- )
151
- if not confirmed:
152
- log.info("Aborted: user declined to create missing file")
153
- return 0
154
- create_if_missing = True
155
-
156
- try:
157
- result = export_to_claude_code(
158
- url=args.url,
159
- api_key=args.api_key,
160
- server_name=args.name,
161
- dry_run=args.dry_run,
162
- force=args.force,
163
- create_if_missing=create_if_missing,
164
- )
165
- except ExportError as e:
166
- log.error(str(e))
167
- return 1
168
-
169
- if result.dry_run:
170
- log.info("Dry-run complete. No changes written.")
171
- return 0
172
-
173
- if result.wrote_changes:
174
- if result.backup_path is not None:
175
- log.info("Backup created at {}", result.backup_path)
176
- log.info("Updated {}", result.target_path)
177
- else:
178
- log.info("No changes were necessary.")
179
- return 0
180
-
181
-
182
- def run_cli(argv: list[str] | None = None) -> int:
183
- parser = build_arg_parser()
184
- args = parser.parse_args(argv)
185
-
186
- if args.target == "cursor":
187
- return _handle_cursor(args)
188
- if args.target == "vscode":
189
- return _handle_vscode(args)
190
- if args.target == "claude-code":
191
- return _handle_claude_code(args)
192
- log.error("Unsupported target: {}", args.target)
193
- return 2
194
-
195
-
196
- def main() -> int:
197
- return run_cli()
198
-
199
-
200
- if __name__ == "__main__":
201
- raise SystemExit(main())