Initial commit

This commit is contained in:
AuroraCrimsonRose
2026-05-27 15:07:22 -05:00
commit cc64e8d41e
31 changed files with 2354 additions and 0 deletions

149
.gitignore vendored Normal file
View File

@@ -0,0 +1,149 @@
# Byte-compiled / Python cache
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Build / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Virtual environments
.env
.envrc
.venv/
venv/
ENV/
env/
env.bak/
venv.bak/
.python-version
# UV / Poetry / PDM / Pixi
uv.lock
poetry.lock
poetry.toml
pdm.lock
pdm.toml
.pdm-python
.pdm-build/
pixi.lock
.pixi/
# Logs / runtime / temp
logs/
*.log
runtime/
tmp/
tempCodeRunnerFile.py
# Testing / coverage
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.pytest_cache/
.hypothesis/
cover/
# Jupyter / IPython
.ipynb_checkpoints/
profile_default/
ipython_config.py
# Databases / local state
db.sqlite3
db.sqlite3-journal
# Redis / brokers
*.rdb
*.aof
*.pid
mnesia/
rabbitmq/
rabbitmq-data/
activemq-data/
# Framework junk
instance/
.webassets-cache/
.scrapy/
.pybuilder/
target/
docs/_build/
site/
celerybeat-schedule
celerybeat.pid
.scrapy/
.abstra/
.streamlit/secrets.toml
# Type checkers / linters
.mypy_cache/
.dmypy.json
dmypy.json
.pyre/
.pytype/
.ruff_cache/
# Editors / IDEs
.vscode/
.idea/
.spyderproject/
.spyproject/
.ropeproject/
# OS junk
.DS_Store
Thumbs.db
# Pip / packaging artifacts
pip-log.txt
pip-delete-this-directory.txt
.pypirc
# Security / env files
.env
.env.*
*.env
# IMPORTANT: ignore ALL dot-folders by default
.*
!.gitignore
!.github/
!.vscode/ # (comment this out if you want vscode tracked configs later)
# Exceptions (keep important hidden project dirs if you add them later)
!.env.example

View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

174
api/server.py Normal file
View File

@@ -0,0 +1,174 @@
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List
from pydantic import BaseModel
from core.metrics import metrics
from core.events import bus, Event
from core.config import API_HOST, API_PORT
from core.event_store import event_store
app = FastAPI(title="CXOS MCP API", version="1.0")
# =========================
# MODELS
# =========================
class ReplayRequest(BaseModel):
source: str | None = None
level: str | None = None
name: str | None = None
# =========================
# WEBSOCKET STATE
# =========================
connected_clients: List[WebSocket] = []
# =========================
# EVENT BROADCAST
# =========================
async def broadcast(event: Event):
dead_clients = []
payload = {
"name": event.name,
"source": event.source,
"level": event.level,
"data": event.data,
"timestamp": event.timestamp,
}
for ws in connected_clients:
try:
await ws.send_json(payload)
except Exception:
dead_clients.append(ws)
for ws in dead_clients:
if ws in connected_clients:
connected_clients.remove(ws)
# subscribe once
bus.subscribe_async(broadcast)
# =========================
# HEALTH / METRICS
# =========================
@app.get("/health")
def health():
return {
"status": "ok",
"service": "cxos-mcp"
}
@app.get("/metrics")
def get_metrics():
return metrics.snapshot()
@app.get("/metrics/prometheus")
def prometheus_metrics():
return metrics.prometheus_format()
# =========================
# LIVE EVENT STREAM
# =========================
@app.websocket("/ws/events")
async def event_stream(ws: WebSocket):
await ws.accept()
connected_clients.append(ws)
bus.emit(Event(
name="client_connected",
source="API",
level="INFO",
data={"clients": len(connected_clients)}
))
try:
while True:
# lightweight keep-alive loop
await ws.receive_text()
except WebSocketDisconnect:
if ws in connected_clients:
connected_clients.remove(ws)
bus.emit(Event(
name="client_disconnected",
source="API",
level="INFO",
data={"clients": len(connected_clients)}
))
# =========================
# EVENT REPLAY (READ ONLY)
# =========================
@app.get("/events/replay")
def replay_events(
source: str | None = None,
level: str | None = None,
name: str | None = None
):
events = event_store.query(source, level, name)
return {
"count": len(events),
"events": [
{
"name": e.name,
"source": e.source,
"level": e.level,
"data": e.data,
"timestamp": e.timestamp,
}
for e in events
]
}
# =========================
# EVENT REPLAY (EXECUTION)
# =========================
@app.post("/events/replay/run")
def replay_run(req: ReplayRequest):
def printer(event):
print(f"[REPLAY] {event.source} | {event.name}")
event_store.replay(
source=req.source,
level=req.level,
name=req.name,
handler=printer
)
return {"status": "replayed"}
# =========================
# SERVER START
# =========================
def run_api():
import uvicorn
uvicorn.run(
"api.server:app",
host=API_HOST,
port=API_PORT,
reload=False,
log_level="info"
)

69
core/config.py Normal file
View File

@@ -0,0 +1,69 @@
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
# =========================
# CORE PATHS
# =========================
WORKSPACE_ROOT = Path(os.getenv("WORKSPACE_ROOT", Path.cwd())).resolve()
LOG_DIR = WORKSPACE_ROOT / "logs"
TMP_DIR = WORKSPACE_ROOT / "tmp"
CONFIG_DIR = WORKSPACE_ROOT / "config"
LOG_DIR.mkdir(parents=True, exist_ok=True)
TMP_DIR.mkdir(parents=True, exist_ok=True)
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
# =========================
# SYSTEM LIMITS
# =========================
MAX_FILE_SIZE = int(os.getenv("MAX_FILE_SIZE", str(10 * 1024 * 1024)))
IGNORE_DIRS = set(
os.getenv(
"IGNORE_DIRS",
".git,__pycache__,node_modules,.venv,venv,dist,build"
).split(",")
)
# =========================
# EVENT / DEBUG FLAGS
# =========================
DEBUG = os.getenv("DEBUG", "false").lower() == "true"
DRY_RUN_DEFAULT = os.getenv("DRY_RUN", "false").lower() == "true"
# =========================
# GITEA CONFIG
# =========================
GITEA_URL = os.getenv("GITEA_URL", "http://localhost:3000")
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "")
GITEA_USER = os.getenv("GITEA_USER", "admin")
GITEA_API_BASE = f"{GITEA_URL}/api/v1"
# =========================
# GIT CONFIG
# =========================
GIT_AUTHOR_NAME = os.getenv("GIT_AUTHOR_NAME", "CXOS")
GIT_AUTHOR_EMAIL = os.getenv("GIT_AUTHOR_EMAIL", "cxos@local")
# =========================
# MCP / SERVER CONFIG
# =========================
SERVER_NAME = os.getenv("SERVER_NAME", "CXOS-MCP")
API_HOST = os.getenv("API_HOST", "0.0.0.0")
API_PORT = int(os.getenv("API_PORT", "5432"))

108
core/event_store.py Normal file
View File

@@ -0,0 +1,108 @@
from collections import deque
from typing import Optional, Any, Callable, Iterator
from core.events import Event, bus
class EventStore:
def __init__(self, max_size: int = 10_000):
self.buffer: deque[Event] = deque(maxlen=max_size)
self._cursor: int = 0
bus.subscribe(self._capture)
# =========================
# CAPTURE
# =========================
def _capture(self, event: Event):
event.meta["cursor"] = self._cursor
self.buffer.append(event)
self._cursor += 1
# =========================
# QUERY
# =========================
def query(
self,
source: Optional[str] = None,
level: Optional[str] = None,
name: Optional[str] = None,
start_cursor: Optional[int] = None,
end_cursor: Optional[int] = None,
) -> list[Event]:
events = list(self.buffer)
if source is not None:
events = [e for e in events if e.source == source]
if level is not None:
events = [e for e in events if e.level == level]
if name is not None:
events = [e for e in events if e.name == name]
def cursor_of(e: Event) -> int:
return e.meta.get("cursor", -1)
if start_cursor is not None:
events = [e for e in events if cursor_of(e) >= start_cursor]
if end_cursor is not None:
events = [e for e in events if cursor_of(e) <= end_cursor]
return events
# =========================
# PAGINATION
# =========================
def page(self, cursor: int = 0, limit: int = 100) -> dict[str, Any]:
events = list(self.buffer)
sliced = events[cursor:cursor + limit]
return {
"cursor": cursor,
"next_cursor": cursor + len(sliced),
"count": len(sliced),
"events": sliced
}
# =========================
# REPLAY
# =========================
def replay(
self,
source: Optional[str] = None,
level: Optional[str] = None,
name: Optional[str] = None,
handler: Optional[Callable[[Event], Any]] = None,
):
for event in self.query(source, level, name):
if handler:
handler(event)
else:
bus.emit(event)
# =========================
# REVERSE DEBUG
# =========================
def replay_reverse(self, handler: Callable[[Event], Any]):
for event in reversed(self.buffer):
handler(event)
# =========================
# STREAM TAIL
# =========================
def tail(self, last_n: int = 50) -> Iterator[Event]:
yield from list(self.buffer)[-last_n:]
# singleton
event_store = EventStore()

147
core/events.py Normal file
View File

@@ -0,0 +1,147 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable
from collections.abc import Awaitable
from datetime import datetime
import asyncio
import uuid
# =========================
# EVENT MODEL (GRAPH-AWARE)
# =========================
@dataclass
class Event:
name: str
source: str
level: str = "INFO"
data: dict[str, Any] | None = None
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
trace_id: str = field(default_factory=lambda: str(uuid.uuid4()))
parent_event_id: str | None = None
meta: dict[str, Any] = field(default_factory=dict)
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
# =========================
# EVENT BUS
# =========================
class EventBus:
def __init__(self):
self._subscribers: list[Callable[[Event], Any]] = []
self._async_subscribers: list[Callable[[Event], Awaitable[Any]]] = []
self._current_trace: str | None = None
self._current_parent: str | None = None
# -------------------------
# SUBSCRIBE
# -------------------------
def subscribe(self, fn: Callable[[Event], Any]):
self._subscribers.append(fn)
def subscribe_async(self, fn: Callable[[Event], Awaitable[Any]]):
self._async_subscribers.append(fn)
# -------------------------
# TRACE CONTROL
# -------------------------
def start_trace(self) -> str:
trace_id = str(uuid.uuid4())
self._current_trace = trace_id
self._current_parent = None
return trace_id
def set_parent(self, event_id: str | None):
self._current_parent = event_id
# -------------------------
# EMIT (CORE SAFE PATH)
# -------------------------
def emit(self, event: Event):
# inject trace
if not event.trace_id:
event.trace_id = self._current_trace or event.trace_id
event.parent_event_id = event.parent_event_id or self._current_parent
# advance causal chain
self._current_parent = event.event_id
# sync subscribers
for fn in self._subscribers:
try:
fn(event)
except Exception:
pass
# async subscribers (SAFE FIX)
if self._async_subscribers:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
return # no loop available
for fn in self._async_subscribers:
try:
coro = fn(event)
# IMPORTANT FIX:
# ensure it's actually awaitable before scheduling
if asyncio.iscoroutine(coro):
loop.create_task(coro)
except Exception:
pass
# -------------------------
# STRICT ASYNC EMIT
# -------------------------
async def emit_async(self, event: Event):
for fn in self._subscribers:
try:
fn(event)
except Exception:
pass
await asyncio.gather(
*(fn(event) for fn in self._async_subscribers),
return_exceptions=True
)
# -------------------------
# LOG CONVENIENCE
# -------------------------
def log(
self,
source: str,
name: str,
level: str = "INFO",
data: dict[str, Any] | None = None
):
self.emit(
Event(
name=name,
source=source,
level=level,
data=data or {}
)
)
# =========================
# SINGLETON
# =========================
bus = EventBus()

85
core/exception.py Normal file
View File

@@ -0,0 +1,85 @@
from dataclasses import dataclass
from typing import Any, Optional
from core.events import bus, Event
# -------------------------
# BASE ERROR
# -------------------------
@dataclass
class CXError(Exception):
code: str
message: str
module: str = "CORE"
status: int = 500
context: Optional[Any] = None
# -------------------------
# SERIALIZATION
# -------------------------
def to_dict(self):
return {
"error": True,
"code": self.code,
"message": self.message,
"module": self.module,
"status": self.status,
"context": self.context,
}
# -------------------------
# EVENT EMISSION
# -------------------------
def emit(self):
bus.emit(Event(
name="exception",
source=self.module,
level="ERROR",
data={
"code": self.code,
"message": self.message,
"status": self.status,
"context": self.context,
}
))
# -------------------------
# 4xx CLIENT / AI ERRORS
# -------------------------
class CXBadRequest(CXError):
def __init__(self, msg, module="CORE"):
super().__init__("CX400", msg, module, 400)
class CXNotFound(CXError):
def __init__(self, msg, module="FILESYSTEM"):
super().__init__("CX404", msg, module, 404)
class CXForbidden(CXError):
def __init__(self, msg, module="SECURITY"):
super().__init__("CX403", msg, module, 403)
class CXInvalidOperation(CXError):
def __init__(self, msg, module="CORE"):
super().__init__("CX422", msg, module, 422)
# -------------------------
# 5xx SYSTEM ERRORS
# -------------------------
class CXInternalError(CXError):
def __init__(self, msg, module="CORE"):
super().__init__("CX500", msg, module, 500)
class CXToolFailure(CXError):
def __init__(self, msg, module="TOOLS"):
super().__init__("CX502", msg, module, 502)

62
core/executor.py Normal file
View File

@@ -0,0 +1,62 @@
from __future__ import annotations
import asyncio
import threading
from typing import Any, TypeVar, Coroutine
T = TypeVar("T")
class LoopExecutor:
def __init__(self):
self._loop: asyncio.AbstractEventLoop | None = None
self._thread: threading.Thread | None = None
# -------------------------
# START LOOP
# -------------------------
def start(self):
if self._loop:
return
def runner():
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._loop.run_forever()
self._thread = threading.Thread(target=runner, daemon=True)
self._thread.start()
while self._loop is None:
pass
# -------------------------
# SUBMIT COROUTINE
# -------------------------
def submit(self, coro: Coroutine[Any, Any, T]):
if not self._loop:
raise RuntimeError("Executor not started")
return asyncio.run_coroutine_threadsafe(coro, self._loop)
# -------------------------
# SYNC BRIDGE
# -------------------------
def run_sync(self, coro: Coroutine[Any, Any, T]) -> T:
future = self.submit(coro)
return future.result()
# -------------------------
# STOP
# -------------------------
def stop(self):
if self._loop:
self._loop.call_soon_threadsafe(self._loop.stop)
self._loop = None
executor = LoopExecutor()

125
core/logging_core.py Normal file
View File

@@ -0,0 +1,125 @@
from __future__ import annotations
import sys
from datetime import datetime
from loguru import logger
from core.config import LOG_DIR
from core.events import Event, bus
class CXLoggerSink:
def __init__(self):
logger.remove()
# =========================
# SESSION LOG FILE
# =========================
timestamp = datetime.utcnow().strftime(
"%Y-%m-%d_%H-%M-%S"
)
self.log_path = LOG_DIR / (
f"MCP-{timestamp}.log"
)
self.log_path.parent.mkdir(
parents=True,
exist_ok=True
)
# =========================
# CONSOLE LOGGER
# =========================
logger.add(
sys.stderr,
level="DEBUG",
colorize=True,
backtrace=False,
diagnose=False,
format=(
"<level>{level:<8}</level> | "
"<cyan>{extra[module]:<12}</cyan> | "
"{time:YYYY-MM-DD HH:mm:ss} | "
"{message}"
)
)
# =========================
# FILE LOGGER
# =========================
logger.add(
str(self.log_path),
level="DEBUG",
enqueue=True,
retention="30 days",
compression="zip",
backtrace=False,
diagnose=False,
format=(
"{time:YYYY-MM-DD HH:mm:ss} | "
"{level:<8} | "
"{extra[module]:<12} | "
"{message}"
)
)
logger.bind(module="LOGGER").info(
f"Session log created: {self.log_path.name}"
)
# =========================
# EVENT HANDLER
# =========================
def handle(
self,
event: Event
):
module = event.source.upper()
level = event.level.lower()
message = event.name
if event.data:
message += (
f" | {event.data}"
)
with logger.contextualize(
module=module
):
log_method = getattr(
logger,
level,
logger.info
)
log_method(message)
# =========================
# OPTIONAL DIRECT ACCESS
# =========================
def get_log_path(self) -> str:
return str(self.log_path)
# =========================
# SINGLETON
# =========================
cxlog_sink = CXLoggerSink()
# =========================
# REGISTER TO EVENT BUS
# =========================
bus.subscribe(
cxlog_sink.handle
)

72
core/metrics.py Normal file
View File

@@ -0,0 +1,72 @@
from collections import defaultdict
from typing import Any, Optional
from datetime import datetime
from core.events import bus, Event
class CXMetrics:
def __init__(self):
self.counters = defaultdict(int)
self.events: list[dict[str, Any]] = []
self.max_events = 5000
# attach to event bus
bus.subscribe(self._on_event)
# -------------------------
# EVENT HOOK
# -------------------------
def _on_event(self, event: Event):
self.counters[f"event_{event.name}"] += 1
self.counters[f"level_{event.level}"] += 1
self.events.append({
"name": event.name,
"source": event.source,
"level": event.level,
"data": event.data,
"timestamp": event.timestamp
})
if len(self.events) > self.max_events:
self.events = self.events[-self.max_events:]
# -------------------------
# MANUAL COUNTERS
# -------------------------
def inc(self, key: str, value: int = 1):
self.counters[key] += value
def event(self, name: str, data: Optional[dict] = None):
bus.emit(Event(
name=name,
source="metrics",
level="INFO",
data=data or {}
))
# -------------------------
# SNAPSHOT (Prometheus / API)
# -------------------------
def snapshot(self) -> dict:
return {
"counters": dict(self.counters),
"recent_events": self.events[-100:],
"timestamp": datetime.utcnow().isoformat()
}
# -------------------------
# SIMPLE EXPORT FORMAT (future Prometheus hook)
# -------------------------
def prometheus_format(self) -> str:
lines = []
for k, v in self.counters.items():
lines.append(f"{k} {v}")
return "\n".join(lines)
metrics = CXMetrics()

181
core/safety.py Normal file
View File

@@ -0,0 +1,181 @@
from __future__ import annotations
from pathlib import Path
from typing import Any, Awaitable, Callable
import asyncio
from core.config import (
WORKSPACE_ROOT,
MAX_FILE_SIZE,
IGNORE_DIRS,
DRY_RUN_DEFAULT,
)
from core.events import bus, Event
from core.metrics import metrics
# =========================
# SAFETY ERROR
# =========================
class SafetyError(Exception):
def __init__(self, code: str, message: str):
super().__init__(message)
self.code = code
self.message = message
# =========================
# SAFETY ENGINE
# =========================
class SafetyEngine:
def __init__(self):
self.dry_run = DRY_RUN_DEFAULT
self.block_destructive = True
# -------------------------
# MODE CONTROL
# -------------------------
def set_dry_run(self, value: bool):
self.dry_run = value
bus.emit(Event(
name="safety_dry_run",
source="SAFETY",
level="INFO",
data={"enabled": value}
))
# -------------------------
# PATH SANDBOXING
# -------------------------
def validate_path(self, path: str) -> Path:
target = (WORKSPACE_ROOT / path).resolve()
if not str(target).startswith(str(WORKSPACE_ROOT)):
self._violation("PATH_ESCAPE", path)
raise SafetyError("PATH_ESCAPE", "Outside workspace")
for part in target.parts:
if part in IGNORE_DIRS:
self._violation("IGNORED_PATH", path)
raise SafetyError("IGNORED_PATH", f"Blocked directory: {part}")
return target
# -------------------------
# FILE SIZE GUARD
# -------------------------
def check_file_write(self, path: Path, content: str):
if len(content.encode("utf-8")) > MAX_FILE_SIZE:
self._violation("FILE_TOO_LARGE", str(path))
raise SafetyError("FILE_TOO_LARGE", "Exceeded limit")
# -------------------------
# DESTRUCTIVE OPS
# -------------------------
def allow_action(self, action: str):
blocked = {
"delete_file",
"rm_rf",
"format",
"wipe",
"drop_db",
}
if self.block_destructive and action in blocked:
self._violation("DESTRUCTIVE_BLOCK", action)
raise SafetyError("DESTRUCTIVE_BLOCK", f"Blocked: {action}")
# =========================================================
# ASYNC SAFE WRAPPER (NEW CORE ENTRYPOINT)
# =========================================================
async def wrap_async(
self,
tool_name: str,
fn: Callable[..., Any | Awaitable[Any]],
*args: Any,
**kwargs: Any,
) -> Any:
bus.emit(Event(
name="tool_start",
source="SAFETY",
level="INFO",
data={"tool": tool_name}
))
if self.dry_run:
return {
"dry_run": True,
"tool": tool_name,
"args": args,
"kwargs": kwargs,
}
try:
result = fn(*args, **kwargs)
if asyncio.iscoroutine(result):
result = await result
metrics.inc(f"tool_{tool_name}")
metrics.inc("tools_executed")
bus.emit(Event(
name="tool_success",
source="SAFETY",
level="SUCCESS",
data={"tool": tool_name}
))
return result
except SafetyError as e:
bus.emit(Event(
name="tool_blocked",
source="SAFETY",
level="ERROR",
data={"code": e.code, "message": e.message}
))
metrics.inc("safety_blocks")
return {"error": True, "code": e.code, "message": e.message}
except Exception as e:
bus.emit(Event(
name="tool_crash",
source="SAFETY",
level="ERROR",
data={"tool": tool_name, "error": str(e)}
))
metrics.inc("tool_errors")
return {"error": True, "code": "UNEXPECTED", "message": str(e)}
# -------------------------
# INTERNAL VIOLATION LOGGER
# -------------------------
def _violation(self, code: str, context: str):
bus.emit(Event(
name="safety_violation",
source="SAFETY",
level="ERROR",
data={"code": code, "context": context}
))
metrics.inc(f"safety_{code}")
# singleton
safety = SafetyEngine()

211
core/subprocess.py Normal file
View File

@@ -0,0 +1,211 @@
from __future__ import annotations
import subprocess
from pathlib import Path
from typing import Any
from core.events import bus, Event
from core.metrics import metrics
from core.safety import safety
# =========================
# PROCESS RUNNER
# =========================
def run_command(
cmd: list[str],
cwd: str | None = None,
timeout: int = 60
) -> dict[str, Any]:
# -------------------------
# VALIDATION
# -------------------------
if not cmd:
raise ValueError(
"Command list cannot be empty"
)
for item in cmd:
if not isinstance(item, str):
raise ValueError(
"cmd must contain only strings"
)
executable = cmd[0].lower()
# -------------------------
# SAFETY CHECKS
# -------------------------
blocked_commands = {
"rm",
"rmdir",
"del",
"erase",
"shutdown",
"reboot",
"mkfs",
"format",
"diskpart"
}
if executable in blocked_commands:
bus.emit(Event(
name="process_blocked",
source="SUBPROCESS",
level="ERROR",
data={
"cmd": cmd,
"reason": "blocked_command"
}
))
metrics.inc(
"subprocess_blocked"
)
return {
"ok": False,
"error": (
f"Blocked command: "
f"{executable}"
),
"code": -1
}
resolved_cwd: Path | None = None
if cwd is not None:
resolved_cwd = (
safety.validate_path(cwd)
)
# -------------------------
# START EVENT
# -------------------------
bus.emit(Event(
name="process_start",
source="SUBPROCESS",
level="INFO",
data={
"cmd": cmd,
"cwd": str(resolved_cwd)
if resolved_cwd else None,
"timeout": timeout
}
))
metrics.inc(
"subprocess_runs"
)
# -------------------------
# EXECUTE
# -------------------------
try:
result = subprocess.run(
cmd,
cwd=(
str(resolved_cwd)
if resolved_cwd
else None
),
capture_output=True,
text=True,
timeout=timeout,
check=False
)
stdout = (
result.stdout or ""
)
stderr = (
result.stderr or ""
)
ok = (
result.returncode == 0
)
# -------------------------
# COMPLETE EVENT
# -------------------------
bus.emit(Event(
name="process_complete",
source="SUBPROCESS",
level=(
"SUCCESS"
if ok
else "ERROR"
),
data={
"cmd": cmd,
"code": result.returncode,
"stdout": stdout,
"stderr": stderr
}
))
return {
"ok": ok,
"stdout": stdout,
"stderr": stderr,
"code": result.returncode
}
# -------------------------
# TIMEOUT
# -------------------------
except subprocess.TimeoutExpired:
metrics.inc(
"subprocess_timeouts"
)
bus.emit(Event(
name="process_timeout",
source="SUBPROCESS",
level="ERROR",
data={
"cmd": cmd,
"timeout": timeout
}
))
return {
"ok": False,
"error": "timeout",
"code": -1
}
# -------------------------
# UNEXPECTED ERROR
# -------------------------
except Exception as e:
metrics.inc(
"subprocess_errors"
)
bus.emit(Event(
name="process_error",
source="SUBPROCESS",
level="ERROR",
data={
"cmd": cmd,
"error": str(e)
}
))
return {
"ok": False,
"error": str(e),
"code": -1
}

93
core/tools/base.py Normal file
View File

@@ -0,0 +1,93 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Protocol
from core.events import bus
# =========================
# TOOL CONTEXT
# =========================
@dataclass
class ToolContext:
user: str | None = None
dry_run: bool = False
# safe mutable metadata container
meta: dict[str, Any] = field(default_factory=dict)
# =========================
# TOOL CONTRACT
# =========================
class Tool(Protocol):
name: str
def execute(self, payload: dict[str, Any], ctx: ToolContext) -> Any:
...
# =========================
# BASE TOOL
# =========================
class BaseTool:
name: str = "base"
# -------------------------
# LIFECYCLE WRAPPER
# -------------------------
def run(self, payload: dict[str, Any], ctx: ToolContext):
bus.log(self.name, "tool_start", "INFO", {"payload": payload})
try:
# attach execution metadata (useful for event graph later)
ctx.meta.setdefault("tool", self.name)
ctx.meta.setdefault("dry_run", ctx.dry_run)
if ctx.dry_run:
result = self.preview(payload, ctx)
else:
result = self.execute(payload, ctx)
bus.log(
self.name,
"tool_success",
"SUCCESS",
{
"result_type": type(result).__name__,
"result": repr(result)[:500] # prevent log explosion
}
)
return result
except Exception as e:
bus.log(
self.name,
"tool_error",
"ERROR",
{
"error_type": type(e).__name__,
"error": str(e)
}
)
raise
# -------------------------
# OVERRIDABLE
# -------------------------
def execute(self, payload: dict[str, Any], ctx: ToolContext):
raise NotImplementedError("Tool must implement execute()")
def preview(self, payload: dict[str, Any], ctx: ToolContext):
return {
"dry_run": True,
"tool": self.name,
"payload": payload
}

124
core/tools/registry.py Normal file
View File

@@ -0,0 +1,124 @@
from __future__ import annotations
from typing import Any
from core.tools.base import BaseTool, ToolContext
from core.events import bus
# =========================
# TOOL REGISTRY
# =========================
class ToolRegistry:
def __init__(self):
self._tools: dict[str, BaseTool] = {}
# -------------------------
# REGISTER
# -------------------------
def register(self, tool: BaseTool):
self._tools[tool.name] = tool
bus.log(
"REGISTRY",
"tool_registered",
"INFO",
{
"tool": tool.name
}
)
# -------------------------
# RESOLVE
# -------------------------
def get(self, name: str) -> BaseTool:
tool = self._tools.get(name)
if tool is None:
bus.log(
"REGISTRY",
"tool_not_found",
"ERROR",
{"tool": name}
)
raise ValueError(f"Tool not found: {name}")
return tool
# -------------------------
# EXECUTE
# -------------------------
def run(
self,
name: str,
payload: dict[str, Any],
ctx: ToolContext
):
tool = self.get(name)
# registry metadata
ctx.meta.setdefault("registry", True)
ctx.meta.setdefault("tool_name", name)
bus.log(
"REGISTRY",
"tool_dispatch",
"INFO",
{
"tool": name,
"dry_run": ctx.dry_run,
}
)
try:
result = tool.run(payload, ctx)
bus.log(
"REGISTRY",
"tool_completed",
"SUCCESS",
{
"tool": name,
"result_type": type(result).__name__
}
)
return result
except Exception as e:
bus.log(
"REGISTRY",
"tool_failed",
"ERROR",
{
"tool": name,
"error": str(e),
"error_type": type(e).__name__
}
)
raise
# -------------------------
# DISCOVERY (NEW)
# -------------------------
def all_tools(self) -> list[BaseTool]:
return list(self._tools.values())
def names(self) -> list[str]:
return list(self._tools.keys())
def exists(self, name: str) -> bool:
return name in self._tools
# =========================
# SINGLETON
# =========================
registry = ToolRegistry()

196
main.py Normal file
View File

@@ -0,0 +1,196 @@
from __future__ import annotations
from mcp.server.fastmcp import FastMCP
from core.config import (
WORKSPACE_ROOT,
API_HOST,
API_PORT,
SERVER_NAME,
)
from core.events import bus, Event
from core.metrics import metrics
from core.safety import safety
from core.executor import executor
from core.tools.registry import registry
from core.tools.base import ToolContext
from tools.discovery import load_all_tools
# =========================
# RUNTIME BOOTSTRAP
# =========================
executor.start()
# =========================
# MCP SERVER
# =========================
mcp = FastMCP(SERVER_NAME)
# =========================
# REGISTRY → MCP AUTO BIND
# =========================
def bind_registry_tools():
for tool in registry.all_tools():
tool_name = tool.name
tool_description = getattr(
tool,
"description",
f"{tool_name} tool"
)
def make_handler(bound_tool):
def handler(**kwargs):
ctx = ToolContext(
dry_run=safety.dry_run
)
return registry.run(
name=bound_tool.name,
payload=kwargs,
ctx=ctx
)
return handler
mcp.tool(
name=tool_name,
description=tool_description
)(make_handler(tool))
bus.log(
"CORE",
"mcp_tool_bound",
"INFO",
{
"tool": tool_name
}
)
# =========================
# SYSTEM BOOT
# =========================
def boot_system():
load_all_tools()
bind_registry_tools()
bus.emit(Event(
name="system_boot",
source="CORE",
level="INFO",
data={
"workspace": str(WORKSPACE_ROOT),
"server": SERVER_NAME,
"tool_count": len(registry._tools)
}
))
# =========================
# METRICS
# =========================
def get_metrics_snapshot():
return metrics.snapshot()
# =========================
# DEBUG EVENTS (OPTIONAL)
# =========================
def debug_event_printer(event: Event):
print(
f"[EVENT] "
f"{event.source} - "
f"{event.level} - "
f"{event.name}"
)
# bus.subscribe(debug_event_printer)
# =========================
# MCP RUNNER
# =========================
def run_mcp():
boot_system()
bus.emit(Event(
name="mcp_start",
source="CORE",
level="INFO",
data={
"server": SERVER_NAME
}
))
mcp.run(transport="stdio")
# =========================
# FASTAPI HOOK (ISOLATED)
# =========================
async def run_api():
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/metrics")
def metrics_route():
return get_metrics_snapshot()
@app.get("/health")
def health():
return {
"status": "ok"
}
config = uvicorn.Config(
app,
host=API_HOST,
port=API_PORT,
log_level="info"
)
server = uvicorn.Server(config)
await server.serve()
# =========================
# ENTRYPOINT
# =========================
if __name__ == "__main__":
try:
run_mcp()
except KeyboardInterrupt:
bus.emit(Event(
name="system_shutdown",
source="CORE",
level="INFO"
))
except Exception as e:
bus.emit(Event(
name="fatal_error",
source="CORE",
level="ERROR",
data={
"error": str(e)
}
))
raise

8
tests/conftest.py Normal file
View File

@@ -0,0 +1,8 @@
import pytest
from pathlib import Path
TEST_ROOT = Path(__file__).parent
@pytest.fixture
def workspace(tmp_path):
return tmp_path

95
tests/profiles.json Normal file
View File

@@ -0,0 +1,95 @@
{
"schema_version": "1.0.0",
"description": "MCP test execution profiles for safe, staged tool testing and CI orchestration.",
"default_profile": "unit",
"global": {
"timeout_seconds": 10,
"allow_network": false,
"allow_subprocess": true,
"allow_filesystem_write": false,
"allow_docker": false,
"allow_cmake": false,
"allow_qemu": false,
"allow_git_push": false,
"log_level": "info"
},
"profiles": {
"unit": {
"description": "Fast isolated tests. No side effects.",
"inherits": "global",
"overrides": {
"allow_filesystem_write": false,
"allow_network": false,
"timeout_seconds": 5
}
},
"integration": {
"description": "Tool interaction tests (http, subprocess, file system).",
"inherits": "global",
"overrides": {
"allow_filesystem_write": true,
"allow_network": true,
"timeout_seconds": 30
}
},
"dangerous": {
"description": "Full system tests. Docker, cmake, qemu allowed.",
"inherits": "global",
"overrides": {
"allow_filesystem_write": true,
"allow_network": true,
"allow_docker": true,
"allow_cmake": true,
"allow_qemu": true,
"timeout_seconds": 600
}
},
"chaos": {
"description": "Stress testing and randomness injection.",
"inherits": "dangerous",
"overrides": {
"allow_network": true,
"timeout_seconds": 120,
"enable_reruns": true,
"enable_random_order": true,
"enable_parallel": true
}
},
"ci": {
"description": "CI-safe deterministic execution.",
"inherits": "unit",
"overrides": {
"timeout_seconds": 15,
"enable_coverage": true
}
}
},
"tool_rules": {
"cmake": {
"allowed_profiles": ["dangerous", "chaos"]
},
"docker": {
"allowed_profiles": ["dangerous", "chaos"]
},
"qemu": {
"allowed_profiles": ["dangerous"]
},
"filesystem_write": {
"allowed_profiles": ["integration", "dangerous", "chaos"]
},
"git_push": {
"allowed_profiles": ["dangerous"]
},
"network": {
"allowed_profiles": ["integration", "dangerous", "chaos"]
}
}
}

23
tests/tool_types.json Normal file
View File

@@ -0,0 +1,23 @@
{
"schema_version": "1.0.0",
"tool_types": {
"filesystem": ["read_file", "write_file", "delete_file", "append_file"],
"build": ["cmake_configure", "cmake_build", "cmake_clean"],
"container": ["docker_run", "docker_exec", "docker_build"],
"vm": ["qemu_run", "bochs_run"],
"media": ["ffmpeg_transcode", "extract_audio", "resize_image"],
"network": ["gitea_push", "http_request"],
"analysis": ["search_text", "analyze_code", "workspace_stats"]
},
"risk_levels": {
"filesystem": "medium",
"build": "high",
"container": "high",
"vm": "critical",
"media": "low",
"network": "medium",
"analysis": "low"
}
}

16
tools/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
"""
Tool bootstrap.
Importing this module forces
tool registration into registry.
"""
from tools.ping import PingTool
from tools.filesystem import FilesystemTool
from tools.subprocess import SubprocessTool
__all__ = [
"PingTool",
"FilesystemTool",
"SubprocessTool",
]

0
tools/bochs.py Normal file
View File

0
tools/cmake.py Normal file
View File

22
tools/discovery.py Normal file
View File

@@ -0,0 +1,22 @@
from __future__ import annotations
import importlib
import pkgutil
import tools as tools_pkg
def load_all_tools():
"""
Explicit tool loader.
Imports all modules inside /tools so they register
themselves into the registry.
"""
for module in pkgutil.iter_modules(
tools_pkg.__path__
):
importlib.import_module(
f"tools.{module.name}"
)

0
tools/docker.py Normal file
View File

0
tools/ffmpeg.py Normal file
View File

225
tools/filesystem.py Normal file
View File

@@ -0,0 +1,225 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
from core.events import bus
from core.safety import safety
from core.tools.base import BaseTool, ToolContext
from core.tools.registry import registry
class FilesystemTool(BaseTool):
name = "filesystem"
description = "Safe filesystem operations"
# =========================
# EXECUTE
# =========================
def execute(
self,
payload: dict[str, Any],
ctx: ToolContext
):
action = str(payload.get("action", "")).strip()
bus.log(
"FILESYSTEM",
"filesystem_execute",
"INFO",
{
"action": action
}
)
match action:
case "read_file":
return self.read_file(payload)
case "write_file":
return self.write_file(payload)
case "list_dir":
return self.list_dir(payload)
case "exists":
return self.exists(payload)
case "mkdir":
return self.mkdir(payload)
case _:
raise ValueError(
f"Unknown filesystem action: {action}"
)
# =========================
# READ FILE
# =========================
def read_file(
self,
payload: dict[str, Any]
):
path_value = payload.get("path")
if not isinstance(path_value, str):
raise ValueError("path must be string")
path = safety.validate_path(path_value)
return {
"path": str(path),
"content": path.read_text(
encoding="utf-8"
)
}
# =========================
# WRITE FILE
# =========================
def write_file(
self,
payload: dict[str, Any]
):
path_value = payload.get("path")
content_value = payload.get("content")
if not isinstance(path_value, str):
raise ValueError("path must be string")
if not isinstance(content_value, str):
raise ValueError(
"content must be string"
)
path = safety.validate_path(path_value)
safety.check_file_write(
path,
content_value
)
path.parent.mkdir(
parents=True,
exist_ok=True
)
path.write_text(
content_value,
encoding="utf-8"
)
return {
"ok": True,
"path": str(path),
"bytes_written": len(
content_value.encode("utf-8")
)
}
# =========================
# LIST DIRECTORY
# =========================
def list_dir(
self,
payload: dict[str, Any]
):
path_value = payload.get(
"path",
"."
)
if not isinstance(path_value, str):
raise ValueError(
"path must be string"
)
path = safety.validate_path(
path_value
)
entries: list[dict[str, Any]] = []
for item in path.iterdir():
entries.append(
{
"name": item.name,
"path": str(item),
"is_dir": item.is_dir(),
"is_file": item.is_file()
}
)
return {
"path": str(path),
"count": len(entries),
"entries": entries
}
# =========================
# EXISTS
# =========================
def exists(
self,
payload: dict[str, Any]
):
path_value = payload.get("path")
if not isinstance(path_value, str):
raise ValueError(
"path must be string"
)
path = safety.validate_path(
path_value
)
return {
"path": str(path),
"exists": path.exists(),
"is_dir": path.is_dir(),
"is_file": path.is_file()
}
# =========================
# MKDIR
# =========================
def mkdir(
self,
payload: dict[str, Any]
):
path_value = payload.get("path")
if not isinstance(path_value, str):
raise ValueError(
"path must be string"
)
path = safety.validate_path(
path_value
)
path.mkdir(
parents=True,
exist_ok=True
)
return {
"ok": True,
"path": str(path)
}
# =========================
# SELF REGISTER
# =========================
registry.register(
FilesystemTool()
)

0
tools/git.py Normal file
View File

50
tools/gitea.py Normal file
View File

@@ -0,0 +1,50 @@
import requests
from core.config import GITEA_URL, GITEA_TOKEN
from core.logging_core import logger
HEADERS = {
"Authorization": f"token {GITEA_TOKEN}",
"Content-Type": "application/json"
}
def create_repo(name: str, private: bool = True):
url = f"{GITEA_URL}/api/v1/user/repos"
payload = {
"name": name,
"private": private,
"auto_init": True
}
r = requests.post(url, json=payload, headers=HEADERS)
logger.info(f"[GITEA] create_repo={name} status={r.status_code}")
return r.json()
import subprocess
from core.subprocess import run_command
def git_push(repo_path: str, message: str):
cmds = [
["git", "-C", repo_path, "add", "."],
["git", "-C", repo_path, "commit", "-m", message],
["git", "-C", repo_path, "push"]
]
results = [run_command(c) for c in cmds]
return results
def create_or_update_file(owner, repo, path, content, message):
url = f"{GITEA_URL}/api/v1/repos/{owner}/{repo}/contents/{path}"
payload = {
"content": content.encode("utf-8").decode("utf-8"),
"message": message
}
r = requests.post(url, json=payload, headers=HEADERS)
logger.info(f"[GITEA] file={path} status={r.status_code}")
return r.json()

45
tools/ping.py Normal file
View File

@@ -0,0 +1,45 @@
from __future__ import annotations
from core.tools.base import BaseTool, ToolContext
from core.tools.registry import registry
from core.events import bus
class PingTool(BaseTool):
name = "ping"
# optional metadata (future MCP auto-binding)
description = "Health check"
# -------------------------
# EXECUTE
# -------------------------
def execute(
self,
payload: dict[str, str],
ctx: ToolContext
):
message = payload.get("message", "pong")
bus.log(
"TOOLS",
"ping_execute",
"INFO",
{
"message": message
}
)
return {
"status": "ok",
"echo": message,
"tool": self.name
}
# =========================
# SELF REGISTER
# =========================
registry.register(PingTool())

0
tools/qemu.py Normal file
View File

66
tools/subprocess.py Normal file
View File

@@ -0,0 +1,66 @@
from __future__ import annotations
from typing import Any
from core.subprocess import run_command
from core.tools.base import BaseTool, ToolContext
from core.tools.registry import registry
class SubprocessTool(BaseTool):
name = "subprocess"
description = "Run a subprocess command safely"
# =========================
# EXECUTE
# =========================
def execute(
self,
payload: dict[str, Any],
ctx: ToolContext
):
cmd = payload.get("cmd")
if not isinstance(cmd, list):
raise ValueError(
"cmd must be list[str]"
)
cwd = payload.get("cwd")
if cwd is not None and not isinstance(
cwd,
str
):
raise ValueError(
"cwd must be string"
)
timeout = payload.get(
"timeout",
60
)
if not isinstance(
timeout,
int
):
raise ValueError(
"timeout must be int"
)
return run_command(
cmd=cmd,
cwd=cwd,
timeout=timeout
)
# =========================
# SELF REGISTER
# =========================
registry.register(
SubprocessTool()
)

0
tools/venv.py Normal file
View File