Contents

Strategy 4: separating content (MCP) from structure (Git)

The problem

You have a Hugo site. You want to:

  1. Edit content via Claude.ai (publish an article, fix a typo, update a draft) without touching an SSH terminal.
  2. Version the structure (layouts, themes, hugo.toml, deploy scripts) in Git, like a serious dev.

First instinct: “everything in Git”. Articles too. The MCP commits, pushes, GitHub webhook triggers a rebuild. Clean, GitOps-philosophy.

Except it doesn’t work that well. Here’s why, and the simple solution I call Strategy 4.

Why “everything in Git” breaks in practice

Imagine your MCP git commits on every create_page. Naive strategy, often suggested. Here are the problems:

Problem 1 — MCP ↔ Git conflict

You push a new layout from your laptop (layouts/index.html modified). At the same time, the MCP is committing a new article version. Race condition: the MCP might git pull --rebase and fail, or worse, overwrite your local commit.

Problem 2 — MCP’s Git identity

Whose commits is the MCP making? With which GPG key? If you have a “signed commits required” policy, the MCP needs to manage a GPG key, which has to be secured, rotated, etc.

Problem 3 — Unwanted auto-commits

You’re testing, you create a draft article to experiment, you delete it. But the MCP already committed. Now you have a “wip test” commit in history, to rebase or squash manually.

Problem 4 — Asymmetric reversibility

A git revert repo-side has no effect on files the MCP already created. You end up with a desynchronized repo and filesystem state.

Strategy 4: separate the zones

The idea: MCP and Git never write to the same files.

ZoneWho editsVersioned in Git?
content/**/*.mdMCP only❌ NO (.gitignore)
layouts/, themes/, static/, hugo.toml, deploy.shGit push only✅ YES

Repo-side .gitignore:

content/
public/
resources/

Implications:

  • The MCP can write to content/ whenever. No Git conflict possible.
  • You can git reset --hard repo-side with confidence — content/ stays intact.
  • No need for the MCP to manage a Git identity.
  • No “commit pollution”.

Trade-off: no content versioning

You lose Git versioning of content. That’s a real loss:

  • No git blame on an article to see who wrote what.
  • No git log content/csp-nonce/index.fr.md for history.
  • No PR review for articles.

Mitigation: VM snapshots + daily encrypted content/ backup to QNAP. That covers disaster recovery, but not fine-grained versioning (who-changed-what-when).

For my personal homelab (single author, technical articles, no editorial validation workflow), it’s an acceptable trade-off. For a 10-contributor team blog, I’d reconsider.

Final architecture

Diagram Diagram
┌────────────────────┐         ┌──────────────────────┐
│  Claude.ai client  │  HTTPS  │  hugo-mcp-proxy NUC  │
│  (content editing) │ ──────► │  (mcp-oauth-proxy)   │
└────────────────────┘         └──────────────────────┘
                                          │
                                          │  internal HTTP
                                          ▼
                              ┌────────────────────────┐
                              │  Hugo MCP Server VM    │
                              │  (FastAPI, 7 tools)    │
                              │                        │
                              │  MCP ZONE:             │
                              │  /home/jm/hugo-site/   │
                              │  └── content/          │
                              └────────────────────────┘

┌────────────────────┐         ┌──────────────────────┐
│  Dev laptop        │  SSH    │  Hugo VM             │
│  (layout editing)  │ ──────► │                      │
└────────────────────┘         │  GIT ZONE:           │
                               │  /home/jm/hugo-site/ │
                               │  ├── layouts/        │
                               │  ├── themes/         │
                               │  ├── static/         │
                               │  ├── hugo.toml       │
                               │  └── deploy.sh       │
                               └──────────────────────┘

Both zones share the same filesystem, but no overlapping files. No conflict.

Git webhook (optional but useful)

To automate rebuilds when you push a new hugo.toml:

  1. GitHub sends a webhook POST to https://mcp-hugo.arleo.eu/webhook/git
  2. Endpoint validates HMAC-SHA256 (shared secret)
  3. If OK, triggers git fetch && git reset --hard origin/main && hugo --minify && rsync
  4. git reset --hard can be called confidently — it never touches content/ (gitignored)

Rate limiting matters: max 1 rebuild/min, to prevent spam pushes (or an attacker who manages to sign a payload) from saturating the server.

What it enables

Concretely, in a typical work session:

  • I write an article via Claude.ai → MCP creates the .md file in content/. Hugo builds, deploys, purges Cloudflare. 5 seconds total.
  • I edit a theme partial via VS Code → SSH push or git push → webhook → rebuild. 30 seconds.

The two flows are independent. If the MCP crashes, I can still push Git code. If Git is down, the MCP keeps serving.

Variants considered and rejected

Strategy 1 — MCP commits each edit

All the problems listed above. Rejected for V1, maybe V2 if I move to a multi-author workflow.

Strategy 2 — Separate branches

main for structure, content for articles. MCP pushes only on content, Git pushes only on main. Elegant but complex: you have to manage merges, rebases, and effective prod state becomes hard to reason about.

Strategy 3 — Git submodule

content/ as a submodule pointing to another repo. MCP commits in the submodule. Too much ceremony for a homelab.

Strategy 4 — Separate zones (this one)

Simple. Robust. No ceremony. Known trade-off (no fine content versioning) but acceptable for my use.

Conclusion

When you combine two tools that want to edit the same files (MCP and Git), the solution isn’t to make them cooperate (sync, lock, merge). It’s to prevent them from editing the same files.

.gitignore content/ is 1 config line that solves 4 classes of problems. That’s architecture by doing less, not more.

The actual implementation (webhook, IPAddressAllow GitHub ranges, HMAC) is documented in the webhook-git-rebuild brief archived in the hugo-mcp/docs/backlogs/ repo. To deploy in a future session — for now, Git editing is via direct SSH, which works too.