gitea-mcp is live at git-mcp.d-ma.be (mathias/infrak3s/apps/gitea-mcp/) but isn't wired as a local plugin MCP for sessions running on koala itself, unlike brain/infra which already are. (Sessions on koala can currently reach gitea only via a claude.ai custom connector -- OAuth-based, works fine, different mechanism from this repo's local .mcp.json.)
Pattern to follow (.mcp.json in this repo)
Both existing entries proxy through Tailscale Serve on koala to a NodePort over loopback, not the public hostname -- avoids Cloudflare/NPM rate limits + message-size caps, satisfies default-deny-ingress NetworkPolicy with no cluster change (mathias/skills#24). Confirmed live:
gitea-mcp specifics -- unlike cad-mcp, this needs a small cluster change first
gitea-mcp's Service is ClusterIP only today -- confirmed live: kubectl get svc -n gitea-mcp gitea-mcp -> 8080/TCP, no NodePort assigned. The Tailscale-Serve-to-loopback pattern needs a NodePort to proxy to. Do in mathias/infra:
# k3s/apps/gitea-mcp/service.yamlspec:type:NodePort # add thisports:- name:httpport:8080targetPort:8080# nodePort: <let k8s assign, or pick explicitly like cad did with 30083>
commit to mathias/infra main (Flux-managed, reconciles in ~60s), then kubectl get svc -n gitea-mcp gitea-mcp to read the assigned NodePort.
Do
Add the NodePort to gitea-mcp's Service (infra repo, one commit, kubectl diff first per that repo's own convention).
tailscale serve --bg --https=8446 http://127.0.0.1:<assigned-nodeport> on koala.
GITEA_MCP_TOKEN should already be sourced on koala (used by other MCP clients per infra#39's credential inventory) -- confirm before assuming it needs minting.
Risk to check BEFORE wiring, not after
brain is currently in .mcp.json's _disabled block: "parked until hyperguild#9 fixes static Bearer precedence -- claude MCP client does OAuth discovery on 401 and ignores the header, breaking on Dex (no dynamic client registration)". gitea-mcp's deployment wires the same dual-auth shape (Authentik OAuth for the claude-ai app issuer, alongside a static-bearer fallback via GITEA_MCP_STATIC_TOKEN/GITEA_MCP_DEFAULT_TOKEN) -- the same structural conflict that broke brain's static-bearer path may apply here too when accessed as a local plugin (as opposed to the working claude.ai OAuth connector, which is a different, already-proven-working path). Verify a static-bearer local-plugin connection actually authenticates end-to-end before calling this done.
Acceptance
gitea-mcp Service has a live NodePort, applied via the normal infra GitOps flow (not a manual kubectl patch).
claude mcp list shows gitea connected via the Tailscale Serve URL, not erroring into an OAuth redirect.
At least one gitea tool (e.g. repo_list) callable from a koala session through this new local wiring.
If blocked by the OAuth-discovery bug, this issue says so explicitly and links hyperguild#9 rather than staying vague.
gitea-mcp is live at `git-mcp.d-ma.be` (`mathias/infra` `k3s/apps/gitea-mcp/`) but isn't wired as a local plugin MCP for sessions running on koala itself, unlike `brain`/`infra` which already are. (Sessions on koala can currently reach gitea only via a claude.ai custom connector -- OAuth-based, works fine, different mechanism from this repo's local `.mcp.json`.)
## Pattern to follow (`.mcp.json` in this repo)
Both existing entries proxy through Tailscale Serve on koala to a NodePort over loopback, not the public hostname -- avoids Cloudflare/NPM rate limits + message-size caps, satisfies default-deny-ingress NetworkPolicy with no cluster change (mathias/skills#24). Confirmed live:
```
tailscale serve status
https://koala.tail30472e.ts.net:8443 -> 127.0.0.1:30330 (brain)
https://koala.tail30472e.ts.net:8444 -> 127.0.0.1:30342 (infra)
```
## gitea-mcp specifics -- unlike cad-mcp, this needs a small cluster change first
`gitea-mcp`'s Service is `ClusterIP` only today -- confirmed live: `kubectl get svc -n gitea-mcp gitea-mcp` -> `8080/TCP`, no NodePort assigned. The Tailscale-Serve-to-loopback pattern needs a NodePort to proxy to. Do in `mathias/infra`:
```yaml
# k3s/apps/gitea-mcp/service.yaml
spec:
type: NodePort # add this
ports:
- name: http
port: 8080
targetPort: 8080
# nodePort: <let k8s assign, or pick explicitly like cad did with 30083>
```
commit to `mathias/infra` main (Flux-managed, reconciles in ~60s), then `kubectl get svc -n gitea-mcp gitea-mcp` to read the assigned NodePort.
## Do
1. Add the NodePort to gitea-mcp's Service (infra repo, one commit, `kubectl diff` first per that repo's own convention).
2. `tailscale serve --bg --https=8446 http://127.0.0.1:<assigned-nodeport>` on koala.
3. Add to `.mcp.json`:
```json
"gitea": {
"type": "http",
"url": "https://koala.tail30472e.ts.net:8446/mcp",
"headers": { "Authorization": "Bearer ${GITEA_MCP_TOKEN}" }
}
```
`GITEA_MCP_TOKEN` should already be sourced on koala (used by other MCP clients per infra#39's credential inventory) -- confirm before assuming it needs minting.
## Risk to check BEFORE wiring, not after
`brain` is currently in `.mcp.json`'s `_disabled` block: *"parked until hyperguild#9 fixes static Bearer precedence -- claude MCP client does OAuth discovery on 401 and ignores the header, breaking on Dex (no dynamic client registration)"*. gitea-mcp's deployment wires the same dual-auth shape (Authentik OAuth for the `claude-ai` app issuer, alongside a static-bearer fallback via `GITEA_MCP_STATIC_TOKEN`/`GITEA_MCP_DEFAULT_TOKEN`) -- the same structural conflict that broke brain's static-bearer path may apply here too when accessed as a local plugin (as opposed to the working claude.ai OAuth connector, which is a different, already-proven-working path). Verify a static-bearer local-plugin connection actually authenticates end-to-end before calling this done.
## Acceptance
- gitea-mcp Service has a live NodePort, applied via the normal infra GitOps flow (not a manual kubectl patch).
- `claude mcp list` shows `gitea` connected via the Tailscale Serve URL, not erroring into an OAuth redirect.
- At least one gitea tool (e.g. `repo_list`) callable from a koala session through this new local wiring.
- If blocked by the OAuth-discovery bug, this issue says so explicitly and links hyperguild#9 rather than staying vague.
Two of this issue's premises are stale, and the real blocker is a third thing. Serve port is up; wiring is parked.
The NodePort already exists — no infra change needed
This issue says gitea-mcp's Service is ClusterIP-only and step 1 is a mathias/infra commit. Live state:
$ kubectl get svc -n gitea-mcp
NAME TYPE CLUSTER-IP PORT(S)
gitea-mcp ClusterIP 10.43.253.121 8080/TCP
gitea-mcp-nodeport NodePort 10.43.19.4 8080:30340/TCP 93d
A separate gitea-mcp-nodeport Service has existed for 93 days. Step 1 is already done, by a different shape than proposed — a second Service rather than a type change on the first. Worth knowing which is intended before anyone "fixes" it.
Done
tailscale serve --bg --https=8446 http://127.0.0.1:30340 (run by Mathias on koala). Unauthenticated probe → 401, so the path is right and auth is enforced.
Blocked, and not on the token this issue expected
The issue says GITEA_MCP_TOKEN "should already be sourced on koala — confirm before assuming it needs minting". It is sourced, and it is the wrong token:
$ op run --env-file ~/.op-env -- curl ... -H "Authorization: Bearer $GITEA_MCP_TOKEN" .../mcp
401
unauthorized
The server takes its static token from a different item. From the ExternalSecret:
~/.op-env's GITEA_MCP_TOKEN points at op://HomeLab/GITEA_MCP_TOKEN/password — a different item entirely. So the confirmation this issue asked for has an answer, and it is "no".
And the correct item is not reachable from here either:
$ op vault list
uormr2z26h5zz3nlbgarfcnx24 HomeLab # and nothing else
$ op run --env-file=<(echo 'T=op://HomeLab/gitea-mcp-shared-token/password') -- ...
[ERROR] could not find item gitea-mcp-shared-token in vault uormr2z26h5zz3nlbgarfcnx24
It lives in AgentSecrets, which koala's op identity cannot see — infra#224, the same blocker as #32. No secret value was retrieved, printed or transformed; the item names come from ExternalSecret references, and the in-cluster secret was deliberately not read.
Not the OAuth bug
This issue and #32 both flagged the static-Bearer-vs-OAuth conflict that parked brain as the likely wall. Two things argue otherwise: brain is no longer parked — it is live in mcpServers and its tools worked all session — and the 401 here is a plain token rejection with body unauthorized, not an OAuth challenge. Not conclusive until a valid token is tried, since a client that ignored a good header would look the same from outside, but the evidence points at the vault, not the protocol.
Recorded, not enabled
.mcp.json gains a _parked block (7ba01ab) with the entry and the blocker inline, referencing ${GITEA_MCP_STATIC_TOKEN} rather than the misleading GITEA_MCP_TOKEN. A live entry that 401s would fail on every session start.
To finish:
Resolve infra#224, or expose gitea-mcp-shared-token via HomeLab.
GITEA_MCP_STATIC_TOKEN=op://<vault>/gitea-mcp-shared-token/password in ~/.op-env.
Move the entry into mcpServers.
Then the acceptance test: repo_list through the Tailscale Serve URL.
Also worth deciding separately: what GITEA_MCP_TOKEN in ~/.op-env is actually for, since it does not authenticate against this server. Either it serves another client or it is dead config, and dead credential config is its own small hazard.
Two of this issue's premises are stale, and the real blocker is a third thing. Serve port is up; wiring is parked.
## The NodePort already exists — no infra change needed
This issue says `gitea-mcp`'s Service is ClusterIP-only and step 1 is a `mathias/infra` commit. Live state:
```
$ kubectl get svc -n gitea-mcp
NAME TYPE CLUSTER-IP PORT(S)
gitea-mcp ClusterIP 10.43.253.121 8080/TCP
gitea-mcp-nodeport NodePort 10.43.19.4 8080:30340/TCP 93d
```
A separate `gitea-mcp-nodeport` Service has existed for 93 days. Step 1 is already done, by a different shape than proposed — a second Service rather than a type change on the first. Worth knowing which is intended before anyone "fixes" it.
## Done
`tailscale serve --bg --https=8446 http://127.0.0.1:30340` (run by Mathias on koala). Unauthenticated probe → **401**, so the path is right and auth is enforced.
## Blocked, and not on the token this issue expected
The issue says `GITEA_MCP_TOKEN` "should already be sourced on koala — confirm before assuming it needs minting". It is sourced, and it is the **wrong token**:
```
$ op run --env-file ~/.op-env -- curl ... -H "Authorization: Bearer $GITEA_MCP_TOKEN" .../mcp
401
unauthorized
```
The server takes its static token from a different item. From the ExternalSecret:
```
gitea-mcp-static-token: remoteRef.key=gitea-mcp-shared-token, property=password
-> secretKey GITEA_MCP_STATIC_TOKEN
```
`~/.op-env`'s `GITEA_MCP_TOKEN` points at `op://HomeLab/GITEA_MCP_TOKEN/password` — a different item entirely. So the confirmation this issue asked for has an answer, and it is "no".
And the correct item is not reachable from here either:
```
$ op vault list
uormr2z26h5zz3nlbgarfcnx24 HomeLab # and nothing else
$ op run --env-file=<(echo 'T=op://HomeLab/gitea-mcp-shared-token/password') -- ...
[ERROR] could not find item gitea-mcp-shared-token in vault uormr2z26h5zz3nlbgarfcnx24
```
It lives in **AgentSecrets**, which koala's `op` identity cannot see — **infra#224**, the same blocker as #32. No secret value was retrieved, printed or transformed; the item names come from ExternalSecret references, and the in-cluster secret was deliberately not read.
## Not the OAuth bug
This issue and #32 both flagged the static-Bearer-vs-OAuth conflict that parked `brain` as the likely wall. Two things argue otherwise: `brain` is no longer parked — it is live in `mcpServers` and its tools worked all session — and the 401 here is a plain token rejection with body `unauthorized`, not an OAuth challenge. Not conclusive until a valid token is tried, since a client that ignored a good header would look the same from outside, but the evidence points at the vault, not the protocol.
## Recorded, not enabled
`.mcp.json` gains a `_parked` block (`7ba01ab`) with the entry and the blocker inline, referencing `${GITEA_MCP_STATIC_TOKEN}` rather than the misleading `GITEA_MCP_TOKEN`. A live entry that 401s would fail on every session start.
To finish:
1. Resolve infra#224, or expose `gitea-mcp-shared-token` via HomeLab.
2. `GITEA_MCP_STATIC_TOKEN=op://<vault>/gitea-mcp-shared-token/password` in `~/.op-env`.
3. Move the entry into `mcpServers`.
4. Then the acceptance test: `repo_list` through the Tailscale Serve URL.
Also worth deciding separately: what `GITEA_MCP_TOKEN` in `~/.op-env` is actually for, since it does not authenticate against this server. Either it serves another client or it is dead config, and dead credential config is its own small hazard.
Correcting my previous comment on the blocker, and confirming the token finding — which turns out to be a known, already-diagnosed structural issue rather than a new one.
gitea-mcp authenticates
koala carries a second scoped service account at ~/.op-agent-write.env (koala-agentsecrets-write), provisioned when infra#224 was closed on 2026-08-04. Citing #224 as an open blocker was wrong; it is closed, and by design rather than by neglect.
$ set -a; . ~/.op-agent-write.env; set +a
$ op run --env-file=/tmp/probe.env -- curl -X POST .../8446/mcp \
-H "Authorization: Bearer $GITEA_MCP_STATIC_TOKEN" ...
200
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}},"protocolVersion":"2025-03-26",
"serverInfo":{"name":"gitea-mcp","version":"0.1.0"}}}
So: NodePort exists (93 days), Serve proxy works, static bearer authenticates, no OAuth wall. Everything this issue set out to verify, verified.
The GITEA_MCP_TOKEN 401 has a documented cause
I reported it as "the wrong token, possibly dead config". The brain has the actual history — a 2026-07-30 entry recorded during infra#228's rotation:
GITEA_MCP_TOKEN (client bearer, consumed by pi-runner/crush-runner/agentsquad-serve) and GITEA_MCP_STATIC_TOKEN (gitea-mcp's own server-side compare value) were two separate 1Password HomeLab items that happened to need the identical value for the static-bearer auth scheme to work. [...] Every rotation required remembering to edit both items to the same new value; nothing enforced it.
That was fixed by pointing every consumer at one item, and moving it to AgentSecrets — correct on both counts. What did not follow was ~/.op-env, which still references the old HomeLab GITEA_MCP_TOKEN item. The item still exists, so the reference resolves cleanly and then fails auth. A stale credential that resolves and silently 401s is worse than an absent one: it reads as configured, which is exactly how I read it an hour ago.
Not the token — the delivery. No single identity on koala reads both vaults (deliberately, per #224's resolution), and the session env comes from one op run --env-file ~/.op-env under the HomeLab-only token. Appending an AgentSecrets reference does not fail gracefully:
$ op run --env-file=/tmp/mixed.env -- sh -c '[ -n "$BRAIN_MCP_TOKEN" ] && echo resolved'
[ERROR] "AgentSecrets" isn't a vault in this account.
op run aborts the whole file — brain would go down with it. Tested before proposing, on a copy, precisely because that is the obvious next move for whoever picks this up.
.mcp.json's _parked entry now names ${GITEA_MCP_STATIC_TOKEN} with the corrected reason (c377c23). Moving it into mcpServers is one env-var away.
Correcting my previous comment on the blocker, and confirming the token finding — which turns out to be a known, already-diagnosed structural issue rather than a new one.
## gitea-mcp authenticates
koala carries a second scoped service account at `~/.op-agent-write.env` (`koala-agentsecrets-write`), provisioned when infra#224 was closed on 2026-08-04. Citing #224 as an open blocker was wrong; it is closed, and by design rather than by neglect.
```
$ set -a; . ~/.op-agent-write.env; set +a
$ op run --env-file=/tmp/probe.env -- curl -X POST .../8446/mcp \
-H "Authorization: Bearer $GITEA_MCP_STATIC_TOKEN" ...
200
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}},"protocolVersion":"2025-03-26",
"serverInfo":{"name":"gitea-mcp","version":"0.1.0"}}}
```
So: NodePort exists (93 days), Serve proxy works, static bearer authenticates, no OAuth wall. Everything this issue set out to verify, verified.
## The `GITEA_MCP_TOKEN` 401 has a documented cause
I reported it as "the wrong token, possibly dead config". The brain has the actual history — a 2026-07-30 entry recorded during infra#228's rotation:
> `GITEA_MCP_TOKEN` (client bearer, consumed by pi-runner/crush-runner/agentsquad-serve) and `GITEA_MCP_STATIC_TOKEN` (gitea-mcp's own server-side compare value) were two *separate* 1Password HomeLab items that happened to need the identical value for the static-bearer auth scheme to work. [...] Every rotation required remembering to edit both items to the same new value; nothing enforced it.
That was fixed by pointing every consumer at **one** item, and moving it to AgentSecrets — correct on both counts. What did not follow was `~/.op-env`, which still references the old HomeLab `GITEA_MCP_TOKEN` item. The item still exists, so the reference resolves cleanly and then fails auth. A stale credential that resolves and silently 401s is worse than an absent one: it reads as configured, which is exactly how I read it an hour ago.
Cleanup is on **mathias/infra#329** along with the real blocker.
## What is actually blocking
Not the token — the delivery. No single identity on koala reads both vaults (deliberately, per #224's resolution), and the session env comes from one `op run --env-file ~/.op-env` under the HomeLab-only token. Appending an AgentSecrets reference does not fail gracefully:
```
$ op run --env-file=/tmp/mixed.env -- sh -c '[ -n "$BRAIN_MCP_TOKEN" ] && echo resolved'
[ERROR] "AgentSecrets" isn't a vault in this account.
```
`op run` aborts the whole file — `brain` would go down with it. Tested before proposing, on a copy, precisely because that is the obvious next move for whoever picks this up.
`.mcp.json`'s `_parked` entry now names `${GITEA_MCP_STATIC_TOKEN}` with the corrected reason (`c377c23`). Moving it into `mcpServers` is one env-var away.
Both stated prerequisites turned out to already be satisfied:
"needs a NodePort first" — gitea-mcp/gitea-mcp-nodeport has exposed 8080:30340 for 102 days, and tailscale serve already maps 8446 -> 127.0.0.1:30340. Nothing had to be provisioned.
"nothing puts its token in the session environment" (the _parked reason, pointing at the AgentSecrets split from infra#224) — this never applied to gitea-mcp. It authenticates by pass-through: the bearer is a Gitea PAT, which the server validates against Gitea and then acts as. There is no dedicated MCP token to be missing. DMABE_GITEA_API_TOKEN is op://HomeLab, already in the session env.
Evidence, koala 2026-09-07 against https://koala.tail30472e.ts.net:8446/mcp:
bearer
result
none
401
GITEA_MCP_TOKEN
401
DMABE_GITEA_API_TOKEN
200, serverInfo: gitea-mcp, tools/list → 41 tools
DMABE_GITEA_API_GITOPS_TOKEN
200
Two different PATs both authenticating while a third bearer 401s is what proves pass-through validation rather than a static-token comparison — a static token would admit exactly one value.
Also lands bin/check-mcp-env + tests/mcp_config_test.sh, wired into task check, so a server interpolating a variable the env-file does not define fails the gate instead of failing silently at runtime as "requires authentication".
Done in v0.19.0 (`da76a6b`).
Both stated prerequisites turned out to already be satisfied:
- **"needs a NodePort first"** — `gitea-mcp/gitea-mcp-nodeport` has exposed `8080:30340` for 102 days, and `tailscale serve` already maps `8446 -> 127.0.0.1:30340`. Nothing had to be provisioned.
- **"nothing puts its token in the session environment"** (the `_parked` reason, pointing at the AgentSecrets split from infra#224) — **this never applied to gitea-mcp.** It authenticates by *pass-through*: the bearer is a Gitea PAT, which the server validates against Gitea and then acts as. There is no dedicated MCP token to be missing. `DMABE_GITEA_API_TOKEN` is `op://HomeLab`, already in the session env.
Evidence, koala 2026-09-07 against `https://koala.tail30472e.ts.net:8446/mcp`:
| bearer | result |
|---|---|
| none | 401 |
| `GITEA_MCP_TOKEN` | 401 |
| `DMABE_GITEA_API_TOKEN` | 200, `serverInfo: gitea-mcp`, `tools/list` → **41 tools** |
| `DMABE_GITEA_API_GITOPS_TOKEN` | 200 |
Two *different* PATs both authenticating while a third bearer 401s is what proves pass-through validation rather than a static-token comparison — a static token would admit exactly one value.
Also lands `bin/check-mcp-env` + `tests/mcp_config_test.sh`, wired into `task check`, so a server interpolating a variable the env-file does not define fails the gate instead of failing silently at runtime as "requires authentication".
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.
gitea-mcp is live at
git-mcp.d-ma.be(mathias/infrak3s/apps/gitea-mcp/) but isn't wired as a local plugin MCP for sessions running on koala itself, unlikebrain/infrawhich already are. (Sessions on koala can currently reach gitea only via a claude.ai custom connector -- OAuth-based, works fine, different mechanism from this repo's local.mcp.json.)Pattern to follow (
.mcp.jsonin this repo)Both existing entries proxy through Tailscale Serve on koala to a NodePort over loopback, not the public hostname -- avoids Cloudflare/NPM rate limits + message-size caps, satisfies default-deny-ingress NetworkPolicy with no cluster change (mathias/skills#24). Confirmed live:
gitea-mcp specifics -- unlike cad-mcp, this needs a small cluster change first
gitea-mcp's Service isClusterIPonly today -- confirmed live:kubectl get svc -n gitea-mcp gitea-mcp->8080/TCP, no NodePort assigned. The Tailscale-Serve-to-loopback pattern needs a NodePort to proxy to. Do inmathias/infra:commit to
mathias/inframain (Flux-managed, reconciles in ~60s), thenkubectl get svc -n gitea-mcp gitea-mcpto read the assigned NodePort.Do
kubectl difffirst per that repo's own convention).tailscale serve --bg --https=8446 http://127.0.0.1:<assigned-nodeport>on koala..mcp.json:GITEA_MCP_TOKENshould already be sourced on koala (used by other MCP clients per infra#39's credential inventory) -- confirm before assuming it needs minting.Risk to check BEFORE wiring, not after
brainis currently in.mcp.json's_disabledblock: "parked until hyperguild#9 fixes static Bearer precedence -- claude MCP client does OAuth discovery on 401 and ignores the header, breaking on Dex (no dynamic client registration)". gitea-mcp's deployment wires the same dual-auth shape (Authentik OAuth for theclaude-aiapp issuer, alongside a static-bearer fallback viaGITEA_MCP_STATIC_TOKEN/GITEA_MCP_DEFAULT_TOKEN) -- the same structural conflict that broke brain's static-bearer path may apply here too when accessed as a local plugin (as opposed to the working claude.ai OAuth connector, which is a different, already-proven-working path). Verify a static-bearer local-plugin connection actually authenticates end-to-end before calling this done.Acceptance
claude mcp listshowsgiteaconnected via the Tailscale Serve URL, not erroring into an OAuth redirect.repo_list) callable from a koala session through this new local wiring.Two of this issue's premises are stale, and the real blocker is a third thing. Serve port is up; wiring is parked.
The NodePort already exists — no infra change needed
This issue says
gitea-mcp's Service is ClusterIP-only and step 1 is amathias/infracommit. Live state:A separate
gitea-mcp-nodeportService has existed for 93 days. Step 1 is already done, by a different shape than proposed — a second Service rather than a type change on the first. Worth knowing which is intended before anyone "fixes" it.Done
tailscale serve --bg --https=8446 http://127.0.0.1:30340(run by Mathias on koala). Unauthenticated probe → 401, so the path is right and auth is enforced.Blocked, and not on the token this issue expected
The issue says
GITEA_MCP_TOKEN"should already be sourced on koala — confirm before assuming it needs minting". It is sourced, and it is the wrong token:The server takes its static token from a different item. From the ExternalSecret:
~/.op-env'sGITEA_MCP_TOKENpoints atop://HomeLab/GITEA_MCP_TOKEN/password— a different item entirely. So the confirmation this issue asked for has an answer, and it is "no".And the correct item is not reachable from here either:
It lives in AgentSecrets, which koala's
opidentity cannot see — infra#224, the same blocker as #32. No secret value was retrieved, printed or transformed; the item names come from ExternalSecret references, and the in-cluster secret was deliberately not read.Not the OAuth bug
This issue and #32 both flagged the static-Bearer-vs-OAuth conflict that parked
brainas the likely wall. Two things argue otherwise:brainis no longer parked — it is live inmcpServersand its tools worked all session — and the 401 here is a plain token rejection with bodyunauthorized, not an OAuth challenge. Not conclusive until a valid token is tried, since a client that ignored a good header would look the same from outside, but the evidence points at the vault, not the protocol.Recorded, not enabled
.mcp.jsongains a_parkedblock (7ba01ab) with the entry and the blocker inline, referencing${GITEA_MCP_STATIC_TOKEN}rather than the misleadingGITEA_MCP_TOKEN. A live entry that 401s would fail on every session start.To finish:
gitea-mcp-shared-tokenvia HomeLab.GITEA_MCP_STATIC_TOKEN=op://<vault>/gitea-mcp-shared-token/passwordin~/.op-env.mcpServers.repo_listthrough the Tailscale Serve URL.Also worth deciding separately: what
GITEA_MCP_TOKENin~/.op-envis actually for, since it does not authenticate against this server. Either it serves another client or it is dead config, and dead credential config is its own small hazard.Correcting my previous comment on the blocker, and confirming the token finding — which turns out to be a known, already-diagnosed structural issue rather than a new one.
gitea-mcp authenticates
koala carries a second scoped service account at
~/.op-agent-write.env(koala-agentsecrets-write), provisioned when infra#224 was closed on 2026-08-04. Citing #224 as an open blocker was wrong; it is closed, and by design rather than by neglect.So: NodePort exists (93 days), Serve proxy works, static bearer authenticates, no OAuth wall. Everything this issue set out to verify, verified.
The
GITEA_MCP_TOKEN401 has a documented causeI reported it as "the wrong token, possibly dead config". The brain has the actual history — a 2026-07-30 entry recorded during infra#228's rotation:
That was fixed by pointing every consumer at one item, and moving it to AgentSecrets — correct on both counts. What did not follow was
~/.op-env, which still references the old HomeLabGITEA_MCP_TOKENitem. The item still exists, so the reference resolves cleanly and then fails auth. A stale credential that resolves and silently 401s is worse than an absent one: it reads as configured, which is exactly how I read it an hour ago.Cleanup is on mathias/infra#329 along with the real blocker.
What is actually blocking
Not the token — the delivery. No single identity on koala reads both vaults (deliberately, per #224's resolution), and the session env comes from one
op run --env-file ~/.op-envunder the HomeLab-only token. Appending an AgentSecrets reference does not fail gracefully:op runaborts the whole file —brainwould go down with it. Tested before proposing, on a copy, precisely because that is the obvious next move for whoever picks this up..mcp.json's_parkedentry now names${GITEA_MCP_STATIC_TOKEN}with the corrected reason (c377c23). Moving it intomcpServersis one env-var away.Done in v0.19.0 (
da76a6b).Both stated prerequisites turned out to already be satisfied:
gitea-mcp/gitea-mcp-nodeporthas exposed8080:30340for 102 days, andtailscale servealready maps8446 -> 127.0.0.1:30340. Nothing had to be provisioned._parkedreason, pointing at the AgentSecrets split from infra#224) — this never applied to gitea-mcp. It authenticates by pass-through: the bearer is a Gitea PAT, which the server validates against Gitea and then acts as. There is no dedicated MCP token to be missing.DMABE_GITEA_API_TOKENisop://HomeLab, already in the session env.Evidence, koala 2026-09-07 against
https://koala.tail30472e.ts.net:8446/mcp:GITEA_MCP_TOKENDMABE_GITEA_API_TOKENserverInfo: gitea-mcp,tools/list→ 41 toolsDMABE_GITEA_API_GITOPS_TOKENTwo different PATs both authenticating while a third bearer 401s is what proves pass-through validation rather than a static-token comparison — a static token would admit exactly one value.
Also lands
bin/check-mcp-env+tests/mcp_config_test.sh, wired intotask check, so a server interpolating a variable the env-file does not define fails the gate instead of failing silently at runtime as "requires authentication".