@frontmcp/sdk 1.8.5 → 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 (85) hide show
  1. package/agent/flows/call-agent.flow.d.ts.map +1 -1
  2. package/auth/flows/session.verify.flow.d.ts.map +1 -1
  3. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  4. package/common/dynamic/dynamic.adapter.d.ts +1 -1
  5. package/common/dynamic/dynamic.adapter.d.ts.map +1 -1
  6. package/common/dynamic/dynamic.plugin.d.ts +18 -3
  7. package/common/dynamic/dynamic.plugin.d.ts.map +1 -1
  8. package/common/dynamic/dynamic.utils.d.ts +10 -1
  9. package/common/dynamic/dynamic.utils.d.ts.map +1 -1
  10. package/common/interfaces/execution-context.interface.d.ts +14 -0
  11. package/common/interfaces/execution-context.interface.d.ts.map +1 -1
  12. package/common/metadata/front-mcp.metadata.d.ts +127 -19
  13. package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
  14. package/common/types/options/http/index.d.ts +1 -1
  15. package/common/types/options/http/index.d.ts.map +1 -1
  16. package/common/types/options/http/interfaces.d.ts +36 -1
  17. package/common/types/options/http/interfaces.d.ts.map +1 -1
  18. package/common/types/options/http/schema.d.ts +12 -0
  19. package/common/types/options/http/schema.d.ts.map +1 -1
  20. package/common/types/options/transport/schema.d.ts +2 -2
  21. package/common/utils/list-all-pages.utils.d.ts +17 -0
  22. package/common/utils/list-all-pages.utils.d.ts.map +1 -0
  23. package/context/frontmcp-context-storage.d.ts.map +1 -1
  24. package/context/frontmcp-context.d.ts +11 -0
  25. package/context/frontmcp-context.d.ts.map +1 -1
  26. package/elicitation/store/elicitation-store.factory.d.ts.map +1 -1
  27. package/errors/misconfiguration.d.ts +30 -0
  28. package/errors/misconfiguration.d.ts.map +1 -0
  29. package/esm/index.mjs +1181 -325
  30. package/flows/flow.instance.d.ts.map +1 -1
  31. package/front-mcp/front-mcp.d.ts.map +1 -1
  32. package/ha/ha-headers.d.ts +16 -0
  33. package/ha/ha-headers.d.ts.map +1 -0
  34. package/health/health.probes.d.ts +1 -1
  35. package/health/health.probes.d.ts.map +1 -1
  36. package/index.js +1259 -407
  37. package/job/store/job-definition-store.factory.d.ts.map +1 -1
  38. package/job/store/job-state-store.factory.d.ts.map +1 -1
  39. package/notification/notification.service.d.ts +4 -1
  40. package/notification/notification.service.d.ts.map +1 -1
  41. package/package.json +10 -10
  42. package/plugin/plugin.registry.d.ts.map +1 -1
  43. package/remote-mcp/mcp-client.service.d.ts.map +1 -1
  44. package/remote-mcp/mcp-stateless-client.adapter.d.ts +25 -6
  45. package/remote-mcp/mcp-stateless-client.adapter.d.ts.map +1 -1
  46. package/remote-mcp/resilience/health-check.d.ts.map +1 -1
  47. package/scope/scope.instance.d.ts +8 -0
  48. package/scope/scope.instance.d.ts.map +1 -1
  49. package/server/adapters/express.host.adapter.d.ts +6 -1
  50. package/server/adapters/express.host.adapter.d.ts.map +1 -1
  51. package/server/middleware/csp.middleware.d.ts +12 -1
  52. package/server/middleware/csp.middleware.d.ts.map +1 -1
  53. package/server/security/security-audit.d.ts.map +1 -1
  54. package/server/server.instance.d.ts.map +1 -1
  55. package/skill/cache/skill-http-cache.factory.d.ts +0 -5
  56. package/skill/cache/skill-http-cache.factory.d.ts.map +1 -1
  57. package/task/store/index.d.ts +1 -1
  58. package/task/store/index.d.ts.map +1 -1
  59. package/task/store/task-store.factory.d.ts +6 -0
  60. package/task/store/task-store.factory.d.ts.map +1 -1
  61. package/tool/flows/call-tool.flow.d.ts +1 -0
  62. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  63. package/tool/ui/ui-option-warnings.d.ts +3 -0
  64. package/tool/ui/ui-option-warnings.d.ts.map +1 -0
  65. package/tool/ui/ui-resource.handler.d.ts.map +1 -1
  66. package/tool/ui/ui-shared.d.ts +14 -0
  67. package/tool/ui/ui-shared.d.ts.map +1 -1
  68. package/transport/event-stores/redis.event-store.d.ts.map +1 -1
  69. package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
  70. package/transport/flows/handle.sse.flow.d.ts +25 -0
  71. package/transport/flows/handle.sse.flow.d.ts.map +1 -1
  72. package/transport/flows/handle.stateless-http.flow.d.ts +2 -1
  73. package/transport/flows/handle.stateless-http.flow.d.ts.map +1 -1
  74. package/transport/flows/handle.streamable-http.flow.d.ts +12 -1
  75. package/transport/flows/handle.streamable-http.flow.d.ts.map +1 -1
  76. package/transport/ipc-server.transport.d.ts +26 -0
  77. package/transport/ipc-server.transport.d.ts.map +1 -0
  78. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts +9 -2
  79. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts.map +1 -1
  80. package/transport/mcp-20260728/dispatcher.d.ts.map +1 -1
  81. package/transport/mcp-20260728/request-state.d.ts +28 -0
  82. package/transport/mcp-20260728/request-state.d.ts.map +1 -1
  83. package/transport/transport.registry.d.ts +34 -0
  84. package/transport/transport.registry.d.ts.map +1 -1
  85. 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"]
@@ -5972,6 +5987,33 @@ var init_errors = __esm({
5972
5987
  }
5973
5988
  });
5974
5989
 
5990
+ // libs/sdk/src/common/utils/list-all-pages.utils.ts
5991
+ async function listAllPages(method, fetchPage, fail = (message) => new InternalMcpError(message, "LIST_PAGINATION_STALLED")) {
5992
+ const items = [];
5993
+ const seenCursors = /* @__PURE__ */ new Set();
5994
+ let cursor;
5995
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
5996
+ const result = await fetchPage(cursor);
5997
+ items.push(...result.items ?? []);
5998
+ const next = result.nextCursor;
5999
+ if (!next) return items;
6000
+ if (seenCursors.has(next)) {
6001
+ throw fail(`${method} returned the cursor "${next}" twice`);
6002
+ }
6003
+ seenCursors.add(next);
6004
+ cursor = next;
6005
+ }
6006
+ throw fail(`${method} did not finish within ${MAX_LIST_PAGES} pages`);
6007
+ }
6008
+ var MAX_LIST_PAGES;
6009
+ var init_list_all_pages_utils = __esm({
6010
+ "libs/sdk/src/common/utils/list-all-pages.utils.ts"() {
6011
+ "use strict";
6012
+ init_mcp_error();
6013
+ MAX_LIST_PAGES = 1e3;
6014
+ }
6015
+ });
6016
+
5975
6017
  // libs/sdk/src/transport/mcp-handlers/skills-mcp.types.ts
5976
6018
  var import_lazy_zod40, SkillsSearchRequestSchema, SkillsSearchResultSchema, SkillsLoadRequestSchema, SkillsLoadResultSchema, SkillsListRequestSchema, SkillsListResultSchema;
5977
6019
  var init_skills_mcp_types = __esm({
@@ -6415,6 +6457,7 @@ var require_dist = __commonJS({
6415
6457
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
6416
6458
  var index_exports2 = {};
6417
6459
  __export2(index_exports2, {
6460
+ SqliteDecryptionError: () => SqliteDecryptionError,
6418
6461
  SqliteElicitationStore: () => SqliteElicitationStore,
6419
6462
  SqliteEventStore: () => SqliteEventStore,
6420
6463
  SqliteKvStore: () => SqliteKvStore,
@@ -6426,34 +6469,47 @@ var require_dist = __commonJS({
6426
6469
  encryptValue: () => encryptValue,
6427
6470
  loadBetterSqlite3: () => loadBetterSqlite3,
6428
6471
  openDatabase: () => openDatabase,
6429
- sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6472
+ sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema,
6473
+ withBusyRetry: () => withBusyRetry
6430
6474
  });
6431
6475
  module2.exports = __toCommonJS2(index_exports2);
6432
- 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
+ };
6433
6486
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6434
6487
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6435
6488
  var KEY_LENGTH = 32;
6436
6489
  function deriveEncryptionKey(secret) {
6437
6490
  const ikm = new TextEncoder().encode(secret);
6438
- 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);
6439
6492
  }
6440
6493
  var SEPARATOR = ":";
6441
6494
  function encryptValue(key, plaintext) {
6442
- const iv = (0, import_utils114.randomBytes)(12);
6495
+ const iv = (0, import_utils119.randomBytes)(12);
6443
6496
  const plaintextBytes = new TextEncoder().encode(plaintext);
6444
- const { ciphertext, tag } = (0, import_utils114.encryptAesGcm)(key, plaintextBytes, iv);
6445
- 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);
6446
6499
  }
6447
6500
  function decryptValue(key, encrypted) {
6448
6501
  const parts = encrypted.split(SEPARATOR);
6449
6502
  if (parts.length !== 3) {
6450
6503
  throw new Error("Invalid encrypted value format");
6451
6504
  }
6452
- const iv = (0, import_utils114.base64urlDecode)(parts[0]);
6453
- const tag = (0, import_utils114.base64urlDecode)(parts[1]);
6454
- const ciphertext = (0, import_utils114.base64urlDecode)(parts[2]);
6455
- const plaintext = (0, import_utils114.decryptAesGcm)(key, ciphertext, iv, tag);
6456
- 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
+ }
6457
6513
  }
6458
6514
  var import_utils210 = require("@frontmcp/utils");
6459
6515
  var cached = null;
@@ -6466,7 +6522,27 @@ var require_dist = __commonJS({
6466
6522
  return mod;
6467
6523
  }
6468
6524
  var MEMORY_PATH = ":memory:";
6469
- 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 = {}) {
6470
6546
  const BetterSqlite3 = loadBetterSqlite3();
6471
6547
  if (path3 !== MEMORY_PATH && path3 !== "") {
6472
6548
  const dir = (0, import_utils210.dirname)(path3);
@@ -6475,16 +6551,28 @@ var require_dist = __commonJS({
6475
6551
  (0, import_utils210.ensureDirSync)(dir);
6476
6552
  } catch (err) {
6477
6553
  const message = err instanceof Error ? err.message : String(err);
6478
- 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
+ });
6479
6557
  }
6480
6558
  }
6481
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
+ }
6482
6567
  try {
6483
- 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"));
6484
6570
  } catch (err) {
6571
+ db.close();
6485
6572
  const message = err instanceof Error ? err.message : String(err);
6486
- 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 });
6487
6574
  }
6575
+ return db;
6488
6576
  }
6489
6577
  var SqliteKvStore = class {
6490
6578
  db;
@@ -6492,14 +6580,14 @@ var require_dist = __commonJS({
6492
6580
  cleanupTimer = null;
6493
6581
  stmts = null;
6494
6582
  constructor(options) {
6495
- this.db = openDatabase(options.path, "SqliteKvStore");
6496
- if (options.walMode !== false) {
6497
- this.db.pragma("journal_mode = WAL");
6498
- }
6583
+ this.db = openDatabase(options.path, "SqliteKvStore", {
6584
+ busyTimeoutMs: options.busyTimeoutMs,
6585
+ walMode: options.walMode !== false
6586
+ });
6499
6587
  if (options.encryption?.secret) {
6500
6588
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
6501
6589
  }
6502
- this.initSchema();
6590
+ withBusyRetry(() => this.initSchema());
6503
6591
  this.prepareStatements();
6504
6592
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
6505
6593
  if (cleanupInterval > 0) {
@@ -6958,17 +7046,17 @@ var require_dist = __commonJS({
6958
7046
  encryptionKey = null;
6959
7047
  stmts = null;
6960
7048
  constructor(options) {
6961
- this.db = openDatabase(options.path, "SqliteTaskStore");
6962
- if (options.walMode !== false) {
6963
- this.db.pragma("journal_mode = WAL");
6964
- }
7049
+ this.db = openDatabase(options.path, "SqliteTaskStore", {
7050
+ busyTimeoutMs: options.busyTimeoutMs,
7051
+ walMode: options.walMode !== false
7052
+ });
6965
7053
  this.logger = options.logger;
6966
7054
  this.liveness = options.livenessProbe ?? defaultLivenessProbe;
6967
7055
  if (options.encryption?.secret) {
6968
7056
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
6969
7057
  }
6970
7058
  this.emitter.setMaxListeners(200);
6971
- this.initSchema();
7059
+ withBusyRetry(() => this.initSchema());
6972
7060
  this.prepareStatements();
6973
7061
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
6974
7062
  if (cleanupInterval > 0) {
@@ -7205,13 +7293,13 @@ var require_dist = __commonJS({
7205
7293
  cleanupTimer = null;
7206
7294
  stmts = null;
7207
7295
  constructor(options) {
7208
- this.db = openDatabase(options.path, "SqliteEventStore");
7209
- if (options.walMode !== false) {
7210
- this.db.pragma("journal_mode = WAL");
7211
- }
7296
+ this.db = openDatabase(options.path, "SqliteEventStore", {
7297
+ busyTimeoutMs: options.busyTimeoutMs,
7298
+ walMode: options.walMode !== false
7299
+ });
7212
7300
  this.maxEvents = options.maxEvents ?? 1e4;
7213
7301
  this.ttlMs = options.ttlMs ?? 3e5;
7214
- this.initSchema();
7302
+ withBusyRetry(() => this.initSchema());
7215
7303
  this.prepareStatements();
7216
7304
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
7217
7305
  if (cleanupInterval > 0) {
@@ -7346,7 +7434,8 @@ var require_dist = __commonJS({
7346
7434
  secret: import_lazy_zod113.z.string().min(1)
7347
7435
  }).optional(),
7348
7436
  ttlCleanupIntervalMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(6e4),
7349
- 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)
7350
7439
  });
7351
7440
  }
7352
7441
  });
@@ -7364,6 +7453,14 @@ function detectStorageType(storage, configuredType) {
7364
7453
  if ((0, import_utils15.getEnv)("REDIS_URL") || (0, import_utils15.getEnv)("REDIS_HOST")) return "redis";
7365
7454
  return "memory";
7366
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
+ }
7367
7464
  async function createTaskStore(options = {}) {
7368
7465
  const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
7369
7466
  if (isEdgeRuntime3 && sqlite) {
@@ -7388,7 +7485,7 @@ async function createTaskStore(options = {}) {
7388
7485
  return { store: store2, type: "sqlite" };
7389
7486
  }
7390
7487
  let finalStorageConfig = storageConfig;
7391
- if (redis && !storageConfig) {
7488
+ if (redis && !isExplicitStorage(storageConfig)) {
7392
7489
  if ("provider" in redis && redis.provider === "vercel-kv") {
7393
7490
  throw new TaskStoreNotSupportedError(
7394
7491
  "Vercel KV is not supported for task stores. Task result blocking and cancel signalling require pub/sub. Use Redis or Upstash instead."
@@ -7416,7 +7513,8 @@ async function createTaskStore(options = {}) {
7416
7513
  };
7417
7514
  }
7418
7515
  }
7419
- 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")) {
7420
7518
  throw new TaskStoreNotSupportedError(
7421
7519
  "Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
7422
7520
  );
@@ -8657,11 +8755,8 @@ var init_get_prompt_request_handler = __esm({
8657
8755
 
8658
8756
  // libs/sdk/src/notification/notification.service.ts
8659
8757
  function hasMcpAppsExtension(capabilities) {
8660
- if (!capabilities?.experimental) {
8661
- return false;
8662
- }
8663
- const uiExtension = capabilities.experimental[MCP_APPS_EXTENSION_KEY];
8664
- 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;
8665
8760
  }
8666
8761
  function detectPlatformFromCapabilities(capabilities) {
8667
8762
  if (hasMcpAppsExtension(capabilities)) {
@@ -11263,6 +11358,8 @@ var init_ui_shared = __esm({
11263
11358
  widgets = /* @__PURE__ */ new Map();
11264
11359
  manifests = /* @__PURE__ */ new Map();
11265
11360
  resourceMeta = /* @__PURE__ */ new Map();
11361
+ uiTools = /* @__PURE__ */ new Set();
11362
+ customUris = /* @__PURE__ */ new Map();
11266
11363
  resolver;
11267
11364
  options;
11268
11365
  constructor(resolver, options = {}) {
@@ -11277,6 +11374,28 @@ var init_ui_shared = __esm({
11277
11374
  logger: this.options.logger
11278
11375
  };
11279
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
+ }
11280
11399
  getStaticWidget(name44) {
11281
11400
  return this.widgets.get(name44);
11282
11401
  }
@@ -11305,8 +11424,9 @@ var init_ui_shared = __esm({
11305
11424
  const output = options["output"] ?? {};
11306
11425
  const uiConfig = options["uiConfig"];
11307
11426
  const resourceMode = uiConfig?.["resourceMode"] ?? options["resourceMode"];
11308
- if (!toolName || !template) return;
11427
+ if (!toolName) return;
11309
11428
  this.updateResourceMetaFromConfig(toolName, uiConfig);
11429
+ if (!template) return;
11310
11430
  const result = (0, import_adapters.renderToolTemplate)({
11311
11431
  toolName,
11312
11432
  input,
@@ -11315,6 +11435,7 @@ var init_ui_shared = __esm({
11315
11435
  resolver: this.resolver,
11316
11436
  resourceMode,
11317
11437
  sizing: extractSizing(uiConfig),
11438
+ csp: uiConfig?.["csp"],
11318
11439
  ...this.stringResultOptions(uiConfig)
11319
11440
  });
11320
11441
  this.widgets.set(toolName, result.html);
@@ -11393,6 +11514,7 @@ var init_ui_shared = __esm({
11393
11514
  resolver: this.resolver,
11394
11515
  resourceMode,
11395
11516
  sizing: extractSizing(uiConfig),
11517
+ csp: uiConfig?.["csp"],
11396
11518
  ...this.stringResultOptions(uiConfig)
11397
11519
  });
11398
11520
  return { meta: result.meta };
@@ -11445,26 +11567,14 @@ function handleUIResourceRead(uri, registry, platformType) {
11445
11567
  return { handled: false };
11446
11568
  }
11447
11569
  const mimeType = getUIResourceMimeType(platformType);
11448
- const widgetParsed = parseWidgetUri(uri);
11570
+ const customTool = registry.resolveCustomUri(uri);
11571
+ const widgetParsed = customTool ? { toolName: customTool } : parseWidgetUri(uri);
11449
11572
  if (widgetParsed) {
11450
- const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11451
- const cachedWidget = registry.getStaticWidget(widgetParsed.toolName);
11452
- if (cachedWidget) {
11453
- return {
11454
- handled: true,
11455
- result: {
11456
- contents: [
11457
- {
11458
- uri,
11459
- mimeType,
11460
- text: cachedWidget,
11461
- ...resourceMeta ? { _meta: resourceMeta } : {}
11462
- }
11463
- ]
11464
- }
11465
- };
11573
+ if (!registry.hasTool(widgetParsed.toolName)) {
11574
+ return { handled: true, error: `No UI widget is registered for ${uri}` };
11466
11575
  }
11467
- const html = generatePlaceholderWidget(widgetParsed.toolName);
11576
+ const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11577
+ const html = registry.getStaticWidget(widgetParsed.toolName) ?? generatePlaceholderWidget(widgetParsed.toolName);
11468
11578
  return {
11469
11579
  handled: true,
11470
11580
  result: {
@@ -12043,6 +12153,7 @@ var init_direct_client = __esm({
12043
12153
  "use strict";
12044
12154
  import_protocol30 = require("@frontmcp/protocol");
12045
12155
  import_utils29 = require("@frontmcp/utils");
12156
+ init_list_all_pages_utils();
12046
12157
  init_errors();
12047
12158
  init_skills_mcp_types();
12048
12159
  init_llm_platform();
@@ -12189,8 +12300,11 @@ var init_direct_client = __esm({
12189
12300
  // Tool Operations (platform-formatted)
12190
12301
  // ─────────────────────────────────────────────────────────────────────────────
12191
12302
  async listTools() {
12192
- const result = await this.mcpClient.listTools();
12193
- return formatToolsForPlatform(result.tools, this.platform);
12303
+ const tools = await listAllPages("tools/list", async (cursor) => {
12304
+ const result = await this.mcpClient.listTools(cursor ? { cursor } : void 0);
12305
+ return { items: result.tools, nextCursor: result.nextCursor };
12306
+ });
12307
+ return formatToolsForPlatform(tools, this.platform);
12194
12308
  }
12195
12309
  async callTool(name44, args) {
12196
12310
  const result = await this.mcpClient.callTool({
@@ -12203,19 +12317,34 @@ var init_direct_client = __esm({
12203
12317
  // Resource Operations (raw format)
12204
12318
  // ─────────────────────────────────────────────────────────────────────────────
12205
12319
  async listResources() {
12206
- 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 };
12207
12325
  }
12208
12326
  async readResource(uri) {
12209
12327
  return this.mcpClient.readResource({ uri });
12210
12328
  }
12211
12329
  async listResourceTemplates() {
12212
- 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 };
12213
12338
  }
12214
12339
  // ─────────────────────────────────────────────────────────────────────────────
12215
12340
  // Prompt Operations (raw format)
12216
12341
  // ─────────────────────────────────────────────────────────────────────────────
12217
12342
  async listPrompts() {
12218
- 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 };
12219
12348
  }
12220
12349
  async getPrompt(name44, args) {
12221
12350
  return this.mcpClient.getPrompt({
@@ -12943,8 +13072,8 @@ function createRemoteAppProbe(appId, provider) {
12943
13072
  name: `remote:${appId}`,
12944
13073
  async check() {
12945
13074
  const result = provider.getHealthStatus(appId);
12946
- if (!result) {
12947
- 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" } };
12948
13077
  }
12949
13078
  return {
12950
13079
  status: result.status === "healthy" ? "healthy" : result.status === "degraded" ? "degraded" : "unhealthy",
@@ -14060,6 +14189,12 @@ var init_frontmcp_context = __esm({
14060
14189
  // =====================
14061
14190
  /** Request metadata (headers, user-agent, etc.) */
14062
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;
14063
14198
  // =====================
14064
14199
  // Auth (mutable, progressively populated)
14065
14200
  // =====================
@@ -14095,6 +14230,7 @@ var init_frontmcp_context = __esm({
14095
14230
  this.traceContext = args.traceContext ?? generateTraceContext();
14096
14231
  this.timestamp = args.timestamp ?? Date.now();
14097
14232
  this._authInfo = args.authInfo ?? {};
14233
+ this.platformEnv = args.platformEnv;
14098
14234
  this.config = {
14099
14235
  forwardCallerTokenTo: toOrigins(args.config?.forwardCallerTokenTo),
14100
14236
  forwardCustomHeadersTo: toOrigins(args.config?.forwardCustomHeadersTo),
@@ -14797,6 +14933,7 @@ var init_frontmcp_context_storage = __esm({
14797
14933
  import_utils39 = require("@frontmcp/utils");
14798
14934
  init_decorators();
14799
14935
  init_metadata();
14936
+ init_tokens();
14800
14937
  init_mcp_error();
14801
14938
  init_frontmcp_context();
14802
14939
  init_metadata_utils();
@@ -14864,7 +15001,12 @@ var init_frontmcp_context_storage = __esm({
14864
15001
  const headers = request.headers ?? {};
14865
15002
  const headerSessionId = typeof headers["mcp-session-id"] === "string" ? headers["mcp-session-id"].trim() : "";
14866
15003
  const sessionId = headerSessionId.length > 0 ? headerSessionId : `anon:${(0, import_utils39.randomUUID)()}`;
14867
- 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
+ );
14868
15010
  }
14869
15011
  withServerConfig(args) {
14870
15012
  return { ...args, config: { ...this.contextConfig, ...args.config } };
@@ -21917,6 +22059,14 @@ var init_tool_credentials_gate = __esm({
21917
22059
  });
21918
22060
 
21919
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
+ }
21920
22070
  function toolNameCandidates(name44) {
21921
22071
  if (!/[-_]/.test(name44)) return [name44];
21922
22072
  return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
@@ -21992,6 +22142,9 @@ var init_call_tool_flow = __esm({
21992
22142
  uiResult: import_lazy_zod57.z.any().optional(),
21993
22143
  // UI metadata from rendering (merged into _meta)
21994
22144
  uiMeta: import_lazy_zod57.z.record(import_lazy_zod57.z.string(), import_lazy_zod57.z.unknown()).optional(),
22145
+ // Metadata hooks add to the result (e.g. the cache plugin's `cache: 'hit'`), merged into the
22146
+ // result's _meta; never merged into rawOutput, which is the tool's data
22147
+ resultMeta: import_lazy_zod57.z.record(import_lazy_zod57.z.string(), import_lazy_zod57.z.unknown()).optional(),
21995
22148
  // Progress token from request's _meta (for progress notifications)
21996
22149
  progressToken: import_lazy_zod57.z.union([import_lazy_zod57.z.string(), import_lazy_zod57.z.number()]).optional(),
21997
22150
  // JSON-RPC request ID (for elicitation routing)
@@ -22647,7 +22800,7 @@ ${JSON.stringify(error.schema, null, 2)}
22647
22800
  this.logger.verbose("execute:done (elicitation fire-and-forget fallback)");
22648
22801
  return;
22649
22802
  }
22650
- if (isClientFacingError(error)) throw error;
22803
+ if (isClientFacingError(error) || error instanceof InvalidOutputError) throw error;
22651
22804
  throw new ToolExecutionError(
22652
22805
  this.state.tool?.metadata.name || "unknown",
22653
22806
  error instanceof Error ? error : void 0
@@ -22690,7 +22843,8 @@ ${JSON.stringify(error.schema, null, 2)}
22690
22843
  }
22691
22844
  async applyUI() {
22692
22845
  this.logger.verbose("applyUI:start");
22693
- const { tool, rawOutput, input } = this.state;
22846
+ const { tool, rawOutput: executeOutput, input } = this.state;
22847
+ const rawOutput = declaredOutput(tool, executeOutput);
22694
22848
  const ctx = this.input.ctx;
22695
22849
  if (ctx?._skipUI) {
22696
22850
  this.logger.verbose("applyUI:skip (agent context - structured data only)");
@@ -22819,7 +22973,7 @@ ${JSON.stringify(error.schema, null, 2)}
22819
22973
  }
22820
22974
  async finalize() {
22821
22975
  this.logger.verbose("finalize:start");
22822
- const { tool, rawOutput, uiResult, uiMeta } = this.state;
22976
+ const { tool, rawOutput, uiResult, uiMeta, resultMeta } = this.state;
22823
22977
  if (!tool) {
22824
22978
  this.logger.verbose("finalize: skipping (no tool in state)");
22825
22979
  return;
@@ -22862,6 +23016,9 @@ ${JSON.stringify(error.schema, null, 2)}
22862
23016
  if (rawMeta) {
22863
23017
  result._meta = { ...result._meta, ...rawMeta };
22864
23018
  }
23019
+ if (resultMeta) {
23020
+ result._meta = { ...result._meta, ...resultMeta };
23021
+ }
22865
23022
  if (uiResult) {
22866
23023
  result.content = uiResult.content;
22867
23024
  if (uiResult.structuredContent !== void 0 && uiResult.structuredContent !== null) {
@@ -24313,6 +24470,38 @@ var init_context = __esm({
24313
24470
  }
24314
24471
  });
24315
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
+
24316
24505
  // libs/sdk/src/server/server.validation.ts
24317
24506
  function applyHeaders(res, headers) {
24318
24507
  if (!headers) return;
@@ -24625,6 +24814,7 @@ var init_flow_instance = __esm({
24625
24814
  init_common();
24626
24815
  init_context();
24627
24816
  init_errors();
24817
+ init_misconfiguration();
24628
24818
  init_server_validation();
24629
24819
  init_flow_http_path();
24630
24820
  init_flow_stages();
@@ -24684,6 +24874,15 @@ var init_flow_instance = __esm({
24684
24874
  return writeHttpResponse(response, e.output);
24685
24875
  }
24686
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
+ }
24687
24886
  if (e instanceof PublicMcpError) {
24688
24887
  const challenge = e.wwwAuthenticate;
24689
24888
  this.logger.warn("Public error in flow", {
@@ -25396,14 +25595,90 @@ function collectDynamicProviders(klass, options) {
25396
25595
  }
25397
25596
  return out;
25398
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
+ }
25399
25621
  function dedupePluginProviders(providers) {
25400
25622
  const map = /* @__PURE__ */ new Map();
25401
25623
  for (const p of providers) map.set(p["provide"] ?? p, p);
25402
25624
  return [...map.values()];
25403
25625
  }
25626
+ var LIST_METADATA_KEYS;
25404
25627
  var init_dynamic_utils = __esm({
25405
25628
  "libs/sdk/src/common/dynamic/dynamic.utils.ts"() {
25406
25629
  "use strict";
25630
+ LIST_METADATA_KEYS = ["exports", "plugins", "adapters", "tools", "resources", "prompts", "skills"];
25631
+ }
25632
+ });
25633
+
25634
+ // libs/sdk/src/common/dynamic/dynamic.plugin.ts
25635
+ function isDynamicPluginClass(value) {
25636
+ return typeof value === "function" && value.prototype instanceof DynamicPlugin;
25637
+ }
25638
+ function initOptionsOf(instance) {
25639
+ return initOptionsByInstance.has(instance) ? { options: initOptionsByInstance.get(instance) } : void 0;
25640
+ }
25641
+ var initOptionsByInstance, DynamicPlugin;
25642
+ var init_dynamic_plugin = __esm({
25643
+ "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
25644
+ "use strict";
25645
+ init_transport_errors();
25646
+ init_dynamic_utils();
25647
+ initOptionsByInstance = /* @__PURE__ */ new WeakMap();
25648
+ DynamicPlugin = class {
25649
+ get(token) {
25650
+ throw new MethodNotImplementedError("DynamicPlugin", "get");
25651
+ }
25652
+ /**
25653
+ * Static init() method to create a plugin provider.
25654
+ * @param options - Input options (with optional fields for defaults). Omitted, the plugin is
25655
+ * built with `{}`, as `ApprovalPlugin.init()` is documented.
25656
+ */
25657
+ static init(options = {}) {
25658
+ const extraProviders = options.providers;
25659
+ const typedOptions = options;
25660
+ if ("useFactory" in options) {
25661
+ return {
25662
+ ...pluginMetadataFromOptions(typedOptions, []),
25663
+ provide: this,
25664
+ inject: options.inject,
25665
+ useFactory: options.useFactory,
25666
+ providers: dedupePluginProviders(extraProviders ?? [])
25667
+ };
25668
+ }
25669
+ const dyn = collectDynamicProviders(this, typedOptions);
25670
+ const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
25671
+ const dynamicTools = collectDynamicTools(this, typedOptions);
25672
+ const instance = new this(options);
25673
+ initOptionsByInstance.set(instance, options);
25674
+ return {
25675
+ ...pluginMetadataFromOptions(typedOptions, dynamicTools),
25676
+ provide: this,
25677
+ useValue: instance,
25678
+ providers: mergedProviders
25679
+ };
25680
+ }
25681
+ };
25407
25682
  }
25408
25683
  });
25409
25684
 
@@ -25659,9 +25934,18 @@ var init_execution_context_interface = __esm({
25659
25934
  if (this._authContext) return this._authContext;
25660
25935
  const rawAuth = require("@frontmcp/auth");
25661
25936
  const auth = rawAuth.default ?? rawAuth;
25662
- this._authContext = auth.buildAuthContext(this._authInfo, this.scope.metadata.authorities?.claimsMapping);
25937
+ this._authContext = auth.buildAuthContext(this.resolveAuthSource(), this.scope.metadata.authorities?.claimsMapping);
25663
25938
  return this._authContext;
25664
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
+ }
25665
25949
  /**
25666
25950
  * Try to get the context, returning undefined if not available.
25667
25951
  *
@@ -25847,6 +26131,23 @@ var init_execution_context_interface = __esm({
25847
26131
  get config() {
25848
26132
  return this.providers.get(ConfigService);
25849
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
+ }
25850
26151
  // ---- Runtime context helpers ----
25851
26152
  /**
25852
26153
  * Get the current runtime context (platform, runtime, deployment, env).
@@ -28895,7 +29196,7 @@ var init_plugin_utils = __esm({
28895
29196
  });
28896
29197
 
28897
29198
  // libs/sdk/src/plugin/plugin.registry.ts
28898
- var import_reflect_metadata12, import_di24, PluginRegistry;
29199
+ var import_reflect_metadata12, import_di24, installedPluginValues, PluginRegistry;
28899
29200
  var init_plugin_registry = __esm({
28900
29201
  "libs/sdk/src/plugin/plugin.registry.ts"() {
28901
29202
  "use strict";
@@ -28903,6 +29204,7 @@ var init_plugin_registry = __esm({
28903
29204
  import_di24 = require("@frontmcp/di");
28904
29205
  init_adapter_registry();
28905
29206
  init_common();
29207
+ init_dynamic_plugin();
28906
29208
  init_dynamic_utils();
28907
29209
  init_context_extension();
28908
29210
  init_errors();
@@ -28915,6 +29217,7 @@ var init_plugin_registry = __esm({
28915
29217
  init_skill_registry();
28916
29218
  init_tool_registry();
28917
29219
  init_plugin_utils();
29220
+ installedPluginValues = /* @__PURE__ */ new WeakSet();
28918
29221
  PluginRegistry = class _PluginRegistry extends RegistryAbstract {
28919
29222
  /** providers by token */
28920
29223
  pProviders = /* @__PURE__ */ new Map();
@@ -29103,8 +29406,15 @@ var init_plugin_registry = __esm({
29103
29406
  pluginInstance: new rec.provide(...depsInstances),
29104
29407
  dynamicProviders: rec.providers
29105
29408
  };
29106
- case "VALUE" /* VALUE */:
29107
- return { pluginInstance: rec.useValue, dynamicProviders: rec.providers };
29409
+ case "VALUE" /* VALUE */: {
29410
+ const value = rec.useValue;
29411
+ const init = installedPluginValues.has(value) ? initOptionsOf(value) : void 0;
29412
+ installedPluginValues.add(value);
29413
+ if (init && isDynamicPluginClass(rec.provide)) {
29414
+ return { pluginInstance: new rec.provide(init.options), dynamicProviders: rec.providers };
29415
+ }
29416
+ return { pluginInstance: value, dynamicProviders: rec.providers };
29417
+ }
29108
29418
  case "FACTORY" /* FACTORY */: {
29109
29419
  const args = [];
29110
29420
  for (const d of rec.inject()) args.push(await this.providers.resolveBootstrapDep(d));
@@ -30644,6 +30954,9 @@ var init_completion_events = __esm({
30644
30954
  });
30645
30955
 
30646
30956
  // libs/sdk/src/agent/flows/call-agent.flow.ts
30957
+ function invalidOutputFrom(outputError) {
30958
+ return outputError instanceof InvalidOutputError ? outputError : new InvalidOutputError();
30959
+ }
30647
30960
  var import_auth26, import_guard6, import_lazy_zod60, import_protocol41, inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallAgentFlow;
30648
30961
  var init_call_agent_flow = __esm({
30649
30962
  "libs/sdk/src/agent/flows/call-agent.flow.ts"() {
@@ -31030,7 +31343,7 @@ var init_call_agent_flow = __esm({
31030
31343
  const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
31031
31344
  if (!agent || executionStartedAt === void 0) return;
31032
31345
  if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
31033
- const failure = executionError ?? (outputError !== void 0 ? new InvalidOutputError() : void 0);
31346
+ const failure = executionError ?? (outputError !== void 0 ? invalidOutputFrom(outputError) : void 0);
31034
31347
  const failed = failure !== void 0;
31035
31348
  const requestId = this.tryGetContext()?.requestId;
31036
31349
  const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
@@ -31060,7 +31373,7 @@ var init_call_agent_flow = __esm({
31060
31373
  agent: agent.metadata.name,
31061
31374
  errors: outputError
31062
31375
  });
31063
- throw new InvalidOutputError();
31376
+ throw invalidOutputFrom(outputError);
31064
31377
  }
31065
31378
  const result = parsedOutput;
31066
31379
  if (executionMeta) {
@@ -31641,12 +31954,36 @@ var init_request_validation = __esm({
31641
31954
  });
31642
31955
 
31643
31956
  // libs/sdk/src/transport/mcp-20260728/request-state.ts
31644
- function getRequestStateKey() {
31957
+ function configuredSecret(name44) {
31958
+ const value = (0, import_utils65.getEnv)(name44);
31959
+ return value && value.trim().length > 0 ? value : void 0;
31960
+ }
31961
+ function resolveKey() {
31645
31962
  if (cachedKey) return cachedKey;
31646
- const secret = process.env["VAULT_SECRET"] ?? process.env["JWT_SECRET"];
31647
- cachedKey = secret ? encoder.encode(secret) : (0, import_utils65.randomBytes)(32);
31963
+ const vault = configuredSecret("VAULT_SECRET");
31964
+ const jwt = vault ? void 0 : configuredSecret("JWT_SECRET");
31965
+ if (vault) cachedKey = { key: encoder.encode(vault), source: "VAULT_SECRET" };
31966
+ else if (jwt) cachedKey = { key: encoder.encode(jwt), source: "JWT_SECRET" };
31967
+ else cachedKey = { key: (0, import_utils65.randomBytes)(32), source: "per-process" };
31648
31968
  return cachedKey;
31649
31969
  }
31970
+ function getRequestStateKey() {
31971
+ return resolveKey().key;
31972
+ }
31973
+ function getRequestStateKeySource() {
31974
+ return resolveKey().source;
31975
+ }
31976
+ function warnIfRequestStateKeyNotShared(options) {
31977
+ if (warnedKeyNotShared || !(0, import_utils65.isProduction)()) return;
31978
+ const { metadata } = options;
31979
+ const persistence = metadata.transport?.persistence;
31980
+ const sharesState = Boolean(metadata.redis) || typeof persistence === "object" && persistence !== null;
31981
+ if (!sharesState || getRequestStateKeySource() !== "per-process") return;
31982
+ warnedKeyNotShared = true;
31983
+ options.logger.warn(
31984
+ "requestState is signed with a per-process key (neither VAULT_SECRET nor JWT_SECRET is set). Multi-round tools (elicit/sample) restart when a round lands on another instance; set VAULT_SECRET (or JWT_SECRET) to the same value on every instance."
31985
+ );
31986
+ }
31650
31987
  function computeRequestBinding(method, params) {
31651
31988
  const salient = {};
31652
31989
  for (const key of ["name", "uri", "arguments"]) {
@@ -31696,13 +32033,14 @@ function decodeRequestState(state, expected) {
31696
32033
  if (payload.b !== expected.binding) return { ok: false, reason: "request-mismatch" };
31697
32034
  return { ok: true, responses: payload.r };
31698
32035
  }
31699
- var import_utils65, DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, encoder, decoder;
32036
+ var import_utils65, DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, warnedKeyNotShared, encoder, decoder;
31700
32037
  var init_request_state = __esm({
31701
32038
  "libs/sdk/src/transport/mcp-20260728/request-state.ts"() {
31702
32039
  "use strict";
31703
32040
  import_utils65 = require("@frontmcp/utils");
31704
32041
  DEFAULT_REQUEST_STATE_TTL_MS = 10 * 60 * 1e3;
31705
32042
  MAX_REQUEST_STATE_BYTES = 256 * 1024;
32043
+ warnedKeyNotShared = false;
31706
32044
  encoder = new TextEncoder();
31707
32045
  decoder = new TextDecoder();
31708
32046
  }
@@ -32423,7 +32761,8 @@ async function dispatch20260728(options) {
32423
32761
  };
32424
32762
  const carried = decodeRequestState(params["requestState"], binding);
32425
32763
  if (!carried.ok && carried.reason !== "absent") {
32426
- scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason });
32764
+ const hint = carried.reason === "bad-signature" && getRequestStateKeySource() === "per-process" ? "requestState is signed with a per-process key; set VAULT_SECRET (or JWT_SECRET) to the same value on every instance so a round can land on any of them" : void 0;
32765
+ scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason, ...hint && { hint } });
32427
32766
  }
32428
32767
  const exchange = new MrtrExchange({
32429
32768
  inputResponses: params["inputResponses"],
@@ -32580,6 +32919,7 @@ var init_mcp_stateless_client = __esm({
32580
32919
  "libs/sdk/src/transport/mcp-20260728/client/mcp-stateless.client.ts"() {
32581
32920
  "use strict";
32582
32921
  import_protocol47 = require("@frontmcp/protocol");
32922
+ init_list_all_pages_utils();
32583
32923
  init_header_codec();
32584
32924
  init_protocol_20260728_constants();
32585
32925
  init_tasks_extension();
@@ -32620,10 +32960,22 @@ var init_mcp_stateless_client = __esm({
32620
32960
  * The spec requires this: one malformed tool definition must not prevent the
32621
32961
  * other tools from being used, so the offender is excluded and logged rather
32622
32962
  * than failing the call.
32963
+ *
32964
+ * Every page is fetched (`nextCursor` is followed to the end), since the
32965
+ * schemas cached here are what later `tools/call` headers are derived from.
32623
32966
  */
32624
32967
  async listTools() {
32625
- const result = await this.request("tools/list");
32626
- const tools = Array.isArray(result["tools"]) ? result["tools"] : [];
32968
+ const tools = await listAllPages(
32969
+ "tools/list",
32970
+ async (cursor) => {
32971
+ const result = await this.request("tools/list", cursor ? { cursor } : {});
32972
+ return {
32973
+ items: Array.isArray(result["tools"]) ? result["tools"] : [],
32974
+ nextCursor: typeof result["nextCursor"] === "string" ? result["nextCursor"] : void 0
32975
+ };
32976
+ },
32977
+ (message) => new McpStatelessError(-32603, message)
32978
+ );
32627
32979
  const usable = [];
32628
32980
  for (const tool of tools) {
32629
32981
  const validation = validateHeaderParams(tool["inputSchema"]);
@@ -32642,11 +32994,17 @@ var init_mcp_stateless_client = __esm({
32642
32994
  async readResource(uri) {
32643
32995
  return this.request("resources/read", { uri });
32644
32996
  }
32645
- async listResources() {
32646
- return this.request("resources/list");
32997
+ /** One page of `resources/list`; pass the previous page's `nextCursor` for the next. */
32998
+ async listResources(cursor) {
32999
+ return this.request("resources/list", cursor ? { cursor } : {});
32647
33000
  }
32648
- async listPrompts() {
32649
- return this.request("prompts/list");
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
+ }
33005
+ /** One page of `prompts/list`; pass the previous page's `nextCursor` for the next. */
33006
+ async listPrompts(cursor) {
33007
+ return this.request("prompts/list", cursor ? { cursor } : {});
32650
33008
  }
32651
33009
  async getPrompt(name44, args = {}) {
32652
33010
  return this.request("prompts/get", { name: name44, arguments: args });
@@ -32932,6 +33290,10 @@ var init_mcp_20260728 = __esm({
32932
33290
  });
32933
33291
 
32934
33292
  // libs/sdk/src/remote-mcp/mcp-stateless-client.adapter.ts
33293
+ function nextCursorOf(result) {
33294
+ const next = result["nextCursor"];
33295
+ return typeof next === "string" && next.length > 0 ? { nextCursor: next } : {};
33296
+ }
32935
33297
  async function negotiateRemoteProtocol(url, configured, headers, fetchImpl = fetch) {
32936
33298
  if (configured === "2026-07-28") return "2026-07-28";
32937
33299
  if (configured !== "auto") return "legacy";
@@ -32974,16 +33336,29 @@ var init_mcp_stateless_client_adapter = __esm({
32974
33336
  async callTool(params) {
32975
33337
  return this.client.callTool(params.name, params.arguments ?? {});
32976
33338
  }
32977
- async listResources() {
32978
- const result = await this.client.listResources();
32979
- return { resources: Array.isArray(result["resources"]) ? result["resources"] : [] };
33339
+ async listResources(params) {
33340
+ const result = await this.client.listResources(params?.cursor);
33341
+ return {
33342
+ resources: Array.isArray(result["resources"]) ? result["resources"] : [],
33343
+ ...nextCursorOf(result)
33344
+ };
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
+ };
32980
33352
  }
32981
33353
  async readResource(params) {
32982
33354
  return this.client.readResource(params.uri);
32983
33355
  }
32984
- async listPrompts() {
32985
- const result = await this.client.listPrompts();
32986
- return { prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [] };
33356
+ async listPrompts(params) {
33357
+ const result = await this.client.listPrompts(params?.cursor);
33358
+ return {
33359
+ prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [],
33360
+ ...nextCursorOf(result)
33361
+ };
32987
33362
  }
32988
33363
  async getPrompt(params) {
32989
33364
  return this.client.getPrompt(params.name, params.arguments ?? {});
@@ -33342,7 +33717,7 @@ var init_health_check = __esm({
33342
33717
  this.consecutiveSuccesses++;
33343
33718
  this.consecutiveFailures = 0;
33344
33719
  const previousStatus = this.status;
33345
- if (this.consecutiveSuccesses >= this.options.healthyThreshold) {
33720
+ if (previousStatus === "unknown" || this.consecutiveSuccesses >= this.options.healthyThreshold) {
33346
33721
  this.status = latencyMs > this.options.degradedLatencyMs ? "degraded" : "healthy";
33347
33722
  }
33348
33723
  this.lastResult = {
@@ -33506,6 +33881,7 @@ var init_mcp_client_service = __esm({
33506
33881
  "libs/sdk/src/remote-mcp/mcp-client.service.ts"() {
33507
33882
  "use strict";
33508
33883
  import_protocol48 = require("@frontmcp/protocol");
33884
+ init_list_all_pages_utils();
33509
33885
  init_mcp_error();
33510
33886
  init_remote_errors();
33511
33887
  init_transport_errors();
@@ -34109,10 +34485,15 @@ var init_mcp_client_service = __esm({
34109
34485
  const httpOptions = request.transportOptions;
34110
34486
  return httpOptions?.fallbackToSSE ?? true;
34111
34487
  }
34488
+ // Each list follows `nextCursor` to the end: a FrontMCP remote pages its lists
34489
+ // (40 per page by default), and proxying only the first page silently dropped
34490
+ // everything after it.
34112
34491
  async listToolsInternal(connection) {
34113
34492
  try {
34114
- const result = await connection.client.listTools();
34115
- return result.tools || [];
34493
+ return await listAllPages("tools/list", async (cursor) => {
34494
+ const result = await connection.client.listTools(cursor ? { cursor } : void 0);
34495
+ return { items: result.tools, nextCursor: result.nextCursor };
34496
+ });
34116
34497
  } catch (error) {
34117
34498
  this.logger.warn(`Failed to list tools: ${error.message}`);
34118
34499
  return [];
@@ -34120,8 +34501,10 @@ var init_mcp_client_service = __esm({
34120
34501
  }
34121
34502
  async listResourcesInternal(connection) {
34122
34503
  try {
34123
- const result = await connection.client.listResources();
34124
- return result.resources || [];
34504
+ return await listAllPages("resources/list", async (cursor) => {
34505
+ const result = await connection.client.listResources(cursor ? { cursor } : void 0);
34506
+ return { items: result.resources, nextCursor: result.nextCursor };
34507
+ });
34125
34508
  } catch (error) {
34126
34509
  this.logger.warn(`Failed to list resources: ${error.message}`);
34127
34510
  return [];
@@ -34129,8 +34512,10 @@ var init_mcp_client_service = __esm({
34129
34512
  }
34130
34513
  async listResourceTemplatesInternal(connection) {
34131
34514
  try {
34132
- const result = await connection.client.listResourceTemplates();
34133
- return result.resourceTemplates || [];
34515
+ return await listAllPages("resources/templates/list", async (cursor) => {
34516
+ const result = await connection.client.listResourceTemplates(cursor ? { cursor } : void 0);
34517
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
34518
+ });
34134
34519
  } catch (error) {
34135
34520
  this.logger.warn(`Failed to list resource templates: ${error.message}`);
34136
34521
  return [];
@@ -34138,8 +34523,10 @@ var init_mcp_client_service = __esm({
34138
34523
  }
34139
34524
  async listPromptsInternal(connection) {
34140
34525
  try {
34141
- const result = await connection.client.listPrompts();
34142
- return result.prompts || [];
34526
+ return await listAllPages("prompts/list", async (cursor) => {
34527
+ const result = await connection.client.listPrompts(cursor ? { cursor } : void 0);
34528
+ return { items: result.prompts, nextCursor: result.nextCursor };
34529
+ });
34143
34530
  } catch (error) {
34144
34531
  this.logger.warn(`Failed to list prompts: ${error.message}`);
34145
34532
  return [];
@@ -41592,8 +41979,11 @@ var init_session_verify_flow = __esm({
41592
41979
  const machineId = (0, import_utils78.getMachineId)();
41593
41980
  const authSignature = subject ? `${authMode}:${subject}` : authMode;
41594
41981
  if (sessionIdHeader) {
41595
- const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
41596
- if (existingPayload && existingPayload.nodeId === machineId) {
41982
+ const body = this.rawInput.request.body;
41983
+ const isInitialize2 = typeof body === "object" && body !== null && "method" in body && body.method === "initialize";
41984
+ const decrypted = decryptPublicSession(sessionIdHeader, authSignature);
41985
+ const existingPayload = decrypted && isInitialize2 && decrypted.nodeId !== machineId ? void 0 : decrypted;
41986
+ if (existingPayload) {
41597
41987
  const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
41598
41988
  const user2 = (0, import_auth39.anonymousCallerClaims)(options, anonId);
41599
41989
  this.respond({
@@ -42549,9 +42939,11 @@ var init_instance_local_primary_auth = __esm({
42549
42939
  } else if ((0, import_utils80.isProduction)() && (0, import_auth12.isOrchestratedMode)(options)) {
42550
42940
  throw new import_auth14.JwtSecretRequiredError(options.mode);
42551
42941
  } else {
42552
- this.logger.warn(
42553
- "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."
42554
- );
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
+ }
42555
42947
  this.secret = getDefaultNoAuthSecret();
42556
42948
  }
42557
42949
  this.tokenStorage = this.readTokenStorage(options);
@@ -46520,7 +46912,14 @@ var init_encrypted_elicitation_store = __esm({
46520
46912
  });
46521
46913
 
46522
46914
  // libs/sdk/src/elicitation/store/elicitation-store.factory.ts
46523
- 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
+ }
46524
46923
  if ((0, import_utils84.getEnv)("UPSTASH_REDIS_REST_URL")) {
46525
46924
  return "upstash";
46526
46925
  }
@@ -46610,7 +47009,8 @@ async function createElicitationStore(options = {}) {
46610
47009
  });
46611
47010
  }
46612
47011
  }
46613
- 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")) {
46614
47014
  throw new ElicitationNotSupportedError(
46615
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."
46616
47016
  );
@@ -46631,7 +47031,7 @@ async function createElicitationStore(options = {}) {
46631
47031
  );
46632
47032
  }
46633
47033
  let store = new StorageElicitationStore(storage, logger);
46634
- const type = detectStorageType2(storage);
47034
+ const type = detectStorageType2(storage, finalStorageConfig?.type);
46635
47035
  const encryptionEnabled = encryption?.enabled ?? "auto";
46636
47036
  const encryptionSecret = encryption?.secret;
46637
47037
  const hasSecret = encryptionSecret || isElicitationEncryptionAvailable();
@@ -49458,6 +49858,7 @@ function createJobDefinitionStore(options, logger) {
49458
49858
  host: options.redis.host ?? "localhost",
49459
49859
  port: options.redis.port ?? 6379
49460
49860
  });
49861
+ (0, import_utils88.attachRedisErrorListener)(client, { label: "JobDefinitionStore", logger: effectiveLogger });
49461
49862
  return {
49462
49863
  // Factory-created client lifetime is bound to the store, so the store
49463
49864
  // must close it on dispose() — pass ownsClient: true.
@@ -49473,10 +49874,11 @@ function createJobDefinitionStore(options, logger) {
49473
49874
  type: "memory"
49474
49875
  };
49475
49876
  }
49476
- var noopFn, noopLogger2;
49877
+ var import_utils88, noopFn, noopLogger2;
49477
49878
  var init_job_definition_store_factory = __esm({
49478
49879
  "libs/sdk/src/job/store/job-definition-store.factory.ts"() {
49479
49880
  "use strict";
49881
+ import_utils88 = require("@frontmcp/utils");
49480
49882
  init_memory_job_definition_store();
49481
49883
  noopFn = () => {
49482
49884
  };
@@ -49669,6 +50071,7 @@ function createJobStateStore(options, logger) {
49669
50071
  host: options.redis.host ?? "localhost",
49670
50072
  port: options.redis.port ?? 6379
49671
50073
  });
50074
+ (0, import_utils89.attachRedisErrorListener)(client, { label: "JobStateStore", logger });
49672
50075
  return {
49673
50076
  store: new RedisJobStateStore2(client, logger, keyPrefix, ttlSeconds),
49674
50077
  type: "redis"
@@ -49682,9 +50085,11 @@ function createJobStateStore(options, logger) {
49682
50085
  type: "memory"
49683
50086
  };
49684
50087
  }
50088
+ var import_utils89;
49685
50089
  var init_job_state_store_factory = __esm({
49686
50090
  "libs/sdk/src/job/store/job-state-store.factory.ts"() {
49687
50091
  "use strict";
50092
+ import_utils89 = require("@frontmcp/utils");
49688
50093
  init_memory_job_state_store();
49689
50094
  }
49690
50095
  });
@@ -50555,15 +50960,15 @@ var init_skill_session_types = __esm({
50555
50960
  });
50556
50961
 
50557
50962
  // libs/sdk/src/skill/session/skill-session.manager.ts
50558
- var import_utils88, SkillSessionManager;
50963
+ var import_utils90, SkillSessionManager;
50559
50964
  var init_skill_session_manager = __esm({
50560
50965
  "libs/sdk/src/skill/session/skill-session.manager.ts"() {
50561
50966
  "use strict";
50562
- import_utils88 = require("@frontmcp/utils");
50967
+ import_utils90 = require("@frontmcp/utils");
50563
50968
  init_errors();
50564
50969
  init_skill_session_types();
50565
- SkillSessionManager = class extends import_utils88.EventEmitter {
50566
- storage = new import_utils88.AsyncLocalStorage();
50970
+ SkillSessionManager = class extends import_utils90.EventEmitter {
50971
+ storage = new import_utils90.AsyncLocalStorage();
50567
50972
  options;
50568
50973
  logger;
50569
50974
  store;
@@ -51762,11 +52167,11 @@ function createSkillHttpAuthValidator(skillsConfig, logger) {
51762
52167
  logger
51763
52168
  });
51764
52169
  }
51765
- var import_utils89, SkillHttpAuthValidator;
52170
+ var import_utils91, SkillHttpAuthValidator;
51766
52171
  var init_skill_http_auth = __esm({
51767
52172
  "libs/sdk/src/skill/auth/skill-http-auth.ts"() {
51768
52173
  "use strict";
51769
- import_utils89 = require("@frontmcp/utils");
52174
+ import_utils91 = require("@frontmcp/utils");
51770
52175
  init_common();
51771
52176
  SkillHttpAuthValidator = class {
51772
52177
  skillsConfig;
@@ -51848,7 +52253,7 @@ var init_skill_http_auth = __esm({
51848
52253
  const keyBytes = encoder5.encode(key);
51849
52254
  if (keyBytes.length === candidateBytes.length) {
51850
52255
  try {
51851
- if ((0, import_utils89.timingSafeEqual)(keyBytes, candidateBytes)) {
52256
+ if ((0, import_utils91.timingSafeEqual)(keyBytes, candidateBytes)) {
51852
52257
  found = true;
51853
52258
  }
51854
52259
  } catch {
@@ -52129,6 +52534,7 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
52129
52534
  enableReadyCheck: false,
52130
52535
  maxRetriesPerRequest: 3
52131
52536
  });
52537
+ (0, import_utils92.attachRedisErrorListener)(client, { label: "SkillHttpCache" });
52132
52538
  await client.connect();
52133
52539
  return new RedisSkillHttpCache({
52134
52540
  getClient: async () => client,
@@ -52136,9 +52542,11 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
52136
52542
  ttlMs
52137
52543
  });
52138
52544
  }
52545
+ var import_utils92;
52139
52546
  var init_skill_http_cache_factory = __esm({
52140
52547
  "libs/sdk/src/skill/cache/skill-http-cache.factory.ts"() {
52141
52548
  "use strict";
52549
+ import_utils92 = require("@frontmcp/utils");
52142
52550
  init_skill_http_cache();
52143
52551
  }
52144
52552
  });
@@ -55309,15 +55717,15 @@ async function readSkillFileByPath(instance, filePath) {
55309
55717
  let candidatePath;
55310
55718
  for (const [subdir, configured] of candidateDirs) {
55311
55719
  if (segments[0] !== subdir) continue;
55312
- const dir = configured ? (0, import_utils91.isAbsolute)(configured) ? configured : (0, import_utils91.pathResolve)(baseDir, configured) : (0, import_utils91.joinPath)(baseDir, subdir);
55313
- 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));
55314
55722
  if (!isInside(candidatePath, dir)) {
55315
55723
  throw new PublicMcpError(`Invalid file path "${filePath}".`, "INVALID_PARAMS", 400);
55316
55724
  }
55317
55725
  break;
55318
55726
  }
55319
55727
  if (!candidatePath) {
55320
- candidatePath = (0, import_utils91.pathResolve)(baseDir, ...segments);
55728
+ candidatePath = (0, import_utils94.pathResolve)(baseDir, ...segments);
55321
55729
  if (!isInside(candidatePath, baseDir)) {
55322
55730
  throw new PublicMcpError(`Invalid file path "${filePath}".`, "INVALID_PARAMS", 400);
55323
55731
  }
@@ -55325,7 +55733,7 @@ async function readSkillFileByPath(instance, filePath) {
55325
55733
  const mimeType = guessMimeType(filePath);
55326
55734
  let buffer;
55327
55735
  try {
55328
- buffer = await (0, import_utils91.readFileBuffer)(candidatePath);
55736
+ buffer = await (0, import_utils94.readFileBuffer)(candidatePath);
55329
55737
  } catch (err) {
55330
55738
  if (isFileMissingError(err)) {
55331
55739
  throw new ResourceNotFoundError(`skill://${instance.getSkillPath()}/${filePath}`);
@@ -55368,7 +55776,7 @@ async function tryResolvedSubFile(instance, kind, tail) {
55368
55776
  const resolvedPath = resolveEntryPath2(instance, kind, absolutePath, filename);
55369
55777
  if (!resolvedPath) continue;
55370
55778
  try {
55371
- const buf = await (0, import_utils91.readFileBuffer)(resolvedPath);
55779
+ const buf = await (0, import_utils94.readFileBuffer)(resolvedPath);
55372
55780
  const text = (Buffer.isBuffer(buf) ? buf : Buffer.from(buf)).toString("utf-8");
55373
55781
  return parseSkillMdFrontmatter(text).body;
55374
55782
  } catch (err) {
@@ -55381,18 +55789,18 @@ async function tryResolvedSubFile(instance, kind, tail) {
55381
55789
  return void 0;
55382
55790
  }
55383
55791
  function resolveEntryPath2(instance, kind, absolutePath, filename) {
55384
- if (absolutePath && (0, import_utils91.isAbsolute)(absolutePath)) return absolutePath;
55792
+ if (absolutePath && (0, import_utils94.isAbsolute)(absolutePath)) return absolutePath;
55385
55793
  if (!filename) return void 0;
55386
55794
  const baseDir = instance.getBaseDir();
55387
55795
  const configured = instance.getResources()?.[kind];
55388
55796
  let dir;
55389
- if (configured && (0, import_utils91.isAbsolute)(configured)) {
55797
+ if (configured && (0, import_utils94.isAbsolute)(configured)) {
55390
55798
  dir = configured;
55391
55799
  } else if (baseDir) {
55392
- 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);
55393
55801
  }
55394
55802
  if (!dir) return void 0;
55395
- const resolved = (0, import_utils91.pathResolve)(dir, filename);
55803
+ const resolved = (0, import_utils94.pathResolve)(dir, filename);
55396
55804
  if (!isInside(resolved, dir)) return void 0;
55397
55805
  return resolved;
55398
55806
  }
@@ -55424,11 +55832,11 @@ function isInside(child, parent) {
55424
55832
  const parentPrefix = normParent.endsWith("/") ? normParent : `${normParent}/`;
55425
55833
  return normChild === normParent || normChild.startsWith(parentPrefix);
55426
55834
  }
55427
- var import_utils91;
55835
+ var import_utils94;
55428
55836
  var init_sep_2640_resource_helpers = __esm({
55429
55837
  "libs/sdk/src/skill/sep-2640/sep-2640.resource-helpers.ts"() {
55430
55838
  "use strict";
55431
- import_utils91 = require("@frontmcp/utils");
55839
+ import_utils94 = require("@frontmcp/utils");
55432
55840
  init_errors();
55433
55841
  init_skill_filter_helper();
55434
55842
  init_skill_md_parser();
@@ -55655,17 +56063,17 @@ async function resolveLastModifiedForSkill(skill) {
55655
56063
  return void 0;
55656
56064
  }
55657
56065
  try {
55658
- const s = await (0, import_utils92.stat)(record.filePath);
56066
+ const s = await (0, import_utils95.stat)(record.filePath);
55659
56067
  return s.mtime instanceof Date ? s.mtime.toISOString() : void 0;
55660
56068
  } catch {
55661
56069
  return void 0;
55662
56070
  }
55663
56071
  }
55664
- var import_utils92;
56072
+ var import_utils95;
55665
56073
  var init_sep_2640_last_modified = __esm({
55666
56074
  "libs/sdk/src/skill/sep-2640/sep-2640.last-modified.ts"() {
55667
56075
  "use strict";
55668
- import_utils92 = require("@frontmcp/utils");
56076
+ import_utils95 = require("@frontmcp/utils");
55669
56077
  }
55670
56078
  });
55671
56079
 
@@ -55855,7 +56263,7 @@ function createDefaultSigner(mod, logger) {
55855
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)."
55856
56264
  );
55857
56265
  }
55858
- const bytes = (0, import_utils93.randomBytes)(32);
56266
+ const bytes = (0, import_utils96.randomBytes)(32);
55859
56267
  logger.warn(
55860
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."
55861
56269
  );
@@ -55867,12 +56275,12 @@ function createDefaultStore(mod, logger) {
55867
56275
  );
55868
56276
  return new mod.MemoryAuditStore();
55869
56277
  }
55870
- var import_di39, import_utils93, injectedFactory;
56278
+ var import_di39, import_utils96, injectedFactory;
55871
56279
  var init_skill_audit_helper = __esm({
55872
56280
  "libs/sdk/src/skill/skill-audit.helper.ts"() {
55873
56281
  "use strict";
55874
56282
  import_di39 = require("@frontmcp/di");
55875
- import_utils93 = require("@frontmcp/utils");
56283
+ import_utils96 = require("@frontmcp/utils");
55876
56284
  }
55877
56285
  });
55878
56286
 
@@ -55944,6 +56352,51 @@ var init_skill_scope_helper = __esm({
55944
56352
  }
55945
56353
  });
55946
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
+
55947
56400
  // libs/sdk/src/transport/bus/redis-transport-bus.ts
55948
56401
  var DEFAULT_BUS_PREFIX, DEFAULT_BUS_TTL_SECONDS, REVOKE_LUA, RedisTransportBus;
55949
56402
  var init_redis_transport_bus = __esm({
@@ -56073,10 +56526,11 @@ var redis_event_store_exports = {};
56073
56526
  __export(redis_event_store_exports, {
56074
56527
  RedisEventStore: () => RedisEventStore
56075
56528
  });
56076
- var RedisEventStore;
56529
+ var import_utils97, RedisEventStore;
56077
56530
  var init_redis_event_store = __esm({
56078
56531
  "libs/sdk/src/transport/event-stores/redis.event-store.ts"() {
56079
56532
  "use strict";
56533
+ import_utils97 = require("@frontmcp/utils");
56080
56534
  init_sdk_errors();
56081
56535
  RedisEventStore = class {
56082
56536
  client;
@@ -56120,6 +56574,9 @@ var init_redis_event_store = __esm({
56120
56574
  enableReadyCheck: false,
56121
56575
  maxRetriesPerRequest: 3
56122
56576
  });
56577
+ (0, import_utils97.attachRedisErrorListener)(this.client, {
56578
+ label: "RedisEventStore"
56579
+ });
56123
56580
  await this.client.connect();
56124
56581
  }
56125
56582
  return this.client;
@@ -56512,11 +56969,11 @@ function createSqliteSessionStore(options, logger) {
56512
56969
  logger?.info("[SessionStoreFactory] Creating SQLite session store", { path: options.path });
56513
56970
  return new SqliteSessionStore(options);
56514
56971
  }
56515
- var import_utils94;
56972
+ var import_utils98;
56516
56973
  var init_session_store_factory = __esm({
56517
56974
  "libs/sdk/src/auth/session/session-store.factory.ts"() {
56518
56975
  "use strict";
56519
- import_utils94 = require("@frontmcp/utils");
56976
+ import_utils98 = require("@frontmcp/utils");
56520
56977
  init_common();
56521
56978
  }
56522
56979
  });
@@ -56591,14 +57048,14 @@ function toClientInfo(value) {
56591
57048
  const { name: name44, version } = value;
56592
57049
  return typeof name44 === "string" && typeof version === "string" ? { name: name44, version } : void 0;
56593
57050
  }
56594
- 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;
56595
57052
  var init_handle_mcp_20260728_flow = __esm({
56596
57053
  "libs/sdk/src/transport/flows/handle.mcp-20260728.flow.ts"() {
56597
57054
  "use strict";
56598
57055
  import_auth55 = require("@frontmcp/auth");
56599
57056
  import_lazy_zod102 = require("@frontmcp/lazy-zod");
56600
57057
  import_protocol52 = require("@frontmcp/protocol");
56601
- import_utils95 = require("@frontmcp/utils");
57058
+ import_utils99 = require("@frontmcp/utils");
56602
57059
  init_common();
56603
57060
  init_context();
56604
57061
  init_notification();
@@ -56696,7 +57153,7 @@ var init_handle_mcp_20260728_flow = __esm({
56696
57153
  if (!context) return (fn) => fn();
56697
57154
  const storage = this.scope.providers.get(FrontMcpContextStorage);
56698
57155
  return async (fn) => {
56699
- await (0, import_utils95.runRequestExclusive)(() => storage.runWithContext(context, fn));
57156
+ await (0, import_utils99.runRequestExclusive)(() => storage.runWithContext(context, fn));
56700
57157
  };
56701
57158
  }
56702
57159
  findToolSchema(scope, toolName) {
@@ -56773,7 +57230,7 @@ var init_handle_mcp_20260728_flow = __esm({
56773
57230
  const sink = new RequestNotificationSink(logLevel, progressToken);
56774
57231
  const clientInfo = toClientInfo(meta[import_protocol52.MCP_20260728_META.clientInfo]);
56775
57232
  if (clientInfo) {
56776
- const platformType = detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
57233
+ const platformType = detectPlatformFromCapabilities(clientCapabilities) ?? detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
56777
57234
  this.tryGetContext()?.setClientInfo(clientInfo, platformType);
56778
57235
  }
56779
57236
  const dispatchOptions = {
@@ -56871,6 +57328,29 @@ var init_handle_mcp_20260728_flow = __esm({
56871
57328
  }
56872
57329
  });
56873
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
+
56874
57354
  // libs/sdk/src/skill/skill-mode.utils.ts
56875
57355
  function detectSkillsOnlyMode(query) {
56876
57356
  if (!query) return false;
@@ -56899,16 +57379,40 @@ function getQuerySessionId2(urlPath) {
56899
57379
  return void 0;
56900
57380
  }
56901
57381
  }
56902
- var import_lazy_zod103, import_utils96, plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
57382
+ function resolveLegacySseSession(params) {
57383
+ const { rawHeader, requestUrl, authorizationSession, createSession } = params;
57384
+ const rawMcpSessionHeader = typeof rawHeader === "string" ? rawHeader : void 0;
57385
+ const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
57386
+ const querySessionId = getQuerySessionId2(requestUrl);
57387
+ const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
57388
+ if (rawHeader !== void 0 && !mcpSessionHeader) {
57389
+ return { responded404: true, createdNew: false };
57390
+ }
57391
+ if (querySessionId !== void 0 && !validatedQuerySessionId) {
57392
+ return { responded404: true, createdNew: false };
57393
+ }
57394
+ const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
57395
+ if (effectiveSessionId) {
57396
+ if (authorizationSession?.id === effectiveSessionId) {
57397
+ return { session: authorizationSession, createdNew: false, responded404: false };
57398
+ }
57399
+ return { responded404: true, createdNew: false };
57400
+ }
57401
+ if (authorizationSession) {
57402
+ return { session: authorizationSession, createdNew: false, responded404: false };
57403
+ }
57404
+ return { session: createSession(), createdNew: true, responded404: false };
57405
+ }
57406
+ var import_lazy_zod103, import_utils101, plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
56903
57407
  var init_handle_sse_flow = __esm({
56904
57408
  "libs/sdk/src/transport/flows/handle.sse.flow.ts"() {
56905
57409
  "use strict";
56906
57410
  import_lazy_zod103 = require("@frontmcp/lazy-zod");
56907
- import_utils96 = require("@frontmcp/utils");
57411
+ import_utils101 = require("@frontmcp/utils");
56908
57412
  init_session_id_utils();
56909
57413
  init_common();
56910
57414
  init_errors();
56911
- init_ha_constants();
57415
+ init_ha_headers();
56912
57416
  init_skill_mode_utils();
56913
57417
  plan38 = {
56914
57418
  pre: ["parseInput", "router"],
@@ -56940,39 +57444,26 @@ var init_handle_sse_flow = __esm({
56940
57444
  const { request } = this.rawInput;
56941
57445
  const authorization = request[ServerRequestTokens.auth];
56942
57446
  const { token } = authorization;
56943
- const raw = request.headers?.["mcp-session-id"];
56944
- const rawMcpSessionHeader = typeof raw === "string" ? raw : void 0;
56945
- const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
56946
57447
  const anyReq = request;
56947
- const querySessionId = getQuerySessionId2(anyReq.url ?? anyReq.path);
56948
- const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
56949
- if (raw !== void 0 && !mcpSessionHeader) {
56950
- this.respond(httpRespond.sessionNotFound("invalid session id"));
56951
- return;
56952
- }
56953
- if (querySessionId !== void 0 && !validatedQuerySessionId) {
57448
+ const resolution = resolveLegacySseSession({
57449
+ rawHeader: request.headers?.["mcp-session-id"],
57450
+ requestUrl: anyReq.url ?? anyReq.path,
57451
+ authorizationSession: authorization.session,
57452
+ createSession: () => {
57453
+ const query = request.query;
57454
+ const skillsOnlyMode = detectSkillsOnlyMode(query);
57455
+ return createSessionId("legacy-sse", token, {
57456
+ userAgent: request.headers?.["user-agent"],
57457
+ platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
57458
+ skillsOnlyMode
57459
+ });
57460
+ }
57461
+ });
57462
+ if (resolution.responded404 || !resolution.session) {
56954
57463
  this.respond(httpRespond.sessionNotFound("invalid session id"));
56955
57464
  return;
56956
57465
  }
56957
- const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
56958
- let session;
56959
- if (effectiveSessionId) {
56960
- if (authorization.session?.id === effectiveSessionId) {
56961
- session = authorization.session;
56962
- } else {
56963
- session = { id: effectiveSessionId };
56964
- }
56965
- } else if (authorization.session) {
56966
- session = authorization.session;
56967
- } else {
56968
- const query = request.query;
56969
- const skillsOnlyMode = detectSkillsOnlyMode(query);
56970
- session = createSessionId("legacy-sse", token, {
56971
- userAgent: request.headers?.["user-agent"],
56972
- platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
56973
- skillsOnlyMode
56974
- });
56975
- }
57466
+ const session = resolution.session;
56976
57467
  this.state.set(stateSchema38.parse({ token, session }));
56977
57468
  }
56978
57469
  async router() {
@@ -56995,16 +57486,7 @@ var init_handle_sse_flow = __esm({
56995
57486
  const { request, response } = this.rawInput;
56996
57487
  const { token, session } = this.state.required;
56997
57488
  const transport = await transportService.createTransporter("sse", token, session.id, response);
56998
- if ((0, import_utils96.getRuntimeContext)().deployment === "distributed") {
56999
- const nodeId = (0, import_utils96.getMachineId)();
57000
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57001
- const cookie = (0, import_utils96.buildSetCookie)({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57002
- if (cookie) {
57003
- const existing = response.getHeader("Set-Cookie");
57004
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57005
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57006
- }
57007
- }
57489
+ applyNodeAffinity(response, request);
57008
57490
  await transport.initialize(request, response);
57009
57491
  this.handled();
57010
57492
  }
@@ -57020,12 +57502,12 @@ var init_handle_sse_flow = __esm({
57020
57502
  const { request, response } = this.rawInput;
57021
57503
  const { token, session } = this.state.required;
57022
57504
  const transport = await transportService.getTransporter("sse", token, session.id);
57023
- if (!transport && (0, import_utils96.getRuntimeContext)().deployment === "distributed") {
57505
+ if (!transport && (0, import_utils101.getRuntimeContext)().deployment === "distributed") {
57024
57506
  const storedSession = await transportService.getStoredSession("sse", token, session.id);
57025
57507
  if (storedSession) {
57026
57508
  const haManager = this.scope.haManager;
57027
57509
  const relay = haManager?.getRelay();
57028
- 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)()) {
57029
57511
  const isAlive2 = await haManager.isNodeAlive(storedSession.session.nodeId);
57030
57512
  if (isAlive2) {
57031
57513
  try {
@@ -57120,9 +57602,10 @@ var init_handle_stateless_http_flow = __esm({
57120
57602
  import_protocol53 = require("@frontmcp/protocol");
57121
57603
  init_common();
57122
57604
  init_errors();
57605
+ init_ha_headers();
57123
57606
  init_transport_types();
57124
57607
  plan39 = {
57125
- pre: ["parseInput", "router"],
57608
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57126
57609
  execute: ["handleRequest"],
57127
57610
  post: [],
57128
57611
  finalize: ["cleanup"]
@@ -57136,6 +57619,9 @@ var init_handle_stateless_http_flow = __esm({
57136
57619
  ({ Stage: Stage40 } = FlowHooksOf(name40));
57137
57620
  HandleStatelessHttpFlow = class extends FlowBase {
57138
57621
  name = name40;
57622
+ async applyNodeHeaders() {
57623
+ applyMachineIdHeader(this.rawInput.response);
57624
+ }
57139
57625
  async parseInput() {
57140
57626
  const { request } = this.rawInput;
57141
57627
  const logger = this.scope.logger.child("HandleStatelessHttpFlow");
@@ -57197,6 +57683,9 @@ var init_handle_stateless_http_flow = __esm({
57197
57683
  this.handled();
57198
57684
  }
57199
57685
  };
57686
+ __decorateClass([
57687
+ Stage40("applyNodeHeaders")
57688
+ ], HandleStatelessHttpFlow.prototype, "applyNodeHeaders", 1);
57200
57689
  __decorateClass([
57201
57690
  Stage40("parseInput")
57202
57691
  ], HandleStatelessHttpFlow.prototype, "parseInput", 1);
@@ -57251,11 +57740,11 @@ var init_ext_apps_types = __esm({
57251
57740
  function createExtAppsMessageHandler(options) {
57252
57741
  return new ExtAppsMessageHandler(options);
57253
57742
  }
57254
- var import_utils97, ExtAppsMessageHandler, ExtAppsError, ExtAppsMethodNotFoundError, ExtAppsInvalidParamsError, ExtAppsNotSupportedError, ExtAppsToolNotFoundError;
57743
+ var import_utils102, ExtAppsMessageHandler, ExtAppsError, ExtAppsMethodNotFoundError, ExtAppsInvalidParamsError, ExtAppsNotSupportedError, ExtAppsToolNotFoundError;
57255
57744
  var init_ext_apps_handler = __esm({
57256
57745
  "libs/sdk/src/ext-apps/ext-apps.handler.ts"() {
57257
57746
  "use strict";
57258
- import_utils97 = require("@frontmcp/utils");
57747
+ import_utils102 = require("@frontmcp/utils");
57259
57748
  init_ext_apps_types();
57260
57749
  ExtAppsMessageHandler = class {
57261
57750
  context;
@@ -57299,7 +57788,7 @@ var init_ext_apps_handler = __esm({
57299
57788
  code: errorCode,
57300
57789
  message: errorMessage,
57301
57790
  // Only include stack traces in development to avoid leaking internals in production
57302
- 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
57303
57792
  }
57304
57793
  };
57305
57794
  }
@@ -57585,7 +58074,7 @@ function resolveStreamableHttpSession(params) {
57585
58074
  if (authorizationSession?.id === mcpSessionHeader) {
57586
58075
  return { session: authorizationSession, createdNew: false, responded404: false };
57587
58076
  }
57588
- return { session: { id: mcpSessionHeader }, createdNew: false, responded404: false };
58077
+ return { responded404: true, createdNew: false };
57589
58078
  }
57590
58079
  if (authorizationSession) {
57591
58080
  return { session: authorizationSession, createdNew: false, responded404: false };
@@ -57661,22 +58150,21 @@ async function lookupStreamableHttpTransport(params) {
57661
58150
  }
57662
58151
  return { kind: "session-not-initialized", recreationError };
57663
58152
  }
57664
- 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;
57665
58154
  var init_handle_streamable_http_flow = __esm({
57666
58155
  "libs/sdk/src/transport/flows/handle.streamable-http.flow.ts"() {
57667
58156
  "use strict";
57668
58157
  import_lazy_zod105 = require("@frontmcp/lazy-zod");
57669
58158
  import_protocol54 = require("@frontmcp/protocol");
57670
- import_utils98 = require("@frontmcp/utils");
57671
58159
  init_session_id_utils();
57672
58160
  init_common();
57673
58161
  init_errors();
57674
58162
  init_ext_apps2();
57675
- init_ha_constants();
58163
+ init_ha_headers();
57676
58164
  init_skill_mode_utils();
57677
58165
  init_mcp_surface();
57678
58166
  plan40 = {
57679
- pre: ["parseInput", "router"],
58167
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57680
58168
  execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
57681
58169
  post: [],
57682
58170
  finalize: ["cleanup"]
@@ -57702,6 +58190,9 @@ var init_handle_streamable_http_flow = __esm({
57702
58190
  ({ Stage: Stage41 } = FlowHooksOf(name41));
57703
58191
  HandleStreamableHttpFlow = class extends FlowBase {
57704
58192
  name = name41;
58193
+ async applyNodeHeaders() {
58194
+ applyMachineIdHeader(this.rawInput.response);
58195
+ }
57705
58196
  async parseInput() {
57706
58197
  const { request } = this.rawInput;
57707
58198
  const authorization = request[ServerRequestTokens.auth];
@@ -57733,7 +58224,7 @@ var init_handle_streamable_http_flow = __esm({
57733
58224
  });
57734
58225
  }
57735
58226
  if (sessionResolution.responded404 || !sessionResolution.session) {
57736
- logger.warn("parseInput: invalid mcp-session-id header");
58227
+ logger.warn("parseInput: mcp-session-id is not a session this server verified");
57737
58228
  this.respond(httpRespond.sessionNotFound("invalid session id"));
57738
58229
  return;
57739
58230
  }
@@ -57800,16 +58291,7 @@ var init_handle_streamable_http_flow = __esm({
57800
58291
  return;
57801
58292
  }
57802
58293
  }
57803
- if ((0, import_utils98.getRuntimeContext)().deployment === "distributed") {
57804
- const nodeId = (0, import_utils98.getMachineId)();
57805
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57806
- const cookie = (0, import_utils98.buildSetCookie)({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57807
- if (cookie) {
57808
- const existing = response.getHeader("Set-Cookie");
57809
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57810
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57811
- }
57812
- }
58294
+ applyNodeAffinity(response, request);
57813
58295
  logger.info("onInitialize: transport created, calling initialize");
57814
58296
  await transport.initialize(request, response);
57815
58297
  logger.info("onInitialize: completed successfully");
@@ -57992,6 +58474,17 @@ var init_handle_streamable_http_flow = __esm({
57992
58474
  const logger = this.scopeLogger.child("handle:streamable-http:onExtApps");
57993
58475
  const { request, response } = this.rawInput;
57994
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
+ }
57995
58488
  logger.info("onExtApps: starting", {
57996
58489
  sessionId: session.id?.slice(0, 20),
57997
58490
  method: request.body?.method
@@ -58043,8 +58536,20 @@ var init_handle_streamable_http_flow = __esm({
58043
58536
  const hostCapabilities = {
58044
58537
  serverToolProxy: configuredCapabilities?.serverToolProxy ?? true,
58045
58538
  logging: configuredCapabilities?.logging ?? true,
58046
- ...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
58047
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
+ }
58048
58553
  const handler = createExtAppsMessageHandler({
58049
58554
  context: {
58050
58555
  sessionId: session.id,
@@ -58078,6 +58583,9 @@ var init_handle_streamable_http_flow = __esm({
58078
58583
  this.handled();
58079
58584
  }
58080
58585
  };
58586
+ __decorateClass([
58587
+ Stage41("applyNodeHeaders")
58588
+ ], HandleStreamableHttpFlow.prototype, "applyNodeHeaders", 1);
58081
58589
  __decorateClass([
58082
58590
  Stage41("parseInput")
58083
58591
  ], HandleStreamableHttpFlow.prototype, "parseInput", 1);
@@ -58122,21 +58630,21 @@ var init_handle_streamable_http_flow = __esm({
58122
58630
  });
58123
58631
 
58124
58632
  // libs/sdk/src/transport/transport.error.ts
58125
- var import_utils99, JSON_RPC2, rpcError, rpcRequest;
58633
+ var import_utils103, JSON_RPC2, rpcError, rpcRequest;
58126
58634
  var init_transport_error = __esm({
58127
58635
  "libs/sdk/src/transport/transport.error.ts"() {
58128
58636
  "use strict";
58129
- import_utils99 = require("@frontmcp/utils");
58637
+ import_utils103 = require("@frontmcp/utils");
58130
58638
  JSON_RPC2 = "2.0";
58131
58639
  rpcError = (message, requestId) => ({
58132
58640
  jsonrpc: JSON_RPC2,
58133
58641
  error: { code: -32e3, message },
58134
- id: requestId ?? (0, import_utils99.randomUUID)()
58642
+ id: requestId ?? (0, import_utils103.randomUUID)()
58135
58643
  // change it to request id + random
58136
58644
  });
58137
58645
  rpcRequest = (requestId, method, params) => ({
58138
58646
  jsonrpc: JSON_RPC2,
58139
- id: requestId ?? (0, import_utils99.randomUUID)(),
58647
+ id: requestId ?? (0, import_utils103.randomUUID)(),
58140
58648
  method,
58141
58649
  ...params !== void 0 && { params }
58142
58650
  });
@@ -58199,14 +58707,14 @@ var init_host_validation = __esm({
58199
58707
  });
58200
58708
 
58201
58709
  // libs/sdk/src/transport/adapters/base-sse-transport.ts
58202
- 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;
58203
58711
  var init_base_sse_transport = __esm({
58204
58712
  "libs/sdk/src/transport/adapters/base-sse-transport.ts"() {
58205
58713
  "use strict";
58206
58714
  import_content_type = __toESM(require("content-type"));
58207
58715
  import_raw_body = __toESM(require("raw-body"));
58208
58716
  import_protocol55 = require("@frontmcp/protocol");
58209
- import_utils100 = require("@frontmcp/utils");
58717
+ import_utils104 = require("@frontmcp/utils");
58210
58718
  init_transport_errors();
58211
58719
  init_host_validation();
58212
58720
  MAXIMUM_MESSAGE_SIZE = "4mb";
@@ -58217,7 +58725,7 @@ var init_base_sse_transport = __esm({
58217
58725
  constructor(_endpoint, res, options) {
58218
58726
  this._endpoint = _endpoint;
58219
58727
  this.res = res;
58220
- this._sessionId = options?.sessionId ?? (0, import_utils100.randomUUID)();
58728
+ this._sessionId = options?.sessionId ?? (0, import_utils104.randomUUID)();
58221
58729
  this._options = options || { enableDnsRebindingProtection: false };
58222
58730
  }
58223
58731
  _endpoint;
@@ -59663,11 +60171,11 @@ var init_transport_remote = __esm({
59663
60171
  });
59664
60172
 
59665
60173
  // libs/sdk/src/transport/transport.registry.ts
59666
- var import_utils101, TransportService;
60174
+ var import_utils105, SESSION_STORE_RETRY_BASE_MS, SESSION_STORE_RETRY_MAX_MS, TransportService;
59667
60175
  var init_transport_registry = __esm({
59668
60176
  "libs/sdk/src/transport/transport.registry.ts"() {
59669
60177
  "use strict";
59670
- import_utils101 = require("@frontmcp/utils");
60178
+ import_utils105 = require("@frontmcp/utils");
59671
60179
  init_session_store_factory();
59672
60180
  init_transport_errors();
59673
60181
  init_handle_mcp_20260728_flow();
@@ -59677,6 +60185,8 @@ var init_transport_registry = __esm({
59677
60185
  init_transport_local();
59678
60186
  init_transport_remote();
59679
60187
  init_transport_types();
60188
+ SESSION_STORE_RETRY_BASE_MS = 1e3;
60189
+ SESSION_STORE_RETRY_MAX_MS = 3e4;
59680
60190
  TransportService = class {
59681
60191
  ready;
59682
60192
  byType = /* @__PURE__ */ new Map();
@@ -59733,8 +60243,28 @@ var init_transport_registry = __esm({
59733
60243
  * Returns undefined if persistence is disabled or not configured.
59734
60244
  */
59735
60245
  getDefaultTtlMs() {
60246
+ if (this.resolvedTtlMs !== void 0) return this.resolvedTtlMs;
59736
60247
  return typeof this.persistenceConfig === "object" ? this.persistenceConfig?.defaultTtlMs : void 0;
59737
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
+ }
59738
60268
  constructor(scope, persistenceConfig, bus) {
59739
60269
  this.scope = scope;
59740
60270
  this.persistenceConfig = persistenceConfig;
@@ -59745,10 +60275,11 @@ var init_transport_registry = __esm({
59745
60275
  this.backendKind = "sqlite";
59746
60276
  const sqliteConfig = persistenceConfig.sqlite;
59747
60277
  this.transportKeyPrefix = "mcp:transport:";
60278
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? 36e5;
59748
60279
  this.pendingStoreConfig = {
59749
60280
  sqlite: sqliteConfig,
59750
60281
  keyPrefix: this.transportKeyPrefix,
59751
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
60282
+ defaultTtlMs: this.resolvedTtlMs
59752
60283
  };
59753
60284
  this.scope.logger.info("[TransportService] sqlite session store will be initialized for transport persistence");
59754
60285
  } else if (persistenceConfig !== false && persistenceConfig?.redis) {
@@ -59757,43 +60288,23 @@ var init_transport_registry = __esm({
59757
60288
  const providerType = "provider" in redisConfig ? redisConfig.provider : "redis";
59758
60289
  this.backendKind = providerType === "vercel-kv" ? "vercel-kv" : "redis";
59759
60290
  this.transportKeyPrefix = redisConfig.keyPrefix ?? "mcp:transport:";
60291
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? redisConfig.defaultTtlMs ?? 36e5;
59760
60292
  this.pendingStoreConfig = {
59761
60293
  ...redisConfig,
59762
60294
  keyPrefix: this.transportKeyPrefix,
59763
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
59764
- // 1 hour default
60295
+ defaultTtlMs: this.resolvedTtlMs
59765
60296
  };
59766
60297
  this.scope.logger.info(
59767
60298
  `[TransportService] ${providerType} session store will be initialized for transport persistence`
59768
60299
  );
59769
60300
  }
60301
+ this.storeRetryConfig = this.pendingStoreConfig;
59770
60302
  this.ready = this.initialize();
59771
60303
  }
59772
60304
  async initialize() {
59773
60305
  if (this.pendingStoreConfig) {
59774
- try {
59775
- const store = await createSessionStore(this.pendingStoreConfig, this.scope.logger.child("SessionStore"));
59776
- this.sessionStore = store;
59777
- this.pendingStoreConfig = void 0;
59778
- } catch (error) {
59779
- const err = error;
59780
- this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
59781
- error: err.message,
59782
- cause: err.cause?.message
59783
- });
59784
- }
59785
- }
59786
- if (this.sessionStore) {
59787
- const isConnected = this.sessionStore.ping ? await this.sessionStore.ping() : true;
59788
- if (!isConnected) {
59789
- const providerType = this.backendKind ?? "redis";
59790
- this.scope.logger.error(
59791
- `[TransportService] Failed to connect to ${providerType} - session persistence disabled`
59792
- );
59793
- await this.teardownSessionStore().catch(() => void 0);
59794
- } else {
59795
- this.scope.logger.info("[TransportService] Session store connection validated successfully");
59796
- }
60306
+ const connected = await this.connectSessionStore();
60307
+ if (!connected && this.backendKind !== "sqlite") this.scheduleSessionStoreRetry();
59797
60308
  }
59798
60309
  await this.scope.registryFlows(
59799
60310
  HandleStreamableHttpFlow,
@@ -59802,7 +60313,70 @@ var init_transport_registry = __esm({
59802
60313
  HandleMcp20260728Flow
59803
60314
  );
59804
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
+ }
59805
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();
59806
60380
  await this.teardownSessionStore();
59807
60381
  }
59808
60382
  /**
@@ -59863,10 +60437,33 @@ var init_transport_registry = __esm({
59863
60437
  getBackendKind() {
59864
60438
  return this.backendKind;
59865
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
+ }
59866
60460
  async getTransporter(type, token, sessionId) {
59867
60461
  const key = this.keyOf(type, token, sessionId);
59868
60462
  const local = this.lookupLocal(key);
59869
- if (local) return local;
60463
+ if (local) {
60464
+ this.refreshStoredSessionTtl(sessionId);
60465
+ return local;
60466
+ }
59870
60467
  if (this.distributed && this.bus) {
59871
60468
  const location = await this.bus.lookup(key);
59872
60469
  if (location) {
@@ -59959,9 +60556,9 @@ var init_transport_registry = __esm({
59959
60556
  protocol: storedSession.session.protocol,
59960
60557
  createdAt: storedSession.createdAt
59961
60558
  });
59962
- const currentNodeId = (0, import_utils101.getMachineId)();
60559
+ const currentNodeId = (0, import_utils105.getMachineId)();
59963
60560
  if (this.scope.haManager && storedSession.session.nodeId && storedSession.session.nodeId !== currentNodeId) {
59964
- const sessionKey = `${this.transportKeyPrefix}${sessionId}`;
60561
+ const sessionKey = `${this.getSessionKeyPrefix()}${sessionId}`;
59965
60562
  const result = await this.scope.haManager.attemptTakeover(sessionKey, storedSession.session.nodeId);
59966
60563
  if (!result.claimed) {
59967
60564
  this.scope.logger.debug("[HA] Session already claimed by another pod", { sessionId: sessionId.slice(0, 20) });
@@ -60054,7 +60651,7 @@ var init_transport_registry = __esm({
60054
60651
  authorizationId: key.tokenHash,
60055
60652
  protocol: type === "sse" ? "sse" : "streamable-http",
60056
60653
  createdAt: Date.now(),
60057
- nodeId: (0, import_utils101.getMachineId)()
60654
+ nodeId: (0, import_utils105.getMachineId)()
60058
60655
  },
60059
60656
  authorizationId: key.tokenHash,
60060
60657
  createdAt: Date.now(),
@@ -60200,7 +60797,7 @@ var init_transport_registry = __esm({
60200
60797
  }
60201
60798
  /* --------------------------------- internals -------------------------------- */
60202
60799
  sha256(value) {
60203
- return (0, import_utils101.sha256Hex)(value);
60800
+ return (0, import_utils105.sha256Hex)(value);
60204
60801
  }
60205
60802
  /**
60206
60803
  * Create a history key from components.
@@ -60294,6 +60891,7 @@ var init_transport_registry = __esm({
60294
60891
  }
60295
60892
  }
60296
60893
  evictLocal(key) {
60894
+ this.lastTtlRefreshAt.delete(key.sessionId);
60297
60895
  const typeBucket = this.byType.get(key.type);
60298
60896
  if (!typeBucket) return;
60299
60897
  const tokenBucket = typeBucket.get(key.tokenHash);
@@ -60384,9 +60982,9 @@ function persistentSessionCallerKey(authorization) {
60384
60982
  const sub = authorization?.user?.sub;
60385
60983
  if (!(0, import_auth56.isAnonymousSubject)(sub)) {
60386
60984
  const iss = authorization?.user?.iss;
60387
- 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]))}`;
60388
60986
  }
60389
- if (authorization?.token) return `token:${(0, import_utils102.sha256Hex)(authorization.token)}`;
60987
+ if (authorization?.token) return `token:${(0, import_utils106.sha256Hex)(authorization.token)}`;
60390
60988
  return null;
60391
60989
  }
60392
60990
  function claimPersistentSession(session, callerKey, store) {
@@ -60399,12 +60997,12 @@ function claimPersistentSession(session, callerKey, store) {
60399
60997
  }
60400
60998
  return owners.get(session) === callerKey ? "owner" : "refused";
60401
60999
  }
60402
- var import_auth56, import_utils102, owners;
61000
+ var import_auth56, import_utils106, owners;
60403
61001
  var init_persistent_session_owner = __esm({
60404
61002
  "libs/sdk/src/transport/persistent-session-owner.ts"() {
60405
61003
  "use strict";
60406
61004
  import_auth56 = require("@frontmcp/auth");
60407
- import_utils102 = require("@frontmcp/utils");
61005
+ import_utils106 = require("@frontmcp/utils");
60408
61006
  owners = /* @__PURE__ */ new WeakMap();
60409
61007
  }
60410
61008
  });
@@ -60441,7 +61039,7 @@ async function runWebStandardMcp(scope, request, options) {
60441
61039
  if (options.persistent) {
60442
61040
  return options.persistent.transport.handleRequest(request, { authInfo: options.authInfo });
60443
61041
  }
60444
- const sessionId = `web:${(0, import_utils103.randomUUID)()}`;
61042
+ const sessionId = `web:${(0, import_utils107.randomUUID)()}`;
60445
61043
  const accept = request.headers.get("accept") ?? "";
60446
61044
  const wantsStream = request.method === "GET" || options.sse && accept.includes("text/event-stream");
60447
61045
  const { mcpServer, transport } = await wireServer(scope, options.serverOptions, sessionId, void 0, !wantsStream);
@@ -60464,23 +61062,23 @@ async function runWebStandardMcp(scope, request, options) {
60464
61062
  }
60465
61063
  return response;
60466
61064
  }
60467
- var import_utils103;
61065
+ var import_utils107;
60468
61066
  var init_web_standard_mcp = __esm({
60469
61067
  "libs/sdk/src/transport/web-standard-mcp.ts"() {
60470
61068
  "use strict";
60471
- import_utils103 = require("@frontmcp/utils");
61069
+ import_utils107 = require("@frontmcp/utils");
60472
61070
  init_build_scoped_server_options();
60473
61071
  }
60474
61072
  });
60475
61073
 
60476
61074
  // libs/sdk/src/scope/flows/http.request.flow.ts
60477
- 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;
60478
61076
  var init_http_request_flow = __esm({
60479
61077
  "libs/sdk/src/scope/flows/http.request.flow.ts"() {
60480
61078
  "use strict";
60481
61079
  import_auth57 = require("@frontmcp/auth");
60482
61080
  import_lazy_zod108 = require("@frontmcp/lazy-zod");
60483
- import_utils104 = require("@frontmcp/utils");
61081
+ import_utils108 = require("@frontmcp/utils");
60484
61082
  init_session_verify_flow();
60485
61083
  init_common();
60486
61084
  init_schema7();
@@ -60559,7 +61157,7 @@ var init_http_request_flow = __esm({
60559
61157
  const { request } = this.rawInput;
60560
61158
  this.requestStartTime = Date.now();
60561
61159
  const ctx = this.tryGetContext();
60562
- this.requestId = ctx?.requestId ?? `req-${(0, import_utils104.randomUUID)()}`;
61160
+ this.requestId = ctx?.requestId ?? `req-${(0, import_utils108.randomUUID)()}`;
60563
61161
  const headers = request.headers ?? {};
60564
61162
  const body = request.body;
60565
61163
  const userAgent = headers["user-agent"];
@@ -61244,13 +61842,13 @@ var init_http_request_flow = __esm({
61244
61842
  });
61245
61843
 
61246
61844
  // libs/sdk/src/scope/scope.instance.ts
61247
- 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;
61248
61846
  var init_scope_instance = __esm({
61249
61847
  "libs/sdk/src/scope/scope.instance.ts"() {
61250
61848
  "use strict";
61251
61849
  import_reflect_metadata25 = require("reflect-metadata");
61252
61850
  import_guard8 = require("@frontmcp/guard");
61253
- import_utils105 = require("@frontmcp/utils");
61851
+ import_utils109 = require("@frontmcp/utils");
61254
61852
  init_agent_registry();
61255
61853
  init_call_agent_flow();
61256
61854
  init_app_registry();
@@ -61291,8 +61889,10 @@ var init_scope_instance = __esm({
61291
61889
  init_tool_registry();
61292
61890
  init_tool_utils();
61293
61891
  init_ui();
61892
+ init_ui_option_warnings();
61294
61893
  init_bus();
61295
61894
  init_event_stores();
61895
+ init_request_state();
61296
61896
  init_transport_registry();
61297
61897
  init_http_ip_filter_flow();
61298
61898
  init_http_request_flow();
@@ -61316,6 +61916,8 @@ var init_scope_instance = __esm({
61316
61916
  transportService;
61317
61917
  // TODO: migrate transport service to transport.registry
61318
61918
  notificationService;
61919
+ /** ioredis client owned by the HA manager and transport bus; closed in shutdown(). */
61920
+ haRedisClient;
61319
61921
  toolUIRegistry;
61320
61922
  authUiRegistry;
61321
61923
  entryPath;
@@ -61383,7 +61985,7 @@ var init_scope_instance = __esm({
61383
61985
  await this.scopeProviders.ready;
61384
61986
  const scopeRef = { kind: "scope", id: this.id, ref: _Scope };
61385
61987
  const scopeProviders = this.scopeProviders;
61386
- const perf = (0, import_utils105.getEnvFlag)("FRONTMCP_PERF");
61988
+ const perf = (0, import_utils109.getEnvFlag)("FRONTMCP_PERF");
61387
61989
  const t0 = perf ? performance.now() : 0;
61388
61990
  const mark = perf ? (label) => this.logger.info(`[PERF] ${label}: ${(performance.now() - t0).toFixed(1)}ms`) : () => {
61389
61991
  };
@@ -61393,24 +61995,26 @@ var init_scope_instance = __esm({
61393
61995
  this.scopeApps = new AppRegistry(this.scopeProviders, this.metadata.apps, scopeRef);
61394
61996
  this.logger.info(`Initializing ${this.metadata.apps.length} app(s)...`);
61395
61997
  const transportConfig = this.metadata.transport;
61396
- const isDistributed = (0, import_utils105.getRuntimeContext)().deployment === "distributed";
61998
+ const isDistributed = (0, import_utils109.getRuntimeContext)().deployment === "distributed";
61397
61999
  if (isDistributed && this.metadata.redis && !this.cliMode) {
61398
62000
  try {
61399
- const haRedis = this.metadata.redis;
62001
+ const haRedis = this.createHaRedisClient();
61400
62002
  this.haManager = HaManager.create({
61401
62003
  redis: haRedis,
61402
- nodeId: (0, import_utils105.getMachineId)(),
62004
+ nodeId: (0, import_utils109.getMachineId)(),
61403
62005
  logger: this.logger
61404
62006
  });
61405
62007
  await this.haManager.start();
61406
62008
  this.logger.info("[HA] Manager started for distributed deployment");
61407
62009
  } catch (err) {
61408
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();
61409
62013
  }
61410
62014
  }
61411
62015
  let transportBus;
61412
- if (this.haManager && isDistributed && this.metadata.redis) {
61413
- 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)(), {
61414
62018
  logger: this.logger
61415
62019
  });
61416
62020
  this.logger.info("[HA] Transport bus created for distributed session routing");
@@ -61421,11 +62025,10 @@ var init_scope_instance = __esm({
61421
62025
  effectivePersistence = { sqlite: resolvedTopLevelSqlite };
61422
62026
  }
61423
62027
  this.transportService = new TransportService(this, effectivePersistence, transportBus);
61424
- if (this.haManager && isDistributed) {
61425
- const persistCfg = transportConfig?.persistence;
61426
- const resolvedPrefix = persistCfg && typeof persistCfg === "object" && persistCfg.redis?.keyPrefix ? persistCfg.redis.keyPrefix : "mcp:transport:";
62028
+ warnIfRequestStateKeyNotShared({ logger: this.logger, metadata: this.metadata });
62029
+ if (this.haManager && isDistributed && this.transportService.getBackendKind() === "redis") {
61427
62030
  this.haManager.startOrphanScanner({
61428
- sessionKeyPrefix: resolvedPrefix,
62031
+ sessionKeyPrefix: this.transportService.getSessionKeyPrefix(),
61429
62032
  onOrphan: (sessionId, previousNodeId) => {
61430
62033
  this.logger.info(
61431
62034
  `[HA] Orphaned session ${sessionId.slice(0, 20)} from ${previousNodeId} \u2014 available for recreation`
@@ -61433,7 +62036,7 @@ var init_scope_instance = __esm({
61433
62036
  }
61434
62037
  });
61435
62038
  }
61436
- if ((0, import_utils105.getRuntimeContext)().deployment === "serverless") {
62039
+ if ((0, import_utils109.getRuntimeContext)().deployment === "serverless") {
61437
62040
  const protocolCfg = transportConfig?.protocol;
61438
62041
  const hasSSE = typeof protocolCfg === "string" ? protocolCfg !== "stateless-api" : protocolCfg?.sse === true;
61439
62042
  if (hasSSE) {
@@ -61460,7 +62063,7 @@ var init_scope_instance = __esm({
61460
62063
  sqlite: elicitationSqlite,
61461
62064
  keyPrefix: elicitationRedis?.keyPrefix ?? "mcp:elicit:",
61462
62065
  logger: this.logger,
61463
- isEdgeRuntime: (0, import_utils105.isEdgeRuntime)()
62066
+ isEdgeRuntime: (0, import_utils109.isEdgeRuntime)()
61464
62067
  });
61465
62068
  this._elicitationStore = elicitStore;
61466
62069
  this.elicitationBackendType = elicitType;
@@ -61474,14 +62077,22 @@ var init_scope_instance = __esm({
61474
62077
  );
61475
62078
  const tasksEnabledForCli = this.cliMode && hasPersistentBackend;
61476
62079
  const hasDistributedTaskBackend = Boolean(tasksConfig?.redis ?? this.metadata.redis);
61477
- const onEdgeRuntime = (0, import_utils105.isEdgeRuntime)();
62080
+ const onEdgeRuntime = (0, import_utils109.isEdgeRuntime)();
61478
62081
  const tasksUnavailableOnEdge = onEdgeRuntime && !hasDistributedTaskBackend && !tasksExplicitlyEnabled;
61479
62082
  if (tasksUnavailableOnEdge) {
61480
62083
  this.logger.info(
61481
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."
61482
62085
  );
61483
62086
  }
61484
- 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);
61485
62096
  const tasksPromise = shouldInitTasks ? (async () => {
61486
62097
  const tasksRedis = tasksConfig?.redis ?? this.metadata.redis;
61487
62098
  const tasksSqlite = tasksConfig?.sqlite ?? (tasksRedis ? void 0 : resolvedTopLevelSqlite);
@@ -61980,6 +62591,16 @@ var init_scope_instance = __esm({
61980
62591
  async compileUIWidgets() {
61981
62592
  const toolsWithUI = this.scopeTools.getTools(true).filter((t) => hasUIConfig(t.metadata));
61982
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
+ }
61983
62604
  this.scopeResources.registerDynamicResource(StaticWidgetResourceTemplate);
61984
62605
  this.logger.verbose(`Registered UI resource template for ${toolsWithUI.length} tool(s) with UI configs`);
61985
62606
  const staticModeTools = toolsWithUI.filter(
@@ -62558,6 +63179,29 @@ var init_scope_instance = __esm({
62558
63179
  }
62559
63180
  throw new FlowExitedWithoutOutputError();
62560
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
+ }
62561
63205
  /**
62562
63206
  * Shut down the scope and release resources.
62563
63207
  * Disconnects channel service connectors, unsubscribes event handlers,
@@ -62571,6 +63215,7 @@ var init_scope_instance = __esm({
62571
63215
  this.logger.error("[HA] Manager shutdown failed", { error: err });
62572
63216
  }
62573
63217
  }
63218
+ await this.closeHaRedisClient();
62574
63219
  if (this._channelTeardown) {
62575
63220
  try {
62576
63221
  await this._channelTeardown();
@@ -63692,6 +64337,178 @@ var init_scope_registry = __esm({
63692
64337
  }
63693
64338
  });
63694
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
+
63695
64512
  // libs/sdk/src/transport/web-response.renderer.ts
63696
64513
  function toByteStream(streamLike) {
63697
64514
  const iter = streamLike[Symbol.asyncIterator];
@@ -63796,6 +64613,7 @@ function isCloudflareWorkersRuntime(request) {
63796
64613
  return typeof cloudflareProperties === "object" && cloudflareProperties !== null;
63797
64614
  }
63798
64615
  function resolvePeerAddress(request, ctx) {
64616
+ if (carriedPeerAddresses.has(request)) return carriedPeerAddresses.get(request);
63799
64617
  if (typeof ctx?.requestIP === "function") return ctx.requestIP(request)?.address;
63800
64618
  if (typeof ctx?.remoteAddr?.hostname === "string") return ctx.remoteAddr.hostname;
63801
64619
  if (isCloudflareWorkersRuntime(request)) return request.headers.get("cf-connecting-ip") ?? void 0;
@@ -63814,7 +64632,13 @@ function mapHttpCors(httpCors, scope) {
63814
64632
  }
63815
64633
  function createWebFetchHandler(scope, options = {}) {
63816
64634
  const httpConfig = scope.metadata.http;
63817
- 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);
63818
64642
  const normalizePath = (p) => {
63819
64643
  const withSlash = p.startsWith("/") ? p : `/${p}`;
63820
64644
  let end = withSlash.length;
@@ -63858,7 +64682,13 @@ function createWebFetchHandler(scope, options = {}) {
63858
64682
  for (const [k, v] of Object.entries(corsHeadersFor(request))) headers.set(k, v);
63859
64683
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
63860
64684
  };
63861
- 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) => {
63862
64692
  const url = new URL(request.url);
63863
64693
  if (hostValidation) {
63864
64694
  const rejection = validateHostHeaders(
@@ -63873,17 +64703,34 @@ function createWebFetchHandler(scope, options = {}) {
63873
64703
  return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
63874
64704
  }
63875
64705
  }
64706
+ const limited = await enforceBodyLimit(request, bodyLimit, ctx);
64707
+ if (limited instanceof Response) return withCors(limited, request);
64708
+ request = limited;
63876
64709
  if (corsEnabled && request.method === "OPTIONS") {
63877
64710
  return new Response(null, { status: 204, headers: corsHeadersFor(request) });
63878
64711
  }
63879
- if (healthPaths.has(url.pathname)) {
63880
- return withCors(
63881
- Response.json(
63882
- { status: "ok", server: scope.metadata.info, transport: "web-fetch" },
63883
- { headers: { "Cache-Control": "no-store" } }
63884
- ),
63885
- request
63886
- );
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
+ }
63887
64734
  }
63888
64735
  if (!entryPaths.has(normalizePath(url.pathname))) {
63889
64736
  const authResponse = await runMatchingHttpFlowWeb(scope, request, { ctx, env });
@@ -63897,13 +64744,60 @@ function createWebFetchHandler(scope, options = {}) {
63897
64744
  const rendered = await runHttpRequestFlowWeb(scope, request, { ctx, env });
63898
64745
  return withCors(rendered ?? Response.json({ error: "Not Found" }, { status: 404 }), request);
63899
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;
63900
64794
  }
63901
64795
  async function runHttpRequestFlowWeb(scope, request, opts = {}) {
63902
64796
  const url = new URL(request.url);
63903
64797
  const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent, opts.env);
63904
64798
  let output;
63905
64799
  try {
63906
- output = await (0, import_utils106.runRequestExclusive)(
64800
+ output = await (0, import_utils111.runRequestExclusive)(
63907
64801
  () => scope.runFlow("http:request", {
63908
64802
  request: serverRequest,
63909
64803
  response: {}
@@ -63921,7 +64815,7 @@ async function runMatchingHttpFlowWeb(scope, request, opts = {}) {
63921
64815
  if (!flowName) return void 0;
63922
64816
  let output;
63923
64817
  try {
63924
- output = await (0, import_utils106.runRequestExclusive)(
64818
+ output = await (0, import_utils111.runRequestExclusive)(
63925
64819
  () => scope.runFlow(flowName, {
63926
64820
  request: serverRequest,
63927
64821
  response: {}
@@ -63987,22 +64881,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
63987
64881
  function misconfigurationResponse(error) {
63988
64882
  const misconfiguration = findMisconfiguration(error);
63989
64883
  if (!misconfiguration) return void 0;
63990
- return Response.json(
63991
- { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy },
63992
- { status: 500 }
63993
- );
63994
- }
63995
- function findMisconfiguration(error) {
63996
- let current = error;
63997
- for (let depth = 0; current instanceof Error && depth < 5; depth++) {
63998
- const code = current.code;
63999
- if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
64000
- return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
64001
- }
64002
- if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
64003
- current = current.cause;
64004
- }
64005
- return void 0;
64884
+ return Response.json(misconfigurationBody(misconfiguration), { status: 500 });
64006
64885
  }
64007
64886
  function createDeferredServerBuild(build, options = {}) {
64008
64887
  const now = options.now ?? Date.now;
@@ -64057,7 +64936,7 @@ function flowErrorToHttpOutput(error) {
64057
64936
  kind: "json",
64058
64937
  status: 500,
64059
64938
  contentType: "application/json; charset=utf-8",
64060
- body: { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy }
64939
+ body: misconfigurationBody(misconfiguration)
64061
64940
  };
64062
64941
  }
64063
64942
  if (error instanceof FlowControl) {
@@ -64083,27 +64962,24 @@ function flowErrorToHttpOutput(error) {
64083
64962
  }
64084
64963
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
64085
64964
  }
64086
- 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;
64087
64966
  var init_web_fetch_handler = __esm({
64088
64967
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
64089
64968
  "use strict";
64090
- import_utils106 = require("@frontmcp/utils");
64969
+ import_utils111 = require("@frontmcp/utils");
64091
64970
  init_common();
64092
64971
  init_server_tokens();
64093
64972
  init_path_utils();
64094
64973
  init_errors();
64974
+ init_misconfiguration();
64975
+ init_health_routes();
64976
+ init_csp_middleware();
64095
64977
  init_host_validation();
64096
64978
  init_web_response_renderer();
64097
64979
  CLOUDFLARE_WORKERS_USER_AGENT = "Cloudflare-Workers";
64098
- MISCONFIGURATION_REMEDIES = {
64099
- 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.",
64100
- 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`.",
64101
- 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.",
64102
- // The startup checks: a server whose entries ask for protection nothing gives them does not start.
64103
- 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.",
64104
- 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."
64105
- };
64106
- 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 };
64107
64983
  STARTUP_RETRY_MIN_MS = 1e3;
64108
64984
  STARTUP_RETRY_MAX_MS = 6e4;
64109
64985
  }
@@ -64135,9 +65011,9 @@ var init_noop_server = __esm({
64135
65011
  });
64136
65012
 
64137
65013
  // libs/sdk/src/server/security/security-audit.ts
64138
- function auditSecurityDefaults(config, isProduction6) {
65014
+ function auditSecurityDefaults(config, isProduction7) {
64139
65015
  const findings = [];
64140
- if (!isProduction6 && config.deploymentMode !== "distributed") {
65016
+ if (!isProduction7 && config.deploymentMode !== "distributed") {
64141
65017
  return findings;
64142
65018
  }
64143
65019
  const strict = config.security?.strict === true;
@@ -64185,13 +65061,13 @@ function auditSecurityDefaults(config, isProduction6) {
64185
65061
  message: `Server bound to ${bindAddress}.`
64186
65062
  });
64187
65063
  }
64188
- const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled ?? strict;
65064
+ const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled !== false;
64189
65065
  if (!dnsProtectionEnabled) {
64190
65066
  findings.push({
64191
65067
  level: "warn",
64192
65068
  code: "DNS_REBINDING_UNPROTECTED",
64193
- message: "DNS rebinding protection is disabled.",
64194
- 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."
64195
65071
  });
64196
65072
  } else {
64197
65073
  findings.push({
@@ -64371,15 +65247,16 @@ var init_base_host_adapter = __esm({
64371
65247
  });
64372
65248
 
64373
65249
  // libs/sdk/src/server/adapters/express.host.adapter.ts
64374
- 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;
64375
65251
  var init_express_host_adapter = __esm({
64376
65252
  "libs/sdk/src/server/adapters/express.host.adapter.ts"() {
64377
65253
  "use strict";
64378
65254
  http = __toESM(require("node:http"));
64379
65255
  import_cors = __toESM(require("cors"));
64380
65256
  import_express = __toESM(require("express"));
64381
- import_utils107 = require("@frontmcp/utils");
65257
+ import_utils112 = require("@frontmcp/utils");
64382
65258
  init_mcp_error();
65259
+ init_csp_middleware();
64383
65260
  init_host_validation_middleware();
64384
65261
  init_resolve_allowed_hosts();
64385
65262
  init_base_host_adapter();
@@ -64394,6 +65271,12 @@ var init_express_host_adapter = __esm({
64394
65271
  deriveHostValidation;
64395
65272
  constructor(options) {
64396
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
+ });
64397
65280
  const corsOptions = options?.cors;
64398
65281
  const corsEnabled = corsOptions?.origin !== void 0 && corsOptions.origin !== false;
64399
65282
  if (corsEnabled) {
@@ -64561,8 +65444,8 @@ var init_express_host_adapter = __esm({
64561
65444
  }
64562
65445
  async cleanupStaleSocket(socketPath) {
64563
65446
  try {
64564
- if (await (0, import_utils107.fileExists)(socketPath)) {
64565
- await (0, import_utils107.unlink)(socketPath);
65447
+ if (await (0, import_utils112.fileExists)(socketPath)) {
65448
+ await (0, import_utils112.unlink)(socketPath);
64566
65449
  }
64567
65450
  } catch {
64568
65451
  }
@@ -64585,11 +65468,11 @@ var init_node_express_host = __esm({
64585
65468
  });
64586
65469
 
64587
65470
  // libs/sdk/src/server/server.instance.ts
64588
- var import_utils108, DEFAULT_CORS, FrontMcpServerInstance;
65471
+ var import_utils113, DEFAULT_CORS, FrontMcpServerInstance;
64589
65472
  var init_server_instance = __esm({
64590
65473
  "libs/sdk/src/server/server.instance.ts"() {
64591
65474
  "use strict";
64592
- import_utils108 = require("@frontmcp/utils");
65475
+ import_utils113 = require("@frontmcp/utils");
64593
65476
  init_common();
64594
65477
  init_health2();
64595
65478
  init_metrics2();
@@ -64621,6 +65504,7 @@ var init_server_instance = __esm({
64621
65504
  this.host = new ExpressHostAdapter2({
64622
65505
  ...corsConfig ? { cors: corsConfig } : {},
64623
65506
  ...this.config.security ? { security: this.config.security } : {},
65507
+ ...this.config.securityHeaders ? { securityHeaders: this.config.securityHeaders } : {},
64624
65508
  ...this.config.bodyLimit !== void 0 ? { bodyLimit: this.config.bodyLimit } : {},
64625
65509
  ...this.config.urlencodedLimit !== void 0 ? { urlencodedLimit: this.config.urlencodedLimit } : {}
64626
65510
  });
@@ -64691,9 +65575,9 @@ var init_server_instance = __esm({
64691
65575
  }
64692
65576
  async start() {
64693
65577
  this.prepare();
64694
- const deploymentMode = (0, import_utils108.getRuntimeContext)().deployment;
65578
+ const deploymentMode = (0, import_utils113.getRuntimeContext)().deployment;
64695
65579
  const bindAddress = resolveBindAddress(this.config.security, deploymentMode);
64696
- const isProduction6 = process.env["NODE_ENV"] === "production";
65580
+ const isProduction7 = process.env["NODE_ENV"] === "production";
64697
65581
  const findings = auditSecurityDefaults(
64698
65582
  {
64699
65583
  cors: this.config.cors,
@@ -64701,7 +65585,7 @@ var init_server_instance = __esm({
64701
65585
  resolvedBindAddress: bindAddress,
64702
65586
  deploymentMode
64703
65587
  },
64704
- isProduction6
65588
+ isProduction7
64705
65589
  );
64706
65590
  logSecurityFindings(findings, console);
64707
65591
  await this.host.start(this.config.socketPath ?? this.config.port, bindAddress);
@@ -64929,11 +65813,11 @@ function resolveConfigInput(optionsOrClass) {
64929
65813
  }
64930
65814
  return optionsOrClass;
64931
65815
  }
64932
- var import_utils109, stdioServing, FrontMcpInstance;
65816
+ var import_utils114, stdioServing, FrontMcpInstance;
64933
65817
  var init_front_mcp = __esm({
64934
65818
  "libs/sdk/src/front-mcp/front-mcp.ts"() {
64935
65819
  "use strict";
64936
- import_utils109 = require("@frontmcp/utils");
65820
+ import_utils114 = require("@frontmcp/utils");
64937
65821
  init_common();
64938
65822
  init_direct();
64939
65823
  init_errors();
@@ -64945,6 +65829,7 @@ var init_front_mcp = __esm({
64945
65829
  init_scope_registry();
64946
65830
  init_skill_instructions_helper();
64947
65831
  init_task();
65832
+ init_ipc_server_transport();
64948
65833
  init_web_fetch_handler();
64949
65834
  init_front_mcp_providers();
64950
65835
  init_static_startup_check();
@@ -65064,7 +65949,13 @@ var init_front_mcp = __esm({
65064
65949
  }
65065
65950
  }
65066
65951
  static async bootstrap(options) {
65067
- 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
+ );
65068
65959
  const daemonSocket = process.env["FRONTMCP_DAEMON_SOCKET"];
65069
65960
  if (daemonSocket) {
65070
65961
  await _FrontMcpInstance2.runUnixSocket({ ...parsedConfig, socketPath: daemonSocket });
@@ -65137,7 +66028,7 @@ var init_front_mcp = __esm({
65137
66028
  frontMcp.log?.info("FrontMCP fetch handler created (web-standard transport)");
65138
66029
  return createWebFetchHandler(scope);
65139
66030
  };
65140
- if (!(0, import_utils109.isEdgeRuntime)()) return build(false);
66031
+ if (!(0, import_utils114.isEdgeRuntime)()) return build(false);
65141
66032
  assertStaticStartupConfig(options);
65142
66033
  const server = createDeferredServerBuild(() => build(true));
65143
66034
  return async (request, ctx, env) => {
@@ -65282,8 +66173,8 @@ var init_front_mcp = __esm({
65282
66173
  frontMcp.log?.info(`MCP server listening on unix://${socketPath}`);
65283
66174
  const cleanup = async () => {
65284
66175
  try {
65285
- if (await (0, import_utils109.fileExists)(socketPath)) {
65286
- await (0, import_utils109.unlink)(socketPath);
66176
+ if (await (0, import_utils114.fileExists)(socketPath)) {
66177
+ await (0, import_utils114.unlink)(socketPath);
65287
66178
  }
65288
66179
  } catch {
65289
66180
  }
@@ -65427,7 +66318,7 @@ var init_front_mcp = __esm({
65427
66318
  };
65428
66319
  const mcpServer = new McpServer2(scope.metadata.info, serverOptions);
65429
66320
  startupCleanups.push(() => mcpServer.close());
65430
- const sessionId = `stdio:${(0, import_utils109.randomUUID)()}`;
66321
+ const sessionId = `stdio:${(0, import_utils114.randomUUID)()}`;
65431
66322
  const handlers = createMcpHandlers2({ scope, serverOptions, composeInstructions });
65432
66323
  for (const handler of handlers) {
65433
66324
  const originalHandler = handler.handler;
@@ -65441,7 +66332,7 @@ var init_front_mcp = __esm({
65441
66332
  };
65442
66333
  mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
65443
66334
  }
65444
- const transport = new StdioServerTransport();
66335
+ const transport = isIpcStdioRequested() ? new IpcServerTransport() : new StdioServerTransport();
65445
66336
  startupCleanups.push(() => transport.close());
65446
66337
  await mcpServer.connect(transport);
65447
66338
  startupSucceeded = true;
@@ -65747,9 +66638,9 @@ function FrontMcp(providedMetadata) {
65747
66638
  Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);
65748
66639
  }
65749
66640
  Reflect.defineMetadata(FRONTMCP_CONFIG_METADATA_KEY, metadata, target);
65750
- const isServerless2 = (0, import_utils110.getEnvFlag)("FRONTMCP_SERVERLESS");
65751
- const isSchemaExtract = (0, import_utils110.getEnvFlag)("FRONTMCP_SCHEMA_EXTRACT");
65752
- 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");
65753
66644
  const taskWorkerId = typeof process !== "undefined" ? process.env?.["FRONTMCP_RUN_TASK_ID"] : void 0;
65754
66645
  if (taskWorkerId && metadata.serve !== false) {
65755
66646
  void (async () => {
@@ -65775,7 +66666,7 @@ function FrontMcp(providedMetadata) {
65775
66666
  } else if (isServerless2) {
65776
66667
  const ServerlessInstance = getFrontMcpInstance();
65777
66668
  const { setServerlessHandler: setServerlessHandler2, setServerlessHandlerPromise: setServerlessHandlerPromise2, setServerlessHandlerError: setServerlessHandlerError2 } = getServerlessHandlerFns();
65778
- const isWorker = (0, import_utils110.getEnvFlag)("FRONTMCP_WORKER");
66669
+ const isWorker = (0, import_utils115.getEnvFlag)("FRONTMCP_WORKER");
65779
66670
  const handlerPromise = isWorker ? ServerlessInstance.createFetchHandler(metadata) : ServerlessInstance.createHandler(metadata);
65780
66671
  setServerlessHandlerPromise2(handlerPromise);
65781
66672
  handlerPromise.then(setServerlessHandler2).catch((err) => {
@@ -65788,12 +66679,12 @@ function FrontMcp(providedMetadata) {
65788
66679
  }
65789
66680
  };
65790
66681
  }
65791
- 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;
65792
66683
  var init_front_mcp_decorator = __esm({
65793
66684
  "libs/sdk/src/common/decorators/front-mcp.decorator.ts"() {
65794
66685
  "use strict";
65795
66686
  import_reflect_metadata28 = require("reflect-metadata");
65796
- import_utils110 = require("@frontmcp/utils");
66687
+ import_utils115 = require("@frontmcp/utils");
65797
66688
  init_decorator_errors();
65798
66689
  init_mcp_error();
65799
66690
  init_metadata();
@@ -66507,7 +67398,7 @@ var init_dynamic_adapter = __esm({
66507
67398
  * @param options - Adapter options including required `name` field
66508
67399
  * @throws Error if `name` is missing/empty or if a duplicate name is detected
66509
67400
  */
66510
- static init(options) {
67401
+ static init(options = {}) {
66511
67402
  const typedOptions = options;
66512
67403
  const adapterName = typedOptions.name;
66513
67404
  if (!adapterName || typeof adapterName !== "string" || adapterName.trim() === "") {
@@ -66546,49 +67437,6 @@ var init_dynamic_adapter = __esm({
66546
67437
  }
66547
67438
  });
66548
67439
 
66549
- // libs/sdk/src/common/dynamic/dynamic.plugin.ts
66550
- function isDynamicPluginClass(value) {
66551
- return typeof value === "function" && value.prototype instanceof DynamicPlugin;
66552
- }
66553
- var DynamicPlugin;
66554
- var init_dynamic_plugin = __esm({
66555
- "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
66556
- "use strict";
66557
- init_transport_errors();
66558
- init_dynamic_utils();
66559
- DynamicPlugin = class {
66560
- get(token) {
66561
- throw new MethodNotImplementedError("DynamicPlugin", "get");
66562
- }
66563
- /**
66564
- * Static init() method to create a plugin provider.
66565
- * @param options - Input options (with optional fields for defaults)
66566
- */
66567
- static init(options) {
66568
- const extraProviders = options.providers;
66569
- const typedOptions = options;
66570
- if ("useFactory" in options) {
66571
- return {
66572
- ...typedOptions,
66573
- provide: this,
66574
- inject: options.inject,
66575
- useFactory: options.useFactory,
66576
- providers: dedupePluginProviders(extraProviders ?? [])
66577
- };
66578
- }
66579
- const dyn = collectDynamicProviders(this, typedOptions);
66580
- const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
66581
- return {
66582
- ...typedOptions,
66583
- provide: this,
66584
- useValue: new this(options),
66585
- providers: mergedProviders
66586
- };
66587
- }
66588
- };
66589
- }
66590
- });
66591
-
66592
67440
  // libs/sdk/src/common/dynamic/index.ts
66593
67441
  var init_dynamic = __esm({
66594
67442
  "libs/sdk/src/common/dynamic/index.ts"() {
@@ -66752,12 +67600,12 @@ var init_adapter_entry = __esm({
66752
67600
  });
66753
67601
 
66754
67602
  // libs/sdk/src/common/entries/tool.entry.ts
66755
- var import_lazy_zod109, import_utils112, ToolEntry;
67603
+ var import_lazy_zod109, import_utils117, ToolEntry;
66756
67604
  var init_tool_entry = __esm({
66757
67605
  "libs/sdk/src/common/entries/tool.entry.ts"() {
66758
67606
  "use strict";
66759
67607
  import_lazy_zod109 = require("@frontmcp/lazy-zod");
66760
- import_utils112 = require("@frontmcp/utils");
67608
+ import_utils117 = require("@frontmcp/utils");
66761
67609
  init_base_entry();
66762
67610
  ToolEntry = class extends BaseEntry {
66763
67611
  owner;
@@ -66824,7 +67672,7 @@ var init_tool_entry = __esm({
66824
67672
  try {
66825
67673
  return (0, import_lazy_zod109.toJSONSchema)(import_lazy_zod109.z.object(this.inputSchema), { io: "input", unrepresentable: "any" });
66826
67674
  } catch (error) {
66827
- if ((0, import_utils112.isDebug)() || (0, import_utils112.isDevelopment)()) {
67675
+ if ((0, import_utils117.isDebug)() || (0, import_utils117.isDevelopment)()) {
66828
67676
  console.warn("[ToolEntry] Failed to convert Zod schema to JSON Schema:", error);
66829
67677
  }
66830
67678
  return { type: "object", properties: {} };
@@ -66870,7 +67718,7 @@ var init_tool_entry = __esm({
66870
67718
  const json = (0, import_lazy_zod109.toJSONSchema)(zodSchema);
66871
67719
  return json && json["type"] === "object" ? json : null;
66872
67720
  } catch (error) {
66873
- if ((0, import_utils112.isDebug)() || (0, import_utils112.isDevelopment)()) {
67721
+ if ((0, import_utils117.isDebug)() || (0, import_utils117.isDevelopment)()) {
66874
67722
  console.warn("[ToolEntry] Failed to convert output Zod schema to JSON Schema:", error);
66875
67723
  }
66876
67724
  return null;
@@ -68188,6 +69036,7 @@ __export(index_exports, {
68188
69036
  httpRequestInputSchema: () => httpRequestInputSchema,
68189
69037
  httpRespond: () => httpRespond,
68190
69038
  incrementalAuthConfigSchema: () => import_auth5.incrementalAuthConfigSchema,
69039
+ initOptionsOf: () => initOptionsOf,
68191
69040
  intentSchema: () => intentSchema,
68192
69041
  isAlive: () => isAlive,
68193
69042
  isAnonymousSubject: () => import_auth61.isAnonymousSubject,
@@ -68310,6 +69159,7 @@ __export(index_exports, {
68310
69159
  resolveSchemaDescriptionFormat: () => resolveSchemaDescriptionFormat,
68311
69160
  resolveToolVisibility: () => resolveToolVisibility,
68312
69161
  resolveWithFallbacks: () => resolveWithFallbacks,
69162
+ resolvesToVercelKvTaskBackend: () => resolvesToVercelKvTaskBackend,
68313
69163
  resource: () => frontMcpResource,
68314
69164
  resourceTemplate: () => frontMcpResourceTemplate,
68315
69165
  resourceUriMatches: () => resourceUriMatches,
@@ -68534,22 +69384,22 @@ function createMemorySkillProvider(options = {}) {
68534
69384
  init_skill_utils();
68535
69385
 
68536
69386
  // libs/sdk/src/skill/skill-directory-loader.ts
68537
- var import_utils113 = require("@frontmcp/utils");
69387
+ var import_utils118 = require("@frontmcp/utils");
68538
69388
  init_skill_metadata();
68539
69389
  init_sdk_errors();
68540
69390
  init_skill_md_parser();
68541
69391
  async function scanSkillResources(dirPath) {
68542
69392
  const resources = {};
68543
- const scriptsPath = (0, import_utils113.joinPath)(dirPath, "scripts");
68544
- const referencesPath = (0, import_utils113.joinPath)(dirPath, "references");
68545
- const assetsPath = (0, import_utils113.joinPath)(dirPath, "assets");
68546
- 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");
68547
69397
  const checks = await Promise.all([
68548
69398
  checkDirectory(scriptsPath),
68549
69399
  checkDirectory(referencesPath),
68550
69400
  checkDirectory(assetsPath),
68551
69401
  checkDirectory(examplesPath),
68552
- (0, import_utils113.fileExists)((0, import_utils113.joinPath)(dirPath, "SKILL.md"))
69402
+ (0, import_utils118.fileExists)((0, import_utils118.joinPath)(dirPath, "SKILL.md"))
68553
69403
  ]);
68554
69404
  if (checks[0]) resources.scripts = scriptsPath;
68555
69405
  if (checks[1]) resources.references = referencesPath;
@@ -68562,7 +69412,7 @@ async function scanSkillResources(dirPath) {
68562
69412
  }
68563
69413
  async function checkDirectory(path3) {
68564
69414
  try {
68565
- const s = await (0, import_utils113.stat)(path3);
69415
+ const s = await (0, import_utils118.stat)(path3);
68566
69416
  return s.isDirectory();
68567
69417
  } catch (err) {
68568
69418
  if (err.code === "ENOENT") return false;
@@ -68575,16 +69425,16 @@ async function findNestedSkillMd(dirPath, maxDepth = Number.POSITIVE_INFINITY) {
68575
69425
  if (depth > maxDepth) return;
68576
69426
  let entries;
68577
69427
  try {
68578
- entries = await (0, import_utils113.readdir)(current);
69428
+ entries = await (0, import_utils118.readdir)(current);
68579
69429
  } catch (err) {
68580
69430
  if (err.code === "ENOENT") return;
68581
69431
  throw err;
68582
69432
  }
68583
69433
  for (const entry of entries) {
68584
- const full = (0, import_utils113.joinPath)(current, entry);
69434
+ const full = (0, import_utils118.joinPath)(current, entry);
68585
69435
  let entryStat;
68586
69436
  try {
68587
- entryStat = await (0, import_utils113.stat)(full);
69437
+ entryStat = await (0, import_utils118.stat)(full);
68588
69438
  } catch (err) {
68589
69439
  if (err.code === "ENOENT") continue;
68590
69440
  throw err;
@@ -68600,19 +69450,19 @@ async function findNestedSkillMd(dirPath, maxDepth = Number.POSITIVE_INFINITY) {
68600
69450
  return found.sort();
68601
69451
  }
68602
69452
  async function loadSkillDirectory(dirPath, logger) {
68603
- const skillMdPath = (0, import_utils113.joinPath)(dirPath, "SKILL.md");
68604
- 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);
68605
69455
  if (!exists) {
68606
- 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");
68607
69457
  }
68608
69458
  let content;
68609
69459
  let partialMetadata;
68610
69460
  try {
68611
- content = await (0, import_utils113.readFile)(skillMdPath, "utf-8");
69461
+ content = await (0, import_utils118.readFile)(skillMdPath, "utf-8");
68612
69462
  const { frontmatter, body } = parseSkillMdFrontmatter(content);
68613
69463
  partialMetadata = skillMdFrontmatterToMetadata(frontmatter, body);
68614
69464
  } catch (err) {
68615
- const name44 = (0, import_utils113.basename)(dirPath);
69465
+ const name44 = (0, import_utils118.basename)(dirPath);
68616
69466
  const message = err instanceof Error ? err.message : String(err);
68617
69467
  throw new InvalidSkillError(name44, `Failed to read or parse SKILL.md: ${message}`);
68618
69468
  }
@@ -68623,13 +69473,13 @@ async function loadSkillDirectory(dirPath, logger) {
68623
69473
  }
68624
69474
  const nested = await findNestedSkillMd(dirPath);
68625
69475
  if (nested.length > 0) {
68626
- const skillName2 = partialMetadata.name ?? (0, import_utils113.basename)(dirPath);
69476
+ const skillName2 = partialMetadata.name ?? (0, import_utils118.basename)(dirPath);
68627
69477
  throw new InvalidSkillError(
68628
69478
  skillName2,
68629
69479
  `Skill directory contains nested SKILL.md files (forbidden by SEP-2640 \xA7Resource Mapping): ${nested.join(", ")}`
68630
69480
  );
68631
69481
  }
68632
- const dirName = (0, import_utils113.basename)(dirPath);
69482
+ const dirName = (0, import_utils118.basename)(dirPath);
68633
69483
  if (partialMetadata.name && dirName && partialMetadata.name !== dirName) {
68634
69484
  const msg = `Skill name "${partialMetadata.name}" does not match directory name "${dirName}"`;
68635
69485
  if (logger) {
@@ -69430,6 +70280,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
69430
70280
  httpRequestInputSchema,
69431
70281
  httpRespond,
69432
70282
  incrementalAuthConfigSchema,
70283
+ initOptionsOf,
69433
70284
  intentSchema,
69434
70285
  isAlive,
69435
70286
  isAnonymousSubject,
@@ -69552,6 +70403,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
69552
70403
  resolveSchemaDescriptionFormat,
69553
70404
  resolveToolVisibility,
69554
70405
  resolveWithFallbacks,
70406
+ resolvesToVercelKvTaskBackend,
69555
70407
  resource,
69556
70408
  resourceTemplate,
69557
70409
  resourceUriMatches,