Skip to content

Research: MCP CLI Command Group

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

MCP Registry API

Decision: Use Official MCP Registry API v0.1

Rationale: The official MCP registry at https://registry.modelcontextprotocol.io provides a well-documented REST API following OpenAPI specifications. No authentication required for read operations.

Alternatives Considered: - Custom registry: Rejected - unnecessary complexity, MCP ecosystem uses official registry - Local server list only: Rejected - would defeat the purpose of discovery

API Endpoints

Endpoint Method Purpose
/v0.1/servers GET List/search servers (paginated)
/v0.1/servers/{name}/versions GET List versions of a server
/v0.1/servers/{name}/versions/{version} GET Get specific server version

Query Parameters (for /v0.1/servers)

Parameter Type Description
search string Filter by server name (substring match)
cursor string Pagination cursor (format: {name}:{version})
limit integer Max items per page

Response Structure

{
  "servers": [
    {
      "server": {
        "name": "io.github.user/server-name",
        "description": "Server description",
        "title": "Human-readable title",
        "version": "1.0.0",
        "repository": {
          "url": "https://github.com/user/repo",
          "source": "github"
        },
        "packages": [
          {
            "registryType": "npm|pypi|docker",
            "identifier": "package-name",
            "version": "1.0.0",
            "transport": {
              "type": "stdio|sse|http"
            },
            "environmentVariables": [
              {
                "name": "API_KEY",
                "description": "Required API key"
              }
            ]
          }
        ]
      },
      "_meta": {
        "io.modelcontextprotocol.registry/official": {
          "status": "active|deprecated",
          "publishedAt": "2025-01-01T00:00:00Z",
          "isLatest": true
        }
      }
    }
  ],
  "metadata": {
    "nextCursor": "io.github.user/next:1.0.0",
    "count": 25
  }
}

YAML Modification Strategy

Decision: Use ruamel.yaml for Comment Preservation

Rationale: PyYAML does not preserve comments. The ruamel.yaml library maintains comments and formatting when modifying YAML files. However, since HoloDeck already uses PyYAML extensively, we will use PyYAML but document that comments may be lost during modification.

Alternatives Considered: - ruamel.yaml: Would preserve comments but adds dependency - Custom YAML parser: Too complex, not worth the effort - PyYAML with manual comment extraction: Fragile and error-prone

Decision Update: Use PyYAML (existing dependency) with clear documentation that YAML modifications may not preserve comments. This is acceptable for CLI tool usage.

Global Config Structure

Decision: Add mcp_servers Field to Existing GlobalConfig

Rationale: The GlobalConfig model already exists in src/holodeck/models/config.py with fields for providers, vectorstores, execution, and deployment. The ConfigLoader in src/holodeck/config/loader.py already handles loading and merging global config. We extend this infrastructure rather than creating new components.

Changes Required: 1. Add mcp_servers: list[MCPTool] | None field to GlobalConfig model 2. Extend ConfigLoader.merge_configs() to merge global MCP servers with agent tools

# ~/.holodeck/config.yaml (extended structure)
providers:
  openai:
    provider: openai
    api_key: ${OPENAI_API_KEY}

vectorstores:
  default:
    provider: postgres
    connection_string: ${DATABASE_URL}

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

  - name: github
    description: "GitHub API access"
    type: mcp
    transport: stdio
    command: npx
    args:
      - "-y"
      - "@modelcontextprotocol/server-github"
    env:
      GITHUB_TOKEN: "${GITHUB_TOKEN}"

Merge Behavior

When loading agent configuration: 1. Load global mcp_servers from ~/.holodeck/config.yaml 2. Load agent tools from agent.yaml 3. For MCP tools with matching names: agent config takes precedence 4. For unique MCP tools: combine both lists

Transport Type Mapping

Decision: Map Registry Transport to MCPTool Model

Registry Transport MCPTool Transport Command
stdio TransportType.STDIO From package registryType
sse TransportType.SSE N/A (uses URL)
streamable-http TransportType.HTTP N/A (uses URL)

Command Mapping by Registry Type

registryType CommandType Args Pattern
npm CommandType.NPX ["-y", "{identifier}@{version}"]
pypi CommandType.UVX ["{identifier}"]
docker CommandType.DOCKER ["run", "-i", "{identifier}:{version}"]

Error Handling Strategy

Decision: Fail Fast with Actionable Messages

Rationale: CLI tools should provide immediate, clear feedback. 5-second timeout prevents hanging on slow networks.

Error Type User Message
Network timeout "Registry unavailable. Check your connection and try again."
404 Not Found "Server '{name}' not found in registry."
Invalid YAML "Invalid YAML in {file}: {details}. File not modified."
Server already exists "Server '{name}' already configured in {location}."
No agent.yaml found "No agent.yaml found. Use --agent or -g for global install."

Dependencies

New Dependencies: None

All required functionality is covered by existing dependencies: - requests: HTTP client for registry API - pyyaml: YAML parsing/writing - click: CLI framework - pydantic: Model validation

Existing Code Reuse

Component Location Modification
GlobalConfig model src/holodeck/models/config.py ADD mcp_servers field
MCPTool model src/holodeck/models/tool.py USE as-is (no changes)
ConfigLoader src/holodeck/config/loader.py EXTEND merge_configs() for MCP servers
CLI main.py src/holodeck/cli/main.py ADD mcp command group registration
CLI patterns src/holodeck/cli/commands/ FOLLOW existing patterns for new mcp.py

Key Insight

The existing ConfigLoader.merge_configs() method already: - Merges global LLM provider configs into agent model - Resolves vectorstore references from global config - Follows agent-takes-precedence pattern

We follow this same pattern for MCP servers, adding a _merge_mcp_servers() helper method.