e1ca7f6a5e
Entscheid nach Probe (Hermes-first): Dispatcher im Gateway, Specifier (triage -> sauberer Auftrag), Profil-Routing und Artefakt-Tracking sind nativ E2E bewiesen — kein Eigenbau. Auftragsbuch bleibt das Klick-Gate. - backend services/ideen.py + routers/ideen.py: MC2-Tuer zur Queue (GET /api/ideen, POST /api/ideen -> kanban create --triage, Archiv) - AuftragsbuchView: Ideen-Queue-Sektion (Eingabefeld + Status-Liste), dist frisch gebaut - mcp_voice.py: idee_notieren (Lucys Sprech-Leitung, Prompt-Diaet: 1 Tool) - chef-gutachter-feed.sh: Morgenlage bekommt kanban stats + Bewegung - Bagatell-Pfad: bagatell-annahme.sh + mc2-bagatell.timer (04:10) — NUR .md-Anlagen/-AEnderungen, max 2/Nacht, gleicher Annahme-Runner mit Health-Gate + Auto-Revert, Chronik/Telegram-Meldung - werkstatt-SOUL.md: Leitplanken des Kanban-Worker-Profils (propose-only, nie main, nie Live-Checkout) — deploy.sh zieht sie kuenftig nach Box-live heute schon (ausserhalb Repo, mit Backups): toolsets+kanban, platform_toolsets.telegram+kanban, SOUL.md-Ideen-Regel, Profil werkstatt (model.default=coder), Gateway neu gestartet + E2E-Tuer-Test gruen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
5.1 KiB
Python
121 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Mission Control 2.0 — schlanker Voice-MCP-Server (für Lucys Sprach-Lane).
|
||
|
||
Prompt-Diät (Faden 7): die api_server-Plattform (Lucy Desktop) bekommt per
|
||
platform_toolsets.api_server NUR diesen dünnen Server statt der vollen
|
||
mission-control-stack-Control-Plane (26 Tools, ~große Schemas). Enthalten ist,
|
||
was man einer gesprochenen Assistentin zuruft: Erinnerungen + „geht's der Box gut?".
|
||
CLI/Telegram/Werkstatt behalten den vollen Stack (mcp_mc.py) — hier NICHTS ergänzen,
|
||
was nicht in ein Sprachgespräch gehört.
|
||
|
||
Env: MC_URL (default http://127.0.0.1:9001).
|
||
"""
|
||
|
||
import os
|
||
|
||
import httpx
|
||
from mcp.server.fastmcp import FastMCP
|
||
|
||
MC_URL = os.environ.get("MC_URL", "http://127.0.0.1:9001").rstrip("/")
|
||
|
||
mcp = FastMCP("mission-control-voice")
|
||
|
||
|
||
def _get(path: str, **params):
|
||
r = httpx.get(f"{MC_URL}{path}", params=params, timeout=30)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
|
||
def _post(path: str, data: dict):
|
||
r = httpx.post(f"{MC_URL}{path}", json=data, timeout=30)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
|
||
@mcp.tool()
|
||
def reminder_create(text: str, when: str, repeat: str = "") -> str:
|
||
"""Legt eine Erinnerung an: zur angegebenen Zeit sagt die Box dem Commander den Text AKTIV
|
||
an (Lucy spricht + Telegram). NUR nutzen, wenn der Commander ausdrücklich erinnert werden
|
||
will ('erinner mich', 'sag mir um', 'jeden Morgen').
|
||
when = ISO-8601 mit Datum UND Uhrzeit in der Lokalzeit des Commanders, z.B. 2026-07-04T09:00.
|
||
Relative Angaben ('in 20 Minuten', 'morgen früh') selbst aus der aktuellen Zeit im Kontext
|
||
umrechnen. repeat: '' einmalig | daily (täglich) | weekdays (Mo–Fr) | weekly (wöchentlich).
|
||
text = die Ansage selbst, kurz und direkt (ohne 'Erinnerung:' davor — das ergänzt die Box)."""
|
||
try:
|
||
item = _post("/api/reminders", {"text": text, "when": when, "repeat": repeat})["item"]
|
||
except httpx.HTTPStatusError as e:
|
||
return f"Fehlgeschlagen: {e.response.json().get('detail', e.response.text[:200])}"
|
||
rep = {"daily": " (täglich)", "weekdays": " (werktags)", "weekly": " (wöchentlich)"}.get(item["repeat"], "")
|
||
return f"Erinnerung #{item['id']} angelegt: {item['when_local']}{rep} — „{item['text']}“"
|
||
|
||
|
||
@mcp.tool()
|
||
def reminder_list() -> str:
|
||
"""Zeigt alle anstehenden Erinnerungen/Routinen (Nummer, Zeit, Text). Vor dem Löschen
|
||
hiermit die Nummer ermitteln."""
|
||
items = _get("/api/reminders")["items"]
|
||
if not items:
|
||
return "Keine Erinnerungen angelegt."
|
||
rep = {"daily": " · täglich", "weekdays": " · werktags", "weekly": " · wöchentlich"}
|
||
return "\n".join(f"#{i['id']} · {i['when_local']}{rep.get(i['repeat'], '')} — {i['text']}" for i in items)
|
||
|
||
|
||
@mcp.tool()
|
||
def reminder_delete(reminder_id: int) -> str:
|
||
"""Löscht eine Erinnerung/Routine endgültig (Nummer aus reminder_list). NUR auf
|
||
ausdrücklichen Wunsch des Commanders."""
|
||
try:
|
||
r = httpx.delete(f"{MC_URL}/api/reminders/{reminder_id}", timeout=30)
|
||
r.raise_for_status()
|
||
gone = r.json()["deleted"]
|
||
except httpx.HTTPStatusError as e:
|
||
return f"Fehlgeschlagen: {e.response.json().get('detail', e.response.text[:200])}"
|
||
return f"Erinnerung #{gone['id']} gelöscht ({gone['when_local']} — „{gone['text']}“)."
|
||
|
||
|
||
@mcp.tool()
|
||
def box_status() -> str:
|
||
"""Kurzer Gesundheits-Check der Box (KI-Server): Engine, Agent-Hirn, Kern-Dienste.
|
||
Nutze dies, wenn der Commander fragt, ob mit der Box/dem System alles in Ordnung ist."""
|
||
try:
|
||
h = _get("/api/health")
|
||
except Exception as exc:
|
||
return f"MC2 antwortet nicht ({exc}) — das Backend selbst könnte down sein."
|
||
brain = h.get("brain") or {}
|
||
lines = [
|
||
f"{'✅' if h.get('engine_reachable') else '❌'} Modell-Engine",
|
||
f"{'✅' if brain.get('ready') else '❌'} Agent-Hirn ({brain.get('model', '?')})",
|
||
]
|
||
try:
|
||
svcs = _get("/api/system/services").get("services", [])
|
||
lines += [f"{'✅' if s.get('ok') else '❌'} {s.get('name')}" for s in svcs]
|
||
except Exception:
|
||
lines.append("(Dienste-Liste nicht abrufbar)")
|
||
return "\n".join(lines)
|
||
|
||
|
||
@mcp.tool()
|
||
def idee_notieren(titel: str, notiz: str = "") -> str:
|
||
"""Legt eine Idee/einen Auftrag des Commanders in die Ideen-Queue der Box. Nutzen, sobald
|
||
der Commander eine Idee für später äußert ('wäre cool wenn …', 'bau mal irgendwann …',
|
||
'die Box soll …') — NICHT nur versprechen. Die Box arbeitet die Idee selbst aus und baut;
|
||
das Ergebnis erscheint als Karte im Auftragsbuch, dort entscheidet der Commander.
|
||
titel = kurze deutsche Überschrift; notiz = seine Worte plus nötiger Kontext."""
|
||
try:
|
||
r = _post("/api/ideen", {"titel": titel, "notiz": notiz})
|
||
except httpx.HTTPStatusError as e:
|
||
try:
|
||
detail = e.response.json().get("detail", "")
|
||
except Exception:
|
||
detail = e.response.text[:200]
|
||
return f"Fehlgeschlagen: {detail}"
|
||
except Exception as exc:
|
||
return f"Queue nicht erreichbar ({exc})."
|
||
return f"Idee ist in der Queue ({r.get('id', '?')}). Die Box arbeitet sie von allein ab."
|
||
|
||
|
||
if __name__ == "__main__":
|
||
mcp.run()
|