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

The problem
You have a Hugo site. You want to:
- Edit content via Claude.ai (publish an article, fix a typo, update a draft) without touching an SSH terminal.
- 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.
| Zone | Who edits | Versioned in Git? |
|---|---|---|
content/**/*.md | MCP only | ❌ NO (.gitignore) |
layouts/, themes/, static/, hugo.toml, deploy.sh | Git 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 --hardrepo-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 blameon an article to see who wrote what. - No
git log content/csp-nonce/index.fr.mdfor 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
┌────────────────────┐ ┌──────────────────────┐
│ 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:
- GitHub sends a webhook POST to
https://mcp-hugo.arleo.eu/webhook/git - Endpoint validates HMAC-SHA256 (shared secret)
- If OK, triggers
git fetch && git reset --hard origin/main && hugo --minify && rsync git reset --hardcan be called confidently — it never touchescontent/(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
.mdfile incontent/. 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.