Skip to content

Contract: ConfigLoader Extensions

Feature: 013-mcp-cli Date: 2025-12-13

Overview

Extensions to the existing ConfigLoader class in src/holodeck/config/loader.py to support MCP server management and merging.

Existing Infrastructure

The ConfigLoader already provides: - load_global_config() - loads ~/.holodeck/config.yaml into GlobalConfig - merge_configs() - merges agent config with global config - YAML parsing and validation

Required Modifications

1. GlobalConfig Model (src/holodeck/models/config.py)

Add new field to existing GlobalConfig class:

from holodeck.models.tool import MCPTool

class GlobalConfig(BaseModel):
    # ... existing fields ...

    mcp_servers: list[MCPTool] | None = Field(
        None, description="Global MCP server configurations"
    )

2. ConfigLoader.merge_configs() Extension

Extend the existing merge_configs() method to merge global MCP servers with agent tools:

def merge_configs(
    self, agent_config: dict[str, Any], global_config: GlobalConfig | None
) -> dict[str, Any]:
    """Merge agent config with global config using proper precedence.

    Extended to support MCP server merging:
    - Global mcp_servers are added to agent tools list
    - Agent-level MCP tools with same name override global
    """
    # ... existing merge logic ...

    # NEW: Merge global MCP servers into agent tools
    if global_config and global_config.mcp_servers:
        self._merge_mcp_servers(agent_config, global_config.mcp_servers)

    return agent_config

def _merge_mcp_servers(
    self,
    agent_config: dict[str, Any],
    global_mcp_servers: list[MCPTool],
) -> None:
    """Merge global MCP servers into agent tools.

    - Agent-level MCP tools take precedence over global
    - Global servers with unique names are added to tools list
    """
    if "tools" not in agent_config:
        agent_config["tools"] = []

    # Get names of existing MCP tools in agent config
    existing_names = {
        tool.get("name")
        for tool in agent_config["tools"]
        if isinstance(tool, dict) and tool.get("type") == "mcp"
    }

    # Add global MCP servers that don't conflict
    for server in global_mcp_servers:
        if server.name not in existing_names:
            agent_config["tools"].append(server.model_dump())

3. New Helper Methods for CLI

Add helper methods to support CLI operations (can be added to ConfigLoader or a new utility module):

def save_global_config(config: GlobalConfig, config_path: Path | None = None) -> None:
    """Save global configuration to ~/.holodeck/config.yaml.

    Creates directory if it doesn't exist.
    """
    if config_path is None:
        config_path = Path.home() / ".holodeck" / "config.yaml"

    config_path.parent.mkdir(parents=True, exist_ok=True)

    with open(config_path, "w", encoding="utf-8") as f:
        yaml.dump(config.model_dump(exclude_none=True), f, default_flow_style=False)


def add_mcp_server_to_agent(
    agent_path: Path,
    server: MCPTool,
) -> bool:
    """Add MCP server to agent.yaml tools section.

    Returns True if added, False if already exists.
    """
    # Load, modify, save agent.yaml
    ...


def remove_mcp_server_from_agent(
    agent_path: Path,
    server_name: str,
) -> bool:
    """Remove MCP server from agent.yaml tools section.

    Returns True if removed, False if not found.
    """
    # Load, modify, save agent.yaml
    ...

Merge Behavior

Scenario Behavior
Server in global only Added to agent tools at runtime
Server in agent only Used as-is
Server in both (same name) Agent config takes precedence
No tools section in agent Created with global servers

File Format Examples

Global Config (~/.holodeck/config.yaml)

providers:
  openai:
    provider: openai
    api_key: ${OPENAI_API_KEY}

mcp_servers:
  - name: filesystem
    description: "File system access"
    type: mcp
    transport: stdio
    command: npx
    args:
      - "-y"
      - "@modelcontextprotocol/server-filesystem"

Agent Config (agent.yaml)

name: my-agent
model:
  provider: openai
  name: gpt-4o

tools:
  - name: custom-tool
    type: mcp
    # ... agent-specific MCP tool

Merged Result (at runtime)

tools:
  - name: custom-tool      # From agent.yaml
    type: mcp
  - name: filesystem       # From global config (merged)
    type: mcp
    transport: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem"]