Wire cad-mcp into koala's local .mcp.json (Tailscale Serve, next free port) #32

Open
opened 2026-08-29 06:58:31 +00:00 by mathias · 3 comments
Owner

cad-mcp is live at cad-mcp.d-ma.be (deployment topology: mathias/brain wiki wing:agentsquad/facts/cad-live-on-koala-topology-and-wiring.md) but isn't wired as a local plugin MCP for sessions running on koala itself, unlike brain/infra which already are.

Pattern to follow (.mcp.json in this repo)

both existing entries go through Tailscale Serve on koala, not the public hostname -- the public path exits through Cloudflare + NPM to reach a k3s service on the same machine most sessions run on, which imposes rate limits and message-size caps. Serve terminates TLS locally with a real Tailscale cert and proxies to the NodePort over loopback, satisfying the default-deny-ingress NetworkPolicy with no cluster change (mathias/skills#24).

Current state, 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)

cad-mcp specifics (no cluster change needed)

cad's k8s Service is already type: NodePort -- confirmed live: kubectl get svc -n cad cad -> 8081:31877/TCP is the MCP port (JSON-RPC + SSE, tool: cad_status). No infra-repo change required for this one, unlike gitea-mcp (companion issue).

Do

  1. tailscale serve --bg --https=8445 http://127.0.0.1:31877 on koala (next free port after 8444).
  2. Add to .mcp.json:
    "cad": {
      "type": "http",
      "url": "https://koala.tail30472e.ts.net:8445/mcp",
      "headers": { "Authorization": "Bearer ${CAD_MCP_TOKEN}" }
    }
    
    (confirm the exact env var name against whatever ESO secret backs CAD_STATIC_TOKEN in k3s/apps/cad/ -- the fact doc names the secret CAD_STATIC_TOKEN, the local env var convention elsewhere is <SERVICE>_MCP_TOKEN, reconcile before wiring.)
  3. Wire the token into wherever koala sources BRAIN_MCP_TOKEN/INFRA_MCP_TOKEN today (~/.op-env) so ${CAD_MCP_TOKEN} resolves.

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)". cad-mcp's own topology doc says it serves /.well-known/oauth-protected-resource unauthenticated by design (v0.6.0 fix, so claude.ai OAuth discovery works) alongside the CAD_STATIC_TOKEN bearer fallback -- structurally the same shape that broke brain. This may hit the identical client-side bug. Verify a static-bearer local-plugin connection actually works end-to-end (not just that the header is sent) before considering this done; if it hits the same wall, this issue is blocked on hyperguild#9, not miswired.

Acceptance

  • claude mcp list (or equivalent) shows cad connected, not erroring into an OAuth redirect.
  • cad_status tool callable from a koala session.
  • If blocked by the OAuth-discovery bug, this issue says so explicitly and links hyperguild#9 rather than staying vague.
cad-mcp is live at `cad-mcp.d-ma.be` (deployment topology: `mathias/brain` wiki `wing:agentsquad/facts/cad-live-on-koala-topology-and-wiring.md`) but isn't wired as a local plugin MCP for sessions running on koala itself, unlike `brain`/`infra` which already are. ## Pattern to follow (`.mcp.json` in this repo) both existing entries go through Tailscale Serve on koala, not the public hostname -- the public path exits through Cloudflare + NPM to reach a k3s service on the *same machine* most sessions run on, which imposes rate limits and message-size caps. Serve terminates TLS locally with a real Tailscale cert and proxies to the NodePort over loopback, satisfying the default-deny-ingress NetworkPolicy with no cluster change (mathias/skills#24). Current state, 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) ``` ## cad-mcp specifics (no cluster change needed) `cad`'s k8s Service is already `type: NodePort` -- confirmed live: `kubectl get svc -n cad cad` -> `8081:31877/TCP` is the MCP port (JSON-RPC + SSE, tool: `cad_status`). No infra-repo change required for this one, unlike gitea-mcp (companion issue). ## Do 1. `tailscale serve --bg --https=8445 http://127.0.0.1:31877` on koala (next free port after 8444). 2. Add to `.mcp.json`: ```json "cad": { "type": "http", "url": "https://koala.tail30472e.ts.net:8445/mcp", "headers": { "Authorization": "Bearer ${CAD_MCP_TOKEN}" } } ``` (confirm the exact env var name against whatever ESO secret backs `CAD_STATIC_TOKEN` in `k3s/apps/cad/` -- the fact doc names the secret `CAD_STATIC_TOKEN`, the local env var convention elsewhere is `<SERVICE>_MCP_TOKEN`, reconcile before wiring.) 3. Wire the token into wherever koala sources `BRAIN_MCP_TOKEN`/`INFRA_MCP_TOKEN` today (`~/.op-env`) so `${CAD_MCP_TOKEN}` resolves. ## 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)"*. cad-mcp's own topology doc says it serves `/.well-known/oauth-protected-resource` **unauthenticated by design** (v0.6.0 fix, so claude.ai OAuth discovery works) alongside the CAD_STATIC_TOKEN bearer fallback -- structurally the same shape that broke brain. This may hit the identical client-side bug. Verify a static-bearer local-plugin connection actually works end-to-end (not just that the header is sent) before considering this done; if it hits the same wall, this issue is blocked on hyperguild#9, not miswired. ## Acceptance - `claude mcp list` (or equivalent) shows `cad` connected, not erroring into an OAuth redirect. - `cad_status` tool callable from a koala session. - If blocked by the OAuth-discovery bug, this issue says so explicitly and links hyperguild#9 rather than staying vague.
Author
Owner

Serve port is up and the proxy works; the wiring is blocked on vault visibility, and not on what this issue predicted.

Done

tailscale serve --bg --https=8445 http://127.0.0.1:31877 (run by Mathias on koala):

https://koala.tail30472e.ts.net:8445 (tailnet only)
|-- / proxy http://127.0.0.1:31877

Unauthenticated probe returns 401, so the path is right and auth is enforced — not a 404, not a hang, not an OAuth redirect.

Blocked

Step 3 — "wire the token into wherever koala sources BRAIN_MCP_TOKEN" — cannot be done from this host. CAD_STATIC_TOKEN lives in the AgentSecrets vault, and koala's op identity sees only HomeLab:

$ op vault list
ID                            NAME
uormr2z26h5zz3nlbgarfcnx24    HomeLab

$ op run --env-file=<(echo 'T=op://HomeLab/CAD_STATIC_TOKEN/password') -- ...
[ERROR] could not resolve item UUID for item CAD_STATIC_TOKEN:
        could not find item CAD_STATIC_TOKEN in vault uormr2z26h5zz3nlbgarfcnx24

$ op run --env-file=<(echo 'T=op://AgentSecrets/CAD_STATIC_TOKEN/password') -- ...
[ERROR] "AgentSecrets" isn't a vault in this account.

That is infra#224, the known gap where koala's service account cannot see AgentSecrets.

The item name comes from the ExternalSecret (cad-static-token → remoteRef.key: CAD_STATIC_TOKEN, property: password), read by reference only. No secret value was retrieved, printed or transformed at any point, and the in-cluster secret was deliberately not read — extracting it to route around the vault would defeat the reason it lives there.

The risk this issue named did not materialise

Both #32 and #33 warned this might hit the OAuth-discovery bug that parked brain. Two observations against that: brain is not parked any more — it is live in mcpServers and its tools worked throughout today's session — and cad's 401 is a plain token rejection, not an OAuth challenge. So the fear was reasonable and appears to be stale. It cannot be fully ruled out until a valid token is presented, because a client that ignores a good header would look identical from the outside.

Recorded, not enabled

.mcp.json gains a _parked block (7ba01ab) holding the exact entry, with the blocker in the file. A live entry that cannot authenticate would fail on every session start on this host, which is worse than an absent one.

To finish, in order:

  1. Resolve infra#224, or add a HomeLab-visible reference for the same secret.
  2. CAD_MCP_TOKEN=op://<vault>/CAD_STATIC_TOKEN/password in ~/.op-env.
  3. Move the entry from _parked into mcpServers.
  4. Then, and only then, this issue's acceptance test: cad_status callable from a koala session.

Naming note for step 2, which the issue asked to reconcile: the local convention is <SERVICE>_MCP_TOKEN, so CAD_MCP_TOKEN pointing at the CAD_STATIC_TOKEN item — env var named for the client, item named for the server.

Serve port is up and the proxy works; the wiring is **blocked on vault visibility**, and not on what this issue predicted. ## Done `tailscale serve --bg --https=8445 http://127.0.0.1:31877` (run by Mathias on koala): ``` https://koala.tail30472e.ts.net:8445 (tailnet only) |-- / proxy http://127.0.0.1:31877 ``` Unauthenticated probe returns **401**, so the path is right and auth is enforced — not a 404, not a hang, not an OAuth redirect. ## Blocked Step 3 — "wire the token into wherever koala sources `BRAIN_MCP_TOKEN`" — cannot be done from this host. `CAD_STATIC_TOKEN` lives in the **AgentSecrets** vault, and koala's `op` identity sees only HomeLab: ``` $ op vault list ID NAME uormr2z26h5zz3nlbgarfcnx24 HomeLab $ op run --env-file=<(echo 'T=op://HomeLab/CAD_STATIC_TOKEN/password') -- ... [ERROR] could not resolve item UUID for item CAD_STATIC_TOKEN: could not find item CAD_STATIC_TOKEN in vault uormr2z26h5zz3nlbgarfcnx24 $ op run --env-file=<(echo 'T=op://AgentSecrets/CAD_STATIC_TOKEN/password') -- ... [ERROR] "AgentSecrets" isn't a vault in this account. ``` That is **infra#224**, the known gap where koala's service account cannot see AgentSecrets. The item name comes from the ExternalSecret (`cad-static-token` → `remoteRef.key: CAD_STATIC_TOKEN`, `property: password`), read by reference only. No secret value was retrieved, printed or transformed at any point, and the in-cluster secret was deliberately not read — extracting it to route around the vault would defeat the reason it lives there. ## The risk this issue named did not materialise Both #32 and #33 warned this might hit the OAuth-discovery bug that parked `brain`. Two observations against that: `brain` is **not** parked any more — it is live in `mcpServers` and its tools worked throughout today's session — and cad's 401 is a plain token rejection, not an OAuth challenge. So the fear was reasonable and appears to be stale. It cannot be fully ruled out until a valid token is presented, because a client that ignores a good header would look identical from the outside. ## Recorded, not enabled `.mcp.json` gains a `_parked` block (`7ba01ab`) holding the exact entry, with the blocker in the file. A live entry that cannot authenticate would fail on every session start on this host, which is worse than an absent one. To finish, in order: 1. Resolve infra#224, or add a HomeLab-visible reference for the same secret. 2. `CAD_MCP_TOKEN=op://<vault>/CAD_STATIC_TOKEN/password` in `~/.op-env`. 3. Move the entry from `_parked` into `mcpServers`. 4. Then, and only then, this issue's acceptance test: `cad_status` callable from a koala session. Naming note for step 2, which the issue asked to reconcile: the local convention is `<SERVICE>_MCP_TOKEN`, so `CAD_MCP_TOKEN` pointing at the `CAD_STATIC_TOKEN` item — env var named for the client, item named for the server.
Author
Owner

Correcting my previous comment: the blocker I named was wrong, and the brain had the answer.

op vault list returning HomeLab only is not the whole story. koala carries a second, scoped service account at ~/.op-agent-write.env (koala-agentsecrets-write). infra#224 was closed on 2026-08-04 by provisioning exactly that, deliberately, rather than widening koala-homelab. So citing #224 as an open blocker was wrong twice — it is closed, and the capability exists.

With the right token sourced, cad authenticates:

$ set -a; . ~/.op-agent-write.env; set +a
$ op vault list
s2hsj4qipqvfynxzijmhszpdy4    AgentSecrets

$ op run --env-file=/tmp/probe.env -- curl -X POST .../8445/mcp -H "Authorization: Bearer $CAD_MCP_TOKEN" ...
200
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}},"protocolVersion":"2025-03-26",
 "serverInfo":{"name":"cad","version":"aed2aaf"}}}

A real MCP initialize result from cad aed2aaf. Nothing about the service, the NodePort, the Serve proxy or the token is broken. The OAuth-discovery risk this issue flagged did not materialise either.

What is actually blocking

Delivery into the session environment. By design, no single identity on koala reads both vaults — koala-homelab (HomeLab only) resolves ~/.op-env, koala-agentsecrets-write (AgentSecrets only) lives in a separate file. The session env comes from one op run --env-file ~/.op-env, so it structurally cannot carry an AgentSecrets reference.

And the obvious workaround is a trap, which is why I tested it before proposing it:

BRAIN_MCP_TOKEN=op://HomeLab/...            # known-good
GITEA_MCP_STATIC_TOKEN=op://AgentSecrets/...

$ 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 entire file. Adding the line to ~/.op-env would leave brain and every other token unresolved at next session start — it breaks what works today to fix what does not.

Filed as mathias/infra#329, recommending a third read-only AgentSecrets token plus two-pass resolution, rather than widening koala-homelab (which would undo what #224 deliberately chose).

.mcp.json's _parked reason is corrected in c377c23 — the entry is right, the token name is right, and the note now says the tokens work and the delivery does not.

Step 4 of this issue's acceptance (cad_status callable from a koala session) is one env-var away, not one debugging session away.

Correcting my previous comment: the blocker I named was wrong, and the brain had the answer. **`op vault list` returning HomeLab only is not the whole story.** koala carries a *second*, scoped service account at `~/.op-agent-write.env` (`koala-agentsecrets-write`). infra#224 was closed on 2026-08-04 by provisioning exactly that, deliberately, rather than widening `koala-homelab`. So citing #224 as an open blocker was wrong twice — it is closed, and the capability exists. With the right token sourced, cad authenticates: ``` $ set -a; . ~/.op-agent-write.env; set +a $ op vault list s2hsj4qipqvfynxzijmhszpdy4 AgentSecrets $ op run --env-file=/tmp/probe.env -- curl -X POST .../8445/mcp -H "Authorization: Bearer $CAD_MCP_TOKEN" ... 200 {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}},"protocolVersion":"2025-03-26", "serverInfo":{"name":"cad","version":"aed2aaf"}}} ``` A real MCP `initialize` result from `cad aed2aaf`. Nothing about the service, the NodePort, the Serve proxy or the token is broken. The OAuth-discovery risk this issue flagged did not materialise either. ## What is actually blocking Delivery into the session environment. By design, no single identity on koala reads both vaults — `koala-homelab` (HomeLab only) resolves `~/.op-env`, `koala-agentsecrets-write` (AgentSecrets only) lives in a separate file. The session env comes from one `op run --env-file ~/.op-env`, so it structurally cannot carry an AgentSecrets reference. And the obvious workaround is a trap, which is why I tested it before proposing it: ``` BRAIN_MCP_TOKEN=op://HomeLab/... # known-good GITEA_MCP_STATIC_TOKEN=op://AgentSecrets/... $ 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 entire file. Adding the line to `~/.op-env` would leave `brain` and every other token unresolved at next session start — it breaks what works today to fix what does not. Filed as **mathias/infra#329**, recommending a third read-only AgentSecrets token plus two-pass resolution, rather than widening `koala-homelab` (which would undo what #224 deliberately chose). `.mcp.json`'s `_parked` reason is corrected in `c377c23` — the entry is right, the token name is right, and the note now says the tokens work and the delivery does not. Step 4 of this issue's acceptance (`cad_status` callable from a koala session) is one env-var away, not one debugging session away.
Author
Owner

Narrowing this one: the shared _parked reason covered cad and gitea together, and it has now been disproven for gitea only (#33) — gitea-mcp is pass-through-authenticated, so it needed no AgentSecrets token at all.

cad is not in that position. It presents CAD_MCP_TOKEN, a dedicated static token in the AgentSecrets vault, so the identity split from infra#224 is a genuine blocker here and this issue stands as written.

Worth checking before doing the identity work, though: if cad-mcp could accept a pass-through credential the session already holds, this issue collapses the same way #33 did. Cheap to test — probe https://koala.tail30472e.ts.net:8445/mcp with a initialize call using each already-present token and see whether any returns 200.

Narrowing this one: the shared `_parked` reason covered **cad and gitea together**, and it has now been disproven for gitea only (#33) — gitea-mcp is pass-through-authenticated, so it needed no AgentSecrets token at all. **cad is not in that position.** It presents `CAD_MCP_TOKEN`, a dedicated static token in the AgentSecrets vault, so the identity split from infra#224 is a genuine blocker here and this issue stands as written. Worth checking before doing the identity work, though: if cad-mcp could accept a pass-through credential the session already holds, this issue collapses the same way #33 did. Cheap to test — probe `https://koala.tail30472e.ts.net:8445/mcp` with a `initialize` call using each already-present token and see whether any returns 200.
mathias added the risk/mediumsize/stype/ops labels 2026-09-16 22:22:12 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/skills#32