@frontmcp/sdk 1.8.4 → 1.8.6

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 (102) hide show
  1. package/agent/agent.instance.d.ts +9 -3
  2. package/agent/agent.instance.d.ts.map +1 -1
  3. package/agent/flows/call-agent.flow.d.ts +19 -1
  4. package/agent/flows/call-agent.flow.d.ts.map +1 -1
  5. package/agent/hooks/agent.hooks.d.ts +4 -4
  6. package/agent/hooks/agent.hooks.d.ts.map +1 -1
  7. package/auth/flows/oauth.auth-ui.flow.d.ts +1 -1
  8. package/auth/flows/oauth.authorize.flow.d.ts +3 -3
  9. package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
  10. package/auth/flows/oauth.callback.flow.d.ts +2 -2
  11. package/auth/flows/oauth.connect.flow.d.ts +1 -1
  12. package/auth/flows/oauth.provider-callback.flow.d.ts +2 -2
  13. package/auth/flows/oauth.provider-callback.flow.d.ts.map +1 -1
  14. package/auth/flows/oauth.register.flow.d.ts +1 -1
  15. package/auth/flows/oauth.token.flow.d.ts +1 -1
  16. package/auth/flows/oauth.userinfo.flow.d.ts +1 -1
  17. package/auth/flows/session.verify.flow.d.ts +1 -0
  18. package/auth/flows/session.verify.flow.d.ts.map +1 -1
  19. package/auth/flows/well-known.jwks.flow.d.ts +3 -3
  20. package/auth/flows/well-known.oauth-authorization-server.flow.d.ts +3 -2
  21. package/auth/flows/well-known.oauth-authorization-server.flow.d.ts.map +1 -1
  22. package/auth/flows/well-known.prm.flow.d.ts +4 -3
  23. package/auth/flows/well-known.prm.flow.d.ts.map +1 -1
  24. package/auth/instances/instance.local-primary-auth.d.ts +30 -19
  25. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  26. package/channel/channel-scope.helper.d.ts +13 -1
  27. package/channel/channel-scope.helper.d.ts.map +1 -1
  28. package/channel/sources/completion-events.d.ts +24 -0
  29. package/channel/sources/completion-events.d.ts.map +1 -0
  30. package/channel/sources/job-completion.source.d.ts +1 -1
  31. package/channel/sources/job-completion.source.d.ts.map +1 -1
  32. package/common/dynamic/dynamic.plugin.d.ts +11 -2
  33. package/common/dynamic/dynamic.plugin.d.ts.map +1 -1
  34. package/common/entries/agent.entry.d.ts +6 -0
  35. package/common/entries/agent.entry.d.ts.map +1 -1
  36. package/common/entries/scope.entry.d.ts +2 -3
  37. package/common/entries/scope.entry.d.ts.map +1 -1
  38. package/common/interfaces/internal/primary-auth-provider.interface.d.ts +5 -5
  39. package/common/interfaces/internal/primary-auth-provider.interface.d.ts.map +1 -1
  40. package/common/interfaces/tool.interface.d.ts.map +1 -1
  41. package/common/metadata/tool.metadata.d.ts +2 -2
  42. package/common/metadata/workflow.metadata.d.ts +1 -1
  43. package/common/schemas/http-output.schema.d.ts +24 -24
  44. package/common/utils/list-all-pages.utils.d.ts +17 -0
  45. package/common/utils/list-all-pages.utils.d.ts.map +1 -0
  46. package/common/utils/path.utils.d.ts +2 -0
  47. package/common/utils/path.utils.d.ts.map +1 -1
  48. package/context/frontmcp-context.d.ts +4 -0
  49. package/context/frontmcp-context.d.ts.map +1 -1
  50. package/elicitation/helpers/elicit.helper.d.ts.map +1 -1
  51. package/esm/index.mjs +1176 -444
  52. package/front-mcp/front-mcp.d.ts.map +1 -1
  53. package/index.d.ts +6 -6
  54. package/index.d.ts.map +1 -1
  55. package/index.js +1276 -550
  56. package/job/job-scope.helper.d.ts.map +1 -1
  57. package/package.json +10 -10
  58. package/plugin/plugin.registry.d.ts.map +1 -1
  59. package/remote-mcp/mcp-client.service.d.ts.map +1 -1
  60. package/remote-mcp/mcp-stateless-client.adapter.d.ts +16 -5
  61. package/remote-mcp/mcp-stateless-client.adapter.d.ts.map +1 -1
  62. package/scope/flows/http.request.flow.d.ts.map +1 -1
  63. package/scope/scope.instance.d.ts +3 -3
  64. package/scope/scope.instance.d.ts.map +1 -1
  65. package/skill/auth/skill-http-auth.d.ts.map +1 -1
  66. package/skill/flows/http/llm-full-txt.flow.d.ts +1 -1
  67. package/skill/flows/http/llm-txt.flow.d.ts +1 -1
  68. package/skill/flows/http/skills-api.flow.d.ts +1 -1
  69. package/skill/flows/http/skills-api.flow.d.ts.map +1 -1
  70. package/skill/flows/load-skill.flow.d.ts.map +1 -1
  71. package/skill/index.d.ts +1 -1
  72. package/skill/index.d.ts.map +1 -1
  73. package/skill/sep-2640/sep-2640.resource-helpers.d.ts +6 -0
  74. package/skill/sep-2640/sep-2640.resource-helpers.d.ts.map +1 -1
  75. package/skill/skill-filter.helper.d.ts +25 -0
  76. package/skill/skill-filter.helper.d.ts.map +1 -1
  77. package/skill/skill-http.utils.d.ts.map +1 -1
  78. package/skill/skill.events.d.ts.map +1 -1
  79. package/skill/skill.registry.d.ts +94 -17
  80. package/skill/skill.registry.d.ts.map +1 -1
  81. package/tool/flows/call-tool.flow.d.ts +1 -0
  82. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  83. package/transport/flows/handle.mcp-20260728.flow.d.ts +1 -0
  84. package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
  85. package/transport/flows/handle.sse.flow.d.ts +25 -0
  86. package/transport/flows/handle.sse.flow.d.ts.map +1 -1
  87. package/transport/flows/handle.streamable-http.flow.d.ts +11 -1
  88. package/transport/flows/handle.streamable-http.flow.d.ts.map +1 -1
  89. package/transport/index.d.ts +1 -1
  90. package/transport/index.d.ts.map +1 -1
  91. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts +7 -2
  92. package/transport/mcp-20260728/client/mcp-stateless.client.d.ts.map +1 -1
  93. package/transport/mcp-20260728/dispatcher.d.ts +5 -0
  94. package/transport/mcp-20260728/dispatcher.d.ts.map +1 -1
  95. package/transport/mcp-20260728/mrtr.d.ts +8 -0
  96. package/transport/mcp-20260728/mrtr.d.ts.map +1 -1
  97. package/transport/mcp-20260728/request-state.d.ts +28 -0
  98. package/transport/mcp-20260728/request-state.d.ts.map +1 -1
  99. package/transport/mcp-handlers/skills-load-request.handler.d.ts.map +1 -1
  100. package/transport/web-fetch-handler.d.ts +33 -0
  101. package/transport/web-fetch-handler.d.ts.map +1 -1
  102. package/workflow/engine/workflow.engine.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -6035,6 +6035,33 @@ var init_errors = __esm({
6035
6035
  }
6036
6036
  });
6037
6037
 
6038
+ // libs/sdk/src/common/utils/list-all-pages.utils.ts
6039
+ async function listAllPages(method, fetchPage, fail = (message) => new InternalMcpError(message, "LIST_PAGINATION_STALLED")) {
6040
+ const items = [];
6041
+ const seenCursors = /* @__PURE__ */ new Set();
6042
+ let cursor;
6043
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
6044
+ const result = await fetchPage(cursor);
6045
+ items.push(...result.items ?? []);
6046
+ const next = result.nextCursor;
6047
+ if (!next) return items;
6048
+ if (seenCursors.has(next)) {
6049
+ throw fail(`${method} returned the cursor "${next}" twice`);
6050
+ }
6051
+ seenCursors.add(next);
6052
+ cursor = next;
6053
+ }
6054
+ throw fail(`${method} did not finish within ${MAX_LIST_PAGES} pages`);
6055
+ }
6056
+ var MAX_LIST_PAGES;
6057
+ var init_list_all_pages_utils = __esm({
6058
+ "libs/sdk/src/common/utils/list-all-pages.utils.ts"() {
6059
+ "use strict";
6060
+ init_mcp_error();
6061
+ MAX_LIST_PAGES = 1e3;
6062
+ }
6063
+ });
6064
+
6038
6065
  // libs/sdk/src/transport/mcp-handlers/skills-mcp.types.ts
6039
6066
  import { z as z40 } from "@frontmcp/lazy-zod";
6040
6067
  var SkillsSearchRequestSchema, SkillsSearchResultSchema, SkillsLoadRequestSchema, SkillsLoadResultSchema, SkillsListRequestSchema, SkillsListResultSchema;
@@ -6497,30 +6524,30 @@ var require_dist = __commonJS({
6497
6524
  sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6498
6525
  });
6499
6526
  module.exports = __toCommonJS2(index_exports);
6500
- var import_utils113 = __require("@frontmcp/utils");
6527
+ var import_utils114 = __require("@frontmcp/utils");
6501
6528
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6502
6529
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6503
6530
  var KEY_LENGTH = 32;
6504
6531
  function deriveEncryptionKey(secret) {
6505
6532
  const ikm = new TextEncoder().encode(secret);
6506
- return (0, import_utils113.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6533
+ return (0, import_utils114.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6507
6534
  }
6508
6535
  var SEPARATOR = ":";
6509
6536
  function encryptValue(key, plaintext) {
6510
- const iv = (0, import_utils113.randomBytes)(12);
6537
+ const iv = (0, import_utils114.randomBytes)(12);
6511
6538
  const plaintextBytes = new TextEncoder().encode(plaintext);
6512
- const { ciphertext, tag } = (0, import_utils113.encryptAesGcm)(key, plaintextBytes, iv);
6513
- return [(0, import_utils113.base64urlEncode)(iv), (0, import_utils113.base64urlEncode)(tag), (0, import_utils113.base64urlEncode)(ciphertext)].join(SEPARATOR);
6539
+ const { ciphertext, tag } = (0, import_utils114.encryptAesGcm)(key, plaintextBytes, iv);
6540
+ return [(0, import_utils114.base64urlEncode)(iv), (0, import_utils114.base64urlEncode)(tag), (0, import_utils114.base64urlEncode)(ciphertext)].join(SEPARATOR);
6514
6541
  }
6515
6542
  function decryptValue(key, encrypted) {
6516
6543
  const parts = encrypted.split(SEPARATOR);
6517
6544
  if (parts.length !== 3) {
6518
6545
  throw new Error("Invalid encrypted value format");
6519
6546
  }
6520
- const iv = (0, import_utils113.base64urlDecode)(parts[0]);
6521
- const tag = (0, import_utils113.base64urlDecode)(parts[1]);
6522
- const ciphertext = (0, import_utils113.base64urlDecode)(parts[2]);
6523
- const plaintext = (0, import_utils113.decryptAesGcm)(key, ciphertext, iv, tag);
6547
+ const iv = (0, import_utils114.base64urlDecode)(parts[0]);
6548
+ const tag = (0, import_utils114.base64urlDecode)(parts[1]);
6549
+ const ciphertext = (0, import_utils114.base64urlDecode)(parts[2]);
6550
+ const plaintext = (0, import_utils114.decryptAesGcm)(key, ciphertext, iv, tag);
6524
6551
  return new TextDecoder().decode(plaintext);
6525
6552
  }
6526
6553
  var import_utils210 = __require("@frontmcp/utils");
@@ -10275,7 +10302,33 @@ async function filterDiscoverableSkillResults(scope, registry, results, caller =
10275
10302
  return entry === void 0 || servable.has(entry);
10276
10303
  });
10277
10304
  }
10278
- var SKILLS_HTTP_SURFACE;
10305
+ function skillToolsForCaller(loaded, tools, ctx, surface) {
10306
+ const caller = callerSurface(ctx, surface);
10307
+ if (!tools || caller === void 0 || loaded.availableTools.length === 0) return loaded;
10308
+ const availability = new Map(tools.getTools(true).map((tool) => [tool.name, tool.metadata?.availableWhen]));
10309
+ const notOffered = loaded.availableTools.filter((name44) => !isOfferedOnSurface(availability.get(name44), caller));
10310
+ if (notOffered.length === 0) return loaded;
10311
+ return {
10312
+ ...loaded,
10313
+ availableTools: loaded.availableTools.filter((name44) => !notOffered.includes(name44)),
10314
+ missingTools: [...loaded.missingTools, ...notOffered],
10315
+ isComplete: false,
10316
+ warning: warningWithMissingTools(loaded.warning, loaded.skill?.name, notOffered)
10317
+ };
10318
+ }
10319
+ function warningWithMissingTools(warning, skillName, names) {
10320
+ const prefix = warning?.match(WARNING_PREFIX);
10321
+ if (warning && prefix && warning.endsWith(WARNING_SUFFIX)) {
10322
+ const parts = warning.slice(prefix[0].length, -WARNING_SUFFIX.length).split("; ");
10323
+ const missing = parts.findIndex((part) => part.startsWith(MISSING_PART));
10324
+ if (missing === -1) parts.unshift(`${MISSING_PART}${names.join(", ")}`);
10325
+ else parts[missing] = `${parts[missing]}, ${names.join(", ")}`;
10326
+ return `${prefix[0]}${parts.join("; ")}${WARNING_SUFFIX}`;
10327
+ }
10328
+ const sentence = `Skill "${skillName ?? "skill"}" references ${MISSING_PART}${names.join(", ")}${WARNING_SUFFIX}`;
10329
+ return warning ? `${warning} ${sentence}` : sentence;
10330
+ }
10331
+ var SKILLS_HTTP_SURFACE, WARNING_PREFIX, WARNING_SUFFIX, MISSING_PART;
10279
10332
  var init_skill_filter_helper = __esm({
10280
10333
  "libs/sdk/src/skill/skill-filter.helper.ts"() {
10281
10334
  "use strict";
@@ -10284,6 +10337,9 @@ var init_skill_filter_helper = __esm({
10284
10337
  init_skill_authorities_helper();
10285
10338
  init_skill_entry_resolver();
10286
10339
  SKILLS_HTTP_SURFACE = "mcp";
10340
+ WARNING_PREFIX = /^Skill "(.*)" references /;
10341
+ WARNING_SUFFIX = ". Some functionality may be limited.";
10342
+ MISSING_PART = "missing tools: ";
10287
10343
  }
10288
10344
  });
10289
10345
 
@@ -10384,7 +10440,13 @@ async function formatSkillsForLlmFull(registry, toolRegistry, visibility = "both
10384
10440
  }
10385
10441
  const loaded = await registry.loadSkill(skill.name);
10386
10442
  if (loaded) {
10387
- parts.push(formatSkillForLLMWithSchemas(loaded.skill, loaded.availableTools, loaded.missingTools, toolRegistry));
10443
+ const { availableTools, missingTools } = skillToolsForCaller(
10444
+ loaded,
10445
+ toolRegistry,
10446
+ void 0,
10447
+ SKILLS_HTTP_SURFACE
10448
+ );
10449
+ parts.push(formatSkillForLLMWithSchemas(loaded.skill, availableTools, missingTools, toolRegistry));
10388
10450
  }
10389
10451
  }
10390
10452
  return parts.join("\n\n---\n\n");
@@ -10504,6 +10566,7 @@ function skillToApiResponse(skill, loadResult) {
10504
10566
  var init_skill_http_utils = __esm({
10505
10567
  "libs/sdk/src/skill/skill-http.utils.ts"() {
10506
10568
  "use strict";
10569
+ init_skill_filter_helper();
10507
10570
  }
10508
10571
  });
10509
10572
 
@@ -11065,7 +11128,11 @@ function skillsLoadRequestHandler({
11065
11128
  throw toSdkMcpError(err);
11066
11129
  }
11067
11130
  }
11068
- const { skill, availableTools, missingTools, isComplete, warning } = loadResult;
11131
+ const { skill, availableTools, missingTools, isComplete, warning } = skillToolsForCaller(
11132
+ loadResult,
11133
+ toolRegistry,
11134
+ withMcpSurface(scope, ctx)
11135
+ );
11069
11136
  if (warning) {
11070
11137
  warnings.push(warning);
11071
11138
  }
@@ -12068,6 +12135,7 @@ var DirectClientImpl;
12068
12135
  var init_direct_client = __esm({
12069
12136
  "libs/sdk/src/direct/direct-client.ts"() {
12070
12137
  "use strict";
12138
+ init_list_all_pages_utils();
12071
12139
  init_errors();
12072
12140
  init_skills_mcp_types();
12073
12141
  init_llm_platform();
@@ -12214,8 +12282,11 @@ var init_direct_client = __esm({
12214
12282
  // Tool Operations (platform-formatted)
12215
12283
  // ─────────────────────────────────────────────────────────────────────────────
12216
12284
  async listTools() {
12217
- const result = await this.mcpClient.listTools();
12218
- return formatToolsForPlatform(result.tools, this.platform);
12285
+ const tools = await listAllPages("tools/list", async (cursor) => {
12286
+ const result = await this.mcpClient.listTools(cursor ? { cursor } : void 0);
12287
+ return { items: result.tools, nextCursor: result.nextCursor };
12288
+ });
12289
+ return formatToolsForPlatform(tools, this.platform);
12219
12290
  }
12220
12291
  async callTool(name44, args) {
12221
12292
  const result = await this.mcpClient.callTool({
@@ -14614,6 +14685,9 @@ function readPublicUrlPin() {
14614
14685
  function isPublicUrlPinned() {
14615
14686
  return readPublicUrlPin() !== void 0;
14616
14687
  }
14688
+ function getPinnedPublicUrl() {
14689
+ return readPublicUrlPin();
14690
+ }
14617
14691
  function isProxyTrusted() {
14618
14692
  try {
14619
14693
  if (typeof process !== "undefined" && process.env) {
@@ -16061,6 +16135,11 @@ async function performElicit(deps, message, requestedSchema, options) {
16061
16135
  }
16062
16136
  const ctx = tryGetContext();
16063
16137
  const mrtr = ctx?.getMrtrExchange?.();
16138
+ if (mrtr && mrtr.clientDeclaredRevision === false && mrtr.supportsElicitation?.(options?.mode ?? "form") !== true) {
16139
+ throw new ElicitationNotSupportedError(
16140
+ "This client does not support elicitation: it declared no elicitation capability, and without a session the elicitation fallback has no one to answer it"
16141
+ );
16142
+ }
16064
16143
  if (mrtr) {
16065
16144
  const zodSchema = requestedSchema instanceof z45.ZodType ? requestedSchema : z45.object(requestedSchema);
16066
16145
  const answer = mrtr.resolveElicitation({
@@ -22008,6 +22087,9 @@ var init_call_tool_flow = __esm({
22008
22087
  uiResult: z57.any().optional(),
22009
22088
  // UI metadata from rendering (merged into _meta)
22010
22089
  uiMeta: z57.record(z57.string(), z57.unknown()).optional(),
22090
+ // Metadata hooks add to the result (e.g. the cache plugin's `cache: 'hit'`), merged into the
22091
+ // result's _meta; never merged into rawOutput, which is the tool's data
22092
+ resultMeta: z57.record(z57.string(), z57.unknown()).optional(),
22011
22093
  // Progress token from request's _meta (for progress notifications)
22012
22094
  progressToken: z57.union([z57.string(), z57.number()]).optional(),
22013
22095
  // JSON-RPC request ID (for elicitation routing)
@@ -22428,6 +22510,7 @@ var init_call_tool_flow = __esm({
22428
22510
  context.mark("createToolCallContext");
22429
22511
  context._toolNameInternal = tool.metadata.id ?? tool.metadata.name;
22430
22512
  context._toolInputInternal = input.arguments;
22513
+ context._jsonRpcRequestIdInternal = this.state.jsonRpcRequestId;
22431
22514
  const frontmcpContext = context.tryGetContext();
22432
22515
  const authInfoWithTransport = authInfo;
22433
22516
  if (frontmcpContext && authInfoWithTransport?.transport?.sendElicitRequest) {
@@ -22834,7 +22917,7 @@ ${JSON.stringify(error.schema, null, 2)}
22834
22917
  }
22835
22918
  async finalize() {
22836
22919
  this.logger.verbose("finalize:start");
22837
- const { tool, rawOutput, uiResult, uiMeta } = this.state;
22920
+ const { tool, rawOutput, uiResult, uiMeta, resultMeta } = this.state;
22838
22921
  if (!tool) {
22839
22922
  this.logger.verbose("finalize: skipping (no tool in state)");
22840
22923
  return;
@@ -22877,6 +22960,9 @@ ${JSON.stringify(error.schema, null, 2)}
22877
22960
  if (rawMeta) {
22878
22961
  result._meta = { ...result._meta, ...rawMeta };
22879
22962
  }
22963
+ if (resultMeta) {
22964
+ result._meta = { ...result._meta, ...resultMeta };
22965
+ }
22880
22966
  if (uiResult) {
22881
22967
  result.content = uiResult.content;
22882
22968
  if (uiResult.structuredContent !== void 0 && uiResult.structuredContent !== null) {
@@ -25419,6 +25505,56 @@ var init_dynamic_utils = __esm({
25419
25505
  }
25420
25506
  });
25421
25507
 
25508
+ // libs/sdk/src/common/dynamic/dynamic.plugin.ts
25509
+ function isDynamicPluginClass(value) {
25510
+ return typeof value === "function" && value.prototype instanceof DynamicPlugin;
25511
+ }
25512
+ function initOptionsOf(instance) {
25513
+ return initOptionsByInstance.has(instance) ? { options: initOptionsByInstance.get(instance) } : void 0;
25514
+ }
25515
+ var initOptionsByInstance, DynamicPlugin;
25516
+ var init_dynamic_plugin = __esm({
25517
+ "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
25518
+ "use strict";
25519
+ init_transport_errors();
25520
+ init_dynamic_utils();
25521
+ initOptionsByInstance = /* @__PURE__ */ new WeakMap();
25522
+ DynamicPlugin = class {
25523
+ get(token) {
25524
+ throw new MethodNotImplementedError("DynamicPlugin", "get");
25525
+ }
25526
+ /**
25527
+ * Static init() method to create a plugin provider.
25528
+ * @param options - Input options (with optional fields for defaults). Omitted, the plugin is
25529
+ * built with `{}`, as `ApprovalPlugin.init()` is documented.
25530
+ */
25531
+ static init(options = {}) {
25532
+ const extraProviders = options.providers;
25533
+ const typedOptions = options;
25534
+ if ("useFactory" in options) {
25535
+ return {
25536
+ ...typedOptions,
25537
+ provide: this,
25538
+ inject: options.inject,
25539
+ useFactory: options.useFactory,
25540
+ providers: dedupePluginProviders(extraProviders ?? [])
25541
+ };
25542
+ }
25543
+ const dyn = collectDynamicProviders(this, typedOptions);
25544
+ const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
25545
+ const instance = new this(options);
25546
+ initOptionsByInstance.set(instance, options);
25547
+ return {
25548
+ ...typedOptions,
25549
+ provide: this,
25550
+ useValue: instance,
25551
+ providers: mergedProviders
25552
+ };
25553
+ }
25554
+ };
25555
+ }
25556
+ });
25557
+
25422
25558
  // libs/sdk/src/common/interfaces/internal/flow.utils.ts
25423
25559
  var FlowState;
25424
25560
  var init_flow_utils2 = __esm({
@@ -27297,7 +27433,11 @@ var init_skill_events = __esm({
27297
27433
  */
27298
27434
  emit(event) {
27299
27435
  for (const listener of [...this.listeners]) {
27300
- listener(event);
27436
+ try {
27437
+ listener(event);
27438
+ } catch (err) {
27439
+ console.error("SkillEmitter listener error:", err instanceof Error ? err.message : "Unknown error");
27440
+ }
27301
27441
  }
27302
27442
  }
27303
27443
  /**
@@ -27578,6 +27718,24 @@ var init_skill_instance = __esm({
27578
27718
  // libs/sdk/src/skill/skill.registry.ts
27579
27719
  import { tokenName as tokenName9 } from "@frontmcp/di";
27580
27720
  import { getRuntimeContext as getRuntimeContext11, isEntryAvailable as isEntryAvailable5 } from "@frontmcp/utils";
27721
+ function findIdPathCollision(skills) {
27722
+ const byPath = /* @__PURE__ */ new Map();
27723
+ for (const skill of skills) {
27724
+ const path3 = skill.getSkillPath();
27725
+ if (!byPath.has(path3)) byPath.set(path3, skill);
27726
+ }
27727
+ for (const idOwner of skills) {
27728
+ const id = idOwner.metadata.id;
27729
+ if (!id || id === idOwner.getSkillPath()) continue;
27730
+ const pathOwner = byPath.get(id);
27731
+ if (pathOwner) return { idOwner, pathOwner };
27732
+ }
27733
+ return void 0;
27734
+ }
27735
+ function describeIdPathCollision({ idOwner, pathOwner }) {
27736
+ const id = idOwner.metadata.id;
27737
+ return `id "${id}" of skill "${idOwner.metadata.name}" is the skill:// path of skill "${pathOwner.metadata.name}", so skill://${id}/SKILL.md would serve one for the other. Give one of them another id or name.`;
27738
+ }
27581
27739
  var DEFAULT_LIST_LIMIT, SkillRegistry;
27582
27740
  var init_skill_registry = __esm({
27583
27741
  "libs/sdk/src/skill/skill.registry.ts"() {
@@ -27609,16 +27767,30 @@ var init_skill_registry = __esm({
27609
27767
  dynamicContents = /* @__PURE__ */ new Map();
27610
27768
  /**
27611
27769
  * Per-id generation counter for dynamic registrations. Bumped on every
27612
- * registerSkillContent call so a stale unregister handle held by an earlier
27613
- * caller cannot remove a replacement registration.
27770
+ * registerSkillContent call, and on every removal, so a stale unregister
27771
+ * handle held by an earlier caller cannot remove a later registration. Never
27772
+ * reset: a counter restarted at 1 would match an old handle's generation.
27614
27773
  */
27615
27774
  dynamicGenerations = /* @__PURE__ */ new Map();
27775
+ /**
27776
+ * Ids of skills that left the registry (its own dynamic skills, or an adopted child's) whose copy
27777
+ * in the storage provider is not removed yet: the removal is pending, or the provider failed it.
27778
+ * Provider-backed reads skip them until the removal succeeds or the id is served again. Each maps
27779
+ * to the number of its withdrawal.
27780
+ */
27781
+ withdrawn = /* @__PURE__ */ new Map();
27782
+ /** Numbers each withdrawal, so a removal that completes late clears only its own. */
27783
+ withdrawals = 0;
27784
+ /** The last storage-provider write queued per skill id ({@link syncProvider}). */
27785
+ providerWrites = /* @__PURE__ */ new Map();
27616
27786
  /** Whether a plugin registers skills at runtime ({@link expectDynamicSkills}). */
27617
27787
  dynamicSkillsExpected = false;
27618
27788
  /** Adopted skills from child registries */
27619
27789
  adopted = /* @__PURE__ */ new Map();
27620
27790
  /** Children registries */
27621
27791
  children = /* @__PURE__ */ new Set();
27792
+ /** The registries that adopted this one: a skill registered here is served through them too. */
27793
+ adopters = /* @__PURE__ */ new Set();
27622
27794
  /** O(1) indexes */
27623
27795
  byQualifiedId = /* @__PURE__ */ new Map();
27624
27796
  /** Owner lineage per skill instance, rebuilt with the index so `lineageOf` is O(1). */
@@ -27703,8 +27875,10 @@ var init_skill_registry = __esm({
27703
27875
  const row = this.makeRow(token, instance, lineage, this);
27704
27876
  this.localRows.push(row);
27705
27877
  try {
27706
- const content = await instance.load();
27707
- await this.storageProvider.add(content);
27878
+ await this.syncProvider(instance.metadata.id ?? instance.metadata.name, async () => {
27879
+ const content = await instance.load();
27880
+ await this.storageProvider.add(content);
27881
+ });
27708
27882
  } catch (error) {
27709
27883
  this.scope.logger.warn(`Failed to load skill ${instance.name}: ${error.message}`);
27710
27884
  }
@@ -27727,6 +27901,10 @@ var init_skill_registry = __esm({
27727
27901
  await this.adoptFromChild(child, child.owner);
27728
27902
  }
27729
27903
  }
27904
+ const collision = findIdPathCollision(this.listAllInstances());
27905
+ if (collision) {
27906
+ throw new InvalidSkillError(collision.idOwner.metadata.name, describeIdPathCollision(collision));
27907
+ }
27730
27908
  this.reindex();
27731
27909
  logAvailabilityFiltering(
27732
27910
  "SkillRegistry",
@@ -27746,24 +27924,31 @@ var init_skill_registry = __esm({
27746
27924
  const adoptedRows = childRows.map((r) => this.relineage(r, prepend));
27747
27925
  this.adopted.set(child, adoptedRows);
27748
27926
  this.children.add(child);
27927
+ child.adopters.add(this);
27749
27928
  child.subscribe({ immediate: false }, () => {
27929
+ const previous = this.adopted.get(child) ?? [];
27750
27930
  const latest = child.listAllIndexed().map((r) => this.relineage(r, prepend));
27751
27931
  this.adopted.set(child, latest);
27752
27932
  this.reindex();
27753
- this.bump("reset");
27933
+ this.withdrawGoneAdoptedSkills(previous);
27754
27934
  for (const row of latest) {
27755
- row.instance.load().then((content) => {
27756
- this.storageProvider.add(content);
27935
+ const skillId = row.instance.metadata.id ?? row.instance.metadata.name;
27936
+ this.syncProvider(skillId, async () => {
27937
+ const content = await row.instance.load();
27938
+ await this.storageProvider.add(content);
27757
27939
  }).catch((error) => {
27758
27940
  this.scope.logger.warn(`Failed to reload adopted skill ${row.baseName}: ${error.message}`);
27759
27941
  });
27760
27942
  }
27943
+ this.bump("reset");
27761
27944
  });
27762
27945
  await Promise.all(
27763
27946
  adoptedRows.map(async (row) => {
27764
27947
  try {
27765
- const content = await row.instance.load();
27766
- await this.storageProvider.add(content);
27948
+ await this.syncProvider(row.instance.metadata.id ?? row.instance.metadata.name, async () => {
27949
+ const content = await row.instance.load();
27950
+ await this.storageProvider.add(content);
27951
+ });
27767
27952
  } catch (error) {
27768
27953
  this.scope.logger.warn(`Failed to load adopted skill ${row.baseName}: ${error.message}`);
27769
27954
  }
@@ -27772,6 +27957,37 @@ var init_skill_registry = __esm({
27772
27957
  this.reindex();
27773
27958
  this.bump("reset");
27774
27959
  }
27960
+ /**
27961
+ * The `skill://` namespaces this registry's skills are served in: its own, and that of every
27962
+ * registry that adopts it, directly or through another, sibling registries included.
27963
+ */
27964
+ namespacesServingThis() {
27965
+ const registries = /* @__PURE__ */ new Set();
27966
+ const climb = (registry) => {
27967
+ if (registries.has(registry)) return;
27968
+ registries.add(registry);
27969
+ for (const adopter of registry.adopters) climb(adopter);
27970
+ };
27971
+ climb(this);
27972
+ return [...registries].map((registry) => registry.liveSkills());
27973
+ }
27974
+ /**
27975
+ * This registry's skills and those of every registry it adopted, read from each registry's own rows.
27976
+ * An adopter's copy of a child's rows is refreshed only on the child's change event, which a
27977
+ * registration emits after it has committed its row and awaited the storage provider.
27978
+ */
27979
+ liveSkills() {
27980
+ const registries = /* @__PURE__ */ new Set();
27981
+ const skills = /* @__PURE__ */ new Set();
27982
+ const descend = (registry) => {
27983
+ if (registries.has(registry)) return;
27984
+ registries.add(registry);
27985
+ for (const row of [...registry.localRows, ...registry.dynamicRows]) skills.add(row.instance);
27986
+ for (const child of registry.children) descend(child);
27987
+ };
27988
+ descend(this);
27989
+ return [...skills];
27990
+ }
27775
27991
  /* -------------------- Public API -------------------- */
27776
27992
  /**
27777
27993
  * Get all skills in the registry.
@@ -27889,7 +28105,14 @@ var init_skill_registry = __esm({
27889
28105
  }
27890
28106
  }
27891
28107
  async search(query, options) {
27892
- const baseResults = await this.storageProvider.search(query, options);
28108
+ const topK = options?.topK;
28109
+ const hidden = this.hiddenFromProviderRead();
28110
+ const providerOptions = hidden.size > 0 && typeof topK === "number" && topK >= 0 ? { ...options, topK: topK + hidden.size } : options;
28111
+ let baseResults = await this.storageProvider.search(query, providerOptions);
28112
+ if (hidden.size > 0 || this.withdrawn.size > 0) {
28113
+ baseResults = baseResults.filter((r) => !hidden.has(r.metadata));
28114
+ if (typeof topK === "number" && topK >= 0) baseResults = baseResults.slice(0, topK);
28115
+ }
27893
28116
  if (this.dynamicContents.size === 0) return baseResults;
27894
28117
  const baseSeen = /* @__PURE__ */ new Set();
27895
28118
  let rewrites = 0;
@@ -27913,7 +28136,6 @@ var init_skill_registry = __esm({
27913
28136
  const overlayOnly = this.searchDynamicOverlay(query, options, baseSeen);
27914
28137
  if (overlayOnly.length === 0 && rewrites === 0) return baseResults;
27915
28138
  const merged = [...rewrittenBase, ...overlayOnly].sort((a, b) => b.score - a.score);
27916
- const topK = options?.topK;
27917
28139
  return typeof topK === "number" && topK >= 0 ? merged.slice(0, topK) : merged;
27918
28140
  }
27919
28141
  /**
@@ -27968,7 +28190,10 @@ var init_skill_registry = __esm({
27968
28190
  warning
27969
28191
  };
27970
28192
  }
28193
+ const hidden = this.hiddenFromProviderRead();
28194
+ if (hidden.has({ name: skillId })) return void 0;
27971
28195
  const result = await this.storageProvider.load(skillId);
28196
+ if (result && hidden.has(result.skill)) return void 0;
27972
28197
  return result ?? void 0;
27973
28198
  }
27974
28199
  /**
@@ -28004,8 +28229,12 @@ var init_skill_registry = __esm({
28004
28229
  * and `hasMore` stay correct.
28005
28230
  */
28006
28231
  async listSkills(options) {
28007
- if (this.dynamicContents.size === 0) return this.storageProvider.list(options);
28008
- const base = await this.listAllFromProvider(options);
28232
+ const hidden = this.hiddenFromProviderRead();
28233
+ if (this.dynamicContents.size === 0 && hidden.size === 0) {
28234
+ const page = await this.storageProvider.list(options);
28235
+ if (this.withdrawn.size === 0) return page;
28236
+ }
28237
+ const base = (await this.listAllFromProvider(options)).filter((meta) => !hidden.has(meta));
28009
28238
  const baseIds = /* @__PURE__ */ new Set();
28010
28239
  const merged = base.map((meta) => {
28011
28240
  const id = meta.id ?? meta.name;
@@ -28065,21 +28294,20 @@ var init_skill_registry = __esm({
28065
28294
  /**
28066
28295
  * Get total skill count.
28067
28296
  *
28068
- * Sums the provider count with overlay rows the provider does not know
28297
+ * Counts the provider's rows with overlay rows the provider does not know
28069
28298
  * about so callers see the same surface as `search` / `listSkills`.
28070
28299
  */
28071
28300
  async count(options) {
28072
- const baseCount = await this.storageProvider.count(options);
28073
- if (this.dynamicContents.size === 0) return baseCount;
28074
- const baseList = await this.storageProvider.list({
28075
- tags: options?.tags,
28076
- includeHidden: options?.includeHidden,
28077
- limit: Number.MAX_SAFE_INTEGER
28078
- });
28301
+ const hidden = this.hiddenFromProviderRead();
28302
+ if (this.dynamicContents.size === 0 && hidden.size === 0) {
28303
+ const counted = await this.storageProvider.count(options);
28304
+ if (this.withdrawn.size === 0) return counted;
28305
+ }
28306
+ const baseList = (await this.listAllFromProvider({ tags: options?.tags, includeHidden: options?.includeHidden })).filter((m) => !hidden.has(m));
28079
28307
  const baseIds = /* @__PURE__ */ new Set();
28080
- for (const m of baseList.skills) baseIds.add(m.id ?? m.name);
28308
+ for (const m of baseList) baseIds.add(m.id ?? m.name);
28081
28309
  const overlay = this.collectDynamicMetadata(options).filter((m) => !baseIds.has(m.id ?? m.name));
28082
- return baseCount + overlay.length;
28310
+ return baseList.length + overlay.length;
28083
28311
  }
28084
28312
  /**
28085
28313
  * Project a SkillContent into the SkillMetadata shape callers expect from
@@ -28369,10 +28597,6 @@ var init_skill_registry = __esm({
28369
28597
  throw new PublicMcpError("registerSkillContent: SkillContent.name is required", "INVALID_PARAMS");
28370
28598
  }
28371
28599
  const id = content.id;
28372
- const existingIdx = this.dynamicRows.findIndex((r) => r.instance.name === id);
28373
- if (existingIdx !== -1) {
28374
- this.dynamicRows.splice(existingIdx, 1);
28375
- }
28376
28600
  const metadata = {
28377
28601
  id,
28378
28602
  name: content.name,
@@ -28408,28 +28632,49 @@ var init_skill_registry = __esm({
28408
28632
  `[SkillRegistry] Failed to pre-load dynamic skill ${id} from source ${opts?.source ?? "unknown"}: ${error.message}`
28409
28633
  );
28410
28634
  }
28635
+ const existingIdx = this.dynamicRows.findIndex((r) => r.instance.name === id);
28636
+ const superseded = new Set(opts?.supersedes);
28637
+ const leaving = new Set(
28638
+ this.dynamicRows.filter((r) => r.instance.name === id || superseded.has(r.instance.name)).map((r) => r.instance)
28639
+ );
28640
+ for (const skills of this.namespacesServingThis()) {
28641
+ const collision = findIdPathCollision([...skills.filter((other) => !leaving.has(other)), instance]);
28642
+ if (collision) {
28643
+ throw new PublicMcpError(`registerSkillContent: ${describeIdPathCollision(collision)}`, "INVALID_PARAMS");
28644
+ }
28645
+ }
28646
+ if (existingIdx !== -1) {
28647
+ this.dynamicRows.splice(existingIdx, 1);
28648
+ }
28649
+ const mutable = this.asMutableProvider(this.storageProvider);
28650
+ const removed = this.dynamicRows.filter((r) => superseded.has(r.instance.name) && findIdPathCollision([r.instance, instance])).map((r) => r.instance.name);
28651
+ for (const removedId of removed) {
28652
+ this.withdrawDynamicSkill(removedId, mutable);
28653
+ }
28411
28654
  const lineage = this.owner ? [this.owner] : [];
28412
28655
  const row = this.makeRow(provideToken, instance, lineage, this);
28413
28656
  this.dynamicRows.push(row);
28414
28657
  this.dynamicContents.set(id, structuredClone(content));
28658
+ this.withdrawn.delete(id);
28415
28659
  const generation = (this.dynamicGenerations.get(id) ?? 0) + 1;
28416
28660
  this.dynamicGenerations.set(id, generation);
28417
- const mutable = this.asMutableProvider(this.storageProvider);
28418
- if (mutable) {
28419
- try {
28661
+ this.reindex();
28662
+ const providerWrites = mutable ? [
28663
+ ...removed.map((removedId) => this.removeFromProvider(mutable, removedId)),
28664
+ this.syncProvider(id, async () => {
28420
28665
  if (await mutable.exists(id)) {
28421
28666
  await mutable.update(id, content);
28422
28667
  } else {
28423
28668
  await mutable.add(content);
28424
28669
  }
28425
- } catch (error) {
28670
+ }).catch((error) => {
28426
28671
  this.scope.logger.warn(
28427
28672
  `[SkillRegistry] Failed to index dynamic skill ${id} in storage provider: ${error.message}`
28428
28673
  );
28429
- }
28430
- }
28431
- this.reindex();
28674
+ })
28675
+ ] : [];
28432
28676
  this.bump("reset");
28677
+ await Promise.all(providerWrites);
28433
28678
  let unregistered = false;
28434
28679
  const unregister = async () => {
28435
28680
  if (unregistered) return;
@@ -28442,32 +28687,99 @@ var init_skill_registry = __esm({
28442
28687
  this.scope.logger.debug(
28443
28688
  `[SkillRegistry] Registered dynamic skill ${id}${opts?.source ? ` (source: ${opts.source})` : ""}`
28444
28689
  );
28445
- return { id, unregister };
28690
+ return { id, unregister, removed };
28446
28691
  }
28447
28692
  /**
28448
28693
  * Remove a previously-registered dynamic skill by id.
28449
28694
  */
28450
28695
  async unregisterSkill(id) {
28451
- const idx = this.dynamicRows.findIndex((r) => r.instance.name === id);
28452
- if (idx === -1) return false;
28453
- this.dynamicRows.splice(idx, 1);
28454
- this.dynamicContents.delete(id);
28455
- this.dynamicGenerations.delete(id);
28696
+ if (!this.dynamicRows.some((r) => r.instance.name === id)) return false;
28456
28697
  const mutable = this.asMutableProvider(this.storageProvider);
28457
- if (mutable) {
28458
- try {
28459
- await mutable.remove(id);
28460
- } catch (error) {
28461
- this.scope.logger.warn(
28462
- `[SkillRegistry] Failed to remove dynamic skill ${id} from storage provider: ${error.message}`
28463
- );
28464
- }
28465
- }
28698
+ this.withdrawDynamicSkill(id, mutable);
28466
28699
  this.reindex();
28700
+ const removal = mutable ? this.removeFromProvider(mutable, id) : void 0;
28467
28701
  this.bump("reset");
28702
+ await removal;
28468
28703
  this.scope.logger.debug(`[SkillRegistry] Unregistered dynamic skill ${id}`);
28469
28704
  return true;
28470
28705
  }
28706
+ /**
28707
+ * Take a dynamic skill out of the registry, synchronously. Its generation advances so a handle
28708
+ * from an earlier registration stays a no-op, and while a mutable provider still holds its copy it
28709
+ * is withdrawn from provider-backed reads.
28710
+ */
28711
+ withdrawDynamicSkill(id, mutable) {
28712
+ this.dynamicRows.splice(
28713
+ this.dynamicRows.findIndex((r) => r.instance.name === id),
28714
+ 1
28715
+ );
28716
+ this.dynamicContents.delete(id);
28717
+ this.dynamicGenerations.set(id, (this.dynamicGenerations.get(id) ?? 0) + 1);
28718
+ if (mutable) this.withdrawn.set(id, ++this.withdrawals);
28719
+ }
28720
+ /** Remove a withdrawn skill's copy from the provider. If that fails it stays withdrawn from reads. */
28721
+ async removeFromProvider(mutable, id) {
28722
+ const withdrawal = this.withdrawn.get(id);
28723
+ try {
28724
+ await this.syncProvider(id, () => mutable.remove(id));
28725
+ if (this.withdrawn.get(id) === withdrawal) this.withdrawn.delete(id);
28726
+ } catch (error) {
28727
+ this.scope.logger.warn(
28728
+ `[SkillRegistry] Failed to remove skill ${id} from storage provider; it stays hidden from provider-backed reads: ${error.message}`
28729
+ );
28730
+ }
28731
+ }
28732
+ /**
28733
+ * Run a storage-provider write for one skill id after the ones queued before it for that id, so a
28734
+ * removal that completes late cannot delete the copy a later registration added. The returned
28735
+ * promise settles with the write; a failure does not hold back the next one.
28736
+ */
28737
+ syncProvider(id, write) {
28738
+ const run = (this.providerWrites.get(id) ?? Promise.resolve()).then(write);
28739
+ const settled = run.then(
28740
+ () => void 0,
28741
+ () => void 0
28742
+ );
28743
+ this.providerWrites.set(id, settled);
28744
+ void settled.then(() => {
28745
+ if (this.providerWrites.get(id) === settled) this.providerWrites.delete(id);
28746
+ });
28747
+ return run;
28748
+ }
28749
+ /**
28750
+ * After a child's change, take the skills that left it (and are not served here some other way)
28751
+ * out of this registry's storage provider too. The re-add on change only adds, so without this an
28752
+ * adopter's provider kept serving a skill the child had removed. Until the removal completes, and
28753
+ * if it fails, provider-backed reads skip it.
28754
+ */
28755
+ withdrawGoneAdoptedSkills(previous) {
28756
+ const mutable = this.asMutableProvider(this.storageProvider);
28757
+ if (!mutable) return;
28758
+ const skillIdOf = (entry) => entry.metadata.id ?? entry.metadata.name;
28759
+ const live = new Set(this.listAllIndexed().map((r) => skillIdOf(r.instance)));
28760
+ for (const id of live) this.withdrawn.delete(id);
28761
+ for (const row of previous) {
28762
+ const id = skillIdOf(row.instance);
28763
+ if (live.has(id) || this.withdrawn.has(id)) continue;
28764
+ this.withdrawn.set(id, ++this.withdrawals);
28765
+ void this.removeFromProvider(mutable, id);
28766
+ }
28767
+ }
28768
+ /**
28769
+ * Which provider-backed rows a read that starts now must skip: a skill withdrawn when the read
28770
+ * starts (its removal may complete while the provider answers, clearing the withdrawal, yet the
28771
+ * answer can still hold it), or withdrawn by the time the answer arrives.
28772
+ */
28773
+ hiddenFromProviderRead() {
28774
+ const atStart = new Set(this.withdrawn.keys());
28775
+ return {
28776
+ size: atStart.size,
28777
+ has: (meta) => {
28778
+ const id = meta.id ?? meta.name;
28779
+ return atStart.has(id) || this.withdrawn.has(id);
28780
+ }
28781
+ };
28782
+ }
28471
28783
  /* -------------------- Internal helpers -------------------- */
28472
28784
  asMutableProvider(provider) {
28473
28785
  const candidate = provider;
@@ -28730,12 +29042,13 @@ var init_plugin_utils = __esm({
28730
29042
  // libs/sdk/src/plugin/plugin.registry.ts
28731
29043
  import "reflect-metadata";
28732
29044
  import { tokenName as tokenName11 } from "@frontmcp/di";
28733
- var PluginRegistry;
29045
+ var installedPluginValues, PluginRegistry;
28734
29046
  var init_plugin_registry = __esm({
28735
29047
  "libs/sdk/src/plugin/plugin.registry.ts"() {
28736
29048
  "use strict";
28737
29049
  init_adapter_registry();
28738
29050
  init_common();
29051
+ init_dynamic_plugin();
28739
29052
  init_dynamic_utils();
28740
29053
  init_context_extension();
28741
29054
  init_errors();
@@ -28748,6 +29061,7 @@ var init_plugin_registry = __esm({
28748
29061
  init_skill_registry();
28749
29062
  init_tool_registry();
28750
29063
  init_plugin_utils();
29064
+ installedPluginValues = /* @__PURE__ */ new WeakSet();
28751
29065
  PluginRegistry = class _PluginRegistry extends RegistryAbstract {
28752
29066
  /** providers by token */
28753
29067
  pProviders = /* @__PURE__ */ new Map();
@@ -28936,8 +29250,15 @@ var init_plugin_registry = __esm({
28936
29250
  pluginInstance: new rec.provide(...depsInstances),
28937
29251
  dynamicProviders: rec.providers
28938
29252
  };
28939
- case "VALUE" /* VALUE */:
28940
- return { pluginInstance: rec.useValue, dynamicProviders: rec.providers };
29253
+ case "VALUE" /* VALUE */: {
29254
+ const value = rec.useValue;
29255
+ const init = installedPluginValues.has(value) ? initOptionsOf(value) : void 0;
29256
+ installedPluginValues.add(value);
29257
+ if (init && isDynamicPluginClass(rec.provide)) {
29258
+ return { pluginInstance: new rec.provide(init.options), dynamicProviders: rec.providers };
29259
+ }
29260
+ return { pluginInstance: value, dynamicProviders: rec.providers };
29261
+ }
28941
29262
  case "FACTORY" /* FACTORY */: {
28942
29263
  const args = [];
28943
29264
  for (const d of rec.inject()) args.push(await this.providers.resolveBootstrapDep(d));
@@ -29450,7 +29771,7 @@ var init_agent_instance = __esm({
29450
29771
  "hideFromDiscovery"
29451
29772
  ]);
29452
29773
  AgentInstance = class extends AgentEntry {
29453
- providers;
29774
+ providerRegistry;
29454
29775
  scope;
29455
29776
  hooks;
29456
29777
  /** The LLM adapter for this agent */
@@ -29468,11 +29789,11 @@ var init_agent_instance = __esm({
29468
29789
  constructor(record, providers, owner) {
29469
29790
  super(record);
29470
29791
  this.owner = owner;
29471
- this.providers = providers;
29792
+ this.providerRegistry = providers;
29472
29793
  this.name = record.metadata.id ?? record.metadata.name;
29473
29794
  this.id = record.metadata.id ?? record.metadata.name;
29474
29795
  this.fullName = this.owner.id + ":" + this.name;
29475
- this.scope = this.providers.getActiveScope();
29796
+ this.scope = this.providerRegistry.getActiveScope();
29476
29797
  this.hooks = this.scope.hooks;
29477
29798
  this.inputSchema = record.metadata.inputSchema ?? {};
29478
29799
  this.outputSchema = record.metadata.outputSchema;
@@ -29531,11 +29852,11 @@ var init_agent_instance = __esm({
29531
29852
  const adapterOptions = {
29532
29853
  providerResolver: {
29533
29854
  get: (token) => {
29534
- return this.providers.get(token);
29855
+ return this.providerRegistry.get(token);
29535
29856
  },
29536
29857
  tryGet: (token) => {
29537
29858
  try {
29538
- return this.providers.get(token);
29859
+ return this.providerRegistry.get(token);
29539
29860
  } catch {
29540
29861
  return void 0;
29541
29862
  }
@@ -29548,7 +29869,7 @@ var init_agent_instance = __esm({
29548
29869
  }
29549
29870
  };
29550
29871
  try {
29551
- const configService = this.providers.get(ConfigService);
29872
+ const configService = this.providerRegistry.get(ConfigService);
29552
29873
  if (configService) {
29553
29874
  adapterOptions.configResolver = this.createConfigResolver(configService);
29554
29875
  }
@@ -29656,7 +29977,7 @@ var init_agent_instance = __esm({
29656
29977
  const toolMetadata = this.buildAgentToolMetadata();
29657
29978
  const agentToolFunction = frontMcpTool(toolMetadata)(this.createAgentToolExecuteHandler());
29658
29979
  const toolRecord = normalizeTool(agentToolFunction);
29659
- this.agentToolInstance = new ToolInstance(toolRecord, this.providers, this.owner);
29980
+ this.agentToolInstance = new ToolInstance(toolRecord, this.providerRegistry, this.owner);
29660
29981
  await this.agentToolInstance.ready;
29661
29982
  this.scope.logger.debug(`Created agent tool instance: ${this.agentToolInstance.name} for agent ${this.name}`);
29662
29983
  } catch (error) {
@@ -29698,16 +30019,34 @@ var init_agent_instance = __esm({
29698
30019
  /**
29699
30020
  * Create the execute handler for the agent tool.
29700
30021
  *
29701
- * This handler is called when the tool is invoked through the standard
29702
- * tools:call-tool flow. It creates an AgentContext and runs the LLM loop.
30022
+ * The tool's `tools:call-tool` flow has already applied everything the agent declares
30023
+ * (authorities, rate limit, concurrency, timeout and plugin gates, copied onto the tool). The
30024
+ * handler then runs the agent through the `agents:call-agent` flow, so the hooks registered for
30025
+ * agent invocation (`AgentCallHook`, and the agent class's own hooks on that flow) run and the
30026
+ * agent's context gets the request's context providers. The flow's own gates are left to the
30027
+ * tool flow (`gatedBy`), so nothing is counted twice.
29703
30028
  */
29704
30029
  createAgentToolExecuteHandler() {
29705
30030
  return async (input, toolCtx) => {
29706
30031
  const authInfo = toolCtx.authInfo;
29707
- const agentCallExtra = { authInfo };
29708
- const agentContext = this.create(input, agentCallExtra);
29709
- const result = await agentContext.execute(input);
29710
- return result;
30032
+ const progressToken = toolCtx._progressTokenInternal;
30033
+ const requestId = toolCtx._jsonRpcRequestIdInternal;
30034
+ return this.scope.runFlowForOutput("agents:call-agent", {
30035
+ request: {
30036
+ method: "tools/call",
30037
+ params: {
30038
+ name: this.id,
30039
+ arguments: input,
30040
+ ...progressToken !== void 0 ? { _meta: { progressToken } } : {}
30041
+ }
30042
+ },
30043
+ ctx: {
30044
+ authInfo,
30045
+ ...requestId !== void 0 ? { requestId } : {},
30046
+ ...toolCtx.signal ? { signal: toolCtx.signal } : {}
30047
+ },
30048
+ gatedBy: "tools:call-tool"
30049
+ });
29711
30050
  };
29712
30051
  }
29713
30052
  /**
@@ -29721,6 +30060,10 @@ var init_agent_instance = __esm({
29721
30060
  getToolInstance() {
29722
30061
  return this.agentToolInstance;
29723
30062
  }
30063
+ /** The provider registry this agent was registered with (its app's). */
30064
+ get providers() {
30065
+ return this.providerRegistry;
30066
+ }
29724
30067
  /**
29725
30068
  * The tools this agent can call: those it declares and those its plugins contribute, as its private
29726
30069
  * scope holds them. Empty until the agent is initialized.
@@ -29770,7 +30113,7 @@ var init_agent_instance = __esm({
29770
30113
  case "VALUE" /* VALUE */:
29771
30114
  return this.record.useValue;
29772
30115
  case "FACTORY" /* FACTORY */:
29773
- return this.record.useFactory(this.providers);
30116
+ return this.record.useFactory(this.providerRegistry);
29774
30117
  case "ESM" /* ESM */:
29775
30118
  throw new AgentNotConfiguredError(`ESM agent "${this.name}" cannot be created via AgentInstance.create()`);
29776
30119
  case "REMOTE" /* REMOTE */:
@@ -29789,11 +30132,13 @@ var init_agent_instance = __esm({
29789
30132
  * @param llmAdapter - The LLM adapter (passed explicitly to avoid non-null assertion)
29790
30133
  */
29791
30134
  buildAgentCtorArgs(input, ctx, llmAdapter) {
29792
- const scope = this.providers.getActiveScope();
30135
+ const scope = this.providerRegistry.getActiveScope();
29793
30136
  return {
29794
30137
  metadata: this.metadata,
29795
30138
  input,
29796
- providers: this.providers,
30139
+ // The request's context-aware providers when the agents:call-agent flow built them, so
30140
+ // `this.context` and CONTEXT-scoped providers resolve inside the agent.
30141
+ providers: ctx.contextProviders ?? this.providerRegistry,
29797
30142
  logger: scope.logger,
29798
30143
  authInfo: ctx.authInfo,
29799
30144
  llmAdapter,
@@ -29864,7 +30209,8 @@ var init_agent_instance = __esm({
29864
30209
  await this.assertToolAuthorized(tool, ctx.authInfo, args);
29865
30210
  const runningTool = { name: tool.name, fullName: tool.fullName };
29866
30211
  return runOnSurface(AGENT_SURFACE, () => {
29867
- const toolContext = runAsTool(runningTool, () => tool.create(args, ctx));
30212
+ const { contextProviders: _agentProviders, ...toolCtx } = ctx;
30213
+ const toolContext = runAsTool(runningTool, () => tool.create(args, toolCtx));
29868
30214
  return runAsTool(runningTool, () => Promise.resolve(toolContext.execute(args)));
29869
30215
  });
29870
30216
  };
@@ -30403,6 +30749,53 @@ var init_agent_registry = __esm({
30403
30749
  }
30404
30750
  });
30405
30751
 
30752
+ // libs/sdk/src/channel/sources/completion-events.ts
30753
+ function completionEventsOf(scope) {
30754
+ let events = byScope.get(scope);
30755
+ if (!events) {
30756
+ events = new ScopeCompletionEvents();
30757
+ byScope.set(scope, events);
30758
+ }
30759
+ return events;
30760
+ }
30761
+ function completionOutputText(value) {
30762
+ if (value === void 0) return void 0;
30763
+ if (typeof value === "string") return value;
30764
+ try {
30765
+ return JSON.stringify(value);
30766
+ } catch {
30767
+ return String(value);
30768
+ }
30769
+ }
30770
+ var CompletionStream, ScopeCompletionEvents, byScope;
30771
+ var init_completion_events = __esm({
30772
+ "libs/sdk/src/channel/sources/completion-events.ts"() {
30773
+ "use strict";
30774
+ CompletionStream = class {
30775
+ listeners = /* @__PURE__ */ new Set();
30776
+ subscribe(listener) {
30777
+ this.listeners.add(listener);
30778
+ return () => {
30779
+ this.listeners.delete(listener);
30780
+ };
30781
+ }
30782
+ emit(event) {
30783
+ for (const listener of [...this.listeners]) {
30784
+ try {
30785
+ listener(event);
30786
+ } catch {
30787
+ }
30788
+ }
30789
+ }
30790
+ };
30791
+ ScopeCompletionEvents = class {
30792
+ agents = new CompletionStream();
30793
+ jobs = new CompletionStream();
30794
+ };
30795
+ byScope = /* @__PURE__ */ new WeakMap();
30796
+ }
30797
+ });
30798
+
30406
30799
  // libs/sdk/src/agent/flows/call-agent.flow.ts
30407
30800
  import { AuthorityDeniedError as AuthorityDeniedError7, resolveRequiredScopes as resolveRequiredScopes5 } from "@frontmcp/auth";
30408
30801
  import { ExecutionTimeoutError as ExecutionTimeoutError2, withTimeout as withTimeout2 } from "@frontmcp/guard";
@@ -30412,13 +30805,22 @@ var inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallA
30412
30805
  var init_call_agent_flow = __esm({
30413
30806
  "libs/sdk/src/agent/flows/call-agent.flow.ts"() {
30414
30807
  "use strict";
30808
+ init_completion_events();
30415
30809
  init_common();
30416
30810
  init_errors();
30417
30811
  init_hooks_utils();
30812
+ init_flow_context_providers();
30418
30813
  inputSchema15 = z60.object({
30419
30814
  request: CallToolRequestSchema3,
30420
30815
  // z.any() used because ctx is the MCP SDK's ToolCallExtra type which varies by SDK version
30421
- ctx: z60.any()
30816
+ ctx: z60.any(),
30817
+ /**
30818
+ * Set when the agent's `invoke_<agent>` tool runs this flow. That tool's `tools:call-tool` flow
30819
+ * has already applied the agent's authorities, rate limit, concurrency and timeout (they are
30820
+ * copied onto the tool), so this flow's stages for them leave the checks to it instead of
30821
+ * counting the call twice. Their hooks still run.
30822
+ */
30823
+ gatedBy: z60.literal("tools:call-tool").optional()
30422
30824
  });
30423
30825
  outputSchema14 = CallToolResultSchema3;
30424
30826
  stateSchema14 = z60.object({
@@ -30451,7 +30853,13 @@ var init_call_agent_flow = __esm({
30451
30853
  // Semaphore ticket for concurrency control (set by acquireSemaphore, used by releaseSemaphore)
30452
30854
  semaphoreTicket: z60.any().optional(),
30453
30855
  // A timed-out execute() that is still running; its concurrency slot is released when it settles
30454
- abandonedExecution: z60.instanceof(Promise).optional()
30856
+ abandonedExecution: z60.instanceof(Promise).optional(),
30857
+ // When execute() started, and the error it ended with (for the completion event)
30858
+ executionStartedAt: z60.number().optional(),
30859
+ executionError: z60.any().optional(),
30860
+ // The MCP result built from rawOutput (parseOutput), or why it could not be built
30861
+ parsedOutput: z60.any().optional(),
30862
+ outputError: z60.any().optional()
30455
30863
  });
30456
30864
  plan14 = {
30457
30865
  pre: [
@@ -30464,12 +30872,16 @@ var init_call_agent_flow = __esm({
30464
30872
  "acquireSemaphore"
30465
30873
  ],
30466
30874
  execute: ["validateInput", "execute", "validateOutput"],
30467
- finalize: ["releaseSemaphore", "releaseQuota", "finalize"]
30875
+ finalize: ["releaseSemaphore", "releaseQuota", "parseOutput", "emitCompletion", "finalize"]
30468
30876
  };
30469
30877
  name14 = "agents:call-agent";
30470
30878
  ({ Stage: Stage14 } = FlowHooksOf(name14));
30471
30879
  CallAgentFlow = class extends FlowBase {
30472
30880
  logger = this.scopeLogger.child("CallAgentFlow");
30881
+ /** Whether the `invoke_<agent>` tool's flow already applied the agent's gates. */
30882
+ get gatedByToolFlow() {
30883
+ return this.input.gatedBy === "tools:call-tool";
30884
+ }
30473
30885
  async parseInput() {
30474
30886
  this.logger.verbose("parseInput:start");
30475
30887
  let method;
@@ -30536,6 +30948,10 @@ var init_call_agent_flow = __esm({
30536
30948
  }
30537
30949
  async checkEntryAuthorities() {
30538
30950
  this.logger.verbose("checkEntryAuthorities:start");
30951
+ if (this.gatedByToolFlow) {
30952
+ this.logger.verbose("checkEntryAuthorities:skip (checked by the invoke tool flow)");
30953
+ return;
30954
+ }
30539
30955
  const engine = this.scope.authoritiesEngine;
30540
30956
  const ctxBuilder = this.scope.authoritiesContextBuilder;
30541
30957
  if (!engine || !ctxBuilder) {
@@ -30601,14 +31017,17 @@ var init_call_agent_flow = __esm({
30601
31017
  const progressToken = this.state.progressToken;
30602
31018
  const authInfo = this.state.authInfo;
30603
31019
  try {
30604
- const context = agent.create(input.arguments, { ...ctx, progressToken });
31020
+ const sessionKey = authInfo?.sessionId ?? "anonymous";
31021
+ const agentViews = await agent.providers.buildViews(sessionKey, new Map(this.deps), this.scope.providers);
31022
+ const contextProviders = new FlowContextProviders(agent.providers, agentViews.context);
31023
+ const context = agent.create(input.arguments, { ...ctx, progressToken, contextProviders });
30605
31024
  this.appendContextHooks(hooksBoundTo(this.scope.hooks.getClsHooks(agent.record.provide), context));
30606
31025
  context.mark("createAgentContext");
30607
31026
  const frontmcpContext = context.tryGetContext();
30608
31027
  const sdkAuthInfo = authInfo;
30609
- if (frontmcpContext && sdkAuthInfo?.transport?.sendElicitRequest) {
31028
+ const jsonRpcRequestId = this.state.jsonRpcRequestId;
31029
+ if (frontmcpContext && sdkAuthInfo?.transport?.sendElicitRequest && jsonRpcRequestId !== void 0) {
30610
31030
  const transport = sdkAuthInfo.transport;
30611
- const jsonRpcRequestId = this.state.jsonRpcRequestId;
30612
31031
  frontmcpContext.setTransport({
30613
31032
  sendElicitRequest: transport.sendElicitRequest.bind(transport),
30614
31033
  type: transport.type ?? "unknown",
@@ -30625,7 +31044,7 @@ var init_call_agent_flow = __esm({
30625
31044
  async acquireQuota() {
30626
31045
  this.logger.verbose("acquireQuota:start");
30627
31046
  const manager = this.scope.rateLimitManager;
30628
- if (!manager) {
31047
+ if (!manager || this.gatedByToolFlow) {
30629
31048
  this.state.agentContext?.mark("acquireQuota");
30630
31049
  this.logger.verbose("acquireQuota:done (no rate limit manager)");
30631
31050
  return;
@@ -30649,7 +31068,7 @@ var init_call_agent_flow = __esm({
30649
31068
  async acquireSemaphore() {
30650
31069
  this.logger.verbose("acquireSemaphore:start");
30651
31070
  const manager = this.scope.rateLimitManager;
30652
- if (!manager) {
31071
+ if (!manager || this.gatedByToolFlow) {
30653
31072
  this.state.agentContext?.mark("acquireSemaphore");
30654
31073
  this.logger.verbose("acquireSemaphore:done (no rate limit manager)");
30655
31074
  return;
@@ -30694,7 +31113,8 @@ var init_call_agent_flow = __esm({
30694
31113
  }
30695
31114
  agentContext.mark("execute");
30696
31115
  const startTime = Date.now();
30697
- const timeoutMs = agent.metadata.timeout?.executeMs ?? agent.metadata.execution?.timeout ?? this.scope.rateLimitManager?.config?.defaultTimeout?.executeMs;
31116
+ this.state.set("executionStartedAt", startTime);
31117
+ const timeoutMs = this.gatedByToolFlow ? agent.metadata.timeout?.executeMs === void 0 ? agent.metadata.execution?.timeout : void 0 : agent.metadata.timeout?.executeMs ?? agent.metadata.execution?.timeout ?? this.scope.rateLimitManager?.config?.defaultTimeout?.executeMs;
30698
31118
  const running = (async () => {
30699
31119
  agentContext.output = await agentContext.execute(agentContext.input);
30700
31120
  })();
@@ -30705,6 +31125,7 @@ var init_call_agent_flow = __esm({
30705
31125
  });
30706
31126
  this.logger.verbose("execute:done");
30707
31127
  } catch (error) {
31128
+ this.state.set("executionError", error);
30708
31129
  if (error instanceof ExecutionTimeoutError2) {
30709
31130
  this.logger.warn("execute: agent execution timed out", {
30710
31131
  agent: agent.metadata.name,
@@ -30713,6 +31134,7 @@ var init_call_agent_flow = __esm({
30713
31134
  this.state.set("abandonedExecution", running);
30714
31135
  throw error;
30715
31136
  }
31137
+ if (this.gatedByToolFlow) throw error;
30716
31138
  throw new AgentExecutionError(agent.metadata.name, error instanceof Error ? error : void 0);
30717
31139
  }
30718
31140
  }
@@ -30750,9 +31172,34 @@ var init_call_agent_flow = __esm({
30750
31172
  this.state.agentContext?.mark("releaseQuota");
30751
31173
  this.logger.verbose("releaseQuota:done");
30752
31174
  }
31175
+ async parseOutput() {
31176
+ const { agent, rawOutput } = this.state;
31177
+ if (!agent || rawOutput === void 0) return;
31178
+ const parseResult = agent.safeParseOutput(rawOutput);
31179
+ if (parseResult.success) this.state.set("parsedOutput", parseResult.data);
31180
+ else this.state.set("outputError", parseResult.error);
31181
+ }
31182
+ async emitCompletion() {
31183
+ const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
31184
+ if (!agent || executionStartedAt === void 0) return;
31185
+ if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
31186
+ const failure = executionError ?? (outputError !== void 0 ? new InvalidOutputError() : void 0);
31187
+ const failed = failure !== void 0;
31188
+ const requestId = this.tryGetContext()?.requestId;
31189
+ const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
31190
+ completionEventsOf(this.scope).agents.emit({
31191
+ agentId: agent.id,
31192
+ agentName: agent.name,
31193
+ status: failed ? "error" : "success",
31194
+ durationMs: Date.now() - executionStartedAt,
31195
+ ...failed ? { error: failure instanceof Error ? failure.message : String(failure) } : { output: completionOutputText(agentContext?.output) },
31196
+ ...requestId ? { runId: requestId } : {},
31197
+ ...sessionId ? { sessionId } : {}
31198
+ });
31199
+ }
30753
31200
  async finalize() {
30754
31201
  this.logger.verbose("finalize:start");
30755
- const { agent, rawOutput, executionMeta } = this.state;
31202
+ const { agent, rawOutput, parsedOutput, outputError, executionMeta } = this.state;
30756
31203
  if (!agent) {
30757
31204
  this.logger.verbose("finalize: skipping (no agent in state)");
30758
31205
  return;
@@ -30761,15 +31208,14 @@ var init_call_agent_flow = __esm({
30761
31208
  this.logger.verbose("finalize: skipping (no output - execute stage likely failed)");
30762
31209
  return;
30763
31210
  }
30764
- const parseResult = agent.safeParseOutput(rawOutput);
30765
- if (!parseResult.success) {
31211
+ if (outputError !== void 0 || parsedOutput === void 0) {
30766
31212
  this.logger.error("finalize: output validation failed", {
30767
31213
  agent: agent.metadata.name,
30768
- errors: parseResult.error
31214
+ errors: outputError
30769
31215
  });
30770
31216
  throw new InvalidOutputError();
30771
31217
  }
30772
- const result = parseResult.data;
31218
+ const result = parsedOutput;
30773
31219
  if (executionMeta) {
30774
31220
  result._meta = {
30775
31221
  ...result._meta,
@@ -30835,6 +31281,12 @@ var init_call_agent_flow = __esm({
30835
31281
  __decorateClass([
30836
31282
  Stage14("releaseQuota")
30837
31283
  ], CallAgentFlow.prototype, "releaseQuota", 1);
31284
+ __decorateClass([
31285
+ Stage14("parseOutput")
31286
+ ], CallAgentFlow.prototype, "parseOutput", 1);
31287
+ __decorateClass([
31288
+ Stage14("emitCompletion")
31289
+ ], CallAgentFlow.prototype, "emitCompletion", 1);
30838
31290
  __decorateClass([
30839
31291
  Stage14("finalize")
30840
31292
  ], CallAgentFlow.prototype, "finalize", 1);
@@ -31341,13 +31793,46 @@ var init_request_validation = __esm({
31341
31793
  });
31342
31794
 
31343
31795
  // libs/sdk/src/transport/mcp-20260728/request-state.ts
31344
- import { base64urlDecode as base64urlDecode2, base64urlEncode as base64urlEncode2, hmacSha256, randomBytes as randomBytes3, sha256Hex as sha256Hex5, timingSafeEqual } from "@frontmcp/utils";
31345
- function getRequestStateKey() {
31796
+ import {
31797
+ base64urlDecode as base64urlDecode2,
31798
+ base64urlEncode as base64urlEncode2,
31799
+ getEnv as getEnv4,
31800
+ hmacSha256,
31801
+ isProduction as isProduction4,
31802
+ randomBytes as randomBytes3,
31803
+ sha256Hex as sha256Hex5,
31804
+ timingSafeEqual
31805
+ } from "@frontmcp/utils";
31806
+ function configuredSecret(name44) {
31807
+ const value = getEnv4(name44);
31808
+ return value && value.trim().length > 0 ? value : void 0;
31809
+ }
31810
+ function resolveKey() {
31346
31811
  if (cachedKey) return cachedKey;
31347
- const secret = process.env["VAULT_SECRET"] ?? process.env["JWT_SECRET"];
31348
- cachedKey = secret ? encoder.encode(secret) : randomBytes3(32);
31812
+ const vault = configuredSecret("VAULT_SECRET");
31813
+ const jwt = vault ? void 0 : configuredSecret("JWT_SECRET");
31814
+ if (vault) cachedKey = { key: encoder.encode(vault), source: "VAULT_SECRET" };
31815
+ else if (jwt) cachedKey = { key: encoder.encode(jwt), source: "JWT_SECRET" };
31816
+ else cachedKey = { key: randomBytes3(32), source: "per-process" };
31349
31817
  return cachedKey;
31350
31818
  }
31819
+ function getRequestStateKey() {
31820
+ return resolveKey().key;
31821
+ }
31822
+ function getRequestStateKeySource() {
31823
+ return resolveKey().source;
31824
+ }
31825
+ function warnIfRequestStateKeyNotShared(options) {
31826
+ if (warnedKeyNotShared || !isProduction4()) return;
31827
+ const { metadata } = options;
31828
+ const persistence = metadata.transport?.persistence;
31829
+ const sharesState = Boolean(metadata.redis) || typeof persistence === "object" && persistence !== null;
31830
+ if (!sharesState || getRequestStateKeySource() !== "per-process") return;
31831
+ warnedKeyNotShared = true;
31832
+ options.logger.warn(
31833
+ "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."
31834
+ );
31835
+ }
31351
31836
  function computeRequestBinding(method, params) {
31352
31837
  const salient = {};
31353
31838
  for (const key of ["name", "uri", "arguments"]) {
@@ -31397,12 +31882,13 @@ function decodeRequestState(state, expected) {
31397
31882
  if (payload.b !== expected.binding) return { ok: false, reason: "request-mismatch" };
31398
31883
  return { ok: true, responses: payload.r };
31399
31884
  }
31400
- var DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, encoder, decoder;
31885
+ var DEFAULT_REQUEST_STATE_TTL_MS, MAX_REQUEST_STATE_BYTES, cachedKey, warnedKeyNotShared, encoder, decoder;
31401
31886
  var init_request_state = __esm({
31402
31887
  "libs/sdk/src/transport/mcp-20260728/request-state.ts"() {
31403
31888
  "use strict";
31404
31889
  DEFAULT_REQUEST_STATE_TTL_MS = 10 * 60 * 1e3;
31405
31890
  MAX_REQUEST_STATE_BYTES = 256 * 1024;
31891
+ warnedKeyNotShared = false;
31406
31892
  encoder = new TextEncoder();
31407
31893
  decoder = new TextDecoder();
31408
31894
  }
@@ -31636,10 +32122,17 @@ var init_mrtr = __esm({
31636
32122
  /** Per-kind call counters, used to derive stable keys across a replay. */
31637
32123
  counters = { elicitation: 0, sampling: 0, roots: 0 };
31638
32124
  clientCapabilities;
32125
+ /**
32126
+ * Whether the client itself declared this revision. `false` when the SERVER served an
32127
+ * unversioned call as 2026-07-28 (`transport.defaultProtocolVersion`, the Worker default): such a
32128
+ * client, typically a 2025-03-26 one, never agreed to the revision's input-request contract.
32129
+ */
32130
+ clientDeclaredRevision;
31639
32131
  binding;
31640
32132
  constructor(params) {
31641
32133
  this.responses = { ...params.carriedResponses ?? {}, ...params.inputResponses ?? {} };
31642
32134
  this.clientCapabilities = params.clientCapabilities;
32135
+ this.clientDeclaredRevision = params.clientDeclaredRevision ?? true;
31643
32136
  this.binding = params.binding;
31644
32137
  }
31645
32138
  /** True when the client declared the capability a given request kind needs. */
@@ -31992,7 +32485,7 @@ function shouldCreateTask(params) {
31992
32485
  return { kind: "create", owner: ownership.owner };
31993
32486
  }
31994
32487
  async function resumeTask(params) {
31995
- const { scope, record, authInfo, clientCapabilities, frontmcpContext } = params;
32488
+ const { scope, record, authInfo, clientCapabilities, frontmcpContext, clientDeclaredRevision } = params;
31996
32489
  const registry = scope.tasks;
31997
32490
  const runner = registry?.runner;
31998
32491
  if (!runner) {
@@ -32003,6 +32496,8 @@ async function resumeTask(params) {
32003
32496
  new MrtrExchange({
32004
32497
  carriedResponses: record.inputResponses ?? {},
32005
32498
  clientCapabilities,
32499
+ // A caller that never declared the revision is refused an elicitation as a legacy caller is.
32500
+ clientDeclaredRevision,
32006
32501
  binding: {
32007
32502
  principal: resolveTaskPrincipal(authInfo),
32008
32503
  binding: computeRequestBinding("tasks/resume", { name: record.taskId })
@@ -32049,6 +32544,7 @@ async function dispatch20260728(options) {
32049
32544
  scope,
32050
32545
  body,
32051
32546
  clientCapabilities,
32547
+ clientDeclaredRevision,
32052
32548
  frontmcpContext,
32053
32549
  authInfo,
32054
32550
  isAnonymous,
@@ -32095,7 +32591,8 @@ async function dispatch20260728(options) {
32095
32591
  record,
32096
32592
  authInfo: { ...authInfo ?? {}, sessionId: ownership.owner },
32097
32593
  clientCapabilities,
32098
- frontmcpContext
32594
+ frontmcpContext,
32595
+ clientDeclaredRevision
32099
32596
  })
32100
32597
  });
32101
32598
  return outcome.kind === "result" ? { kind: "result", result: decorate(outcome.result) } : outcome;
@@ -32119,13 +32616,15 @@ async function dispatch20260728(options) {
32119
32616
  };
32120
32617
  const carried = decodeRequestState(params["requestState"], binding);
32121
32618
  if (!carried.ok && carried.reason !== "absent") {
32122
- scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason });
32619
+ 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;
32620
+ scope.logger.warn("mcp-20260728: rejected requestState", { method, reason: carried.reason, ...hint && { hint } });
32123
32621
  }
32124
32622
  const exchange = new MrtrExchange({
32125
32623
  inputResponses: params["inputResponses"],
32126
32624
  carriedResponses: carried.ok ? carried.responses : {},
32127
32625
  clientCapabilities,
32128
- binding
32626
+ binding,
32627
+ clientDeclaredRevision
32129
32628
  });
32130
32629
  frontmcpContext?.setMrtrExchange(exchange);
32131
32630
  if (notificationSink) frontmcpContext?.setRequestNotificationSink(notificationSink);
@@ -32272,6 +32771,7 @@ var McpStatelessError, DEFAULT_CLIENT_INFO, McpStatelessClient, TASKS_CLIENT_CAP
32272
32771
  var init_mcp_stateless_client = __esm({
32273
32772
  "libs/sdk/src/transport/mcp-20260728/client/mcp-stateless.client.ts"() {
32274
32773
  "use strict";
32774
+ init_list_all_pages_utils();
32275
32775
  init_header_codec();
32276
32776
  init_protocol_20260728_constants();
32277
32777
  init_tasks_extension();
@@ -32312,10 +32812,22 @@ var init_mcp_stateless_client = __esm({
32312
32812
  * The spec requires this: one malformed tool definition must not prevent the
32313
32813
  * other tools from being used, so the offender is excluded and logged rather
32314
32814
  * than failing the call.
32815
+ *
32816
+ * Every page is fetched (`nextCursor` is followed to the end), since the
32817
+ * schemas cached here are what later `tools/call` headers are derived from.
32315
32818
  */
32316
32819
  async listTools() {
32317
- const result = await this.request("tools/list");
32318
- const tools = Array.isArray(result["tools"]) ? result["tools"] : [];
32820
+ const tools = await listAllPages(
32821
+ "tools/list",
32822
+ async (cursor) => {
32823
+ const result = await this.request("tools/list", cursor ? { cursor } : {});
32824
+ return {
32825
+ items: Array.isArray(result["tools"]) ? result["tools"] : [],
32826
+ nextCursor: typeof result["nextCursor"] === "string" ? result["nextCursor"] : void 0
32827
+ };
32828
+ },
32829
+ (message) => new McpStatelessError(-32603, message)
32830
+ );
32319
32831
  const usable = [];
32320
32832
  for (const tool of tools) {
32321
32833
  const validation = validateHeaderParams(tool["inputSchema"]);
@@ -32334,11 +32846,13 @@ var init_mcp_stateless_client = __esm({
32334
32846
  async readResource(uri) {
32335
32847
  return this.request("resources/read", { uri });
32336
32848
  }
32337
- async listResources() {
32338
- return this.request("resources/list");
32849
+ /** One page of `resources/list`; pass the previous page's `nextCursor` for the next. */
32850
+ async listResources(cursor) {
32851
+ return this.request("resources/list", cursor ? { cursor } : {});
32339
32852
  }
32340
- async listPrompts() {
32341
- return this.request("prompts/list");
32853
+ /** One page of `prompts/list`; pass the previous page's `nextCursor` for the next. */
32854
+ async listPrompts(cursor) {
32855
+ return this.request("prompts/list", cursor ? { cursor } : {});
32342
32856
  }
32343
32857
  async getPrompt(name44, args = {}) {
32344
32858
  return this.request("prompts/get", { name: name44, arguments: args });
@@ -32624,6 +33138,10 @@ var init_mcp_20260728 = __esm({
32624
33138
  });
32625
33139
 
32626
33140
  // libs/sdk/src/remote-mcp/mcp-stateless-client.adapter.ts
33141
+ function nextCursorOf(result) {
33142
+ const next = result["nextCursor"];
33143
+ return typeof next === "string" && next.length > 0 ? { nextCursor: next } : {};
33144
+ }
32627
33145
  async function negotiateRemoteProtocol(url, configured, headers, fetchImpl = fetch) {
32628
33146
  if (configured === "2026-07-28") return "2026-07-28";
32629
33147
  if (configured !== "auto") return "legacy";
@@ -32666,16 +33184,22 @@ var init_mcp_stateless_client_adapter = __esm({
32666
33184
  async callTool(params) {
32667
33185
  return this.client.callTool(params.name, params.arguments ?? {});
32668
33186
  }
32669
- async listResources() {
32670
- const result = await this.client.listResources();
32671
- return { resources: Array.isArray(result["resources"]) ? result["resources"] : [] };
33187
+ async listResources(params) {
33188
+ const result = await this.client.listResources(params?.cursor);
33189
+ return {
33190
+ resources: Array.isArray(result["resources"]) ? result["resources"] : [],
33191
+ ...nextCursorOf(result)
33192
+ };
32672
33193
  }
32673
33194
  async readResource(params) {
32674
33195
  return this.client.readResource(params.uri);
32675
33196
  }
32676
- async listPrompts() {
32677
- const result = await this.client.listPrompts();
32678
- return { prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [] };
33197
+ async listPrompts(params) {
33198
+ const result = await this.client.listPrompts(params?.cursor);
33199
+ return {
33200
+ prompts: Array.isArray(result["prompts"]) ? result["prompts"] : [],
33201
+ ...nextCursorOf(result)
33202
+ };
32679
33203
  }
32680
33204
  async getPrompt(params) {
32681
33205
  return this.client.getPrompt(params.name, params.arguments ?? {});
@@ -33202,6 +33726,7 @@ var DEFAULT_RETRY_OPTIONS2, DEFAULT_CIRCUIT_BREAKER_OPTIONS, DEFAULT_HEALTH_CHEC
33202
33726
  var init_mcp_client_service = __esm({
33203
33727
  "libs/sdk/src/remote-mcp/mcp-client.service.ts"() {
33204
33728
  "use strict";
33729
+ init_list_all_pages_utils();
33205
33730
  init_mcp_error();
33206
33731
  init_remote_errors();
33207
33732
  init_transport_errors();
@@ -33805,10 +34330,15 @@ var init_mcp_client_service = __esm({
33805
34330
  const httpOptions = request.transportOptions;
33806
34331
  return httpOptions?.fallbackToSSE ?? true;
33807
34332
  }
34333
+ // Each list follows `nextCursor` to the end: a FrontMCP remote pages its lists
34334
+ // (40 per page by default), and proxying only the first page silently dropped
34335
+ // everything after it.
33808
34336
  async listToolsInternal(connection) {
33809
34337
  try {
33810
- const result = await connection.client.listTools();
33811
- return result.tools || [];
34338
+ return await listAllPages("tools/list", async (cursor) => {
34339
+ const result = await connection.client.listTools(cursor ? { cursor } : void 0);
34340
+ return { items: result.tools, nextCursor: result.nextCursor };
34341
+ });
33812
34342
  } catch (error) {
33813
34343
  this.logger.warn(`Failed to list tools: ${error.message}`);
33814
34344
  return [];
@@ -33816,8 +34346,10 @@ var init_mcp_client_service = __esm({
33816
34346
  }
33817
34347
  async listResourcesInternal(connection) {
33818
34348
  try {
33819
- const result = await connection.client.listResources();
33820
- return result.resources || [];
34349
+ return await listAllPages("resources/list", async (cursor) => {
34350
+ const result = await connection.client.listResources(cursor ? { cursor } : void 0);
34351
+ return { items: result.resources, nextCursor: result.nextCursor };
34352
+ });
33821
34353
  } catch (error) {
33822
34354
  this.logger.warn(`Failed to list resources: ${error.message}`);
33823
34355
  return [];
@@ -33825,8 +34357,10 @@ var init_mcp_client_service = __esm({
33825
34357
  }
33826
34358
  async listResourceTemplatesInternal(connection) {
33827
34359
  try {
33828
- const result = await connection.client.listResourceTemplates();
33829
- return result.resourceTemplates || [];
34360
+ return await listAllPages("resources/templates/list", async (cursor) => {
34361
+ const result = await connection.client.listResourceTemplates(cursor ? { cursor } : void 0);
34362
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
34363
+ });
33830
34364
  } catch (error) {
33831
34365
  this.logger.warn(`Failed to list resource templates: ${error.message}`);
33832
34366
  return [];
@@ -33834,8 +34368,10 @@ var init_mcp_client_service = __esm({
33834
34368
  }
33835
34369
  async listPromptsInternal(connection) {
33836
34370
  try {
33837
- const result = await connection.client.listPrompts();
33838
- return result.prompts || [];
34371
+ return await listAllPages("prompts/list", async (cursor) => {
34372
+ const result = await connection.client.listPrompts(cursor ? { cursor } : void 0);
34373
+ return { items: result.prompts, nextCursor: result.nextCursor };
34374
+ });
33839
34375
  } catch (error) {
33840
34376
  this.logger.warn(`Failed to list prompts: ${error.message}`);
33841
34377
  return [];
@@ -38134,6 +38670,8 @@ var init_oauth_authorize_flow = __esm({
38134
38670
  if (state) {
38135
38671
  url.searchParams.set("state", state);
38136
38672
  }
38673
+ const issuer = this.resolveIssuer(this.rawInput.request);
38674
+ if (issuer) url.searchParams.set("iss", issuer);
38137
38675
  this.respond(httpRespond.redirect(url.toString()));
38138
38676
  return;
38139
38677
  }
@@ -40292,7 +40830,7 @@ var init_oauth_provider_callback_flow = __esm({
40292
40830
  if (session.state) {
40293
40831
  url.searchParams.set("state", session.state);
40294
40832
  }
40295
- url.searchParams.set("iss", this.getLocalAuth().issuer);
40833
+ url.searchParams.set("iss", this.getLocalAuth().issuerFor(this.rawInput.request));
40296
40834
  this.logger.info(
40297
40835
  `Federated auth complete: ${selectedProviderIds.length} providers authenticated, redirecting to client`
40298
40836
  );
@@ -41205,7 +41743,7 @@ var init_oauth_userinfo_flow = __esm({
41205
41743
  return;
41206
41744
  }
41207
41745
  const localAuth = this.scope.auth;
41208
- const issuer = localAuth.issuerFor?.(this.rawInput.request);
41746
+ const issuer = localAuth.acceptedIssuersFor?.(this.rawInput.request);
41209
41747
  const result = await localAuth.verifyGatewayToken(token, baseUrl, void 0, issuer);
41210
41748
  if (!result.ok) {
41211
41749
  this.logger.warn("userinfo: token verification failed", { error: result.error });
@@ -41264,6 +41802,7 @@ var init_oauth_userinfo_flow = __esm({
41264
41802
  // libs/sdk/src/auth/flows/session.verify.flow.ts
41265
41803
  import "reflect-metadata";
41266
41804
  import {
41805
+ anonymousCallerClaims,
41267
41806
  buildInsufficientScopeHeader,
41268
41807
  buildInvalidTokenHeader,
41269
41808
  buildUnauthorizedHeader,
@@ -41300,7 +41839,14 @@ var init_session_verify_flow = __esm({
41300
41839
  init_common();
41301
41840
  init_notification_service();
41302
41841
  init_session_id_utils();
41303
- inputSchema23 = httpRequestInputSchema;
41842
+ inputSchema23 = httpRequestInputSchema.extend({
41843
+ /**
41844
+ * The request is served without a session (MCP 2026-07-28): an anonymous or static-key caller is
41845
+ * identified for this request only, and no session is minted for it (#629). Minting one needs
41846
+ * `MCP_SESSION_SECRET`, for a session id the client would never present.
41847
+ */
41848
+ sessionless: z72.boolean().optional()
41849
+ });
41304
41850
  stateSchema22 = z72.object({
41305
41851
  baseUrl: z72.string().min(1),
41306
41852
  authorizationHeader: z72.string().optional(),
@@ -41358,20 +41904,25 @@ var init_session_verify_flow = __esm({
41358
41904
  * Encapsulates the shared logic for session creation, payload encryption, and user derivation.
41359
41905
  */
41360
41906
  createAnonymousSession(options) {
41361
- const { authMode, issuer, scopes = ["anonymous"], subject, sessionIdHeader } = options;
41907
+ const { authMode, subject, sessionIdHeader } = options;
41362
41908
  this.logger.verbose("createAnonymousSession", { authMode, hasExistingSession: !!sessionIdHeader });
41909
+ if (this.rawInput.sessionless) {
41910
+ this.respond({
41911
+ kind: "authorized",
41912
+ authorization: { token: "", user: anonymousCallerClaims(options, randomUUID20()) }
41913
+ });
41914
+ return;
41915
+ }
41363
41916
  const machineId = getMachineId2();
41364
41917
  const authSignature = subject ? `${authMode}:${subject}` : authMode;
41365
41918
  if (sessionIdHeader) {
41366
- const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
41367
- if (existingPayload && existingPayload.nodeId === machineId) {
41919
+ const body = this.rawInput.request.body;
41920
+ const isInitialize2 = typeof body === "object" && body !== null && "method" in body && body.method === "initialize";
41921
+ const decrypted = decryptPublicSession(sessionIdHeader, authSignature);
41922
+ const existingPayload = decrypted && isInitialize2 && decrypted.nodeId !== machineId ? void 0 : decrypted;
41923
+ if (existingPayload) {
41368
41924
  const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
41369
- const user2 = {
41370
- sub: subject ?? `anon:${anonId}`,
41371
- iss: issuer,
41372
- name: subject ? "Static token" : "Anonymous",
41373
- scope: scopes.join(" ")
41374
- };
41925
+ const user2 = anonymousCallerClaims(options, anonId);
41375
41926
  this.respond({
41376
41927
  kind: "authorized",
41377
41928
  authorization: {
@@ -41385,13 +41936,8 @@ var init_session_verify_flow = __esm({
41385
41936
  this.logger.verbose("createAnonymousSession: ignoring unrecognized client session id; minting a fresh one");
41386
41937
  }
41387
41938
  const now = Date.now();
41388
- const user = {
41389
- sub: subject ?? `anon:${randomUUID20()}`,
41390
- iss: issuer,
41391
- name: subject ? "Static token" : "Anonymous",
41392
- scope: scopes.join(" ")
41393
- };
41394
41939
  const uuid = randomUUID20();
41940
+ const user = anonymousCallerClaims(options, uuid);
41395
41941
  const platformDetectionConfig = this.scope.metadata.transport?.platformDetection;
41396
41942
  const platformType = detectPlatformFromUserAgent(this.state.userAgent, platformDetectionConfig);
41397
41943
  const payload = {
@@ -41584,7 +42130,7 @@ var init_session_verify_flow = __esm({
41584
42130
  } else {
41585
42131
  const configuredAudience = isOrchestratedMode(authOptions) ? authOptions.expectedAudience : void 0;
41586
42132
  const resource = computeResource(this.rawInput.request, this.scope.entryPath, this.scope.routeBase);
41587
- const issuer = auth.issuerFor?.(this.rawInput.request);
42133
+ const issuer = auth.acceptedIssuersFor?.(this.rawInput.request);
41588
42134
  verify = auth.verifyGatewayToken(token, this.state.required.baseUrl, configuredAudience ?? resource, issuer);
41589
42135
  }
41590
42136
  const result = await verify;
@@ -41834,7 +42380,7 @@ var init_well_known_jwks_flow = __esm({
41834
42380
  import "reflect-metadata";
41835
42381
  import { advertisedScopes } from "@frontmcp/auth";
41836
42382
  import { z as z74 } from "@frontmcp/lazy-zod";
41837
- import { isProduction as isProduction4 } from "@frontmcp/utils";
42383
+ import { isProduction as isProduction5 } from "@frontmcp/utils";
41838
42384
  function resolveDcrEnabled(auth) {
41839
42385
  if (auth && isLocalMode(auth)) {
41840
42386
  const dcr = auth.dcr;
@@ -41842,7 +42388,7 @@ function resolveDcrEnabled(auth) {
41842
42388
  return dcr.enabled;
41843
42389
  }
41844
42390
  }
41845
- return !isProduction4();
42391
+ return !isProduction5();
41846
42392
  }
41847
42393
  var inputSchema25, AuthServerMetadataSchema, outputSchema23, wellKnownAsStateSchema, wellKnownAsPlan, name24, Stage24, WellKnownAsFlow;
41848
42394
  var init_well_known_oauth_authorization_server_flow = __esm({
@@ -41877,6 +42423,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41877
42423
  wellKnownAsStateSchema = z74.object({
41878
42424
  baseUrl: z74.string().min(1),
41879
42425
  // baseUrl + entryPrefix (unsuffixed)
42426
+ // The issuer this server names in answer to the request (`LocalPrimaryAuth.issuerFor`, #629): the
42427
+ // one on its authorization responses and tokens. Without it, `baseUrl`.
42428
+ issuer: z74.string().min(1).optional(),
41880
42429
  // Root origin (proto://host WITHOUT the entryPath prefix). The OAuth
41881
42430
  // endpoints (/oauth/authorize, /oauth/token, /oauth/register, …) are
41882
42431
  // registered at literal root paths by their flows, NOT under entryPath, so
@@ -41908,6 +42457,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41908
42457
  if (!request) throw new FlowInputMissingError("request", "well-known:oauth-authorization-server");
41909
42458
  const { metadata } = this.scope;
41910
42459
  const baseUrl = getRequestBaseUrl(request, this.scope.entryPath);
42460
+ const issuer = this.scope.auth?.issuerFor?.(
42461
+ request
42462
+ );
41911
42463
  const oauthBaseUrl = getRequestBaseUrl(request);
41912
42464
  let cimdEnabled = true;
41913
42465
  if (metadata.auth && isOrchestratedMode(metadata.auth)) {
@@ -41916,6 +42468,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41916
42468
  this.state.set(
41917
42469
  wellKnownAsStateSchema.parse({
41918
42470
  baseUrl,
42471
+ issuer,
41919
42472
  oauthBaseUrl,
41920
42473
  // The scopes this server grants (`allowedScopes`, #262): the literal
41921
42474
  // entries; a `*` glob names no scope a client could ask for.
@@ -41930,6 +42483,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41930
42483
  );
41931
42484
  }
41932
42485
  async collectData() {
42486
+ const { issuer } = this.state;
41933
42487
  const {
41934
42488
  baseUrl,
41935
42489
  oauthBaseUrl,
@@ -41940,7 +42494,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41940
42494
  cimdEnabled
41941
42495
  } = this.state.required;
41942
42496
  if (isOrchestrated) {
41943
- const baseIssuer = `${baseUrl}`;
42497
+ const baseIssuer = issuer ?? baseUrl;
41944
42498
  this.respond({
41945
42499
  kind: "json",
41946
42500
  contentType: "application/json; charset=utf-8",
@@ -41967,6 +42521,8 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41967
42521
  grant_types_supported: ["authorization_code", "refresh_token"],
41968
42522
  scopes_supported: scopesSupported,
41969
42523
  code_challenge_methods_supported: ["S256"],
42524
+ // Every authorization response, error responses included, carries `iss` (RFC 9207 §2, §3).
42525
+ authorization_response_iss_parameter_supported: true,
41970
42526
  // CIMD support advertisement per draft-ietf-oauth-client-id-metadata-document-00
41971
42527
  client_id_metadata_document_supported: cimdEnabled
41972
42528
  }
@@ -42003,7 +42559,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
42003
42559
 
42004
42560
  // libs/sdk/src/auth/flows/well-known.prm.flow.ts
42005
42561
  import "reflect-metadata";
42006
- import { advertisedScopes as advertisedScopes2 } from "@frontmcp/auth";
42562
+ import { resourceScopesFor } from "@frontmcp/auth";
42007
42563
  import { z as z75 } from "@frontmcp/lazy-zod";
42008
42564
  var inputSchema26, stateSchema24, outputSchema24, plan24, name25, Stage25, WellKnownPrmFlow;
42009
42565
  var init_well_known_prm_flow = __esm({
@@ -42015,14 +42571,16 @@ var init_well_known_prm_flow = __esm({
42015
42571
  stateSchema24 = z75.object({
42016
42572
  resource: z75.string().min(1),
42017
42573
  baseUrl: z75.string().min(1),
42018
- scopesSupported: z75.array(z75.string()).default(["openid", "profile", "email"]),
42574
+ // The authorization server to name: the issuer this server names for the request (#629).
42575
+ authorizationServer: z75.string().min(1).optional(),
42576
+ scopesSupported: z75.array(z75.string()),
42019
42577
  isOrchestrated: z75.boolean()
42020
42578
  });
42021
42579
  outputSchema24 = HttpJsonSchema.extend({
42022
42580
  body: z75.object({
42023
42581
  resource: z75.string().min(1),
42024
42582
  authorization_servers: z75.array(z75.string().min(1)).min(1),
42025
- scopes_supported: z75.array(z75.string()).default(["openid", "profile", "email"]),
42583
+ scopes_supported: z75.array(z75.string()).optional(),
42026
42584
  bearer_methods_supported: z75.array(z75.string()).default(["header"])
42027
42585
  }).passthrough()
42028
42586
  });
@@ -42046,12 +42604,17 @@ var init_well_known_prm_flow = __esm({
42046
42604
  if (!request) throw new FlowInputMissingError("request", "well-known:prm");
42047
42605
  const resource = computeResource(request, scope.entryPath, scope.routeBase);
42048
42606
  const baseUrl = getRequestBaseUrl(request, scope.entryPath);
42049
- const auth = scope.metadata.auth;
42607
+ const authorizationServer = scope.auth?.issuerFor?.(request);
42608
+ const authOptions = scope.auth?.options ?? scope.metadata.auth;
42609
+ const granted = resourceScopesFor(authOptions);
42610
+ const orchestrated = authOptions?.mode === "local" || authOptions?.mode === "remote";
42611
+ const scopesSupported = orchestrated ? granted : [.../* @__PURE__ */ new Set([...granted, ...scope.getAllSupportedScopes()])];
42050
42612
  this.state.set(
42051
42613
  stateSchema24.parse({
42052
42614
  resource,
42053
42615
  baseUrl,
42054
- scopesSupported: auth && isOrchestratedMode(auth) ? advertisedScopes2(auth.allowedScopes) : scope.getAllSupportedScopes(),
42616
+ authorizationServer,
42617
+ scopesSupported,
42055
42618
  isOrchestrated: false
42056
42619
  //scope.orchestrated,// TODO: fix
42057
42620
  })
@@ -42059,6 +42622,8 @@ var init_well_known_prm_flow = __esm({
42059
42622
  }
42060
42623
  async collectData() {
42061
42624
  const { resource, baseUrl, scopesSupported, isOrchestrated } = this.state.required;
42625
+ const { authorizationServer } = this.state;
42626
+ const scopes = scopesSupported.length > 0 ? { scopes_supported: scopesSupported } : {};
42062
42627
  if (isOrchestrated) {
42063
42628
  this.respond({
42064
42629
  kind: "json",
@@ -42070,8 +42635,8 @@ var init_well_known_prm_flow = __esm({
42070
42635
  headers: { "cache-control": "no-store" },
42071
42636
  body: {
42072
42637
  resource,
42073
- authorization_servers: [baseUrl],
42074
- scopes_supported: scopesSupported,
42638
+ authorization_servers: [authorizationServer ?? baseUrl],
42639
+ ...scopes,
42075
42640
  bearer_methods_supported: ["header"]
42076
42641
  }
42077
42642
  });
@@ -42084,8 +42649,8 @@ var init_well_known_prm_flow = __esm({
42084
42649
  headers: { "cache-control": "no-store" },
42085
42650
  body: {
42086
42651
  resource,
42087
- authorization_servers: [baseUrl],
42088
- scopes_supported: scopesSupported,
42652
+ authorization_servers: [authorizationServer ?? baseUrl],
42653
+ ...scopes,
42089
42654
  bearer_methods_supported: ["header"]
42090
42655
  }
42091
42656
  });
@@ -42225,8 +42790,8 @@ import {
42225
42790
  verifyPkce
42226
42791
  } from "@frontmcp/auth";
42227
42792
  import {
42228
- getEnv as getEnv4,
42229
- isProduction as isProduction5,
42793
+ getEnv as getEnv5,
42794
+ isProduction as isProduction6,
42230
42795
  MemoryStorageAdapter,
42231
42796
  randomBytes as randomBytes8,
42232
42797
  randomUUID as randomUUID21,
@@ -42264,6 +42829,14 @@ function audienceMatchesResource(aud, expected) {
42264
42829
  function isUnsupportedOperation(error) {
42265
42830
  return error instanceof StorageNotSupportedError;
42266
42831
  }
42832
+ function originOf2(value) {
42833
+ try {
42834
+ const origin = new URL(value).origin;
42835
+ return origin === "null" ? void 0 : origin;
42836
+ } catch {
42837
+ return void 0;
42838
+ }
42839
+ }
42267
42840
  var MIN_HS256_SECRET_BYTES, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
42268
42841
  var init_instance_local_primary_auth = __esm({
42269
42842
  "libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
@@ -42314,21 +42887,21 @@ var init_instance_local_primary_auth = __esm({
42314
42887
  this.providers = providers;
42315
42888
  this.logger = this.providers.getActiveScope().logger.child("LocalPrimaryAuth");
42316
42889
  this.port = this.providers.getActiveScope().metadata.http?.port ?? 3001;
42317
- this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
42890
+ this.host = getEnv5("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
42318
42891
  this.issuer = this.deriveIssuer(options);
42319
- this.issuerIsConfigured = this.configuredIssuer(options) !== void 0 || !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
42320
- const jwtSecret = getEnv4("JWT_SECRET");
42892
+ this.publicHostPinned = !!getEnv5("FRONTMCP_PUBLIC_HOST")?.trim();
42893
+ const jwtSecret = getEnv5("JWT_SECRET");
42321
42894
  const hasJwtSecret = !!jwtSecret?.trim();
42322
42895
  if (hasJwtSecret && jwtSecret) {
42323
42896
  const encoded = new TextEncoder().encode(jwtSecret);
42324
42897
  if (encoded.length < MIN_HS256_SECRET_BYTES) {
42325
- if (isProduction5()) throw new JwtSecretWeakError(encoded.length);
42898
+ if (isProduction6()) throw new JwtSecretWeakError(encoded.length);
42326
42899
  this.logger.warn(
42327
42900
  `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\`.`
42328
42901
  );
42329
42902
  }
42330
42903
  this.secret = encoded;
42331
- } else if (isProduction5() && isOrchestratedMode(options)) {
42904
+ } else if (isProduction6() && isOrchestratedMode(options)) {
42332
42905
  throw new JwtSecretRequiredError(options.mode);
42333
42906
  } else {
42334
42907
  this.logger.warn(
@@ -42357,8 +42930,8 @@ var init_instance_local_primary_auth = __esm({
42357
42930
  port;
42358
42931
  /** The boot-time issuer: the configured one, else this server's listener address (see {@link issuerFor}). */
42359
42932
  issuer;
42360
- /** Whether the operator named the issuer (`issuer` / `local.issuer`, or its host with `FRONTMCP_PUBLIC_HOST`). */
42361
- issuerIsConfigured;
42933
+ /** Whether `FRONTMCP_PUBLIC_HOST` names the boot-time issuer's host, pinning it for every request. */
42934
+ publicHostPinned;
42362
42935
  keys = [];
42363
42936
  secret;
42364
42937
  logger;
@@ -42472,7 +43045,7 @@ var init_instance_local_primary_auth = __esm({
42472
43045
  if (dcr && typeof dcr.enabled === "boolean") {
42473
43046
  return dcr.enabled;
42474
43047
  }
42475
- return !isProduction5();
43048
+ return !isProduction6();
42476
43049
  }
42477
43050
  /** Provider configurations (indexed by provider ID) */
42478
43051
  providerConfigs = /* @__PURE__ */ new Map();
@@ -42516,27 +43089,52 @@ var init_instance_local_primary_auth = __esm({
42516
43089
  return void 0;
42517
43090
  }
42518
43091
  /**
42519
- * The issuer (`iss`) this server names on the tokens it issues in answer to
42520
- * `request`, and requires on the tokens it accepts there (#269). Issuing and
42521
- * verifying both ask this, so they can't disagree:
43092
+ * The issuer this server names in answer to `request`: in its discovery
43093
+ * documents (`/.well-known/oauth-authorization-server`'s `issuer`, the
43094
+ * protected resource metadata's `authorization_servers`), on its
43095
+ * authorization responses (the RFC 9207 `iss`, errors included), and as the
43096
+ * `iss` of the tokens it issues; the tokens it accepts there must name it
43097
+ * (#269, and see {@link acceptedIssuersFor}). Every entry point asks this, so
43098
+ * none of them can disagree, on the Node server and under a Web fetch
43099
+ * handler alike (#629). In order:
42522
43100
  *
42523
- * - a configured issuer (`issuer` / `local.issuer`) is the issuer;
42524
- * - with `FRONTMCP_PUBLIC_URL` pinned, the issuer is that URL plus this
42525
- * scope's path, the address discovery advertises;
42526
- * - with `FRONTMCP_PUBLIC_HOST` or `expectedAudience` set, the boot-time
42527
- * issuer, so a token serves at every address the server answers to;
42528
- * - a request that came through a Web fetch handler (a Worker, Deno, Bun, a
42529
- * Durable Object) has no listener the boot-time issuer could describe, so
42530
- * the issuer is the request's own origin plus this scope's path;
42531
- * - otherwise (the Node server) the boot-time issuer.
43101
+ * 1. a configured issuer (`issuer` / `local.issuer`);
43102
+ * 2. with `FRONTMCP_PUBLIC_URL` pinned, that URL plus this scope's path;
43103
+ * 3. with `FRONTMCP_PUBLIC_HOST` set, the boot-time issuer it names the host
43104
+ * of (`http://<host>:<http.port><path>`);
43105
+ * 4. the request's own origin plus this scope's path;
43106
+ * 5. without a request, the boot-time issuer.
42532
43107
  */
42533
43108
  issuerFor(request) {
42534
- if (!request || this.issuerIsConfigured) return this.issuer;
42535
- if (isPublicUrlPinned()) return computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
43109
+ const configured = this.configuredIssuer(this.options);
43110
+ if (configured !== void 0) return configured;
43111
+ const pinned = getPinnedPublicUrl();
43112
+ if (pinned !== void 0) {
43113
+ return request ? computeIssuer(request, this.scope.entryPath, this.scope.routeBase) : `${pinned}${this.scope.fullPath}`;
43114
+ }
43115
+ if (this.publicHostPinned || !request) return this.issuer;
43116
+ return computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
43117
+ }
43118
+ /**
43119
+ * The issuers a token presented with `request` may name: {@link issuerFor}
43120
+ * the request. When the issuer follows the request (nothing pins it) and
43121
+ * local or remote mode lists `expectedAudience`, a token for any listed
43122
+ * address was issued under that address's issuer, so each listed address's
43123
+ * issuer is accepted too, and so is the boot-time issuer that 1.8.4 named on
43124
+ * those tokens. The token's `aud` must still name a listed resource.
43125
+ */
43126
+ acceptedIssuersFor(request) {
43127
+ const issuer = this.issuerFor(request);
42536
43128
  const options = this.options;
42537
- if (isOrchestratedMode(options) && options.expectedAudience !== void 0) return this.issuer;
42538
- const webRequest = request[ServerRequestTokens.webRequest];
42539
- return webRequest === void 0 ? this.issuer : computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
43129
+ const followsRequest = !!request && this.configuredIssuer(options) === void 0 && getPinnedPublicUrl() === void 0 && !this.publicHostPinned;
43130
+ if (!followsRequest || !isOrchestratedMode(options) || options.expectedAudience === void 0) return issuer;
43131
+ const listed = Array.isArray(options.expectedAudience) ? options.expectedAudience : [options.expectedAudience];
43132
+ const accepted = /* @__PURE__ */ new Set([issuer, this.issuer]);
43133
+ for (const audience of listed) {
43134
+ const origin = originOf2(audience);
43135
+ if (origin) accepted.add(`${origin}${this.scope.fullPath}`);
43136
+ }
43137
+ return [...accepted];
42540
43138
  }
42541
43139
  /**
42542
43140
  * Read the `tokenStorage` selection off the auth options. Only
@@ -42676,7 +43274,7 @@ var init_instance_local_primary_auth = __esm({
42676
43274
  await memory.connect();
42677
43275
  storage = memory;
42678
43276
  }
42679
- const pepper = getEnv4("VAULT_SECRET") ?? getEnv4("JWT_SECRET") ?? void 0;
43277
+ const pepper = getEnv5("VAULT_SECRET") ?? getEnv5("JWT_SECRET") ?? void 0;
42680
43278
  this.credentialVaultImpl = new SessionCredentialVault({
42681
43279
  storage,
42682
43280
  pepper,
@@ -42727,7 +43325,7 @@ var init_instance_local_primary_auth = __esm({
42727
43325
  }
42728
43326
  const config = this.readSecureStoreConfig();
42729
43327
  const reuseAdapter = this.storageAdapter && this.secureStoreSharesTokenStorage(config) ? this.storageAdapter : void 0;
42730
- const pepper = getEnv4("VAULT_SECRET") ?? getEnv4("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
43328
+ const pepper = getEnv5("VAULT_SECRET") ?? getEnv5("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
42731
43329
  const resolved = await createSecureStore({
42732
43330
  config,
42733
43331
  pepper,
@@ -42787,10 +43385,10 @@ var init_instance_local_primary_auth = __esm({
42787
43385
  *
42788
43386
  * The secret alone doesn't make a token this server's: every server started
42789
43387
  * with the same JWT_SECRET holds it. So a token must also (#269):
42790
- * - name THIS instance as its issuer (`iss`): `expectedIssuer`, which callers
42791
- * take from {@link issuerFor} the request, as the token endpoint does when
42792
- * it signs; the boot-time issuer when not given. Never the token's own
42793
- * claim; and
43388
+ * - name THIS instance as its issuer (`iss`): `expectedIssuer` (one issuer or
43389
+ * several), which callers take from {@link acceptedIssuersFor} the request,
43390
+ * matching what the token endpoint names when it signs ({@link issuerFor});
43391
+ * the boot-time issuer when not given. Never the token's own claim; and
42794
43392
  * - when `expectedAudience` is given, be issued for that protected resource,
42795
43393
  * or one of those resources (`aud`, compared as RFC 8707 resource URIs).
42796
43394
  * `session:verify` passes the request's resource URL, or the configured
@@ -42803,7 +43401,7 @@ var init_instance_local_primary_auth = __esm({
42803
43401
  try {
42804
43402
  const { payload, protectedHeader } = await jwtVerify(token, this.secret, {
42805
43403
  algorithms: ["HS256"],
42806
- issuer: expectedIssuer ?? this.issuer,
43404
+ issuer: typeof expectedIssuer === "string" ? expectedIssuer : expectedIssuer ? [...expectedIssuer] : this.issuer,
42807
43405
  requiredClaims: ["exp"]
42808
43406
  });
42809
43407
  if (expectedAudience !== void 0 && !audienceMatchesResource(payload.aud, expectedAudience)) {
@@ -43759,6 +44357,111 @@ var init_auth_registry = __esm({
43759
44357
  }
43760
44358
  });
43761
44359
 
44360
+ // libs/sdk/src/server/custom-routes.helper.ts
44361
+ function computeReservedPaths(entryPath, routeBase) {
44362
+ const prefix = normalizeEntryPrefix(entryPath);
44363
+ const base = normalizeScopeBase(routeBase);
44364
+ const mcpBase = `${prefix}${base}`;
44365
+ const exact = /* @__PURE__ */ new Set([
44366
+ mcpBase === "" ? "/" : mcpBase,
44367
+ `${mcpBase}/sse`,
44368
+ `${mcpBase}/message`,
44369
+ "/health",
44370
+ "/metrics"
44371
+ ]);
44372
+ return { exact, prefixes: ["/oauth", "/.well-known"] };
44373
+ }
44374
+ function normalizeForCompare(path3) {
44375
+ if (path3.length > 1 && path3.endsWith("/")) return path3.slice(0, -1);
44376
+ return path3;
44377
+ }
44378
+ function assertNotReserved(method, path3, reserved) {
44379
+ const normalized = normalizeForCompare(path3);
44380
+ if (reserved.exact.has(normalized)) {
44381
+ throw new ReservedRouteCollisionError(method, path3, normalized);
44382
+ }
44383
+ for (const prefix of reserved.prefixes) {
44384
+ if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
44385
+ throw new ReservedRouteCollisionError(method, path3, prefix);
44386
+ }
44387
+ }
44388
+ }
44389
+ function wrapWithAuth(handler, verifySession, logger) {
44390
+ return async (req, res, next) => {
44391
+ let result;
44392
+ try {
44393
+ result = await verifySession(req);
44394
+ } catch (error) {
44395
+ if (error instanceof FlowControl && error.type === "respond") {
44396
+ result = error.output;
44397
+ } else {
44398
+ throw error;
44399
+ }
44400
+ }
44401
+ if (!result || result.kind === "unauthorized") {
44402
+ logger.verbose("custom-route auth: unauthorized");
44403
+ return writeHttpResponse(
44404
+ res,
44405
+ httpRespond.unauthorized({
44406
+ headers: result?.kind === "unauthorized" ? { "WWW-Authenticate": result.prmMetadataHeader } : void 0
44407
+ })
44408
+ );
44409
+ }
44410
+ if (result.kind === "forbidden") {
44411
+ logger.verbose("custom-route auth: forbidden (insufficient scope)");
44412
+ return writeHttpResponse(
44413
+ res,
44414
+ httpRespond.forbidden({
44415
+ headers: { "WWW-Authenticate": result.prmMetadataHeader }
44416
+ })
44417
+ );
44418
+ }
44419
+ req.authSession = result.authorization;
44420
+ req[ServerRequestTokens.auth] = result.authorization;
44421
+ return handler(req, res, next);
44422
+ };
44423
+ }
44424
+ function wrapWithIpFilter(handler, checkClientIp) {
44425
+ return async (req, res, next) => {
44426
+ const rejection = await checkClientIp(req, res);
44427
+ if (rejection) return writeHttpResponse(res, rejection);
44428
+ return handler(req, res, next);
44429
+ };
44430
+ }
44431
+ function registerCustomHttpRoutes(args) {
44432
+ const { routes, server, verifySession, checkClientIp, entryPath, routeBase, logger } = args;
44433
+ if (!routes || routes.length === 0) return;
44434
+ const reserved = computeReservedPaths(entryPath, routeBase);
44435
+ for (const route of routes) {
44436
+ assertNotReserved(route.method, route.path, reserved);
44437
+ const handler = route.auth === true ? wrapWithAuth(route.handler, verifySession, logger) : route.handler;
44438
+ server.registerRoute(route.method, route.path, wrapWithIpFilter(handler, checkClientIp));
44439
+ logger.info(`Registered custom http route: ${route.method} ${route.path}${route.auth ? " (auth)" : ""}`);
44440
+ }
44441
+ }
44442
+ var ReservedRouteCollisionError;
44443
+ var init_custom_routes_helper = __esm({
44444
+ "libs/sdk/src/server/custom-routes.helper.ts"() {
44445
+ "use strict";
44446
+ init_common();
44447
+ init_server_validation();
44448
+ ReservedRouteCollisionError = class extends Error {
44449
+ constructor(method, path3, reserved) {
44450
+ super(
44451
+ `Custom http.route "${method} ${path3}" collides with the reserved FrontMCP path "${reserved}". Reserved prefixes are the MCP entry path (and its /sse + /message siblings), /oauth/*, /.well-known/*, /health, and /metrics. Choose a different path.`
44452
+ );
44453
+ this.method = method;
44454
+ this.path = path3;
44455
+ this.reserved = reserved;
44456
+ this.name = "ReservedRouteCollisionError";
44457
+ }
44458
+ method;
44459
+ path;
44460
+ reserved;
44461
+ };
44462
+ }
44463
+ });
44464
+
43762
44465
  // libs/sdk/src/channel/channel-notification.service.ts
43763
44466
  var CHANNEL_NOTIFICATION_METHOD, ChannelNotificationService;
43764
44467
  var init_channel_notification_service = __esm({
@@ -44728,6 +45431,12 @@ function wireJobCompletionSource(channel2, sourceConfig, jobEmitterSubscribe, lo
44728
45431
  return;
44729
45432
  }
44730
45433
  }
45434
+ if (!event.sessionId) {
45435
+ logger.verbose(
45436
+ `Skipping job completion for channel "${channel2.name}": no sessionId (prevents cross-session leak)`
45437
+ );
45438
+ return;
45439
+ }
44731
45440
  logger.verbose(`Job completion event for channel "${channel2.name}": job=${event.jobName}, status=${event.status}`);
44732
45441
  channel2.handleEvent(event, event.sessionId).catch((err) => {
44733
45442
  logger.error(`Failed to handle job completion event in channel "${channel2.name}"`, { error: err });
@@ -44740,6 +45449,41 @@ var init_job_completion_source = __esm({
44740
45449
  }
44741
45450
  });
44742
45451
 
45452
+ // libs/sdk/src/channel/sources/webhook.source.ts
45453
+ function createWebhookMiddleware(channel2, sourceConfig, logger) {
45454
+ return async (req, res) => {
45455
+ const rawHeaders = req.headers ?? {};
45456
+ const normalizedHeaders = {};
45457
+ for (const [key, value] of Object.entries(rawHeaders)) {
45458
+ normalizedHeaders[key.toLowerCase()] = value;
45459
+ }
45460
+ const payload = {
45461
+ body: req.body,
45462
+ headers: normalizedHeaders,
45463
+ method: req.method ?? "POST",
45464
+ query: req.query
45465
+ };
45466
+ logger.verbose(`Webhook received for channel "${channel2.name}" at ${sourceConfig.path}`);
45467
+ try {
45468
+ const notification = await runOnSurface("http-trigger", () => channel2.handleEvent(payload));
45469
+ if (notification) {
45470
+ res.status(200).json({ ok: true, channel: channel2.name });
45471
+ } else {
45472
+ res.status(200).json({ ok: true, channel: channel2.name, skipped: true });
45473
+ }
45474
+ } catch (err) {
45475
+ logger.error(`Webhook handler failed for channel "${channel2.name}"`, { error: err });
45476
+ res.status(500).json({ ok: false, error: "Internal channel error" });
45477
+ }
45478
+ };
45479
+ }
45480
+ var init_webhook_source = __esm({
45481
+ "libs/sdk/src/channel/sources/webhook.source.ts"() {
45482
+ "use strict";
45483
+ init_call_surface();
45484
+ }
45485
+ });
45486
+
44743
45487
  // libs/sdk/src/channel/channel-scope.helper.ts
44744
45488
  async function registerChannelCapabilities(args) {
44745
45489
  const {
@@ -44750,11 +45494,15 @@ async function registerChannelCapabilities(args) {
44750
45494
  notificationService,
44751
45495
  flowRegistry,
44752
45496
  toolRegistry,
44753
- agentEmitterSubscribe,
44754
- jobEmitterSubscribe,
45497
+ http: http2,
44755
45498
  logger
44756
45499
  } = args;
45500
+ const completions = completionEventsOf(providers.getActiveScope());
45501
+ const agentEmitterSubscribe = args.agentEmitterSubscribe ?? ((cb) => completions.agents.subscribe(cb));
45502
+ const jobEmitterSubscribe = args.jobEmitterSubscribe ?? ((cb) => completions.jobs.subscribe(cb));
44757
45503
  const unsubscribers = [];
45504
+ const reservedPaths = http2 ? computeReservedPaths(http2.entryPath, http2.routeBase) : void 0;
45505
+ const webhookPaths = /* @__PURE__ */ new Map();
44758
45506
  const channelRegistry = new ChannelRegistry(providers, channelsList, owner);
44759
45507
  await channelRegistry.ready;
44760
45508
  const channelNotificationService = new ChannelNotificationService(
@@ -44770,31 +45518,23 @@ async function registerChannelCapabilities(args) {
44770
45518
  const sourceType = instance.source.type;
44771
45519
  switch (sourceType) {
44772
45520
  case "agent-completion": {
44773
- if (agentEmitterSubscribe) {
44774
- const unsub = wireAgentCompletionSource(
44775
- instance,
44776
- instance.metadata.source,
44777
- agentEmitterSubscribe,
44778
- logger
44779
- );
44780
- unsubscribers.push(unsub);
44781
- } else {
44782
- logger.warn(`Channel "${instance.name}" has agent-completion source but no agent emitter available`);
44783
- }
45521
+ const unsub = wireAgentCompletionSource(
45522
+ instance,
45523
+ instance.metadata.source,
45524
+ agentEmitterSubscribe,
45525
+ logger
45526
+ );
45527
+ unsubscribers.push(unsub);
44784
45528
  break;
44785
45529
  }
44786
45530
  case "job-completion": {
44787
- if (jobEmitterSubscribe) {
44788
- const unsub = wireJobCompletionSource(
44789
- instance,
44790
- instance.metadata.source,
44791
- jobEmitterSubscribe,
44792
- logger
44793
- );
44794
- unsubscribers.push(unsub);
44795
- } else {
44796
- logger.warn(`Channel "${instance.name}" has job-completion source but no job emitter available`);
44797
- }
45531
+ const unsub = wireJobCompletionSource(
45532
+ instance,
45533
+ instance.metadata.source,
45534
+ jobEmitterSubscribe,
45535
+ logger
45536
+ );
45537
+ unsubscribers.push(unsub);
44798
45538
  break;
44799
45539
  }
44800
45540
  case "app-event": {
@@ -44803,7 +45543,25 @@ async function registerChannelCapabilities(args) {
44803
45543
  unsubscribers.push(unsub);
44804
45544
  break;
44805
45545
  }
44806
- case "webhook":
45546
+ case "webhook": {
45547
+ const source = instance.metadata.source;
45548
+ if (!http2 || !reservedPaths) {
45549
+ logger.warn(`Channel "${instance.name}" has a webhook source but no HTTP server to serve ${source.path}`);
45550
+ break;
45551
+ }
45552
+ assertNotReserved("POST", source.path, reservedPaths);
45553
+ const claimedBy = webhookPaths.get(source.path);
45554
+ if (claimedBy) {
45555
+ throw new Error(
45556
+ `Channels "${claimedBy}" and "${instance.name}" both declare the webhook path ${source.path}. Give each webhook channel its own path.`
45557
+ );
45558
+ }
45559
+ webhookPaths.set(source.path, instance.name);
45560
+ const handler = createWebhookMiddleware(instance, source, logger);
45561
+ await http2.server.registerRoute("POST", source.path, wrapWithIpFilter(handler, http2.checkClientIp));
45562
+ logger.info(`Registered webhook route for channel "${instance.name}": POST ${source.path}`);
45563
+ break;
45564
+ }
44807
45565
  case "manual":
44808
45566
  break;
44809
45567
  case "service":
@@ -44886,6 +45644,7 @@ async function registerChannelCapabilities(args) {
44886
45644
  var init_channel_scope_helper = __esm({
44887
45645
  "libs/sdk/src/channel/channel-scope.helper.ts"() {
44888
45646
  "use strict";
45647
+ init_custom_routes_helper();
44889
45648
  init_tool_instance();
44890
45649
  init_tool_utils();
44891
45650
  init_channel_notification_service();
@@ -44895,7 +45654,9 @@ var init_channel_scope_helper = __esm({
44895
45654
  init_channel_reply_tool();
44896
45655
  init_agent_completion_source();
44897
45656
  init_app_event_source();
45657
+ init_completion_events();
44898
45658
  init_job_completion_source();
45659
+ init_webhook_source();
44899
45660
  }
44900
45661
  });
44901
45662
 
@@ -45641,10 +46402,10 @@ import {
45641
46402
  randomBytes as randomBytes9,
45642
46403
  base64urlEncode as base64urlEncode6,
45643
46404
  base64urlDecode as base64urlDecode3,
45644
- getEnv as getEnv5
46405
+ getEnv as getEnv6
45645
46406
  } from "@frontmcp/utils";
45646
46407
  function getElicitationSecret() {
45647
- return getEnv5("MCP_ELICITATION_SECRET") || getEnv5("MCP_SESSION_SECRET") || getEnv5("MCP_SERVER_SECRET") || null;
46408
+ return getEnv6("MCP_ELICITATION_SECRET") || getEnv6("MCP_SESSION_SECRET") || getEnv6("MCP_SERVER_SECRET") || null;
45648
46409
  }
45649
46410
  function isElicitationEncryptionAvailable() {
45650
46411
  return getElicitationSecret() !== null;
@@ -46121,12 +46882,12 @@ var init_encrypted_elicitation_store = __esm({
46121
46882
  });
46122
46883
 
46123
46884
  // libs/sdk/src/elicitation/store/elicitation-store.factory.ts
46124
- import { createMemoryStorage as createMemoryStorage2, createStorage as createStorage2, getEnv as getEnv6 } from "@frontmcp/utils";
46885
+ import { createMemoryStorage as createMemoryStorage2, createStorage as createStorage2, getEnv as getEnv7 } from "@frontmcp/utils";
46125
46886
  function detectStorageType2(storage) {
46126
- if (getEnv6("UPSTASH_REDIS_REST_URL")) {
46887
+ if (getEnv7("UPSTASH_REDIS_REST_URL")) {
46127
46888
  return "upstash";
46128
46889
  }
46129
- if (getEnv6("REDIS_URL") || getEnv6("REDIS_HOST")) {
46890
+ if (getEnv7("REDIS_URL") || getEnv7("REDIS_HOST")) {
46130
46891
  return "redis";
46131
46892
  }
46132
46893
  return "memory";
@@ -46212,7 +46973,7 @@ async function createElicitationStore(options = {}) {
46212
46973
  });
46213
46974
  }
46214
46975
  }
46215
- if (finalStorageConfig?.type === "vercel-kv" || getEnv6("KV_REST_API_URL")) {
46976
+ if (finalStorageConfig?.type === "vercel-kv" || getEnv7("KV_REST_API_URL")) {
46216
46977
  throw new ElicitationNotSupportedError(
46217
46978
  "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."
46218
46979
  );
@@ -47968,6 +48729,7 @@ var init_workflow_step_executor = __esm({
47968
48729
  });
47969
48730
 
47970
48731
  // libs/sdk/src/workflow/engine/workflow.engine.ts
48732
+ import { getAsyncContextMode } from "@frontmcp/utils";
47971
48733
  var WorkflowEngine;
47972
48734
  var init_workflow_engine = __esm({
47973
48735
  "libs/sdk/src/workflow/engine/workflow.engine.ts"() {
@@ -48037,7 +48799,7 @@ var init_workflow_engine = __esm({
48037
48799
  }
48038
48800
  continue;
48039
48801
  }
48040
- const batch = ready.slice(0, this.maxConcurrency);
48802
+ const batch = ready.slice(0, getAsyncContextMode() === "serialized" ? 1 : this.maxConcurrency);
48041
48803
  await Promise.allSettled(
48042
48804
  batch.map(async (step) => {
48043
48805
  if (step.condition) {
@@ -49634,7 +50396,11 @@ async function registerJobCapabilities(args) {
49634
50396
  for (const dynWf of dynamicWorkflows) {
49635
50397
  workflowRegistry.registerDynamic(dynWf);
49636
50398
  }
49637
- const executionManager = new JobExecutionManager(stateStore, logger, notifyFn);
50399
+ const completions = completionEventsOf(providers.getActiveScope());
50400
+ const executionManager = new JobExecutionManager(stateStore, logger, async (data) => {
50401
+ await notifyFn?.(data);
50402
+ await publishJobCompletion(data, stateStore, completions);
50403
+ });
49638
50404
  const managementTools = [
49639
50405
  ListJobsTool,
49640
50406
  ExecuteJobTool,
@@ -49663,9 +50429,27 @@ async function registerJobCapabilities(args) {
49663
50429
  managementTools
49664
50430
  };
49665
50431
  }
50432
+ async function publishJobCompletion(data, stateStore, completions) {
50433
+ const { type, state, runId } = data;
50434
+ if (type !== "job:status" && type !== "workflow:status") return;
50435
+ if (state !== "completed" && state !== "failed" || typeof runId !== "string") return;
50436
+ const run = await stateStore.getRun(runId);
50437
+ if (!run) return;
50438
+ const workflowName = type === "workflow:status" ? run.workflowName : void 0;
50439
+ completions.jobs.emit({
50440
+ jobName: workflowName ?? run.jobName,
50441
+ jobId: run.jobId,
50442
+ status: state === "completed" ? "success" : "error",
50443
+ ...run.completedAt !== void 0 ? { durationMs: run.completedAt - run.startedAt } : {},
50444
+ ...state === "completed" ? { output: completionOutputText(run.result) } : { error: run.error?.message },
50445
+ attempt: run.attempt,
50446
+ ...run.sessionId ? { sessionId: run.sessionId } : {}
50447
+ });
50448
+ }
49666
50449
  var init_job_scope_helper = __esm({
49667
50450
  "libs/sdk/src/job/job-scope.helper.ts"() {
49668
50451
  "use strict";
50452
+ init_completion_events();
49669
50453
  init_execute_workflow_tool();
49670
50454
  init_get_workflow_status_tool();
49671
50455
  init_list_workflows_tool();
@@ -49890,111 +50674,6 @@ var init_enforced_metadata_check = __esm({
49890
50674
  }
49891
50675
  });
49892
50676
 
49893
- // libs/sdk/src/server/custom-routes.helper.ts
49894
- function computeReservedPaths(entryPath, routeBase) {
49895
- const prefix = normalizeEntryPrefix(entryPath);
49896
- const base = normalizeScopeBase(routeBase);
49897
- const mcpBase = `${prefix}${base}`;
49898
- const exact = /* @__PURE__ */ new Set([
49899
- mcpBase === "" ? "/" : mcpBase,
49900
- `${mcpBase}/sse`,
49901
- `${mcpBase}/message`,
49902
- "/health",
49903
- "/metrics"
49904
- ]);
49905
- return { exact, prefixes: ["/oauth", "/.well-known"] };
49906
- }
49907
- function normalizeForCompare(path3) {
49908
- if (path3.length > 1 && path3.endsWith("/")) return path3.slice(0, -1);
49909
- return path3;
49910
- }
49911
- function assertNotReserved(method, path3, reserved) {
49912
- const normalized = normalizeForCompare(path3);
49913
- if (reserved.exact.has(normalized)) {
49914
- throw new ReservedRouteCollisionError(method, path3, normalized);
49915
- }
49916
- for (const prefix of reserved.prefixes) {
49917
- if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
49918
- throw new ReservedRouteCollisionError(method, path3, prefix);
49919
- }
49920
- }
49921
- }
49922
- function wrapWithAuth(handler, verifySession, logger) {
49923
- return async (req, res, next) => {
49924
- let result;
49925
- try {
49926
- result = await verifySession(req);
49927
- } catch (error) {
49928
- if (error instanceof FlowControl && error.type === "respond") {
49929
- result = error.output;
49930
- } else {
49931
- throw error;
49932
- }
49933
- }
49934
- if (!result || result.kind === "unauthorized") {
49935
- logger.verbose("custom-route auth: unauthorized");
49936
- return writeHttpResponse(
49937
- res,
49938
- httpRespond.unauthorized({
49939
- headers: result?.kind === "unauthorized" ? { "WWW-Authenticate": result.prmMetadataHeader } : void 0
49940
- })
49941
- );
49942
- }
49943
- if (result.kind === "forbidden") {
49944
- logger.verbose("custom-route auth: forbidden (insufficient scope)");
49945
- return writeHttpResponse(
49946
- res,
49947
- httpRespond.forbidden({
49948
- headers: { "WWW-Authenticate": result.prmMetadataHeader }
49949
- })
49950
- );
49951
- }
49952
- req.authSession = result.authorization;
49953
- req[ServerRequestTokens.auth] = result.authorization;
49954
- return handler(req, res, next);
49955
- };
49956
- }
49957
- function wrapWithIpFilter(handler, checkClientIp) {
49958
- return async (req, res, next) => {
49959
- const rejection = await checkClientIp(req, res);
49960
- if (rejection) return writeHttpResponse(res, rejection);
49961
- return handler(req, res, next);
49962
- };
49963
- }
49964
- function registerCustomHttpRoutes(args) {
49965
- const { routes, server, verifySession, checkClientIp, entryPath, routeBase, logger } = args;
49966
- if (!routes || routes.length === 0) return;
49967
- const reserved = computeReservedPaths(entryPath, routeBase);
49968
- for (const route of routes) {
49969
- assertNotReserved(route.method, route.path, reserved);
49970
- const handler = route.auth === true ? wrapWithAuth(route.handler, verifySession, logger) : route.handler;
49971
- server.registerRoute(route.method, route.path, wrapWithIpFilter(handler, checkClientIp));
49972
- logger.info(`Registered custom http route: ${route.method} ${route.path}${route.auth ? " (auth)" : ""}`);
49973
- }
49974
- }
49975
- var ReservedRouteCollisionError;
49976
- var init_custom_routes_helper = __esm({
49977
- "libs/sdk/src/server/custom-routes.helper.ts"() {
49978
- "use strict";
49979
- init_common();
49980
- init_server_validation();
49981
- ReservedRouteCollisionError = class extends Error {
49982
- constructor(method, path3, reserved) {
49983
- super(
49984
- `Custom http.route "${method} ${path3}" collides with the reserved FrontMCP path "${reserved}". Reserved prefixes are the MCP entry path (and its /sse + /message siblings), /oauth/*, /.well-known/*, /health, and /metrics. Choose a different path.`
49985
- );
49986
- this.method = method;
49987
- this.path = path3;
49988
- this.reserved = reserved;
49989
- this.name = "ReservedRouteCollisionError";
49990
- }
49991
- method;
49992
- path;
49993
- reserved;
49994
- };
49995
- }
49996
- });
49997
-
49998
50677
  // libs/sdk/src/skill/errors/tool-not-allowed.error.ts
49999
50678
  var ToolNotAllowedError, ToolApprovalRequiredError;
50000
50679
  var init_tool_not_allowed_error = __esm({
@@ -51169,7 +51848,7 @@ var init_load_skill_flow = __esm({
51169
51848
  }
51170
51849
  await assertSkillAuthorized(this.scope, entry, authInfo);
51171
51850
  }
51172
- loadResults.push({ loadResult: result });
51851
+ loadResults.push({ loadResult: skillToolsForCaller(result, this.scope.tools, ctx) });
51173
51852
  }
51174
51853
  this.state.set({ loadResults, warnings });
51175
51854
  this.logger.verbose("loadSkills:done", { loaded: loadResults.length, notFound: warnings.length });
@@ -51415,7 +52094,10 @@ async function authorizeSkillHttpRequest(scope, skillsConfig, request, logger) {
51415
52094
  if (mode === "inherit") {
51416
52095
  const authOptions = scope.auth?.options;
51417
52096
  if (!authOptions || isPublicMode(authOptions)) return { allowed: true, authInfo: {} };
51418
- const verified = await scope.runFlow("session:verify", { request });
52097
+ const verified = await scope.runFlow("session:verify", {
52098
+ request,
52099
+ sessionless: true
52100
+ });
51419
52101
  if (verified?.kind === "authorized") {
51420
52102
  return { allowed: true, authInfo: { ...authInfoFromAuthorization(verified.authorization) } };
51421
52103
  }
@@ -52384,7 +53066,12 @@ var init_skills_api_flow = __esm({
52384
53066
  respondNotFound();
52385
53067
  return;
52386
53068
  }
52387
- const { skill, availableTools, missingTools, isComplete, warning } = loadResult;
53069
+ const { skill, availableTools, missingTools, isComplete, warning } = skillToolsForCaller(
53070
+ loadResult,
53071
+ toolRegistry,
53072
+ void 0,
53073
+ SKILLS_HTTP_SURFACE
53074
+ );
52388
53075
  const skillEntry = createSkillEntryResolver(skillRegistry)(skillId, skill.id);
52389
53076
  if (skillEntry) {
52390
53077
  if (!await isSkillServable(this.scope, skillEntry, void 0, SKILLS_HTTP_SURFACE)) {
@@ -52745,6 +53432,8 @@ var init_tool_interface = __esm({
52745
53432
  _toolNameInternal;
52746
53433
  /** @internal Tool input for fallback elicitation - set by CallToolFlow */
52747
53434
  _toolInputInternal;
53435
+ /** @internal JSON-RPC id of the tools/call request - set by CallToolFlow, for flows the call is handed on to */
53436
+ _jsonRpcRequestIdInternal;
52748
53437
  // ---- INPUT storages (backing fields)
52749
53438
  _rawInput;
52750
53439
  _input;
@@ -52777,6 +53466,10 @@ var init_tool_interface = __esm({
52777
53466
  this._progressToken = progressToken;
52778
53467
  this.signal = signal;
52779
53468
  }
53469
+ /** @internal The request's progress token, for flows the call is handed on to (the agent tool). */
53470
+ get _progressTokenInternal() {
53471
+ return this._progressToken;
53472
+ }
52780
53473
  /**
52781
53474
  * Like {@link ExecutionContextBase.fetch}, and also aborted with {@link signal}, so a
52782
53475
  * cancelled or timed-out call stops its outbound requests.
@@ -54824,10 +55517,10 @@ var init_primary_auth_provider_interface = __esm({
54824
55517
  * @param _expectedAudience The protected resource(s) the token must be for
54825
55518
  * (`aud` names at least one), when the caller is a
54826
55519
  * resource endpoint.
54827
- * @param _expectedIssuer The issuer the token must name: the one this
54828
- * instance issues tokens under for the request
54829
- * (`LocalPrimaryAuth.issuerFor`). Default: its
54830
- * boot-time issuer.
55520
+ * @param _expectedIssuer The issuer the token must name (or one of them):
55521
+ * the ones this instance accepts for the request
55522
+ * (`LocalPrimaryAuth.acceptedIssuersFor`). Default:
55523
+ * its boot-time issuer.
54831
55524
  */
54832
55525
  verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience, _expectedIssuer) {
54833
55526
  return Promise.resolve({
@@ -54902,7 +55595,10 @@ function findSkillByPath(scope, skillPath) {
54902
55595
  const segments = skillPath.split("/").filter((s) => s.length > 0);
54903
55596
  if (segments.length === 0) return void 0;
54904
55597
  const lastSegment = segments[segments.length - 1];
54905
- return visible.find((s) => s.name === lastSegment && s.getSkillPath() === lastSegment);
55598
+ const byName = visible.find((s) => s.name === lastSegment && s.getSkillPath() === lastSegment);
55599
+ if (byName) return byName;
55600
+ if (segments.length !== 1) return void 0;
55601
+ return visible.find((s) => s.metadata.id === lastSegment && s.getSkillPath() === s.metadata.name);
54906
55602
  }
54907
55603
  async function findAndLoadSkillByPath(scope, skillPath) {
54908
55604
  const registry = scope.skills;
@@ -56283,7 +56979,9 @@ var init_handle_mcp_20260728_flow = __esm({
56283
56979
  /** Negotiated protocol version for this request. */
56284
56980
  version: z102.string().optional(),
56285
56981
  requestType: z102.enum(["notification", "subscriptions", "message"]).optional(),
56286
- isAnonymous: z102.boolean().default(true)
56982
+ isAnonymous: z102.boolean().default(true),
56983
+ /** Whether the client declared this revision itself (not served under it by default). */
56984
+ clientDeclaredRevision: z102.boolean().optional()
56287
56985
  });
56288
56986
  name38 = "handle:mcp-202607280728";
56289
56987
  ({ Stage: Stage38 } = FlowHooksOf(name38));
@@ -56330,6 +57028,7 @@ var init_handle_mcp_20260728_flow = __esm({
56330
57028
  return;
56331
57029
  }
56332
57030
  this.state.set("version", result.version);
57031
+ this.state.set("clientDeclaredRevision", strictHeaders);
56333
57032
  }
56334
57033
  /**
56335
57034
  * Resolve a tool's input JSON Schema so `x-mcp-header` annotations can be
@@ -56445,10 +57144,12 @@ var init_handle_mcp_20260728_flow = __esm({
56445
57144
  // authenticated caller.
56446
57145
  ...authInfoFromAuthorization(auth),
56447
57146
  // The shared handlers still key per-request state (provider views, notifications)
56448
- // off an id. This one names the request only; the request context does not vouch for it.
56449
- sessionId: auth.session?.id
57147
+ // off an id. It names the request only, the same per-request id as the request
57148
+ // context's `sessionId`; the request context does not vouch for it (#629).
57149
+ sessionId: auth.session?.id ?? this.tryGetContext()?.sessionId
56450
57150
  } : void 0,
56451
57151
  isAnonymous: this.state.required.isAnonymous,
57152
+ clientDeclaredRevision: this.state.clientDeclaredRevision !== false,
56452
57153
  composeInstructions: () => this.scope.metadata.instructions,
56453
57154
  notificationSink: sink,
56454
57155
  traceContext: extractTraceContext(meta)
@@ -56557,6 +57258,30 @@ function getQuerySessionId2(urlPath) {
56557
57258
  return void 0;
56558
57259
  }
56559
57260
  }
57261
+ function resolveLegacySseSession(params) {
57262
+ const { rawHeader, requestUrl, authorizationSession, createSession } = params;
57263
+ const rawMcpSessionHeader = typeof rawHeader === "string" ? rawHeader : void 0;
57264
+ const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
57265
+ const querySessionId = getQuerySessionId2(requestUrl);
57266
+ const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
57267
+ if (rawHeader !== void 0 && !mcpSessionHeader) {
57268
+ return { responded404: true, createdNew: false };
57269
+ }
57270
+ if (querySessionId !== void 0 && !validatedQuerySessionId) {
57271
+ return { responded404: true, createdNew: false };
57272
+ }
57273
+ const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
57274
+ if (effectiveSessionId) {
57275
+ if (authorizationSession?.id === effectiveSessionId) {
57276
+ return { session: authorizationSession, createdNew: false, responded404: false };
57277
+ }
57278
+ return { responded404: true, createdNew: false };
57279
+ }
57280
+ if (authorizationSession) {
57281
+ return { session: authorizationSession, createdNew: false, responded404: false };
57282
+ }
57283
+ return { session: createSession(), createdNew: true, responded404: false };
57284
+ }
56560
57285
  var plan38, stateSessionSchema, stateSchema38, name39, Stage39, HandleSseFlow;
56561
57286
  var init_handle_sse_flow = __esm({
56562
57287
  "libs/sdk/src/transport/flows/handle.sse.flow.ts"() {
@@ -56596,39 +57321,26 @@ var init_handle_sse_flow = __esm({
56596
57321
  const { request } = this.rawInput;
56597
57322
  const authorization = request[ServerRequestTokens.auth];
56598
57323
  const { token } = authorization;
56599
- const raw = request.headers?.["mcp-session-id"];
56600
- const rawMcpSessionHeader = typeof raw === "string" ? raw : void 0;
56601
- const mcpSessionHeader = validateMcpSessionHeader(rawMcpSessionHeader);
56602
57324
  const anyReq = request;
56603
- const querySessionId = getQuerySessionId2(anyReq.url ?? anyReq.path);
56604
- const validatedQuerySessionId = querySessionId ? validateMcpSessionHeader(querySessionId) : void 0;
56605
- if (raw !== void 0 && !mcpSessionHeader) {
56606
- this.respond(httpRespond.sessionNotFound("invalid session id"));
56607
- return;
56608
- }
56609
- if (querySessionId !== void 0 && !validatedQuerySessionId) {
57325
+ const resolution = resolveLegacySseSession({
57326
+ rawHeader: request.headers?.["mcp-session-id"],
57327
+ requestUrl: anyReq.url ?? anyReq.path,
57328
+ authorizationSession: authorization.session,
57329
+ createSession: () => {
57330
+ const query = request.query;
57331
+ const skillsOnlyMode = detectSkillsOnlyMode(query);
57332
+ return createSessionId("legacy-sse", token, {
57333
+ userAgent: request.headers?.["user-agent"],
57334
+ platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
57335
+ skillsOnlyMode
57336
+ });
57337
+ }
57338
+ });
57339
+ if (resolution.responded404 || !resolution.session) {
56610
57340
  this.respond(httpRespond.sessionNotFound("invalid session id"));
56611
57341
  return;
56612
57342
  }
56613
- const effectiveSessionId = mcpSessionHeader ?? validatedQuerySessionId;
56614
- let session;
56615
- if (effectiveSessionId) {
56616
- if (authorization.session?.id === effectiveSessionId) {
56617
- session = authorization.session;
56618
- } else {
56619
- session = { id: effectiveSessionId };
56620
- }
56621
- } else if (authorization.session) {
56622
- session = authorization.session;
56623
- } else {
56624
- const query = request.query;
56625
- const skillsOnlyMode = detectSkillsOnlyMode(query);
56626
- session = createSessionId("legacy-sse", token, {
56627
- userAgent: request.headers?.["user-agent"],
56628
- platformDetectionConfig: this.scope.metadata.transport?.platformDetection,
56629
- skillsOnlyMode
56630
- });
56631
- }
57343
+ const session = resolution.session;
56632
57344
  this.state.set(stateSchema38.parse({ token, session }));
56633
57345
  }
56634
57346
  async router() {
@@ -57244,7 +57956,7 @@ function resolveStreamableHttpSession(params) {
57244
57956
  if (authorizationSession?.id === mcpSessionHeader) {
57245
57957
  return { session: authorizationSession, createdNew: false, responded404: false };
57246
57958
  }
57247
- return { session: { id: mcpSessionHeader }, createdNew: false, responded404: false };
57959
+ return { responded404: true, createdNew: false };
57248
57960
  }
57249
57961
  if (authorizationSession) {
57250
57962
  return { session: authorizationSession, createdNew: false, responded404: false };
@@ -57389,7 +58101,7 @@ var init_handle_streamable_http_flow = __esm({
57389
58101
  });
57390
58102
  }
57391
58103
  if (sessionResolution.responded404 || !sessionResolution.session) {
57392
- logger.warn("parseInput: invalid mcp-session-id header");
58104
+ logger.warn("parseInput: mcp-session-id is not a session this server verified");
57393
58105
  this.respond(httpRespond.sessionNotFound("invalid session id"));
57394
58106
  return;
57395
58107
  }
@@ -60297,7 +61009,10 @@ var init_http_request_flow = __esm({
60297
61009
  const { request } = this.rawInput;
60298
61010
  this.logger.verbose(`[${this.requestId}] checkAuthorization: verifying session`);
60299
61011
  try {
60300
- const result = await this.scope.runFlow("session:verify", { request });
61012
+ const result = await this.scope.runFlow("session:verify", {
61013
+ request,
61014
+ sessionless: this.servesProtocol20260728()
61015
+ });
60301
61016
  if (!result) {
60302
61017
  this.logger.error(`[${this.requestId}] failed to verify session`);
60303
61018
  throw new SessionVerificationFailedError();
@@ -60956,6 +61671,7 @@ var init_scope_instance = __esm({
60956
61671
  init_ui();
60957
61672
  init_bus();
60958
61673
  init_event_stores();
61674
+ init_request_state();
60959
61675
  init_transport_registry();
60960
61676
  init_http_ip_filter_flow();
60961
61677
  init_http_request_flow();
@@ -61084,6 +61800,7 @@ var init_scope_instance = __esm({
61084
61800
  effectivePersistence = { sqlite: resolvedTopLevelSqlite };
61085
61801
  }
61086
61802
  this.transportService = new TransportService(this, effectivePersistence, transportBus);
61803
+ warnIfRequestStateKeyNotShared({ logger: this.logger, metadata: this.metadata });
61087
61804
  if (this.haManager && isDistributed) {
61088
61805
  const persistCfg = transportConfig?.persistence;
61089
61806
  const resolvedPrefix = persistCfg && typeof persistCfg === "object" && persistCfg.redis?.keyPrefix ? persistCfg.redis.keyPrefix : "mcp:transport:";
@@ -61581,6 +62298,15 @@ var init_scope_instance = __esm({
61581
62298
  notificationService: this.notificationService,
61582
62299
  flowRegistry: this.scopeFlows,
61583
62300
  toolRegistry: this.scopeTools,
62301
+ http: {
62302
+ server: this.server,
62303
+ checkClientIp: (request, response) => this.runFlow("http:ip-filter", {
62304
+ request,
62305
+ response
62306
+ }),
62307
+ entryPath: this.entryPath,
62308
+ routeBase: this.routeBase
62309
+ },
61584
62310
  logger: this.logger
61585
62311
  });
61586
62312
  this._scopeChannels = channelResult.channelRegistry;
@@ -61985,12 +62711,12 @@ var init_scope_instance = __esm({
61985
62711
  return this._authoritiesScopeMapping;
61986
62712
  }
61987
62713
  /**
61988
- * Collect all supported OAuth scopes from base OIDC scopes and
61989
- * tool-level authProvider scope declarations.
61990
- * Used by PRM endpoint to populate `scopes_supported` (RFC 9728).
62714
+ * Collect the OAuth scopes entries declare on their `authProviders`. The PRM
62715
+ * endpoint advertises them in `scopes_supported` (RFC 9728) next to the scopes
62716
+ * the auth mode grants (`resourceScopesFor`), not a fixed OIDC list (#629).
61991
62717
  */
61992
62718
  getAllSupportedScopes() {
61993
- const scopes = /* @__PURE__ */ new Set(["openid", "profile", "email"]);
62719
+ const scopes = /* @__PURE__ */ new Set();
61994
62720
  const collectScopes = (metadata) => {
61995
62721
  const authProviders = metadata?.["authProviders"];
61996
62722
  if (!Array.isArray(authProviders)) return;
@@ -63661,10 +64387,57 @@ function findMisconfiguration(error) {
63661
64387
  if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
63662
64388
  return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
63663
64389
  }
64390
+ if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
63664
64391
  current = current.cause;
63665
64392
  }
63666
64393
  return void 0;
63667
64394
  }
64395
+ function createDeferredServerBuild(build, options = {}) {
64396
+ const now = options.now ?? Date.now;
64397
+ let built;
64398
+ let inFlight;
64399
+ let failure;
64400
+ return {
64401
+ get(arg) {
64402
+ if (built) return Promise.resolve(built.value);
64403
+ if (inFlight) return inFlight;
64404
+ if (failure && now() < failure.retryAt) return Promise.reject(failure.error);
64405
+ const failures = failure?.count ?? 0;
64406
+ inFlight = build(arg).then(
64407
+ (value) => {
64408
+ built = { value };
64409
+ failure = void 0;
64410
+ inFlight = void 0;
64411
+ return value;
64412
+ },
64413
+ (error) => {
64414
+ const delay = Math.min(STARTUP_RETRY_MAX_MS, STARTUP_RETRY_MIN_MS * 2 ** failures);
64415
+ failure = { error, retryAt: now() + delay, count: failures + 1 };
64416
+ inFlight = void 0;
64417
+ options.onFailure?.(error);
64418
+ throw error;
64419
+ }
64420
+ );
64421
+ return inFlight;
64422
+ },
64423
+ retryAfterSeconds() {
64424
+ if (!failure) return 0;
64425
+ return Math.max(1, Math.ceil((failure.retryAt - now()) / 1e3));
64426
+ }
64427
+ };
64428
+ }
64429
+ function startupFailureResponse(error, retryAfterSeconds = 1) {
64430
+ const misconfigured = misconfigurationResponse(error);
64431
+ if (misconfigured) return misconfigured;
64432
+ return Response.json(
64433
+ {
64434
+ error: "server_unavailable",
64435
+ code: "SERVER_START_FAILED",
64436
+ message: "The server failed to start, so it refuses requests. Its log has the cause; a request after the Retry-After delay tries to start it again."
64437
+ },
64438
+ { status: 503, headers: { "Retry-After": String(Math.max(1, retryAfterSeconds)) } }
64439
+ );
64440
+ }
63668
64441
  function flowErrorToHttpOutput(error) {
63669
64442
  const misconfiguration = findMisconfiguration(error);
63670
64443
  if (misconfiguration) {
@@ -63698,7 +64471,7 @@ function flowErrorToHttpOutput(error) {
63698
64471
  }
63699
64472
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
63700
64473
  }
63701
- var CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES;
64474
+ var CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
63702
64475
  var init_web_fetch_handler = __esm({
63703
64476
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
63704
64477
  "use strict";
@@ -63717,6 +64490,9 @@ var init_web_fetch_handler = __esm({
63717
64490
  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.",
63718
64491
  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."
63719
64492
  };
64493
+ CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
64494
+ STARTUP_RETRY_MIN_MS = 1e3;
64495
+ STARTUP_RETRY_MAX_MS = 6e4;
63720
64496
  }
63721
64497
  });
63722
64498
 
@@ -63746,9 +64522,9 @@ var init_noop_server = __esm({
63746
64522
  });
63747
64523
 
63748
64524
  // libs/sdk/src/server/security/security-audit.ts
63749
- function auditSecurityDefaults(config, isProduction6) {
64525
+ function auditSecurityDefaults(config, isProduction7) {
63750
64526
  const findings = [];
63751
- if (!isProduction6 && config.deploymentMode !== "distributed") {
64527
+ if (!isProduction7 && config.deploymentMode !== "distributed") {
63752
64528
  return findings;
63753
64529
  }
63754
64530
  const strict = config.security?.strict === true;
@@ -64304,7 +65080,7 @@ var init_server_instance = __esm({
64304
65080
  this.prepare();
64305
65081
  const deploymentMode = getRuntimeContext16().deployment;
64306
65082
  const bindAddress = resolveBindAddress(this.config.security, deploymentMode);
64307
- const isProduction6 = process.env["NODE_ENV"] === "production";
65083
+ const isProduction7 = process.env["NODE_ENV"] === "production";
64308
65084
  const findings = auditSecurityDefaults(
64309
65085
  {
64310
65086
  cors: this.config.cors,
@@ -64312,7 +65088,7 @@ var init_server_instance = __esm({
64312
65088
  resolvedBindAddress: bindAddress,
64313
65089
  deploymentMode
64314
65090
  },
64315
- isProduction6
65091
+ isProduction7
64316
65092
  );
64317
65093
  logSecurityFindings(findings, console);
64318
65094
  await this.host.start(this.config.socketPath ?? this.config.port, bindAddress);
@@ -64729,11 +65505,16 @@ var init_front_mcp = __esm({
64729
65505
  */
64730
65506
  static async createFetchHandler(options) {
64731
65507
  const build = async (deferred) => {
64732
- const frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
65508
+ let frontMcp;
64733
65509
  try {
65510
+ frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
64734
65511
  await frontMcp.ready;
64735
65512
  } catch (err) {
64736
- if (deferred) frontMcp.log?.error("FrontMCP failed to start; requests are refused until it does", err);
65513
+ if (deferred) {
65514
+ const message = "FrontMCP failed to start; requests are refused until a retry succeeds";
65515
+ if (frontMcp?.log) frontMcp.log.error(message, err);
65516
+ else console.error(`[frontmcp] ${message}`, err);
65517
+ }
64737
65518
  throw err;
64738
65519
  }
64739
65520
  const scope = frontMcp.getPrimaryScope();
@@ -64745,23 +65526,13 @@ var init_front_mcp = __esm({
64745
65526
  };
64746
65527
  if (!isEdgeRuntime2()) return build(false);
64747
65528
  assertStaticStartupConfig(options);
64748
- let inner;
64749
- let building;
65529
+ const server = createDeferredServerBuild(() => build(true));
64750
65530
  return async (request, ctx, env) => {
64751
- if (!inner) {
64752
- if (!building) {
64753
- building = build(true).catch((err) => {
64754
- building = void 0;
64755
- throw err;
64756
- });
64757
- }
64758
- try {
64759
- inner = await building;
64760
- } catch (err) {
64761
- const misconfigured = misconfigurationResponse(err);
64762
- if (misconfigured) return misconfigured;
64763
- throw err;
64764
- }
65531
+ let inner;
65532
+ try {
65533
+ inner = await server.get();
65534
+ } catch (err) {
65535
+ return startupFailureResponse(err, server.retryAfterSeconds());
64765
65536
  }
64766
65537
  return inner(request, ctx, env);
64767
65538
  };
@@ -66156,49 +66927,6 @@ var init_dynamic_adapter = __esm({
66156
66927
  }
66157
66928
  });
66158
66929
 
66159
- // libs/sdk/src/common/dynamic/dynamic.plugin.ts
66160
- function isDynamicPluginClass(value) {
66161
- return typeof value === "function" && value.prototype instanceof DynamicPlugin;
66162
- }
66163
- var DynamicPlugin;
66164
- var init_dynamic_plugin = __esm({
66165
- "libs/sdk/src/common/dynamic/dynamic.plugin.ts"() {
66166
- "use strict";
66167
- init_transport_errors();
66168
- init_dynamic_utils();
66169
- DynamicPlugin = class {
66170
- get(token) {
66171
- throw new MethodNotImplementedError("DynamicPlugin", "get");
66172
- }
66173
- /**
66174
- * Static init() method to create a plugin provider.
66175
- * @param options - Input options (with optional fields for defaults)
66176
- */
66177
- static init(options) {
66178
- const extraProviders = options.providers;
66179
- const typedOptions = options;
66180
- if ("useFactory" in options) {
66181
- return {
66182
- ...typedOptions,
66183
- provide: this,
66184
- inject: options.inject,
66185
- useFactory: options.useFactory,
66186
- providers: dedupePluginProviders(extraProviders ?? [])
66187
- };
66188
- }
66189
- const dyn = collectDynamicProviders(this, typedOptions);
66190
- const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
66191
- return {
66192
- ...typedOptions,
66193
- provide: this,
66194
- useValue: new this(options),
66195
- providers: mergedProviders
66196
- };
66197
- }
66198
- };
66199
- }
66200
- });
66201
-
66202
66930
  // libs/sdk/src/common/dynamic/index.ts
66203
66931
  var init_dynamic = __esm({
66204
66932
  "libs/sdk/src/common/dynamic/index.ts"() {
@@ -68088,6 +68816,7 @@ export {
68088
68816
  create,
68089
68817
  createChildSpanContext,
68090
68818
  createContextResolver,
68819
+ createDeferredServerBuild,
68091
68820
  createDirectResolver,
68092
68821
  createElicitationStore,
68093
68822
  createElicitationStoreFromStorage,
@@ -68195,6 +68924,7 @@ export {
68195
68924
  getElicitationSecret,
68196
68925
  getGlobalStoreConfig,
68197
68926
  getNestedValue,
68927
+ getPinnedPublicUrl,
68198
68928
  getRegistryUrl,
68199
68929
  getRequestBaseUrl,
68200
68930
  getRunningTool,
@@ -68212,6 +68942,7 @@ export {
68212
68942
  httpRequestInputSchema,
68213
68943
  httpRespond,
68214
68944
  incrementalAuthConfigSchema,
68945
+ initOptionsOf,
68215
68946
  intentSchema,
68216
68947
  isAlive,
68217
68948
  isAnonymousSubject7 as isAnonymousSubject,
@@ -68370,6 +69101,7 @@ export {
68370
69101
  skillsConfigOptionsSchema,
68371
69102
  skippedAppBehaviorSchema,
68372
69103
  sqliteOptionsSchema,
69104
+ startupFailureResponse,
68373
69105
  staticAuthOptionsSchema,
68374
69106
  storageProviderSchema,
68375
69107
  supportsStreaming,