Contents

Hugo MCP Server: Connecting Claude.ai to a Static Hugo Site

โšก In short

Connect Claude.ai to a Hugo site hosted in a KVM VM in 30 minutes: a FastAPI server exposes 6 MCP tools (read, create, modify, delete pages, rebuild the site) via JSON-RPC 2.0, an OAuth proxy reuses existing infrastructure, and every modification automatically triggers a Hugo rebuild + Cloudflare cache purge.

The code is available on GitHub:

๐Ÿง  Why

Anthropic’s MCP (Model Context Protocol) allows Claude.ai to connect to external data sources via standardized tools. Unlike Grav CMS which is dynamic (PHP), Hugo generates pure static HTML โ€” making content management via MCP even more powerful: every modification is compiled and deployed instantly.

This homelab stack enables:

  • Creating and modifying Hugo pages directly from Claude.ai without manually editing Markdown files
  • Automatically rebuilding the site after every modification via hugo --minify
  • Purging the Cloudflare cache in a targeted way after each change
  • Managing bilingual content (FR + EN) in a single conversation

๐Ÿ”ง What was done

๐Ÿ—๏ธ Global architecture

        Claude.ai
            โ”‚
            โ”‚ HTTPS (OAuth 2.1 + PKCE)
            โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  Cloudflare   โ”‚  WAF + Cache
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚     nginx     โ”‚  SSL termination
    โ”‚mcp-hugo.arleo โ”‚  location /mcp โ†’ proxy
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚ FastAPI proxy โ”‚  Port 8084 (NUC host)
    โ”‚  OAuth 2.1    โ”‚  Token validation
    โ”‚  mcp-proxy    โ”‚  SHA-256 hashing
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  hugo-mcp     โ”‚  Port 8000 (KVM VM)
    โ”‚  FastAPI      โ”‚  6 MCP tools
    โ”‚  Python 3.12  โ”‚  192.168.122.69
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ–ผ                โ–ผ
~/hugo-site/    deploy.sh โ†’ /var/www/hugo
content/*.md    + Cloudflare purge

๐Ÿ“ฆ Components

ComponentTechnologyRole
hugo-mcpFastAPI + Python 3.12Exposes 6 MCP tools via JSON-RPC 2.0
mcp-oauth-proxyFastAPI + PythonHandles OAuth 2.1, PKCE S256, tokens
nginx vhostnginxSSL reverse proxy, location /mcp
deploy.shbashHugo rebuild + rsync to /var/www/hugo
systemd unitssystemdHardened services on VM + host

๐Ÿ”Œ Step 1 โ€” Install hugo-mcp in the VM

# Clone the repo in the VM
git clone https://github.com/jmrGrav/hugo-mcp.git ~/hugo-mcp
cd ~/hugo-mcp

# Create the Python virtual environment
sudo apt install -y python3.12-venv
python3 -m venv venv
venv/bin/pip install -r requirements.txt

# Generate the MCP token
MCP_TOKEN=$(openssl rand -hex 32)

# Configure secrets
cat > .env << EOF
MCP_TOKEN=$MCP_TOKEN
CF_TOKEN=<your_cloudflare_token>
CF_ZONE_ID=<your_zone_id>
EOF

# Install the systemd service
sudo cp systemd/hugo-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now hugo-mcp

# Verify
curl -s http://localhost:8000/health

๐Ÿ” Step 2 โ€” Configure the OAuth proxy on the host

The OAuth proxy reuses mcp-oauth-proxy already installed for Grav, as an independent second instance on port 8084:

# Create Hugo MCP secrets
sudo mkdir -p /etc/hugo-mcp
sudo tee /etc/hugo-mcp/secrets.env << EOF
HUGO_MCP_URL=http://192.168.122.69:8000
HUGO_MCP_TOKEN=$MCP_TOKEN
MCP_CLIENT_ID=hugo-mcp
MCP_CLIENT_SECRET=$(openssl rand -hex 32)
TOKEN_SECRET=$(openssl rand -hex 32)
PROXY_PORT=8084
PROXY_BASE_URL=https://mcp-hugo.your-domain.com
EOF
sudo chmod 600 /etc/hugo-mcp/secrets.env

# Open port 8000 in the VM firewall
# only from the NUC host
sudo ufw allow from 192.168.122.1 to any port 8000 proto tcp

# Install and start the proxy service
sudo systemctl enable --now hugo-mcp-proxy

๐ŸŒ Step 3 โ€” Configure nginx

server {
    listen 443 ssl http2;
    server_name mcp-hugo.your-domain.com;

    include snippets/ssl.conf;

    location /mcp {
        proxy_pass http://127.0.0.1:8084/mcp;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /.well-known/oauth-authorization-server {
        proxy_pass http://127.0.0.1:8084/.well-known/oauth-authorization-server;
        proxy_set_header Host $host;
    }

    location /oauth/ {
        proxy_pass http://127.0.0.1:8084/oauth/;
        proxy_set_header Host $host;
    }
}

๐Ÿค Step 4 โ€” Connect Claude.ai

  1. Go to claude.ai โ†’ Settings โ†’ Connectors
  2. Click Add connector
  3. Enter the URL: https://mcp-hugo.your-domain.com/mcp
  4. Follow the OAuth flow โ€” authorize access
  5. The 6 tools appear in the connectors list โœ…

๐Ÿ› ๏ธ The 6 available tools

ToolDescription
list_pagesList all pages with language and section filters
get_pageRead the frontmatter and Markdown content of a page
create_pageCreate a page + Hugo rebuild + targeted CF purge
update_pageModify a page + Hugo rebuild + targeted CF purge
delete_pageDelete a page + Hugo rebuild + full CF purge
build_siteFull Hugo rebuild + deployment + full CF purge

๐Ÿ”’ Security

  • OAuth 2.1 + PKCE S256 โ€” secure authentication flow compliant with RFC 9728
  • SHA-256 token hashing โ€” tokens are never stored in plain text
  • UFW โ€” VM port 8000 open only from the NUC host (192.168.122.1)
  • Systemd hardening โ€” NoNewPrivileges, service isolated in the VM
  • Targeted CF purge โ€” only modified URLs are purged

๐Ÿ”„ Page modification flow

Claude.ai โ†’ create_page / update_page
                โ”‚
                โ–ผ
        hugo-mcp writes the .md file
                โ”‚
                โ–ผ
        deploy.sh: hugo --minify
                โ”‚
                โ–ผ
        rsync public/ โ†’ /var/www/hugo/
                โ”‚
                โ–ผ
        Cloudflare API: purge_cache (targeted)
                โ”‚
                โ–ผ
        Page live in ~2 seconds

๐Ÿ Conclusion

This stack transforms Hugo into an active context source for Claude.ai โ€” create, modify, and manage the static site through conversation, with automatic rebuild and cache purge. The whole setup reuses the OAuth infrastructure already in place for Grav and is fully available on GitHub under the MIT license.

Next steps:

  • ๐Ÿ’ก Add a list_assets tool to manage images and static files
  • ๐Ÿ’ก Implement a Git webhook to automatically rebuild after a push
  • ๐Ÿ’ก Migrate content from Grav to Hugo by automating frontmatter conversion