diff --git a/agent/agent.py b/agent/agent.py index 55ce7ae..1ff31f2 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -70,48 +70,54 @@ SYSTEM_PROMPT = textwrap.dedent("""\ """) -# ── MCP servers (web access + any extra configured servers) ───────────────── +# ── MCP toolsets (web access + any extra configured servers) ──────────────── -def build_mcp_servers() -> list[mcp.MCPServer]: - """Build the list of MCP servers to attach to the agent. +def build_mcp_toolsets() -> list[mcp.MCPToolset]: + """Build the list of MCP toolsets to attach to the agent. Always includes the local web-access server (Firecrawl-backed search/scrape) when WEB_MCP_ENABLED is true. Additional servers can be configured via the EXTRA_MCP_SERVERS env var (JSON list of {url, transport} objects). """ - servers: list[mcp.MCPServer] = [] + toolsets: list[mcp.MCPToolset] = [] if WEB_MCP_ENABLED: python_bin = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".venv", "bin", "python") web_mcp_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web_mcp.py") - servers.append( - mcp.MCPServerStdio( - command=python_bin, - args=[web_mcp_script], - env={**os.environ, "FIRECRAWL_BASE": FIRECRAWL_BASE}, - client_session_timeout_seconds=120, + toolsets.append( + mcp.MCPToolset( + id="web-access", + mcp_server=mcp.MCPServerStdio( + command=python_bin, + args=[web_mcp_script], + env={**os.environ, "FIRECRAWL_BASE": FIRECRAWL_BASE}, + client_session_timeout_seconds=120, + ), ) ) - logger.info("Web-access MCP server enabled (Firecrawl at %s)", FIRECRAWL_BASE) + logger.info("Web-access MCP toolset enabled (Firecrawl at %s)", FIRECRAWL_BASE) extra = os.environ.get("EXTRA_MCP_SERVERS", "") if extra: try: - for entry in json.loads(extra): + for i, entry in enumerate(json.loads(extra)): url = entry.get("url", "") transport = entry.get("transport") # "sse" | "streamable_http" | None (auto) - servers.append( - mcp.MCPServerHTTP( - url=url, - transport_type=transport, - client_session_timeout_seconds=120, + toolsets.append( + mcp.MCPToolset( + id=f"extra-{i}", + mcp_server=mcp.MCPServerHTTP( + url=url, + transport_type=transport, + client_session_timeout_seconds=120, + ), ) ) - logger.info("Extra MCP server: %s (%s)", url, transport or "auto") + logger.info("Extra MCP toolset: %s (%s)", url, transport or "auto") except (json.JSONDecodeError, TypeError) as e: logger.error("Failed to parse EXTRA_MCP_SERVERS: %s", e) - return servers + return toolsets class VoiceAssistant(Agent): @@ -125,7 +131,7 @@ class VoiceAssistant(Agent): api_key=GEMMA_API_KEY, ), instructions=SYSTEM_PROMPT, - mcp_servers=build_mcp_servers(), + tools=build_mcp_toolsets(), ) @@ -219,6 +225,11 @@ async def handle_job(ctx: JobContext) -> None: agent=VoiceAssistant(), room=ctx.room, room_options=room_io.RoomOptions( + # Keep the agent in the room when a participant leaves; it must + # survive page reloads/reconnects, otherwise the next join lands + # in an agent-less room (LiveKit does not reliably re-dispatch + # into an existing room). + close_on_disconnect=False, audio_input=room_io.AudioInputOptions( # No noise cancellation plugin (self-hosted, no ai-coustics) ), diff --git a/supervisord.conf b/supervisord.conf index 7d6f017..4b7ca64 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -27,7 +27,9 @@ environment= AZURE_TTS_VOICE="%(ENV_AZURE_TTS_VOICE)s", GEMMA_BASE_URL="%(ENV_GEMMA_BASE_URL)s", GEMMA_MODEL="%(ENV_GEMMA_MODEL)s", - GEMMA_API_KEY="%(ENV_GEMMA_API_KEY)s" + GEMMA_API_KEY="%(ENV_GEMMA_API_KEY)s", + WEB_MCP_ENABLED="%(ENV_WEB_MCP_ENABLED)s", + FIRECRAWL_BASE="%(ENV_FIRECRAWL_BASE)s" [program:web] command=/usr/sbin/nginx -g "daemon off;"