"""FastMCP stdio server — MCP entry point for interns.""" from __future__ import annotations import os from pathlib import Path from typing import Any from dotenv import load_dotenv from fastmcp import FastMCP from .client import ClientPool from .registry import load_config, load_interns from .safety import check_paths # Resolve config path relative to .common root; secrets live outside the git tree. _COMMON_ROOT = Path(__file__).resolve().parents[3] # lib/interns-mcp/interns_mcp/ → .common/ CONFIG_PATH = _COMMON_ROOT / "config" / "interns" / "config.yaml" SECRETS_PATH = Path( os.environ.get("INTERNS_SECRETS_PATH") or (Path.home() / ".config" / "projects-secrets" / "interns.env") ) mcp = FastMCP("interns") def _get_api_key(endpoint_name: str, endpoints: dict[str, Any]) -> str: env_var = endpoints[endpoint_name]["api_key_env"] key = os.environ.get(env_var, "") if not key: raise RuntimeError(f"Missing API key: set {env_var} in {SECRETS_PATH}") return key def _build_extra_body(endpoint_cfg: dict[str, Any]) -> dict[str, Any]: return endpoint_cfg.get("request_defaults", {}).get("extra_body", {}) def register_tool(intern: Any, client_pool: ClientPool, endpoints: dict[str, Any]) -> None: """Register a single intern as an MCP tool.""" @mcp.tool(name=intern.id, description=intern.description) def intern_tool(paths: list[str], question: str, max_tokens: int | None = None) -> dict: if intern.safety: blocked = check_paths(paths) if blocked: return {"blocked": True, "path": blocked[0]["path"], "pattern": blocked[0]["pattern"], "reason": blocked[0]["reason"]} # Wire endpoint client if intern needs one. if intern.endpoint and intern.client is None: ep_cfg = endpoints[intern.endpoint] api_key = _get_api_key(intern.endpoint, endpoints) intern.client = client_pool.get(intern.endpoint, ep_cfg, api_key) kwargs: dict[str, Any] = {} if intern.endpoint and intern.endpoint in endpoints: kwargs["extra_body"] = _build_extra_body(endpoints[intern.endpoint]) if max_tokens is not None: kwargs["max_tokens"] = max_tokens result = intern.run(paths, question, **kwargs) if isinstance(result, dict) and result.get("blocked"): return result return {"text": result.text, "usage": result.usage} def register_grep_audit_tool(intern: Any) -> None: """Register grep_audit with its distinct (paths, patterns, output, case_sensitive) shape. Deterministic: no LLM client wiring path. Safety gate still applies — server is the single trust boundary regardless of whether the intern hits an endpoint. """ @mcp.tool(name=intern.id, description=intern.description) def grep_audit_tool( paths: list[str], patterns: list[Any], output: str = "table", case_sensitive: bool = True, ) -> dict: if intern.safety: blocked = check_paths(paths) if blocked: return {"blocked": True, "path": blocked[0]["path"], "pattern": blocked[0]["pattern"], "reason": blocked[0]["reason"]} result = intern.run( paths=paths, patterns=patterns, output=output, case_sensitive=case_sensitive, ) return {"text": result.text, "usage": result.usage} def main() -> None: load_dotenv(SECRETS_PATH) config = load_config(CONFIG_PATH) endpoints = config.get("endpoints", {}) pool = ClientPool() from . import safety as _safety_mod interns = load_interns(config, safety=_safety_mod, client=None) for intern in interns: if intern.id == "grep_audit": register_grep_audit_tool(intern) else: register_tool(intern, pool, endpoints) mcp.run(transport="stdio") if __name__ == "__main__": main()