Add .gitignore, remove __pycache__ from tracking, and update MCP/orchestrator modules

- Add .gitignore to exclude .env, __pycache__, node_modules, and IDE files
- Remove all __pycache__ bytecode files from version control
- Add MCP config files (mcp.json, mcp.json.example)
- Add MCP manager, registry, and config modules
- Update routes, orchestrator engine, and agent base with latest changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordan
2026-04-01 23:47:26 +01:00
parent 91cfdaee72
commit 264acc90b4
49 changed files with 749 additions and 68 deletions

View File

@@ -13,6 +13,7 @@ from pydantic import BaseModel, Field
from ..models.context import MemoryDocument, MemoryType
from ..models.session import SessionState, SessionStatus
from ..orchestrator.engine import OrchestratorEngine
from ..streaming.sse import EventType
logger = logging.getLogger(__name__)
@@ -28,6 +29,10 @@ class CreateSessionRequest(BaseModel):
project_profile: dict[str, Any] = Field(default_factory=dict)
immutable_rules: list[str] = Field(default_factory=list)
metadata: dict[str, Any] = Field(default_factory=dict)
mcp_env: dict[str, str] = Field(
default_factory=dict,
description="Per-project env vars for MCP servers (e.g. ACAI_WEB_URL, ACAI_PROJECT_DIR)",
)
class CreateSessionResponse(BaseModel):
@@ -59,32 +64,43 @@ _deps: dict[str, Any] = {}
def set_dependencies(
storage: Any,
orchestrator: Any,
model_adapter: Any,
context_engine: Any,
memory_store: Any,
sse_emitter: Any,
context_engine: Any = None,
memory_store: Any = None,
mcp_registry: Any,
) -> None:
_deps["storage"] = storage
_deps["orchestrator"] = orchestrator
_deps["model_adapter"] = model_adapter
_deps["context_engine"] = context_engine
_deps["memory_store"] = memory_store
_deps["sse"] = sse_emitter
if context_engine:
_deps["context_engine"] = context_engine
if memory_store:
_deps["memory_store"] = memory_store
_deps["mcp_registry"] = mcp_registry
def _get_storage():
return _deps["storage"]
def _get_orchestrator():
return _deps["orchestrator"]
def _get_sse():
return _deps["sse"]
def _get_mcp_registry():
return _deps["mcp_registry"]
def _build_orchestrator(mcp_manager) -> OrchestratorEngine:
"""Build an orchestrator with a session-specific MCPManager."""
return OrchestratorEngine(
model_adapter=_deps["model_adapter"],
context_engine=_deps["context_engine"],
mcp_client=mcp_manager,
memory_store=_deps["memory_store"],
sse_emitter=_deps["sse"],
)
# ------------------------------------------------------------------
# POST /sessions
# ------------------------------------------------------------------
@@ -97,8 +113,17 @@ async def create_session(body: CreateSessionRequest) -> CreateSessionResponse:
immutable_rules=body.immutable_rules,
metadata=body.metadata,
)
# Store mcp_env in session metadata for reconnection
if body.mcp_env:
session.metadata["mcp_env"] = body.mcp_env
await storage.create_session(session)
# Start per-session MCP servers with project-specific env
registry = _get_mcp_registry()
if registry.has_config:
await registry.create_for_session(session.session_id, body.mcp_env)
sse = _get_sse()
await sse.emit(
EventType.SESSION_CREATED,
@@ -126,7 +151,16 @@ async def send_message(
if not session:
raise HTTPException(status_code=404, detail="Session not found")
orchestrator = _get_orchestrator()
# Get or create session's MCP manager
registry = _get_mcp_registry()
mcp_manager = registry.get_for_session(session_id)
if not mcp_manager and registry.has_config:
# Reconnect MCP (e.g. after server restart)
mcp_env = session.metadata.get("mcp_env", {})
mcp_manager = await registry.create_for_session(session_id, mcp_env)
from ..mcp.manager import MCPManager
orchestrator = _build_orchestrator(mcp_manager or MCPManager())
if body.stream:
asyncio.create_task(_execute_and_persist(orchestrator, storage, session, body.message))
@@ -225,6 +259,10 @@ async def delete_session(session_id: str) -> dict[str, str]:
if not deleted:
raise HTTPException(status_code=404, detail="Session not found")
# Stop session's MCP servers
registry = _get_mcp_registry()
await registry.destroy_for_session(session_id)
sse = _get_sse()
sse.cleanup_session(session_id)
@@ -373,3 +411,28 @@ async def delete_knowledge(doc_id: str) -> dict[str, str]:
if not deleted:
raise HTTPException(status_code=404, detail="Document not found")
return {"status": "deleted", "id": doc_id}
# ------------------------------------------------------------------
# MCP Management
# ------------------------------------------------------------------
@router.get("/mcp/status")
async def mcp_status() -> dict[str, Any]:
"""Status of all MCP servers across sessions."""
registry = _get_mcp_registry()
return registry.get_status()
@router.post("/mcp/reload")
async def mcp_reload() -> dict[str, Any]:
"""Hot-reload MCP config template (does not affect running sessions)."""
registry = _get_mcp_registry()
try:
registry.load_config()
return {
"status": "reloaded",
"servers": registry.server_names,
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))