@emseepea/server 0.8.1 → 0.10.0

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.
package/dist/index.js CHANGED
@@ -4,15 +4,17 @@ import { toNodeHandler } from "@modelcontextprotocol/node";
4
4
  import { McpServer, InMemoryServerEventBus, OAuthError, OAuthErrorCode, ProtocolError, ProtocolErrorCode, ResourceTemplate, bearerAuthChallengeResponse, buildOAuthProtectedResourceMetadata, checkResourceAllowed, classifyInboundRequest, completable, createMcpHandler, createRequestStateCodec, getOAuthProtectedResourceMetadataUrl, inputRequired as sdkInputRequired, acceptedContent as sdkAcceptedContent, inputResponse as sdkInputResponse, isInputRequiredResult, oauthMetadataResponse, specTypeSchemas, verifyBearerToken, } from "@modelcontextprotocol/server";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash } from "node:crypto";
7
+ import { readFileSync } from "node:fs";
7
8
  import { readdir, realpath } from "node:fs/promises";
8
9
  import { isIP } from "node:net";
9
- import { join } from "node:path";
10
+ import { isAbsolute, join } from "node:path";
10
11
  import { fileURLToPath, pathToFileURL } from "node:url";
11
12
  import { z } from "zod";
12
13
  import { installObservability, } from "./telemetry.js";
13
14
  export { openTelemetry, structuredLogging, } from "./telemetry.js";
14
15
  export * from "./ui.js";
15
16
  export const inputRequired = Object.assign((spec) => sdkInputRequired(spec), {
17
+ roots: sdkInputRequired.listRoots,
16
18
  elicit: sdkInputRequired.elicit,
17
19
  elicitUrl: sdkInputRequired.elicitUrl,
18
20
  });
@@ -23,6 +25,20 @@ export function inputResponse(responses, key) {
23
25
  const response = sdkInputResponse(checkedInputResponses(responses), key);
24
26
  return response.kind === "elicit" ? response : { kind: "missing" };
25
27
  }
28
+ const checkedRootsResponses = new WeakSet();
29
+ /** Reads roots validated by the framework; missing keys return undefined. */
30
+ export function rootsResponse(responses, key) {
31
+ if (responses === undefined)
32
+ return undefined;
33
+ if (!checkedRootsResponses.has(responses))
34
+ throw new TypeError("Roots responses have not been checked");
35
+ if (!Object.hasOwn(responses, key))
36
+ return undefined;
37
+ const value = responses[key];
38
+ if (!isRecord(value) || !Array.isArray(value.roots))
39
+ throw new TypeError("Response is not roots");
40
+ return value.roots;
41
+ }
26
42
  const PROTOCOL_VERSION = "2026-07-28";
27
43
  const LEGACY_PROTOCOL_VERSIONS = Object.freeze([
28
44
  "2025-11-25",
@@ -98,6 +114,51 @@ function createRequestStateRuntime(options) {
98
114
  },
99
115
  });
100
116
  }
117
+ export function loadDeploymentProfile(environment = process.env) {
118
+ const mode = environment.EMSEEPEA_DEPLOYMENT_MODE;
119
+ const configPath = environment.EMSEEPEA_DEPLOYMENT_CONFIG_FILE;
120
+ if (mode === undefined) {
121
+ if (configPath !== undefined)
122
+ throw new TypeError("Deployment config requires an explicit deployment mode");
123
+ return { mode: "loopback" };
124
+ }
125
+ if (mode !== "production-behind-proxy")
126
+ throw new TypeError(`Unsupported deployment mode: ${mode}`);
127
+ if (!configPath || !isAbsolute(configPath)) {
128
+ throw new TypeError("Production deployment config must be an absolute file path");
129
+ }
130
+ const bytes = readFileSync(configPath);
131
+ if (bytes.byteLength > 16 * 1024)
132
+ throw new TypeError("Production deployment config exceeds 16 KiB");
133
+ const source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
134
+ const parsed = z.strictObject({
135
+ allowedAuthorities: z.array(z.string()).min(1),
136
+ allowedOrigins: z.array(z.string()).min(1),
137
+ trustedProxyAddresses: z.array(z.string()).min(1),
138
+ rateLimit: z.strictObject({
139
+ maxRequests: z.number().int().positive(),
140
+ windowMs: z.number().int().positive(),
141
+ maxClients: z.number().int().positive(),
142
+ }),
143
+ }).parse(JSON.parse(source));
144
+ const profile = { mode, ...parsed };
145
+ const normalized = normalizeDeployment(profile);
146
+ if (normalized.mode !== "production-behind-proxy")
147
+ throw new TypeError("Production deployment config is invalid");
148
+ for (const [label, values, normalizedValues] of [
149
+ ["allowed authority", parsed.allowedAuthorities, normalized.allowedAuthorities],
150
+ ["allowed origin", parsed.allowedOrigins, normalized.allowedOrigins],
151
+ ["trusted proxy address", parsed.trustedProxyAddresses, normalized.trustedProxyAddresses],
152
+ ]) {
153
+ if (normalizedValues.size !== values.length)
154
+ throw new TypeError(`Duplicate ${label}`);
155
+ }
156
+ for (const address of parsed.trustedProxyAddresses) {
157
+ if (normalizeIp(address) !== address)
158
+ throw new TypeError(`Trusted proxy address is not normalized: ${address}`);
159
+ }
160
+ return profile;
161
+ }
101
162
  export function defineTool(definition) {
102
163
  const handler = definition.handler;
103
164
  return createCheckedTool(definition, handler, false, true);
@@ -173,13 +234,13 @@ export function defineResource(definition) {
173
234
  value: listing,
174
235
  }),
175
236
  [HAS_COMPLETION]: false,
176
- [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState) {
237
+ [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
177
238
  server.registerResource(name, uri, cacheHint ? { ...metadata, cacheHint } : metadata, async (_requestedUri, context) => {
178
239
  try {
179
240
  const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
180
241
  return await runWithDeadline(context.mcpReq.signal, deadlineMs, async (signal) => {
181
242
  signal.throwIfAborted();
182
- const result = await handler(directHandlerContext(access, context, signal, deadlineMs, requestState));
243
+ const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler(directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
183
244
  signal.throwIfAborted();
184
245
  if (isInputRequiredResult(result)) {
185
246
  await assertInputRequired(result, requestState, context);
@@ -245,7 +306,7 @@ export function defineResourceTemplate(definition) {
245
306
  value: listing,
246
307
  }),
247
308
  [HAS_COMPLETION]: completions.size > 0,
248
- [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState) {
309
+ [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
249
310
  const registeredTemplate = completions.size === 0
250
311
  ? template
251
312
  : new ResourceTemplate(uriTemplate, {
@@ -260,7 +321,7 @@ export function defineResourceTemplate(definition) {
260
321
  const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
261
322
  return await runWithDeadline(context.mcpReq.signal, deadlineMs, async (signal) => {
262
323
  signal.throwIfAborted();
263
- const result = await handler({ uri: requestedUri.href, variables }, directHandlerContext(access, context, signal, deadlineMs, requestState));
324
+ const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler({ uri: requestedUri.href, variables }, directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
264
325
  signal.throwIfAborted();
265
326
  if (isInputRequiredResult(result)) {
266
327
  await assertInputRequired(result, requestState, context);
@@ -311,7 +372,7 @@ export function definePrompt(definition) {
311
372
  [PROMPT_ACCESS]: access,
312
373
  [HAS_COMPLETION]: completions.size > 0,
313
374
  [PROMPT_LISTING]: listing,
314
- [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState) {
375
+ [REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
315
376
  server.registerPrompt(name, {
316
377
  ...metadata,
317
378
  argsSchema: sdkPromptMetadataSchema(argsSchema, completions, argumentNames, timeoutMs, maxApplicationResultBytes),
@@ -323,7 +384,7 @@ export function definePrompt(definition) {
323
384
  if (!parsedArgs.success)
324
385
  throw new Error("Prompt received invalid arguments");
325
386
  signal.throwIfAborted();
326
- const result = await handler(parsedArgs.data, directHandlerContext(access, context, signal, deadlineMs, requestState));
387
+ const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler(parsedArgs.data, directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
327
388
  signal.throwIfAborted();
328
389
  if (isInputRequiredResult(result)) {
329
390
  await assertInputRequired(result, requestState, context);
@@ -496,7 +557,10 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
496
557
  [TOOL_ACCESS]: access,
497
558
  [TOOL_STREAMING]: streaming,
498
559
  [TOOL_LISTING]: listing,
499
- [REGISTER](server, timeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState) {
560
+ [REGISTER](server, timeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState, clientLogging) {
561
+ const runLoggedTool = clientLogging
562
+ ? createLoggedToolRunner(clientLogging, execute, allowsInputRequired, access, requestState)
563
+ : undefined;
500
564
  server.registerTool(name, metadata, async (input, context) => {
501
565
  try {
502
566
  const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
@@ -510,9 +574,12 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
510
574
  ? progressReporter(context, signal, maxProgressEvents, maxProgressEventBytes)
511
575
  : undefined;
512
576
  let result;
513
- try {
514
- result = await execute(parsedInput.data, {
515
- ...(allowsInputRequired
577
+ if (runLoggedTool) {
578
+ result = await runLoggedTool(parsedInput.data, context, signal, deadlineMs, reporter);
579
+ }
580
+ else {
581
+ try {
582
+ const handlerContext = allowsInputRequired
516
583
  ? directHandlerContext(access, context, signal, deadlineMs, requestState)
517
584
  : {
518
585
  signal,
@@ -520,14 +587,16 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
520
587
  principal: access === "public"
521
588
  ? undefined
522
589
  : principalFrom(context.http?.authInfo),
523
- }),
524
- ...(reporter ? { reportProgress: reporter.report } : {}),
525
- });
526
- }
527
- finally {
528
- await reporter?.finish();
590
+ };
591
+ if (reporter)
592
+ Object.assign(handlerContext, { reportProgress: reporter.report });
593
+ result = await execute(parsedInput.data, handlerContext);
594
+ }
595
+ finally {
596
+ await reporter?.finish();
597
+ }
598
+ reporter?.throwIfFailed();
529
599
  }
530
- reporter?.throwIfFailed();
531
600
  if (allowsInputRequired && isInputRequiredResult(result)) {
532
601
  signal.throwIfAborted();
533
602
  await assertInputRequired(result, requestState, context);
@@ -561,6 +630,33 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
561
630
  };
562
631
  return Object.freeze(registration);
563
632
  }
633
+ function createLoggedToolRunner(clientLogging, execute, allowsInputRequired, access, requestState) {
634
+ return async (input, context, signal, deadlineMs, reporter) => {
635
+ const logReporter = clientLogReporter(context, signal, clientLogging.maxEvents, clientLogging.maxEventBytes);
636
+ let result;
637
+ try {
638
+ result = await execute(input, {
639
+ ...(allowsInputRequired
640
+ ? directHandlerContext(access, context, signal, deadlineMs, requestState, logReporter.report)
641
+ : {
642
+ signal,
643
+ deadlineMs,
644
+ principal: access === "public"
645
+ ? undefined
646
+ : principalFrom(context.http?.authInfo),
647
+ reportLog: logReporter.report,
648
+ }),
649
+ ...(reporter ? { reportProgress: reporter.report } : {}),
650
+ });
651
+ }
652
+ finally {
653
+ await Promise.all([reporter?.finish(), logReporter.finish()]);
654
+ }
655
+ reporter?.throwIfFailed();
656
+ logReporter.throwIfFailed();
657
+ return result;
658
+ };
659
+ }
564
660
  function progressReporter(context, signal, maxEvents, maxEventBytes) {
565
661
  const token = context.mcpReq._meta?.progressToken;
566
662
  let closed = false;
@@ -616,6 +712,74 @@ function progressReporter(context, signal, maxEvents, maxEventBytes) {
616
712
  throw failure; },
617
713
  };
618
714
  }
715
+ function clientLogReporter(context, signal, maxEvents, maxEventBytes) {
716
+ let closed = false;
717
+ let failure;
718
+ let attempts = 0;
719
+ const pending = new Set();
720
+ return {
721
+ report(message) {
722
+ if (closed)
723
+ return Promise.reject(new Error("Client logging is no longer available"));
724
+ if (failure)
725
+ return Promise.reject(failure);
726
+ const operation = (async () => {
727
+ try {
728
+ signal.throwIfAborted();
729
+ attempts += 1;
730
+ if (attempts > maxEvents)
731
+ throw new Error("Client log event limit exceeded");
732
+ const notification = {
733
+ method: "notifications/message",
734
+ params: {
735
+ level: message.level,
736
+ data: message.data,
737
+ ...(message.logger === undefined ? {} : { logger: message.logger }),
738
+ },
739
+ };
740
+ assertJsonValue(notification, new WeakSet());
741
+ const encoded = JSON.stringify(notification);
742
+ if (Buffer.byteLength(encoded, "utf8") > maxEventBytes) {
743
+ throw new Error("Client log event exceeds configured size limit");
744
+ }
745
+ const parsed = specTypeSchemas.LoggingMessageNotification["~standard"].validate(JSON.parse(encoded));
746
+ if (parsed instanceof Promise || "issues" in parsed) {
747
+ throw new Error("Client log event is invalid");
748
+ }
749
+ const params = parsed.value.params;
750
+ await context.mcpReq.log(params.level, params.data, params.logger);
751
+ }
752
+ catch (error) {
753
+ failure = error instanceof Error ? error : new Error("Client log emission failed");
754
+ throw failure;
755
+ }
756
+ })();
757
+ pending.add(operation);
758
+ void operation.then(() => pending.delete(operation), () => pending.delete(operation));
759
+ return operation;
760
+ },
761
+ async finish() {
762
+ closed = true;
763
+ await Promise.allSettled(pending);
764
+ },
765
+ throwIfFailed() { if (failure)
766
+ throw failure; },
767
+ };
768
+ }
769
+ async function withClientLogReporter(context, signal, options, run) {
770
+ const reporter = options
771
+ ? clientLogReporter(context, signal, options.maxEvents, options.maxEventBytes)
772
+ : undefined;
773
+ let result;
774
+ try {
775
+ result = await run(reporter?.report);
776
+ }
777
+ finally {
778
+ await reporter?.finish();
779
+ }
780
+ reporter?.throwIfFailed();
781
+ return result;
782
+ }
619
783
  function catalogueListings(tools, resources, prompts, supportedTools = tools, supportedResources = resources, supportedPrompts = prompts) {
620
784
  const catalogues = new Map();
621
785
  if (supportedTools.length)
@@ -778,10 +942,25 @@ export function createEmseepea(options) {
778
942
  ? normalizeListPagination(options.listPagination)
779
943
  : undefined;
780
944
  const maxRequestBytes = positiveInteger("maxRequestBytes", options.maxRequestBytes ?? 1024 * 1024);
945
+ if (options.clientRoots !== undefined && !isRecord(options.clientRoots)) {
946
+ throw new TypeError("clientRoots must be an object");
947
+ }
948
+ const maxClientRoots = options.clientRoots === undefined ? undefined
949
+ : positiveInteger("clientRoots.maxRoots", options.clientRoots.maxRoots === undefined ? 100 : options.clientRoots.maxRoots);
781
950
  const requestState = options.requestState
782
951
  ? createRequestStateRuntime(options.requestState)
783
952
  : undefined;
784
953
  const maxApplicationResultBytes = positiveInteger("maxApplicationResultBytes", options.maxApplicationResultBytes ?? 1024 * 1024);
954
+ const clientLoggingOptions = options.clientLogging;
955
+ if (clientLoggingOptions !== undefined && !isRecord(clientLoggingOptions)) {
956
+ throw new TypeError("clientLogging must be an object");
957
+ }
958
+ const clientLogging = clientLoggingOptions === undefined
959
+ ? undefined
960
+ : Object.freeze({
961
+ maxEvents: positiveInteger("clientLogging.maxEvents", clientLoggingOptions.maxEvents ?? 32),
962
+ maxEventBytes: positiveInteger("clientLogging.maxEventBytes", clientLoggingOptions.maxEventBytes ?? 8 * 1024),
963
+ });
785
964
  const maxProgressEvents = positiveInteger("maxProgressEvents", options.maxProgressEvents ?? 32);
786
965
  const maxProgressEventBytes = positiveInteger("maxProgressEventBytes", options.maxProgressEventBytes ?? 8 * 1024);
787
966
  const operationTimeoutMs = positiveInteger("operationTimeoutMs", options.operationTimeoutMs ?? 30_000);
@@ -861,6 +1040,7 @@ export function createEmseepea(options) {
861
1040
  : discoverablePrompts;
862
1041
  const activeHasCompletion = activeResources.some((resource) => resource[HAS_COMPLETION]) ||
863
1042
  activePrompts.some((prompt) => prompt[HAS_COMPLETION]);
1043
+ const activeClientLogging = request?.legacy ? undefined : clientLogging;
864
1044
  const server = new McpServer(serverInfo, {
865
1045
  capabilities: {
866
1046
  ...(activeTools.length ? { tools: { listChanged: false } } : {}),
@@ -872,6 +1052,7 @@ export function createEmseepea(options) {
872
1052
  } : {}),
873
1053
  ...(activePrompts.length ? { prompts: { listChanged: false } } : {}),
874
1054
  ...(activeHasCompletion ? { completions: {} } : {}),
1055
+ ...(activeClientLogging ? { logging: {} } : {}),
875
1056
  },
876
1057
  instructions: options.instructions,
877
1058
  cacheHints: request?.filterCatalogues || request?.legacy ? undefined : cacheHints,
@@ -879,13 +1060,13 @@ export function createEmseepea(options) {
879
1060
  ...(requestState && !request?.legacy ? { requestState: { verify: requestState.verify } } : {}),
880
1061
  });
881
1062
  for (const tool of activeTools) {
882
- tool[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState);
1063
+ tool[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState, activeClientLogging);
883
1064
  }
884
1065
  for (const resource of activeResources) {
885
- resource[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState);
1066
+ resource[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState, activeClientLogging);
886
1067
  }
887
1068
  for (const prompt of activePrompts) {
888
- prompt[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState);
1069
+ prompt[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState, activeClientLogging);
889
1070
  }
890
1071
  const filteredCatalogues = request?.filterCatalogues
891
1072
  ? catalogueListings(activeDiscoverableTools, activeDiscoverableResources, activeDiscoverablePrompts, activeTools, activeResources, activePrompts)
@@ -903,7 +1084,7 @@ export function createEmseepea(options) {
903
1084
  installListPagination(server, pagination);
904
1085
  return server;
905
1086
  }, {
906
- responseMode: hasStreaming || resourceSubscriptions ? "auto" : "json",
1087
+ responseMode: hasStreaming || resourceSubscriptions || clientLogging ? "auto" : "json",
907
1088
  keepAliveMs: 0,
908
1089
  });
909
1090
  const nodeHandler = toNodeHandler(sdkHandler);
@@ -1111,6 +1292,7 @@ export function createEmseepea(options) {
1111
1292
  filterCatalogues: protectsCatalogue,
1112
1293
  capability,
1113
1294
  legacy,
1295
+ maxClientRoots,
1114
1296
  }, () => nodeHandler(request.raw, reply.raw, request.body));
1115
1297
  }
1116
1298
  finally {
@@ -1313,7 +1495,7 @@ export async function serveEmseepea(app, options = {}) {
1313
1495
  const runtime = runtimes.get(app);
1314
1496
  if (!runtime)
1315
1497
  throw new TypeError("serveEmseepea requires an app created by createEmseepea");
1316
- const host = options.host ?? "127.0.0.1";
1498
+ const host = options.host ?? (runtime.deployment.mode === "loopback" ? "127.0.0.1" : "0.0.0.0");
1317
1499
  if (runtime.deployment.mode === "loopback" && !isLoopbackHost(host)) {
1318
1500
  throw new TypeError("The loopback deployment profile cannot bind publicly");
1319
1501
  }
@@ -2083,31 +2265,88 @@ function nonNegativePort(value) {
2083
2265
  }
2084
2266
  return value;
2085
2267
  }
2086
- function directHandlerContext(access, context, signal, deadlineMs, requestState) {
2268
+ function directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog) {
2087
2269
  return {
2088
2270
  signal,
2089
2271
  deadlineMs,
2090
2272
  principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
2091
- inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
2273
+ inputResponses: checkedDirectInputResponses(context, signal, deadlineMs),
2092
2274
  ...(requestState ? {
2093
2275
  requestState: context.mcpReq.requestState(),
2094
2276
  mintRequestState: (payload) => requestState.mint(payload, context),
2095
2277
  } : {}),
2278
+ ...(reportLog ? { reportLog } : {}),
2096
2279
  };
2097
2280
  }
2098
2281
  async function assertInputRequired(result, requestState, context) {
2099
2282
  if (!result.inputRequests && result.requestState === undefined) {
2100
2283
  throw new Error("Client-input result must include requests or state");
2101
2284
  }
2102
- if (result.inputRequests && Object.values(result.inputRequests).some((request) => request.method !== "elicitation/create")) {
2285
+ if (result.inputRequests && Object.values(result.inputRequests).some((request) => request.method !== "elicitation/create" && request.method !== "roots/list")) {
2103
2286
  throw new Error("Client-input request kind is not supported");
2104
2287
  }
2288
+ if (result.inputRequests && Object.values(result.inputRequests).some((request) => request.method === "roots/list")) {
2289
+ const operation = requestOperations.getStore();
2290
+ if (operation?.legacy || operation?.maxClientRoots === undefined) {
2291
+ throw new Error("Client roots are unavailable for this request");
2292
+ }
2293
+ // The SDK checks outgoing requests against this round's client capabilities.
2294
+ }
2105
2295
  if (result.requestState !== undefined) {
2106
2296
  if (!requestState)
2107
2297
  throw new Error("Client-input request state is not configured");
2108
2298
  await requestState.verify(result.requestState, context);
2109
2299
  }
2110
2300
  }
2301
+ function assertClientRootsEnabled(context) {
2302
+ const operation = requestOperations.getStore();
2303
+ const envelope = context.mcpReq.envelope;
2304
+ const capabilities = isRecord(envelope) ? envelope["io.modelcontextprotocol/clientCapabilities"] : undefined;
2305
+ if (operation?.legacy || operation?.maxClientRoots === undefined ||
2306
+ !isRecord(capabilities) || !isRecord(capabilities.roots)) {
2307
+ throw new Error("Client roots are unavailable for this request");
2308
+ }
2309
+ return operation.maxClientRoots;
2310
+ }
2311
+ function checkedDirectInputResponses(context, signal, deadlineMs) {
2312
+ const operation = requestOperations.getStore();
2313
+ if (operation?.maxClientRoots === undefined || operation.legacy) {
2314
+ return checkedInputResponses(context.mcpReq.inputResponses);
2315
+ }
2316
+ if (context.mcpReq.droppedInputResponseKeys?.length)
2317
+ throw new Error("Malformed client input response");
2318
+ const responses = context.mcpReq.inputResponses;
2319
+ if (responses === undefined)
2320
+ return undefined;
2321
+ const checked = Object.create(null);
2322
+ for (const [key, value] of Object.entries(responses)) {
2323
+ if (isRecord(value) && Object.hasOwn(value, "roots")) {
2324
+ const maxRoots = assertClientRootsEnabled(context);
2325
+ if (!Array.isArray(value.roots) || value.roots.length > maxRoots || Object.hasOwn(value, "action")) {
2326
+ throw new Error("Invalid roots response");
2327
+ }
2328
+ const result = specTypeSchemas.ListRootsResult["~standard"].validate(value);
2329
+ if (result instanceof Promise || "issues" in result)
2330
+ throw new Error("Invalid roots response");
2331
+ for (const root of result.value.roots) {
2332
+ if (new URL(root.uri).protocol !== "file:")
2333
+ throw new Error("Invalid root URI");
2334
+ }
2335
+ checked[key] = deepFreeze(structuredClone(result.value));
2336
+ }
2337
+ else {
2338
+ const result = specTypeSchemas.ElicitResult["~standard"].validate(value);
2339
+ if (result instanceof Promise || "issues" in result)
2340
+ throw new Error("Invalid client input response");
2341
+ checked[key] = deepFreeze(structuredClone(result.value));
2342
+ }
2343
+ }
2344
+ signal.throwIfAborted();
2345
+ if (Date.now() >= deadlineMs)
2346
+ throw new Error("Client input validation exceeded its deadline");
2347
+ checkedRootsResponses.add(checked);
2348
+ return Object.freeze(checked);
2349
+ }
2111
2350
  function assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal) {
2112
2351
  let serialized;
2113
2352
  try {