open-edison 0.1.26__py3-none-any.whl → 0.1.30__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.
@@ -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-DOR5YaNc.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
+
@@ -233,7 +233,11 @@ class SessionTrackingMiddleware(Middleware):
233
233
  # Get or create session stats
234
234
  _, _session_id = self._get_or_create_session_stats(context)
235
235
 
236
- return await call_next(context) # type: ignore
236
+ try:
237
+ return await call_next(context) # type: ignore
238
+ except Exception:
239
+ log.exception("MCP request handling failed")
240
+ raise
237
241
 
238
242
  # Hooks for Tools
239
243
  async def on_list_tools( # noqa
@@ -243,7 +247,11 @@ class SessionTrackingMiddleware(Middleware):
243
247
  ) -> Any:
244
248
  log.debug("🔍 on_list_tools")
245
249
  # Get the original response
246
- response = await call_next(context)
250
+ try:
251
+ response = await call_next(context)
252
+ except Exception:
253
+ log.exception("MCP list_tools failed")
254
+ raise
247
255
  log.trace(f"🔍 on_list_tools response: {response}")
248
256
 
249
257
  session_id = current_session_id_ctxvar.get()
@@ -368,7 +376,11 @@ class SessionTrackingMiddleware(Middleware):
368
376
  """Process resource access and track security implications."""
369
377
  log.trace("🔍 on_list_resources")
370
378
  # Get the original response
371
- response = await call_next(context)
379
+ try:
380
+ response = await call_next(context)
381
+ except Exception:
382
+ log.exception("MCP list_resources failed")
383
+ raise
372
384
  log.trace(f"🔍 on_list_resources response: {response}")
373
385
 
374
386
  session_id = current_session_id_ctxvar.get()
@@ -415,7 +427,11 @@ class SessionTrackingMiddleware(Middleware):
415
427
  session_id = current_session_id_ctxvar.get()
416
428
  if session_id is None:
417
429
  log.warning("No session ID found for resource access tracking")
418
- return await call_next(context)
430
+ try:
431
+ return await call_next(context)
432
+ except Exception:
433
+ log.exception("MCP read_resource failed")
434
+ raise
419
435
 
420
436
  session = get_session_from_db(session_id)
421
437
  log.trace(f"Adding resource access to session {session_id}")
@@ -457,7 +473,11 @@ class SessionTrackingMiddleware(Middleware):
457
473
  db_session.commit()
458
474
 
459
475
  log.trace(f"Resource access {resource_name} added to session {session_id}")
460
- return await call_next(context)
476
+ try:
477
+ return await call_next(context)
478
+ except Exception:
479
+ log.exception("MCP read_resource failed")
480
+ raise
461
481
 
462
482
  # Hooks for Prompts
463
483
  async def on_list_prompts( # noqa
@@ -468,7 +488,11 @@ class SessionTrackingMiddleware(Middleware):
468
488
  """Process resource access and track security implications."""
469
489
  log.debug("🔍 on_list_prompts")
470
490
  # Get the original response
471
- response = await call_next(context)
491
+ try:
492
+ response = await call_next(context)
493
+ except Exception:
494
+ log.exception("MCP list_prompts failed")
495
+ raise
472
496
  log.debug(f"🔍 on_list_prompts response: {response}")
473
497
 
474
498
  session_id = current_session_id_ctxvar.get()
@@ -515,7 +539,11 @@ class SessionTrackingMiddleware(Middleware):
515
539
  session_id = current_session_id_ctxvar.get()
516
540
  if session_id is None:
517
541
  log.warning("No session ID found for prompt access tracking")
518
- return await call_next(context)
542
+ try:
543
+ return await call_next(context)
544
+ except Exception:
545
+ log.exception("MCP get_prompt failed")
546
+ raise
519
547
 
520
548
  session = get_session_from_db(session_id)
521
549
  log.trace(f"Adding prompt access to session {session_id}")
@@ -554,4 +582,8 @@ class SessionTrackingMiddleware(Middleware):
554
582
  db_session.commit()
555
583
 
556
584
  log.trace(f"Prompt access {prompt_name} added to session {session_id}")
557
- return await call_next(context)
585
+ try:
586
+ return await call_next(context)
587
+ except Exception:
588
+ log.exception("MCP get_prompt failed")
589
+ raise
src/permissions.py CHANGED
@@ -14,22 +14,8 @@ from loguru import logger as log
14
14
 
15
15
  from src.config import Config, get_config_dir
16
16
 
17
- # Detect repository root (same logic as in src.config)
18
- _ROOT_DIR = Path(__file__).parent.parent
19
-
20
17
 
21
18
  def _default_permissions_dir() -> Path:
22
- """Resolve default permissions directory.
23
-
24
- In development (repo checkout with pyproject.toml), prefer repository root so
25
- we use repo-local tool/resource/prompt permissions JSON files. Otherwise fall
26
- back to the standard user config directory.
27
- """
28
- try:
29
- if (_ROOT_DIR / "pyproject.toml").exists():
30
- return _ROOT_DIR
31
- except Exception:
32
- pass
33
19
  return get_config_dir()
34
20
 
35
21
 
@@ -76,6 +62,9 @@ class PromptPermission:
76
62
  write_operation: bool = False
77
63
  read_private_data: bool = False
78
64
  read_untrusted_public_data: bool = False
65
+ # Optional metadata fields (ignored by enforcement but accepted from JSON)
66
+ description: str | None = None
67
+ acl: str = "PUBLIC"
79
68
 
80
69
 
81
70
  @dataclass
src/server.py CHANGED
@@ -30,7 +30,7 @@ from loguru import logger as log
30
30
  from pydantic import BaseModel, Field
31
31
 
32
32
  from src import events
33
- from src.config import Config, MCPServerConfig
33
+ from src.config import Config, MCPServerConfig, load_json_file
34
34
  from src.config import get_config_dir as _get_cfg_dir # type: ignore[attr-defined]
35
35
  from src.middleware.session_tracking import (
36
36
  MCPSessionModel,
@@ -86,10 +86,16 @@ class OpenEdisonProxy:
86
86
  # If packaged frontend assets exist, mount at /dashboard
87
87
  try:
88
88
  # Prefer packaged assets under src/frontend_dist
89
- static_dir = Path(__file__).parent / "frontend_dist"
90
- if not static_dir.exists():
91
- # Fallback to repo root or site-packages root (older layout)
92
- static_dir = Path(__file__).parent.parent / "frontend_dist"
89
+ primary_candidate = Path(__file__).parent / "frontend_dist"
90
+ secondary_candidate = Path(__file__).parent.parent / "frontend_dist"
91
+ log.trace(
92
+ "Checking dashboard assets candidates: primary={}, exists={}, secondary={}, exists={}",
93
+ primary_candidate,
94
+ primary_candidate.exists(),
95
+ secondary_candidate,
96
+ secondary_candidate.exists(),
97
+ )
98
+ static_dir = primary_candidate if primary_candidate.exists() else secondary_candidate
93
99
  if static_dir.exists():
94
100
  app.mount(
95
101
  "/dashboard",
@@ -121,9 +127,18 @@ class OpenEdisonProxy:
121
127
  app.add_api_route("/favicon.ico", _favicon, methods=["GET"]) # type: ignore[arg-type]
122
128
  log.info(f"📊 Dashboard static assets mounted at /dashboard from {static_dir}")
123
129
  else:
124
- log.debug("No packaged frontend assets found; skipping static mount")
130
+ # Emit a more detailed diagnostic and fail fast when assets are not found
131
+ cwd = Path.cwd()
132
+ msg = (
133
+ "Packaged dashboard assets not found. Expected at one of: "
134
+ f"{primary_candidate} or {secondary_candidate}. "
135
+ f"cwd={cwd}, __file__={Path(__file__).resolve()}"
136
+ )
137
+ log.error(msg)
138
+ raise RuntimeError(msg)
125
139
  except Exception as mount_err: # noqa: BLE001
126
- log.warning(f"Failed to mount dashboard static assets: {mount_err}")
140
+ log.error(f"Failed to mount dashboard static assets: {mount_err}")
141
+ raise
127
142
 
128
143
  # Special-case: serve SQLite db and config JSONs for dashboard (prod replacement for Vite @fs)
129
144
  def _resolve_db_path() -> Path:
@@ -250,6 +265,11 @@ class OpenEdisonProxy:
250
265
  _ = json.loads(content or "{}")
251
266
  target.write_text(content or "{}", encoding="utf-8")
252
267
  log.debug(f"Saved JSON config to {target}")
268
+
269
+ # Clear cache for the config file, if it was config.json
270
+ if name == "config.json":
271
+ load_json_file.cache_clear()
272
+
253
273
  return {"status": "ok"}
254
274
  except Exception as e: # noqa: BLE001
255
275
  raise HTTPException(status_code=400, detail=f"Save failed: {e}") from e
@@ -538,11 +558,6 @@ class OpenEdisonProxy:
538
558
  try:
539
559
  log.info("🔄 Reinitializing MCP servers via API endpoint")
540
560
 
541
- # Create a completely new SingleUserMCP instance to ensure clean state
542
- old_mcp = self.single_user_mcp
543
- self.single_user_mcp = SingleUserMCP()
544
- del old_mcp
545
-
546
561
  # Initialize the new instance with fresh config
547
562
  await self.single_user_mcp.initialize()
548
563
 
src/single_user_mcp.py CHANGED
@@ -49,7 +49,8 @@ class SingleUserMCP(FastMCP[Any]):
49
49
  """
50
50
 
51
51
  def __init__(self):
52
- super().__init__(name="open-edison-single-user")
52
+ # Disable error masking so upstream error details are preserved in responses
53
+ super().__init__(name="open-edison-single-user", mask_error_details=False)
53
54
 
54
55
  # Add session tracking middleware for data access monitoring
55
56
  self.add_middleware(SessionTrackingMiddleware())
@@ -288,6 +289,10 @@ class SingleUserMCP(FastMCP[Any]):
288
289
  f"Found {len(enabled_servers)} enabled servers: {[s.name for s in enabled_servers]}"
289
290
  )
290
291
 
292
+ # Unmount all servers
293
+ for server_name in list(mounted_servers.keys()):
294
+ await self.unmount(server_name)
295
+
291
296
  # Create composite proxy for all real servers
292
297
  success = await self.create_composite_proxy(enabled_servers)
293
298
  if not success:
@@ -296,6 +301,11 @@ class SingleUserMCP(FastMCP[Any]):
296
301
 
297
302
  log.info("✅ Single User MCP server initialized with composite proxy")
298
303
 
304
+ # Invalidate and warm lists to ensure reload
305
+ _ = await self._tool_manager.list_tools()
306
+ _ = await self._resource_manager.list_resources()
307
+ _ = await self._prompt_manager.list_prompts()
308
+
299
309
  def _calculate_risk_level(self, trifecta: dict[str, bool]) -> str:
300
310
  """
301
311
  Calculate a human-readable risk level based on trifecta flags.
@@ -386,7 +396,7 @@ class SingleUserMCP(FastMCP[Any]):
386
396
  """
387
397
  tool_list = await self._tool_manager.list_tools()
388
398
  available_tools: list[str] = []
389
- log.debug(f"Raw tool list: {tool_list}")
399
+ log.trace(f"Raw tool list: {tool_list}")
390
400
  perms = Permissions()
391
401
  for tool in tool_list:
392
402
  # Use the prefixed key (e.g., "filesystem_read_file") to match flattened permissions
@@ -1,17 +0,0 @@
1
- src/__init__.py,sha256=QWeZdjAm2D2B0eWhd8m2-DPpWvIP26KcNJxwEoU1oEQ,254
2
- src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
- src/cli.py,sha256=_F1xtUU2h4snWUHf1NptRWGQaD2OSIhEPGLh9Rzmtis,10032
4
- src/config.py,sha256=jZYX4q09hg2VlLCq7FIKa_bL7NpNNMStYMQyEMZPrDg,9910
5
- src/events.py,sha256=rBH7rnaSWZ7GIC8zyBTwpcvIKWmKYCki-DNGgJhxPow,5001
6
- src/oauth_manager.py,sha256=qcQa5BDRZr4bjqiXNflCnrXOh9mo9JVjvP2Caseg2Uc,9943
7
- src/permissions.py,sha256=kIbLPtaJAwV5CF-YECLhU7HEF704LdQCI2xIh-TCu4I,10834
8
- src/server.py,sha256=Gg2DnnvZr59e0PGtRXVD94fxVxtiNnizlIrVkeLGIok,44504
9
- src/single_user_mcp.py,sha256=bbWbuuBxjL0DJY9kAHtHt8jwQNePXPa4cD4gnbD9XmE,16897
10
- src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
11
- src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
12
- src/middleware/session_tracking.py,sha256=p3UMruIe5RBYnfAzmSaHMOeN3xKN-9EiCSd7nAGjgrw,22138
13
- open_edison-0.1.26.dist-info/METADATA,sha256=Tfw-vOv_KlIvUBMcU5DjwHNiksFxJejJBurC0dp_b4Q,11685
14
- open_edison-0.1.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- open_edison-0.1.26.dist-info/entry_points.txt,sha256=qNAkJcnoTXRhj8J--3PDmXz_TQKdB8H_0C9wiCtDIyA,72
16
- open_edison-0.1.26.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
17
- open_edison-0.1.26.dist-info/RECORD,,