Chapter 19

MCP Integration Architecture

Chapter 19: MCP Integration Architecture

The Model Context Protocol (MCP) is an open standard proposed by Anthropic in late 2024, designed to establish a unified communication interface between AI models and external tools or data sources. Hermes Agent, functioning as an MCP client, can seamlessly connect to any MCP Server and incorporate its capabilities into its own tool ecosystem. This chapter provides a deep dive into MCP's core concepts, Hermes's integration architecture, and a complete walkthrough of connecting to a Playwright MCP Server in practice.


19.1 MCP Core Concepts

MCP's design philosophy is "tools as a service": wrap any external capability as a standardized MCP Server, and AI clients discover and call those capabilities through a unified protocol.

19.1.1 The Three Elements of MCP

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    MCP Ecosystem                      โ”‚
โ”‚                                                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      MCP Protocol     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚  MCP Client  โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ MCP Serverโ”‚ โ”‚
โ”‚  โ”‚(Hermes, etc.)โ”‚    JSON-RPC 2.0       โ”‚(tool host)โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚         โ”‚                                    โ”‚        โ”‚
โ”‚   Discover tools                      Expose tools    โ”‚
โ”‚   Invoke tools                        Execute logic   โ”‚
โ”‚   Receive results                     Return results  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Component Role Examples
MCP Client Initiates tool discovery and invocation requests Hermes Agent, Claude Desktop
MCP Server Provides tool capabilities as a service Playwright MCP, GitHub MCP
MCP Protocol JSON-RPC 2.0 based communication spec tools/list, tools/call methods

19.1.2 Core MCP Protocol Methods

// Tool discovery: client requests the tool list from the server
// Request: tools/list
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "playwright_navigate",
        "description": "Navigate to a URL in the browser",
        "inputSchema": {
          "type": "object",
          "properties": {
            "url": {"type": "string"},
            "wait_until": {
              "type": "string",
              "enum": ["load", "domcontentloaded", "networkidle"],
              "default": "load"
            }
          },
          "required": ["url"]
        }
      }
    ]
  }
}

// Tool invocation: client calls a specific tool
// Request: tools/call
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "playwright_navigate",
    "arguments": {
      "url": "https://nousresearch.com",
      "wait_until": "networkidle"
    }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Successfully navigated to https://nousresearch.com. Page title: NousResearch"
      }
    ],
    "isError": false
  }
}

19.1.3 MCP Transport Layers

Transport Use Case Characteristics
stdio Local process communication Lowest latency, no network needed
SSE Remote HTTP services Unidirectional streaming, ideal for cloud
WebSocket Real-time bidirectional comms Best for high-frequency interactions

19.2 Hermes as an MCP Client

Hermes has a built-in MCP client implementation that automatically connects to configured MCP Servers at startup and registers their tools into the unified tool registry.

19.2.1 MCP Client Initialization Flow

from hermes.mcp import MCPClient, MCPTransport
from hermes.tools import ToolRegistry

class HermesMCPManager:
    def __init__(self, config: HermesConfig):
        self.config = config
        self.clients: dict[str, MCPClient] = {}
        self.tool_registry = ToolRegistry()
    
    async def initialize(self):
        """Connect to all configured MCP Servers at startup"""
        for server_config in self.config.mcp_servers:
            client = await self._connect_server(server_config)
            self.clients[server_config.name] = client
            
            tools = await client.list_tools()
            for tool in tools:
                self.tool_registry.register_mcp_tool(
                    tool=tool,
                    server_name=server_config.name,
                    client=client,
                )
            
            print(f"[MCP] Connected to {server_config.name}, "
                  f"registered {len(tools)} tools")
    
    async def _connect_server(self, config: MCPServerConfig) -> MCPClient:
        transport = MCPTransport.create(
            transport_type=config.transport,
            command=config.command,
            url=config.url,
            env=config.env,
        )
        
        client = MCPClient(transport=transport)
        await client.connect()
        await client.initialize(
            client_info={"name": "hermes-agent", "version": "4.0.0"}
        )
        return client

19.2.2 Transparent MCP Tool Proxy

When an Agent invokes an MCP tool, Hermes automatically handles protocol translation:

class MCPToolProxy:
    """Wraps an MCP tool in the Hermes native tool format"""
    
    def __init__(self, mcp_tool: MCPToolDefinition, client: MCPClient):
        self.mcp_tool = mcp_tool
        self.client = client
    
    @property
    def name(self) -> str:
        # Prefix with server name to avoid naming collisions
        return f"mcp_{self.client.server_name}_{self.mcp_tool.name}"
    
    @property
    def description(self) -> str:
        return f"[MCP:{self.client.server_name}] {self.mcp_tool.description}"
    
    async def run(self, params: dict, context) -> ToolResult:
        try:
            mcp_result = await self.client.call_tool(
                name=self.mcp_tool.name,
                arguments=params,
            )
            return ToolResult(
                success=not mcp_result.isError,
                output=self._extract_content(mcp_result.content),
                metadata={
                    "mcp_server": self.client.server_name,
                    "mcp_tool": self.mcp_tool.name,
                }
            )
        except MCPConnectionError as e:
            await self.client.reconnect()
            raise ToolExecutionError(f"MCP connection lost: {e}")
    
    def _extract_content(self, content: list) -> str | dict:
        texts = [c["text"] for c in content if c["type"] == "text"]
        images = [c for c in content if c["type"] == "image"]
        if images and not texts:
            return {"type": "image", "data": images[0]["data"]}
        return "\n".join(texts)

19.3 Configuring External MCP Servers

19.3.1 YAML Configuration

# hermes_config.yaml
mcp:
  enabled: true
  connection_timeout_seconds: 30
  tool_prefix: "mcp"
  
  servers:
    # Method 1: stdio process (most common)
    - name: playwright
      transport: stdio
      command: ["npx", "@playwright/mcp@latest"]
      env:
        PLAYWRIGHT_BROWSERS_PATH: "/usr/local/share/playwright"
      auto_restart: true
      health_check_interval_seconds: 60
    
    # Method 2: Local Python MCP Server
    - name: file_system
      transport: stdio
      command: ["python", "-m", "mcp_server_filesystem", "--root", "/workspace"]
    
    # Method 3: Remote SSE service
    - name: company_api
      transport: sse
      url: "https://mcp.internal.company.com/v1"
      headers:
        Authorization: "Bearer ${INTERNAL_API_TOKEN}"
    
    # Method 4: WebSocket service
    - name: realtime_data
      transport: websocket
      url: "wss://data.example.com/mcp"
      reconnect_on_failure: true
      max_reconnect_attempts: 5

19.3.2 CLI Dynamic Management

# Add a Playwright MCP Server
hermes mcp add playwright \
  --transport stdio \
  --command "npx @playwright/mcp@latest"

# Add a remote SSE MCP Server
hermes mcp add company-api \
  --transport sse \
  --url "https://mcp.company.com/v1" \
  --header "Authorization: Bearer $TOKEN"

# List connected servers and tools
hermes mcp list

# Test a specific tool
hermes mcp test playwright --tool playwright_navigate \
  --args '{"url": "https://example.com"}'

# Remove a server
hermes mcp remove playwright

19.3.3 Programmatic Management

import asyncio
from hermes import HermesAgent
from hermes.mcp import MCPServerConfig, MCPTransportType

async def main():
    agent = HermesAgent()
    
    playwright_config = MCPServerConfig(
        name="playwright",
        transport=MCPTransportType.STDIO,
        command=["npx", "@playwright/mcp@latest"],
        env={"DISPLAY": ":0"},
    )
    
    await agent.mcp_manager.add_server(playwright_config)
    
    tools = agent.tool_registry.list_tools(prefix="mcp_playwright")
    print(f"Playwright MCP provides {len(tools)} tools:")
    for tool in tools:
        print(f"  - {tool.name}: {tool.description}")
    
    result = await agent.run(
        "Visit https://nousresearch.com, take a screenshot, "
        "and summarize the main content of the page."
    )
    print(result)

asyncio.run(main())

19.4 MCP Tools vs. Native Tool Priority

When Hermes native tools and MCP tools overlap in functionality, explicit priority rules determine which is used.

Priority Order (Highest to Lowest)

1. Explicit specification (Agent names the tool directly in the prompt)
2. User config overrides (hermes_config.yaml tool_preference)
3. Native tools (Hermes built-in)
4. MCP tools (registration order; first registered = higher priority)

Configuring Tool Priority Overrides

tool_resolution:
  # For browser operations, prefer Playwright MCP
  overrides:
    browser_navigate: "mcp_playwright_playwright_navigate"
    browser_screenshot: "mcp_playwright_playwright_screenshot"
  
  # Disable native tools (force use of MCP versions)
  disabled_native_tools:
    - browser_navigate
    - browser_screenshot

19.5 Practice: Connecting Playwright MCP Server

Step 1: Installation

npm install -g @playwright/mcp
npx playwright install chromium
npx @playwright/mcp@latest --version

Step 2: Configuration

mcp:
  servers:
    - name: playwright
      transport: stdio
      command: ["npx", "@playwright/mcp@latest", "--browser", "chromium"]
      env:
        PLAYWRIGHT_HEADLESS: "true"

Step 3: Full Usage Example

import asyncio
from hermes import HermesAgent

async def scrape_product_info():
    agent = HermesAgent(config_path="hermes_config.yaml")
    await agent.initialize()
    
    result = await agent.run("""
    Complete the following tasks:
    1. Navigate to https://www.amazon.com/dp/B0C9RD2FN9
    2. Capture a screenshot of the main product image
    3. Extract: product name, price, rating, review count
    4. Return the extracted information as JSON
    """)
    
    print(result)
    # Expected output:
    # {
    #   "product_name": "Echo Dot (5th Gen) Smart Speaker",
    #   "price": "$49.99",
    #   "rating": 4.7,
    #   "review_count": 187432
    # }

asyncio.run(scrape_product_info())

Playwright MCP Core Tools

mcp_playwright_playwright_navigate    โ€” Navigate to a URL
mcp_playwright_playwright_screenshot  โ€” Capture page screenshot
mcp_playwright_playwright_click       โ€” Click an element
mcp_playwright_playwright_fill        โ€” Fill input fields
mcp_playwright_playwright_select      โ€” Select dropdown options
mcp_playwright_playwright_evaluate    โ€” Execute JavaScript
mcp_playwright_playwright_wait        โ€” Wait for element/condition
mcp_playwright_playwright_get_text    โ€” Get element text content
mcp_playwright_playwright_get_html    โ€” Get full page HTML
mcp_playwright_playwright_pdf         โ€” Export page as PDF

19.6 MCP Security Best Practices

mcp:
  security:
    # Tool whitelist: only allow specific MCP tools
    tool_whitelist:
      playwright:
        - playwright_navigate
        - playwright_screenshot
        - playwright_get_text
        # playwright_evaluate is excluded to prevent arbitrary JS execution
    
    # URL filtering: prevent SSRF attacks
    url_filters:
      allowed_domains:
        - "*.amazon.com"
        - "*.google.com"
      blocked_domains:
        - "169.254.169.254"    # AWS metadata endpoint
        - "localhost"
        - "*.internal"
    
    # Resource limits
    resource_limits:
      max_page_load_time_seconds: 30
      max_screenshot_size_mb: 5
      max_concurrent_pages: 3

19.7 Summary

This chapter provided a systematic look at Hermes Agent's MCP integration architecture:

MCP transforms Hermes from a closed tool system into an open capability platform โ€” any third party can extend Hermes's capabilities by publishing an MCP Server.

Review Questions

  1. Why does MCP use JSON-RPC 2.0 rather than a REST API? What special value does JSON-RPC's bidirectional notification mechanism offer in Agent scenarios?

  2. When an MCP Server crashes and restarts, what happens to the tools already registered in Hermes's tool registry? How would you design a "tool health check" mechanism?

  3. If you were to wrap your company's internal private database as an MCP Server, how would you design the authentication mechanism and data access audit logging?

Rate this chapter
4.6  / 5  (15 ratings)

๐Ÿ’ฌ Comments