mcp-ts-template 2.4.0 → 2.4.2

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.
Files changed (2) hide show
  1. package/dist/index.js +846 -212
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29010,7 +29010,7 @@ var require_gaxios = __commonJS((exports) => {
29010
29010
  function hasFetch() {
29011
29011
  return hasWindow() && !!window.fetch;
29012
29012
  }
29013
- function hasBuffer() {
29013
+ function hasBuffer2() {
29014
29014
  return typeof Buffer !== "undefined";
29015
29015
  }
29016
29016
  function hasHeader(options, header) {
@@ -29236,7 +29236,7 @@ Content-Type: ${partContentType}\r
29236
29236
  const isFormData = typeof FormData === "undefined" ? false : (opts === null || opts === undefined ? undefined : opts.data) instanceof FormData;
29237
29237
  if (is_stream_1.default.readable(opts.data)) {
29238
29238
  opts.body = opts.data;
29239
- } else if (hasBuffer() && Buffer.isBuffer(opts.data)) {
29239
+ } else if (hasBuffer2() && Buffer.isBuffer(opts.data)) {
29240
29240
  opts.body = opts.data;
29241
29241
  if (!hasHeader(opts, "Content-Type")) {
29242
29242
  opts.headers["Content-Type"] = "application/json";
@@ -117141,7 +117141,7 @@ var z = /* @__PURE__ */ Object.freeze({
117141
117141
  // package.json
117142
117142
  var package_default = {
117143
117143
  name: "mcp-ts-template",
117144
- version: "2.3.9",
117144
+ version: "2.4.1",
117145
117145
  mcpName: "io.github.cyanheads/mcp-ts-template",
117146
117146
  description: "The definitive, production-grade template for building powerful and scalable Model Context Protocol (MCP) servers with TypeScript, featuring built-in observability (OpenTelemetry), declarative tooling, robust error handling, and a modular, DI-driven architecture.",
117147
117147
  main: "dist/index.js",
@@ -117645,81 +117645,175 @@ var config = parseConfig();
117645
117645
 
117646
117646
  // src/utils/telemetry/instrumentation.ts
117647
117647
  var import_api = __toESM(require_src(), 1);
117648
- var import_auto_instrumentations_node = __toESM(require_src58(), 1);
117649
- var import_exporter_metrics_otlp_http = __toESM(require_src62(), 1);
117650
- var import_exporter_trace_otlp_http = __toESM(require_src63(), 1);
117651
- var import_instrumentation_pino = __toESM(require_src40(), 1);
117652
- var import_resources = __toESM(require_src52(), 1);
117653
- var import_sdk_metrics = __toESM(require_src59(), 1);
117654
- var import_sdk_node = __toESM(require_src83(), 1);
117655
- var import_sdk_trace_node = __toESM(require_src67(), 1);
117656
- var import_incubating = __toESM(require_index_incubating(), 1);
117648
+
117649
+ // src/utils/internal/runtime.ts
117650
+ var safeHas = (key) => {
117651
+ try {
117652
+ return typeof globalThis[key] !== "undefined";
117653
+ } catch {
117654
+ return false;
117655
+ }
117656
+ };
117657
+ var isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
117658
+ var hasProcess = typeof process !== "undefined";
117659
+ var hasBuffer = typeof Buffer !== "undefined";
117660
+ var hasTextEncoder = safeHas("TextEncoder");
117661
+ var hasPerformanceNow = typeof globalThis.performance?.now === "function";
117662
+ var isWorkerLike = !isNode && typeof globalThis.WorkerGlobalScope !== "undefined";
117663
+ var isBrowserLike = !isNode && !isWorkerLike && safeHas("window");
117664
+ var runtimeCaps = {
117665
+ isNode,
117666
+ isWorkerLike,
117667
+ isBrowserLike,
117668
+ hasProcess,
117669
+ hasBuffer,
117670
+ hasTextEncoder,
117671
+ hasPerformanceNow
117672
+ };
117673
+
117674
+ // src/utils/telemetry/instrumentation.ts
117657
117675
  var sdk = null;
117658
117676
  var isOtelInitialized = false;
117659
- if (config.openTelemetry.enabled && !isOtelInitialized) {
117660
- isOtelInitialized = true;
117661
- try {
117662
- const otelLogLevelString = config.openTelemetry.logLevel.toUpperCase();
117663
- const otelLogLevel = import_api.DiagLogLevel[otelLogLevelString] ?? import_api.DiagLogLevel.INFO;
117664
- import_api.diag.setLogger(new import_api.DiagConsoleLogger, otelLogLevel);
117665
- const tracesEndpoint = config.openTelemetry.tracesEndpoint;
117666
- const metricsEndpoint = config.openTelemetry.metricsEndpoint;
117667
- if (!tracesEndpoint && !metricsEndpoint) {
117668
- import_api.diag.warn("OTEL_ENABLED is true, but no OTLP endpoint for traces or metrics is configured. OpenTelemetry will not export any telemetry.");
117669
- }
117670
- const resource = import_resources.resourceFromAttributes({
117671
- [import_incubating.ATTR_SERVICE_NAME]: config.openTelemetry.serviceName,
117672
- [import_incubating.ATTR_SERVICE_VERSION]: config.openTelemetry.serviceVersion,
117673
- "deployment.environment.name": config.environment
117674
- });
117675
- const spanProcessors = [];
117676
- if (tracesEndpoint) {
117677
- import_api.diag.info(`Using OTLP exporter for traces, endpoint: ${tracesEndpoint}`);
117678
- const traceExporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({ url: tracesEndpoint });
117679
- spanProcessors.push(new import_sdk_trace_node.BatchSpanProcessor(traceExporter));
117680
- } else {
117681
- import_api.diag.info("No OTLP traces endpoint configured. Traces will not be exported.");
117682
- }
117683
- const metricReader = metricsEndpoint ? new import_sdk_metrics.PeriodicExportingMetricReader({
117684
- exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({ url: metricsEndpoint }),
117685
- exportIntervalMillis: 15000
117686
- }) : undefined;
117687
- sdk = new import_sdk_node.NodeSDK({
117688
- resource,
117689
- spanProcessors,
117690
- ...metricReader && { metricReader },
117691
- sampler: new import_sdk_trace_node.TraceIdRatioBasedSampler(config.openTelemetry.samplingRatio),
117692
- instrumentations: [
117693
- import_auto_instrumentations_node.getNodeAutoInstrumentations({
117694
- "@opentelemetry/instrumentation-http": {
117695
- enabled: true,
117696
- ignoreIncomingRequestHook: (req) => req.url === "/healthz"
117697
- },
117698
- "@opentelemetry/instrumentation-fs": { enabled: false }
117699
- }),
117700
- new import_instrumentation_pino.PinoInstrumentation({
117701
- logHook: (_span, record) => {
117702
- record["trace_id"] = _span.spanContext().traceId;
117703
- record["span_id"] = _span.spanContext().spanId;
117704
- }
117705
- })
117706
- ]
117707
- });
117708
- sdk.start();
117709
- import_api.diag.info(`OpenTelemetry initialized for ${config.openTelemetry.serviceName} v${config.openTelemetry.serviceVersion}`);
117710
- } catch (error) {
117711
- import_api.diag.error("Error initializing OpenTelemetry", error);
117712
- sdk = null;
117677
+ var initializationPromise = null;
117678
+ function canUseNodeSDK() {
117679
+ return runtimeCaps.isNode && typeof process?.versions?.node === "string" && typeof process.env === "object";
117680
+ }
117681
+ function detectCloudResource() {
117682
+ const attrs = {};
117683
+ if (runtimeCaps.isWorkerLike) {
117684
+ attrs["cloud.provider"] = "cloudflare";
117685
+ attrs["cloud.platform"] = "cloudflare_workers";
117686
+ }
117687
+ if (typeof process !== "undefined" && process.env?.AWS_LAMBDA_FUNCTION_NAME) {
117688
+ attrs["cloud.provider"] = "aws";
117689
+ attrs["cloud.platform"] = "aws_lambda";
117690
+ if (process.env.AWS_REGION) {
117691
+ attrs["cloud.region"] = process.env.AWS_REGION;
117692
+ }
117693
+ }
117694
+ if (typeof process !== "undefined" && (process.env?.FUNCTION_TARGET || process.env?.K_SERVICE)) {
117695
+ attrs["cloud.provider"] = "gcp";
117696
+ attrs["cloud.platform"] = process.env.FUNCTION_TARGET ? "gcp_cloud_functions" : "gcp_cloud_run";
117697
+ if (process.env.GCP_REGION) {
117698
+ attrs["cloud.region"] = process.env.GCP_REGION;
117699
+ }
117713
117700
  }
117701
+ attrs["deployment.environment.name"] = config.environment;
117702
+ return attrs;
117714
117703
  }
117715
- async function shutdownOpenTelemetry() {
117716
- if (sdk) {
117704
+ async function initializeOpenTelemetry() {
117705
+ if (initializationPromise) {
117706
+ return initializationPromise;
117707
+ }
117708
+ if (isOtelInitialized) {
117709
+ return;
117710
+ }
117711
+ initializationPromise = (async () => {
117712
+ if (!config.openTelemetry.enabled) {
117713
+ import_api.diag.info("OpenTelemetry disabled via configuration.");
117714
+ isOtelInitialized = true;
117715
+ return;
117716
+ }
117717
+ if (!canUseNodeSDK()) {
117718
+ import_api.diag.info("NodeSDK unavailable in this runtime. Using lightweight telemetry mode.");
117719
+ isOtelInitialized = true;
117720
+ return;
117721
+ }
117722
+ isOtelInitialized = true;
117717
117723
  try {
117718
- await sdk.shutdown();
117719
- import_api.diag.info("OpenTelemetry terminated successfully.");
117724
+ const [
117725
+ { getNodeAutoInstrumentations },
117726
+ { OTLPMetricExporter },
117727
+ { OTLPTraceExporter },
117728
+ { PinoInstrumentation },
117729
+ { resourceFromAttributes },
117730
+ { PeriodicExportingMetricReader },
117731
+ { NodeSDK },
117732
+ { BatchSpanProcessor, TraceIdRatioBasedSampler },
117733
+ { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION }
117734
+ ] = await Promise.all([
117735
+ Promise.resolve().then(() => __toESM(require_src58(), 1)),
117736
+ Promise.resolve().then(() => __toESM(require_src62(), 1)),
117737
+ Promise.resolve().then(() => __toESM(require_src63(), 1)),
117738
+ Promise.resolve().then(() => __toESM(require_src40(), 1)),
117739
+ Promise.resolve().then(() => __toESM(require_src52(), 1)),
117740
+ Promise.resolve().then(() => __toESM(require_src59(), 1)),
117741
+ Promise.resolve().then(() => __toESM(require_src83(), 1)),
117742
+ Promise.resolve().then(() => __toESM(require_src67(), 1)),
117743
+ Promise.resolve().then(() => __toESM(require_index_incubating(), 1))
117744
+ ]);
117745
+ const otelLogLevelString = config.openTelemetry.logLevel.toUpperCase();
117746
+ const otelLogLevel = import_api.DiagLogLevel[otelLogLevelString] ?? import_api.DiagLogLevel.INFO;
117747
+ import_api.diag.setLogger(new import_api.DiagConsoleLogger, otelLogLevel);
117748
+ const tracesEndpoint = config.openTelemetry.tracesEndpoint;
117749
+ const metricsEndpoint = config.openTelemetry.metricsEndpoint;
117750
+ if (!tracesEndpoint && !metricsEndpoint) {
117751
+ import_api.diag.warn("OTEL_ENABLED is true, but no OTLP endpoint for traces or metrics is configured. OpenTelemetry will not export any telemetry.");
117752
+ }
117753
+ const resource = resourceFromAttributes({
117754
+ [ATTR_SERVICE_NAME]: config.openTelemetry.serviceName,
117755
+ [ATTR_SERVICE_VERSION]: config.openTelemetry.serviceVersion,
117756
+ ...detectCloudResource()
117757
+ });
117758
+ const spanProcessors = [];
117759
+ if (tracesEndpoint) {
117760
+ import_api.diag.info(`Using OTLP exporter for traces, endpoint: ${tracesEndpoint}`);
117761
+ const traceExporter = new OTLPTraceExporter({ url: tracesEndpoint });
117762
+ spanProcessors.push(new BatchSpanProcessor(traceExporter));
117763
+ } else {
117764
+ import_api.diag.info("No OTLP traces endpoint configured. Traces will not be exported.");
117765
+ }
117766
+ const metricReader = metricsEndpoint ? new PeriodicExportingMetricReader({
117767
+ exporter: new OTLPMetricExporter({ url: metricsEndpoint }),
117768
+ exportIntervalMillis: 15000
117769
+ }) : undefined;
117770
+ sdk = new NodeSDK({
117771
+ resource,
117772
+ spanProcessors,
117773
+ ...metricReader && { metricReader },
117774
+ sampler: new TraceIdRatioBasedSampler(config.openTelemetry.samplingRatio),
117775
+ instrumentations: [
117776
+ getNodeAutoInstrumentations({
117777
+ "@opentelemetry/instrumentation-http": {
117778
+ enabled: true,
117779
+ ignoreIncomingRequestHook: (req) => req.url === "/healthz"
117780
+ },
117781
+ "@opentelemetry/instrumentation-fs": { enabled: false }
117782
+ }),
117783
+ new PinoInstrumentation({
117784
+ logHook: (_span, record) => {
117785
+ record["trace_id"] = _span.spanContext().traceId;
117786
+ record["span_id"] = _span.spanContext().spanId;
117787
+ }
117788
+ })
117789
+ ]
117790
+ });
117791
+ sdk.start();
117792
+ import_api.diag.info(`OpenTelemetry NodeSDK initialized for ${config.openTelemetry.serviceName} v${config.openTelemetry.serviceVersion}`);
117720
117793
  } catch (error) {
117721
- import_api.diag.error("Error terminating OpenTelemetry", error);
117794
+ import_api.diag.error("Error initializing OpenTelemetry", error);
117795
+ sdk = null;
117796
+ throw error;
117722
117797
  }
117798
+ })();
117799
+ return initializationPromise;
117800
+ }
117801
+ async function shutdownOpenTelemetry(timeoutMs = 5000) {
117802
+ if (!sdk) {
117803
+ return;
117804
+ }
117805
+ try {
117806
+ const shutdownPromise = sdk.shutdown();
117807
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("OpenTelemetry SDK shutdown timeout")), timeoutMs));
117808
+ await Promise.race([shutdownPromise, timeoutPromise]);
117809
+ import_api.diag.info("OpenTelemetry SDK terminated successfully.");
117810
+ } catch (error) {
117811
+ import_api.diag.error("Error terminating OpenTelemetry SDK", error);
117812
+ throw error;
117813
+ } finally {
117814
+ sdk = null;
117815
+ isOtelInitialized = false;
117816
+ initializationPromise = null;
117723
117817
  }
117724
117818
  }
117725
117819
 
@@ -117789,6 +117883,26 @@ var requestContextServiceInstance = {
117789
117883
  }
117790
117884
  }
117791
117885
  return context;
117886
+ },
117887
+ withAuthInfo(authInfo, parentContext) {
117888
+ const baseContext = this.createRequestContext({
117889
+ operation: "withAuthInfo",
117890
+ parentContext,
117891
+ additionalContext: {
117892
+ tenantId: authInfo.tenantId
117893
+ }
117894
+ });
117895
+ const authContext2 = {
117896
+ sub: authInfo.subject ?? authInfo.clientId,
117897
+ scopes: authInfo.scopes,
117898
+ clientId: authInfo.clientId,
117899
+ token: authInfo.token,
117900
+ ...authInfo.tenantId ? { tenantId: authInfo.tenantId } : {}
117901
+ };
117902
+ return {
117903
+ ...baseContext,
117904
+ auth: authContext2
117905
+ };
117792
117906
  }
117793
117907
  };
117794
117908
  var requestContextService = requestContextServiceInstance;
@@ -118201,8 +118315,10 @@ class Logger {
118201
118315
  }
118202
118316
  async createPinoLogger(level) {
118203
118317
  const pinoLevel = mcpToPinoLevel[level] || "info";
118318
+ const isTest = config.environment === "testing";
118319
+ const enableTestLogs = process.env.ENABLE_TEST_LOGS === "true";
118204
118320
  const pinoOptions = {
118205
- level: pinoLevel,
118321
+ level: isTest && !enableTestLogs ? "silent" : pinoLevel,
118206
118322
  base: {
118207
118323
  env: config.environment,
118208
118324
  version: config.mcpServerVersion,
@@ -118220,7 +118336,6 @@ class Logger {
118220
118336
  const { default: path } = await import("path");
118221
118337
  const transports = [];
118222
118338
  const isDevelopment = config.environment === "development";
118223
- const isTest = config.environment === "testing";
118224
118339
  if (isDevelopment && !isServerless2) {
118225
118340
  try {
118226
118341
  const { createRequire: createRequire2 } = await import("node:module");
@@ -118436,6 +118551,25 @@ class Logger {
118436
118551
  var logger = Logger.getInstance();
118437
118552
 
118438
118553
  // src/utils/internal/error-handler/mappings.ts
118554
+ var COMPILED_PATTERN_CACHE = new Map;
118555
+ function getCompiledPattern(pattern) {
118556
+ const cacheKey = pattern instanceof RegExp ? pattern.source + pattern.flags : pattern;
118557
+ if (COMPILED_PATTERN_CACHE.has(cacheKey)) {
118558
+ return COMPILED_PATTERN_CACHE.get(cacheKey);
118559
+ }
118560
+ let compiled;
118561
+ if (pattern instanceof RegExp) {
118562
+ let flags = pattern.flags.replace("g", "");
118563
+ if (!flags.includes("i")) {
118564
+ flags += "i";
118565
+ }
118566
+ compiled = new RegExp(pattern.source, flags);
118567
+ } else {
118568
+ compiled = new RegExp(pattern, "i");
118569
+ }
118570
+ COMPILED_PATTERN_CACHE.set(cacheKey, compiled);
118571
+ return compiled;
118572
+ }
118439
118573
  var ERROR_TYPE_MAPPINGS = {
118440
118574
  SyntaxError: -32007 /* ValidationError */,
118441
118575
  TypeError: -32007 /* ValidationError */,
@@ -118487,17 +118621,76 @@ var COMMON_ERROR_PATTERNS = [
118487
118621
  errorCode: -32007 /* ValidationError */
118488
118622
  }
118489
118623
  ];
118624
+ var COMPILED_ERROR_PATTERNS = COMMON_ERROR_PATTERNS.map((mapping) => ({
118625
+ ...mapping,
118626
+ compiledPattern: getCompiledPattern(mapping.pattern)
118627
+ }));
118628
+ var PROVIDER_ERROR_PATTERNS = [
118629
+ {
118630
+ pattern: /ThrottlingException|TooManyRequestsException/i,
118631
+ errorCode: -32003 /* RateLimited */
118632
+ },
118633
+ {
118634
+ pattern: /AccessDenied|UnauthorizedOperation/i,
118635
+ errorCode: -32005 /* Forbidden */
118636
+ },
118637
+ {
118638
+ pattern: /ResourceNotFoundException/i,
118639
+ errorCode: -32001 /* NotFound */
118640
+ },
118641
+ { pattern: /status code 401/i, errorCode: -32006 /* Unauthorized */ },
118642
+ { pattern: /status code 403/i, errorCode: -32005 /* Forbidden */ },
118643
+ { pattern: /status code 404/i, errorCode: -32001 /* NotFound */ },
118644
+ { pattern: /status code 409/i, errorCode: -32002 /* Conflict */ },
118645
+ { pattern: /status code 429/i, errorCode: -32003 /* RateLimited */ },
118646
+ {
118647
+ pattern: /status code 5\d\d/i,
118648
+ errorCode: -32000 /* ServiceUnavailable */
118649
+ },
118650
+ {
118651
+ pattern: /ECONNREFUSED|connection refused/i,
118652
+ errorCode: -32000 /* ServiceUnavailable */
118653
+ },
118654
+ {
118655
+ pattern: /ETIMEDOUT|connection timeout/i,
118656
+ errorCode: -32004 /* Timeout */
118657
+ },
118658
+ {
118659
+ pattern: /unique constraint|duplicate key/i,
118660
+ errorCode: -32002 /* Conflict */
118661
+ },
118662
+ {
118663
+ pattern: /foreign key constraint/i,
118664
+ errorCode: -32007 /* ValidationError */
118665
+ },
118666
+ { pattern: /JWT expired/i, errorCode: -32006 /* Unauthorized */ },
118667
+ {
118668
+ pattern: /row level security/i,
118669
+ errorCode: -32005 /* Forbidden */
118670
+ },
118671
+ {
118672
+ pattern: /insufficient_quota|quota exceeded/i,
118673
+ errorCode: -32003 /* RateLimited */
118674
+ },
118675
+ { pattern: /model_not_found/i, errorCode: -32001 /* NotFound */ },
118676
+ {
118677
+ pattern: /context_length_exceeded/i,
118678
+ errorCode: -32007 /* ValidationError */
118679
+ },
118680
+ { pattern: /ENOTFOUND|DNS/i, errorCode: -32000 /* ServiceUnavailable */ },
118681
+ {
118682
+ pattern: /ECONNRESET|connection reset/i,
118683
+ errorCode: -32000 /* ServiceUnavailable */
118684
+ }
118685
+ ];
118686
+ var COMPILED_PROVIDER_PATTERNS = PROVIDER_ERROR_PATTERNS.map((mapping) => ({
118687
+ ...mapping,
118688
+ compiledPattern: getCompiledPattern(mapping.pattern)
118689
+ }));
118490
118690
 
118491
118691
  // src/utils/internal/error-handler/helpers.ts
118492
118692
  function createSafeRegex(pattern) {
118493
- if (pattern instanceof RegExp) {
118494
- let flags = pattern.flags.replace("g", "");
118495
- if (!flags.includes("i")) {
118496
- flags += "i";
118497
- }
118498
- return new RegExp(pattern.source, flags);
118499
- }
118500
- return new RegExp(pattern, "i");
118693
+ return getCompiledPattern(pattern);
118501
118694
  }
118502
118695
  function getErrorName(error) {
118503
118696
  if (error instanceof Error) {
@@ -118558,6 +118751,61 @@ function getErrorMessage(error) {
118558
118751
  return `Error converting error to string: ${conversionError instanceof Error ? conversionError.message : "Unknown conversion error"}`;
118559
118752
  }
118560
118753
  }
118754
+ function extractErrorCauseChain(error, maxDepth = 20) {
118755
+ const chain = [];
118756
+ const seen = new WeakSet;
118757
+ let current = error;
118758
+ let depth = 0;
118759
+ while (current && depth < maxDepth) {
118760
+ if (typeof current === "object" && current !== null) {
118761
+ if (seen.has(current)) {
118762
+ chain.push({
118763
+ name: "CircularReference",
118764
+ message: "Circular reference detected in error cause chain",
118765
+ depth
118766
+ });
118767
+ break;
118768
+ }
118769
+ seen.add(current);
118770
+ }
118771
+ if (current instanceof Error) {
118772
+ const node = {
118773
+ name: current.name,
118774
+ message: current.message,
118775
+ depth,
118776
+ ...current.stack !== undefined ? { stack: current.stack } : {}
118777
+ };
118778
+ if (current instanceof McpError && current.data) {
118779
+ node.data = current.data;
118780
+ }
118781
+ chain.push(node);
118782
+ current = current.cause;
118783
+ } else if (typeof current === "string") {
118784
+ chain.push({
118785
+ name: "StringError",
118786
+ message: current,
118787
+ depth
118788
+ });
118789
+ break;
118790
+ } else {
118791
+ chain.push({
118792
+ name: getErrorName(current),
118793
+ message: getErrorMessage(current),
118794
+ depth
118795
+ });
118796
+ break;
118797
+ }
118798
+ depth++;
118799
+ }
118800
+ if (depth >= maxDepth) {
118801
+ chain.push({
118802
+ name: "MaxDepthExceeded",
118803
+ message: `Error cause chain exceeded maximum depth of ${maxDepth}`,
118804
+ depth
118805
+ });
118806
+ }
118807
+ return chain;
118808
+ }
118561
118809
 
118562
118810
  // src/utils/internal/error-handler/errorHandler.ts
118563
118811
  class ErrorHandler {
@@ -118571,9 +118819,13 @@ class ErrorHandler {
118571
118819
  if (mappedFromType) {
118572
118820
  return mappedFromType;
118573
118821
  }
118574
- for (const mapping of COMMON_ERROR_PATTERNS) {
118575
- const regex = createSafeRegex(mapping.pattern);
118576
- if (regex.test(errorMessage) || regex.test(errorName)) {
118822
+ for (const mapping of COMPILED_PROVIDER_PATTERNS) {
118823
+ if (mapping.compiledPattern.test(errorMessage) || mapping.compiledPattern.test(errorName)) {
118824
+ return mapping.errorCode;
118825
+ }
118826
+ }
118827
+ for (const mapping of COMPILED_ERROR_PATTERNS) {
118828
+ if (mapping.compiledPattern.test(errorMessage) || mapping.compiledPattern.test(errorName)) {
118577
118829
  return mapping.errorCode;
118578
118830
  }
118579
118831
  }
@@ -118620,17 +118872,19 @@ class ErrorHandler {
118620
118872
  consolidatedData.originalStack = originalStack;
118621
118873
  }
118622
118874
  const cause = error instanceof Error ? error : undefined;
118623
- const rootCause = (() => {
118624
- let current = cause;
118625
- let depth = 0;
118626
- while (current && current instanceof Error && current.cause && depth < 5) {
118627
- current = current.cause;
118628
- depth += 1;
118875
+ const causeChain = extractErrorCauseChain(error);
118876
+ if (causeChain.length > 0) {
118877
+ const rootCause = causeChain[causeChain.length - 1];
118878
+ if (rootCause) {
118879
+ consolidatedData["rootCause"] = {
118880
+ name: rootCause.name,
118881
+ message: rootCause.message
118882
+ };
118629
118883
  }
118630
- return current instanceof Error ? { name: current.name, message: current.message } : undefined;
118631
- })();
118632
- if (rootCause) {
118633
- consolidatedData["rootCause"] = rootCause;
118884
+ consolidatedData["causeChain"] = causeChain;
118885
+ }
118886
+ if (context && "metadata" in context && context.metadata && typeof context.metadata === "object" && "breadcrumbs" in context.metadata) {
118887
+ consolidatedData["breadcrumbs"] = context.metadata.breadcrumbs;
118634
118888
  }
118635
118889
  if (error instanceof McpError) {
118636
118890
  loggedErrorCode = error.code;
@@ -118714,32 +118968,119 @@ class ErrorHandler {
118714
118968
  });
118715
118969
  }
118716
118970
  }
118717
- }
118718
- // src/utils/internal/runtime.ts
118719
- var safeHas = (key) => {
118720
- try {
118721
- return typeof globalThis[key] !== "undefined";
118722
- } catch {
118723
- return false;
118971
+ static async tryAsResult(fn, options) {
118972
+ try {
118973
+ const value = await Promise.resolve(fn());
118974
+ return { ok: true, value };
118975
+ } catch (caughtError) {
118976
+ const error = ErrorHandler.handleError(caughtError, {
118977
+ ...options,
118978
+ rethrow: false
118979
+ });
118980
+ return { ok: false, error };
118981
+ }
118724
118982
  }
118725
- };
118726
- var isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
118727
- var hasProcess = typeof process !== "undefined";
118728
- var hasBuffer = typeof Buffer !== "undefined";
118729
- var hasTextEncoder = safeHas("TextEncoder");
118730
- var hasPerformanceNow = typeof globalThis.performance?.now === "function";
118731
- var isWorkerLike = !isNode && typeof globalThis.WorkerGlobalScope !== "undefined";
118732
- var isBrowserLike = !isNode && !isWorkerLike && safeHas("window");
118733
- var runtimeCaps = {
118734
- isNode,
118735
- isWorkerLike,
118736
- isBrowserLike,
118737
- hasProcess,
118738
- hasBuffer,
118739
- hasTextEncoder,
118740
- hasPerformanceNow
118741
- };
118742
-
118983
+ static mapResult(result, fn) {
118984
+ if (result.ok) {
118985
+ try {
118986
+ return { ok: true, value: fn(result.value) };
118987
+ } catch (error) {
118988
+ return {
118989
+ ok: false,
118990
+ error: new McpError(-32603 /* InternalError */, `Error mapping result: ${getErrorMessage(error)}`)
118991
+ };
118992
+ }
118993
+ }
118994
+ return result;
118995
+ }
118996
+ static flatMapResult(result, fn) {
118997
+ if (result.ok) {
118998
+ return fn(result.value);
118999
+ }
119000
+ return result;
119001
+ }
119002
+ static recoverResult(result, fallback) {
119003
+ if (result.ok) {
119004
+ return result.value;
119005
+ }
119006
+ return typeof fallback === "function" ? fallback(result.error) : fallback;
119007
+ }
119008
+ static addBreadcrumb(context, operation, additionalData) {
119009
+ const breadcrumbs = context.metadata?.breadcrumbs || [];
119010
+ breadcrumbs.push({
119011
+ timestamp: new Date().toISOString(),
119012
+ operation,
119013
+ ...additionalData !== undefined ? { context: additionalData } : {}
119014
+ });
119015
+ return {
119016
+ ...context,
119017
+ metadata: {
119018
+ ...context.metadata,
119019
+ breadcrumbs
119020
+ }
119021
+ };
119022
+ }
119023
+ static async tryCatchWithRetry(fn, options, strategy) {
119024
+ let lastError;
119025
+ for (let attempt = 1;attempt <= strategy.maxAttempts; attempt++) {
119026
+ try {
119027
+ return await Promise.resolve(fn());
119028
+ } catch (caughtError) {
119029
+ lastError = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
119030
+ if (attempt < strategy.maxAttempts && strategy.shouldRetry(lastError, attempt)) {
119031
+ const delay = strategy.getRetryDelay(attempt);
119032
+ const retryContext = {
119033
+ ...options.context,
119034
+ requestId: options.context?.requestId || generateUUID(),
119035
+ timestamp: new Date().toISOString(),
119036
+ operation: options.operation,
119037
+ error: lastError.message,
119038
+ attempt
119039
+ };
119040
+ logger.warning(`Retry attempt ${attempt}/${strategy.maxAttempts} after ${delay}ms`, retryContext);
119041
+ strategy.onRetry?.(lastError, attempt);
119042
+ await new Promise((resolve) => setTimeout(resolve, delay));
119043
+ } else {
119044
+ throw ErrorHandler.handleError(lastError, {
119045
+ ...options,
119046
+ rethrow: true,
119047
+ context: {
119048
+ ...options.context,
119049
+ retryAttempts: attempt,
119050
+ finalAttempt: true
119051
+ }
119052
+ });
119053
+ }
119054
+ }
119055
+ }
119056
+ throw ErrorHandler.handleError(lastError, {
119057
+ ...options,
119058
+ rethrow: true
119059
+ });
119060
+ }
119061
+ static createExponentialBackoffStrategy(maxAttempts = 3, baseDelay = 1000, maxDelay = 30000) {
119062
+ return {
119063
+ maxAttempts,
119064
+ shouldRetry: (error) => {
119065
+ if (error instanceof McpError) {
119066
+ const nonRetryableCodes = [
119067
+ -32007 /* ValidationError */,
119068
+ -32006 /* Unauthorized */,
119069
+ -32005 /* Forbidden */,
119070
+ -32001 /* NotFound */
119071
+ ];
119072
+ return !nonRetryableCodes.includes(error.code);
119073
+ }
119074
+ return true;
119075
+ },
119076
+ getRetryDelay: (attemptNumber) => {
119077
+ const exponentialDelay = baseDelay * Math.pow(2, attemptNumber - 1);
119078
+ const jitter = Math.random() * 0.3 * exponentialDelay;
119079
+ return Math.min(exponentialDelay + jitter, maxDelay);
119080
+ }
119081
+ };
119082
+ }
119083
+ }
118743
119084
  // src/utils/internal/performance.ts
118744
119085
  var import_api4 = __toESM(require_src(), 1);
118745
119086
 
@@ -125638,52 +125979,196 @@ class SpeechService2 {
125638
125979
  }
125639
125980
  // src/storage/core/StorageService.ts
125640
125981
  var import_tsyringe5 = __toESM(require_cjs3(), 1);
125641
- function requireTenantId(context) {
125642
- const tenantId = context.tenantId;
125643
- if (tenantId === undefined || tenantId === null) {
125644
- throw new McpError(-32603 /* InternalError */, "Tenant ID is required for storage operations but was not found in the request context.", {
125645
- operation: "StorageService.requireTenantId",
125646
- requestId: context.requestId
125647
- });
125982
+
125983
+ // src/utils/internal/encoding.ts
125984
+ function arrayBufferToBase64(buffer) {
125985
+ if (runtimeCaps.hasBuffer) {
125986
+ return Buffer.from(buffer).toString("base64");
125987
+ } else {
125988
+ let binary = "";
125989
+ const bytes = new Uint8Array(buffer);
125990
+ const len = bytes.byteLength;
125991
+ for (let i = 0;i < len; i++) {
125992
+ binary += String.fromCharCode(bytes[i]);
125993
+ }
125994
+ return btoa(binary);
125648
125995
  }
125649
- if (typeof tenantId !== "string" || tenantId.trim().length === 0) {
125650
- throw new McpError(-32602 /* InvalidParams */, "Tenant ID cannot be an empty string.", {
125651
- operation: "StorageService.requireTenantId",
125652
- requestId: context.requestId,
125653
- tenantId
125654
- });
125996
+ }
125997
+ function stringToBase64(str2) {
125998
+ if (runtimeCaps.hasBuffer) {
125999
+ return Buffer.from(str2, "utf-8").toString("base64");
126000
+ } else {
126001
+ const encoder = new TextEncoder;
126002
+ const bytes = encoder.encode(str2);
126003
+ return arrayBufferToBase64(bytes.buffer);
126004
+ }
126005
+ }
126006
+ function base64ToString(base642) {
126007
+ if (runtimeCaps.hasBuffer) {
126008
+ return Buffer.from(base642, "base64").toString("utf-8");
126009
+ } else {
126010
+ const decoded = atob(base642);
126011
+ const decoder = new TextDecoder;
126012
+ const bytes = new Uint8Array(decoded.split("").map((c) => c.charCodeAt(0)));
126013
+ return decoder.decode(bytes);
126014
+ }
126015
+ }
126016
+
126017
+ // src/storage/core/storageValidation.ts
126018
+ var MAX_TENANT_ID_LENGTH = 128;
126019
+ var MAX_KEY_LENGTH = 1024;
126020
+ var MAX_PREFIX_LENGTH = 512;
126021
+ var MAX_LIST_LIMIT = 1e4;
126022
+ var VALID_TENANT_ID_PATTERN = /^[a-zA-Z0-9]$|^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$/;
126023
+ var VALID_KEY_PATTERN = /^[a-zA-Z0-9_.\-/]+$/;
126024
+ function validateTenantId(tenantId, context) {
126025
+ if (typeof tenantId !== "string") {
126026
+ throw new McpError(-32602 /* InvalidParams */, "Tenant ID must be a string.", { ...context, tenantId });
125655
126027
  }
125656
126028
  const trimmedTenantId = tenantId.trim();
125657
- if (trimmedTenantId.length > 128) {
125658
- throw new McpError(-32602 /* InvalidParams */, "Tenant ID exceeds maximum length of 128 characters.", {
125659
- operation: "StorageService.requireTenantId",
125660
- requestId: context.requestId,
125661
- tenantIdLength: trimmedTenantId.length
126029
+ if (trimmedTenantId.length === 0) {
126030
+ throw new McpError(-32602 /* InvalidParams */, "Tenant ID cannot be an empty string.", { ...context, tenantId });
126031
+ }
126032
+ if (trimmedTenantId.length > MAX_TENANT_ID_LENGTH) {
126033
+ throw new McpError(-32602 /* InvalidParams */, `Tenant ID exceeds maximum length of ${MAX_TENANT_ID_LENGTH} characters.`, { ...context, tenantIdLength: trimmedTenantId.length });
126034
+ }
126035
+ if (!VALID_TENANT_ID_PATTERN.test(trimmedTenantId)) {
126036
+ throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains invalid characters. Only alphanumeric characters, hyphens, underscores, and dots are allowed. Must start and end with alphanumeric characters.", { ...context, tenantId: trimmedTenantId });
126037
+ }
126038
+ if (trimmedTenantId.includes("..")) {
126039
+ throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains consecutive dots, which are not allowed.", { ...context, tenantId: trimmedTenantId });
126040
+ }
126041
+ if (trimmedTenantId.includes("../") || trimmedTenantId.includes("..\\")) {
126042
+ throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains path traversal sequences, which are not allowed.", { ...context, tenantId: trimmedTenantId });
126043
+ }
126044
+ }
126045
+ function validateKey(key, context) {
126046
+ if (!key || typeof key !== "string") {
126047
+ throw new McpError(-32007 /* ValidationError */, "Key must be a non-empty string.", { ...context, key });
126048
+ }
126049
+ if (key.length > MAX_KEY_LENGTH) {
126050
+ throw new McpError(-32007 /* ValidationError */, `Key exceeds maximum length of ${MAX_KEY_LENGTH} characters.`, { ...context, key: key.substring(0, 50) + "..." });
126051
+ }
126052
+ if (!VALID_KEY_PATTERN.test(key)) {
126053
+ throw new McpError(-32007 /* ValidationError */, "Key contains invalid characters. Only alphanumeric, hyphens, underscores, dots, and slashes are allowed.", { ...context, key });
126054
+ }
126055
+ if (key.includes("..")) {
126056
+ throw new McpError(-32007 /* ValidationError */, 'Key must not contain ".." (path traversal attempt).', { ...context, key });
126057
+ }
126058
+ }
126059
+ function validatePrefix(prefix, context) {
126060
+ if (typeof prefix !== "string") {
126061
+ throw new McpError(-32007 /* ValidationError */, "Prefix must be a string.", { ...context, operation: "validatePrefix", prefix });
126062
+ }
126063
+ if (prefix === "") {
126064
+ return;
126065
+ }
126066
+ if (prefix.length > MAX_PREFIX_LENGTH) {
126067
+ throw new McpError(-32007 /* ValidationError */, `Prefix exceeds maximum length of ${MAX_PREFIX_LENGTH} characters.`, {
126068
+ ...context,
126069
+ operation: "validatePrefix",
126070
+ prefix: prefix.substring(0, 50) + "..."
125662
126071
  });
125663
126072
  }
125664
- const validTenantIdPattern = /^[a-zA-Z0-9]$|^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$/;
125665
- if (!validTenantIdPattern.test(trimmedTenantId)) {
125666
- throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains invalid characters. Only alphanumeric characters, hyphens, underscores, and dots are allowed. Must start and end with alphanumeric characters.", {
125667
- operation: "StorageService.requireTenantId",
125668
- requestId: context.requestId,
125669
- tenantId: trimmedTenantId
126073
+ if (!VALID_KEY_PATTERN.test(prefix)) {
126074
+ throw new McpError(-32007 /* ValidationError */, "Prefix contains invalid characters. Only alphanumeric, hyphens, underscores, dots, and slashes are allowed.", {
126075
+ ...context,
126076
+ operation: "validatePrefix",
126077
+ prefix: prefix.length > 50 ? prefix.substring(0, 50) + "..." : prefix
125670
126078
  });
125671
126079
  }
125672
- if (trimmedTenantId.includes("../") || trimmedTenantId.includes("..\\")) {
125673
- throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains path traversal sequences, which are not allowed.", {
125674
- operation: "StorageService.requireTenantId",
125675
- requestId: context.requestId,
125676
- tenantId: trimmedTenantId
126080
+ if (prefix.includes("..")) {
126081
+ throw new McpError(-32007 /* ValidationError */, 'Prefix must not contain ".." (path traversal attempt).', { ...context, operation: "validatePrefix", prefix });
126082
+ }
126083
+ }
126084
+ function validateStorageOptions(options, context) {
126085
+ if (!options) {
126086
+ return;
126087
+ }
126088
+ if (options.ttl !== undefined) {
126089
+ if (typeof options.ttl !== "number") {
126090
+ throw new McpError(-32007 /* ValidationError */, "TTL must be a number (seconds).", { ...context, ttl: options.ttl });
126091
+ }
126092
+ if (options.ttl < 0) {
126093
+ throw new McpError(-32007 /* ValidationError */, "TTL must be a non-negative number. Use 0 for immediate expiration.", { ...context, ttl: options.ttl });
126094
+ }
126095
+ if (!Number.isFinite(options.ttl)) {
126096
+ throw new McpError(-32007 /* ValidationError */, "TTL must be a finite number.", { ...context, ttl: options.ttl });
126097
+ }
126098
+ }
126099
+ }
126100
+ function validateListOptions(options, context) {
126101
+ if (!options) {
126102
+ return;
126103
+ }
126104
+ if (options.limit !== undefined) {
126105
+ if (typeof options.limit !== "number") {
126106
+ throw new McpError(-32007 /* ValidationError */, "List limit must be a number.", { ...context, operation: "validateListOptions", limit: options.limit });
126107
+ }
126108
+ if (!Number.isInteger(options.limit)) {
126109
+ throw new McpError(-32007 /* ValidationError */, "List limit must be an integer.", { ...context, operation: "validateListOptions", limit: options.limit });
126110
+ }
126111
+ if (options.limit < 1) {
126112
+ throw new McpError(-32007 /* ValidationError */, "List limit must be at least 1.", { ...context, operation: "validateListOptions", limit: options.limit });
126113
+ }
126114
+ if (options.limit > MAX_LIST_LIMIT) {
126115
+ throw new McpError(-32007 /* ValidationError */, `List limit exceeds maximum of ${MAX_LIST_LIMIT}.`, { ...context, operation: "validateListOptions", limit: options.limit });
126116
+ }
126117
+ if (!Number.isFinite(options.limit)) {
126118
+ throw new McpError(-32007 /* ValidationError */, "List limit must be a finite number.", { ...context, operation: "validateListOptions", limit: options.limit });
126119
+ }
126120
+ }
126121
+ if (options.cursor !== undefined) {
126122
+ if (typeof options.cursor !== "string") {
126123
+ throw new McpError(-32007 /* ValidationError */, "Cursor must be a string.", { ...context, operation: "validateListOptions" });
126124
+ }
126125
+ if (options.cursor.trim() === "") {
126126
+ throw new McpError(-32007 /* ValidationError */, "Cursor must not be an empty string.", { ...context, operation: "validateListOptions" });
126127
+ }
126128
+ if (!/^[A-Za-z0-9+/=]+$/.test(options.cursor)) {
126129
+ throw new McpError(-32007 /* ValidationError */, "Cursor contains invalid characters for base64.", { ...context, operation: "validateListOptions" });
126130
+ }
126131
+ }
126132
+ }
126133
+ function encodeCursor(lastKey, tenantId) {
126134
+ const data = { k: lastKey, t: tenantId };
126135
+ return stringToBase64(JSON.stringify(data));
126136
+ }
126137
+ function decodeCursor(cursor, tenantId, context) {
126138
+ try {
126139
+ const decoded = base64ToString(cursor);
126140
+ const data = JSON.parse(decoded);
126141
+ if (!data || typeof data !== "object" || !("k" in data) || !("t" in data)) {
126142
+ throw new McpError(-32602 /* InvalidParams */, "Invalid cursor format.", { ...context, operation: "decodeCursor" });
126143
+ }
126144
+ if (data.t !== tenantId) {
126145
+ throw new McpError(-32602 /* InvalidParams */, "Cursor tenant ID mismatch. Cursor may have been tampered with.", { ...context, operation: "decodeCursor" });
126146
+ }
126147
+ return data.k;
126148
+ } catch (error2) {
126149
+ if (error2 instanceof McpError) {
126150
+ throw error2;
126151
+ }
126152
+ throw new McpError(-32602 /* InvalidParams */, "Failed to decode cursor. Cursor may be corrupted or invalid.", {
126153
+ ...context,
126154
+ operation: "decodeCursor",
126155
+ rawError: error2 instanceof Error ? error2.stack : String(error2)
125677
126156
  });
125678
126157
  }
125679
- if (trimmedTenantId.includes("..")) {
125680
- throw new McpError(-32602 /* InvalidParams */, "Tenant ID contains consecutive dots, which are not allowed.", {
125681
- operation: "StorageService.requireTenantId",
126158
+ }
126159
+
126160
+ // src/storage/core/StorageService.ts
126161
+ function requireTenantId(context) {
126162
+ const tenantId = context.tenantId;
126163
+ if (tenantId === undefined || tenantId === null) {
126164
+ throw new McpError(-32603 /* InternalError */, "Tenant ID is required for storage operations but was not found in the request context.", {
126165
+ operation: context.operation || "StorageService.requireTenantId",
125682
126166
  requestId: context.requestId,
125683
- tenantId: trimmedTenantId
126167
+ calledFrom: "StorageService"
125684
126168
  });
125685
126169
  }
125686
- return trimmedTenantId;
126170
+ validateTenantId(tenantId, context);
126171
+ return tenantId.trim();
125687
126172
  }
125688
126173
 
125689
126174
  class StorageService2 {
@@ -125693,34 +126178,103 @@ class StorageService2 {
125693
126178
  }
125694
126179
  get(key, context) {
125695
126180
  const tenantId = requireTenantId(context);
126181
+ validateKey(key, context);
126182
+ logger.debug("[StorageService] get operation", {
126183
+ ...context,
126184
+ operation: "StorageService.get",
126185
+ tenantId,
126186
+ key
126187
+ });
125696
126188
  return this.provider.get(tenantId, key, context);
125697
126189
  }
125698
126190
  set(key, value, context, options) {
125699
126191
  const tenantId = requireTenantId(context);
126192
+ validateKey(key, context);
126193
+ validateStorageOptions(options, context);
126194
+ logger.debug("[StorageService] set operation", {
126195
+ ...context,
126196
+ operation: "StorageService.set",
126197
+ tenantId,
126198
+ key,
126199
+ hasTTL: options?.ttl !== undefined,
126200
+ ttl: options?.ttl
126201
+ });
125700
126202
  return this.provider.set(tenantId, key, value, context, options);
125701
126203
  }
125702
126204
  delete(key, context) {
125703
126205
  const tenantId = requireTenantId(context);
126206
+ validateKey(key, context);
126207
+ logger.debug("[StorageService] delete operation", {
126208
+ ...context,
126209
+ operation: "StorageService.delete",
126210
+ tenantId,
126211
+ key
126212
+ });
125704
126213
  return this.provider.delete(tenantId, key, context);
125705
126214
  }
125706
126215
  list(prefix, context, options) {
125707
126216
  const tenantId = requireTenantId(context);
126217
+ validatePrefix(prefix, context);
126218
+ validateListOptions(options, context);
126219
+ logger.debug("[StorageService] list operation", {
126220
+ ...context,
126221
+ operation: "StorageService.list",
126222
+ tenantId,
126223
+ prefix,
126224
+ limit: options?.limit,
126225
+ hasCursor: !!options?.cursor
126226
+ });
125708
126227
  return this.provider.list(tenantId, prefix, context, options);
125709
126228
  }
125710
126229
  getMany(keys, context) {
125711
126230
  const tenantId = requireTenantId(context);
126231
+ for (const key of keys) {
126232
+ validateKey(key, context);
126233
+ }
126234
+ logger.debug("[StorageService] getMany operation", {
126235
+ ...context,
126236
+ operation: "StorageService.getMany",
126237
+ tenantId,
126238
+ keyCount: keys.length
126239
+ });
125712
126240
  return this.provider.getMany(tenantId, keys, context);
125713
126241
  }
125714
126242
  setMany(entries, context, options) {
125715
126243
  const tenantId = requireTenantId(context);
126244
+ validateStorageOptions(options, context);
126245
+ for (const key of entries.keys()) {
126246
+ validateKey(key, context);
126247
+ }
126248
+ logger.debug("[StorageService] setMany operation", {
126249
+ ...context,
126250
+ operation: "StorageService.setMany",
126251
+ tenantId,
126252
+ entryCount: entries.size,
126253
+ hasTTL: options?.ttl !== undefined,
126254
+ ttl: options?.ttl
126255
+ });
125716
126256
  return this.provider.setMany(tenantId, entries, context, options);
125717
126257
  }
125718
126258
  deleteMany(keys, context) {
125719
126259
  const tenantId = requireTenantId(context);
126260
+ for (const key of keys) {
126261
+ validateKey(key, context);
126262
+ }
126263
+ logger.debug("[StorageService] deleteMany operation", {
126264
+ ...context,
126265
+ operation: "StorageService.deleteMany",
126266
+ tenantId,
126267
+ keyCount: keys.length
126268
+ });
125720
126269
  return this.provider.deleteMany(tenantId, keys, context);
125721
126270
  }
125722
126271
  clear(context) {
125723
126272
  const tenantId = requireTenantId(context);
126273
+ logger.info("[StorageService] clear operation", {
126274
+ ...context,
126275
+ operation: "StorageService.clear",
126276
+ tenantId
126277
+ });
125724
126278
  return this.provider.clear(tenantId, context);
125725
126279
  }
125726
126280
  }
@@ -125779,9 +126333,12 @@ class FileSystemProvider {
125779
126333
  return filePath;
125780
126334
  }
125781
126335
  buildEnvelope(value, options) {
125782
- const expiresAt = options?.ttl ? Date.now() + options.ttl * 1000 : undefined;
126336
+ const expiresAt = options?.ttl !== undefined ? Date.now() + options.ttl * 1000 : undefined;
125783
126337
  return {
125784
- __mcp: { v: FILE_ENVELOPE_VERSION, ...expiresAt ? { expiresAt } : {} },
126338
+ __mcp: {
126339
+ v: FILE_ENVELOPE_VERSION,
126340
+ ...expiresAt !== undefined ? { expiresAt } : {}
126341
+ },
125785
126342
  value
125786
126343
  };
125787
126344
  }
@@ -125889,13 +126446,14 @@ class FileSystemProvider {
125889
126446
  const limit2 = options?.limit ?? DEFAULT_LIST_LIMIT;
125890
126447
  let startIndex = 0;
125891
126448
  if (options?.cursor) {
125892
- const cursorIndex = validKeys.indexOf(options.cursor);
126449
+ const lastKey = decodeCursor(options.cursor, tenantId, context);
126450
+ const cursorIndex = validKeys.indexOf(lastKey);
125893
126451
  if (cursorIndex !== -1) {
125894
126452
  startIndex = cursorIndex + 1;
125895
126453
  }
125896
126454
  }
125897
126455
  const paginatedKeys = validKeys.slice(startIndex, startIndex + limit2);
125898
- const nextCursor = startIndex + limit2 < validKeys.length ? paginatedKeys[paginatedKeys.length - 1] : undefined;
126456
+ const nextCursor = startIndex + limit2 < validKeys.length && paginatedKeys.length > 0 ? encodeCursor(paginatedKeys[paginatedKeys.length - 1], tenantId) : undefined;
125899
126457
  return {
125900
126458
  keys: paginatedKeys,
125901
126459
  nextCursor
@@ -125908,13 +126466,18 @@ class FileSystemProvider {
125908
126466
  }
125909
126467
  async getMany(tenantId, keys, context) {
125910
126468
  return ErrorHandler.tryCatch(async () => {
126469
+ if (keys.length === 0) {
126470
+ return new Map;
126471
+ }
126472
+ const promises = keys.map((key) => this.get(tenantId, key, context));
126473
+ const values2 = await Promise.all(promises);
125911
126474
  const results = new Map;
125912
- for (const key of keys) {
125913
- const value = await this.get(tenantId, key, context);
126475
+ keys.forEach((key, i) => {
126476
+ const value = values2[i];
125914
126477
  if (value !== null) {
125915
126478
  results.set(key, value);
125916
126479
  }
125917
- }
126480
+ });
125918
126481
  return results;
125919
126482
  }, {
125920
126483
  operation: "FileSystemProvider.getMany",
@@ -125924,9 +126487,11 @@ class FileSystemProvider {
125924
126487
  }
125925
126488
  async setMany(tenantId, entries, context, options) {
125926
126489
  return ErrorHandler.tryCatch(async () => {
125927
- for (const [key, value] of entries.entries()) {
125928
- await this.set(tenantId, key, value, context, options);
126490
+ if (entries.size === 0) {
126491
+ return;
125929
126492
  }
126493
+ const promises = Array.from(entries.entries()).map(([key, value]) => this.set(tenantId, key, value, context, options));
126494
+ await Promise.all(promises);
125930
126495
  }, {
125931
126496
  operation: "FileSystemProvider.setMany",
125932
126497
  context,
@@ -125935,14 +126500,12 @@ class FileSystemProvider {
125935
126500
  }
125936
126501
  async deleteMany(tenantId, keys, context) {
125937
126502
  return ErrorHandler.tryCatch(async () => {
125938
- let deletedCount = 0;
125939
- for (const key of keys) {
125940
- const deleted = await this.delete(tenantId, key, context);
125941
- if (deleted) {
125942
- deletedCount++;
125943
- }
126503
+ if (keys.length === 0) {
126504
+ return 0;
125944
126505
  }
125945
- return deletedCount;
126506
+ const promises = keys.map((key) => this.delete(tenantId, key, context));
126507
+ const results = await Promise.all(promises);
126508
+ return results.filter((deleted) => deleted).length;
125946
126509
  }, {
125947
126510
  operation: "FileSystemProvider.deleteMany",
125948
126511
  context,
@@ -125999,10 +126562,10 @@ class InMemoryProvider {
125999
126562
  set(tenantId, key, value, context, options) {
126000
126563
  logger.debug(`[InMemoryProvider] Setting key: ${key} for tenant: ${tenantId}`, context);
126001
126564
  const tenantStore = this.getTenantStore(tenantId);
126002
- const expiresAt = options?.ttl ? Date.now() + options.ttl * 1000 : undefined;
126565
+ const expiresAt = options?.ttl !== undefined ? Date.now() + options.ttl * 1000 : undefined;
126003
126566
  tenantStore.set(key, {
126004
126567
  value,
126005
- ...expiresAt && { expiresAt }
126568
+ ...expiresAt !== undefined && { expiresAt }
126006
126569
  });
126007
126570
  return Promise.resolve();
126008
126571
  }
@@ -126029,44 +126592,54 @@ class InMemoryProvider {
126029
126592
  const limit2 = options?.limit ?? DEFAULT_LIST_LIMIT2;
126030
126593
  let startIndex = 0;
126031
126594
  if (options?.cursor) {
126032
- const cursorIndex = allKeys.indexOf(options.cursor);
126595
+ const lastKey = decodeCursor(options.cursor, tenantId, context);
126596
+ const cursorIndex = allKeys.indexOf(lastKey);
126033
126597
  if (cursorIndex !== -1) {
126034
126598
  startIndex = cursorIndex + 1;
126035
126599
  }
126036
126600
  }
126037
126601
  const paginatedKeys = allKeys.slice(startIndex, startIndex + limit2);
126038
- const nextCursor = startIndex + limit2 < allKeys.length ? paginatedKeys[paginatedKeys.length - 1] : undefined;
126602
+ const nextCursor = startIndex + limit2 < allKeys.length && paginatedKeys.length > 0 ? encodeCursor(paginatedKeys[paginatedKeys.length - 1], tenantId) : undefined;
126039
126603
  return Promise.resolve({
126040
126604
  keys: paginatedKeys,
126041
126605
  nextCursor
126042
126606
  });
126043
126607
  }
126044
126608
  async getMany(tenantId, keys, context) {
126609
+ if (keys.length === 0) {
126610
+ return new Map;
126611
+ }
126045
126612
  logger.debug(`[InMemoryProvider] Getting ${keys.length} keys for tenant: ${tenantId}`, context);
126613
+ const promises = keys.map((key) => this.get(tenantId, key, context));
126614
+ const values2 = await Promise.all(promises);
126046
126615
  const results = new Map;
126047
- for (const key of keys) {
126048
- const value = await this.get(tenantId, key, context);
126616
+ keys.forEach((key, i) => {
126617
+ const value = values2[i];
126049
126618
  if (value !== null) {
126050
126619
  results.set(key, value);
126051
126620
  }
126052
- }
126621
+ });
126622
+ logger.debug(`[InMemoryProvider] Retrieved ${results.size}/${keys.length} keys for tenant: ${tenantId}`, context);
126053
126623
  return results;
126054
126624
  }
126055
126625
  async setMany(tenantId, entries, context, options) {
126056
- logger.debug(`[InMemoryProvider] Setting ${entries.size} keys for tenant: ${tenantId}`, context);
126057
- for (const [key, value] of entries.entries()) {
126058
- await this.set(tenantId, key, value, context, options);
126626
+ if (entries.size === 0) {
126627
+ return;
126059
126628
  }
126629
+ logger.debug(`[InMemoryProvider] Setting ${entries.size} keys for tenant: ${tenantId}`, context);
126630
+ const promises = Array.from(entries.entries()).map(([key, value]) => this.set(tenantId, key, value, context, options));
126631
+ await Promise.all(promises);
126632
+ logger.debug(`[InMemoryProvider] Successfully set ${entries.size} keys for tenant: ${tenantId}`, context);
126060
126633
  }
126061
126634
  async deleteMany(tenantId, keys, context) {
126062
- logger.debug(`[InMemoryProvider] Deleting ${keys.length} keys for tenant: ${tenantId}`, context);
126063
- let deletedCount = 0;
126064
- for (const key of keys) {
126065
- const deleted = await this.delete(tenantId, key, context);
126066
- if (deleted) {
126067
- deletedCount++;
126068
- }
126635
+ if (keys.length === 0) {
126636
+ return 0;
126069
126637
  }
126638
+ logger.debug(`[InMemoryProvider] Deleting ${keys.length} keys for tenant: ${tenantId}`, context);
126639
+ const promises = keys.map((key) => this.delete(tenantId, key, context));
126640
+ const results = await Promise.all(promises);
126641
+ const deletedCount = results.filter((deleted) => deleted).length;
126642
+ logger.debug(`[InMemoryProvider] Deleted ${deletedCount}/${keys.length} keys for tenant: ${tenantId}`, context);
126070
126643
  return deletedCount;
126071
126644
  }
126072
126645
  clear(tenantId, context) {
@@ -126116,7 +126689,7 @@ class SupabaseProvider {
126116
126689
  }
126117
126690
  async set(tenantId, key, value, context, options) {
126118
126691
  return ErrorHandler.tryCatch(async () => {
126119
- const expires_at = options?.ttl ? new Date(Date.now() + options.ttl * 1000).toISOString() : null;
126692
+ const expires_at = options?.ttl !== undefined ? new Date(Date.now() + options.ttl * 1000).toISOString() : null;
126120
126693
  const { error: error2 } = await this.getClient().from(TABLE_NAME).upsert({
126121
126694
  tenant_id: tenantId,
126122
126695
  key,
@@ -126149,7 +126722,8 @@ class SupabaseProvider {
126149
126722
  const limit2 = options?.limit ?? DEFAULT_LIST_LIMIT3;
126150
126723
  let query = this.getClient().from(TABLE_NAME).select("key").eq("tenant_id", tenantId).like("key", `${prefix}%`).or(`expires_at.is.null,expires_at.gt.${now}`).order("key", { ascending: true }).limit(limit2 + 1);
126151
126724
  if (options?.cursor) {
126152
- query = query.gt("key", options.cursor);
126725
+ const lastKey = decodeCursor(options.cursor, tenantId, context);
126726
+ query = query.gt("key", lastKey);
126153
126727
  }
126154
126728
  const { data, error: error2 } = await query;
126155
126729
  if (error2)
@@ -126157,7 +126731,7 @@ class SupabaseProvider {
126157
126731
  const keys = data?.map((item) => item.key) ?? [];
126158
126732
  const hasMore = keys.length > limit2;
126159
126733
  const resultKeys = hasMore ? keys.slice(0, limit2) : keys;
126160
- const nextCursor = hasMore ? resultKeys[resultKeys.length - 1] : undefined;
126734
+ const nextCursor = hasMore && resultKeys.length > 0 ? encodeCursor(resultKeys[resultKeys.length - 1], tenantId) : undefined;
126161
126735
  return {
126162
126736
  keys: resultKeys,
126163
126737
  nextCursor
@@ -126196,7 +126770,7 @@ class SupabaseProvider {
126196
126770
  if (entries.size === 0) {
126197
126771
  return;
126198
126772
  }
126199
- const expires_at = options?.ttl ? new Date(Date.now() + options.ttl * 1000).toISOString() : null;
126773
+ const expires_at = options?.ttl !== undefined ? new Date(Date.now() + options.ttl * 1000).toISOString() : null;
126200
126774
  const rows = Array.from(entries.entries()).map(([key, value]) => ({
126201
126775
  tenant_id: tenantId,
126202
126776
  key,
@@ -126265,9 +126839,12 @@ class R2Provider {
126265
126839
  return `${tenantId}:${key}`;
126266
126840
  }
126267
126841
  buildEnvelope(value, options) {
126268
- const expiresAt = options?.ttl ? Date.now() + options.ttl * 1000 : undefined;
126842
+ const expiresAt = options?.ttl !== undefined ? Date.now() + options.ttl * 1000 : undefined;
126269
126843
  return {
126270
- __mcp: { v: R2_ENVELOPE_VERSION, ...expiresAt ? { expiresAt } : {} },
126844
+ __mcp: {
126845
+ v: R2_ENVELOPE_VERSION,
126846
+ ...expiresAt !== undefined ? { expiresAt } : {}
126847
+ },
126271
126848
  value
126272
126849
  };
126273
126850
  }
@@ -126377,6 +126954,9 @@ class R2Provider {
126377
126954
  }
126378
126955
  async getMany(tenantId, keys, context) {
126379
126956
  return ErrorHandler.tryCatch(async () => {
126957
+ if (keys.length === 0) {
126958
+ return new Map;
126959
+ }
126380
126960
  const results = new Map;
126381
126961
  for (const key of keys) {
126382
126962
  const value = await this.get(tenantId, key, context);
@@ -126393,6 +126973,9 @@ class R2Provider {
126393
126973
  }
126394
126974
  async setMany(tenantId, entries, context, options) {
126395
126975
  return ErrorHandler.tryCatch(async () => {
126976
+ if (entries.size === 0) {
126977
+ return;
126978
+ }
126396
126979
  const promises = Array.from(entries.entries()).map(([key, value]) => this.set(tenantId, key, value, context, options));
126397
126980
  await Promise.all(promises);
126398
126981
  }, {
@@ -126403,6 +126986,9 @@ class R2Provider {
126403
126986
  }
126404
126987
  async deleteMany(tenantId, keys, context) {
126405
126988
  return ErrorHandler.tryCatch(async () => {
126989
+ if (keys.length === 0) {
126990
+ return 0;
126991
+ }
126406
126992
  const promises = keys.map((key) => this.delete(tenantId, key, context));
126407
126993
  const results = await Promise.all(promises);
126408
126994
  return results.filter((deleted) => deleted).length;
@@ -126482,7 +127068,7 @@ class KvProvider {
126482
127068
  });
126483
127069
  const valueToStore = JSON.stringify(value);
126484
127070
  const putOptions = {};
126485
- if (options?.ttl) {
127071
+ if (options?.ttl !== undefined) {
126486
127072
  putOptions.expirationTtl = options.ttl;
126487
127073
  }
126488
127074
  await this.kv.put(kvKey, valueToStore, putOptions);
@@ -126543,6 +127129,9 @@ class KvProvider {
126543
127129
  }
126544
127130
  async getMany(tenantId, keys, context) {
126545
127131
  return ErrorHandler.tryCatch(async () => {
127132
+ if (keys.length === 0) {
127133
+ return new Map;
127134
+ }
126546
127135
  const results = new Map;
126547
127136
  for (const key of keys) {
126548
127137
  const value = await this.get(tenantId, key, context);
@@ -126559,6 +127148,9 @@ class KvProvider {
126559
127148
  }
126560
127149
  async setMany(tenantId, entries, context, options) {
126561
127150
  return ErrorHandler.tryCatch(async () => {
127151
+ if (entries.size === 0) {
127152
+ return;
127153
+ }
126562
127154
  const promises = Array.from(entries.entries()).map(([key, value]) => this.set(tenantId, key, value, context, options));
126563
127155
  await Promise.all(promises);
126564
127156
  }, {
@@ -126569,6 +127161,9 @@ class KvProvider {
126569
127161
  }
126570
127162
  async deleteMany(tenantId, keys, context) {
126571
127163
  return ErrorHandler.tryCatch(async () => {
127164
+ if (keys.length === 0) {
127165
+ return 0;
127166
+ }
126572
127167
  const promises = keys.map((key) => this.delete(tenantId, key, context));
126573
127168
  const results = await Promise.all(promises);
126574
127169
  return results.filter((deleted) => deleted).length;
@@ -130518,21 +131113,6 @@ var echoTool = {
130518
131113
  responseFormatter: responseFormatter3
130519
131114
  };
130520
131115
 
130521
- // src/utils/internal/encoding.ts
130522
- function arrayBufferToBase64(buffer) {
130523
- if (runtimeCaps.hasBuffer) {
130524
- return Buffer.from(buffer).toString("base64");
130525
- } else {
130526
- let binary = "";
130527
- const bytes = new Uint8Array(buffer);
130528
- const len = bytes.byteLength;
130529
- for (let i = 0;i < len; i++) {
130530
- binary += String.fromCharCode(bytes[i]);
130531
- }
130532
- return btoa(binary);
130533
- }
130534
- }
130535
-
130536
131116
  // src/mcp-server/tools/definitions/template-image-test.tool.ts
130537
131117
  var TOOL_NAME4 = "template_image_test";
130538
131118
  var TOOL_TITLE4 = "Template Image Test";
@@ -133589,7 +134169,6 @@ var cors = (options) => {
133589
134169
 
133590
134170
  // src/mcp-server/transports/http/httpTransport.ts
133591
134171
  import http from "http";
133592
- import { randomUUID } from "node:crypto";
133593
134172
  // src/mcp-server/transports/auth/authFactory.ts
133594
134173
  var import_tsyringe16 = __toESM(require_cjs3(), 1);
133595
134174
 
@@ -135373,6 +135952,7 @@ function createAuthStrategy() {
135373
135952
  }
135374
135953
  }
135375
135954
  // src/mcp-server/transports/auth/authMiddleware.ts
135955
+ var import_api6 = __toESM(require_src(), 1);
135376
135956
  function createAuthMiddleware(strategy) {
135377
135957
  return async function authMiddleware(c, next) {
135378
135958
  const context = requestContextService.createRequestContext({
@@ -135404,6 +135984,20 @@ function createAuthMiddleware(strategy) {
135404
135984
  scopes: authInfo.scopes
135405
135985
  };
135406
135986
  logger.info("Authentication successful. Auth context populated.", authLogContext);
135987
+ const activeSpan = import_api6.trace.getActiveSpan();
135988
+ if (activeSpan) {
135989
+ activeSpan.setAttributes({
135990
+ "auth.client_id": authInfo.clientId,
135991
+ "auth.tenant_id": authInfo.tenantId ?? "none",
135992
+ "auth.scopes": authInfo.scopes.join(","),
135993
+ "auth.subject": authInfo.subject ?? "unknown",
135994
+ "auth.method": "bearer"
135995
+ });
135996
+ logger.debug("Added auth context to OpenTelemetry span", {
135997
+ ...authLogContext,
135998
+ spanId: activeSpan.spanContext().spanId
135999
+ });
136000
+ }
135407
136001
  await authContext.run({ authInfo }, next);
135408
136002
  } catch (error2) {
135409
136003
  logger.warning("Authentication verification failed.", {
@@ -135516,6 +136110,22 @@ var httpErrorHandler = async (err, c) => {
135516
136110
  return c.json(errorResponse);
135517
136111
  };
135518
136112
 
136113
+ // src/mcp-server/transports/http/sessionIdUtils.ts
136114
+ import { randomBytes as randomBytes2 } from "crypto";
136115
+ function generateSecureSessionId() {
136116
+ if (runtimeCaps.isNode && runtimeCaps.hasBuffer) {
136117
+ const bytes = randomBytes2(32);
136118
+ return bytes.toString("hex");
136119
+ } else {
136120
+ const bytes = new Uint8Array(32);
136121
+ crypto.getRandomValues(bytes);
136122
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
136123
+ }
136124
+ }
136125
+ function validateSessionIdFormat(sessionId) {
136126
+ return /^[a-f0-9]{64}$/.test(sessionId);
136127
+ }
136128
+
135519
136129
  // src/mcp-server/transports/http/sessionStore.ts
135520
136130
  class SessionStore {
135521
136131
  sessions = new Map;
@@ -135525,6 +136135,14 @@ class SessionStore {
135525
136135
  setInterval(() => this.cleanupStaleSessions(), 60000);
135526
136136
  }
135527
136137
  getOrCreate(sessionId, identity) {
136138
+ if (!validateSessionIdFormat(sessionId)) {
136139
+ const context = requestContextService.createRequestContext({
136140
+ operation: "SessionStore.getOrCreate",
136141
+ sessionIdPrefix: sessionId.substring(0, 16)
136142
+ });
136143
+ logger.warning("Invalid session ID format rejected", context);
136144
+ throw new McpError(-32602 /* InvalidParams */, "Invalid session ID format. Session IDs must be 64 hexadecimal characters.", context);
136145
+ }
135528
136146
  let session = this.sessions.get(sessionId);
135529
136147
  if (!session) {
135530
136148
  const newSession = {
@@ -135697,15 +136315,26 @@ function createHttpApp(mcpServer, parentContext) {
135697
136315
  app.get("/healthz", (c) => c.json({ status: "ok" }));
135698
136316
  app.get("/.well-known/oauth-protected-resource", (c) => {
135699
136317
  if (!config.oauthIssuerUrl) {
136318
+ logger.debug("OAuth Protected Resource Metadata requested but OAuth not configured", transportContext);
135700
136319
  return c.json({ error: "OAuth not configured on this server" }, { status: 404 });
135701
136320
  }
135702
- return c.json({
135703
- resource: config.mcpServerResourceIdentifier || config.oauthAudience,
136321
+ const origin = new URL(c.req.url).origin;
136322
+ const resourceIdentifier = config.mcpServerResourceIdentifier ?? config.oauthAudience ?? `${origin}/mcp`;
136323
+ const metadata = {
136324
+ resource: resourceIdentifier,
135704
136325
  authorization_servers: [config.oauthIssuerUrl],
135705
136326
  bearer_methods_supported: ["header"],
135706
136327
  resource_signing_alg_values_supported: ["RS256", "ES256", "PS256"],
136328
+ resource_documentation: `${origin}/docs`,
135707
136329
  ...config.oauthJwksUri && { jwks_uri: config.oauthJwksUri }
136330
+ };
136331
+ c.header("Cache-Control", "public, max-age=3600");
136332
+ c.header("Content-Type", "application/json");
136333
+ logger.debug("Serving OAuth Protected Resource Metadata", {
136334
+ ...transportContext,
136335
+ resourceIdentifier
135708
136336
  });
136337
+ return c.json(metadata);
135709
136338
  });
135710
136339
  app.get(config.mcpHttpEndpointPath, (c) => {
135711
136340
  return c.json({
@@ -135770,7 +136399,7 @@ function createHttpApp(mcpServer, parentContext) {
135770
136399
  }, 400);
135771
136400
  }
135772
136401
  const providedSessionId = c.req.header("mcp-session-id");
135773
- const sessionId = providedSessionId ?? randomUUID();
136402
+ const sessionId = providedSessionId ?? generateSecureSessionId();
135774
136403
  const authStore = authContext.getStore();
135775
136404
  let sessionIdentity;
135776
136405
  if (authStore?.authInfo) {
@@ -136173,6 +136802,11 @@ var start = async () => {
136173
136802
  await shutdownOpenTelemetry();
136174
136803
  process.exit(1);
136175
136804
  }
136805
+ try {
136806
+ await initializeOpenTelemetry();
136807
+ } catch (error2) {
136808
+ console.error("[Startup] Failed to initialize OpenTelemetry:", error2);
136809
+ }
136176
136810
  await initializePerformance_Hrt();
136177
136811
  const validMcpLogLevels = [
136178
136812
  "debug",