Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
5 changes: 3 additions & 2 deletions src/bin/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions test/oauth.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down