MCP tool calls to the claude.ai Gitea (and by extension Brain) connectors intermittently fail with a Cloudflare "Sorry, you have been blocked" page. The block is on the Anthropic-hosted MCP relay POST -- the error page names anthropic.com (e.g. IP 5.150.212.35), not git.d-ma.be. It triggers on large or command-shaped bodies (WAF: "a SQL command or malformed data"). Observed 2026-06-20 while filing infra#129 (a multi-code-block issue): issue_create over MCP returned the CF block page; the identical issue filed cleanly via the Gitea REST API.
The WAF sits upstream of our gitea-mcp server, in front of Anthropic's connector gateway -- we cannot tune it. The only durable lever is to route around the relay.
Decision (see brain: homelab/decisions/prefer-rest-api-skills-over-mcp-on-cli-cloudflare-waf)
Claude Code CLI (koala/iguana/flamingo): default to direct REST-API skills for Gitea + Brain writes / large / command-shaped payloads. koala -> git.d-ma.be / brain-mcp.d-ma.be directly, never through the Anthropic relay -> sidesteps the WAF class entirely.
MCP connectors: keep as fallback AND as the only option on claude.ai Desktop/web (no shell / no ~/.op-env there). This is NOT a removal of the MCP.
Deliverables
1. gitea-rest skill
Wrap the common Gitea ops as REST calls via op run --env-file ~/.op-env:
issue: create / comment / get / list / edit / close
file: read / write-branch / delete
pr: create / list / get / merge
repo: get / list / status
Pattern (token stays in the subprocess; body from file, never argv):
Both wrap all secret use in op run --env-file ~/.op-env; tokens never echoed or placed in argv; bodies passed via file/stdin.
Skill trigger guidance: on Claude Code CLI prefer these over the MCP for writes / large or command-shaped payloads; fall back to MCP if ~/.op-env is absent.
SKILLS_INDEX.md updated.
Smoke test each: create a throwaway issue + a brain query, confirm no relay/WAF in the path.
Constraints / footguns
Requires ~/.op-env; non-interactive Bash lacks on-demand 1P (gitea-mcp#36) -> always op run --env-file ~/.op-env.
Accept skill+MCP duplication: the relay WAF is upstream and unfixable from our side.
Gitea REST per-repo paths work directly (unlike gitea-mcp per-repo tools that 404 to /api/swagger -- see brain reference_gitea_api_fallback).
## Problem
MCP tool calls to the claude.ai **Gitea** (and by extension **Brain**) connectors intermittently fail with a Cloudflare "Sorry, you have been blocked" page. The block is on the **Anthropic-hosted MCP relay POST** -- the error page names `anthropic.com` (e.g. IP 5.150.212.35), not `git.d-ma.be`. It triggers on large or command-shaped bodies (WAF: "a SQL command or malformed data"). Observed 2026-06-20 while filing infra#129 (a multi-code-block issue): `issue_create` over MCP returned the CF block page; the identical issue filed cleanly via the Gitea REST API.
The WAF sits **upstream of our `gitea-mcp` server**, in front of Anthropic's connector gateway -- we cannot tune it. The only durable lever is to route around the relay.
## Decision (see brain: homelab/decisions/prefer-rest-api-skills-over-mcp-on-cli-cloudflare-waf)
- **Claude Code CLI (koala/iguana/flamingo):** default to direct REST-API skills for Gitea + Brain writes / large / command-shaped payloads. koala -> `git.d-ma.be` / `brain-mcp.d-ma.be` directly, never through the Anthropic relay -> sidesteps the WAF class entirely.
- **MCP connectors:** keep as fallback AND as the only option on claude.ai Desktop/web (no shell / no `~/.op-env` there). This is NOT a removal of the MCP.
## Deliverables
### 1. `gitea-rest` skill
Wrap the common Gitea ops as REST calls via `op run --env-file ~/.op-env`:
- issue: create / comment / get / list / edit / close
- file: read / write-branch / delete
- pr: create / list / get / merge
- repo: get / list / status
Pattern (token stays in the subprocess; body from file, never argv):
```bash
op run --env-file ~/.op-env -- python3 - <<'PY'
import os, json, urllib.request
body = open('/tmp/body.md').read()
req = urllib.request.Request(
'https://git.d-ma.be/api/v1/repos/OWNER/REPO/issues',
data=json.dumps({'title': T, 'body': body}).encode(),
headers={'Authorization': 'token '+os.environ['DMABE_GITEA_API_TOKEN'],
'Content-Type': 'application/json'}, method='POST')
print(json.load(urllib.request.urlopen(req, timeout=30))['html_url'])
PY
```
### 2. `brain-rest` skill
- `query`: `POST https://brain-mcp.d-ma.be/query` `{"query": "..."}` with `$BRAIN_MCP_TOKEN`
- `write`: `POST .../write` `{"content": "...", "filename": "...", "wing": "...", "hall": "..."}`
- `answer`: `POST .../answer` (if exposed)
## Acceptance criteria
- [ ] `gitea-rest` SKILL.md installed in `~/dev/.skills/` + `mathias/skills`, covering the issue/file/pr/repo ops above.
- [ ] `brain-rest` SKILL.md covering query/write (+ answer).
- [ ] Both wrap all secret use in `op run --env-file ~/.op-env`; tokens never echoed or placed in argv; bodies passed via file/stdin.
- [ ] Skill trigger guidance: on Claude Code CLI prefer these over the MCP for writes / large or command-shaped payloads; fall back to MCP if `~/.op-env` is absent.
- [ ] SKILLS_INDEX.md updated.
- [ ] Smoke test each: create a throwaway issue + a brain query, confirm no relay/WAF in the path.
## Constraints / footguns
- Requires `~/.op-env`; non-interactive Bash lacks on-demand 1P (gitea-mcp#36) -> always `op run --env-file ~/.op-env`.
- Accept skill+MCP duplication: the relay WAF is upstream and unfixable from our side.
- Gitea REST per-repo paths work directly (unlike gitea-mcp per-repo tools that 404 to /api/swagger -- see brain reference_gitea_api_fallback).
_Refs: gitea-mcp#36, infra#129, agentsquad#31. Brain: homelab/decisions/prefer-rest-api-skills-over-mcp-on-cli-cloudflare-waf._
Premise check before building anything, per this issue's own risk label.
The evidence against the premise still holding
This session (2026-08-29) has pushed roughly forty writes through the claude.ai Gitea MCP connector — the exact relay path this issue routes around. Many were multi-kilobyte issue comments containing fenced code blocks, shell pipelines, JSON payloads, grep -E regexes, sed expressions and quoted error strings. Several are longer than the infra#129 body that originally triggered the block.
Zero Cloudflare blocks. Not one retry, not one HTML error page.
Command-shaped content specifically — the WAF's stated trigger ("a SQL command or malformed data") — went through repeatedly: op run --env-file=<(...) invocations, git push origin HEAD:main "$TAG", sed -n 's/.*"version"...', kubectl get externalsecret -o jsonpath=.... All accepted.
What that does and does not prove
It does not prove the WAF rule was removed — I cannot see Cloudflare's config, and a rule that fires on a narrower pattern than I happened to send would look identical to no rule at all. What it does establish is that the operational problem this issue describes is not currently reproducing at the volume and shape that produced it.
The recommendation, which is yours to take or leave
Do not build gitea-rest and brain-rest yet. Two new skills exist to be loaded and followed; if the failure they route around does not occur, they are two skills competing for selection against the ones that should fire, plus a second code path for every Gitea operation, plus their own maintenance. #29 exists precisely because unfireable skills degrade selection for the rest.
Three options as I see them:
Close as superseded, with the brain decision note updated to say the WAF blocks stopped reproducing on 2026-08-29 and under what volume. If it returns, the issue is one revert away and the evidence is recorded.
Reduce scope to a smoke test — a small script that exercises the relay with a deliberately command-shaped payload, run on a schedule, so the next occurrence is detected rather than stumbled into. This is the "small tool over prose" answer from #19, and it is maybe thirty lines.
Build both skills as designed. Defensible if you have hit the block recently on a host I have not seen, which I would not know about.
I lean 2: it costs a fraction of 1, and it converts "we think this stopped" into something that will tell us if it starts again.
Worth noting independently of the verdict: the direct-REST pattern this issue specifies is in use and does work. Fetching Gitea Actions job logs today needed it, because no MCP tool exposes that endpoint — op run --env-file ~/.op-env -- curl ... /actions/jobs/{id}/logs. So the technique is proven; the question is only whether it deserves to be a loaded skill or stays an occasional inline call.
Premise check before building anything, per this issue's own risk label.
## The evidence against the premise still holding
This session (2026-08-29) has pushed roughly **forty** writes through the claude.ai Gitea MCP connector — the exact relay path this issue routes around. Many were multi-kilobyte issue comments containing fenced code blocks, shell pipelines, JSON payloads, `grep -E` regexes, `sed` expressions and quoted error strings. Several are longer than the infra#129 body that originally triggered the block.
**Zero Cloudflare blocks.** Not one retry, not one HTML error page.
Command-shaped content specifically — the WAF's stated trigger ("a SQL command or malformed data") — went through repeatedly: `op run --env-file=<(...)` invocations, `git push origin HEAD:main "$TAG"`, `sed -n 's/.*"version"...'`, `kubectl get externalsecret -o jsonpath=...`. All accepted.
## What that does and does not prove
It does not prove the WAF rule was removed — I cannot see Cloudflare's config, and a rule that fires on a narrower pattern than I happened to send would look identical to no rule at all. What it does establish is that the *operational* problem this issue describes is not currently reproducing at the volume and shape that produced it.
## The recommendation, which is yours to take or leave
**Do not build `gitea-rest` and `brain-rest` yet.** Two new skills exist to be loaded and followed; if the failure they route around does not occur, they are two skills competing for selection against the ones that should fire, plus a second code path for every Gitea operation, plus their own maintenance. #29 exists precisely because unfireable skills degrade selection for the rest.
Three options as I see them:
1. **Close as superseded**, with the brain decision note updated to say the WAF blocks stopped reproducing on 2026-08-29 and under what volume. If it returns, the issue is one revert away and the evidence is recorded.
2. **Reduce scope to a smoke test** — a small script that exercises the relay with a deliberately command-shaped payload, run on a schedule, so the next occurrence is detected rather than stumbled into. This is the "small tool over prose" answer from #19, and it is maybe thirty lines.
3. **Build both skills as designed.** Defensible if you have hit the block recently on a host I have not seen, which I would not know about.
I lean 2: it costs a fraction of 1, and it converts "we think this stopped" into something that will tell us if it starts again.
Worth noting independently of the verdict: the direct-REST pattern this issue specifies **is** in use and does work. Fetching Gitea Actions job logs today needed it, because no MCP tool exposes that endpoint — `op run --env-file ~/.op-env -- curl ... /actions/jobs/{id}/logs`. So the technique is proven; the question is only whether it deserves to be a loaded skill or stays an occasional inline call.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
MCP tool calls to the claude.ai Gitea (and by extension Brain) connectors intermittently fail with a Cloudflare "Sorry, you have been blocked" page. The block is on the Anthropic-hosted MCP relay POST -- the error page names
anthropic.com(e.g. IP 5.150.212.35), notgit.d-ma.be. It triggers on large or command-shaped bodies (WAF: "a SQL command or malformed data"). Observed 2026-06-20 while filing infra#129 (a multi-code-block issue):issue_createover MCP returned the CF block page; the identical issue filed cleanly via the Gitea REST API.The WAF sits upstream of our
gitea-mcpserver, in front of Anthropic's connector gateway -- we cannot tune it. The only durable lever is to route around the relay.Decision (see brain: homelab/decisions/prefer-rest-api-skills-over-mcp-on-cli-cloudflare-waf)
git.d-ma.be/brain-mcp.d-ma.bedirectly, never through the Anthropic relay -> sidesteps the WAF class entirely.~/.op-envthere). This is NOT a removal of the MCP.Deliverables
1.
gitea-restskillWrap the common Gitea ops as REST calls via
op run --env-file ~/.op-env:Pattern (token stays in the subprocess; body from file, never argv):
2.
brain-restskillquery:POST https://brain-mcp.d-ma.be/query{"query": "..."}with$BRAIN_MCP_TOKENwrite:POST .../write{"content": "...", "filename": "...", "wing": "...", "hall": "..."}answer:POST .../answer(if exposed)Acceptance criteria
gitea-restSKILL.md installed in~/dev/.skills/+mathias/skills, covering the issue/file/pr/repo ops above.brain-restSKILL.md covering query/write (+ answer).op run --env-file ~/.op-env; tokens never echoed or placed in argv; bodies passed via file/stdin.~/.op-envis absent.Constraints / footguns
~/.op-env; non-interactive Bash lacks on-demand 1P (gitea-mcp#36) -> alwaysop run --env-file ~/.op-env.Refs: gitea-mcp#36, infra#129, agentsquad#31. Brain: homelab/decisions/prefer-rest-api-skills-over-mcp-on-cli-cloudflare-waf.
Premise check before building anything, per this issue's own risk label.
The evidence against the premise still holding
This session (2026-08-29) has pushed roughly forty writes through the claude.ai Gitea MCP connector — the exact relay path this issue routes around. Many were multi-kilobyte issue comments containing fenced code blocks, shell pipelines, JSON payloads,
grep -Eregexes,sedexpressions and quoted error strings. Several are longer than the infra#129 body that originally triggered the block.Zero Cloudflare blocks. Not one retry, not one HTML error page.
Command-shaped content specifically — the WAF's stated trigger ("a SQL command or malformed data") — went through repeatedly:
op run --env-file=<(...)invocations,git push origin HEAD:main "$TAG",sed -n 's/.*"version"...',kubectl get externalsecret -o jsonpath=.... All accepted.What that does and does not prove
It does not prove the WAF rule was removed — I cannot see Cloudflare's config, and a rule that fires on a narrower pattern than I happened to send would look identical to no rule at all. What it does establish is that the operational problem this issue describes is not currently reproducing at the volume and shape that produced it.
The recommendation, which is yours to take or leave
Do not build
gitea-restandbrain-restyet. Two new skills exist to be loaded and followed; if the failure they route around does not occur, they are two skills competing for selection against the ones that should fire, plus a second code path for every Gitea operation, plus their own maintenance. #29 exists precisely because unfireable skills degrade selection for the rest.Three options as I see them:
I lean 2: it costs a fraction of 1, and it converts "we think this stopped" into something that will tell us if it starts again.
Worth noting independently of the verdict: the direct-REST pattern this issue specifies is in use and does work. Fetching Gitea Actions job logs today needed it, because no MCP tool exposes that endpoint —
op run --env-file ~/.op-env -- curl ... /actions/jobs/{id}/logs. So the technique is proven; the question is only whether it deserves to be a loaded skill or stays an occasional inline call.