@frontmcp/sdk 1.8.6 → 1.8.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.
Files changed (75) hide show
  1. package/agent/flows/call-agent.flow.d.ts.map +1 -1
  2. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  3. package/common/dynamic/dynamic.adapter.d.ts +1 -1
  4. package/common/dynamic/dynamic.adapter.d.ts.map +1 -1
  5. package/common/dynamic/dynamic.plugin.d.ts +7 -1
  6. package/common/dynamic/dynamic.plugin.d.ts.map +1 -1
  7. package/common/dynamic/dynamic.utils.d.ts +10 -1
  8. package/common/dynamic/dynamic.utils.d.ts.map +1 -1
  9. package/common/interfaces/execution-context.interface.d.ts +14 -0
  10. package/common/interfaces/execution-context.interface.d.ts.map +1 -1
  11. package/common/metadata/front-mcp.metadata.d.ts +127 -19
  12. package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
  13. package/common/types/options/http/index.d.ts +1 -1
  14. package/common/types/options/http/index.d.ts.map +1 -1
  15. package/common/types/options/http/interfaces.d.ts +36 -1
  16. package/common/types/options/http/interfaces.d.ts.map +1 -1
  17. package/common/types/options/http/schema.d.ts +12 -0
  18. package/common/types/options/http/schema.d.ts.map +1 -1
  19. package/common/types/options/transport/schema.d.ts +2 -2
  20. package/context/frontmcp-context-storage.d.ts.map +1 -1
  21. package/context/frontmcp-context.d.ts +11 -0
  22. package/context/frontmcp-context.d.ts.map +1 -1
  23. package/elicitation/store/elicitation-store.factory.d.ts.map +1 -1
  24. package/errors/misconfiguration.d.ts +30 -0
  25. package/errors/misconfiguration.d.ts.map +1 -0
  26. package/esm/index.mjs +912 -198
  27. package/flows/flow.instance.d.ts.map +1 -1
  28. package/front-mcp/front-mcp.d.ts.map +1 -1
  29. package/ha/ha-headers.d.ts +16 -0
  30. package/ha/ha-headers.d.ts.map +1 -0
  31. package/health/health.probes.d.ts +1 -1
  32. package/health/health.probes.d.ts.map +1 -1
  33. package/index.js +1016 -298
  34. package/job/store/job-definition-store.factory.d.ts.map +1 -1
  35. package/job/store/job-state-store.factory.d.ts.map +1 -1
  36. package/notification/notification.service.d.ts +4 -1
  37. package/notification/notification.service.d.ts.map +1 -1
  38. package/package.json +10 -10
  39. package/remote-mcp/mcp-stateless-client.adapter.d.ts +9 -1
  40. package/remote-mcp/mcp-stateless-client.adapter.d.ts.map +1 -1
  41. package/remote-mcp/resilience/health-check.d.ts.map +1 -1
  42. package/scope/scope.instance.d.ts +8 -0
  43. package/scope/scope.instance.d.ts.map +1 -1
  44. package/server/adapters/express.host.adapter.d.ts +6 -1
  45. package/server/adapters/express.host.adapter.d.ts.map +1 -1
  46. package/server/middleware/csp.middleware.d.ts +12 -1
  47. package/server/middleware/csp.middleware.d.ts.map +1 -1
  48. package/server/security/security-audit.d.ts.map +1 -1
  49. package/server/server.instance.d.ts.map +1 -1
  50. package/skill/cache/skill-http-cache.factory.d.ts +0 -5
  51. package/skill/cache/skill-http-cache.factory.d.ts.map +1 -1
  52. package/task/store/index.d.ts +1 -1
  53. package/task/store/index.d.ts.map +1 -1
  54. package/task/store/task-store.factory.d.ts +6 -0
  55. package/task/store/task-store.factory.d.ts.map +1 -1
  56. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  57. package/tool/ui/ui-option-warnings.d.ts +3 -0
  58. package/tool/ui/ui-option-warnings.d.ts.map +1 -0
  59. package/tool/ui/ui-resource.handler.d.ts.map +1 -1
  60. package/tool/ui/ui-shared.d.ts +14 -0
  61. package/tool/ui/ui-shared.d.ts.map +1 -1
  62. package/transport/event-stores/redis.event-store.d.ts.map +1 -1
  63. package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
  64. package/transport/flows/handle.sse.flow.d.ts.map +1 -1
  65. package/transport/flows/handle.stateless-http.flow.d.ts +2 -1
  66. package/transport/flows/handle.stateless-http.flow.d.ts.map +1 -1
  67. package/transport/flows/handle.streamable-http.flow.d.ts +2 -1
  68. package/transport/flows/handle.streamable-http.flow.d.ts.map +1 -1
  69. package/transport/ipc-server.transport.d.ts +26 -0
  70. package/transport/ipc-server.transport.d.ts.map +1 -0
  71. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts +2 -0
  72. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts.map +1 -1
  73. package/transport/transport.registry.d.ts +34 -0
  74. package/transport/transport.registry.d.ts.map +1 -1
  75. package/transport/web-fetch-handler.d.ts.map +1 -1
package/index.js CHANGED
@@ -1859,7 +1859,7 @@ var init_session = __esm({
1859
1859
  });
1860
1860
 
1861
1861
  // libs/sdk/src/common/types/options/http/schema.ts
1862
- var import_lazy_zod8, HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, httpOptionsSchema;
1862
+ var import_lazy_zod8, HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, securityHeaderValueSchema, securityHeadersSchema, httpOptionsSchema;
1863
1863
  var init_schema3 = __esm({
1864
1864
  "libs/sdk/src/common/types/options/http/schema.ts"() {
1865
1865
  "use strict";
@@ -1897,6 +1897,19 @@ var init_schema3 = __esm({
1897
1897
  credentials: import_lazy_zod8.z.boolean().optional(),
1898
1898
  maxAge: import_lazy_zod8.z.number().optional()
1899
1899
  });
1900
+ securityHeaderValueSchema = import_lazy_zod8.z.union([import_lazy_zod8.z.string(), import_lazy_zod8.z.literal(false)]);
1901
+ securityHeadersSchema = import_lazy_zod8.z.object({
1902
+ hsts: securityHeaderValueSchema.optional(),
1903
+ contentTypeOptions: securityHeaderValueSchema.optional(),
1904
+ frameOptions: securityHeaderValueSchema.optional(),
1905
+ custom: import_lazy_zod8.z.record(import_lazy_zod8.z.string(), import_lazy_zod8.z.string()).optional(),
1906
+ csp: import_lazy_zod8.z.object({
1907
+ enabled: import_lazy_zod8.z.boolean().optional(),
1908
+ directives: import_lazy_zod8.z.record(import_lazy_zod8.z.string(), import_lazy_zod8.z.union([import_lazy_zod8.z.string(), import_lazy_zod8.z.array(import_lazy_zod8.z.string())])).optional(),
1909
+ reportUri: import_lazy_zod8.z.string().optional(),
1910
+ reportOnly: import_lazy_zod8.z.boolean().optional()
1911
+ }).optional()
1912
+ });
1900
1913
  httpOptionsSchema = import_lazy_zod8.z.object({
1901
1914
  port: import_lazy_zod8.z.number().optional().default(Number(process.env["PORT"]) || 3e3),
1902
1915
  // `FRONTMCP_HTTP_ENTRY_PATH` lets `frontmcp dev` propagate the configured
@@ -1955,7 +1968,9 @@ var init_schema3 = __esm({
1955
1968
  * app as the MCP endpoint; public by default, `auth: true` opts into the MCP
1956
1969
  * `session:verify` flow. Reserved-path collisions are rejected at startup.
1957
1970
  */
1958
- routes: import_lazy_zod8.z.array(httpRouteSchema).optional()
1971
+ routes: import_lazy_zod8.z.array(httpRouteSchema).optional(),
1972
+ /** Response security headers; see `SecurityHeadersOptions`. */
1973
+ securityHeaders: securityHeadersSchema.optional()
1959
1974
  });
1960
1975
  }
1961
1976
  });
@@ -2357,7 +2372,7 @@ var init_schema7 = __esm({
2357
2372
  persistenceConfigSchema = import_lazy_zod12.z.object({
2358
2373
  redis: redisOptionsSchema.optional(),
2359
2374
  sqlite: sqliteOptionsSchema.optional(),
2360
- defaultTtlMs: import_lazy_zod12.z.number().int().positive().default(36e5)
2375
+ defaultTtlMs: import_lazy_zod12.z.number().int().positive().optional()
2361
2376
  }).refine((cfg) => !(cfg.redis && cfg.sqlite), {
2362
2377
  message: "Invalid persistence config: choose either redis or sqlite, not both",
2363
2378
  path: ["sqlite"]
@@ -6442,6 +6457,7 @@ var require_dist = __commonJS({
6442
6457
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
6443
6458
  var index_exports2 = {};
6444
6459
  __export2(index_exports2, {
6460
+ SqliteDecryptionError: () => SqliteDecryptionError,
6445
6461
  SqliteElicitationStore: () => SqliteElicitationStore,
6446
6462
  SqliteEventStore: () => SqliteEventStore,
6447
6463
  SqliteKvStore: () => SqliteKvStore,
@@ -6453,34 +6469,47 @@ var require_dist = __commonJS({
6453
6469
  encryptValue: () => encryptValue,
6454
6470
  loadBetterSqlite3: () => loadBetterSqlite3,
6455
6471
  openDatabase: () => openDatabase,
6456
- sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6472
+ sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema,
6473
+ withBusyRetry: () => withBusyRetry
6457
6474
  });
6458
6475
  module2.exports = __toCommonJS2(index_exports2);
6459
- var import_utils114 = require("@frontmcp/utils");
6476
+ var import_utils119 = require("@frontmcp/utils");
6477
+ var SqliteDecryptionError = class extends Error {
6478
+ code = "SQLITE_DECRYPTION_FAILED";
6479
+ constructor() {
6480
+ super(
6481
+ "SqliteKvStore: failed to decrypt a stored value. The encryption secret differs from the one the database was written with (or the value is corrupted). Restore the original secret, or delete the database file to start empty."
6482
+ );
6483
+ this.name = "SqliteDecryptionError";
6484
+ }
6485
+ };
6460
6486
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6461
6487
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6462
6488
  var KEY_LENGTH = 32;
6463
6489
  function deriveEncryptionKey(secret) {
6464
6490
  const ikm = new TextEncoder().encode(secret);
6465
- return (0, import_utils114.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6491
+ return (0, import_utils119.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6466
6492
  }
6467
6493
  var SEPARATOR = ":";
6468
6494
  function encryptValue(key, plaintext) {
6469
- const iv = (0, import_utils114.randomBytes)(12);
6495
+ const iv = (0, import_utils119.randomBytes)(12);
6470
6496
  const plaintextBytes = new TextEncoder().encode(plaintext);
6471
- const { ciphertext, tag } = (0, import_utils114.encryptAesGcm)(key, plaintextBytes, iv);
6472
- return [(0, import_utils114.base64urlEncode)(iv), (0, import_utils114.base64urlEncode)(tag), (0, import_utils114.base64urlEncode)(ciphertext)].join(SEPARATOR);
6497
+ const { ciphertext, tag } = (0, import_utils119.encryptAesGcm)(key, plaintextBytes, iv);
6498
+ return [(0, import_utils119.base64urlEncode)(iv), (0, import_utils119.base64urlEncode)(tag), (0, import_utils119.base64urlEncode)(ciphertext)].join(SEPARATOR);
6473
6499
  }
6474
6500
  function decryptValue(key, encrypted) {
6475
6501
  const parts = encrypted.split(SEPARATOR);
6476
6502
  if (parts.length !== 3) {
6477
6503
  throw new Error("Invalid encrypted value format");
6478
6504
  }
6479
- const iv = (0, import_utils114.base64urlDecode)(parts[0]);
6480
- const tag = (0, import_utils114.base64urlDecode)(parts[1]);
6481
- const ciphertext = (0, import_utils114.base64urlDecode)(parts[2]);
6482
- const plaintext = (0, import_utils114.decryptAesGcm)(key, ciphertext, iv, tag);
6483
- return new TextDecoder().decode(plaintext);
6505
+ const iv = (0, import_utils119.base64urlDecode)(parts[0]);
6506
+ const tag = (0, import_utils119.base64urlDecode)(parts[1]);
6507
+ const ciphertext = (0, import_utils119.base64urlDecode)(parts[2]);
6508
+ try {
6509
+ return new TextDecoder().decode((0, import_utils119.decryptAesGcm)(key, ciphertext, iv, tag));
6510
+ } catch {
6511
+ throw new SqliteDecryptionError();
6512
+ }
6484
6513
  }
6485
6514
  var import_utils210 = require("@frontmcp/utils");
6486
6515
  var cached = null;
@@ -6493,7 +6522,27 @@ var require_dist = __commonJS({
6493
6522
  return mod;
6494
6523
  }
6495
6524
  var MEMORY_PATH = ":memory:";
6496
- function openDatabase(path3, storeName) {
6525
+ var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
6526
+ function isBusyError(err) {
6527
+ const code = err?.code;
6528
+ return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || code === "SQLITE_BUSY_SNAPSHOT";
6529
+ }
6530
+ function sleepSync(ms) {
6531
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
6532
+ }
6533
+ function withBusyRetry(operation, options = {}) {
6534
+ const attempts = options.attempts ?? 5;
6535
+ const delayMs = options.delayMs ?? 50;
6536
+ for (let attempt = 1; ; attempt++) {
6537
+ try {
6538
+ return operation();
6539
+ } catch (err) {
6540
+ if (!isBusyError(err) || attempt >= attempts) throw err;
6541
+ sleepSync(delayMs * attempt);
6542
+ }
6543
+ }
6544
+ }
6545
+ function openDatabase(path3, storeName, options = {}) {
6497
6546
  const BetterSqlite3 = loadBetterSqlite3();
6498
6547
  if (path3 !== MEMORY_PATH && path3 !== "") {
6499
6548
  const dir = (0, import_utils210.dirname)(path3);
@@ -6502,16 +6551,28 @@ var require_dist = __commonJS({
6502
6551
  (0, import_utils210.ensureDirSync)(dir);
6503
6552
  } catch (err) {
6504
6553
  const message = err instanceof Error ? err.message : String(err);
6505
- throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}`);
6554
+ throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}`, {
6555
+ cause: err
6556
+ });
6506
6557
  }
6507
6558
  }
6508
6559
  }
6560
+ let db;
6561
+ try {
6562
+ db = new BetterSqlite3(path3);
6563
+ } catch (err) {
6564
+ const message = err instanceof Error ? err.message : String(err);
6565
+ throw new Error(`${storeName}: failed to open database at "${path3}": ${message}`, { cause: err });
6566
+ }
6509
6567
  try {
6510
- return new BetterSqlite3(path3);
6568
+ db.pragma(`busy_timeout = ${Math.max(0, Math.floor(options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS))}`);
6569
+ if (options.walMode) withBusyRetry(() => db.pragma("journal_mode = WAL"));
6511
6570
  } catch (err) {
6571
+ db.close();
6512
6572
  const message = err instanceof Error ? err.message : String(err);
6513
- throw new Error(`${storeName}: failed to open database at "${path3}": ${message}`);
6573
+ throw new Error(`${storeName}: failed to configure database at "${path3}": ${message}`, { cause: err });
6514
6574
  }
6575
+ return db;
6515
6576
  }
6516
6577
  var SqliteKvStore = class {
6517
6578
  db;
@@ -6519,14 +6580,14 @@ var require_dist = __commonJS({
6519
6580
  cleanupTimer = null;
6520
6581
  stmts = null;
6521
6582
  constructor(options) {
6522
- this.db = openDatabase(options.path, "SqliteKvStore");
6523
- if (options.walMode !== false) {
6524
- this.db.pragma("journal_mode = WAL");
6525
- }
6583
+ this.db = openDatabase(options.path, "SqliteKvStore", {
6584
+ busyTimeoutMs: options.busyTimeoutMs,
6585
+ walMode: options.walMode !== false
6586
+ });
6526
6587
  if (options.encryption?.secret) {
6527
6588
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
6528
6589
  }
6529
- this.initSchema();
6590
+ withBusyRetry(() => this.initSchema());
6530
6591
  this.prepareStatements();
6531
6592
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
6532
6593
  if (cleanupInterval > 0) {
@@ -6985,17 +7046,17 @@ var require_dist = __commonJS({
6985
7046
  encryptionKey = null;
6986
7047
  stmts = null;
6987
7048
  constructor(options) {
6988
- this.db = openDatabase(options.path, "SqliteTaskStore");
6989
- if (options.walMode !== false) {
6990
- this.db.pragma("journal_mode = WAL");
6991
- }
7049
+ this.db = openDatabase(options.path, "SqliteTaskStore", {
7050
+ busyTimeoutMs: options.busyTimeoutMs,
7051
+ walMode: options.walMode !== false
7052
+ });
6992
7053
  this.logger = options.logger;
6993
7054
  this.liveness = options.livenessProbe ?? defaultLivenessProbe;
6994
7055
  if (options.encryption?.secret) {
6995
7056
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
6996
7057
  }
6997
7058
  this.emitter.setMaxListeners(200);
6998
- this.initSchema();
7059
+ withBusyRetry(() => this.initSchema());
6999
7060
  this.prepareStatements();
7000
7061
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
7001
7062
  if (cleanupInterval > 0) {
@@ -7232,13 +7293,13 @@ var require_dist = __commonJS({
7232
7293
  cleanupTimer = null;
7233
7294
  stmts = null;
7234
7295
  constructor(options) {
7235
- this.db = openDatabase(options.path, "SqliteEventStore");
7236
- if (options.walMode !== false) {
7237
- this.db.pragma("journal_mode = WAL");
7238
- }
7296
+ this.db = openDatabase(options.path, "SqliteEventStore", {
7297
+ busyTimeoutMs: options.busyTimeoutMs,
7298
+ walMode: options.walMode !== false
7299
+ });
7239
7300
  this.maxEvents = options.maxEvents ?? 1e4;
7240
7301
  this.ttlMs = options.ttlMs ?? 3e5;
7241
- this.initSchema();
7302
+ withBusyRetry(() => this.initSchema());
7242
7303
  this.prepareStatements();
7243
7304
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
7244
7305
  if (cleanupInterval > 0) {
@@ -7373,7 +7434,8 @@ var require_dist = __commonJS({
7373
7434
  secret: import_lazy_zod113.z.string().min(1)
7374
7435
  }).optional(),
7375
7436
  ttlCleanupIntervalMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(6e4),
7376
- walMode: import_lazy_zod113.z.boolean().optional().default(true)
7437
+ walMode: import_lazy_zod113.z.boolean().optional().default(true),
7438
+ busyTimeoutMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(5e3)
7377
7439
  });
7378
7440
  }
7379
7441
  });
@@ -7391,6 +7453,14 @@ function detectStorageType(storage, configuredType) {
7391
7453
  if ((0, import_utils15.getEnv)("REDIS_URL") || (0, import_utils15.getEnv)("REDIS_HOST")) return "redis";
7392
7454
  return "memory";
7393
7455
  }
7456
+ function isExplicitStorage(storage) {
7457
+ return Boolean(storage?.type && storage.type !== "auto");
7458
+ }
7459
+ function resolvesToVercelKvTaskBackend(redis, storage) {
7460
+ if (isExplicitStorage(storage)) return storage.type === "vercel-kv";
7461
+ if (redis) return "provider" in redis && redis.provider === "vercel-kv";
7462
+ return Boolean((0, import_utils15.getEnv)("KV_REST_API_URL"));
7463
+ }
7394
7464
  async function createTaskStore(options = {}) {
7395
7465
  const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
7396
7466
  if (isEdgeRuntime3 && sqlite) {
@@ -7415,7 +7485,7 @@ async function createTaskStore(options = {}) {
7415
7485
  return { store: store2, type: "sqlite" };
7416
7486
  }
7417
7487
  let finalStorageConfig = storageConfig;
7418
- if (redis && !storageConfig) {
7488
+ if (redis && !isExplicitStorage(storageConfig)) {
7419
7489
  if ("provider" in redis && redis.provider === "vercel-kv") {
7420
7490
  throw new TaskStoreNotSupportedError(
7421
7491
  "Vercel KV is not supported for task stores. Task result blocking and cancel signalling require pub/sub. Use Redis or Upstash instead."
@@ -7443,7 +7513,8 @@ async function createTaskStore(options = {}) {
7443
7513
  };
7444
7514
  }
7445
7515
  }
7446
- if (finalStorageConfig?.type === "vercel-kv" || (0, import_utils15.getEnv)("KV_REST_API_URL")) {
7516
+ const explicitBackend = isExplicitStorage(storageConfig) || Boolean(redis);
7517
+ if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && (0, import_utils15.getEnv)("KV_REST_API_URL")) {
7447
7518
  throw new TaskStoreNotSupportedError(
7448
7519
  "Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
7449
7520
  );
@@ -8684,11 +8755,8 @@ var init_get_prompt_request_handler = __esm({
8684
8755
 
8685
8756
  // libs/sdk/src/notification/notification.service.ts
8686
8757
  function hasMcpAppsExtension(capabilities) {
8687
- if (!capabilities?.experimental) {
8688
- return false;
8689
- }
8690
- const uiExtension = capabilities.experimental[MCP_APPS_EXTENSION_KEY];
8691
- return uiExtension !== void 0 && uiExtension !== null;
8758
+ const declared = capabilities?.experimental?.[MCP_APPS_EXTENSION_KEY] ?? capabilities?.extensions?.[MCP_APPS_EXTENSION_KEY];
8759
+ return declared !== void 0 && declared !== null;
8692
8760
  }
8693
8761
  function detectPlatformFromCapabilities(capabilities) {
8694
8762
  if (hasMcpAppsExtension(capabilities)) {
@@ -11290,6 +11358,8 @@ var init_ui_shared = __esm({
11290
11358
  widgets = /* @__PURE__ */ new Map();
11291
11359
  manifests = /* @__PURE__ */ new Map();
11292
11360
  resourceMeta = /* @__PURE__ */ new Map();
11361
+ uiTools = /* @__PURE__ */ new Set();
11362
+ customUris = /* @__PURE__ */ new Map();
11293
11363
  resolver;
11294
11364
  options;
11295
11365
  constructor(resolver, options = {}) {
@@ -11304,6 +11374,28 @@ var init_ui_shared = __esm({
11304
11374
  logger: this.options.logger
11305
11375
  };
11306
11376
  }
11377
+ /**
11378
+ * Record that `toolName` has a UI, and the custom `ui.resourceUri` it advertises (if any).
11379
+ * `tools/list` advertises that URI verbatim, so `resources/read` must resolve it back to the
11380
+ * tool; and only registered tools may be served a placeholder widget.
11381
+ */
11382
+ registerTool(toolName, resourceUri) {
11383
+ this.uiTools.add(toolName);
11384
+ if (resourceUri) this.customUris.set(resourceUri, toolName);
11385
+ }
11386
+ /** The tool that advertised `uri` as its custom `ui.resourceUri`. */
11387
+ resolveCustomUri(uri) {
11388
+ return this.customUris.get(uri);
11389
+ }
11390
+ /**
11391
+ * Whether `name` (or its app-qualified form `app:name`) is a tool with a UI.
11392
+ */
11393
+ hasTool(name44) {
11394
+ const known = (n) => this.uiTools.has(n) || this.widgets.has(n) || this.resourceMeta.has(n);
11395
+ if (known(name44)) return true;
11396
+ const sep = name44.indexOf(":");
11397
+ return sep > 0 && known(name44.slice(sep + 1));
11398
+ }
11307
11399
  getStaticWidget(name44) {
11308
11400
  return this.widgets.get(name44);
11309
11401
  }
@@ -11332,8 +11424,9 @@ var init_ui_shared = __esm({
11332
11424
  const output = options["output"] ?? {};
11333
11425
  const uiConfig = options["uiConfig"];
11334
11426
  const resourceMode = uiConfig?.["resourceMode"] ?? options["resourceMode"];
11335
- if (!toolName || !template) return;
11427
+ if (!toolName) return;
11336
11428
  this.updateResourceMetaFromConfig(toolName, uiConfig);
11429
+ if (!template) return;
11337
11430
  const result = (0, import_adapters.renderToolTemplate)({
11338
11431
  toolName,
11339
11432
  input,
@@ -11342,6 +11435,7 @@ var init_ui_shared = __esm({
11342
11435
  resolver: this.resolver,
11343
11436
  resourceMode,
11344
11437
  sizing: extractSizing(uiConfig),
11438
+ csp: uiConfig?.["csp"],
11345
11439
  ...this.stringResultOptions(uiConfig)
11346
11440
  });
11347
11441
  this.widgets.set(toolName, result.html);
@@ -11420,6 +11514,7 @@ var init_ui_shared = __esm({
11420
11514
  resolver: this.resolver,
11421
11515
  resourceMode,
11422
11516
  sizing: extractSizing(uiConfig),
11517
+ csp: uiConfig?.["csp"],
11423
11518
  ...this.stringResultOptions(uiConfig)
11424
11519
  });
11425
11520
  return { meta: result.meta };
@@ -11472,26 +11567,14 @@ function handleUIResourceRead(uri, registry, platformType) {
11472
11567
  return { handled: false };
11473
11568
  }
11474
11569
  const mimeType = getUIResourceMimeType(platformType);
11475
- const widgetParsed = parseWidgetUri(uri);
11570
+ const customTool = registry.resolveCustomUri(uri);
11571
+ const widgetParsed = customTool ? { toolName: customTool } : parseWidgetUri(uri);
11476
11572
  if (widgetParsed) {
11477
- const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11478
- const cachedWidget = registry.getStaticWidget(widgetParsed.toolName);
11479
- if (cachedWidget) {
11480
- return {
11481
- handled: true,
11482
- result: {
11483
- contents: [
11484
- {
11485
- uri,
11486
- mimeType,
11487
- text: cachedWidget,
11488
- ...resourceMeta ? { _meta: resourceMeta } : {}
11489
- }
11490
- ]
11491
- }
11492
- };
11573
+ if (!registry.hasTool(widgetParsed.toolName)) {
11574
+ return { handled: true, error: `No UI widget is registered for ${uri}` };
11493
11575
  }
11494
- const html = generatePlaceholderWidget(widgetParsed.toolName);
11576
+ const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11577
+ const html = registry.getStaticWidget(widgetParsed.toolName) ?? generatePlaceholderWidget(widgetParsed.toolName);
11495
11578
  return {
11496
11579
  handled: true,
11497
11580
  result: {
@@ -12234,19 +12317,34 @@ var init_direct_client = __esm({
12234
12317
  // Resource Operations (raw format)
12235
12318
  // ─────────────────────────────────────────────────────────────────────────────
12236
12319
  async listResources() {
12237
- return this.mcpClient.listResources();
12320
+ const resources = await listAllPages("resources/list", async (cursor) => {
12321
+ const result = await this.mcpClient.listResources(cursor ? { cursor } : void 0);
12322
+ return { items: result.resources, nextCursor: result.nextCursor };
12323
+ });
12324
+ return { resources };
12238
12325
  }
12239
12326
  async readResource(uri) {
12240
12327
  return this.mcpClient.readResource({ uri });
12241
12328
  }
12242
12329
  async listResourceTemplates() {
12243
- return this.mcpClient.listResourceTemplates();
12330
+ const resourceTemplates = await listAllPages(
12331
+ "resources/templates/list",
12332
+ async (cursor) => {
12333
+ const result = await this.mcpClient.listResourceTemplates(cursor ? { cursor } : void 0);
12334
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
12335
+ }
12336
+ );
12337
+ return { resourceTemplates };
12244
12338
  }
12245
12339
  // ─────────────────────────────────────────────────────────────────────────────
12246
12340
  // Prompt Operations (raw format)
12247
12341
  // ─────────────────────────────────────────────────────────────────────────────
12248
12342
  async listPrompts() {
12249
- return this.mcpClient.listPrompts();
12343
+ const prompts = await listAllPages("prompts/list", async (cursor) => {
12344
+ const result = await this.mcpClient.listPrompts(cursor ? { cursor } : void 0);
12345
+ return { items: result.prompts, nextCursor: result.nextCursor };
12346
+ });
12347
+ return { prompts };
12250
12348
  }
12251
12349
  async getPrompt(name44, args) {
12252
12350
  return this.mcpClient.getPrompt({
@@ -12974,8 +13072,8 @@ function createRemoteAppProbe(appId, provider) {
12974
13072
  name: `remote:${appId}`,
12975
13073
  async check() {
12976
13074
  const result = provider.getHealthStatus(appId);
12977
- if (!result) {
12978
- return { status: "unhealthy", error: "No health check result available" };
13075
+ if (!result || result.status === "unknown" && result.consecutiveFailures === 0) {
13076
+ return { status: "degraded", details: { state: "unknown" } };
12979
13077
  }
12980
13078
  return {
12981
13079
  status: result.status === "healthy" ? "healthy" : result.status === "degraded" ? "degraded" : "unhealthy",
@@ -14091,6 +14189,12 @@ var init_frontmcp_context = __esm({
14091
14189
  // =====================
14092
14190
  /** Request metadata (headers, user-agent, etc.) */
14093
14191
  metadata;
14192
+ /**
14193
+ * The hosting platform's bindings object for this request: a Cloudflare Worker's `env`
14194
+ * (KV namespaces, D1 databases, R2 buckets, Durable Objects, `[vars]`, secrets).
14195
+ * `undefined` on Node, where bindings do not exist.
14196
+ */
14197
+ platformEnv;
14094
14198
  // =====================
14095
14199
  // Auth (mutable, progressively populated)
14096
14200
  // =====================
@@ -14126,6 +14230,7 @@ var init_frontmcp_context = __esm({
14126
14230
  this.traceContext = args.traceContext ?? generateTraceContext();
14127
14231
  this.timestamp = args.timestamp ?? Date.now();
14128
14232
  this._authInfo = args.authInfo ?? {};
14233
+ this.platformEnv = args.platformEnv;
14129
14234
  this.config = {
14130
14235
  forwardCallerTokenTo: toOrigins(args.config?.forwardCallerTokenTo),
14131
14236
  forwardCustomHeadersTo: toOrigins(args.config?.forwardCustomHeadersTo),
@@ -14828,6 +14933,7 @@ var init_frontmcp_context_storage = __esm({
14828
14933
  import_utils39 = require("@frontmcp/utils");
14829
14934
  init_decorators();
14830
14935
  init_metadata();
14936
+ init_tokens();
14831
14937
  init_mcp_error();
14832
14938
  init_frontmcp_context();
14833
14939
  init_metadata_utils();
@@ -14895,7 +15001,12 @@ var init_frontmcp_context_storage = __esm({
14895
15001
  const headers = request.headers ?? {};
14896
15002
  const headerSessionId = typeof headers["mcp-session-id"] === "string" ? headers["mcp-session-id"].trim() : "";
14897
15003
  const sessionId = headerSessionId.length > 0 ? headerSessionId : `anon:${(0, import_utils39.randomUUID)()}`;
14898
- return this.runFromHeaders(headers, { sessionId, scopeId, peerAddress: request.socket?.remoteAddress }, fn);
15004
+ const platformEnv = request[ServerRequestTokens.webEnv];
15005
+ return this.runFromHeaders(
15006
+ headers,
15007
+ { sessionId, scopeId, peerAddress: request.socket?.remoteAddress, platformEnv },
15008
+ fn
15009
+ );
14899
15010
  }
14900
15011
  withServerConfig(args) {
14901
15012
  return { ...args, config: { ...this.contextConfig, ...args.config } };
@@ -21948,6 +22059,14 @@ var init_tool_credentials_gate = __esm({
21948
22059
  });
21949
22060
 
21950
22061
  // libs/sdk/src/tool/flows/call-tool.flow.ts
22062
+ function declaredOutput(tool, output) {
22063
+ if (!tool?.outputSchema || typeof output !== "object" || output === null || Array.isArray(output)) return output;
22064
+ if (Array.isArray(output["content"])) return output;
22065
+ const parsed = tool.safeParseOutput(output);
22066
+ if (!parsed.success) return output;
22067
+ const structured = parsed.data.structuredContent;
22068
+ return typeof structured === "object" && structured !== null && !Array.isArray(structured) ? structured : output;
22069
+ }
21951
22070
  function toolNameCandidates(name44) {
21952
22071
  if (!/[-_]/.test(name44)) return [name44];
21953
22072
  return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
@@ -22681,7 +22800,7 @@ ${JSON.stringify(error.schema, null, 2)}
22681
22800
  this.logger.verbose("execute:done (elicitation fire-and-forget fallback)");
22682
22801
  return;
22683
22802
  }
22684
- if (isClientFacingError(error)) throw error;
22803
+ if (isClientFacingError(error) || error instanceof InvalidOutputError) throw error;
22685
22804
  throw new ToolExecutionError(
22686
22805
  this.state.tool?.metadata.name || "unknown",
22687
22806
  error instanceof Error ? error : void 0
@@ -22724,7 +22843,8 @@ ${JSON.stringify(error.schema, null, 2)}
22724
22843
  }
22725
22844
  async applyUI() {
22726
22845
  this.logger.verbose("applyUI:start");
22727
- const { tool, rawOutput, input } = this.state;
22846
+ const { tool, rawOutput: executeOutput, input } = this.state;
22847
+ const rawOutput = declaredOutput(tool, executeOutput);
22728
22848
  const ctx = this.input.ctx;
22729
22849
  if (ctx?._skipUI) {
22730
22850
  this.logger.verbose("applyUI:skip (agent context - structured data only)");
@@ -24350,6 +24470,38 @@ var init_context = __esm({
24350
24470
  }
24351
24471
  });
24352
24472
 
24473
+ // libs/sdk/src/errors/misconfiguration.ts
24474
+ function findMisconfiguration(error) {
24475
+ let current = error;
24476
+ for (let depth = 0; current instanceof Error && depth < 5; depth++) {
24477
+ const code = current.code;
24478
+ if (typeof code === "string" && Object.prototype.hasOwnProperty.call(MISCONFIGURATION_REMEDIES, code)) {
24479
+ return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
24480
+ }
24481
+ if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
24482
+ current = current.cause;
24483
+ }
24484
+ return void 0;
24485
+ }
24486
+ function misconfigurationBody(m) {
24487
+ return { error: "server_misconfigured", code: m.code, message: m.remedy };
24488
+ }
24489
+ var MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY;
24490
+ var init_misconfiguration = __esm({
24491
+ "libs/sdk/src/errors/misconfiguration.ts"() {
24492
+ "use strict";
24493
+ MISCONFIGURATION_REMEDIES = {
24494
+ SESSION_SECRET_REQUIRED: "Set MCP_SESSION_SECRET in the deployment environment (e.g. `wrangler secret put MCP_SESSION_SECRET` on Cloudflare, or the platform environment settings on Node, Vercel and Lambda). Session IDs are encrypted with it, and production refuses the development machine-id fallback.",
24495
+ JWT_SECRET_INVALID: "JWT_SECRET is present but too weak: HS256 requires at least 32 bytes (RFC 7518). Replace it with `openssl rand -hex 32`.",
24496
+ JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances.",
24497
+ // The startup checks: a server whose entries ask for protection nothing gives them does not start.
24498
+ UNENFORCED_METADATA: "An entry declares a field only a plugin enforces (approval, featureFlag, ...) and no installed plugin that enforces it reaches the entry, so the server refuses to start. Install the plugin or remove the field; the server log names the entries.",
24499
+ AUTH_CONFIGURATION_ERROR: "The auth or authorities configuration is invalid (for example, entries declare authorities and the server has no authorities option, or a rule checks nothing), so the server refuses to start. The server log names the entries."
24500
+ };
24501
+ CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
24502
+ }
24503
+ });
24504
+
24353
24505
  // libs/sdk/src/server/server.validation.ts
24354
24506
  function applyHeaders(res, headers) {
24355
24507
  if (!headers) return;
@@ -24662,6 +24814,7 @@ var init_flow_instance = __esm({
24662
24814
  init_common();
24663
24815
  init_context();
24664
24816
  init_errors();
24817
+ init_misconfiguration();
24665
24818
  init_server_validation();
24666
24819
  init_flow_http_path();
24667
24820
  init_flow_stages();
@@ -24721,6 +24874,15 @@ var init_flow_instance = __esm({
24721
24874
  return writeHttpResponse(response, e.output);
24722
24875
  }
24723
24876
  }
24877
+ const misconfiguration = findMisconfiguration(e);
24878
+ if (misconfiguration) {
24879
+ this.logger.error("Server misconfigured", { flow: this.name, code: misconfiguration.code });
24880
+ return writeHttpResponse(response, {
24881
+ kind: "json",
24882
+ status: 500,
24883
+ body: misconfigurationBody(misconfiguration)
24884
+ });
24885
+ }
24724
24886
  if (e instanceof PublicMcpError) {
24725
24887
  const challenge = e.wwwAuthenticate;
24726
24888
  this.logger.warn("Public error in flow", {
@@ -25433,14 +25595,39 @@ function collectDynamicProviders(klass, options) {
25433
25595
  }
25434
25596
  return out;
25435
25597
  }
25598
+ function collectDynamicTools(klass, options) {
25599
+ const chain = [];
25600
+ for (let k = klass; k && k !== Function.prototype; k = Object.getPrototypeOf(k)) {
25601
+ chain.push(k);
25602
+ }
25603
+ const out = [];
25604
+ for (let i = chain.length - 1; i >= 0; i--) {
25605
+ const k = chain[i];
25606
+ if (Object.prototype.hasOwnProperty.call(k, "dynamicTools") && typeof k.dynamicTools === "function") {
25607
+ out.push(...k.dynamicTools(options) ?? []);
25608
+ }
25609
+ }
25610
+ return out;
25611
+ }
25612
+ function pluginMetadataFromOptions(options, dynamicTools) {
25613
+ const out = { ...options };
25614
+ for (const key of LIST_METADATA_KEYS) {
25615
+ if (key in out && !Array.isArray(out[key])) delete out[key];
25616
+ }
25617
+ const tools = [...out["tools"] ?? [], ...dynamicTools];
25618
+ if (tools.length > 0) out["tools"] = tools;
25619
+ return out;
25620
+ }
25436
25621
  function dedupePluginProviders(providers) {
25437
25622
  const map = /* @__PURE__ */ new Map();
25438
25623
  for (const p of providers) map.set(p["provide"] ?? p, p);
25439
25624
  return [...map.values()];
25440
25625
  }
25626
+ var LIST_METADATA_KEYS;
25441
25627
  var init_dynamic_utils = __esm({
25442
25628
  "libs/sdk/src/common/dynamic/dynamic.utils.ts"() {
25443
25629
  "use strict";
25630
+ LIST_METADATA_KEYS = ["exports", "plugins", "adapters", "tools", "resources", "prompts", "skills"];
25444
25631
  }
25445
25632
  });
25446
25633
 
@@ -25472,7 +25659,7 @@ var init_dynamic_plugin = __esm({
25472
25659
  const typedOptions = options;
25473
25660
  if ("useFactory" in options) {
25474
25661
  return {
25475
- ...typedOptions,
25662
+ ...pluginMetadataFromOptions(typedOptions, []),
25476
25663
  provide: this,
25477
25664
  inject: options.inject,
25478
25665
  useFactory: options.useFactory,
@@ -25481,10 +25668,11 @@ var init_dynamic_plugin = __esm({
25481
25668
  }
25482
25669
  const dyn = collectDynamicProviders(this, typedOptions);
25483
25670
  const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
25671
+ const dynamicTools = collectDynamicTools(this, typedOptions);
25484
25672
  const instance = new this(options);
25485
25673
  initOptionsByInstance.set(instance, options);
25486
25674
  return {
25487
- ...typedOptions,
25675
+ ...pluginMetadataFromOptions(typedOptions, dynamicTools),
25488
25676
  provide: this,
25489
25677
  useValue: instance,
25490
25678
  providers: mergedProviders
@@ -25746,9 +25934,18 @@ var init_execution_context_interface = __esm({
25746
25934
  if (this._authContext) return this._authContext;
25747
25935
  const rawAuth = require("@frontmcp/auth");
25748
25936
  const auth = rawAuth.default ?? rawAuth;
25749
- this._authContext = auth.buildAuthContext(this._authInfo, this.scope.metadata.authorities?.claimsMapping);
25937
+ this._authContext = auth.buildAuthContext(this.resolveAuthSource(), this.scope.metadata.authorities?.claimsMapping);
25750
25938
  return this._authContext;
25751
25939
  }
25940
+ resolveAuthSource() {
25941
+ const fromContext = this.tryGetContext()?.authInfo;
25942
+ if (!fromContext) return this._authInfo;
25943
+ const merged = { ...this._authInfo };
25944
+ for (const [key, value] of Object.entries(fromContext)) {
25945
+ if (value !== void 0) merged[key] = value;
25946
+ }
25947
+ return merged;
25948
+ }
25752
25949
  /**
25753
25950
  * Try to get the context, returning undefined if not available.
25754
25951
  *
@@ -25934,6 +26131,23 @@ var init_execution_context_interface = __esm({
25934
26131
  get config() {
25935
26132
  return this.providers.get(ConfigService);
25936
26133
  }
26134
+ // ---- Platform bindings ----
26135
+ /**
26136
+ * The hosting platform's bindings for the current request — on a Cloudflare Worker, the `env`
26137
+ * object holding KV namespaces, D1 databases, R2 buckets, Durable Object namespaces, `[vars]`
26138
+ * and secrets. `undefined` on Node/Express, where no such object exists.
26139
+ *
26140
+ * Only string bindings are mirrored into `process.env`; use this for the rest.
26141
+ *
26142
+ * @example
26143
+ * ```typescript
26144
+ * const kv = this.workerEnv?.['MY_KV'] as KVNamespace | undefined;
26145
+ * ```
26146
+ */
26147
+ get workerEnv() {
26148
+ const env = this.tryGetContext()?.platformEnv;
26149
+ return env !== null && typeof env === "object" ? env : void 0;
26150
+ }
25937
26151
  // ---- Runtime context helpers ----
25938
26152
  /**
25939
26153
  * Get the current runtime context (platform, runtime, deployment, env).
@@ -30740,6 +30954,9 @@ var init_completion_events = __esm({
30740
30954
  });
30741
30955
 
30742
30956
  // libs/sdk/src/agent/flows/call-agent.flow.ts
30957
+ function invalidOutputFrom(outputError) {
30958
+ return outputError instanceof InvalidOutputError ? outputError : new InvalidOutputError();
30959
+ }
30743
30960
  var import_auth26, import_guard6, import_lazy_zod60, import_protocol41, inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallAgentFlow;
30744
30961
  var init_call_agent_flow = __esm({
30745
30962
  "libs/sdk/src/agent/flows/call-agent.flow.ts"() {
@@ -31126,7 +31343,7 @@ var init_call_agent_flow = __esm({
31126
31343
  const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
31127
31344
  if (!agent || executionStartedAt === void 0) return;
31128
31345
  if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
31129
- const failure = executionError ?? (outputError !== void 0 ? new InvalidOutputError() : void 0);
31346
+ const failure = executionError ?? (outputError !== void 0 ? invalidOutputFrom(outputError) : void 0);
31130
31347
  const failed = failure !== void 0;
31131
31348
  const requestId = this.tryGetContext()?.requestId;
31132
31349
  const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
@@ -31156,7 +31373,7 @@ var init_call_agent_flow = __esm({
31156
31373
  agent: agent.metadata.name,
31157
31374
  errors: outputError
31158
31375
  });
31159
- throw new InvalidOutputError();
31376
+ throw invalidOutputFrom(outputError);
31160
31377
  }
31161
31378
  const result = parsedOutput;
31162
31379
  if (executionMeta) {
@@ -32781,6 +32998,10 @@ var init_mcp_stateless_client = __esm({
32781
32998
  async listResources(cursor) {
32782
32999
  return this.request("resources/list", cursor ? { cursor } : {});
32783
33000
  }
33001
+ /** One page of `resources/templates/list`; pass the previous page's `nextCursor` for the next. */
33002
+ async listResourceTemplates(cursor) {
33003
+ return this.request("resources/templates/list", cursor ? { cursor } : {});
33004
+ }
32784
33005
  /** One page of `prompts/list`; pass the previous page's `nextCursor` for the next. */
32785
33006
  async listPrompts(cursor) {
32786
33007
  return this.request("prompts/list", cursor ? { cursor } : {});
@@ -33122,6 +33343,13 @@ var init_mcp_stateless_client_adapter = __esm({
33122
33343
  ...nextCursorOf(result)
33123
33344
  };
33124
33345
  }
33346
+ async listResourceTemplates(params) {
33347
+ const result = await this.client.listResourceTemplates(params?.cursor);
33348
+ return {
33349
+ resourceTemplates: Array.isArray(result["resourceTemplates"]) ? result["resourceTemplates"] : [],
33350
+ ...nextCursorOf(result)
33351
+ };
33352
+ }
33125
33353
  async readResource(params) {
33126
33354
  return this.client.readResource(params.uri);
33127
33355
  }
@@ -33489,7 +33717,7 @@ var init_health_check = __esm({
33489
33717
  this.consecutiveSuccesses++;
33490
33718
  this.consecutiveFailures = 0;
33491
33719
  const previousStatus = this.status;
33492
- if (this.consecutiveSuccesses >= this.options.healthyThreshold) {
33720
+ if (previousStatus === "unknown" || this.consecutiveSuccesses >= this.options.healthyThreshold) {
33493
33721
  this.status = latencyMs > this.options.degradedLatencyMs ? "degraded" : "healthy";
33494
33722
  }
33495
33723
  this.lastResult = {
@@ -42711,9 +42939,11 @@ var init_instance_local_primary_auth = __esm({
42711
42939
  } else if ((0, import_utils80.isProduction)() && (0, import_auth12.isOrchestratedMode)(options)) {
42712
42940
  throw new import_auth14.JwtSecretRequiredError(options.mode);
42713
42941
  } else {
42714
- this.logger.warn(
42715
- "JWT_SECRET is not set; signing with a random per-process secret. Tokens will not survive a restart and will not verify across instances. Set JWT_SECRET for any deployment that mints tokens."
42716
- );
42942
+ if (!(0, import_auth12.isPublicMode)(options)) {
42943
+ this.logger.warn(
42944
+ "JWT_SECRET is not set; signing with a random per-process secret. Tokens will not survive a restart and will not verify across instances. Set JWT_SECRET for any deployment that mints tokens."
42945
+ );
42946
+ }
42717
42947
  this.secret = getDefaultNoAuthSecret();
42718
42948
  }
42719
42949
  this.tokenStorage = this.readTokenStorage(options);
@@ -46682,7 +46912,14 @@ var init_encrypted_elicitation_store = __esm({
46682
46912
  });
46683
46913
 
46684
46914
  // libs/sdk/src/elicitation/store/elicitation-store.factory.ts
46685
- function detectStorageType2(storage) {
46915
+ function detectStorageType2(storage, configuredType) {
46916
+ if (configuredType === "redis" || configuredType === "upstash" || configuredType === "memory") {
46917
+ return configuredType;
46918
+ }
46919
+ const backend = storage.type ?? storage.backendName;
46920
+ if (backend === "redis" || backend === "upstash" || backend === "memory") {
46921
+ return backend;
46922
+ }
46686
46923
  if ((0, import_utils84.getEnv)("UPSTASH_REDIS_REST_URL")) {
46687
46924
  return "upstash";
46688
46925
  }
@@ -46772,7 +47009,8 @@ async function createElicitationStore(options = {}) {
46772
47009
  });
46773
47010
  }
46774
47011
  }
46775
- if (finalStorageConfig?.type === "vercel-kv" || (0, import_utils84.getEnv)("KV_REST_API_URL")) {
47012
+ const explicitBackend = Boolean(storageConfig || redis);
47013
+ if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && (0, import_utils84.getEnv)("KV_REST_API_URL")) {
46776
47014
  throw new ElicitationNotSupportedError(
46777
47015
  "Vercel KV is not supported for elicitation stores. Elicitation requires pub/sub for cross-node result routing, which Vercel KV does not support. Use Redis or Upstash instead."
46778
47016
  );
@@ -46793,7 +47031,7 @@ async function createElicitationStore(options = {}) {
46793
47031
  );
46794
47032
  }
46795
47033
  let store = new StorageElicitationStore(storage, logger);
46796
- const type = detectStorageType2(storage);
47034
+ const type = detectStorageType2(storage, finalStorageConfig?.type);
46797
47035
  const encryptionEnabled = encryption?.enabled ?? "auto";
46798
47036
  const encryptionSecret = encryption?.secret;
46799
47037
  const hasSecret = encryptionSecret || isElicitationEncryptionAvailable();
@@ -49620,6 +49858,7 @@ function createJobDefinitionStore(options, logger) {
49620
49858
  host: options.redis.host ?? "localhost",
49621
49859
  port: options.redis.port ?? 6379
49622
49860
  });
49861
+ (0, import_utils88.attachRedisErrorListener)(client, { label: "JobDefinitionStore", logger: effectiveLogger });
49623
49862
  return {
49624
49863
  // Factory-created client lifetime is bound to the store, so the store
49625
49864
  // must close it on dispose() — pass ownsClient: true.
@@ -49635,10 +49874,11 @@ function createJobDefinitionStore(options, logger) {
49635
49874
  type: "memory"
49636
49875
  };
49637
49876
  }
49638
- var noopFn, noopLogger2;
49877
+ var import_utils88, noopFn, noopLogger2;
49639
49878
  var init_job_definition_store_factory = __esm({
49640
49879
  "libs/sdk/src/job/store/job-definition-store.factory.ts"() {
49641
49880
  "use strict";
49881
+ import_utils88 = require("@frontmcp/utils");
49642
49882
  init_memory_job_definition_store();
49643
49883
  noopFn = () => {
49644
49884
  };
@@ -49831,6 +50071,7 @@ function createJobStateStore(options, logger) {
49831
50071
  host: options.redis.host ?? "localhost",
49832
50072
  port: options.redis.port ?? 6379
49833
50073
  });
50074
+ (0, import_utils89.attachRedisErrorListener)(client, { label: "JobStateStore", logger });
49834
50075
  return {
49835
50076
  store: new RedisJobStateStore2(client, logger, keyPrefix, ttlSeconds),
49836
50077
  type: "redis"
@@ -49844,9 +50085,11 @@ function createJobStateStore(options, logger) {
49844
50085
  type: "memory"
49845
50086
  };
49846
50087
  }
50088
+ var import_utils89;
49847
50089
  var init_job_state_store_factory = __esm({
49848
50090
  "libs/sdk/src/job/store/job-state-store.factory.ts"() {
49849
50091
  "use strict";
50092
+ import_utils89 = require("@frontmcp/utils");
49850
50093
  init_memory_job_state_store();
49851
50094
  }
49852
50095
  });
@@ -50717,15 +50960,15 @@ var init_skill_session_types = __esm({
50717
50960
  });
50718
50961
 
50719
50962
  // libs/sdk/src/skill/session/skill-session.manager.ts
50720
- var import_utils88, SkillSessionManager;
50963
+ var import_utils90, SkillSessionManager;
50721
50964
  var init_skill_session_manager = __esm({
50722
50965
  "libs/sdk/src/skill/session/skill-session.manager.ts"() {
50723
50966
  "use strict";
50724
- import_utils88 = require("@frontmcp/utils");
50967
+ import_utils90 = require("@frontmcp/utils");
50725
50968
  init_errors();
50726
50969
  init_skill_session_types();
50727
- SkillSessionManager = class extends import_utils88.EventEmitter {
50728
- storage = new import_utils88.AsyncLocalStorage();
50970
+ SkillSessionManager = class extends import_utils90.EventEmitter {
50971
+ storage = new import_utils90.AsyncLocalStorage();
50729
50972
  options;
50730
50973
  logger;
50731
50974
  store;
@@ -51924,11 +52167,11 @@ function createSkillHttpAuthValidator(skillsConfig, logger) {
51924
52167
  logger
51925
52168
  });
51926
52169
  }
51927
- var import_utils89, SkillHttpAuthValidator;
52170
+ var import_utils91, SkillHttpAuthValidator;
51928
52171
  var init_skill_http_auth = __esm({
51929
52172
  "libs/sdk/src/skill/auth/skill-http-auth.ts"() {
51930
52173
  "use strict";
51931
- import_utils89 = require("@frontmcp/utils");
52174
+ import_utils91 = require("@frontmcp/utils");
51932
52175
  init_common();
51933
52176
  SkillHttpAuthValidator = class {
51934
52177
  skillsConfig;
@@ -52010,7 +52253,7 @@ var init_skill_http_auth = __esm({
52010
52253
  const keyBytes = encoder5.encode(key);
52011
52254
  if (keyBytes.length === candidateBytes.length) {
52012
52255
  try {
52013
- if ((0, import_utils89.timingSafeEqual)(keyBytes, candidateBytes)) {
52256
+ if ((0, import_utils91.timingSafeEqual)(keyBytes, candidateBytes)) {
52014
52257
  found = true;
52015
52258
  }
52016
52259
  } catch {
@@ -52291,6 +52534,7 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
52291
52534
  enableReadyCheck: false,
52292
52535
  maxRetriesPerRequest: 3
52293
52536
  });
52537
+ (0, import_utils92.attachRedisErrorListener)(client, { label: "SkillHttpCache" });
52294
52538
  await client.connect();
52295
52539
  return new RedisSkillHttpCache({
52296
52540
  getClient: async () => client,
@@ -52298,9 +52542,11 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
52298
52542
  ttlMs
52299
52543
  });
52300
52544
  }
52545
+ var import_utils92;
52301
52546
  var init_skill_http_cache_factory = __esm({
52302
52547
  "libs/sdk/src/skill/cache/skill-http-cache.factory.ts"() {
52303
52548
  "use strict";
52549
+ import_utils92 = require("@frontmcp/utils");
52304
52550
  init_skill_http_cache();
52305
52551
  }
52306
52552
  });
@@ -55471,15 +55717,15 @@ async function readSkillFileByPath(instance, filePath) {
55471
55717
  let candidatePath;
55472
55718
  for (const [subdir, configured] of candidateDirs) {
55473
55719
  if (segments[0] !== subdir) continue;
55474
- const dir = configured ? (0, import_utils91.isAbsolute)(configured) ? configured : (0, import_utils91.pathResolve)(baseDir, configured) : (0, import_utils91.joinPath)(baseDir, subdir);
55475
- candidatePath = (0, import_utils91.pathResolve)(dir, ...segments.slice(1));
55720
+ const dir = configured ? (0, import_utils94.isAbsolute)(configured) ? configured : (0, import_utils94.pathResolve)(baseDir, configured) : (0, import_utils94.joinPath)(baseDir, subdir);
55721
+ candidatePath = (0, import_utils94.pathResolve)(dir, ...segments.slice(1));
55476
55722
  if (!isInside(candidatePath, dir)) {
55477
55723
  throw new PublicMcpError(`Invalid file path "${filePath}".`, "INVALID_PARAMS", 400);
55478
55724
  }
55479
55725
  break;
55480
55726
  }
55481
55727
  if (!candidatePath) {
55482
- candidatePath = (0, import_utils91.pathResolve)(baseDir, ...segments);
55728
+ candidatePath = (0, import_utils94.pathResolve)(baseDir, ...segments);
55483
55729
  if (!isInside(candidatePath, baseDir)) {
55484
55730
  throw new PublicMcpError(`Invalid file path "${filePath}".`, "INVALID_PARAMS", 400);
55485
55731
  }
@@ -55487,7 +55733,7 @@ async function readSkillFileByPath(instance, filePath) {
55487
55733
  const mimeType = guessMimeType(filePath);
55488
55734
  let buffer;
55489
55735
  try {
55490
- buffer = await (0, import_utils91.readFileBuffer)(candidatePath);
55736
+ buffer = await (0, import_utils94.readFileBuffer)(candidatePath);
55491
55737
  } catch (err) {
55492
55738
  if (isFileMissingError(err)) {
55493
55739
  throw new ResourceNotFoundError(`skill://${instance.getSkillPath()}/${filePath}`);
@@ -55530,7 +55776,7 @@ async function tryResolvedSubFile(instance, kind, tail) {
55530
55776
  const resolvedPath = resolveEntryPath2(instance, kind, absolutePath, filename);
55531
55777
  if (!resolvedPath) continue;
55532
55778
  try {
55533
- const buf = await (0, import_utils91.readFileBuffer)(resolvedPath);
55779
+ const buf = await (0, import_utils94.readFileBuffer)(resolvedPath);
55534
55780
  const text = (Buffer.isBuffer(buf) ? buf : Buffer.from(buf)).toString("utf-8");
55535
55781
  return parseSkillMdFrontmatter(text).body;
55536
55782
  } catch (err) {
@@ -55543,18 +55789,18 @@ async function tryResolvedSubFile(instance, kind, tail) {
55543
55789
  return void 0;
55544
55790
  }
55545
55791
  function resolveEntryPath2(instance, kind, absolutePath, filename) {
55546
- if (absolutePath && (0, import_utils91.isAbsolute)(absolutePath)) return absolutePath;
55792
+ if (absolutePath && (0, import_utils94.isAbsolute)(absolutePath)) return absolutePath;
55547
55793
  if (!filename) return void 0;
55548
55794
  const baseDir = instance.getBaseDir();
55549
55795
  const configured = instance.getResources()?.[kind];
55550
55796
  let dir;
55551
- if (configured && (0, import_utils91.isAbsolute)(configured)) {
55797
+ if (configured && (0, import_utils94.isAbsolute)(configured)) {
55552
55798
  dir = configured;
55553
55799
  } else if (baseDir) {
55554
- dir = configured ? (0, import_utils91.pathResolve)(baseDir, configured) : (0, import_utils91.joinPath)(baseDir, kind);
55800
+ dir = configured ? (0, import_utils94.pathResolve)(baseDir, configured) : (0, import_utils94.joinPath)(baseDir, kind);
55555
55801
  }
55556
55802
  if (!dir) return void 0;
55557
- const resolved = (0, import_utils91.pathResolve)(dir, filename);
55803
+ const resolved = (0, import_utils94.pathResolve)(dir, filename);
55558
55804
  if (!isInside(resolved, dir)) return void 0;
55559
55805
  return resolved;
55560
55806
  }
@@ -55586,11 +55832,11 @@ function isInside(child, parent) {
55586
55832
  const parentPrefix = normParent.endsWith("/") ? normParent : `${normParent}/`;
55587
55833
  return normChild === normParent || normChild.startsWith(parentPrefix);
55588
55834
  }
55589
- var import_utils91;
55835
+ var import_utils94;
55590
55836
  var init_sep_2640_resource_helpers = __esm({
55591
55837
  "libs/sdk/src/skill/sep-2640/sep-2640.resource-helpers.ts"() {
55592
55838
  "use strict";
55593
- import_utils91 = require("@frontmcp/utils");
55839
+ import_utils94 = require("@frontmcp/utils");
55594
55840
  init_errors();
55595
55841
  init_skill_filter_helper();
55596
55842
  init_skill_md_parser();
@@ -55817,17 +56063,17 @@ async function resolveLastModifiedForSkill(skill) {
55817
56063
  return void 0;
55818
56064
  }
55819
56065
  try {
55820
- const s = await (0, import_utils92.stat)(record.filePath);
56066
+ const s = await (0, import_utils95.stat)(record.filePath);
55821
56067
  return s.mtime instanceof Date ? s.mtime.toISOString() : void 0;
55822
56068
  } catch {
55823
56069
  return void 0;
55824
56070
  }
55825
56071
  }
55826
- var import_utils92;
56072
+ var import_utils95;
55827
56073
  var init_sep_2640_last_modified = __esm({
55828
56074
  "libs/sdk/src/skill/sep-2640/sep-2640.last-modified.ts"() {
55829
56075
  "use strict";
55830
- import_utils92 = require("@frontmcp/utils");
56076
+ import_utils95 = require("@frontmcp/utils");
55831
56077
  }
55832
56078
  });
55833
56079
 
@@ -56017,7 +56263,7 @@ function createDefaultSigner(mod, logger) {
56017
56263
  "[skill-audit] refusing to use the default HS256 signer in production. Configure skillsConfig.audit.signer with a host-managed key (e.g. RS256 pointing at the same key registry your bundle signing uses)."
56018
56264
  );
56019
56265
  }
56020
- const bytes = (0, import_utils93.randomBytes)(32);
56266
+ const bytes = (0, import_utils96.randomBytes)(32);
56021
56267
  logger.warn(
56022
56268
  "[skill-audit] no audit signer configured \u2014 using process-local HS256 with a CSPRNG-generated secret. Records signed by this instance cannot be verified after restart. Configure skillsConfig.audit.signer for production."
56023
56269
  );
@@ -56029,12 +56275,12 @@ function createDefaultStore(mod, logger) {
56029
56275
  );
56030
56276
  return new mod.MemoryAuditStore();
56031
56277
  }
56032
- var import_di39, import_utils93, injectedFactory;
56278
+ var import_di39, import_utils96, injectedFactory;
56033
56279
  var init_skill_audit_helper = __esm({
56034
56280
  "libs/sdk/src/skill/skill-audit.helper.ts"() {
56035
56281
  "use strict";
56036
56282
  import_di39 = require("@frontmcp/di");
56037
- import_utils93 = require("@frontmcp/utils");
56283
+ import_utils96 = require("@frontmcp/utils");
56038
56284
  }
56039
56285
  });
56040
56286
 
@@ -56106,6 +56352,51 @@ var init_skill_scope_helper = __esm({
56106
56352
  }
56107
56353
  });
56108
56354
 
56355
+ // libs/sdk/src/tool/ui/ui-option-warnings.ts
56356
+ function describeIgnoredUiOptions(toolName, ui) {
56357
+ if (!ui) return [];
56358
+ const messages = [];
56359
+ const ignored = IGNORED_UI_OPTIONS.filter((key) => ui[key] !== void 0);
56360
+ if (ignored.length > 0) {
56361
+ messages.push(
56362
+ `Tool "${toolName}": \`ui.${ignored.join("`, `ui.")}\` ${ignored.length === 1 ? "is" : "are"} accepted but not used yet, so ${ignored.length === 1 ? "it has" : "they have"} no effect.`
56363
+ );
56364
+ }
56365
+ const mode = ui["servingMode"];
56366
+ if (UNSUPPORTED_SERVING_MODES.includes(mode)) {
56367
+ messages.push(
56368
+ `Tool "${toolName}": \`ui.servingMode: '${String(mode)}'\` is not implemented; the widget is served inline, as with \`servingMode: 'inline'\`.`
56369
+ );
56370
+ }
56371
+ if (mode === "hybrid") {
56372
+ messages.push(
56373
+ `Tool "${toolName}": \`ui.servingMode: 'hybrid'\` sends only a reference in \`_meta['ui/component']\` ({ type, hash, toolName }), not the component code; the widget is rendered from its \`ui://\` resource.`
56374
+ );
56375
+ }
56376
+ return messages;
56377
+ }
56378
+ var IGNORED_UI_OPTIONS, UNSUPPORTED_SERVING_MODES;
56379
+ var init_ui_option_warnings = __esm({
56380
+ "libs/sdk/src/tool/ui/ui-option-warnings.ts"() {
56381
+ "use strict";
56382
+ IGNORED_UI_OPTIONS = [
56383
+ "widgetDescription",
56384
+ "widgetAccessible",
56385
+ "displayMode",
56386
+ "prefersBorder",
56387
+ "sandboxDomain",
56388
+ "contentSecurity",
56389
+ "hydrate",
56390
+ "runtimeOptions",
56391
+ "mdxComponents",
56392
+ "bundlingMode",
56393
+ "uiType",
56394
+ "htmlResponsePrefix"
56395
+ ];
56396
+ UNSUPPORTED_SERVING_MODES = ["direct-url", "custom-url"];
56397
+ }
56398
+ });
56399
+
56109
56400
  // libs/sdk/src/transport/bus/redis-transport-bus.ts
56110
56401
  var DEFAULT_BUS_PREFIX, DEFAULT_BUS_TTL_SECONDS, REVOKE_LUA, RedisTransportBus;
56111
56402
  var init_redis_transport_bus = __esm({
@@ -56235,10 +56526,11 @@ var redis_event_store_exports = {};
56235
56526
  __export(redis_event_store_exports, {
56236
56527
  RedisEventStore: () => RedisEventStore
56237
56528
  });
56238
- var RedisEventStore;
56529
+ var import_utils97, RedisEventStore;
56239
56530
  var init_redis_event_store = __esm({
56240
56531
  "libs/sdk/src/transport/event-stores/redis.event-store.ts"() {
56241
56532
  "use strict";
56533
+ import_utils97 = require("@frontmcp/utils");
56242
56534
  init_sdk_errors();
56243
56535
  RedisEventStore = class {
56244
56536
  client;
@@ -56282,6 +56574,9 @@ var init_redis_event_store = __esm({
56282
56574
  enableReadyCheck: false,
56283
56575
  maxRetriesPerRequest: 3
56284
56576
  });
56577
+ (0, import_utils97.attachRedisErrorListener)(this.client, {
56578
+ label: "RedisEventStore"
56579
+ });
56285
56580
  await this.client.connect();
56286
56581
  }
56287
56582
  return this.client;
@@ -56674,11 +56969,11 @@ function createSqliteSessionStore(options, logger) {
56674
56969
  logger?.info("[SessionStoreFactory] Creating SQLite session store", { path: options.path });
56675
56970
  return new SqliteSessionStore(options);
56676
56971
  }
56677
- var import_utils94;
56972
+ var import_utils98;
56678
56973
  var init_session_store_factory = __esm({
56679
56974
  "libs/sdk/src/auth/session/session-store.factory.ts"() {
56680
56975
  "use strict";
56681
- import_utils94 = require("@frontmcp/utils");
56976
+ import_utils98 = require("@frontmcp/utils");
56682
56977
  init_common();
56683
56978
  }
56684
56979
  });
@@ -56753,14 +57048,14 @@ function toClientInfo(value) {
56753
57048
  const { name: name44, version } = value;
56754
57049
  return typeof name44 === "string" && typeof version === "string" ? { name: name44, version } : void 0;
56755
57050
  }
56756
- var import_auth55, import_lazy_zod102, import_protocol52, import_utils95, plan37, subscriptionParamsSchema, requestIdSchema, stateSchema37, name38, Stage38, encoder3, TRACE_META_KEYS, HandleMcp20260728Flow;
57051
+ var import_auth55, import_lazy_zod102, import_protocol52, import_utils99, plan37, subscriptionParamsSchema, requestIdSchema, stateSchema37, name38, Stage38, encoder3, TRACE_META_KEYS, HandleMcp20260728Flow;
56757
57052
  var init_handle_mcp_20260728_flow = __esm({
56758
57053
  "libs/sdk/src/transport/flows/handle.mcp-20260728.flow.ts"() {
56759
57054
  "use strict";
56760
57055
  import_auth55 = require("@frontmcp/auth");
56761
57056
  import_lazy_zod102 = require("@frontmcp/lazy-zod");
56762
57057
  import_protocol52 = require("@frontmcp/protocol");
56763
- import_utils95 = require("@frontmcp/utils");
57058
+ import_utils99 = require("@frontmcp/utils");
56764
57059
  init_common();
56765
57060
  init_context();
56766
57061
  init_notification();
@@ -56858,7 +57153,7 @@ var init_handle_mcp_20260728_flow = __esm({
56858
57153
  if (!context) return (fn) => fn();
56859
57154
  const storage = this.scope.providers.get(FrontMcpContextStorage);
56860
57155
  return async (fn) => {
56861
- await (0, import_utils95.runRequestExclusive)(() => storage.runWithContext(context, fn));
57156
+ await (0, import_utils99.runRequestExclusive)(() => storage.runWithContext(context, fn));
56862
57157
  };
56863
57158
  }
56864
57159
  findToolSchema(scope, toolName) {
@@ -56935,7 +57230,7 @@ var init_handle_mcp_20260728_flow = __esm({
56935
57230
  const sink = new RequestNotificationSink(logLevel, progressToken);
56936
57231
  const clientInfo = toClientInfo(meta[import_protocol52.MCP_20260728_META.clientInfo]);
56937
57232
  if (clientInfo) {
56938
- const platformType = detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
57233
+ const platformType = detectPlatformFromCapabilities(clientCapabilities) ?? detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
56939
57234
  this.tryGetContext()?.setClientInfo(clientInfo, platformType);
56940
57235
  }
56941
57236
  const dispatchOptions = {
@@ -57033,6 +57328,29 @@ var init_handle_mcp_20260728_flow = __esm({
57033
57328
  }
57034
57329
  });
57035
57330
 
57331
+ // libs/sdk/src/ha/ha-headers.ts
57332
+ function applyMachineIdHeader(response) {
57333
+ if ((0, import_utils100.getRuntimeContext)().deployment !== "distributed") return false;
57334
+ response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, (0, import_utils100.getMachineId)());
57335
+ return true;
57336
+ }
57337
+ function applyNodeAffinity(response, request) {
57338
+ if (!applyMachineIdHeader(response)) return;
57339
+ const cookie = (0, import_utils100.buildSetCookie)({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: (0, import_utils100.getMachineId)() }, request);
57340
+ if (!cookie) return;
57341
+ const existing = response.getHeader("Set-Cookie");
57342
+ const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57343
+ response.setHeader("Set-Cookie", [...existingArr, cookie]);
57344
+ }
57345
+ var import_utils100;
57346
+ var init_ha_headers = __esm({
57347
+ "libs/sdk/src/ha/ha-headers.ts"() {
57348
+ "use strict";
57349
+ import_utils100 = require("@frontmcp/utils");
57350
+ init_ha_constants();
57351
+ }
57352
+ });
57353
+
57036
57354
  // libs/sdk/src/skill/skill-mode.utils.ts
57037
57355
  function detectSkillsOnlyMode(query) {
57038
57356
  if (!query) return false;
@@ -57085,16 +57403,16 @@ function resolveLegacySseSession(params) {
57085
57403
  }
57086
57404
  return { session: createSession(), createdNew: true, responded404: false };
57087
57405
  }
57088
- var import_lazy_zod103, import_utils96, plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
57406
+ var import_lazy_zod103, import_utils101, plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
57089
57407
  var init_handle_sse_flow = __esm({
57090
57408
  "libs/sdk/src/transport/flows/handle.sse.flow.ts"() {
57091
57409
  "use strict";
57092
57410
  import_lazy_zod103 = require("@frontmcp/lazy-zod");
57093
- import_utils96 = require("@frontmcp/utils");
57411
+ import_utils101 = require("@frontmcp/utils");
57094
57412
  init_session_id_utils();
57095
57413
  init_common();
57096
57414
  init_errors();
57097
- init_ha_constants();
57415
+ init_ha_headers();
57098
57416
  init_skill_mode_utils();
57099
57417
  plan38 = {
57100
57418
  pre: ["parseInput", "router"],
@@ -57168,16 +57486,7 @@ var init_handle_sse_flow = __esm({
57168
57486
  const { request, response } = this.rawInput;
57169
57487
  const { token, session } = this.state.required;
57170
57488
  const transport = await transportService.createTransporter("sse", token, session.id, response);
57171
- if ((0, import_utils96.getRuntimeContext)().deployment === "distributed") {
57172
- const nodeId = (0, import_utils96.getMachineId)();
57173
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57174
- const cookie = (0, import_utils96.buildSetCookie)({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57175
- if (cookie) {
57176
- const existing = response.getHeader("Set-Cookie");
57177
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57178
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57179
- }
57180
- }
57489
+ applyNodeAffinity(response, request);
57181
57490
  await transport.initialize(request, response);
57182
57491
  this.handled();
57183
57492
  }
@@ -57193,12 +57502,12 @@ var init_handle_sse_flow = __esm({
57193
57502
  const { request, response } = this.rawInput;
57194
57503
  const { token, session } = this.state.required;
57195
57504
  const transport = await transportService.getTransporter("sse", token, session.id);
57196
- if (!transport && (0, import_utils96.getRuntimeContext)().deployment === "distributed") {
57505
+ if (!transport && (0, import_utils101.getRuntimeContext)().deployment === "distributed") {
57197
57506
  const storedSession = await transportService.getStoredSession("sse", token, session.id);
57198
57507
  if (storedSession) {
57199
57508
  const haManager = this.scope.haManager;
57200
57509
  const relay = haManager?.getRelay();
57201
- if (relay && storedSession.session.nodeId && storedSession.session.nodeId !== (0, import_utils96.getMachineId)()) {
57510
+ if (relay && storedSession.session.nodeId && storedSession.session.nodeId !== (0, import_utils101.getMachineId)()) {
57202
57511
  const isAlive2 = await haManager.isNodeAlive(storedSession.session.nodeId);
57203
57512
  if (isAlive2) {
57204
57513
  try {
@@ -57293,9 +57602,10 @@ var init_handle_stateless_http_flow = __esm({
57293
57602
  import_protocol53 = require("@frontmcp/protocol");
57294
57603
  init_common();
57295
57604
  init_errors();
57605
+ init_ha_headers();
57296
57606
  init_transport_types();
57297
57607
  plan39 = {
57298
- pre: ["parseInput", "router"],
57608
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57299
57609
  execute: ["handleRequest"],
57300
57610
  post: [],
57301
57611
  finalize: ["cleanup"]
@@ -57309,6 +57619,9 @@ var init_handle_stateless_http_flow = __esm({
57309
57619
  ({ Stage: Stage40 } = FlowHooksOf(name40));
57310
57620
  HandleStatelessHttpFlow = class extends FlowBase {
57311
57621
  name = name40;
57622
+ async applyNodeHeaders() {
57623
+ applyMachineIdHeader(this.rawInput.response);
57624
+ }
57312
57625
  async parseInput() {
57313
57626
  const { request } = this.rawInput;
57314
57627
  const logger = this.scope.logger.child("HandleStatelessHttpFlow");
@@ -57370,6 +57683,9 @@ var init_handle_stateless_http_flow = __esm({
57370
57683
  this.handled();
57371
57684
  }
57372
57685
  };
57686
+ __decorateClass([
57687
+ Stage40("applyNodeHeaders")
57688
+ ], HandleStatelessHttpFlow.prototype, "applyNodeHeaders", 1);
57373
57689
  __decorateClass([
57374
57690
  Stage40("parseInput")
57375
57691
  ], HandleStatelessHttpFlow.prototype, "parseInput", 1);
@@ -57424,11 +57740,11 @@ var init_ext_apps_types = __esm({
57424
57740
  function createExtAppsMessageHandler(options) {
57425
57741
  return new ExtAppsMessageHandler(options);
57426
57742
  }
57427
- var import_utils97, ExtAppsMessageHandler, ExtAppsError, ExtAppsMethodNotFoundError, ExtAppsInvalidParamsError, ExtAppsNotSupportedError, ExtAppsToolNotFoundError;
57743
+ var import_utils102, ExtAppsMessageHandler, ExtAppsError, ExtAppsMethodNotFoundError, ExtAppsInvalidParamsError, ExtAppsNotSupportedError, ExtAppsToolNotFoundError;
57428
57744
  var init_ext_apps_handler = __esm({
57429
57745
  "libs/sdk/src/ext-apps/ext-apps.handler.ts"() {
57430
57746
  "use strict";
57431
- import_utils97 = require("@frontmcp/utils");
57747
+ import_utils102 = require("@frontmcp/utils");
57432
57748
  init_ext_apps_types();
57433
57749
  ExtAppsMessageHandler = class {
57434
57750
  context;
@@ -57472,7 +57788,7 @@ var init_ext_apps_handler = __esm({
57472
57788
  code: errorCode,
57473
57789
  message: errorMessage,
57474
57790
  // Only include stack traces in development to avoid leaking internals in production
57475
- data: (0, import_utils97.isDevelopment)() && error instanceof Error ? { stack: error.stack } : void 0
57791
+ data: (0, import_utils102.isDevelopment)() && error instanceof Error ? { stack: error.stack } : void 0
57476
57792
  }
57477
57793
  };
57478
57794
  }
@@ -57834,22 +58150,21 @@ async function lookupStreamableHttpTransport(params) {
57834
58150
  }
57835
58151
  return { kind: "session-not-initialized", recreationError };
57836
58152
  }
57837
- var import_lazy_zod105, import_protocol54, import_utils98, plan40, stateSessionSchema2, stateSchema40, name41, Stage41, HandleStreamableHttpFlow;
58153
+ var import_lazy_zod105, import_protocol54, plan40, stateSessionSchema2, stateSchema40, name41, Stage41, HandleStreamableHttpFlow;
57838
58154
  var init_handle_streamable_http_flow = __esm({
57839
58155
  "libs/sdk/src/transport/flows/handle.streamable-http.flow.ts"() {
57840
58156
  "use strict";
57841
58157
  import_lazy_zod105 = require("@frontmcp/lazy-zod");
57842
58158
  import_protocol54 = require("@frontmcp/protocol");
57843
- import_utils98 = require("@frontmcp/utils");
57844
58159
  init_session_id_utils();
57845
58160
  init_common();
57846
58161
  init_errors();
57847
58162
  init_ext_apps2();
57848
- init_ha_constants();
58163
+ init_ha_headers();
57849
58164
  init_skill_mode_utils();
57850
58165
  init_mcp_surface();
57851
58166
  plan40 = {
57852
- pre: ["parseInput", "router"],
58167
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57853
58168
  execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
57854
58169
  post: [],
57855
58170
  finalize: ["cleanup"]
@@ -57875,6 +58190,9 @@ var init_handle_streamable_http_flow = __esm({
57875
58190
  ({ Stage: Stage41 } = FlowHooksOf(name41));
57876
58191
  HandleStreamableHttpFlow = class extends FlowBase {
57877
58192
  name = name41;
58193
+ async applyNodeHeaders() {
58194
+ applyMachineIdHeader(this.rawInput.response);
58195
+ }
57878
58196
  async parseInput() {
57879
58197
  const { request } = this.rawInput;
57880
58198
  const authorization = request[ServerRequestTokens.auth];
@@ -57973,16 +58291,7 @@ var init_handle_streamable_http_flow = __esm({
57973
58291
  return;
57974
58292
  }
57975
58293
  }
57976
- if ((0, import_utils98.getRuntimeContext)().deployment === "distributed") {
57977
- const nodeId = (0, import_utils98.getMachineId)();
57978
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57979
- const cookie = (0, import_utils98.buildSetCookie)({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57980
- if (cookie) {
57981
- const existing = response.getHeader("Set-Cookie");
57982
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57983
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57984
- }
57985
- }
58294
+ applyNodeAffinity(response, request);
57986
58295
  logger.info("onInitialize: transport created, calling initialize");
57987
58296
  await transport.initialize(request, response);
57988
58297
  logger.info("onInitialize: completed successfully");
@@ -58165,6 +58474,17 @@ var init_handle_streamable_http_flow = __esm({
58165
58474
  const logger = this.scopeLogger.child("handle:streamable-http:onExtApps");
58166
58475
  const { request, response } = this.rawInput;
58167
58476
  const { token, session } = this.state.required;
58477
+ if (this.scope.metadata.extApps?.enabled === false) {
58478
+ const rpc = request.body;
58479
+ logger.verbose("onExtApps: ext-apps disabled by config", { method: rpc?.method });
58480
+ response.status(200).json({
58481
+ jsonrpc: "2.0",
58482
+ id: rpc?.id ?? null,
58483
+ error: { code: -32601, message: `Method not found: ${rpc?.method}` }
58484
+ });
58485
+ this.handled();
58486
+ return;
58487
+ }
58168
58488
  logger.info("onExtApps: starting", {
58169
58489
  sessionId: session.id?.slice(0, 20),
58170
58490
  method: request.body?.method
@@ -58216,8 +58536,20 @@ var init_handle_streamable_http_flow = __esm({
58216
58536
  const hostCapabilities = {
58217
58537
  serverToolProxy: configuredCapabilities?.serverToolProxy ?? true,
58218
58538
  logging: configuredCapabilities?.logging ?? true,
58219
- ...configuredCapabilities
58539
+ ...configuredCapabilities,
58540
+ // Only advertise what this host can serve: the context below provides callTool and nothing else
58541
+ openLink: false,
58542
+ modelContextUpdate: false,
58543
+ widgetTools: false
58220
58544
  };
58545
+ const unsupported = ["openLink", "modelContextUpdate", "widgetTools"].filter(
58546
+ (key) => configuredCapabilities?.[key] === true
58547
+ );
58548
+ if (unsupported.length > 0) {
58549
+ logger.warn(
58550
+ `onExtApps: extApps.hostCapabilities ${unsupported.join(", ")} are not supported yet and are not advertised`
58551
+ );
58552
+ }
58221
58553
  const handler = createExtAppsMessageHandler({
58222
58554
  context: {
58223
58555
  sessionId: session.id,
@@ -58251,6 +58583,9 @@ var init_handle_streamable_http_flow = __esm({
58251
58583
  this.handled();
58252
58584
  }
58253
58585
  };
58586
+ __decorateClass([
58587
+ Stage41("applyNodeHeaders")
58588
+ ], HandleStreamableHttpFlow.prototype, "applyNodeHeaders", 1);
58254
58589
  __decorateClass([
58255
58590
  Stage41("parseInput")
58256
58591
  ], HandleStreamableHttpFlow.prototype, "parseInput", 1);
@@ -58295,21 +58630,21 @@ var init_handle_streamable_http_flow = __esm({
58295
58630
  });
58296
58631
 
58297
58632
  // libs/sdk/src/transport/transport.error.ts
58298
- var import_utils99, JSON_RPC2, rpcError, rpcRequest;
58633
+ var import_utils103, JSON_RPC2, rpcError, rpcRequest;
58299
58634
  var init_transport_error = __esm({
58300
58635
  "libs/sdk/src/transport/transport.error.ts"() {
58301
58636
  "use strict";
58302
- import_utils99 = require("@frontmcp/utils");
58637
+ import_utils103 = require("@frontmcp/utils");
58303
58638
  JSON_RPC2 = "2.0";
58304
58639
  rpcError = (message, requestId) => ({
58305
58640
  jsonrpc: JSON_RPC2,
58306
58641
  error: { code: -32e3, message },
58307
- id: requestId ?? (0, import_utils99.randomUUID)()
58642
+ id: requestId ?? (0, import_utils103.randomUUID)()
58308
58643
  // change it to request id + random
58309
58644
  });
58310
58645
  rpcRequest = (requestId, method, params) => ({
58311
58646
  jsonrpc: JSON_RPC2,
58312
- id: requestId ?? (0, import_utils99.randomUUID)(),
58647
+ id: requestId ?? (0, import_utils103.randomUUID)(),
58313
58648
  method,
58314
58649
  ...params !== void 0 && { params }
58315
58650
  });
@@ -58372,14 +58707,14 @@ var init_host_validation = __esm({
58372
58707
  });
58373
58708
 
58374
58709
  // libs/sdk/src/transport/adapters/base-sse-transport.ts
58375
- var import_content_type, import_raw_body, import_protocol55, import_utils100, MAXIMUM_MESSAGE_SIZE, SSEServerTransport;
58710
+ var import_content_type, import_raw_body, import_protocol55, import_utils104, MAXIMUM_MESSAGE_SIZE, SSEServerTransport;
58376
58711
  var init_base_sse_transport = __esm({
58377
58712
  "libs/sdk/src/transport/adapters/base-sse-transport.ts"() {
58378
58713
  "use strict";
58379
58714
  import_content_type = __toESM(require("content-type"));
58380
58715
  import_raw_body = __toESM(require("raw-body"));
58381
58716
  import_protocol55 = require("@frontmcp/protocol");
58382
- import_utils100 = require("@frontmcp/utils");
58717
+ import_utils104 = require("@frontmcp/utils");
58383
58718
  init_transport_errors();
58384
58719
  init_host_validation();
58385
58720
  MAXIMUM_MESSAGE_SIZE = "4mb";
@@ -58390,7 +58725,7 @@ var init_base_sse_transport = __esm({
58390
58725
  constructor(_endpoint, res, options) {
58391
58726
  this._endpoint = _endpoint;
58392
58727
  this.res = res;
58393
- this._sessionId = options?.sessionId ?? (0, import_utils100.randomUUID)();
58728
+ this._sessionId = options?.sessionId ?? (0, import_utils104.randomUUID)();
58394
58729
  this._options = options || { enableDnsRebindingProtection: false };
58395
58730
  }
58396
58731
  _endpoint;
@@ -59836,11 +60171,11 @@ var init_transport_remote = __esm({
59836
60171
  });
59837
60172
 
59838
60173
  // libs/sdk/src/transport/transport.registry.ts
59839
- var import_utils101, TransportService;
60174
+ var import_utils105, SESSION_STORE_RETRY_BASE_MS, SESSION_STORE_RETRY_MAX_MS, TransportService;
59840
60175
  var init_transport_registry = __esm({
59841
60176
  "libs/sdk/src/transport/transport.registry.ts"() {
59842
60177
  "use strict";
59843
- import_utils101 = require("@frontmcp/utils");
60178
+ import_utils105 = require("@frontmcp/utils");
59844
60179
  init_session_store_factory();
59845
60180
  init_transport_errors();
59846
60181
  init_handle_mcp_20260728_flow();
@@ -59850,6 +60185,8 @@ var init_transport_registry = __esm({
59850
60185
  init_transport_local();
59851
60186
  init_transport_remote();
59852
60187
  init_transport_types();
60188
+ SESSION_STORE_RETRY_BASE_MS = 1e3;
60189
+ SESSION_STORE_RETRY_MAX_MS = 3e4;
59853
60190
  TransportService = class {
59854
60191
  ready;
59855
60192
  byType = /* @__PURE__ */ new Map();
@@ -59906,8 +60243,28 @@ var init_transport_registry = __esm({
59906
60243
  * Returns undefined if persistence is disabled or not configured.
59907
60244
  */
59908
60245
  getDefaultTtlMs() {
60246
+ if (this.resolvedTtlMs !== void 0) return this.resolvedTtlMs;
59909
60247
  return typeof this.persistenceConfig === "object" ? this.persistenceConfig?.defaultTtlMs : void 0;
59910
60248
  }
60249
+ /**
60250
+ * TTL resolved from `persistence.defaultTtlMs`, then `persistence.redis.defaultTtlMs`, then 1 hour.
60251
+ * Set only when a persistence backend is configured.
60252
+ */
60253
+ resolvedTtlMs;
60254
+ /** Store config kept after a successful create so a failed ping can be retried. */
60255
+ storeRetryConfig;
60256
+ storeRetryTimer;
60257
+ storeRetryAttempt = 0;
60258
+ destroyed = false;
60259
+ /** Last time a request refreshed the stored session's TTL, by session id. */
60260
+ lastTtlRefreshAt = /* @__PURE__ */ new Map();
60261
+ /**
60262
+ * Redis key prefix under which session records are stored.
60263
+ * The session store appends `session:` to the transport key prefix.
60264
+ */
60265
+ getSessionKeyPrefix() {
60266
+ return `${this.transportKeyPrefix}session:`;
60267
+ }
59911
60268
  constructor(scope, persistenceConfig, bus) {
59912
60269
  this.scope = scope;
59913
60270
  this.persistenceConfig = persistenceConfig;
@@ -59918,10 +60275,11 @@ var init_transport_registry = __esm({
59918
60275
  this.backendKind = "sqlite";
59919
60276
  const sqliteConfig = persistenceConfig.sqlite;
59920
60277
  this.transportKeyPrefix = "mcp:transport:";
60278
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? 36e5;
59921
60279
  this.pendingStoreConfig = {
59922
60280
  sqlite: sqliteConfig,
59923
60281
  keyPrefix: this.transportKeyPrefix,
59924
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
60282
+ defaultTtlMs: this.resolvedTtlMs
59925
60283
  };
59926
60284
  this.scope.logger.info("[TransportService] sqlite session store will be initialized for transport persistence");
59927
60285
  } else if (persistenceConfig !== false && persistenceConfig?.redis) {
@@ -59930,43 +60288,23 @@ var init_transport_registry = __esm({
59930
60288
  const providerType = "provider" in redisConfig ? redisConfig.provider : "redis";
59931
60289
  this.backendKind = providerType === "vercel-kv" ? "vercel-kv" : "redis";
59932
60290
  this.transportKeyPrefix = redisConfig.keyPrefix ?? "mcp:transport:";
60291
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? redisConfig.defaultTtlMs ?? 36e5;
59933
60292
  this.pendingStoreConfig = {
59934
60293
  ...redisConfig,
59935
60294
  keyPrefix: this.transportKeyPrefix,
59936
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
59937
- // 1 hour default
60295
+ defaultTtlMs: this.resolvedTtlMs
59938
60296
  };
59939
60297
  this.scope.logger.info(
59940
60298
  `[TransportService] ${providerType} session store will be initialized for transport persistence`
59941
60299
  );
59942
60300
  }
60301
+ this.storeRetryConfig = this.pendingStoreConfig;
59943
60302
  this.ready = this.initialize();
59944
60303
  }
59945
60304
  async initialize() {
59946
60305
  if (this.pendingStoreConfig) {
59947
- try {
59948
- const store = await createSessionStore(this.pendingStoreConfig, this.scope.logger.child("SessionStore"));
59949
- this.sessionStore = store;
59950
- this.pendingStoreConfig = void 0;
59951
- } catch (error) {
59952
- const err = error;
59953
- this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
59954
- error: err.message,
59955
- cause: err.cause?.message
59956
- });
59957
- }
59958
- }
59959
- if (this.sessionStore) {
59960
- const isConnected = this.sessionStore.ping ? await this.sessionStore.ping() : true;
59961
- if (!isConnected) {
59962
- const providerType = this.backendKind ?? "redis";
59963
- this.scope.logger.error(
59964
- `[TransportService] Failed to connect to ${providerType} - session persistence disabled`
59965
- );
59966
- await this.teardownSessionStore().catch(() => void 0);
59967
- } else {
59968
- this.scope.logger.info("[TransportService] Session store connection validated successfully");
59969
- }
60306
+ const connected = await this.connectSessionStore();
60307
+ if (!connected && this.backendKind !== "sqlite") this.scheduleSessionStoreRetry();
59970
60308
  }
59971
60309
  await this.scope.registryFlows(
59972
60310
  HandleStreamableHttpFlow,
@@ -59975,7 +60313,70 @@ var init_transport_registry = __esm({
59975
60313
  HandleMcp20260728Flow
59976
60314
  );
59977
60315
  }
60316
+ /**
60317
+ * Create the session store and validate the connection.
60318
+ * Returns true when the store is usable; on failure the store is left undefined.
60319
+ */
60320
+ async connectSessionStore() {
60321
+ const storeConfig = this.pendingStoreConfig ?? this.storeRetryConfig;
60322
+ if (!storeConfig) return false;
60323
+ try {
60324
+ const store = await createSessionStore(storeConfig, this.scope.logger.child("SessionStore"));
60325
+ this.sessionStore = store;
60326
+ this.pendingStoreConfig = void 0;
60327
+ } catch (error) {
60328
+ const err = error;
60329
+ this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
60330
+ error: err.message,
60331
+ cause: err.cause?.message
60332
+ });
60333
+ return false;
60334
+ }
60335
+ const isConnected = this.sessionStore?.ping ? await this.sessionStore.ping() : true;
60336
+ if (!isConnected) {
60337
+ const providerType = this.backendKind ?? "redis";
60338
+ this.scope.logger.error(`[TransportService] Failed to connect to ${providerType} - session persistence disabled`);
60339
+ await this.teardownSessionStore().catch(() => void 0);
60340
+ return false;
60341
+ }
60342
+ this.scope.logger.info("[TransportService] Session store connection validated successfully");
60343
+ return true;
60344
+ }
60345
+ /**
60346
+ * Retry the session store with exponential backoff (1s doubling to 30s) so a Redis that
60347
+ * was down at startup is picked up when it comes back, and `/readyz` turns healthy.
60348
+ */
60349
+ scheduleSessionStoreRetry() {
60350
+ if (this.destroyed || this.storeRetryTimer) return;
60351
+ const delay = Math.min(SESSION_STORE_RETRY_MAX_MS, SESSION_STORE_RETRY_BASE_MS * 2 ** this.storeRetryAttempt);
60352
+ this.storeRetryAttempt++;
60353
+ this.storeRetryTimer = setTimeout(() => {
60354
+ this.storeRetryTimer = void 0;
60355
+ void this.retrySessionStore();
60356
+ }, delay);
60357
+ this.storeRetryTimer.unref?.();
60358
+ }
60359
+ async retrySessionStore() {
60360
+ if (this.destroyed || this.sessionStore) return;
60361
+ const connected = await this.connectSessionStore().catch(() => false);
60362
+ if (this.destroyed) {
60363
+ await this.teardownSessionStore().catch(() => void 0);
60364
+ return;
60365
+ }
60366
+ if (connected) {
60367
+ this.storeRetryAttempt = 0;
60368
+ this.scope.logger.info("[TransportService] Session store recovered after startup failure");
60369
+ return;
60370
+ }
60371
+ this.scheduleSessionStoreRetry();
60372
+ }
59978
60373
  async destroy() {
60374
+ this.destroyed = true;
60375
+ if (this.storeRetryTimer) {
60376
+ clearTimeout(this.storeRetryTimer);
60377
+ this.storeRetryTimer = void 0;
60378
+ }
60379
+ this.lastTtlRefreshAt.clear();
59979
60380
  await this.teardownSessionStore();
59980
60381
  }
59981
60382
  /**
@@ -60036,10 +60437,33 @@ var init_transport_registry = __esm({
60036
60437
  getBackendKind() {
60037
60438
  return this.backendKind;
60038
60439
  }
60440
+ /**
60441
+ * Slide the stored session's TTL while this instance is serving it. Redis and Vercel KV
60442
+ * session stores extend the TTL on read, so a plain `get` is enough. Throttled per session
60443
+ * to a quarter of the TTL, fire-and-forget.
60444
+ */
60445
+ refreshStoredSessionTtl(sessionId) {
60446
+ const store = this.sessionStore;
60447
+ const ttlMs = this.getDefaultTtlMs();
60448
+ if (!store || !ttlMs || this.backendKind !== "redis" && this.backendKind !== "vercel-kv") return;
60449
+ const now = Date.now();
60450
+ const last = this.lastTtlRefreshAt.get(sessionId);
60451
+ if (last !== void 0 && now - last < Math.max(1e3, Math.floor(ttlMs / 4))) return;
60452
+ this.lastTtlRefreshAt.set(sessionId, now);
60453
+ store.get(sessionId).catch((err) => {
60454
+ this.scope.logger.warn("[TransportService] Failed to refresh session TTL", {
60455
+ sessionId: sessionId.slice(0, 20),
60456
+ error: err instanceof Error ? err.message : String(err)
60457
+ });
60458
+ });
60459
+ }
60039
60460
  async getTransporter(type, token, sessionId) {
60040
60461
  const key = this.keyOf(type, token, sessionId);
60041
60462
  const local = this.lookupLocal(key);
60042
- if (local) return local;
60463
+ if (local) {
60464
+ this.refreshStoredSessionTtl(sessionId);
60465
+ return local;
60466
+ }
60043
60467
  if (this.distributed && this.bus) {
60044
60468
  const location = await this.bus.lookup(key);
60045
60469
  if (location) {
@@ -60132,9 +60556,9 @@ var init_transport_registry = __esm({
60132
60556
  protocol: storedSession.session.protocol,
60133
60557
  createdAt: storedSession.createdAt
60134
60558
  });
60135
- const currentNodeId = (0, import_utils101.getMachineId)();
60559
+ const currentNodeId = (0, import_utils105.getMachineId)();
60136
60560
  if (this.scope.haManager && storedSession.session.nodeId && storedSession.session.nodeId !== currentNodeId) {
60137
- const sessionKey = `${this.transportKeyPrefix}${sessionId}`;
60561
+ const sessionKey = `${this.getSessionKeyPrefix()}${sessionId}`;
60138
60562
  const result = await this.scope.haManager.attemptTakeover(sessionKey, storedSession.session.nodeId);
60139
60563
  if (!result.claimed) {
60140
60564
  this.scope.logger.debug("[HA] Session already claimed by another pod", { sessionId: sessionId.slice(0, 20) });
@@ -60227,7 +60651,7 @@ var init_transport_registry = __esm({
60227
60651
  authorizationId: key.tokenHash,
60228
60652
  protocol: type === "sse" ? "sse" : "streamable-http",
60229
60653
  createdAt: Date.now(),
60230
- nodeId: (0, import_utils101.getMachineId)()
60654
+ nodeId: (0, import_utils105.getMachineId)()
60231
60655
  },
60232
60656
  authorizationId: key.tokenHash,
60233
60657
  createdAt: Date.now(),
@@ -60373,7 +60797,7 @@ var init_transport_registry = __esm({
60373
60797
  }
60374
60798
  /* --------------------------------- internals -------------------------------- */
60375
60799
  sha256(value) {
60376
- return (0, import_utils101.sha256Hex)(value);
60800
+ return (0, import_utils105.sha256Hex)(value);
60377
60801
  }
60378
60802
  /**
60379
60803
  * Create a history key from components.
@@ -60467,6 +60891,7 @@ var init_transport_registry = __esm({
60467
60891
  }
60468
60892
  }
60469
60893
  evictLocal(key) {
60894
+ this.lastTtlRefreshAt.delete(key.sessionId);
60470
60895
  const typeBucket = this.byType.get(key.type);
60471
60896
  if (!typeBucket) return;
60472
60897
  const tokenBucket = typeBucket.get(key.tokenHash);
@@ -60557,9 +60982,9 @@ function persistentSessionCallerKey(authorization) {
60557
60982
  const sub = authorization?.user?.sub;
60558
60983
  if (!(0, import_auth56.isAnonymousSubject)(sub)) {
60559
60984
  const iss = authorization?.user?.iss;
60560
- return `sub:${(0, import_utils102.sha256Hex)(JSON.stringify([typeof iss === "string" ? iss : "", sub]))}`;
60985
+ return `sub:${(0, import_utils106.sha256Hex)(JSON.stringify([typeof iss === "string" ? iss : "", sub]))}`;
60561
60986
  }
60562
- if (authorization?.token) return `token:${(0, import_utils102.sha256Hex)(authorization.token)}`;
60987
+ if (authorization?.token) return `token:${(0, import_utils106.sha256Hex)(authorization.token)}`;
60563
60988
  return null;
60564
60989
  }
60565
60990
  function claimPersistentSession(session, callerKey, store) {
@@ -60572,12 +60997,12 @@ function claimPersistentSession(session, callerKey, store) {
60572
60997
  }
60573
60998
  return owners.get(session) === callerKey ? "owner" : "refused";
60574
60999
  }
60575
- var import_auth56, import_utils102, owners;
61000
+ var import_auth56, import_utils106, owners;
60576
61001
  var init_persistent_session_owner = __esm({
60577
61002
  "libs/sdk/src/transport/persistent-session-owner.ts"() {
60578
61003
  "use strict";
60579
61004
  import_auth56 = require("@frontmcp/auth");
60580
- import_utils102 = require("@frontmcp/utils");
61005
+ import_utils106 = require("@frontmcp/utils");
60581
61006
  owners = /* @__PURE__ */ new WeakMap();
60582
61007
  }
60583
61008
  });
@@ -60614,7 +61039,7 @@ async function runWebStandardMcp(scope, request, options) {
60614
61039
  if (options.persistent) {
60615
61040
  return options.persistent.transport.handleRequest(request, { authInfo: options.authInfo });
60616
61041
  }
60617
- const sessionId = `web:${(0, import_utils103.randomUUID)()}`;
61042
+ const sessionId = `web:${(0, import_utils107.randomUUID)()}`;
60618
61043
  const accept = request.headers.get("accept") ?? "";
60619
61044
  const wantsStream = request.method === "GET" || options.sse && accept.includes("text/event-stream");
60620
61045
  const { mcpServer, transport } = await wireServer(scope, options.serverOptions, sessionId, void 0, !wantsStream);
@@ -60637,23 +61062,23 @@ async function runWebStandardMcp(scope, request, options) {
60637
61062
  }
60638
61063
  return response;
60639
61064
  }
60640
- var import_utils103;
61065
+ var import_utils107;
60641
61066
  var init_web_standard_mcp = __esm({
60642
61067
  "libs/sdk/src/transport/web-standard-mcp.ts"() {
60643
61068
  "use strict";
60644
- import_utils103 = require("@frontmcp/utils");
61069
+ import_utils107 = require("@frontmcp/utils");
60645
61070
  init_build_scoped_server_options();
60646
61071
  }
60647
61072
  });
60648
61073
 
60649
61074
  // libs/sdk/src/scope/flows/http.request.flow.ts
60650
- var import_auth57, import_lazy_zod108, import_utils104, plan42, httpRequestStateSchema, name43, Stage43, HttpRequestFlow;
61075
+ var import_auth57, import_lazy_zod108, import_utils108, plan42, httpRequestStateSchema, name43, Stage43, HttpRequestFlow;
60651
61076
  var init_http_request_flow = __esm({
60652
61077
  "libs/sdk/src/scope/flows/http.request.flow.ts"() {
60653
61078
  "use strict";
60654
61079
  import_auth57 = require("@frontmcp/auth");
60655
61080
  import_lazy_zod108 = require("@frontmcp/lazy-zod");
60656
- import_utils104 = require("@frontmcp/utils");
61081
+ import_utils108 = require("@frontmcp/utils");
60657
61082
  init_session_verify_flow();
60658
61083
  init_common();
60659
61084
  init_schema7();
@@ -60732,7 +61157,7 @@ var init_http_request_flow = __esm({
60732
61157
  const { request } = this.rawInput;
60733
61158
  this.requestStartTime = Date.now();
60734
61159
  const ctx = this.tryGetContext();
60735
- this.requestId = ctx?.requestId ?? `req-${(0, import_utils104.randomUUID)()}`;
61160
+ this.requestId = ctx?.requestId ?? `req-${(0, import_utils108.randomUUID)()}`;
60736
61161
  const headers = request.headers ?? {};
60737
61162
  const body = request.body;
60738
61163
  const userAgent = headers["user-agent"];
@@ -61417,13 +61842,13 @@ var init_http_request_flow = __esm({
61417
61842
  });
61418
61843
 
61419
61844
  // libs/sdk/src/scope/scope.instance.ts
61420
- var import_reflect_metadata25, import_guard8, import_utils105, SCOPE_NON_DISPATCHABLE_FLOWS, Scope;
61845
+ var import_reflect_metadata25, import_guard8, import_utils109, SCOPE_NON_DISPATCHABLE_FLOWS, Scope;
61421
61846
  var init_scope_instance = __esm({
61422
61847
  "libs/sdk/src/scope/scope.instance.ts"() {
61423
61848
  "use strict";
61424
61849
  import_reflect_metadata25 = require("reflect-metadata");
61425
61850
  import_guard8 = require("@frontmcp/guard");
61426
- import_utils105 = require("@frontmcp/utils");
61851
+ import_utils109 = require("@frontmcp/utils");
61427
61852
  init_agent_registry();
61428
61853
  init_call_agent_flow();
61429
61854
  init_app_registry();
@@ -61464,6 +61889,7 @@ var init_scope_instance = __esm({
61464
61889
  init_tool_registry();
61465
61890
  init_tool_utils();
61466
61891
  init_ui();
61892
+ init_ui_option_warnings();
61467
61893
  init_bus();
61468
61894
  init_event_stores();
61469
61895
  init_request_state();
@@ -61490,6 +61916,8 @@ var init_scope_instance = __esm({
61490
61916
  transportService;
61491
61917
  // TODO: migrate transport service to transport.registry
61492
61918
  notificationService;
61919
+ /** ioredis client owned by the HA manager and transport bus; closed in shutdown(). */
61920
+ haRedisClient;
61493
61921
  toolUIRegistry;
61494
61922
  authUiRegistry;
61495
61923
  entryPath;
@@ -61557,7 +61985,7 @@ var init_scope_instance = __esm({
61557
61985
  await this.scopeProviders.ready;
61558
61986
  const scopeRef = { kind: "scope", id: this.id, ref: _Scope };
61559
61987
  const scopeProviders = this.scopeProviders;
61560
- const perf = (0, import_utils105.getEnvFlag)("FRONTMCP_PERF");
61988
+ const perf = (0, import_utils109.getEnvFlag)("FRONTMCP_PERF");
61561
61989
  const t0 = perf ? performance.now() : 0;
61562
61990
  const mark = perf ? (label) => this.logger.info(`[PERF] ${label}: ${(performance.now() - t0).toFixed(1)}ms`) : () => {
61563
61991
  };
@@ -61567,24 +61995,26 @@ var init_scope_instance = __esm({
61567
61995
  this.scopeApps = new AppRegistry(this.scopeProviders, this.metadata.apps, scopeRef);
61568
61996
  this.logger.info(`Initializing ${this.metadata.apps.length} app(s)...`);
61569
61997
  const transportConfig = this.metadata.transport;
61570
- const isDistributed = (0, import_utils105.getRuntimeContext)().deployment === "distributed";
61998
+ const isDistributed = (0, import_utils109.getRuntimeContext)().deployment === "distributed";
61571
61999
  if (isDistributed && this.metadata.redis && !this.cliMode) {
61572
62000
  try {
61573
- const haRedis = this.metadata.redis;
62001
+ const haRedis = this.createHaRedisClient();
61574
62002
  this.haManager = HaManager.create({
61575
62003
  redis: haRedis,
61576
- nodeId: (0, import_utils105.getMachineId)(),
62004
+ nodeId: (0, import_utils109.getMachineId)(),
61577
62005
  logger: this.logger
61578
62006
  });
61579
62007
  await this.haManager.start();
61580
62008
  this.logger.info("[HA] Manager started for distributed deployment");
61581
62009
  } catch (err) {
61582
62010
  this.logger.warn("[HA] Failed to start HA manager \u2014 running without HA", { error: err });
62011
+ this.haManager = void 0;
62012
+ await this.closeHaRedisClient();
61583
62013
  }
61584
62014
  }
61585
62015
  let transportBus;
61586
- if (this.haManager && isDistributed && this.metadata.redis) {
61587
- transportBus = new RedisTransportBus(this.metadata.redis, (0, import_utils105.getMachineId)(), {
62016
+ if (this.haManager && this.haRedisClient && isDistributed) {
62017
+ transportBus = new RedisTransportBus(this.haRedisClient, (0, import_utils109.getMachineId)(), {
61588
62018
  logger: this.logger
61589
62019
  });
61590
62020
  this.logger.info("[HA] Transport bus created for distributed session routing");
@@ -61596,11 +62026,9 @@ var init_scope_instance = __esm({
61596
62026
  }
61597
62027
  this.transportService = new TransportService(this, effectivePersistence, transportBus);
61598
62028
  warnIfRequestStateKeyNotShared({ logger: this.logger, metadata: this.metadata });
61599
- if (this.haManager && isDistributed) {
61600
- const persistCfg = transportConfig?.persistence;
61601
- const resolvedPrefix = persistCfg && typeof persistCfg === "object" && persistCfg.redis?.keyPrefix ? persistCfg.redis.keyPrefix : "mcp:transport:";
62029
+ if (this.haManager && isDistributed && this.transportService.getBackendKind() === "redis") {
61602
62030
  this.haManager.startOrphanScanner({
61603
- sessionKeyPrefix: resolvedPrefix,
62031
+ sessionKeyPrefix: this.transportService.getSessionKeyPrefix(),
61604
62032
  onOrphan: (sessionId, previousNodeId) => {
61605
62033
  this.logger.info(
61606
62034
  `[HA] Orphaned session ${sessionId.slice(0, 20)} from ${previousNodeId} \u2014 available for recreation`
@@ -61608,7 +62036,7 @@ var init_scope_instance = __esm({
61608
62036
  }
61609
62037
  });
61610
62038
  }
61611
- if ((0, import_utils105.getRuntimeContext)().deployment === "serverless") {
62039
+ if ((0, import_utils109.getRuntimeContext)().deployment === "serverless") {
61612
62040
  const protocolCfg = transportConfig?.protocol;
61613
62041
  const hasSSE = typeof protocolCfg === "string" ? protocolCfg !== "stateless-api" : protocolCfg?.sse === true;
61614
62042
  if (hasSSE) {
@@ -61635,7 +62063,7 @@ var init_scope_instance = __esm({
61635
62063
  sqlite: elicitationSqlite,
61636
62064
  keyPrefix: elicitationRedis?.keyPrefix ?? "mcp:elicit:",
61637
62065
  logger: this.logger,
61638
- isEdgeRuntime: (0, import_utils105.isEdgeRuntime)()
62066
+ isEdgeRuntime: (0, import_utils109.isEdgeRuntime)()
61639
62067
  });
61640
62068
  this._elicitationStore = elicitStore;
61641
62069
  this.elicitationBackendType = elicitType;
@@ -61649,14 +62077,22 @@ var init_scope_instance = __esm({
61649
62077
  );
61650
62078
  const tasksEnabledForCli = this.cliMode && hasPersistentBackend;
61651
62079
  const hasDistributedTaskBackend = Boolean(tasksConfig?.redis ?? this.metadata.redis);
61652
- const onEdgeRuntime = (0, import_utils105.isEdgeRuntime)();
62080
+ const onEdgeRuntime = (0, import_utils109.isEdgeRuntime)();
61653
62081
  const tasksUnavailableOnEdge = onEdgeRuntime && !hasDistributedTaskBackend && !tasksExplicitlyEnabled;
61654
62082
  if (tasksUnavailableOnEdge) {
61655
62083
  this.logger.info(
61656
62084
  "[tasks] Background tasks are unavailable on this edge/serverless runtime: the in-process runner cannot continue past the HTTP response, and an in-memory store is not shared between isolates. Serving without tasks. Configure `tasks.redis` (Upstash Redis over HTTP works on Workers) to enable them."
61657
62085
  );
61658
62086
  }
61659
- const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
62087
+ const gateTasksRedis = tasksConfig?.redis ?? this.metadata.redis;
62088
+ const tasksUseSqlite = Boolean(tasksConfig?.sqlite ?? (gateTasksRedis ? void 0 : resolvedTopLevelSqlite));
62089
+ const tasksUnavailableOnVercelKv = !tasksExplicitlyEnabled && !tasksUseSqlite && resolvesToVercelKvTaskBackend(gateTasksRedis);
62090
+ if (tasksUnavailableOnVercelKv && !tasksExplicitlyDisabled) {
62091
+ this.logger.warn(
62092
+ "[tasks] Background tasks are disabled: Vercel KV has no pub/sub, which task results and cancellation require. Serving without tasks. Configure `tasks.redis` with Redis or Upstash to enable them."
62093
+ );
62094
+ }
62095
+ const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && !tasksUnavailableOnVercelKv && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
61660
62096
  const tasksPromise = shouldInitTasks ? (async () => {
61661
62097
  const tasksRedis = tasksConfig?.redis ?? this.metadata.redis;
61662
62098
  const tasksSqlite = tasksConfig?.sqlite ?? (tasksRedis ? void 0 : resolvedTopLevelSqlite);
@@ -62155,6 +62591,16 @@ var init_scope_instance = __esm({
62155
62591
  async compileUIWidgets() {
62156
62592
  const toolsWithUI = this.scopeTools.getTools(true).filter((t) => hasUIConfig(t.metadata));
62157
62593
  if (toolsWithUI.length === 0) return;
62594
+ for (const tool of toolsWithUI) {
62595
+ const resourceUri = tool.metadata.ui?.resourceUri;
62596
+ this.toolUIRegistry.registerTool(tool.metadata.name, typeof resourceUri === "string" ? resourceUri : void 0);
62597
+ for (const message of describeIgnoredUiOptions(
62598
+ tool.metadata.name,
62599
+ tool.metadata.ui
62600
+ )) {
62601
+ this.logger.warn(message);
62602
+ }
62603
+ }
62158
62604
  this.scopeResources.registerDynamicResource(StaticWidgetResourceTemplate);
62159
62605
  this.logger.verbose(`Registered UI resource template for ${toolsWithUI.length} tool(s) with UI configs`);
62160
62606
  const staticModeTools = toolsWithUI.filter(
@@ -62733,6 +63179,29 @@ var init_scope_instance = __esm({
62733
63179
  }
62734
63180
  throw new FlowExitedWithoutOutputError();
62735
63181
  }
63182
+ /**
63183
+ * Build the ioredis client shared by the HA manager and the transport bus from `metadata.redis`.
63184
+ * The client reconnects on its own and has a permanent, rate-limited 'error' listener.
63185
+ */
63186
+ createHaRedisClient() {
63187
+ const redis = this.metadata.redis;
63188
+ if (!redis || redis.provider === "vercel-kv") {
63189
+ throw new Error("Distributed HA requires a TCP Redis (ioredis); Vercel KV over REST has no Lua/pub-sub support");
63190
+ }
63191
+ const client = (0, import_utils109.createRedisClient)({ ...redis, label: "ha", logger: this.logger });
63192
+ this.haRedisClient = client;
63193
+ return client;
63194
+ }
63195
+ async closeHaRedisClient() {
63196
+ const client = this.haRedisClient;
63197
+ if (!client) return;
63198
+ this.haRedisClient = void 0;
63199
+ try {
63200
+ await client.quit();
63201
+ } catch {
63202
+ client.disconnect();
63203
+ }
63204
+ }
62736
63205
  /**
62737
63206
  * Shut down the scope and release resources.
62738
63207
  * Disconnects channel service connectors, unsubscribes event handlers,
@@ -62746,6 +63215,7 @@ var init_scope_instance = __esm({
62746
63215
  this.logger.error("[HA] Manager shutdown failed", { error: err });
62747
63216
  }
62748
63217
  }
63218
+ await this.closeHaRedisClient();
62749
63219
  if (this._channelTeardown) {
62750
63220
  try {
62751
63221
  await this._channelTeardown();
@@ -63867,6 +64337,178 @@ var init_scope_registry = __esm({
63867
64337
  }
63868
64338
  });
63869
64339
 
64340
+ // libs/sdk/src/transport/ipc-server.transport.ts
64341
+ function isIpcStdioRequested() {
64342
+ return !!process.env["FRONTMCP_DEV_STDIO_FD"] && typeof process.send === "function";
64343
+ }
64344
+ var IPC_READY_MESSAGE, IpcServerTransport;
64345
+ var init_ipc_server_transport = __esm({
64346
+ "libs/sdk/src/transport/ipc-server.transport.ts"() {
64347
+ "use strict";
64348
+ IPC_READY_MESSAGE = { __frontmcp: "ready" };
64349
+ IpcServerTransport = class {
64350
+ onclose;
64351
+ onerror;
64352
+ onmessage;
64353
+ started = false;
64354
+ handleMessage = (raw) => {
64355
+ if (!raw || typeof raw !== "object" || raw.__frontmcp !== void 0) return;
64356
+ this.onmessage?.(raw);
64357
+ };
64358
+ handleDisconnect = () => {
64359
+ this.onclose?.();
64360
+ };
64361
+ async start() {
64362
+ if (this.started) throw new Error("IpcServerTransport already started");
64363
+ if (typeof process.send !== "function") {
64364
+ throw new Error("IpcServerTransport requires a process forked with an IPC channel");
64365
+ }
64366
+ this.started = true;
64367
+ process.on("message", this.handleMessage);
64368
+ process.once("disconnect", this.handleDisconnect);
64369
+ process.send(IPC_READY_MESSAGE);
64370
+ }
64371
+ async send(message) {
64372
+ const send = process.send;
64373
+ if (!process.connected || typeof send !== "function") {
64374
+ throw new Error("IPC channel is closed");
64375
+ }
64376
+ await new Promise((resolve, reject) => {
64377
+ send.call(process, message, (err) => err ? reject(err) : resolve());
64378
+ });
64379
+ }
64380
+ async close() {
64381
+ process.off("message", this.handleMessage);
64382
+ process.off("disconnect", this.handleDisconnect);
64383
+ this.started = false;
64384
+ this.onclose?.();
64385
+ }
64386
+ };
64387
+ }
64388
+ });
64389
+
64390
+ // libs/sdk/src/server/middleware/csp.middleware.ts
64391
+ function readCspFromEnv() {
64392
+ if (!(0, import_utils110.getEnvFlag)("FRONTMCP_CSP_ENABLED")) return void 0;
64393
+ const rawDirectives = (0, import_utils110.getEnv)("FRONTMCP_CSP_DIRECTIVES") ?? "";
64394
+ const directives = {};
64395
+ for (const part of rawDirectives.split(";")) {
64396
+ const trimmed = part.trim();
64397
+ if (!trimmed) continue;
64398
+ const spaceIdx = trimmed.indexOf(" ");
64399
+ if (spaceIdx > 0) {
64400
+ directives[trimmed.slice(0, spaceIdx)] = trimmed.slice(spaceIdx + 1);
64401
+ } else {
64402
+ directives[trimmed] = "";
64403
+ }
64404
+ }
64405
+ return {
64406
+ enabled: true,
64407
+ directives,
64408
+ reportUri: (0, import_utils110.getEnv)("FRONTMCP_CSP_REPORT_URI"),
64409
+ reportOnly: (0, import_utils110.getEnvFlag)("FRONTMCP_CSP_REPORT_ONLY")
64410
+ };
64411
+ }
64412
+ function buildCspHeaderValue(options) {
64413
+ const parts = [];
64414
+ for (const [directive, value] of Object.entries(options.directives)) {
64415
+ parts.push(value ? `${directive} ${value}` : directive);
64416
+ }
64417
+ if (options.reportUri) {
64418
+ parts.push(`report-uri ${options.reportUri}`);
64419
+ }
64420
+ return parts.join("; ");
64421
+ }
64422
+ function getCspHeaderName(reportOnly) {
64423
+ return reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
64424
+ }
64425
+ function readSecurityHeadersFromEnv() {
64426
+ return {
64427
+ hsts: readHeaderEnv("FRONTMCP_HSTS"),
64428
+ contentTypeOptions: readHeaderEnv("FRONTMCP_CONTENT_TYPE_OPTIONS", "nosniff"),
64429
+ frameOptions: readHeaderEnv("FRONTMCP_FRAME_OPTIONS", "DENY"),
64430
+ custom: readCustomHeadersFromEnv()
64431
+ };
64432
+ }
64433
+ function readHeaderEnv(name44, fallback) {
64434
+ const value = (0, import_utils110.getEnv)(name44);
64435
+ if (value === void 0 || value === "") return fallback;
64436
+ return ["off", "false", "none"].includes(value.trim().toLowerCase()) ? void 0 : value;
64437
+ }
64438
+ function readCustomHeadersFromEnv() {
64439
+ const raw = (0, import_utils110.getEnv)("FRONTMCP_HEADERS_CUSTOM");
64440
+ if (!raw) return void 0;
64441
+ try {
64442
+ const parsed = JSON.parse(raw);
64443
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
64444
+ return Object.fromEntries(
64445
+ Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
64446
+ );
64447
+ } catch {
64448
+ return void 0;
64449
+ }
64450
+ }
64451
+ function applySecurityHeaders(res, headers, csp) {
64452
+ if (headers.hsts) {
64453
+ res.setHeader("Strict-Transport-Security", headers.hsts);
64454
+ }
64455
+ if (headers.contentTypeOptions) {
64456
+ res.setHeader("X-Content-Type-Options", headers.contentTypeOptions);
64457
+ }
64458
+ if (headers.frameOptions) {
64459
+ res.setHeader("X-Frame-Options", headers.frameOptions);
64460
+ }
64461
+ if (headers.custom) {
64462
+ for (const [name44, value] of Object.entries(headers.custom)) {
64463
+ res.setHeader(name44, value);
64464
+ }
64465
+ }
64466
+ if (csp?.enabled) {
64467
+ const headerName = getCspHeaderName(csp.reportOnly);
64468
+ const headerValue = buildCspHeaderValue(csp);
64469
+ if (headerValue) {
64470
+ res.setHeader(headerName, headerValue);
64471
+ }
64472
+ }
64473
+ }
64474
+ function resolveSecurityHeaders(options = {}) {
64475
+ const env = readSecurityHeadersFromEnv();
64476
+ const pick = (explicit, fromEnv) => explicit === false ? void 0 : explicit ?? fromEnv;
64477
+ const resolved = {
64478
+ hsts: pick(options.hsts, env.hsts),
64479
+ contentTypeOptions: pick(options.contentTypeOptions, env.contentTypeOptions),
64480
+ frameOptions: pick(options.frameOptions, env.frameOptions),
64481
+ custom: { ...env.custom, ...options.custom }
64482
+ };
64483
+ let csp;
64484
+ if (options.csp?.enabled === false) {
64485
+ csp = void 0;
64486
+ } else if (options.csp?.enabled === true) {
64487
+ const directives = {};
64488
+ for (const [name44, value] of Object.entries(options.csp.directives ?? {})) {
64489
+ directives[name44] = Array.isArray(value) ? value.join(" ") : value;
64490
+ }
64491
+ csp = {
64492
+ enabled: true,
64493
+ directives,
64494
+ reportUri: options.csp.reportUri,
64495
+ reportOnly: options.csp.reportOnly ?? false
64496
+ };
64497
+ } else {
64498
+ csp = readCspFromEnv();
64499
+ }
64500
+ const headers = {};
64501
+ applySecurityHeaders({ setHeader: (name44, value) => void (headers[name44] = value) }, resolved, csp);
64502
+ return headers;
64503
+ }
64504
+ var import_utils110;
64505
+ var init_csp_middleware = __esm({
64506
+ "libs/sdk/src/server/middleware/csp.middleware.ts"() {
64507
+ "use strict";
64508
+ import_utils110 = require("@frontmcp/utils");
64509
+ }
64510
+ });
64511
+
63870
64512
  // libs/sdk/src/transport/web-response.renderer.ts
63871
64513
  function toByteStream(streamLike) {
63872
64514
  const iter = streamLike[Symbol.asyncIterator];
@@ -63971,6 +64613,7 @@ function isCloudflareWorkersRuntime(request) {
63971
64613
  return typeof cloudflareProperties === "object" && cloudflareProperties !== null;
63972
64614
  }
63973
64615
  function resolvePeerAddress(request, ctx) {
64616
+ if (carriedPeerAddresses.has(request)) return carriedPeerAddresses.get(request);
63974
64617
  if (typeof ctx?.requestIP === "function") return ctx.requestIP(request)?.address;
63975
64618
  if (typeof ctx?.remoteAddr?.hostname === "string") return ctx.remoteAddr.hostname;
63976
64619
  if (isCloudflareWorkersRuntime(request)) return request.headers.get("cf-connecting-ip") ?? void 0;
@@ -63989,7 +64632,13 @@ function mapHttpCors(httpCors, scope) {
63989
64632
  }
63990
64633
  function createWebFetchHandler(scope, options = {}) {
63991
64634
  const httpConfig = scope.metadata.http;
63992
- const healthPaths = new Set(options.healthPaths ?? ["/healthz", "/readyz"]);
64635
+ const healthConfig = scope.metadata.health ?? {};
64636
+ const healthEnabled = healthConfig.enabled !== false;
64637
+ const healthzPath = healthConfig.healthzPath ?? "/healthz";
64638
+ const readyzPath = healthConfig.readyzPath ?? "/readyz";
64639
+ const readyzEnabled = isReadyzEnabled(healthConfig);
64640
+ const bodyLimit = parseByteLimit(httpConfig?.bodyLimit);
64641
+ const securityHeaders = resolveSecurityHeaders(httpConfig?.securityHeaders);
63993
64642
  const normalizePath = (p) => {
63994
64643
  const withSlash = p.startsWith("/") ? p : `/${p}`;
63995
64644
  let end = withSlash.length;
@@ -64033,7 +64682,13 @@ function createWebFetchHandler(scope, options = {}) {
64033
64682
  for (const [k, v] of Object.entries(corsHeadersFor(request))) headers.set(k, v);
64034
64683
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
64035
64684
  };
64036
- return async function handle(request, ctx, env) {
64685
+ const withSecurityHeaders = (response) => {
64686
+ const headers = new Headers(response.headers);
64687
+ for (const [k, v] of Object.entries(securityHeaders)) if (!headers.has(k)) headers.set(k, v);
64688
+ headers.delete("x-powered-by");
64689
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
64690
+ };
64691
+ const handleRequest = async (request, ctx, env) => {
64037
64692
  const url = new URL(request.url);
64038
64693
  if (hostValidation) {
64039
64694
  const rejection = validateHostHeaders(
@@ -64048,17 +64703,34 @@ function createWebFetchHandler(scope, options = {}) {
64048
64703
  return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
64049
64704
  }
64050
64705
  }
64706
+ const limited = await enforceBodyLimit(request, bodyLimit, ctx);
64707
+ if (limited instanceof Response) return withCors(limited, request);
64708
+ request = limited;
64051
64709
  if (corsEnabled && request.method === "OPTIONS") {
64052
64710
  return new Response(null, { status: 204, headers: corsHeadersFor(request) });
64053
64711
  }
64054
- if (healthPaths.has(url.pathname)) {
64055
- return withCors(
64056
- Response.json(
64057
- { status: "ok", server: scope.metadata.info, transport: "web-fetch" },
64058
- { headers: { "Cache-Control": "no-store" } }
64059
- ),
64060
- request
64061
- );
64712
+ if (options.healthPaths ? options.healthPaths.includes(url.pathname) : healthEnabled) {
64713
+ const isReadyz = !options.healthPaths && url.pathname === readyzPath && readyzEnabled;
64714
+ const isHealthz = options.healthPaths ? url.pathname !== "/readyz" : url.pathname === healthzPath || url.pathname === "/health";
64715
+ if (isReadyz && scope.healthService) {
64716
+ const readiness = await scope.healthService.getReadyz();
64717
+ return withCors(
64718
+ Response.json(
64719
+ { ...readiness, transport: "web-fetch" },
64720
+ { status: readiness.status === "ready" ? 200 : 503, headers: { "Cache-Control": "no-store" } }
64721
+ ),
64722
+ request
64723
+ );
64724
+ }
64725
+ if (isHealthz || isReadyz || options.healthPaths) {
64726
+ return withCors(
64727
+ Response.json(
64728
+ { status: "ok", server: scope.metadata.info, transport: "web-fetch" },
64729
+ { headers: { "Cache-Control": "no-store" } }
64730
+ ),
64731
+ request
64732
+ );
64733
+ }
64062
64734
  }
64063
64735
  if (!entryPaths.has(normalizePath(url.pathname))) {
64064
64736
  const authResponse = await runMatchingHttpFlowWeb(scope, request, { ctx, env });
@@ -64072,13 +64744,60 @@ function createWebFetchHandler(scope, options = {}) {
64072
64744
  const rendered = await runHttpRequestFlowWeb(scope, request, { ctx, env });
64073
64745
  return withCors(rendered ?? Response.json({ error: "Not Found" }, { status: 404 }), request);
64074
64746
  };
64747
+ return async function handle(request, ctx, env) {
64748
+ return withSecurityHeaders(await handleRequest(request, ctx, env));
64749
+ };
64750
+ }
64751
+ function parseByteLimit(limit) {
64752
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) return limit;
64753
+ if (typeof limit === "string") {
64754
+ const match = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i.exec(limit.trim());
64755
+ if (match) return Math.floor(Number(match[1]) * BYTE_UNITS[(match[2] ?? "b").toLowerCase()]);
64756
+ }
64757
+ return DEFAULT_BODY_LIMIT_BYTES;
64758
+ }
64759
+ function payloadTooLarge(limit, length) {
64760
+ const error = new PayloadTooLargeError(limit, length);
64761
+ return Response.json({ jsonrpc: "2.0", error: error.toJsonRpcError(), id: null }, { status: 413 });
64762
+ }
64763
+ async function enforceBodyLimit(request, limit, ctx) {
64764
+ const method = request.method.toUpperCase();
64765
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS" || !request.body) return request;
64766
+ const declared = request.headers.get("content-length");
64767
+ if (declared !== null) {
64768
+ const length = Number(declared);
64769
+ if (Number.isFinite(length) && length > limit) return payloadTooLarge(limit, length);
64770
+ }
64771
+ const peerAddress = resolvePeerAddress(request, ctx);
64772
+ const reader = request.body.getReader();
64773
+ const chunks = [];
64774
+ let total = 0;
64775
+ for (; ; ) {
64776
+ const { done, value } = await reader.read();
64777
+ if (done) break;
64778
+ total += value.byteLength;
64779
+ if (total > limit) {
64780
+ await reader.cancel().catch(() => void 0);
64781
+ return payloadTooLarge(limit, total);
64782
+ }
64783
+ chunks.push(value);
64784
+ }
64785
+ const buffered = new Uint8Array(total);
64786
+ let offset = 0;
64787
+ for (const chunk of chunks) {
64788
+ buffered.set(chunk, offset);
64789
+ offset += chunk.byteLength;
64790
+ }
64791
+ const rebuilt = new Request(request.url, { method: request.method, headers: request.headers, body: buffered });
64792
+ carriedPeerAddresses.set(rebuilt, peerAddress);
64793
+ return rebuilt;
64075
64794
  }
64076
64795
  async function runHttpRequestFlowWeb(scope, request, opts = {}) {
64077
64796
  const url = new URL(request.url);
64078
64797
  const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent, opts.env);
64079
64798
  let output;
64080
64799
  try {
64081
- output = await (0, import_utils106.runRequestExclusive)(
64800
+ output = await (0, import_utils111.runRequestExclusive)(
64082
64801
  () => scope.runFlow("http:request", {
64083
64802
  request: serverRequest,
64084
64803
  response: {}
@@ -64096,7 +64815,7 @@ async function runMatchingHttpFlowWeb(scope, request, opts = {}) {
64096
64815
  if (!flowName) return void 0;
64097
64816
  let output;
64098
64817
  try {
64099
- output = await (0, import_utils106.runRequestExclusive)(
64818
+ output = await (0, import_utils111.runRequestExclusive)(
64100
64819
  () => scope.runFlow(flowName, {
64101
64820
  request: serverRequest,
64102
64821
  response: {}
@@ -64162,22 +64881,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
64162
64881
  function misconfigurationResponse(error) {
64163
64882
  const misconfiguration = findMisconfiguration(error);
64164
64883
  if (!misconfiguration) return void 0;
64165
- return Response.json(
64166
- { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy },
64167
- { status: 500 }
64168
- );
64169
- }
64170
- function findMisconfiguration(error) {
64171
- let current = error;
64172
- for (let depth = 0; current instanceof Error && depth < 5; depth++) {
64173
- const code = current.code;
64174
- if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
64175
- return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
64176
- }
64177
- if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
64178
- current = current.cause;
64179
- }
64180
- return void 0;
64884
+ return Response.json(misconfigurationBody(misconfiguration), { status: 500 });
64181
64885
  }
64182
64886
  function createDeferredServerBuild(build, options = {}) {
64183
64887
  const now = options.now ?? Date.now;
@@ -64232,7 +64936,7 @@ function flowErrorToHttpOutput(error) {
64232
64936
  kind: "json",
64233
64937
  status: 500,
64234
64938
  contentType: "application/json; charset=utf-8",
64235
- body: { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy }
64939
+ body: misconfigurationBody(misconfiguration)
64236
64940
  };
64237
64941
  }
64238
64942
  if (error instanceof FlowControl) {
@@ -64258,27 +64962,24 @@ function flowErrorToHttpOutput(error) {
64258
64962
  }
64259
64963
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
64260
64964
  }
64261
- var import_utils106, CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
64965
+ var import_utils111, CLOUDFLARE_WORKERS_USER_AGENT, carriedPeerAddresses, DEFAULT_BODY_LIMIT_BYTES, BYTE_UNITS, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
64262
64966
  var init_web_fetch_handler = __esm({
64263
64967
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
64264
64968
  "use strict";
64265
- import_utils106 = require("@frontmcp/utils");
64969
+ import_utils111 = require("@frontmcp/utils");
64266
64970
  init_common();
64267
64971
  init_server_tokens();
64268
64972
  init_path_utils();
64269
64973
  init_errors();
64974
+ init_misconfiguration();
64975
+ init_health_routes();
64976
+ init_csp_middleware();
64270
64977
  init_host_validation();
64271
64978
  init_web_response_renderer();
64272
64979
  CLOUDFLARE_WORKERS_USER_AGENT = "Cloudflare-Workers";
64273
- MISCONFIGURATION_REMEDIES = {
64274
- SESSION_SECRET_REQUIRED: "Set MCP_SESSION_SECRET in the deployment environment (e.g. `wrangler secret put MCP_SESSION_SECRET`). Session IDs are encrypted with it, and production refuses the development machine-id fallback.",
64275
- JWT_SECRET_INVALID: "JWT_SECRET is present but too weak: HS256 requires at least 32 bytes (RFC 7518). Replace it with `openssl rand -hex 32`.",
64276
- JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances.",
64277
- // The startup checks: a server whose entries ask for protection nothing gives them does not start.
64278
- UNENFORCED_METADATA: "An entry declares a field only a plugin enforces (approval, featureFlag, ...) and no installed plugin that enforces it reaches the entry, so the server refuses to start. Install the plugin or remove the field; the server log names the entries.",
64279
- AUTH_CONFIGURATION_ERROR: "The auth or authorities configuration is invalid (for example, entries declare authorities and the server has no authorities option, or a rule checks nothing), so the server refuses to start. The server log names the entries."
64280
- };
64281
- CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
64980
+ carriedPeerAddresses = /* @__PURE__ */ new WeakMap();
64981
+ DEFAULT_BODY_LIMIT_BYTES = 4 * 1024 * 1024;
64982
+ BYTE_UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };
64282
64983
  STARTUP_RETRY_MIN_MS = 1e3;
64283
64984
  STARTUP_RETRY_MAX_MS = 6e4;
64284
64985
  }
@@ -64360,13 +65061,13 @@ function auditSecurityDefaults(config, isProduction7) {
64360
65061
  message: `Server bound to ${bindAddress}.`
64361
65062
  });
64362
65063
  }
64363
- const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled ?? strict;
65064
+ const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled !== false;
64364
65065
  if (!dnsProtectionEnabled) {
64365
65066
  findings.push({
64366
65067
  level: "warn",
64367
65068
  code: "DNS_REBINDING_UNPROTECTED",
64368
- message: "DNS rebinding protection is disabled.",
64369
- recommendation: "Enable security.dnsRebindingProtection with allowedHosts to prevent DNS rebinding attacks."
65069
+ message: "DNS rebinding protection is disabled (security.dnsRebindingProtection.enabled is false).",
65070
+ recommendation: "Remove `enabled: false`, or set security.dnsRebindingProtection.allowedHosts, to prevent DNS rebinding attacks."
64370
65071
  });
64371
65072
  } else {
64372
65073
  findings.push({
@@ -64546,15 +65247,16 @@ var init_base_host_adapter = __esm({
64546
65247
  });
64547
65248
 
64548
65249
  // libs/sdk/src/server/adapters/express.host.adapter.ts
64549
- var http, import_cors, import_express, import_utils107, DEFAULT_EXPRESS_BODY_LIMIT, ExpressHostAdapter;
65250
+ var http, import_cors, import_express, import_utils112, DEFAULT_EXPRESS_BODY_LIMIT, ExpressHostAdapter;
64550
65251
  var init_express_host_adapter = __esm({
64551
65252
  "libs/sdk/src/server/adapters/express.host.adapter.ts"() {
64552
65253
  "use strict";
64553
65254
  http = __toESM(require("node:http"));
64554
65255
  import_cors = __toESM(require("cors"));
64555
65256
  import_express = __toESM(require("express"));
64556
- import_utils107 = require("@frontmcp/utils");
65257
+ import_utils112 = require("@frontmcp/utils");
64557
65258
  init_mcp_error();
65259
+ init_csp_middleware();
64558
65260
  init_host_validation_middleware();
64559
65261
  init_resolve_allowed_hosts();
64560
65262
  init_base_host_adapter();
@@ -64569,6 +65271,12 @@ var init_express_host_adapter = __esm({
64569
65271
  deriveHostValidation;
64570
65272
  constructor(options) {
64571
65273
  super();
65274
+ this.app.disable("x-powered-by");
65275
+ const securityHeaders = resolveSecurityHeaders(options?.securityHeaders);
65276
+ this.app.use((_req, res, next) => {
65277
+ for (const [name44, value] of Object.entries(securityHeaders)) res.setHeader(name44, value);
65278
+ next();
65279
+ });
64572
65280
  const corsOptions = options?.cors;
64573
65281
  const corsEnabled = corsOptions?.origin !== void 0 && corsOptions.origin !== false;
64574
65282
  if (corsEnabled) {
@@ -64736,8 +65444,8 @@ var init_express_host_adapter = __esm({
64736
65444
  }
64737
65445
  async cleanupStaleSocket(socketPath) {
64738
65446
  try {
64739
- if (await (0, import_utils107.fileExists)(socketPath)) {
64740
- await (0, import_utils107.unlink)(socketPath);
65447
+ if (await (0, import_utils112.fileExists)(socketPath)) {
65448
+ await (0, import_utils112.unlink)(socketPath);
64741
65449
  }
64742
65450
  } catch {
64743
65451
  }
@@ -64760,11 +65468,11 @@ var init_node_express_host = __esm({
64760
65468
  });
64761
65469
 
64762
65470
  // libs/sdk/src/server/server.instance.ts
64763
- var import_utils108, DEFAULT_CORS, FrontMcpServerInstance;
65471
+ var import_utils113, DEFAULT_CORS, FrontMcpServerInstance;
64764
65472
  var init_server_instance = __esm({
64765
65473
  "libs/sdk/src/server/server.instance.ts"() {
64766
65474
  "use strict";
64767
- import_utils108 = require("@frontmcp/utils");
65475
+ import_utils113 = require("@frontmcp/utils");
64768
65476
  init_common();
64769
65477
  init_health2();
64770
65478
  init_metrics2();
@@ -64796,6 +65504,7 @@ var init_server_instance = __esm({
64796
65504
  this.host = new ExpressHostAdapter2({
64797
65505
  ...corsConfig ? { cors: corsConfig } : {},
64798
65506
  ...this.config.security ? { security: this.config.security } : {},
65507
+ ...this.config.securityHeaders ? { securityHeaders: this.config.securityHeaders } : {},
64799
65508
  ...this.config.bodyLimit !== void 0 ? { bodyLimit: this.config.bodyLimit } : {},
64800
65509
  ...this.config.urlencodedLimit !== void 0 ? { urlencodedLimit: this.config.urlencodedLimit } : {}
64801
65510
  });
@@ -64866,7 +65575,7 @@ var init_server_instance = __esm({
64866
65575
  }
64867
65576
  async start() {
64868
65577
  this.prepare();
64869
- const deploymentMode = (0, import_utils108.getRuntimeContext)().deployment;
65578
+ const deploymentMode = (0, import_utils113.getRuntimeContext)().deployment;
64870
65579
  const bindAddress = resolveBindAddress(this.config.security, deploymentMode);
64871
65580
  const isProduction7 = process.env["NODE_ENV"] === "production";
64872
65581
  const findings = auditSecurityDefaults(
@@ -65104,11 +65813,11 @@ function resolveConfigInput(optionsOrClass) {
65104
65813
  }
65105
65814
  return optionsOrClass;
65106
65815
  }
65107
- var import_utils109, stdioServing, FrontMcpInstance;
65816
+ var import_utils114, stdioServing, FrontMcpInstance;
65108
65817
  var init_front_mcp = __esm({
65109
65818
  "libs/sdk/src/front-mcp/front-mcp.ts"() {
65110
65819
  "use strict";
65111
- import_utils109 = require("@frontmcp/utils");
65820
+ import_utils114 = require("@frontmcp/utils");
65112
65821
  init_common();
65113
65822
  init_direct();
65114
65823
  init_errors();
@@ -65120,6 +65829,7 @@ var init_front_mcp = __esm({
65120
65829
  init_scope_registry();
65121
65830
  init_skill_instructions_helper();
65122
65831
  init_task();
65832
+ init_ipc_server_transport();
65123
65833
  init_web_fetch_handler();
65124
65834
  init_front_mcp_providers();
65125
65835
  init_static_startup_check();
@@ -65239,7 +65949,13 @@ var init_front_mcp = __esm({
65239
65949
  }
65240
65950
  }
65241
65951
  static async bootstrap(options) {
65242
- const parsedConfig = frontMcpMetadataSchema.parse(options);
65952
+ const sqlitePath = process.env["FRONTMCP_SQLITE_PATH"];
65953
+ const parsedConfig = frontMcpMetadataSchema.parse(
65954
+ sqlitePath ? {
65955
+ ...options,
65956
+ sqlite: { ...options.sqlite ?? {}, path: sqlitePath }
65957
+ } : options
65958
+ );
65243
65959
  const daemonSocket = process.env["FRONTMCP_DAEMON_SOCKET"];
65244
65960
  if (daemonSocket) {
65245
65961
  await _FrontMcpInstance2.runUnixSocket({ ...parsedConfig, socketPath: daemonSocket });
@@ -65312,7 +66028,7 @@ var init_front_mcp = __esm({
65312
66028
  frontMcp.log?.info("FrontMCP fetch handler created (web-standard transport)");
65313
66029
  return createWebFetchHandler(scope);
65314
66030
  };
65315
- if (!(0, import_utils109.isEdgeRuntime)()) return build(false);
66031
+ if (!(0, import_utils114.isEdgeRuntime)()) return build(false);
65316
66032
  assertStaticStartupConfig(options);
65317
66033
  const server = createDeferredServerBuild(() => build(true));
65318
66034
  return async (request, ctx, env) => {
@@ -65457,8 +66173,8 @@ var init_front_mcp = __esm({
65457
66173
  frontMcp.log?.info(`MCP server listening on unix://${socketPath}`);
65458
66174
  const cleanup = async () => {
65459
66175
  try {
65460
- if (await (0, import_utils109.fileExists)(socketPath)) {
65461
- await (0, import_utils109.unlink)(socketPath);
66176
+ if (await (0, import_utils114.fileExists)(socketPath)) {
66177
+ await (0, import_utils114.unlink)(socketPath);
65462
66178
  }
65463
66179
  } catch {
65464
66180
  }
@@ -65602,7 +66318,7 @@ var init_front_mcp = __esm({
65602
66318
  };
65603
66319
  const mcpServer = new McpServer2(scope.metadata.info, serverOptions);
65604
66320
  startupCleanups.push(() => mcpServer.close());
65605
- const sessionId = `stdio:${(0, import_utils109.randomUUID)()}`;
66321
+ const sessionId = `stdio:${(0, import_utils114.randomUUID)()}`;
65606
66322
  const handlers = createMcpHandlers2({ scope, serverOptions, composeInstructions });
65607
66323
  for (const handler of handlers) {
65608
66324
  const originalHandler = handler.handler;
@@ -65616,7 +66332,7 @@ var init_front_mcp = __esm({
65616
66332
  };
65617
66333
  mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
65618
66334
  }
65619
- const transport = new StdioServerTransport();
66335
+ const transport = isIpcStdioRequested() ? new IpcServerTransport() : new StdioServerTransport();
65620
66336
  startupCleanups.push(() => transport.close());
65621
66337
  await mcpServer.connect(transport);
65622
66338
  startupSucceeded = true;
@@ -65922,9 +66638,9 @@ function FrontMcp(providedMetadata) {
65922
66638
  Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);
65923
66639
  }
65924
66640
  Reflect.defineMetadata(FRONTMCP_CONFIG_METADATA_KEY, metadata, target);
65925
- const isServerless2 = (0, import_utils110.getEnvFlag)("FRONTMCP_SERVERLESS");
65926
- const isSchemaExtract = (0, import_utils110.getEnvFlag)("FRONTMCP_SCHEMA_EXTRACT");
65927
- const isStdio = (0, import_utils110.getEnvFlag)("FRONTMCP_STDIO");
66641
+ const isServerless2 = (0, import_utils115.getEnvFlag)("FRONTMCP_SERVERLESS");
66642
+ const isSchemaExtract = (0, import_utils115.getEnvFlag)("FRONTMCP_SCHEMA_EXTRACT");
66643
+ const isStdio = (0, import_utils115.getEnvFlag)("FRONTMCP_STDIO");
65928
66644
  const taskWorkerId = typeof process !== "undefined" ? process.env?.["FRONTMCP_RUN_TASK_ID"] : void 0;
65929
66645
  if (taskWorkerId && metadata.serve !== false) {
65930
66646
  void (async () => {
@@ -65950,7 +66666,7 @@ function FrontMcp(providedMetadata) {
65950
66666
  } else if (isServerless2) {
65951
66667
  const ServerlessInstance = getFrontMcpInstance();
65952
66668
  const { setServerlessHandler: setServerlessHandler2, setServerlessHandlerPromise: setServerlessHandlerPromise2, setServerlessHandlerError: setServerlessHandlerError2 } = getServerlessHandlerFns();
65953
- const isWorker = (0, import_utils110.getEnvFlag)("FRONTMCP_WORKER");
66669
+ const isWorker = (0, import_utils115.getEnvFlag)("FRONTMCP_WORKER");
65954
66670
  const handlerPromise = isWorker ? ServerlessInstance.createFetchHandler(metadata) : ServerlessInstance.createHandler(metadata);
65955
66671
  setServerlessHandlerPromise2(handlerPromise);
65956
66672
  handlerPromise.then(setServerlessHandler2).catch((err) => {
@@ -65963,12 +66679,12 @@ function FrontMcp(providedMetadata) {
65963
66679
  }
65964
66680
  };
65965
66681
  }
65966
- var import_reflect_metadata28, import_utils110, FRONT_MCP_DECORATOR_BASENAMES, FRONTMCP_CONFIG_METADATA_KEY, _FrontMcpInstance, _serverlessHandlerFns;
66682
+ var import_reflect_metadata28, import_utils115, FRONT_MCP_DECORATOR_BASENAMES, FRONTMCP_CONFIG_METADATA_KEY, _FrontMcpInstance, _serverlessHandlerFns;
65967
66683
  var init_front_mcp_decorator = __esm({
65968
66684
  "libs/sdk/src/common/decorators/front-mcp.decorator.ts"() {
65969
66685
  "use strict";
65970
66686
  import_reflect_metadata28 = require("reflect-metadata");
65971
- import_utils110 = require("@frontmcp/utils");
66687
+ import_utils115 = require("@frontmcp/utils");
65972
66688
  init_decorator_errors();
65973
66689
  init_mcp_error();
65974
66690
  init_metadata();
@@ -66682,7 +67398,7 @@ var init_dynamic_adapter = __esm({
66682
67398
  * @param options - Adapter options including required `name` field
66683
67399
  * @throws Error if `name` is missing/empty or if a duplicate name is detected
66684
67400
  */
66685
- static init(options) {
67401
+ static init(options = {}) {
66686
67402
  const typedOptions = options;
66687
67403
  const adapterName = typedOptions.name;
66688
67404
  if (!adapterName || typeof adapterName !== "string" || adapterName.trim() === "") {
@@ -66884,12 +67600,12 @@ var init_adapter_entry = __esm({
66884
67600
  });
66885
67601
 
66886
67602
  // libs/sdk/src/common/entries/tool.entry.ts
66887
- var import_lazy_zod109, import_utils112, ToolEntry;
67603
+ var import_lazy_zod109, import_utils117, ToolEntry;
66888
67604
  var init_tool_entry = __esm({
66889
67605
  "libs/sdk/src/common/entries/tool.entry.ts"() {
66890
67606
  "use strict";
66891
67607
  import_lazy_zod109 = require("@frontmcp/lazy-zod");
66892
- import_utils112 = require("@frontmcp/utils");
67608
+ import_utils117 = require("@frontmcp/utils");
66893
67609
  init_base_entry();
66894
67610
  ToolEntry = class extends BaseEntry {
66895
67611
  owner;
@@ -66956,7 +67672,7 @@ var init_tool_entry = __esm({
66956
67672
  try {
66957
67673
  return (0, import_lazy_zod109.toJSONSchema)(import_lazy_zod109.z.object(this.inputSchema), { io: "input", unrepresentable: "any" });
66958
67674
  } catch (error) {
66959
- if ((0, import_utils112.isDebug)() || (0, import_utils112.isDevelopment)()) {
67675
+ if ((0, import_utils117.isDebug)() || (0, import_utils117.isDevelopment)()) {
66960
67676
  console.warn("[ToolEntry] Failed to convert Zod schema to JSON Schema:", error);
66961
67677
  }
66962
67678
  return { type: "object", properties: {} };
@@ -67002,7 +67718,7 @@ var init_tool_entry = __esm({
67002
67718
  const json = (0, import_lazy_zod109.toJSONSchema)(zodSchema);
67003
67719
  return json && json["type"] === "object" ? json : null;
67004
67720
  } catch (error) {
67005
- if ((0, import_utils112.isDebug)() || (0, import_utils112.isDevelopment)()) {
67721
+ if ((0, import_utils117.isDebug)() || (0, import_utils117.isDevelopment)()) {
67006
67722
  console.warn("[ToolEntry] Failed to convert output Zod schema to JSON Schema:", error);
67007
67723
  }
67008
67724
  return null;
@@ -68443,6 +69159,7 @@ __export(index_exports, {
68443
69159
  resolveSchemaDescriptionFormat: () => resolveSchemaDescriptionFormat,
68444
69160
  resolveToolVisibility: () => resolveToolVisibility,
68445
69161
  resolveWithFallbacks: () => resolveWithFallbacks,
69162
+ resolvesToVercelKvTaskBackend: () => resolvesToVercelKvTaskBackend,
68446
69163
  resource: () => frontMcpResource,
68447
69164
  resourceTemplate: () => frontMcpResourceTemplate,
68448
69165
  resourceUriMatches: () => resourceUriMatches,
@@ -68667,22 +69384,22 @@ function createMemorySkillProvider(options = {}) {
68667
69384
  init_skill_utils();
68668
69385
 
68669
69386
  // libs/sdk/src/skill/skill-directory-loader.ts
68670
- var import_utils113 = require("@frontmcp/utils");
69387
+ var import_utils118 = require("@frontmcp/utils");
68671
69388
  init_skill_metadata();
68672
69389
  init_sdk_errors();
68673
69390
  init_skill_md_parser();
68674
69391
  async function scanSkillResources(dirPath) {
68675
69392
  const resources = {};
68676
- const scriptsPath = (0, import_utils113.joinPath)(dirPath, "scripts");
68677
- const referencesPath = (0, import_utils113.joinPath)(dirPath, "references");
68678
- const assetsPath = (0, import_utils113.joinPath)(dirPath, "assets");
68679
- const examplesPath = (0, import_utils113.joinPath)(dirPath, "examples");
69393
+ const scriptsPath = (0, import_utils118.joinPath)(dirPath, "scripts");
69394
+ const referencesPath = (0, import_utils118.joinPath)(dirPath, "references");
69395
+ const assetsPath = (0, import_utils118.joinPath)(dirPath, "assets");
69396
+ const examplesPath = (0, import_utils118.joinPath)(dirPath, "examples");
68680
69397
  const checks = await Promise.all([
68681
69398
  checkDirectory(scriptsPath),
68682
69399
  checkDirectory(referencesPath),
68683
69400
  checkDirectory(assetsPath),
68684
69401
  checkDirectory(examplesPath),
68685
- (0, import_utils113.fileExists)((0, import_utils113.joinPath)(dirPath, "SKILL.md"))
69402
+ (0, import_utils118.fileExists)((0, import_utils118.joinPath)(dirPath, "SKILL.md"))
68686
69403
  ]);
68687
69404
  if (checks[0]) resources.scripts = scriptsPath;
68688
69405
  if (checks[1]) resources.references = referencesPath;
@@ -68695,7 +69412,7 @@ async function scanSkillResources(dirPath) {
68695
69412
  }
68696
69413
  async function checkDirectory(path3) {
68697
69414
  try {
68698
- const s = await (0, import_utils113.stat)(path3);
69415
+ const s = await (0, import_utils118.stat)(path3);
68699
69416
  return s.isDirectory();
68700
69417
  } catch (err) {
68701
69418
  if (err.code === "ENOENT") return false;
@@ -68708,16 +69425,16 @@ async function findNestedSkillMd(dirPath, maxDepth = Number.POSITIVE_INFINITY) {
68708
69425
  if (depth > maxDepth) return;
68709
69426
  let entries;
68710
69427
  try {
68711
- entries = await (0, import_utils113.readdir)(current);
69428
+ entries = await (0, import_utils118.readdir)(current);
68712
69429
  } catch (err) {
68713
69430
  if (err.code === "ENOENT") return;
68714
69431
  throw err;
68715
69432
  }
68716
69433
  for (const entry of entries) {
68717
- const full = (0, import_utils113.joinPath)(current, entry);
69434
+ const full = (0, import_utils118.joinPath)(current, entry);
68718
69435
  let entryStat;
68719
69436
  try {
68720
- entryStat = await (0, import_utils113.stat)(full);
69437
+ entryStat = await (0, import_utils118.stat)(full);
68721
69438
  } catch (err) {
68722
69439
  if (err.code === "ENOENT") continue;
68723
69440
  throw err;
@@ -68733,19 +69450,19 @@ async function findNestedSkillMd(dirPath, maxDepth = Number.POSITIVE_INFINITY) {
68733
69450
  return found.sort();
68734
69451
  }
68735
69452
  async function loadSkillDirectory(dirPath, logger) {
68736
- const skillMdPath = (0, import_utils113.joinPath)(dirPath, "SKILL.md");
68737
- const exists = await (0, import_utils113.fileExists)(skillMdPath);
69453
+ const skillMdPath = (0, import_utils118.joinPath)(dirPath, "SKILL.md");
69454
+ const exists = await (0, import_utils118.fileExists)(skillMdPath);
68738
69455
  if (!exists) {
68739
- throw new InvalidSkillError((0, import_utils113.basename)(dirPath), "SKILL.md not found in directory");
69456
+ throw new InvalidSkillError((0, import_utils118.basename)(dirPath), "SKILL.md not found in directory");
68740
69457
  }
68741
69458
  let content;
68742
69459
  let partialMetadata;
68743
69460
  try {
68744
- content = await (0, import_utils113.readFile)(skillMdPath, "utf-8");
69461
+ content = await (0, import_utils118.readFile)(skillMdPath, "utf-8");
68745
69462
  const { frontmatter, body } = parseSkillMdFrontmatter(content);
68746
69463
  partialMetadata = skillMdFrontmatterToMetadata(frontmatter, body);
68747
69464
  } catch (err) {
68748
- const name44 = (0, import_utils113.basename)(dirPath);
69465
+ const name44 = (0, import_utils118.basename)(dirPath);
68749
69466
  const message = err instanceof Error ? err.message : String(err);
68750
69467
  throw new InvalidSkillError(name44, `Failed to read or parse SKILL.md: ${message}`);
68751
69468
  }
@@ -68756,13 +69473,13 @@ async function loadSkillDirectory(dirPath, logger) {
68756
69473
  }
68757
69474
  const nested = await findNestedSkillMd(dirPath);
68758
69475
  if (nested.length > 0) {
68759
- const skillName2 = partialMetadata.name ?? (0, import_utils113.basename)(dirPath);
69476
+ const skillName2 = partialMetadata.name ?? (0, import_utils118.basename)(dirPath);
68760
69477
  throw new InvalidSkillError(
68761
69478
  skillName2,
68762
69479
  `Skill directory contains nested SKILL.md files (forbidden by SEP-2640 \xA7Resource Mapping): ${nested.join(", ")}`
68763
69480
  );
68764
69481
  }
68765
- const dirName = (0, import_utils113.basename)(dirPath);
69482
+ const dirName = (0, import_utils118.basename)(dirPath);
68766
69483
  if (partialMetadata.name && dirName && partialMetadata.name !== dirName) {
68767
69484
  const msg = `Skill name "${partialMetadata.name}" does not match directory name "${dirName}"`;
68768
69485
  if (logger) {
@@ -69686,6 +70403,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
69686
70403
  resolveSchemaDescriptionFormat,
69687
70404
  resolveToolVisibility,
69688
70405
  resolveWithFallbacks,
70406
+ resolvesToVercelKvTaskBackend,
69689
70407
  resource,
69690
70408
  resourceTemplate,
69691
70409
  resourceUriMatches,