@frontmcp/sdk 1.8.4 → 1.8.5

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 (86) 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/entries/agent.entry.d.ts +6 -0
  33. package/common/entries/agent.entry.d.ts.map +1 -1
  34. package/common/entries/scope.entry.d.ts +2 -3
  35. package/common/entries/scope.entry.d.ts.map +1 -1
  36. package/common/interfaces/internal/primary-auth-provider.interface.d.ts +5 -5
  37. package/common/interfaces/internal/primary-auth-provider.interface.d.ts.map +1 -1
  38. package/common/interfaces/tool.interface.d.ts.map +1 -1
  39. package/common/metadata/tool.metadata.d.ts +2 -2
  40. package/common/metadata/workflow.metadata.d.ts +1 -1
  41. package/common/schemas/http-output.schema.d.ts +24 -24
  42. package/common/utils/path.utils.d.ts +2 -0
  43. package/common/utils/path.utils.d.ts.map +1 -1
  44. package/context/frontmcp-context.d.ts +4 -0
  45. package/context/frontmcp-context.d.ts.map +1 -1
  46. package/elicitation/helpers/elicit.helper.d.ts.map +1 -1
  47. package/esm/index.mjs +905 -315
  48. package/front-mcp/front-mcp.d.ts.map +1 -1
  49. package/index.d.ts +6 -6
  50. package/index.d.ts.map +1 -1
  51. package/index.js +1031 -439
  52. package/job/job-scope.helper.d.ts.map +1 -1
  53. package/package.json +10 -10
  54. package/scope/flows/http.request.flow.d.ts.map +1 -1
  55. package/scope/scope.instance.d.ts +3 -3
  56. package/scope/scope.instance.d.ts.map +1 -1
  57. package/skill/auth/skill-http-auth.d.ts.map +1 -1
  58. package/skill/flows/http/llm-full-txt.flow.d.ts +1 -1
  59. package/skill/flows/http/llm-txt.flow.d.ts +1 -1
  60. package/skill/flows/http/skills-api.flow.d.ts +1 -1
  61. package/skill/flows/http/skills-api.flow.d.ts.map +1 -1
  62. package/skill/flows/load-skill.flow.d.ts.map +1 -1
  63. package/skill/index.d.ts +1 -1
  64. package/skill/index.d.ts.map +1 -1
  65. package/skill/sep-2640/sep-2640.resource-helpers.d.ts +6 -0
  66. package/skill/sep-2640/sep-2640.resource-helpers.d.ts.map +1 -1
  67. package/skill/skill-filter.helper.d.ts +25 -0
  68. package/skill/skill-filter.helper.d.ts.map +1 -1
  69. package/skill/skill-http.utils.d.ts.map +1 -1
  70. package/skill/skill.events.d.ts.map +1 -1
  71. package/skill/skill.registry.d.ts +94 -17
  72. package/skill/skill.registry.d.ts.map +1 -1
  73. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  74. package/transport/flows/handle.mcp-20260728.flow.d.ts +1 -0
  75. package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
  76. package/transport/flows/handle.streamable-http.flow.d.ts +1 -1
  77. package/transport/index.d.ts +1 -1
  78. package/transport/index.d.ts.map +1 -1
  79. package/transport/mcp-20260728/dispatcher.d.ts +5 -0
  80. package/transport/mcp-20260728/dispatcher.d.ts.map +1 -1
  81. package/transport/mcp-20260728/mrtr.d.ts +8 -0
  82. package/transport/mcp-20260728/mrtr.d.ts.map +1 -1
  83. package/transport/mcp-handlers/skills-load-request.handler.d.ts.map +1 -1
  84. package/transport/web-fetch-handler.d.ts +33 -0
  85. package/transport/web-fetch-handler.d.ts.map +1 -1
  86. package/workflow/engine/workflow.engine.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -6497,30 +6497,30 @@ var require_dist = __commonJS({
6497
6497
  sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6498
6498
  });
6499
6499
  module.exports = __toCommonJS2(index_exports);
6500
- var import_utils113 = __require("@frontmcp/utils");
6500
+ var import_utils114 = __require("@frontmcp/utils");
6501
6501
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6502
6502
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6503
6503
  var KEY_LENGTH = 32;
6504
6504
  function deriveEncryptionKey(secret) {
6505
6505
  const ikm = new TextEncoder().encode(secret);
6506
- return (0, import_utils113.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6506
+ return (0, import_utils114.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6507
6507
  }
6508
6508
  var SEPARATOR = ":";
6509
6509
  function encryptValue(key, plaintext) {
6510
- const iv = (0, import_utils113.randomBytes)(12);
6510
+ const iv = (0, import_utils114.randomBytes)(12);
6511
6511
  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);
6512
+ const { ciphertext, tag } = (0, import_utils114.encryptAesGcm)(key, plaintextBytes, iv);
6513
+ return [(0, import_utils114.base64urlEncode)(iv), (0, import_utils114.base64urlEncode)(tag), (0, import_utils114.base64urlEncode)(ciphertext)].join(SEPARATOR);
6514
6514
  }
6515
6515
  function decryptValue(key, encrypted) {
6516
6516
  const parts = encrypted.split(SEPARATOR);
6517
6517
  if (parts.length !== 3) {
6518
6518
  throw new Error("Invalid encrypted value format");
6519
6519
  }
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);
6520
+ const iv = (0, import_utils114.base64urlDecode)(parts[0]);
6521
+ const tag = (0, import_utils114.base64urlDecode)(parts[1]);
6522
+ const ciphertext = (0, import_utils114.base64urlDecode)(parts[2]);
6523
+ const plaintext = (0, import_utils114.decryptAesGcm)(key, ciphertext, iv, tag);
6524
6524
  return new TextDecoder().decode(plaintext);
6525
6525
  }
6526
6526
  var import_utils210 = __require("@frontmcp/utils");
@@ -10275,7 +10275,33 @@ async function filterDiscoverableSkillResults(scope, registry, results, caller =
10275
10275
  return entry === void 0 || servable.has(entry);
10276
10276
  });
10277
10277
  }
10278
- var SKILLS_HTTP_SURFACE;
10278
+ function skillToolsForCaller(loaded, tools, ctx, surface) {
10279
+ const caller = callerSurface(ctx, surface);
10280
+ if (!tools || caller === void 0 || loaded.availableTools.length === 0) return loaded;
10281
+ const availability = new Map(tools.getTools(true).map((tool) => [tool.name, tool.metadata?.availableWhen]));
10282
+ const notOffered = loaded.availableTools.filter((name44) => !isOfferedOnSurface(availability.get(name44), caller));
10283
+ if (notOffered.length === 0) return loaded;
10284
+ return {
10285
+ ...loaded,
10286
+ availableTools: loaded.availableTools.filter((name44) => !notOffered.includes(name44)),
10287
+ missingTools: [...loaded.missingTools, ...notOffered],
10288
+ isComplete: false,
10289
+ warning: warningWithMissingTools(loaded.warning, loaded.skill?.name, notOffered)
10290
+ };
10291
+ }
10292
+ function warningWithMissingTools(warning, skillName, names) {
10293
+ const prefix = warning?.match(WARNING_PREFIX);
10294
+ if (warning && prefix && warning.endsWith(WARNING_SUFFIX)) {
10295
+ const parts = warning.slice(prefix[0].length, -WARNING_SUFFIX.length).split("; ");
10296
+ const missing = parts.findIndex((part) => part.startsWith(MISSING_PART));
10297
+ if (missing === -1) parts.unshift(`${MISSING_PART}${names.join(", ")}`);
10298
+ else parts[missing] = `${parts[missing]}, ${names.join(", ")}`;
10299
+ return `${prefix[0]}${parts.join("; ")}${WARNING_SUFFIX}`;
10300
+ }
10301
+ const sentence = `Skill "${skillName ?? "skill"}" references ${MISSING_PART}${names.join(", ")}${WARNING_SUFFIX}`;
10302
+ return warning ? `${warning} ${sentence}` : sentence;
10303
+ }
10304
+ var SKILLS_HTTP_SURFACE, WARNING_PREFIX, WARNING_SUFFIX, MISSING_PART;
10279
10305
  var init_skill_filter_helper = __esm({
10280
10306
  "libs/sdk/src/skill/skill-filter.helper.ts"() {
10281
10307
  "use strict";
@@ -10284,6 +10310,9 @@ var init_skill_filter_helper = __esm({
10284
10310
  init_skill_authorities_helper();
10285
10311
  init_skill_entry_resolver();
10286
10312
  SKILLS_HTTP_SURFACE = "mcp";
10313
+ WARNING_PREFIX = /^Skill "(.*)" references /;
10314
+ WARNING_SUFFIX = ". Some functionality may be limited.";
10315
+ MISSING_PART = "missing tools: ";
10287
10316
  }
10288
10317
  });
10289
10318
 
@@ -10384,7 +10413,13 @@ async function formatSkillsForLlmFull(registry, toolRegistry, visibility = "both
10384
10413
  }
10385
10414
  const loaded = await registry.loadSkill(skill.name);
10386
10415
  if (loaded) {
10387
- parts.push(formatSkillForLLMWithSchemas(loaded.skill, loaded.availableTools, loaded.missingTools, toolRegistry));
10416
+ const { availableTools, missingTools } = skillToolsForCaller(
10417
+ loaded,
10418
+ toolRegistry,
10419
+ void 0,
10420
+ SKILLS_HTTP_SURFACE
10421
+ );
10422
+ parts.push(formatSkillForLLMWithSchemas(loaded.skill, availableTools, missingTools, toolRegistry));
10388
10423
  }
10389
10424
  }
10390
10425
  return parts.join("\n\n---\n\n");
@@ -10504,6 +10539,7 @@ function skillToApiResponse(skill, loadResult) {
10504
10539
  var init_skill_http_utils = __esm({
10505
10540
  "libs/sdk/src/skill/skill-http.utils.ts"() {
10506
10541
  "use strict";
10542
+ init_skill_filter_helper();
10507
10543
  }
10508
10544
  });
10509
10545
 
@@ -11065,7 +11101,11 @@ function skillsLoadRequestHandler({
11065
11101
  throw toSdkMcpError(err);
11066
11102
  }
11067
11103
  }
11068
- const { skill, availableTools, missingTools, isComplete, warning } = loadResult;
11104
+ const { skill, availableTools, missingTools, isComplete, warning } = skillToolsForCaller(
11105
+ loadResult,
11106
+ toolRegistry,
11107
+ withMcpSurface(scope, ctx)
11108
+ );
11069
11109
  if (warning) {
11070
11110
  warnings.push(warning);
11071
11111
  }
@@ -14614,6 +14654,9 @@ function readPublicUrlPin() {
14614
14654
  function isPublicUrlPinned() {
14615
14655
  return readPublicUrlPin() !== void 0;
14616
14656
  }
14657
+ function getPinnedPublicUrl() {
14658
+ return readPublicUrlPin();
14659
+ }
14617
14660
  function isProxyTrusted() {
14618
14661
  try {
14619
14662
  if (typeof process !== "undefined" && process.env) {
@@ -16061,6 +16104,11 @@ async function performElicit(deps, message, requestedSchema, options) {
16061
16104
  }
16062
16105
  const ctx = tryGetContext();
16063
16106
  const mrtr = ctx?.getMrtrExchange?.();
16107
+ if (mrtr && mrtr.clientDeclaredRevision === false && mrtr.supportsElicitation?.(options?.mode ?? "form") !== true) {
16108
+ throw new ElicitationNotSupportedError(
16109
+ "This client does not support elicitation: it declared no elicitation capability, and without a session the elicitation fallback has no one to answer it"
16110
+ );
16111
+ }
16064
16112
  if (mrtr) {
16065
16113
  const zodSchema = requestedSchema instanceof z45.ZodType ? requestedSchema : z45.object(requestedSchema);
16066
16114
  const answer = mrtr.resolveElicitation({
@@ -22428,6 +22476,7 @@ var init_call_tool_flow = __esm({
22428
22476
  context.mark("createToolCallContext");
22429
22477
  context._toolNameInternal = tool.metadata.id ?? tool.metadata.name;
22430
22478
  context._toolInputInternal = input.arguments;
22479
+ context._jsonRpcRequestIdInternal = this.state.jsonRpcRequestId;
22431
22480
  const frontmcpContext = context.tryGetContext();
22432
22481
  const authInfoWithTransport = authInfo;
22433
22482
  if (frontmcpContext && authInfoWithTransport?.transport?.sendElicitRequest) {
@@ -27297,7 +27346,11 @@ var init_skill_events = __esm({
27297
27346
  */
27298
27347
  emit(event) {
27299
27348
  for (const listener of [...this.listeners]) {
27300
- listener(event);
27349
+ try {
27350
+ listener(event);
27351
+ } catch (err) {
27352
+ console.error("SkillEmitter listener error:", err instanceof Error ? err.message : "Unknown error");
27353
+ }
27301
27354
  }
27302
27355
  }
27303
27356
  /**
@@ -27578,6 +27631,24 @@ var init_skill_instance = __esm({
27578
27631
  // libs/sdk/src/skill/skill.registry.ts
27579
27632
  import { tokenName as tokenName9 } from "@frontmcp/di";
27580
27633
  import { getRuntimeContext as getRuntimeContext11, isEntryAvailable as isEntryAvailable5 } from "@frontmcp/utils";
27634
+ function findIdPathCollision(skills) {
27635
+ const byPath = /* @__PURE__ */ new Map();
27636
+ for (const skill of skills) {
27637
+ const path3 = skill.getSkillPath();
27638
+ if (!byPath.has(path3)) byPath.set(path3, skill);
27639
+ }
27640
+ for (const idOwner of skills) {
27641
+ const id = idOwner.metadata.id;
27642
+ if (!id || id === idOwner.getSkillPath()) continue;
27643
+ const pathOwner = byPath.get(id);
27644
+ if (pathOwner) return { idOwner, pathOwner };
27645
+ }
27646
+ return void 0;
27647
+ }
27648
+ function describeIdPathCollision({ idOwner, pathOwner }) {
27649
+ const id = idOwner.metadata.id;
27650
+ 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.`;
27651
+ }
27581
27652
  var DEFAULT_LIST_LIMIT, SkillRegistry;
27582
27653
  var init_skill_registry = __esm({
27583
27654
  "libs/sdk/src/skill/skill.registry.ts"() {
@@ -27609,16 +27680,30 @@ var init_skill_registry = __esm({
27609
27680
  dynamicContents = /* @__PURE__ */ new Map();
27610
27681
  /**
27611
27682
  * 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.
27683
+ * registerSkillContent call, and on every removal, so a stale unregister
27684
+ * handle held by an earlier caller cannot remove a later registration. Never
27685
+ * reset: a counter restarted at 1 would match an old handle's generation.
27614
27686
  */
27615
27687
  dynamicGenerations = /* @__PURE__ */ new Map();
27688
+ /**
27689
+ * Ids of skills that left the registry (its own dynamic skills, or an adopted child's) whose copy
27690
+ * in the storage provider is not removed yet: the removal is pending, or the provider failed it.
27691
+ * Provider-backed reads skip them until the removal succeeds or the id is served again. Each maps
27692
+ * to the number of its withdrawal.
27693
+ */
27694
+ withdrawn = /* @__PURE__ */ new Map();
27695
+ /** Numbers each withdrawal, so a removal that completes late clears only its own. */
27696
+ withdrawals = 0;
27697
+ /** The last storage-provider write queued per skill id ({@link syncProvider}). */
27698
+ providerWrites = /* @__PURE__ */ new Map();
27616
27699
  /** Whether a plugin registers skills at runtime ({@link expectDynamicSkills}). */
27617
27700
  dynamicSkillsExpected = false;
27618
27701
  /** Adopted skills from child registries */
27619
27702
  adopted = /* @__PURE__ */ new Map();
27620
27703
  /** Children registries */
27621
27704
  children = /* @__PURE__ */ new Set();
27705
+ /** The registries that adopted this one: a skill registered here is served through them too. */
27706
+ adopters = /* @__PURE__ */ new Set();
27622
27707
  /** O(1) indexes */
27623
27708
  byQualifiedId = /* @__PURE__ */ new Map();
27624
27709
  /** Owner lineage per skill instance, rebuilt with the index so `lineageOf` is O(1). */
@@ -27703,8 +27788,10 @@ var init_skill_registry = __esm({
27703
27788
  const row = this.makeRow(token, instance, lineage, this);
27704
27789
  this.localRows.push(row);
27705
27790
  try {
27706
- const content = await instance.load();
27707
- await this.storageProvider.add(content);
27791
+ await this.syncProvider(instance.metadata.id ?? instance.metadata.name, async () => {
27792
+ const content = await instance.load();
27793
+ await this.storageProvider.add(content);
27794
+ });
27708
27795
  } catch (error) {
27709
27796
  this.scope.logger.warn(`Failed to load skill ${instance.name}: ${error.message}`);
27710
27797
  }
@@ -27727,6 +27814,10 @@ var init_skill_registry = __esm({
27727
27814
  await this.adoptFromChild(child, child.owner);
27728
27815
  }
27729
27816
  }
27817
+ const collision = findIdPathCollision(this.listAllInstances());
27818
+ if (collision) {
27819
+ throw new InvalidSkillError(collision.idOwner.metadata.name, describeIdPathCollision(collision));
27820
+ }
27730
27821
  this.reindex();
27731
27822
  logAvailabilityFiltering(
27732
27823
  "SkillRegistry",
@@ -27746,24 +27837,31 @@ var init_skill_registry = __esm({
27746
27837
  const adoptedRows = childRows.map((r) => this.relineage(r, prepend));
27747
27838
  this.adopted.set(child, adoptedRows);
27748
27839
  this.children.add(child);
27840
+ child.adopters.add(this);
27749
27841
  child.subscribe({ immediate: false }, () => {
27842
+ const previous = this.adopted.get(child) ?? [];
27750
27843
  const latest = child.listAllIndexed().map((r) => this.relineage(r, prepend));
27751
27844
  this.adopted.set(child, latest);
27752
27845
  this.reindex();
27753
- this.bump("reset");
27846
+ this.withdrawGoneAdoptedSkills(previous);
27754
27847
  for (const row of latest) {
27755
- row.instance.load().then((content) => {
27756
- this.storageProvider.add(content);
27848
+ const skillId = row.instance.metadata.id ?? row.instance.metadata.name;
27849
+ this.syncProvider(skillId, async () => {
27850
+ const content = await row.instance.load();
27851
+ await this.storageProvider.add(content);
27757
27852
  }).catch((error) => {
27758
27853
  this.scope.logger.warn(`Failed to reload adopted skill ${row.baseName}: ${error.message}`);
27759
27854
  });
27760
27855
  }
27856
+ this.bump("reset");
27761
27857
  });
27762
27858
  await Promise.all(
27763
27859
  adoptedRows.map(async (row) => {
27764
27860
  try {
27765
- const content = await row.instance.load();
27766
- await this.storageProvider.add(content);
27861
+ await this.syncProvider(row.instance.metadata.id ?? row.instance.metadata.name, async () => {
27862
+ const content = await row.instance.load();
27863
+ await this.storageProvider.add(content);
27864
+ });
27767
27865
  } catch (error) {
27768
27866
  this.scope.logger.warn(`Failed to load adopted skill ${row.baseName}: ${error.message}`);
27769
27867
  }
@@ -27772,6 +27870,37 @@ var init_skill_registry = __esm({
27772
27870
  this.reindex();
27773
27871
  this.bump("reset");
27774
27872
  }
27873
+ /**
27874
+ * The `skill://` namespaces this registry's skills are served in: its own, and that of every
27875
+ * registry that adopts it, directly or through another, sibling registries included.
27876
+ */
27877
+ namespacesServingThis() {
27878
+ const registries = /* @__PURE__ */ new Set();
27879
+ const climb = (registry) => {
27880
+ if (registries.has(registry)) return;
27881
+ registries.add(registry);
27882
+ for (const adopter of registry.adopters) climb(adopter);
27883
+ };
27884
+ climb(this);
27885
+ return [...registries].map((registry) => registry.liveSkills());
27886
+ }
27887
+ /**
27888
+ * This registry's skills and those of every registry it adopted, read from each registry's own rows.
27889
+ * An adopter's copy of a child's rows is refreshed only on the child's change event, which a
27890
+ * registration emits after it has committed its row and awaited the storage provider.
27891
+ */
27892
+ liveSkills() {
27893
+ const registries = /* @__PURE__ */ new Set();
27894
+ const skills = /* @__PURE__ */ new Set();
27895
+ const descend = (registry) => {
27896
+ if (registries.has(registry)) return;
27897
+ registries.add(registry);
27898
+ for (const row of [...registry.localRows, ...registry.dynamicRows]) skills.add(row.instance);
27899
+ for (const child of registry.children) descend(child);
27900
+ };
27901
+ descend(this);
27902
+ return [...skills];
27903
+ }
27775
27904
  /* -------------------- Public API -------------------- */
27776
27905
  /**
27777
27906
  * Get all skills in the registry.
@@ -27889,7 +28018,14 @@ var init_skill_registry = __esm({
27889
28018
  }
27890
28019
  }
27891
28020
  async search(query, options) {
27892
- const baseResults = await this.storageProvider.search(query, options);
28021
+ const topK = options?.topK;
28022
+ const hidden = this.hiddenFromProviderRead();
28023
+ const providerOptions = hidden.size > 0 && typeof topK === "number" && topK >= 0 ? { ...options, topK: topK + hidden.size } : options;
28024
+ let baseResults = await this.storageProvider.search(query, providerOptions);
28025
+ if (hidden.size > 0 || this.withdrawn.size > 0) {
28026
+ baseResults = baseResults.filter((r) => !hidden.has(r.metadata));
28027
+ if (typeof topK === "number" && topK >= 0) baseResults = baseResults.slice(0, topK);
28028
+ }
27893
28029
  if (this.dynamicContents.size === 0) return baseResults;
27894
28030
  const baseSeen = /* @__PURE__ */ new Set();
27895
28031
  let rewrites = 0;
@@ -27913,7 +28049,6 @@ var init_skill_registry = __esm({
27913
28049
  const overlayOnly = this.searchDynamicOverlay(query, options, baseSeen);
27914
28050
  if (overlayOnly.length === 0 && rewrites === 0) return baseResults;
27915
28051
  const merged = [...rewrittenBase, ...overlayOnly].sort((a, b) => b.score - a.score);
27916
- const topK = options?.topK;
27917
28052
  return typeof topK === "number" && topK >= 0 ? merged.slice(0, topK) : merged;
27918
28053
  }
27919
28054
  /**
@@ -27968,7 +28103,10 @@ var init_skill_registry = __esm({
27968
28103
  warning
27969
28104
  };
27970
28105
  }
28106
+ const hidden = this.hiddenFromProviderRead();
28107
+ if (hidden.has({ name: skillId })) return void 0;
27971
28108
  const result = await this.storageProvider.load(skillId);
28109
+ if (result && hidden.has(result.skill)) return void 0;
27972
28110
  return result ?? void 0;
27973
28111
  }
27974
28112
  /**
@@ -28004,8 +28142,12 @@ var init_skill_registry = __esm({
28004
28142
  * and `hasMore` stay correct.
28005
28143
  */
28006
28144
  async listSkills(options) {
28007
- if (this.dynamicContents.size === 0) return this.storageProvider.list(options);
28008
- const base = await this.listAllFromProvider(options);
28145
+ const hidden = this.hiddenFromProviderRead();
28146
+ if (this.dynamicContents.size === 0 && hidden.size === 0) {
28147
+ const page = await this.storageProvider.list(options);
28148
+ if (this.withdrawn.size === 0) return page;
28149
+ }
28150
+ const base = (await this.listAllFromProvider(options)).filter((meta) => !hidden.has(meta));
28009
28151
  const baseIds = /* @__PURE__ */ new Set();
28010
28152
  const merged = base.map((meta) => {
28011
28153
  const id = meta.id ?? meta.name;
@@ -28065,21 +28207,20 @@ var init_skill_registry = __esm({
28065
28207
  /**
28066
28208
  * Get total skill count.
28067
28209
  *
28068
- * Sums the provider count with overlay rows the provider does not know
28210
+ * Counts the provider's rows with overlay rows the provider does not know
28069
28211
  * about so callers see the same surface as `search` / `listSkills`.
28070
28212
  */
28071
28213
  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
- });
28214
+ const hidden = this.hiddenFromProviderRead();
28215
+ if (this.dynamicContents.size === 0 && hidden.size === 0) {
28216
+ const counted = await this.storageProvider.count(options);
28217
+ if (this.withdrawn.size === 0) return counted;
28218
+ }
28219
+ const baseList = (await this.listAllFromProvider({ tags: options?.tags, includeHidden: options?.includeHidden })).filter((m) => !hidden.has(m));
28079
28220
  const baseIds = /* @__PURE__ */ new Set();
28080
- for (const m of baseList.skills) baseIds.add(m.id ?? m.name);
28221
+ for (const m of baseList) baseIds.add(m.id ?? m.name);
28081
28222
  const overlay = this.collectDynamicMetadata(options).filter((m) => !baseIds.has(m.id ?? m.name));
28082
- return baseCount + overlay.length;
28223
+ return baseList.length + overlay.length;
28083
28224
  }
28084
28225
  /**
28085
28226
  * Project a SkillContent into the SkillMetadata shape callers expect from
@@ -28369,10 +28510,6 @@ var init_skill_registry = __esm({
28369
28510
  throw new PublicMcpError("registerSkillContent: SkillContent.name is required", "INVALID_PARAMS");
28370
28511
  }
28371
28512
  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
28513
  const metadata = {
28377
28514
  id,
28378
28515
  name: content.name,
@@ -28408,28 +28545,49 @@ var init_skill_registry = __esm({
28408
28545
  `[SkillRegistry] Failed to pre-load dynamic skill ${id} from source ${opts?.source ?? "unknown"}: ${error.message}`
28409
28546
  );
28410
28547
  }
28548
+ const existingIdx = this.dynamicRows.findIndex((r) => r.instance.name === id);
28549
+ const superseded = new Set(opts?.supersedes);
28550
+ const leaving = new Set(
28551
+ this.dynamicRows.filter((r) => r.instance.name === id || superseded.has(r.instance.name)).map((r) => r.instance)
28552
+ );
28553
+ for (const skills of this.namespacesServingThis()) {
28554
+ const collision = findIdPathCollision([...skills.filter((other) => !leaving.has(other)), instance]);
28555
+ if (collision) {
28556
+ throw new PublicMcpError(`registerSkillContent: ${describeIdPathCollision(collision)}`, "INVALID_PARAMS");
28557
+ }
28558
+ }
28559
+ if (existingIdx !== -1) {
28560
+ this.dynamicRows.splice(existingIdx, 1);
28561
+ }
28562
+ const mutable = this.asMutableProvider(this.storageProvider);
28563
+ const removed = this.dynamicRows.filter((r) => superseded.has(r.instance.name) && findIdPathCollision([r.instance, instance])).map((r) => r.instance.name);
28564
+ for (const removedId of removed) {
28565
+ this.withdrawDynamicSkill(removedId, mutable);
28566
+ }
28411
28567
  const lineage = this.owner ? [this.owner] : [];
28412
28568
  const row = this.makeRow(provideToken, instance, lineage, this);
28413
28569
  this.dynamicRows.push(row);
28414
28570
  this.dynamicContents.set(id, structuredClone(content));
28571
+ this.withdrawn.delete(id);
28415
28572
  const generation = (this.dynamicGenerations.get(id) ?? 0) + 1;
28416
28573
  this.dynamicGenerations.set(id, generation);
28417
- const mutable = this.asMutableProvider(this.storageProvider);
28418
- if (mutable) {
28419
- try {
28574
+ this.reindex();
28575
+ const providerWrites = mutable ? [
28576
+ ...removed.map((removedId) => this.removeFromProvider(mutable, removedId)),
28577
+ this.syncProvider(id, async () => {
28420
28578
  if (await mutable.exists(id)) {
28421
28579
  await mutable.update(id, content);
28422
28580
  } else {
28423
28581
  await mutable.add(content);
28424
28582
  }
28425
- } catch (error) {
28583
+ }).catch((error) => {
28426
28584
  this.scope.logger.warn(
28427
28585
  `[SkillRegistry] Failed to index dynamic skill ${id} in storage provider: ${error.message}`
28428
28586
  );
28429
- }
28430
- }
28431
- this.reindex();
28587
+ })
28588
+ ] : [];
28432
28589
  this.bump("reset");
28590
+ await Promise.all(providerWrites);
28433
28591
  let unregistered = false;
28434
28592
  const unregister = async () => {
28435
28593
  if (unregistered) return;
@@ -28442,32 +28600,99 @@ var init_skill_registry = __esm({
28442
28600
  this.scope.logger.debug(
28443
28601
  `[SkillRegistry] Registered dynamic skill ${id}${opts?.source ? ` (source: ${opts.source})` : ""}`
28444
28602
  );
28445
- return { id, unregister };
28603
+ return { id, unregister, removed };
28446
28604
  }
28447
28605
  /**
28448
28606
  * Remove a previously-registered dynamic skill by id.
28449
28607
  */
28450
28608
  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);
28609
+ if (!this.dynamicRows.some((r) => r.instance.name === id)) return false;
28456
28610
  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
- }
28611
+ this.withdrawDynamicSkill(id, mutable);
28466
28612
  this.reindex();
28613
+ const removal = mutable ? this.removeFromProvider(mutable, id) : void 0;
28467
28614
  this.bump("reset");
28615
+ await removal;
28468
28616
  this.scope.logger.debug(`[SkillRegistry] Unregistered dynamic skill ${id}`);
28469
28617
  return true;
28470
28618
  }
28619
+ /**
28620
+ * Take a dynamic skill out of the registry, synchronously. Its generation advances so a handle
28621
+ * from an earlier registration stays a no-op, and while a mutable provider still holds its copy it
28622
+ * is withdrawn from provider-backed reads.
28623
+ */
28624
+ withdrawDynamicSkill(id, mutable) {
28625
+ this.dynamicRows.splice(
28626
+ this.dynamicRows.findIndex((r) => r.instance.name === id),
28627
+ 1
28628
+ );
28629
+ this.dynamicContents.delete(id);
28630
+ this.dynamicGenerations.set(id, (this.dynamicGenerations.get(id) ?? 0) + 1);
28631
+ if (mutable) this.withdrawn.set(id, ++this.withdrawals);
28632
+ }
28633
+ /** Remove a withdrawn skill's copy from the provider. If that fails it stays withdrawn from reads. */
28634
+ async removeFromProvider(mutable, id) {
28635
+ const withdrawal = this.withdrawn.get(id);
28636
+ try {
28637
+ await this.syncProvider(id, () => mutable.remove(id));
28638
+ if (this.withdrawn.get(id) === withdrawal) this.withdrawn.delete(id);
28639
+ } catch (error) {
28640
+ this.scope.logger.warn(
28641
+ `[SkillRegistry] Failed to remove skill ${id} from storage provider; it stays hidden from provider-backed reads: ${error.message}`
28642
+ );
28643
+ }
28644
+ }
28645
+ /**
28646
+ * Run a storage-provider write for one skill id after the ones queued before it for that id, so a
28647
+ * removal that completes late cannot delete the copy a later registration added. The returned
28648
+ * promise settles with the write; a failure does not hold back the next one.
28649
+ */
28650
+ syncProvider(id, write) {
28651
+ const run = (this.providerWrites.get(id) ?? Promise.resolve()).then(write);
28652
+ const settled = run.then(
28653
+ () => void 0,
28654
+ () => void 0
28655
+ );
28656
+ this.providerWrites.set(id, settled);
28657
+ void settled.then(() => {
28658
+ if (this.providerWrites.get(id) === settled) this.providerWrites.delete(id);
28659
+ });
28660
+ return run;
28661
+ }
28662
+ /**
28663
+ * After a child's change, take the skills that left it (and are not served here some other way)
28664
+ * out of this registry's storage provider too. The re-add on change only adds, so without this an
28665
+ * adopter's provider kept serving a skill the child had removed. Until the removal completes, and
28666
+ * if it fails, provider-backed reads skip it.
28667
+ */
28668
+ withdrawGoneAdoptedSkills(previous) {
28669
+ const mutable = this.asMutableProvider(this.storageProvider);
28670
+ if (!mutable) return;
28671
+ const skillIdOf = (entry) => entry.metadata.id ?? entry.metadata.name;
28672
+ const live = new Set(this.listAllIndexed().map((r) => skillIdOf(r.instance)));
28673
+ for (const id of live) this.withdrawn.delete(id);
28674
+ for (const row of previous) {
28675
+ const id = skillIdOf(row.instance);
28676
+ if (live.has(id) || this.withdrawn.has(id)) continue;
28677
+ this.withdrawn.set(id, ++this.withdrawals);
28678
+ void this.removeFromProvider(mutable, id);
28679
+ }
28680
+ }
28681
+ /**
28682
+ * Which provider-backed rows a read that starts now must skip: a skill withdrawn when the read
28683
+ * starts (its removal may complete while the provider answers, clearing the withdrawal, yet the
28684
+ * answer can still hold it), or withdrawn by the time the answer arrives.
28685
+ */
28686
+ hiddenFromProviderRead() {
28687
+ const atStart = new Set(this.withdrawn.keys());
28688
+ return {
28689
+ size: atStart.size,
28690
+ has: (meta) => {
28691
+ const id = meta.id ?? meta.name;
28692
+ return atStart.has(id) || this.withdrawn.has(id);
28693
+ }
28694
+ };
28695
+ }
28471
28696
  /* -------------------- Internal helpers -------------------- */
28472
28697
  asMutableProvider(provider) {
28473
28698
  const candidate = provider;
@@ -29450,7 +29675,7 @@ var init_agent_instance = __esm({
29450
29675
  "hideFromDiscovery"
29451
29676
  ]);
29452
29677
  AgentInstance = class extends AgentEntry {
29453
- providers;
29678
+ providerRegistry;
29454
29679
  scope;
29455
29680
  hooks;
29456
29681
  /** The LLM adapter for this agent */
@@ -29468,11 +29693,11 @@ var init_agent_instance = __esm({
29468
29693
  constructor(record, providers, owner) {
29469
29694
  super(record);
29470
29695
  this.owner = owner;
29471
- this.providers = providers;
29696
+ this.providerRegistry = providers;
29472
29697
  this.name = record.metadata.id ?? record.metadata.name;
29473
29698
  this.id = record.metadata.id ?? record.metadata.name;
29474
29699
  this.fullName = this.owner.id + ":" + this.name;
29475
- this.scope = this.providers.getActiveScope();
29700
+ this.scope = this.providerRegistry.getActiveScope();
29476
29701
  this.hooks = this.scope.hooks;
29477
29702
  this.inputSchema = record.metadata.inputSchema ?? {};
29478
29703
  this.outputSchema = record.metadata.outputSchema;
@@ -29531,11 +29756,11 @@ var init_agent_instance = __esm({
29531
29756
  const adapterOptions = {
29532
29757
  providerResolver: {
29533
29758
  get: (token) => {
29534
- return this.providers.get(token);
29759
+ return this.providerRegistry.get(token);
29535
29760
  },
29536
29761
  tryGet: (token) => {
29537
29762
  try {
29538
- return this.providers.get(token);
29763
+ return this.providerRegistry.get(token);
29539
29764
  } catch {
29540
29765
  return void 0;
29541
29766
  }
@@ -29548,7 +29773,7 @@ var init_agent_instance = __esm({
29548
29773
  }
29549
29774
  };
29550
29775
  try {
29551
- const configService = this.providers.get(ConfigService);
29776
+ const configService = this.providerRegistry.get(ConfigService);
29552
29777
  if (configService) {
29553
29778
  adapterOptions.configResolver = this.createConfigResolver(configService);
29554
29779
  }
@@ -29656,7 +29881,7 @@ var init_agent_instance = __esm({
29656
29881
  const toolMetadata = this.buildAgentToolMetadata();
29657
29882
  const agentToolFunction = frontMcpTool(toolMetadata)(this.createAgentToolExecuteHandler());
29658
29883
  const toolRecord = normalizeTool(agentToolFunction);
29659
- this.agentToolInstance = new ToolInstance(toolRecord, this.providers, this.owner);
29884
+ this.agentToolInstance = new ToolInstance(toolRecord, this.providerRegistry, this.owner);
29660
29885
  await this.agentToolInstance.ready;
29661
29886
  this.scope.logger.debug(`Created agent tool instance: ${this.agentToolInstance.name} for agent ${this.name}`);
29662
29887
  } catch (error) {
@@ -29698,16 +29923,34 @@ var init_agent_instance = __esm({
29698
29923
  /**
29699
29924
  * Create the execute handler for the agent tool.
29700
29925
  *
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.
29926
+ * The tool's `tools:call-tool` flow has already applied everything the agent declares
29927
+ * (authorities, rate limit, concurrency, timeout and plugin gates, copied onto the tool). The
29928
+ * handler then runs the agent through the `agents:call-agent` flow, so the hooks registered for
29929
+ * agent invocation (`AgentCallHook`, and the agent class's own hooks on that flow) run and the
29930
+ * agent's context gets the request's context providers. The flow's own gates are left to the
29931
+ * tool flow (`gatedBy`), so nothing is counted twice.
29703
29932
  */
29704
29933
  createAgentToolExecuteHandler() {
29705
29934
  return async (input, toolCtx) => {
29706
29935
  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;
29936
+ const progressToken = toolCtx._progressTokenInternal;
29937
+ const requestId = toolCtx._jsonRpcRequestIdInternal;
29938
+ return this.scope.runFlowForOutput("agents:call-agent", {
29939
+ request: {
29940
+ method: "tools/call",
29941
+ params: {
29942
+ name: this.id,
29943
+ arguments: input,
29944
+ ...progressToken !== void 0 ? { _meta: { progressToken } } : {}
29945
+ }
29946
+ },
29947
+ ctx: {
29948
+ authInfo,
29949
+ ...requestId !== void 0 ? { requestId } : {},
29950
+ ...toolCtx.signal ? { signal: toolCtx.signal } : {}
29951
+ },
29952
+ gatedBy: "tools:call-tool"
29953
+ });
29711
29954
  };
29712
29955
  }
29713
29956
  /**
@@ -29721,6 +29964,10 @@ var init_agent_instance = __esm({
29721
29964
  getToolInstance() {
29722
29965
  return this.agentToolInstance;
29723
29966
  }
29967
+ /** The provider registry this agent was registered with (its app's). */
29968
+ get providers() {
29969
+ return this.providerRegistry;
29970
+ }
29724
29971
  /**
29725
29972
  * The tools this agent can call: those it declares and those its plugins contribute, as its private
29726
29973
  * scope holds them. Empty until the agent is initialized.
@@ -29770,7 +30017,7 @@ var init_agent_instance = __esm({
29770
30017
  case "VALUE" /* VALUE */:
29771
30018
  return this.record.useValue;
29772
30019
  case "FACTORY" /* FACTORY */:
29773
- return this.record.useFactory(this.providers);
30020
+ return this.record.useFactory(this.providerRegistry);
29774
30021
  case "ESM" /* ESM */:
29775
30022
  throw new AgentNotConfiguredError(`ESM agent "${this.name}" cannot be created via AgentInstance.create()`);
29776
30023
  case "REMOTE" /* REMOTE */:
@@ -29789,11 +30036,13 @@ var init_agent_instance = __esm({
29789
30036
  * @param llmAdapter - The LLM adapter (passed explicitly to avoid non-null assertion)
29790
30037
  */
29791
30038
  buildAgentCtorArgs(input, ctx, llmAdapter) {
29792
- const scope = this.providers.getActiveScope();
30039
+ const scope = this.providerRegistry.getActiveScope();
29793
30040
  return {
29794
30041
  metadata: this.metadata,
29795
30042
  input,
29796
- providers: this.providers,
30043
+ // The request's context-aware providers when the agents:call-agent flow built them, so
30044
+ // `this.context` and CONTEXT-scoped providers resolve inside the agent.
30045
+ providers: ctx.contextProviders ?? this.providerRegistry,
29797
30046
  logger: scope.logger,
29798
30047
  authInfo: ctx.authInfo,
29799
30048
  llmAdapter,
@@ -29864,7 +30113,8 @@ var init_agent_instance = __esm({
29864
30113
  await this.assertToolAuthorized(tool, ctx.authInfo, args);
29865
30114
  const runningTool = { name: tool.name, fullName: tool.fullName };
29866
30115
  return runOnSurface(AGENT_SURFACE, () => {
29867
- const toolContext = runAsTool(runningTool, () => tool.create(args, ctx));
30116
+ const { contextProviders: _agentProviders, ...toolCtx } = ctx;
30117
+ const toolContext = runAsTool(runningTool, () => tool.create(args, toolCtx));
29868
30118
  return runAsTool(runningTool, () => Promise.resolve(toolContext.execute(args)));
29869
30119
  });
29870
30120
  };
@@ -30403,6 +30653,53 @@ var init_agent_registry = __esm({
30403
30653
  }
30404
30654
  });
30405
30655
 
30656
+ // libs/sdk/src/channel/sources/completion-events.ts
30657
+ function completionEventsOf(scope) {
30658
+ let events = byScope.get(scope);
30659
+ if (!events) {
30660
+ events = new ScopeCompletionEvents();
30661
+ byScope.set(scope, events);
30662
+ }
30663
+ return events;
30664
+ }
30665
+ function completionOutputText(value) {
30666
+ if (value === void 0) return void 0;
30667
+ if (typeof value === "string") return value;
30668
+ try {
30669
+ return JSON.stringify(value);
30670
+ } catch {
30671
+ return String(value);
30672
+ }
30673
+ }
30674
+ var CompletionStream, ScopeCompletionEvents, byScope;
30675
+ var init_completion_events = __esm({
30676
+ "libs/sdk/src/channel/sources/completion-events.ts"() {
30677
+ "use strict";
30678
+ CompletionStream = class {
30679
+ listeners = /* @__PURE__ */ new Set();
30680
+ subscribe(listener) {
30681
+ this.listeners.add(listener);
30682
+ return () => {
30683
+ this.listeners.delete(listener);
30684
+ };
30685
+ }
30686
+ emit(event) {
30687
+ for (const listener of [...this.listeners]) {
30688
+ try {
30689
+ listener(event);
30690
+ } catch {
30691
+ }
30692
+ }
30693
+ }
30694
+ };
30695
+ ScopeCompletionEvents = class {
30696
+ agents = new CompletionStream();
30697
+ jobs = new CompletionStream();
30698
+ };
30699
+ byScope = /* @__PURE__ */ new WeakMap();
30700
+ }
30701
+ });
30702
+
30406
30703
  // libs/sdk/src/agent/flows/call-agent.flow.ts
30407
30704
  import { AuthorityDeniedError as AuthorityDeniedError7, resolveRequiredScopes as resolveRequiredScopes5 } from "@frontmcp/auth";
30408
30705
  import { ExecutionTimeoutError as ExecutionTimeoutError2, withTimeout as withTimeout2 } from "@frontmcp/guard";
@@ -30412,13 +30709,22 @@ var inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallA
30412
30709
  var init_call_agent_flow = __esm({
30413
30710
  "libs/sdk/src/agent/flows/call-agent.flow.ts"() {
30414
30711
  "use strict";
30712
+ init_completion_events();
30415
30713
  init_common();
30416
30714
  init_errors();
30417
30715
  init_hooks_utils();
30716
+ init_flow_context_providers();
30418
30717
  inputSchema15 = z60.object({
30419
30718
  request: CallToolRequestSchema3,
30420
30719
  // z.any() used because ctx is the MCP SDK's ToolCallExtra type which varies by SDK version
30421
- ctx: z60.any()
30720
+ ctx: z60.any(),
30721
+ /**
30722
+ * Set when the agent's `invoke_<agent>` tool runs this flow. That tool's `tools:call-tool` flow
30723
+ * has already applied the agent's authorities, rate limit, concurrency and timeout (they are
30724
+ * copied onto the tool), so this flow's stages for them leave the checks to it instead of
30725
+ * counting the call twice. Their hooks still run.
30726
+ */
30727
+ gatedBy: z60.literal("tools:call-tool").optional()
30422
30728
  });
30423
30729
  outputSchema14 = CallToolResultSchema3;
30424
30730
  stateSchema14 = z60.object({
@@ -30451,7 +30757,13 @@ var init_call_agent_flow = __esm({
30451
30757
  // Semaphore ticket for concurrency control (set by acquireSemaphore, used by releaseSemaphore)
30452
30758
  semaphoreTicket: z60.any().optional(),
30453
30759
  // A timed-out execute() that is still running; its concurrency slot is released when it settles
30454
- abandonedExecution: z60.instanceof(Promise).optional()
30760
+ abandonedExecution: z60.instanceof(Promise).optional(),
30761
+ // When execute() started, and the error it ended with (for the completion event)
30762
+ executionStartedAt: z60.number().optional(),
30763
+ executionError: z60.any().optional(),
30764
+ // The MCP result built from rawOutput (parseOutput), or why it could not be built
30765
+ parsedOutput: z60.any().optional(),
30766
+ outputError: z60.any().optional()
30455
30767
  });
30456
30768
  plan14 = {
30457
30769
  pre: [
@@ -30464,12 +30776,16 @@ var init_call_agent_flow = __esm({
30464
30776
  "acquireSemaphore"
30465
30777
  ],
30466
30778
  execute: ["validateInput", "execute", "validateOutput"],
30467
- finalize: ["releaseSemaphore", "releaseQuota", "finalize"]
30779
+ finalize: ["releaseSemaphore", "releaseQuota", "parseOutput", "emitCompletion", "finalize"]
30468
30780
  };
30469
30781
  name14 = "agents:call-agent";
30470
30782
  ({ Stage: Stage14 } = FlowHooksOf(name14));
30471
30783
  CallAgentFlow = class extends FlowBase {
30472
30784
  logger = this.scopeLogger.child("CallAgentFlow");
30785
+ /** Whether the `invoke_<agent>` tool's flow already applied the agent's gates. */
30786
+ get gatedByToolFlow() {
30787
+ return this.input.gatedBy === "tools:call-tool";
30788
+ }
30473
30789
  async parseInput() {
30474
30790
  this.logger.verbose("parseInput:start");
30475
30791
  let method;
@@ -30536,6 +30852,10 @@ var init_call_agent_flow = __esm({
30536
30852
  }
30537
30853
  async checkEntryAuthorities() {
30538
30854
  this.logger.verbose("checkEntryAuthorities:start");
30855
+ if (this.gatedByToolFlow) {
30856
+ this.logger.verbose("checkEntryAuthorities:skip (checked by the invoke tool flow)");
30857
+ return;
30858
+ }
30539
30859
  const engine = this.scope.authoritiesEngine;
30540
30860
  const ctxBuilder = this.scope.authoritiesContextBuilder;
30541
30861
  if (!engine || !ctxBuilder) {
@@ -30601,14 +30921,17 @@ var init_call_agent_flow = __esm({
30601
30921
  const progressToken = this.state.progressToken;
30602
30922
  const authInfo = this.state.authInfo;
30603
30923
  try {
30604
- const context = agent.create(input.arguments, { ...ctx, progressToken });
30924
+ const sessionKey = authInfo?.sessionId ?? "anonymous";
30925
+ const agentViews = await agent.providers.buildViews(sessionKey, new Map(this.deps), this.scope.providers);
30926
+ const contextProviders = new FlowContextProviders(agent.providers, agentViews.context);
30927
+ const context = agent.create(input.arguments, { ...ctx, progressToken, contextProviders });
30605
30928
  this.appendContextHooks(hooksBoundTo(this.scope.hooks.getClsHooks(agent.record.provide), context));
30606
30929
  context.mark("createAgentContext");
30607
30930
  const frontmcpContext = context.tryGetContext();
30608
30931
  const sdkAuthInfo = authInfo;
30609
- if (frontmcpContext && sdkAuthInfo?.transport?.sendElicitRequest) {
30932
+ const jsonRpcRequestId = this.state.jsonRpcRequestId;
30933
+ if (frontmcpContext && sdkAuthInfo?.transport?.sendElicitRequest && jsonRpcRequestId !== void 0) {
30610
30934
  const transport = sdkAuthInfo.transport;
30611
- const jsonRpcRequestId = this.state.jsonRpcRequestId;
30612
30935
  frontmcpContext.setTransport({
30613
30936
  sendElicitRequest: transport.sendElicitRequest.bind(transport),
30614
30937
  type: transport.type ?? "unknown",
@@ -30625,7 +30948,7 @@ var init_call_agent_flow = __esm({
30625
30948
  async acquireQuota() {
30626
30949
  this.logger.verbose("acquireQuota:start");
30627
30950
  const manager = this.scope.rateLimitManager;
30628
- if (!manager) {
30951
+ if (!manager || this.gatedByToolFlow) {
30629
30952
  this.state.agentContext?.mark("acquireQuota");
30630
30953
  this.logger.verbose("acquireQuota:done (no rate limit manager)");
30631
30954
  return;
@@ -30649,7 +30972,7 @@ var init_call_agent_flow = __esm({
30649
30972
  async acquireSemaphore() {
30650
30973
  this.logger.verbose("acquireSemaphore:start");
30651
30974
  const manager = this.scope.rateLimitManager;
30652
- if (!manager) {
30975
+ if (!manager || this.gatedByToolFlow) {
30653
30976
  this.state.agentContext?.mark("acquireSemaphore");
30654
30977
  this.logger.verbose("acquireSemaphore:done (no rate limit manager)");
30655
30978
  return;
@@ -30694,7 +31017,8 @@ var init_call_agent_flow = __esm({
30694
31017
  }
30695
31018
  agentContext.mark("execute");
30696
31019
  const startTime = Date.now();
30697
- const timeoutMs = agent.metadata.timeout?.executeMs ?? agent.metadata.execution?.timeout ?? this.scope.rateLimitManager?.config?.defaultTimeout?.executeMs;
31020
+ this.state.set("executionStartedAt", startTime);
31021
+ 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
31022
  const running = (async () => {
30699
31023
  agentContext.output = await agentContext.execute(agentContext.input);
30700
31024
  })();
@@ -30705,6 +31029,7 @@ var init_call_agent_flow = __esm({
30705
31029
  });
30706
31030
  this.logger.verbose("execute:done");
30707
31031
  } catch (error) {
31032
+ this.state.set("executionError", error);
30708
31033
  if (error instanceof ExecutionTimeoutError2) {
30709
31034
  this.logger.warn("execute: agent execution timed out", {
30710
31035
  agent: agent.metadata.name,
@@ -30713,6 +31038,7 @@ var init_call_agent_flow = __esm({
30713
31038
  this.state.set("abandonedExecution", running);
30714
31039
  throw error;
30715
31040
  }
31041
+ if (this.gatedByToolFlow) throw error;
30716
31042
  throw new AgentExecutionError(agent.metadata.name, error instanceof Error ? error : void 0);
30717
31043
  }
30718
31044
  }
@@ -30750,9 +31076,34 @@ var init_call_agent_flow = __esm({
30750
31076
  this.state.agentContext?.mark("releaseQuota");
30751
31077
  this.logger.verbose("releaseQuota:done");
30752
31078
  }
31079
+ async parseOutput() {
31080
+ const { agent, rawOutput } = this.state;
31081
+ if (!agent || rawOutput === void 0) return;
31082
+ const parseResult = agent.safeParseOutput(rawOutput);
31083
+ if (parseResult.success) this.state.set("parsedOutput", parseResult.data);
31084
+ else this.state.set("outputError", parseResult.error);
31085
+ }
31086
+ async emitCompletion() {
31087
+ const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
31088
+ if (!agent || executionStartedAt === void 0) return;
31089
+ if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
31090
+ const failure = executionError ?? (outputError !== void 0 ? new InvalidOutputError() : void 0);
31091
+ const failed = failure !== void 0;
31092
+ const requestId = this.tryGetContext()?.requestId;
31093
+ const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
31094
+ completionEventsOf(this.scope).agents.emit({
31095
+ agentId: agent.id,
31096
+ agentName: agent.name,
31097
+ status: failed ? "error" : "success",
31098
+ durationMs: Date.now() - executionStartedAt,
31099
+ ...failed ? { error: failure instanceof Error ? failure.message : String(failure) } : { output: completionOutputText(agentContext?.output) },
31100
+ ...requestId ? { runId: requestId } : {},
31101
+ ...sessionId ? { sessionId } : {}
31102
+ });
31103
+ }
30753
31104
  async finalize() {
30754
31105
  this.logger.verbose("finalize:start");
30755
- const { agent, rawOutput, executionMeta } = this.state;
31106
+ const { agent, rawOutput, parsedOutput, outputError, executionMeta } = this.state;
30756
31107
  if (!agent) {
30757
31108
  this.logger.verbose("finalize: skipping (no agent in state)");
30758
31109
  return;
@@ -30761,15 +31112,14 @@ var init_call_agent_flow = __esm({
30761
31112
  this.logger.verbose("finalize: skipping (no output - execute stage likely failed)");
30762
31113
  return;
30763
31114
  }
30764
- const parseResult = agent.safeParseOutput(rawOutput);
30765
- if (!parseResult.success) {
31115
+ if (outputError !== void 0 || parsedOutput === void 0) {
30766
31116
  this.logger.error("finalize: output validation failed", {
30767
31117
  agent: agent.metadata.name,
30768
- errors: parseResult.error
31118
+ errors: outputError
30769
31119
  });
30770
31120
  throw new InvalidOutputError();
30771
31121
  }
30772
- const result = parseResult.data;
31122
+ const result = parsedOutput;
30773
31123
  if (executionMeta) {
30774
31124
  result._meta = {
30775
31125
  ...result._meta,
@@ -30835,6 +31185,12 @@ var init_call_agent_flow = __esm({
30835
31185
  __decorateClass([
30836
31186
  Stage14("releaseQuota")
30837
31187
  ], CallAgentFlow.prototype, "releaseQuota", 1);
31188
+ __decorateClass([
31189
+ Stage14("parseOutput")
31190
+ ], CallAgentFlow.prototype, "parseOutput", 1);
31191
+ __decorateClass([
31192
+ Stage14("emitCompletion")
31193
+ ], CallAgentFlow.prototype, "emitCompletion", 1);
30838
31194
  __decorateClass([
30839
31195
  Stage14("finalize")
30840
31196
  ], CallAgentFlow.prototype, "finalize", 1);
@@ -31636,10 +31992,17 @@ var init_mrtr = __esm({
31636
31992
  /** Per-kind call counters, used to derive stable keys across a replay. */
31637
31993
  counters = { elicitation: 0, sampling: 0, roots: 0 };
31638
31994
  clientCapabilities;
31995
+ /**
31996
+ * Whether the client itself declared this revision. `false` when the SERVER served an
31997
+ * unversioned call as 2026-07-28 (`transport.defaultProtocolVersion`, the Worker default): such a
31998
+ * client, typically a 2025-03-26 one, never agreed to the revision's input-request contract.
31999
+ */
32000
+ clientDeclaredRevision;
31639
32001
  binding;
31640
32002
  constructor(params) {
31641
32003
  this.responses = { ...params.carriedResponses ?? {}, ...params.inputResponses ?? {} };
31642
32004
  this.clientCapabilities = params.clientCapabilities;
32005
+ this.clientDeclaredRevision = params.clientDeclaredRevision ?? true;
31643
32006
  this.binding = params.binding;
31644
32007
  }
31645
32008
  /** True when the client declared the capability a given request kind needs. */
@@ -31992,7 +32355,7 @@ function shouldCreateTask(params) {
31992
32355
  return { kind: "create", owner: ownership.owner };
31993
32356
  }
31994
32357
  async function resumeTask(params) {
31995
- const { scope, record, authInfo, clientCapabilities, frontmcpContext } = params;
32358
+ const { scope, record, authInfo, clientCapabilities, frontmcpContext, clientDeclaredRevision } = params;
31996
32359
  const registry = scope.tasks;
31997
32360
  const runner = registry?.runner;
31998
32361
  if (!runner) {
@@ -32003,6 +32366,8 @@ async function resumeTask(params) {
32003
32366
  new MrtrExchange({
32004
32367
  carriedResponses: record.inputResponses ?? {},
32005
32368
  clientCapabilities,
32369
+ // A caller that never declared the revision is refused an elicitation as a legacy caller is.
32370
+ clientDeclaredRevision,
32006
32371
  binding: {
32007
32372
  principal: resolveTaskPrincipal(authInfo),
32008
32373
  binding: computeRequestBinding("tasks/resume", { name: record.taskId })
@@ -32049,6 +32414,7 @@ async function dispatch20260728(options) {
32049
32414
  scope,
32050
32415
  body,
32051
32416
  clientCapabilities,
32417
+ clientDeclaredRevision,
32052
32418
  frontmcpContext,
32053
32419
  authInfo,
32054
32420
  isAnonymous,
@@ -32095,7 +32461,8 @@ async function dispatch20260728(options) {
32095
32461
  record,
32096
32462
  authInfo: { ...authInfo ?? {}, sessionId: ownership.owner },
32097
32463
  clientCapabilities,
32098
- frontmcpContext
32464
+ frontmcpContext,
32465
+ clientDeclaredRevision
32099
32466
  })
32100
32467
  });
32101
32468
  return outcome.kind === "result" ? { kind: "result", result: decorate(outcome.result) } : outcome;
@@ -32125,7 +32492,8 @@ async function dispatch20260728(options) {
32125
32492
  inputResponses: params["inputResponses"],
32126
32493
  carriedResponses: carried.ok ? carried.responses : {},
32127
32494
  clientCapabilities,
32128
- binding
32495
+ binding,
32496
+ clientDeclaredRevision
32129
32497
  });
32130
32498
  frontmcpContext?.setMrtrExchange(exchange);
32131
32499
  if (notificationSink) frontmcpContext?.setRequestNotificationSink(notificationSink);
@@ -38134,6 +38502,8 @@ var init_oauth_authorize_flow = __esm({
38134
38502
  if (state) {
38135
38503
  url.searchParams.set("state", state);
38136
38504
  }
38505
+ const issuer = this.resolveIssuer(this.rawInput.request);
38506
+ if (issuer) url.searchParams.set("iss", issuer);
38137
38507
  this.respond(httpRespond.redirect(url.toString()));
38138
38508
  return;
38139
38509
  }
@@ -40292,7 +40662,7 @@ var init_oauth_provider_callback_flow = __esm({
40292
40662
  if (session.state) {
40293
40663
  url.searchParams.set("state", session.state);
40294
40664
  }
40295
- url.searchParams.set("iss", this.getLocalAuth().issuer);
40665
+ url.searchParams.set("iss", this.getLocalAuth().issuerFor(this.rawInput.request));
40296
40666
  this.logger.info(
40297
40667
  `Federated auth complete: ${selectedProviderIds.length} providers authenticated, redirecting to client`
40298
40668
  );
@@ -41205,7 +41575,7 @@ var init_oauth_userinfo_flow = __esm({
41205
41575
  return;
41206
41576
  }
41207
41577
  const localAuth = this.scope.auth;
41208
- const issuer = localAuth.issuerFor?.(this.rawInput.request);
41578
+ const issuer = localAuth.acceptedIssuersFor?.(this.rawInput.request);
41209
41579
  const result = await localAuth.verifyGatewayToken(token, baseUrl, void 0, issuer);
41210
41580
  if (!result.ok) {
41211
41581
  this.logger.warn("userinfo: token verification failed", { error: result.error });
@@ -41264,6 +41634,7 @@ var init_oauth_userinfo_flow = __esm({
41264
41634
  // libs/sdk/src/auth/flows/session.verify.flow.ts
41265
41635
  import "reflect-metadata";
41266
41636
  import {
41637
+ anonymousCallerClaims,
41267
41638
  buildInsufficientScopeHeader,
41268
41639
  buildInvalidTokenHeader,
41269
41640
  buildUnauthorizedHeader,
@@ -41300,7 +41671,14 @@ var init_session_verify_flow = __esm({
41300
41671
  init_common();
41301
41672
  init_notification_service();
41302
41673
  init_session_id_utils();
41303
- inputSchema23 = httpRequestInputSchema;
41674
+ inputSchema23 = httpRequestInputSchema.extend({
41675
+ /**
41676
+ * The request is served without a session (MCP 2026-07-28): an anonymous or static-key caller is
41677
+ * identified for this request only, and no session is minted for it (#629). Minting one needs
41678
+ * `MCP_SESSION_SECRET`, for a session id the client would never present.
41679
+ */
41680
+ sessionless: z72.boolean().optional()
41681
+ });
41304
41682
  stateSchema22 = z72.object({
41305
41683
  baseUrl: z72.string().min(1),
41306
41684
  authorizationHeader: z72.string().optional(),
@@ -41358,20 +41736,22 @@ var init_session_verify_flow = __esm({
41358
41736
  * Encapsulates the shared logic for session creation, payload encryption, and user derivation.
41359
41737
  */
41360
41738
  createAnonymousSession(options) {
41361
- const { authMode, issuer, scopes = ["anonymous"], subject, sessionIdHeader } = options;
41739
+ const { authMode, subject, sessionIdHeader } = options;
41362
41740
  this.logger.verbose("createAnonymousSession", { authMode, hasExistingSession: !!sessionIdHeader });
41741
+ if (this.rawInput.sessionless) {
41742
+ this.respond({
41743
+ kind: "authorized",
41744
+ authorization: { token: "", user: anonymousCallerClaims(options, randomUUID20()) }
41745
+ });
41746
+ return;
41747
+ }
41363
41748
  const machineId = getMachineId2();
41364
41749
  const authSignature = subject ? `${authMode}:${subject}` : authMode;
41365
41750
  if (sessionIdHeader) {
41366
41751
  const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
41367
41752
  if (existingPayload && existingPayload.nodeId === machineId) {
41368
41753
  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
- };
41754
+ const user2 = anonymousCallerClaims(options, anonId);
41375
41755
  this.respond({
41376
41756
  kind: "authorized",
41377
41757
  authorization: {
@@ -41385,13 +41765,8 @@ var init_session_verify_flow = __esm({
41385
41765
  this.logger.verbose("createAnonymousSession: ignoring unrecognized client session id; minting a fresh one");
41386
41766
  }
41387
41767
  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
41768
  const uuid = randomUUID20();
41769
+ const user = anonymousCallerClaims(options, uuid);
41395
41770
  const platformDetectionConfig = this.scope.metadata.transport?.platformDetection;
41396
41771
  const platformType = detectPlatformFromUserAgent(this.state.userAgent, platformDetectionConfig);
41397
41772
  const payload = {
@@ -41584,7 +41959,7 @@ var init_session_verify_flow = __esm({
41584
41959
  } else {
41585
41960
  const configuredAudience = isOrchestratedMode(authOptions) ? authOptions.expectedAudience : void 0;
41586
41961
  const resource = computeResource(this.rawInput.request, this.scope.entryPath, this.scope.routeBase);
41587
- const issuer = auth.issuerFor?.(this.rawInput.request);
41962
+ const issuer = auth.acceptedIssuersFor?.(this.rawInput.request);
41588
41963
  verify = auth.verifyGatewayToken(token, this.state.required.baseUrl, configuredAudience ?? resource, issuer);
41589
41964
  }
41590
41965
  const result = await verify;
@@ -41877,6 +42252,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41877
42252
  wellKnownAsStateSchema = z74.object({
41878
42253
  baseUrl: z74.string().min(1),
41879
42254
  // baseUrl + entryPrefix (unsuffixed)
42255
+ // The issuer this server names in answer to the request (`LocalPrimaryAuth.issuerFor`, #629): the
42256
+ // one on its authorization responses and tokens. Without it, `baseUrl`.
42257
+ issuer: z74.string().min(1).optional(),
41880
42258
  // Root origin (proto://host WITHOUT the entryPath prefix). The OAuth
41881
42259
  // endpoints (/oauth/authorize, /oauth/token, /oauth/register, …) are
41882
42260
  // registered at literal root paths by their flows, NOT under entryPath, so
@@ -41908,6 +42286,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41908
42286
  if (!request) throw new FlowInputMissingError("request", "well-known:oauth-authorization-server");
41909
42287
  const { metadata } = this.scope;
41910
42288
  const baseUrl = getRequestBaseUrl(request, this.scope.entryPath);
42289
+ const issuer = this.scope.auth?.issuerFor?.(
42290
+ request
42291
+ );
41911
42292
  const oauthBaseUrl = getRequestBaseUrl(request);
41912
42293
  let cimdEnabled = true;
41913
42294
  if (metadata.auth && isOrchestratedMode(metadata.auth)) {
@@ -41916,6 +42297,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41916
42297
  this.state.set(
41917
42298
  wellKnownAsStateSchema.parse({
41918
42299
  baseUrl,
42300
+ issuer,
41919
42301
  oauthBaseUrl,
41920
42302
  // The scopes this server grants (`allowedScopes`, #262): the literal
41921
42303
  // entries; a `*` glob names no scope a client could ask for.
@@ -41930,6 +42312,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41930
42312
  );
41931
42313
  }
41932
42314
  async collectData() {
42315
+ const { issuer } = this.state;
41933
42316
  const {
41934
42317
  baseUrl,
41935
42318
  oauthBaseUrl,
@@ -41940,7 +42323,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41940
42323
  cimdEnabled
41941
42324
  } = this.state.required;
41942
42325
  if (isOrchestrated) {
41943
- const baseIssuer = `${baseUrl}`;
42326
+ const baseIssuer = issuer ?? baseUrl;
41944
42327
  this.respond({
41945
42328
  kind: "json",
41946
42329
  contentType: "application/json; charset=utf-8",
@@ -41967,6 +42350,8 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41967
42350
  grant_types_supported: ["authorization_code", "refresh_token"],
41968
42351
  scopes_supported: scopesSupported,
41969
42352
  code_challenge_methods_supported: ["S256"],
42353
+ // Every authorization response, error responses included, carries `iss` (RFC 9207 §2, §3).
42354
+ authorization_response_iss_parameter_supported: true,
41970
42355
  // CIMD support advertisement per draft-ietf-oauth-client-id-metadata-document-00
41971
42356
  client_id_metadata_document_supported: cimdEnabled
41972
42357
  }
@@ -42003,7 +42388,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
42003
42388
 
42004
42389
  // libs/sdk/src/auth/flows/well-known.prm.flow.ts
42005
42390
  import "reflect-metadata";
42006
- import { advertisedScopes as advertisedScopes2 } from "@frontmcp/auth";
42391
+ import { resourceScopesFor } from "@frontmcp/auth";
42007
42392
  import { z as z75 } from "@frontmcp/lazy-zod";
42008
42393
  var inputSchema26, stateSchema24, outputSchema24, plan24, name25, Stage25, WellKnownPrmFlow;
42009
42394
  var init_well_known_prm_flow = __esm({
@@ -42015,14 +42400,16 @@ var init_well_known_prm_flow = __esm({
42015
42400
  stateSchema24 = z75.object({
42016
42401
  resource: z75.string().min(1),
42017
42402
  baseUrl: z75.string().min(1),
42018
- scopesSupported: z75.array(z75.string()).default(["openid", "profile", "email"]),
42403
+ // The authorization server to name: the issuer this server names for the request (#629).
42404
+ authorizationServer: z75.string().min(1).optional(),
42405
+ scopesSupported: z75.array(z75.string()),
42019
42406
  isOrchestrated: z75.boolean()
42020
42407
  });
42021
42408
  outputSchema24 = HttpJsonSchema.extend({
42022
42409
  body: z75.object({
42023
42410
  resource: z75.string().min(1),
42024
42411
  authorization_servers: z75.array(z75.string().min(1)).min(1),
42025
- scopes_supported: z75.array(z75.string()).default(["openid", "profile", "email"]),
42412
+ scopes_supported: z75.array(z75.string()).optional(),
42026
42413
  bearer_methods_supported: z75.array(z75.string()).default(["header"])
42027
42414
  }).passthrough()
42028
42415
  });
@@ -42046,12 +42433,17 @@ var init_well_known_prm_flow = __esm({
42046
42433
  if (!request) throw new FlowInputMissingError("request", "well-known:prm");
42047
42434
  const resource = computeResource(request, scope.entryPath, scope.routeBase);
42048
42435
  const baseUrl = getRequestBaseUrl(request, scope.entryPath);
42049
- const auth = scope.metadata.auth;
42436
+ const authorizationServer = scope.auth?.issuerFor?.(request);
42437
+ const authOptions = scope.auth?.options ?? scope.metadata.auth;
42438
+ const granted = resourceScopesFor(authOptions);
42439
+ const orchestrated = authOptions?.mode === "local" || authOptions?.mode === "remote";
42440
+ const scopesSupported = orchestrated ? granted : [.../* @__PURE__ */ new Set([...granted, ...scope.getAllSupportedScopes()])];
42050
42441
  this.state.set(
42051
42442
  stateSchema24.parse({
42052
42443
  resource,
42053
42444
  baseUrl,
42054
- scopesSupported: auth && isOrchestratedMode(auth) ? advertisedScopes2(auth.allowedScopes) : scope.getAllSupportedScopes(),
42445
+ authorizationServer,
42446
+ scopesSupported,
42055
42447
  isOrchestrated: false
42056
42448
  //scope.orchestrated,// TODO: fix
42057
42449
  })
@@ -42059,6 +42451,8 @@ var init_well_known_prm_flow = __esm({
42059
42451
  }
42060
42452
  async collectData() {
42061
42453
  const { resource, baseUrl, scopesSupported, isOrchestrated } = this.state.required;
42454
+ const { authorizationServer } = this.state;
42455
+ const scopes = scopesSupported.length > 0 ? { scopes_supported: scopesSupported } : {};
42062
42456
  if (isOrchestrated) {
42063
42457
  this.respond({
42064
42458
  kind: "json",
@@ -42070,8 +42464,8 @@ var init_well_known_prm_flow = __esm({
42070
42464
  headers: { "cache-control": "no-store" },
42071
42465
  body: {
42072
42466
  resource,
42073
- authorization_servers: [baseUrl],
42074
- scopes_supported: scopesSupported,
42467
+ authorization_servers: [authorizationServer ?? baseUrl],
42468
+ ...scopes,
42075
42469
  bearer_methods_supported: ["header"]
42076
42470
  }
42077
42471
  });
@@ -42084,8 +42478,8 @@ var init_well_known_prm_flow = __esm({
42084
42478
  headers: { "cache-control": "no-store" },
42085
42479
  body: {
42086
42480
  resource,
42087
- authorization_servers: [baseUrl],
42088
- scopes_supported: scopesSupported,
42481
+ authorization_servers: [authorizationServer ?? baseUrl],
42482
+ ...scopes,
42089
42483
  bearer_methods_supported: ["header"]
42090
42484
  }
42091
42485
  });
@@ -42264,6 +42658,14 @@ function audienceMatchesResource(aud, expected) {
42264
42658
  function isUnsupportedOperation(error) {
42265
42659
  return error instanceof StorageNotSupportedError;
42266
42660
  }
42661
+ function originOf2(value) {
42662
+ try {
42663
+ const origin = new URL(value).origin;
42664
+ return origin === "null" ? void 0 : origin;
42665
+ } catch {
42666
+ return void 0;
42667
+ }
42668
+ }
42267
42669
  var MIN_HS256_SECRET_BYTES, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
42268
42670
  var init_instance_local_primary_auth = __esm({
42269
42671
  "libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
@@ -42316,7 +42718,7 @@ var init_instance_local_primary_auth = __esm({
42316
42718
  this.port = this.providers.getActiveScope().metadata.http?.port ?? 3001;
42317
42719
  this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
42318
42720
  this.issuer = this.deriveIssuer(options);
42319
- this.issuerIsConfigured = this.configuredIssuer(options) !== void 0 || !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
42721
+ this.publicHostPinned = !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
42320
42722
  const jwtSecret = getEnv4("JWT_SECRET");
42321
42723
  const hasJwtSecret = !!jwtSecret?.trim();
42322
42724
  if (hasJwtSecret && jwtSecret) {
@@ -42357,8 +42759,8 @@ var init_instance_local_primary_auth = __esm({
42357
42759
  port;
42358
42760
  /** The boot-time issuer: the configured one, else this server's listener address (see {@link issuerFor}). */
42359
42761
  issuer;
42360
- /** Whether the operator named the issuer (`issuer` / `local.issuer`, or its host with `FRONTMCP_PUBLIC_HOST`). */
42361
- issuerIsConfigured;
42762
+ /** Whether `FRONTMCP_PUBLIC_HOST` names the boot-time issuer's host, pinning it for every request. */
42763
+ publicHostPinned;
42362
42764
  keys = [];
42363
42765
  secret;
42364
42766
  logger;
@@ -42516,27 +42918,52 @@ var init_instance_local_primary_auth = __esm({
42516
42918
  return void 0;
42517
42919
  }
42518
42920
  /**
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:
42921
+ * The issuer this server names in answer to `request`: in its discovery
42922
+ * documents (`/.well-known/oauth-authorization-server`'s `issuer`, the
42923
+ * protected resource metadata's `authorization_servers`), on its
42924
+ * authorization responses (the RFC 9207 `iss`, errors included), and as the
42925
+ * `iss` of the tokens it issues; the tokens it accepts there must name it
42926
+ * (#269, and see {@link acceptedIssuersFor}). Every entry point asks this, so
42927
+ * none of them can disagree, on the Node server and under a Web fetch
42928
+ * handler alike (#629). In order:
42522
42929
  *
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.
42930
+ * 1. a configured issuer (`issuer` / `local.issuer`);
42931
+ * 2. with `FRONTMCP_PUBLIC_URL` pinned, that URL plus this scope's path;
42932
+ * 3. with `FRONTMCP_PUBLIC_HOST` set, the boot-time issuer it names the host
42933
+ * of (`http://<host>:<http.port><path>`);
42934
+ * 4. the request's own origin plus this scope's path;
42935
+ * 5. without a request, the boot-time issuer.
42532
42936
  */
42533
42937
  issuerFor(request) {
42534
- if (!request || this.issuerIsConfigured) return this.issuer;
42535
- if (isPublicUrlPinned()) return computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
42938
+ const configured = this.configuredIssuer(this.options);
42939
+ if (configured !== void 0) return configured;
42940
+ const pinned = getPinnedPublicUrl();
42941
+ if (pinned !== void 0) {
42942
+ return request ? computeIssuer(request, this.scope.entryPath, this.scope.routeBase) : `${pinned}${this.scope.fullPath}`;
42943
+ }
42944
+ if (this.publicHostPinned || !request) return this.issuer;
42945
+ return computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
42946
+ }
42947
+ /**
42948
+ * The issuers a token presented with `request` may name: {@link issuerFor}
42949
+ * the request. When the issuer follows the request (nothing pins it) and
42950
+ * local or remote mode lists `expectedAudience`, a token for any listed
42951
+ * address was issued under that address's issuer, so each listed address's
42952
+ * issuer is accepted too, and so is the boot-time issuer that 1.8.4 named on
42953
+ * those tokens. The token's `aud` must still name a listed resource.
42954
+ */
42955
+ acceptedIssuersFor(request) {
42956
+ const issuer = this.issuerFor(request);
42536
42957
  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);
42958
+ const followsRequest = !!request && this.configuredIssuer(options) === void 0 && getPinnedPublicUrl() === void 0 && !this.publicHostPinned;
42959
+ if (!followsRequest || !isOrchestratedMode(options) || options.expectedAudience === void 0) return issuer;
42960
+ const listed = Array.isArray(options.expectedAudience) ? options.expectedAudience : [options.expectedAudience];
42961
+ const accepted = /* @__PURE__ */ new Set([issuer, this.issuer]);
42962
+ for (const audience of listed) {
42963
+ const origin = originOf2(audience);
42964
+ if (origin) accepted.add(`${origin}${this.scope.fullPath}`);
42965
+ }
42966
+ return [...accepted];
42540
42967
  }
42541
42968
  /**
42542
42969
  * Read the `tokenStorage` selection off the auth options. Only
@@ -42787,10 +43214,10 @@ var init_instance_local_primary_auth = __esm({
42787
43214
  *
42788
43215
  * The secret alone doesn't make a token this server's: every server started
42789
43216
  * 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
43217
+ * - name THIS instance as its issuer (`iss`): `expectedIssuer` (one issuer or
43218
+ * several), which callers take from {@link acceptedIssuersFor} the request,
43219
+ * matching what the token endpoint names when it signs ({@link issuerFor});
43220
+ * the boot-time issuer when not given. Never the token's own claim; and
42794
43221
  * - when `expectedAudience` is given, be issued for that protected resource,
42795
43222
  * or one of those resources (`aud`, compared as RFC 8707 resource URIs).
42796
43223
  * `session:verify` passes the request's resource URL, or the configured
@@ -42803,7 +43230,7 @@ var init_instance_local_primary_auth = __esm({
42803
43230
  try {
42804
43231
  const { payload, protectedHeader } = await jwtVerify(token, this.secret, {
42805
43232
  algorithms: ["HS256"],
42806
- issuer: expectedIssuer ?? this.issuer,
43233
+ issuer: typeof expectedIssuer === "string" ? expectedIssuer : expectedIssuer ? [...expectedIssuer] : this.issuer,
42807
43234
  requiredClaims: ["exp"]
42808
43235
  });
42809
43236
  if (expectedAudience !== void 0 && !audienceMatchesResource(payload.aud, expectedAudience)) {
@@ -43759,6 +44186,111 @@ var init_auth_registry = __esm({
43759
44186
  }
43760
44187
  });
43761
44188
 
44189
+ // libs/sdk/src/server/custom-routes.helper.ts
44190
+ function computeReservedPaths(entryPath, routeBase) {
44191
+ const prefix = normalizeEntryPrefix(entryPath);
44192
+ const base = normalizeScopeBase(routeBase);
44193
+ const mcpBase = `${prefix}${base}`;
44194
+ const exact = /* @__PURE__ */ new Set([
44195
+ mcpBase === "" ? "/" : mcpBase,
44196
+ `${mcpBase}/sse`,
44197
+ `${mcpBase}/message`,
44198
+ "/health",
44199
+ "/metrics"
44200
+ ]);
44201
+ return { exact, prefixes: ["/oauth", "/.well-known"] };
44202
+ }
44203
+ function normalizeForCompare(path3) {
44204
+ if (path3.length > 1 && path3.endsWith("/")) return path3.slice(0, -1);
44205
+ return path3;
44206
+ }
44207
+ function assertNotReserved(method, path3, reserved) {
44208
+ const normalized = normalizeForCompare(path3);
44209
+ if (reserved.exact.has(normalized)) {
44210
+ throw new ReservedRouteCollisionError(method, path3, normalized);
44211
+ }
44212
+ for (const prefix of reserved.prefixes) {
44213
+ if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
44214
+ throw new ReservedRouteCollisionError(method, path3, prefix);
44215
+ }
44216
+ }
44217
+ }
44218
+ function wrapWithAuth(handler, verifySession, logger) {
44219
+ return async (req, res, next) => {
44220
+ let result;
44221
+ try {
44222
+ result = await verifySession(req);
44223
+ } catch (error) {
44224
+ if (error instanceof FlowControl && error.type === "respond") {
44225
+ result = error.output;
44226
+ } else {
44227
+ throw error;
44228
+ }
44229
+ }
44230
+ if (!result || result.kind === "unauthorized") {
44231
+ logger.verbose("custom-route auth: unauthorized");
44232
+ return writeHttpResponse(
44233
+ res,
44234
+ httpRespond.unauthorized({
44235
+ headers: result?.kind === "unauthorized" ? { "WWW-Authenticate": result.prmMetadataHeader } : void 0
44236
+ })
44237
+ );
44238
+ }
44239
+ if (result.kind === "forbidden") {
44240
+ logger.verbose("custom-route auth: forbidden (insufficient scope)");
44241
+ return writeHttpResponse(
44242
+ res,
44243
+ httpRespond.forbidden({
44244
+ headers: { "WWW-Authenticate": result.prmMetadataHeader }
44245
+ })
44246
+ );
44247
+ }
44248
+ req.authSession = result.authorization;
44249
+ req[ServerRequestTokens.auth] = result.authorization;
44250
+ return handler(req, res, next);
44251
+ };
44252
+ }
44253
+ function wrapWithIpFilter(handler, checkClientIp) {
44254
+ return async (req, res, next) => {
44255
+ const rejection = await checkClientIp(req, res);
44256
+ if (rejection) return writeHttpResponse(res, rejection);
44257
+ return handler(req, res, next);
44258
+ };
44259
+ }
44260
+ function registerCustomHttpRoutes(args) {
44261
+ const { routes, server, verifySession, checkClientIp, entryPath, routeBase, logger } = args;
44262
+ if (!routes || routes.length === 0) return;
44263
+ const reserved = computeReservedPaths(entryPath, routeBase);
44264
+ for (const route of routes) {
44265
+ assertNotReserved(route.method, route.path, reserved);
44266
+ const handler = route.auth === true ? wrapWithAuth(route.handler, verifySession, logger) : route.handler;
44267
+ server.registerRoute(route.method, route.path, wrapWithIpFilter(handler, checkClientIp));
44268
+ logger.info(`Registered custom http route: ${route.method} ${route.path}${route.auth ? " (auth)" : ""}`);
44269
+ }
44270
+ }
44271
+ var ReservedRouteCollisionError;
44272
+ var init_custom_routes_helper = __esm({
44273
+ "libs/sdk/src/server/custom-routes.helper.ts"() {
44274
+ "use strict";
44275
+ init_common();
44276
+ init_server_validation();
44277
+ ReservedRouteCollisionError = class extends Error {
44278
+ constructor(method, path3, reserved) {
44279
+ super(
44280
+ `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.`
44281
+ );
44282
+ this.method = method;
44283
+ this.path = path3;
44284
+ this.reserved = reserved;
44285
+ this.name = "ReservedRouteCollisionError";
44286
+ }
44287
+ method;
44288
+ path;
44289
+ reserved;
44290
+ };
44291
+ }
44292
+ });
44293
+
43762
44294
  // libs/sdk/src/channel/channel-notification.service.ts
43763
44295
  var CHANNEL_NOTIFICATION_METHOD, ChannelNotificationService;
43764
44296
  var init_channel_notification_service = __esm({
@@ -44728,6 +45260,12 @@ function wireJobCompletionSource(channel2, sourceConfig, jobEmitterSubscribe, lo
44728
45260
  return;
44729
45261
  }
44730
45262
  }
45263
+ if (!event.sessionId) {
45264
+ logger.verbose(
45265
+ `Skipping job completion for channel "${channel2.name}": no sessionId (prevents cross-session leak)`
45266
+ );
45267
+ return;
45268
+ }
44731
45269
  logger.verbose(`Job completion event for channel "${channel2.name}": job=${event.jobName}, status=${event.status}`);
44732
45270
  channel2.handleEvent(event, event.sessionId).catch((err) => {
44733
45271
  logger.error(`Failed to handle job completion event in channel "${channel2.name}"`, { error: err });
@@ -44740,6 +45278,41 @@ var init_job_completion_source = __esm({
44740
45278
  }
44741
45279
  });
44742
45280
 
45281
+ // libs/sdk/src/channel/sources/webhook.source.ts
45282
+ function createWebhookMiddleware(channel2, sourceConfig, logger) {
45283
+ return async (req, res) => {
45284
+ const rawHeaders = req.headers ?? {};
45285
+ const normalizedHeaders = {};
45286
+ for (const [key, value] of Object.entries(rawHeaders)) {
45287
+ normalizedHeaders[key.toLowerCase()] = value;
45288
+ }
45289
+ const payload = {
45290
+ body: req.body,
45291
+ headers: normalizedHeaders,
45292
+ method: req.method ?? "POST",
45293
+ query: req.query
45294
+ };
45295
+ logger.verbose(`Webhook received for channel "${channel2.name}" at ${sourceConfig.path}`);
45296
+ try {
45297
+ const notification = await runOnSurface("http-trigger", () => channel2.handleEvent(payload));
45298
+ if (notification) {
45299
+ res.status(200).json({ ok: true, channel: channel2.name });
45300
+ } else {
45301
+ res.status(200).json({ ok: true, channel: channel2.name, skipped: true });
45302
+ }
45303
+ } catch (err) {
45304
+ logger.error(`Webhook handler failed for channel "${channel2.name}"`, { error: err });
45305
+ res.status(500).json({ ok: false, error: "Internal channel error" });
45306
+ }
45307
+ };
45308
+ }
45309
+ var init_webhook_source = __esm({
45310
+ "libs/sdk/src/channel/sources/webhook.source.ts"() {
45311
+ "use strict";
45312
+ init_call_surface();
45313
+ }
45314
+ });
45315
+
44743
45316
  // libs/sdk/src/channel/channel-scope.helper.ts
44744
45317
  async function registerChannelCapabilities(args) {
44745
45318
  const {
@@ -44750,11 +45323,15 @@ async function registerChannelCapabilities(args) {
44750
45323
  notificationService,
44751
45324
  flowRegistry,
44752
45325
  toolRegistry,
44753
- agentEmitterSubscribe,
44754
- jobEmitterSubscribe,
45326
+ http: http2,
44755
45327
  logger
44756
45328
  } = args;
45329
+ const completions = completionEventsOf(providers.getActiveScope());
45330
+ const agentEmitterSubscribe = args.agentEmitterSubscribe ?? ((cb) => completions.agents.subscribe(cb));
45331
+ const jobEmitterSubscribe = args.jobEmitterSubscribe ?? ((cb) => completions.jobs.subscribe(cb));
44757
45332
  const unsubscribers = [];
45333
+ const reservedPaths = http2 ? computeReservedPaths(http2.entryPath, http2.routeBase) : void 0;
45334
+ const webhookPaths = /* @__PURE__ */ new Map();
44758
45335
  const channelRegistry = new ChannelRegistry(providers, channelsList, owner);
44759
45336
  await channelRegistry.ready;
44760
45337
  const channelNotificationService = new ChannelNotificationService(
@@ -44770,31 +45347,23 @@ async function registerChannelCapabilities(args) {
44770
45347
  const sourceType = instance.source.type;
44771
45348
  switch (sourceType) {
44772
45349
  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
- }
45350
+ const unsub = wireAgentCompletionSource(
45351
+ instance,
45352
+ instance.metadata.source,
45353
+ agentEmitterSubscribe,
45354
+ logger
45355
+ );
45356
+ unsubscribers.push(unsub);
44784
45357
  break;
44785
45358
  }
44786
45359
  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
- }
45360
+ const unsub = wireJobCompletionSource(
45361
+ instance,
45362
+ instance.metadata.source,
45363
+ jobEmitterSubscribe,
45364
+ logger
45365
+ );
45366
+ unsubscribers.push(unsub);
44798
45367
  break;
44799
45368
  }
44800
45369
  case "app-event": {
@@ -44803,7 +45372,25 @@ async function registerChannelCapabilities(args) {
44803
45372
  unsubscribers.push(unsub);
44804
45373
  break;
44805
45374
  }
44806
- case "webhook":
45375
+ case "webhook": {
45376
+ const source = instance.metadata.source;
45377
+ if (!http2 || !reservedPaths) {
45378
+ logger.warn(`Channel "${instance.name}" has a webhook source but no HTTP server to serve ${source.path}`);
45379
+ break;
45380
+ }
45381
+ assertNotReserved("POST", source.path, reservedPaths);
45382
+ const claimedBy = webhookPaths.get(source.path);
45383
+ if (claimedBy) {
45384
+ throw new Error(
45385
+ `Channels "${claimedBy}" and "${instance.name}" both declare the webhook path ${source.path}. Give each webhook channel its own path.`
45386
+ );
45387
+ }
45388
+ webhookPaths.set(source.path, instance.name);
45389
+ const handler = createWebhookMiddleware(instance, source, logger);
45390
+ await http2.server.registerRoute("POST", source.path, wrapWithIpFilter(handler, http2.checkClientIp));
45391
+ logger.info(`Registered webhook route for channel "${instance.name}": POST ${source.path}`);
45392
+ break;
45393
+ }
44807
45394
  case "manual":
44808
45395
  break;
44809
45396
  case "service":
@@ -44886,6 +45473,7 @@ async function registerChannelCapabilities(args) {
44886
45473
  var init_channel_scope_helper = __esm({
44887
45474
  "libs/sdk/src/channel/channel-scope.helper.ts"() {
44888
45475
  "use strict";
45476
+ init_custom_routes_helper();
44889
45477
  init_tool_instance();
44890
45478
  init_tool_utils();
44891
45479
  init_channel_notification_service();
@@ -44895,7 +45483,9 @@ var init_channel_scope_helper = __esm({
44895
45483
  init_channel_reply_tool();
44896
45484
  init_agent_completion_source();
44897
45485
  init_app_event_source();
45486
+ init_completion_events();
44898
45487
  init_job_completion_source();
45488
+ init_webhook_source();
44899
45489
  }
44900
45490
  });
44901
45491
 
@@ -47968,6 +48558,7 @@ var init_workflow_step_executor = __esm({
47968
48558
  });
47969
48559
 
47970
48560
  // libs/sdk/src/workflow/engine/workflow.engine.ts
48561
+ import { getAsyncContextMode } from "@frontmcp/utils";
47971
48562
  var WorkflowEngine;
47972
48563
  var init_workflow_engine = __esm({
47973
48564
  "libs/sdk/src/workflow/engine/workflow.engine.ts"() {
@@ -48037,7 +48628,7 @@ var init_workflow_engine = __esm({
48037
48628
  }
48038
48629
  continue;
48039
48630
  }
48040
- const batch = ready.slice(0, this.maxConcurrency);
48631
+ const batch = ready.slice(0, getAsyncContextMode() === "serialized" ? 1 : this.maxConcurrency);
48041
48632
  await Promise.allSettled(
48042
48633
  batch.map(async (step) => {
48043
48634
  if (step.condition) {
@@ -49634,7 +50225,11 @@ async function registerJobCapabilities(args) {
49634
50225
  for (const dynWf of dynamicWorkflows) {
49635
50226
  workflowRegistry.registerDynamic(dynWf);
49636
50227
  }
49637
- const executionManager = new JobExecutionManager(stateStore, logger, notifyFn);
50228
+ const completions = completionEventsOf(providers.getActiveScope());
50229
+ const executionManager = new JobExecutionManager(stateStore, logger, async (data) => {
50230
+ await notifyFn?.(data);
50231
+ await publishJobCompletion(data, stateStore, completions);
50232
+ });
49638
50233
  const managementTools = [
49639
50234
  ListJobsTool,
49640
50235
  ExecuteJobTool,
@@ -49663,9 +50258,27 @@ async function registerJobCapabilities(args) {
49663
50258
  managementTools
49664
50259
  };
49665
50260
  }
50261
+ async function publishJobCompletion(data, stateStore, completions) {
50262
+ const { type, state, runId } = data;
50263
+ if (type !== "job:status" && type !== "workflow:status") return;
50264
+ if (state !== "completed" && state !== "failed" || typeof runId !== "string") return;
50265
+ const run = await stateStore.getRun(runId);
50266
+ if (!run) return;
50267
+ const workflowName = type === "workflow:status" ? run.workflowName : void 0;
50268
+ completions.jobs.emit({
50269
+ jobName: workflowName ?? run.jobName,
50270
+ jobId: run.jobId,
50271
+ status: state === "completed" ? "success" : "error",
50272
+ ...run.completedAt !== void 0 ? { durationMs: run.completedAt - run.startedAt } : {},
50273
+ ...state === "completed" ? { output: completionOutputText(run.result) } : { error: run.error?.message },
50274
+ attempt: run.attempt,
50275
+ ...run.sessionId ? { sessionId: run.sessionId } : {}
50276
+ });
50277
+ }
49666
50278
  var init_job_scope_helper = __esm({
49667
50279
  "libs/sdk/src/job/job-scope.helper.ts"() {
49668
50280
  "use strict";
50281
+ init_completion_events();
49669
50282
  init_execute_workflow_tool();
49670
50283
  init_get_workflow_status_tool();
49671
50284
  init_list_workflows_tool();
@@ -49890,111 +50503,6 @@ var init_enforced_metadata_check = __esm({
49890
50503
  }
49891
50504
  });
49892
50505
 
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
50506
  // libs/sdk/src/skill/errors/tool-not-allowed.error.ts
49999
50507
  var ToolNotAllowedError, ToolApprovalRequiredError;
50000
50508
  var init_tool_not_allowed_error = __esm({
@@ -51169,7 +51677,7 @@ var init_load_skill_flow = __esm({
51169
51677
  }
51170
51678
  await assertSkillAuthorized(this.scope, entry, authInfo);
51171
51679
  }
51172
- loadResults.push({ loadResult: result });
51680
+ loadResults.push({ loadResult: skillToolsForCaller(result, this.scope.tools, ctx) });
51173
51681
  }
51174
51682
  this.state.set({ loadResults, warnings });
51175
51683
  this.logger.verbose("loadSkills:done", { loaded: loadResults.length, notFound: warnings.length });
@@ -51415,7 +51923,10 @@ async function authorizeSkillHttpRequest(scope, skillsConfig, request, logger) {
51415
51923
  if (mode === "inherit") {
51416
51924
  const authOptions = scope.auth?.options;
51417
51925
  if (!authOptions || isPublicMode(authOptions)) return { allowed: true, authInfo: {} };
51418
- const verified = await scope.runFlow("session:verify", { request });
51926
+ const verified = await scope.runFlow("session:verify", {
51927
+ request,
51928
+ sessionless: true
51929
+ });
51419
51930
  if (verified?.kind === "authorized") {
51420
51931
  return { allowed: true, authInfo: { ...authInfoFromAuthorization(verified.authorization) } };
51421
51932
  }
@@ -52384,7 +52895,12 @@ var init_skills_api_flow = __esm({
52384
52895
  respondNotFound();
52385
52896
  return;
52386
52897
  }
52387
- const { skill, availableTools, missingTools, isComplete, warning } = loadResult;
52898
+ const { skill, availableTools, missingTools, isComplete, warning } = skillToolsForCaller(
52899
+ loadResult,
52900
+ toolRegistry,
52901
+ void 0,
52902
+ SKILLS_HTTP_SURFACE
52903
+ );
52388
52904
  const skillEntry = createSkillEntryResolver(skillRegistry)(skillId, skill.id);
52389
52905
  if (skillEntry) {
52390
52906
  if (!await isSkillServable(this.scope, skillEntry, void 0, SKILLS_HTTP_SURFACE)) {
@@ -52745,6 +53261,8 @@ var init_tool_interface = __esm({
52745
53261
  _toolNameInternal;
52746
53262
  /** @internal Tool input for fallback elicitation - set by CallToolFlow */
52747
53263
  _toolInputInternal;
53264
+ /** @internal JSON-RPC id of the tools/call request - set by CallToolFlow, for flows the call is handed on to */
53265
+ _jsonRpcRequestIdInternal;
52748
53266
  // ---- INPUT storages (backing fields)
52749
53267
  _rawInput;
52750
53268
  _input;
@@ -52777,6 +53295,10 @@ var init_tool_interface = __esm({
52777
53295
  this._progressToken = progressToken;
52778
53296
  this.signal = signal;
52779
53297
  }
53298
+ /** @internal The request's progress token, for flows the call is handed on to (the agent tool). */
53299
+ get _progressTokenInternal() {
53300
+ return this._progressToken;
53301
+ }
52780
53302
  /**
52781
53303
  * Like {@link ExecutionContextBase.fetch}, and also aborted with {@link signal}, so a
52782
53304
  * cancelled or timed-out call stops its outbound requests.
@@ -54824,10 +55346,10 @@ var init_primary_auth_provider_interface = __esm({
54824
55346
  * @param _expectedAudience The protected resource(s) the token must be for
54825
55347
  * (`aud` names at least one), when the caller is a
54826
55348
  * 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.
55349
+ * @param _expectedIssuer The issuer the token must name (or one of them):
55350
+ * the ones this instance accepts for the request
55351
+ * (`LocalPrimaryAuth.acceptedIssuersFor`). Default:
55352
+ * its boot-time issuer.
54831
55353
  */
54832
55354
  verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience, _expectedIssuer) {
54833
55355
  return Promise.resolve({
@@ -54902,7 +55424,10 @@ function findSkillByPath(scope, skillPath) {
54902
55424
  const segments = skillPath.split("/").filter((s) => s.length > 0);
54903
55425
  if (segments.length === 0) return void 0;
54904
55426
  const lastSegment = segments[segments.length - 1];
54905
- return visible.find((s) => s.name === lastSegment && s.getSkillPath() === lastSegment);
55427
+ const byName = visible.find((s) => s.name === lastSegment && s.getSkillPath() === lastSegment);
55428
+ if (byName) return byName;
55429
+ if (segments.length !== 1) return void 0;
55430
+ return visible.find((s) => s.metadata.id === lastSegment && s.getSkillPath() === s.metadata.name);
54906
55431
  }
54907
55432
  async function findAndLoadSkillByPath(scope, skillPath) {
54908
55433
  const registry = scope.skills;
@@ -56283,7 +56808,9 @@ var init_handle_mcp_20260728_flow = __esm({
56283
56808
  /** Negotiated protocol version for this request. */
56284
56809
  version: z102.string().optional(),
56285
56810
  requestType: z102.enum(["notification", "subscriptions", "message"]).optional(),
56286
- isAnonymous: z102.boolean().default(true)
56811
+ isAnonymous: z102.boolean().default(true),
56812
+ /** Whether the client declared this revision itself (not served under it by default). */
56813
+ clientDeclaredRevision: z102.boolean().optional()
56287
56814
  });
56288
56815
  name38 = "handle:mcp-202607280728";
56289
56816
  ({ Stage: Stage38 } = FlowHooksOf(name38));
@@ -56330,6 +56857,7 @@ var init_handle_mcp_20260728_flow = __esm({
56330
56857
  return;
56331
56858
  }
56332
56859
  this.state.set("version", result.version);
56860
+ this.state.set("clientDeclaredRevision", strictHeaders);
56333
56861
  }
56334
56862
  /**
56335
56863
  * Resolve a tool's input JSON Schema so `x-mcp-header` annotations can be
@@ -56445,10 +56973,12 @@ var init_handle_mcp_20260728_flow = __esm({
56445
56973
  // authenticated caller.
56446
56974
  ...authInfoFromAuthorization(auth),
56447
56975
  // 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
56976
+ // off an id. It names the request only, the same per-request id as the request
56977
+ // context's `sessionId`; the request context does not vouch for it (#629).
56978
+ sessionId: auth.session?.id ?? this.tryGetContext()?.sessionId
56450
56979
  } : void 0,
56451
56980
  isAnonymous: this.state.required.isAnonymous,
56981
+ clientDeclaredRevision: this.state.clientDeclaredRevision !== false,
56452
56982
  composeInstructions: () => this.scope.metadata.instructions,
56453
56983
  notificationSink: sink,
56454
56984
  traceContext: extractTraceContext(meta)
@@ -60297,7 +60827,10 @@ var init_http_request_flow = __esm({
60297
60827
  const { request } = this.rawInput;
60298
60828
  this.logger.verbose(`[${this.requestId}] checkAuthorization: verifying session`);
60299
60829
  try {
60300
- const result = await this.scope.runFlow("session:verify", { request });
60830
+ const result = await this.scope.runFlow("session:verify", {
60831
+ request,
60832
+ sessionless: this.servesProtocol20260728()
60833
+ });
60301
60834
  if (!result) {
60302
60835
  this.logger.error(`[${this.requestId}] failed to verify session`);
60303
60836
  throw new SessionVerificationFailedError();
@@ -61581,6 +62114,15 @@ var init_scope_instance = __esm({
61581
62114
  notificationService: this.notificationService,
61582
62115
  flowRegistry: this.scopeFlows,
61583
62116
  toolRegistry: this.scopeTools,
62117
+ http: {
62118
+ server: this.server,
62119
+ checkClientIp: (request, response) => this.runFlow("http:ip-filter", {
62120
+ request,
62121
+ response
62122
+ }),
62123
+ entryPath: this.entryPath,
62124
+ routeBase: this.routeBase
62125
+ },
61584
62126
  logger: this.logger
61585
62127
  });
61586
62128
  this._scopeChannels = channelResult.channelRegistry;
@@ -61985,12 +62527,12 @@ var init_scope_instance = __esm({
61985
62527
  return this._authoritiesScopeMapping;
61986
62528
  }
61987
62529
  /**
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).
62530
+ * Collect the OAuth scopes entries declare on their `authProviders`. The PRM
62531
+ * endpoint advertises them in `scopes_supported` (RFC 9728) next to the scopes
62532
+ * the auth mode grants (`resourceScopesFor`), not a fixed OIDC list (#629).
61991
62533
  */
61992
62534
  getAllSupportedScopes() {
61993
- const scopes = /* @__PURE__ */ new Set(["openid", "profile", "email"]);
62535
+ const scopes = /* @__PURE__ */ new Set();
61994
62536
  const collectScopes = (metadata) => {
61995
62537
  const authProviders = metadata?.["authProviders"];
61996
62538
  if (!Array.isArray(authProviders)) return;
@@ -63661,10 +64203,57 @@ function findMisconfiguration(error) {
63661
64203
  if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
63662
64204
  return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
63663
64205
  }
64206
+ if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
63664
64207
  current = current.cause;
63665
64208
  }
63666
64209
  return void 0;
63667
64210
  }
64211
+ function createDeferredServerBuild(build, options = {}) {
64212
+ const now = options.now ?? Date.now;
64213
+ let built;
64214
+ let inFlight;
64215
+ let failure;
64216
+ return {
64217
+ get(arg) {
64218
+ if (built) return Promise.resolve(built.value);
64219
+ if (inFlight) return inFlight;
64220
+ if (failure && now() < failure.retryAt) return Promise.reject(failure.error);
64221
+ const failures = failure?.count ?? 0;
64222
+ inFlight = build(arg).then(
64223
+ (value) => {
64224
+ built = { value };
64225
+ failure = void 0;
64226
+ inFlight = void 0;
64227
+ return value;
64228
+ },
64229
+ (error) => {
64230
+ const delay = Math.min(STARTUP_RETRY_MAX_MS, STARTUP_RETRY_MIN_MS * 2 ** failures);
64231
+ failure = { error, retryAt: now() + delay, count: failures + 1 };
64232
+ inFlight = void 0;
64233
+ options.onFailure?.(error);
64234
+ throw error;
64235
+ }
64236
+ );
64237
+ return inFlight;
64238
+ },
64239
+ retryAfterSeconds() {
64240
+ if (!failure) return 0;
64241
+ return Math.max(1, Math.ceil((failure.retryAt - now()) / 1e3));
64242
+ }
64243
+ };
64244
+ }
64245
+ function startupFailureResponse(error, retryAfterSeconds = 1) {
64246
+ const misconfigured = misconfigurationResponse(error);
64247
+ if (misconfigured) return misconfigured;
64248
+ return Response.json(
64249
+ {
64250
+ error: "server_unavailable",
64251
+ code: "SERVER_START_FAILED",
64252
+ 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."
64253
+ },
64254
+ { status: 503, headers: { "Retry-After": String(Math.max(1, retryAfterSeconds)) } }
64255
+ );
64256
+ }
63668
64257
  function flowErrorToHttpOutput(error) {
63669
64258
  const misconfiguration = findMisconfiguration(error);
63670
64259
  if (misconfiguration) {
@@ -63698,7 +64287,7 @@ function flowErrorToHttpOutput(error) {
63698
64287
  }
63699
64288
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
63700
64289
  }
63701
- var CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES;
64290
+ var CLOUDFLARE_WORKERS_USER_AGENT, MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
63702
64291
  var init_web_fetch_handler = __esm({
63703
64292
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
63704
64293
  "use strict";
@@ -63717,6 +64306,9 @@ var init_web_fetch_handler = __esm({
63717
64306
  UNENFORCED_METADATA: "An entry declares a field only a plugin enforces (approval, featureFlag, ...) and no installed plugin that enforces it reaches the entry, so the server refuses to start. Install the plugin or remove the field; the server log names the entries.",
63718
64307
  AUTH_CONFIGURATION_ERROR: "The auth or authorities configuration is invalid (for example, entries declare authorities and the server has no authorities option, or a rule checks nothing), so the server refuses to start. The server log names the entries."
63719
64308
  };
64309
+ CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
64310
+ STARTUP_RETRY_MIN_MS = 1e3;
64311
+ STARTUP_RETRY_MAX_MS = 6e4;
63720
64312
  }
63721
64313
  });
63722
64314
 
@@ -64729,11 +65321,16 @@ var init_front_mcp = __esm({
64729
65321
  */
64730
65322
  static async createFetchHandler(options) {
64731
65323
  const build = async (deferred) => {
64732
- const frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
65324
+ let frontMcp;
64733
65325
  try {
65326
+ frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
64734
65327
  await frontMcp.ready;
64735
65328
  } catch (err) {
64736
- if (deferred) frontMcp.log?.error("FrontMCP failed to start; requests are refused until it does", err);
65329
+ if (deferred) {
65330
+ const message = "FrontMCP failed to start; requests are refused until a retry succeeds";
65331
+ if (frontMcp?.log) frontMcp.log.error(message, err);
65332
+ else console.error(`[frontmcp] ${message}`, err);
65333
+ }
64737
65334
  throw err;
64738
65335
  }
64739
65336
  const scope = frontMcp.getPrimaryScope();
@@ -64745,23 +65342,13 @@ var init_front_mcp = __esm({
64745
65342
  };
64746
65343
  if (!isEdgeRuntime2()) return build(false);
64747
65344
  assertStaticStartupConfig(options);
64748
- let inner;
64749
- let building;
65345
+ const server = createDeferredServerBuild(() => build(true));
64750
65346
  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
- }
65347
+ let inner;
65348
+ try {
65349
+ inner = await server.get();
65350
+ } catch (err) {
65351
+ return startupFailureResponse(err, server.retryAfterSeconds());
64765
65352
  }
64766
65353
  return inner(request, ctx, env);
64767
65354
  };
@@ -68088,6 +68675,7 @@ export {
68088
68675
  create,
68089
68676
  createChildSpanContext,
68090
68677
  createContextResolver,
68678
+ createDeferredServerBuild,
68091
68679
  createDirectResolver,
68092
68680
  createElicitationStore,
68093
68681
  createElicitationStoreFromStorage,
@@ -68195,6 +68783,7 @@ export {
68195
68783
  getElicitationSecret,
68196
68784
  getGlobalStoreConfig,
68197
68785
  getNestedValue,
68786
+ getPinnedPublicUrl,
68198
68787
  getRegistryUrl,
68199
68788
  getRequestBaseUrl,
68200
68789
  getRunningTool,
@@ -68370,6 +68959,7 @@ export {
68370
68959
  skillsConfigOptionsSchema,
68371
68960
  skippedAppBehaviorSchema,
68372
68961
  sqliteOptionsSchema,
68962
+ startupFailureResponse,
68373
68963
  staticAuthOptionsSchema,
68374
68964
  storageProviderSchema,
68375
68965
  supportsStreaming,