Source code for ralph.config.mcp_server_spec

"""MCP server configuration model for `mcp.toml`."""

from __future__ import annotations

from typing import Literal, Self

from pydantic import ConfigDict, Field, model_validator

from ralph.mcp.tools.names import RALPH_MCP_SERVER_NAME
from ralph.pydantic_compat import RalphBaseModel


[docs] class McpServerSpec(RalphBaseModel): """Schema for a single MCP server entry in `mcp.toml`.""" model_config = ConfigDict(frozen=True) name: str = Field(..., pattern=r"^[a-z][a-z0-9_-]*$") transport: Literal["http", "stdio"] url: str | None = None command: str | None = None args: list[str] = Field(default_factory=list) env: dict[str, str] = Field(default_factory=dict) chains: list[str] | None = Field( default=None, description=( "Reserved for v2; MUST be None in v1 (schema-forward-compatible, runtime ignores)." ), ) @model_validator(mode="after") def validate_name(self) -> Self: if self.name == RALPH_MCP_SERVER_NAME: raise ValueError(f"server name '{RALPH_MCP_SERVER_NAME}' is reserved") if "__" in self.name: raise ValueError("server name must not contain '__'") return self @model_validator(mode="after") def validate_transport_fields(self) -> Self: if self.transport == "http": if not self.url: raise ValueError("http transport requires url") if self.command: raise ValueError("http transport must not set command") if self.args: raise ValueError("http transport must not set args") elif self.transport == "stdio": if not self.command: raise ValueError("stdio transport requires command") if self.url: raise ValueError("stdio transport must not set url") return self
__all__ = ["McpServerSpec"]