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 fundamentals: Three elements (Client/Server/Protocol), JSON-RPC 2.0, three transport layers
- Hermes MCP client: Initialization flow, MCPToolProxy transparent proxy, protocol format conversion
- Configuration methods: YAML declarative config, CLI dynamic management, Python API programmatic access
- Priority rules: Four-layer resolution chain with tool overrides and native tool disabling
- Playwright in practice: Complete installation โ configuration โ usage โ security hardening
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
-
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?
-
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?
-
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?