{"version":3,"file":"index.cjs","names":[],"sources":["../src/middleware/hostHeaderValidation.ts","../src/middleware/originValidation.ts","../src/fastify.ts"],"sourcesContent":["import { localhostAllowedHostnames, validateHostHeader } from '@modelcontextprotocol/server';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\n/**\n * Fastify onRequest hook for DNS rebinding protection.\n * Validates `Host` header hostname (port-agnostic) against an allowed list.\n *\n * This is particularly important for servers without authorization or HTTPS,\n * such as localhost servers or development servers. DNS rebinding attacks can\n * bypass same-origin policy by manipulating DNS to point a domain to a\n * localhost address, allowing malicious websites to access your local server.\n *\n * @param allowedHostnames - List of allowed hostnames (without ports).\n *   For IPv6, provide the address with brackets (e.g., `[::1]`).\n * @returns Fastify onRequest hook handler\n *\n * @example\n * ```ts source=\"./hostHeaderValidation.examples.ts#hostHeaderValidation_basicUsage\"\n * app.addHook('onRequest', hostHeaderValidation(['localhost', '127.0.0.1', '[::1]']));\n * ```\n */\nexport function hostHeaderValidation(allowedHostnames: string[]) {\n    return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n        const result = validateHostHeader(request.headers.host, allowedHostnames);\n        if (!result.ok) {\n            await reply.code(403).send({\n                jsonrpc: '2.0',\n                error: {\n                    code: -32_000,\n                    message: result.message\n                },\n                id: null\n            });\n        }\n    };\n}\n\n/**\n * Convenience hook for localhost DNS rebinding protection.\n * Allows only `localhost`, `127.0.0.1`, and `[::1]` (IPv6 localhost) hostnames.\n *\n * @example\n * ```ts source=\"./hostHeaderValidation.examples.ts#localhostHostValidation_basicUsage\"\n * app.addHook('onRequest', localhostHostValidation());\n * ```\n */\nexport function localhostHostValidation() {\n    return hostHeaderValidation(localhostAllowedHostnames());\n}\n","import { localhostAllowedOrigins, validateOriginHeader } from '@modelcontextprotocol/server';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\n/**\n * Fastify onRequest hook for Origin header validation.\n * Validates the `Origin` header hostname (port-agnostic) against an allowed list.\n *\n * Browsers attach an `Origin` header to cross-origin requests; validating it —\n * alongside Host header validation — protects localhost and development servers\n * against DNS rebinding and cross-site request forgery. Requests without an\n * `Origin` header pass (non-browser MCP clients do not send one); a present\n * value that is not allowed, or that cannot be parsed, is rejected with `403`.\n *\n * @param allowedOriginHostnames - List of allowed origin hostnames (without scheme or port), or `<scheme>://*` entries.\n *   For IPv6, provide the address with brackets (e.g., `[::1]`).\n * @returns Fastify onRequest hook handler\n *\n * @example\n * ```ts\n * app.addHook('onRequest', originValidation(['localhost', '127.0.0.1', '[::1]']));\n * ```\n */\nexport function originValidation(allowedOriginHostnames: string[]) {\n    return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n        const result = validateOriginHeader(request.headers.origin, allowedOriginHostnames);\n        if (!result.ok) {\n            await reply.code(403).send({\n                jsonrpc: '2.0',\n                error: {\n                    code: -32_000,\n                    message: result.message\n                },\n                id: null\n            });\n        }\n    };\n}\n\n/**\n * Convenience hook for localhost Origin validation.\n * Allows only origins whose hostname is `localhost`, `127.0.0.1`, or `[::1]` (IPv6 localhost).\n *\n * @example\n * ```ts\n * app.addHook('onRequest', localhostOriginValidation());\n * ```\n */\nexport function localhostOriginValidation() {\n    return originValidation(localhostAllowedOrigins());\n}\n","import type { FastifyInstance } from 'fastify';\nimport Fastify from 'fastify';\n\nimport { hostHeaderValidation, localhostHostValidation } from './middleware/hostHeaderValidation';\nimport { localhostOriginValidation, originValidation } from './middleware/originValidation';\n\n/**\n * Options for creating an MCP Fastify application.\n */\nexport interface CreateMcpFastifyAppOptions {\n    /**\n     * The hostname to bind to. Defaults to `'127.0.0.1'`.\n     * When set to `'127.0.0.1'`, `'localhost'`, or `'::1'`, DNS rebinding protection is automatically enabled.\n     */\n    host?: string;\n\n    /**\n     * List of allowed hostnames for DNS rebinding protection.\n     * If provided, host header validation will be applied using this list.\n     * For IPv6, provide addresses with brackets (e.g., `'[::1]'`).\n     *\n     * This is useful when binding to `'0.0.0.0'` or `'::'` but still wanting\n     * to restrict which hostnames are allowed.\n     */\n    allowedHosts?: string[];\n\n    /**\n     * List of allowed origin hostnames for Origin header validation.\n     * If provided, Origin validation will be applied using this list (port-agnostic,\n     * hostnames only — the same convention as `allowedHosts`).\n     *\n     * When omitted, Origin validation is automatically enabled for localhost-class\n     * binds (the same condition as host validation): requests without an `Origin`\n     * header pass, while a present `Origin` whose hostname is not localhost-class\n     * is rejected with `403`.\n     *\n     * A client that runs as a browser extension sends its extension ID as the origin\n     * hostname: list that ID to admit one extension. A lowercase entry of the form\n     * `<scheme>://*` (e.g. `'moz-extension://*'`) admits every extension of that browser.\n     * `http://*` and `https://*` are not honoured.\n     */\n    allowedOrigins?: string[];\n}\n\n/**\n * Creates a Fastify application pre-configured for MCP servers.\n *\n * When the host is `'127.0.0.1'`, `'localhost'`, or `'::1'` (the default is `'127.0.0.1'`),\n * DNS rebinding protection is automatically applied via an onRequest hook to protect against\n * DNS rebinding attacks on localhost servers.\n *\n * Fastify parses JSON request bodies by default, so no additional middleware is required\n * for MCP Streamable HTTP endpoints.\n *\n * @param options - Configuration options\n * @returns A configured Fastify application\n *\n * @example Basic usage - defaults to 127.0.0.1 with DNS rebinding protection\n * ```ts source=\"./fastify.examples.ts#createMcpFastifyApp_default\"\n * const app = createMcpFastifyApp();\n * ```\n *\n * @example Custom host - DNS rebinding protection only applied for localhost hosts\n * ```ts source=\"./fastify.examples.ts#createMcpFastifyApp_customHost\"\n * const appOpen = createMcpFastifyApp({ host: '0.0.0.0' }); // No automatic DNS rebinding protection\n * const appLocal = createMcpFastifyApp({ host: 'localhost' }); // DNS rebinding protection enabled\n * ```\n *\n * @example Custom allowed hosts for non-localhost binding\n * ```ts source=\"./fastify.examples.ts#createMcpFastifyApp_allowedHosts\"\n * const app = createMcpFastifyApp({ host: '0.0.0.0', allowedHosts: ['myapp.local', 'localhost'] });\n * ```\n */\nexport function createMcpFastifyApp(options: CreateMcpFastifyAppOptions = {}): FastifyInstance {\n    const { host = '127.0.0.1', allowedHosts, allowedOrigins } = options;\n\n    const app = Fastify();\n\n    // Fastify parses JSON by default - no middleware needed\n\n    // If allowedHosts is explicitly provided, use that for validation\n    if (allowedHosts) {\n        app.addHook('onRequest', hostHeaderValidation(allowedHosts));\n    } else {\n        // Apply DNS rebinding protection automatically for localhost hosts\n        const localhostHosts = ['127.0.0.1', 'localhost', '::1'];\n        if (localhostHosts.includes(host)) {\n            app.addHook('onRequest', localhostHostValidation());\n        } else if (host === '0.0.0.0' || host === '::') {\n            // Warn when binding to all interfaces without DNS rebinding protection\n            app.log.warn(\n                `Server is binding to ${host} without DNS rebinding protection. ` +\n                    'Consider using the allowedHosts option to restrict allowed hosts, ' +\n                    'or use authentication to protect your server.'\n            );\n        }\n    }\n\n    // Origin validation follows the same arming ladder as host validation:\n    // an explicit allowlist wins; otherwise localhost-class binds are protected\n    // by default. Requests without an Origin header always pass.\n    if (allowedOrigins) {\n        app.addHook('onRequest', originValidation(allowedOrigins));\n    } else if (['127.0.0.1', 'localhost', '::1'].includes(host)) {\n        app.addHook('onRequest', localhostOriginValidation());\n    }\n\n    return app;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBAAqB,kBAA4B;AAC7D,QAAO,OAAO,SAAyB,UAAuC;EAC1E,MAAM,8DAA4B,QAAQ,QAAQ,MAAM,iBAAiB;AACzE,MAAI,CAAC,OAAO,GACR,OAAM,MAAM,KAAK,IAAI,CAAC,KAAK;GACvB,SAAS;GACT,OAAO;IACH,MAAM;IACN,SAAS,OAAO;IACnB;GACD,IAAI;GACP,CAAC;;;;;;;;;;;;AAcd,SAAgB,0BAA0B;AACtC,QAAO,kFAAgD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;ACzB5D,SAAgB,iBAAiB,wBAAkC;AAC/D,QAAO,OAAO,SAAyB,UAAuC;EAC1E,MAAM,gEAA8B,QAAQ,QAAQ,QAAQ,uBAAuB;AACnF,MAAI,CAAC,OAAO,GACR,OAAM,MAAM,KAAK,IAAI,CAAC,KAAK;GACvB,SAAS;GACT,OAAO;IACH,MAAM;IACN,SAAS,OAAO;IACnB;GACD,IAAI;GACP,CAAC;;;;;;;;;;;;AAcd,SAAgB,4BAA4B;AACxC,QAAO,4EAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyBtD,SAAgB,oBAAoB,UAAsC,EAAE,EAAmB;CAC3F,MAAM,EAAE,OAAO,aAAa,cAAc,mBAAmB;CAE7D,MAAM,4BAAe;AAKrB,KAAI,aACA,KAAI,QAAQ,aAAa,qBAAqB,aAAa,CAAC;UAGrC;EAAC;EAAa;EAAa;EAAM,CACrC,SAAS,KAAK,CAC7B,KAAI,QAAQ,aAAa,yBAAyB,CAAC;UAC5C,SAAS,aAAa,SAAS,KAEtC,KAAI,IAAI,KACJ,wBAAwB,KAAK,oJAGhC;AAOT,KAAI,eACA,KAAI,QAAQ,aAAa,iBAAiB,eAAe,CAAC;UACnD;EAAC;EAAa;EAAa;EAAM,CAAC,SAAS,KAAK,CACvD,KAAI,QAAQ,aAAa,2BAA2B,CAAC;AAGzD,QAAO"}