@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/esm/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
1
+ const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1861,7 +1862,7 @@ var init_session = __esm({
1861
1862
 
1862
1863
  // libs/sdk/src/common/types/options/http/schema.ts
1863
1864
  import { z as z8 } from "@frontmcp/lazy-zod";
1864
- var HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, httpOptionsSchema;
1865
+ var HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, securityHeaderValueSchema, securityHeadersSchema, httpOptionsSchema;
1865
1866
  var init_schema3 = __esm({
1866
1867
  "libs/sdk/src/common/types/options/http/schema.ts"() {
1867
1868
  "use strict";
@@ -1898,6 +1899,19 @@ var init_schema3 = __esm({
1898
1899
  credentials: z8.boolean().optional(),
1899
1900
  maxAge: z8.number().optional()
1900
1901
  });
1902
+ securityHeaderValueSchema = z8.union([z8.string(), z8.literal(false)]);
1903
+ securityHeadersSchema = z8.object({
1904
+ hsts: securityHeaderValueSchema.optional(),
1905
+ contentTypeOptions: securityHeaderValueSchema.optional(),
1906
+ frameOptions: securityHeaderValueSchema.optional(),
1907
+ custom: z8.record(z8.string(), z8.string()).optional(),
1908
+ csp: z8.object({
1909
+ enabled: z8.boolean().optional(),
1910
+ directives: z8.record(z8.string(), z8.union([z8.string(), z8.array(z8.string())])).optional(),
1911
+ reportUri: z8.string().optional(),
1912
+ reportOnly: z8.boolean().optional()
1913
+ }).optional()
1914
+ });
1901
1915
  httpOptionsSchema = z8.object({
1902
1916
  port: z8.number().optional().default(Number(process.env["PORT"]) || 3e3),
1903
1917
  // `FRONTMCP_HTTP_ENTRY_PATH` lets `frontmcp dev` propagate the configured
@@ -1956,7 +1970,9 @@ var init_schema3 = __esm({
1956
1970
  * app as the MCP endpoint; public by default, `auth: true` opts into the MCP
1957
1971
  * `session:verify` flow. Reserved-path collisions are rejected at startup.
1958
1972
  */
1959
- routes: z8.array(httpRouteSchema).optional()
1973
+ routes: z8.array(httpRouteSchema).optional(),
1974
+ /** Response security headers; see `SecurityHeadersOptions`. */
1975
+ securityHeaders: securityHeadersSchema.optional()
1960
1976
  });
1961
1977
  }
1962
1978
  });
@@ -2394,7 +2410,7 @@ var init_schema7 = __esm({
2394
2410
  persistenceConfigSchema = z12.object({
2395
2411
  redis: redisOptionsSchema.optional(),
2396
2412
  sqlite: sqliteOptionsSchema.optional(),
2397
- defaultTtlMs: z12.number().int().positive().default(36e5)
2413
+ defaultTtlMs: z12.number().int().positive().optional()
2398
2414
  }).refine((cfg) => !(cfg.redis && cfg.sqlite), {
2399
2415
  message: "Invalid persistence config: choose either redis or sqlite, not both",
2400
2416
  path: ["sqlite"]
@@ -6035,6 +6051,33 @@ var init_errors = __esm({
6035
6051
  }
6036
6052
  });
6037
6053
 
6054
+ // libs/sdk/src/common/utils/list-all-pages.utils.ts
6055
+ async function listAllPages(method, fetchPage, fail = (message) => new InternalMcpError(message, "LIST_PAGINATION_STALLED")) {
6056
+ const items = [];
6057
+ const seenCursors = /* @__PURE__ */ new Set();
6058
+ let cursor;
6059
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
6060
+ const result = await fetchPage(cursor);
6061
+ items.push(...result.items ?? []);
6062
+ const next = result.nextCursor;
6063
+ if (!next) return items;
6064
+ if (seenCursors.has(next)) {
6065
+ throw fail(`${method} returned the cursor "${next}" twice`);
6066
+ }
6067
+ seenCursors.add(next);
6068
+ cursor = next;
6069
+ }
6070
+ throw fail(`${method} did not finish within ${MAX_LIST_PAGES} pages`);
6071
+ }
6072
+ var MAX_LIST_PAGES;
6073
+ var init_list_all_pages_utils = __esm({
6074
+ "libs/sdk/src/common/utils/list-all-pages.utils.ts"() {
6075
+ "use strict";
6076
+ init_mcp_error();
6077
+ MAX_LIST_PAGES = 1e3;
6078
+ }
6079
+ });
6080
+
6038
6081
  // libs/sdk/src/transport/mcp-handlers/skills-mcp.types.ts
6039
6082
  import { z as z40 } from "@frontmcp/lazy-zod";
6040
6083
  var SkillsSearchRequestSchema, SkillsSearchResultSchema, SkillsLoadRequestSchema, SkillsLoadResultSchema, SkillsListRequestSchema, SkillsListResultSchema;
@@ -6483,6 +6526,7 @@ var require_dist = __commonJS({
6483
6526
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
6484
6527
  var index_exports = {};
6485
6528
  __export2(index_exports, {
6529
+ SqliteDecryptionError: () => SqliteDecryptionError,
6486
6530
  SqliteElicitationStore: () => SqliteElicitationStore,
6487
6531
  SqliteEventStore: () => SqliteEventStore,
6488
6532
  SqliteKvStore: () => SqliteKvStore,
@@ -6494,34 +6538,47 @@ var require_dist = __commonJS({
6494
6538
  encryptValue: () => encryptValue,
6495
6539
  loadBetterSqlite3: () => loadBetterSqlite3,
6496
6540
  openDatabase: () => openDatabase,
6497
- sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6541
+ sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema,
6542
+ withBusyRetry: () => withBusyRetry
6498
6543
  });
6499
6544
  module.exports = __toCommonJS2(index_exports);
6500
- var import_utils114 = __require("@frontmcp/utils");
6545
+ var import_utils119 = __require("@frontmcp/utils");
6546
+ var SqliteDecryptionError = class extends Error {
6547
+ code = "SQLITE_DECRYPTION_FAILED";
6548
+ constructor() {
6549
+ super(
6550
+ "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."
6551
+ );
6552
+ this.name = "SqliteDecryptionError";
6553
+ }
6554
+ };
6501
6555
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6502
6556
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6503
6557
  var KEY_LENGTH = 32;
6504
6558
  function deriveEncryptionKey(secret) {
6505
6559
  const ikm = new TextEncoder().encode(secret);
6506
- return (0, import_utils114.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6560
+ return (0, import_utils119.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6507
6561
  }
6508
6562
  var SEPARATOR = ":";
6509
6563
  function encryptValue(key, plaintext) {
6510
- const iv = (0, import_utils114.randomBytes)(12);
6564
+ const iv = (0, import_utils119.randomBytes)(12);
6511
6565
  const plaintextBytes = new TextEncoder().encode(plaintext);
6512
- const { ciphertext, tag } = (0, import_utils114.encryptAesGcm)(key, plaintextBytes, iv);
6513
- return [(0, import_utils114.base64urlEncode)(iv), (0, import_utils114.base64urlEncode)(tag), (0, import_utils114.base64urlEncode)(ciphertext)].join(SEPARATOR);
6566
+ const { ciphertext, tag } = (0, import_utils119.encryptAesGcm)(key, plaintextBytes, iv);
6567
+ return [(0, import_utils119.base64urlEncode)(iv), (0, import_utils119.base64urlEncode)(tag), (0, import_utils119.base64urlEncode)(ciphertext)].join(SEPARATOR);
6514
6568
  }
6515
6569
  function decryptValue(key, encrypted) {
6516
6570
  const parts = encrypted.split(SEPARATOR);
6517
6571
  if (parts.length !== 3) {
6518
6572
  throw new Error("Invalid encrypted value format");
6519
6573
  }
6520
- const iv = (0, import_utils114.base64urlDecode)(parts[0]);
6521
- const tag = (0, import_utils114.base64urlDecode)(parts[1]);
6522
- const ciphertext = (0, import_utils114.base64urlDecode)(parts[2]);
6523
- const plaintext = (0, import_utils114.decryptAesGcm)(key, ciphertext, iv, tag);
6524
- return new TextDecoder().decode(plaintext);
6574
+ const iv = (0, import_utils119.base64urlDecode)(parts[0]);
6575
+ const tag = (0, import_utils119.base64urlDecode)(parts[1]);
6576
+ const ciphertext = (0, import_utils119.base64urlDecode)(parts[2]);
6577
+ try {
6578
+ return new TextDecoder().decode((0, import_utils119.decryptAesGcm)(key, ciphertext, iv, tag));
6579
+ } catch {
6580
+ throw new SqliteDecryptionError();
6581
+ }
6525
6582
  }
6526
6583
  var import_utils210 = __require("@frontmcp/utils");
6527
6584
  var cached = null;
@@ -6534,7 +6591,27 @@ var require_dist = __commonJS({
6534
6591
  return mod;
6535
6592
  }
6536
6593
  var MEMORY_PATH = ":memory:";
6537
- function openDatabase(path3, storeName) {
6594
+ var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
6595
+ function isBusyError(err) {
6596
+ const code = err?.code;
6597
+ return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || code === "SQLITE_BUSY_SNAPSHOT";
6598
+ }
6599
+ function sleepSync(ms) {
6600
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
6601
+ }
6602
+ function withBusyRetry(operation, options = {}) {
6603
+ const attempts = options.attempts ?? 5;
6604
+ const delayMs = options.delayMs ?? 50;
6605
+ for (let attempt = 1; ; attempt++) {
6606
+ try {
6607
+ return operation();
6608
+ } catch (err) {
6609
+ if (!isBusyError(err) || attempt >= attempts) throw err;
6610
+ sleepSync(delayMs * attempt);
6611
+ }
6612
+ }
6613
+ }
6614
+ function openDatabase(path3, storeName, options = {}) {
6538
6615
  const BetterSqlite3 = loadBetterSqlite3();
6539
6616
  if (path3 !== MEMORY_PATH && path3 !== "") {
6540
6617
  const dir = (0, import_utils210.dirname)(path3);
@@ -6543,16 +6620,28 @@ var require_dist = __commonJS({
6543
6620
  (0, import_utils210.ensureDirSync)(dir);
6544
6621
  } catch (err) {
6545
6622
  const message = err instanceof Error ? err.message : String(err);
6546
- throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}`);
6623
+ throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}`, {
6624
+ cause: err
6625
+ });
6547
6626
  }
6548
6627
  }
6549
6628
  }
6629
+ let db;
6630
+ try {
6631
+ db = new BetterSqlite3(path3);
6632
+ } catch (err) {
6633
+ const message = err instanceof Error ? err.message : String(err);
6634
+ throw new Error(`${storeName}: failed to open database at "${path3}": ${message}`, { cause: err });
6635
+ }
6550
6636
  try {
6551
- return new BetterSqlite3(path3);
6637
+ db.pragma(`busy_timeout = ${Math.max(0, Math.floor(options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS))}`);
6638
+ if (options.walMode) withBusyRetry(() => db.pragma("journal_mode = WAL"));
6552
6639
  } catch (err) {
6640
+ db.close();
6553
6641
  const message = err instanceof Error ? err.message : String(err);
6554
- throw new Error(`${storeName}: failed to open database at "${path3}": ${message}`);
6642
+ throw new Error(`${storeName}: failed to configure database at "${path3}": ${message}`, { cause: err });
6555
6643
  }
6644
+ return db;
6556
6645
  }
6557
6646
  var SqliteKvStore = class {
6558
6647
  db;
@@ -6560,14 +6649,14 @@ var require_dist = __commonJS({
6560
6649
  cleanupTimer = null;
6561
6650
  stmts = null;
6562
6651
  constructor(options) {
6563
- this.db = openDatabase(options.path, "SqliteKvStore");
6564
- if (options.walMode !== false) {
6565
- this.db.pragma("journal_mode = WAL");
6566
- }
6652
+ this.db = openDatabase(options.path, "SqliteKvStore", {
6653
+ busyTimeoutMs: options.busyTimeoutMs,
6654
+ walMode: options.walMode !== false
6655
+ });
6567
6656
  if (options.encryption?.secret) {
6568
6657
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
6569
6658
  }
6570
- this.initSchema();
6659
+ withBusyRetry(() => this.initSchema());
6571
6660
  this.prepareStatements();
6572
6661
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
6573
6662
  if (cleanupInterval > 0) {
@@ -7026,17 +7115,17 @@ var require_dist = __commonJS({
7026
7115
  encryptionKey = null;
7027
7116
  stmts = null;
7028
7117
  constructor(options) {
7029
- this.db = openDatabase(options.path, "SqliteTaskStore");
7030
- if (options.walMode !== false) {
7031
- this.db.pragma("journal_mode = WAL");
7032
- }
7118
+ this.db = openDatabase(options.path, "SqliteTaskStore", {
7119
+ busyTimeoutMs: options.busyTimeoutMs,
7120
+ walMode: options.walMode !== false
7121
+ });
7033
7122
  this.logger = options.logger;
7034
7123
  this.liveness = options.livenessProbe ?? defaultLivenessProbe;
7035
7124
  if (options.encryption?.secret) {
7036
7125
  this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
7037
7126
  }
7038
7127
  this.emitter.setMaxListeners(200);
7039
- this.initSchema();
7128
+ withBusyRetry(() => this.initSchema());
7040
7129
  this.prepareStatements();
7041
7130
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
7042
7131
  if (cleanupInterval > 0) {
@@ -7273,13 +7362,13 @@ var require_dist = __commonJS({
7273
7362
  cleanupTimer = null;
7274
7363
  stmts = null;
7275
7364
  constructor(options) {
7276
- this.db = openDatabase(options.path, "SqliteEventStore");
7277
- if (options.walMode !== false) {
7278
- this.db.pragma("journal_mode = WAL");
7279
- }
7365
+ this.db = openDatabase(options.path, "SqliteEventStore", {
7366
+ busyTimeoutMs: options.busyTimeoutMs,
7367
+ walMode: options.walMode !== false
7368
+ });
7280
7369
  this.maxEvents = options.maxEvents ?? 1e4;
7281
7370
  this.ttlMs = options.ttlMs ?? 3e5;
7282
- this.initSchema();
7371
+ withBusyRetry(() => this.initSchema());
7283
7372
  this.prepareStatements();
7284
7373
  const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
7285
7374
  if (cleanupInterval > 0) {
@@ -7414,7 +7503,8 @@ var require_dist = __commonJS({
7414
7503
  secret: import_lazy_zod113.z.string().min(1)
7415
7504
  }).optional(),
7416
7505
  ttlCleanupIntervalMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(6e4),
7417
- walMode: import_lazy_zod113.z.boolean().optional().default(true)
7506
+ walMode: import_lazy_zod113.z.boolean().optional().default(true),
7507
+ busyTimeoutMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(5e3)
7418
7508
  });
7419
7509
  }
7420
7510
  });
@@ -7433,6 +7523,14 @@ function detectStorageType(storage, configuredType) {
7433
7523
  if (getEnv("REDIS_URL") || getEnv("REDIS_HOST")) return "redis";
7434
7524
  return "memory";
7435
7525
  }
7526
+ function isExplicitStorage(storage) {
7527
+ return Boolean(storage?.type && storage.type !== "auto");
7528
+ }
7529
+ function resolvesToVercelKvTaskBackend(redis, storage) {
7530
+ if (isExplicitStorage(storage)) return storage.type === "vercel-kv";
7531
+ if (redis) return "provider" in redis && redis.provider === "vercel-kv";
7532
+ return Boolean(getEnv("KV_REST_API_URL"));
7533
+ }
7436
7534
  async function createTaskStore(options = {}) {
7437
7535
  const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
7438
7536
  if (isEdgeRuntime3 && sqlite) {
@@ -7457,7 +7555,7 @@ async function createTaskStore(options = {}) {
7457
7555
  return { store: store2, type: "sqlite" };
7458
7556
  }
7459
7557
  let finalStorageConfig = storageConfig;
7460
- if (redis && !storageConfig) {
7558
+ if (redis && !isExplicitStorage(storageConfig)) {
7461
7559
  if ("provider" in redis && redis.provider === "vercel-kv") {
7462
7560
  throw new TaskStoreNotSupportedError(
7463
7561
  "Vercel KV is not supported for task stores. Task result blocking and cancel signalling require pub/sub. Use Redis or Upstash instead."
@@ -7485,7 +7583,8 @@ async function createTaskStore(options = {}) {
7485
7583
  };
7486
7584
  }
7487
7585
  }
7488
- if (finalStorageConfig?.type === "vercel-kv" || getEnv("KV_REST_API_URL")) {
7586
+ const explicitBackend = isExplicitStorage(storageConfig) || Boolean(redis);
7587
+ if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && getEnv("KV_REST_API_URL")) {
7489
7588
  throw new TaskStoreNotSupportedError(
7490
7589
  "Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
7491
7590
  );
@@ -8726,11 +8825,8 @@ import {
8726
8825
  } from "@frontmcp/protocol";
8727
8826
  import { awaitOutsideRequest } from "@frontmcp/utils";
8728
8827
  function hasMcpAppsExtension(capabilities) {
8729
- if (!capabilities?.experimental) {
8730
- return false;
8731
- }
8732
- const uiExtension = capabilities.experimental[MCP_APPS_EXTENSION_KEY];
8733
- return uiExtension !== void 0 && uiExtension !== null;
8828
+ const declared = capabilities?.experimental?.[MCP_APPS_EXTENSION_KEY] ?? capabilities?.extensions?.[MCP_APPS_EXTENSION_KEY];
8829
+ return declared !== void 0 && declared !== null;
8734
8830
  }
8735
8831
  function detectPlatformFromCapabilities(capabilities) {
8736
8832
  if (hasMcpAppsExtension(capabilities)) {
@@ -11337,6 +11433,8 @@ var init_ui_shared = __esm({
11337
11433
  widgets = /* @__PURE__ */ new Map();
11338
11434
  manifests = /* @__PURE__ */ new Map();
11339
11435
  resourceMeta = /* @__PURE__ */ new Map();
11436
+ uiTools = /* @__PURE__ */ new Set();
11437
+ customUris = /* @__PURE__ */ new Map();
11340
11438
  resolver;
11341
11439
  options;
11342
11440
  constructor(resolver, options = {}) {
@@ -11351,6 +11449,28 @@ var init_ui_shared = __esm({
11351
11449
  logger: this.options.logger
11352
11450
  };
11353
11451
  }
11452
+ /**
11453
+ * Record that `toolName` has a UI, and the custom `ui.resourceUri` it advertises (if any).
11454
+ * `tools/list` advertises that URI verbatim, so `resources/read` must resolve it back to the
11455
+ * tool; and only registered tools may be served a placeholder widget.
11456
+ */
11457
+ registerTool(toolName, resourceUri) {
11458
+ this.uiTools.add(toolName);
11459
+ if (resourceUri) this.customUris.set(resourceUri, toolName);
11460
+ }
11461
+ /** The tool that advertised `uri` as its custom `ui.resourceUri`. */
11462
+ resolveCustomUri(uri) {
11463
+ return this.customUris.get(uri);
11464
+ }
11465
+ /**
11466
+ * Whether `name` (or its app-qualified form `app:name`) is a tool with a UI.
11467
+ */
11468
+ hasTool(name44) {
11469
+ const known = (n) => this.uiTools.has(n) || this.widgets.has(n) || this.resourceMeta.has(n);
11470
+ if (known(name44)) return true;
11471
+ const sep = name44.indexOf(":");
11472
+ return sep > 0 && known(name44.slice(sep + 1));
11473
+ }
11354
11474
  getStaticWidget(name44) {
11355
11475
  return this.widgets.get(name44);
11356
11476
  }
@@ -11379,8 +11499,9 @@ var init_ui_shared = __esm({
11379
11499
  const output = options["output"] ?? {};
11380
11500
  const uiConfig = options["uiConfig"];
11381
11501
  const resourceMode = uiConfig?.["resourceMode"] ?? options["resourceMode"];
11382
- if (!toolName || !template) return;
11502
+ if (!toolName) return;
11383
11503
  this.updateResourceMetaFromConfig(toolName, uiConfig);
11504
+ if (!template) return;
11384
11505
  const result = renderToolTemplate({
11385
11506
  toolName,
11386
11507
  input,
@@ -11389,6 +11510,7 @@ var init_ui_shared = __esm({
11389
11510
  resolver: this.resolver,
11390
11511
  resourceMode,
11391
11512
  sizing: extractSizing(uiConfig),
11513
+ csp: uiConfig?.["csp"],
11392
11514
  ...this.stringResultOptions(uiConfig)
11393
11515
  });
11394
11516
  this.widgets.set(toolName, result.html);
@@ -11467,6 +11589,7 @@ var init_ui_shared = __esm({
11467
11589
  resolver: this.resolver,
11468
11590
  resourceMode,
11469
11591
  sizing: extractSizing(uiConfig),
11592
+ csp: uiConfig?.["csp"],
11470
11593
  ...this.stringResultOptions(uiConfig)
11471
11594
  });
11472
11595
  return { meta: result.meta };
@@ -11519,26 +11642,14 @@ function handleUIResourceRead(uri, registry, platformType) {
11519
11642
  return { handled: false };
11520
11643
  }
11521
11644
  const mimeType = getUIResourceMimeType(platformType);
11522
- const widgetParsed = parseWidgetUri(uri);
11645
+ const customTool = registry.resolveCustomUri(uri);
11646
+ const widgetParsed = customTool ? { toolName: customTool } : parseWidgetUri(uri);
11523
11647
  if (widgetParsed) {
11524
- const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11525
- const cachedWidget = registry.getStaticWidget(widgetParsed.toolName);
11526
- if (cachedWidget) {
11527
- return {
11528
- handled: true,
11529
- result: {
11530
- contents: [
11531
- {
11532
- uri,
11533
- mimeType,
11534
- text: cachedWidget,
11535
- ...resourceMeta ? { _meta: resourceMeta } : {}
11536
- }
11537
- ]
11538
- }
11539
- };
11648
+ if (!registry.hasTool(widgetParsed.toolName)) {
11649
+ return { handled: true, error: `No UI widget is registered for ${uri}` };
11540
11650
  }
11541
- const html = generatePlaceholderWidget(widgetParsed.toolName);
11651
+ const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
11652
+ const html = registry.getStaticWidget(widgetParsed.toolName) ?? generatePlaceholderWidget(widgetParsed.toolName);
11542
11653
  return {
11543
11654
  handled: true,
11544
11655
  result: {
@@ -12108,6 +12219,7 @@ var DirectClientImpl;
12108
12219
  var init_direct_client = __esm({
12109
12220
  "libs/sdk/src/direct/direct-client.ts"() {
12110
12221
  "use strict";
12222
+ init_list_all_pages_utils();
12111
12223
  init_errors();
12112
12224
  init_skills_mcp_types();
12113
12225
  init_llm_platform();
@@ -12254,8 +12366,11 @@ var init_direct_client = __esm({
12254
12366
  // Tool Operations (platform-formatted)
12255
12367
  // ─────────────────────────────────────────────────────────────────────────────
12256
12368
  async listTools() {
12257
- const result = await this.mcpClient.listTools();
12258
- return formatToolsForPlatform(result.tools, this.platform);
12369
+ const tools = await listAllPages("tools/list", async (cursor) => {
12370
+ const result = await this.mcpClient.listTools(cursor ? { cursor } : void 0);
12371
+ return { items: result.tools, nextCursor: result.nextCursor };
12372
+ });
12373
+ return formatToolsForPlatform(tools, this.platform);
12259
12374
  }
12260
12375
  async callTool(name44, args) {
12261
12376
  const result = await this.mcpClient.callTool({
@@ -12268,19 +12383,34 @@ var init_direct_client = __esm({
12268
12383
  // Resource Operations (raw format)
12269
12384
  // ─────────────────────────────────────────────────────────────────────────────
12270
12385
  async listResources() {
12271
- return this.mcpClient.listResources();
12386
+ const resources = await listAllPages("resources/list", async (cursor) => {
12387
+ const result = await this.mcpClient.listResources(cursor ? { cursor } : void 0);
12388
+ return { items: result.resources, nextCursor: result.nextCursor };
12389
+ });
12390
+ return { resources };
12272
12391
  }
12273
12392
  async readResource(uri) {
12274
12393
  return this.mcpClient.readResource({ uri });
12275
12394
  }
12276
12395
  async listResourceTemplates() {
12277
- return this.mcpClient.listResourceTemplates();
12396
+ const resourceTemplates = await listAllPages(
12397
+ "resources/templates/list",
12398
+ async (cursor) => {
12399
+ const result = await this.mcpClient.listResourceTemplates(cursor ? { cursor } : void 0);
12400
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
12401
+ }
12402
+ );
12403
+ return { resourceTemplates };
12278
12404
  }
12279
12405
  // ─────────────────────────────────────────────────────────────────────────────
12280
12406
  // Prompt Operations (raw format)
12281
12407
  // ─────────────────────────────────────────────────────────────────────────────
12282
12408
  async listPrompts() {
12283
- return this.mcpClient.listPrompts();
12409
+ const prompts = await listAllPages("prompts/list", async (cursor) => {
12410
+ const result = await this.mcpClient.listPrompts(cursor ? { cursor } : void 0);
12411
+ return { items: result.prompts, nextCursor: result.nextCursor };
12412
+ });
12413
+ return { prompts };
12284
12414
  }
12285
12415
  async getPrompt(name44, args) {
12286
12416
  return this.mcpClient.getPrompt({
@@ -13008,8 +13138,8 @@ function createRemoteAppProbe(appId, provider) {
13008
13138
  name: `remote:${appId}`,
13009
13139
  async check() {
13010
13140
  const result = provider.getHealthStatus(appId);
13011
- if (!result) {
13012
- return { status: "unhealthy", error: "No health check result available" };
13141
+ if (!result || result.status === "unknown" && result.consecutiveFailures === 0) {
13142
+ return { status: "degraded", details: { state: "unknown" } };
13013
13143
  }
13014
13144
  return {
13015
13145
  status: result.status === "healthy" ? "healthy" : result.status === "degraded" ? "degraded" : "unhealthy",
@@ -14122,6 +14252,12 @@ var init_frontmcp_context = __esm({
14122
14252
  // =====================
14123
14253
  /** Request metadata (headers, user-agent, etc.) */
14124
14254
  metadata;
14255
+ /**
14256
+ * The hosting platform's bindings object for this request: a Cloudflare Worker's `env`
14257
+ * (KV namespaces, D1 databases, R2 buckets, Durable Objects, `[vars]`, secrets).
14258
+ * `undefined` on Node, where bindings do not exist.
14259
+ */
14260
+ platformEnv;
14125
14261
  // =====================
14126
14262
  // Auth (mutable, progressively populated)
14127
14263
  // =====================
@@ -14157,6 +14293,7 @@ var init_frontmcp_context = __esm({
14157
14293
  this.traceContext = args.traceContext ?? generateTraceContext();
14158
14294
  this.timestamp = args.timestamp ?? Date.now();
14159
14295
  this._authInfo = args.authInfo ?? {};
14296
+ this.platformEnv = args.platformEnv;
14160
14297
  this.config = {
14161
14298
  forwardCallerTokenTo: toOrigins(args.config?.forwardCallerTokenTo),
14162
14299
  forwardCustomHeadersTo: toOrigins(args.config?.forwardCustomHeadersTo),
@@ -14858,6 +14995,7 @@ var init_frontmcp_context_storage = __esm({
14858
14995
  "use strict";
14859
14996
  init_decorators();
14860
14997
  init_metadata();
14998
+ init_tokens();
14861
14999
  init_mcp_error();
14862
15000
  init_frontmcp_context();
14863
15001
  init_metadata_utils();
@@ -14925,7 +15063,12 @@ var init_frontmcp_context_storage = __esm({
14925
15063
  const headers = request.headers ?? {};
14926
15064
  const headerSessionId = typeof headers["mcp-session-id"] === "string" ? headers["mcp-session-id"].trim() : "";
14927
15065
  const sessionId = headerSessionId.length > 0 ? headerSessionId : `anon:${randomUUID9()}`;
14928
- return this.runFromHeaders(headers, { sessionId, scopeId, peerAddress: request.socket?.remoteAddress }, fn);
15066
+ const platformEnv = request[ServerRequestTokens.webEnv];
15067
+ return this.runFromHeaders(
15068
+ headers,
15069
+ { sessionId, scopeId, peerAddress: request.socket?.remoteAddress, platformEnv },
15070
+ fn
15071
+ );
14929
15072
  }
14930
15073
  withServerConfig(args) {
14931
15074
  return { ...args, config: { ...this.contextConfig, ...args.config } };
@@ -21987,6 +22130,14 @@ import {
21987
22130
  isDevelopment as isDevelopment2,
21988
22131
  randomUUID as randomUUID11
21989
22132
  } from "@frontmcp/utils";
22133
+ function declaredOutput(tool, output) {
22134
+ if (!tool?.outputSchema || typeof output !== "object" || output === null || Array.isArray(output)) return output;
22135
+ if (Array.isArray(output["content"])) return output;
22136
+ const parsed = tool.safeParseOutput(output);
22137
+ if (!parsed.success) return output;
22138
+ const structured = parsed.data.structuredContent;
22139
+ return typeof structured === "object" && structured !== null && !Array.isArray(structured) ? structured : output;
22140
+ }
21990
22141
  function toolNameCandidates(name44) {
21991
22142
  if (!/[-_]/.test(name44)) return [name44];
21992
22143
  return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
@@ -22056,6 +22207,9 @@ var init_call_tool_flow = __esm({
22056
22207
  uiResult: z57.any().optional(),
22057
22208
  // UI metadata from rendering (merged into _meta)
22058
22209
  uiMeta: z57.record(z57.string(), z57.unknown()).optional(),
22210
+ // Metadata hooks add to the result (e.g. the cache plugin's `cache: 'hit'`), merged into the
22211
+ // result's _meta; never merged into rawOutput, which is the tool's data
22212
+ resultMeta: z57.record(z57.string(), z57.unknown()).optional(),
22059
22213
  // Progress token from request's _meta (for progress notifications)
22060
22214
  progressToken: z57.union([z57.string(), z57.number()]).optional(),
22061
22215
  // JSON-RPC request ID (for elicitation routing)
@@ -22711,7 +22865,7 @@ ${JSON.stringify(error.schema, null, 2)}
22711
22865
  this.logger.verbose("execute:done (elicitation fire-and-forget fallback)");
22712
22866
  return;
22713
22867
  }
22714
- if (isClientFacingError(error)) throw error;
22868
+ if (isClientFacingError(error) || error instanceof InvalidOutputError) throw error;
22715
22869
  throw new ToolExecutionError(
22716
22870
  this.state.tool?.metadata.name || "unknown",
22717
22871
  error instanceof Error ? error : void 0
@@ -22754,7 +22908,8 @@ ${JSON.stringify(error.schema, null, 2)}
22754
22908
  }
22755
22909
  async applyUI() {
22756
22910
  this.logger.verbose("applyUI:start");
22757
- const { tool, rawOutput, input } = this.state;
22911
+ const { tool, rawOutput: executeOutput, input } = this.state;
22912
+ const rawOutput = declaredOutput(tool, executeOutput);
22758
22913
  const ctx = this.input.ctx;
22759
22914
  if (ctx?._skipUI) {
22760
22915
  this.logger.verbose("applyUI:skip (agent context - structured data only)");
@@ -22883,7 +23038,7 @@ ${JSON.stringify(error.schema, null, 2)}
22883
23038
  }
22884
23039
  async finalize() {
22885
23040
  this.logger.verbose("finalize:start");
22886
- const { tool, rawOutput, uiResult, uiMeta } = this.state;
23041
+ const { tool, rawOutput, uiResult, uiMeta, resultMeta } = this.state;
22887
23042
  if (!tool) {
22888
23043
  this.logger.verbose("finalize: skipping (no tool in state)");
22889
23044
  return;
@@ -22926,6 +23081,9 @@ ${JSON.stringify(error.schema, null, 2)}
22926
23081
  if (rawMeta) {
22927
23082
  result._meta = { ...result._meta, ...rawMeta };
22928
23083
  }
23084
+ if (resultMeta) {
23085
+ result._meta = { ...result._meta, ...resultMeta };
23086
+ }
22929
23087
  if (uiResult) {
22930
23088
  result.content = uiResult.content;
22931
23089
  if (uiResult.structuredContent !== void 0 && uiResult.structuredContent !== null) {
@@ -24376,6 +24534,38 @@ var init_context = __esm({
24376
24534
  }
24377
24535
  });
24378
24536
 
24537
+ // libs/sdk/src/errors/misconfiguration.ts
24538
+ function findMisconfiguration(error) {
24539
+ let current = error;
24540
+ for (let depth = 0; current instanceof Error && depth < 5; depth++) {
24541
+ const code = current.code;
24542
+ if (typeof code === "string" && Object.prototype.hasOwnProperty.call(MISCONFIGURATION_REMEDIES, code)) {
24543
+ return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
24544
+ }
24545
+ if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
24546
+ current = current.cause;
24547
+ }
24548
+ return void 0;
24549
+ }
24550
+ function misconfigurationBody(m) {
24551
+ return { error: "server_misconfigured", code: m.code, message: m.remedy };
24552
+ }
24553
+ var MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY;
24554
+ var init_misconfiguration = __esm({
24555
+ "libs/sdk/src/errors/misconfiguration.ts"() {
24556
+ "use strict";
24557
+ MISCONFIGURATION_REMEDIES = {
24558
+ 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.",
24559
+ 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`.",
24560
+ 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.",
24561
+ // The startup checks: a server whose entries ask for protection nothing gives them does not start.
24562
+ 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.",
24563
+ 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."
24564
+ };
24565
+ CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
24566
+ }
24567
+ });
24568
+
24379
24569
  // libs/sdk/src/server/server.validation.ts
24380
24570
  import { base64Decode } from "@frontmcp/utils";
24381
24571
  function applyHeaders(res, headers) {
@@ -24687,6 +24877,7 @@ var init_flow_instance = __esm({
24687
24877
  init_common();
24688
24878
  init_context();
24689
24879
  init_errors();
24880
+ init_misconfiguration();
24690
24881
  init_server_validation();
24691
24882
  init_flow_http_path();
24692
24883
  init_flow_stages();
@@ -24746,6 +24937,15 @@ var init_flow_instance = __esm({
24746
24937
  return writeHttpResponse(response, e.output);
24747
24938
  }
24748
24939
  }
24940
+ const misconfiguration = findMisconfiguration(e);
24941
+ if (misconfiguration) {
24942
+ this.logger.error("Server misconfigured", { flow: this.name, code: misconfiguration.code });
24943
+ return writeHttpResponse(response, {
24944
+ kind: "json",
24945
+ status: 500,
24946
+ body: misconfigurationBody(misconfiguration)
24947
+ });
24948
+ }
24749
24949
  if (e instanceof PublicMcpError) {
24750
24950
  const challenge = e.wwwAuthenticate;
24751
24951
  this.logger.warn("Public error in flow", {
@@ -25457,14 +25657,90 @@ function collectDynamicProviders(klass, options) {
25457
25657
  }
25458
25658
  return out;
25459
25659
  }
25660
+ function collectDynamicTools(klass, options) {
25661
+ const chain = [];
25662
+ for (let k = klass; k && k !== Function.prototype; k = Object.getPrototypeOf(k)) {
25663
+ chain.push(k);
25664
+ }
25665
+ const out = [];
25666
+ for (let i = chain.length - 1; i >= 0; i--) {
25667
+ const k = chain[i];
25668
+ if (Object.prototype.hasOwnProperty.call(k, "dynamicTools") && typeof k.dynamicTools === "function") {
25669
+ out.push(...k.dynamicTools(options) ?? []);
25670
+ }
25671
+ }
25672
+ return out;
25673
+ }
25674
+ function pluginMetadataFromOptions(options, dynamicTools) {
25675
+ const out = { ...options };
25676
+ for (const key of LIST_METADATA_KEYS) {
25677
+ if (key in out && !Array.isArray(out[key])) delete out[key];
25678
+ }
25679
+ const tools = [...out["tools"] ?? [], ...dynamicTools];
25680
+ if (tools.length > 0) out["tools"] = tools;
25681
+ return out;
25682
+ }
25460
25683
  function dedupePluginProviders(providers) {
25461
25684
  const map = /* @__PURE__ */ new Map();
25462
25685
  for (const p of providers) map.set(p["provide"] ?? p, p);
25463
25686
  return [...map.values()];
25464
25687
  }
25688
+ var LIST_METADATA_KEYS;
25465
25689
  var init_dynamic_utils = __esm({
25466
25690
  "libs/sdk/src/common/dynamic/dynamic.utils.ts"() {
25467
25691
  "use strict";
25692
+ LIST_METADATA_KEYS = ["exports", "plugins", "adapters", "tools", "resources", "prompts", "skills"];
25693
+ }
25694
+ });
25695
+
25696
+ // libs/sdk/src/common/dynamic/dynamic.plugin.ts
25697
+ function isDynamicPluginClass(value) {
25698
+ return typeof value === "function" && value.prototype instanceof DynamicPlugin;
25699
+ }
25700
+ function initOptionsOf(instance) {
25701
+ return initOptionsByInstance.has(instance) ? { options: initOptionsByInstance.get(instance) } : void 0;
25702
+ }
25703
+ var initOptionsByInstance, DynamicPlugin;
25704
+ var init_dynamic_plugin = __esm({
25705
+ "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
25706
+ "use strict";
25707
+ init_transport_errors();
25708
+ init_dynamic_utils();
25709
+ initOptionsByInstance = /* @__PURE__ */ new WeakMap();
25710
+ DynamicPlugin = class {
25711
+ get(token) {
25712
+ throw new MethodNotImplementedError("DynamicPlugin", "get");
25713
+ }
25714
+ /**
25715
+ * Static init() method to create a plugin provider.
25716
+ * @param options - Input options (with optional fields for defaults). Omitted, the plugin is
25717
+ * built with `{}`, as `ApprovalPlugin.init()` is documented.
25718
+ */
25719
+ static init(options = {}) {
25720
+ const extraProviders = options.providers;
25721
+ const typedOptions = options;
25722
+ if ("useFactory" in options) {
25723
+ return {
25724
+ ...pluginMetadataFromOptions(typedOptions, []),
25725
+ provide: this,
25726
+ inject: options.inject,
25727
+ useFactory: options.useFactory,
25728
+ providers: dedupePluginProviders(extraProviders ?? [])
25729
+ };
25730
+ }
25731
+ const dyn = collectDynamicProviders(this, typedOptions);
25732
+ const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
25733
+ const dynamicTools = collectDynamicTools(this, typedOptions);
25734
+ const instance = new this(options);
25735
+ initOptionsByInstance.set(instance, options);
25736
+ return {
25737
+ ...pluginMetadataFromOptions(typedOptions, dynamicTools),
25738
+ provide: this,
25739
+ useValue: instance,
25740
+ providers: mergedProviders
25741
+ };
25742
+ }
25743
+ };
25468
25744
  }
25469
25745
  });
25470
25746
 
@@ -25720,9 +25996,18 @@ var init_execution_context_interface = __esm({
25720
25996
  if (this._authContext) return this._authContext;
25721
25997
  const rawAuth = __require("@frontmcp/auth");
25722
25998
  const auth = rawAuth.default ?? rawAuth;
25723
- this._authContext = auth.buildAuthContext(this._authInfo, this.scope.metadata.authorities?.claimsMapping);
25999
+ this._authContext = auth.buildAuthContext(this.resolveAuthSource(), this.scope.metadata.authorities?.claimsMapping);
25724
26000
  return this._authContext;
25725
26001
  }
26002
+ resolveAuthSource() {
26003
+ const fromContext = this.tryGetContext()?.authInfo;
26004
+ if (!fromContext) return this._authInfo;
26005
+ const merged = { ...this._authInfo };
26006
+ for (const [key, value] of Object.entries(fromContext)) {
26007
+ if (value !== void 0) merged[key] = value;
26008
+ }
26009
+ return merged;
26010
+ }
25726
26011
  /**
25727
26012
  * Try to get the context, returning undefined if not available.
25728
26013
  *
@@ -25908,6 +26193,23 @@ var init_execution_context_interface = __esm({
25908
26193
  get config() {
25909
26194
  return this.providers.get(ConfigService);
25910
26195
  }
26196
+ // ---- Platform bindings ----
26197
+ /**
26198
+ * The hosting platform's bindings for the current request — on a Cloudflare Worker, the `env`
26199
+ * object holding KV namespaces, D1 databases, R2 buckets, Durable Object namespaces, `[vars]`
26200
+ * and secrets. `undefined` on Node/Express, where no such object exists.
26201
+ *
26202
+ * Only string bindings are mirrored into `process.env`; use this for the rest.
26203
+ *
26204
+ * @example
26205
+ * ```typescript
26206
+ * const kv = this.workerEnv?.['MY_KV'] as KVNamespace | undefined;
26207
+ * ```
26208
+ */
26209
+ get workerEnv() {
26210
+ const env = this.tryGetContext()?.platformEnv;
26211
+ return env !== null && typeof env === "object" ? env : void 0;
26212
+ }
25911
26213
  // ---- Runtime context helpers ----
25912
26214
  /**
25913
26215
  * Get the current runtime context (platform, runtime, deployment, env).
@@ -28955,12 +29257,13 @@ var init_plugin_utils = __esm({
28955
29257
  // libs/sdk/src/plugin/plugin.registry.ts
28956
29258
  import "reflect-metadata";
28957
29259
  import { tokenName as tokenName11 } from "@frontmcp/di";
28958
- var PluginRegistry;
29260
+ var installedPluginValues, PluginRegistry;
28959
29261
  var init_plugin_registry = __esm({
28960
29262
  "libs/sdk/src/plugin/plugin.registry.ts"() {
28961
29263
  "use strict";
28962
29264
  init_adapter_registry();
28963
29265
  init_common();
29266
+ init_dynamic_plugin();
28964
29267
  init_dynamic_utils();
28965
29268
  init_context_extension();
28966
29269
  init_errors();
@@ -28973,6 +29276,7 @@ var init_plugin_registry = __esm({
28973
29276
  init_skill_registry();
28974
29277
  init_tool_registry();
28975
29278
  init_plugin_utils();
29279
+ installedPluginValues = /* @__PURE__ */ new WeakSet();
28976
29280
  PluginRegistry = class _PluginRegistry extends RegistryAbstract {
28977
29281
  /** providers by token */
28978
29282
  pProviders = /* @__PURE__ */ new Map();
@@ -29161,8 +29465,15 @@ var init_plugin_registry = __esm({
29161
29465
  pluginInstance: new rec.provide(...depsInstances),
29162
29466
  dynamicProviders: rec.providers
29163
29467
  };
29164
- case "VALUE" /* VALUE */:
29165
- return { pluginInstance: rec.useValue, dynamicProviders: rec.providers };
29468
+ case "VALUE" /* VALUE */: {
29469
+ const value = rec.useValue;
29470
+ const init = installedPluginValues.has(value) ? initOptionsOf(value) : void 0;
29471
+ installedPluginValues.add(value);
29472
+ if (init && isDynamicPluginClass(rec.provide)) {
29473
+ return { pluginInstance: new rec.provide(init.options), dynamicProviders: rec.providers };
29474
+ }
29475
+ return { pluginInstance: value, dynamicProviders: rec.providers };
29476
+ }
29166
29477
  case "FACTORY" /* FACTORY */: {
29167
29478
  const args = [];
29168
29479
  for (const d of rec.inject()) args.push(await this.providers.resolveBootstrapDep(d));
@@ -30705,6 +31016,9 @@ import { AuthorityDeniedError as AuthorityDeniedError7, resolveRequiredScopes as
30705
31016
  import { ExecutionTimeoutError as ExecutionTimeoutError2, withTimeout as withTimeout2 } from "@frontmcp/guard";
30706
31017
  import { z as z60 } from "@frontmcp/lazy-zod";
30707
31018
  import { CallToolRequestSchema as CallToolRequestSchema3, CallToolResultSchema as CallToolResultSchema3 } from "@frontmcp/protocol";
31019
+ function invalidOutputFrom(outputError) {
31020
+ return outputError instanceof InvalidOutputError ? outputError : new InvalidOutputError();
31021
+ }
30708
31022
  var inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallAgentFlow;
30709
31023
  var init_call_agent_flow = __esm({
30710
31024
  "libs/sdk/src/agent/flows/call-agent.flow.ts"() {
@@ -31087,7 +31401,7 @@ var init_call_agent_flow = __esm({
31087
31401
  const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
31088
31402
  if (!agent || executionStartedAt === void 0) return;
31089
31403
  if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
31090
- const failure = executionError ?? (outputError !== void 0 ? new InvalidOutputError() : void 0);
31404
+ const failure = executionError ?? (outputError !== void 0 ? invalidOutputFrom(outputError) : void 0);
31091
31405
  const failed = failure !== void 0;
31092
31406
  const requestId = this.tryGetContext()?.requestId;
31093
31407
  const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
@@ -31117,7 +31431,7 @@ var init_call_agent_flow = __esm({
31117
31431
  agent: agent.metadata.name,
31118
31432
  errors: outputError
31119
31433
  });
31120
- throw new InvalidOutputError();
31434
+ throw invalidOutputFrom(outputError);
31121
31435
  }
31122
31436
  const result = parsedOutput;
31123
31437
  if (executionMeta) {
@@ -31697,13 +32011,46 @@ var init_request_validation = __esm({
31697
32011
  });
31698
32012
 
31699
32013
  // libs/sdk/src/transport/mcp-20260728/request-state.ts
31700
- import { base64urlDecode as base64urlDecode2, base64urlEncode as base64urlEncode2, hmacSha256, randomBytes as randomBytes3, sha256Hex as sha256Hex5, timingSafeEqual } from "@frontmcp/utils";
31701
- function getRequestStateKey() {
32014
+ import {
32015
+ base64urlDecode as base64urlDecode2,
32016
+ base64urlEncode as base64urlEncode2,
32017
+ getEnv as getEnv4,
32018
+ hmacSha256,
32019
+ isProduction as isProduction4,
32020
+ randomBytes as randomBytes3,
32021
+ sha256Hex as sha256Hex5,
32022
+ timingSafeEqual
32023
+ } from "@frontmcp/utils";
32024
+ function configuredSecret(name44) {
32025
+ const value = getEnv4(name44);
32026
+ return value && value.trim().length > 0 ? value : void 0;
32027
+ }
32028
+ function resolveKey() {
31702
32029
  if (cachedKey) return cachedKey;
31703
- const secret = process.env["VAULT_SECRET"] ?? process.env["JWT_SECRET"];
31704
- cachedKey = secret ? encoder.encode(secret) : randomBytes3(32);
32030
+ const vault = configuredSecret("VAULT_SECRET");
32031
+ const jwt = vault ? void 0 : configuredSecret("JWT_SECRET");
32032
+ if (vault) cachedKey = { key: encoder.encode(vault), source: "VAULT_SECRET" };
32033
+ else if (jwt) cachedKey = { key: encoder.encode(jwt), source: "JWT_SECRET" };
32034
+ else cachedKey = { key: randomBytes3(32), source: "per-process" };
31705
32035
  return cachedKey;
31706
32036
  }
32037
+ function getRequestStateKey() {
32038
+ return resolveKey().key;
32039
+ }
32040
+ function getRequestStateKeySource() {
32041
+ return resolveKey().source;
32042
+ }
32043
+ function warnIfRequestStateKeyNotShared(options) {
32044
+ if (warnedKeyNotShared || !isProduction4()) return;
32045
+ const { metadata } = options;
32046
+ const persistence = metadata.transport?.persistence;
32047
+ const sharesState = Boolean(metadata.redis) || typeof persistence === "object" && persistence !== null;
32048
+ if (!sharesState || getRequestStateKeySource() !== "per-process") return;
32049
+ warnedKeyNotShared = true;
32050
+ options.logger.warn(
32051
+ "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."
32052
+ );
32053
+ }
31707
32054
  function computeRequestBinding(method, params) {
31708
32055
  const salient = {};
31709
32056
  for (const key of ["name", "uri", "arguments"]) {
@@ -31753,12 +32100,13 @@ function decodeRequestState(state, expected) {
31753
32100
  if (payload.b !== expected.binding) return { ok: false, reason: "request-mismatch" };
31754
32101
  return { ok: true, responses: payload.r };
31755
32102
  }
31756
- var DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, encoder, decoder;
32103
+ var DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, warnedKeyNotShared, encoder, decoder;
31757
32104
  var init_request_state = __esm({
31758
32105
  "libs/sdk/src/transport/mcp-20260728/request-state.ts"() {
31759
32106
  "use strict";
31760
32107
  DEFAULT_REQUEST_STATE_TTL_MS = 10 * 60 * 1e3;
31761
32108
  MAX_REQUEST_STATE_BYTES = 256 * 1024;
32109
+ warnedKeyNotShared = false;
31762
32110
  encoder = new TextEncoder();
31763
32111
  decoder = new TextDecoder();
31764
32112
  }
@@ -32486,7 +32834,8 @@ async function dispatch20260728(options) {
32486
32834
  };
32487
32835
  const carried = decodeRequestState(params["requestState"], binding);
32488
32836
  if (!carried.ok && carried.reason !== "absent") {
32489
- scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason });
32837
+ 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;
32838
+ scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason, ...hint && { hint } });
32490
32839
  }
32491
32840
  const exchange = new MrtrExchange({
32492
32841
  inputResponses: params["inputResponses"],
@@ -32640,6 +32989,7 @@ var McpStatelessError, DEFAULT_CLIENT_INFO, McpStatelessClient, TASKS_CLIENT_CAP
32640
32989
  var init_mcp_stateless_client = __esm({
32641
32990
  "libs/sdk/src/transport/mcp-20260728/client/mcp-stateless.client.ts"() {
32642
32991
  "use strict";
32992
+ init_list_all_pages_utils();
32643
32993
  init_header_codec();
32644
32994
  init_protocol_20260728_constants();
32645
32995
  init_tasks_extension();
@@ -32680,10 +33030,22 @@ var init_mcp_stateless_client = __esm({
32680
33030
  * The spec requires this: one malformed tool definition must not prevent the
32681
33031
  * other tools from being used, so the offender is excluded and logged rather
32682
33032
  * than failing the call.
33033
+ *
33034
+ * Every page is fetched (`nextCursor` is followed to the end), since the
33035
+ * schemas cached here are what later `tools/call` headers are derived from.
32683
33036
  */
32684
33037
  async listTools() {
32685
- const result = await this.request("tools/list");
32686
- const tools = Array.isArray(result["tools"]) ? result["tools"] : [];
33038
+ const tools = await listAllPages(
33039
+ "tools/list",
33040
+ async (cursor) => {
33041
+ const result = await this.request("tools/list", cursor ? { cursor } : {});
33042
+ return {
33043
+ items: Array.isArray(result["tools"]) ? result["tools"] : [],
33044
+ nextCursor: typeof result["nextCursor"] === "string" ? result["nextCursor"] : void 0
33045
+ };
33046
+ },
33047
+ (message) => new McpStatelessError(-32603, message)
33048
+ );
32687
33049
  const usable = [];
32688
33050
  for (const tool of tools) {
32689
33051
  const validation = validateHeaderParams(tool["inputSchema"]);
@@ -32702,11 +33064,17 @@ var init_mcp_stateless_client = __esm({
32702
33064
  async readResource(uri) {
32703
33065
  return this.request("resources/read", { uri });
32704
33066
  }
32705
- async listResources() {
32706
- return this.request("resources/list");
33067
+ /** One page of `resources/list`; pass the previous page's `nextCursor` for the next. */
33068
+ async listResources(cursor) {
33069
+ return this.request("resources/list", cursor ? { cursor } : {});
32707
33070
  }
32708
- async listPrompts() {
32709
- return this.request("prompts/list");
33071
+ /** One page of `resources/templates/list`; pass the previous page's `nextCursor` for the next. */
33072
+ async listResourceTemplates(cursor) {
33073
+ return this.request("resources/templates/list", cursor ? { cursor } : {});
33074
+ }
33075
+ /** One page of `prompts/list`; pass the previous page's `nextCursor` for the next. */
33076
+ async listPrompts(cursor) {
33077
+ return this.request("prompts/list", cursor ? { cursor } : {});
32710
33078
  }
32711
33079
  async getPrompt(name44, args = {}) {
32712
33080
  return this.request("prompts/get", { name: name44, arguments: args });
@@ -32992,6 +33360,10 @@ var init_mcp_20260728 = __esm({
32992
33360
  });
32993
33361
 
32994
33362
  // libs/sdk/src/remote-mcp/mcp-stateless-client.adapter.ts
33363
+ function nextCursorOf(result) {
33364
+ const next = result["nextCursor"];
33365
+ return typeof next === "string" && next.length > 0 ? { nextCursor: next } : {};
33366
+ }
32995
33367
  async function negotiateRemoteProtocol(url, configured, headers, fetchImpl = fetch) {
32996
33368
  if (configured === "2026-07-28") return "2026-07-28";
32997
33369
  if (configured !== "auto") return "legacy";
@@ -33034,16 +33406,29 @@ var init_mcp_stateless_client_adapter = __esm({
33034
33406
  async callTool(params) {
33035
33407
  return this.client.callTool(params.name, params.arguments ?? {});
33036
33408
  }
33037
- async listResources() {
33038
- const result = await this.client.listResources();
33039
- return { resources: Array.isArray(result["resources"]) ? result["resources"] : [] };
33409
+ async listResources(params) {
33410
+ const result = await this.client.listResources(params?.cursor);
33411
+ return {
33412
+ resources: Array.isArray(result["resources"]) ? result["resources"] : [],
33413
+ ...nextCursorOf(result)
33414
+ };
33415
+ }
33416
+ async listResourceTemplates(params) {
33417
+ const result = await this.client.listResourceTemplates(params?.cursor);
33418
+ return {
33419
+ resourceTemplates: Array.isArray(result["resourceTemplates"]) ? result["resourceTemplates"] : [],
33420
+ ...nextCursorOf(result)
33421
+ };
33040
33422
  }
33041
33423
  async readResource(params) {
33042
33424
  return this.client.readResource(params.uri);
33043
33425
  }
33044
- async listPrompts() {
33045
- const result = await this.client.listPrompts();
33046
- return { prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [] };
33426
+ async listPrompts(params) {
33427
+ const result = await this.client.listPrompts(params?.cursor);
33428
+ return {
33429
+ prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [],
33430
+ ...nextCursorOf(result)
33431
+ };
33047
33432
  }
33048
33433
  async getPrompt(params) {
33049
33434
  return this.client.getPrompt(params.name, params.arguments ?? {});
@@ -33402,7 +33787,7 @@ var init_health_check = __esm({
33402
33787
  this.consecutiveSuccesses++;
33403
33788
  this.consecutiveFailures = 0;
33404
33789
  const previousStatus = this.status;
33405
- if (this.consecutiveSuccesses >= this.options.healthyThreshold) {
33790
+ if (previousStatus === "unknown" || this.consecutiveSuccesses >= this.options.healthyThreshold) {
33406
33791
  this.status = latencyMs > this.options.degradedLatencyMs ? "degraded" : "healthy";
33407
33792
  }
33408
33793
  this.lastResult = {
@@ -33570,6 +33955,7 @@ var DEFAULT_RETRY_OPTIONS2, DEFAULT_CIRCUIT_BREAKER_OPTIONS, DEFAULT_HEALTH_CHEC
33570
33955
  var init_mcp_client_service = __esm({
33571
33956
  "libs/sdk/src/remote-mcp/mcp-client.service.ts"() {
33572
33957
  "use strict";
33958
+ init_list_all_pages_utils();
33573
33959
  init_mcp_error();
33574
33960
  init_remote_errors();
33575
33961
  init_transport_errors();
@@ -34173,10 +34559,15 @@ var init_mcp_client_service = __esm({
34173
34559
  const httpOptions = request.transportOptions;
34174
34560
  return httpOptions?.fallbackToSSE ?? true;
34175
34561
  }
34562
+ // Each list follows `nextCursor` to the end: a FrontMCP remote pages its lists
34563
+ // (40 per page by default), and proxying only the first page silently dropped
34564
+ // everything after it.
34176
34565
  async listToolsInternal(connection) {
34177
34566
  try {
34178
- const result = await connection.client.listTools();
34179
- return result.tools || [];
34567
+ return await listAllPages("tools/list", async (cursor) => {
34568
+ const result = await connection.client.listTools(cursor ? { cursor } : void 0);
34569
+ return { items: result.tools, nextCursor: result.nextCursor };
34570
+ });
34180
34571
  } catch (error) {
34181
34572
  this.logger.warn(`Failed to list tools: ${error.message}`);
34182
34573
  return [];
@@ -34184,8 +34575,10 @@ var init_mcp_client_service = __esm({
34184
34575
  }
34185
34576
  async listResourcesInternal(connection) {
34186
34577
  try {
34187
- const result = await connection.client.listResources();
34188
- return result.resources || [];
34578
+ return await listAllPages("resources/list", async (cursor) => {
34579
+ const result = await connection.client.listResources(cursor ? { cursor } : void 0);
34580
+ return { items: result.resources, nextCursor: result.nextCursor };
34581
+ });
34189
34582
  } catch (error) {
34190
34583
  this.logger.warn(`Failed to list resources: ${error.message}`);
34191
34584
  return [];
@@ -34193,8 +34586,10 @@ var init_mcp_client_service = __esm({
34193
34586
  }
34194
34587
  async listResourceTemplatesInternal(connection) {
34195
34588
  try {
34196
- const result = await connection.client.listResourceTemplates();
34197
- return result.resourceTemplates || [];
34589
+ return await listAllPages("resources/templates/list", async (cursor) => {
34590
+ const result = await connection.client.listResourceTemplates(cursor ? { cursor } : void 0);
34591
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
34592
+ });
34198
34593
  } catch (error) {
34199
34594
  this.logger.warn(`Failed to list resource templates: ${error.message}`);
34200
34595
  return [];
@@ -34202,8 +34597,10 @@ var init_mcp_client_service = __esm({
34202
34597
  }
34203
34598
  async listPromptsInternal(connection) {
34204
34599
  try {
34205
- const result = await connection.client.listPrompts();
34206
- return result.prompts || [];
34600
+ return await listAllPages("prompts/list", async (cursor) => {
34601
+ const result = await connection.client.listPrompts(cursor ? { cursor } : void 0);
34602
+ return { items: result.prompts, nextCursor: result.nextCursor };
34603
+ });
34207
34604
  } catch (error) {
34208
34605
  this.logger.warn(`Failed to list prompts: ${error.message}`);
34209
34606
  return [];
@@ -41748,8 +42145,11 @@ var init_session_verify_flow = __esm({
41748
42145
  const machineId = getMachineId2();
41749
42146
  const authSignature = subject ? `${authMode}:${subject}` : authMode;
41750
42147
  if (sessionIdHeader) {
41751
- const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
41752
- if (existingPayload && existingPayload.nodeId === machineId) {
42148
+ const body = this.rawInput.request.body;
42149
+ const isInitialize2 = typeof body === "object" && body !== null && "method" in body && body.method === "initialize";
42150
+ const decrypted = decryptPublicSession(sessionIdHeader, authSignature);
42151
+ const existingPayload = decrypted && isInitialize2 && decrypted.nodeId !== machineId ? void 0 : decrypted;
42152
+ if (existingPayload) {
41753
42153
  const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
41754
42154
  const user2 = anonymousCallerClaims(options, anonId);
41755
42155
  this.respond({
@@ -42209,7 +42609,7 @@ var init_well_known_jwks_flow = __esm({
42209
42609
  import "reflect-metadata";
42210
42610
  import { advertisedScopes } from "@frontmcp/auth";
42211
42611
  import { z as z74 } from "@frontmcp/lazy-zod";
42212
- import { isProduction as isProduction4 } from "@frontmcp/utils";
42612
+ import { isProduction as isProduction5 } from "@frontmcp/utils";
42213
42613
  function resolveDcrEnabled(auth) {
42214
42614
  if (auth && isLocalMode(auth)) {
42215
42615
  const dcr = auth.dcr;
@@ -42217,7 +42617,7 @@ function resolveDcrEnabled(auth) {
42217
42617
  return dcr.enabled;
42218
42618
  }
42219
42619
  }
42220
- return !isProduction4();
42620
+ return !isProduction5();
42221
42621
  }
42222
42622
  var inputSchema25, AuthServerMetadataSchema, outputSchema23, wellKnownAsStateSchema, wellKnownAsPlan, name24, Stage24, WellKnownAsFlow;
42223
42623
  var init_well_known_oauth_authorization_server_flow = __esm({
@@ -42619,8 +43019,8 @@ import {
42619
43019
  verifyPkce
42620
43020
  } from "@frontmcp/auth";
42621
43021
  import {
42622
- getEnv as getEnv4,
42623
- isProduction as isProduction5,
43022
+ getEnv as getEnv5,
43023
+ isProduction as isProduction6,
42624
43024
  MemoryStorageAdapter,
42625
43025
  randomBytes as randomBytes8,
42626
43026
  randomUUID as randomUUID21,
@@ -42716,26 +43116,28 @@ var init_instance_local_primary_auth = __esm({
42716
43116
  this.providers = providers;
42717
43117
  this.logger = this.providers.getActiveScope().logger.child("LocalPrimaryAuth");
42718
43118
  this.port = this.providers.getActiveScope().metadata.http?.port ?? 3001;
42719
- this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
43119
+ this.host = getEnv5("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
42720
43120
  this.issuer = this.deriveIssuer(options);
42721
- this.publicHostPinned = !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
42722
- const jwtSecret = getEnv4("JWT_SECRET");
43121
+ this.publicHostPinned = !!getEnv5("FRONTMCP_PUBLIC_HOST")?.trim();
43122
+ const jwtSecret = getEnv5("JWT_SECRET");
42723
43123
  const hasJwtSecret = !!jwtSecret?.trim();
42724
43124
  if (hasJwtSecret && jwtSecret) {
42725
43125
  const encoded = new TextEncoder().encode(jwtSecret);
42726
43126
  if (encoded.length < MIN_HS256_SECRET_BYTES) {
42727
- if (isProduction5()) throw new JwtSecretWeakError(encoded.length);
43127
+ if (isProduction6()) throw new JwtSecretWeakError(encoded.length);
42728
43128
  this.logger.warn(
42729
43129
  `JWT_SECRET is ${encoded.length} bytes; HS256 wants at least ${MIN_HS256_SECRET_BYTES} (RFC 7518). This is refused in production \u2014 generate one with \`openssl rand -hex 32\`.`
42730
43130
  );
42731
43131
  }
42732
43132
  this.secret = encoded;
42733
- } else if (isProduction5() && isOrchestratedMode(options)) {
43133
+ } else if (isProduction6() && isOrchestratedMode(options)) {
42734
43134
  throw new JwtSecretRequiredError(options.mode);
42735
43135
  } else {
42736
- this.logger.warn(
42737
- "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."
42738
- );
43136
+ if (!isPublicMode(options)) {
43137
+ this.logger.warn(
43138
+ "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."
43139
+ );
43140
+ }
42739
43141
  this.secret = getDefaultNoAuthSecret();
42740
43142
  }
42741
43143
  this.tokenStorage = this.readTokenStorage(options);
@@ -42874,7 +43276,7 @@ var init_instance_local_primary_auth = __esm({
42874
43276
  if (dcr && typeof dcr.enabled === "boolean") {
42875
43277
  return dcr.enabled;
42876
43278
  }
42877
- return !isProduction5();
43279
+ return !isProduction6();
42878
43280
  }
42879
43281
  /** Provider configurations (indexed by provider ID) */
42880
43282
  providerConfigs = /* @__PURE__ */ new Map();
@@ -43103,7 +43505,7 @@ var init_instance_local_primary_auth = __esm({
43103
43505
  await memory.connect();
43104
43506
  storage = memory;
43105
43507
  }
43106
- const pepper = getEnv4("VAULT_SECRET") ?? getEnv4("JWT_SECRET") ?? void 0;
43508
+ const pepper = getEnv5("VAULT_SECRET") ?? getEnv5("JWT_SECRET") ?? void 0;
43107
43509
  this.credentialVaultImpl = new SessionCredentialVault({
43108
43510
  storage,
43109
43511
  pepper,
@@ -43154,7 +43556,7 @@ var init_instance_local_primary_auth = __esm({
43154
43556
  }
43155
43557
  const config = this.readSecureStoreConfig();
43156
43558
  const reuseAdapter = this.storageAdapter && this.secureStoreSharesTokenStorage(config) ? this.storageAdapter : void 0;
43157
- const pepper = getEnv4("VAULT_SECRET") ?? getEnv4("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
43559
+ const pepper = getEnv5("VAULT_SECRET") ?? getEnv5("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
43158
43560
  const resolved = await createSecureStore({
43159
43561
  config,
43160
43562
  pepper,
@@ -46231,10 +46633,10 @@ import {
46231
46633
  randomBytes as randomBytes9,
46232
46634
  base64urlEncode as base64urlEncode6,
46233
46635
  base64urlDecode as base64urlDecode3,
46234
- getEnv as getEnv5
46636
+ getEnv as getEnv6
46235
46637
  } from "@frontmcp/utils";
46236
46638
  function getElicitationSecret() {
46237
- return getEnv5("MCP_ELICITATION_SECRET") || getEnv5("MCP_SESSION_SECRET") || getEnv5("MCP_SERVER_SECRET") || null;
46639
+ return getEnv6("MCP_ELICITATION_SECRET") || getEnv6("MCP_SESSION_SECRET") || getEnv6("MCP_SERVER_SECRET") || null;
46238
46640
  }
46239
46641
  function isElicitationEncryptionAvailable() {
46240
46642
  return getElicitationSecret() !== null;
@@ -46711,12 +47113,19 @@ var init_encrypted_elicitation_store = __esm({
46711
47113
  });
46712
47114
 
46713
47115
  // libs/sdk/src/elicitation/store/elicitation-store.factory.ts
46714
- import { createMemoryStorage as createMemoryStorage2, createStorage as createStorage2, getEnv as getEnv6 } from "@frontmcp/utils";
46715
- function detectStorageType2(storage) {
46716
- if (getEnv6("UPSTASH_REDIS_REST_URL")) {
47116
+ import { createMemoryStorage as createMemoryStorage2, createStorage as createStorage2, getEnv as getEnv7 } from "@frontmcp/utils";
47117
+ function detectStorageType2(storage, configuredType) {
47118
+ if (configuredType === "redis" || configuredType === "upstash" || configuredType === "memory") {
47119
+ return configuredType;
47120
+ }
47121
+ const backend = storage.type ?? storage.backendName;
47122
+ if (backend === "redis" || backend === "upstash" || backend === "memory") {
47123
+ return backend;
47124
+ }
47125
+ if (getEnv7("UPSTASH_REDIS_REST_URL")) {
46717
47126
  return "upstash";
46718
47127
  }
46719
- if (getEnv6("REDIS_URL") || getEnv6("REDIS_HOST")) {
47128
+ if (getEnv7("REDIS_URL") || getEnv7("REDIS_HOST")) {
46720
47129
  return "redis";
46721
47130
  }
46722
47131
  return "memory";
@@ -46802,7 +47211,8 @@ async function createElicitationStore(options = {}) {
46802
47211
  });
46803
47212
  }
46804
47213
  }
46805
- if (finalStorageConfig?.type === "vercel-kv" || getEnv6("KV_REST_API_URL")) {
47214
+ const explicitBackend = Boolean(storageConfig || redis);
47215
+ if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && getEnv7("KV_REST_API_URL")) {
46806
47216
  throw new ElicitationNotSupportedError(
46807
47217
  "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."
46808
47218
  );
@@ -46823,7 +47233,7 @@ async function createElicitationStore(options = {}) {
46823
47233
  );
46824
47234
  }
46825
47235
  let store = new StorageElicitationStore(storage, logger);
46826
- const type = detectStorageType2(storage);
47236
+ const type = detectStorageType2(storage, finalStorageConfig?.type);
46827
47237
  const encryptionEnabled = encryption?.enabled ?? "auto";
46828
47238
  const encryptionSecret = encryption?.secret;
46829
47239
  const hasSecret = encryptionSecret || isElicitationEncryptionAvailable();
@@ -49635,6 +50045,7 @@ var init_redis_job_definition_store = __esm({
49635
50045
  });
49636
50046
 
49637
50047
  // libs/sdk/src/job/store/job-definition-store.factory.ts
50048
+ import { attachRedisErrorListener } from "@frontmcp/utils";
49638
50049
  function createJobDefinitionStore(options, logger) {
49639
50050
  const keyPrefix = options?.keyPrefix ?? "mcp:jobs:def:";
49640
50051
  const effectiveLogger = logger ?? noopLogger2;
@@ -49646,6 +50057,7 @@ function createJobDefinitionStore(options, logger) {
49646
50057
  host: options.redis.host ?? "localhost",
49647
50058
  port: options.redis.port ?? 6379
49648
50059
  });
50060
+ attachRedisErrorListener(client, { label: "JobDefinitionStore", logger: effectiveLogger });
49649
50061
  return {
49650
50062
  // Factory-created client lifetime is bound to the store, so the store
49651
50063
  // must close it on dispose() — pass ownsClient: true.
@@ -49846,6 +50258,7 @@ var init_redis_job_state_store = __esm({
49846
50258
  });
49847
50259
 
49848
50260
  // libs/sdk/src/job/store/job-state-store.factory.ts
50261
+ import { attachRedisErrorListener as attachRedisErrorListener2 } from "@frontmcp/utils";
49849
50262
  function createJobStateStore(options, logger) {
49850
50263
  const keyPrefix = options?.keyPrefix ?? "mcp:jobs:";
49851
50264
  const ttlSeconds = options?.ttlSeconds ?? 86400;
@@ -49857,6 +50270,7 @@ function createJobStateStore(options, logger) {
49857
50270
  host: options.redis.host ?? "localhost",
49858
50271
  port: options.redis.port ?? 6379
49859
50272
  });
50273
+ attachRedisErrorListener2(client, { label: "JobStateStore", logger });
49860
50274
  return {
49861
50275
  store: new RedisJobStateStore2(client, logger, keyPrefix, ttlSeconds),
49862
50276
  type: "redis"
@@ -52272,6 +52686,7 @@ var init_skill_http_cache = __esm({
52272
52686
  });
52273
52687
 
52274
52688
  // libs/sdk/src/skill/cache/skill-http-cache.factory.ts
52689
+ import { attachRedisErrorListener as attachRedisErrorListener3 } from "@frontmcp/utils";
52275
52690
  function hasRedisProvider(redis) {
52276
52691
  if (!redis?.provider) return false;
52277
52692
  const provider = redis.provider;
@@ -52316,6 +52731,7 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
52316
52731
  enableReadyCheck: false,
52317
52732
  maxRetriesPerRequest: 3
52318
52733
  });
52734
+ attachRedisErrorListener3(client, { label: "SkillHttpCache" });
52319
52735
  await client.connect();
52320
52736
  return new RedisSkillHttpCache({
52321
52737
  getClient: async () => client,
@@ -56131,6 +56547,51 @@ var init_skill_scope_helper = __esm({
56131
56547
  }
56132
56548
  });
56133
56549
 
56550
+ // libs/sdk/src/tool/ui/ui-option-warnings.ts
56551
+ function describeIgnoredUiOptions(toolName, ui) {
56552
+ if (!ui) return [];
56553
+ const messages = [];
56554
+ const ignored = IGNORED_UI_OPTIONS.filter((key) => ui[key] !== void 0);
56555
+ if (ignored.length > 0) {
56556
+ messages.push(
56557
+ `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.`
56558
+ );
56559
+ }
56560
+ const mode = ui["servingMode"];
56561
+ if (UNSUPPORTED_SERVING_MODES.includes(mode)) {
56562
+ messages.push(
56563
+ `Tool "${toolName}": \`ui.servingMode: '${String(mode)}'\` is not implemented; the widget is served inline, as with \`servingMode: 'inline'\`.`
56564
+ );
56565
+ }
56566
+ if (mode === "hybrid") {
56567
+ messages.push(
56568
+ `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.`
56569
+ );
56570
+ }
56571
+ return messages;
56572
+ }
56573
+ var IGNORED_UI_OPTIONS, UNSUPPORTED_SERVING_MODES;
56574
+ var init_ui_option_warnings = __esm({
56575
+ "libs/sdk/src/tool/ui/ui-option-warnings.ts"() {
56576
+ "use strict";
56577
+ IGNORED_UI_OPTIONS = [
56578
+ "widgetDescription",
56579
+ "widgetAccessible",
56580
+ "displayMode",
56581
+ "prefersBorder",
56582
+ "sandboxDomain",
56583
+ "contentSecurity",
56584
+ "hydrate",
56585
+ "runtimeOptions",
56586
+ "mdxComponents",
56587
+ "bundlingMode",
56588
+ "uiType",
56589
+ "htmlResponsePrefix"
56590
+ ];
56591
+ UNSUPPORTED_SERVING_MODES = ["direct-url", "custom-url"];
56592
+ }
56593
+ });
56594
+
56134
56595
  // libs/sdk/src/transport/bus/redis-transport-bus.ts
56135
56596
  var DEFAULT_BUS_PREFIX, DEFAULT_BUS_TTL_SECONDS, REVOKE_LUA, RedisTransportBus;
56136
56597
  var init_redis_transport_bus = __esm({
@@ -56260,6 +56721,7 @@ var redis_event_store_exports = {};
56260
56721
  __export(redis_event_store_exports, {
56261
56722
  RedisEventStore: () => RedisEventStore
56262
56723
  });
56724
+ import { attachRedisErrorListener as attachRedisErrorListener4 } from "@frontmcp/utils";
56263
56725
  var RedisEventStore;
56264
56726
  var init_redis_event_store = __esm({
56265
56727
  "libs/sdk/src/transport/event-stores/redis.event-store.ts"() {
@@ -56307,6 +56769,9 @@ var init_redis_event_store = __esm({
56307
56769
  enableReadyCheck: false,
56308
56770
  maxRetriesPerRequest: 3
56309
56771
  });
56772
+ attachRedisErrorListener4(this.client, {
56773
+ label: "RedisEventStore"
56774
+ });
56310
56775
  await this.client.connect();
56311
56776
  }
56312
56777
  return this.client;
@@ -56959,7 +57424,7 @@ var init_handle_mcp_20260728_flow = __esm({
56959
57424
  const sink = new RequestNotificationSink(logLevel, progressToken);
56960
57425
  const clientInfo = toClientInfo(meta[MCP_20260728_META5.clientInfo]);
56961
57426
  if (clientInfo) {
56962
- const platformType = detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
57427
+ const platformType = detectPlatformFromCapabilities(clientCapabilities) ?? detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
56963
57428
  this.tryGetContext()?.setClientInfo(clientInfo, platformType);
56964
57429
  }
56965
57430
  const dispatchOptions = {
@@ -57057,6 +57522,28 @@ var init_handle_mcp_20260728_flow = __esm({
57057
57522
  }
57058
57523
  });
57059
57524
 
57525
+ // libs/sdk/src/ha/ha-headers.ts
57526
+ import { buildSetCookie, getMachineId as getMachineId3, getRuntimeContext as getRuntimeContext13 } from "@frontmcp/utils";
57527
+ function applyMachineIdHeader(response) {
57528
+ if (getRuntimeContext13().deployment !== "distributed") return false;
57529
+ response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, getMachineId3());
57530
+ return true;
57531
+ }
57532
+ function applyNodeAffinity(response, request) {
57533
+ if (!applyMachineIdHeader(response)) return;
57534
+ const cookie = buildSetCookie({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: getMachineId3() }, request);
57535
+ if (!cookie) return;
57536
+ const existing = response.getHeader("Set-Cookie");
57537
+ const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57538
+ response.setHeader("Set-Cookie", [...existingArr, cookie]);
57539
+ }
57540
+ var init_ha_headers = __esm({
57541
+ "libs/sdk/src/ha/ha-headers.ts"() {
57542
+ "use strict";
57543
+ init_ha_constants();
57544
+ }
57545
+ });
57546
+
57060
57547
  // libs/sdk/src/skill/skill-mode.utils.ts
57061
57548
  function detectSkillsOnlyMode(query) {
57062
57549
  if (!query) return false;
@@ -57077,7 +57564,7 @@ var init_skill_mode_utils = __esm({
57077
57564
 
57078
57565
  // libs/sdk/src/transport/flows/handle.sse.flow.ts
57079
57566
  import { z as z103 } from "@frontmcp/lazy-zod";
57080
- import { buildSetCookie, getMachineId as getMachineId3, getRuntimeContext as getRuntimeContext13 } from "@frontmcp/utils";
57567
+ import { getMachineId as getMachineId4, getRuntimeContext as getRuntimeContext14 } from "@frontmcp/utils";
57081
57568
  function getQuerySessionId2(urlPath) {
57082
57569
  if (!urlPath) return void 0;
57083
57570
  try {
@@ -57087,6 +57574,30 @@ function getQuerySessionId2(urlPath) {
57087
57574
  return void 0;
57088
57575
  }
57089
57576
  }
57577
+ function resolveLegacySseSession(params) {
57578
+ const { rawHeader, requestUrl, authorizationSession, createSession } = params;
57579
+ const rawMcpSessionHeader = typeof rawHeader === "string" ? rawHeader : void 0;
57580
+ const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
57581
+ const querySessionId = getQuerySessionId2(requestUrl);
57582
+ const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
57583
+ if (rawHeader !== void 0 && !mcpSessionHeader) {
57584
+ return { responded404: true, createdNew: false };
57585
+ }
57586
+ if (querySessionId !== void 0 && !validatedQuerySessionId) {
57587
+ return { responded404: true, createdNew: false };
57588
+ }
57589
+ const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
57590
+ if (effectiveSessionId) {
57591
+ if (authorizationSession?.id === effectiveSessionId) {
57592
+ return { session: authorizationSession, createdNew: false, responded404: false };
57593
+ }
57594
+ return { responded404: true, createdNew: false };
57595
+ }
57596
+ if (authorizationSession) {
57597
+ return { session: authorizationSession, createdNew: false, responded404: false };
57598
+ }
57599
+ return { session: createSession(), createdNew: true, responded404: false };
57600
+ }
57090
57601
  var plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
57091
57602
  var init_handle_sse_flow = __esm({
57092
57603
  "libs/sdk/src/transport/flows/handle.sse.flow.ts"() {
@@ -57094,7 +57605,7 @@ var init_handle_sse_flow = __esm({
57094
57605
  init_session_id_utils();
57095
57606
  init_common();
57096
57607
  init_errors();
57097
- init_ha_constants();
57608
+ init_ha_headers();
57098
57609
  init_skill_mode_utils();
57099
57610
  plan38 = {
57100
57611
  pre: ["parseInput", "router"],
@@ -57126,39 +57637,26 @@ var init_handle_sse_flow = __esm({
57126
57637
  const { request } = this.rawInput;
57127
57638
  const authorization = request[ServerRequestTokens.auth];
57128
57639
  const { token } = authorization;
57129
- const raw = request.headers?.["mcp-session-id"];
57130
- const rawMcpSessionHeader = typeof raw === "string" ? raw : void 0;
57131
- const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
57132
57640
  const anyReq = request;
57133
- const querySessionId = getQuerySessionId2(anyReq.url ?? anyReq.path);
57134
- const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
57135
- if (raw !== void 0 && !mcpSessionHeader) {
57136
- this.respond(httpRespond.sessionNotFound("invalid session id"));
57137
- return;
57138
- }
57139
- if (querySessionId !== void 0 && !validatedQuerySessionId) {
57641
+ const resolution = resolveLegacySseSession({
57642
+ rawHeader: request.headers?.["mcp-session-id"],
57643
+ requestUrl: anyReq.url ?? anyReq.path,
57644
+ authorizationSession: authorization.session,
57645
+ createSession: () => {
57646
+ const query = request.query;
57647
+ const skillsOnlyMode = detectSkillsOnlyMode(query);
57648
+ return createSessionId("legacy-sse", token, {
57649
+ userAgent: request.headers?.["user-agent"],
57650
+ platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
57651
+ skillsOnlyMode
57652
+ });
57653
+ }
57654
+ });
57655
+ if (resolution.responded404 || !resolution.session) {
57140
57656
  this.respond(httpRespond.sessionNotFound("invalid session id"));
57141
57657
  return;
57142
57658
  }
57143
- const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
57144
- let session;
57145
- if (effectiveSessionId) {
57146
- if (authorization.session?.id === effectiveSessionId) {
57147
- session = authorization.session;
57148
- } else {
57149
- session = { id: effectiveSessionId };
57150
- }
57151
- } else if (authorization.session) {
57152
- session = authorization.session;
57153
- } else {
57154
- const query = request.query;
57155
- const skillsOnlyMode = detectSkillsOnlyMode(query);
57156
- session = createSessionId("legacy-sse", token, {
57157
- userAgent: request.headers?.["user-agent"],
57158
- platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
57159
- skillsOnlyMode
57160
- });
57161
- }
57659
+ const session = resolution.session;
57162
57660
  this.state.set(stateSchema38.parse({ token, session }));
57163
57661
  }
57164
57662
  async router() {
@@ -57181,16 +57679,7 @@ var init_handle_sse_flow = __esm({
57181
57679
  const { request, response } = this.rawInput;
57182
57680
  const { token, session } = this.state.required;
57183
57681
  const transport = await transportService.createTransporter("sse", token, session.id, response);
57184
- if (getRuntimeContext13().deployment === "distributed") {
57185
- const nodeId = getMachineId3();
57186
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57187
- const cookie = buildSetCookie({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57188
- if (cookie) {
57189
- const existing = response.getHeader("Set-Cookie");
57190
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57191
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57192
- }
57193
- }
57682
+ applyNodeAffinity(response, request);
57194
57683
  await transport.initialize(request, response);
57195
57684
  this.handled();
57196
57685
  }
@@ -57206,12 +57695,12 @@ var init_handle_sse_flow = __esm({
57206
57695
  const { request, response } = this.rawInput;
57207
57696
  const { token, session } = this.state.required;
57208
57697
  const transport = await transportService.getTransporter("sse", token, session.id);
57209
- if (!transport && getRuntimeContext13().deployment === "distributed") {
57698
+ if (!transport && getRuntimeContext14().deployment === "distributed") {
57210
57699
  const storedSession = await transportService.getStoredSession("sse", token, session.id);
57211
57700
  if (storedSession) {
57212
57701
  const haManager = this.scope.haManager;
57213
57702
  const relay = haManager?.getRelay();
57214
- if (relay && storedSession.session.nodeId && storedSession.session.nodeId !== getMachineId3()) {
57703
+ if (relay && storedSession.session.nodeId && storedSession.session.nodeId !== getMachineId4()) {
57215
57704
  const isAlive2 = await haManager.isNodeAlive(storedSession.session.nodeId);
57216
57705
  if (isAlive2) {
57217
57706
  try {
@@ -57306,9 +57795,10 @@ var init_handle_stateless_http_flow = __esm({
57306
57795
  "use strict";
57307
57796
  init_common();
57308
57797
  init_errors();
57798
+ init_ha_headers();
57309
57799
  init_transport_types();
57310
57800
  plan39 = {
57311
- pre: ["parseInput", "router"],
57801
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57312
57802
  execute: ["handleRequest"],
57313
57803
  post: [],
57314
57804
  finalize: ["cleanup"]
@@ -57322,6 +57812,9 @@ var init_handle_stateless_http_flow = __esm({
57322
57812
  ({ Stage: Stage40 } = FlowHooksOf(name40));
57323
57813
  HandleStatelessHttpFlow = class extends FlowBase {
57324
57814
  name = name40;
57815
+ async applyNodeHeaders() {
57816
+ applyMachineIdHeader(this.rawInput.response);
57817
+ }
57325
57818
  async parseInput() {
57326
57819
  const { request } = this.rawInput;
57327
57820
  const logger = this.scope.logger.child("HandleStatelessHttpFlow");
@@ -57383,6 +57876,9 @@ var init_handle_stateless_http_flow = __esm({
57383
57876
  this.handled();
57384
57877
  }
57385
57878
  };
57879
+ __decorateClass([
57880
+ Stage40("applyNodeHeaders")
57881
+ ], HandleStatelessHttpFlow.prototype, "applyNodeHeaders", 1);
57386
57882
  __decorateClass([
57387
57883
  Stage40("parseInput")
57388
57884
  ], HandleStatelessHttpFlow.prototype, "parseInput", 1);
@@ -57762,7 +58258,6 @@ var init_ext_apps2 = __esm({
57762
58258
  // libs/sdk/src/transport/flows/handle.streamable-http.flow.ts
57763
58259
  import { z as z105 } from "@frontmcp/lazy-zod";
57764
58260
  import { CallToolResultSchema as CallToolResultSchema4, ElicitResultSchema, RequestSchema as RequestSchema2 } from "@frontmcp/protocol";
57765
- import { buildSetCookie as buildSetCookie2, getMachineId as getMachineId4, getRuntimeContext as getRuntimeContext14 } from "@frontmcp/utils";
57766
58261
  function resolveStreamableHttpSession(params) {
57767
58262
  const { rawHeader, authorizationSession, createSession } = params;
57768
58263
  const rawMcpSessionHeader = typeof rawHeader === "string" ? rawHeader : void 0;
@@ -57774,7 +58269,7 @@ function resolveStreamableHttpSession(params) {
57774
58269
  if (authorizationSession?.id === mcpSessionHeader) {
57775
58270
  return { session: authorizationSession, createdNew: false, responded404: false };
57776
58271
  }
57777
- return { session: { id: mcpSessionHeader }, createdNew: false, responded404: false };
58272
+ return { responded404: true, createdNew: false };
57778
58273
  }
57779
58274
  if (authorizationSession) {
57780
58275
  return { session: authorizationSession, createdNew: false, responded404: false };
@@ -57858,11 +58353,11 @@ var init_handle_streamable_http_flow = __esm({
57858
58353
  init_common();
57859
58354
  init_errors();
57860
58355
  init_ext_apps2();
57861
- init_ha_constants();
58356
+ init_ha_headers();
57862
58357
  init_skill_mode_utils();
57863
58358
  init_mcp_surface();
57864
58359
  plan40 = {
57865
- pre: ["parseInput", "router"],
58360
+ pre: ["applyNodeHeaders", "parseInput", "router"],
57866
58361
  execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
57867
58362
  post: [],
57868
58363
  finalize: ["cleanup"]
@@ -57888,6 +58383,9 @@ var init_handle_streamable_http_flow = __esm({
57888
58383
  ({ Stage: Stage41 } = FlowHooksOf(name41));
57889
58384
  HandleStreamableHttpFlow = class extends FlowBase {
57890
58385
  name = name41;
58386
+ async applyNodeHeaders() {
58387
+ applyMachineIdHeader(this.rawInput.response);
58388
+ }
57891
58389
  async parseInput() {
57892
58390
  const { request } = this.rawInput;
57893
58391
  const authorization = request[ServerRequestTokens.auth];
@@ -57919,7 +58417,7 @@ var init_handle_streamable_http_flow = __esm({
57919
58417
  });
57920
58418
  }
57921
58419
  if (sessionResolution.responded404 || !sessionResolution.session) {
57922
- logger.warn("parseInput: invalid mcp-session-id header");
58420
+ logger.warn("parseInput: mcp-session-id is not a session this server verified");
57923
58421
  this.respond(httpRespond.sessionNotFound("invalid session id"));
57924
58422
  return;
57925
58423
  }
@@ -57986,16 +58484,7 @@ var init_handle_streamable_http_flow = __esm({
57986
58484
  return;
57987
58485
  }
57988
58486
  }
57989
- if (getRuntimeContext14().deployment === "distributed") {
57990
- const nodeId = getMachineId4();
57991
- response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
57992
- const cookie = buildSetCookie2({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
57993
- if (cookie) {
57994
- const existing = response.getHeader("Set-Cookie");
57995
- const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
57996
- response.setHeader("Set-Cookie", [...existingArr, cookie]);
57997
- }
57998
- }
58487
+ applyNodeAffinity(response, request);
57999
58488
  logger.info("onInitialize: transport created, calling initialize");
58000
58489
  await transport.initialize(request, response);
58001
58490
  logger.info("onInitialize: completed successfully");
@@ -58178,6 +58667,17 @@ var init_handle_streamable_http_flow = __esm({
58178
58667
  const logger = this.scopeLogger.child("handle:streamable-http:onExtApps");
58179
58668
  const { request, response } = this.rawInput;
58180
58669
  const { token, session } = this.state.required;
58670
+ if (this.scope.metadata.extApps?.enabled === false) {
58671
+ const rpc = request.body;
58672
+ logger.verbose("onExtApps: ext-apps disabled by config", { method: rpc?.method });
58673
+ response.status(200).json({
58674
+ jsonrpc: "2.0",
58675
+ id: rpc?.id ?? null,
58676
+ error: { code: -32601, message: `Method not found: ${rpc?.method}` }
58677
+ });
58678
+ this.handled();
58679
+ return;
58680
+ }
58181
58681
  logger.info("onExtApps: starting", {
58182
58682
  sessionId: session.id?.slice(0, 20),
58183
58683
  method: request.body?.method
@@ -58229,8 +58729,20 @@ var init_handle_streamable_http_flow = __esm({
58229
58729
  const hostCapabilities = {
58230
58730
  serverToolProxy: configuredCapabilities?.serverToolProxy ?? true,
58231
58731
  logging: configuredCapabilities?.logging ?? true,
58232
- ...configuredCapabilities
58732
+ ...configuredCapabilities,
58733
+ // Only advertise what this host can serve: the context below provides callTool and nothing else
58734
+ openLink: false,
58735
+ modelContextUpdate: false,
58736
+ widgetTools: false
58233
58737
  };
58738
+ const unsupported = ["openLink", "modelContextUpdate", "widgetTools"].filter(
58739
+ (key) => configuredCapabilities?.[key] === true
58740
+ );
58741
+ if (unsupported.length > 0) {
58742
+ logger.warn(
58743
+ `onExtApps: extApps.hostCapabilities ${unsupported.join(", ")} are not supported yet and are not advertised`
58744
+ );
58745
+ }
58234
58746
  const handler = createExtAppsMessageHandler({
58235
58747
  context: {
58236
58748
  sessionId: session.id,
@@ -58264,6 +58776,9 @@ var init_handle_streamable_http_flow = __esm({
58264
58776
  this.handled();
58265
58777
  }
58266
58778
  };
58779
+ __decorateClass([
58780
+ Stage41("applyNodeHeaders")
58781
+ ], HandleStreamableHttpFlow.prototype, "applyNodeHeaders", 1);
58267
58782
  __decorateClass([
58268
58783
  Stage41("parseInput")
58269
58784
  ], HandleStreamableHttpFlow.prototype, "parseInput", 1);
@@ -59856,7 +60371,7 @@ var init_transport_remote = __esm({
59856
60371
 
59857
60372
  // libs/sdk/src/transport/transport.registry.ts
59858
60373
  import { getMachineId as getMachineId5, sha256Hex as sha256Hex12 } from "@frontmcp/utils";
59859
- var TransportService;
60374
+ var SESSION_STORE_RETRY_BASE_MS, SESSION_STORE_RETRY_MAX_MS, TransportService;
59860
60375
  var init_transport_registry = __esm({
59861
60376
  "libs/sdk/src/transport/transport.registry.ts"() {
59862
60377
  "use strict";
@@ -59869,6 +60384,8 @@ var init_transport_registry = __esm({
59869
60384
  init_transport_local();
59870
60385
  init_transport_remote();
59871
60386
  init_transport_types();
60387
+ SESSION_STORE_RETRY_BASE_MS = 1e3;
60388
+ SESSION_STORE_RETRY_MAX_MS = 3e4;
59872
60389
  TransportService = class {
59873
60390
  ready;
59874
60391
  byType = /* @__PURE__ */ new Map();
@@ -59925,8 +60442,28 @@ var init_transport_registry = __esm({
59925
60442
  * Returns undefined if persistence is disabled or not configured.
59926
60443
  */
59927
60444
  getDefaultTtlMs() {
60445
+ if (this.resolvedTtlMs !== void 0) return this.resolvedTtlMs;
59928
60446
  return typeof this.persistenceConfig === "object" ? this.persistenceConfig?.defaultTtlMs : void 0;
59929
60447
  }
60448
+ /**
60449
+ * TTL resolved from `persistence.defaultTtlMs`, then `persistence.redis.defaultTtlMs`, then 1 hour.
60450
+ * Set only when a persistence backend is configured.
60451
+ */
60452
+ resolvedTtlMs;
60453
+ /** Store config kept after a successful create so a failed ping can be retried. */
60454
+ storeRetryConfig;
60455
+ storeRetryTimer;
60456
+ storeRetryAttempt = 0;
60457
+ destroyed = false;
60458
+ /** Last time a request refreshed the stored session's TTL, by session id. */
60459
+ lastTtlRefreshAt = /* @__PURE__ */ new Map();
60460
+ /**
60461
+ * Redis key prefix under which session records are stored.
60462
+ * The session store appends `session:` to the transport key prefix.
60463
+ */
60464
+ getSessionKeyPrefix() {
60465
+ return `${this.transportKeyPrefix}session:`;
60466
+ }
59930
60467
  constructor(scope, persistenceConfig, bus) {
59931
60468
  this.scope = scope;
59932
60469
  this.persistenceConfig = persistenceConfig;
@@ -59937,10 +60474,11 @@ var init_transport_registry = __esm({
59937
60474
  this.backendKind = "sqlite";
59938
60475
  const sqliteConfig = persistenceConfig.sqlite;
59939
60476
  this.transportKeyPrefix = "mcp:transport:";
60477
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? 36e5;
59940
60478
  this.pendingStoreConfig = {
59941
60479
  sqlite: sqliteConfig,
59942
60480
  keyPrefix: this.transportKeyPrefix,
59943
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
60481
+ defaultTtlMs: this.resolvedTtlMs
59944
60482
  };
59945
60483
  this.scope.logger.info("[TransportService] sqlite session store will be initialized for transport persistence");
59946
60484
  } else if (persistenceConfig !== false && persistenceConfig?.redis) {
@@ -59949,43 +60487,23 @@ var init_transport_registry = __esm({
59949
60487
  const providerType = "provider" in redisConfig ? redisConfig.provider : "redis";
59950
60488
  this.backendKind = providerType === "vercel-kv" ? "vercel-kv" : "redis";
59951
60489
  this.transportKeyPrefix = redisConfig.keyPrefix ?? "mcp:transport:";
60490
+ this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? redisConfig.defaultTtlMs ?? 36e5;
59952
60491
  this.pendingStoreConfig = {
59953
60492
  ...redisConfig,
59954
60493
  keyPrefix: this.transportKeyPrefix,
59955
- defaultTtlMs: persistenceConfig.defaultTtlMs ?? 36e5
59956
- // 1 hour default
60494
+ defaultTtlMs: this.resolvedTtlMs
59957
60495
  };
59958
60496
  this.scope.logger.info(
59959
60497
  `[TransportService] ${providerType} session store will be initialized for transport persistence`
59960
60498
  );
59961
60499
  }
60500
+ this.storeRetryConfig = this.pendingStoreConfig;
59962
60501
  this.ready = this.initialize();
59963
60502
  }
59964
60503
  async initialize() {
59965
60504
  if (this.pendingStoreConfig) {
59966
- try {
59967
- const store = await createSessionStore(this.pendingStoreConfig, this.scope.logger.child("SessionStore"));
59968
- this.sessionStore = store;
59969
- this.pendingStoreConfig = void 0;
59970
- } catch (error) {
59971
- const err = error;
59972
- this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
59973
- error: err.message,
59974
- cause: err.cause?.message
59975
- });
59976
- }
59977
- }
59978
- if (this.sessionStore) {
59979
- const isConnected = this.sessionStore.ping ? await this.sessionStore.ping() : true;
59980
- if (!isConnected) {
59981
- const providerType = this.backendKind ?? "redis";
59982
- this.scope.logger.error(
59983
- `[TransportService] Failed to connect to ${providerType} - session persistence disabled`
59984
- );
59985
- await this.teardownSessionStore().catch(() => void 0);
59986
- } else {
59987
- this.scope.logger.info("[TransportService] Session store connection validated successfully");
59988
- }
60505
+ const connected = await this.connectSessionStore();
60506
+ if (!connected && this.backendKind !== "sqlite") this.scheduleSessionStoreRetry();
59989
60507
  }
59990
60508
  await this.scope.registryFlows(
59991
60509
  HandleStreamableHttpFlow,
@@ -59994,7 +60512,70 @@ var init_transport_registry = __esm({
59994
60512
  HandleMcp20260728Flow
59995
60513
  );
59996
60514
  }
60515
+ /**
60516
+ * Create the session store and validate the connection.
60517
+ * Returns true when the store is usable; on failure the store is left undefined.
60518
+ */
60519
+ async connectSessionStore() {
60520
+ const storeConfig = this.pendingStoreConfig ?? this.storeRetryConfig;
60521
+ if (!storeConfig) return false;
60522
+ try {
60523
+ const store = await createSessionStore(storeConfig, this.scope.logger.child("SessionStore"));
60524
+ this.sessionStore = store;
60525
+ this.pendingStoreConfig = void 0;
60526
+ } catch (error) {
60527
+ const err = error;
60528
+ this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
60529
+ error: err.message,
60530
+ cause: err.cause?.message
60531
+ });
60532
+ return false;
60533
+ }
60534
+ const isConnected = this.sessionStore?.ping ? await this.sessionStore.ping() : true;
60535
+ if (!isConnected) {
60536
+ const providerType = this.backendKind ?? "redis";
60537
+ this.scope.logger.error(`[TransportService] Failed to connect to ${providerType} - session persistence disabled`);
60538
+ await this.teardownSessionStore().catch(() => void 0);
60539
+ return false;
60540
+ }
60541
+ this.scope.logger.info("[TransportService] Session store connection validated successfully");
60542
+ return true;
60543
+ }
60544
+ /**
60545
+ * Retry the session store with exponential backoff (1s doubling to 30s) so a Redis that
60546
+ * was down at startup is picked up when it comes back, and `/readyz` turns healthy.
60547
+ */
60548
+ scheduleSessionStoreRetry() {
60549
+ if (this.destroyed || this.storeRetryTimer) return;
60550
+ const delay = Math.min(SESSION_STORE_RETRY_MAX_MS, SESSION_STORE_RETRY_BASE_MS * 2 ** this.storeRetryAttempt);
60551
+ this.storeRetryAttempt++;
60552
+ this.storeRetryTimer = setTimeout(() => {
60553
+ this.storeRetryTimer = void 0;
60554
+ void this.retrySessionStore();
60555
+ }, delay);
60556
+ this.storeRetryTimer.unref?.();
60557
+ }
60558
+ async retrySessionStore() {
60559
+ if (this.destroyed || this.sessionStore) return;
60560
+ const connected = await this.connectSessionStore().catch(() => false);
60561
+ if (this.destroyed) {
60562
+ await this.teardownSessionStore().catch(() => void 0);
60563
+ return;
60564
+ }
60565
+ if (connected) {
60566
+ this.storeRetryAttempt = 0;
60567
+ this.scope.logger.info("[TransportService] Session store recovered after startup failure");
60568
+ return;
60569
+ }
60570
+ this.scheduleSessionStoreRetry();
60571
+ }
59997
60572
  async destroy() {
60573
+ this.destroyed = true;
60574
+ if (this.storeRetryTimer) {
60575
+ clearTimeout(this.storeRetryTimer);
60576
+ this.storeRetryTimer = void 0;
60577
+ }
60578
+ this.lastTtlRefreshAt.clear();
59998
60579
  await this.teardownSessionStore();
59999
60580
  }
60000
60581
  /**
@@ -60055,10 +60636,33 @@ var init_transport_registry = __esm({
60055
60636
  getBackendKind() {
60056
60637
  return this.backendKind;
60057
60638
  }
60639
+ /**
60640
+ * Slide the stored session's TTL while this instance is serving it. Redis and Vercel KV
60641
+ * session stores extend the TTL on read, so a plain `get` is enough. Throttled per session
60642
+ * to a quarter of the TTL, fire-and-forget.
60643
+ */
60644
+ refreshStoredSessionTtl(sessionId) {
60645
+ const store = this.sessionStore;
60646
+ const ttlMs = this.getDefaultTtlMs();
60647
+ if (!store || !ttlMs || this.backendKind !== "redis" && this.backendKind !== "vercel-kv") return;
60648
+ const now = Date.now();
60649
+ const last = this.lastTtlRefreshAt.get(sessionId);
60650
+ if (last !== void 0 && now - last < Math.max(1e3, Math.floor(ttlMs / 4))) return;
60651
+ this.lastTtlRefreshAt.set(sessionId, now);
60652
+ store.get(sessionId).catch((err) => {
60653
+ this.scope.logger.warn("[TransportService] Failed to refresh session TTL", {
60654
+ sessionId: sessionId.slice(0, 20),
60655
+ error: err instanceof Error ? err.message : String(err)
60656
+ });
60657
+ });
60658
+ }
60058
60659
  async getTransporter(type, token, sessionId) {
60059
60660
  const key = this.keyOf(type, token, sessionId);
60060
60661
  const local = this.lookupLocal(key);
60061
- if (local) return local;
60662
+ if (local) {
60663
+ this.refreshStoredSessionTtl(sessionId);
60664
+ return local;
60665
+ }
60062
60666
  if (this.distributed && this.bus) {
60063
60667
  const location = await this.bus.lookup(key);
60064
60668
  if (location) {
@@ -60153,7 +60757,7 @@ var init_transport_registry = __esm({
60153
60757
  });
60154
60758
  const currentNodeId = getMachineId5();
60155
60759
  if (this.scope.haManager && storedSession.session.nodeId && storedSession.session.nodeId !== currentNodeId) {
60156
- const sessionKey = `${this.transportKeyPrefix}${sessionId}`;
60760
+ const sessionKey = `${this.getSessionKeyPrefix()}${sessionId}`;
60157
60761
  const result = await this.scope.haManager.attemptTakeover(sessionKey, storedSession.session.nodeId);
60158
60762
  if (!result.claimed) {
60159
60763
  this.scope.logger.debug("[HA] Session already claimed by another pod", { sessionId: sessionId.slice(0, 20) });
@@ -60486,6 +61090,7 @@ var init_transport_registry = __esm({
60486
61090
  }
60487
61091
  }
60488
61092
  evictLocal(key) {
61093
+ this.lastTtlRefreshAt.delete(key.sessionId);
60489
61094
  const typeBucket = this.byType.get(key.type);
60490
61095
  if (!typeBucket) return;
60491
61096
  const tokenBucket = typeBucket.get(key.tokenHash);
@@ -61442,7 +62047,7 @@ var init_http_request_flow = __esm({
61442
62047
  // libs/sdk/src/scope/scope.instance.ts
61443
62048
  import "reflect-metadata";
61444
62049
  import { createGuardManager } from "@frontmcp/guard";
61445
- import { getEnvFlag, getMachineId as getMachineId6, getRuntimeContext as getRuntimeContext15, isEdgeRuntime } from "@frontmcp/utils";
62050
+ import { createRedisClient, getEnvFlag, getMachineId as getMachineId6, getRuntimeContext as getRuntimeContext15, isEdgeRuntime } from "@frontmcp/utils";
61446
62051
  var SCOPE_NON_DISPATCHABLE_FLOWS, Scope;
61447
62052
  var init_scope_instance = __esm({
61448
62053
  "libs/sdk/src/scope/scope.instance.ts"() {
@@ -61487,8 +62092,10 @@ var init_scope_instance = __esm({
61487
62092
  init_tool_registry();
61488
62093
  init_tool_utils();
61489
62094
  init_ui();
62095
+ init_ui_option_warnings();
61490
62096
  init_bus();
61491
62097
  init_event_stores();
62098
+ init_request_state();
61492
62099
  init_transport_registry();
61493
62100
  init_http_ip_filter_flow();
61494
62101
  init_http_request_flow();
@@ -61512,6 +62119,8 @@ var init_scope_instance = __esm({
61512
62119
  transportService;
61513
62120
  // TODO: migrate transport service to transport.registry
61514
62121
  notificationService;
62122
+ /** ioredis client owned by the HA manager and transport bus; closed in shutdown(). */
62123
+ haRedisClient;
61515
62124
  toolUIRegistry;
61516
62125
  authUiRegistry;
61517
62126
  entryPath;
@@ -61592,7 +62201,7 @@ var init_scope_instance = __esm({
61592
62201
  const isDistributed = getRuntimeContext15().deployment === "distributed";
61593
62202
  if (isDistributed && this.metadata.redis && !this.cliMode) {
61594
62203
  try {
61595
- const haRedis = this.metadata.redis;
62204
+ const haRedis = this.createHaRedisClient();
61596
62205
  this.haManager = HaManager.create({
61597
62206
  redis: haRedis,
61598
62207
  nodeId: getMachineId6(),
@@ -61602,11 +62211,13 @@ var init_scope_instance = __esm({
61602
62211
  this.logger.info("[HA] Manager started for distributed deployment");
61603
62212
  } catch (err) {
61604
62213
  this.logger.warn("[HA] Failed to start HA manager \u2014 running without HA", { error: err });
62214
+ this.haManager = void 0;
62215
+ await this.closeHaRedisClient();
61605
62216
  }
61606
62217
  }
61607
62218
  let transportBus;
61608
- if (this.haManager && isDistributed && this.metadata.redis) {
61609
- transportBus = new RedisTransportBus(this.metadata.redis, getMachineId6(), {
62219
+ if (this.haManager && this.haRedisClient && isDistributed) {
62220
+ transportBus = new RedisTransportBus(this.haRedisClient, getMachineId6(), {
61610
62221
  logger: this.logger
61611
62222
  });
61612
62223
  this.logger.info("[HA] Transport bus created for distributed session routing");
@@ -61617,11 +62228,10 @@ var init_scope_instance = __esm({
61617
62228
  effectivePersistence = { sqlite: resolvedTopLevelSqlite };
61618
62229
  }
61619
62230
  this.transportService = new TransportService(this, effectivePersistence, transportBus);
61620
- if (this.haManager && isDistributed) {
61621
- const persistCfg = transportConfig?.persistence;
61622
- const resolvedPrefix = persistCfg && typeof persistCfg === "object" && persistCfg.redis?.keyPrefix ? persistCfg.redis.keyPrefix : "mcp:transport:";
62231
+ warnIfRequestStateKeyNotShared({ logger: this.logger, metadata: this.metadata });
62232
+ if (this.haManager && isDistributed && this.transportService.getBackendKind() === "redis") {
61623
62233
  this.haManager.startOrphanScanner({
61624
- sessionKeyPrefix: resolvedPrefix,
62234
+ sessionKeyPrefix: this.transportService.getSessionKeyPrefix(),
61625
62235
  onOrphan: (sessionId, previousNodeId) => {
61626
62236
  this.logger.info(
61627
62237
  `[HA] Orphaned session ${sessionId.slice(0, 20)} from ${previousNodeId} \u2014 available for recreation`
@@ -61677,7 +62287,15 @@ var init_scope_instance = __esm({
61677
62287
  "[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."
61678
62288
  );
61679
62289
  }
61680
- const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
62290
+ const gateTasksRedis = tasksConfig?.redis ?? this.metadata.redis;
62291
+ const tasksUseSqlite = Boolean(tasksConfig?.sqlite ?? (gateTasksRedis ? void 0 : resolvedTopLevelSqlite));
62292
+ const tasksUnavailableOnVercelKv = !tasksExplicitlyEnabled && !tasksUseSqlite && resolvesToVercelKvTaskBackend(gateTasksRedis);
62293
+ if (tasksUnavailableOnVercelKv && !tasksExplicitlyDisabled) {
62294
+ this.logger.warn(
62295
+ "[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."
62296
+ );
62297
+ }
62298
+ const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && !tasksUnavailableOnVercelKv && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
61681
62299
  const tasksPromise = shouldInitTasks ? (async () => {
61682
62300
  const tasksRedis = tasksConfig?.redis ?? this.metadata.redis;
61683
62301
  const tasksSqlite = tasksConfig?.sqlite ?? (tasksRedis ? void 0 : resolvedTopLevelSqlite);
@@ -62176,6 +62794,16 @@ var init_scope_instance = __esm({
62176
62794
  async compileUIWidgets() {
62177
62795
  const toolsWithUI = this.scopeTools.getTools(true).filter((t) => hasUIConfig(t.metadata));
62178
62796
  if (toolsWithUI.length === 0) return;
62797
+ for (const tool of toolsWithUI) {
62798
+ const resourceUri = tool.metadata.ui?.resourceUri;
62799
+ this.toolUIRegistry.registerTool(tool.metadata.name, typeof resourceUri === "string" ? resourceUri : void 0);
62800
+ for (const message of describeIgnoredUiOptions(
62801
+ tool.metadata.name,
62802
+ tool.metadata.ui
62803
+ )) {
62804
+ this.logger.warn(message);
62805
+ }
62806
+ }
62179
62807
  this.scopeResources.registerDynamicResource(StaticWidgetResourceTemplate);
62180
62808
  this.logger.verbose(`Registered UI resource template for ${toolsWithUI.length} tool(s) with UI configs`);
62181
62809
  const staticModeTools = toolsWithUI.filter(
@@ -62754,6 +63382,29 @@ var init_scope_instance = __esm({
62754
63382
  }
62755
63383
  throw new FlowExitedWithoutOutputError();
62756
63384
  }
63385
+ /**
63386
+ * Build the ioredis client shared by the HA manager and the transport bus from `metadata.redis`.
63387
+ * The client reconnects on its own and has a permanent, rate-limited 'error' listener.
63388
+ */
63389
+ createHaRedisClient() {
63390
+ const redis = this.metadata.redis;
63391
+ if (!redis || redis.provider === "vercel-kv") {
63392
+ throw new Error("Distributed HA requires a TCP Redis (ioredis); Vercel KV over REST has no Lua/pub-sub support");
63393
+ }
63394
+ const client = createRedisClient({ ...redis, label: "ha", logger: this.logger });
63395
+ this.haRedisClient = client;
63396
+ return client;
63397
+ }
63398
+ async closeHaRedisClient() {
63399
+ const client = this.haRedisClient;
63400
+ if (!client) return;
63401
+ this.haRedisClient = void 0;
63402
+ try {
63403
+ await client.quit();
63404
+ } catch {
63405
+ client.disconnect();
63406
+ }
63407
+ }
62757
63408
  /**
62758
63409
  * Shut down the scope and release resources.
62759
63410
  * Disconnects channel service connectors, unsubscribes event handlers,
@@ -62767,6 +63418,7 @@ var init_scope_instance = __esm({
62767
63418
  this.logger.error("[HA] Manager shutdown failed", { error: err });
62768
63419
  }
62769
63420
  }
63421
+ await this.closeHaRedisClient();
62770
63422
  if (this._channelTeardown) {
62771
63423
  try {
62772
63424
  await this._channelTeardown();
@@ -63895,6 +64547,177 @@ var init_scope_registry = __esm({
63895
64547
  }
63896
64548
  });
63897
64549
 
64550
+ // libs/sdk/src/transport/ipc-server.transport.ts
64551
+ function isIpcStdioRequested() {
64552
+ return !!process.env["FRONTMCP_DEV_STDIO_FD"] && typeof process.send === "function";
64553
+ }
64554
+ var IPC_READY_MESSAGE, IpcServerTransport;
64555
+ var init_ipc_server_transport = __esm({
64556
+ "libs/sdk/src/transport/ipc-server.transport.ts"() {
64557
+ "use strict";
64558
+ IPC_READY_MESSAGE = { __frontmcp: "ready" };
64559
+ IpcServerTransport = class {
64560
+ onclose;
64561
+ onerror;
64562
+ onmessage;
64563
+ started = false;
64564
+ handleMessage = (raw) => {
64565
+ if (!raw || typeof raw !== "object" || raw.__frontmcp !== void 0) return;
64566
+ this.onmessage?.(raw);
64567
+ };
64568
+ handleDisconnect = () => {
64569
+ this.onclose?.();
64570
+ };
64571
+ async start() {
64572
+ if (this.started) throw new Error("IpcServerTransport already started");
64573
+ if (typeof process.send !== "function") {
64574
+ throw new Error("IpcServerTransport requires a process forked with an IPC channel");
64575
+ }
64576
+ this.started = true;
64577
+ process.on("message", this.handleMessage);
64578
+ process.once("disconnect", this.handleDisconnect);
64579
+ process.send(IPC_READY_MESSAGE);
64580
+ }
64581
+ async send(message) {
64582
+ const send = process.send;
64583
+ if (!process.connected || typeof send !== "function") {
64584
+ throw new Error("IPC channel is closed");
64585
+ }
64586
+ await new Promise((resolve, reject) => {
64587
+ send.call(process, message, (err) => err ? reject(err) : resolve());
64588
+ });
64589
+ }
64590
+ async close() {
64591
+ process.off("message", this.handleMessage);
64592
+ process.off("disconnect", this.handleDisconnect);
64593
+ this.started = false;
64594
+ this.onclose?.();
64595
+ }
64596
+ };
64597
+ }
64598
+ });
64599
+
64600
+ // libs/sdk/src/server/middleware/csp.middleware.ts
64601
+ import { getEnv as getEnv8, getEnvFlag as getEnvFlag2 } from "@frontmcp/utils";
64602
+ function readCspFromEnv() {
64603
+ if (!getEnvFlag2("FRONTMCP_CSP_ENABLED")) return void 0;
64604
+ const rawDirectives = getEnv8("FRONTMCP_CSP_DIRECTIVES") ?? "";
64605
+ const directives = {};
64606
+ for (const part of rawDirectives.split(";")) {
64607
+ const trimmed = part.trim();
64608
+ if (!trimmed) continue;
64609
+ const spaceIdx = trimmed.indexOf(" ");
64610
+ if (spaceIdx > 0) {
64611
+ directives[trimmed.slice(0, spaceIdx)] = trimmed.slice(spaceIdx + 1);
64612
+ } else {
64613
+ directives[trimmed] = "";
64614
+ }
64615
+ }
64616
+ return {
64617
+ enabled: true,
64618
+ directives,
64619
+ reportUri: getEnv8("FRONTMCP_CSP_REPORT_URI"),
64620
+ reportOnly: getEnvFlag2("FRONTMCP_CSP_REPORT_ONLY")
64621
+ };
64622
+ }
64623
+ function buildCspHeaderValue(options) {
64624
+ const parts = [];
64625
+ for (const [directive, value] of Object.entries(options.directives)) {
64626
+ parts.push(value ? `${directive} ${value}` : directive);
64627
+ }
64628
+ if (options.reportUri) {
64629
+ parts.push(`report-uri ${options.reportUri}`);
64630
+ }
64631
+ return parts.join("; ");
64632
+ }
64633
+ function getCspHeaderName(reportOnly) {
64634
+ return reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
64635
+ }
64636
+ function readSecurityHeadersFromEnv() {
64637
+ return {
64638
+ hsts: readHeaderEnv("FRONTMCP_HSTS"),
64639
+ contentTypeOptions: readHeaderEnv("FRONTMCP_CONTENT_TYPE_OPTIONS", "nosniff"),
64640
+ frameOptions: readHeaderEnv("FRONTMCP_FRAME_OPTIONS", "DENY"),
64641
+ custom: readCustomHeadersFromEnv()
64642
+ };
64643
+ }
64644
+ function readHeaderEnv(name44, fallback) {
64645
+ const value = getEnv8(name44);
64646
+ if (value === void 0 || value === "") return fallback;
64647
+ return ["off", "false", "none"].includes(value.trim().toLowerCase()) ? void 0 : value;
64648
+ }
64649
+ function readCustomHeadersFromEnv() {
64650
+ const raw = getEnv8("FRONTMCP_HEADERS_CUSTOM");
64651
+ if (!raw) return void 0;
64652
+ try {
64653
+ const parsed = JSON.parse(raw);
64654
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
64655
+ return Object.fromEntries(
64656
+ Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
64657
+ );
64658
+ } catch {
64659
+ return void 0;
64660
+ }
64661
+ }
64662
+ function applySecurityHeaders(res, headers, csp) {
64663
+ if (headers.hsts) {
64664
+ res.setHeader("Strict-Transport-Security", headers.hsts);
64665
+ }
64666
+ if (headers.contentTypeOptions) {
64667
+ res.setHeader("X-Content-Type-Options", headers.contentTypeOptions);
64668
+ }
64669
+ if (headers.frameOptions) {
64670
+ res.setHeader("X-Frame-Options", headers.frameOptions);
64671
+ }
64672
+ if (headers.custom) {
64673
+ for (const [name44, value] of Object.entries(headers.custom)) {
64674
+ res.setHeader(name44, value);
64675
+ }
64676
+ }
64677
+ if (csp?.enabled) {
64678
+ const headerName = getCspHeaderName(csp.reportOnly);
64679
+ const headerValue = buildCspHeaderValue(csp);
64680
+ if (headerValue) {
64681
+ res.setHeader(headerName, headerValue);
64682
+ }
64683
+ }
64684
+ }
64685
+ function resolveSecurityHeaders(options = {}) {
64686
+ const env = readSecurityHeadersFromEnv();
64687
+ const pick = (explicit, fromEnv) => explicit === false ? void 0 : explicit ?? fromEnv;
64688
+ const resolved = {
64689
+ hsts: pick(options.hsts, env.hsts),
64690
+ contentTypeOptions: pick(options.contentTypeOptions, env.contentTypeOptions),
64691
+ frameOptions: pick(options.frameOptions, env.frameOptions),
64692
+ custom: { ...env.custom, ...options.custom }
64693
+ };
64694
+ let csp;
64695
+ if (options.csp?.enabled === false) {
64696
+ csp = void 0;
64697
+ } else if (options.csp?.enabled === true) {
64698
+ const directives = {};
64699
+ for (const [name44, value] of Object.entries(options.csp.directives ?? {})) {
64700
+ directives[name44] = Array.isArray(value) ? value.join(" ") : value;
64701
+ }
64702
+ csp = {
64703
+ enabled: true,
64704
+ directives,
64705
+ reportUri: options.csp.reportUri,
64706
+ reportOnly: options.csp.reportOnly ?? false
64707
+ };
64708
+ } else {
64709
+ csp = readCspFromEnv();
64710
+ }
64711
+ const headers = {};
64712
+ applySecurityHeaders({ setHeader: (name44, value) => void (headers[name44] = value) }, resolved, csp);
64713
+ return headers;
64714
+ }
64715
+ var init_csp_middleware = __esm({
64716
+ "libs/sdk/src/server/middleware/csp.middleware.ts"() {
64717
+ "use strict";
64718
+ }
64719
+ });
64720
+
63898
64721
  // libs/sdk/src/transport/web-response.renderer.ts
63899
64722
  function toByteStream(streamLike) {
63900
64723
  const iter = streamLike[Symbol.asyncIterator];
@@ -64000,6 +64823,7 @@ function isCloudflareWorkersRuntime(request) {
64000
64823
  return typeof cloudflareProperties === "object" && cloudflareProperties !== null;
64001
64824
  }
64002
64825
  function resolvePeerAddress(request, ctx) {
64826
+ if (carriedPeerAddresses.has(request)) return carriedPeerAddresses.get(request);
64003
64827
  if (typeof ctx?.requestIP === "function") return ctx.requestIP(request)?.address;
64004
64828
  if (typeof ctx?.remoteAddr?.hostname === "string") return ctx.remoteAddr.hostname;
64005
64829
  if (isCloudflareWorkersRuntime(request)) return request.headers.get("cf-connecting-ip") ?? void 0;
@@ -64018,7 +64842,13 @@ function mapHttpCors(httpCors, scope) {
64018
64842
  }
64019
64843
  function createWebFetchHandler(scope, options = {}) {
64020
64844
  const httpConfig = scope.metadata.http;
64021
- const healthPaths = new Set(options.healthPaths ?? ["/healthz", "/readyz"]);
64845
+ const healthConfig = scope.metadata.health ?? {};
64846
+ const healthEnabled = healthConfig.enabled !== false;
64847
+ const healthzPath = healthConfig.healthzPath ?? "/healthz";
64848
+ const readyzPath = healthConfig.readyzPath ?? "/readyz";
64849
+ const readyzEnabled = isReadyzEnabled(healthConfig);
64850
+ const bodyLimit = parseByteLimit(httpConfig?.bodyLimit);
64851
+ const securityHeaders = resolveSecurityHeaders(httpConfig?.securityHeaders);
64022
64852
  const normalizePath = (p) => {
64023
64853
  const withSlash = p.startsWith("/") ? p : `/${p}`;
64024
64854
  let end = withSlash.length;
@@ -64062,7 +64892,13 @@ function createWebFetchHandler(scope, options = {}) {
64062
64892
  for (const [k, v] of Object.entries(corsHeadersFor(request))) headers.set(k, v);
64063
64893
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
64064
64894
  };
64065
- return async function handle(request, ctx, env) {
64895
+ const withSecurityHeaders = (response) => {
64896
+ const headers = new Headers(response.headers);
64897
+ for (const [k, v] of Object.entries(securityHeaders)) if (!headers.has(k)) headers.set(k, v);
64898
+ headers.delete("x-powered-by");
64899
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
64900
+ };
64901
+ const handleRequest = async (request, ctx, env) => {
64066
64902
  const url = new URL(request.url);
64067
64903
  if (hostValidation) {
64068
64904
  const rejection = validateHostHeaders(
@@ -64077,17 +64913,34 @@ function createWebFetchHandler(scope, options = {}) {
64077
64913
  return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
64078
64914
  }
64079
64915
  }
64916
+ const limited = await enforceBodyLimit(request, bodyLimit, ctx);
64917
+ if (limited instanceof Response) return withCors(limited, request);
64918
+ request = limited;
64080
64919
  if (corsEnabled && request.method === "OPTIONS") {
64081
64920
  return new Response(null, { status: 204, headers: corsHeadersFor(request) });
64082
64921
  }
64083
- if (healthPaths.has(url.pathname)) {
64084
- return withCors(
64085
- Response.json(
64086
- { status: "ok", server: scope.metadata.info, transport: "web-fetch" },
64087
- { headers: { "Cache-Control": "no-store" } }
64088
- ),
64089
- request
64090
- );
64922
+ if (options.healthPaths ? options.healthPaths.includes(url.pathname) : healthEnabled) {
64923
+ const isReadyz = !options.healthPaths && url.pathname === readyzPath && readyzEnabled;
64924
+ const isHealthz = options.healthPaths ? url.pathname !== "/readyz" : url.pathname === healthzPath || url.pathname === "/health";
64925
+ if (isReadyz && scope.healthService) {
64926
+ const readiness = await scope.healthService.getReadyz();
64927
+ return withCors(
64928
+ Response.json(
64929
+ { ...readiness, transport: "web-fetch" },
64930
+ { status: readiness.status === "ready" ? 200 : 503, headers: { "Cache-Control": "no-store" } }
64931
+ ),
64932
+ request
64933
+ );
64934
+ }
64935
+ if (isHealthz || isReadyz || options.healthPaths) {
64936
+ return withCors(
64937
+ Response.json(
64938
+ { status: "ok", server: scope.metadata.info, transport: "web-fetch" },
64939
+ { headers: { "Cache-Control": "no-store" } }
64940
+ ),
64941
+ request
64942
+ );
64943
+ }
64091
64944
  }
64092
64945
  if (!entryPaths.has(normalizePath(url.pathname))) {
64093
64946
  const authResponse = await runMatchingHttpFlowWeb(scope, request, { ctx, env });
@@ -64101,6 +64954,53 @@ function createWebFetchHandler(scope, options = {}) {
64101
64954
  const rendered = await runHttpRequestFlowWeb(scope, request, { ctx, env });
64102
64955
  return withCors(rendered ?? Response.json({ error: "Not Found" }, { status: 404 }), request);
64103
64956
  };
64957
+ return async function handle(request, ctx, env) {
64958
+ return withSecurityHeaders(await handleRequest(request, ctx, env));
64959
+ };
64960
+ }
64961
+ function parseByteLimit(limit) {
64962
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) return limit;
64963
+ if (typeof limit === "string") {
64964
+ const match = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i.exec(limit.trim());
64965
+ if (match) return Math.floor(Number(match[1]) * BYTE_UNITS[(match[2] ?? "b").toLowerCase()]);
64966
+ }
64967
+ return DEFAULT_BODY_LIMIT_BYTES;
64968
+ }
64969
+ function payloadTooLarge(limit, length) {
64970
+ const error = new PayloadTooLargeError(limit, length);
64971
+ return Response.json({ jsonrpc: "2.0", error: error.toJsonRpcError(), id: null }, { status: 413 });
64972
+ }
64973
+ async function enforceBodyLimit(request, limit, ctx) {
64974
+ const method = request.method.toUpperCase();
64975
+ if (method === "GET" || method === "HEAD" || method === "OPTIONS" || !request.body) return request;
64976
+ const declared = request.headers.get("content-length");
64977
+ if (declared !== null) {
64978
+ const length = Number(declared);
64979
+ if (Number.isFinite(length) && length > limit) return payloadTooLarge(limit, length);
64980
+ }
64981
+ const peerAddress = resolvePeerAddress(request, ctx);
64982
+ const reader = request.body.getReader();
64983
+ const chunks = [];
64984
+ let total = 0;
64985
+ for (; ; ) {
64986
+ const { done, value } = await reader.read();
64987
+ if (done) break;
64988
+ total += value.byteLength;
64989
+ if (total > limit) {
64990
+ await reader.cancel().catch(() => void 0);
64991
+ return payloadTooLarge(limit, total);
64992
+ }
64993
+ chunks.push(value);
64994
+ }
64995
+ const buffered = new Uint8Array(total);
64996
+ let offset = 0;
64997
+ for (const chunk of chunks) {
64998
+ buffered.set(chunk, offset);
64999
+ offset += chunk.byteLength;
65000
+ }
65001
+ const rebuilt = new Request(request.url, { method: request.method, headers: request.headers, body: buffered });
65002
+ carriedPeerAddresses.set(rebuilt, peerAddress);
65003
+ return rebuilt;
64104
65004
  }
64105
65005
  async function runHttpRequestFlowWeb(scope, request, opts = {}) {
64106
65006
  const url = new URL(request.url);
@@ -64191,22 +65091,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
64191
65091
  function misconfigurationResponse(error) {
64192
65092
  const misconfiguration = findMisconfiguration(error);
64193
65093
  if (!misconfiguration) return void 0;
64194
- return Response.json(
64195
- { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy },
64196
- { status: 500 }
64197
- );
64198
- }
64199
- function findMisconfiguration(error) {
64200
- let current = error;
64201
- for (let depth = 0; current instanceof Error && depth < 5; depth++) {
64202
- const code = current.code;
64203
- if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
64204
- return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
64205
- }
64206
- if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
64207
- current = current.cause;
64208
- }
64209
- return void 0;
65094
+ return Response.json(misconfigurationBody(misconfiguration), { status: 500 });
64210
65095
  }
64211
65096
  function createDeferredServerBuild(build, options = {}) {
64212
65097
  const now = options.now ?? Date.now;
@@ -64261,7 +65146,7 @@ function flowErrorToHttpOutput(error) {
64261
65146
  kind: "json",
64262
65147
  status: 500,
64263
65148
  contentType: "application/json; charset=utf-8",
64264
- body: { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy }
65149
+ body: misconfigurationBody(misconfiguration)
64265
65150
  };
64266
65151
  }
64267
65152
  if (error instanceof FlowControl) {
@@ -64287,7 +65172,7 @@ function flowErrorToHttpOutput(error) {
64287
65172
  }
64288
65173
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
64289
65174
  }
64290
- var CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
65175
+ var CLOUDFLARE_WORKERS_USER_AGENT, carriedPeerAddresses, DEFAULT_BODY_LIMIT_BYTES, BYTE_UNITS, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
64291
65176
  var init_web_fetch_handler = __esm({
64292
65177
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
64293
65178
  "use strict";
@@ -64295,18 +65180,15 @@ var init_web_fetch_handler = __esm({
64295
65180
  init_server_tokens();
64296
65181
  init_path_utils();
64297
65182
  init_errors();
65183
+ init_misconfiguration();
65184
+ init_health_routes();
65185
+ init_csp_middleware();
64298
65186
  init_host_validation();
64299
65187
  init_web_response_renderer();
64300
65188
  CLOUDFLARE_WORKERS_USER_AGENT = "Cloudflare-Workers";
64301
- MISCONFIGURATION_REMEDIES = {
64302
- 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.",
64303
- 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`.",
64304
- 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.",
64305
- // The startup checks: a server whose entries ask for protection nothing gives them does not start.
64306
- 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.",
64307
- 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."
64308
- };
64309
- CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
65189
+ carriedPeerAddresses = /* @__PURE__ */ new WeakMap();
65190
+ DEFAULT_BODY_LIMIT_BYTES = 4 * 1024 * 1024;
65191
+ BYTE_UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };
64310
65192
  STARTUP_RETRY_MIN_MS = 1e3;
64311
65193
  STARTUP_RETRY_MAX_MS = 6e4;
64312
65194
  }
@@ -64338,9 +65220,9 @@ var init_noop_server = __esm({
64338
65220
  });
64339
65221
 
64340
65222
  // libs/sdk/src/server/security/security-audit.ts
64341
- function auditSecurityDefaults(config, isProduction6) {
65223
+ function auditSecurityDefaults(config, isProduction7) {
64342
65224
  const findings = [];
64343
- if (!isProduction6 && config.deploymentMode !== "distributed") {
65225
+ if (!isProduction7 && config.deploymentMode !== "distributed") {
64344
65226
  return findings;
64345
65227
  }
64346
65228
  const strict = config.security?.strict === true;
@@ -64388,13 +65270,13 @@ function auditSecurityDefaults(config, isProduction6) {
64388
65270
  message: `Server bound to ${bindAddress}.`
64389
65271
  });
64390
65272
  }
64391
- const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled ?? strict;
65273
+ const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled !== false;
64392
65274
  if (!dnsProtectionEnabled) {
64393
65275
  findings.push({
64394
65276
  level: "warn",
64395
65277
  code: "DNS_REBINDING_UNPROTECTED",
64396
- message: "DNS rebinding protection is disabled.",
64397
- recommendation: "Enable security.dnsRebindingProtection with allowedHosts to prevent DNS rebinding attacks."
65278
+ message: "DNS rebinding protection is disabled (security.dnsRebindingProtection.enabled is false).",
65279
+ recommendation: "Remove `enabled: false`, or set security.dnsRebindingProtection.allowedHosts, to prevent DNS rebinding attacks."
64398
65280
  });
64399
65281
  } else {
64400
65282
  findings.push({
@@ -64583,6 +65465,7 @@ var init_express_host_adapter = __esm({
64583
65465
  "libs/sdk/src/server/adapters/express.host.adapter.ts"() {
64584
65466
  "use strict";
64585
65467
  init_mcp_error();
65468
+ init_csp_middleware();
64586
65469
  init_host_validation_middleware();
64587
65470
  init_resolve_allowed_hosts();
64588
65471
  init_base_host_adapter();
@@ -64597,6 +65480,12 @@ var init_express_host_adapter = __esm({
64597
65480
  deriveHostValidation;
64598
65481
  constructor(options) {
64599
65482
  super();
65483
+ this.app.disable("x-powered-by");
65484
+ const securityHeaders = resolveSecurityHeaders(options?.securityHeaders);
65485
+ this.app.use((_req, res, next) => {
65486
+ for (const [name44, value] of Object.entries(securityHeaders)) res.setHeader(name44, value);
65487
+ next();
65488
+ });
64600
65489
  const corsOptions = options?.cors;
64601
65490
  const corsEnabled = corsOptions?.origin !== void 0 && corsOptions.origin !== false;
64602
65491
  if (corsEnabled) {
@@ -64824,6 +65713,7 @@ var init_server_instance = __esm({
64824
65713
  this.host = new ExpressHostAdapter2({
64825
65714
  ...corsConfig ? { cors: corsConfig } : {},
64826
65715
  ...this.config.security ? { security: this.config.security } : {},
65716
+ ...this.config.securityHeaders ? { securityHeaders: this.config.securityHeaders } : {},
64827
65717
  ...this.config.bodyLimit !== void 0 ? { bodyLimit: this.config.bodyLimit } : {},
64828
65718
  ...this.config.urlencodedLimit !== void 0 ? { urlencodedLimit: this.config.urlencodedLimit } : {}
64829
65719
  });
@@ -64896,7 +65786,7 @@ var init_server_instance = __esm({
64896
65786
  this.prepare();
64897
65787
  const deploymentMode = getRuntimeContext16().deployment;
64898
65788
  const bindAddress = resolveBindAddress(this.config.security, deploymentMode);
64899
- const isProduction6 = process.env["NODE_ENV"] === "production";
65789
+ const isProduction7 = process.env["NODE_ENV"] === "production";
64900
65790
  const findings = auditSecurityDefaults(
64901
65791
  {
64902
65792
  cors: this.config.cors,
@@ -64904,7 +65794,7 @@ var init_server_instance = __esm({
64904
65794
  resolvedBindAddress: bindAddress,
64905
65795
  deploymentMode
64906
65796
  },
64907
- isProduction6
65797
+ isProduction7
64908
65798
  );
64909
65799
  logSecurityFindings(findings, console);
64910
65800
  await this.host.start(this.config.socketPath ?? this.config.port, bindAddress);
@@ -65148,6 +66038,7 @@ var init_front_mcp = __esm({
65148
66038
  init_scope_registry();
65149
66039
  init_skill_instructions_helper();
65150
66040
  init_task();
66041
+ init_ipc_server_transport();
65151
66042
  init_web_fetch_handler();
65152
66043
  init_front_mcp_providers();
65153
66044
  init_static_startup_check();
@@ -65267,7 +66158,13 @@ var init_front_mcp = __esm({
65267
66158
  }
65268
66159
  }
65269
66160
  static async bootstrap(options) {
65270
- const parsedConfig = frontMcpMetadataSchema.parse(options);
66161
+ const sqlitePath = process.env["FRONTMCP_SQLITE_PATH"];
66162
+ const parsedConfig = frontMcpMetadataSchema.parse(
66163
+ sqlitePath ? {
66164
+ ...options,
66165
+ sqlite: { ...options.sqlite ?? {}, path: sqlitePath }
66166
+ } : options
66167
+ );
65271
66168
  const daemonSocket = process.env["FRONTMCP_DAEMON_SOCKET"];
65272
66169
  if (daemonSocket) {
65273
66170
  await _FrontMcpInstance2.runUnixSocket({ ...parsedConfig, socketPath: daemonSocket });
@@ -65644,7 +66541,7 @@ var init_front_mcp = __esm({
65644
66541
  };
65645
66542
  mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
65646
66543
  }
65647
- const transport = new StdioServerTransport();
66544
+ const transport = isIpcStdioRequested() ? new IpcServerTransport() : new StdioServerTransport();
65648
66545
  startupCleanups.push(() => transport.close());
65649
66546
  await mcpServer.connect(transport);
65650
66547
  startupSucceeded = true;
@@ -65897,7 +66794,7 @@ var init_execute_task = __esm({
65897
66794
 
65898
66795
  // libs/sdk/src/common/decorators/front-mcp.decorator.ts
65899
66796
  import "reflect-metadata";
65900
- import { getEnvFlag as getEnvFlag2 } from "@frontmcp/utils";
66797
+ import { getEnvFlag as getEnvFlag3 } from "@frontmcp/utils";
65901
66798
  function getDecoratorConfig(target) {
65902
66799
  if (typeof target !== "function") return void 0;
65903
66800
  if (typeof Reflect === "undefined") return void 0;
@@ -65952,9 +66849,9 @@ function FrontMcp(providedMetadata) {
65952
66849
  Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);
65953
66850
  }
65954
66851
  Reflect.defineMetadata(FRONTMCP_CONFIG_METADATA_KEY, metadata, target);
65955
- const isServerless2 = getEnvFlag2("FRONTMCP_SERVERLESS");
65956
- const isSchemaExtract = getEnvFlag2("FRONTMCP_SCHEMA_EXTRACT");
65957
- const isStdio = getEnvFlag2("FRONTMCP_STDIO");
66852
+ const isServerless2 = getEnvFlag3("FRONTMCP_SERVERLESS");
66853
+ const isSchemaExtract = getEnvFlag3("FRONTMCP_SCHEMA_EXTRACT");
66854
+ const isStdio = getEnvFlag3("FRONTMCP_STDIO");
65958
66855
  const taskWorkerId = typeof process !== "undefined" ? process.env?.["FRONTMCP_RUN_TASK_ID"] : void 0;
65959
66856
  if (taskWorkerId && metadata.serve !== false) {
65960
66857
  void (async () => {
@@ -65980,7 +66877,7 @@ function FrontMcp(providedMetadata) {
65980
66877
  } else if (isServerless2) {
65981
66878
  const ServerlessInstance = getFrontMcpInstance();
65982
66879
  const { setServerlessHandler: setServerlessHandler2, setServerlessHandlerPromise: setServerlessHandlerPromise2, setServerlessHandlerError: setServerlessHandlerError2 } = getServerlessHandlerFns();
65983
- const isWorker = getEnvFlag2("FRONTMCP_WORKER");
66880
+ const isWorker = getEnvFlag3("FRONTMCP_WORKER");
65984
66881
  const handlerPromise = isWorker ? ServerlessInstance.createFetchHandler(metadata) : ServerlessInstance.createHandler(metadata);
65985
66882
  setServerlessHandlerPromise2(handlerPromise);
65986
66883
  handlerPromise.then(setServerlessHandler2).catch((err) => {
@@ -66704,7 +67601,7 @@ var init_dynamic_adapter = __esm({
66704
67601
  * @param options - Adapter options including required `name` field
66705
67602
  * @throws Error if `name` is missing/empty or if a duplicate name is detected
66706
67603
  */
66707
- static init(options) {
67604
+ static init(options = {}) {
66708
67605
  const typedOptions = options;
66709
67606
  const adapterName = typedOptions.name;
66710
67607
  if (!adapterName || typeof adapterName !== "string" || adapterName.trim() === "") {
@@ -66743,49 +67640,6 @@ var init_dynamic_adapter = __esm({
66743
67640
  }
66744
67641
  });
66745
67642
 
66746
- // libs/sdk/src/common/dynamic/dynamic.plugin.ts
66747
- function isDynamicPluginClass(value) {
66748
- return typeof value === "function" && value.prototype instanceof DynamicPlugin;
66749
- }
66750
- var DynamicPlugin;
66751
- var init_dynamic_plugin = __esm({
66752
- "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
66753
- "use strict";
66754
- init_transport_errors();
66755
- init_dynamic_utils();
66756
- DynamicPlugin = class {
66757
- get(token) {
66758
- throw new MethodNotImplementedError("DynamicPlugin", "get");
66759
- }
66760
- /**
66761
- * Static init() method to create a plugin provider.
66762
- * @param options - Input options (with optional fields for defaults)
66763
- */
66764
- static init(options) {
66765
- const extraProviders = options.providers;
66766
- const typedOptions = options;
66767
- if ("useFactory" in options) {
66768
- return {
66769
- ...typedOptions,
66770
- provide: this,
66771
- inject: options.inject,
66772
- useFactory: options.useFactory,
66773
- providers: dedupePluginProviders(extraProviders ?? [])
66774
- };
66775
- }
66776
- const dyn = collectDynamicProviders(this, typedOptions);
66777
- const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
66778
- return {
66779
- ...typedOptions,
66780
- provide: this,
66781
- useValue: new this(options),
66782
- providers: mergedProviders
66783
- };
66784
- }
66785
- };
66786
- }
66787
- });
66788
-
66789
67643
  // libs/sdk/src/common/dynamic/index.ts
66790
67644
  var init_dynamic = __esm({
66791
67645
  "libs/sdk/src/common/dynamic/index.ts"() {
@@ -68801,6 +69655,7 @@ export {
68801
69655
  httpRequestInputSchema,
68802
69656
  httpRespond,
68803
69657
  incrementalAuthConfigSchema,
69658
+ initOptionsOf,
68804
69659
  intentSchema,
68805
69660
  isAlive,
68806
69661
  isAnonymousSubject7 as isAnonymousSubject,
@@ -68923,6 +69778,7 @@ export {
68923
69778
  resolveSchemaDescriptionFormat,
68924
69779
  resolveToolVisibility,
68925
69780
  resolveWithFallbacks,
69781
+ resolvesToVercelKvTaskBackend,
68926
69782
  frontMcpResource as resource,
68927
69783
  frontMcpResourceTemplate as resourceTemplate,
68928
69784
  resourceUriMatches,