open-edison 0.1.10__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.10.dist-info/METADATA +332 -0
- open_edison-0.1.10.dist-info/RECORD +17 -0
- open_edison-0.1.10.dist-info/WHEEL +4 -0
- open_edison-0.1.10.dist-info/entry_points.txt +3 -0
- open_edison-0.1.10.dist-info/licenses/LICENSE +674 -0
- src/__init__.py +11 -0
- src/__main__.py +10 -0
- src/cli.py +274 -0
- src/config.py +224 -0
- src/frontend_dist/assets/index-CKkid2y-.js +51 -0
- src/frontend_dist/assets/index-CRxojymD.css +1 -0
- src/frontend_dist/index.html +21 -0
- src/mcp_manager.py +137 -0
- src/middleware/data_access_tracker.py +510 -0
- src/middleware/session_tracking.py +477 -0
- src/server.py +560 -0
- src/single_user_mcp.py +403 -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}}.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}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-\[580px\]{height:580px}.w-2{width:.5rem}.w-4{width:1rem}.w-full{width:100%}.min-w-\[240px\]{min-width:240px}.max-w-\[1400px\]{max-width:1400px}.max-w-\[260px\]{max-width:260px}.border-collapse{border-collapse:collapse}.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-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-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-border{background-color:var(--border)}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-6{padding:1.5rem}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!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-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-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.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-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}: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)}@media (min-width: 640px){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}}@media (min-width: 1024px){.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-CKkid2y-.js"></script>
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CRxojymD.css">
|
15
|
+
</head>
|
16
|
+
|
17
|
+
<body>
|
18
|
+
<div id="root"></div>
|
19
|
+
</body>
|
20
|
+
|
21
|
+
</html>
|
src/mcp_manager.py
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
"""
|
2
|
+
MCP Manager
|
3
|
+
|
4
|
+
Handles subprocess management of MCP servers.
|
5
|
+
Separate from FastMCP to keep concerns clean.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import subprocess
|
9
|
+
|
10
|
+
from loguru import logger as log
|
11
|
+
|
12
|
+
from src.config import MCPServerConfig, config
|
13
|
+
|
14
|
+
|
15
|
+
class MCPManager:
|
16
|
+
"""
|
17
|
+
Manages MCP server subprocesses.
|
18
|
+
|
19
|
+
This class is responsible for starting/stopping MCP server processes,
|
20
|
+
but not for the FastMCP protocol handling.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self):
|
24
|
+
self.processes: dict[str, subprocess.Popen[str]] = {}
|
25
|
+
self.server_configs: dict[str, MCPServerConfig] = {
|
26
|
+
server.name: server for server in config.mcp_servers
|
27
|
+
}
|
28
|
+
|
29
|
+
async def start_server(self, server_name: str) -> bool:
|
30
|
+
"""Start an MCP server subprocess.
|
31
|
+
|
32
|
+
Returns whether a new process was started (False if already running).
|
33
|
+
Raises on error when creating server process.
|
34
|
+
"""
|
35
|
+
if server_name in self.processes and self.processes[server_name].poll() is None:
|
36
|
+
log.warning(f"Server {server_name} is already running")
|
37
|
+
return False
|
38
|
+
|
39
|
+
server_config = self.server_configs.get(server_name)
|
40
|
+
if not server_config:
|
41
|
+
raise ValueError(f"Server configuration not found: {server_name}")
|
42
|
+
|
43
|
+
if not server_config.enabled:
|
44
|
+
raise ValueError(f"Server {server_name} is disabled")
|
45
|
+
|
46
|
+
try:
|
47
|
+
# Build the command
|
48
|
+
cmd = [server_config.command] + server_config.args
|
49
|
+
|
50
|
+
# Set up environment - inherit system environment and add server-specific vars
|
51
|
+
import os
|
52
|
+
|
53
|
+
env = os.environ.copy()
|
54
|
+
if server_config.env:
|
55
|
+
env.update(server_config.env)
|
56
|
+
|
57
|
+
log.info(f"Starting MCP server {server_name}: {' '.join(cmd)}")
|
58
|
+
|
59
|
+
# Start the process
|
60
|
+
process = subprocess.Popen(
|
61
|
+
cmd,
|
62
|
+
env=env,
|
63
|
+
stdout=subprocess.PIPE,
|
64
|
+
stderr=subprocess.PIPE,
|
65
|
+
stdin=subprocess.PIPE,
|
66
|
+
text=True,
|
67
|
+
)
|
68
|
+
|
69
|
+
self.processes[server_name] = process
|
70
|
+
log.info(f"✅ MCP server {server_name} started with PID {process.pid}")
|
71
|
+
|
72
|
+
except Exception as e:
|
73
|
+
log.error(f"Failed to start MCP server {server_name}: {e}")
|
74
|
+
raise
|
75
|
+
|
76
|
+
return True
|
77
|
+
|
78
|
+
async def stop_server(self, server_name: str) -> None:
|
79
|
+
"""Stop an MCP server subprocess."""
|
80
|
+
if server_name not in self.processes:
|
81
|
+
log.warning(f"Server {server_name} is not running")
|
82
|
+
return
|
83
|
+
|
84
|
+
process = self.processes[server_name]
|
85
|
+
|
86
|
+
try:
|
87
|
+
if process.poll() is None: # Process is still running
|
88
|
+
log.info(f"Stopping MCP server {server_name} (PID {process.pid})")
|
89
|
+
|
90
|
+
# Try graceful shutdown first
|
91
|
+
process.terminate()
|
92
|
+
|
93
|
+
# Wait for graceful shutdown
|
94
|
+
try:
|
95
|
+
_ = process.wait(timeout=5)
|
96
|
+
except subprocess.TimeoutExpired:
|
97
|
+
# Force kill if graceful shutdown fails
|
98
|
+
log.warning(f"Force killing MCP server {server_name}")
|
99
|
+
process.kill()
|
100
|
+
_ = process.wait()
|
101
|
+
|
102
|
+
log.info(f"✅ MCP server {server_name} stopped")
|
103
|
+
|
104
|
+
del self.processes[server_name]
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
log.error(f"Failed to stop MCP server {server_name}: {e}")
|
108
|
+
raise
|
109
|
+
|
110
|
+
async def is_server_running(self, server_name: str) -> bool:
|
111
|
+
"""Check if a server subprocess is running."""
|
112
|
+
if server_name not in self.processes:
|
113
|
+
return False
|
114
|
+
|
115
|
+
process = self.processes[server_name]
|
116
|
+
return process.poll() is None
|
117
|
+
|
118
|
+
async def shutdown(self) -> None:
|
119
|
+
"""Shutdown all running server subprocesses."""
|
120
|
+
log.info("Shutting down all MCP servers")
|
121
|
+
|
122
|
+
for server_name in list(self.processes.keys()):
|
123
|
+
await self.stop_server(server_name)
|
124
|
+
|
125
|
+
log.info("✅ All MCP servers stopped")
|
126
|
+
|
127
|
+
def get_server_status(self) -> list[dict[str, str | bool]]:
|
128
|
+
"""Get status of all configured servers."""
|
129
|
+
return [
|
130
|
+
{
|
131
|
+
"name": server.name,
|
132
|
+
"enabled": server.enabled,
|
133
|
+
"running": server.name in self.processes
|
134
|
+
and self.processes[server.name].poll() is None,
|
135
|
+
}
|
136
|
+
for server in config.mcp_servers
|
137
|
+
]
|