diff --git a/README.md b/README.md index b011177..e79b89b 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ steered at an arbitrary host or driven with an unverified token. | `NETBIRD_MAX_RPM` | both | `110` | Client-side rate cap (under NetBird's 120/min) | | `NETBIRD_TIMEOUT_MS` | both | `30000` | Per-request timeout | | `LOG_LEVEL` | both | `info` | `debug` \| `info` \| `warn` \| `error` | +| `HOST` | cloud | `0.0.0.0` | Interface the HTTP server binds to; use `127.0.0.1` for a local-only daemon | | `PORT` | cloud | `3000` | HTTP listen port | | `PUBLIC_BASE_URL` | cloud | `http://localhost:PORT` | Public HTTPS origin advertised in OAuth metadata | | `NETBIRD_ENABLE_OAUTH` | cloud | `true` | Enable the OAuth 2.1 authorization server | diff --git a/src/bin/http.ts b/src/bin/http.ts index f84565a..9da8566 100644 --- a/src/bin/http.ts +++ b/src/bin/http.ts @@ -27,7 +27,7 @@ import { loginRateLimiter } from "../oauth/loginRateLimit.js"; */ const config = loadConfigOrExit(); const logger = createLogger(config.logLevel); -const { port, tokenHeader, urlHeader, oauthEnabled, directPatEnabled, publicBaseUrl, verifyPatOnLogin, trustProxy } = +const { host, port, tokenHeader, urlHeader, oauthEnabled, directPatEnabled, publicBaseUrl, verifyPatOnLogin, trustProxy } = config.http; const provider = new NetBirdOAuthProvider({ @@ -158,8 +158,9 @@ const methodNotAllowed = (_req: express.Request, res: express.Response) => { app.get("/mcp", methodNotAllowed); app.delete("/mcp", methodNotAllowed); -app.listen(port, () => { +app.listen(port, host, () => { logger.info("netbird mcp server ready (http)", { + host, port, endpoint: "/mcp", publicBaseUrl, diff --git a/src/config.ts b/src/config.ts index 27c5826..32483e6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,6 +20,8 @@ export type LogLevel = "debug" | "info" | "warn" | "error"; /** HTTP (cloud) entrypoint settings — unused by the stdio entrypoint. */ export interface HttpConfig { + /** Host/interface the Streamable HTTP server binds to. */ + host: string; /** Port the Streamable HTTP server listens on. */ port: number; /** Header carrying a direct NetBird PAT (fallback auth path). */ @@ -160,6 +162,7 @@ export function loadServerConfig(env: NodeJS.ProcessEnv = process.env): ServerCo logLevel, allowedApiHosts, http: { + host: env.HOST?.trim() || "0.0.0.0", port, tokenHeader: env.NETBIRD_TOKEN_HEADER ?? DEFAULT_TOKEN_HEADER, urlHeader: env.NETBIRD_URL_HEADER ?? DEFAULT_URL_HEADER, diff --git a/test/config.test.ts b/test/config.test.ts index 33b678f..abbdee5 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -49,6 +49,7 @@ describe("loadServerConfig — http sub-object defaults", () => { it("resolves documented defaults when nothing is set", () => { const config = loadServerConfig({} as NodeJS.ProcessEnv); expect(config.http).toEqual({ + host: "0.0.0.0", port: 3000, tokenHeader: "x-netbird-token", urlHeader: "x-netbird-api-url", @@ -70,6 +71,7 @@ describe("loadServerConfig — http sub-object defaults", () => { describe("loadServerConfig — http sub-object explicit values", () => { it("honors every explicit http value", () => { const config = loadServerConfig({ + HOST: "127.0.0.1", PORT: "4000", NETBIRD_TOKEN_HEADER: "x-custom-token", NETBIRD_URL_HEADER: "x-custom-url", @@ -78,6 +80,7 @@ describe("loadServerConfig — http sub-object explicit values", () => { NETBIRD_VERIFY_PAT_ON_LOGIN: "false", } as NodeJS.ProcessEnv); expect(config.http).toEqual({ + host: "127.0.0.1", port: 4000, tokenHeader: "x-custom-token", urlHeader: "x-custom-url", @@ -94,6 +97,13 @@ describe("loadServerConfig — http sub-object explicit values", () => { expect(config.http.port).toBe(9090); }); + it("trims HOST and falls back to all interfaces when blank", () => { + expect(loadServerConfig({ HOST: " 127.0.0.1 " } as NodeJS.ProcessEnv).http.host).toBe( + "127.0.0.1", + ); + expect(loadServerConfig({ HOST: " " } as NodeJS.ProcessEnv).http.host).toBe("0.0.0.0"); + }); + it("parses NETBIRD_TRUST_PROXY: unset -> false, integer -> hop count, preset -> passthrough", () => { const trust = (v?: string) => loadServerConfig({ NETBIRD_TRUST_PROXY: v } as NodeJS.ProcessEnv).http.trustProxy; diff --git a/test/oauth.e2e.test.ts b/test/oauth.e2e.test.ts index 71f766d..7a07fbe 100644 --- a/test/oauth.e2e.test.ts +++ b/test/oauth.e2e.test.ts @@ -119,6 +119,7 @@ beforeAll(async () => { child = spawn(TSX, [ENTRY], { env: { ...process.env, + HOST: "127.0.0.1", PORT: String(port), NETBIRD_ENABLE_OAUTH: "true", PUBLIC_BASE_URL: base, diff --git a/test/tools.test.ts b/test/tools.test.ts index ceca317..a33cff1 100644 --- a/test/tools.test.ts +++ b/test/tools.test.ts @@ -36,6 +36,7 @@ const baseConfig: ServerConfig = { logLevel: "error", allowedApiHosts: ["api.netbird.io"], http: { + host: "127.0.0.1", port: 3000, tokenHeader: "x-netbird-token", urlHeader: "x-netbird-api-url",