MCPHost Documentation

repository·main·Indexed 23 days ago

https://github.com/mark3labs/mcphost

MCPHost is a CLI and Go SDK that acts as a host for the Model Context Protocol (MCP), enabling LLMs from providers such as Claude, OpenAI, Gemini, and Ollama to interact with external tools and data sources via local, remote, and builtin MCP servers.

Tokens
15.5K
Snippets
38
Records
96
Agent score
76%

What's inside MCPHost

  1. Overview of MCPHost architecture

    main

    MCPHost operates as a Host in the Model Context Protocol (MCP) client-server architecture.

    • Hosts (like MCPHost): LLM applications that manage connections and interactions.
    • Clients: Maintain 1:1 connections with MCP servers.
    • Servers: Provide context, tools, and capabilities to the LLMs.

    This setup allows LLMs to access external tools/data, maintain consistent context, and execute commands safely. Supported models include Anthropic Claude, OpenAI, Google Gemini, and any Ollama-compatible model with function calling support.

  2. Understand the Unified Bubble Tea Architecture

    main

    MCPHost uses a layered architecture to separate core logic from the Terminal User Interface (TUI).

    • App Layer (app.App): The thick logic layer responsible for running agents, managing sessions, and handling tool execution. It provides methods like Run(), RunOnce(), CancelCurrentStep(), and Close().
    • UI Layer (ui.AppModel): A Bubble Tea model that acts as a state machine. It manages the transition between different UI states and routes events from the App layer to specific components.
    • Components: Specialized UI units like InputComponent (user input/autocomplete), StreamComponent (streaming output/spinners), and ApprovalComponent (tool approval dialogs).

    State Machine Transitions

    The AppModel transitions between three primary states:

    1. stateInput: The UI is focused on user input.
    2. stateWorking: An agent is running and streaming output.
    3. stateApproval: A tool approval dialog is active, waiting for user input.

    Event Flow

    1. User Input: User submits a prompt via InputComponent.
    2. Execution: The parent model calls app.Run(prompt) in a goroutine.
    3. Streaming: The App layer sends events (e.g., SpinnerEvent, StreamChunkEvent, ToolResultEvent) to the TUI via program.Send().
    4. Routing: The parent model receives these events and routes them to the appropriate component (e.g., StreamComponent.Update()).
    5. Completion: Once the agent finishes, the App layer sends a StepCompleteEvent, and the UI transitions back to stateInput.
  3. Manage Session Persistence

    main

    Persistence is handled by the session.Manager passed into App.Options.

    • Saving: The app layer automatically calls session.Manager.AddMessages() after each step completion and when the message queue is drained.
    • Loading: To resume a session, use the --load-session flag (handled in cmd/root.go). The loaded messages are passed to App.New() as the initial history.
    • Clearing: Calling MessageStore.Clear() also triggers session.Manager.ReplaceAllMessages() to ensure the persistent store is synchronized.
  4. Implement the Cancel Flow in MCPHost

    main

    MCPHost supports a 'double-tap' cancellation mechanism to prevent accidental interruptions during agent execution.

    1. First ESC: When a user presses ESC during stateWorking, the parent model sets canceling=true and initiates a 2-second timer (cancelTimerCmd).
    2. Second ESC: If the user presses ESC again within that 2-second window, the parent calls app.CancelCurrentStep().
    3. Cleanup: app.CancelCurrentStep() cancels the step's context, causing the agent goroutine to exit and unblocking any pending ToolApprovalFunc via ctx.Done(). An error or completion event is then emitted, and the UI returns to stateInput.
    4. Timeout: If the second ESC is not pressed, a cancelTimerExpiredMsg arrives, resetting canceling to false without cancelling the operation.
  5. Use environment variables and ${env://VAR} syntax

    main

    For security and flexibility, avoid hardcoding sensitive data like API keys. Instead, use environment variables. MCPHost supports the ${env://VAR} syntax for environment variable substitution within configuration files and scripts.

    • Standard substitution: ${env://VAR}
    • Substitution with default value: ${env://VAR:-default_value} (e.g., ${env://DEBUG:-false})

    This is particularly useful when configuring MCP servers in your configuration files to pass credentials like GITHUB_TOKEN or OPENAI_API_KEY.

    mcpServers:
      github:
        environment:
          GITHUB_TOKEN: "${env://GITHUB_TOKEN}"
          DEBUG: "${env://DEBUG:-false}"
  6. Use variable substitution in MCPHost scripts

    main

    Scripts support two types of variable substitution, processed in a specific order:

    1. Environment Variables: ${env://VAR} or ${env://VAR:-default} (Processed first).
    2. Script Arguments: ${variable} or ${variable:-default} (Processed after environment variables).

    Arguments are passed via the CLI using the --args:<name> <value> syntax.

    #!/usr/bin/env -S mcphost script
    ---
    mcpServers:
      github:
        type: "local"
        command: ["gh", "api"]
        environment:
          GITHUB_TOKEN: "${env://GITHUB_TOKEN}"
          DEBUG: "${env://DEBUG:-false}"
    
    model: "${env://MODEL:-anthropic/claude-sonnet-4-5-20250929}"
    ---
    Hello ${name:-World}! Please list ${repo_type:-public} repositories for user ${username}.
    Working directory is ${env://WORK_DIR:-/tmp}.
    # Execution example
    mcphost script myscript.sh --args:name "John" --args:username "alice"
  7. Implement the App Layer for MCPHost

    main

    The App Layer is the core orchestration engine of MCPHost. It manages agentic steps, tool approvals, message persistence, and usage tracking. To implement or extend the app layer, you must define the following components:

    1. Event Types: The app communicates via events such as StreamChunkEvent, ToolCallStartedEvent, ToolExecutionEvent, ToolResultEvent, ToolCallContentEvent, ResponseCompleteEvent, StepCompleteEvent (includes usage data), StepErrorEvent, QueueUpdatedEvent, ToolApprovalNeededEvent (includes a ResponseChan chan<- bool), SpinnerEvent, HookBlockedEvent, and MessageCreatedEvent.
    2. Options: Use an Options struct to configure the app. This includes a ToolApprovalFunc with the signature func(ctx context.Context, toolName, toolArgs string) (bool, error).
    3. MessageStore: A wrapper around []fantasy.Message that provides Add, Replace, GetAll, and Clear methods. It should bridge to a session.Manager for persistence.
    4. App Struct: The main entry point providing methods like New(opts, initialMessages), SetProgram(*tea.Program), Run(prompt), RunOnce(ctx, prompt, io.Writer), CancelCurrentStep(), QueueLength(), ClearQueue(), ClearMessages(), and Close().
  8. Configure variables in MCPHost scripts

    main

    MCPHost scripts use a specific syntax for variables within the script content. You can define two types of variables:

    1. Required Variables: Use ${variable}. The script will fail if these are not provided via the --args:variable flag.
    2. Optional Variables with Defaults: Use ${variable:-default_value}. If the variable is not provided via command line, the default_value is used. This supports complex defaults like paths (e.g., ${path:-/tmp/default/path}) or empty defaults (e.g., ${var:-}).

    To override these variables at runtime, use the --args:<variable_name> <value> flag.

  9. Handle Tool Approvals in the TUI

    main

    When the agent requires tool approval, the TUI transitions to a stateApproval mode. The ApprovalComponent renders a dialog showing the tool name and arguments. The user can then select [Yes] or [No].

    Internally, the component returns an approvalResultMsg{approved: bool} as a tea.Cmd, which the parent AppModel uses to send the result back to the App via an approvalChan.

  10. Implement Tool Approval via ToolApprovalFunc

    main

    When the agent requires tool permission, it triggers the ToolApprovalFunc provided in Options.

    Interactive Mode Workflow:

    1. The agent blocks on the ToolApprovalFunc callback.
    2. The callback emits a ToolApprovalNeededEvent containing a chan<- bool response channel.
    3. The TUI transitions to stateApproval to show a dialog to the user.
    4. The user's decision is sent back through the channel.
    5. The callback must use a select statement against the app context (ctx.Done()) to prevent goroutine leaks during shutdown.

    Non-Interactive Mode Workflow:

    • The ToolApprovalFunc should be configured to auto-approve all requests.
  11. Filter MCP Server tools

    main

    You can restrict the capabilities of an MCP server using tool filtering. This is applied to all server types (local, remote, and builtin).

    • allowedTools: A whitelist. Only the tools listed in this array will be available.
    • excludedTools: A blacklist. All tools from the server will be available except those listed in this array.

    Important: You cannot use both allowedTools and excludedTools on the same server entry; they are mutually exclusive.

    {
      "mcpServers": {
        "filesystem-readonly": {
          "type": "builtin",
          "name": "fs",
          "allowedTools": ["read_file", "list_directory"]
        }
      }
    }
  12. Use environment variable substitution in configuration

    main

    MCPHost supports environment variable substitution in configuration files and script frontmatter. This is useful for managing sensitive data like API keys.

    Syntax:

    • ${env://VAR}: Required environment variable. The configuration will fail if this variable is not set.
    • ${env://VAR:-default}: Optional environment variable. If not set, it uses the provided default value.

    Example usage in YAML:

    model: "${env://MODEL:-anthropic/claude-sonnet-4-5-20250929}"
    provider-api-key: "${env://OPENAI_API_KEY}"
    mcpServers:
      github:
        type: local
        command: ["docker", "run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN=${env://GITHUB_TOKEN}", "ghcr.io/github/github-mcp-server"]
        environment:
          DEBUG: "${env://DEBUG:-false}"
          LOG_LEVEL: "${env://LOG_LEVEL:-info}"
    
    model: "${env://MODEL:-anthropic/claude-sonnet-4-5-20250929}"
    provider-api-key: "${env://OPENAI_API_KEY}"  # Required - will fail if not set