Skip to content

fix(mcp): add --host flag to HTTP server bind address#232

Open
kleddbot wants to merge 3 commits intotobi:mainfrom
kleddbot:fix/mcp-http-host-flag
Open

fix(mcp): add --host flag to HTTP server bind address#232
kleddbot wants to merge 3 commits intotobi:mainfrom
kleddbot:fix/mcp-http-host-flag

Conversation

@kleddbot
Copy link

@kleddbot kleddbot commented Feb 20, 2026

Summary

Re-implements the --host MCP HTTP bind fix on top of current main (v1.1.x code shape), while keeping default behavior unchanged.

Closes #227.

Problem (still present on current main before this change)

  • qmd mcp --http binds with listen(port, "localhost").
  • In affected macOS/Colima setups, localhost resolves to ::1, creating an IPv6-only listener.
  • Docker clients hitting host.docker.internal (IPv4 path) cannot reach the daemon.

Implementation (v1.1.x-compatible)

1) Add explicit host control

  • New CLI option: qmd mcp --http --host <ADDR> [--port N]
  • --host is forwarded in daemon mode as well.

2) Centralize host normalization/validation

  • Added src/mcp-host.ts with:
    • validateMcpHostInput(rawHost)
    • normalizeMcpHost(rawHost)
  • Behavior:
    • "[::1]" -> bind "::1", display "[::1]"
    • empty/whitespace -> localhost
    • URL-like values (:// or /) rejected with clear error

3) Update MCP HTTP bind path

  • startMcpHttpServer(port, { quiet?, host? })
  • Binds to normalized host (bindHost) instead of hardcoded localhost.
  • Logs host-aware URL (displayHost).

4) Keep internal MCP Request URL host-independent

  • Internal new Request(...) wrappers still use http://localhost:${listeningPort}/mcp.
  • This avoids host-literal edge cases and keeps transport routing stable.

5) Fix latent ephemeral-port issue

  • Internal URL construction now uses the actual bound port (listeningPort set in listen() callback), avoiding :0 on ephemeral binds.

Files changed

  • src/mcp-host.ts (new)
  • src/mcp.ts
  • src/qmd.ts
  • test/mcp-host.test.ts (new)
  • test/mcp.test.ts
  • test/cli.test.ts
  • README.md
  • CLAUDE.md
  • skills/qmd/references/mcp-setup.md

Tests

Targeted

  • pnpm -s tsc -p tsconfig.build.json
  • pnpm vitest run test/mcp-host.test.ts (5/5)
  • pnpm vitest run test/mcp.test.ts (59/59)
  • pnpm vitest run test/cli.test.ts (69/69)

Full suite

  • pnpm vitest run test/ (556/556, 13 files)

Real-world pre-push smoke (Docker/Colima path)

Run in same host environment before push.

Case A: default bind (qmd mcp --http)

  • Host health (http://localhost:18921/health): 200
  • Container health (http://host.docker.internal:18921/health): 000
  • Listener: [::1]:18921

Case B: explicit IPv4 (qmd mcp --http --host 127.0.0.1)

  • Host health (http://localhost:18922/health): 200
  • Container health (http://host.docker.internal:18922/health): 200
  • Listener: 127.0.0.1:18922

This reproduces #227 and confirms the fix.

Backward compatibility

  • Without --host, behavior remains localhost bind.
  • Stdio MCP transport unchanged.
  • No changes to unrelated query/search behavior.

Follow-up (6ba8786): --host validation hardening

Found parseArgs({ strict: false }) edge cases where malformed --host values reached bind-time:

  • --hostcli.values.host === trueENOTFOUND true
  • --host --daemoncli.values.host === "--daemon"ENOTFOUND --daemon
  • --host localhost:8181ENOTFOUND localhost:8181

Fixes:

  • src/qmd.ts: reject non-string --host values early.
  • src/mcp-host.ts: reject flag-like values and host:port input; keep bare IPv6 valid via net.isIP().

Tests:

  • tsc -p tsconfig.build.json: clean
  • mcp-host.test.ts: 9/9 (was 5)
  • cli.test.ts: 72/72 (was 69)
  • Full suite: 563/563 (local run; CI is the merge gate)

@kleddbot kleddbot changed the title Add --host flag to MCP HTTP server feat(mcp): add --host flag to HTTP server bind address Feb 21, 2026
@kleddbot kleddbot changed the title feat(mcp): add --host flag to HTTP server bind address fix(mcp): add --host flag to HTTP server bind address Feb 22, 2026
Kledd added 2 commits February 25, 2026 00:38
Reuse a single normalized host value for daemon output and EADDRINUSE messaging in qmd mcp --http path.
@kleddbot kleddbot force-pushed the fix/mcp-http-host-flag branch from 8738690 to 330452d Compare February 25, 2026 06:30
parseArgs with strict:false produces boolean true for bare --host and
steals the next flag as the value for --host --daemon. Add a typeof
guard in qmd.ts and strengthen validateMcpHostInput to reject flag-like
values, host:port patterns (while preserving bare IPv6), and add
corresponding unit and integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kleddbot
Copy link
Author

Added follow-up hardening in 6ba8786; PR body updated with details.

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 --http] macOS: server binds IPv6-only via "localhost", unreachable from IPv4 clients (Docker/Colima)

1 participant