@daloyjs/core 1.0.0-rc.6 → 1.0.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,8 +42,22 @@ export interface IpRestrictionOptions {
42
42
  * to `false` because those headers are client-spoofable unless every
43
43
  * request reaches Daloy through a proxy chain you control. Pair with
44
44
  * `new App({ trustProxy: true })` in production.
45
+ *
46
+ * When enabled, the resolver reads the **rightmost** `X-Forwarded-For`
47
+ * entry — the one your immediate proxy appended — never the
48
+ * attacker-influenceable leftmost one, so a spoofed left entry cannot
49
+ * bypass an allow-list or dodge a deny. Behind more than one proxy hop,
50
+ * set {@link trustedHops} instead.
45
51
  */
46
52
  trustProxyHeaders?: boolean;
53
+ /**
54
+ * Declare exactly how many proxy hops sit between Daloy and the public
55
+ * internet. Implies proxy-header trust and reads the client IP that many
56
+ * entries from the right of `X-Forwarded-For` via
57
+ * {@link "./conn-info.js".resolveForwardedClientIp}. Must be an integer in
58
+ * [1, 64]; validated at construction.
59
+ */
60
+ trustedHops?: number;
47
61
  /**
48
62
  * Response message when a request is rejected. Defaults to
49
63
  * `"IP address not permitted"`. Avoid echoing the client IP back —
@@ -8,6 +8,7 @@
8
8
  * @since 0.19.0
9
9
  */
10
10
  import { ForbiddenError } from "./errors.js";
11
+ import { resolveForwardedClientIp, resolveForwardedTrust } from "./conn-info.js";
11
12
  /**
12
13
  * Block or allow requests by source IP / CIDR range. In direct Web-standard
13
14
  * runtimes, pass `resolveIp` from the adapter-specific connection metadata.
@@ -40,7 +41,8 @@ export function ipRestriction(opts) {
40
41
  }
41
42
  const allow = (opts.allow ?? []).map(compileCidrMatcher);
42
43
  const deny = (opts.deny ?? []).map(compileCidrMatcher);
43
- const resolveIp = opts.resolveIp ?? (opts.trustProxyHeaders ? forwardedIpResolver : noIpResolver);
44
+ const hops = resolveForwardedTrust("ipRestriction()", opts);
45
+ const resolveIp = opts.resolveIp ?? (hops !== undefined ? forwardedIpResolver(hops) : noIpResolver);
44
46
  const message = opts.message ?? "IP address not permitted";
45
47
  return {
46
48
  beforeHandle(ctx) {
@@ -62,12 +64,8 @@ export function ipRestriction(opts) {
62
64
  function noIpResolver(_ctx) {
63
65
  return undefined;
64
66
  }
65
- function forwardedIpResolver(ctx) {
66
- const headers = ctx.request.headers;
67
- const forwarded = headers.get("x-forwarded-for");
68
- if (forwarded)
69
- return forwarded.split(",")[0]?.trim();
70
- return headers.get("x-real-ip") ?? undefined;
67
+ function forwardedIpResolver(hops) {
68
+ return (ctx) => resolveForwardedClientIp(ctx.request, hops);
71
69
  }
72
70
  /**
73
71
  * Test whether a parsed IP falls inside a compiled CIDR matcher, comparing
package/dist/mcp.d.ts CHANGED
@@ -2,17 +2,65 @@ import type { PathString, RouteDefinition } from "./types.js";
2
2
  /**
3
3
  * Latest MCP protocol version DaloyJS negotiates by default.
4
4
  *
5
- * @see https://modelcontextprotocol.io/specification/2025-11-25
5
+ * @see https://modelcontextprotocol.io/specification/2026-07-28
6
6
  * @since 1.0.0
7
7
  */
8
- export declare const MCP_PROTOCOL_VERSION = "2025-11-25";
8
+ export declare const MCP_PROTOCOL_VERSION = "2026-07-28";
9
+ /**
10
+ * First MCP revision of the *stateless* ("modern") era: version, client
11
+ * identity, and client capabilities travel in each request's `_meta` instead
12
+ * of being established once by an `initialize` handshake.
13
+ *
14
+ * Revisions are `YYYY-MM-DD` strings, so a lexicographic comparison against
15
+ * this constant correctly classifies every past and future revision.
16
+ *
17
+ * @since 1.0.0
18
+ */
19
+ export declare const MCP_MODERN_ERA_MIN_VERSION = "2026-07-28";
9
20
  /**
10
21
  * Protocol revisions accepted by {@link createMcpHandler} unless the caller
11
22
  * provides an explicit `protocolVersions` list.
12
23
  *
24
+ * The list spans both protocol eras: the stateless `2026-07-28` revision and
25
+ * the older handshake-based revisions, so one endpoint serves modern and
26
+ * legacy MCP clients at the same time.
27
+ *
13
28
  * @since 1.0.0
14
29
  */
15
30
  export declare const MCP_PROTOCOL_VERSIONS: readonly string[];
31
+ /**
32
+ * Reserved `_meta` keys defined by MCP `2026-07-28` for per-request protocol
33
+ * metadata. Exported so applications and tests can build spec-compliant
34
+ * requests without hard-coding string literals.
35
+ *
36
+ * @since 1.0.0
37
+ */
38
+ export declare const MCP_META_KEYS: Readonly<{
39
+ /** Protocol version for this request. Required on every modern request. */
40
+ readonly protocolVersion: "io.modelcontextprotocol/protocolVersion";
41
+ /** Self-reported client name/version. Advisory only; never a security input. */
42
+ readonly clientInfo: "io.modelcontextprotocol/clientInfo";
43
+ /** Client capabilities relevant to this request. Required on every modern request. */
44
+ readonly clientCapabilities: "io.modelcontextprotocol/clientCapabilities";
45
+ /** Minimum log level the server should emit for this request. */
46
+ readonly logLevel: "io.modelcontextprotocol/logLevel";
47
+ /** Self-reported server name/version, returned in each modern result's `_meta`. */
48
+ readonly serverInfo: "io.modelcontextprotocol/serverInfo";
49
+ }>;
50
+ /**
51
+ * JSON-RPC error codes defined by the MCP specification in its reserved
52
+ * `-32020`..`-32099` sub-range.
53
+ *
54
+ * @since 1.0.0
55
+ */
56
+ export declare const MCP_ERROR_CODES: Readonly<{
57
+ /** HTTP headers disagree with the request body, or a required header is missing. */
58
+ readonly headerMismatch: -32020;
59
+ /** The request needs a client capability the client did not declare. */
60
+ readonly missingRequiredClientCapability: -32021;
61
+ /** The requested protocol version is not implemented by this server. */
62
+ readonly unsupportedProtocolVersion: -32022;
63
+ }>;
16
64
  /**
17
65
  * Default maximum accepted JSON-RPC request body for a DaloyJS MCP endpoint.
18
66
  * The cap is intentionally small because MCP calls should carry parameters,
@@ -21,6 +69,30 @@ export declare const MCP_PROTOCOL_VERSIONS: readonly string[];
21
69
  * @since 1.0.0
22
70
  */
23
71
  export declare const MCP_DEFAULT_MAX_BODY_BYTES: number;
72
+ /**
73
+ * Maximum accepted length of a client-supplied `params.requestState` string.
74
+ *
75
+ * `requestState` is opaque server state that round-trips through an untrusted
76
+ * client during a multi round-trip request, so it is bounded independently of
77
+ * the body cap to keep a hostile client from forcing large state parsing.
78
+ *
79
+ * @since 1.0.0
80
+ */
81
+ export declare const MCP_MAX_REQUEST_STATE_LENGTH = 8192;
82
+ /**
83
+ * Report whether a protocol revision belongs to the stateless ("modern") MCP
84
+ * era introduced by {@link MCP_MODERN_ERA_MIN_VERSION}.
85
+ *
86
+ * Modern requests carry their protocol version, client identity, and client
87
+ * capabilities in `_meta` and are validated against the standard
88
+ * `MCP-Protocol-Version` / `Mcp-Method` / `Mcp-Name` HTTP headers. Older
89
+ * revisions keep the `initialize` handshake instead.
90
+ *
91
+ * @param version - A protocol revision string such as `"2026-07-28"`.
92
+ * @returns `true` when the revision uses per-request metadata.
93
+ * @since 1.0.0
94
+ */
95
+ export declare function isModernProtocolVersion(version: string): boolean;
24
96
  /**
25
97
  * JSON value accepted in MCP schemas, structured results, and metadata.
26
98
  *
@@ -94,6 +166,34 @@ export interface McpServerInfo {
94
166
  /** Optional icons clients may display for this server (MCP 2025-11-25). */
95
167
  icons?: McpIcon[];
96
168
  }
169
+ /**
170
+ * Self-reported implementation identity (`serverInfo` / `clientInfo`).
171
+ *
172
+ * Values are supplied by the peer and are **not** verified by the protocol.
173
+ * Use them for display, logging, and debugging only — never for authorization
174
+ * or any other security decision.
175
+ *
176
+ * @since 1.0.0
177
+ */
178
+ export interface McpImplementation {
179
+ /** Stable machine-readable implementation name. */
180
+ name: string;
181
+ /** Implementation version string. */
182
+ version: string;
183
+ /** Optional human-readable display title. */
184
+ title?: string;
185
+ }
186
+ /**
187
+ * Protocol era a request was served under.
188
+ *
189
+ * - `"modern"`: MCP `2026-07-28` and later. Stateless; version, identity, and
190
+ * capabilities arrive in `_meta` and are mirrored into HTTP headers.
191
+ * - `"legacy"`: MCP `2025-11-25` and earlier. Established by an `initialize`
192
+ * handshake.
193
+ *
194
+ * @since 1.0.0
195
+ */
196
+ export type McpProtocolEra = "modern" | "legacy";
97
197
  /**
98
198
  * Per-request context passed to tool, resource, and prompt handlers.
99
199
  *
@@ -103,16 +203,57 @@ export interface McpRequestContext {
103
203
  /** The original HTTP request received by the DaloyJS route. */
104
204
  request: Request;
105
205
  /**
106
- * Protocol version selected for this call. `initialize` negotiates it from
107
- * `params.protocolVersion`; other calls take the `MCP-Protocol-Version`
108
- * header, falling back to `2025-03-26` (the spec's assumption for
109
- * headerless requests) when supported, otherwise the preferred version.
206
+ * Protocol version selected for this call. On a modern request this is the
207
+ * verified `io.modelcontextprotocol/protocolVersion` from `_meta`. On a
208
+ * legacy request, `initialize` negotiates it from `params.protocolVersion`
209
+ * and other calls take the `MCP-Protocol-Version` header, falling back to
210
+ * `2025-03-26` (the spec's assumption for headerless requests) when
211
+ * supported, otherwise the preferred version.
110
212
  */
111
213
  protocolVersion: string;
214
+ /**
215
+ * Protocol era this request was served under. Handlers that emit multi
216
+ * round-trip results must check this: `input_required` only exists in the
217
+ * `"modern"` era.
218
+ */
219
+ era: McpProtocolEra;
112
220
  /** JSON-RPC id for request/response correlation. */
113
221
  id: McpJsonRpcId;
114
222
  /** Raw MCP method name, such as `"tools/call"` or `"resources/read"`. */
115
223
  method: string;
224
+ /**
225
+ * Capabilities the client declared for this request
226
+ * (`io.modelcontextprotocol/clientCapabilities`). Empty on legacy requests,
227
+ * which declare capabilities once during `initialize` instead.
228
+ */
229
+ clientCapabilities: McpJsonObject;
230
+ /**
231
+ * Self-reported client identity (`io.modelcontextprotocol/clientInfo`), when
232
+ * the client sent one. Advisory metadata — never a security input.
233
+ */
234
+ clientInfo?: McpImplementation;
235
+ /**
236
+ * Minimum log level the client asked the server to emit for this request
237
+ * (`io.modelcontextprotocol/logLevel`), when supplied.
238
+ */
239
+ logLevel?: string;
240
+ /**
241
+ * Client answers to a previous {@link McpInputRequiredResult}, keyed by the
242
+ * identifiers the server assigned in `inputRequests`. Present only on a
243
+ * multi round-trip retry.
244
+ */
245
+ inputResponses?: McpInputResponses;
246
+ /**
247
+ * Opaque state the server emitted on a previous
248
+ * {@link McpInputRequiredResult} and the client echoed back.
249
+ *
250
+ * Security: this value round-trips through an untrusted client. If it
251
+ * influences authorization, resource access, or business logic, integrity-
252
+ * protect it (HMAC or AEAD), bind it to the authenticated principal and the
253
+ * originating request, give it a short expiry, and reject anything that
254
+ * fails verification.
255
+ */
256
+ requestState?: string;
116
257
  }
117
258
  /**
118
259
  * Text content block returned from an MCP tool, resource, or prompt.
@@ -179,6 +320,69 @@ export interface McpToolResult {
179
320
  /** Set to `true` for domain/tool errors the model may recover from. */
180
321
  isError?: boolean;
181
322
  }
323
+ /**
324
+ * Server-to-client request embedded in an {@link McpInputRequiredResult}.
325
+ *
326
+ * MCP `2026-07-28` removed server-initiated JSON-RPC requests. A server that
327
+ * needs elicitation, sampling, or the client's roots returns them here and the
328
+ * client supplies the answers on a retry of the original request.
329
+ *
330
+ * @since 1.0.0
331
+ */
332
+ export interface McpInputRequest {
333
+ /** The client-side method being requested. */
334
+ method: "elicitation/create" | "sampling/createMessage" | "roots/list";
335
+ /** Method parameters, as defined by the MCP client-features specification. */
336
+ params?: McpJsonObject;
337
+ }
338
+ /**
339
+ * Map of server-assigned identifiers to server-to-client requests.
340
+ *
341
+ * @since 1.0.0
342
+ */
343
+ export type McpInputRequests = {
344
+ [id: string]: McpInputRequest;
345
+ };
346
+ /**
347
+ * Map of the same identifiers to the client's answers, echoed back on the
348
+ * retry of the original request.
349
+ *
350
+ * @since 1.0.0
351
+ */
352
+ export type McpInputResponses = {
353
+ [id: string]: McpJsonValue;
354
+ };
355
+ /**
356
+ * Interim result telling the client that more input is required before the
357
+ * call can complete (MCP `2026-07-28` multi round-trip requests).
358
+ *
359
+ * Return this from a tool, resource, or prompt handler instead of a final
360
+ * result. The client gathers the requested input and retries the original
361
+ * request — with a **new** JSON-RPC id — carrying `inputResponses` and, when
362
+ * present, the exact `requestState` string it received.
363
+ *
364
+ * At least one of `inputRequests` or `requestState` must be set. DaloyJS
365
+ * refuses to emit an `inputRequests` entry whose method the client did not
366
+ * declare support for, answering `-32021` instead of leaking a request the
367
+ * client cannot fulfil.
368
+ *
369
+ * @since 1.0.0
370
+ */
371
+ export interface McpInputRequiredResult {
372
+ /** Discriminator literal identifying this as a multi round-trip result. */
373
+ resultType: "input_required";
374
+ /** Requests the client must fulfil before retrying. */
375
+ inputRequests?: McpInputRequests;
376
+ /**
377
+ * Opaque state the client must echo back verbatim on the retry.
378
+ *
379
+ * Security: it passes through an untrusted client. Integrity-protect it
380
+ * (HMAC or AEAD) whenever it influences authorization, resource access, or
381
+ * business logic, bind it to the authenticated principal and originating
382
+ * request, and give it a short expiry.
383
+ */
384
+ requestState?: string;
385
+ }
182
386
  /**
183
387
  * Behavioral hints a tool can advertise to MCP clients. Hints are untrusted
184
388
  * metadata for UX decisions (confirmation prompts, badges); clients must not
@@ -208,13 +412,15 @@ export interface McpToolAnnotations {
208
412
  * declared shape holds at runtime. Constraints expressed only through
209
413
  * unsupported schema keywords (e.g. `pattern`) remain the handler's job.
210
414
  * @param ctx - Request metadata and the original HTTP request.
211
- * @returns Text shorthand or a full {@link McpToolResult}.
415
+ * @returns Text shorthand, a full {@link McpToolResult}, or an
416
+ * {@link McpInputRequiredResult} to ask the client for more input first
417
+ * (modern protocol era only).
212
418
  * @throws {McpToolError} for caller-correctable failures that should be
213
419
  * returned as an MCP tool error result.
214
420
  *
215
421
  * @since 1.0.0
216
422
  */
217
- export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, ctx: McpRequestContext) => string | McpToolResult | Promise<string | McpToolResult>;
423
+ export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, ctx: McpRequestContext) => string | McpToolResult | McpInputRequiredResult | Promise<string | McpToolResult | McpInputRequiredResult>;
218
424
  /**
219
425
  * Definition of a callable MCP tool.
220
426
  *
@@ -301,9 +507,11 @@ export interface McpResourceDefinition extends McpResource {
301
507
  * Read the resource contents for `resources/read`.
302
508
  *
303
509
  * @param ctx - Request metadata and the original HTTP request.
304
- * @returns One or more content entries for this resource.
510
+ * @returns One or more content entries for this resource, or an
511
+ * {@link McpInputRequiredResult} to ask the client for more input first
512
+ * (modern protocol era only).
305
513
  */
306
- read: (ctx: McpRequestContext) => McpResourceContents | McpResourceContents[] | Promise<McpResourceContents | McpResourceContents[]>;
514
+ read: (ctx: McpRequestContext) => McpResourceContents | McpResourceContents[] | McpInputRequiredResult | Promise<McpResourceContents | McpResourceContents[] | McpInputRequiredResult>;
307
515
  }
308
516
  /**
309
517
  * Resource template metadata returned from `resources/templates/list`.
@@ -344,11 +552,13 @@ export interface McpResourceTemplateDefinition extends McpResourceTemplate {
344
552
  * @param uri - The full resource URI requested by the client.
345
553
  * @param variables - Template variable values extracted from `uri`.
346
554
  * @param ctx - Request metadata and the original HTTP request.
347
- * @returns One or more content entries for this resource.
555
+ * @returns One or more content entries for this resource, or an
556
+ * {@link McpInputRequiredResult} to ask the client for more input first
557
+ * (modern protocol era only).
348
558
  * @throws {McpToolError} for caller-correctable failures such as an unknown
349
559
  * record id; these become JSON-RPC invalid-params errors.
350
560
  */
351
- read: (uri: string, variables: Record<string, string>, ctx: McpRequestContext) => McpResourceContents | McpResourceContents[] | Promise<McpResourceContents | McpResourceContents[]>;
561
+ read: (uri: string, variables: Record<string, string>, ctx: McpRequestContext) => McpResourceContents | McpResourceContents[] | McpInputRequiredResult | Promise<McpResourceContents | McpResourceContents[] | McpInputRequiredResult>;
352
562
  }
353
563
  /**
354
564
  * Argument metadata for an MCP prompt.
@@ -417,9 +627,10 @@ export interface McpPromptDefinition extends McpPrompt {
417
627
  *
418
628
  * @param args - Prompt arguments supplied by the MCP client.
419
629
  * @param ctx - Request metadata and the original HTTP request.
420
- * @returns Prompt messages.
630
+ * @returns Prompt messages, or an {@link McpInputRequiredResult} to ask the
631
+ * client for more input first (modern protocol era only).
421
632
  */
422
- get: (args: Record<string, unknown>, ctx: McpRequestContext) => McpPromptResult | Promise<McpPromptResult>;
633
+ get: (args: Record<string, unknown>, ctx: McpRequestContext) => McpPromptResult | McpInputRequiredResult | Promise<McpPromptResult | McpInputRequiredResult>;
423
634
  }
424
635
  /**
425
636
  * Caller-correctable MCP tool/resource/prompt error.
@@ -440,6 +651,31 @@ export declare class McpToolError extends Error {
440
651
  */
441
652
  constructor(message: string);
442
653
  }
654
+ /**
655
+ * Client-side caching hints attached to every cacheable modern result
656
+ * (`server/discover`, the four list methods, and `resources/read`).
657
+ *
658
+ * @since 1.0.0
659
+ */
660
+ export interface McpCacheHints {
661
+ /**
662
+ * Freshness hint in milliseconds. `0` (the default) tells clients to
663
+ * revalidate on every call.
664
+ *
665
+ * @defaultValue 0
666
+ */
667
+ ttlMs?: number;
668
+ /**
669
+ * Whether shared intermediaries may cache the response. DaloyJS defaults to
670
+ * `"private"` because MCP list results legitimately vary by the credential
671
+ * presented on the request — a `"public"` scope on an authorization-scoped
672
+ * tool list would let a proxy serve one caller's tools to another. Only set
673
+ * `"public"` for a server whose results are identical for every caller.
674
+ *
675
+ * @defaultValue "private"
676
+ */
677
+ scope?: "public" | "private";
678
+ }
443
679
  /**
444
680
  * Options for {@link createMcpHandler}.
445
681
  *
@@ -473,6 +709,21 @@ export interface McpHandlerOptions {
473
709
  * other origin is rejected with `403` unless listed here.
474
710
  */
475
711
  allowedOrigins?: readonly string[];
712
+ /**
713
+ * Optional extensions advertised in `capabilities.extensions`, keyed by
714
+ * extension identifier (for example `"io.modelcontextprotocol/tasks"`), with
715
+ * each value the extension's settings object. Identifiers must carry a
716
+ * reverse-DNS prefix, per the `_meta` key naming rules.
717
+ *
718
+ * DaloyJS core implements no extension itself; declaring one here advertises
719
+ * that *your* handlers implement it.
720
+ */
721
+ extensions?: Record<string, McpJsonObject>;
722
+ /**
723
+ * Caching hints returned on cacheable modern results. Defaults to
724
+ * `{ ttlMs: 0, scope: "private" }` — no caching, no sharing.
725
+ */
726
+ cache?: McpCacheHints;
476
727
  /** Accepted MCP protocol versions. Defaults to {@link MCP_PROTOCOL_VERSIONS}. */
477
728
  protocolVersions?: readonly string[];
478
729
  /**
@@ -532,31 +783,51 @@ export declare function validateMcpInput(schema: McpJsonSchema, value: unknown):
532
783
  /**
533
784
  * Create a dependency-free MCP Streamable HTTP endpoint handler.
534
785
  *
535
- * The handler implements the server side of MCP over one HTTP endpoint:
536
- * `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`,
537
- * `resources/templates/list`, `resources/read`, `prompts/list`, and
538
- * `prompts/get`. It accepts JSON-RPC requests over `POST`, acknowledges
539
- * notifications with `202`, validates the `MCP-Protocol-Version` header,
540
- * bounds request bodies, enforces required prompt arguments, and returns
541
- * JSON-RPC errors for malformed input.
542
- *
543
- * Security: per the Streamable HTTP spec's DNS-rebinding guidance, every
544
- * request bearing an `Origin` header is validated. Same-origin and loopback
545
- * origins pass; anything else is rejected with `403` unless listed in
546
- * {@link McpHandlerOptions.allowedOrigins}.
547
- *
548
- * It intentionally does not spawn stdio servers, manage OAuth metadata, keep
549
- * durable sessions, or open server-initiated SSE streams. Use DaloyJS
550
- * middleware for authentication and authorization, and run this on a dedicated
551
- * Daloy app when your MCP server has a different trust boundary than your REST
552
- * API.
786
+ * The handler serves **both MCP protocol eras** on one endpoint:
787
+ *
788
+ * - **Modern (`2026-07-28`+, stateless).** No handshake. Every request carries
789
+ * its protocol version, client identity, and client capabilities in `_meta`,
790
+ * mirrored into the required `MCP-Protocol-Version`, `Mcp-Method`, and
791
+ * `Mcp-Name` headers. Methods: `server/discover`, `tools/list`,
792
+ * `tools/call`, `resources/list`, `resources/templates/list`,
793
+ * `resources/read`, `prompts/list`, `prompts/get`. Every result carries
794
+ * `resultType`, the server identity in `_meta`, and on cacheable methods —
795
+ * `ttlMs` / `cacheScope`. Handlers may return an
796
+ * {@link McpInputRequiredResult} to run a multi round-trip request.
797
+ * - **Legacy (`2025-11-25` and earlier).** The `initialize` / `ping` handshake
798
+ * protocol, unchanged, so existing clients keep working.
799
+ *
800
+ * A request is served as modern when its `_meta` protocol version (or the
801
+ * `MCP-Protocol-Version` header) is `2026-07-28` or later; otherwise it takes
802
+ * the legacy path.
803
+ *
804
+ * Security, on top of the era-independent body cap, prototype-pollution-safe
805
+ * parsing, and `inputSchema` enforcement:
806
+ *
807
+ * - Per the Streamable HTTP spec's DNS-rebinding guidance, every request
808
+ * bearing an `Origin` header is validated. Loopback origins pass; anything
809
+ * else is rejected with `403` unless listed in
810
+ * {@link McpHandlerOptions.allowedOrigins}.
811
+ * - Modern requests are rejected with `400` and `-32020` (`HeaderMismatch`)
812
+ * when a required standard header is missing or disagrees with the body.
813
+ * This closes the header/body confusion gap that lets a gateway route on one
814
+ * value while the server executes another.
815
+ * - `Mcp-Session-Id` and `Last-Event-ID` are ignored; no session is ever minted
816
+ * or echoed.
817
+ *
818
+ * It intentionally does not spawn stdio servers, manage OAuth metadata, open
819
+ * `subscriptions/listen` notification streams, or implement the tasks
820
+ * extension. Use DaloyJS middleware for authentication and authorization, and
821
+ * run this on a dedicated Daloy app when your MCP server has a different trust
822
+ * boundary than your REST API.
553
823
  *
554
824
  * @param options - Server identity, capabilities, limits, and response headers.
555
825
  * @returns A Fetch-compatible request handler suitable for {@link mcpRoutes}
556
826
  * or for direct use in any web-standard runtime.
557
827
  * @throws {TypeError} at construction for invalid serverInfo, protocol
558
- * versions, body limits, duplicate names/URIs, malformed `allowedOrigins`
559
- * entries, or unsupported URI template expressions.
828
+ * versions, body limits, cache hints, extension identifiers, duplicate
829
+ * names/URIs, malformed `allowedOrigins` entries, invalid `x-mcp-header`
830
+ * annotations, or unsupported URI template expressions.
560
831
  *
561
832
  * @example
562
833
  * ```ts