Skip to content

Commit 4feacb5

Browse files
committed
feat: add quiet option to suppress DNS rebinding warning
When binding to 0.0.0.0 or :: without allowedHosts, the server logs a warning about missing DNS rebinding protection. This is noisy for users who intentionally bind to all interfaces (e.g. behind a reverse proxy in a container). The new quiet option lets them opt out. Closes #1515
1 parent e563e63 commit 4feacb5

4 files changed

Lines changed: 58 additions & 4 deletions

File tree

packages/middleware/express/src/express.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ export interface CreateMcpExpressAppOptions {
3131
* @example '1mb', '500kb', '10mb'
3232
*/
3333
jsonLimit?: string;
34+
35+
/**
36+
* When `true`, suppresses the warning logged when binding to `'0.0.0.0'` or `'::'`
37+
* without `allowedHosts`. Useful when the server is behind a reverse proxy or
38+
* in a containerised environment where DNS rebinding is not a concern.
39+
*/
40+
quiet?: boolean;
3441
}
3542

3643
/**
@@ -60,7 +67,7 @@ export interface CreateMcpExpressAppOptions {
6067
* ```
6168
*/
6269
export function createMcpExpressApp(options: CreateMcpExpressAppOptions = {}): Express {
63-
const { host = '127.0.0.1', allowedHosts, jsonLimit } = options;
70+
const { host = '127.0.0.1', allowedHosts, jsonLimit, quiet } = options;
6471

6572
const app = express();
6673
app.use(express.json(jsonLimit ? { limit: jsonLimit } : undefined));
@@ -73,7 +80,7 @@ export function createMcpExpressApp(options: CreateMcpExpressAppOptions = {}): E
7380
const localhostHosts = ['127.0.0.1', 'localhost', '::1'];
7481
if (localhostHosts.includes(host)) {
7582
app.use(localhostHostValidation());
76-
} else if (host === '0.0.0.0' || host === '::') {
83+
} else if ((host === '0.0.0.0' || host === '::') && !quiet) {
7784
// Warn when binding to all interfaces without DNS rebinding protection
7885
// eslint-disable-next-line no-console
7986
console.warn(

packages/middleware/express/test/express.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,26 @@ describe('@modelcontextprotocol/express', () => {
167167
warn.mockRestore();
168168
});
169169

170+
test('should not warn for 0.0.0.0 when quiet is true', () => {
171+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
172+
173+
createMcpExpressApp({ host: '0.0.0.0', quiet: true });
174+
175+
expect(warn).not.toHaveBeenCalled();
176+
177+
warn.mockRestore();
178+
});
179+
180+
test('should not warn for :: when quiet is true', () => {
181+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
182+
183+
createMcpExpressApp({ host: '::', quiet: true });
184+
185+
expect(warn).not.toHaveBeenCalled();
186+
187+
warn.mockRestore();
188+
});
189+
170190
test('should not apply host validation for non-localhost hosts without allowedHosts', () => {
171191
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
172192

packages/middleware/hono/src/hono.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ export interface CreateMcpHonoAppOptions {
2222
* to restrict which hostnames are allowed.
2323
*/
2424
allowedHosts?: string[];
25+
26+
/**
27+
* When `true`, suppresses the warning logged when binding to `'0.0.0.0'` or `'::'`
28+
* without `allowedHosts`. Useful when the server is behind a reverse proxy or
29+
* in a containerised environment where DNS rebinding is not a concern.
30+
*/
31+
quiet?: boolean;
2532
}
2633

2734
/**
@@ -39,7 +46,7 @@ export interface CreateMcpHonoAppOptions {
3946
* @returns A configured Hono application
4047
*/
4148
export function createMcpHonoApp(options: CreateMcpHonoAppOptions = {}): Hono {
42-
const { host = '127.0.0.1', allowedHosts } = options;
49+
const { host = '127.0.0.1', allowedHosts, quiet } = options;
4350

4451
const app = new Hono();
4552

@@ -75,7 +82,7 @@ export function createMcpHonoApp(options: CreateMcpHonoAppOptions = {}): Hono {
7582
const localhostHosts = ['127.0.0.1', 'localhost', '::1'];
7683
if (localhostHosts.includes(host)) {
7784
app.use('*', localhostHostValidation());
78-
} else if (host === '0.0.0.0' || host === '::') {
85+
} else if ((host === '0.0.0.0' || host === '::') && !quiet) {
7986
// Warn when binding to all interfaces without DNS rebinding protection.
8087
// eslint-disable-next-line no-console
8188
console.warn(

packages/middleware/hono/test/hono.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ describe('@modelcontextprotocol/hono', () => {
6464
expect(res.status).toBe(200);
6565
});
6666

67+
test('createMcpHonoApp does not warn for 0.0.0.0 when quiet is true', () => {
68+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
69+
70+
createMcpHonoApp({ host: '0.0.0.0', quiet: true });
71+
72+
expect(warn).not.toHaveBeenCalled();
73+
74+
warn.mockRestore();
75+
});
76+
77+
test('createMcpHonoApp does not warn for :: when quiet is true', () => {
78+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
79+
80+
createMcpHonoApp({ host: '::', quiet: true });
81+
82+
expect(warn).not.toHaveBeenCalled();
83+
84+
warn.mockRestore();
85+
});
86+
6787
test('createMcpHonoApp parses JSON bodies into parsedBody (express.json()-like)', async () => {
6888
const app = createMcpHonoApp();
6989
app.post('/echo', (c: Context) => c.json(c.get('parsedBody')));

0 commit comments

Comments
 (0)