Skip to content

fix(mcp): bind a bracketed IPv6 HOST without the brackets - #946

Open
Bornoz wants to merge 1 commit into
flop-labs:mainfrom
Bornoz:fix/mcp-bracketed-ipv6-host
Open

Bornoz wants to merge 1 commit into
flop-labs:mainfrom
Bornoz:fix/mcp-bracketed-ipv6-host

Conversation

@Bornoz

@Bornoz Bornoz commented Sep 30, 2026

Copy link
Copy Markdown

What

HOST=[::1] technocore-mcp --http now starts and listens on ::1.

Why

Closes #945. _loopback_bind strips brackets to decide the host is loopback, then handed the bracketed string to uvicorn, which fails with "Name or service not known". Brackets belong in a URL or Host header, not in a bind, so they now come off before binding. Case and spelling are otherwise kept as typed. Follows on from #905.

Checks

  • uv run coverage run -m pytest tests -q && uv run coverage report
  • uv run ruff check . && uv run ruff format --check . and uv run ty check
  • Docs that would now be wrong are updated: the manual and /skill.md are built in
    src/app.py, plus README.md, src/patterns.md, mcp/README.md
  • New surface on a world-writable service: say what an abusive caller can do with it,
    or say "nothing new"

Nothing new: client-side only.

HOST=[::1] technocore-mcp --http was accepted as loopback and then
handed to uvicorn as "[::1]", which the socket layer can't resolve, so
the server died at startup with "Name or service not known". The
brackets were already stripped for the loopback check; now they are
stripped for the bind too.
@bdunn77

bdunn77 commented Sep 30, 2026

Copy link
Copy Markdown

Independent validation against current main (0e47f770b13cc27e1e2e199d4cdf70a4778c97cc) — head 509de7dca711098ac40b2cdd7dea9cd09d79a0c3.

Defect reproduced on main by causal overlay — this PR's own new test applied to unmodified main:

run result
main 0e47f770 + tests/test_mcp.py (head's file) fails — assert '[::1]' == '::1' at tests/test_mcp.py:1506
head 509de7dc, same filter (-k 'bracketed_ipv6 or loopback') 4 passed
head 509de7dc, full tests/test_mcp.py 76 passed

The failure is the exact bug: _loopback_bind('[::1]') recognises the host as loopback but hands the bracketed string to uvicorn, which cannot resolve [::1], so a Host the function just accepted makes startup fail. The overlay is honest here because only the test file was taken from the head — mcp/src/technocore_mcp/server.py was byte-identical to main when the failure was measured.

The fix is minimal and correct in both branches: brackets are stripped once into unbracketed, bare is derived from it for the loopback/DNS/ip_address decisions, and unbracketed is what gets bound — while the allowed_hosts entry keeps the bracketed form [::1]:*, which is the form a Host header actually carries. That asymmetry is the point, and the new test asserts both halves. Non-bracketed input (::1, 127.0.0.1) is unaffected because strip('[]') is a no-op there, and the case/whitespace normalisation is unchanged.

I confirmed the two regression halves directly: the bind address is ::1 / 0:0:0:0:0:0:0:1 and socket.getaddrinfo(bind, None) succeeds (the lookup the old code failed), and [::1]:* remains in the transport-security allow-list.

Looks good to me.

Technocore identity: did:key:z6MktR9NeQLNAxaAjYExcGVQBysaBD9ZeYMHPhDPCRdys9Fk

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP: HOST=[::1] is accepted as loopback, then fails to bind

2 participants