---
title: "Strategy 4: separating content (MCP) from structure (Git)"
description: "Architecture for a Hugo site managed simultaneously by MCP (content) and Git (structure). The .gitignore content/ that solves everything. Why MCP should never commit."
url: "https://www.arleo.eu/en/posts/strategie-4-mcp-vs-git/"
language: "en"
datePublished: "2026-05-09T13:03:25+02:00"
dateModified: "2026-05-15T23:51:25+02:00"
tags: ["mcp","git","hugo","architecture","homelab"]
categories: ["infrastructure"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Strategy 4: separating content (MCP) from structure (Git)
Architecture for a Hugo site managed simultaneously by MCP (content) and Git (structure). The .gitignore content/ that solves everything. Why MCP should never commit.



## 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 commit`s 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 --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

<!--more-->
<!-- mermaid-source:Z3JhcGggVEIKICAgIHN1YmdyYXBoIGNsaWVudHNbIkNsaWVudHMiXQogICAgICAgIEMxWyJDbGF1ZGUuYWk8YnIvPmNvbnRlbnQgZWRpdGluZyJdCiAgICAgICAgQzJbIlZTIENvZGUgLyBTU0g8YnIvPnN0cnVjdHVyZSBlZGl0aW5nIl0KICAgIGVuZAogICAgc3ViZ3JhcGggbnVjWyJOVUMg4oCUIE9BdXRoIHByb3h5Il0KICAgICAgICBQWyJtY3Atb2F1dGgtcHJveHkgOjgwODQiXQogICAgZW5kCiAgICBzdWJncmFwaCB2bVsiSHVnbyBWTSJdCiAgICAgICAgTUNQWyJodWdvLW1jcCBGYXN0QVBJPGJyLz43IE1DUCB0b29scyJdCiAgICAgICAgc3ViZ3JhcGggem9uZV9tY3BbIk1DUCBab25lIOKAlCBjb250ZW50LyJdCiAgICAgICAgICAgIE1EWyIqLmZyLm1kIMK3ICouZW4ubWQiXQogICAgICAgIGVuZAogICAgICAgIHN1YmdyYXBoIHpvbmVfZ2l0WyJHaXQgWm9uZSDigJQgbGF5b3V0cy8gdGhlbWVzLyBzdGF0aWMvIl0KICAgICAgICAgICAgR0lUWyLihpAgZ2l0IHB1bGwgR2l0SHViIl0KICAgICAgICBlbmQKICAgIGVuZAogICAgQzEgLS0+fCJIVFRQUyBPQXV0aCAyLjEifCBQIC0tPnwiaW50ZXJuYWwgSFRUUFMifCBNQ1AKICAgIE1DUCAtLT4gem9uZV9tY3AKICAgIEMyIC0tPnwiU1NIICsgZ2l0IHB1c2gifCB6b25lX2dpdAogICAgem9uZV9tY3AgLS4gIi5naXRpZ25vcmUgY29udGVudC8g4oCUIG5vIGNvbmZsaWN0cyIgLi0+IHpvbmVfZ2l0CiAgICBzdHlsZSB6b25lX21jcCBmaWxsOiMxYTNhNWMKICAgIHN0eWxlIHpvbmVfZ2l0IGZpbGw6IzE0NWEzMg== -->
{{< mermaid-svg hash="856c6ef7" >}}

```
┌────────────────────┐         ┌──────────────────────┐
│  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.



## Tags

- mcp
- git
- hugo
- architecture
- homelab

## Categories

- infrastructure
