Fix HTTP MCP Host header rejection for lunch-money-mcp #24

Closed
opened 2026-07-08 16:16:53 +00:00 by coilyco-ops · 2 comments
Member

The live lunch-money MCP is broken after the deploy workflow self-triggered on coilyco-bridge/deploy@ace09843ff.

Observed:

  • deploy run: https://forgejo.coilysiren.me/coilyco-bridge/deploy/actions/runs/477
  • run metadata: deploy-lunch-money-mcp.yml, commit ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1, failed in ~2s.
  • deploy pin at that commit: services/lunch-money-mcp/values.env has IMAGE_TAG=sha-a0b5197.
  • source main is a0b51979a635c961f0ac8cb41bed2e392cb7b496 and build-publish.yml succeeded for that exact commit, so the deploy pin matches the intended source image tag.
  • live metadata is up: https://lunch-money.coilysiren.me/.well-known/oauth-protected-resource returns 200.
  • live public MCP is broken: https://lunch-money.coilysiren.me/mcp returns HTTP 500 without a bearer token, where the other gated MCPs normally fail as 401/302-ish auth gates rather than backend errors.
  • tailnet direct is broken: http://kai-server.tail09a41b.ts.net:30080/mcp returns HTTP/1.1 421 Misdirected Request with body Invalid Host header from uvicorn.

Likely root cause: MCP Python SDK / FastMCP DNS rebinding protection. lunch_money_mcp/server.py constructs mcp = FastMCP("lunch-money") at import time, then later mutates mcp.settings.host / mcp.settings.port in main(). Recent MCP SDK versions decide transport security / allowed hosts during FastMCP(...) construction, so late host mutation leaves a localhost-only allowed-host policy and rejects real deployment hosts.

Please fix the source server so HTTP mode accepts the deployed hosts intentionally, without disabling the safety boundary blindly. Expected hosts for this deployment:

  • lunch-money.coilysiren.me
  • kai-server:30080
  • kai-server.tail09a41b.ts.net:30080
  • localhost:30080 or equivalent local test host if useful

Prefer env-driven configuration so deploy can own hostnames without source edits, for example LUNCH_MONEY_MCP_ALLOWED_HOSTS / LUNCH_MONEY_MCP_ALLOWED_ORIGINS parsed by the server and passed into FastMCP transport security at construction time, or pass the HTTP host/port to FastMCP(...) early enough that the SDK configures itself correctly. Add focused tests for the HTTP configuration path.

After the source fix lands and the image publishes, update coilyco-bridge/deploy (granted repo) to pin the new sha-<short> image tag in services/lunch-money-mcp/values.env and roll it. Verify both:

  • curl -sI https://lunch-money.coilysiren.me/mcp no longer returns backend 500 for a plain unauthenticated request.
  • curl -sI http://kai-server.tail09a41b.ts.net:30080/mcp no longer returns 421 Invalid Host header.
The live lunch-money MCP is broken after the deploy workflow self-triggered on coilyco-bridge/deploy@ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1. Observed: - deploy run: https://forgejo.coilysiren.me/coilyco-bridge/deploy/actions/runs/477 - run metadata: `deploy-lunch-money-mcp.yml`, commit `ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1`, failed in ~2s. - deploy pin at that commit: `services/lunch-money-mcp/values.env` has `IMAGE_TAG=sha-a0b5197`. - source main is `a0b51979a635c961f0ac8cb41bed2e392cb7b496` and `build-publish.yml` succeeded for that exact commit, so the deploy pin matches the intended source image tag. - live metadata is up: `https://lunch-money.coilysiren.me/.well-known/oauth-protected-resource` returns 200. - live public MCP is broken: `https://lunch-money.coilysiren.me/mcp` returns HTTP 500 without a bearer token, where the other gated MCPs normally fail as 401/302-ish auth gates rather than backend errors. - tailnet direct is broken: `http://kai-server.tail09a41b.ts.net:30080/mcp` returns `HTTP/1.1 421 Misdirected Request` with body `Invalid Host header` from uvicorn. Likely root cause: MCP Python SDK / FastMCP DNS rebinding protection. `lunch_money_mcp/server.py` constructs `mcp = FastMCP("lunch-money")` at import time, then later mutates `mcp.settings.host` / `mcp.settings.port` in `main()`. Recent MCP SDK versions decide transport security / allowed hosts during `FastMCP(...)` construction, so late host mutation leaves a localhost-only allowed-host policy and rejects real deployment hosts. Please fix the source server so HTTP mode accepts the deployed hosts intentionally, without disabling the safety boundary blindly. Expected hosts for this deployment: - `lunch-money.coilysiren.me` - `kai-server:30080` - `kai-server.tail09a41b.ts.net:30080` - `localhost:30080` or equivalent local test host if useful Prefer env-driven configuration so deploy can own hostnames without source edits, for example `LUNCH_MONEY_MCP_ALLOWED_HOSTS` / `LUNCH_MONEY_MCP_ALLOWED_ORIGINS` parsed by the server and passed into FastMCP transport security at construction time, or pass the HTTP host/port to `FastMCP(...)` early enough that the SDK configures itself correctly. Add focused tests for the HTTP configuration path. After the source fix lands and the image publishes, update `coilyco-bridge/deploy` (granted repo) to pin the new `sha-<short>` image tag in `services/lunch-money-mcp/values.env` and roll it. Verify both: - `curl -sI https://lunch-money.coilysiren.me/mcp` no longer returns backend 500 for a plain unauthenticated request. - `curl -sI http://kai-server.tail09a41b.ts.net:30080/mcp` no longer returns `421 Invalid Host header`.
Author
Member

🔒 Reserved by ward agent --harness codex — container engineer-codex-lunch-money-k8s-24 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-08T16:17:03Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/lunch-money-k8s#24 · branch issue-24 · harness codex · workflow direct-main
  • Run: engineer-codex-lunch-money-k8s-24 · ward v0.444.0 · dispatched 2026-07-08T16:17:03Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Issue body as seeded:

The live lunch-money MCP is broken after the deploy workflow self-triggered on coilyco-bridge/deploy@ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1.

Observed:

- deploy run: https://forgejo.coilysiren.me/coilyco-bridge/deploy/actions/runs/477
- run metadata: `deploy-lunch-money-mcp.yml`, commit `ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1`, failed in ~2s.
- deploy pin at that commit: `services/lunch-money-mcp/values.env` has `IMAGE_TAG=sha-a0b5197`.
- source main is `a0b51979a635c961f0ac8cb41bed2e392cb7b496` and `build-publish.yml` succeeded for that exact commit, so the deploy pin matches the intended source image tag.
- live metadata is up: `https://lunch-money.coilysiren.me/.well-known/oauth-protected-resource` returns 200.
- live public MCP is broken: `https://lunch-money.coilysiren.me/mcp` returns HTTP 500 without a bearer token, where the other gated MCPs normally fail as 401/302-ish auth gates rather than backend errors.
- tailnet direct is broken: `http://kai-server.tail09a41b.ts.net:30080/mcp` returns `HTTP/1.1 421 Misdirected Request` with body `Invalid Host header` from uvicorn.

Likely root cause: MCP Python SDK / FastMCP DNS rebinding protection. `lunch_money_mcp/server.py` constructs `mcp = FastMCP("lunch-money")` at import time, then later mutates `mcp.settings.host` / `mcp.settings.port` in `main()`. Recent MCP SDK versions decide transport security / allowed hosts during `FastMCP(...)` construction, so late host mutation leaves a localhost-only allowed-host policy and rejects real deployment hosts.

Please fix the source server so HTTP mode accepts the deployed hosts intentionally, without disabling the safety boundary blindly. Expected hosts for this deployment:

- `lunch-money.coilysiren.me`
- `kai-server:30080`
- `kai-server.tail09a41b.ts.net:30080`
- `localhost:30080` or equivalent local test host if useful

Prefer env-driven configuration so deploy can own hostnames without source edits, for example `LUNCH_MONEY_MCP_ALLOWED_HOSTS` / `LUNCH_MONEY_MCP_AL

… (truncated to 2000 chars; full body is on this issue)

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.444.0).

— Codex, via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --harness codex` — container `engineer-codex-lunch-money-k8s-24` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-08T16:17:03Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/lunch-money-k8s#24` · branch `issue-24` · harness `codex` · workflow `direct-main` - **Run:** `engineer-codex-lunch-money-k8s-24` · ward `v0.444.0` · dispatched `2026-07-08T16:17:03Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). **Issue body as seeded:** ``` The live lunch-money MCP is broken after the deploy workflow self-triggered on coilyco-bridge/deploy@ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1. Observed: - deploy run: https://forgejo.coilysiren.me/coilyco-bridge/deploy/actions/runs/477 - run metadata: `deploy-lunch-money-mcp.yml`, commit `ace09843ff8b3702c1b4a79d4d10460c6a7c3dd1`, failed in ~2s. - deploy pin at that commit: `services/lunch-money-mcp/values.env` has `IMAGE_TAG=sha-a0b5197`. - source main is `a0b51979a635c961f0ac8cb41bed2e392cb7b496` and `build-publish.yml` succeeded for that exact commit, so the deploy pin matches the intended source image tag. - live metadata is up: `https://lunch-money.coilysiren.me/.well-known/oauth-protected-resource` returns 200. - live public MCP is broken: `https://lunch-money.coilysiren.me/mcp` returns HTTP 500 without a bearer token, where the other gated MCPs normally fail as 401/302-ish auth gates rather than backend errors. - tailnet direct is broken: `http://kai-server.tail09a41b.ts.net:30080/mcp` returns `HTTP/1.1 421 Misdirected Request` with body `Invalid Host header` from uvicorn. Likely root cause: MCP Python SDK / FastMCP DNS rebinding protection. `lunch_money_mcp/server.py` constructs `mcp = FastMCP("lunch-money")` at import time, then later mutates `mcp.settings.host` / `mcp.settings.port` in `main()`. Recent MCP SDK versions decide transport security / allowed hosts during `FastMCP(...)` construction, so late host mutation leaves a localhost-only allowed-host policy and rejects real deployment hosts. Please fix the source server so HTTP mode accepts the deployed hosts intentionally, without disabling the safety boundary blindly. Expected hosts for this deployment: - `lunch-money.coilysiren.me` - `kai-server:30080` - `kai-server.tail09a41b.ts.net:30080` - `localhost:30080` or equivalent local test host if useful Prefer env-driven configuration so deploy can own hostnames without source edits, for example `LUNCH_MONEY_MCP_ALLOWED_HOSTS` / `LUNCH_MONEY_MCP_AL ``` … (truncated to 2000 chars; full body is on this issue) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.444.0). </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

Additional fleet probe from the deploy director on 2026-07-08:

  • node-stats: public metadata 200, public /mcp 401, tailnet MCP initialize 200.
  • reddit: public metadata 200, public /mcp 401, tailnet MCP initialize 200.
  • steam: public metadata 200, public /mcp 401, tailnet MCP initialize 200.
  • skillsmp: public metadata 200, public /mcp 401. No deploy-repo NodePort found to test locally from this surface.
  • forgejo-mcp: public metadata 200, public /mcp 401. No deploy-repo NodePort found to test locally from this surface.
  • lunch-money: public metadata 200, public /mcp 500, tailnet MCP initialize to http://kai-server.tail09a41b.ts.net:30080/mcp returns 421 Invalid Host header.

So lunch-money is the only non-Playwright MCP failing this fleet probe, and its failure still matches the FastMCP Host-header diagnosis in this issue.

Additional fleet probe from the deploy director on 2026-07-08: - node-stats: public metadata 200, public /mcp 401, tailnet MCP initialize 200. - reddit: public metadata 200, public /mcp 401, tailnet MCP initialize 200. - steam: public metadata 200, public /mcp 401, tailnet MCP initialize 200. - skillsmp: public metadata 200, public /mcp 401. No deploy-repo NodePort found to test locally from this surface. - forgejo-mcp: public metadata 200, public /mcp 401. No deploy-repo NodePort found to test locally from this surface. - lunch-money: public metadata 200, public /mcp 500, tailnet MCP initialize to `http://kai-server.tail09a41b.ts.net:30080/mcp` returns 421 `Invalid Host header`. So lunch-money is the only non-Playwright MCP failing this fleet probe, and its failure still matches the FastMCP Host-header diagnosis in this issue.
Sign in to join this conversation.
No description provided.