#!/usr/bin/env python3 """Operator-owned tool MCP registry, validation, and Codex rendering. Tool MCP servers expose third-party tools to Codex. They are deliberately separate from the MMO Agent MCP server (``mmo_mesh``), which launches and supervises profile participants. """ from __future__ import annotations import math import os import re import shutil from collections.abc import Mapping from pathlib import Path from typing import Any from urllib.parse import urlsplit from mmo_util import ( config_root, parse_env_file, read_toml, valid_absolute_uri, valid_http_header_value, validate_id, ) from mmo_version import MMO_SCHEMA_VERSION TOOL_MCP_RESERVED_IDS = {"mmo_mesh"} TOOL_MCP_TRANSPORTS = {"stdio", "streamable_http"} TOOL_MCP_APPROVAL_MODES = {"auto", "prompt", "writes", "approve"} _DOCUMENT_FIELDS = {"schema_version", "tool_mcp_servers"} _COMMON_SERVER_FIELDS = { "transport", "enabled_tools", "default_tools_approval_mode", "startup_timeout_sec", "tool_timeout_sec", "supports_parallel_tool_calls", "tools", } _STDIO_FIELDS = {"command", "args", "env", "env_vars", "cwd"} _HTTP_FIELDS = { "url", "bearer_token_env_var", "http_headers", "env_http_headers", } _OAUTH_FIELDS = {"auth", "scopes", "oauth", "oauth_resource", "environment_id"} _REGISTRY_OWNED_POLICY_FIELDS = {"enabled", "required", "disabled_tools"} _TOOL_FIELDS = {"approval_mode"} _ENVIRONMENT_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") _HEADER_NAME = re.compile(r"[!#$%&'*+.^_`|~0-9A-Za-z-]+") _SENSITIVE_ENVIRONMENT_NAME = re.compile( r"(?:^|_)(?:API_?KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|COOKIE|AUTH|PRIVATE)(?:_|$)", re.IGNORECASE, ) _SENSITIVE_HEADER_NAME = re.compile( r"(?:^|-)(?:authorization|cookie|api-?key|key|token|secret|password|passwd|" r"credential|auth|private)(?:-|$)", re.IGNORECASE, ) def tool_mcp_registry_root() -> Path: """Return the operator-owned directory containing registry fragments.""" return config_root() / "tool-mcp.d" def _reject_unknown_fields(value: Mapping[str, Any], allowed: set[str], label: str) -> None: unknown = sorted(set(value) - allowed) if unknown: raise ValueError(f"{label} has unknown fields: {', '.join(unknown)}") def _nonempty_string(value: Any, label: str) -> str: if not isinstance(value, str) or not value.strip(): raise ValueError(f"{label} must be a non-empty string") if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value): raise ValueError(f"{label} cannot contain control characters") return value def _string_list(value: Any, label: str, *, allow_empty: bool) -> list[str]: if not isinstance(value, list) or not all(isinstance(item, str) for item in value): raise ValueError(f"{label} must be an array of strings") if not allow_empty and not value: raise ValueError(f"{label} cannot be empty") if any(not item.strip() for item in value): raise ValueError(f"{label} cannot contain empty strings") if len(value) != len(set(value)): raise ValueError(f"{label} cannot contain duplicates") return list(value) def _environment_name(value: Any, label: str) -> str: if not isinstance(value, str) or not _ENVIRONMENT_NAME.fullmatch(value): raise ValueError(f"{label} must be a valid environment variable name") return value def _positive_seconds(value: Any, label: str) -> float: if ( not isinstance(value, (int, float)) or isinstance(value, bool) or not math.isfinite(float(value)) or not 0 < float(value) <= 172_800 ): raise ValueError(f"{label} must be a finite number between 0 and 172800") return float(value) def _string_map(value: Any, label: str) -> dict[str, str]: if not isinstance(value, Mapping): raise ValueError(f"{label} must be a table of strings") result: dict[str, str] = {} for raw_key, raw_value in value.items(): if not isinstance(raw_key, str) or not isinstance(raw_value, str): raise ValueError(f"{label} must be a table of strings") result[raw_key] = raw_value return result def _validate_stdio_environment(value: Any, label: str) -> dict[str, str]: environment = _string_map(value, label) result: dict[str, str] = {} for name, raw_value in environment.items(): _environment_name(name, f"{label} key") if _SENSITIVE_ENVIRONMENT_NAME.search(name): raise ValueError( f"{label}.{name} looks credential-bearing; use env_vars and credentials.env" ) if "\x00" in raw_value: raise ValueError(f"{label}.{name} cannot contain NUL") result[name] = raw_value return dict(sorted(result.items())) def _validate_headers(value: Any, label: str, *, environment_backed: bool) -> dict[str, str]: headers = _string_map(value, label) result: dict[str, str] = {} seen: set[str] = set() for raw_name, raw_value in headers.items(): if not _HEADER_NAME.fullmatch(raw_name): raise ValueError(f"{label} contains an invalid HTTP header name: {raw_name!r}") normalized = raw_name.lower() if normalized in seen: raise ValueError(f"{label} contains duplicate case-insensitive header {raw_name!r}") seen.add(normalized) if environment_backed: _environment_name(raw_value, f"{label}.{raw_name}") else: if _SENSITIVE_HEADER_NAME.search(raw_name): raise ValueError( f"{label}.{raw_name} is credential-bearing; use bearer_token_env_var " "or env_http_headers" ) if not valid_http_header_value(raw_value): raise ValueError(f"{label}.{raw_name} contains an invalid HTTP header value") result[raw_name] = raw_value return dict(sorted(result.items(), key=lambda item: item[0].lower())) def _validate_http_url(value: Any, label: str) -> str: url = _nonempty_string(value, label) if not valid_absolute_uri(url): raise ValueError(f"{label} must be a valid HTTP(S) URL") try: parsed = urlsplit(url) _ = parsed.port except ValueError as exc: raise ValueError(f"{label} must be a valid HTTP(S) URL") from exc if parsed.scheme.lower() not in {"http", "https"} or not parsed.hostname: raise ValueError(f"{label} must be a valid HTTP(S) URL") if parsed.username is not None or parsed.password is not None: raise ValueError(f"{label} cannot contain embedded credentials") if parsed.fragment: raise ValueError(f"{label} cannot contain a URL fragment") return url def _validate_stdio_command(value: Any, label: str) -> str: command = _nonempty_string(value, label) if "/" not in command and not command.startswith("~"): return command try: path = Path(command).expanduser() except RuntimeError as exc: raise ValueError(f"{label} has an unknown home-directory user") from exc if not path.is_absolute(): raise ValueError(f"{label} must be a PATH executable name or an absolute path") return str(path.resolve()) def _validate_tool_policy( value: Any, *, label: str, enabled_tools: set[str], ) -> dict[str, dict[str, str]]: if value is None: return {} if not isinstance(value, Mapping): raise ValueError(f"{label} must be a table") result: dict[str, dict[str, str]] = {} for raw_name, raw_policy in value.items(): tool_name = _nonempty_string(raw_name, f"{label} tool name") if tool_name not in enabled_tools: raise ValueError(f"{label}.{tool_name} is not present in enabled_tools") if not isinstance(raw_policy, Mapping): raise ValueError(f"{label}.{tool_name} must be a table") _reject_unknown_fields(raw_policy, _TOOL_FIELDS, f"{label}.{tool_name}") approval = raw_policy.get("approval_mode") if not isinstance(approval, str) or approval not in TOOL_MCP_APPROVAL_MODES: raise ValueError( f"{label}.{tool_name}.approval_mode must be one of " f"{sorted(TOOL_MCP_APPROVAL_MODES)}" ) result[tool_name] = {"approval_mode": approval} return dict(sorted(result.items())) def validate_tool_mcp_server(server_id: str, value: Any, *, label: str) -> dict[str, Any]: """Validate and normalize one operator-defined tool MCP server.""" server_id = validate_id(server_id, "tool MCP server id") if server_id in TOOL_MCP_RESERVED_IDS: raise ValueError(f"tool MCP server id {server_id!r} is reserved for Agent MCP") if not isinstance(value, Mapping): raise ValueError(f"{label} must be a table") if "bearer_token" in value: raise ValueError(f"{label}.bearer_token is forbidden; use bearer_token_env_var") oauth_fields = sorted(set(value) & _OAUTH_FIELDS) if oauth_fields: raise ValueError( f"{label} uses unsupported OAuth fields: {', '.join(oauth_fields)}; " "the Tool MCP registry supports environment-backed authentication only" ) policy_fields = sorted(set(value) & _REGISTRY_OWNED_POLICY_FIELDS) if policy_fields: raise ValueError(f"{label} cannot set agent-owned fields: {', '.join(policy_fields)}") transport = value.get("transport") if not isinstance(transport, str) or transport not in TOOL_MCP_TRANSPORTS: raise ValueError(f"{label}.transport must be one of {sorted(TOOL_MCP_TRANSPORTS)}") allowed = _COMMON_SERVER_FIELDS | (_STDIO_FIELDS if transport == "stdio" else _HTTP_FIELDS) _reject_unknown_fields(value, allowed, label) enabled_tools = _string_list( value.get("enabled_tools"), f"{label}.enabled_tools", allow_empty=False ) approval = value.get("default_tools_approval_mode") if not isinstance(approval, str) or approval not in TOOL_MCP_APPROVAL_MODES: raise ValueError( f"{label}.default_tools_approval_mode must be one of {sorted(TOOL_MCP_APPROVAL_MODES)}" ) normalized: dict[str, Any] = { "transport": transport, "enabled_tools": enabled_tools, "default_tools_approval_mode": approval, "supports_parallel_tool_calls": False, } if "supports_parallel_tool_calls" in value: if not isinstance(value["supports_parallel_tool_calls"], bool): raise ValueError(f"{label}.supports_parallel_tool_calls must be boolean") normalized["supports_parallel_tool_calls"] = value["supports_parallel_tool_calls"] for timeout_field in ("startup_timeout_sec", "tool_timeout_sec"): if timeout_field in value: normalized[timeout_field] = _positive_seconds( value[timeout_field], f"{label}.{timeout_field}" ) normalized["tools"] = _validate_tool_policy( value.get("tools"), label=f"{label}.tools", enabled_tools=set(enabled_tools), ) if transport == "stdio": normalized["command"] = _validate_stdio_command(value.get("command"), f"{label}.command") normalized["args"] = _string_list(value.get("args", []), f"{label}.args", allow_empty=True) if any("\x00" in argument for argument in normalized["args"]): raise ValueError(f"{label}.args cannot contain NUL") normalized["env"] = _validate_stdio_environment(value.get("env", {}), f"{label}.env") env_vars = _string_list(value.get("env_vars", []), f"{label}.env_vars", allow_empty=True) for name in env_vars: _environment_name(name, f"{label}.env_vars") overlap = sorted(set(normalized["env"]) & set(env_vars)) if overlap: raise ValueError( f"{label} defines the same variables in env and env_vars: {', '.join(overlap)}" ) normalized["env_vars"] = env_vars if "cwd" in value: raw_cwd = _nonempty_string(value["cwd"], f"{label}.cwd") try: cwd = Path(raw_cwd).expanduser() except RuntimeError as exc: raise ValueError(f"{label}.cwd has an unknown home-directory user") from exc if not cwd.is_absolute(): raise ValueError(f"{label}.cwd must expand to an absolute path") normalized["cwd"] = str(cwd.resolve()) else: normalized["url"] = _validate_http_url(value.get("url"), f"{label}.url") if "bearer_token_env_var" in value: normalized["bearer_token_env_var"] = _environment_name( value["bearer_token_env_var"], f"{label}.bearer_token_env_var" ) normalized["http_headers"] = _validate_headers( value.get("http_headers", {}), f"{label}.http_headers", environment_backed=False, ) normalized["env_http_headers"] = _validate_headers( value.get("env_http_headers", {}), f"{label}.env_http_headers", environment_backed=True, ) static_names = {name.lower() for name in normalized["http_headers"]} environment_names = {name.lower() for name in normalized["env_http_headers"]} overlap = sorted(static_names & environment_names) if overlap: raise ValueError( f"{label} defines headers in both http_headers and env_http_headers: " + ", ".join(overlap) ) return normalized def _load_tool_mcp_fragment(path: Path) -> dict[str, dict[str, Any]]: data = read_toml(path) _reject_unknown_fields(data, _DOCUMENT_FIELDS, f"tool MCP registry {path}") schema = data.get("schema_version") if not isinstance(schema, int) or isinstance(schema, bool) or schema != MMO_SCHEMA_VERSION: raise ValueError(f"unsupported tool MCP registry schema_version in {path}") raw_servers = data.get("tool_mcp_servers", {}) if not isinstance(raw_servers, Mapping): raise ValueError(f"tool_mcp_servers must be a table in {path}") return { server_id: validate_tool_mcp_server( server_id, server, label=f"tool MCP server {server_id} in {path}", ) for server_id, server in raw_servers.items() } def load_tool_mcp_registry_with_sources() -> tuple[dict[str, dict[str, Any]], dict[str, str]]: """Load deterministic operator fragments, replacing duplicate entries whole.""" registry: dict[str, dict[str, Any]] = {} sources: dict[str, str] = {} root = tool_mcp_registry_root() if root.is_dir(): for path in sorted(root.glob("*.toml")): for server_id, server in _load_tool_mcp_fragment(path).items(): registry[server_id] = server sources[server_id] = str(path) return dict(sorted(registry.items())), dict(sorted(sources.items())) def load_tool_mcp_registry() -> dict[str, dict[str, Any]]: return load_tool_mcp_registry_with_sources()[0] def validate_tool_mcp_grants( value: Any, registry: Mapping[str, Mapping[str, Any]], *, label: str, ) -> dict[str, dict[str, Any]]: """Validate an agent's required/optional grants against operator maxima.""" if value is None: return {} if not isinstance(value, Mapping): raise ValueError(f"{label} must be a table") result: dict[str, dict[str, Any]] = {} for raw_server_id, raw_grant in value.items(): server_id = validate_id(raw_server_id, "tool MCP server id") if server_id not in registry: raise ValueError( f"{label}.{server_id} references an undefined operator tool MCP server; " f"define it under {tool_mcp_registry_root()}" ) if not isinstance(raw_grant, Mapping): raise ValueError(f"{label}.{server_id} must be a table") _reject_unknown_fields( raw_grant, {"required", "enabled_tools"}, f"{label}.{server_id}", ) required = raw_grant.get("required", True) if not isinstance(required, bool): raise ValueError(f"{label}.{server_id}.required must be boolean") maximum = list(registry[server_id]["enabled_tools"]) selected = ( maximum if "enabled_tools" not in raw_grant else _string_list( raw_grant["enabled_tools"], f"{label}.{server_id}.enabled_tools", allow_empty=False, ) ) outside_maximum = sorted(set(selected) - set(maximum)) if outside_maximum: raise ValueError( f"{label}.{server_id}.enabled_tools exceeds the operator allowlist: " + ", ".join(outside_maximum) ) result[server_id] = {"required": required, "enabled_tools": selected} return dict(sorted(result.items())) def tool_mcp_environment_names(server: Mapping[str, Any]) -> list[str]: """Return process environment names referenced by a normalized definition.""" names: set[str] = set() if server["transport"] == "stdio": names.update(str(name) for name in server.get("env_vars", [])) else: names.update(tool_mcp_http_environment_names(server)) return sorted(names) def tool_mcp_http_environment_names(server: Mapping[str, Any]) -> list[str]: """Return environment names whose values become HTTP header material.""" if server["transport"] != "streamable_http": return [] names = {str(name) for name in server.get("env_http_headers", {}).values()} bearer = server.get("bearer_token_env_var") if bearer: names.add(str(bearer)) return sorted(names) def codex_tool_mcp_server_config( server: Mapping[str, Any], grant: Mapping[str, Any] | None, ) -> dict[str, Any]: """Compile one normalized definition/grant to Codex's mcp_servers shape.""" transport = str(server["transport"]) fields = _STDIO_FIELDS if transport == "stdio" else _HTTP_FIELDS result = { key: server[key] for key in sorted(fields) if key in server and server[key] not in ({}, []) } maximum = list(server["enabled_tools"]) selected = set(grant["enabled_tools"] if grant is not None else ()) result.update( { "enabled": grant is not None, "required": bool(grant["required"]) if grant is not None else False, "supports_parallel_tool_calls": bool(server["supports_parallel_tool_calls"]), "enabled_tools": maximum, # Always emit this array so a native role can clear a narrower # deny-list inherited from its parent config layer. "disabled_tools": [tool for tool in maximum if tool not in selected], "default_tools_approval_mode": server["default_tools_approval_mode"], } ) for timeout_field in ("startup_timeout_sec", "tool_timeout_sec"): if timeout_field in server: result[timeout_field] = server[timeout_field] if server.get("tools"): result["tools"] = server["tools"] return result def tool_mcp_readiness( server_id: str, server: Mapping[str, Any], *, sources: Mapping[str, str] | None = None, ) -> dict[str, Any]: """Return non-launching command, cwd, and environment readiness.""" values = parse_env_file(config_root() / "credentials.env") values.update({key: value for key, value in os.environ.items() if value}) http_environment = set(tool_mcp_http_environment_names(server)) environment: dict[str, bool] = {} invalid_environment: list[str] = [] for name in tool_mcp_environment_names(server): value = values.get(name) valid = bool(value) and "\x00" not in str(value) if valid and name in http_environment: valid = valid_http_header_value(str(value)) environment[name] = valid if value and not valid: invalid_environment.append(name) result: dict[str, Any] = { "id": server_id, "source": (sources or {}).get(server_id), "transport": server["transport"], "enabled_tools": list(server["enabled_tools"]), "default_tools_approval_mode": server["default_tools_approval_mode"], "environment": environment, "invalid_environment": invalid_environment, } if server["transport"] == "stdio": command = str(server["command"]) command_path = Path(command).expanduser() resolved = ( str(command_path.resolve()) if "/" in command and command_path.is_file() and os.access(command_path, os.X_OK) else shutil.which(command) ) result["command"] = command result["resolved_command"] = resolved cwd = server.get("cwd") result["cwd"] = cwd result["cwd_ready"] = cwd is None or Path(str(cwd)).is_dir() result["transport_ready"] = bool(resolved) and bool(result["cwd_ready"]) else: result["url"] = server["url"] result["transport_ready"] = True result["environment_ready"] = all(environment.values()) result["ready"] = bool(result["transport_ready"]) and bool(result["environment_ready"]) return result