open-edison 0.1.38__py3-none-any.whl → 0.1.40__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 (37) hide show
  1. {open_edison-0.1.38.dist-info → open_edison-0.1.40.dist-info}/METADATA +10 -10
  2. open_edison-0.1.40.dist-info/RECORD +39 -0
  3. open_edison-0.1.40.dist-info/entry_points.txt +5 -0
  4. src/frontend_dist/assets/index-BUUcUfTt.js +51 -0
  5. src/frontend_dist/assets/index-o6_8mdM8.css +1 -0
  6. src/frontend_dist/index.html +21 -0
  7. src/frontend_dist/sw.js +71 -0
  8. src/mcp_importer/__init__.py +15 -0
  9. src/mcp_importer/__main__.py +19 -0
  10. src/mcp_importer/api.py +196 -0
  11. src/mcp_importer/cli.py +113 -0
  12. src/mcp_importer/export_cli.py +201 -0
  13. src/mcp_importer/exporters.py +393 -0
  14. src/mcp_importer/import_api.py +3 -0
  15. src/mcp_importer/importers.py +63 -0
  16. src/mcp_importer/merge.py +47 -0
  17. src/mcp_importer/parsers.py +148 -0
  18. src/mcp_importer/paths.py +92 -0
  19. src/mcp_importer/quick_cli.py +62 -0
  20. src/mcp_importer/types.py +5 -0
  21. oauth_manager.py → src/oauth_manager.py +1 -1
  22. open_edison-0.1.38.dist-info/RECORD +0 -22
  23. open_edison-0.1.38.dist-info/entry_points.txt +0 -5
  24. {open_edison-0.1.38.dist-info → open_edison-0.1.40.dist-info}/WHEEL +0 -0
  25. {open_edison-0.1.38.dist-info → open_edison-0.1.40.dist-info}/licenses/LICENSE +0 -0
  26. /__init__.py → /src/__init__.py +0 -0
  27. /__main__.py → /src/__main__.py +0 -0
  28. /cli.py → /src/cli.py +0 -0
  29. /config.py → /src/config.py +0 -0
  30. /config.pyi → /src/config.pyi +0 -0
  31. /events.py → /src/events.py +0 -0
  32. {middleware → src/middleware}/data_access_tracker.py +0 -0
  33. {middleware → src/middleware}/session_tracking.py +0 -0
  34. /permissions.py → /src/permissions.py +0 -0
  35. /server.py → /src/server.py +0 -0
  36. /single_user_mcp.py → /src/single_user_mcp.py +0 -0
  37. /telemetry.py → /src/telemetry.py +0 -0
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.fixed{position:fixed}.relative{position:relative}.bottom-4{bottom:1rem}.right-4{right:1rem}.z-50{z-index:50}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[580px\]{height:580px}.w-10{width:2.5rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-\[260px\]{width:260px}.w-full{width:100%}.min-w-\[240px\]{min-width:240px}.max-w-\[1400px\]{max-width:1400px}.max-w-\[260px\]{max-width:260px}.max-w-sm{max-width:24rem}.border-collapse{border-collapse:collapse}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-amber-400\/30{border-color:#fbbf244d}.border-app-accent{border-color:var(--accent)}.border-app-border{border-color:var(--border)}.border-blue-400\/30{border-color:#60a5fa4d}.border-rose-400\/30{border-color:#fb71854d}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-app-accent{background-color:var(--accent)}.bg-app-bg{background-color:var(--bg)}.bg-app-border{background-color:var(--border)}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.\!px-2{padding-left:.5rem!important;padding-right:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.\!py-1\.5{padding-top:.375rem!important;padding-bottom:.375rem!important}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-bottom{vertical-align:bottom}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-app-accent{color:var(--accent)}.text-app-muted{color:var(--muted)}.text-app-text{color:var(--text)}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.accent-blue-500{accent-color:#3b82f6}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4;--accent: #7c3aed;--success: #10b981;--warning: #f59e0b;--danger: #ef4444}[data-theme=dark]{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4}[data-theme=light]{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}@media (prefers-color-scheme: light){:root{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}}html,body,#root{height:100%}body{margin:0;background:var(--bg);color:var(--text)}.container{margin:0 auto;padding:24px;max-width:1100px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;box-shadow:0 1px 2px #0000000a,0 2px 12px #00000014}.stat{display:flex;align-items:center;gap:12px}.badge{display:inline-block;font-size:12px;padding:2px 8px;border-radius:999px;border:1px solid var(--border);background:#7c3aed14;color:var(--text)}.table{width:100%;border-collapse:collapse}.table th,.table td{border-bottom:1px solid var(--border);padding:8px 4px;text-align:left}.muted{color:var(--muted)}.accent{color:var(--accent)}.success{color:var(--success)}.warning{color:var(--warning)}.danger{color:var(--danger)}.toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px}.button{border:1px solid var(--border);background:var(--card);color:var(--text);padding:6px 10px;border-radius:8px;cursor:pointer}.button:hover{filter:brightness(1.05)}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-blue-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(96 165 250 / var(--tw-ring-opacity, 1))}@media (min-width: 640px){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[220px_1fr\]{grid-template-columns:220px 1fr}}
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <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">
15
+ </head>
16
+
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+
21
+ </html>
@@ -0,0 +1,71 @@
1
+ // Minimal service worker for actionable notifications
2
+
3
+ self.addEventListener('install', (event) => {
4
+ self.skipWaiting();
5
+ });
6
+
7
+ self.addEventListener('activate', (event) => {
8
+ event.waitUntil(self.clients.claim());
9
+ });
10
+
11
+ // Receive messages from pages to show notifications with actions
12
+ self.addEventListener('message', (event) => {
13
+ try {
14
+ const data = event.data || {};
15
+ if (data && data.type === 'SHOW_MCP_BLOCK_NOTIFICATION') {
16
+ const title = data.title || 'Action required';
17
+ const body = data.body || 'Approve or deny the request';
18
+ const payload = data.data || {};
19
+ event.waitUntil(
20
+ self.registration.showNotification(title, {
21
+ body,
22
+ requireInteraction: true,
23
+ data: payload,
24
+ actions: [
25
+ { action: 'approve', title: 'Approve' },
26
+ { action: 'deny', title: 'Deny' }
27
+ ]
28
+ })
29
+ );
30
+ }
31
+ } catch (e) {
32
+ // swallow
33
+ }
34
+ });
35
+
36
+ // Handle action button clicks and generic clicks
37
+ self.addEventListener('notificationclick', (event) => {
38
+ try {
39
+ const payload = (event.notification && event.notification.data) || {};
40
+ const action = event.action;
41
+ event.notification.close();
42
+
43
+ if (action === 'approve') {
44
+ const body = {
45
+ session_id: payload.sessionId,
46
+ kind: payload.kind,
47
+ name: payload.name
48
+ };
49
+ event.waitUntil(
50
+ fetch('/api/approve', {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify(body)
54
+ }).catch(() => { })
55
+ );
56
+ return;
57
+ }
58
+
59
+ if (action === 'deny') {
60
+ // No-op; could notify page if desired
61
+ return;
62
+ }
63
+
64
+ // Generic click: open dashboard as a safe fallback
65
+ event.waitUntil(self.clients.openWindow('/dashboard').catch(() => { }));
66
+ } catch (e) {
67
+ // swallow
68
+ }
69
+ });
70
+
71
+
@@ -0,0 +1,15 @@
1
+ """MCP importer package for Open Edison scripts.
2
+
3
+ Import submodules explicitly as needed, e.g. `from src.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 src.mcp_importer.cli import run_cli as import_run_cli
6
+ from src.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())
@@ -0,0 +1,196 @@
1
+ # pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false
2
+ import asyncio
3
+ from collections.abc import Awaitable
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Any, Protocol, cast, runtime_checkable
7
+
8
+ from fastmcp import FastMCP
9
+
10
+ from src.config import Config, MCPServerConfig, get_config_json_path
11
+ 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,
17
+ )
18
+ from src.mcp_importer.importers import (
19
+ import_from_claude_code,
20
+ import_from_cursor,
21
+ import_from_vscode,
22
+ )
23
+ 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
+
34
+
35
+ class CLIENT(str, Enum):
36
+ CURSOR = "cursor"
37
+ VSCODE = "vscode"
38
+ CLAUDE_CODE = "claude-code"
39
+
40
+ def __str__(self) -> str:
41
+ return self.value.capitalize()
42
+
43
+ def __repr__(self) -> str:
44
+ return str(self)
45
+
46
+
47
+ def detect_clients() -> set[CLIENT]:
48
+ detected: set[CLIENT] = set()
49
+ if _paths.detect_cursor_config_path() is not None:
50
+ detected.add(CLIENT.CURSOR)
51
+ if _paths.detect_vscode_config_path() is not None:
52
+ detected.add(CLIENT.VSCODE)
53
+ if _paths.detect_claude_code_config_path() is not None:
54
+ detected.add(CLIENT.CLAUDE_CODE)
55
+ return detected
56
+
57
+
58
+ def import_from(client: CLIENT) -> list[MCPServerConfig]:
59
+ if client == CLIENT.CURSOR:
60
+ return import_from_cursor()
61
+ if client == CLIENT.VSCODE:
62
+ return import_from_vscode()
63
+ if client == CLIENT.CLAUDE_CODE:
64
+ return import_from_claude_code()
65
+ raise ValueError(f"Unsupported client: {client}")
66
+
67
+
68
+ def save_imported_servers(
69
+ servers: list[MCPServerConfig],
70
+ *,
71
+ dry_run: bool = False,
72
+ merge_policy: str = MergePolicy.SKIP,
73
+ config_dir: Path | None = None,
74
+ ) -> Path | None:
75
+ target_path: Path = (
76
+ get_config_json_path() if config_dir is None else (Path(config_dir) / "config.json")
77
+ )
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
+ cfg: Config = Config(target_path)
84
+ merged = merge_servers(existing=cfg.mcp_servers, imported=servers, policy=merge_policy)
85
+ cfg.mcp_servers = merged
86
+ cfg.save(target_path)
87
+ return target_path
88
+
89
+
90
+ def export_edison_to(
91
+ client: CLIENT,
92
+ *,
93
+ url: str = "http://localhost:3000/mcp/",
94
+ api_key: str = "dev-api-key-change-me",
95
+ server_name: str = "open-edison",
96
+ dry_run: bool = False,
97
+ force: bool = False,
98
+ 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
+ )
110
+ match client:
111
+ case CLIENT.CURSOR:
112
+ return export_to_cursor(
113
+ url=url,
114
+ api_key=api_key,
115
+ server_name=server_name,
116
+ dry_run=dry_run,
117
+ force=force,
118
+ create_if_missing=create_if_missing,
119
+ )
120
+ case CLIENT.VSCODE:
121
+ return export_to_vscode(
122
+ url=url,
123
+ api_key=api_key,
124
+ server_name=server_name,
125
+ dry_run=dry_run,
126
+ force=force,
127
+ create_if_missing=create_if_missing,
128
+ )
129
+ case CLIENT.CLAUDE_CODE:
130
+ return export_to_claude_code(
131
+ url=url,
132
+ api_key=api_key,
133
+ server_name=server_name,
134
+ dry_run=dry_run,
135
+ force=force,
136
+ create_if_missing=create_if_missing,
137
+ )
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())
@@ -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())
@@ -0,0 +1,201 @@
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())