diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..fb61814
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,7 @@
+GATEWAY_PORT=8080
+
+# Override ambiente per ogni MCP exchange (precedenza: env > secret > default)
+DERIBIT_TESTNET=true
+BYBIT_TESTNET=true
+HYPERLIQUID_TESTNET=true
+ALPACA_PAPER=true
diff --git a/gateway/Caddyfile b/gateway/Caddyfile
new file mode 100644
index 0000000..140b3d9
--- /dev/null
+++ b/gateway/Caddyfile
@@ -0,0 +1,36 @@
+{
+ admin off
+ auto_https off
+}
+
+:8080 {
+ log {
+ output stdout
+ format console
+ }
+
+ handle_path /mcp-deribit/* {
+ reverse_proxy mcp-deribit:9011
+ }
+ handle_path /mcp-hyperliquid/* {
+ reverse_proxy mcp-hyperliquid:9012
+ }
+ handle_path /mcp-bybit/* {
+ reverse_proxy mcp-bybit:9019
+ }
+ handle_path /mcp-alpaca/* {
+ reverse_proxy mcp-alpaca:9020
+ }
+ handle_path /mcp-macro/* {
+ reverse_proxy mcp-macro:9013
+ }
+ handle_path /mcp-sentiment/* {
+ reverse_proxy mcp-sentiment:9014
+ }
+
+ # Landing page statica
+ handle {
+ root * /srv
+ file_server
+ }
+}
diff --git a/gateway/public/index.html b/gateway/public/index.html
new file mode 100644
index 0000000..ee81341
--- /dev/null
+++ b/gateway/public/index.html
@@ -0,0 +1,97 @@
+
+
+
+
+Cerbero — MCP gateway
+
+
+
+
+
+
+
+
+
+ | Stato |
+ Servizio |
+ Porta int. |
+ Descrizione |
+ Link |
+
+
+
+
+ |
+ mcp-memory |
+ 9015 |
+ Store L1/L2, system prompt base + dyn |
+ health · docs |
+
+
+ |
+ mcp-scheduler |
+ 9016 |
+ Recurring task + core agent runner |
+ health · docs |
+
+
+ |
+ mcp-deribit |
+ 9011 |
+ Options testnet order/market |
+ health · docs |
+
+
+ |
+ mcp-hyperliquid |
+ 9012 |
+ Perp DEX testnet |
+ health · docs |
+
+
+ |
+ mcp-macro |
+ 9013 |
+ FRED indicators + Finnhub calendar |
+ health · docs |
+
+
+ |
+ mcp-sentiment |
+ 9014 |
+ CryptoPanic news feed |
+ health · docs |
+
+
+ |
+ mcp-telegram |
+ 9017 |
+ Bot commands + notifiche operatore |
+ health · docs |
+
+
+ |
+ mcp-portfolio |
+ 9018 |
+ Holdings + yfinance + UI htmx |
+ health · gui · docs |
+
+
+
+
+
+ Console operativa
+ /console — run del core agent, eventi stdout/stderr, L1 live, trigger manuale.
+
+
+
+
+
+
+
+
diff --git a/gateway/public/status.js b/gateway/public/status.js
new file mode 100644
index 0000000..0d548b0
--- /dev/null
+++ b/gateway/public/status.js
@@ -0,0 +1,23 @@
+const rows = document.querySelectorAll("tr[data-path]");
+
+async function poll() {
+ for (const row of rows) {
+ const dot = row.querySelector(".status");
+ try {
+ const r = await fetch(`${row.dataset.path}/health`, {
+ method: "GET",
+ cache: "no-store",
+ });
+ dot.classList.toggle("ok", r.ok);
+ dot.classList.toggle("err", !r.ok);
+ dot.setAttribute("aria-label", r.ok ? "ok" : "error");
+ } catch {
+ dot.classList.remove("ok");
+ dot.classList.add("err");
+ dot.setAttribute("aria-label", "unreachable");
+ }
+ }
+}
+
+poll();
+setInterval(poll, 5000);
diff --git a/gateway/public/style.css b/gateway/public/style.css
new file mode 100644
index 0000000..a650f40
--- /dev/null
+++ b/gateway/public/style.css
@@ -0,0 +1,101 @@
+:root {
+ --bg: #0f172a;
+ --fg: #e2e8f0;
+ --muted: #94a3b8;
+ --card: #1e293b;
+ --border: #334155;
+ --ok: #22c55e;
+ --err: #ef4444;
+ --unknown: #64748b;
+ --accent: #38bdf8;
+}
+
+* { box-sizing: border-box; }
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background: var(--bg);
+ color: var(--fg);
+ line-height: 1.5;
+}
+
+header, main, footer {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 1.5rem;
+}
+
+header h1 {
+ margin: 0 0 0.25rem;
+ color: var(--accent);
+ font-size: 2rem;
+}
+
+header p {
+ margin: 0;
+ color: var(--muted);
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ background: var(--card);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+th, td {
+ padding: 0.75rem 1rem;
+ text-align: left;
+ border-bottom: 1px solid var(--border);
+}
+
+th {
+ background: #0f172a;
+ color: var(--muted);
+ font-weight: 600;
+ font-size: 0.85rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+tr:last-child td { border-bottom: none; }
+
+td:nth-child(3) {
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ color: var(--muted);
+}
+
+a {
+ color: var(--accent);
+ text-decoration: none;
+ margin-right: 0.5rem;
+}
+
+a:hover { text-decoration: underline; }
+
+.status {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: var(--unknown);
+ transition: background 0.3s ease;
+}
+
+.status.ok { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
+.status.err { background: var(--err); box-shadow: 0 0 8px var(--err); }
+
+footer {
+ color: var(--muted);
+ font-size: 0.85rem;
+ margin-top: 2rem;
+}
+
+code {
+ background: var(--border);
+ padding: 0.1rem 0.3rem;
+ border-radius: 3px;
+ font-size: 0.9em;
+}