"""Руки: какие MCP есть у сетапа и кому из агентов какая рука выдана. Секреты MCP - только через env подпроцесса, никогда argv: процесс модели видит `ps` всего контейнера. Рука, чей env не задан, просто не появляется. """ from __future__ import annotations import os from pathlib import Path from beaver_gateway.agents.base import ExposedMcp from beaver_gateway.mcp.types import McpServer, McpServerT from beaver_agent.hands.calendars import calendars from beaver_agent.hands.homeassistant import HomeAssistant from beaver_agent.hands.komodo import Komodo from beaver_agent.hands.vibegram import Vibegram from beaver_agent.vault import TZ env = os.environ.get obsidian = McpServer.stdio( name="obsidian-fs", command=["bunx", "-y", "@modelcontextprotocol/server-filesystem", "/vault"], lenient=True, ) """Файловые тулзы для тех, у кого нет своих (Raycast); у Claude vault смонтирован.""" firefly = ( McpServer.stdio( name="firefly", command=["bunx", "-y", "@firefly-iii-mcp/local", "--preset", "default"], env={ "FIREFLY_III_PAT": os.environ["FIREFLY_PAT"], "FIREFLY_III_BASE_URL": os.environ["FIREFLY_BASE_URL"], }, lenient=True, ) if env("FIREFLY_PAT") else None ) firefly_exposed = (ExposedMcp(name="firefly", deny=("delete_*",)),) if firefly else () telegram = ( McpServer.http(name="telegram", url=os.environ["BEAVERGRAM_MCP"]) if env("BEAVERGRAM_MCP") else None ) """beavergram: история чатов Бобра в Telegram.""" calendar_servers = calendars(env("CALENDAR_MCPS", "")) calendar_exposed = tuple(ExposedMcp(name=m.name) for m in calendar_servers) KOMODO = ( Komodo( url=os.environ["KOMODO_URL"], key=os.environ["KOMODO_KEY"], secret=os.environ["KOMODO_SECRET"], ) if env("KOMODO_URL") else None ) """Флот: перечислимые действия, ключ остаётся в gateway.""" komodo = McpServer.python_tool(name="komodo", tools=[KOMODO.komodo]) if KOMODO else None komodo_exposed = (ExposedMcp(name="komodo"),) if KOMODO else () HA = ( HomeAssistant(url=os.environ["HA_URL"], token=os.environ["HA_TOKEN"]) if env("HA_URL") and env("HA_TOKEN") else None ) """Дом: HA живёт на хосте, из контейнера - host.docker.internal.""" ha = McpServer.python_tool(name="ha", tools=[HA.ha]) if HA else None ha_exposed = (ExposedMcp(name="ha"),) if HA else () VIBEGRAM_REPO = Path("/vibegram") / env("VIBEGRAM_REPO", "org/room").split("/")[-1] VIBEGRAM = ( Vibegram( hub=os.environ["VIBEGRAM_HUB"], token=os.environ["VIBEGRAM_TOKEN"], nick=os.environ["VIBEGRAM_NICK"], repo=VIBEGRAM_REPO if VIBEGRAM_REPO.exists() else None, tz=TZ, ) if env("VIBEGRAM_TOKEN") and env("VIBEGRAM_HUB") and env("VIBEGRAM_NICK") else None ) """Комната агентов: диспетчер сидит в ней сам; клон репы комнаты - том `vibegram`.""" vibegram = ( McpServer.python_tool(name="vibegram", tools=[VIBEGRAM.vibegram]) if VIBEGRAM else None ) vibegram_exposed = (ExposedMcp(name="vibegram"),) if VIBEGRAM else () t3code = ( McpServer.http(name="t3code", url=env("T3CODE_MCP", "http://t3code-mcp:8000/mcp")) if "t3" in env("COMPOSE_PROFILES", "").split(",") else None ) """t3code-mcp - сосед по compose (профиль t3); машины и allowlist в его t3code.toml.""" t3code_exposed = (ExposedMcp(name="t3code"),) if t3code else () msos = ( McpServer.http(name="msos", url=os.environ["MSOS_MCP"]) if env("MSOS_MCP") else None ) """Таскер рабочих проектов (скилл `задачи`).""" msos_exposed = (ExposedMcp(name="msos"),) if msos else () mcps: list[McpServerT] = [ m for m in ( obsidian, firefly, telegram, *calendar_servers, komodo, ha, vibegram, t3code, msos, ) if m is not None ] CLAUDE = ( *firefly_exposed, ExposedMcp(name="telegram"), *calendar_exposed, *komodo_exposed, *ha_exposed, ) """Руки любого Claude-агента: firefly без delete, телега, календари, флот, дом.""" DISPATCHER = (*CLAUDE, *t3code_exposed, *vibegram_exposed, *msos_exposed) """Диспетчер ещё ходит в код, в комнату агентов и в таскер; глубоким это не даётся.""" RAYCAST = ( ExposedMcp(name="obsidian-fs"), *firefly_exposed, ExposedMcp(name="telegram"), *calendar_exposed, )